feat: add IM admin management
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imadmin"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/simple"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
const (
|
||||
imAdminOperationAuditTable = "im_admin_operation_audit"
|
||||
)
|
||||
|
||||
func resolveImAdminScope(ctx context.Context, selectedTenantId int64) (scope imadmin.AdminScope, err error) {
|
||||
operatorId := contexts.GetUserId(ctx)
|
||||
scope.OperatorId = operatorId
|
||||
scope.RoleKey = contexts.GetRoleKey(ctx)
|
||||
scope.RequestId = gctx.CtxId(ctx)
|
||||
if service.AdminMember().VerifySuperId(ctx, operatorId) {
|
||||
if selectedTenantId <= 0 {
|
||||
return scope, gerror.New("请选择IM租户")
|
||||
}
|
||||
if err = ensureImTenantActive(ctx, selectedTenantId); err != nil {
|
||||
return scope, err
|
||||
}
|
||||
scope.TenantId = selectedTenantId
|
||||
return scope, nil
|
||||
}
|
||||
|
||||
if contexts.GetDeptType(ctx) != consts.DeptTypeTenant {
|
||||
return scope, gerror.New("当前账号不是租户管理员,不能管理邀请码")
|
||||
}
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
if tenantId <= 0 {
|
||||
return scope, gerror.New("当前管理员未关联租户")
|
||||
}
|
||||
if err = ensureImTenantActive(ctx, tenantId); err != nil {
|
||||
return scope, err
|
||||
}
|
||||
scope.TenantId = tenantId
|
||||
return scope, nil
|
||||
}
|
||||
|
||||
func ensureImTenantActive(ctx context.Context, tenantId int64) (err error) {
|
||||
var tenant *entity.ImTenant
|
||||
if err = imtenant.RegistryDB(ctx).Model(dao.ImTenant.Table()).Safe().Ctx(ctx).WherePri(tenantId).Scan(&tenant); err != nil {
|
||||
return gerror.Wrap(err, "获取IM租户失败")
|
||||
}
|
||||
if tenant == nil || tenant.Id <= 0 {
|
||||
return gerror.New("IM租户不存在")
|
||||
}
|
||||
if tenant.State != consts.ImTenantStateActive {
|
||||
return gerror.New("IM租户未启用")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeImAdminAudit(ctx context.Context, tenantId int64, action string, targetType string, targetId int64, result string, detail g.Map) error {
|
||||
operatorId := contexts.GetUserId(ctx)
|
||||
request := ghttp.RequestFromCtx(ctx)
|
||||
ip := ""
|
||||
if request != nil {
|
||||
ip = request.GetClientIp()
|
||||
}
|
||||
detailJson := gjson.New(detail)
|
||||
_, err := g.DB().Exec(ctx,
|
||||
"INSERT INTO "+imAdminOperationAuditTable+" (request_id, tenant_id, operator_id, action, target_type, target_id, result, ip, detail_json, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, CAST(? AS JSON), ?)",
|
||||
gctx.CtxId(ctx),
|
||||
tenantId,
|
||||
operatorId,
|
||||
action,
|
||||
targetType,
|
||||
targetId,
|
||||
result,
|
||||
ip,
|
||||
detailJson.String(),
|
||||
gtime.Now(),
|
||||
)
|
||||
if err != nil && !simple.IsDemo(ctx) {
|
||||
g.Log().Warningf(ctx, "写入IM后台审计失败 action:%s err:%+v", action, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type sSysImAuditLog struct{}
|
||||
|
||||
func NewSysImAuditLog() *sSysImAuditLog {
|
||||
return &sSysImAuditLog{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImAuditLog(NewSysImAuditLog())
|
||||
}
|
||||
|
||||
// List 获取IM审计日志列表
|
||||
func (s *sSysImAuditLog) List(ctx context.Context, in *sysin.ImAuditLogListInp) (list []*sysin.ImAuditLogListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mod := s.model(ctx, db)
|
||||
mod = s.applyWhere(mod, in)
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("id")
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM审计日志列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImAuditLog) model(ctx context.Context, db gdb.DB) *gdb.Model {
|
||||
return db.Model("im_audit_logs").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"id",
|
||||
"actor_uid",
|
||||
"actor_role",
|
||||
"action",
|
||||
"target_type",
|
||||
"target_id",
|
||||
"reason",
|
||||
"request_id",
|
||||
"ip",
|
||||
"user_agent",
|
||||
"CAST(before_snapshot AS CHAR) before_snapshot",
|
||||
"CAST(after_snapshot AS CHAR) after_snapshot",
|
||||
"CAST(metadata AS CHAR) metadata",
|
||||
"created_at",
|
||||
)
|
||||
}
|
||||
|
||||
func (s *sSysImAuditLog) applyWhere(mod *gdb.Model, in *sysin.ImAuditLogListInp) *gdb.Model {
|
||||
if in.ActorUid > 0 {
|
||||
mod = mod.Where("actor_uid", in.ActorUid)
|
||||
}
|
||||
if in.ActorRole != "" {
|
||||
mod = mod.WhereLike("actor_role", "%"+in.ActorRole+"%")
|
||||
}
|
||||
if in.Action != "" {
|
||||
mod = mod.WhereLike("action", "%"+in.Action+"%")
|
||||
}
|
||||
if in.TargetType != "" {
|
||||
mod = mod.Where("target_type", in.TargetType)
|
||||
}
|
||||
if in.TargetId != "" {
|
||||
mod = mod.WhereLike("target_id", "%"+in.TargetId+"%")
|
||||
}
|
||||
if in.RequestId != "" {
|
||||
mod = mod.WhereLike("request_id", "%"+in.RequestId+"%")
|
||||
}
|
||||
if in.Ip != "" {
|
||||
mod = mod.WhereLike("ip", "%"+in.Ip+"%")
|
||||
}
|
||||
if in.Keyword != "" {
|
||||
like := "%" + in.Keyword + "%"
|
||||
mod = mod.Where("(action LIKE ? OR target_type LIKE ? OR target_id LIKE ? OR reason LIKE ? OR request_id LIKE ?)", like, like, like, like, like)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween("created_at", in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
// View 获取IM审计日志指定信息
|
||||
func (s *sSysImAuditLog) View(ctx context.Context, in *sysin.ImAuditLogViewInp) (res *sysin.ImAuditLogViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.model(ctx, db).Where("id", in.Id).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
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
// 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/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type sSysImGroup struct{}
|
||||
|
||||
func NewSysImGroup() *sSysImGroup {
|
||||
return &sSysImGroup{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImGroup(NewSysImGroup())
|
||||
}
|
||||
|
||||
// List 获取IM群组列表
|
||||
func (s *sSysImGroup) List(ctx context.Context, in *sysin.ImGroupListInp) (list []*sysin.ImGroupListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mod := s.groupModel(ctx, db)
|
||||
mod = s.applyGroupWhere(mod, in)
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("t.touchedat").OrderDesc("t.id")
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM群组列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) groupModel(ctx context.Context, db gdb.DB) *gdb.Model {
|
||||
return db.Model("im_topics t").Safe().Ctx(ctx).
|
||||
LeftJoin("im_group_settings gs", "gs.topic = t.name").
|
||||
Fields(
|
||||
"t.name topic",
|
||||
"t.state",
|
||||
"COALESCE(gs.owner_user_id, t.owner) owner_id",
|
||||
"t.seqid seq_id",
|
||||
"t.delid del_id",
|
||||
"t.subcnt sub_cnt",
|
||||
"t.touchedat touched_at",
|
||||
"t.createdat created_at",
|
||||
"t.updatedat updated_at",
|
||||
"t.public public_json",
|
||||
"t.trusted trusted_json",
|
||||
"t.tags tags_json",
|
||||
"t.aux aux_json",
|
||||
"COALESCE(gs.name, '') group_name",
|
||||
"COALESCE(gs.avatar_file_id, '') avatar_file_id",
|
||||
"COALESCE(gs.description, '') description",
|
||||
"COALESCE(gs.group_status, '') group_status",
|
||||
"COALESCE(gs.join_approval, 0) join_approval",
|
||||
"COALESCE(gs.mute_all, 0) mute_all",
|
||||
"COALESCE(gs.member_upload_files, 0) member_upload_files",
|
||||
"COALESCE(gs.member_private_chat, 0) member_private_chat",
|
||||
"COALESCE(gs.member_invite, 0) member_invite",
|
||||
"COALESCE(gs.max_member_count, 0) max_member_count",
|
||||
"COALESCE(gs.max_admin_count, 0) max_admin_count",
|
||||
"COALESCE(gs.member_count, 0) business_member_count",
|
||||
"COALESCE(gs.version, 0) settings_version",
|
||||
"gs.updated_at settings_updated_at",
|
||||
).
|
||||
WhereLike("t.name", "grp%")
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) applyGroupWhere(mod *gdb.Model, in *sysin.ImGroupListInp) *gdb.Model {
|
||||
if in.Topic != "" {
|
||||
mod = mod.WhereLike("t.name", "%"+in.Topic+"%")
|
||||
}
|
||||
if in.OwnerId > 0 {
|
||||
mod = mod.Where("COALESCE(gs.owner_user_id, t.owner)", in.OwnerId)
|
||||
}
|
||||
if in.State != nil {
|
||||
mod = mod.Where("t.state", *in.State)
|
||||
}
|
||||
if in.GroupStatus != "" {
|
||||
mod = mod.Where("gs.group_status", in.GroupStatus)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween("t.createdat", in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
if len(in.TouchedAt) == 2 {
|
||||
mod = mod.WhereBetween("t.touchedat", in.TouchedAt[0], in.TouchedAt[1])
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
// View 获取IM群组指定信息
|
||||
func (s *sSysImGroup) View(ctx context.Context, in *sysin.ImGroupViewInp) (res *sysin.ImGroupViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Model("im_topics t").Safe().Ctx(ctx).
|
||||
LeftJoin("im_group_settings gs", "gs.topic = t.name").
|
||||
Fields(
|
||||
"t.name topic",
|
||||
"t.state",
|
||||
"COALESCE(gs.owner_user_id, t.owner) owner_id",
|
||||
"t.seqid seq_id",
|
||||
"t.delid del_id",
|
||||
"t.subcnt sub_cnt",
|
||||
"t.touchedat touched_at",
|
||||
"t.createdat created_at",
|
||||
"t.updatedat updated_at",
|
||||
"t.public public_json",
|
||||
"t.trusted trusted_json",
|
||||
"t.tags tags_json",
|
||||
"t.aux aux_json",
|
||||
"t.access access_json",
|
||||
"t.usebt use_bt",
|
||||
"COALESCE(gs.name, '') group_name",
|
||||
"COALESCE(gs.avatar_file_id, '') avatar_file_id",
|
||||
"COALESCE(gs.description, '') description",
|
||||
"COALESCE(gs.group_status, '') group_status",
|
||||
"COALESCE(gs.join_approval, 0) join_approval",
|
||||
"COALESCE(gs.mute_all, 0) mute_all",
|
||||
"COALESCE(gs.member_upload_files, 0) member_upload_files",
|
||||
"COALESCE(gs.member_private_chat, 0) member_private_chat",
|
||||
"COALESCE(gs.member_invite, 0) member_invite",
|
||||
"COALESCE(gs.max_member_count, 0) max_member_count",
|
||||
"COALESCE(gs.max_admin_count, 0) max_admin_count",
|
||||
"COALESCE(gs.member_count, 0) business_member_count",
|
||||
"COALESCE(gs.version, 0) settings_version",
|
||||
"gs.updated_at settings_updated_at",
|
||||
).
|
||||
Where("t.name", in.Topic).
|
||||
WhereLike("t.name", "grp%").
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
// MemberList 获取IM群成员列表
|
||||
func (s *sSysImGroup) MemberList(ctx context.Context, in *sysin.ImGroupMemberListInp) (list []*sysin.ImGroupMemberListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.hasGroupMemberTruth(ctx, db, in.Topic) {
|
||||
return s.memberListFromBusiness(ctx, db, in)
|
||||
}
|
||||
return s.memberListFromProjection(ctx, db, in)
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) hasGroupMemberTruth(ctx context.Context, db gdb.DB, topic string) bool {
|
||||
count, err := db.Model("im_group_members").Safe().Ctx(ctx).Where("topic", topic).Count()
|
||||
return err == nil && count > 0
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) memberListFromBusiness(ctx context.Context, db gdb.DB, in *sysin.ImGroupMemberListInp) (list []*sysin.ImGroupMemberListModel, totalCount int, err error) {
|
||||
mod := db.Model("im_group_members gm").Safe().Ctx(ctx).
|
||||
LeftJoin("im_users u", "u.id = gm.user_id").
|
||||
LeftJoin("im_user_profiles up", "up.user_id = gm.user_id").
|
||||
LeftJoin("im_subscriptions s", "s.topic = gm.topic AND s.userid = gm.user_id").
|
||||
Fields(
|
||||
"gm.user_id",
|
||||
"up.nickname",
|
||||
"u.state",
|
||||
"s.modewant mode_want",
|
||||
"s.modegiven mode_given",
|
||||
"s.recvseqid recv_seq_id",
|
||||
"s.readseqid read_seq_id",
|
||||
"s.delid del_id",
|
||||
"s.deletedat deleted_at",
|
||||
"s.createdat created_at",
|
||||
"s.updatedat updated_at",
|
||||
"s.private private_json",
|
||||
"gm.role",
|
||||
"gm.member_status",
|
||||
"gm.nickname group_nickname",
|
||||
"gm.pinned",
|
||||
"gm.mute_notifications",
|
||||
"gm.join_source",
|
||||
"gm.joined_by",
|
||||
"gm.joined_at",
|
||||
"gm.left_at",
|
||||
"gm.removed_by",
|
||||
"gm.version",
|
||||
).
|
||||
Where("gm.topic", in.Topic)
|
||||
if in.UserId > 0 {
|
||||
mod = mod.Where("gm.user_id", in.UserId)
|
||||
}
|
||||
if in.Role != "" {
|
||||
mod = mod.Where("gm.role", in.Role)
|
||||
}
|
||||
if in.MemberStatus != "" {
|
||||
mod = mod.Where("gm.member_status", in.MemberStatus)
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderAsc("gm.member_status").OrderAsc("gm.role").OrderAsc("gm.id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM群成员列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) memberListFromProjection(ctx context.Context, db gdb.DB, in *sysin.ImGroupMemberListInp) (list []*sysin.ImGroupMemberListModel, totalCount int, err error) {
|
||||
mod := db.Model("im_subscriptions s").Safe().Ctx(ctx).
|
||||
LeftJoin("im_users u", "u.id = s.userid").
|
||||
LeftJoin("im_user_profiles up", "up.user_id = s.userid").
|
||||
Fields(
|
||||
"s.userid user_id",
|
||||
"up.nickname",
|
||||
"u.state",
|
||||
"s.modewant mode_want",
|
||||
"s.modegiven mode_given",
|
||||
"s.recvseqid recv_seq_id",
|
||||
"s.readseqid read_seq_id",
|
||||
"s.delid del_id",
|
||||
"s.deletedat deleted_at",
|
||||
"s.createdat created_at",
|
||||
"s.updatedat updated_at",
|
||||
"s.private private_json",
|
||||
"'member' role",
|
||||
"IF(s.deletedat IS NULL, 'active', 'left') member_status",
|
||||
"'' group_nickname",
|
||||
"0 pinned",
|
||||
"0 mute_notifications",
|
||||
"'projection' join_source",
|
||||
"0 joined_by",
|
||||
"s.createdat joined_at",
|
||||
"s.deletedat left_at",
|
||||
"0 removed_by",
|
||||
"0 version",
|
||||
).
|
||||
Where("s.topic", in.Topic)
|
||||
|
||||
if in.UserId > 0 {
|
||||
mod = mod.Where("s.userid", in.UserId)
|
||||
}
|
||||
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderAsc("s.deletedat").OrderAsc("s.userid")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM群成员列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Governance 获取IM群治理配置
|
||||
func (s *sSysImGroup) Governance(ctx context.Context, in *sysin.ImGroupTopicInp) (res *sysin.ImGroupGovernanceModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = db.Model("im_group_settings").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"topic",
|
||||
"owner_user_id",
|
||||
"name",
|
||||
"avatar_file_id",
|
||||
"description",
|
||||
"group_status",
|
||||
"join_approval",
|
||||
"mute_all",
|
||||
"member_upload_files",
|
||||
"member_private_chat",
|
||||
"member_invite",
|
||||
"max_member_count",
|
||||
"max_admin_count",
|
||||
"member_count",
|
||||
"version",
|
||||
"created_by",
|
||||
"dissolved_by",
|
||||
"dissolved_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
).
|
||||
Where("topic", in.Topic).
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
// JoinRequestList 获取IM入群申请列表
|
||||
func (s *sSysImGroup) JoinRequestList(ctx context.Context, in *sysin.ImGroupJoinRequestListInp) (list []*sysin.ImGroupJoinRequestListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mod := db.Model("im_group_join_requests").Safe().Ctx(ctx).Where("topic", in.Topic)
|
||||
if in.Status != "" {
|
||||
mod = mod.Where("status", in.Status)
|
||||
}
|
||||
if in.ApplicantUserId > 0 {
|
||||
mod = mod.Where("applicant_user_id", in.ApplicantUserId)
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM入群申请列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// InvitationList 获取IM群邀请列表
|
||||
func (s *sSysImGroup) InvitationList(ctx context.Context, in *sysin.ImGroupInvitationListInp) (list []*sysin.ImGroupInvitationListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mod := db.Model("im_group_invitations").Safe().Ctx(ctx).Where("topic", in.Topic)
|
||||
if in.Status != "" {
|
||||
mod = mod.Where("status", in.Status)
|
||||
}
|
||||
if in.TargetUserId > 0 {
|
||||
mod = mod.Where("target_user_id", in.TargetUserId)
|
||||
}
|
||||
if in.InviterUserId > 0 {
|
||||
mod = mod.Where("inviter_user_id", in.InviterUserId)
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM群邀请列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AnnouncementList 获取IM群公告列表
|
||||
func (s *sSysImGroup) AnnouncementList(ctx context.Context, in *sysin.ImGroupAnnouncementListInp) (list []*sysin.ImGroupAnnouncementListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mod := db.Model("im_group_announcements").Safe().Ctx(ctx).Where("topic", in.Topic)
|
||||
if in.Status != "" {
|
||||
mod = mod.Where("status", in.Status)
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("published_at").OrderDesc("id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM群公告列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MuteList 获取IM群禁言列表
|
||||
func (s *sSysImGroup) MuteList(ctx context.Context, in *sysin.ImGroupMuteListInp) (list []*sysin.ImGroupMuteListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mod := db.Model("im_group_mutes").Safe().Ctx(ctx).Where("topic", in.Topic)
|
||||
if in.Status != "" {
|
||||
mod = mod.Where("status", in.Status)
|
||||
}
|
||||
if in.UserId > 0 {
|
||||
mod = mod.Where("user_id", in.UserId)
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("muted_at").OrderDesc("user_id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM群禁言列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AdminLogList 获取IM群管理日志列表
|
||||
func (s *sSysImGroup) AdminLogList(ctx context.Context, in *sysin.ImGroupAdminLogListInp) (list []*sysin.ImGroupAdminLogListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mod := db.Model("im_group_admin_logs").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"id",
|
||||
"log_id",
|
||||
"topic",
|
||||
"operator_id",
|
||||
"target_user_id",
|
||||
"action",
|
||||
"object_type",
|
||||
"object_id",
|
||||
"CAST(before_data AS CHAR) before_data",
|
||||
"CAST(after_data AS CHAR) after_data",
|
||||
"request_id",
|
||||
"idempotency_key",
|
||||
"created_at",
|
||||
).
|
||||
Where("topic", in.Topic)
|
||||
if in.Action != "" {
|
||||
mod = mod.WhereLike("action", "%"+in.Action+"%")
|
||||
}
|
||||
if in.OperatorId > 0 {
|
||||
mod = mod.Where("operator_id", in.OperatorId)
|
||||
}
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("id")
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM群管理日志列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hotgo/internal/library/imadmin"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysImInvite struct{}
|
||||
|
||||
func NewSysImInvite() *sSysImInvite {
|
||||
return &sSysImInvite{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImInvite(NewSysImInvite())
|
||||
}
|
||||
|
||||
type imInviteListRequest struct {
|
||||
Page int `json:"page,omitempty"`
|
||||
PerPage int `json:"per_page,omitempty"`
|
||||
InviteCode string `json:"invite_code,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
BatchID int64 `json:"batch_id,omitempty"`
|
||||
CreatedBy int64 `json:"created_by,omitempty"`
|
||||
CreatedAtStart string `json:"created_at_start,omitempty"`
|
||||
CreatedAtEnd string `json:"created_at_end,omitempty"`
|
||||
ExpiresAtStart string `json:"expires_at_start,omitempty"`
|
||||
ExpiresAtEnd string `json:"expires_at_end,omitempty"`
|
||||
}
|
||||
|
||||
type imInviteCreateRequest struct {
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
Remark string `json:"remark,omitempty"`
|
||||
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
||||
}
|
||||
|
||||
type imInviteBatchCreateRequest struct {
|
||||
Quantity int `json:"quantity"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
Remark string `json:"remark,omitempty"`
|
||||
IdempotencyKey string `json:"idempotency_key,omitempty"`
|
||||
}
|
||||
|
||||
type imInviteCodeResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
BatchID *int64 `json:"batch_id"`
|
||||
Code string `json:"code"`
|
||||
Status string `json:"status"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
UsedBy *int64 `json:"used_by"`
|
||||
UsedAt *time.Time `json:"used_at"`
|
||||
RequestID string `json:"request_id"`
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type imInviteBatchResponse struct {
|
||||
BatchID int64 `json:"batch_id"`
|
||||
Quantity int `json:"quantity"`
|
||||
ActiveCount int `json:"active_count"`
|
||||
UsedCount int `json:"used_count"`
|
||||
RevokedCount int `json:"revoked_count"`
|
||||
ExpiredCount int `json:"expired_count"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (s *sSysImInvite) List(ctx context.Context, in *sysin.ImInviteListInp) (list []*sysin.ImInviteCodeModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req, err := buildInviteListRequest(in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var page imadmin.Page[imInviteCodeResponse]
|
||||
if err = imadmin.DefaultClient(ctx).ListCodes(ctx, scope, req, &page); err != nil {
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_list", "invite_code", 0, "failed", g.Map{"hasInviteCode": in.InviteCode != ""})
|
||||
return
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_list", "invite_code", 0, "success", g.Map{"hasInviteCode": in.InviteCode != ""})
|
||||
list = make([]*sysin.ImInviteCodeModel, 0, len(page.Items))
|
||||
for i := range page.Items {
|
||||
list = append(list, convertInviteCode(page.Items[i]))
|
||||
}
|
||||
totalCount = page.Total
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImInvite) Create(ctx context.Context, in *sysin.ImInviteCreateInp) (res *sysin.ImInviteCodeModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req := imInviteCreateRequest{ExpiresAt: formatRFC3339(in.ExpiresAt), Remark: in.Remark, IdempotencyKey: in.IdempotencyKey}
|
||||
var upstream imInviteCodeResponse
|
||||
err = imadmin.DefaultClient(ctx).CreateCode(ctx, scope, req, &upstream)
|
||||
res = convertInviteCode(upstream)
|
||||
result := "success"
|
||||
if err != nil {
|
||||
result = "failed"
|
||||
}
|
||||
targetId, _ := strconv.ParseInt(res.Id, 10, 64)
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_create", "invite_code", targetId, result, g.Map{"expiresAt": in.ExpiresAt, "remark": in.Remark})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImInvite) BatchCreate(ctx context.Context, in *sysin.ImInviteBatchCreateInp) (res *sysin.ImInviteBatchModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req := imInviteBatchCreateRequest{Quantity: in.Quantity, ExpiresAt: formatRFC3339(in.ExpiresAt), Remark: in.Remark, IdempotencyKey: in.IdempotencyKey}
|
||||
var upstream imInviteBatchResponse
|
||||
err = imadmin.DefaultClient(ctx).CreateBatch(ctx, scope, req, &upstream)
|
||||
res = convertInviteBatch(upstream)
|
||||
result := "success"
|
||||
if err != nil {
|
||||
result = "failed"
|
||||
}
|
||||
targetId, _ := strconv.ParseInt(res.BatchId, 10, 64)
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_batch_create", "invite_batch", targetId, result, g.Map{"quantity": in.Quantity, "expiresAt": in.ExpiresAt, "remark": in.Remark})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImInvite) BatchView(ctx context.Context, in *sysin.ImInviteBatchViewInp) (res *sysin.ImInviteBatchDetailModel, err error) {
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
batchId, err := sysin.ParseOptionalInt64(in.BatchId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var upstream imInviteBatchResponse
|
||||
err = imadmin.DefaultClient(ctx).GetBatch(ctx, scope, batchId, &upstream)
|
||||
res = &sysin.ImInviteBatchDetailModel{ImInviteBatchModel: *convertInviteBatch(upstream)}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImInvite) Revoke(ctx context.Context, in *sysin.ImInviteRevokeInp) (err error) {
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
id, err := sysin.ParseOptionalInt64(in.Id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = imadmin.DefaultClient(ctx).RevokeCode(ctx, scope, id)
|
||||
result := "success"
|
||||
if err != nil {
|
||||
result = "failed"
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_revoke", "invite_code", id, result, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImInvite) BatchRevoke(ctx context.Context, in *sysin.ImInviteBatchRevokeInp) (res *sysin.ImInviteBatchModel, err error) {
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
batchId, err := sysin.ParseOptionalInt64(in.BatchId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var upstream struct {
|
||||
Batch *imInviteBatchResponse `json:"batch"`
|
||||
RevokedCount int `json:"revoked_count"`
|
||||
}
|
||||
err = imadmin.DefaultClient(ctx).RevokeBatch(ctx, scope, batchId, &upstream)
|
||||
if upstream.Batch != nil {
|
||||
res = convertInviteBatch(*upstream.Batch)
|
||||
} else {
|
||||
res = &sysin.ImInviteBatchModel{BatchId: strconv.FormatInt(batchId, 10), RevokedCount: upstream.RevokedCount}
|
||||
}
|
||||
result := "success"
|
||||
if err != nil {
|
||||
result = "failed"
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_batch_revoke", "invite_batch", batchId, result, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImInvite) Export(ctx context.Context, in *sysin.ImInviteListInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
scope, err := resolveImAdminScope(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req, err := buildInviteListRequest(in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := imadmin.DefaultClient(ctx).ExportCodes(ctx, scope, req)
|
||||
if err != nil {
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_export", "invite_code", 0, "failed", g.Map{"hasInviteCode": in.InviteCode != ""})
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
request := ghttp.RequestFromCtx(ctx)
|
||||
if request == nil {
|
||||
return gerror.New("请求上下文不存在")
|
||||
}
|
||||
filename := resp.Header.Get("Content-Disposition")
|
||||
if filename == "" {
|
||||
filename = `attachment; filename="registration-invite-codes.csv"`
|
||||
}
|
||||
request.Response.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
|
||||
request.Response.Header().Set("Content-Disposition", filename)
|
||||
if _, err = io.Copy(request.Response.Writer, resp.Body); err != nil {
|
||||
return gerror.Wrap(err, "转发导出文件失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, scope.TenantId, "invite_export", "invite_code", 0, "success", g.Map{"hasInviteCode": in.InviteCode != ""})
|
||||
fmt.Fprint(request.Response.Writer, "")
|
||||
return
|
||||
}
|
||||
|
||||
func buildInviteListRequest(in *sysin.ImInviteListInp) (imInviteListRequest, error) {
|
||||
batchId, err := sysin.ParseOptionalInt64(in.BatchId)
|
||||
if err != nil {
|
||||
return imInviteListRequest{}, err
|
||||
}
|
||||
createdBy, err := sysin.ParseOptionalInt64(in.CreatedBy)
|
||||
if err != nil {
|
||||
return imInviteListRequest{}, err
|
||||
}
|
||||
return imInviteListRequest{
|
||||
Page: in.Page,
|
||||
PerPage: in.PerPage,
|
||||
InviteCode: in.InviteCode,
|
||||
Status: in.Status,
|
||||
BatchID: batchId,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAtStart: formatRFC3339(in.CreatedAtStart),
|
||||
CreatedAtEnd: formatRFC3339(in.CreatedAtEnd),
|
||||
ExpiresAtStart: formatRFC3339(in.ExpiresAtStart),
|
||||
ExpiresAtEnd: formatRFC3339(in.ExpiresAtEnd),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func formatRFC3339(t *gtime.Time) string {
|
||||
if t == nil {
|
||||
return ""
|
||||
}
|
||||
return t.Time.UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func convertTime(t *time.Time) *gtime.Time {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return gtime.New(t)
|
||||
}
|
||||
|
||||
func formatOptionalInt64(value *int64) string {
|
||||
if value == nil || *value == 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.FormatInt(*value, 10)
|
||||
}
|
||||
|
||||
func convertInviteCode(item imInviteCodeResponse) *sysin.ImInviteCodeModel {
|
||||
return &sysin.ImInviteCodeModel{
|
||||
Id: strconv.FormatInt(item.ID, 10),
|
||||
BatchId: formatOptionalInt64(item.BatchID),
|
||||
Code: item.Code,
|
||||
Status: item.Status,
|
||||
ExpiresAt: convertTime(item.ExpiresAt),
|
||||
UsedBy: formatOptionalInt64(item.UsedBy),
|
||||
UsedAt: convertTime(item.UsedAt),
|
||||
RequestId: item.RequestID,
|
||||
CreatedBy: strconv.FormatInt(item.CreatedBy, 10),
|
||||
Remark: item.Remark,
|
||||
CreatedAt: convertTime(item.CreatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func convertInviteBatch(item imInviteBatchResponse) *sysin.ImInviteBatchModel {
|
||||
return &sysin.ImInviteBatchModel{
|
||||
BatchId: strconv.FormatInt(item.BatchID, 10),
|
||||
Quantity: item.Quantity,
|
||||
ActiveCount: item.ActiveCount,
|
||||
UsedCount: item.UsedCount,
|
||||
RevokedCount: item.RevokedCount,
|
||||
ExpiredCount: item.ExpiredCount,
|
||||
ExpiresAt: convertTime(item.ExpiresAt),
|
||||
CreatedAt: convertTime(item.CreatedAt),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysImRealname struct{}
|
||||
|
||||
func NewSysImRealname() *sSysImRealname {
|
||||
return &sSysImRealname{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImRealname(NewSysImRealname())
|
||||
}
|
||||
|
||||
// List 获取IM实名审核列表
|
||||
func (s *sSysImRealname) List(ctx context.Context, in *sysin.ImRealnameListInp) (list []*sysin.ImRealnameListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mod := s.model(ctx, db)
|
||||
mod = s.applyWhere(mod, in)
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("id")
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM实名审核列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImRealname) model(ctx context.Context, db gdb.DB) *gdb.Model {
|
||||
return db.Model("im_realname_verifications").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"id",
|
||||
"user_id",
|
||||
"submission_id",
|
||||
"status",
|
||||
"id_type",
|
||||
"masked_name",
|
||||
"masked_id_number",
|
||||
"review_reason",
|
||||
"verified_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
)
|
||||
}
|
||||
|
||||
func (s *sSysImRealname) applyWhere(mod *gdb.Model, in *sysin.ImRealnameListInp) *gdb.Model {
|
||||
if in.UserId > 0 {
|
||||
mod = mod.Where("user_id", in.UserId)
|
||||
}
|
||||
if in.SubmissionId != "" {
|
||||
mod = mod.WhereLike("submission_id", "%"+in.SubmissionId+"%")
|
||||
}
|
||||
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])
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
// View 获取IM实名审核指定信息
|
||||
func (s *sSysImRealname) View(ctx context.Context, in *sysin.ImRealnameViewInp) (res *sysin.ImRealnameViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Model("im_realname_verifications").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"id",
|
||||
"user_id",
|
||||
"submission_id",
|
||||
"status",
|
||||
"id_type",
|
||||
"masked_name",
|
||||
"masked_id_number",
|
||||
"id_number_hash",
|
||||
"review_reason",
|
||||
"verified_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
).
|
||||
Where("id", in.Id).
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
// Review 审核IM实名
|
||||
func (s *sSysImRealname) Review(ctx context.Context, in *sysin.ImRealnameReviewInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data := g.Map{
|
||||
"status": in.Status,
|
||||
"review_reason": in.ReviewReason,
|
||||
"updated_at": gtime.Now(),
|
||||
}
|
||||
if in.Status == consts.ImRealnameStatusVerified {
|
||||
data["verified_at"] = gtime.Now()
|
||||
} else {
|
||||
data["verified_at"] = nil
|
||||
}
|
||||
|
||||
result, err := db.Model("im_realname_verifications").Safe().Ctx(ctx).
|
||||
Where("id", in.Id).
|
||||
Where("status", consts.ImRealnameStatusPending).
|
||||
Data(data).
|
||||
Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "审核IM实名失败,请稍后重试!")
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "读取审核结果失败,请稍后重试!")
|
||||
}
|
||||
if affected == 0 {
|
||||
return gerror.New("当前记录不是待审核状态,不能重复审核")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
const (
|
||||
imRegistrationConfigGroup = "im"
|
||||
imRegistrationSmsKey = "register_sms_enabled"
|
||||
)
|
||||
|
||||
type sSysImRegistration struct{}
|
||||
|
||||
func NewSysImRegistration() *sSysImRegistration {
|
||||
return &sSysImRegistration{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImRegistration(NewSysImRegistration())
|
||||
}
|
||||
|
||||
func (s *sSysImRegistration) GetConfig(ctx context.Context) (res *sysin.ImRegistrationConfigModel, err error) {
|
||||
if err = s.verifySuper(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return s.getConfig(ctx)
|
||||
}
|
||||
|
||||
func (s *sSysImRegistration) UpdateConfig(ctx context.Context, in *sysin.ImRegistrationConfigUpdateInp) (err error) {
|
||||
if err = s.verifySuper(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
old, err := s.getConfig(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
value := "0"
|
||||
if in.RegisterSmsEnabled {
|
||||
value = "1"
|
||||
}
|
||||
_, err = dao.SysConfig.Ctx(ctx).
|
||||
Where(dao.SysConfig.Columns().Group, imRegistrationConfigGroup).
|
||||
Where(dao.SysConfig.Columns().Key, imRegistrationSmsKey).
|
||||
Data(g.Map{
|
||||
dao.SysConfig.Columns().Value: value,
|
||||
dao.SysConfig.Columns().UpdatedAt: gtime.Now(),
|
||||
}).
|
||||
Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新IM注册短信开关失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, 0, "registration_sms_config_update", "config", 0, "success", g.Map{
|
||||
"old": old.RegisterSmsEnabled,
|
||||
"new": in.RegisterSmsEnabled,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImRegistration) getConfig(ctx context.Context) (res *sysin.ImRegistrationConfigModel, err error) {
|
||||
var record gdb.Record
|
||||
record, err = dao.SysConfig.Ctx(ctx).
|
||||
Where(dao.SysConfig.Columns().Group, imRegistrationConfigGroup).
|
||||
Where(dao.SysConfig.Columns().Key, imRegistrationSmsKey).
|
||||
Where(dao.SysConfig.Columns().Status, 1).
|
||||
One()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM注册短信开关失败")
|
||||
}
|
||||
if record.IsEmpty() {
|
||||
if err = s.seedConfig(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.getConfig(ctx)
|
||||
}
|
||||
return &sysin.ImRegistrationConfigModel{
|
||||
RegisterSmsEnabled: record[dao.SysConfig.Columns().Value].String() == "1",
|
||||
UpdatedAt: record[dao.SysConfig.Columns().UpdatedAt].GTime(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sSysImRegistration) seedConfig(ctx context.Context) (err error) {
|
||||
_, err = dao.SysConfig.Ctx(ctx).Data(g.Map{
|
||||
dao.SysConfig.Columns().Group: imRegistrationConfigGroup,
|
||||
dao.SysConfig.Columns().Name: "注册短信发送",
|
||||
dao.SysConfig.Columns().Type: "bool",
|
||||
dao.SysConfig.Columns().Key: imRegistrationSmsKey,
|
||||
dao.SysConfig.Columns().Value: "1",
|
||||
dao.SysConfig.Columns().DefaultValue: "1",
|
||||
dao.SysConfig.Columns().Sort: 10,
|
||||
dao.SysConfig.Columns().Tip: "控制 IM 注册验证码是否真实发送",
|
||||
dao.SysConfig.Columns().IsDefault: 1,
|
||||
dao.SysConfig.Columns().Status: 1,
|
||||
dao.SysConfig.Columns().CreatedAt: gtime.Now(),
|
||||
dao.SysConfig.Columns().UpdatedAt: gtime.Now(),
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "初始化IM注册短信开关失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImRegistration) verifySuper(ctx context.Context) error {
|
||||
if !service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx)) {
|
||||
return gerror.New("仅平台超级管理员可操作IM注册配置")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
// 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/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/hgorm/handler"
|
||||
"hotgo/internal/library/hgorm/hook"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type sSysImTenant struct{}
|
||||
|
||||
func NewSysImTenant() *sSysImTenant {
|
||||
return &sSysImTenant{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImTenant(NewSysImTenant())
|
||||
}
|
||||
|
||||
// Model IM租户ORM模型
|
||||
func (s *sSysImTenant) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
return handler.Model(imtenant.RegistryDB(ctx).Model(dao.ImTenant.Table()).Safe().Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysImTenant) configModel(ctx context.Context) *gdb.Model {
|
||||
return imtenant.RegistryDB(ctx).Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// List 获取IM租户列表
|
||||
func (s *sSysImTenant) List(ctx context.Context, in *sysin.ImTenantListInp) (list []*sysin.ImTenantListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mod := s.Model(ctx).Fields(
|
||||
dao.ImTenant.Columns().Id,
|
||||
dao.ImTenant.Columns().Code,
|
||||
dao.ImTenant.Columns().Name,
|
||||
dao.ImTenant.Columns().TenantDesc,
|
||||
dao.ImTenant.Columns().DbName,
|
||||
dao.ImTenant.Columns().State,
|
||||
dao.ImTenant.Columns().CreatedBy,
|
||||
dao.ImTenant.Columns().UpdatedBy,
|
||||
dao.ImTenant.Columns().CreatedAt,
|
||||
dao.ImTenant.Columns().UpdatedAt,
|
||||
)
|
||||
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where(dao.ImTenant.Columns().Id, in.Id)
|
||||
}
|
||||
if in.Code != "" {
|
||||
mod = mod.WhereLike(dao.ImTenant.Columns().Code, "%"+in.Code+"%")
|
||||
}
|
||||
if in.Name != "" {
|
||||
mod = mod.WhereLike(dao.ImTenant.Columns().Name, "%"+in.Name+"%")
|
||||
}
|
||||
if in.DbName != "" {
|
||||
mod = mod.WhereLike(dao.ImTenant.Columns().DbName, "%"+in.DbName+"%")
|
||||
}
|
||||
if in.State != nil {
|
||||
mod = mod.Where(dao.ImTenant.Columns().State, *in.State)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween(dao.ImTenant.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
|
||||
mod = mod.Page(in.Page, in.PerPage).
|
||||
OrderDesc(dao.ImTenant.Columns().Id).
|
||||
Hook(hook.MemberSummary)
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM租户列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.fillListConfig(ctx, list); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImTenant) fillListConfig(ctx context.Context, list []*sysin.ImTenantListModel) (err error) {
|
||||
ids := make([]int64, 0, len(list))
|
||||
for _, item := range list {
|
||||
ids = append(ids, item.Id)
|
||||
}
|
||||
|
||||
var configs []*entity.ImTenantConfig
|
||||
if err = s.configModel(ctx).
|
||||
WhereIn(dao.ImTenantConfig.Columns().TenantId, ids).
|
||||
Scan(&configs); err != nil {
|
||||
return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
|
||||
}
|
||||
configMap := make(map[int64]*entity.ImTenantConfig, len(configs))
|
||||
for _, item := range configs {
|
||||
configMap[item.TenantId] = item
|
||||
}
|
||||
|
||||
var tenants []*entity.ImTenant
|
||||
if err = s.Model(ctx).
|
||||
Fields(dao.ImTenant.Columns().Id, dao.ImTenant.Columns().DbDsn).
|
||||
WhereIn(dao.ImTenant.Columns().Id, ids).
|
||||
Scan(&tenants); err != nil {
|
||||
return gerror.Wrap(err, "获取IM租户连接配置失败,请稍后重试!")
|
||||
}
|
||||
dsnMap := make(map[int64]string, len(tenants))
|
||||
for _, item := range tenants {
|
||||
dsnMap[item.Id] = item.DbDsn
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
if conf, ok := configMap[item.Id]; ok {
|
||||
item.MaxUsers = conf.MaxUsers
|
||||
item.MaxGroups = conf.MaxGroups
|
||||
item.MaxGroupMembers = conf.MaxGroupMembers
|
||||
}
|
||||
item.DbDsnConfigured = dsnMap[item.Id] != ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 修改/新增IM租户
|
||||
func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return imtenant.RegistryDB(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
if err = s.checkCodeUnique(ctx, in.Id, in.Code); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userId := contexts.GetUserId(ctx)
|
||||
if in.Id > 0 {
|
||||
in.UpdatedBy = userId
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(sysin.ImTenantUpdateFields{}).
|
||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
||||
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
|
||||
}
|
||||
return s.saveConfig(ctx, in.Id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, userId)
|
||||
}
|
||||
|
||||
in.CreatedBy = userId
|
||||
id, err := s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(sysin.ImTenantInsertFields{}).
|
||||
Data(in).InsertAndGetId()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "新增IM租户失败,请稍后重试!")
|
||||
}
|
||||
return s.saveConfig(ctx, id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, userId)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code string) (err error) {
|
||||
mod := s.Model(ctx).Where(dao.ImTenant.Columns().Code, code)
|
||||
if id > 0 {
|
||||
mod = mod.Where(dao.ImTenant.Columns().Id+"<>?", id)
|
||||
}
|
||||
count, err := mod.Count()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "检查IM租户编码失败,请稍后重试!")
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("租户编码已存在")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers uint64, maxGroups uint64, maxGroupMembers uint, userId int64) (err error) {
|
||||
var conf *entity.ImTenantConfig
|
||||
if err = s.configModel(ctx).
|
||||
Where(dao.ImTenantConfig.Columns().TenantId, tenantId).
|
||||
Scan(&conf); err != nil {
|
||||
return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
|
||||
}
|
||||
|
||||
data := g.Map{
|
||||
dao.ImTenantConfig.Columns().MaxUsers: maxUsers,
|
||||
dao.ImTenantConfig.Columns().MaxGroups: maxGroups,
|
||||
dao.ImTenantConfig.Columns().MaxGroupMembers: maxGroupMembers,
|
||||
}
|
||||
if conf != nil && conf.Id > 0 {
|
||||
data[dao.ImTenantConfig.Columns().UpdatedBy] = userId
|
||||
if _, err = s.configModel(ctx).
|
||||
Fields(
|
||||
dao.ImTenantConfig.Columns().MaxUsers,
|
||||
dao.ImTenantConfig.Columns().MaxGroups,
|
||||
dao.ImTenantConfig.Columns().MaxGroupMembers,
|
||||
dao.ImTenantConfig.Columns().UpdatedBy,
|
||||
).
|
||||
WherePri(conf.Id).Data(data).Update(); err != nil {
|
||||
return gerror.Wrap(err, "修改IM租户配置失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data[dao.ImTenantConfig.Columns().TenantId] = tenantId
|
||||
data[dao.ImTenantConfig.Columns().CreatedBy] = userId
|
||||
if _, err = s.configModel(ctx).
|
||||
Fields(
|
||||
dao.ImTenantConfig.Columns().TenantId,
|
||||
dao.ImTenantConfig.Columns().MaxUsers,
|
||||
dao.ImTenantConfig.Columns().MaxGroups,
|
||||
dao.ImTenantConfig.Columns().MaxGroupMembers,
|
||||
dao.ImTenantConfig.Columns().CreatedBy,
|
||||
).
|
||||
Data(data).Insert(); err != nil {
|
||||
return gerror.Wrap(err, "新增IM租户配置失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取IM租户指定信息
|
||||
func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res *sysin.ImTenantViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM租户信息失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
if res == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var conf *entity.ImTenantConfig
|
||||
if err = s.configModel(ctx).
|
||||
Where(dao.ImTenantConfig.Columns().TenantId, in.Id).
|
||||
Scan(&conf); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
if conf != nil {
|
||||
res.MaxUsers = conf.MaxUsers
|
||||
res.MaxGroups = conf.MaxGroups
|
||||
res.MaxGroupMembers = conf.MaxGroupMembers
|
||||
}
|
||||
res.DbDsnConfigured = res.DbDsn != ""
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新IM租户状态
|
||||
func (s *sSysImTenant) Status(ctx context.Context, in *sysin.ImTenantStatusInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
|
||||
dao.ImTenant.Columns().State: in.State,
|
||||
dao.ImTenant.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
}).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新IM租户状态失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// 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/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type sSysImUser struct{}
|
||||
|
||||
func NewSysImUser() *sSysImUser {
|
||||
return &sSysImUser{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImUser(NewSysImUser())
|
||||
}
|
||||
|
||||
// List 获取IM用户列表
|
||||
func (s *sSysImUser) List(ctx context.Context, in *sysin.ImUserListInp) (list []*sysin.ImUserListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
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 len(list) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
if err = s.fillAuthAndCredentials(ctx, db, list); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImUser) listModel(ctx context.Context, db gdb.DB) *gdb.Model {
|
||||
return 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(
|
||||
"u.id user_id",
|
||||
"u.state",
|
||||
"u.lastseen last_seen",
|
||||
"u.useragent user_agent",
|
||||
"u.createdat created_at",
|
||||
"u.updatedat updated_at",
|
||||
"up.nickname",
|
||||
"up.public_account",
|
||||
"up.gender",
|
||||
"up.region",
|
||||
"us.auth_version",
|
||||
"us.password_updated_at",
|
||||
)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) applyListWhere(mod *gdb.Model, in *sysin.ImUserListInp) *gdb.Model {
|
||||
if in.UserId > 0 {
|
||||
mod = mod.Where("u.id", in.UserId)
|
||||
}
|
||||
if in.Nickname != "" {
|
||||
mod = mod.WhereLike("up.nickname", "%"+in.Nickname+"%")
|
||||
}
|
||||
if in.AuthName != "" {
|
||||
mod = mod.Where("EXISTS (SELECT 1 FROM im_auth a WHERE a.userid = u.id AND a.uname LIKE ?)", "%"+in.AuthName+"%")
|
||||
}
|
||||
if in.State != nil {
|
||||
mod = mod.Where("u.state", *in.State)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween("u.createdat", in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
if len(in.LastSeen) == 2 {
|
||||
mod = mod.WhereBetween("u.lastseen", in.LastSeen[0], in.LastSeen[1])
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
func (s *sSysImUser) fillAuthAndCredentials(ctx context.Context, db gdb.DB, list []*sysin.ImUserListModel) (err error) {
|
||||
ids := make([]int64, 0, len(list))
|
||||
userMap := make(map[int64]*sysin.ImUserListModel, len(list))
|
||||
for _, item := range list {
|
||||
ids = append(ids, item.UserId)
|
||||
userMap[item.UserId] = item
|
||||
}
|
||||
|
||||
authRows, err := db.Model("im_auth").Safe().Ctx(ctx).
|
||||
Fields("userid", "scheme", "uname").
|
||||
WhereIn("userid", ids).
|
||||
OrderAsc("scheme").OrderAsc("uname").
|
||||
All()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "获取IM用户认证标识失败,请稍后重试!")
|
||||
}
|
||||
authMap := make(map[int64][]string)
|
||||
for _, row := range authRows {
|
||||
userId := row["userid"].Int64()
|
||||
scheme := row["scheme"].String()
|
||||
uname := row["uname"].String()
|
||||
authMap[userId] = append(authMap[userId], scheme+":"+uname)
|
||||
}
|
||||
|
||||
credentialRows, err := db.Model("im_credentials").Safe().Ctx(ctx).
|
||||
Fields("userid", "method", "value", "done").
|
||||
WhereIn("userid", ids).
|
||||
WhereNull("deletedat").
|
||||
OrderAsc("method").OrderAsc("value").
|
||||
All()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "获取IM用户凭证失败,请稍后重试!")
|
||||
}
|
||||
credentialMap := make(map[int64][]string)
|
||||
for _, row := range credentialRows {
|
||||
userId := row["userid"].Int64()
|
||||
method := row["method"].String()
|
||||
value := row["value"].String()
|
||||
done := row["done"].Bool()
|
||||
if !done {
|
||||
value += "(未验证)"
|
||||
}
|
||||
credentialMap[userId] = append(credentialMap[userId], method+":"+value)
|
||||
}
|
||||
|
||||
for userId, item := range userMap {
|
||||
item.AuthNames = strings.Join(authMap[userId], ", ")
|
||||
item.Credentials = strings.Join(credentialMap[userId], ", ")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取IM用户指定信息
|
||||
func (s *sSysImUser) View(ctx context.Context, in *sysin.ImUserViewInp) (res *sysin.ImUserViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fields := append(s.viewFields(), "u.access access_json", "u.public profile_json", "u.trusted trusted_json", "u.tags tags_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", in.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
|
||||
}
|
||||
|
||||
func (s *sSysImUser) viewFields() []interface{} {
|
||||
return []interface{}{
|
||||
"u.id user_id",
|
||||
"u.state",
|
||||
"u.lastseen last_seen",
|
||||
"u.useragent user_agent",
|
||||
"u.createdat created_at",
|
||||
"u.updatedat updated_at",
|
||||
"up.nickname",
|
||||
"up.public_account",
|
||||
"up.gender",
|
||||
"up.region",
|
||||
"up.signature",
|
||||
"up.avatar_file_id",
|
||||
"CAST(up.birth_date AS CHAR) birth_date",
|
||||
"up.version profile_version",
|
||||
"us.auth_version",
|
||||
"us.password_updated_at",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysImWorkOrder struct{}
|
||||
|
||||
func NewSysImWorkOrder() *sSysImWorkOrder {
|
||||
return &sSysImWorkOrder{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImWorkOrder(NewSysImWorkOrder())
|
||||
}
|
||||
|
||||
// List 获取IM举报反馈工单列表
|
||||
func (s *sSysImWorkOrder) List(ctx context.Context, in *sysin.ImWorkOrderListInp) (list []*sysin.ImWorkOrderListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mod := s.listModel(ctx, db, in.Type)
|
||||
mod = s.applyWhere(mod, in)
|
||||
mod = mod.Page(in.Page, in.PerPage).OrderDesc("created_at").OrderDesc("id")
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取IM举报反馈工单列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImWorkOrder) listModel(ctx context.Context, db gdb.DB, typ string) *gdb.Model {
|
||||
if typ == sysin.ImWorkOrderTypeAbuse {
|
||||
return db.Model("im_abuse_reports").Safe().Ctx(ctx).Fields(s.abuseListFields()...)
|
||||
}
|
||||
return db.Model("im_feedback_submissions").Safe().Ctx(ctx).Fields(s.feedbackListFields()...)
|
||||
}
|
||||
|
||||
func (s *sSysImWorkOrder) abuseListFields() []interface{} {
|
||||
return []interface{}{
|
||||
"'abuse' type",
|
||||
"id",
|
||||
"report_id order_id",
|
||||
"reporter_id user_id",
|
||||
"target_type",
|
||||
"CONCAT(target_topic, IF(target_seq>0, CONCAT('#', target_seq), ''), IF(target_user_id>0, CONCAT(' user:', target_user_id), '')) target_summary",
|
||||
"reason_code category",
|
||||
"description content",
|
||||
"status",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sSysImWorkOrder) feedbackListFields() []interface{} {
|
||||
return []interface{}{
|
||||
"'feedback' type",
|
||||
"id",
|
||||
"feedback_id order_id",
|
||||
"user_id",
|
||||
"'' target_type",
|
||||
"'' target_summary",
|
||||
"category",
|
||||
"content",
|
||||
"status",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sSysImWorkOrder) applyWhere(mod *gdb.Model, in *sysin.ImWorkOrderListInp) *gdb.Model {
|
||||
if in.UserId > 0 {
|
||||
mod = mod.Where("user_id", in.UserId)
|
||||
}
|
||||
if in.Status != "" {
|
||||
mod = mod.Where("status", in.Status)
|
||||
}
|
||||
if in.Keyword != "" {
|
||||
mod = mod.WhereLike("content", "%"+in.Keyword+"%")
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween("created_at", in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
return mod
|
||||
}
|
||||
|
||||
// View 获取IM举报反馈工单指定信息
|
||||
func (s *sSysImWorkOrder) View(ctx context.Context, in *sysin.ImWorkOrderViewInp) (res *sysin.ImWorkOrderViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if in.Type == sysin.ImWorkOrderTypeAbuse {
|
||||
res, err = s.viewAbuse(ctx, db, in.Id)
|
||||
} else {
|
||||
res, err = s.viewFeedback(ctx, db, in.Id)
|
||||
}
|
||||
if err != nil || res == nil {
|
||||
return
|
||||
}
|
||||
res.TenantId = tenant.Id
|
||||
res.TenantName = tenant.Name
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImWorkOrder) viewAbuse(ctx context.Context, db gdb.DB, id int64) (res *sysin.ImWorkOrderViewModel, err error) {
|
||||
err = db.Model("im_abuse_reports").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"'abuse' type",
|
||||
"id",
|
||||
"report_id order_id",
|
||||
"reporter_id user_id",
|
||||
"target_type",
|
||||
"CONCAT(target_topic, IF(target_seq>0, CONCAT('#', target_seq), ''), IF(target_user_id>0, CONCAT(' user:', target_user_id), '')) target_summary",
|
||||
"reason_code category",
|
||||
"description content",
|
||||
"description",
|
||||
"evidence_file_ids evidence_json",
|
||||
"snapshot snapshot_json",
|
||||
"idempotency_key",
|
||||
"status",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
).
|
||||
Where("id", id).
|
||||
Scan(&res)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM举报工单信息失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImWorkOrder) viewFeedback(ctx context.Context, db gdb.DB, id int64) (res *sysin.ImWorkOrderViewModel, err error) {
|
||||
err = db.Model("im_feedback_submissions").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"'feedback' type",
|
||||
"id",
|
||||
"feedback_id order_id",
|
||||
"user_id",
|
||||
"'' target_type",
|
||||
"'' target_summary",
|
||||
"category",
|
||||
"content",
|
||||
"contact",
|
||||
"attachment_file_ids evidence_json",
|
||||
"client_context client_context_json",
|
||||
"include_diagnostics",
|
||||
"idempotency_key",
|
||||
"status",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
).
|
||||
Where("id", id).
|
||||
Scan(&res)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM反馈工单信息失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新IM举报反馈工单状态
|
||||
func (s *sSysImWorkOrder) Status(ctx context.Context, in *sysin.ImWorkOrderStatusInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
table := "im_abuse_reports"
|
||||
if in.Type == sysin.ImWorkOrderTypeFeedback {
|
||||
table = "im_feedback_submissions"
|
||||
}
|
||||
if _, err = db.Model(table).Safe().Ctx(ctx).
|
||||
Where("id", in.Id).
|
||||
Data(g.Map{
|
||||
"status": in.Status,
|
||||
"updated_at": gtime.Now(),
|
||||
}).
|
||||
Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新IM举报反馈工单状态失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -159,7 +159,7 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
|
||||
module = mctx.Module
|
||||
clientIp = location.GetClientIp(request)
|
||||
postData = gjson.New(consts.NilJsonToString)
|
||||
getData = gjson.New(request.URL.Query())
|
||||
getData = s.MaskSensitiveJson(gjson.New(request.URL.Query()))
|
||||
headerData = gjson.New(consts.NilJsonToString)
|
||||
errorData = gjson.New(consts.NilJsonToString)
|
||||
data entity.SysLog
|
||||
@@ -194,7 +194,7 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
|
||||
|
||||
// post参数
|
||||
if body, ok := mctx.Data["request.body"].(*gjson.Json); ok {
|
||||
postData = body
|
||||
postData = s.MaskSensitiveJson(body)
|
||||
}
|
||||
|
||||
// post表单
|
||||
@@ -208,12 +208,7 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
|
||||
if postData.IsNil() || len(postData.Map()) == 0 {
|
||||
postData = gjson.New(consts.NilJsonToString)
|
||||
} else {
|
||||
// 隐藏明文显示的密码
|
||||
for k := range postData.Map() {
|
||||
if gstr.ContainsI(k, "password") {
|
||||
postData.MustSet(k, "******")
|
||||
}
|
||||
}
|
||||
postData = s.MaskSensitiveJson(postData)
|
||||
}
|
||||
|
||||
// 当前登录用户
|
||||
@@ -247,7 +242,7 @@ func (s *sSysLog) AnalysisLog(ctx context.Context) entity.SysLog {
|
||||
Url: request.URL.Path,
|
||||
GetData: getData,
|
||||
PostData: postData,
|
||||
HeaderData: s.SimplifyHeaderParams(headerData),
|
||||
HeaderData: s.SimplifyHeaderParams(s.MaskSensitiveJson(headerData)),
|
||||
Ip: clientIp,
|
||||
ProvinceId: ipData.ProvinceCode,
|
||||
CityId: ipData.CityCode,
|
||||
@@ -273,6 +268,10 @@ func (s *sSysLog) SimplifyHeaderParams(data *gjson.Json) *gjson.Json {
|
||||
var filters = []string{"Accept", "Authorization", "Cookie"}
|
||||
for _, filter := range filters {
|
||||
v := data.Get(filter)
|
||||
if isSensitiveLogField(filter) {
|
||||
data.MustSet(filter, "******")
|
||||
continue
|
||||
}
|
||||
if len(v.String()) > 128 {
|
||||
data.MustRemove(filter)
|
||||
}
|
||||
@@ -280,6 +279,58 @@ func (s *sSysLog) SimplifyHeaderParams(data *gjson.Json) *gjson.Json {
|
||||
return data
|
||||
}
|
||||
|
||||
// MaskSensitiveJson masks sensitive request fields before access logs are stored.
|
||||
func (s *sSysLog) MaskSensitiveJson(data *gjson.Json) *gjson.Json {
|
||||
if data == nil || data.IsNil() {
|
||||
return data
|
||||
}
|
||||
return gjson.New(maskSensitiveValue(data.Map()))
|
||||
}
|
||||
|
||||
func maskSensitiveValue(v interface{}) interface{} {
|
||||
switch value := v.(type) {
|
||||
case map[string]interface{}:
|
||||
res := make(map[string]interface{}, len(value))
|
||||
for k, item := range value {
|
||||
if isSensitiveLogField(k) {
|
||||
res[k] = "******"
|
||||
continue
|
||||
}
|
||||
res[k] = maskSensitiveValue(item)
|
||||
}
|
||||
return res
|
||||
case map[string][]string:
|
||||
res := make(map[string]interface{}, len(value))
|
||||
for k, item := range value {
|
||||
if isSensitiveLogField(k) {
|
||||
res[k] = "******"
|
||||
continue
|
||||
}
|
||||
res[k] = item
|
||||
}
|
||||
return res
|
||||
case []interface{}:
|
||||
for i, item := range value {
|
||||
value[i] = maskSensitiveValue(item)
|
||||
}
|
||||
return value
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func isSensitiveLogField(field string) bool {
|
||||
sensitive := []string{"password", "invitecode", "invite_code", "servicesecret", "service_secret", "authorization", "x-service-secret"}
|
||||
normalized := gstr.Replace(gstr.Replace(gstr.ToLower(field), "-", ""), "_", "")
|
||||
for _, item := range sensitive {
|
||||
item = gstr.Replace(gstr.Replace(item, "-", ""), "_", "")
|
||||
if normalized == item || gstr.Contains(normalized, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// View 获取指定请求日志信息
|
||||
func (s *sSysLog) View(ctx context.Context, in *sysin.LogViewInp) (res *sysin.LogViewModel, err error) {
|
||||
mod := s.Model(ctx)
|
||||
|
||||
Reference in New Issue
Block a user