package admin import ( "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "net/http" "strconv" "strings" "time" "git.imall.cloud/openim/chat/internal/api/util" "git.imall.cloud/openim/chat/pkg/common/apistruct" "git.imall.cloud/openim/chat/pkg/common/config" "git.imall.cloud/openim/chat/pkg/common/imapi" "git.imall.cloud/openim/chat/pkg/common/mctx" "git.imall.cloud/openim/chat/pkg/common/xlsx" "git.imall.cloud/openim/chat/pkg/common/xlsx/model" "git.imall.cloud/openim/chat/pkg/protocol/admin" "git.imall.cloud/openim/chat/pkg/protocol/chat" "git.imall.cloud/openim/protocol/constant" "git.imall.cloud/openim/protocol/sdkws" "git.imall.cloud/openim/protocol/user" wrapperspb "git.imall.cloud/openim/protocol/wrapperspb" "github.com/gin-gonic/gin" "github.com/openimsdk/tools/a2r" "github.com/openimsdk/tools/apiresp" "github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/log" "github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/encrypt" ) func New(chatClient chat.ChatClient, adminClient admin.AdminClient, imApiCaller imapi.CallerInterface, api *util.Api) *Api { return &Api{ Api: api, chatClient: chatClient, adminClient: adminClient, imApiCaller: imApiCaller, } } type Api struct { *util.Api chatClient chat.ChatClient adminClient admin.AdminClient imApiCaller imapi.CallerInterface } func (o *Api) AdminLogin(c *gin.Context) { req, err := a2r.ParseRequest[admin.LoginReq](c) if err != nil { apiresp.GinError(c, err) return } // if req.Version == "" { // apiresp.GinError(c, errs.New("openim-admin-front version too old, please use new version").Wrap()) // return // } loginResp, err := o.adminClient.Login(c, req) if err != nil { apiresp.GinError(c, err) return } imAdminUserID := o.GetDefaultIMAdminUserID() imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } var resp apistruct.AdminLoginResp if err := datautil.CopyStructFields(&resp, loginResp); err != nil { apiresp.GinError(c, err) return } resp.ImToken = imToken resp.ImUserID = imAdminUserID apiresp.GinSuccess(c, resp) } func (o *Api) ResetUserPassword(c *gin.Context) { req, err := a2r.ParseRequest[chat.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) } func (o *Api) AdminUpdateInfo(c *gin.Context) { req, err := a2r.ParseRequest[admin.AdminUpdateInfoReq](c) if err != nil { apiresp.GinError(c, err) return } resp, err := o.adminClient.AdminUpdateInfo(c, req) if err != nil { apiresp.GinError(c, err) return } imAdminUserID := o.GetDefaultIMAdminUserID() imToken, err := o.imApiCaller.GetAdminTokenCache(c, imAdminUserID) if err != nil { log.ZError(c, "AdminUpdateInfo ImAdminTokenWithDefaultAdmin", err, "imAdminUserID", imAdminUserID) return } if err := o.imApiCaller.UpdateUserInfo(mctx.WithApiToken(c, imToken), imAdminUserID, resp.Nickname, resp.FaceURL, 1, ""); err != nil { log.ZError(c, "AdminUpdateInfo UpdateUserInfo", err, "userID", resp.UserID, "nickName", resp.Nickname, "faceURL", resp.FaceURL) } apiresp.GinSuccess(c, nil) } func (o *Api) AdminInfo(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetAdminInfo, o.adminClient) } func (o *Api) ChangeAdminPassword(c *gin.Context) { a2r.Call(c, admin.AdminClient.ChangeAdminPassword, o.adminClient) } func (o *Api) ChangeOperationPassword(c *gin.Context) { a2r.Call(c, admin.AdminClient.ChangeOperationPassword, o.adminClient) } func (o *Api) SetGoogleAuthKey(c *gin.Context) { a2r.Call(c, admin.AdminClient.SetGoogleAuthKey, o.adminClient) } func (o *Api) AddAdminAccount(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddAdminAccount, o.adminClient) } func (o *Api) AddUserAccount(c *gin.Context) { req, err := a2r.ParseRequest[chat.AddUserAccountReq](c) if err != nil { apiresp.GinError(c, err) return } ip, err := o.GetClientIP(c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } ctx := o.WithAdminUser(mctx.WithApiToken(c, imToken)) err = o.registerChatUser(ctx, ip, []*chat.RegisterUserInfo{req.User}) if err != nil { return } apiresp.GinSuccess(c, nil) } func (o *Api) DelAdminAccount(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelAdminAccount, o.adminClient) } func (o *Api) SearchAdminAccount(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchAdminAccount, o.adminClient) } func (o *Api) GetStatistics(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetStatistics, o.adminClient) } func (o *Api) AddDefaultFriend(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddDefaultFriend, o.adminClient) } func (o *Api) DelDefaultFriend(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelDefaultFriend, o.adminClient) } func (o *Api) SearchDefaultFriend(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchDefaultFriend, o.adminClient) } func (o *Api) FindDefaultFriend(c *gin.Context) { a2r.Call(c, admin.AdminClient.FindDefaultFriend, o.adminClient) } func (o *Api) AddDefaultGroup(c *gin.Context) { req, err := a2r.ParseRequest[admin.AddDefaultGroupReq](c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } groups, err := o.imApiCaller.FindGroupInfo(mctx.WithApiToken(c, imToken), req.GroupIDs) if err != nil { apiresp.GinError(c, err) return } if len(req.GroupIDs) != len(groups) { apiresp.GinError(c, errs.ErrArgs.WrapMsg("group id not found")) return } resp, err := o.adminClient.AddDefaultGroup(c, &admin.AddDefaultGroupReq{ GroupIDs: req.GroupIDs, }) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } func (o *Api) DelDefaultGroup(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelDefaultGroup, o.adminClient) } func (o *Api) FindDefaultGroup(c *gin.Context) { a2r.Call(c, admin.AdminClient.FindDefaultGroup, o.adminClient) } func (o *Api) SearchDefaultGroup(c *gin.Context) { req, err := a2r.ParseRequest[admin.SearchDefaultGroupReq](c) if err != nil { apiresp.GinError(c, err) return } searchResp, err := o.adminClient.SearchDefaultGroup(c, req) if err != nil { apiresp.GinError(c, err) return } resp := apistruct.SearchDefaultGroupResp{ Total: searchResp.Total, Groups: make([]*sdkws.GroupInfo, 0, len(searchResp.GroupIDs)), } if len(searchResp.GroupIDs) > 0 { imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } groups, err := o.imApiCaller.FindGroupInfo(mctx.WithApiToken(c, imToken), searchResp.GroupIDs) if err != nil { apiresp.GinError(c, err) return } groupMap := make(map[string]*sdkws.GroupInfo) for _, group := range groups { groupMap[group.GroupID] = group } for _, groupID := range searchResp.GroupIDs { if group, ok := groupMap[groupID]; ok { resp.Groups = append(resp.Groups, group) } else { resp.Groups = append(resp.Groups, &sdkws.GroupInfo{ GroupID: groupID, }) } } } apiresp.GinSuccess(c, resp) } func (o *Api) AddInvitationCode(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddInvitationCode, o.adminClient) } func (o *Api) GenInvitationCode(c *gin.Context) { a2r.Call(c, admin.AdminClient.GenInvitationCode, o.adminClient) } func (o *Api) DelInvitationCode(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelInvitationCode, o.adminClient) } func (o *Api) SearchInvitationCode(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchInvitationCode, o.adminClient) } func (o *Api) AddUserIPLimitLogin(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddUserIPLimitLogin, o.adminClient) } func (o *Api) SearchUserIPLimitLogin(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchUserIPLimitLogin, o.adminClient) } func (o *Api) DelUserIPLimitLogin(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelUserIPLimitLogin, o.adminClient) } func (o *Api) SearchIPForbidden(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchIPForbidden, o.adminClient) } func (o *Api) AddIPForbidden(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddIPForbidden, o.adminClient) } func (o *Api) DelIPForbidden(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelIPForbidden, o.adminClient) } func (o *Api) ParseToken(c *gin.Context) { a2r.Call(c, admin.AdminClient.ParseToken, o.adminClient) } func (o *Api) BlockUser(c *gin.Context) { req, err := a2r.ParseRequest[admin.BlockUserReq](c) if err != nil { apiresp.GinError(c, err) return } resp, err := o.adminClient.BlockUser(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) } func (o *Api) UnblockUser(c *gin.Context) { a2r.Call(c, admin.AdminClient.UnblockUser, o.adminClient) } func (o *Api) SearchBlockUser(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchBlockUser, o.adminClient) } func (o *Api) SetClientConfig(c *gin.Context) { a2r.Call(c, admin.AdminClient.SetClientConfig, o.adminClient) } func (o *Api) DelClientConfig(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelClientConfig, o.adminClient) } func (o *Api) GetClientConfig(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetClientConfig, o.adminClient) } func (o *Api) AddApplet(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddApplet, o.adminClient) } func (o *Api) DelApplet(c *gin.Context) { a2r.Call(c, admin.AdminClient.DelApplet, o.adminClient) } func (o *Api) UpdateApplet(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateApplet, o.adminClient) } func (o *Api) SearchApplet(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchApplet, o.adminClient) } func (o *Api) LoginUserCount(c *gin.Context) { a2r.Call(c, chat.ChatClient.UserLoginCount, o.chatClient) } // OnlineUserCount 在线人数统计 func (o *Api) OnlineUserCount(c *gin.Context) { imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } resp, err := o.imApiCaller.OnlineUserCount(mctx.WithApiToken(c, imToken)) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } // OnlineUserCountTrend 在线人数走势统计 func (o *Api) OnlineUserCountTrend(c *gin.Context) { req, err := a2r.ParseRequest[imapi.OnlineUserCountTrendReq](c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } resp, err := o.imApiCaller.OnlineUserCountTrend(mctx.WithApiToken(c, imToken), req) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } // UserSendMsgCount 用户发送消息总数统计 func (o *Api) UserSendMsgCount(c *gin.Context) { req, err := a2r.ParseRequest[imapi.UserSendMsgCountReq](c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } resp, err := o.imApiCaller.UserSendMsgCount(mctx.WithApiToken(c, imToken), req) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } // UserSendMsgCountTrend 用户发送消息走势统计 func (o *Api) UserSendMsgCountTrend(c *gin.Context) { req, err := a2r.ParseRequest[imapi.UserSendMsgCountTrendReq](c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } resp, err := o.imApiCaller.UserSendMsgCountTrend(mctx.WithApiToken(c, imToken), req) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } // UserSendMsgQuery 用户发送消息查询 func (o *Api) UserSendMsgQuery(c *gin.Context) { req, err := a2r.ParseRequest[imapi.UserSendMsgQueryReq](c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } resp, err := o.imApiCaller.UserSendMsgQuery(mctx.WithApiToken(c, imToken), req) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } func (o *Api) NewUserCount(c *gin.Context) { req, err := a2r.ParseRequest[user.UserRegisterCountReq](c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } dateCount, total, err := o.imApiCaller.UserRegisterCount(mctx.WithApiToken(c, imToken), req.Start, req.End) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, &apistruct.NewUserCountResp{ DateCount: dateCount, Total: total, }) } func (o *Api) ImportUserByXlsx(c *gin.Context) { formFile, err := c.FormFile("data") if err != nil { apiresp.GinError(c, err) return } ip, err := o.GetClientIP(c) if err != nil { apiresp.GinError(c, err) return } file, err := formFile.Open() if err != nil { apiresp.GinError(c, err) return } defer file.Close() var users []model.User if err := xlsx.ParseAll(file, &users); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("xlsx file parse error "+err.Error())) return } us, err := o.xlsx2user(users) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } ctx := o.WithAdminUser(mctx.WithApiToken(c, imToken)) apiresp.GinError(c, o.registerChatUser(ctx, ip, us)) } func (o *Api) ImportUserByJson(c *gin.Context) { req, err := a2r.ParseRequest[struct { Users []*chat.RegisterUserInfo `json:"users"` }](c) if err != nil { apiresp.GinError(c, err) return } ip, err := o.GetClientIP(c) if err != nil { apiresp.GinError(c, err) return } imToken, err := o.imApiCaller.ImAdminTokenWithDefaultAdmin(c) if err != nil { apiresp.GinError(c, err) return } ctx := o.WithAdminUser(mctx.WithApiToken(c, imToken)) apiresp.GinError(c, o.registerChatUser(ctx, ip, req.Users)) } func (o *Api) xlsx2user(users []model.User) ([]*chat.RegisterUserInfo, error) { chatUsers := make([]*chat.RegisterUserInfo, len(users)) for i, info := range users { if info.Nickname == "" { return nil, errs.ErrArgs.WrapMsg("nickname is empty") } if info.AreaCode == "" || info.PhoneNumber == "" { return nil, errs.ErrArgs.WrapMsg("areaCode or phoneNumber is empty") } if info.Password == "" { return nil, errs.ErrArgs.WrapMsg("password is empty") } if !strings.HasPrefix(info.AreaCode, "+") { return nil, errs.ErrArgs.WrapMsg("areaCode format error") } if _, err := strconv.ParseUint(info.AreaCode[1:], 10, 16); err != nil { return nil, errs.ErrArgs.WrapMsg("areaCode format error") } gender, _ := strconv.Atoi(info.Gender) chatUsers[i] = &chat.RegisterUserInfo{ UserID: info.UserID, Nickname: info.Nickname, FaceURL: info.FaceURL, Birth: o.xlsxBirth(info.Birth).UnixMilli(), Gender: int32(gender), AreaCode: info.AreaCode, PhoneNumber: info.PhoneNumber, Email: info.Email, Account: info.Account, Password: encrypt.Md5(info.Password), } } return chatUsers, nil } func (o *Api) xlsxBirth(s string) time.Time { if s == "" { return time.Now() } var separator byte for _, b := range []byte(s) { if b < '0' || b > '9' { separator = b } } arr := strings.Split(s, string([]byte{separator})) if len(arr) != 3 { return time.Now() } year, _ := strconv.Atoi(arr[0]) month, _ := strconv.Atoi(arr[1]) day, _ := strconv.Atoi(arr[2]) t := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local) if t.Before(time.Date(1900, 0, 0, 0, 0, 0, 0, time.Local)) { return time.Now() } return t } func (o *Api) registerChatUser(ctx context.Context, ip string, users []*chat.RegisterUserInfo) error { if len(users) == 0 { return errs.ErrArgs.WrapMsg("users is empty") } for _, info := range users { respRegisterUser, err := o.chatClient.RegisterUser(ctx, &chat.RegisterUserReq{Ip: ip, User: info, Platform: constant.AdminPlatformID}) if err != nil { return err } exMap := make(map[string]interface{}) exMap["userType"] = info.UserType exMap["userFlag"] = info.UserFlag ex, err := json.Marshal(exMap) if err != nil { return err } userInfo := &sdkws.UserInfo{ UserID: respRegisterUser.UserID, Nickname: info.Nickname, FaceURL: info.FaceURL, UserType: info.UserType, // 使用传入的用户类型,如果没有则默认为0 UserFlag: info.UserFlag, // 使用传入的用户标签 Ex: string(ex), } if err = o.imApiCaller.RegisterUser(ctx, []*sdkws.UserInfo{userInfo}); err != nil { return err } if resp, err := o.adminClient.FindDefaultFriend(ctx, &admin.FindDefaultFriendReq{}); err == nil { _ = o.imApiCaller.ImportFriend(ctx, respRegisterUser.UserID, resp.UserIDs) } if resp, err := o.adminClient.FindDefaultGroup(ctx, &admin.FindDefaultGroupReq{}); err == nil { _ = o.imApiCaller.InviteToGroup(ctx, respRegisterUser.UserID, resp.GroupIDs) } } return nil } func (o *Api) BatchImportTemplate(c *gin.Context) { md5Sum := md5.Sum(config.ImportTemplate) md5Val := hex.EncodeToString(md5Sum[:]) if c.GetHeader("If-None-Match") == md5Val { c.Status(http.StatusNotModified) return } c.Header("Content-Disposition", "attachment; filename=template.xlsx") c.Header("Content-Transfer-Encoding", "binary") c.Header("Content-Description", "File Transfer") c.Header("Content-Length", strconv.Itoa(len(config.ImportTemplate))) c.Header("ETag", md5Val) c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", config.ImportTemplate) } func (o *Api) SetAllowRegister(c *gin.Context) { a2r.Call(c, chat.ChatClient.SetAllowRegister, o.chatClient) } func (o *Api) GetAllowRegister(c *gin.Context) { a2r.Call(c, chat.ChatClient.GetAllowRegister, o.chatClient) } 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) } func (o *Api) AddApplicationVersion(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddApplicationVersion, o.adminClient) } func (o *Api) UpdateApplicationVersion(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateApplicationVersion, o.adminClient) } func (o *Api) DeleteApplicationVersion(c *gin.Context) { a2r.Call(c, admin.AdminClient.DeleteApplicationVersion, o.adminClient) } // ==================== 敏感词管理相关 API ==================== // 敏感词管理 func (o *Api) AddSensitiveWord(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddSensitiveWord, o.adminClient) } func (o *Api) UpdateSensitiveWord(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateSensitiveWord, o.adminClient) } func (o *Api) DeleteSensitiveWord(c *gin.Context) { a2r.Call(c, admin.AdminClient.DeleteSensitiveWord, o.adminClient) } func (o *Api) GetSensitiveWord(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetSensitiveWord, o.adminClient) } func (o *Api) SearchSensitiveWords(c *gin.Context) { a2r.Call(c, admin.AdminClient.SearchSensitiveWords, o.adminClient) } func (o *Api) BatchAddSensitiveWords(c *gin.Context) { a2r.Call(c, admin.AdminClient.BatchAddSensitiveWords, o.adminClient) } func (o *Api) BatchUpdateSensitiveWords(c *gin.Context) { a2r.Call(c, admin.AdminClient.BatchUpdateSensitiveWords, o.adminClient) } func (o *Api) BatchDeleteSensitiveWords(c *gin.Context) { a2r.Call(c, admin.AdminClient.BatchDeleteSensitiveWords, o.adminClient) } // 敏感词分组管理 func (o *Api) AddSensitiveWordGroup(c *gin.Context) { a2r.Call(c, admin.AdminClient.AddSensitiveWordGroup, o.adminClient) } func (o *Api) UpdateSensitiveWordGroup(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateSensitiveWordGroup, o.adminClient) } func (o *Api) DeleteSensitiveWordGroup(c *gin.Context) { a2r.Call(c, admin.AdminClient.DeleteSensitiveWordGroup, o.adminClient) } func (o *Api) GetSensitiveWordGroup(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetSensitiveWordGroup, o.adminClient) } func (o *Api) GetAllSensitiveWordGroups(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetAllSensitiveWordGroups, o.adminClient) } // 敏感词配置管理 func (o *Api) GetSensitiveWordConfig(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetSensitiveWordConfig, o.adminClient) } func (o *Api) UpdateSensitiveWordConfig(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateSensitiveWordConfig, o.adminClient) } // 敏感词日志管理 func (o *Api) GetSensitiveWordLogs(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetSensitiveWordLogs, o.adminClient) } func (o *Api) DeleteSensitiveWordLogs(c *gin.Context) { a2r.Call(c, admin.AdminClient.DeleteSensitiveWordLogs, o.adminClient) } // 用户登录记录管理 func (o *Api) GetUserLoginRecords(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetUserLoginRecords, o.adminClient) } // 敏感词统计 func (o *Api) GetSensitiveWordStats(c *gin.Context) { req := &admin.GetSensitiveWordStatsReq{} resp, err := o.adminClient.GetSensitiveWordStats(c, req) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } func (o *Api) GetSensitiveWordLogStats(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetSensitiveWordLogStats, o.adminClient) } // ==================== 定时任务管理相关 API ==================== // GetScheduledTasks 获取定时任务列表 func (o *Api) GetScheduledTasks(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetScheduledTasks, o.adminClient) } // DeleteScheduledTask 删除定时任务 func (o *Api) DeleteScheduledTask(c *gin.Context) { a2r.Call(c, admin.AdminClient.DeleteScheduledTask, o.adminClient) } // ==================== 系统配置管理相关 API ==================== // CreateSystemConfig 创建系统配置 func (o *Api) CreateSystemConfig(c *gin.Context) { // 自定义处理,支持 value 字段为多种类型 var req struct { Key string `json:"key" binding:"required"` Title string `json:"title"` Value interface{} `json:"value"` // 支持多种类型:string, number, bool, object ValueType int32 `json:"valueType"` Description string `json:"description"` Enabled bool `json:"enabled"` ShowInApp interface{} `json:"showInApp"` // 是否在APP端展示 } if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request: "+err.Error())) return } // 如果未设置值类型,默认为字符串类型 valueType := req.ValueType if valueType == 0 { valueType = 1 // ConfigValueTypeString } // 将 value 转换为字符串 valueStr := "" if req.Value != nil { // 根据 valueType 转换 switch valueType { case 1: // String if str, ok := req.Value.(string); ok { valueStr = str } else { valueStr = fmt.Sprintf("%v", req.Value) } case 2: // Number switch v := req.Value.(type) { case string: valueStr = v case float64: valueStr = strconv.FormatFloat(v, 'f', -1, 64) case float32: valueStr = strconv.FormatFloat(float64(v), 'f', -1, 32) case int: valueStr = strconv.Itoa(v) case int64: valueStr = strconv.FormatInt(v, 10) case int32: valueStr = strconv.FormatInt(int64(v), 10) default: valueStr = fmt.Sprintf("%v", v) } case 3: // Bool switch v := req.Value.(type) { case string: // 对于字符串,先尝试解析为布尔值,如果失败则报错 if v == "" { apiresp.GinError(c, errs.ErrArgs.WrapMsg("boolean value cannot be empty, must be 'true' or 'false'")) return } // 标准化布尔值字符串(支持 "1", "t", "T", "true", "TRUE", "True" 等) if parsed, err := strconv.ParseBool(v); err == nil { valueStr = strconv.FormatBool(parsed) } else { apiresp.GinError(c, errs.ErrArgs.WrapMsg(fmt.Sprintf("invalid boolean value '%s', must be 'true' or 'false'", v))) return } case bool: valueStr = strconv.FormatBool(v) default: // 对于其他类型,尝试转换为字符串后再解析 strVal := fmt.Sprintf("%v", v) if strVal == "" { apiresp.GinError(c, errs.ErrArgs.WrapMsg("boolean value cannot be empty, must be 'true' or 'false'")) return } if parsed, err := strconv.ParseBool(strVal); err == nil { valueStr = strconv.FormatBool(parsed) } else { apiresp.GinError(c, errs.ErrArgs.WrapMsg(fmt.Sprintf("invalid boolean value '%s', must be 'true' or 'false'", strVal))) return } } case 4: // JSON switch v := req.Value.(type) { case string: valueStr = v default: // 序列化为 JSON jsonBytes, err := json.Marshal(v) if err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid JSON value")) return } valueStr = string(jsonBytes) } default: valueStr = fmt.Sprintf("%v", req.Value) } } // 如果 value 未提供,根据 valueType 设置默认值 if valueStr == "" { switch valueType { case 3: // Bool // 布尔类型默认值为 false valueStr = "false" case 2: // Number // 数字类型默认值为 0 valueStr = "0" case 4: // JSON // JSON 类型默认值为空对象 valueStr = "{}" // String 类型允许空字符串,保持 valueStr = "" } } // 验证转换后的值 if err := o.validateValueByType(valueStr, valueType); err != nil { apiresp.GinError(c, err) return } // 处理 showInApp 字段(默认为 false) showInApp := false if req.ShowInApp != nil { if sa, ok := req.ShowInApp.(bool); ok { showInApp = sa } } // 构建 protobuf 请求 pbReq := &admin.CreateSystemConfigReq{ Key: req.Key, Title: req.Title, Value: valueStr, ValueType: valueType, Description: req.Description, Enabled: req.Enabled, ShowInApp: showInApp, } // 调用 RPC resp, err := o.adminClient.CreateSystemConfig(c, pbReq) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } // validateValueByType 验证值是否符合类型要求(API 层辅助函数) func (o *Api) validateValueByType(value string, valueType int32) error { switch valueType { case 1: // String return nil case 2: // Number if _, err := strconv.ParseFloat(value, 64); err != nil { return errs.ErrArgs.WrapMsg("value must be a valid number") } return nil case 3: // Bool if value == "" { return errs.ErrArgs.WrapMsg("boolean value cannot be empty, must be 'true' or 'false'") } if _, err := strconv.ParseBool(value); err != nil { return errs.ErrArgs.WrapMsg(fmt.Sprintf("invalid boolean value '%s', must be 'true' or 'false' (also accepts: '1', '0', 't', 'f', 'T', 'F')", value)) } return nil case 4: // JSON var js interface{} if err := json.Unmarshal([]byte(value), &js); err != nil { return errs.ErrArgs.WrapMsg("value must be a valid JSON") } return nil default: return errs.ErrArgs.WrapMsg("invalid value type") } } // GetSystemConfig 获取系统配置详情 func (o *Api) GetSystemConfig(c *gin.Context) { // 使用自定义处理,转换返回的 value 类型 var req admin.GetSystemConfigReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetSystemConfig(c, &req) if err != nil { apiresp.GinError(c, err) return } // 转换 value 为对应类型 convertedValue := o.convertValueFromString(resp.Config.Value, resp.Config.ValueType) // 构建响应 result := map[string]interface{}{ "key": resp.Config.Key, "title": resp.Config.Title, "value": convertedValue, "valueType": resp.Config.ValueType, "description": resp.Config.Description, "enabled": resp.Config.Enabled, "showInApp": resp.Config.ShowInApp, "createTime": resp.Config.CreateTime, "updateTime": resp.Config.UpdateTime, } apiresp.GinSuccess(c, map[string]interface{}{"config": result}) } // 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 } } // GetAllSystemConfigs 获取所有系统配置(分页) func (o *Api) GetAllSystemConfigs(c *gin.Context) { var req admin.GetAllSystemConfigsReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetAllSystemConfigs(c, &req) if err != nil { apiresp.GinError(c, err) return } // 转换列表中的 value 为对应类型 list := make([]map[string]interface{}, 0, len(resp.List)) for _, config := range resp.List { convertedValue := o.convertValueFromString(config.Value, config.ValueType) list = append(list, map[string]interface{}{ "key": config.Key, "title": config.Title, "value": convertedValue, "valueType": config.ValueType, "description": config.Description, "enabled": config.Enabled, "showInApp": config.ShowInApp, "createTime": config.CreateTime, "updateTime": config.UpdateTime, }) } apiresp.GinSuccess(c, map[string]interface{}{ "total": resp.Total, "list": list, }) } // UpdateSystemConfig 更新系统配置 func (o *Api) UpdateSystemConfig(c *gin.Context) { // 自定义处理,支持 value 字段为多种类型 var req struct { Key string `json:"key" binding:"required"` Title interface{} `json:"title"` Value interface{} `json:"value"` // 支持多种类型 ValueType interface{} `json:"valueType"` Description interface{} `json:"description"` Enabled interface{} `json:"enabled"` ShowInApp interface{} `json:"showInApp"` // 是否在APP端展示 } if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } // 获取当前配置以确定 valueType currentConfig, err := o.adminClient.GetSystemConfig(c, &admin.GetSystemConfigReq{Key: req.Key}) if err != nil { apiresp.GinError(c, err) return } // 确定要使用的 valueType valueType := currentConfig.Config.ValueType if req.ValueType != nil { // 处理 JSON 中的数字类型(可能是 float64) switch v := req.ValueType.(type) { case float64: valueType = int32(v) case int: valueType = int32(v) case int32: valueType = v } } // 构建 protobuf 请求 pbReq := &admin.UpdateSystemConfigReq{ Key: req.Key, } // 处理可选字段 if req.Title != nil { if titleStr, ok := req.Title.(string); ok { pbReq.Title = &wrapperspb.StringValue{Value: titleStr} } } if req.Description != nil { if descStr, ok := req.Description.(string); ok { pbReq.Description = &wrapperspb.StringValue{Value: descStr} } } if req.Enabled != nil { if enabled, ok := req.Enabled.(bool); ok { pbReq.Enabled = &wrapperspb.BoolValue{Value: enabled} } } if req.ShowInApp != nil { if showInApp, ok := req.ShowInApp.(bool); ok { pbReq.ShowInApp = &wrapperspb.BoolValue{Value: showInApp} } } if req.ValueType != nil { if vt, ok := req.ValueType.(float64); ok { pbReq.ValueType = &wrapperspb.Int32Value{Value: int32(vt)} } } // 如果提供了 value,转换为字符串 if req.Value != nil { valueStr := o.convertValueToString(req.Value, valueType) // 验证转换后的值 if err := o.validateValueByType(valueStr, valueType); err != nil { apiresp.GinError(c, err) return } pbReq.Value = &wrapperspb.StringValue{Value: valueStr} } // 调用 RPC resp, err := o.adminClient.UpdateSystemConfig(c, pbReq) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } // convertValueToString 将任意类型的值转换为字符串 func (o *Api) convertValueToString(value interface{}, valueType int32) string { if value == nil { return "" } switch valueType { case 1: // String if str, ok := value.(string); ok { return str } return fmt.Sprintf("%v", value) case 2: // Number switch v := value.(type) { case string: return v case float64: return strconv.FormatFloat(v, 'f', -1, 64) case float32: return strconv.FormatFloat(float64(v), 'f', -1, 32) case int: return strconv.Itoa(v) case int64: return strconv.FormatInt(v, 10) case int32: return strconv.FormatInt(int64(v), 10) default: return fmt.Sprintf("%v", v) } case 3: // Bool switch v := value.(type) { case string: return v case bool: return strconv.FormatBool(v) default: return fmt.Sprintf("%v", v) } case 4: // JSON switch v := value.(type) { case string: return v default: jsonBytes, _ := json.Marshal(v) return string(jsonBytes) } default: return fmt.Sprintf("%v", value) } } // UpdateSystemConfigValue 更新系统配置值 func (o *Api) UpdateSystemConfigValue(c *gin.Context) { var req struct { Key string `json:"key" binding:"required"` Value interface{} `json:"value"` // 支持多种类型 } if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } // 获取当前配置以确定 valueType currentConfig, err := o.adminClient.GetSystemConfig(c, &admin.GetSystemConfigReq{Key: req.Key}) if err != nil { apiresp.GinError(c, err) return } // 转换为字符串 valueStr := o.convertValueToString(req.Value, currentConfig.Config.ValueType) // 验证转换后的值 if err := o.validateValueByType(valueStr, currentConfig.Config.ValueType); err != nil { apiresp.GinError(c, err) return } // 调用 RPC resp, err := o.adminClient.UpdateSystemConfigValue(c, &admin.UpdateSystemConfigValueReq{ Key: req.Key, Value: valueStr, }) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) } // UpdateSystemConfigEnabled 更新系统配置启用状态 func (o *Api) UpdateSystemConfigEnabled(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateSystemConfigEnabled, o.adminClient) } // DeleteSystemConfig 删除系统配置 func (o *Api) DeleteSystemConfig(c *gin.Context) { a2r.Call(c, admin.AdminClient.DeleteSystemConfig, o.adminClient) } // GetEnabledSystemConfigs 获取所有已启用的配置 func (o *Api) GetEnabledSystemConfigs(c *gin.Context) { resp, err := o.adminClient.GetEnabledSystemConfigs(c, &admin.GetEnabledSystemConfigsReq{}) if err != nil { apiresp.GinError(c, err) return } // 转换列表中的 value 为对应类型 list := make([]map[string]interface{}, 0, len(resp.List)) for _, config := range resp.List { convertedValue := o.convertValueFromString(config.Value, config.ValueType) list = append(list, map[string]interface{}{ "key": config.Key, "title": config.Title, "value": convertedValue, "valueType": config.ValueType, "description": config.Description, "enabled": config.Enabled, "showInApp": config.ShowInApp, "createTime": config.CreateTime, "updateTime": config.UpdateTime, }) } apiresp.GinSuccess(c, map[string]interface{}{"list": list}) } // ==================== 钱包管理相关 API ==================== // GetUserWallet 获取用户钱包信息 func (o *Api) GetUserWallet(c *gin.Context) { var req admin.GetUserWalletReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetUserWallet(c, &req) if err != nil { apiresp.GinError(c, err) return } // 构建响应 result := map[string]interface{}{ "userID": resp.Wallet.UserID, "balance": resp.Wallet.Balance, "withdrawAccount": resp.Wallet.WithdrawAccount, "withdrawReceiveAccount": resp.Wallet.WithdrawReceiveAccount, "hasPaymentPassword": resp.Wallet.HasPaymentPassword, "createTime": resp.Wallet.CreateTime, "updateTime": resp.Wallet.UpdateTime, } // 添加实名认证信息(如果存在) if resp.Wallet.RealNameAuth != nil { result["realNameAuth"] = map[string]interface{}{ "idCard": resp.Wallet.RealNameAuth.IdCard, "name": resp.Wallet.RealNameAuth.Name, "idCardPhotoFront": resp.Wallet.RealNameAuth.IdCardPhotoFront, "idCardPhotoBack": resp.Wallet.RealNameAuth.IdCardPhotoBack, "auditStatus": resp.Wallet.RealNameAuth.AuditStatus, } } apiresp.GinSuccess(c, map[string]interface{}{"wallet": result}) } // UpdateUserWalletBalance 更新用户余额(后台充值/扣款) func (o *Api) UpdateUserWalletBalance(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateUserWalletBalance, o.adminClient) } // BatchUpdateWalletBalance 批量更新用户余额(后台批量充值/扣款) func (o *Api) BatchUpdateWalletBalance(c *gin.Context) { a2r.Call(c, admin.AdminClient.BatchUpdateWalletBalance, o.adminClient) } // GetWallets 获取钱包列表 func (o *Api) GetWallets(c *gin.Context) { a2r.Call(c, admin.AdminClient.GetWallets, o.adminClient) } // GetUserWalletBalanceRecords 获取用户余额变动记录列表 func (o *Api) GetUserWalletBalanceRecords(c *gin.Context) { var req admin.GetUserWalletBalanceRecordsReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetUserWalletBalanceRecords(c, &req) if err != nil { apiresp.GinError(c, err) return } // 转换列表格式(Ant Design Pro 标准格式) list := make([]map[string]interface{}, 0, len(resp.List)) for _, record := range resp.List { list = append(list, 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, "list": list, }) } // UpdateUserPaymentPassword 修改用户支付密码(后台) func (o *Api) UpdateUserPaymentPassword(c *gin.Context) { a2r.Call(c, admin.AdminClient.UpdateUserPaymentPassword, o.adminClient) } // SetUserWithdrawAccount 设置用户提款账号(后台) func (o *Api) SetUserWithdrawAccount(c *gin.Context) { a2r.Call(c, admin.AdminClient.SetUserWithdrawAccount, o.adminClient) } // ==================== 提现管理相关 API(操作 withdraw_applications)==================== // GetWithdraw 获取提现申请详情 func (o *Api) GetWithdraw(c *gin.Context) { var req admin.GetWithdrawReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetWithdraw(c, &req) if err != nil { apiresp.GinError(c, err) return } // 构建响应 result := map[string]interface{}{ "id": resp.Withdraw.Id, "userID": resp.Withdraw.UserID, "amount": resp.Withdraw.Amount, "withdrawAccount": resp.Withdraw.WithdrawAccount, "status": resp.Withdraw.Status, "auditorID": resp.Withdraw.AuditorID, "auditTime": resp.Withdraw.AuditTime, "auditRemark": resp.Withdraw.AuditRemark, "ip": resp.Withdraw.Ip, "deviceID": resp.Withdraw.DeviceID, "platform": resp.Withdraw.Platform, "deviceModel": resp.Withdraw.DeviceModel, "deviceBrand": resp.Withdraw.DeviceBrand, "osVersion": resp.Withdraw.OsVersion, "appVersion": resp.Withdraw.AppVersion, "createTime": resp.Withdraw.CreateTime, "updateTime": resp.Withdraw.UpdateTime, } apiresp.GinSuccess(c, map[string]interface{}{"withdraw": result}) } // GetUserWithdraws 获取用户的提现申请列表 func (o *Api) GetUserWithdraws(c *gin.Context) { var req admin.GetUserWithdrawsReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetUserWithdraws(c, &req) if err != nil { apiresp.GinError(c, err) return } // 转换列表格式 list := make([]map[string]interface{}, 0, len(resp.List)) for _, withdraw := range resp.List { item := map[string]interface{}{ "id": withdraw.Id, "userID": withdraw.UserID, "amount": withdraw.Amount, "withdrawAccount": withdraw.WithdrawAccount, "status": withdraw.Status, "auditorID": withdraw.AuditorID, "auditTime": withdraw.AuditTime, "auditRemark": withdraw.AuditRemark, "ip": withdraw.Ip, "deviceID": withdraw.DeviceID, "platform": withdraw.Platform, "deviceModel": withdraw.DeviceModel, "deviceBrand": withdraw.DeviceBrand, "osVersion": withdraw.OsVersion, "appVersion": withdraw.AppVersion, "createTime": withdraw.CreateTime, "updateTime": withdraw.UpdateTime, } // 添加用户实名认证信息(如果存在) if withdraw.RealNameAuth != nil { item["realNameAuth"] = map[string]interface{}{ "idCard": withdraw.RealNameAuth.IdCard, "name": withdraw.RealNameAuth.Name, "idCardPhotoFront": withdraw.RealNameAuth.IdCardPhotoFront, "idCardPhotoBack": withdraw.RealNameAuth.IdCardPhotoBack, "auditStatus": withdraw.RealNameAuth.AuditStatus, } } list = append(list, item) } apiresp.GinSuccess(c, map[string]interface{}{ "total": resp.Total, "list": list, }) } // GetWithdraws 获取提现申请列表(后台,支持按状态筛选) func (o *Api) GetWithdraws(c *gin.Context) { var req admin.GetWithdrawsReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetWithdraws(c, &req) if err != nil { apiresp.GinError(c, err) return } // 转换列表格式 list := make([]map[string]interface{}, 0, len(resp.List)) for _, withdraw := range resp.List { item := map[string]interface{}{ "id": withdraw.Id, "userID": withdraw.UserID, "amount": withdraw.Amount, "withdrawAccount": withdraw.WithdrawAccount, "status": withdraw.Status, "auditorID": withdraw.AuditorID, "auditTime": withdraw.AuditTime, "auditRemark": withdraw.AuditRemark, "ip": withdraw.Ip, "deviceID": withdraw.DeviceID, "platform": withdraw.Platform, "deviceModel": withdraw.DeviceModel, "deviceBrand": withdraw.DeviceBrand, "osVersion": withdraw.OsVersion, "appVersion": withdraw.AppVersion, "createTime": withdraw.CreateTime, "updateTime": withdraw.UpdateTime, } // 添加用户实名认证信息(如果存在) if withdraw.RealNameAuth != nil { item["realNameAuth"] = map[string]interface{}{ "idCard": withdraw.RealNameAuth.IdCard, "name": withdraw.RealNameAuth.Name, "idCardPhotoFront": withdraw.RealNameAuth.IdCardPhotoFront, "idCardPhotoBack": withdraw.RealNameAuth.IdCardPhotoBack, "auditStatus": withdraw.RealNameAuth.AuditStatus, } } list = append(list, item) } apiresp.GinSuccess(c, map[string]interface{}{ "total": resp.Total, "list": list, }) } // AuditWithdraw 批量审核提现申请 func (o *Api) AuditWithdraw(c *gin.Context) { var req admin.AuditWithdrawReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.AuditWithdraw(c, &req) if err != nil { apiresp.GinError(c, err) return } // 构建响应 result := map[string]interface{}{ "successCount": resp.SuccessCount, "failCount": resp.FailCount, "failedIDs": resp.FailedIDs, } apiresp.GinSuccess(c, result) } // GetRealNameAuths 获取实名认证列表(支持按审核状态筛选) func (o *Api) GetRealNameAuths(c *gin.Context) { var req admin.GetRealNameAuthsReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.GetRealNameAuths(c, &req) if err != nil { apiresp.GinError(c, err) return } // 转换列表格式 list := make([]map[string]interface{}, 0, len(resp.List)) for _, auth := range resp.List { list = append(list, map[string]interface{}{ "userID": auth.UserID, "nickname": auth.Nickname, "faceURL": auth.FaceURL, "idCard": auth.IdCard, "name": auth.Name, "idCardPhotoFront": auth.IdCardPhotoFront, "idCardPhotoBack": auth.IdCardPhotoBack, "auditStatus": auth.AuditStatus, "createTime": auth.CreateTime, "updateTime": auth.UpdateTime, }) } apiresp.GinSuccess(c, map[string]interface{}{ "total": resp.Total, "list": list, }) } // AuditRealNameAuth 审核实名认证(通过/拒绝) func (o *Api) AuditRealNameAuth(c *gin.Context) { var req admin.AuditRealNameAuthReq if err := c.ShouldBindJSON(&req); err != nil { apiresp.GinError(c, errs.ErrArgs.WrapMsg("invalid request")) return } resp, err := o.adminClient.AuditRealNameAuth(c, &req) if err != nil { apiresp.GinError(c, err) return } apiresp.GinSuccess(c, resp) }