Skip to content

Commit

Permalink
feat: add a callback example that receive the message and return (ope…
Browse files Browse the repository at this point in the history
…nimsdk#379)

* feat: add a callback example

* fix: fix the robotics not found error

* fix: fix the user create error

* fix: fix the unmarshal error

* fix: fix the url error

* fix: find the error of unmarshal

* fix: fix the unmarshal failed

* fix: update the url

* fix: fix the robotics send message error
  • Loading branch information
luhaoling committed Jan 21, 2024
1 parent 097ab43 commit 316e2d8
Show file tree
Hide file tree
Showing 4 changed files with 442 additions and 1 deletion.
27 changes: 26 additions & 1 deletion internal/api/admin.go
Expand Up @@ -136,7 +136,32 @@ func (o *AdminApi) AddAdminAccount(c *gin.Context) {
}

func (o *AdminApi) AddUserAccount(c *gin.Context) {
a2r.Call(chat.ChatClient.AddUserAccount, o.chatClient, c)
var req chat.AddUserAccountReq
if err := c.BindJSON(&req); err != nil {
apiresp.GinError(c, err)
return
}
if err := checker.Validate(&req); err != nil {
apiresp.GinError(c, err) // 参数校验失败
return
}

_, err := o.chatClient.AddUserAccount(c, &req)

userInfo := &sdkws.UserInfo{
UserID: req.User.UserID,
Nickname: req.User.Nickname,
FaceURL: req.User.FaceURL,
CreateTime: time.Now().UnixMilli(),
}
err = o.imApiCaller.RegisterUser(c, []*sdkws.UserInfo{userInfo})
if err != nil {
apiresp.GinError(c, err)
return
}

apiresp.GinSuccess(c, nil)

}

func (o *AdminApi) DelAdminAccount(c *gin.Context) {
Expand Down
310 changes: 310 additions & 0 deletions internal/api/chat.go
Expand Up @@ -15,9 +15,19 @@
package api

import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/OpenIMSDK/protocol/msg"
"github.com/OpenIMSDK/tools/utils"
"io"
"net"
"net/http"
"reflect"
"strings"
"time"

"github.com/OpenIMSDK/chat/pkg/common/apicall"
Expand Down Expand Up @@ -347,3 +357,303 @@ func (o *ChatApi) SearchFriend(c *gin.Context) {
}
apiresp.GinSuccess(c, resp)
}

func (m *ChatApi) CallbackExample(c *gin.Context) {

// 1. Callback after sending a single chat message
var req apistruct.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 := apistruct.CallbackAfterSendSingleMsgResp{
CommonCallbackResp: apistruct.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

if req.SendID == "robotics" || req.RecvID != "robotics" {
return
}

if req.ContentType != constant.Picture && req.ContentType != constant.Text {
return
}

// Administrator token
url := "http://127.0.0.1:10009/account/login"
adminID := config.Config.ChatAdmin[0].AdminID
paswd := md5.Sum([]byte(adminID))

admin_input := admin.LoginReq{
Account: config.Config.ChatAdmin[0].AdminID,
Password: hex.EncodeToString(paswd[:]),
}

header := make(map[string]string, 2)
header["operationID"] = "111"

b, err := Post(c, url, header, admin_input, 10)
if err != nil {
log.ZError(c, "CallbackExample send message failed", err)
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
return
}

type TokenInfo struct {
ErrCode int `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
Data apistruct.AdminLoginResp `json:"data,omitempty"`
}

admin_output := &TokenInfo{}

if err = json.Unmarshal(b, admin_output); err != nil {
log.ZError(c, "CallbackExample unmarshal failed", err)
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
return
}

header["token"] = admin_output.Data.AdminToken

url = "http://127.0.0.1:10008/user/find/public"

search_input := chat.FindUserFullInfoReq{
UserIDs: []string{"robotics"},
}

b, err = Post(c, url, header, search_input, 10)
if err != nil {
log.ZError(c, "CallbackExample unmarshal failed", err)
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
return
}

type UserInfo struct {
ErrCode int `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
Data chat.FindUserFullInfoResp `json:"data,omitempty"`
}

search_output := &UserInfo{}

if err = json.Unmarshal(b, search_output); err != nil {
log.ZError(c, "search_output unmarshal failed", err)
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
return
}

if len(search_output.Data.Users) == 0 {
apiresp.GinError(c, errs.ErrRecordNotFound.Wrap("the robotics not found"))
return
}

log.ZDebug(c, "callback", "searchUserAccount", search_output)

text := apistruct.TextElem{}
picture := apistruct.PictureElem{}
mapStruct := make(map[string]any)
// Processing text messages

if err != nil {
log.ZError(c, "CallbackExample get Sender failed", err)
apiresp.GinError(c, errs.ErrInternalServer.WithDetail(err.Error()).Wrap())
return
}

// Handle message structures
if req.ContentType == constant.Text {
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", text)
mapStruct["content"] = text.Content
} else {
err = json.Unmarshal([]byte(req.Content), &picture)
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", picture)
if strings.Contains(picture.SourcePicture.Type, "/") {
arr := strings.Split(picture.SourcePicture.Type, "/")
picture.SourcePicture.Type = arr[1]
}

if strings.Contains(picture.BigPicture.Type, "/") {
arr := strings.Split(picture.BigPicture.Type, "/")
picture.BigPicture.Type = arr[1]
}

if len(picture.SnapshotPicture.Type) == 0 {
picture.SnapshotPicture.Type = picture.SourcePicture.Type
}

mapStructSnap := make(map[string]interface{})
if mapStructSnap, err = convertStructToMap(picture.SnapshotPicture); err != nil {
log.ZError(c, "CallbackExample struct to map failed", err)
apiresp.GinError(c, err)
return
}
mapStruct["snapshotPicture"] = mapStructSnap

mapStructBig := make(map[string]interface{})
if mapStructBig, err = convertStructToMap(picture.BigPicture); err != nil {
log.ZError(c, "CallbackExample struct to map failed", err)
apiresp.GinError(c, err)
return
}
mapStruct["bigPicture"] = mapStructBig

mapStructSource := make(map[string]interface{})
if mapStructSource, err = convertStructToMap(picture.SourcePicture); err != nil {
log.ZError(c, "CallbackExample struct to map failed", err)
apiresp.GinError(c, err)
return
}
mapStruct["sourcePicture"] = mapStructSource
mapStruct["sourcePath"] = picture.SourcePath
}

log.ZDebug(c, "callback", "mapStruct", mapStruct, "mapStructSnap")
header["token"] = admin_output.Data.ImToken

input := &apistruct.SendMsgReq{
RecvID: req.SendID,
SendMsg: apistruct.SendMsg{
SendID: search_output.Data.Users[0].UserID,
SenderNickname: search_output.Data.Users[0].Nickname,
SenderFaceURL: search_output.Data.Users[0].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"

type sendResp struct {
ErrCode int `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
Data msg.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 = 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)
}

// struct to map
func convertStructToMap(input interface{}) (map[string]interface{}, error) {
result := make(map[string]interface{})
inputType := reflect.TypeOf(input)

inputValue := reflect.ValueOf(input)

if inputType.Kind() != reflect.Struct {
return nil, errs.ErrArgs.Wrap("input is not a struct")
}

for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i)
fieldValue := inputValue.Field(i)

mapKey := field.Tag.Get("mapstructure")
fmt.Println(mapKey)

if mapKey == "" {
mapKey = field.Name
}

mapKey = strings.ToLower(mapKey)

result[mapKey] = fieldValue.Interface()
}

return result, nil
}

func Post(ctx context.Context, url string, header map[string]string, data any, timeout int) (content []byte, err error) {
var (
// define http client.
client = &http.Client{
Timeout: 15 * time.Second, // max timeout is 15s
}
)

if timeout > 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, time.Second*time.Duration(timeout))
defer cancel()
}

jsonStr, err := json.Marshal(data)
if err != nil {
return nil, err
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonStr))
if err != nil {
return nil, err
}

if operationID, _ := ctx.Value(constant.OperationID).(string); operationID != "" {
req.Header.Set(constant.OperationID, operationID)
}
for k, v := range header {
req.Header.Set(k, v)
}
req.Header.Add("content-type", "application/json; charset=utf-8")

resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

result, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

return result, nil
}
2 changes: 2 additions & 0 deletions internal/api/router.go
Expand Up @@ -56,6 +56,8 @@ func NewChatRoute(router gin.IRouter, discov discoveryregistry.SvcDiscoveryRegis

router.Group("/callback").POST("/open_im", chat.OpenIMCallback) // Callback

router.Group("/callbackExample").POST("/callbackAfterSendSingleMsgCommand", chat.CallbackExample)

logs := router.Group("/logs", mw.CheckToken)
logs.POST("/upload", chat.UploadLogs)
}
Expand Down

0 comments on commit 316e2d8

Please sign in to comment.