1664 lines
46 KiB
Go
1664 lines
46 KiB
Go
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)
|
||
}
|