feat: refine IM admin management workflows
This commit is contained in:
@@ -60,7 +60,9 @@ type GovernanceUpdateReq struct {
|
||||
sysin.ImGroupGovernanceUpdateInp
|
||||
}
|
||||
|
||||
type GovernanceUpdateRes struct{}
|
||||
type GovernanceUpdateRes struct {
|
||||
*sysin.ImGroupCommandModel
|
||||
}
|
||||
|
||||
// ProfileUpdateReq 修改IM群资料
|
||||
type ProfileUpdateReq struct {
|
||||
@@ -78,7 +80,9 @@ type DissolveReq struct {
|
||||
sysin.ImGroupDissolveInp
|
||||
}
|
||||
|
||||
type DissolveRes struct{}
|
||||
type DissolveRes struct {
|
||||
*sysin.ImGroupCommandModel
|
||||
}
|
||||
|
||||
// MuteReq 禁言IM群成员
|
||||
type MuteReq struct {
|
||||
@@ -86,7 +90,9 @@ type MuteReq struct {
|
||||
sysin.ImGroupMuteInp
|
||||
}
|
||||
|
||||
type MuteRes struct{}
|
||||
type MuteRes struct {
|
||||
*sysin.ImGroupCommandModel
|
||||
}
|
||||
|
||||
// MemberMuteReq Kafka禁言IM群成员
|
||||
type MemberMuteReq struct {
|
||||
@@ -104,7 +110,9 @@ type UnmuteReq struct {
|
||||
sysin.ImGroupUnmuteInp
|
||||
}
|
||||
|
||||
type UnmuteRes struct{}
|
||||
type UnmuteRes struct {
|
||||
*sysin.ImGroupCommandModel
|
||||
}
|
||||
|
||||
// MemberUnmuteReq Kafka解除IM群成员禁言
|
||||
type MemberUnmuteReq struct {
|
||||
@@ -148,7 +156,9 @@ type RoleUpdateReq struct {
|
||||
sysin.ImGroupRoleUpdateInp
|
||||
}
|
||||
|
||||
type RoleUpdateRes struct{}
|
||||
type RoleUpdateRes struct {
|
||||
*sysin.ImGroupCommandModel
|
||||
}
|
||||
|
||||
// AdminSetReq 设置IM群管理员
|
||||
type AdminSetReq struct {
|
||||
@@ -176,7 +186,9 @@ type OwnerTransferReq struct {
|
||||
sysin.ImGroupOwnerTransferInp
|
||||
}
|
||||
|
||||
type OwnerTransferRes struct{}
|
||||
type OwnerTransferRes struct {
|
||||
*sysin.ImGroupCommandModel
|
||||
}
|
||||
|
||||
// JoinRequestListReq 查询IM入群申请列表
|
||||
type JoinRequestListReq struct {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gcmd"
|
||||
"github.com/gogf/gf/v2/util/gmode"
|
||||
"hotgo/internal/controller/health"
|
||||
"hotgo/internal/library/addons"
|
||||
"hotgo/internal/library/casbin"
|
||||
"hotgo/internal/library/hggen"
|
||||
@@ -33,6 +34,7 @@ var (
|
||||
|
||||
// 请求响应结束后回调
|
||||
s.BindHookHandler("/*any", ghttp.HookAfterOutput, service.Hook().AfterOutput)
|
||||
s.BindHandler("/healthz", health.Serve)
|
||||
|
||||
// 注册全局中间件
|
||||
s.BindMiddleware("/*any", []ghttp.HandlerFunc{
|
||||
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
ImAdminCommandGroupMemberUnmute = "group.member.unmute"
|
||||
ImAdminCommandGroupMemberRemove = "group.member.remove"
|
||||
ImAdminCommandGroupOwnerTransfer = "group.owner.transfer"
|
||||
ImAdminCommandGroupDissolve = "group.dissolve"
|
||||
ImAdminCommandUserBan = "user.ban"
|
||||
ImAdminCommandUserUnban = "user.unban"
|
||||
ImAdminCommandUserFreeze = "user.freeze"
|
||||
|
||||
@@ -77,7 +77,12 @@ func (c *cImGroup) Governance(ctx context.Context, req *imgroup.GovernanceReq) (
|
||||
|
||||
// GovernanceUpdate 更新IM群治理配置
|
||||
func (c *cImGroup) GovernanceUpdate(ctx context.Context, req *imgroup.GovernanceUpdateReq) (res *imgroup.GovernanceUpdateRes, err error) {
|
||||
err = service.SysImGroup().GovernanceUpdate(ctx, &req.ImGroupGovernanceUpdateInp)
|
||||
data, err := service.SysImGroup().GovernanceUpdate(ctx, &req.ImGroupGovernanceUpdateInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = new(imgroup.GovernanceUpdateRes)
|
||||
res.ImGroupCommandModel = data
|
||||
return
|
||||
}
|
||||
|
||||
@@ -94,13 +99,23 @@ func (c *cImGroup) ProfileUpdate(ctx context.Context, req *imgroup.ProfileUpdate
|
||||
|
||||
// Dissolve 解散IM群
|
||||
func (c *cImGroup) Dissolve(ctx context.Context, req *imgroup.DissolveReq) (res *imgroup.DissolveRes, err error) {
|
||||
err = service.SysImGroup().Dissolve(ctx, &req.ImGroupDissolveInp)
|
||||
data, err := service.SysImGroup().Dissolve(ctx, &req.ImGroupDissolveInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = new(imgroup.DissolveRes)
|
||||
res.ImGroupCommandModel = data
|
||||
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)
|
||||
data, err := service.SysImGroup().Mute(ctx, &req.ImGroupMuteInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = new(imgroup.MuteRes)
|
||||
res.ImGroupCommandModel = data
|
||||
return
|
||||
}
|
||||
|
||||
@@ -117,7 +132,12 @@ func (c *cImGroup) MemberMute(ctx context.Context, req *imgroup.MemberMuteReq) (
|
||||
|
||||
// Unmute 解除IM群成员禁言
|
||||
func (c *cImGroup) Unmute(ctx context.Context, req *imgroup.UnmuteReq) (res *imgroup.UnmuteRes, err error) {
|
||||
err = service.SysImGroup().Unmute(ctx, &req.ImGroupUnmuteInp)
|
||||
data, err := service.SysImGroup().Unmute(ctx, &req.ImGroupUnmuteInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = new(imgroup.UnmuteRes)
|
||||
res.ImGroupCommandModel = data
|
||||
return
|
||||
}
|
||||
|
||||
@@ -157,7 +177,12 @@ func (c *cImGroup) JoinRequestReject(ctx context.Context, req *imgroup.JoinReque
|
||||
|
||||
// RoleUpdate 更新IM群成员角色
|
||||
func (c *cImGroup) RoleUpdate(ctx context.Context, req *imgroup.RoleUpdateReq) (res *imgroup.RoleUpdateRes, err error) {
|
||||
err = service.SysImGroup().RoleUpdate(ctx, &req.ImGroupRoleUpdateInp)
|
||||
data, err := service.SysImGroup().RoleUpdate(ctx, &req.ImGroupRoleUpdateInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = new(imgroup.RoleUpdateRes)
|
||||
res.ImGroupCommandModel = data
|
||||
return
|
||||
}
|
||||
|
||||
@@ -185,7 +210,12 @@ func (c *cImGroup) AdminRemove(ctx context.Context, req *imgroup.AdminRemoveReq)
|
||||
|
||||
// OwnerTransfer 转让IM群主
|
||||
func (c *cImGroup) OwnerTransfer(ctx context.Context, req *imgroup.OwnerTransferReq) (res *imgroup.OwnerTransferRes, err error) {
|
||||
err = service.SysImGroup().OwnerTransfer(ctx, &req.ImGroupOwnerTransferInp)
|
||||
data, err := service.SysImGroup().OwnerTransfer(ctx, &req.ImGroupOwnerTransferInp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
res = new(imgroup.OwnerTransferRes)
|
||||
res.ImGroupCommandModel = data
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
|
||||
"hotgo/internal/library/queue"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
Status string `json:"status"`
|
||||
Checks map[string]string `json:"checks"`
|
||||
}
|
||||
|
||||
// Serve exposes an unauthenticated dependency health endpoint for container probes.
|
||||
func Serve(r *ghttp.Request) {
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
r.Response.Header().Set("Allow", http.MethodGet+", "+http.MethodHead)
|
||||
r.Response.WriteStatusExit(http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
checks := map[string]string{}
|
||||
statusCode := http.StatusOK
|
||||
|
||||
if err := g.DB().Ctx(ctx).Raw("SELECT 1").Scan(new(int)); err != nil {
|
||||
checks["mysql"] = err.Error()
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
} else {
|
||||
checks["mysql"] = "ok"
|
||||
}
|
||||
|
||||
if _, err := g.Redis().Do(ctx, "PING"); err != nil {
|
||||
checks["redis"] = err.Error()
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
} else {
|
||||
checks["redis"] = "ok"
|
||||
}
|
||||
|
||||
if strings.EqualFold(strings.TrimSpace(g.Cfg().MustGet(ctx, "queue.driver").String()), "kafka") {
|
||||
if err := queue.KafkaHealthCheck(ctx); err != nil {
|
||||
checks["kafka"] = err.Error()
|
||||
statusCode = http.StatusServiceUnavailable
|
||||
} else {
|
||||
checks["kafka"] = "ok"
|
||||
}
|
||||
} else {
|
||||
checks["kafka"] = "disabled"
|
||||
}
|
||||
|
||||
body := response{Status: "ok", Checks: checks}
|
||||
if statusCode != http.StatusOK {
|
||||
body.Status = "error"
|
||||
}
|
||||
r.Response.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
r.Response.WriteHeader(statusCode)
|
||||
if r.Method != http.MethodHead {
|
||||
r.Response.WriteJsonExit(body)
|
||||
return
|
||||
}
|
||||
r.Exit()
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"hash"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -270,6 +271,46 @@ func configureKafkaSASL(conf *sarama.Config, connOpt KafkaConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// KafkaHealthCheck verifies broker connectivity by creating a short-lived Sarama client.
|
||||
func KafkaHealthCheck(ctx context.Context) error {
|
||||
if len(config.Kafka.Address) == 0 {
|
||||
return errors.New("queue kafka address is empty")
|
||||
}
|
||||
kfkVersion, err := parseKafkaVersion(config.Kafka.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !validateVersion(kfkVersion) {
|
||||
kfkVersion = sarama.V2_4_0_0
|
||||
}
|
||||
conf := sarama.NewConfig()
|
||||
conf.Version = kfkVersion
|
||||
conf.ClientID = "hotgo-healthz"
|
||||
conf.Net.DialTimeout = 2 * time.Second
|
||||
conf.Net.ReadTimeout = 2 * time.Second
|
||||
conf.Net.WriteTimeout = 2 * time.Second
|
||||
configureKafkaSASL(conf, KafkaConfig{
|
||||
UserName: config.Kafka.Username,
|
||||
Password: config.Kafka.Password,
|
||||
Mechanism: config.Kafka.Mechanism,
|
||||
})
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
client, err := sarama.NewClient(config.Kafka.Address, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = client.Close() }()
|
||||
if len(client.Brokers()) == 0 {
|
||||
return errors.New("no kafka brokers available")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scramClientGenerator(hashFunc func() hash.Hash) func() sarama.SCRAMClient {
|
||||
return func() sarama.SCRAMClient {
|
||||
return &xdgSCRAMClient{HashGeneratorFcn: scram.HashGeneratorFcn(hashFunc)}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (s *sSysImGroup) groupModel(ctx context.Context, db gdb.DB) *gdb.Model {
|
||||
"t.trusted trusted_json",
|
||||
"t.tags tags_json",
|
||||
"t.aux aux_json",
|
||||
"COALESCE(gs.name, '') group_name",
|
||||
"COALESCE(NULLIF(gs.name, ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.fn')), ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.name')), ''), t.name) group_name",
|
||||
"COALESCE(gs.avatar_file_id, '') avatar_file_id",
|
||||
"COALESCE(gs.description, '') description",
|
||||
"COALESCE(gs.group_status, '') group_status",
|
||||
@@ -161,7 +161,7 @@ func (s *sSysImGroup) View(ctx context.Context, in *sysin.ImGroupViewInp) (res *
|
||||
"t.aux aux_json",
|
||||
"t.access access_json",
|
||||
"t.usebt use_bt",
|
||||
"COALESCE(gs.name, '') group_name",
|
||||
"COALESCE(NULLIF(gs.name, ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.fn')), ''), NULLIF(JSON_UNQUOTE(JSON_EXTRACT(t.public, '$.name')), ''), t.name) group_name",
|
||||
"COALESCE(gs.avatar_file_id, '') avatar_file_id",
|
||||
"COALESCE(gs.description, '') description",
|
||||
"COALESCE(gs.group_status, '') group_status",
|
||||
@@ -349,50 +349,24 @@ func (s *sSysImGroup) Governance(ctx context.Context, in *sysin.ImGroupTopicInp)
|
||||
}
|
||||
|
||||
// GovernanceUpdate 更新IM群治理配置
|
||||
func (s *sSysImGroup) GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGovernanceUpdateInp) (err error) {
|
||||
func (s *sSysImGroup) GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGovernanceUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"governance": map[string]any{
|
||||
"join_approval": in.JoinApproval == 1,
|
||||
"mute_all": in.MuteAll == 1,
|
||||
"member_upload_files": in.MemberUploadFiles == 1,
|
||||
"member_private_chat": in.MemberPrivateChat == 1,
|
||||
"member_invite": in.MemberInvite == 1,
|
||||
"max_member_count": in.MaxMemberCount,
|
||||
"max_admin_count": in.MaxAdminCount,
|
||||
},
|
||||
}
|
||||
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,
|
||||
})
|
||||
})
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupGovernUpdate, payload, 0)
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) ProfileUpdate(ctx context.Context, in *sysin.ImGroupProfileUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
@@ -412,101 +386,21 @@ func (s *sSysImGroup) ProfileUpdate(ctx context.Context, in *sysin.ImGroupProfil
|
||||
}
|
||||
|
||||
// Dissolve 解散IM群
|
||||
func (s *sSysImGroup) Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (err error) {
|
||||
func (s *sSysImGroup) Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"reason": in.Reason,
|
||||
}
|
||||
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,
|
||||
})
|
||||
})
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupDissolve, payload, 0)
|
||||
}
|
||||
|
||||
// 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,
|
||||
})
|
||||
})
|
||||
func (s *sSysImGroup) Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
return s.MemberMute(ctx, in)
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) MemberMute(ctx context.Context, in *sysin.ImGroupMuteInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
@@ -526,49 +420,8 @@ func (s *sSysImGroup) MemberMute(ctx context.Context, in *sysin.ImGroupMuteInp)
|
||||
}
|
||||
|
||||
// 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) Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
return s.MemberUnmute(ctx, in)
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) MemberUnmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
@@ -677,77 +530,18 @@ func (s *sSysImGroup) resolveJoinRequest(ctx context.Context, in *sysin.ImGroupJ
|
||||
}
|
||||
|
||||
// RoleUpdate 更新IM群成员角色
|
||||
func (s *sSysImGroup) RoleUpdate(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (err error) {
|
||||
func (s *sSysImGroup) RoleUpdate(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
if in.Role != consts.ImGroupRoleAdmin && in.Role != consts.ImGroupRoleMember {
|
||||
return gerror.New("只能设置管理员或普通成员")
|
||||
switch in.Role {
|
||||
case consts.ImGroupRoleAdmin:
|
||||
return s.AdminSet(ctx, in)
|
||||
case consts.ImGroupRoleMember:
|
||||
return s.AdminRemove(ctx, in)
|
||||
default:
|
||||
return nil, gerror.New("只能设置管理员或普通成员")
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
actor := contexts.GetUserId(ctx)
|
||||
requestId := gctx.CtxId(ctx)
|
||||
if requestId == "" {
|
||||
requestId = guid.S()
|
||||
}
|
||||
modeGiven := imGroupModeMember
|
||||
action := "member_role_member"
|
||||
if in.Role == consts.ImGroupRoleAdmin {
|
||||
modeGiven = imGroupModeAdmin
|
||||
action = "member_role_admin"
|
||||
}
|
||||
now := gtime.Now()
|
||||
return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
if err := s.ensureActiveGroup(ctx, tx, in.Topic); err != nil {
|
||||
return err
|
||||
}
|
||||
before, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if before == nil || before["member_status"].String() != consts.ImGroupMemberStatusActive {
|
||||
return gerror.New("成员不在群内或不是正常状态")
|
||||
}
|
||||
if before["role"].String() == consts.ImGroupRoleOwner {
|
||||
return gerror.New("群主角色请通过群主转让处理")
|
||||
}
|
||||
if in.Role == consts.ImGroupRoleAdmin {
|
||||
if txErr = s.ensureAdminLimit(ctx, tx, in.Topic, in.UserId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
}
|
||||
subResult, txErr := tx.Exec(`UPDATE im_subscriptions SET modegiven=?,updatedat=?
|
||||
WHERE topic=? AND userid=? AND deletedat IS NULL`, modeGiven, now, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if affected, _ := subResult.RowsAffected(); affected == 0 {
|
||||
return gerror.New("成员通信订阅不存在")
|
||||
}
|
||||
result, txErr := tx.Exec(`UPDATE im_group_members
|
||||
SET role=?,version=version+1,updated_at=?
|
||||
WHERE topic=? AND user_id=? AND member_status=?`,
|
||||
in.Role, now, in.Topic, in.UserId, consts.ImGroupMemberStatusActive)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if affected, _ := result.RowsAffected(); affected == 0 {
|
||||
return gerror.New("成员不在群内或不是正常状态")
|
||||
}
|
||||
after, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, action, "member", fmt.Sprintf("%d", in.UserId), recordMap(before), recordMap(after), requestId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.role.updated", g.Map{
|
||||
"topic": in.Topic, "user_id": in.UserId, "role": in.Role, "request_id": requestId,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) AdminSet(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
@@ -775,105 +569,16 @@ func (s *sSysImGroup) AdminRemove(ctx context.Context, in *sysin.ImGroupRoleUpda
|
||||
}
|
||||
|
||||
// OwnerTransfer 转让IM群主
|
||||
func (s *sSysImGroup) OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerTransferInp) (err error) {
|
||||
func (s *sSysImGroup) OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerTransferInp) (res *sysin.ImGroupCommandModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
db, _, err := resolveImTenantDB(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
payload := map[string]any{
|
||||
"topic": in.Topic,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"target_user_id": strconv.FormatInt(in.UserId, 10),
|
||||
}
|
||||
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 {
|
||||
beforeGroup, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if beforeGroup == nil {
|
||||
return gerror.New("群治理配置不存在")
|
||||
}
|
||||
if beforeGroup["group_status"].String() == consts.ImGroupStatusDissolved {
|
||||
return gerror.New("已解散群不能转让群主")
|
||||
}
|
||||
previousOwner := beforeGroup["owner_user_id"].Int64()
|
||||
if previousOwner <= 0 {
|
||||
return gerror.New("原群主不存在")
|
||||
}
|
||||
if previousOwner == in.UserId {
|
||||
return gerror.New("新群主不能与原群主相同")
|
||||
}
|
||||
if txErr = s.ensureActiveGroupMember(ctx, tx, in.Topic, previousOwner); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if txErr = s.ensureActiveGroupMember(ctx, tx, in.Topic, in.UserId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
beforeOld, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, previousOwner)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
beforeNew, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
oldSubResult, txErr := tx.Exec(`UPDATE im_subscriptions SET modegiven=?,updatedat=?
|
||||
WHERE topic=? AND userid=? AND deletedat IS NULL`, imGroupModeAdmin, now, in.Topic, previousOwner)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if affected, _ := oldSubResult.RowsAffected(); affected == 0 {
|
||||
return gerror.New("原群主通信订阅不存在")
|
||||
}
|
||||
if _, txErr = tx.Exec(`UPDATE im_group_members SET role=?,version=version+1,updated_at=?
|
||||
WHERE topic=? AND user_id=? AND member_status=?`, consts.ImGroupRoleAdmin, now, in.Topic, previousOwner, consts.ImGroupMemberStatusActive); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
newSubResult, txErr := tx.Exec(`UPDATE im_subscriptions SET modegiven=?,updatedat=?
|
||||
WHERE topic=? AND userid=? AND deletedat IS NULL`, imGroupModeOwner, now, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if affected, _ := newSubResult.RowsAffected(); affected == 0 {
|
||||
return gerror.New("新群主通信订阅不存在")
|
||||
}
|
||||
if _, txErr = tx.Exec(`UPDATE im_group_members SET role=?,version=version+1,updated_at=?
|
||||
WHERE topic=? AND user_id=? AND member_status=?`, consts.ImGroupRoleOwner, now, in.Topic, in.UserId, consts.ImGroupMemberStatusActive); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if _, txErr = tx.Exec(`UPDATE im_topics SET owner=?,updatedat=? WHERE name=?`, in.UserId, now, in.Topic); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if _, txErr = tx.Exec(`UPDATE im_group_settings SET owner_user_id=?,version=version+1,updated_at=? WHERE topic=?`, in.UserId, now, in.Topic); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
afterGroup, txErr := s.groupGovernanceSnapshot(ctx, tx, in.Topic)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
afterOld, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, previousOwner)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
afterNew, txErr := s.groupMemberSnapshot(ctx, tx, in.Topic, in.UserId)
|
||||
if txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
if txErr = s.insertGroupAdminLog(ctx, tx, in.Topic, actor, in.UserId, "owner_transfer", "group", in.Topic, g.Map{
|
||||
"group": recordMap(beforeGroup), "previous_owner": recordMap(beforeOld), "new_owner": recordMap(beforeNew),
|
||||
}, g.Map{
|
||||
"group": recordMap(afterGroup), "previous_owner": recordMap(afterOld), "new_owner": recordMap(afterNew),
|
||||
}, requestId); txErr != nil {
|
||||
return txErr
|
||||
}
|
||||
return s.insertGroupEvent(ctx, tx, in.Topic, actor, "group.owner.transferred", g.Map{
|
||||
"topic": in.Topic, "previous_owner": previousOwner, "owner": in.UserId, "request_id": requestId,
|
||||
})
|
||||
})
|
||||
return s.publishGroupCommand(ctx, in.TenantId, in.Topic, consts.ImAdminCommandGroupOwnerTransfer, payload, 0)
|
||||
}
|
||||
|
||||
func (s *sSysImGroup) publishGroupCommand(ctx context.Context, tenantId int64, topic string, eventType string, payload map[string]any, version int64) (*sysin.ImGroupCommandModel, error) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imadmin"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
@@ -483,21 +482,21 @@ func (s *sSysImUser) ResetPassword(ctx context.Context, in *sysin.ImUserPassword
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userId, err := sysin.ParseOptionalInt64(in.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
command, err := s.publishPasswordResetCommand(ctx, tenant, userId, in.NewPassword)
|
||||
res, err = s.syncPasswordReset(ctx, in, userId)
|
||||
result := "success"
|
||||
if err != nil {
|
||||
result = "failed"
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenant.Id, "user_password_reset", "user", userId, result, nil)
|
||||
return command, err
|
||||
tenantId := in.TenantId
|
||||
if res != nil && res.TenantID > 0 {
|
||||
tenantId = res.TenantID
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantId, "user_password_reset", "user", userId, result, nil)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (s *sSysImUser) RevokeSessions(ctx context.Context, in *sysin.ImUserRevokeSessionsInp) (err error) {
|
||||
@@ -686,59 +685,53 @@ func (s *sSysImUser) Devices(ctx context.Context, in *sysin.ImUserDeviceListInp)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) Ban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserBan)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserBan)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) Unban(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnban)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnban)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) Freeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserFreeze)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserFreeze)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) Unfreeze(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnfreeze)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserUnfreeze)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) DeviceKick(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
if err = in.FilterDevice(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserDeviceKick)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserDeviceKick)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) LogoutAll(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserLogoutAll)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserLogoutAll)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) AccountCancel(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserCancel)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserCancel)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) AccountDelete(ctx context.Context, in *sysin.ImUserGovernanceInp) (res *sysin.ImUserCommandModel, err error) {
|
||||
in.Hard = true
|
||||
return s.publishGovernanceCommand(ctx, in, consts.ImAdminCommandUserDelete)
|
||||
return s.syncGovernanceCommand(ctx, in, consts.ImAdminCommandUserDelete)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) publishGovernanceCommand(ctx context.Context, in *sysin.ImUserGovernanceInp, eventType string) (*sysin.ImUserCommandModel, error) {
|
||||
func (s *sSysImUser) syncGovernanceCommand(ctx context.Context, in *sysin.ImUserGovernanceInp, eventType string) (*sysin.ImUserCommandModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, tenant, err := imtenant.DB(ctx, in.TenantId)
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actorName := ""
|
||||
if user != nil {
|
||||
actorName = user.RealName
|
||||
}
|
||||
payload := map[string]any{
|
||||
"actor_user_id": strconv.FormatInt(in.ActorUserId, 10),
|
||||
"user_id": strconv.FormatInt(in.UserId, 10),
|
||||
"target_user_id": strconv.FormatInt(in.UserId, 10),
|
||||
"reason": in.Reason,
|
||||
"event_type": eventType,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"reason": in.Reason,
|
||||
}
|
||||
if in.DeviceId != "" {
|
||||
payload["device_id"] = in.DeviceId
|
||||
@@ -746,75 +739,51 @@ func (s *sSysImUser) publishGovernanceCommand(ctx context.Context, in *sysin.ImU
|
||||
if eventType == consts.ImAdminCommandUserCancel || eventType == consts.ImAdminCommandUserDelete {
|
||||
payload["hard"] = in.Hard
|
||||
}
|
||||
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
|
||||
TenantID: tenant.Id,
|
||||
TenantCode: tenant.Code,
|
||||
EventType: eventType,
|
||||
BizType: "user",
|
||||
BizID: strconv.FormatInt(in.UserId, 10),
|
||||
Actor: &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
DisplayName: actorName,
|
||||
},
|
||||
Payload: payload,
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
res, err := s.syncUserAction(ctx, tenant, in.UserId, eventType, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImUserCommandModel{
|
||||
CommandID: command.CommandID,
|
||||
EventID: command.EventID,
|
||||
Status: command.Status,
|
||||
TenantID: command.TenantID,
|
||||
TenantCode: command.TenantCode,
|
||||
EventType: command.EventType,
|
||||
IdempotencyKey: command.IdempotencyKey,
|
||||
}, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *sSysImUser) publishPasswordResetCommand(ctx context.Context, tenant *entity.ImTenant, userId int64, newPassword string) (*sysin.ImUserCommandModel, error) {
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||
func (s *sSysImUser) syncPasswordReset(ctx context.Context, in *sysin.ImUserPasswordResetInp, userId int64) (*sysin.ImUserCommandModel, error) {
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(in.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "生成密码摘要失败")
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actorName := ""
|
||||
if user != nil {
|
||||
actorName = user.RealName
|
||||
}
|
||||
payload := map[string]any{
|
||||
"operator_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"user_id": strconv.FormatInt(userId, 10),
|
||||
"target_user_id": strconv.FormatInt(userId, 10),
|
||||
"password_hash": base64.StdEncoding.EncodeToString(passwordHash),
|
||||
"event_type": consts.ImAdminCommandUserPasswordReset,
|
||||
"actor_user_id": strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
"password_hash": base64.StdEncoding.EncodeToString(passwordHash),
|
||||
}
|
||||
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
|
||||
TenantID: tenant.Id,
|
||||
TenantCode: tenant.Code,
|
||||
EventType: consts.ImAdminCommandUserPasswordReset,
|
||||
BizType: "user",
|
||||
BizID: strconv.FormatInt(userId, 10),
|
||||
Actor: &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
DisplayName: actorName,
|
||||
},
|
||||
Payload: payload,
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
return s.syncUserAction(ctx, tenant, userId, consts.ImAdminCommandUserPasswordReset, payload)
|
||||
}
|
||||
|
||||
func (s *sSysImUser) syncUserAction(ctx context.Context, tenant *entity.ImTenant, userId int64, eventType string, payload map[string]any) (*sysin.ImUserCommandModel, error) {
|
||||
var out struct {
|
||||
UserId int64 `json:"user_id"`
|
||||
EventType string `json:"event_type"`
|
||||
Status string `json:"status"`
|
||||
Result map[string]any `json:"result"`
|
||||
}
|
||||
path := "/internal/admin-query/tenants/" + url.PathEscape(tenant.Code) + "/users/" + strconv.FormatInt(userId, 10) + "/action"
|
||||
err := imadmin.DefaultClient(ctx).InternalPost(ctx, path, payload, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status := out.Status
|
||||
if status == "" {
|
||||
status = consts.ImAdminCommandStatusSucceeded
|
||||
}
|
||||
return &sysin.ImUserCommandModel{
|
||||
CommandID: command.CommandID,
|
||||
EventID: command.EventID,
|
||||
Status: command.Status,
|
||||
TenantID: command.TenantID,
|
||||
TenantCode: command.TenantCode,
|
||||
EventType: command.EventType,
|
||||
IdempotencyKey: command.IdempotencyKey,
|
||||
Status: status,
|
||||
TenantID: tenant.Id,
|
||||
TenantCode: tenant.Code,
|
||||
EventType: eventType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ type ImGroupListModel struct {
|
||||
TenantName string `json:"tenantName" dc:"租户名称"`
|
||||
Topic string `json:"topic" dc:"Topic"`
|
||||
State int `json:"state" dc:"状态"`
|
||||
OwnerId int64 `json:"ownerId" dc:"群主ID"`
|
||||
OwnerId int64 `json:"ownerId,string" dc:"群主ID"`
|
||||
OwnerNickname string `json:"ownerNickname" dc:"群主昵称"`
|
||||
OwnerUsername string `json:"ownerUsername" dc:"群主用户名"`
|
||||
SeqId int `json:"seqId" dc:"最新消息序号"`
|
||||
@@ -101,7 +101,7 @@ func (in *ImGroupMemberListInp) Filter(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
type ImGroupMemberListModel struct {
|
||||
UserId int64 `json:"userId" dc:"用户ID"`
|
||||
UserId int64 `json:"userId,string" dc:"用户ID"`
|
||||
Nickname string `json:"nickname" dc:"昵称"`
|
||||
State int `json:"state" dc:"用户状态"`
|
||||
ModeWant string `json:"modeWant" dc:"期望权限"`
|
||||
@@ -119,10 +119,10 @@ type ImGroupMemberListModel struct {
|
||||
Pinned int `json:"pinned" dc:"置顶"`
|
||||
MuteNotifications int `json:"muteNotifications" dc:"免打扰"`
|
||||
JoinSource string `json:"joinSource" dc:"加入来源"`
|
||||
JoinedBy int64 `json:"joinedBy" dc:"邀请/处理人"`
|
||||
JoinedBy int64 `json:"joinedBy,string" dc:"邀请/处理人"`
|
||||
JoinedAt *gtime.Time `json:"joinedAt" dc:"业务加入时间"`
|
||||
LeftAt *gtime.Time `json:"leftAt" dc:"离开时间"`
|
||||
RemovedBy int64 `json:"removedBy" dc:"移除人"`
|
||||
RemovedBy int64 `json:"removedBy,string" dc:"移除人"`
|
||||
Version int `json:"version" dc:"版本"`
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ type ImGroupGovernanceModel struct {
|
||||
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||
TenantName string `json:"tenantName" dc:"租户名称"`
|
||||
Topic string `json:"topic" dc:"Topic"`
|
||||
OwnerUserId int64 `json:"ownerUserId" dc:"群主ID"`
|
||||
OwnerUserId int64 `json:"ownerUserId,string" dc:"群主ID"`
|
||||
Name string `json:"name" dc:"群名称"`
|
||||
AvatarFileId string `json:"avatarFileId" dc:"头像文件ID"`
|
||||
Description string `json:"description" dc:"简介"`
|
||||
@@ -157,8 +157,8 @@ type ImGroupGovernanceModel struct {
|
||||
MaxAdminCount int `json:"maxAdminCount" dc:"最大管理员数"`
|
||||
MemberCount int `json:"memberCount" dc:"成员数"`
|
||||
Version int `json:"version" dc:"版本"`
|
||||
CreatedBy int64 `json:"createdBy" dc:"创建人"`
|
||||
DissolvedBy int64 `json:"dissolvedBy" dc:"解散人"`
|
||||
CreatedBy int64 `json:"createdBy,string" dc:"创建人"`
|
||||
DissolvedBy int64 `json:"dissolvedBy,string" dc:"解散人"`
|
||||
DissolvedAt *gtime.Time `json:"dissolvedAt" dc:"解散时间"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
|
||||
@@ -338,13 +338,13 @@ type ImGroupJoinRequestListModel struct {
|
||||
Id int64 `json:"id" dc:"ID"`
|
||||
RequestId string `json:"requestId" dc:"申请ID"`
|
||||
Topic string `json:"topic" dc:"Topic"`
|
||||
ApplicantUserId int64 `json:"applicantUserId" dc:"申请人ID"`
|
||||
InviterUserId int64 `json:"inviterUserId" dc:"邀请人ID"`
|
||||
ApplicantUserId int64 `json:"applicantUserId,string" dc:"申请人ID"`
|
||||
InviterUserId int64 `json:"inviterUserId,string" dc:"邀请人ID"`
|
||||
Source string `json:"source" dc:"来源"`
|
||||
Message string `json:"message" dc:"申请消息"`
|
||||
Status string `json:"status" dc:"状态"`
|
||||
RejectReason string `json:"rejectReason" dc:"拒绝原因"`
|
||||
HandledBy int64 `json:"handledBy" dc:"处理人"`
|
||||
HandledBy int64 `json:"handledBy,string" dc:"处理人"`
|
||||
HandledAt *gtime.Time `json:"handledAt" dc:"处理时间"`
|
||||
ExpiresAt *gtime.Time `json:"expiresAt" dc:"过期时间"`
|
||||
Version int `json:"version" dc:"版本"`
|
||||
@@ -368,13 +368,13 @@ type ImGroupFileListModel struct {
|
||||
FileId string `json:"fileId" dc:"文件ID"`
|
||||
SourceTopic string `json:"sourceTopic" dc:"来源Topic"`
|
||||
SourceSeq int `json:"sourceSeq" dc:"来源消息序号"`
|
||||
UploaderId int64 `json:"uploaderId" dc:"上传人ID"`
|
||||
UploaderId int64 `json:"uploaderId,string" dc:"上传人ID"`
|
||||
FileName string `json:"fileName" dc:"文件名"`
|
||||
MimeType string `json:"mimeType" dc:"MIME类型"`
|
||||
FileType string `json:"fileType" dc:"文件类型"`
|
||||
FileSize int64 `json:"fileSize" dc:"文件大小"`
|
||||
Status string `json:"status" dc:"状态"`
|
||||
DeletedBy int64 `json:"deletedBy" dc:"删除人"`
|
||||
DeletedBy int64 `json:"deletedBy,string" dc:"删除人"`
|
||||
DeletedAt *gtime.Time `json:"deletedAt" dc:"删除时间"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
|
||||
@@ -407,8 +407,8 @@ type ImGroupInvitationListModel struct {
|
||||
Id int64 `json:"id" dc:"ID"`
|
||||
InvitationId string `json:"invitationId" dc:"邀请ID"`
|
||||
Topic string `json:"topic" dc:"Topic"`
|
||||
InviterUserId int64 `json:"inviterUserId" dc:"邀请人ID"`
|
||||
TargetUserId int64 `json:"targetUserId" dc:"目标用户ID"`
|
||||
InviterUserId int64 `json:"inviterUserId,string" dc:"邀请人ID"`
|
||||
TargetUserId int64 `json:"targetUserId,string" dc:"目标用户ID"`
|
||||
Message string `json:"message" dc:"邀请消息"`
|
||||
Status string `json:"status" dc:"状态"`
|
||||
Result string `json:"result" dc:"结果"`
|
||||
@@ -429,12 +429,12 @@ type ImGroupAnnouncementListModel struct {
|
||||
Id int64 `json:"id" dc:"ID"`
|
||||
AnnouncementId string `json:"announcementId" dc:"公告ID"`
|
||||
Topic string `json:"topic" dc:"Topic"`
|
||||
AuthorUserId int64 `json:"authorUserId" dc:"发布人ID"`
|
||||
AuthorUserId int64 `json:"authorUserId,string" dc:"发布人ID"`
|
||||
Content string `json:"content" dc:"内容"`
|
||||
Status string `json:"status" dc:"状态"`
|
||||
Version int `json:"version" dc:"版本"`
|
||||
PublishedAt *gtime.Time `json:"publishedAt" dc:"发布时间"`
|
||||
DeletedBy int64 `json:"deletedBy" dc:"删除人"`
|
||||
DeletedBy int64 `json:"deletedBy,string" dc:"删除人"`
|
||||
DeletedAt *gtime.Time `json:"deletedAt" dc:"删除时间"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
|
||||
@@ -449,13 +449,13 @@ type ImGroupMuteListInp struct {
|
||||
|
||||
type ImGroupMuteListModel struct {
|
||||
Topic string `json:"topic" dc:"Topic"`
|
||||
UserId int64 `json:"userId" dc:"用户ID"`
|
||||
MutedBy int64 `json:"mutedBy" dc:"禁言人"`
|
||||
UserId int64 `json:"userId,string" dc:"用户ID"`
|
||||
MutedBy int64 `json:"mutedBy,string" dc:"禁言人"`
|
||||
MutedAt *gtime.Time `json:"mutedAt" dc:"禁言时间"`
|
||||
ExpiresAt *gtime.Time `json:"expiresAt" dc:"过期时间"`
|
||||
Reason string `json:"reason" dc:"原因"`
|
||||
Status string `json:"status" dc:"状态"`
|
||||
RevokedBy int64 `json:"revokedBy" dc:"解除人"`
|
||||
RevokedBy int64 `json:"revokedBy,string" dc:"解除人"`
|
||||
RevokedAt *gtime.Time `json:"revokedAt" dc:"解除时间"`
|
||||
Version int `json:"version" dc:"版本"`
|
||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||
@@ -473,8 +473,8 @@ type ImGroupAdminLogListModel struct {
|
||||
Id int64 `json:"id" dc:"ID"`
|
||||
LogId string `json:"logId" dc:"日志ID"`
|
||||
Topic string `json:"topic" dc:"Topic"`
|
||||
OperatorId int64 `json:"operatorId" dc:"操作人"`
|
||||
TargetUserId int64 `json:"targetUserId" dc:"目标用户"`
|
||||
OperatorId int64 `json:"operatorId,string" dc:"操作人"`
|
||||
TargetUserId int64 `json:"targetUserId,string" dc:"目标用户"`
|
||||
Action string `json:"action" dc:"动作"`
|
||||
ObjectType string `json:"objectType" dc:"对象类型"`
|
||||
ObjectId string `json:"objectId" dc:"对象ID"`
|
||||
|
||||
@@ -264,17 +264,17 @@ type (
|
||||
// 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)
|
||||
GovernanceUpdate(ctx context.Context, in *sysin.ImGroupGovernanceUpdateInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// ProfileUpdate 修改IM群资料
|
||||
ProfileUpdate(ctx context.Context, in *sysin.ImGroupProfileUpdateInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// Dissolve 解散IM群
|
||||
Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (err error)
|
||||
Dissolve(ctx context.Context, in *sysin.ImGroupDissolveInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// Mute 禁言IM群成员
|
||||
Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (err error)
|
||||
Mute(ctx context.Context, in *sysin.ImGroupMuteInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// MemberMute Kafka禁言IM群成员
|
||||
MemberMute(ctx context.Context, in *sysin.ImGroupMuteInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// Unmute 解除IM群成员禁言
|
||||
Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (err error)
|
||||
Unmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// MemberUnmute Kafka解除IM群成员禁言
|
||||
MemberUnmute(ctx context.Context, in *sysin.ImGroupUnmuteInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// MemberRemove 移出IM群成员
|
||||
@@ -284,13 +284,13 @@ type (
|
||||
// JoinRequestReject 拒绝IM入群申请
|
||||
JoinRequestReject(ctx context.Context, in *sysin.ImGroupJoinRequestResolveInp) (err error)
|
||||
// RoleUpdate 更新IM群成员角色
|
||||
RoleUpdate(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (err error)
|
||||
RoleUpdate(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// AdminSet 设置IM群管理员
|
||||
AdminSet(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// AdminRemove 删除IM群管理员
|
||||
AdminRemove(ctx context.Context, in *sysin.ImGroupRoleUpdateInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// OwnerTransfer 转让IM群主
|
||||
OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerTransferInp) (err error)
|
||||
OwnerTransfer(ctx context.Context, in *sysin.ImGroupOwnerTransferInp) (res *sysin.ImGroupCommandModel, err error)
|
||||
// JoinRequestList 获取IM入群申请列表
|
||||
JoinRequestList(ctx context.Context, in *sysin.ImGroupJoinRequestListInp) (list []*sysin.ImGroupJoinRequestListModel, totalCount int, err error)
|
||||
// FileList 获取IM群文件列表
|
||||
|
||||
Reference in New Issue
Block a user