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

905 lines
24 KiB
Go
Raw 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 (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"time"
apiuserutil "git.imall.cloud/openim/chat/internal/api/util"
"strconv"
"git.imall.cloud/openim/chat/pkg/common/apistruct"
"git.imall.cloud/openim/chat/pkg/common/imapi"
"git.imall.cloud/openim/chat/pkg/common/mctx"
"git.imall.cloud/openim/chat/pkg/eerrs"
"git.imall.cloud/openim/chat/pkg/protocol/admin"
chatpb "git.imall.cloud/openim/chat/pkg/protocol/chat"
constantpb "git.imall.cloud/openim/protocol/constant"
"git.imall.cloud/openim/protocol/sdkws"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/openimsdk/tools/a2r"
"github.com/openimsdk/tools/apiresp"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
)
func New(chatClient chatpb.ChatClient, adminClient admin.AdminClient, imApiCaller imapi.CallerInterface, api *apiuserutil.Api) *Api {
return &Api{
Api: api,
chatClient: chatClient,
adminClient: adminClient,
imApiCaller: imApiCaller,
}
}
type Api struct {
*apiuserutil.Api
chatClient chatpb.ChatClient
adminClient admin.AdminClient
imApiCaller imapi.CallerInterface
}
// operationIDKey 用于 context.WithValue 的自定义类型,避免使用字符串导致类型冲突
type operationIDKey struct{}
// ################## ACCOUNT ##################
func (o *Api) SendVerifyCode(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.SendVerifyCodeReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
ip, err := o.GetClientIP(c)
if err != nil {
apiresp.GinError(c, err)
return
}
req.Ip = ip
resp, err := o.chatClient.SendVerifyCode(c, req)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, resp)
}
func (o *Api) VerifyCode(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.VerifyCode, o.chatClient)
}
func (o *Api) GetCaptchaImage(c *gin.Context) {
// 获取或生成operationID图片请求可能没有operationID header
operationID := c.GetHeader("operationID")
if operationID == "" {
// 如果没有operationID生成一个UUID
operationID = uuid.New().String()
}
// 将operationID设置到context中确保RPC调用能获取到
// 使用自定义类型作为 key避免使用字符串导致类型冲突
ctx := context.WithValue(c.Request.Context(), operationIDKey{}, operationID)
// 调用RPC方法获取验证码6位数字
resp, err := o.chatClient.GetCaptchaImage(ctx, &chatpb.GetCaptchaImageReq{})
if err != nil {
apiresp.GinError(c, err)
return
}
// 在API层生成验证码图片
imgBytes, err := apiuserutil.GenerateCaptchaImageFromCode(resp.Code)
if err != nil {
apiresp.GinError(c, err)
return
}
// 验证图片数据
if len(imgBytes) == 0 {
apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("generated captcha image is empty"))
return
}
// 检查GIF文件头
if len(imgBytes) < 6 {
apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("generated captcha image data too short"))
return
}
// 设置响应头返回GIF图片
c.Header("Content-Type", "image/gif")
c.Header("Content-Length", fmt.Sprintf("%d", len(imgBytes)))
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
// 将验证码ID放在响应头中客户端可用于后续验证
c.Header("X-Captcha-ID", resp.CaptchaID)
// 返回图片数据
c.Data(200, "image/gif", imgBytes)
}
func (o *Api) RegisterUser(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.RegisterUserReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
ip, err := o.GetClientIP(c)
if err != nil {
apiresp.GinError(c, err)
return
}
req.Ip = ip
imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c)
if err != nil {
apiresp.GinError(c, err)
return
}
apiCtx := mctx.WithApiToken(c, imToken)
rpcCtx := o.WithAdminUser(c)
checkResp, err := o.chatClient.CheckUserExist(rpcCtx, &chatpb.CheckUserExistReq{User: req.User})
if err != nil {
apiresp.GinError(c, err)
return
}
if checkResp.IsRegistered {
isUserNotExist, err := o.imApiCaller.AccountCheckSingle(apiCtx, checkResp.Userid)
if err != nil {
apiresp.GinError(c, err)
return
}
// if User is not exist in SDK server. You need delete this user and register new user again.
if isUserNotExist {
_, err := o.chatClient.DelUserAccount(rpcCtx, &chatpb.DelUserAccountReq{UserIDs: []string{checkResp.Userid}})
if err != nil {
apiresp.GinError(c, err)
return
}
}
}
// H5注册场景提供了registerToken默认自动登录
if req.RegisterToken != "" && !req.AutoLogin {
req.AutoLogin = true
}
respRegisterUser, err := o.chatClient.RegisterUser(c, req)
if err != nil {
apiresp.GinError(c, err)
return
}
userInfo := &sdkws.UserInfo{
UserID: respRegisterUser.UserID,
Nickname: req.User.Nickname,
FaceURL: req.User.FaceURL,
CreateTime: time.Now().UnixMilli(),
UserType: 0, // 设置默认用户类型
UserFlag: "",
}
err = o.imApiCaller.RegisterUser(apiCtx, []*sdkws.UserInfo{userInfo})
if err != nil {
apiresp.GinError(c, err)
return
}
if resp, err := o.adminClient.FindDefaultFriend(rpcCtx, &admin.FindDefaultFriendReq{}); err == nil {
_ = o.imApiCaller.ImportFriend(apiCtx, respRegisterUser.UserID, resp.UserIDs)
}
if resp, err := o.adminClient.FindDefaultGroup(rpcCtx, &admin.FindDefaultGroupReq{}); err == nil {
_ = o.imApiCaller.InviteToGroup(apiCtx, respRegisterUser.UserID, resp.GroupIDs)
}
var resp apistruct.UserRegisterResp
if req.AutoLogin {
resp.ImToken, err = o.imApiCaller.GetUserToken(apiCtx, respRegisterUser.UserID, req.Platform)
if err != nil {
apiresp.GinError(c, err)
return
}
}
resp.ChatToken = respRegisterUser.ChatToken
resp.UserID = respRegisterUser.UserID
apiresp.GinSuccess(c, &resp)
}
func (o *Api) Login(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.LoginReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
ip, err := o.GetClientIP(c)
if err != nil {
apiresp.GinError(c, err)
return
}
req.Ip = ip
resp, err := o.chatClient.Login(c, req)
if err != nil {
// 调试日志:打印错误的详细信息
errStr := err.Error()
log.ZError(c, "Login error detected", err,
"errorString", errStr,
"errorType", fmt.Sprintf("%T", err),
"contains20015", strings.Contains(errStr, "20015"),
"containsAccountBlocked", strings.Contains(errStr, "AccountBlocked"),
"contains封禁", strings.Contains(errStr, "账户已被封禁"))
// 检查是否是账户被封禁的错误,确保返回明确的错误提示
// 需要检查错误字符串中是否包含封禁相关的关键词,即使错误被包装了
if strings.Contains(errStr, "20015") || strings.Contains(errStr, "AccountBlocked") || strings.Contains(errStr, "账户已被封禁") {
log.ZInfo(c, "Detected AccountBlocked error, returning explicit error", "originalError", errStr)
apiresp.GinError(c, eerrs.ErrAccountBlocked.WrapMsg("账户已被封禁"))
return
}
// 如果错误被包装成 ServerInternalError尝试从错误链中提取原始错误
// 检查错误消息中是否包含封禁相关的堆栈信息
if strings.Contains(errStr, "ServerInternalError") && (strings.Contains(errStr, "20015") || strings.Contains(errStr, "AccountBlocked")) {
log.ZInfo(c, "Detected AccountBlocked error in wrapped ServerInternalError, returning explicit error", "originalError", errStr)
apiresp.GinError(c, eerrs.ErrAccountBlocked.WrapMsg("账户已被封禁"))
return
}
log.ZError(c, "Login error not AccountBlocked, returning original error", err)
apiresp.GinError(c, err)
return
}
adminToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c)
if err != nil {
apiresp.GinError(c, err)
return
}
apiCtx := mctx.WithApiToken(c, adminToken)
imToken, err := o.imApiCaller.GetUserToken(apiCtx, resp.UserID, req.Platform)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, &apistruct.LoginResp{
ImToken: imToken,
UserID: resp.UserID,
ChatToken: resp.ChatToken,
})
}
func (o *Api) ResetPassword(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.ResetPassword, o.chatClient)
}
func (o *Api) ChangePassword(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.ChangePasswordReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
resp, err := o.chatClient.ChangePassword(c, req)
if err != nil {
apiresp.GinError(c, err)
return
}
imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c)
if err != nil {
apiresp.GinError(c, err)
return
}
err = o.imApiCaller.ForceOffLine(mctx.WithApiToken(c, imToken), req.UserID)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, resp)
}
// ################## USER ##################
func (o *Api) UpdateUserInfo(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.UpdateUserInfoReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
// 检查req是否为nil
if req == nil {
apiresp.GinError(c, errs.ErrArgs.WrapMsg("request is nil"))
return
}
// 检查必要参数
if req.UserID == "" {
apiresp.GinError(c, errs.ErrArgs.WrapMsg("userID is required"))
return
}
respUpdate, err := o.chatClient.UpdateUserInfo(c, req)
if err != nil {
apiresp.GinError(c, err)
return
}
var imToken string
imToken, err = o.imApiCaller.ImAdminTokenWithDefaultAdmin(c)
if err != nil {
apiresp.GinError(c, err)
return
}
// 检查token是否为空
if imToken == "" {
apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to get admin token"))
return
}
// 检查imApiCaller是否为nil
if o.imApiCaller == nil {
apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("imApiCaller is nil"))
return
}
var (
nickName string
faceURL string
userFlag string
)
if req.Nickname != nil {
nickName = req.Nickname.Value
} else {
nickName = respUpdate.NickName
}
if req.FaceURL != nil {
faceURL = req.FaceURL.Value
} else {
faceURL = respUpdate.FaceUrl
}
// 处理UserFlag字段
if req.UserFlag != nil {
userFlag = req.UserFlag.Value
}
// 确保字符串参数不为nil虽然这里应该是安全的但添加额外保护
if nickName == "" {
nickName = ""
}
if faceURL == "" {
faceURL = ""
}
err = o.imApiCaller.UpdateUserInfo(mctx.WithApiToken(c, imToken), req.UserID, nickName, faceURL, req.UserType, userFlag)
if err != nil {
apiresp.GinError(c, err)
return
}
// 构造 ex 字段的 JSON 数据(包含 userFlag 和 userType
type UserEx struct {
UserFlag string `json:"userFlag"`
UserType int32 `json:"userType"`
}
userEx := UserEx{
UserFlag: userFlag,
UserType: req.UserType,
}
exBytes, err := json.Marshal(userEx)
if err != nil {
apiresp.GinError(c, errs.ErrInternalServer.WrapMsg("failed to marshal user ex"))
return
}
// 调用 UpdateUserInfoEx 更新 ex 字段
err = o.imApiCaller.UpdateUserInfoEx(mctx.WithApiToken(c, imToken), req.UserID, string(exBytes))
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, apistruct.UpdateUserInfoResp{})
}
func (o *Api) FindUserPublicInfo(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.FindUserPublicInfo, o.chatClient)
}
func (o *Api) FindUserFullInfo(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.FindUserFullInfo, o.chatClient)
}
func (o *Api) SearchUserFullInfo(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.SearchUserFullInfo, o.chatClient)
}
func (o *Api) SearchUserPublicInfo(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.SearchUserPublicInfo, o.chatClient)
}
func (o *Api) GetTokenForVideoMeeting(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetTokenForVideoMeeting, o.chatClient)
}
// ################## APPLET ##################
func (o *Api) FindApplet(c *gin.Context) {
a2r.Call(c, admin.AdminClient.FindApplet, o.adminClient)
}
// ################## CONFIG ##################
func (o *Api) GetClientConfig(c *gin.Context) {
a2r.Call(c, admin.AdminClient.GetClientConfig, o.adminClient)
}
// ################## CALLBACK ##################
func (o *Api) OpenIMCallback(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
apiresp.GinError(c, err)
return
}
req := &chatpb.OpenIMCallbackReq{
Command: c.Query(constantpb.CallbackCommand),
Body: string(body),
}
if _, err := o.chatClient.OpenIMCallback(c, req); err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, nil)
}
func (o *Api) SearchFriend(c *gin.Context) {
req, err := a2r.ParseRequest[struct {
UserID string `json:"userID"`
chatpb.SearchUserInfoReq
}](c)
if err != nil {
apiresp.GinError(c, err)
return
}
if req.UserID == "" {
req.UserID = mctx.GetOpUserID(c)
}
imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c)
if err != nil {
apiresp.GinError(c, err)
return
}
userIDs, err := o.imApiCaller.FriendUserIDs(mctx.WithApiToken(c, imToken), req.UserID)
if err != nil {
apiresp.GinError(c, err)
return
}
if len(userIDs) == 0 {
apiresp.GinSuccess(c, &chatpb.SearchUserInfoResp{})
return
}
req.SearchUserInfoReq.UserIDs = userIDs
resp, err := o.chatClient.SearchUserInfo(c, &req.SearchUserInfoReq)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, resp)
}
func (o *Api) AddFriend(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.AddFriendReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
// 获取当前用户ID发起添加好友的用户
ownerUserID := mctx.GetOpUserID(c)
// 获取用户信息,检查 userType
userInfoResp, err := o.chatClient.FindUserFullInfo(c, &chatpb.FindUserFullInfoReq{UserIDs: []string{ownerUserID}})
if err != nil {
apiresp.GinError(c, err)
return
}
if len(userInfoResp.Users) == 0 || userInfoResp.Users[0].UserType != 1 {
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only userType=1 users can add friends directly"))
return
}
// 调用 chat-deploy 的 ImportFriends
imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c)
if err != nil {
apiresp.GinError(c, err)
return
}
// 调用 ImportFriend传入要添加的好友ID
err = o.imApiCaller.ImportFriend(mctx.WithApiToken(c, imToken), ownerUserID, []string{req.UserID})
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, nil)
}
func (o *Api) LatestApplicationVersion(c *gin.Context) {
a2r.Call(c, admin.AdminClient.LatestApplicationVersion, o.adminClient)
}
func (o *Api) PageApplicationVersion(c *gin.Context) {
a2r.Call(c, admin.AdminClient.PageApplicationVersion, o.adminClient)
}
// ==================== 敏感词检测相关 API ====================
// GetSensitiveWords 获取敏感词列表
func (o *Api) GetSensitiveWords(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetSensitiveWords, o.chatClient)
}
// CheckSensitiveWords 检测敏感词
func (o *Api) CheckSensitiveWords(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.CheckSensitiveWords, o.chatClient)
}
// ==================== 收藏相关 API ====================
// CreateFavorite 创建收藏
func (o *Api) CreateFavorite(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.CreateFavorite, o.chatClient)
}
// GetFavorite 获取收藏详情
func (o *Api) GetFavorite(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetFavorite, o.chatClient)
}
// GetFavorites 获取收藏列表
func (o *Api) GetFavorites(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetFavorites, o.chatClient)
}
// SearchFavorites 搜索收藏
func (o *Api) SearchFavorites(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.SearchFavorites, o.chatClient)
}
// UpdateFavorite 更新收藏
func (o *Api) UpdateFavorite(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.UpdateFavorite, o.chatClient)
}
// DeleteFavorite 删除收藏
func (o *Api) DeleteFavorite(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.DeleteFavorite, o.chatClient)
}
// GetFavoritesByTags 根据标签获取收藏
func (o *Api) GetFavoritesByTags(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetFavoritesByTags, o.chatClient)
}
// GetFavoriteCount 获取收藏数量
func (o *Api) GetFavoriteCount(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetFavoriteCount, o.chatClient)
}
// ==================== 定时任务相关 API ====================
// CreateScheduledTask 创建定时任务
func (o *Api) CreateScheduledTask(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.CreateScheduledTask, o.chatClient)
}
// GetScheduledTask 获取定时任务详情
func (o *Api) GetScheduledTask(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetScheduledTask, o.chatClient)
}
// GetScheduledTasks 获取定时任务列表
func (o *Api) GetScheduledTasks(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.GetScheduledTasks, o.chatClient)
}
// UpdateScheduledTask 更新定时任务
func (o *Api) UpdateScheduledTask(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.UpdateScheduledTask, o.chatClient)
}
// DeleteScheduledTask 删除定时任务
func (o *Api) DeleteScheduledTask(c *gin.Context) {
a2r.Call(c, chatpb.ChatClient.DeleteScheduledTask, o.chatClient)
}
// ==================== 系统配置相关 API ====================
// GetAppSystemConfigs 获取APP端配置返回所有 show_in_app=true 且 enabled=true 的配置)
func (o *Api) GetAppSystemConfigs(c *gin.Context) {
resp, err := o.chatClient.GetAppSystemConfigs(c, &chatpb.GetAppSystemConfigsReq{})
if err != nil {
apiresp.GinError(c, err)
return
}
// 转换为对象格式key 作为对象的键value 作为值
configMap := make(map[string]interface{})
for _, config := range resp.Configs {
convertedValue := o.convertValueFromString(config.Value, config.ValueType)
configMap[config.Key] = convertedValue
}
apiresp.GinSuccess(c, configMap)
}
// convertValueFromString 将字符串值转换为对应类型(用于返回给前端)
func (o *Api) convertValueFromString(value string, valueType int32) interface{} {
switch valueType {
case 1: // String
return value
case 2: // Number
// 尝试解析为数字
if num, err := strconv.ParseFloat(value, 64); err == nil {
// 如果是整数,返回整数;否则返回浮点数
if num == float64(int64(num)) {
return int64(num)
}
return num
}
return value
case 3: // Bool
if b, err := strconv.ParseBool(value); err == nil {
return b
}
return value
case 4: // JSON
var js interface{}
if err := json.Unmarshal([]byte(value), &js); err == nil {
return js
}
return value
default:
return value
}
}
// ==================== 钱包相关 API ====================
// GetWalletBalance 获取钱包余额
func (o *Api) GetWalletBalance(c *gin.Context) {
resp, err := o.chatClient.GetWalletBalance(c, &chatpb.GetWalletBalanceReq{})
if err != nil {
apiresp.GinError(c, err)
return
}
// 将余额从分转换为元(前端显示用)
apiresp.GinSuccess(c, map[string]interface{}{
"balance": resp.Balance, // 余额(单位:分)
})
}
// GetWalletInfo 获取钱包详细信息
func (o *Api) GetWalletInfo(c *gin.Context) {
resp, err := o.chatClient.GetWalletInfo(c, &chatpb.GetWalletInfoReq{})
if err != nil {
apiresp.GinError(c, err)
return
}
// 构建响应
result := map[string]interface{}{
"balance": resp.Balance, // 余额(单位:分)
"withdrawAccount": resp.WithdrawAccount,
"withdrawAccountType": resp.WithdrawAccountType,
"withdrawReceiveAccount": resp.WithdrawReceiveAccount,
"hasPaymentPassword": resp.HasPaymentPassword,
}
// 添加实名认证信息(如果存在)
if resp.RealNameAuth != nil {
result["realNameAuth"] = map[string]interface{}{
"idCard": resp.RealNameAuth.IdCard,
"idCardPhotoFront": resp.RealNameAuth.IdCardPhotoFront,
"idCardPhotoBack": resp.RealNameAuth.IdCardPhotoBack,
"name": resp.RealNameAuth.Name,
"auditStatus": resp.RealNameAuth.AuditStatus,
}
}
apiresp.GinSuccess(c, result)
}
// GetWalletBalanceRecords 获取余额明细
func (o *Api) GetWalletBalanceRecords(c *gin.Context) {
var req chatpb.GetWalletBalanceRecordsReq
if err := c.ShouldBindJSON(&req); err != nil {
apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request"))
return
}
resp, err := o.chatClient.GetWalletBalanceRecords(c, &req)
if err != nil {
apiresp.GinError(c, err)
return
}
// 转换为响应格式
records := make([]map[string]interface{}, 0, len(resp.Records))
for _, record := range resp.Records {
records = append(records, map[string]interface{}{
"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,
})
}
apiresp.GinSuccess(c, map[string]interface{}{
"total": resp.Total,
"records": records,
})
}
// SetPaymentPassword 设置支付密码
func (o *Api) SetPaymentPassword(c *gin.Context) {
var req chatpb.SetPaymentPasswordReq
if err := c.ShouldBindJSON(&req); err != nil {
apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request"))
return
}
resp, err := o.chatClient.SetPaymentPassword(c, &req)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, resp)
}
// SetWithdrawAccount 设置提现账号
func (o *Api) SetWithdrawAccount(c *gin.Context) {
var req chatpb.SetWithdrawAccountReq
if err := c.ShouldBindJSON(&req); err != nil {
apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request"))
return
}
resp, err := o.chatClient.SetWithdrawAccount(c, &req)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, resp)
}
// CreateWithdrawApplication 申请提现
func (o *Api) CreateWithdrawApplication(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.CreateWithdrawApplicationReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
// 从请求中获取客户端IP
ip, err := o.GetClientIP(c)
if err != nil {
apiresp.GinError(c, errs.ErrArgs.WrapMsg("无法获取客户端IP"))
return
}
req.Ip = ip
resp, err := o.chatClient.CreateWithdrawApplication(c, req)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, map[string]interface{}{
"applicationID": resp.ApplicationID,
})
}
// GetWithdrawApplications 获取提现申请列表
func (o *Api) GetWithdrawApplications(c *gin.Context) {
var req chatpb.GetWithdrawApplicationsReq
if err := c.ShouldBindJSON(&req); err != nil {
apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request"))
return
}
resp, err := o.chatClient.GetWithdrawApplications(c, &req)
if err != nil {
apiresp.GinError(c, err)
return
}
// 转换为响应格式
applications := make([]map[string]interface{}, 0, len(resp.Applications))
for _, app := range resp.Applications {
applications = append(applications, map[string]interface{}{
"id": app.Id,
"userID": app.UserID,
"amount": app.Amount,
"withdrawAccount": app.WithdrawAccount,
"withdrawAccountType": app.WithdrawAccountType,
"status": app.Status,
"auditorID": app.AuditorID,
"auditTime": app.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,
"updateTime": app.UpdateTime,
})
}
apiresp.GinSuccess(c, map[string]interface{}{
"total": resp.Total,
"applications": applications,
})
}
// RealNameAuth 实名认证
func (o *Api) RealNameAuth(c *gin.Context) {
req, err := a2r.ParseRequest[chatpb.RealNameAuthReq](c)
if err != nil {
apiresp.GinError(c, err)
return
}
resp, err := o.chatClient.RealNameAuth(c, req)
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, map[string]interface{}{
"success": resp.Success,
"message": resp.Message,
"idCardPhotoFront": resp.IdCardPhotoFront,
"idCardPhotoBack": resp.IdCardPhotoBack,
})
}