// 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 }