8 Commits

Author SHA1 Message Date
9f8acb951c feat(app): 提供通用缓存 2025-12-02 14:07:14 +08:00
8bde0f660d feat(app): update 2025-12-02 13:44:25 +08:00
7a7706d0fb update 2025-11-28 16:42:18 +08:00
ef99f24495 feat: 添加云信群组api 2025-11-28 14:12:33 +08:00
e0fbc8567b feat: 添加云信群组api 2025-11-28 13:59:36 +08:00
465eeb5699 feat: 添加云信群组api 2025-11-28 13:28:00 +08:00
a131bf5cce feat: 添加云信群组api 2025-11-28 13:24:11 +08:00
1c8b60f4d2 feat(app): update 2025-11-19 14:27:07 +08:00
4 changed files with 563 additions and 10 deletions

122
pkg/cache/common/common.go vendored Normal file
View File

@ -0,0 +1,122 @@
package common
import (
"encoding/json"
"errors"
"gitea.ddegame.cn/open/servicebase/pkg/cache"
"github.com/redis/go-redis/v9"
)
type DBCacheFetcher[T any] func(fieldKey string) (*T, error)
type DBCacheFetcherMany[T any] func(fieldKeys []string) (map[string]*T, error)
func GetOrCacheOne[T any](cli *redis.Client, hashKey string, fieldKey string, dbFetcher DBCacheFetcher[T]) (*T, error) {
if len(fieldKey) == 0 {
return nil, nil
}
var cachedStr string
var err error
if cachedStr, err = cli.HGet(cache.Ctx(), hashKey, fieldKey).Result(); err != nil && !errors.Is(err, redis.Nil) {
return nil, err
}
var model *T
if len(cachedStr) > 0 {
model = new(T)
if err = json.Unmarshal([]byte(cachedStr), model); err == nil {
return model, nil
}
}
if model, err = dbFetcher(fieldKey); err != nil {
return nil, err
}
if model == nil {
return nil, nil
}
bytes, marshalErr := json.Marshal(model)
if marshalErr != nil {
return model, nil
}
_, _ = cli.HSet(cache.Ctx(), hashKey, fieldKey, string(bytes)).Result()
return model, nil
}
func GetOrCacheMany[T any](cli *redis.Client, hashKey string, fieldKeyList []string, dbFetcherMany DBCacheFetcherMany[T]) ([]*T, error) {
if len(fieldKeyList) == 0 {
return make([]*T, 0), nil
}
finalResults := make([]*T, len(fieldKeyList))
missingKeysMap := make(map[string]int)
missingKeysList := make([]string, 0)
values, err := cli.HMGet(cache.Ctx(), hashKey, fieldKeyList...).Result()
if err != nil && !errors.Is(err, redis.Nil) {
return nil, err
}
for i, fieldKey := range fieldKeyList {
value := values[i]
if value == nil {
missingKeysMap[fieldKey] = i
missingKeysList = append(missingKeysList, fieldKey)
continue
}
cachedStr, ok := value.(string)
if !ok {
missingKeysMap[fieldKey] = i
missingKeysList = append(missingKeysList, fieldKey)
continue
}
model := new(T)
if jsonErr := json.Unmarshal([]byte(cachedStr), model); jsonErr == nil {
finalResults[i] = model
} else {
missingKeysMap[fieldKey] = i
missingKeysList = append(missingKeysList, fieldKey)
}
}
if len(missingKeysList) == 0 {
return finalResults, nil
}
dbModelsMap, err := dbFetcherMany(missingKeysList)
if err != nil {
return nil, err
}
cacheMap := make(map[string]any)
for fieldKey, model := range dbModelsMap {
originalIndex, found := missingKeysMap[fieldKey]
if !found {
continue
}
finalResults[originalIndex] = model
if model != nil {
if bytes, marshalErr := json.Marshal(model); marshalErr == nil {
cacheMap[fieldKey] = string(bytes)
}
}
}
if len(cacheMap) > 0 {
_, _ = cli.HSet(cache.Ctx(), hashKey, cacheMap).Result()
}
return finalResults, nil
}
func DelCacheFields(cli *redis.Client, hashKey string, fieldKeyList ...string) error {
if len(fieldKeyList) == 0 {
return nil
}
_, err := cli.HDel(cache.Ctx(), hashKey, fieldKeyList...).Result()
if err != nil && !errors.Is(err, redis.Nil) {
return err
}
return nil
}
func DelCacheHash(cli *redis.Client, hashKey string) error {
if len(hashKey) == 0 {
return nil
}
_, err := cli.Del(cache.Ctx(), hashKey).Result()
if err != nil && !errors.Is(err, redis.Nil) {
return err
}
return nil
}

View File

@ -38,7 +38,9 @@ const (
DateDefaultFormat = "Y-m-d"
//post请求
HTTP_METHOD_POST = "POST"
HTTP_METHOD_POST = "POST"
HTTP_METHOD_PATCH = "PATCH"
HTTP_METHOD_DELETE = "DELETE"
// 消费单位名称
CONSUME_UNIT_NAME = "钻石"

View File

@ -11,6 +11,7 @@ import (
yunxin "gitea.ddegame.cn/open/servicebase/pkg/common/netease/dto"
"gitea.ddegame.cn/open/servicebase/pkg/htools"
"gitea.ddegame.cn/open/servicebase/pkg/log"
"gitea.ddegame.cn/open/servicebase/pkg/tools"
"github.com/pkg/errors"
"github.com/spf13/cast"
@ -689,7 +690,6 @@ func (client *ImClient) SendAttachMsg(parameters map[string]string) error {
// CreateChatGroup 创建群组
func (client *ImClient) CreateChatGroup(ctx context.Context, ownerUserId string, groupName string, inviteMem []string) (string, error) {
url := "https://open.yunxinapi.com/im/v2.1/teams"
httpMethod := common.HTTP_METHOD_POST
header := client.generateJsonHeader()
@ -736,6 +736,437 @@ func (client *ImClient) CreateChatGroup(ctx context.Context, ownerUserId string,
return cast.ToString(body.Data.TeamInfo.TeamID), nil
}
// P2PMsgSend 发送单聊消息
func (client *ImClient) P2PMsgSend(from, to, _type, content, msg string) error {
url := "https://api.netease.im/nimserver/msg/sendMsg.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := HyTools.NewStringBuilder()
sb.Append("from=" + from)
sb.Append("&")
sb.Append("ope=" + "0")
sb.Append("&")
sb.Append("to=" + to)
sb.Append("&")
sb.Append("type=" + _type) // 0文本消息1图片消息2语音消息3视频消息4地理位置消息6文件消息10提示消息100自定义消息
sb.Append("&")
sb.Append("body=" + content)
sb.Append("&")
sb.Append("antispam=true")
res, err := HyTools.HttpDo(httpMethod, url, header, sb.ToString())
// code Integer 状态码
// tid Long 网易云信服务器产生,群唯一标识
// faccid String 入群失败的账号accid列表格式为 JSONArray如果创建时邀请的成员中存在加群数量超过限制的情况会返回入群失败的 accid 以及附言msg
if err != nil {
// {"desc":"already register","code":414}
log.InfoF("GroupMsgSend error %s:", err.Error())
return err
} else {
var resDTO CreateImRes
json.Unmarshal([]byte(res), &resDTO)
if resDTO.Code != 200 {
log.InfoF("GroupMsgSend code error %s", resDTO.Desc)
} else {
log.InfoF("GroupMsgSend success from=%s to=%s type=%s content=%s res=%s", from, to, _type, content, res)
}
}
var body map[string]any
if errJson := json.Unmarshal([]byte(res), &body); errJson != nil {
return errJson
}
if int(body["code"].(float64)) != 200 {
return errors.New(body["desc"].(string))
}
return nil
}
// GroupMsgSend 发送群消息
func (client *ImClient) GroupMsgSend(from, tid, _type, content, msg string) error {
url := "https://api.netease.im/nimserver/msg/sendMsg.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := HyTools.NewStringBuilder()
sb.Append("from=" + from)
sb.Append("&")
sb.Append("ope=" + "1")
sb.Append("&")
sb.Append("to=" + tid)
sb.Append("&")
sb.Append("type=" + _type) // 0文本消息1图片消息2语音消息3视频消息4地理位置消息6文件消息10提示消息100自定义消息
sb.Append("&")
sb.Append("body=" + content)
sb.Append("&")
sb.Append("antispam=true")
res, err := HyTools.HttpDo(httpMethod, url, header, sb.ToString())
// code Integer 状态码
// tid Long 网易云信服务器产生,群唯一标识
// faccid String 入群失败的账号accid列表格式为 JSONArray如果创建时邀请的成员中存在加群数量超过限制的情况会返回入群失败的 accid 以及附言msg
if err != nil {
// {"desc":"already register","code":414}
log.InfoF("GroupMsgSend error %s:", err.Error())
return err
} else {
var resDTO CreateImRes
json.Unmarshal([]byte(res), &resDTO)
if resDTO.Code != 200 {
log.InfoF("GroupMsgSend code error %s", resDTO.Desc)
} else {
log.InfoF("GroupMsgSend success from=%s to=%s type=%s content=%s res=%s", from, tid, _type, content, res)
}
}
var body map[string]any
if errJson := json.Unmarshal([]byte(res), &body); errJson != nil {
return errJson
}
if int(body["code"].(float64)) != 200 {
return errors.New(body["desc"].(string))
}
return nil
}
// 创建云信账户
// 创建高级群,创建时即可通过设置群成员列表邀请用户入群。
// 建群成功会返回 tid网易云信服务器产生群唯一标识该字段需要保存以便于加人与踢人等后续操作。
// 如果创建时邀请的成员中存在加群数量超过限制的情况,会返回 faccid加群失败成员的 IM 账号)。
// 每个用户可创建的群数量有限制,限制值由 IM 套餐的群组配置决定,具体可前往 网易云信控制台 查看。
func (client *ImClient) CreateProGroup(tname, owner, announcement, avatar, msg string) (string, error) {
url := "https://api.netease.im/nimserver/team/create.action"
httpMethod := common.HTTP_METHOD_POST
header := client.generateHeader()
sb := HyTools.NewStringBuilder()
sb.Append("tname=" + tname)
sb.Append("&")
sb.Append("owner=" + owner)
sb.Append("&")
sb.Append("announcement=" + announcement)
sb.Append("&")
sb.Append("msg=" + msg)
sb.Append("&")
sb.Append("icon=" + avatar)
sb.Append("&")
sb.Append("members=[]")
sb.Append("&")
sb.Append("joinmode=" + "0")
sb.Append("&")
sb.Append("beinvitemode=" + "1")
sb.Append("&")
sb.Append("invitemode=" + "1") // 邀请权限即谁可以邀请他人入群0群主和管理员默认。1所有人
sb.Append("&")
sb.Append("teamMemberLimit=" + "3000") // 最大群成员数(包含群主),[2200(默认)]
sb.Append("&")
sb.Append("isNotifyCloseOnline=" + "0") // 是否关闭群通知消息在线发送0否。1
sb.Append("&")
sb.Append("isNotifyClosePersistent=" + "0") // 是否关闭存储离线/漫游/历史的群通知消息0否。1
res, err := HyTools.HttpDo(httpMethod, url, header, sb.ToString())
// code Integer 状态码
// tid Long 网易云信服务器产生,群唯一标识
// faccid String 入群失败的账号accid列表格式为 JSONArray如果创建时邀请的成员中存在加群数量超过限制的情况会返回入群失败的 accid 以及附言msg
if err != nil {
// {"desc":"already register","code":414}
log.Info("CreateProGroup %s:" + err.Error())
return "", err
} else {
var resDTO CreateImRes
json.Unmarshal([]byte(res), &resDTO)
if resDTO.Code != 200 {
log.Info("CreateProGroup_%s:" + resDTO.Desc)
} else {
log.Info("CreateProGroup_%s:" + res)
}
}
var body map[string]any
if errJson := json.Unmarshal([]byte(res), &body); errJson != nil {
return "", errJson
}
if int(body["code"].(float64)) != 200 {
return "", errors.New(body["desc"].(string))
}
return body["tid"].(string), nil
}
// GroupUpdate 更新群组
func (client *ImClient) GroupUpdate(ctx context.Context, id, operator_id, name, announcement, icon string) error {
url := "https://open.yunxinapi.com/im/v2.1/teams/" + id
httpMethod := "PATCH"
header := client.generateJsonHeader()
reqBody := map[string]any{
"operator_id": operator_id,
"team_type": 1,
"name": name,
"announcement": announcement,
// "intro": intro,
// "members_limit": members_limit,
// "join_mode": join_mode, 通过 SDK 侧操作申请入群的验证方式。 0默认无需验证直接入群。 1需要群主或管理员验证通过才能入群。 2不允许任何人申请入群。
// "agree_mode": agree_mode, 邀请入群时是否需要被邀请人的同意。0默认需要被邀请人同意才能入群。1不需要被邀请人同意直接入群。
// "invite_mode": invite_mode, 邀请权限即谁可以邀请他人入群。0默认群主和管理员。1所有人。
}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return errors.WithStack(err)
}
res, err := HyTools.HttpDo(httpMethod, url, header, string(bodyBytes))
if err != nil {
log.InfoF("GroupUpdate %s:"+err.Error(), operator_id)
return errors.WithStack(err)
}
log.InfoF("update group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupUpdate code(%d) not 200,msg: %s", body.Code, body.Msg)
}
return nil
}
// GroupTransfer 转让群组
func (client *ImClient) GroupTransfer(ctx context.Context, id, new_owner, extension string) error {
url := "https://open.yunxinapi.com/im/v2.1/teams/" + id + "/actions/transfer_owner"
httpMethod := "PATCH"
header := client.generateJsonHeader()
reqBody := map[string]any{
"team_type": 1,
"new_owner_account_id": new_owner,
"leave": 2,
"extension": extension,
}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return errors.WithStack(err)
}
res, err := HyTools.HttpDo(httpMethod, url, header, string(bodyBytes))
if err != nil {
log.InfoF("GroupTransfer %s:"+err.Error(), id)
return errors.WithStack(err)
}
log.InfoF("GroupTransfer group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupTransfer code(%d) not 200,msg: %s", body.Code, body.Msg)
}
return nil
}
// GroupManagerAdd
func (client *ImClient) GroupManagerAdd(ctx context.Context, id, operator_id, extension string, managers []string) error {
url := "https://open.yunxinapi.com/im/v2.1/teams/" + id + "/actions/add_manager"
httpMethod := common.HTTP_METHOD_POST
header := client.generateJsonHeader()
reqBody := map[string]any{
"team_type": 1,
"managers": managers,
"operator_id": operator_id,
// "extension": extension,
}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return errors.WithStack(err)
}
res, err := HyTools.HttpDo(httpMethod, url, header, string(bodyBytes))
if err != nil {
log.InfoF("GroupManagerAdd %s:"+err.Error(), operator_id)
return errors.WithStack(err)
}
log.InfoF("GroupManagerAdd group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupManagerAdd code(%d) not 200,msg: %s", body.Code, body.Msg)
}
return nil
}
// GroupManagerRemove
func (client *ImClient) GroupManagerRemove(ctx context.Context, id, operator_id, extension string, managers []string) error {
url := "https://open.yunxinapi.com/im/v2.1/teams/" + id + "/actions/remove_manager"
httpMethod := "DELETE"
header := client.generateJsonHeader()
reqBody := map[string]any{
"team_type": 1,
"managers": managers,
"operator_id": operator_id,
// "extension": extension,
}
// var params []string
// for k, v := range reqBody {
// params = append(params, fmt.Sprintf("%s=%v", k, v))
// }
// url = fmt.Sprintf("%s?%s", url, strings.Join(params, "&"))
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return errors.WithStack(err)
}
res, err := HyTools.HttpDo(httpMethod, url, header, string(bodyBytes))
if err != nil {
log.InfoF("GroupManagerRemove %s:"+err.Error(), operator_id)
return errors.WithStack(err)
}
log.InfoF("GroupManagerRemove group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupManagerRemove code(%d) not 200 url=%s id=%s operator=%s managers=%v,msg: %s", body.Code, url, id, operator_id, managers, body.Msg)
}
return nil
}
// GroupRemove
func (client *ImClient) GroupRemove(ctx context.Context, id, operator_id, extension string) error {
url := "https://open.yunxinapi.com/im/v2.1/teams/" + id
httpMethod := "DELETE"
header := client.generateJsonHeader()
reqBody := map[string]any{
"team_type": 1,
"operator_id": operator_id,
// "extension": extension,
}
var params []string
for k, v := range reqBody {
params = append(params, fmt.Sprintf("%s=%v", k, v))
}
url = fmt.Sprintf("%s?%s", url, strings.Join(params, "&"))
// bodyBytes, err := json.Marshal(reqBody)
// if err != nil {
// return errors.WithStack(err)
// }
res, err := HyTools.HttpDo(httpMethod, url, header, "")
if err != nil {
log.InfoF("GroupRemove %s:"+err.Error(), operator_id)
return errors.WithStack(err)
}
log.InfoF("GroupRemove group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupRemove code(%d) not 200,msg: %s", body.Code, body.Msg)
}
return nil
}
// GroupMemberKick
func (client *ImClient) GroupMemberKick(ctx context.Context, tid, operator_id, extension string, members []string) error {
url := "https://open.yunxinapi.com/im/v2/team_members/actions/kick_member"
httpMethod := "DELETE"
header := client.generateJsonHeader()
reqBody := map[string]any{
"team_type": 1,
"operator_id": operator_id,
"team_id": tools.StrToInt(tid),
"kick_account_ids": members,
// "extension": extension,
}
var params []string
for k, v := range reqBody {
params = append(params, fmt.Sprintf("%s=%v", k, v))
}
url = fmt.Sprintf("%s?%s", url, strings.Join(params, "&"))
// bodyBytes, err := json.Marshal(reqBody)
// if err != nil {
// return errors.WithStack(err)
// }
res, err := HyTools.HttpDo(httpMethod, url, header, "")
if err != nil {
log.InfoF("GroupMemberKick %s:"+err.Error(), operator_id)
return errors.WithStack(err)
}
log.InfoF("GroupMemberKick group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupMemberKick code(%d) not 200,msg: %s", body.Code, body.Msg)
}
return nil
}
// GroupMemberLeave
func (client *ImClient) GroupMemberLeave(ctx context.Context, tid, account_id, extension string) error {
url := "https://open.yunxinapi.com/im/v2/team_members/actions/leave"
httpMethod := "DELETE"
header := client.generateJsonHeader()
reqBody := map[string]any{
"team_type": 1,
"account_id": account_id,
"team_id": tools.StrToInt(tid),
// "extension": extension,
}
var params []string
for k, v := range reqBody {
params = append(params, fmt.Sprintf("%s=%v", k, v))
}
url = fmt.Sprintf("%s?%s", url, strings.Join(params, "&"))
// bodyBytes, err := json.Marshal(reqBody)
// if err != nil {
// return errors.WithStack(err)
// }
res, err := HyTools.HttpDo(httpMethod, url, header, "")
if err != nil {
log.InfoF("GroupMemberLeave %s:"+err.Error(), tid)
return errors.WithStack(err)
}
log.InfoF("GroupMemberLeave group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupMemberLeave code(%d) not 200,msg: %s", body.Code, body.Msg)
}
return nil
}
// GroupMemberJoin
func (client *ImClient) GroupMemberJoin(ctx context.Context, tid, operator_id, msg, extension string, invite_account_ids []string) error {
url := "https://open.yunxinapi.com/im/v2/team_members"
httpMethod := common.HTTP_METHOD_POST
header := client.generateJsonHeader()
reqBody := map[string]any{
"team_type": 1,
"operator_id": operator_id,
"team_id": tools.StrToInt(tid),
"msg": msg,
"invite_account_ids": invite_account_ids,
"extension": extension,
}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return errors.WithStack(err)
}
res, err := HyTools.HttpDo(httpMethod, url, header, string(bodyBytes))
if err != nil {
log.InfoF("GroupMemberJoin %s:"+err.Error(), tid)
return errors.WithStack(err)
}
log.InfoF("GroupMemberJoin group chat resp: %s", res)
var body BaseRes[CreateChatGroupRsp]
err = json.Unmarshal([]byte(res), &body)
if err != nil {
return errors.WithStack(err)
}
if body.Code != 200 {
return errors.Errorf("GroupMemberJoin code(%d) not 200,msg: %s", body.Code, body.Msg)
}
return nil
}
// ================ 私有方法 ========================
func generateBodyData(parameters map[string]string) string {
sb := htools.NewStringBuilder()

View File

@ -2,8 +2,6 @@ package request
import (
"errors"
"gitea.ddegame.cn/open/servicebase/pkg/common/order"
)
type GetSkillLevelListRequest struct {
@ -46,12 +44,12 @@ func (request *GetOrderRankReq) CheckParameter() (err error) {
return
}
if len(request.GroupByFields) != 0 {
for _, field := range request.GroupByFields {
_, ok := order.WhitelistFields[field]
if !ok {
err = errors.New("包含不支持的字段")
return
}
for _, _ = range request.GroupByFields {
// _, ok := order.WhitelistFields[field]
// if !ok {
// err = errors.New("包含不支持的字段")
// return
// }
}
}
return