feat: add im kafka admin integration
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/imadmin"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/entity"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type imAdminQueryPage struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"per_page"`
|
||||
}
|
||||
|
||||
func resolveImAdminTenant(ctx context.Context, selectedTenantId int64) (tenant *entity.ImTenant, err error) {
|
||||
scope, err := resolveImAdminScope(ctx, selectedTenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).WherePri(scope.TenantId).Scan(&tenant); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM租户失败")
|
||||
}
|
||||
if tenant == nil || tenant.Id <= 0 || tenant.Code == "" {
|
||||
return nil, gerror.New("IM租户不存在或编码为空")
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
func imAdminQueryList[T any](ctx context.Context, tenant *entity.ImTenant, path string, values url.Values, aliases map[string]string) (list []*T, totalCount int, err error) {
|
||||
if values == nil {
|
||||
values = url.Values{}
|
||||
}
|
||||
query := values.Encode()
|
||||
if query != "" {
|
||||
path += "?" + query
|
||||
}
|
||||
var page imAdminQueryPage
|
||||
if err = imadmin.DefaultClient(ctx).InternalGet(ctx, path, &page); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
normalizeImAdminRows(page.Items, aliases)
|
||||
if len(page.Items) == 0 {
|
||||
return []*T{}, page.Total, nil
|
||||
}
|
||||
raw, err := json.Marshal(page.Items)
|
||||
if err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "转换IM内部查询列表失败")
|
||||
}
|
||||
if err = json.Unmarshal(raw, &list); err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "解析IM内部查询列表失败")
|
||||
}
|
||||
fillImTenantFields(tenant, &list)
|
||||
return list, page.Total, nil
|
||||
}
|
||||
|
||||
func imAdminQueryOne[T any](ctx context.Context, tenant *entity.ImTenant, path string, aliases map[string]string) (res *T, err error) {
|
||||
row := map[string]any{}
|
||||
if err = imadmin.DefaultClient(ctx).InternalGet(ctx, path, &row); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
normalizeImAdminRow(row, aliases)
|
||||
raw, err := json.Marshal(row)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "转换IM内部查询详情失败")
|
||||
}
|
||||
if err = json.Unmarshal(raw, &res); err != nil {
|
||||
return nil, gerror.Wrap(err, "解析IM内部查询详情失败")
|
||||
}
|
||||
fillImTenantFields(tenant, res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func normalizeImAdminRows(rows []map[string]any, aliases map[string]string) {
|
||||
for _, row := range rows {
|
||||
normalizeImAdminRow(row, aliases)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeImAdminRow(row map[string]any, aliases map[string]string) {
|
||||
for key, value := range row {
|
||||
target := snakeToLowerCamel(key)
|
||||
if aliases != nil {
|
||||
if alias := aliases[key]; alias != "" {
|
||||
target = alias
|
||||
}
|
||||
}
|
||||
value = normalizeImAdminValue(target, value)
|
||||
if target == key {
|
||||
row[key] = value
|
||||
continue
|
||||
}
|
||||
row[target] = value
|
||||
delete(row, key)
|
||||
}
|
||||
if _, ok := row["authNames"]; !ok {
|
||||
username, _ := row["username"].(string)
|
||||
phone, _ := row["phone"].(string)
|
||||
authNames := username
|
||||
if phone != "" && phone != username {
|
||||
if authNames != "" {
|
||||
authNames += ", "
|
||||
}
|
||||
authNames += phone
|
||||
}
|
||||
if authNames != "" {
|
||||
row["authNames"] = authNames
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeImAdminValue(target string, value any) any {
|
||||
if target != "credentials" {
|
||||
return value
|
||||
}
|
||||
items, ok := value.([]any)
|
||||
if !ok {
|
||||
return value
|
||||
}
|
||||
parts := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
m, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
method, _ := m["method"].(string)
|
||||
val, _ := m["value"].(string)
|
||||
if method == "" && val == "" {
|
||||
continue
|
||||
}
|
||||
text := method
|
||||
if val != "" {
|
||||
if text != "" {
|
||||
text += ":"
|
||||
}
|
||||
text += val
|
||||
}
|
||||
if done, ok := m["done"].(bool); ok && !done {
|
||||
text += "(未验证)"
|
||||
}
|
||||
parts = append(parts, text)
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
func snakeToLowerCamel(s string) string {
|
||||
if !strings.Contains(s, "_") {
|
||||
return s
|
||||
}
|
||||
parts := strings.Split(s, "_")
|
||||
var b strings.Builder
|
||||
for i, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
b.WriteString(part)
|
||||
continue
|
||||
}
|
||||
b.WriteString(strings.ToUpper(part[:1]))
|
||||
if len(part) > 1 {
|
||||
b.WriteString(part[1:])
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func fillImTenantFields(tenant *entity.ImTenant, target any) {
|
||||
raw, err := json.Marshal(target)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var rows []map[string]any
|
||||
if err = json.Unmarshal(raw, &rows); err == nil {
|
||||
for _, row := range rows {
|
||||
row["tenantId"] = tenant.Id
|
||||
row["tenantName"] = tenant.Name
|
||||
}
|
||||
updated, _ := json.Marshal(rows)
|
||||
_ = json.Unmarshal(updated, target)
|
||||
return
|
||||
}
|
||||
var row map[string]any
|
||||
if err = json.Unmarshal(raw, &row); err == nil {
|
||||
row["tenantId"] = tenant.Id
|
||||
row["tenantName"] = tenant.Name
|
||||
updated, _ := json.Marshal(row)
|
||||
_ = json.Unmarshal(updated, target)
|
||||
}
|
||||
}
|
||||
|
||||
func addPageQuery(values url.Values, page int, perPage int) url.Values {
|
||||
if values == nil {
|
||||
values = url.Values{}
|
||||
}
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if perPage <= 0 {
|
||||
perPage = 20
|
||||
}
|
||||
values.Set("page", intToString(page))
|
||||
values.Set("per_page", intToString(perPage))
|
||||
return values
|
||||
}
|
||||
|
||||
func addTimeRangeQuery(values url.Values, prefix string, times []*gtime.Time) {
|
||||
if len(times) != 2 {
|
||||
return
|
||||
}
|
||||
if times[0] != nil {
|
||||
values.Set(prefix+"_start", times[0].Time.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
if times[1] != nil {
|
||||
values.Set(prefix+"_end", times[1].Time.UTC().Format(time.RFC3339Nano))
|
||||
}
|
||||
}
|
||||
|
||||
func intToString(v int) string {
|
||||
return strconvFormatInt(int64(v))
|
||||
}
|
||||
|
||||
func int64ToString(v int64) string {
|
||||
return strconvFormatInt(v)
|
||||
}
|
||||
|
||||
func strconvFormatInt(v int64) string {
|
||||
return strconv.FormatInt(v, 10)
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// Package sys
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2026 HotGo
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imadmin"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type sSysImDeadLetter struct{}
|
||||
|
||||
func NewSysImDeadLetter() *sSysImDeadLetter {
|
||||
return &sSysImDeadLetter{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImDeadLetter(NewSysImDeadLetter())
|
||||
}
|
||||
|
||||
// List 获取IM Kafka死信列表
|
||||
func (s *sSysImDeadLetter) List(ctx context.Context, in *sysin.ImDeadLetterListInp) (res *sysin.ImDeadLetterPageModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := s.getTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := url.Values{}
|
||||
values.Set("limit", strconv.Itoa(in.Limit))
|
||||
if in.Status != "" {
|
||||
values.Set("status", in.Status)
|
||||
}
|
||||
if in.BizType != "" {
|
||||
values.Set("biz_type", in.BizType)
|
||||
}
|
||||
if in.BizId != "" {
|
||||
values.Set("biz_id", in.BizId)
|
||||
}
|
||||
if in.EventType != "" {
|
||||
values.Set("event_type", in.EventType)
|
||||
}
|
||||
if in.Cursor != "" {
|
||||
values.Set("cursor", in.Cursor)
|
||||
}
|
||||
var raw sysin.ImDeadLetterPageRaw
|
||||
path := fmt.Sprintf("/internal/admin-query/tenants/%s/kafka/dead-letters?%s", url.PathEscape(tenant.Code), values.Encode())
|
||||
if err = imadmin.DefaultClient(ctx).InternalGet(ctx, path, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &sysin.ImDeadLetterPageModel{
|
||||
List: make([]*sysin.ImDeadLetterListModel, 0, len(raw.Items)),
|
||||
NextCursor: raw.NextCursor,
|
||||
HasMore: raw.HasMore,
|
||||
}
|
||||
for _, item := range raw.Items {
|
||||
res.List = append(res.List, convertDeadLetterModel(item))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// View 获取IM Kafka死信详情
|
||||
func (s *sSysImDeadLetter) View(ctx context.Context, in *sysin.ImDeadLetterViewInp) (res *sysin.ImDeadLetterViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := s.getTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var raw sysin.ImDeadLetterViewRaw
|
||||
path := fmt.Sprintf("/internal/admin-query/tenants/%s/kafka/dead-letters/%s", url.PathEscape(tenant.Code), url.PathEscape(in.DeadId))
|
||||
if err = imadmin.DefaultClient(ctx).InternalGet(ctx, path, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &sysin.ImDeadLetterViewModel{
|
||||
ImDeadLetterListModel: *convertDeadLetterModel(&raw.ImDeadLetterListModelRaw),
|
||||
EnvelopeRaw: raw.EnvelopeRaw,
|
||||
}
|
||||
if raw.Envelope != nil {
|
||||
b, _ := json.MarshalIndent(raw.Envelope, "", " ")
|
||||
res.Envelope = string(b)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Replay 重放IM Kafka死信
|
||||
func (s *sSysImDeadLetter) Replay(ctx context.Context, in *sysin.ImDeadLetterActionInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
return s.publishActionCommand(ctx, in, consts.ImAdminCommandKafkaDeadReplay)
|
||||
}
|
||||
|
||||
// Ignore 忽略IM Kafka死信
|
||||
func (s *sSysImDeadLetter) Ignore(ctx context.Context, in *sysin.ImDeadLetterActionInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
return s.publishActionCommand(ctx, in, consts.ImAdminCommandKafkaDeadIgnore)
|
||||
}
|
||||
|
||||
// Resolve 标记IM Kafka死信已处理
|
||||
func (s *sSysImDeadLetter) Resolve(ctx context.Context, in *sysin.ImDeadLetterActionInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
return s.publishActionCommand(ctx, in, consts.ImAdminCommandKafkaDeadResolve)
|
||||
}
|
||||
|
||||
func (s *sSysImDeadLetter) publishActionCommand(ctx context.Context, in *sysin.ImDeadLetterActionInp, eventType string) (*sysin.ImGroupCommandModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := s.getTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actorName := ""
|
||||
if user != nil {
|
||||
actorName = user.RealName
|
||||
}
|
||||
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
|
||||
TenantID: tenant.Id,
|
||||
TenantCode: tenant.Code,
|
||||
EventType: eventType,
|
||||
BizType: "kafka_dead_letter",
|
||||
BizID: in.DeadId,
|
||||
Actor: &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
DisplayName: actorName,
|
||||
},
|
||||
Payload: map[string]any{
|
||||
"command_id": "",
|
||||
"actor_user_id": strconv.FormatInt(in.ActorUserId, 10),
|
||||
"dead_id": in.DeadId,
|
||||
"note": in.Note,
|
||||
},
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImGroupCommandModel{
|
||||
CommandID: command.CommandID,
|
||||
EventID: command.EventID,
|
||||
Status: command.Status,
|
||||
TenantID: command.TenantID,
|
||||
TenantCode: command.TenantCode,
|
||||
EventType: command.EventType,
|
||||
IdempotencyKey: command.IdempotencyKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sSysImDeadLetter) getTenant(ctx context.Context, tenantId int64) (tenant *entity.ImTenant, err error) {
|
||||
if tenantId <= 0 {
|
||||
return nil, gerror.New("请选择IM租户")
|
||||
}
|
||||
if err = imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).WherePri(tenantId).Scan(&tenant); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM租户失败")
|
||||
}
|
||||
if tenant == nil || tenant.Id <= 0 {
|
||||
return nil, gerror.New("IM租户不存在")
|
||||
}
|
||||
if tenant.State != consts.ImTenantStateActive {
|
||||
return nil, gerror.New("IM租户未启用")
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
func convertDeadLetterModel(in *sysin.ImDeadLetterListModelRaw) *sysin.ImDeadLetterListModel {
|
||||
if in == nil {
|
||||
return &sysin.ImDeadLetterListModel{}
|
||||
}
|
||||
return &sysin.ImDeadLetterListModel{
|
||||
Id: in.Id,
|
||||
DeadId: in.DeadId,
|
||||
EventId: in.EventId,
|
||||
ParentEventId: in.ParentEventId,
|
||||
SourceTopic: in.SourceTopic,
|
||||
ConsumerGroup: in.ConsumerGroup,
|
||||
EventType: in.EventType,
|
||||
BizType: in.BizType,
|
||||
BizId: in.BizId,
|
||||
RetryCount: in.RetryCount,
|
||||
LastReason: in.LastReason,
|
||||
LastError: in.LastError,
|
||||
Status: in.Status,
|
||||
ReplayCount: in.ReplayCount,
|
||||
CreatedAt: in.CreatedAt,
|
||||
UpdatedAt: in.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"fmt"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"strconv"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
@@ -385,6 +387,22 @@ func (s *sSysImGroup) GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGov
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) ProfileUpdate(ctx context.Context, in *sysin.ImGroupProfileUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"profile": map[string]any{
|
||||
"name": in.Name,
|
||||
"avatar_file_id": in.AvatarFileId,
|
||||
"description": in.Description,
|
||||
},
|
||||
}
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupProfileUpdate, payload, in.Version)
|
||||
}
|
||||
|
||||
// Dissolve 解散IM群
|
||||
func (s *sSysImGroup) Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
@@ -483,6 +501,22 @@ func (s *sSysImGroup) Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (err e
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) MemberMute(ctx context.Context, in *sysin.ImGroupMuteInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"user_id": strconv.FormatInt(in.UserId, 10),
|
||||
"reason": in.Reason,
|
||||
}
|
||||
if in.ExpiresAt != nil {
|
||||
payload["expires_at"] = in.ExpiresAt.Time.Format("2006-01-02T15:04:05.000Z07:00")
|
||||
}
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupMemberMute, payload, 0)
|
||||
}
|
||||
|
||||
// Unmute 解除IM群成员禁言
|
||||
func (s *sSysImGroup) Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
@@ -529,6 +563,31 @@ func (s *sSysImGroup) Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (e
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) MemberUnmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"user_id": strconv.FormatInt(in.UserId, 10),
|
||||
}
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupMemberUnmute, payload, 0)
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) MemberRemove(ctx context.Context, in *sysin.ImGroupMemberRemoveInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"user_id": strconv.FormatInt(in.UserId, 10),
|
||||
"reason": in.Reason,
|
||||
}
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupMemberRemove, payload, 0)
|
||||
}
|
||||
|
||||
// JoinRequestApprove 通过IM入群申请
|
||||
func (s *sSysImGroup) JoinRequestApprove(ctx context.Context, in *sysin.ImGroupJoinRequestResolveInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
@@ -683,6 +742,30 @@ func (s *sSysImGroup) RoleUpdate(ctx context.Context, in *sysin.ImGroupRoleUpdat
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) AdminSet(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"user_ids": []string{strconv.FormatInt(in.UserId, 10)},
|
||||
}
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupAdminSet, payload, 0)
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) AdminRemove(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"user_id": strconv.FormatInt(in.UserId, 10),
|
||||
}
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupAdminRemove, payload, 0)
|
||||
}
|
||||
|
||||
// OwnerTransfer 转让IM群主
|
||||
func (s *sSysImGroup) OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerTransferInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
@@ -785,6 +868,45 @@ func (s *sSysImGroup) OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerT
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) publishGroupCommand(ctx context.Context, tenantId int64, topic string, eventType string, payload map[string]any, version int64) (*sysin.ImGroupCommandModel, error) {
|
||||
_, tenant, err := resolveImTenantDB(ctx, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actorName := ""
|
||||
if user != nil {
|
||||
actorName = user.RealName
|
||||
}
|
||||
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
|
||||
TenantID: tenant.Id,
|
||||
TenantCode: tenant.Code,
|
||||
EventType: eventType,
|
||||
BizType: "group",
|
||||
BizID: topic,
|
||||
Version: version,
|
||||
Actor: &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
DisplayName: actorName,
|
||||
},
|
||||
Payload: payload,
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImGroupCommandModel{
|
||||
CommandID: command.CommandID,
|
||||
EventID: command.EventID,
|
||||
Status: command.Status,
|
||||
TenantID: command.TenantID,
|
||||
TenantCode: command.TenantCode,
|
||||
EventType: command.EventType,
|
||||
IdempotencyKey: command.IdempotencyKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) groupGovernanceSnapshot(ctx context.Context, tx gdb.TX, topic string) (res gdb.Record, err error) {
|
||||
return tx.Model("im_group_settings").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type sSysImKafkaAudit struct{}
|
||||
|
||||
func NewSysImKafkaAudit() *sSysImKafkaAudit {
|
||||
return &sSysImKafkaAudit{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImKafkaAudit(NewSysImKafkaAudit())
|
||||
}
|
||||
|
||||
func (s *sSysImKafkaAudit) List(ctx context.Context, in *sysin.ImKafkaAuditListInp) (list []*sysin.ImKafkaAuditListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
mod := g.DB().Model("im_audit_events").Safe().Ctx(ctx).
|
||||
Fields("id", "event_id", "tenant_code", "event_type", "biz_type", "biz_id", "actor_type", "actor_id", "action", "resource", "resource_id", "result", "reason", "occurred_at", "received_at")
|
||||
if in.TenantCode != "" {
|
||||
mod = mod.Where("tenant_code", in.TenantCode)
|
||||
}
|
||||
if in.EventId != "" {
|
||||
mod = mod.WhereLike("event_id", "%"+in.EventId+"%")
|
||||
}
|
||||
if in.EventType != "" {
|
||||
mod = mod.WhereLike("event_type", "%"+in.EventType+"%")
|
||||
}
|
||||
if in.BizType != "" {
|
||||
mod = mod.Where("biz_type", in.BizType)
|
||||
}
|
||||
if in.BizId != "" {
|
||||
mod = mod.WhereLike("biz_id", "%"+in.BizId+"%")
|
||||
}
|
||||
if in.ActorType != "" {
|
||||
mod = mod.Where("actor_type", in.ActorType)
|
||||
}
|
||||
if in.ActorId != "" {
|
||||
mod = mod.WhereLike("actor_id", "%"+in.ActorId+"%")
|
||||
}
|
||||
if in.Action != "" {
|
||||
mod = mod.WhereLike("action", "%"+in.Action+"%")
|
||||
}
|
||||
if in.Resource != "" {
|
||||
mod = mod.Where("resource", in.Resource)
|
||||
}
|
||||
if in.ResourceId != "" {
|
||||
mod = mod.WhereLike("resource_id", "%"+in.ResourceId+"%")
|
||||
}
|
||||
if in.Result != "" {
|
||||
mod = mod.Where("result", in.Result)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween("received_at", in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM Kafka审计事件列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImKafkaAudit) View(ctx context.Context, in *sysin.ImKafkaAuditViewInp) (res *sysin.ImKafkaAuditViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mod := g.DB().Model("im_audit_events").Safe().Ctx(ctx).
|
||||
Fields("id", "event_id", "tenant_code", "event_type", "biz_type", "biz_id", "actor_type", "actor_id", "action", "resource", "resource_id", "result", "reason", "CAST(payload AS CHAR) payload", "CAST(envelope AS CHAR) envelope", "occurred_at", "received_at")
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where("id", in.Id)
|
||||
} else {
|
||||
mod = mod.Where("event_id", in.EventId)
|
||||
}
|
||||
if err = mod.Scan(&res); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM Kafka审计事件详情失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Package sys
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2026 HotGo
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysImKafkaCommand struct{}
|
||||
|
||||
func NewSysImKafkaCommand() *sSysImKafkaCommand {
|
||||
return &sSysImKafkaCommand{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImKafkaCommand(NewSysImKafkaCommand())
|
||||
}
|
||||
|
||||
func (s *sSysImKafkaCommand) List(ctx context.Context, in *sysin.ImKafkaCommandListInp) (list []*sysin.ImKafkaCommandListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
mod := g.DB().Model("im_admin_commands").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"id",
|
||||
"command_id",
|
||||
"tenant_id",
|
||||
"tenant_code",
|
||||
"event_id",
|
||||
"event_type",
|
||||
"biz_type",
|
||||
"biz_id",
|
||||
"idempotency_key",
|
||||
"status",
|
||||
"reason",
|
||||
"message",
|
||||
"sent_at",
|
||||
"completed_at",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
)
|
||||
if in.TenantId > 0 {
|
||||
mod = mod.Where("tenant_id", in.TenantId)
|
||||
}
|
||||
if in.CommandId != "" {
|
||||
mod = mod.WhereLike("command_id", "%"+in.CommandId+"%")
|
||||
}
|
||||
if in.EventId != "" {
|
||||
mod = mod.WhereLike("event_id", "%"+in.EventId+"%")
|
||||
}
|
||||
if in.EventType != "" {
|
||||
mod = mod.WhereLike("event_type", "%"+in.EventType+"%")
|
||||
}
|
||||
if in.BizType != "" {
|
||||
mod = mod.Where("biz_type", in.BizType)
|
||||
}
|
||||
if in.BizId != "" {
|
||||
mod = mod.WhereLike("biz_id", "%"+in.BizId+"%")
|
||||
}
|
||||
if in.Status != "" {
|
||||
mod = mod.Where("status", in.Status)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween("created_at", in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM后台命令列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImKafkaCommand) View(ctx context.Context, in *sysin.ImKafkaCommandViewInp) (res *sysin.ImKafkaCommandViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mod := g.DB().Model("im_admin_commands").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"id",
|
||||
"command_id",
|
||||
"tenant_id",
|
||||
"tenant_code",
|
||||
"event_id",
|
||||
"event_type",
|
||||
"biz_type",
|
||||
"biz_id",
|
||||
"idempotency_key",
|
||||
"status",
|
||||
"reason",
|
||||
"message",
|
||||
"CAST(payload AS CHAR) payload",
|
||||
"CAST(envelope AS CHAR) envelope",
|
||||
"CAST(result AS CHAR) result",
|
||||
"sent_at",
|
||||
"completed_at",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
)
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where("id", in.Id)
|
||||
} else {
|
||||
mod = mod.Where("command_id", in.CommandId)
|
||||
}
|
||||
if err = mod.Scan(&res); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM后台命令详情失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImKafkaCommand) CacheInvalidate(ctx context.Context, in *sysin.ImKafkaCacheInvalidateInp) (res *sysin.ImKafkaEventModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actor := &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: gconv.String(contexts.GetUserId(ctx)),
|
||||
}
|
||||
if user != nil {
|
||||
actor.DisplayName = user.RealName
|
||||
}
|
||||
payload := map[string]any{
|
||||
"tenant_code": in.TenantCode,
|
||||
"namespace": in.Namespace,
|
||||
"keys": in.Keys,
|
||||
"reason": in.Reason,
|
||||
}
|
||||
eventID, err := imkafka.PublishEvent(ctx, imkafka.EventInput{
|
||||
Topic: consts.QueueImCacheInvalidateTopic,
|
||||
TenantCode: in.TenantCode,
|
||||
EventType: "cache.invalidate",
|
||||
BizType: "cache",
|
||||
BizID: in.Namespace,
|
||||
Actor: actor,
|
||||
Payload: payload,
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImKafkaEventModel{EventId: eventID}, nil
|
||||
}
|
||||
|
||||
func (s *sSysImKafkaCommand) WorkbenchEvent(ctx context.Context, in *sysin.ImKafkaWorkbenchEventInp) (res *sysin.ImKafkaEventModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actor := &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: gconv.String(contexts.GetUserId(ctx)),
|
||||
}
|
||||
if user != nil {
|
||||
actor.DisplayName = user.RealName
|
||||
}
|
||||
payload := in.Payload
|
||||
payload["tenant_code"] = in.TenantCode
|
||||
payload["source"] = "admin"
|
||||
eventID, err := imkafka.PublishEvent(ctx, imkafka.EventInput{
|
||||
Topic: consts.QueueImWorkbenchEventTopic,
|
||||
TenantCode: in.TenantCode,
|
||||
EventType: in.EventType,
|
||||
BizType: "workbench",
|
||||
BizID: in.BizId,
|
||||
Actor: actor,
|
||||
Payload: payload,
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImKafkaEventModel{EventId: eventID}, nil
|
||||
}
|
||||
@@ -25,8 +25,6 @@ import (
|
||||
|
||||
type sSysImTenant struct{}
|
||||
|
||||
const imRegistryDbGroup = "imRegistry"
|
||||
|
||||
func NewSysImTenant() *sSysImTenant {
|
||||
return &sSysImTenant{}
|
||||
}
|
||||
@@ -37,11 +35,11 @@ func init() {
|
||||
|
||||
// Model IM租户ORM模型
|
||||
func (s *sSysImTenant) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
return handler.Model(g.DB(imRegistryDbGroup).Model(dao.ImTenant.Table()).Safe().Ctx(ctx), option...)
|
||||
return handler.Model(imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysImTenant) configModel(ctx context.Context) *gdb.Model {
|
||||
return g.DB(imRegistryDbGroup).Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx)
|
||||
return imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// List 获取IM租户列表
|
||||
@@ -146,7 +144,7 @@ func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err
|
||||
return
|
||||
}
|
||||
|
||||
return g.DB(imRegistryDbGroup).Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
return imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
if err = s.checkCodeUnique(ctx, in.Id, in.Code); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,9 +12,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -40,36 +44,34 @@ func (s *sSysImUser) List(ctx context.Context, in *sysin.ImUserListInp) (list []
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, scope.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
values := addPageQuery(url.Values{}, in.Page, in.PerPage)
|
||||
if in.UserId != "" {
|
||||
values.Set("user_id", in.UserId)
|
||||
}
|
||||
|
||||
mod := s.listModel(ctx, db)
|
||||
mod = s.applyListWhere(mod, in)
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("u.id")
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM用户列表失败,请稍后重试!")
|
||||
return
|
||||
if in.Nickname != "" {
|
||||
values.Set("nickname", in.Nickname)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return
|
||||
if in.AuthName != "" {
|
||||
values.Set("auth_name", in.AuthName)
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
if in.State != nil {
|
||||
values.Set("state", intToString(*in.State))
|
||||
}
|
||||
if err = s.fillAuthAndCredentials(ctx, db, list); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
addTimeRangeQuery(values, "created_at", in.CreatedAt)
|
||||
addTimeRangeQuery(values, "last_seen", in.LastSeen)
|
||||
return imAdminQueryList[sysin.ImUserListModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/users",
|
||||
values,
|
||||
map[string]string{
|
||||
"public": "profileJson",
|
||||
"trusted": "trustedJson",
|
||||
"access": "accessJson",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) listModel(ctx context.Context, db gdb.DB) *gdb.Model {
|
||||
@@ -120,13 +122,16 @@ func (s *sSysImUser) fillAuthAndCredentials(ctx context.Context, db gdb.DB, list
|
||||
ids := make([]int64, 0, len(list))
|
||||
userMap := make(map[int64]*sysin.ImUserListModel, len(list))
|
||||
for _, item := range list {
|
||||
userId, parseErr := sysin.ParseOptionalInt64(item.UserId)
|
||||
if parseErr != nil {
|
||||
return parseErr
|
||||
userId := item.UserId
|
||||
if userId <= 0 {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, userId)
|
||||
userMap[userId] = item
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
credentialRows, err := db.Model("im_credentials").Safe().Ctx(ctx).
|
||||
Fields("userid", "method", "value", "done").
|
||||
@@ -164,41 +169,18 @@ func (s *sSysImUser) View(ctx context.Context, in *sysin.ImUserViewInp) (res *sy
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, scope.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userId, err := sysin.ParseOptionalInt64(in.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields := append(s.viewFields(), "u.access access_json", "u.public profile_json", "u.trusted trusted_json")
|
||||
err = db.Model("im_users u").Safe().Ctx(ctx).
|
||||
LeftJoin("im_user_profiles up", "up.user_id = u.id").
|
||||
LeftJoin("im_user_security us", "us.user_id = u.id").
|
||||
Fields(fields...).
|
||||
Where("u.id", userId).
|
||||
Scan(&res)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM用户信息失败,请稍后重试!")
|
||||
}
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
res.TenantId = tenant.Id
|
||||
res.TenantName = tenant.Name
|
||||
list := []*sysin.ImUserListModel{&res.ImUserListModel}
|
||||
if err = s.fillAuthAndCredentials(ctx, db, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
return imAdminQueryOne[sysin.ImUserViewModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/users/"+url.PathEscape(in.UserId),
|
||||
map[string]string{
|
||||
"public": "profileJson",
|
||||
"trusted": "trustedJson",
|
||||
"access": "accessJson",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) viewFields() []interface{} {
|
||||
@@ -306,45 +288,104 @@ func (s *sSysImUser) Devices(ctx context.Context, in *sysin.ImUserDeviceListInp)
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userId, err := sysin.ParseOptionalInt64(in.UserId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, scope.TenantId)
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return imAdminQueryList[sysin.ImUserDeviceListModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/users/"+url.PathEscape(in.UserId)+"/devices",
|
||||
addPageQuery(url.Values{}, in.Page, in.PerPage),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
mod := db.Model("im_devices d").Safe().Ctx(ctx).
|
||||
LeftJoin("im_device_push_tokens p", "p.user_id=d.userid AND p.device_id=d.deviceid").
|
||||
Fields(
|
||||
"CAST(d.userid AS CHAR) user_id",
|
||||
"d.deviceid device_id",
|
||||
"COALESCE(d.platform, '') platform",
|
||||
"COALESCE(d.lang, '') lang",
|
||||
"d.lastseen last_seen",
|
||||
"COALESCE(p.provider, '') push_provider",
|
||||
"COALESCE(p.platform, '') push_platform",
|
||||
"COALESCE(p.app_version, '') push_app_version",
|
||||
"(p.id IS NOT NULL) push_token_configured",
|
||||
"p.updated_at push_updated_at",
|
||||
).
|
||||
Where("d.userid", userId).
|
||||
Page(in.Page, in.PerPage).
|
||||
OrderDesc("d.lastseen")
|
||||
func (s *sSysImUser) Ban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserBan)
|
||||
}
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "获取IM用户设备列表失败,请稍后重试!")
|
||||
func (s *sSysImUser) Unban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnban)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) Freeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserFreeze)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) Unfreeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnfreeze)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) DeviceKick(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
if err = in.FilterDevice(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserDeviceKick)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) LogoutAll(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserLogoutAll)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) AccountCancel(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserCancel)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) AccountDelete(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
in.Hard = true
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserDelete)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) publishGovernanceCommand(ctx context.Context, in *sysin.ImUserGovernanceInp, eventType string) (*sysin.ImUserCommandModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
_, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actorName := ""
|
||||
if user != nil {
|
||||
actorName = user.RealName
|
||||
}
|
||||
payload := map[string]any{
|
||||
"actor_user_id": strconv.FormatInt(in.ActorUserId, 10),
|
||||
"user_id": strconv.FormatInt(in.UserId, 10),
|
||||
"target_user_id": strconv.FormatInt(in.UserId, 10),
|
||||
"reason": in.Reason,
|
||||
}
|
||||
if in.DeviceId != "" {
|
||||
payload["device_id"] = in.DeviceId
|
||||
}
|
||||
if eventType == consts.ImAdminCommandUserCancel || eventType == consts.ImAdminCommandUserDelete {
|
||||
payload["hard"] = in.Hard
|
||||
}
|
||||
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
|
||||
TenantID: tenant.Id,
|
||||
TenantCode: tenant.Code,
|
||||
EventType: eventType,
|
||||
BizType: "user",
|
||||
BizID: strconv.FormatInt(in.UserId, 10),
|
||||
Actor: &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
DisplayName: actorName,
|
||||
},
|
||||
Payload: payload,
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImUserCommandModel{
|
||||
CommandID: command.CommandID,
|
||||
EventID: command.EventID,
|
||||
Status: command.Status,
|
||||
TenantID: command.TenantID,
|
||||
TenantCode: command.TenantCode,
|
||||
EventType: command.EventType,
|
||||
IdempotencyKey: command.IdempotencyKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func updateIMUserStatus(ctx context.Context, db gdb.DB, scope imAdminScope, userId int64, state int) error {
|
||||
|
||||
Reference in New Issue
Block a user