Files
chat-deploy/internal/rpc/chat/wallet.go
kim.dev.6789 b7f8db7d08 复制项目
2026-01-14 22:35:45 +08:00

754 lines
26 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright © 2023 OpenIM open source community. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package chat
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"time"
chatdb "git.imall.cloud/openim/chat/pkg/common/db/table/chat"
"git.imall.cloud/openim/chat/pkg/common/mctx"
"git.imall.cloud/openim/chat/pkg/common/util"
"git.imall.cloud/openim/chat/pkg/eerrs"
chatpb "git.imall.cloud/openim/chat/pkg/protocol/chat"
"github.com/google/uuid"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"go.mongodb.org/mongo-driver/mongo"
)
// GetWalletBalance 获取钱包余额
func (o *chatSvr) GetWalletBalance(ctx context.Context, req *chatpb.GetWalletBalanceReq) (*chatpb.GetWalletBalanceResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
// 获取钱包信息
wallet, err := o.Database.GetWallet(ctx, userID)
if err != nil {
// 如果钱包不存在返回余额为0
if errors.Is(err, mongo.ErrNoDocuments) {
return &chatpb.GetWalletBalanceResp{
Balance: 0,
}, nil
}
return nil, err
}
return &chatpb.GetWalletBalanceResp{
Balance: wallet.Balance,
}, nil
}
// GetWalletInfo 获取钱包详细信息
func (o *chatSvr) GetWalletInfo(ctx context.Context, req *chatpb.GetWalletInfoReq) (*chatpb.GetWalletInfoResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
// 获取钱包信息
wallet, err := o.Database.GetWallet(ctx, userID)
if err != nil {
// 如果钱包不存在,返回默认值
if errors.Is(err, mongo.ErrNoDocuments) {
return &chatpb.GetWalletInfoResp{
Balance: 0,
WithdrawAccount: "",
WithdrawAccountType: 0,
RealNameAuth: nil,
WithdrawReceiveAccount: "",
HasPaymentPassword: false,
}, nil
}
return nil, err
}
// 转换实名认证信息
var realNameAuth *chatpb.RealNameAuthInfo
if wallet.RealNameAuth.IDCard != "" {
realNameAuth = &chatpb.RealNameAuthInfo{
IdCard: wallet.RealNameAuth.IDCard,
IdCardPhotoFront: wallet.RealNameAuth.IDCardPhotoFront,
IdCardPhotoBack: wallet.RealNameAuth.IDCardPhotoBack,
Name: wallet.RealNameAuth.Name,
AuditStatus: wallet.RealNameAuth.AuditStatus,
}
}
return &chatpb.GetWalletInfoResp{
Balance: wallet.Balance,
WithdrawAccount: wallet.WithdrawAccount,
WithdrawAccountType: wallet.WithdrawAccountType,
RealNameAuth: realNameAuth,
WithdrawReceiveAccount: wallet.WithdrawReceiveAccount,
HasPaymentPassword: wallet.PaymentPassword != "",
}, nil
}
// GetWalletBalanceRecords 获取余额明细(余额变动记录)
func (o *chatSvr) GetWalletBalanceRecords(ctx context.Context, req *chatpb.GetWalletBalanceRecordsReq) (*chatpb.GetWalletBalanceRecordsResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
var total int64
var records []*chatdb.WalletBalanceRecord
// 根据类型查询或查询所有
if req.Type > 0 {
// 按类型查询
total, records, err = o.Database.GetWalletBalanceRecordsByUserIDAndType(ctx, userID, req.Type, req.Pagination)
} else {
// 查询所有
total, records, err = o.Database.GetWalletBalanceRecords(ctx, userID, req.Pagination)
}
if err != nil {
return nil, err
}
// 转换为响应格式
recordInfos := make([]*chatpb.WalletBalanceRecordInfo, 0, len(records))
for _, record := range records {
recordInfos = append(recordInfos, &chatpb.WalletBalanceRecordInfo{
Id: record.ID,
UserID: record.UserID,
Amount: record.Amount,
Type: record.Type,
BeforeBalance: record.BeforeBalance,
AfterBalance: record.AfterBalance,
OrderID: record.OrderID,
TransactionID: record.TransactionID,
RedPacketID: record.RedPacketID,
Remark: record.Remark,
CreateTime: record.CreateTime.UnixMilli(),
})
}
return &chatpb.GetWalletBalanceRecordsResp{
Total: uint32(total),
Records: recordInfos,
}, nil
}
// SetWithdrawAccount 设置提现账号
func (o *chatSvr) SetWithdrawAccount(ctx context.Context, req *chatpb.SetWithdrawAccountReq) (*chatpb.SetWithdrawAccountResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
// 验证必填字段
if req.Account == "" {
return nil, errs.ErrArgs.WrapMsg("提现账号不能为空")
}
if req.AccountType <= 0 || req.AccountType > 3 {
return nil, errs.ErrArgs.WrapMsg("账号类型无效必须是1-支付宝2-微信3-银行卡")
}
// 更新提现账号
if err := o.Database.UpdateWalletWithdrawAccountWithType(ctx, userID, req.Account, req.AccountType); err != nil {
return nil, err
}
return &chatpb.SetWithdrawAccountResp{}, nil
}
// SetPaymentPassword 设置支付密码(首次设置或修改)
func (o *chatSvr) SetPaymentPassword(ctx context.Context, req *chatpb.SetPaymentPasswordReq) (*chatpb.SetPaymentPasswordResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
// 验证必填字段
if req.NewPassword == "" {
return nil, errs.ErrArgs.WrapMsg("新支付密码不能为空")
}
// 清理新密码(去除首尾空格)
newPassword := strings.TrimSpace(req.NewPassword)
if newPassword == "" {
return nil, errs.ErrArgs.WrapMsg("新支付密码不能为空")
}
// 获取钱包信息
wallet, err := o.Database.GetWallet(ctx, userID)
if err != nil {
// 如果钱包不存在,创建钱包并设置支付密码
if errors.Is(err, mongo.ErrNoDocuments) {
// 首次设置,不需要验证旧密码
if req.OldPassword != "" {
return nil, errs.ErrArgs.WrapMsg("首次设置支付密码不需要提供旧密码")
}
// 创建钱包并设置支付密码
now := time.Now()
newWallet := &chatdb.Wallet{
UserID: userID,
Balance: 0,
PaymentPassword: newPassword,
CreateTime: now,
UpdateTime: now,
}
if err := o.Database.CreateWallet(ctx, newWallet); err != nil {
return nil, err
}
return &chatpb.SetPaymentPasswordResp{}, nil
}
return nil, err
}
// 钱包已存在,判断是首次设置还是修改
hasPaymentPassword := wallet.PaymentPassword != ""
if hasPaymentPassword {
// 修改支付密码,需要验证旧密码
if req.OldPassword == "" {
return nil, errs.ErrArgs.WrapMsg("修改支付密码需要提供旧密码")
}
// 清理旧密码和存储的密码(去除首尾空格)
oldPassword := strings.TrimSpace(req.OldPassword)
storedPassword := strings.TrimSpace(wallet.PaymentPassword)
if storedPassword != oldPassword {
return nil, errs.ErrArgs.WrapMsg("旧支付密码错误")
}
if newPassword == oldPassword {
return nil, errs.ErrArgs.WrapMsg("新密码不能与旧密码相同")
}
} else {
// 首次设置支付密码,不需要验证旧密码
if req.OldPassword != "" {
return nil, errs.ErrArgs.WrapMsg("首次设置支付密码不需要提供旧密码")
}
}
// 更新支付密码
if err := o.Database.UpdateWalletPaymentPassword(ctx, userID, newPassword); err != nil {
return nil, err
}
return &chatpb.SetPaymentPasswordResp{}, nil
}
// CreateWithdrawApplication 申请提现
func (o *chatSvr) CreateWithdrawApplication(ctx context.Context, req *chatpb.CreateWithdrawApplicationReq) (*chatpb.CreateWithdrawApplicationResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
// 验证必填字段
if req.Amount <= 0 {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "提现金额必须大于0")
}
if req.PaymentPassword == "" {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "支付密码不能为空")
}
// 从数据库 SystemConfig 集合读取 withdraw_limit 配置并验证提现限额
withdrawLimitConfig, _ := o.Database.GetSystemConfig(ctx, "withdraw_limit")
if withdrawLimitConfig != nil {
// 如果配置存在但未启用,跳过验证
if !withdrawLimitConfig.Enabled {
log.ZInfo(ctx, "withdraw_limit config is disabled, skipping validation")
} else {
// 配置存在且启用,必须验证
limitValue := strings.TrimSpace(withdrawLimitConfig.Value)
if limitValue == "" {
log.ZWarn(ctx, "withdraw_limit config value is empty", nil)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "提现限额配置错误,请联系管理员")
}
// 解析提现限制配置(格式:最低限制-最高限制,单位:元,需要转换为分)
parts := strings.Split(limitValue, "-")
if len(parts) != 2 {
log.ZWarn(ctx, "Invalid withdraw_limit config format, expected 'min-max'", nil,
"value", limitValue)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "提现限额配置格式错误,请联系管理员")
}
minLimitStr := strings.TrimSpace(parts[0])
maxLimitStr := strings.TrimSpace(parts[1])
minLimitYuan, err1 := strconv.ParseFloat(minLimitStr, 64)
maxLimitYuan, err2 := strconv.ParseFloat(maxLimitStr, 64)
if err1 != nil || err2 != nil {
log.ZWarn(ctx, "Failed to parse withdraw_limit config values", nil,
"minLimitStr", minLimitStr,
"maxLimitStr", maxLimitStr,
"minLimitErr", err1,
"maxLimitErr", err2)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "提现限额配置解析失败,请联系管理员")
}
// 将元转换为分乘以100
minLimit := int64(minLimitYuan * 100)
maxLimit := int64(maxLimitYuan * 100)
// 验证配置值的有效性
if minLimit <= 0 {
log.ZWarn(ctx, "Invalid withdraw_limit minLimit, must be greater than 0", nil,
"minLimitYuan", minLimitYuan,
"minLimit", minLimit)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "提现最低限额配置无效,请联系管理员")
}
if maxLimit <= 0 {
log.ZWarn(ctx, "Invalid withdraw_limit maxLimit, must be greater than 0", nil,
"maxLimitYuan", maxLimitYuan,
"maxLimit", maxLimit)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "提现最高限额配置无效,请联系管理员")
}
if minLimit > maxLimit {
log.ZWarn(ctx, "Invalid withdraw_limit config, minLimit > maxLimit", nil,
"minLimitYuan", minLimitYuan,
"maxLimitYuan", maxLimitYuan,
"minLimit", minLimit,
"maxLimit", maxLimit)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "提现限额配置错误(最低限额不能大于最高限额),请联系管理员")
}
// 验证提现金额是否在限制范围内req.Amount 单位是分)
if req.Amount < minLimit {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), fmt.Sprintf("提现金额不能少于 %.2f 元(%d 分)", minLimitYuan, minLimit))
}
if req.Amount > maxLimit {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), fmt.Sprintf("提现金额不能超过 %.2f 元(%d 分)", maxLimitYuan, maxLimit))
}
log.ZInfo(ctx, "Withdraw amount validated against withdraw_limit config",
"amount", req.Amount,
"minLimit", minLimit,
"maxLimit", maxLimit)
}
}
// 清理支付密码(去除首尾空格)
paymentPassword := strings.TrimSpace(req.PaymentPassword)
if paymentPassword == "" {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "支付密码不能为空")
}
// 获取钱包信息,验证余额是否足够
wallet, err := o.Database.GetWallet(ctx, userID)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "钱包不存在,无法申请提现")
}
return nil, err
}
// 检查是否已完成实名认证
if wallet.RealNameAuth.IDCard == "" || wallet.RealNameAuth.Name == "" {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "请先完成实名认证才能申请提现")
}
// 检查实名认证审核状态必须为审核通过1才能提现
if wallet.RealNameAuth.AuditStatus != 1 {
switch wallet.RealNameAuth.AuditStatus {
case 0:
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "实名认证正在审核中,请等待审核通过后再申请提现")
case 2:
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "实名认证审核未通过,无法申请提现")
default:
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "实名认证状态异常,无法申请提现")
}
}
// 检查是否设置了支付密码
if wallet.PaymentPassword == "" {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "请先设置支付密码")
}
// 清理数据库中存储的支付密码(去除首尾空格)
storedPassword := strings.TrimSpace(wallet.PaymentPassword)
// 调试日志:打印支付密码验证信息
log.ZInfo(ctx, "支付密码验证调试",
"userID", userID,
"inputPassword", paymentPassword,
"inputPasswordLen", len(paymentPassword),
"storedPassword", storedPassword,
"storedPasswordLen", len(storedPassword),
"storedPasswordRaw", wallet.PaymentPassword,
"storedPasswordRawLen", len(wallet.PaymentPassword),
"match", storedPassword == paymentPassword,
)
// 验证支付密码
if storedPassword != paymentPassword {
log.ZWarn(ctx, "支付密码验证失败", nil,
"userID", userID,
"inputPassword", paymentPassword,
"storedPassword", storedPassword,
)
return nil, eerrs.ErrPaymentPassword.WrapMsg("支付密码错误")
}
// 检查余额是否足够
if wallet.Balance < req.Amount {
return nil, eerrs.ErrInsufficientBalance.WrapMsg("余额不足,无法申请提现")
}
// 从钱包中获取提现账号
if wallet.WithdrawAccount == "" {
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "请先在钱包中设置提现账号")
}
// 使用事务:扣减余额、创建余额变动记录、创建提现申请
applicationID := uuid.New().String()
now := time.Now()
// 扣减余额(使用负数表示扣款)
beforeBalance, afterBalance, err := o.Database.IncrementWalletBalance(ctx, userID, -req.Amount)
if err != nil {
// IncrementWalletBalance 已经返回了具体的错误信息(如余额不足),直接返回
return nil, err
}
// 创建余额变动记录
balanceRecord := &chatdb.WalletBalanceRecord{
ID: uuid.New().String(),
UserID: userID,
Amount: -req.Amount, // 负数表示减少
Type: chatdb.BalanceRecordTypeWithdraw, // 提现/提款
BeforeBalance: beforeBalance,
AfterBalance: afterBalance,
Remark: "提现申请",
CreateTime: now,
}
if err := o.Database.CreateWalletBalanceRecord(ctx, balanceRecord); err != nil {
// 如果创建记录失败,回滚余额(增加回去)
_, _, _ = o.Database.IncrementWalletBalance(ctx, userID, req.Amount)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "创建余额变动记录失败")
}
// 创建提现申请
application := &chatdb.WithdrawApplication{
ID: applicationID,
UserID: userID,
Amount: req.Amount,
WithdrawAccount: wallet.WithdrawAccount,
WithdrawAccountType: wallet.WithdrawAccountType,
Status: chatdb.WithdrawApplicationStatusPending, // 待审核
IP: req.Ip,
DeviceID: req.DeviceID,
Platform: req.Platform,
DeviceModel: req.DeviceModel,
DeviceBrand: req.DeviceBrand,
OSVersion: req.OsVersion,
AppVersion: req.AppVersion,
Remark: "", // 备注由后台管理员填写
CreateTime: now,
UpdateTime: now,
}
// 保存提现申请
if err := o.Database.CreateWithdrawApplication(ctx, application); err != nil {
// 如果创建申请失败,回滚余额(增加回去)
// 注意:余额变动记录保留,因为余额确实已经扣减了
// 如果后续需要可以通过记录ID删除余额变动记录
_, _, _ = o.Database.IncrementWalletBalance(ctx, userID, req.Amount)
return nil, errs.NewCodeError(errs.ErrArgs.Code(), "创建提现申请失败,余额已回滚")
}
return &chatpb.CreateWithdrawApplicationResp{
ApplicationID: applicationID,
}, nil
}
// GetWithdrawApplications 获取用户的提现申请列表
func (o *chatSvr) GetWithdrawApplications(ctx context.Context, req *chatpb.GetWithdrawApplicationsReq) (*chatpb.GetWithdrawApplicationsResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
// 获取提现申请列表
total, applications, err := o.Database.GetWithdrawApplicationsByUserID(ctx, userID, req.Pagination)
if err != nil {
return nil, err
}
// 转换为响应格式
applicationInfos := make([]*chatpb.WithdrawApplicationInfo, 0, len(applications))
for _, app := range applications {
var auditTime int64
if !app.AuditTime.IsZero() {
auditTime = app.AuditTime.UnixMilli()
}
applicationInfos = append(applicationInfos, &chatpb.WithdrawApplicationInfo{
Id: app.ID,
UserID: app.UserID,
Amount: app.Amount,
WithdrawAccount: app.WithdrawAccount,
WithdrawAccountType: app.WithdrawAccountType,
Status: app.Status,
AuditorID: app.AuditorID,
AuditTime: auditTime,
AuditRemark: app.AuditRemark,
Ip: app.IP,
DeviceID: app.DeviceID,
Platform: app.Platform,
DeviceModel: app.DeviceModel,
DeviceBrand: app.DeviceBrand,
OsVersion: app.OSVersion,
AppVersion: app.AppVersion,
Remark: app.Remark,
CreateTime: app.CreateTime.UnixMilli(),
UpdateTime: app.UpdateTime.UnixMilli(),
})
}
return &chatpb.GetWithdrawApplicationsResp{
Total: uint32(total),
Applications: applicationInfos,
}, nil
}
// RealNameAuth 实名认证
func (o *chatSvr) RealNameAuth(ctx context.Context, req *chatpb.RealNameAuthReq) (*chatpb.RealNameAuthResp, error) {
// 获取用户ID
userID, _, err := mctx.Check(ctx)
if err != nil {
return nil, err
}
// 检查用户是否已经实名认证且审核通过
wallet, err := o.Database.GetWallet(ctx, userID)
if err == nil && wallet != nil {
// 如果已经实名认证且审核状态为通过1不允许重新认证
if wallet.RealNameAuth.IDCard != "" && wallet.RealNameAuth.AuditStatus == 1 {
return nil, errs.ErrArgs.WrapMsg("您已经完成实名认证,不能重新认证")
}
} else if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
// 如果不是"文档不存在"的错误,返回错误
return nil, err
}
// 验证必填字段
if req.IdCard == "" {
return nil, errs.ErrArgs.WrapMsg("身份证号不能为空")
}
if req.Name == "" {
return nil, errs.ErrArgs.WrapMsg("真实姓名不能为空")
}
if req.IdCardPhotoFront == "" {
return nil, errs.ErrArgs.WrapMsg("身份证正面照片不能为空")
}
if req.IdCardPhotoBack == "" {
return nil, errs.ErrArgs.WrapMsg("身份证反面照片不能为空")
}
// 清理输入(去除首尾空格)
idCard := strings.TrimSpace(req.IdCard)
name := strings.TrimSpace(req.Name)
idCardPhotoFront := strings.TrimSpace(req.IdCardPhotoFront)
idCardPhotoBack := strings.TrimSpace(req.IdCardPhotoBack)
if idCard == "" || name == "" {
return nil, errs.ErrArgs.WrapMsg("身份证号和姓名不能为空")
}
if idCardPhotoFront == "" || idCardPhotoBack == "" {
return nil, errs.ErrArgs.WrapMsg("身份证正面照片和反面照片不能为空")
}
// 验证姓名只能包含中文字符(不允许英文、数字和标点符号)
chineseRegex := regexp.MustCompile(`^[\p{Han}]+$`)
if !chineseRegex.MatchString(name) {
return nil, errs.ErrArgs.WrapMsg("真实姓名只能包含中文,不能包含英文、数字或标点符号")
}
// 构建原始数据 JSON
rawData := map[string]string{
"cardNo": idCard,
"realName": name,
}
rawDataJSON, err := json.Marshal(rawData)
if err != nil {
return nil, errs.WrapMsg(err, "构建数据失败")
}
// AES 密钥32字节的十六进制字符串
aesKey := "a7f3c9e2b8d4f1a6c3e9b2d7f4a1c8e5b2d9f6a3c8e1b4d7f2a9c5e8b1d4f7a2"
// 在客户端本地加密数据(使用 AES-GCM 模式)
log.ZInfo(ctx, "开始本地加密实名认证数据", "userID", userID, "rawData", string(rawDataJSON), "idCard", idCard, "name", name)
encryptedData, err := util.EncryptRealNameAuthData(string(rawDataJSON), aesKey)
if err != nil {
log.ZError(ctx, "本地加密失败", err, "userID", userID)
return nil, errs.WrapMsg(err, "加密数据失败")
}
log.ZInfo(ctx, "本地加密成功", "userID", userID, "encryptedLength", len(encryptedData), "encryptedData", encryptedData)
// 调用验证接口(直接发送加密后的字符串)
baseURL := "http://95.40.154.128"
verifyURL := baseURL + "/idcheck"
log.ZInfo(ctx, "准备调用验证接口", "userID", userID, "url", verifyURL, "encryptedLength", len(encryptedData))
// 创建请求,请求体直接是加密后的字符串
httpReq, err := http.NewRequest("POST", verifyURL, bytes.NewBufferString(encryptedData))
if err != nil {
log.ZError(ctx, "创建验证请求失败", err, "userID", userID, "url", verifyURL)
return nil, errs.WrapMsg(err, "创建验证请求失败")
}
httpReq.Header.Set("Content-Type", "application/json")
// 发送请求
client := &http.Client{Timeout: 30 * time.Second}
verifyResp, err := client.Do(httpReq)
if err != nil {
log.ZError(ctx, "调用验证接口失败", err, "userID", userID, "url", verifyURL)
return nil, errs.WrapMsg(err, "调用验证接口失败")
}
defer verifyResp.Body.Close()
verifyRespBody, err := io.ReadAll(verifyResp.Body)
if err != nil {
return nil, errs.WrapMsg(err, "读取验证接口响应失败")
}
log.ZInfo(ctx, "验证接口响应", "userID", userID, "statusCode", verifyResp.StatusCode, "responseBody", string(verifyRespBody), "responseLength", len(verifyRespBody))
// 检查 HTTP 状态码
if verifyResp.StatusCode != http.StatusOK {
log.ZWarn(ctx, "验证接口返回错误状态码", nil, "userID", userID, "statusCode", verifyResp.StatusCode, "response", string(verifyRespBody))
return &chatpb.RealNameAuthResp{
Success: false,
Message: fmt.Sprintf("验证请求失败,状态码: %d, 响应: %s", verifyResp.StatusCode, string(verifyRespBody)),
}, nil
}
// 解析响应(格式:{"success": bool, "data": interface{}, "error": string, "message": string}
var verifyResult struct {
Success bool `json:"success"`
Data interface{} `json:"data"`
Error string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
}
if err := json.Unmarshal(verifyRespBody, &verifyResult); err != nil {
log.ZWarn(ctx, "解析验证接口响应失败", err, "userID", userID, "response", string(verifyRespBody))
return &chatpb.RealNameAuthResp{
Success: false,
Message: fmt.Sprintf("解析验证结果失败: %s, 响应: %s", err.Error(), string(verifyRespBody)),
}, nil
}
// 检查验证结果
if !verifyResult.Success {
errorMsg := verifyResult.Error
if errorMsg == "" {
errorMsg = verifyResult.Message
}
if errorMsg == "" {
errorMsg = "验证失败"
}
return &chatpb.RealNameAuthResp{
Success: false,
Message: errorMsg,
}, nil
}
// 验证成功,保存实名认证信息到数据库
if verifyResult.Success {
// 获取或创建钱包
_, err := o.Database.GetWallet(ctx, userID)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
// 钱包不存在,创建钱包
now := time.Now()
newWallet := &chatdb.Wallet{
UserID: userID,
Balance: 0,
RealNameAuth: chatdb.RealNameAuth{
IDCard: idCard,
Name: name,
IDCardPhotoFront: idCardPhotoFront,
IDCardPhotoBack: idCardPhotoBack,
AuditStatus: 0, // 0-未审核
},
CreateTime: now,
UpdateTime: now,
}
if err := o.Database.CreateWallet(ctx, newWallet); err != nil {
return nil, errs.WrapMsg(err, "创建钱包失败")
}
} else {
return nil, err
}
} else {
// 更新实名认证信息(重新提交后,审核状态重置为待审核)
realNameAuth := chatdb.RealNameAuth{
IDCard: idCard,
Name: name,
IDCardPhotoFront: idCardPhotoFront,
IDCardPhotoBack: idCardPhotoBack,
AuditStatus: 0, // 0-未审核(重新提交后重置为待审核状态)
}
if err := o.Database.UpdateWalletRealNameAuth(ctx, userID, realNameAuth); err != nil {
return nil, errs.WrapMsg(err, "更新实名认证信息失败")
}
log.ZInfo(ctx, "实名认证信息已更新,审核状态重置为待审核", "userID", userID, "idCard", idCard, "name", name, "auditStatus", 0)
}
log.ZInfo(ctx, "实名认证成功并已保存", "userID", userID, "idCard", idCard, "name", name)
// 获取更新后的钱包信息返回身份证照片URL
updatedWallet, err := o.Database.GetWallet(ctx, userID)
var idCardPhotoFront, idCardPhotoBack string
if err == nil && updatedWallet != nil {
idCardPhotoFront = updatedWallet.RealNameAuth.IDCardPhotoFront
idCardPhotoBack = updatedWallet.RealNameAuth.IDCardPhotoBack
}
return &chatpb.RealNameAuthResp{
Success: true,
Message: "提交成功了,请等待审核",
IdCardPhotoFront: idCardPhotoFront,
IdCardPhotoBack: idCardPhotoBack,
}, nil
}
// 这行代码永远不会执行到,因为如果 verifyResult.Success 为 false已经在前面返回了
// 但为了代码完整性保留
log.ZError(ctx, "代码逻辑错误:验证失败但未返回", nil, "userID", userID, "verifyResult", verifyResult)
return &chatpb.RealNameAuthResp{
Success: false,
Message: "验证失败",
}, nil
}