feat: add IM governance admin enhancements
This commit is contained in:
@@ -54,6 +54,38 @@ type GovernanceRes struct {
|
||||
*sysin.ImGroupGovernanceModel
|
||||
}
|
||||
|
||||
// GovernanceUpdateReq 更新IM群治理配置
|
||||
type GovernanceUpdateReq struct {
|
||||
g.Meta `path:"/imGroup/governanceUpdate" method:"post" tags:"IM群组" summary:"更新IM群治理配置"`
|
||||
sysin.ImGroupGovernanceUpdateInp
|
||||
}
|
||||
|
||||
type GovernanceUpdateRes struct{}
|
||||
|
||||
// DissolveReq 解散IM群
|
||||
type DissolveReq struct {
|
||||
g.Meta `path:"/imGroup/dissolve" method:"post" tags:"IM群组" summary:"解散IM群"`
|
||||
sysin.ImGroupDissolveInp
|
||||
}
|
||||
|
||||
type DissolveRes struct{}
|
||||
|
||||
// MuteReq 禁言IM群成员
|
||||
type MuteReq struct {
|
||||
g.Meta `path:"/imGroup/mute" method:"post" tags:"IM群组" summary:"禁言IM群成员"`
|
||||
sysin.ImGroupMuteInp
|
||||
}
|
||||
|
||||
type MuteRes struct{}
|
||||
|
||||
// UnmuteReq 解除IM群成员禁言
|
||||
type UnmuteReq struct {
|
||||
g.Meta `path:"/imGroup/unmute" method:"post" tags:"IM群组" summary:"解除IM群成员禁言"`
|
||||
sysin.ImGroupUnmuteInp
|
||||
}
|
||||
|
||||
type UnmuteRes struct{}
|
||||
|
||||
// JoinRequestListReq 查询IM入群申请列表
|
||||
type JoinRequestListReq struct {
|
||||
g.Meta `path:"/imGroup/joinRequestList" method:"get" tags:"IM群组" summary:"获取IM入群申请列表"`
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Package imoutbox
|
||||
// @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 imoutbox
|
||||
|
||||
import (
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type ListReq struct {
|
||||
g.Meta `path:"/imOutbox/list" method:"get" tags:"IM Outbox" summary:"获取IM Outbox列表"`
|
||||
sysin.ImOutboxListInp
|
||||
}
|
||||
|
||||
type ListRes struct {
|
||||
form.PageRes
|
||||
List []*sysin.ImOutboxListModel `json:"list" dc:"数据列表"`
|
||||
}
|
||||
|
||||
type ViewReq struct {
|
||||
g.Meta `path:"/imOutbox/view" method:"get" tags:"IM Outbox" summary:"获取IM Outbox详情"`
|
||||
sysin.ImOutboxViewInp
|
||||
}
|
||||
|
||||
type ViewRes struct {
|
||||
*sysin.ImOutboxViewModel
|
||||
}
|
||||
|
||||
type RetryReq struct {
|
||||
g.Meta `path:"/imOutbox/retry" method:"post" tags:"IM Outbox" summary:"重试IM Outbox事件"`
|
||||
sysin.ImOutboxActionInp
|
||||
}
|
||||
|
||||
type RetryRes struct{}
|
||||
|
||||
type DeadReq struct {
|
||||
g.Meta `path:"/imOutbox/dead" method:"post" tags:"IM Outbox" summary:"标记IM Outbox死信"`
|
||||
sysin.ImOutboxActionInp
|
||||
}
|
||||
|
||||
type DeadRes struct{}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Package consts
|
||||
// @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 consts
|
||||
|
||||
import (
|
||||
"hotgo/internal/library/dict"
|
||||
"hotgo/internal/model"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dict.RegisterEnums("ImOutboxSourceOptions", "IM Outbox来源选项", ImOutboxSourceOptions)
|
||||
dict.RegisterEnums("ImOutboxStatusOptions", "IM Outbox状态选项", ImOutboxStatusOptions)
|
||||
}
|
||||
|
||||
const (
|
||||
ImOutboxSourceBusiness = "business"
|
||||
ImOutboxSourceGroup = "group"
|
||||
|
||||
ImOutboxStatusPending = "pending"
|
||||
ImOutboxStatusDelivered = "delivered"
|
||||
ImOutboxStatusFailed = "failed"
|
||||
ImOutboxStatusDead = "dead"
|
||||
)
|
||||
|
||||
var ImOutboxSourceOptions = []*model.Option{
|
||||
dict.GenPrimaryOption(ImOutboxSourceBusiness, "业务事件"),
|
||||
dict.GenInfoOption(ImOutboxSourceGroup, "群事件"),
|
||||
}
|
||||
|
||||
var ImOutboxStatusOptions = []*model.Option{
|
||||
dict.GenWarningOption(ImOutboxStatusPending, "待投递"),
|
||||
dict.GenSuccessOption(ImOutboxStatusDelivered, "已投递"),
|
||||
dict.GenErrorOption(ImOutboxStatusFailed, "失败"),
|
||||
dict.GenDefaultOption(ImOutboxStatusDead, "死信"),
|
||||
}
|
||||
@@ -75,6 +75,30 @@ func (c *cImGroup) Governance(ctx context.Context, req *imgroup.GovernanceReq) (
|
||||
return
|
||||
}
|
||||
|
||||
// GovernanceUpdate 更新IM群治理配置
|
||||
func (c *cImGroup) GovernanceUpdate(ctx context.Context, req *imgroup.GovernanceUpdateReq) (res *imgroup.GovernanceUpdateRes, err error) {
|
||||
err = service.SysImGroup().GovernanceUpdate(ctx, &req.ImGroupGovernanceUpdateInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Dissolve 解散IM群
|
||||
func (c *cImGroup) Dissolve(ctx context.Context, req *imgroup.DissolveReq) (res *imgroup.DissolveRes, err error) {
|
||||
err = service.SysImGroup().Dissolve(ctx, &req.ImGroupDissolveInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Mute 禁言IM群成员
|
||||
func (c *cImGroup) Mute(ctx context.Context, req *imgroup.MuteReq) (res *imgroup.MuteRes, err error) {
|
||||
err = service.SysImGroup().Mute(ctx, &req.ImGroupMuteInp)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmute 解除IM群成员禁言
|
||||
func (c *cImGroup) Unmute(ctx context.Context, req *imgroup.UnmuteReq) (res *imgroup.UnmuteRes, err error) {
|
||||
err = service.SysImGroup().Unmute(ctx, &req.ImGroupUnmuteInp)
|
||||
return
|
||||
}
|
||||
|
||||
// JoinRequestList 查看IM入群申请列表
|
||||
func (c *cImGroup) JoinRequestList(ctx context.Context, req *imgroup.JoinRequestListReq) (res *imgroup.JoinRequestListRes, err error) {
|
||||
list, totalCount, err := service.SysImGroup().JoinRequestList(ctx, &req.ImGroupJoinRequestListInp)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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/api/admin/imoutbox"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
)
|
||||
|
||||
var (
|
||||
ImOutbox = cImOutbox{}
|
||||
)
|
||||
|
||||
type cImOutbox struct{}
|
||||
|
||||
func (c *cImOutbox) List(ctx context.Context, req *imoutbox.ListReq) (res *imoutbox.ListRes, err error) {
|
||||
list, totalCount, err := service.SysImOutbox().List(ctx, &req.ImOutboxListInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if list == nil {
|
||||
list = []*sysin.ImOutboxListModel{}
|
||||
}
|
||||
|
||||
res = new(imoutbox.ListRes)
|
||||
res.List = list
|
||||
res.PageRes.Pack(req, totalCount)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cImOutbox) View(ctx context.Context, req *imoutbox.ViewReq) (res *imoutbox.ViewRes, err error) {
|
||||
data, err := service.SysImOutbox().View(ctx, &req.ImOutboxViewInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = new(imoutbox.ViewRes)
|
||||
res.ImOutboxViewModel = data
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cImOutbox) Retry(ctx context.Context, req *imoutbox.RetryReq) (res *imoutbox.RetryRes, err error) {
|
||||
err = service.SysImOutbox().Retry(ctx, &req.ImOutboxActionInp)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cImOutbox) Dead(ctx context.Context, req *imoutbox.DeadReq) (res *imoutbox.DeadRes, err error) {
|
||||
err = service.SysImOutbox().Dead(ctx, &req.ImOutboxActionInp)
|
||||
return
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/simple"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@@ -80,6 +81,14 @@ func ensureImTenantActive(ctx context.Context, tenantId int64) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveImTenantDB(ctx context.Context, selectedTenantId int64) (db gdb.DB, tenant *entity.ImTenant, err error) {
|
||||
scope, err := resolveImAdminScope(ctx, selectedTenantId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return imtenant.DB(ctx, scope.TenantId)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -7,12 +7,20 @@ package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"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/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/guid"
|
||||
)
|
||||
|
||||
type sSysImGroup struct{}
|
||||
@@ -31,7 +39,7 @@ func (s *sSysImGroup) List(ctx context.Context, in *sysin.ImGroupListInp) (list
|
||||
return
|
||||
}
|
||||
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -114,7 +122,7 @@ func (s *sSysImGroup) View(ctx context.Context, in *sysin.ImGroupViewInp) (res *
|
||||
return
|
||||
}
|
||||
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -172,7 +180,7 @@ func (s *sSysImGroup) MemberList(ctx context.Context, in *sysin.ImGroupMemberLis
|
||||
return
|
||||
}
|
||||
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -284,7 +292,7 @@ func (s *sSysImGroup) Governance(ctx context.Context, in *sysin.ImGroupTopicInp)
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -324,12 +332,276 @@ func (s *sSysImGroup) Governance(ctx context.Context, in *sysin.ImGroupTopicInp)
|
||||
return
|
||||
}
|
||||
|
||||
// GovernanceUpdate 更新IM群治理配置
|
||||
func (s *sSysImGroup) GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGovernanceUpdateInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actor := contexts.GetUserId(ctx)
|
||||
requestId := gctx.CtxId(ctx)
|
||||
if requestId == "" {
|
||||
requestId = guid.S()
|
||||
}
|
||||
now := gtime.Now()
|
||||
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
before, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if before == nil {
|
||||
return gerror.New("群治理配置不存在")
|
||||
}
|
||||
if before["group_status"].String() == consts.ImGroupStatusDissolved {
|
||||
return gerror.New("已解散群不能修改治理配置")
|
||||
}
|
||||
if _, txErr = tx.Exec(`UPDATE im_group_settings SET
|
||||
join_approval=?,mute_all=?,member_upload_files=?,member_private_chat=?,member_invite=?,
|
||||
max_member_count=?,max_admin_count=?,version=version+1,updated_at=?
|
||||
WHERE topic=?`,
|
||||
in.JoinApproval, in.MuteAll, in.MemberUploadFiles, in.MemberPrivateChat, in.MemberInvite,
|
||||
in.MaxMemberCount, in.MaxAdminCount, now, in.Topic); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
after, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, 0, "governance_update", "group_settings", in.Topic, before.Map(), after.Map(), requestId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.governance_updated", g.Map{
|
||||
"topic": in.Topic, "operator_id": actor, "request_id": requestId,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Dissolve 解散IM群
|
||||
func (s *sSysImGroup) Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actor := contexts.GetUserId(ctx)
|
||||
requestId := gctx.CtxId(ctx)
|
||||
if requestId == "" {
|
||||
requestId = guid.S()
|
||||
}
|
||||
now := gtime.Now()
|
||||
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
before, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if before == nil {
|
||||
return gerror.New("群治理配置不存在")
|
||||
}
|
||||
if before["group_status"].String() == consts.ImGroupStatusDissolved {
|
||||
return gerror.New("群已解散")
|
||||
}
|
||||
result, txErr := tx.Exec(`UPDATE im_group_settings
|
||||
SET group_status=?,dissolved_by=?,dissolved_at=?,version=version+1,updated_at=?
|
||||
WHERE topic=? AND group_status<>?`,
|
||||
consts.ImGroupStatusDissolved, actor, now, now, in.Topic, consts.ImGroupStatusDissolved)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if affected, _ := result.RowsAffected(); affected == 0 {
|
||||
return gerror.New("群已解散")
|
||||
}
|
||||
after, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, 0, "group_dissolve", "group", in.Topic, before.Map(), after.Map(), requestId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.dissolved", g.Map{
|
||||
"topic": in.Topic, "operator_id": actor, "request_id": requestId, "reason": in.Reason,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Mute 禁言IM群成员
|
||||
func (s *sSysImGroup) Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actor := contexts.GetUserId(ctx)
|
||||
requestId := gctx.CtxId(ctx)
|
||||
if requestId == "" {
|
||||
requestId = guid.S()
|
||||
}
|
||||
now := gtime.Now()
|
||||
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
if txErr := s.ensureActiveGroupMember(ctx, tx, in.Topic, in.UserId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
before, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
muteId, txErr := imtenant.NextSnowflakeID(ctx)
|
||||
if txErr != nil {
|
||||
return gerror.Wrap(txErr, "生成禁言ID失败")
|
||||
}
|
||||
if _, txErr = tx.Exec(`INSERT INTO im_group_mutes
|
||||
(id,topic,user_id,muted_by,muted_at,expires_at,reason,status,version)
|
||||
VALUES(?,?,?,?,?,?,?, ?,1)
|
||||
ON DUPLICATE KEY UPDATE muted_by=VALUES(muted_by),muted_at=VALUES(muted_at),
|
||||
expires_at=VALUES(expires_at),reason=VALUES(reason),status=VALUES(status),
|
||||
revoked_by=NULL,revoked_at=NULL,version=version+1,updated_at=CURRENT_TIMESTAMP(3)`,
|
||||
muteId, in.Topic, in.UserId, actor, now, in.ExpiresAt, in.Reason, consts.ImGroupMuteStatusActive); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
after, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, "member_mute", "member", fmt.Sprintf("%d", in.UserId), recordMap(before), recordMap(after), requestId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.mute.updated", g.Map{
|
||||
"topic": in.Topic, "user_id": in.UserId, "muted": true, "expires_at": in.ExpiresAt, "request_id": requestId,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Unmute 解除IM群成员禁言
|
||||
func (s *sSysImGroup) Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actor := contexts.GetUserId(ctx)
|
||||
requestId := gctx.CtxId(ctx)
|
||||
if requestId == "" {
|
||||
requestId = guid.S()
|
||||
}
|
||||
now := gtime.Now()
|
||||
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
before, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if before == nil || before["status"].String() != consts.ImGroupMuteStatusActive {
|
||||
return gerror.New("成员未处于禁言状态")
|
||||
}
|
||||
result, txErr := tx.Exec(`UPDATE im_group_mutes
|
||||
SET status=?,revoked_by=?,revoked_at=?,version=version+1,updated_at=?
|
||||
WHERE topic=? AND user_id=? AND status=?`,
|
||||
consts.ImGroupMuteStatusRevoked, actor, now, now, in.Topic, in.UserId, consts.ImGroupMuteStatusActive)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if affected, _ := result.RowsAffected(); affected == 0 {
|
||||
return gerror.New("成员未处于禁言状态")
|
||||
}
|
||||
after, txErr := s.groupMuteSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, "member_unmute", "member", fmt.Sprintf("%d", in.UserId), recordMap(before), recordMap(after), requestId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.mute.updated", g.Map{
|
||||
"topic": in.Topic, "user_id": in.UserId, "muted": false, "request_id": requestId,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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(
|
||||
"topic", "owner_user_id", "name", "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", topic).
|
||||
One()
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) groupMuteSnapshot(ctx context.Context, tx gdb.TX, topic string, userId int64) (res gdb.Record, err error) {
|
||||
return tx.Model("im_group_mutes").Safe().Ctx(ctx).
|
||||
Fields(
|
||||
"topic", "user_id", "muted_by", "muted_at", "expires_at", "reason", "status",
|
||||
"revoked_by", "revoked_at", "version", "created_at", "updated_at",
|
||||
).
|
||||
Where("topic", topic).
|
||||
Where("user_id", userId).
|
||||
One()
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) ensureActiveGroupMember(ctx context.Context, tx gdb.TX, topic string, userId int64) error {
|
||||
count, err := tx.Model("im_group_members").Safe().Ctx(ctx).
|
||||
Where("topic", topic).
|
||||
Where("user_id", userId).
|
||||
Where("member_status", consts.ImGroupMemberStatusActive).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return gerror.New("成员不在群内或不是正常状态")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recordMap(record gdb.Record) any {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
return record.Map()
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) insertGroupAdminLog(ctx context.Context, tx gdb.TX, topic string, operatorId int64, targetUserId int64, action string, objectType string, objectId string, beforeData any, afterData any, requestId string) error {
|
||||
id, err := imtenant.NextSnowflakeID(ctx)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "生成群管理日志ID失败")
|
||||
}
|
||||
logId := fmt.Sprintf("gal_%d", id)
|
||||
beforeJson, _ := json.Marshal(beforeData)
|
||||
afterJson, _ := json.Marshal(afterData)
|
||||
_, err = tx.Exec(`INSERT INTO im_group_admin_logs
|
||||
(log_id,topic,operator_id,target_user_id,action,object_type,object_id,before_data,after_data,request_id,idempotency_key)
|
||||
VALUES(?,?,?,?,?,?,?,CAST(? AS JSON),CAST(? AS JSON),?,?)`,
|
||||
logId, topic, operatorId, targetUserId, action, objectType, objectId, string(beforeJson), string(afterJson), requestId, requestId)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) insertGroupEvent(ctx context.Context, tx gdb.TX, topic string, actorId int64, eventType string, payload any) error {
|
||||
id, err := imtenant.NextSnowflakeID(ctx)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "生成群事件ID失败")
|
||||
}
|
||||
payloadJson, _ := json.Marshal(payload)
|
||||
_, err = tx.Exec(`INSERT INTO im_group_events_outbox
|
||||
(event_id,topic,actor_user_id,event_type,payload,status)
|
||||
VALUES(?,?,?,?,CAST(? AS JSON),'pending')`,
|
||||
fmt.Sprintf("gev_%d", id), topic, actorId, eventType, string(payloadJson))
|
||||
return err
|
||||
}
|
||||
|
||||
// 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)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -352,7 +624,7 @@ func (s *sSysImGroup) InvitationList(ctx context.Context, in *sysin.ImGroupInvit
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -378,7 +650,7 @@ func (s *sSysImGroup) AnnouncementList(ctx context.Context, in *sysin.ImGroupAnn
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -398,7 +670,7 @@ func (s *sSysImGroup) MuteList(ctx context.Context, in *sysin.ImGroupMuteListInp
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -421,7 +693,7 @@ func (s *sSysImGroup) AdminLogList(ctx context.Context, in *sysin.ImGroupAdminLo
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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/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"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysImOutbox struct{}
|
||||
|
||||
func NewSysImOutbox() *sSysImOutbox {
|
||||
return &sSysImOutbox{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImOutbox(NewSysImOutbox())
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) List(ctx context.Context, in *sysin.ImOutboxListInp) (list []*sysin.ImOutboxListModel, totalCount int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := s.tenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mod := s.model(ctx, db, in.Source)
|
||||
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 Outbox列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
for _, item := range list {
|
||||
item.TenantId = tenant.Id
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) View(ctx context.Context, in *sysin.ImOutboxViewInp) (res *sysin.ImOutboxViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := s.tenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.model(ctx, db, in.Source).Where("id", in.Id).Scan(&res); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取IM Outbox详情失败,请稍后重试!")
|
||||
}
|
||||
if res == nil {
|
||||
return nil, nil
|
||||
}
|
||||
res.TenantId = tenant.Id
|
||||
res.TenantName = tenant.Name
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) Retry(ctx context.Context, in *sysin.ImOutboxActionInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return s.updateStatus(ctx, in, sysin.ImOutboxStatusPending)
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) Dead(ctx context.Context, in *sysin.ImOutboxActionInp) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return s.updateStatus(ctx, in, sysin.ImOutboxStatusDead)
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) tenantDB(ctx context.Context, selectedTenantId int64) (db gdb.DB, tenant *entity.ImTenant, err error) {
|
||||
scope, err := resolveImAdminScope(ctx, selectedTenantId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return imtenant.DB(ctx, scope.TenantId)
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) model(ctx context.Context, db gdb.DB, source string) *gdb.Model {
|
||||
if source == sysin.ImOutboxSourceGroup {
|
||||
return db.Model("im_group_events_outbox").Safe().Ctx(ctx).Fields(
|
||||
"'group' source",
|
||||
"id",
|
||||
"event_id",
|
||||
"'topic' target_type",
|
||||
"topic target",
|
||||
"actor_user_id",
|
||||
"event_type",
|
||||
"CAST(payload AS CHAR) payload",
|
||||
"status",
|
||||
"attempts",
|
||||
"next_retry_at",
|
||||
"delivered_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
)
|
||||
}
|
||||
return db.Model("im_business_events_outbox").Safe().Ctx(ctx).Fields(
|
||||
"'business' source",
|
||||
"id",
|
||||
"event_id",
|
||||
"target_type",
|
||||
"target",
|
||||
"actor_user_id",
|
||||
"event_type",
|
||||
"CAST(payload AS CHAR) payload",
|
||||
"status",
|
||||
"attempts",
|
||||
"next_retry_at",
|
||||
"delivered_at",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
)
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) applyWhere(mod *gdb.Model, in *sysin.ImOutboxListInp) *gdb.Model {
|
||||
if in.EventId != "" {
|
||||
mod = mod.WhereLike("event_id", "%"+in.EventId+"%")
|
||||
}
|
||||
if in.EventType != "" {
|
||||
mod = mod.Where("event_type", in.EventType)
|
||||
}
|
||||
if in.TargetType != "" && in.Source == sysin.ImOutboxSourceBusiness {
|
||||
mod = mod.Where("target_type", in.TargetType)
|
||||
}
|
||||
if in.Target != "" {
|
||||
if in.Source == sysin.ImOutboxSourceGroup {
|
||||
mod = mod.WhereLike("topic", "%"+in.Target+"%")
|
||||
} else {
|
||||
mod = mod.WhereLike("target", "%"+in.Target+"%")
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (s *sSysImOutbox) updateStatus(ctx context.Context, in *sysin.ImOutboxActionInp, status string) (err error) {
|
||||
db, _, err := s.tenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
table := "im_business_events_outbox"
|
||||
if in.Source == sysin.ImOutboxSourceGroup {
|
||||
table = "im_group_events_outbox"
|
||||
}
|
||||
current, err := db.Model(table).Safe().Ctx(ctx).Fields("status").Where("id", in.Id).Value()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "获取IM Outbox事件失败,请稍后重试!")
|
||||
}
|
||||
if current.IsNil() {
|
||||
return gerror.New("IM Outbox事件不存在")
|
||||
}
|
||||
if current.String() == sysin.ImOutboxStatusDelivered {
|
||||
return gerror.New("已投递事件不能重新处置")
|
||||
}
|
||||
|
||||
data := g.Map{
|
||||
"status": status,
|
||||
"updated_at": gtime.Now(),
|
||||
}
|
||||
if status == sysin.ImOutboxStatusPending {
|
||||
data["next_retry_at"] = nil
|
||||
data["delivered_at"] = nil
|
||||
}
|
||||
if _, err = db.Model(table).Safe().Ctx(ctx).Where("id", in.Id).Data(data).Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新IM Outbox事件失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -8,7 +8,6 @@ package sys
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
@@ -33,7 +32,7 @@ func (s *sSysImRealname) List(ctx context.Context, in *sysin.ImRealnameListInp)
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -91,7 +90,7 @@ func (s *sSysImRealname) View(ctx context.Context, in *sysin.ImRealnameViewInp)
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -129,7 +128,7 @@ func (s *sSysImRealname) Review(ctx context.Context, in *sysin.ImRealnameReviewI
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
@@ -32,7 +31,7 @@ func (s *sSysImWorkOrder) List(ctx context.Context, in *sysin.ImWorkOrderListInp
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -112,7 +111,7 @@ func (s *sSysImWorkOrder) View(ctx context.Context, in *sysin.ImWorkOrderViewInp
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,7 +189,7 @@ func (s *sSysImWorkOrder) Status(ctx context.Context, in *sysin.ImWorkOrderStatu
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
db, _, err := imtenant.DB(ctx, in.TenantId)
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,9 +26,6 @@ type ImGroupListInp struct {
|
||||
}
|
||||
|
||||
func (in *ImGroupListInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,9 +69,6 @@ type ImGroupViewInp struct {
|
||||
}
|
||||
|
||||
func (in *ImGroupViewInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Topic == "" {
|
||||
return gerror.New("Topic不能为空")
|
||||
}
|
||||
@@ -98,9 +92,6 @@ type ImGroupMemberListInp struct {
|
||||
}
|
||||
|
||||
func (in *ImGroupMemberListInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Topic == "" {
|
||||
return gerror.New("Topic不能为空")
|
||||
}
|
||||
@@ -140,9 +131,6 @@ type ImGroupTopicInp struct {
|
||||
}
|
||||
|
||||
func (in *ImGroupTopicInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Topic == "" {
|
||||
return gerror.New("Topic不能为空")
|
||||
}
|
||||
@@ -174,6 +162,92 @@ type ImGroupGovernanceModel struct {
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
|
||||
}
|
||||
|
||||
type ImGroupGovernanceUpdateInp struct {
|
||||
ImGroupTopicInp
|
||||
JoinApproval int `json:"joinApproval" dc:"入群审批"`
|
||||
MuteAll int `json:"muteAll" dc:"全员禁言"`
|
||||
MemberUploadFiles int `json:"memberUploadFiles" dc:"成员上传文件"`
|
||||
MemberPrivateChat int `json:"memberPrivateChat" dc:"成员私聊"`
|
||||
MemberInvite int `json:"memberInvite" dc:"成员邀请"`
|
||||
MaxMemberCount int `json:"maxMemberCount" dc:"最大成员数"`
|
||||
MaxAdminCount int `json:"maxAdminCount" dc:"最大管理员数"`
|
||||
}
|
||||
|
||||
func (in *ImGroupGovernanceUpdateInp) Filter(ctx context.Context) (err error) {
|
||||
if err = in.ImGroupTopicInp.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if in.JoinApproval != 0 && in.JoinApproval != 1 {
|
||||
return gerror.New("入群审批值不正确")
|
||||
}
|
||||
if in.MuteAll != 0 && in.MuteAll != 1 {
|
||||
return gerror.New("全员禁言值不正确")
|
||||
}
|
||||
if in.MemberUploadFiles != 0 && in.MemberUploadFiles != 1 {
|
||||
return gerror.New("成员上传文件值不正确")
|
||||
}
|
||||
if in.MemberPrivateChat != 0 && in.MemberPrivateChat != 1 {
|
||||
return gerror.New("成员私聊值不正确")
|
||||
}
|
||||
if in.MemberInvite != 0 && in.MemberInvite != 1 {
|
||||
return gerror.New("成员邀请值不正确")
|
||||
}
|
||||
if in.MaxMemberCount < 0 || in.MaxAdminCount < 0 {
|
||||
return gerror.New("人数上限不能小于0")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ImGroupDissolveInp struct {
|
||||
ImGroupTopicInp
|
||||
Reason string `json:"reason" dc:"解散原因"`
|
||||
}
|
||||
|
||||
func (in *ImGroupDissolveInp) Filter(ctx context.Context) (err error) {
|
||||
if err = in.ImGroupTopicInp.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if len([]rune(in.Reason)) > 200 {
|
||||
return gerror.New("解散原因不能超过200个字符")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ImGroupMuteInp struct {
|
||||
ImGroupTopicInp
|
||||
UserId int64 `json:"userId" v:"required#用户ID不能为空" dc:"用户ID"`
|
||||
ExpiresAt *gtime.Time `json:"expiresAt" dc:"过期时间"`
|
||||
Reason string `json:"reason" dc:"禁言原因"`
|
||||
}
|
||||
|
||||
func (in *ImGroupMuteInp) Filter(ctx context.Context) (err error) {
|
||||
if err = in.ImGroupTopicInp.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if in.UserId <= 0 {
|
||||
return gerror.New("用户ID不能为空")
|
||||
}
|
||||
if len([]rune(in.Reason)) > 200 {
|
||||
return gerror.New("禁言原因不能超过200个字符")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ImGroupUnmuteInp struct {
|
||||
ImGroupTopicInp
|
||||
UserId int64 `json:"userId" v:"required#用户ID不能为空" dc:"用户ID"`
|
||||
}
|
||||
|
||||
func (in *ImGroupUnmuteInp) Filter(ctx context.Context) (err error) {
|
||||
if err = in.ImGroupTopicInp.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if in.UserId <= 0 {
|
||||
return gerror.New("用户ID不能为空")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ImGroupJoinRequestListInp struct {
|
||||
form.PageReq
|
||||
ImGroupTopicInp
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Package sysin
|
||||
// @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 sysin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/model/input/form"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
const (
|
||||
ImOutboxSourceBusiness = "business"
|
||||
ImOutboxSourceGroup = "group"
|
||||
|
||||
ImOutboxStatusPending = "pending"
|
||||
ImOutboxStatusDelivered = "delivered"
|
||||
ImOutboxStatusFailed = "failed"
|
||||
ImOutboxStatusDead = "dead"
|
||||
)
|
||||
|
||||
type ImOutboxListInp struct {
|
||||
form.PageReq
|
||||
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||
Source string `json:"source" dc:"事件来源"`
|
||||
EventId string `json:"eventId" dc:"事件ID"`
|
||||
EventType string `json:"eventType" dc:"事件类型"`
|
||||
TargetType string `json:"targetType" dc:"目标类型"`
|
||||
Target string `json:"target" dc:"目标"`
|
||||
Status string `json:"status" dc:"状态"`
|
||||
CreatedAt []*gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
}
|
||||
|
||||
func (in *ImOutboxListInp) Filter(ctx context.Context) (err error) {
|
||||
if in.Source == "" {
|
||||
in.Source = ImOutboxSourceBusiness
|
||||
}
|
||||
if in.Source != ImOutboxSourceBusiness && in.Source != ImOutboxSourceGroup {
|
||||
return gerror.New("Outbox来源不正确")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ImOutboxListModel struct {
|
||||
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||
TenantName string `json:"tenantName" dc:"租户名称"`
|
||||
Source string `json:"source" dc:"事件来源"`
|
||||
Id int64 `json:"id" dc:"ID"`
|
||||
EventId string `json:"eventId" dc:"事件ID"`
|
||||
TargetType string `json:"targetType" dc:"目标类型"`
|
||||
Target string `json:"target" dc:"目标"`
|
||||
ActorUserId int64 `json:"actorUserId" dc:"操作用户ID"`
|
||||
EventType string `json:"eventType" dc:"事件类型"`
|
||||
Payload string `json:"payload" dc:"事件负载"`
|
||||
Status string `json:"status" dc:"状态"`
|
||||
Attempts int `json:"attempts" dc:"尝试次数"`
|
||||
NextRetryAt *gtime.Time `json:"nextRetryAt" dc:"下次重试时间"`
|
||||
DeliveredAt *gtime.Time `json:"deliveredAt" dc:"投递时间"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"更新时间"`
|
||||
}
|
||||
|
||||
type ImOutboxViewInp struct {
|
||||
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||
Source string `json:"source" v:"required#事件来源不能为空" dc:"事件来源"`
|
||||
Id int64 `json:"id" v:"required#ID不能为空" dc:"ID"`
|
||||
}
|
||||
|
||||
func (in *ImOutboxViewInp) Filter(ctx context.Context) (err error) {
|
||||
if in.Source != ImOutboxSourceBusiness && in.Source != ImOutboxSourceGroup {
|
||||
return gerror.New("Outbox来源不正确")
|
||||
}
|
||||
if in.Id <= 0 {
|
||||
return gerror.New("ID不能为空")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type ImOutboxViewModel struct {
|
||||
ImOutboxListModel
|
||||
}
|
||||
|
||||
type ImOutboxActionInp struct {
|
||||
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||
Source string `json:"source" v:"required#事件来源不能为空" dc:"事件来源"`
|
||||
Id int64 `json:"id" v:"required#ID不能为空" dc:"ID"`
|
||||
}
|
||||
|
||||
func (in *ImOutboxActionInp) Filter(ctx context.Context) (err error) {
|
||||
if in.Source != ImOutboxSourceBusiness && in.Source != ImOutboxSourceGroup {
|
||||
return gerror.New("Outbox来源不正确")
|
||||
}
|
||||
if in.Id <= 0 {
|
||||
return gerror.New("ID不能为空")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -26,9 +26,6 @@ type ImRealnameListInp struct {
|
||||
}
|
||||
|
||||
func (in *ImRealnameListInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -55,9 +52,6 @@ type ImRealnameViewInp struct {
|
||||
}
|
||||
|
||||
func (in *ImRealnameViewInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Id <= 0 {
|
||||
return gerror.New("ID不能为空")
|
||||
}
|
||||
@@ -78,9 +72,6 @@ type ImRealnameReviewInp struct {
|
||||
}
|
||||
|
||||
func (in *ImRealnameReviewInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Id <= 0 {
|
||||
return gerror.New("ID不能为空")
|
||||
}
|
||||
|
||||
@@ -32,9 +32,6 @@ type ImWorkOrderListInp struct {
|
||||
}
|
||||
|
||||
func (in *ImWorkOrderListInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Type == "" {
|
||||
return gerror.New("请选择工单类型")
|
||||
}
|
||||
@@ -68,9 +65,6 @@ type ImWorkOrderViewInp struct {
|
||||
}
|
||||
|
||||
func (in *ImWorkOrderViewInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Type != ImWorkOrderTypeAbuse && in.Type != ImWorkOrderTypeFeedback {
|
||||
return gerror.New("工单类型不正确")
|
||||
}
|
||||
@@ -100,9 +94,6 @@ type ImWorkOrderStatusInp struct {
|
||||
}
|
||||
|
||||
func (in *ImWorkOrderStatusInp) Filter(ctx context.Context) (err error) {
|
||||
if in.TenantId <= 0 {
|
||||
return gerror.New("请选择租户")
|
||||
}
|
||||
if in.Type != ImWorkOrderTypeAbuse && in.Type != ImWorkOrderTypeFeedback {
|
||||
return gerror.New("工单类型不正确")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Package genrouter
|
||||
// @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 genrouter
|
||||
|
||||
import "hotgo/internal/controller/admin/sys"
|
||||
|
||||
func init() {
|
||||
LoginRequiredRouter = append(LoginRequiredRouter, sys.ImOutbox) // IM Outbox
|
||||
}
|
||||
@@ -236,6 +236,14 @@ type (
|
||||
MemberList(ctx context.Context, in *sysin.ImGroupMemberListInp) (list []*sysin.ImGroupMemberListModel, totalCount int, err error)
|
||||
// Governance 获取IM群治理配置
|
||||
Governance(ctx context.Context, in *sysin.ImGroupTopicInp) (res *sysin.ImGroupGovernanceModel, err error)
|
||||
// GovernanceUpdate 更新IM群治理配置
|
||||
GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGovernanceUpdateInp) (err error)
|
||||
// Dissolve 解散IM群
|
||||
Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (err error)
|
||||
// Mute 禁言IM群成员
|
||||
Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (err error)
|
||||
// Unmute 解除IM群成员禁言
|
||||
Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (err error)
|
||||
// JoinRequestList 获取IM入群申请列表
|
||||
JoinRequestList(ctx context.Context, in *sysin.ImGroupJoinRequestListInp) (list []*sysin.ImGroupJoinRequestListModel, totalCount int, err error)
|
||||
// InvitationList 获取IM群邀请列表
|
||||
@@ -263,6 +271,16 @@ type (
|
||||
// Status 更新IM举报反馈工单状态
|
||||
Status(ctx context.Context, in *sysin.ImWorkOrderStatusInp) (err error)
|
||||
}
|
||||
ISysImOutbox interface {
|
||||
// List 获取IM Outbox列表
|
||||
List(ctx context.Context, in *sysin.ImOutboxListInp) (list []*sysin.ImOutboxListModel, totalCount int, err error)
|
||||
// View 获取IM Outbox详情
|
||||
View(ctx context.Context, in *sysin.ImOutboxViewInp) (res *sysin.ImOutboxViewModel, err error)
|
||||
// Retry 重试IM Outbox事件
|
||||
Retry(ctx context.Context, in *sysin.ImOutboxActionInp) (err error)
|
||||
// Dead 标记IM Outbox死信
|
||||
Dead(ctx context.Context, in *sysin.ImOutboxActionInp) (err error)
|
||||
}
|
||||
ISysImAuditLog interface {
|
||||
// List 获取IM审计日志列表
|
||||
List(ctx context.Context, in *sysin.ImAuditLogListInp) (list []*sysin.ImAuditLogListModel, totalCount int, err error)
|
||||
@@ -521,6 +539,7 @@ var (
|
||||
localSysImGroup ISysImGroup
|
||||
localSysImRealname ISysImRealname
|
||||
localSysImWorkOrder ISysImWorkOrder
|
||||
localSysImOutbox ISysImOutbox
|
||||
localSysImAuditLog ISysImAuditLog
|
||||
localSysDictData ISysDictData
|
||||
localSysDictType ISysDictType
|
||||
@@ -702,6 +721,17 @@ func RegisterSysImWorkOrder(i ISysImWorkOrder) {
|
||||
localSysImWorkOrder = i
|
||||
}
|
||||
|
||||
func SysImOutbox() ISysImOutbox {
|
||||
if localSysImOutbox == nil {
|
||||
panic("implement not found for interface ISysImOutbox, forgot register?")
|
||||
}
|
||||
return localSysImOutbox
|
||||
}
|
||||
|
||||
func RegisterSysImOutbox(i ISysImOutbox) {
|
||||
localSysImOutbox = i
|
||||
}
|
||||
|
||||
func SysImAuditLog() ISysImAuditLog {
|
||||
if localSysImAuditLog == nil {
|
||||
panic("implement not found for interface ISysImAuditLog, forgot register?")
|
||||
|
||||
@@ -36,6 +36,22 @@ INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type
|
||||
SELECT NULL, @groupListId, '群治理配置', 'imGroupGovernance', '', '', '3', '', '/imGroup/governance', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT('tr_', @imRootId, ' tr_', @groupDirId, ' tr_', @groupListId, ' '), '30', '', '1', @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imGroupGovernance');
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @groupListId, '更新群治理配置', 'imGroupGovernanceUpdate', '', '', '3', '', '/imGroup/governanceUpdate', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT('tr_', @imRootId, ' tr_', @groupDirId, ' tr_', @groupListId, ' '), '40', '', '1', @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imGroupGovernanceUpdate');
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @groupListId, '解散群', 'imGroupDissolve', '', '', '3', '', '/imGroup/dissolve', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT('tr_', @imRootId, ' tr_', @groupDirId, ' tr_', @groupListId, ' '), '50', '', '1', @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imGroupDissolve');
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @groupListId, '禁言群成员', 'imGroupMute', '', '', '3', '', '/imGroup/mute', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT('tr_', @imRootId, ' tr_', @groupDirId, ' tr_', @groupListId, ' '), '60', '', '1', @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imGroupMute');
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @groupListId, '解除成员禁言', 'imGroupUnmute', '', '', '3', '', '/imGroup/unmute', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT('tr_', @imRootId, ' tr_', @groupDirId, ' tr_', @groupListId, ' '), '70', '', '1', @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imGroupUnmute');
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @groupListId, '入群申请列表', 'imGroupJoinRequestList', '', '', '3', '', '/imGroup/joinRequestList', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT('tr_', @imRootId, ' tr_', @groupDirId, ' tr_', @groupListId, ' '), '40', '', '1', @now, @now
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imGroupJoinRequestList');
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
SET @now = NOW();
|
||||
|
||||
SELECT @imRootId := `id` FROM `hg_admin_menu` WHERE `name` = 'imManager' LIMIT 1;
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, '0', 'IM管理', 'imManager', '/im', '', '1', '/im/tenant/index', '', '', 'LAYOUT', '1', '', '1', '0', '', '0', '0', '0', '1', '', '200', '', '1', @now, @now
|
||||
WHERE @imRootId IS NULL;
|
||||
|
||||
SELECT @imRootId := `id` FROM `hg_admin_menu` WHERE `name` = 'imManager' LIMIT 1;
|
||||
|
||||
SELECT @outboxDirId := `id` FROM `hg_admin_menu` WHERE `name` = 'imOutbox' LIMIT 1;
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @imRootId, 'Outbox运维', 'imOutbox', 'outbox', '', '1', '/im/outbox/index', '', '', 'ParentLayout', '1', '', '0', '0', '', '0', '0', '0', '2', CONCAT('tr_', @imRootId, ' '), '65', '', '1', @now, @now
|
||||
WHERE @outboxDirId IS NULL;
|
||||
|
||||
SELECT @outboxDirId := `id` FROM `hg_admin_menu` WHERE `name` = 'imOutbox' LIMIT 1;
|
||||
|
||||
SELECT @outboxListId := `id` FROM `hg_admin_menu` WHERE `name` = 'imOutboxIndex' LIMIT 1;
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @outboxDirId, 'Outbox列表', 'imOutboxIndex', 'index', '', '2', '', '/imOutbox/list', '', '/imOutbox/index', '1', 'imOutbox', '0', '0', '', '0', '0', '0', '3', CONCAT('tr_', @imRootId, ' tr_', @outboxDirId, ' '), '10', '', '1', @now, @now
|
||||
WHERE @outboxListId IS NULL;
|
||||
|
||||
SELECT @outboxListId := `id` FROM `hg_admin_menu` WHERE `name` = 'imOutboxIndex' LIMIT 1;
|
||||
|
||||
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||
SELECT NULL, @outboxListId, title, name, '', '', '3', '', permissions, '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT('tr_', @imRootId, ' tr_', @outboxDirId, ' tr_', @outboxListId, ' '), sort, '', '1', @now, @now
|
||||
FROM (
|
||||
SELECT 'Outbox详情' title, 'imOutboxView' name, '/imOutbox/view' permissions, 10 sort UNION ALL
|
||||
SELECT '重试Outbox事件', 'imOutboxRetry', '/imOutbox/retry', 20 UNION ALL
|
||||
SELECT '标记Outbox死信', 'imOutboxDead', '/imOutbox/dead', 30
|
||||
) p
|
||||
WHERE @outboxListId IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = p.name);
|
||||
Reference in New Issue
Block a user