This commit is contained in:
Austin
2026-09-21 09:28:09 +08:00
28 changed files with 1607 additions and 142 deletions
+1
View File
@@ -13,6 +13,7 @@ const (
RegionSpilt = " / " // 地区分隔符
Unknown = "Unknown" // Unknown
SuperRoleKey = "super" // 超管角色唯一标识符,通过角色验证超管
ImPlatformRoleKey = "supreme_manager" // IM平台超管角色唯一标识符
MaxServeLogContentLen = 2048 // 最大保留服务日志内容大小
SysDefaultLanguage = "zh_CN" // 系统默认语言,当配置文件没有国际化配置时生效
)
@@ -0,0 +1,12 @@
package consts
const (
ImInternalSecretDictType = "im_internal_secret"
ImMessageRootKEKLabel = "core_material_01"
ImMessageKeyAlgorithm = "AES-256-GCM"
ImMessageKeyStatusPending = "pending"
ImMessageKeyStatusActive = "active"
ImMessageKeyStatusRetired = "retired"
ImMessageKeyStatusDisabled = "disabled"
)
@@ -0,0 +1,41 @@
package sys
import (
"context"
"hotgo/api/admin/immessageencryption"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
)
var ImMessageEncryption = cImMessageEncryption{}
type cImMessageEncryption struct{}
func (c *cImMessageEncryption) List(ctx context.Context, req *immessageencryption.ListReq) (res *immessageencryption.ListRes, err error) {
list, total, err := service.SysImMessageEncryption().List(ctx, &req.ImMessageEncryptionListInp)
if err != nil {
return nil, err
}
if list == nil {
list = []*sysin.ImMessageEncryptionListModel{}
}
res = new(immessageencryption.ListRes)
res.List = list
res.PageRes.Pack(req, total)
return res, nil
}
func (c *cImMessageEncryption) Initialize(ctx context.Context, req *immessageencryption.InitializeReq) (res *immessageencryption.InitializeRes, err error) {
err = service.SysImMessageEncryption().Initialize(ctx, &req.ImMessageEncryptionActionInp)
return
}
func (c *cImMessageEncryption) Rotate(ctx context.Context, req *immessageencryption.RotateReq) (res *immessageencryption.RotateRes, err error) {
err = service.SysImMessageEncryption().Rotate(ctx, &req.ImMessageEncryptionActionInp)
return
}
func (c *cImMessageEncryption) Switch(ctx context.Context, req *immessageencryption.SwitchReq) (res *immessageencryption.SwitchRes, err error) {
err = service.SysImMessageEncryption().Switch(ctx, &req.ImMessageEncryptionSwitchInp)
return
}
@@ -9,6 +9,7 @@ import (
"hotgo/internal/library/immoderation"
"hotgo/internal/library/imtenant"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"strings"
@@ -302,6 +303,29 @@ func (c *cConfig) SecurityPolicy(ctx context.Context, req *api.SecurityPolicyReq
return res, nil
}
func (c *cConfig) RealnamePolicy(ctx context.Context, req *api.RealnamePolicyReq) (res *api.RealnamePolicyRes, err error) {
tenant, err := getTenant(ctx, dao.ImTenant.Columns().Id, req.Id)
if err != nil {
return nil, err
}
res = defaultRealnamePolicy(tenant.Id)
var row *struct {
RealnameReviewPolicy int `json:"realname_review_policy"`
}
if err = imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
Fields("realname_review_policy").
Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).Scan(&row); err != nil {
return nil, gerror.Wrap(err, "获取租户实名认证策略失败")
}
if row == nil {
return res, nil
}
if row.RealnameReviewPolicy == 1 {
res.RealnameReviewPolicy = 1
}
return res, nil
}
func parseStringArray(raw string) []string {
values := []string{}
if strings.TrimSpace(raw) == "" {
@@ -359,6 +383,27 @@ func (c *cConfig) SensitiveWords(ctx context.Context, req *api.SensitiveWordsReq
return res, nil
}
func (c *cConfig) MessageKeyActive(ctx context.Context, req *api.MessageKeyActiveReq) (res *api.MessageKeyActiveRes, err error) {
key, err := service.SysImMessageEncryption().GetKey(ctx, &sysin.ImMessageKeyGetInp{TenantCode: req.TenantCode})
if err != nil {
return nil, err
}
return &api.MessageKeyActiveRes{ImMessageKeyModel: key}, nil
}
func (c *cConfig) MessageKeyVersion(ctx context.Context, req *api.MessageKeyVersionReq) (res *api.MessageKeyVersionRes, err error) {
key, err := service.SysImMessageEncryption().GetKey(ctx, &sysin.ImMessageKeyGetInp{TenantCode: req.TenantCode, Version: req.Version})
if err != nil {
return nil, err
}
return &api.MessageKeyVersionRes{ImMessageKeyModel: key}, nil
}
func (c *cConfig) MessageKeyAck(ctx context.Context, req *api.MessageKeyAckReq) (res *api.MessageKeyAckRes, err error) {
err = service.SysImMessageEncryption().Ack(ctx, &sysin.ImMessageKeyAckInp{TenantCode: req.TenantCode, Version: req.Version})
return
}
func defaultModerationPolicy(tenant *entity.ImTenant) *api.ModerationPolicyRes {
return &api.ModerationPolicyRes{TenantID: tenant.Id, TenantCode: tenant.Code, Enabled: false,
AliyunEnabled: false, LocalWordsEnabled: false, Scenes: cloneBoolMap(defaultModerationScenes),
@@ -366,6 +411,13 @@ func defaultModerationPolicy(tenant *entity.ImTenant) *api.ModerationPolicyRes {
BlockMessage: "内容不符合平台规范,请修改后重试"}
}
func defaultRealnamePolicy(tenantID int64) *api.RealnamePolicyRes {
return &api.RealnamePolicyRes{
TenantID: tenantID,
RealnameReviewPolicy: 0,
}
}
func cloneBoolMap(src map[string]bool) map[string]bool {
dst := make(map[string]bool, len(src))
for k, v := range src {
@@ -0,0 +1,76 @@
package immessagecrypto
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/utility/encrypt"
"github.com/gogf/gf/v2/errors/gerror"
)
func GenerateDEK() ([]byte, error) {
key := make([]byte, encrypt.AESGCMKeySize)
if _, err := rand.Read(key); err != nil {
return nil, gerror.Wrap(err, "生成商户消息密钥失败")
}
return key, nil
}
func WrapDEK(ctx context.Context, tenantID int64, version uint, dek []byte) (ciphertext, nonce []byte, err error) {
kek, err := loadRootKEK(ctx)
if err != nil {
return nil, nil, err
}
return encrypt.AESGCMEncrypt(dek, kek, keyAAD(tenantID, version))
}
func UnwrapDEK(ctx context.Context, tenantID int64, version uint, ciphertext, nonce []byte) ([]byte, error) {
kek, err := loadRootKEK(ctx)
if err != nil {
return nil, err
}
dek, err := encrypt.AESGCMDecrypt(ciphertext, nonce, kek, keyAAD(tenantID, version))
if err != nil {
return nil, gerror.Wrap(err, "解包商户消息密钥失败")
}
if len(dek) != encrypt.AESGCMKeySize {
return nil, gerror.New("商户消息密钥长度不正确")
}
return dek, nil
}
func EncodeDEK(dek []byte) string {
return base64.StdEncoding.EncodeToString(dek)
}
func loadRootKEK(ctx context.Context) ([]byte, error) {
columns := dao.SysDictData.Columns()
value, err := dao.SysDictData.Ctx(ctx).
Fields(columns.Value).
Where(columns.Type, consts.ImInternalSecretDictType).
Where(columns.Label, consts.ImMessageRootKEKLabel).
Where(columns.Status, consts.StatusEnabled).
Value()
if err != nil {
return nil, gerror.Wrap(err, "读取消息根密钥失败")
}
if value.IsEmpty() {
return nil, gerror.New("消息根密钥未初始化")
}
kek, err := base64.StdEncoding.DecodeString(value.String())
if err != nil || len(kek) != encrypt.AESGCMKeySize {
return nil, gerror.New("消息根密钥格式不正确")
}
return kek, nil
}
func keyAAD(tenantID int64, version uint) []byte {
aad := make([]byte, 16)
binary.BigEndian.PutUint64(aad[:8], uint64(tenantID))
binary.BigEndian.PutUint64(aad[8:], uint64(version))
return aad
}
+52 -4
View File
@@ -468,7 +468,7 @@ func (s *sAdminMember) Edit(ctx context.Context, in *adminin.MemberEditInp) (err
return
}
if service.AdminMember().VerifySuperId(ctx, opMemberId) {
if service.AdminMember().VerifySuperId(ctx, opMemberId) || contexts.GetRoleKey(ctx) == consts.ImPlatformRoleKey {
if in.TenantId > 0 {
count, queryErr := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
Where(dao.ImTenant.Columns().Id, in.TenantId).
@@ -508,6 +508,9 @@ func (s *sAdminMember) Edit(ctx context.Context, in *adminin.MemberEditInp) (err
if err = service.AdminRole().VerifyRoleId(ctx, in.RoleId); err != nil {
return
}
if err = s.normalizeTenantByRole(ctx, in); err != nil {
return
}
// 验证部门ID
if err = service.AdminDept().VerifyDeptId(ctx, in.DeptId); err != nil {
@@ -606,6 +609,50 @@ func (s *sAdminMember) Edit(ctx context.Context, in *adminin.MemberEditInp) (err
})
}
func (s *sAdminMember) normalizeTenantByRole(ctx context.Context, in *adminin.MemberEditInp) error {
var role *entity.AdminRole
if err := dao.AdminRole.Ctx(ctx).
Fields(dao.AdminRole.Columns().Id, dao.AdminRole.Columns().Key).
WherePri(in.RoleId).
Scan(&role); err != nil {
return gerror.Wrap(err, "获取角色信息失败,请稍后重试!")
}
if role == nil || role.Id <= 0 {
return gerror.New("角色不存在")
}
switch role.Key {
case consts.ImPlatformRoleKey:
deptId, err := s.getCompanyDeptId(ctx)
if err != nil {
return err
}
in.TenantId = 0
in.DeptId = deptId
case "imManage":
if in.TenantId <= 0 {
return gerror.New("IM管理员必须选择所属商户")
}
}
return nil
}
func (s *sAdminMember) getCompanyDeptId(ctx context.Context) (int64, error) {
value, err := dao.AdminDept.Ctx(ctx).
Fields(dao.AdminDept.Columns().Id).
Where(dao.AdminDept.Columns().Type, consts.DeptTypeCompany).
Where(dao.AdminDept.Columns().Status, consts.StatusEnabled).
OrderAsc(dao.AdminDept.Columns().Id).
Limit(1).
Value()
if err != nil {
return 0, gerror.Wrap(err, "获取公司部门失败,请稍后重试!")
}
if value.IsEmpty() {
return 0, gerror.New("请先配置公司部门")
}
return value.Int64(), nil
}
// View 获取用户信息
func (s *sAdminMember) View(ctx context.Context, in *adminin.MemberViewInp) (res *adminin.MemberViewModel, err error) {
if err = s.FilterAuthModel(ctx, contexts.GetUserId(ctx)).Hook(hook.MemberInfo).WherePri(in.Id).Scan(&res); err != nil {
@@ -912,9 +959,6 @@ func (s *sAdminMember) ClusterSyncSuperAdmin(ctx context.Context, message *gredi
// 非超管用户只能操作自己的下级角色用户,并且需要满足自身角色的数据权限设置
func (s *sAdminMember) FilterAuthModel(ctx context.Context, memberId int64) *gdb.Model {
m := dao.AdminMember.Ctx(ctx)
if s.VerifySuperId(ctx, memberId) {
return m
}
var roleId int64
if contexts.GetUserId(ctx) == memberId {
@@ -929,6 +973,10 @@ func (s *sAdminMember) FilterAuthModel(ctx context.Context, memberId int64) *gdb
roleId = ro.Int64()
}
if isAdminScopeSuper(ctx, memberId, roleId) {
return m
}
roleIds, err := service.AdminRole().GetSubRoleIds(ctx, roleId, false)
if err != nil {
g.Log().Panicf(ctx, "get the subordinate role permission exception, err:%+v", err)
+39 -4
View File
@@ -37,6 +37,35 @@ func init() {
service.RegisterAdminRole(NewAdminRole())
}
func isAdminScopeSuper(ctx context.Context, memberId, roleId int64) bool {
if service.AdminMember().VerifySuperId(ctx, memberId) {
return true
}
if memberId == contexts.GetUserId(ctx) && contexts.GetRoleKey(ctx) == consts.ImPlatformRoleKey {
return true
}
if roleId <= 0 {
return false
}
value, err := dao.AdminRole.Ctx(ctx).Fields(dao.AdminRole.Columns().Key).WherePri(roleId).Value()
if err != nil {
g.Log().Warningf(ctx, "query admin role key failed, roleId:%d err:%+v", roleId, err)
return false
}
return value.String() == consts.ImPlatformRoleKey
}
func getSuperRoleId(ctx context.Context) (int64, error) {
value, err := dao.AdminRole.Ctx(ctx).Fields(dao.AdminRole.Columns().Id).Where(dao.AdminRole.Columns().Key, consts.SuperRoleKey).Value()
if err != nil {
return 0, err
}
return value.Int64(), nil
}
// Verify 验证权限
func (s *sAdminRole) Verify(ctx context.Context, path, method string) bool {
var (
@@ -64,13 +93,19 @@ func (s *sAdminRole) Verify(ctx context.Context, path, method string) bool {
// List 获取列表
func (s *sAdminRole) List(ctx context.Context, in *adminin.RoleListInp) (res *adminin.RoleListModel, totalCount int, err error) {
var (
mod = dao.AdminRole.Ctx(ctx)
mod = dao.AdminRole.Ctx(ctx).WhereNot(dao.AdminRole.Columns().Key, consts.SuperRoleKey)
models []*entity.AdminRole
pid int64 = 0
)
// 非超管只获取下级角色
if !service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx)) {
// 超管隐藏内置根角色,但从内置根角色的子级开始展示角色树。
if isAdminScopeSuper(ctx, contexts.GetUserId(ctx), contexts.GetRoleId(ctx)) {
pid, err = getSuperRoleId(ctx)
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
return
}
} else {
pid = contexts.GetRoleId(ctx)
mod = mod.WhereLike(dao.AdminRole.Columns().Tree, "%"+tree.GetIdLabel(pid)+"%")
}
@@ -326,7 +361,7 @@ func (s *sAdminRole) VerifyRoleId(ctx context.Context, id int64) (err error) {
return
}
ids, err := s.GetSubRoleIds(ctx, mb.RoleId, service.AdminMember().VerifySuperId(ctx, mb.Id))
ids, err := s.GetSubRoleIds(ctx, mb.RoleId, isAdminScopeSuper(ctx, mb.Id, mb.RoleId))
if err != nil {
err = gerror.New("验证角色信息失败!")
return
+17 -1
View File
@@ -30,6 +30,11 @@ func init() {
// Delete 删除
func (s *sSysDictData) Delete(ctx context.Context, in *sysin.DictDataDeleteInp) error {
if count, err := dao.SysDictData.Ctx(ctx).WherePri(in.Id).Where(dao.SysDictData.Columns().Type, consts.ImInternalSecretDictType).Count(); err != nil {
return gerror.Wrap(err, consts.ErrorORM)
} else if count > 0 {
return gerror.New("内部密钥字典禁止通过通用接口操作")
}
_, err := dao.SysDictData.Ctx(ctx).Where("id", in.Id).Delete()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
@@ -42,6 +47,11 @@ func (s *sSysDictData) Delete(ctx context.Context, in *sysin.DictDataDeleteInp)
func (s *sSysDictData) Edit(ctx context.Context, in *sysin.DictDataEditInp) (err error) {
// 修改
if in.Id > 0 {
if count, queryErr := dao.SysDictData.Ctx(ctx).WherePri(in.Id).Where(dao.SysDictData.Columns().Type, consts.ImInternalSecretDictType).Count(); queryErr != nil {
return gerror.Wrap(queryErr, consts.ErrorORM)
} else if count > 0 {
return gerror.New("内部密钥字典禁止通过通用接口操作")
}
_, err = dao.SysDictData.Ctx(ctx).Fields(sysin.DictDataUpdateFields{}).WherePri(in.Id).Data(in).Update()
if err != nil {
err = gerror.Wrap(err, consts.ErrorORM)
@@ -60,6 +70,9 @@ func (s *sSysDictData) Edit(ctx context.Context, in *sysin.DictDataEditInp) (err
if in.Type == "" {
return gerror.Wrap(err, "类型选择无效,请检查")
}
if in.Type == consts.ImInternalSecretDictType {
return gerror.New("内部密钥字典禁止通过通用接口操作")
}
_, err = dao.SysDictData.Ctx(ctx).Fields(sysin.DictDataInsertFields{}).Data(in).OmitEmptyData().Insert()
if err != nil {
@@ -71,7 +84,7 @@ func (s *sSysDictData) Edit(ctx context.Context, in *sysin.DictDataEditInp) (err
// List 获取列表
func (s *sSysDictData) List(ctx context.Context, in *sysin.DictDataListInp) (list []*sysin.DictDataListModel, totalCount int, err error) {
mod := dao.SysDictData.Ctx(ctx)
mod := dao.SysDictData.Ctx(ctx).WhereNot(dao.SysDictData.Columns().Type, consts.ImInternalSecretDictType)
// 类型ID
if in.TypeID > 0 {
types, err := s.GetTypes(ctx, in.TypeID)
@@ -151,6 +164,9 @@ func (s *sSysDictData) GetTypes(ctx context.Context, id int64) (types []string,
// Select 获取列表
func (s *sSysDictData) Select(ctx context.Context, in *sysin.DataSelectInp) (list sysin.DataSelectModel, err error) {
if in.Type == consts.ImInternalSecretDictType {
return nil, gerror.New("内部密钥字典禁止通过通用接口读取")
}
// 优先从内置字典中获取
options, err := dict.GetOptions(ctx, in.Type)
if err == nil {
+17 -2
View File
@@ -31,7 +31,7 @@ func init() {
// Tree 树
func (s *sSysDictType) Tree(ctx context.Context) (list []*sysin.DictTypeTree, err error) {
var (
mod = dao.SysDictType.Ctx(ctx)
mod = dao.SysDictType.Ctx(ctx).WhereNot(dao.SysDictType.Columns().Type, consts.ImInternalSecretDictType)
models []*entity.SysDictType
)
@@ -55,6 +55,9 @@ func (s *sSysDictType) Delete(ctx context.Context, in *sysin.DictTypeDeleteInp)
err = gerror.New("数据不存在或已删除!")
return
}
if models.Type == consts.ImInternalSecretDictType {
return gerror.New("内部密钥字典禁止通过通用接口操作")
}
exist, err := dao.SysDictData.Ctx(ctx).Where("type", models.Type).One()
if err != nil {
@@ -83,6 +86,18 @@ func (s *sSysDictType) Delete(ctx context.Context, in *sysin.DictTypeDeleteInp)
// Edit 修改/新增
func (s *sSysDictType) Edit(ctx context.Context, in *sysin.DictTypeEditInp) (err error) {
if in.Type == consts.ImInternalSecretDictType {
return gerror.New("内部密钥字典禁止通过通用接口操作")
}
if in.Id > 0 {
count, queryErr := dao.SysDictType.Ctx(ctx).WherePri(in.Id).Where(dao.SysDictType.Columns().Type, consts.ImInternalSecretDictType).Count()
if queryErr != nil {
return gerror.Wrap(queryErr, consts.ErrorORM)
}
if count > 0 {
return gerror.New("内部密钥字典禁止通过通用接口操作")
}
}
if err = hgorm.IsUnique(ctx, &dao.SysDictType, g.Map{dao.SysDictType.Columns().Name: in.Name}, "名称已存在", in.Id); err != nil {
return
}
@@ -105,7 +120,7 @@ func (s *sSysDictType) Edit(ctx context.Context, in *sysin.DictTypeEditInp) (err
// TreeSelect 获取类型关系树选项
func (s *sSysDictType) TreeSelect(ctx context.Context, in *sysin.DictTreeSelectInp) (list []*sysin.DictTypeTree, err error) {
var (
mod = dao.SysDictType.Ctx(ctx)
mod = dao.SysDictType.Ctx(ctx).WhereNot(dao.SysDictType.Columns().Type, consts.ImInternalSecretDictType)
models []*entity.SysDictType
)
@@ -71,6 +71,9 @@ func isImPlatformAdmin(ctx context.Context, operatorId int64) bool {
if service.AdminMember().VerifySuperId(ctx, operatorId) {
return true
}
if contexts.GetRoleKey(ctx) == consts.ImPlatformRoleKey {
return true
}
return contexts.IsCompanyDept(ctx) && contexts.GetTenantId(ctx) <= 0
}
@@ -0,0 +1,298 @@
package sys
import (
"context"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/library/contexts"
"hotgo/internal/library/immessagecrypto"
"hotgo/internal/library/imtenant"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"strings"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
const (
imMessageEncryptionKeyTable = "im_message_encryption_keys"
imTenantMessageEncryptionColumn = "message_encryption_enabled"
)
type sSysImMessageEncryption struct{}
func NewSysImMessageEncryption() *sSysImMessageEncryption { return &sSysImMessageEncryption{} }
func init() {
service.RegisterSysImMessageEncryption(NewSysImMessageEncryption())
}
func (s *sSysImMessageEncryption) List(ctx context.Context, in *sysin.ImMessageEncryptionListInp) (list []*sysin.ImMessageEncryptionListModel, total int, err error) {
if err = in.Filter(ctx); err != nil {
return
}
if err = s.requireSuperAdmin(ctx); err != nil {
return nil, 0, err
}
db := imtenant.RegistryDB()
mod := db.Model(dao.ImTenant.Table()+" t").Safe().Ctx(ctx).
LeftJoin(dao.ImTenantConfig.Table()+" c", "c.tenant_id=t.id").
Fields(
"t.id tenant_id", "t.code tenant_code", "t.name tenant_name",
"COALESCE(c."+imTenantMessageEncryptionColumn+",0) message_encryption_enabled",
"COALESCE((SELECT MAX(k.key_version) FROM "+imMessageEncryptionKeyTable+" k WHERE k.tenant_id=t.id AND k.status='active'),0) active_version",
"COALESCE((SELECT MAX(k.key_version) FROM "+imMessageEncryptionKeyTable+" k WHERE k.tenant_id=t.id AND k.status='pending'),0) pending_version",
"(SELECT COUNT(1) FROM "+imMessageEncryptionKeyTable+" k WHERE k.tenant_id=t.id) key_count",
"(SELECT MAX(COALESCE(k.activated_at,k.created_at)) FROM "+imMessageEncryptionKeyTable+" k WHERE k.tenant_id=t.id) last_rotated_at",
)
if in.TenantId > 0 {
mod = mod.Where("t.id", in.TenantId)
}
if keyword := strings.TrimSpace(in.Keyword); keyword != "" {
mod = mod.Where("t.code LIKE ? OR t.name LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
}
if in.Enabled != nil {
mod = mod.Where("COALESCE(c."+imTenantMessageEncryptionColumn+",0)=?", *in.Enabled)
}
if err = mod.Page(in.Page, in.PerPage).OrderAsc("t.id").ScanAndCount(&list, &total, false); err != nil {
return nil, 0, gerror.Wrap(err, "获取商户消息加密配置失败")
}
return
}
func (s *sSysImMessageEncryption) Initialize(ctx context.Context, in *sysin.ImMessageEncryptionActionInp) error {
if err := in.Filter(ctx); err != nil {
return err
}
if err := s.requireSuperAdmin(ctx); err != nil {
return err
}
tenant, err := s.tenantByID(ctx, in.TenantId)
if err != nil {
return err
}
dek, err := immessagecrypto.GenerateDEK()
if err != nil {
return err
}
ciphertext, nonce, err := immessagecrypto.WrapDEK(ctx, tenant.Id, 1, dek)
if err != nil {
return err
}
err = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
count, queryErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).Where("tenant_id", tenant.Id).Count()
if queryErr != nil {
return gerror.Wrap(queryErr, "检查商户消息密钥失败")
}
if count > 0 {
return gerror.New("该商户已经初始化消息密钥")
}
_, insertErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).Data(g.Map{
"tenant_id": tenant.Id, "key_version": 1, "algorithm": consts.ImMessageKeyAlgorithm,
"encrypted_dek": ciphertext, "dek_nonce": nonce, "status": consts.ImMessageKeyStatusActive,
"created_at": gtime.Now(), "activated_at": gtime.Now(), "created_by": contexts.GetUserId(ctx),
"remark": strings.TrimSpace(in.Remark),
}).Insert()
if insertErr != nil {
return gerror.Wrap(insertErr, "初始化商户消息密钥失败")
}
return nil
})
if err != nil {
return err
}
_ = writeImAdminAudit(ctx, tenant.Id, "message_key_initialize", imMessageEncryptionKeyTable, 0, "success", g.Map{"version": 1})
return publishImPolicyEvent(ctx, tenant.Id, tenant.Code, "message.keys.updated", "message_encryption", 1)
}
func (s *sSysImMessageEncryption) Rotate(ctx context.Context, in *sysin.ImMessageEncryptionActionInp) error {
if err := in.Filter(ctx); err != nil {
return err
}
if err := s.requireSuperAdmin(ctx); err != nil {
return err
}
tenant, err := s.tenantByID(ctx, in.TenantId)
if err != nil {
return err
}
var version uint
err = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
pending, queryErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Where("tenant_id", tenant.Id).Where("status", consts.ImMessageKeyStatusPending).Count()
if queryErr != nil {
return gerror.Wrap(queryErr, "检查待激活消息密钥失败")
}
if pending > 0 {
return gerror.New("该商户已有待激活密钥,请等待IM Server确认")
}
value, queryErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Fields("COALESCE(MAX(key_version),0)").Where("tenant_id", tenant.Id).Value()
if queryErr != nil {
return gerror.Wrap(queryErr, "获取消息密钥版本失败")
}
if value.Uint() == 0 {
return gerror.New("请先初始化商户消息密钥")
}
version = value.Uint() + 1
dek, generateErr := immessagecrypto.GenerateDEK()
if generateErr != nil {
return generateErr
}
ciphertext, nonce, wrapErr := immessagecrypto.WrapDEK(ctx, tenant.Id, version, dek)
if wrapErr != nil {
return wrapErr
}
_, insertErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).Data(g.Map{
"tenant_id": tenant.Id, "key_version": version, "algorithm": consts.ImMessageKeyAlgorithm,
"encrypted_dek": ciphertext, "dek_nonce": nonce, "status": consts.ImMessageKeyStatusPending,
"created_at": gtime.Now(), "created_by": contexts.GetUserId(ctx), "remark": strings.TrimSpace(in.Remark),
}).Insert()
if insertErr != nil {
return gerror.Wrap(insertErr, "创建待激活消息密钥失败")
}
return nil
})
if err != nil {
return err
}
_ = writeImAdminAudit(ctx, tenant.Id, "message_key_rotate", imMessageEncryptionKeyTable, 0, "success", g.Map{"version": version, "status": consts.ImMessageKeyStatusPending})
return publishImPolicyEvent(ctx, tenant.Id, tenant.Code, "message.keys.updated", "message_encryption", version)
}
func (s *sSysImMessageEncryption) Switch(ctx context.Context, in *sysin.ImMessageEncryptionSwitchInp) error {
if err := in.Filter(ctx); err != nil {
return err
}
if err := s.requireSuperAdmin(ctx); err != nil {
return err
}
tenant, err := s.tenantByID(ctx, in.TenantId)
if err != nil {
return err
}
if in.Enabled == 1 {
count, queryErr := imtenant.RegistryDB().Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Where("tenant_id", tenant.Id).Where("status", consts.ImMessageKeyStatusActive).Count()
if queryErr != nil {
return gerror.Wrap(queryErr, "检查商户消息密钥失败")
}
if count == 0 {
return gerror.New("启用消息加密前必须先初始化有效密钥")
}
}
result, err := imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).
Data(g.Map{imTenantMessageEncryptionColumn: in.Enabled, dao.ImTenantConfig.Columns().UpdatedBy: contexts.GetUserId(ctx)}).Update()
if err != nil {
return gerror.Wrap(err, "更新商户消息加密开关失败")
}
if affected, _ := result.RowsAffected(); affected == 0 {
return gerror.New("商户配置不存在")
}
_ = writeImAdminAudit(ctx, tenant.Id, "message_encryption_switch", "im_tenant_config", tenant.Id, "success", g.Map{"enabled": in.Enabled})
return publishImPolicyEvent(ctx, tenant.Id, tenant.Code, "tenant.features.updated", "message_encryption", 0)
}
func (s *sSysImMessageEncryption) GetKey(ctx context.Context, in *sysin.ImMessageKeyGetInp) (*sysin.ImMessageKeyModel, error) {
tenant, err := s.tenantByCode(ctx, in.TenantCode)
if err != nil {
return nil, err
}
enabledValue, err := imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
Fields(imTenantMessageEncryptionColumn).Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).Value()
if err != nil {
return nil, gerror.Wrap(err, "获取商户消息加密开关失败")
}
mod := imtenant.RegistryDB().Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).Where("tenant_id", tenant.Id)
if in.Version > 0 {
mod = mod.Where("key_version", in.Version).WhereIn("status", []string{consts.ImMessageKeyStatusPending, consts.ImMessageKeyStatusActive, consts.ImMessageKeyStatusRetired})
} else {
mod = mod.Where("status", consts.ImMessageKeyStatusActive).OrderDesc("key_version")
}
row, err := mod.One()
if err != nil {
return nil, gerror.Wrap(err, "获取商户消息密钥失败")
}
if row.IsEmpty() {
return nil, gerror.New("商户消息密钥不存在")
}
version := row["key_version"].Uint()
dek, err := immessagecrypto.UnwrapDEK(ctx, tenant.Id, version, row["encrypted_dek"].Bytes(), row["dek_nonce"].Bytes())
if err != nil {
return nil, err
}
return &sysin.ImMessageKeyModel{
TenantId: tenant.Id, TenantCode: tenant.Code, Enabled: enabledValue.Int() == 1,
Version: version, Algorithm: row["algorithm"].String(), Status: row["status"].String(), Key: immessagecrypto.EncodeDEK(dek),
}, nil
}
func (s *sSysImMessageEncryption) Ack(ctx context.Context, in *sysin.ImMessageKeyAckInp) error {
tenant, err := s.tenantByCode(ctx, in.TenantCode)
if err != nil {
return err
}
if in.Version == 0 {
return gerror.New("消息密钥版本不能为空")
}
err = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
row, queryErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Where("tenant_id", tenant.Id).Where("key_version", in.Version).One()
if queryErr != nil {
return gerror.Wrap(queryErr, "获取待激活消息密钥失败")
}
if row.IsEmpty() || row["status"].String() != consts.ImMessageKeyStatusPending {
return gerror.New("待激活消息密钥不存在")
}
if _, updateErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Where("tenant_id", tenant.Id).Where("status", consts.ImMessageKeyStatusActive).
Data(g.Map{"status": consts.ImMessageKeyStatusRetired, "retired_at": gtime.Now()}).Update(); updateErr != nil {
return gerror.Wrap(updateErr, "退役旧消息密钥失败")
}
if _, updateErr := tx.Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Where("tenant_id", tenant.Id).Where("key_version", in.Version).
Data(g.Map{"status": consts.ImMessageKeyStatusActive, "activated_at": gtime.Now()}).Update(); updateErr != nil {
return gerror.Wrap(updateErr, "激活新消息密钥失败")
}
return nil
})
if err != nil {
return err
}
return publishImPolicyEvent(ctx, tenant.Id, tenant.Code, "message.keys.activated", "message_encryption", in.Version)
}
func (s *sSysImMessageEncryption) requireSuperAdmin(ctx context.Context) error {
if !service.AdminMember().VerifySuperId(ctx, contexts.GetUserId(ctx)) {
return gerror.New("只有最高权限平台管理员可以管理消息密钥")
}
return nil
}
func (s *sSysImMessageEncryption) tenantByID(ctx context.Context, tenantID int64) (*entity.ImTenant, error) {
var tenant *entity.ImTenant
if err := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).WherePri(tenantID).Scan(&tenant); err != nil {
return nil, gerror.Wrap(err, "获取商户失败")
}
if tenant == nil || tenant.Id == 0 {
return nil, gerror.New("商户不存在")
}
return tenant, nil
}
func (s *sSysImMessageEncryption) tenantByCode(ctx context.Context, code string) (*entity.ImTenant, error) {
var tenant *entity.ImTenant
if err := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
Where(dao.ImTenant.Columns().Code, strings.TrimSpace(code)).Scan(&tenant); err != nil {
return nil, gerror.Wrap(err, "获取商户失败")
}
if tenant == nil || tenant.Id == 0 {
return nil, gerror.New("商户不存在")
}
return tenant, nil
}
+46 -19
View File
@@ -26,18 +26,22 @@ import (
type sSysImTenant struct{}
const (
tenantConfigAliyunModerationEnabled = "aliyun_moderation_enabled"
tenantConfigLocalModerationEnabled = "local_moderation_enabled"
tenantConfigAliyunModerationEnabled = "aliyun_moderation_enabled"
tenantConfigLocalModerationEnabled = "local_moderation_enabled"
tenantConfigMessageEncryptionEnabled = "message_encryption_enabled"
tenantConfigRealnameReviewPolicy = "realname_review_policy"
)
type imTenantConfigModeration struct {
Id int64 `json:"id"`
TenantId int64 `json:"tenant_id"`
MaxUsers uint64 `json:"max_users"`
MaxGroups uint64 `json:"max_groups"`
MaxGroupMembers uint `json:"max_group_members"`
AliyunModerationEnabled int `json:"aliyun_moderation_enabled"`
LocalModerationEnabled int `json:"local_moderation_enabled"`
Id int64 `json:"id" orm:"id"`
TenantId int64 `json:"tenant_id" orm:"tenant_id"`
MaxUsers uint64 `json:"max_users" orm:"max_users"`
MaxGroups uint64 `json:"max_groups" orm:"max_groups"`
MaxGroupMembers uint `json:"max_group_members" orm:"max_group_members"`
AliyunModerationEnabled int `json:"aliyun_moderation_enabled" orm:"aliyun_moderation_enabled"`
LocalModerationEnabled int `json:"local_moderation_enabled" orm:"local_moderation_enabled"`
MessageEncryptionEnabled int `json:"message_encryption_enabled" orm:"message_encryption_enabled"`
RealnameReviewPolicy int `json:"realname_review_policy" orm:"realname_review_policy"`
}
func NewSysImTenant() *sSysImTenant {
@@ -157,6 +161,8 @@ func (s *sSysImTenant) fillListConfig(ctx context.Context, list []*sysin.ImTenan
item.MaxGroupMembers = conf.MaxGroupMembers
item.AliyunModerationEnabled = conf.AliyunModerationEnabled
item.LocalModerationEnabled = conf.LocalModerationEnabled
item.MessageEncryptionEnabled = conf.MessageEncryptionEnabled
item.RealnameReviewPolicy = conf.RealnameReviewPolicy
}
item.DbDsnConfigured = dsnMap[item.Id] != ""
}
@@ -210,7 +216,7 @@ func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp
WherePri(in.Id).Data(in).Update(); err != nil {
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
}
return s.saveConfig(ctx, in.Id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, in.AliyunModerationEnabled, in.LocalModerationEnabled, userId)
return s.saveConfig(ctx, in, userId)
}
in.CreatedBy = userId
@@ -222,7 +228,8 @@ func (s *sSysImTenant) saveTenant(ctx context.Context, in *sysin.ImTenantEditInp
return gerror.Wrap(err, "新增IM租户失败,请稍后重试!")
}
tenantID = id
return s.saveConfig(ctx, id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, in.AliyunModerationEnabled, in.LocalModerationEnabled, userId)
in.Id = id
return s.saveConfig(ctx, in, userId)
})
if err != nil {
return err
@@ -246,20 +253,34 @@ func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code strin
return
}
func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers uint64, maxGroups uint64, maxGroupMembers uint, aliyunModerationEnabled, localModerationEnabled int, userId int64) (err error) {
func (s *sSysImTenant) saveConfig(ctx context.Context, in *sysin.ImTenantEditInp, userId int64) (err error) {
if in.MessageEncryptionEnabled == 1 {
count, queryErr := imtenant.RegistryDB().Model(imMessageEncryptionKeyTable).Safe().Ctx(ctx).
Where("tenant_id", in.Id).
Where("status", consts.ImMessageKeyStatusActive).
Count()
if queryErr != nil {
return gerror.Wrap(queryErr, "检查商户消息密钥失败")
}
if count == 0 {
return gerror.New("启用消息加密前必须先在消息密钥管理中初始化有效密钥")
}
}
var conf *entity.ImTenantConfig
if err = s.configModel(ctx).
Where(dao.ImTenantConfig.Columns().TenantId, tenantId).
Where(dao.ImTenantConfig.Columns().TenantId, in.Id).
Scan(&conf); err != nil {
return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
}
data := g.Map{
dao.ImTenantConfig.Columns().MaxUsers: maxUsers,
dao.ImTenantConfig.Columns().MaxGroups: maxGroups,
dao.ImTenantConfig.Columns().MaxGroupMembers: maxGroupMembers,
tenantConfigAliyunModerationEnabled: aliyunModerationEnabled,
tenantConfigLocalModerationEnabled: localModerationEnabled,
dao.ImTenantConfig.Columns().MaxUsers: in.MaxUsers,
dao.ImTenantConfig.Columns().MaxGroups: in.MaxGroups,
dao.ImTenantConfig.Columns().MaxGroupMembers: in.MaxGroupMembers,
tenantConfigAliyunModerationEnabled: in.AliyunModerationEnabled,
tenantConfigLocalModerationEnabled: in.LocalModerationEnabled,
tenantConfigMessageEncryptionEnabled: in.MessageEncryptionEnabled,
tenantConfigRealnameReviewPolicy: in.RealnameReviewPolicy,
}
if conf != nil && conf.Id > 0 {
data[dao.ImTenantConfig.Columns().UpdatedBy] = userId
@@ -270,6 +291,8 @@ func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers
dao.ImTenantConfig.Columns().MaxGroupMembers,
tenantConfigAliyunModerationEnabled,
tenantConfigLocalModerationEnabled,
tenantConfigMessageEncryptionEnabled,
tenantConfigRealnameReviewPolicy,
dao.ImTenantConfig.Columns().UpdatedBy,
).
WherePri(conf.Id).Data(data).Update(); err != nil {
@@ -278,7 +301,7 @@ func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers
return
}
data[dao.ImTenantConfig.Columns().TenantId] = tenantId
data[dao.ImTenantConfig.Columns().TenantId] = in.Id
data[dao.ImTenantConfig.Columns().CreatedBy] = userId
data[dao.ImTenantConfig.Columns().UpdatedBy] = userId
if _, err = s.configModel(ctx).
@@ -289,6 +312,8 @@ func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers
dao.ImTenantConfig.Columns().MaxGroupMembers,
tenantConfigAliyunModerationEnabled,
tenantConfigLocalModerationEnabled,
tenantConfigMessageEncryptionEnabled,
tenantConfigRealnameReviewPolicy,
dao.ImTenantConfig.Columns().CreatedBy,
dao.ImTenantConfig.Columns().UpdatedBy,
).
@@ -324,6 +349,8 @@ func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res
res.MaxGroupMembers = conf.MaxGroupMembers
res.AliyunModerationEnabled = conf.AliyunModerationEnabled
res.LocalModerationEnabled = conf.LocalModerationEnabled
res.MessageEncryptionEnabled = conf.MessageEncryptionEnabled
res.RealnameReviewPolicy = conf.RealnameReviewPolicy
}
res.DbDsnConfigured = res.DbDsn != ""
return
@@ -0,0 +1,78 @@
package sysin
import (
"context"
"hotgo/internal/model/input/form"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
type ImMessageEncryptionListInp struct {
form.PageReq
TenantId int64 `json:"tenantId" dc:"商户ID"`
Keyword string `json:"keyword" dc:"商户编码或名称"`
Enabled *int `json:"enabled" dc:"消息加密开关"`
}
func (in *ImMessageEncryptionListInp) Filter(ctx context.Context) error {
return nil
}
type ImMessageEncryptionListModel struct {
TenantId int64 `json:"tenantId" dc:"商户ID"`
TenantCode string `json:"tenantCode" dc:"商户编码"`
TenantName string `json:"tenantName" dc:"商户名称"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
ActiveVersion uint `json:"activeVersion" dc:"当前密钥版本"`
PendingVersion uint `json:"pendingVersion" dc:"待激活密钥版本"`
KeyCount int `json:"keyCount" dc:"密钥版本数"`
LastRotatedAt *gtime.Time `json:"lastRotatedAt" dc:"最后轮换时间"`
}
type ImMessageEncryptionActionInp struct {
TenantId int64 `json:"tenantId" v:"required|min:1#请选择商户|商户不正确" dc:"商户ID"`
Remark string `json:"remark" dc:"操作备注"`
}
func (in *ImMessageEncryptionActionInp) Filter(ctx context.Context) error {
if in.TenantId <= 0 {
return gerror.New("请选择商户")
}
return nil
}
type ImMessageEncryptionSwitchInp struct {
TenantId int64 `json:"tenantId" v:"required|min:1#请选择商户|商户不正确" dc:"商户ID"`
Enabled int `json:"enabled" dc:"是否启用"`
}
func (in *ImMessageEncryptionSwitchInp) Filter(ctx context.Context) error {
if in.TenantId <= 0 {
return gerror.New("请选择商户")
}
if in.Enabled != 0 && in.Enabled != 1 {
return gerror.New("消息加密开关值不正确")
}
return nil
}
type ImMessageKeyGetInp struct {
TenantCode string `json:"tenantCode" dc:"商户编码"`
Version uint `json:"version" dc:"密钥版本,0表示当前版本"`
}
type ImMessageKeyModel struct {
TenantId int64 `json:"tenantId" dc:"商户ID"`
TenantCode string `json:"tenantCode" dc:"商户编码"`
Enabled bool `json:"enabled" dc:"消息加密是否启用"`
Version uint `json:"version" dc:"密钥版本"`
Algorithm string `json:"algorithm" dc:"算法"`
Status string `json:"status" dc:"密钥状态"`
Key string `json:"key" dc:"Base64编码DEK,仅内部接口返回"`
}
type ImMessageKeyAckInp struct {
TenantCode string `json:"tenantCode" dc:"商户编码"`
Version uint `json:"version" dc:"密钥版本"`
}
+62 -48
View File
@@ -43,33 +43,39 @@ type ImTenantInsertFields struct {
// ImTenantConfigUpdateFields 修改IM租户配置字段过滤
type ImTenantConfigUpdateFields struct {
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
}
// ImTenantConfigInsertFields 新增IM租户配置字段过滤
type ImTenantConfigInsertFields struct {
TenantId int64 `json:"tenantId" dc:"租户ID"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
CreatedBy int64 `json:"createdBy" dc:"创建者"`
TenantId int64 `json:"tenantId" dc:"租户ID"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
CreatedBy int64 `json:"createdBy" dc:"创建者"`
}
// ImTenantEditInp 修改/新增IM租户
type ImTenantEditInp struct {
entity.ImTenant
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
}
func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
@@ -86,8 +92,12 @@ func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
return gerror.New("租户状态不正确")
}
if (in.AliyunModerationEnabled != 0 && in.AliyunModerationEnabled != 1) ||
(in.LocalModerationEnabled != 0 && in.LocalModerationEnabled != 1) {
return gerror.New("内容审核开关值不正确")
(in.LocalModerationEnabled != 0 && in.LocalModerationEnabled != 1) ||
(in.MessageEncryptionEnabled != 0 && in.MessageEncryptionEnabled != 1) {
return gerror.New("商户功能开关值不正确")
}
if in.RealnameReviewPolicy != 0 && in.RealnameReviewPolicy != 1 {
return gerror.New("实名审核策略不正确")
}
return
}
@@ -105,16 +115,18 @@ func (in *ImTenantViewInp) Filter(ctx context.Context) (err error) {
type ImTenantViewModel struct {
entity.ImTenant
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
TenantDbReachable bool `json:"tenantDbReachable" dc:"业务库是否可达"`
TenantDbCheckError string `json:"tenantDbCheckError" dc:"业务库检查错误"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
TenantDbReachable bool `json:"tenantDbReachable" dc:"业务库是否可达"`
TenantDbCheckError string `json:"tenantDbCheckError" dc:"业务库检查错误"`
}
// ImTenantListInp 获取IM租户列表
@@ -133,24 +145,26 @@ func (in *ImTenantListInp) Filter(ctx context.Context) (err error) {
}
type ImTenantListModel struct {
Id int64 `json:"id" dc:"ID"`
Code string `json:"code" dc:"租户编码"`
Name string `json:"name" dc:"租户名称"`
TenantDesc string `json:"tenantDesc" dc:"租户描述"`
DbName string `json:"dbName" dc:"业务库名"`
State int `json:"state" dc:"状态"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
CreatedBy int64 `json:"createdBy" dc:"创建者"`
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
Id int64 `json:"id" dc:"ID"`
Code string `json:"code" dc:"租户编码"`
Name string `json:"name" dc:"租户名称"`
TenantDesc string `json:"tenantDesc" dc:"租户描述"`
DbName string `json:"dbName" dc:"业务库名"`
State int `json:"state" dc:"状态"`
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
MessageEncryptionEnabled int `json:"messageEncryptionEnabled" dc:"是否启用消息加密"`
RealnameReviewPolicy int `json:"realnameReviewPolicy" dc:"实名审核策略"`
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
CreatedBy int64 `json:"createdBy" dc:"创建者"`
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
}
// ImTenantStatusInp 更新IM租户状态
@@ -0,0 +1,7 @@
package genrouter
import "hotgo/internal/controller/admin/sys"
func init() {
LoginRequiredRouter = append(LoginRequiredRouter, sys.ImMessageEncryption)
}
+74 -54
View File
@@ -211,6 +211,14 @@ type (
// Restore 恢复IM租户
Restore(ctx context.Context, in *sysin.ImTenantLifecycleInp) (err error)
}
ISysImMessageEncryption interface {
List(ctx context.Context, in *sysin.ImMessageEncryptionListInp) (list []*sysin.ImMessageEncryptionListModel, total int, err error)
Initialize(ctx context.Context, in *sysin.ImMessageEncryptionActionInp) error
Rotate(ctx context.Context, in *sysin.ImMessageEncryptionActionInp) error
Switch(ctx context.Context, in *sysin.ImMessageEncryptionSwitchInp) error
GetKey(ctx context.Context, in *sysin.ImMessageKeyGetInp) (*sysin.ImMessageKeyModel, error)
Ack(ctx context.Context, in *sysin.ImMessageKeyAckInp) error
}
ISysImRegistration interface {
// GetConfig 获取IM注册配置
GetConfig(ctx context.Context) (res *sysin.ImRegistrationConfigModel, err error)
@@ -781,60 +789,61 @@ type (
)
var (
localSysAddons ISysAddons
localSysAddonsConfig ISysAddonsConfig
localSysAttachment ISysAttachment
localSysBlacklist ISysBlacklist
localSysConfig ISysConfig
localSysCron ISysCron
localSysCronGroup ISysCronGroup
localSysCurdDemo ISysCurdDemo
localSysCultivateFlower ISysCultivateFlower
localSysCultivateContent ISysCultivateContent
localSysImTenant ISysImTenant
localSysImRegistration ISysImRegistration
localSysImInvite ISysImInvite
localSysImUser ISysImUser
localSysImGroup ISysImGroup
localSysImRealname ISysImRealname
localSysImWorkOrder ISysImWorkOrder
localSysImOutbox ISysImOutbox
localSysImOrg ISysImOrg
localSysImFile ISysImFile
localSysImFilePolicy ISysImFilePolicy
localSysImUserPolicyGroup ISysImUserPolicyGroup
localSysImSensitiveWord ISysImSensitiveWord
localSysImSecurityPolicy ISysImSecurityPolicy
localSysImDashboard ISysImDashboard
localSysImPrivateMessage ISysImPrivateMessage
localSysImGroupMessage ISysImGroupMessage
localSysImUserLoginHistory ISysImUserLoginHistory
localSysImLoginFailureLog ISysImLoginFailureLog
localSysImExternalConfig ISysImExternalConfig
localSysImNotification ISysImNotification
localSysImAppRelease ISysImAppRelease
localSysImAbuseReason ISysImAbuseReason
localSysImMailboxConfig ISysImMailboxConfig
localSysImFeedbackConfig ISysImFeedbackConfig
localSysImWorkbench ISysImWorkbench
localSysImTaskDashboard ISysImTaskDashboard
localSysImAuditLog ISysImAuditLog
localSysImKafkaAudit ISysImKafkaAudit
localSysImKafkaCommand ISysImKafkaCommand
localSysImDeadLetter ISysImDeadLetter
localSysDictData ISysDictData
localSysDictType ISysDictType
localSysEmsLog ISysEmsLog
localSysGenCodes ISysGenCodes
localSysLog ISysLog
localSysLoginLog ISysLoginLog
localSysNormalTreeDemo ISysNormalTreeDemo
localSysOptionTreeDemo ISysOptionTreeDemo
localSysProvinces ISysProvinces
localSysServeLicense ISysServeLicense
localSysServeLog ISysServeLog
localSysSmsLog ISysSmsLog
localSysTestCategory ISysTestCategory
localSysAddons ISysAddons
localSysAddonsConfig ISysAddonsConfig
localSysAttachment ISysAttachment
localSysBlacklist ISysBlacklist
localSysConfig ISysConfig
localSysCron ISysCron
localSysCronGroup ISysCronGroup
localSysCurdDemo ISysCurdDemo
localSysCultivateFlower ISysCultivateFlower
localSysCultivateContent ISysCultivateContent
localSysImTenant ISysImTenant
localSysImMessageEncryption ISysImMessageEncryption
localSysImRegistration ISysImRegistration
localSysImInvite ISysImInvite
localSysImUser ISysImUser
localSysImGroup ISysImGroup
localSysImRealname ISysImRealname
localSysImWorkOrder ISysImWorkOrder
localSysImOutbox ISysImOutbox
localSysImOrg ISysImOrg
localSysImFile ISysImFile
localSysImFilePolicy ISysImFilePolicy
localSysImUserPolicyGroup ISysImUserPolicyGroup
localSysImSensitiveWord ISysImSensitiveWord
localSysImSecurityPolicy ISysImSecurityPolicy
localSysImDashboard ISysImDashboard
localSysImPrivateMessage ISysImPrivateMessage
localSysImGroupMessage ISysImGroupMessage
localSysImUserLoginHistory ISysImUserLoginHistory
localSysImLoginFailureLog ISysImLoginFailureLog
localSysImExternalConfig ISysImExternalConfig
localSysImNotification ISysImNotification
localSysImAppRelease ISysImAppRelease
localSysImAbuseReason ISysImAbuseReason
localSysImMailboxConfig ISysImMailboxConfig
localSysImFeedbackConfig ISysImFeedbackConfig
localSysImWorkbench ISysImWorkbench
localSysImTaskDashboard ISysImTaskDashboard
localSysImAuditLog ISysImAuditLog
localSysImKafkaAudit ISysImKafkaAudit
localSysImKafkaCommand ISysImKafkaCommand
localSysImDeadLetter ISysImDeadLetter
localSysDictData ISysDictData
localSysDictType ISysDictType
localSysEmsLog ISysEmsLog
localSysGenCodes ISysGenCodes
localSysLog ISysLog
localSysLoginLog ISysLoginLog
localSysNormalTreeDemo ISysNormalTreeDemo
localSysOptionTreeDemo ISysOptionTreeDemo
localSysProvinces ISysProvinces
localSysServeLicense ISysServeLicense
localSysServeLog ISysServeLog
localSysSmsLog ISysSmsLog
localSysTestCategory ISysTestCategory
)
func SysAddons() ISysAddons {
@@ -956,6 +965,17 @@ func RegisterSysImTenant(i ISysImTenant) {
localSysImTenant = i
}
func SysImMessageEncryption() ISysImMessageEncryption {
if localSysImMessageEncryption == nil {
panic("implement not found for interface ISysImMessageEncryption, forgot register?")
}
return localSysImMessageEncryption
}
func RegisterSysImMessageEncryption(i ISysImMessageEncryption) {
localSysImMessageEncryption = i
}
func SysImRegistration() ISysImRegistration {
if localSysImRegistration == nil {
panic("implement not found for interface ISysImRegistration, forgot register?")