mirror of
https://github.com/openimsdk/open-im-server.git
synced 2025-12-03 02:42:19 +08:00
566 lines
17 KiB
Go
566 lines
17 KiB
Go
// Copyright © 2023 OpenIM. All rights reserved.
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
package api
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"github.com/OpenIMSDK/protocol/constant"
|
||
"github.com/OpenIMSDK/protocol/msg"
|
||
"github.com/OpenIMSDK/protocol/sdkws"
|
||
"github.com/OpenIMSDK/tools/a2r"
|
||
"github.com/OpenIMSDK/tools/apiresp"
|
||
"github.com/OpenIMSDK/tools/errs"
|
||
"github.com/OpenIMSDK/tools/log"
|
||
"github.com/OpenIMSDK/tools/mcontext"
|
||
"github.com/OpenIMSDK/tools/utils"
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/go-playground/validator/v10"
|
||
"github.com/mitchellh/mapstructure"
|
||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||
http2 "github.com/openimsdk/open-im-server/v3/pkg/common/http"
|
||
pbmsg "github.com/openimsdk/open-im-server/v3/tools/data-conversion/openim/proto/msg"
|
||
"net/http"
|
||
"reflect"
|
||
"strings"
|
||
|
||
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
|
||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
|
||
)
|
||
|
||
type MessageApi struct {
|
||
*rpcclient.Message
|
||
validate *validator.Validate
|
||
userRpcClient *rpcclient.UserRpcClient
|
||
}
|
||
|
||
func NewMessageApi(msgRpcClient *rpcclient.Message, userRpcClient *rpcclient.User) MessageApi {
|
||
return MessageApi{Message: msgRpcClient, validate: validator.New(), userRpcClient: rpcclient.NewUserRpcClientByUser(userRpcClient)}
|
||
}
|
||
|
||
func (MessageApi) SetOptions(options map[string]bool, value bool) {
|
||
utils.SetSwitchFromOptions(options, constant.IsHistory, value)
|
||
utils.SetSwitchFromOptions(options, constant.IsPersistent, value)
|
||
utils.SetSwitchFromOptions(options, constant.IsSenderSync, value)
|
||
utils.SetSwitchFromOptions(options, constant.IsConversationUpdate, value)
|
||
}
|
||
|
||
func (m MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) *msg.SendMsgReq {
|
||
var newContent string
|
||
options := make(map[string]bool, 5)
|
||
switch params.ContentType {
|
||
case constant.OANotification:
|
||
notification := sdkws.NotificationElem{}
|
||
notification.Detail = utils.StructToJsonString(params.Content)
|
||
newContent = utils.StructToJsonString(¬ification)
|
||
case constant.Text:
|
||
fallthrough
|
||
case constant.Picture:
|
||
fallthrough
|
||
case constant.Custom:
|
||
fallthrough
|
||
case constant.Voice:
|
||
fallthrough
|
||
case constant.Video:
|
||
fallthrough
|
||
case constant.File:
|
||
fallthrough
|
||
default:
|
||
newContent = utils.StructToJsonString(params.Content)
|
||
}
|
||
if params.IsOnlineOnly {
|
||
m.SetOptions(options, false)
|
||
}
|
||
if params.NotOfflinePush {
|
||
utils.SetSwitchFromOptions(options, constant.IsOfflinePush, false)
|
||
}
|
||
pbData := msg.SendMsgReq{
|
||
MsgData: &sdkws.MsgData{
|
||
SendID: params.SendID,
|
||
GroupID: params.GroupID,
|
||
ClientMsgID: utils.GetMsgID(params.SendID),
|
||
SenderPlatformID: params.SenderPlatformID,
|
||
SenderNickname: params.SenderNickname,
|
||
SenderFaceURL: params.SenderFaceURL,
|
||
SessionType: params.SessionType,
|
||
MsgFrom: constant.SysMsgType,
|
||
ContentType: params.ContentType,
|
||
Content: []byte(newContent),
|
||
CreateTime: utils.GetCurrentTimestampByMill(),
|
||
SendTime: params.SendTime,
|
||
Options: options,
|
||
OfflinePushInfo: params.OfflinePushInfo,
|
||
},
|
||
}
|
||
return &pbData
|
||
}
|
||
|
||
func (m *MessageApi) GetSeq(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.GetMaxSeq, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) PullMsgBySeqs(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.PullMessageBySeqs, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) RevokeMsg(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.RevokeMsg, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) MarkMsgsAsRead(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.MarkMsgsAsRead, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) MarkConversationAsRead(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.MarkConversationAsRead, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) GetConversationsHasReadAndMaxSeq(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.GetConversationsHasReadAndMaxSeq, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) SetConversationHasReadSeq(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.SetConversationHasReadSeq, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) ClearConversationsMsg(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.ClearConversationsMsg, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) UserClearAllMsg(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.UserClearAllMsg, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) DeleteMsgs(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.DeleteMsgs, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) DeleteMsgPhysicalBySeq(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.DeleteMsgPhysicalBySeq, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) DeleteMsgPhysical(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.DeleteMsgPhysical, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendMsgReq *msg.SendMsgReq, err error) {
|
||
var data any
|
||
log.ZDebug(c, "getSendMsgReq", "req", req.Content)
|
||
switch req.ContentType {
|
||
case constant.Text:
|
||
data = apistruct.TextElem{}
|
||
case constant.Picture:
|
||
data = apistruct.PictureElem{}
|
||
case constant.Voice:
|
||
data = apistruct.SoundElem{}
|
||
case constant.Video:
|
||
data = apistruct.VideoElem{}
|
||
case constant.File:
|
||
data = apistruct.FileElem{}
|
||
case constant.AtText:
|
||
data = apistruct.AtElem{}
|
||
case constant.Custom:
|
||
data = apistruct.CustomElem{}
|
||
case constant.OANotification:
|
||
data = apistruct.OANotificationElem{}
|
||
req.SessionType = constant.NotificationChatType
|
||
if err = m.userRpcClient.GetNotificationByID(c, req.SendID); err != nil {
|
||
return nil, err
|
||
}
|
||
default:
|
||
return nil, errs.ErrArgs.WithDetail("not support err contentType")
|
||
}
|
||
if err := mapstructure.WeakDecode(req.Content, &data); err != nil {
|
||
return nil, err
|
||
}
|
||
log.ZDebug(c, "getSendMsgReq", "req", req.Content)
|
||
if err := m.validate.Struct(data); err != nil {
|
||
return nil, err
|
||
}
|
||
return m.newUserSendMsgReq(c, &req), nil
|
||
}
|
||
|
||
// SendMessage handles the sending of a message. It's an HTTP handler function to be used with Gin framework.
|
||
func (m *MessageApi) SendMessage(c *gin.Context) {
|
||
// Initialize a request struct for sending a message.
|
||
req := apistruct.SendMsgReq{}
|
||
|
||
// Bind the JSON request body to the request struct.
|
||
if err := c.BindJSON(&req); err != nil {
|
||
// Respond with an error if request body binding fails.
|
||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
|
||
// Check if the user has the app manager role.
|
||
if !authverify.IsAppManagerUid(c) {
|
||
// Respond with a permission error if the user is not an app manager.
|
||
apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message"))
|
||
return
|
||
}
|
||
|
||
// Prepare the message request with additional required data.
|
||
sendMsgReq, err := m.getSendMsgReq(c, req.SendMsg)
|
||
if err != nil {
|
||
// Log and respond with an error if preparation fails.
|
||
log.ZError(c, "decodeData failed", err)
|
||
apiresp.GinError(c, err)
|
||
return
|
||
}
|
||
|
||
// Set the receiver ID in the message data.
|
||
sendMsgReq.MsgData.RecvID = req.RecvID
|
||
|
||
// Declare a variable to store the message sending status.
|
||
var status int
|
||
|
||
// Attempt to send the message using the client.
|
||
respPb, err := m.Client.SendMsg(c, sendMsgReq)
|
||
if err != nil {
|
||
// Set the status to failed and respond with an error if sending fails.
|
||
status = constant.MsgSendFailed
|
||
log.ZError(c, "send message err", err)
|
||
apiresp.GinError(c, err)
|
||
return
|
||
}
|
||
|
||
// Set the status to successful if the message is sent.
|
||
status = constant.MsgSendSuccessed
|
||
|
||
// Attempt to update the message sending status in the system.
|
||
_, err = m.Client.SetSendMsgStatus(c, &msg.SetSendMsgStatusReq{
|
||
Status: int32(status),
|
||
})
|
||
if err != nil {
|
||
// Log the error if updating the status fails.
|
||
log.ZError(c, "SetSendMsgStatus failed", err)
|
||
}
|
||
|
||
// Respond with a success message and the response payload.
|
||
apiresp.GinSuccess(c, respPb)
|
||
}
|
||
|
||
func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
|
||
req := struct {
|
||
Key string `json:"key"`
|
||
Data string `json:"data"`
|
||
SendUserID string `json:"sendUserID" binding:"required"`
|
||
RecvUserID string `json:"recvUserID" binding:"required"`
|
||
}{}
|
||
if err := c.BindJSON(&req); err != nil {
|
||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
|
||
if !authverify.IsAppManagerUid(c) {
|
||
apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message"))
|
||
return
|
||
}
|
||
sendMsgReq := msg.SendMsgReq{
|
||
MsgData: &sdkws.MsgData{
|
||
SendID: req.SendUserID,
|
||
RecvID: req.RecvUserID,
|
||
Content: []byte(utils.StructToJsonString(&sdkws.NotificationElem{
|
||
Detail: utils.StructToJsonString(&struct {
|
||
Key string `json:"key"`
|
||
Data string `json:"data"`
|
||
}{Key: req.Key, Data: req.Data}),
|
||
})),
|
||
MsgFrom: constant.SysMsgType,
|
||
ContentType: constant.BusinessNotification,
|
||
SessionType: constant.SingleChatType,
|
||
CreateTime: utils.GetCurrentTimestampByMill(),
|
||
ClientMsgID: utils.GetMsgID(mcontext.GetOpUserID(c)),
|
||
Options: config.GetOptionsByNotification(config.NotificationConf{
|
||
IsSendMsg: false,
|
||
ReliabilityLevel: 1,
|
||
UnreadCount: false,
|
||
}),
|
||
},
|
||
}
|
||
respPb, err := m.Client.SendMsg(c, &sendMsgReq)
|
||
if err != nil {
|
||
apiresp.GinError(c, err)
|
||
return
|
||
}
|
||
apiresp.GinSuccess(c, respPb)
|
||
}
|
||
|
||
func (m *MessageApi) BatchSendMsg(c *gin.Context) {
|
||
var (
|
||
req apistruct.BatchSendMsgReq
|
||
resp apistruct.BatchSendMsgResp
|
||
)
|
||
if err := c.BindJSON(&req); err != nil {
|
||
log.ZError(c, "BatchSendMsg BindJSON failed", err)
|
||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
log.ZInfo(c, "BatchSendMsg", "req", req)
|
||
if err := authverify.CheckAdmin(c); err != nil {
|
||
apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message"))
|
||
return
|
||
}
|
||
|
||
var recvIDs []string
|
||
var err error
|
||
if req.IsSendAll {
|
||
pageNumber := 1
|
||
showNumber := 500
|
||
for {
|
||
recvIDsPart, err := m.userRpcClient.GetAllUserIDs(c, int32(pageNumber), int32(showNumber))
|
||
if err != nil {
|
||
log.ZError(c, "GetAllUserIDs failed", err)
|
||
apiresp.GinError(c, err)
|
||
return
|
||
}
|
||
recvIDs = append(recvIDs, recvIDsPart...)
|
||
if len(recvIDsPart) < showNumber {
|
||
break
|
||
}
|
||
pageNumber++
|
||
}
|
||
} else {
|
||
recvIDs = req.RecvIDs
|
||
}
|
||
log.ZDebug(c, "BatchSendMsg nums", "nums ", len(recvIDs))
|
||
sendMsgReq, err := m.getSendMsgReq(c, req.SendMsg)
|
||
if err != nil {
|
||
log.ZError(c, "decodeData failed", err)
|
||
apiresp.GinError(c, err)
|
||
return
|
||
}
|
||
for _, recvID := range recvIDs {
|
||
sendMsgReq.MsgData.RecvID = recvID
|
||
rpcResp, err := m.Client.SendMsg(c, sendMsgReq)
|
||
if err != nil {
|
||
resp.FailedIDs = append(resp.FailedIDs, recvID)
|
||
continue
|
||
}
|
||
resp.Results = append(resp.Results, &apistruct.SingleReturnResult{
|
||
ServerMsgID: rpcResp.ServerMsgID,
|
||
ClientMsgID: rpcResp.ClientMsgID,
|
||
SendTime: rpcResp.SendTime,
|
||
RecvID: recvID,
|
||
})
|
||
}
|
||
apiresp.GinSuccess(c, resp)
|
||
}
|
||
|
||
func (m *MessageApi) CheckMsgIsSendSuccess(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.GetSendMsgStatus, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) GetUsersOnlineStatus(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.GetSendMsgStatus, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) GetActiveUser(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.GetActiveUser, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) GetActiveGroup(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.GetActiveGroup, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) SearchMsg(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.SearchMessage, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) GetServerTime(c *gin.Context) {
|
||
a2r.Call(msg.MsgClient.GetServerTime, m.Client, c)
|
||
}
|
||
|
||
func (m *MessageApi) CallbackExample(c *gin.Context) {
|
||
// 1. Callback after sending a single chat message
|
||
var req callbackstruct.CallbackAfterSendSingleMsgReq
|
||
|
||
if err := c.BindJSON(&req); err != nil {
|
||
log.ZError(c, "CallbackExample BindJSON failed", err)
|
||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
|
||
resp := callbackstruct.CallbackAfterSendSingleMsgResp{
|
||
CommonCallbackResp: callbackstruct.CommonCallbackResp{
|
||
ActionCode: 0,
|
||
ErrCode: 200,
|
||
ErrMsg: "success",
|
||
ErrDlt: "successful",
|
||
NextCode: 0,
|
||
},
|
||
}
|
||
c.JSON(http.StatusOK, resp)
|
||
|
||
// 2. If the user receiving the message is a customer service bot, return the message.
|
||
|
||
// UserID of the robot account
|
||
robotics := "5078764102"
|
||
// Administrator token
|
||
imtoken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOiJpbUFkbWluIiwiUGxhdGZvcm1JRCI6MTAsImV4cCI6MTcxMzI1MjI0OSwibmJmIjoxNzA1NDc1OTQ5LCJpYXQiOjE3MDU0NzYyNDl9.Zi-uFre8zq6msT3mFOumgcfNKBJ92kTw9ewsKeRVbZ4"
|
||
if req.SendID == robotics {
|
||
return
|
||
}
|
||
// Processing text messages
|
||
if req.ContentType == constant.Picture {
|
||
user, err := m.userRpcClient.GetUserInfo(c, robotics)
|
||
if err != nil {
|
||
log.ZError(c, "CallbackExample get Sender failed", err)
|
||
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
|
||
// Handle message structures
|
||
text := apistruct.PictureElem{}
|
||
log.ZDebug(c, "callback", "contextCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", req.Content)
|
||
err = json.Unmarshal([]byte(req.Content), &text)
|
||
if err != nil {
|
||
log.ZError(c, "CallbackExample unmarshal failed", err)
|
||
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
log.ZDebug(c, "callback", "text%TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT", text)
|
||
|
||
if strings.Contains(text.SourcePicture.Type, "/") {
|
||
arr := strings.Split(text.SourcePicture.Type, "/")
|
||
text.SourcePicture.Type = arr[1]
|
||
}
|
||
|
||
if strings.Contains(text.BigPicture.Type, "/") {
|
||
arr := strings.Split(text.BigPicture.Type, "/")
|
||
text.BigPicture.Type = arr[1]
|
||
}
|
||
|
||
if len(text.SnapshotPicture.Type) == 0 {
|
||
text.SnapshotPicture.Type = text.SourcePicture.Type
|
||
}
|
||
|
||
mapStruct := make(map[string]any)
|
||
mapStruct1, err := convertStructToMap(text.SnapshotPicture)
|
||
|
||
if err != nil {
|
||
log.ZError(c, "CallbackExample struct to map failed", err)
|
||
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
mapStruct["snapshotPicture"] = mapStruct1
|
||
|
||
mapStruct2, err := convertStructToMap(text.BigPicture)
|
||
if err != nil {
|
||
log.ZError(c, "CallbackExample struct to map failed", err)
|
||
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
mapStruct["bigPicture"] = mapStruct2
|
||
|
||
mapStruct3, err := convertStructToMap(text.SourcePicture)
|
||
if err != nil {
|
||
log.ZError(c, "CallbackExample struct to map failed", err)
|
||
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
mapStruct["sourcePicture"] = mapStruct3
|
||
mapStruct["sourcePath"] = text.SourcePath
|
||
|
||
log.ZDebug(c, "callback", "contextAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", mapStruct)
|
||
|
||
input := &apistruct.SendMsgReq{
|
||
RecvID: req.SendID,
|
||
SendMsg: apistruct.SendMsg{
|
||
SendID: user.UserID,
|
||
SenderNickname: user.Nickname,
|
||
SenderFaceURL: user.FaceURL,
|
||
SenderPlatformID: req.SenderPlatformID,
|
||
Content: mapStruct,
|
||
ContentType: req.ContentType,
|
||
SessionType: req.SessionType,
|
||
SendTime: utils.GetCurrentTimestampByMill(), // millisecond
|
||
},
|
||
}
|
||
|
||
url := "http://127.0.0.1:10002/msg/send_msg"
|
||
header := make(map[string]string, 2)
|
||
header["token"] = imtoken
|
||
type sendResp struct {
|
||
ErrCode int `json:"errCode"`
|
||
ErrMsg string `json:"errMsg"`
|
||
ErrDlt string `json:"errDlt"`
|
||
Data pbmsg.SendMsgResp `json:"data,omitempty"`
|
||
}
|
||
|
||
output := &sendResp{}
|
||
|
||
// Initiate a post request that calls the interface that sends the message (the bot sends a message to user)
|
||
b, err := http2.Post(c, url, header, input, 10)
|
||
if err != nil {
|
||
log.ZError(c, "CallbackExample send message failed", err)
|
||
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
if err = json.Unmarshal(b, output); err != nil {
|
||
log.ZError(c, "CallbackExample unmarshal failed", err)
|
||
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
|
||
return
|
||
}
|
||
res := &msg.SendMsgResp{
|
||
ServerMsgID: output.Data.ServerMsgID,
|
||
ClientMsgID: output.Data.ClientMsgID,
|
||
SendTime: output.Data.SendTime,
|
||
}
|
||
|
||
apiresp.GinSuccess(c, res)
|
||
}
|
||
}
|
||
|
||
func convertStructToMap(input interface{}) (map[string]interface{}, error) {
|
||
// 使用反射创建一个空的 map
|
||
result := make(map[string]interface{})
|
||
|
||
// 获取结构体的类型信息
|
||
inputType := reflect.TypeOf(input)
|
||
|
||
// 获取结构体的值信息
|
||
inputValue := reflect.ValueOf(input)
|
||
|
||
// 确保输入是结构体类型
|
||
if inputType.Kind() != reflect.Struct {
|
||
return nil, fmt.Errorf("Input is not a struct")
|
||
}
|
||
|
||
// 遍历结构体的字段
|
||
for i := 0; i < inputType.NumField(); i++ {
|
||
field := inputType.Field(i)
|
||
fieldValue := inputValue.Field(i)
|
||
|
||
// 获取mapstructure标签的值作为map的键
|
||
mapKey := field.Tag.Get("mapstructure")
|
||
fmt.Println(mapKey)
|
||
// 如果没有mapstructure标签,则使用字段名作为键
|
||
if mapKey == "" {
|
||
mapKey = field.Name
|
||
}
|
||
|
||
// 转换为小写形式,以匹配JSON的命名约定
|
||
mapKey = strings.ToLower(mapKey)
|
||
|
||
// 将字段名作为 map 的键,字段值作为 map 的值
|
||
result[mapKey] = fieldValue.Interface()
|
||
}
|
||
|
||
return result, nil
|
||
}
|