feat: 完成鸽达达后台安全与审计改造
This commit is contained in:
@@ -150,7 +150,7 @@ func normalizeImAdminValue(target string, value any) any {
|
||||
parts = append(parts, text)
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
case "profileJson", "trustedJson", "accessJson":
|
||||
case "profileJson", "trustedJson", "accessJson", "head", "content", "metadata", "atUsers":
|
||||
return normalizeImAdminJSONString(value)
|
||||
default:
|
||||
return value
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type sSysImDashboard struct{}
|
||||
|
||||
func NewSysImDashboard() *sSysImDashboard { return &sSysImDashboard{} }
|
||||
|
||||
func init() { service.RegisterSysImDashboard(NewSysImDashboard()) }
|
||||
|
||||
func (s *sSysImDashboard) Overview(ctx context.Context, in *sysin.ImDashboardOverviewInp) (*sysin.ImDashboardOverviewModel, error) {
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return imAdminQueryOne[sysin.ImDashboardOverviewModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/dashboard/overview", nil)
|
||||
}
|
||||
|
||||
func (s *sSysImDashboard) Trends(ctx context.Context, in *sysin.ImDashboardTrendsInp) ([]*sysin.ImDashboardTrendModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values := addPageQuery(url.Values{}, 1, in.Days)
|
||||
values.Set("days", intToString(in.Days))
|
||||
list, _, err := imAdminQueryList[sysin.ImDashboardTrendModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/dashboard/trends", values, nil)
|
||||
return list, err
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/excel"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
type sSysImUserLoginHistory struct{}
|
||||
type sSysImLoginFailureLog struct{}
|
||||
|
||||
func NewSysImUserLoginHistory() *sSysImUserLoginHistory { return &sSysImUserLoginHistory{} }
|
||||
func NewSysImLoginFailureLog() *sSysImLoginFailureLog { return &sSysImLoginFailureLog{} }
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImUserLoginHistory(NewSysImUserLoginHistory())
|
||||
service.RegisterSysImLoginFailureLog(NewSysImLoginFailureLog())
|
||||
}
|
||||
|
||||
func (s *sSysImUserLoginHistory) List(ctx context.Context, in *sysin.ImUserLoginHistoryListInp) ([]*sysin.ImUserLoginHistoryModel, int, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
values := addPageQuery(url.Values{}, in.Page, in.PerPage)
|
||||
setQuery(values, "user_id", in.UserId)
|
||||
setQuery(values, "account", in.Account)
|
||||
setQuery(values, "login_type", in.LoginType)
|
||||
setQuery(values, "event_type", in.EventType)
|
||||
setQuery(values, "platform", in.Platform)
|
||||
setQuery(values, "ip", in.Ip)
|
||||
addTimeRangeQuery(values, "login_at", in.LoginAt)
|
||||
return imAdminQueryList[sysin.ImUserLoginHistoryModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/user-login-logs", values, nil)
|
||||
}
|
||||
|
||||
func (s *sSysImUserLoginHistory) Export(ctx context.Context, in *sysin.ImUserLoginHistoryListInp) error {
|
||||
list, total, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exports := make([]sysin.ImLoginAuditExportModel, 0, len(list))
|
||||
for _, item := range list {
|
||||
exports = append(exports, sysin.ImLoginAuditExportModel{Id: item.Id, UserId: item.UserId, AccountMask: item.AccountMask, EventType: item.EventType, Platform: item.Platform, Ip: item.Ip, Region: item.Region, RiskLevel: item.RiskLevel, OccurredAt: item.LoginAt})
|
||||
}
|
||||
return exportLoginAudit(ctx, exports, total, in.Page, in.PerPage, "用户登录历史")
|
||||
}
|
||||
|
||||
func (s *sSysImLoginFailureLog) List(ctx context.Context, in *sysin.ImLoginFailureLogListInp) ([]*sysin.ImLoginFailureLogModel, int, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
values := addPageQuery(url.Values{}, in.Page, in.PerPage)
|
||||
setQuery(values, "user_id", in.UserId)
|
||||
setQuery(values, "account", in.Account)
|
||||
setQuery(values, "failure_type", in.FailureType)
|
||||
setQuery(values, "platform", in.Platform)
|
||||
setQuery(values, "ip", in.Ip)
|
||||
addTimeRangeQuery(values, "attempt_at", in.AttemptAt)
|
||||
return imAdminQueryList[sysin.ImLoginFailureLogModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/login-failure-logs", values, nil)
|
||||
}
|
||||
|
||||
func (s *sSysImLoginFailureLog) View(ctx context.Context, in *sysin.ImLoginFailureViewInp) (*sysin.ImLoginFailureLogModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return imAdminQueryOne[sysin.ImLoginFailureLogModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/login-failure-logs/"+strconv.FormatInt(in.Id, 10), nil)
|
||||
}
|
||||
|
||||
func (s *sSysImLoginFailureLog) Export(ctx context.Context, in *sysin.ImLoginFailureLogListInp) error {
|
||||
list, total, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exports := make([]sysin.ImLoginAuditExportModel, 0, len(list))
|
||||
for _, item := range list {
|
||||
exports = append(exports, sysin.ImLoginAuditExportModel{Id: item.Id, UserId: item.UserId, AccountMask: item.AccountMask, EventType: item.FailureType, Platform: item.Platform, Ip: item.Ip, Region: item.Region, RiskLevel: item.RiskLevel, Reason: item.Reason, OccurredAt: item.AttemptAt})
|
||||
}
|
||||
return exportLoginAudit(ctx, exports, total, in.Page, in.PerPage, "登录失败日志")
|
||||
}
|
||||
|
||||
func exportLoginAudit(ctx context.Context, exports []sysin.ImLoginAuditExportModel, total int, page int, perPage int, label string) error {
|
||||
tags, err := convert.GetEntityDescTags(sysin.ImLoginAuditExportModel{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileName := "导出" + label + "-" + gctx.CtxId(ctx)
|
||||
sheetName := fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", total, form.CalPageCount(total, perPage), page, len(exports))
|
||||
return excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
}
|
||||
|
||||
func (s *sSysImLoginFailureLog) AddBlacklist(ctx context.Context, in *sysin.ImLoginFailureAddBlacklistInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
remark := in.Remark
|
||||
if remark == "" {
|
||||
remark = "由登录失败日志加入"
|
||||
}
|
||||
return service.SysImSecurityPolicy().IPRuleEdit(ctx, &sysin.ImSecurityIPRuleEditInp{
|
||||
TenantId: in.TenantId, RuleType: "blacklist", IpCidr: in.Ip, Action: "deny",
|
||||
SceneJson: gjson.New([]string{"login"}), RiskLevel: "high", Status: consts.StatusEnabled, Remark: remark,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sSysImLoginFailureLog) BanUser(ctx context.Context, in *sysin.ImLoginFailureBanUserInp) (*sysin.ImUserCommandModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actorName := ""
|
||||
if user != nil {
|
||||
actorName = user.RealName
|
||||
}
|
||||
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
|
||||
TenantID: tenant.Id, TenantCode: tenant.Code, EventType: consts.ImAdminCommandUserBan,
|
||||
BizType: "user", BizID: strconv.FormatInt(in.UserId, 10),
|
||||
Actor: &imkafka.Actor{Type: "admin", ID: strconv.FormatInt(contexts.GetUserId(ctx), 10), DisplayName: actorName},
|
||||
Payload: map[string]any{"user_id": strconv.FormatInt(in.UserId, 10), "target_user_id": strconv.FormatInt(in.UserId, 10), "reason": in.Reason},
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImUserCommandModel{CommandID: command.CommandID, EventID: command.EventID, Status: command.Status, TenantID: command.TenantID, TenantCode: command.TenantCode, EventType: command.EventType, IdempotencyKey: command.IdempotencyKey}, nil
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/model/input/form"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
"hotgo/utility/convert"
|
||||
"hotgo/utility/excel"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysImPrivateMessage struct{}
|
||||
type sSysImGroupMessage struct{}
|
||||
|
||||
func NewSysImPrivateMessage() *sSysImPrivateMessage { return &sSysImPrivateMessage{} }
|
||||
func NewSysImGroupMessage() *sSysImGroupMessage { return &sSysImGroupMessage{} }
|
||||
|
||||
func init() {
|
||||
service.RegisterSysImPrivateMessage(NewSysImPrivateMessage())
|
||||
service.RegisterSysImGroupMessage(NewSysImGroupMessage())
|
||||
}
|
||||
|
||||
func (s *sSysImPrivateMessage) List(ctx context.Context, in *sysin.ImPrivateMessageListInp) ([]*sysin.ImMessageListModel, int, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
values := addPageQuery(url.Values{}, in.Page, in.PerPage)
|
||||
setQuery(values, "topic", in.Topic)
|
||||
setQuery(values, "from_user_id", in.FromUserId)
|
||||
setQuery(values, "to_user_id", in.ToUserId)
|
||||
setQuery(values, "content_type", in.ContentType)
|
||||
setQuery(values, "status", in.Status)
|
||||
setQuery(values, "keyword", in.Keyword)
|
||||
addTimeRangeQuery(values, "created_at", in.CreatedAt)
|
||||
return queryImMessages(ctx, in.TenantId, "private-messages", values)
|
||||
}
|
||||
|
||||
func (s *sSysImGroupMessage) List(ctx context.Context, in *sysin.ImGroupMessageListInp) ([]*sysin.ImMessageListModel, int, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
values := addPageQuery(url.Values{}, in.Page, in.PerPage)
|
||||
setQuery(values, "topic", in.Topic)
|
||||
setQuery(values, "sender_user_id", in.SenderUserId)
|
||||
setQuery(values, "content_type", in.ContentType)
|
||||
setQuery(values, "status", in.Status)
|
||||
setQuery(values, "keyword", in.Keyword)
|
||||
addTimeRangeQuery(values, "created_at", in.CreatedAt)
|
||||
return queryImMessages(ctx, in.TenantId, "group-messages", values)
|
||||
}
|
||||
|
||||
func queryImMessages(ctx context.Context, tenantId int64, resource string, values url.Values) ([]*sysin.ImMessageListModel, int, error) {
|
||||
tenant, err := resolveImAdminTenant(ctx, tenantId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return imAdminQueryList[sysin.ImMessageListModel](ctx, tenant,
|
||||
"/internal/admin-query/tenants/"+url.PathEscape(tenant.Code)+"/"+resource, values,
|
||||
map[string]string{"sender_user_id": "fromUserId", "sent_at": "createdAt", "message_type": "contentType", "content_summary": "contentExcerpt"})
|
||||
}
|
||||
|
||||
func (s *sSysImPrivateMessage) View(ctx context.Context, in *sysin.ImMessageViewInp) (*sysin.ImMessageViewModel, error) {
|
||||
return viewImMessage(ctx, in)
|
||||
}
|
||||
|
||||
func (s *sSysImGroupMessage) View(ctx context.Context, in *sysin.ImMessageViewInp) (*sysin.ImMessageViewModel, error) {
|
||||
return viewImMessage(ctx, in)
|
||||
}
|
||||
|
||||
func viewImMessage(ctx context.Context, in *sysin.ImMessageViewInp) (*sysin.ImMessageViewModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := "/internal/admin-query/tenants/" + url.PathEscape(tenant.Code) + "/messages/" + url.PathEscape(in.Topic) + "/" + strconv.FormatInt(in.SeqId, 10)
|
||||
return imAdminQueryOne[sysin.ImMessageViewModel](ctx, tenant, path,
|
||||
map[string]string{"sender_user_id": "fromUserId", "sent_at": "createdAt", "message_type": "contentType"})
|
||||
}
|
||||
|
||||
func (s *sSysImPrivateMessage) Hide(ctx context.Context, in *sysin.ImMessageActionInp) (*sysin.ImMessageCommandModel, error) {
|
||||
return publishMessageCommand(ctx, in, "private", consts.ImAdminCommandMessageHide)
|
||||
}
|
||||
|
||||
func (s *sSysImPrivateMessage) Delete(ctx context.Context, in *sysin.ImMessageActionInp) (*sysin.ImMessageCommandModel, error) {
|
||||
return publishMessageCommand(ctx, in, "private", consts.ImAdminCommandMessageDelete)
|
||||
}
|
||||
|
||||
func (s *sSysImGroupMessage) Hide(ctx context.Context, in *sysin.ImMessageActionInp) (*sysin.ImMessageCommandModel, error) {
|
||||
return publishMessageCommand(ctx, in, "group", consts.ImAdminCommandMessageHide)
|
||||
}
|
||||
|
||||
func (s *sSysImGroupMessage) Delete(ctx context.Context, in *sysin.ImMessageActionInp) (*sysin.ImMessageCommandModel, error) {
|
||||
return publishMessageCommand(ctx, in, "group", consts.ImAdminCommandMessageDelete)
|
||||
}
|
||||
|
||||
func publishMessageCommand(ctx context.Context, in *sysin.ImMessageActionInp, conversationType string, eventType string) (*sysin.ImMessageCommandModel, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
actorName := ""
|
||||
if user != nil {
|
||||
actorName = user.RealName
|
||||
}
|
||||
bizId := in.Topic + ":" + strconv.FormatInt(in.SeqId, 10)
|
||||
command, err := imkafka.PublishAdminCommand(ctx, imkafka.AdminCommandInput{
|
||||
TenantID: tenant.Id, TenantCode: tenant.Code, EventType: eventType, BizType: "message", BizID: bizId,
|
||||
Actor: &imkafka.Actor{Type: "admin", ID: strconv.FormatInt(contexts.GetUserId(ctx), 10), DisplayName: actorName},
|
||||
Payload: map[string]any{"conversation_type": conversationType, "topic": in.Topic, "seq_id": in.SeqId, "reason": in.Reason},
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
result := "success"
|
||||
if err != nil {
|
||||
result = "failed"
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenant.Id, eventType, "message", in.SeqId, result, map[string]any{"conversation_type": conversationType, "topic": in.Topic, "reason": in.Reason})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImMessageCommandModel{CommandId: command.CommandID, EventId: command.EventID, Status: command.Status}, nil
|
||||
}
|
||||
|
||||
func (s *sSysImPrivateMessage) Export(ctx context.Context, in *sysin.ImPrivateMessageListInp) error {
|
||||
list, total, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return exportImMessages(ctx, list, total, in.Page, in.PerPage, "单聊消息")
|
||||
}
|
||||
|
||||
func (s *sSysImGroupMessage) Export(ctx context.Context, in *sysin.ImGroupMessageListInp) error {
|
||||
list, total, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return exportImMessages(ctx, list, total, in.Page, in.PerPage, "群聊消息")
|
||||
}
|
||||
|
||||
func exportImMessages(ctx context.Context, list []*sysin.ImMessageListModel, total int, page int, perPage int, label string) error {
|
||||
tags, err := convert.GetEntityDescTags(sysin.ImMessageExportModel{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var exports []sysin.ImMessageExportModel
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return err
|
||||
}
|
||||
fileName := "导出" + label + "-" + gctx.CtxId(ctx)
|
||||
sheetName := fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", total, form.CalPageCount(total, perPage), page, len(list))
|
||||
return excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
}
|
||||
|
||||
func setQuery(values url.Values, key string, value string) {
|
||||
if value != "" {
|
||||
values.Set(key, value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imkafka"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/entity"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
func resolveImPolicyTenant(ctx context.Context, selectedTenantID int64) (tenantID int64, tenantCode string, err error) {
|
||||
operatorID := contexts.GetUserId(ctx)
|
||||
if service.AdminMember().VerifySuperId(ctx, operatorID) && selectedTenantID == 0 {
|
||||
return 0, "*", nil
|
||||
}
|
||||
scope, err := resolveImAdminScope(ctx, selectedTenantID)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
var tenant *entity.ImTenant
|
||||
if err = imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).
|
||||
Where(dao.ImTenant.Columns().Id, scope.TenantId).Scan(&tenant); err != nil {
|
||||
return 0, "", gerror.Wrap(err, "获取IM租户失败")
|
||||
}
|
||||
if tenant == nil {
|
||||
return 0, "", gerror.New("IM租户不存在")
|
||||
}
|
||||
return tenant.Id, tenant.Code, nil
|
||||
}
|
||||
|
||||
func policyJSON(value *gjson.Json, fallback string) string {
|
||||
if value == nil || value.String() == "" || value.String() == "null" {
|
||||
return fallback
|
||||
}
|
||||
return value.String()
|
||||
}
|
||||
|
||||
func publishImPolicyEvent(ctx context.Context, tenantID int64, tenantCode, eventType, group string, version uint) error {
|
||||
return imkafka.PublishConfigEvent(ctx, imkafka.ConfigEventInput{
|
||||
TenantID: tenantID,
|
||||
TenantCode: tenantCode,
|
||||
EventType: eventType,
|
||||
Group: group,
|
||||
Keys: []string{"version", "payload"},
|
||||
BizID: fmt.Sprintf("%s:%d:%d", group, tenantID, version),
|
||||
Actor: &imkafka.Actor{
|
||||
Type: "admin",
|
||||
ID: strconv.FormatInt(contexts.GetUserId(ctx), 10),
|
||||
},
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysImSecurityPolicy struct{}
|
||||
|
||||
func NewSysImSecurityPolicy() *sSysImSecurityPolicy { return &sSysImSecurityPolicy{} }
|
||||
func init() { service.RegisterSysImSecurityPolicy(NewSysImSecurityPolicy()) }
|
||||
|
||||
func securityPolicyFields(alias string) []any {
|
||||
c := dao.ImSecurityPolicy.Columns()
|
||||
p := alias
|
||||
if p != "" {
|
||||
p += "."
|
||||
}
|
||||
return []any{p + c.Id, p + c.TenantId, p + c.PolicyVersion, p + c.PolicyName, p + c.PublishStatus, "CAST(" + p + c.PasswordPolicy + " AS CHAR) password_policy", "CAST(" + p + c.LoginRateLimit + " AS CHAR) login_rate_limit", "CAST(" + p + c.SensitiveWordPolicy + " AS CHAR) sensitive_word_policy", "CAST(" + p + c.FileReviewPolicy + " AS CHAR) file_review_policy", "CAST(" + p + c.RealnamePolicy + " AS CHAR) realname_policy", "CAST(" + p + c.RiskChallengePolicy + " AS CHAR) risk_challenge_policy", p + c.GlobalFriendEnabled, "CAST(" + p + c.FriendDisabledPolicy + " AS CHAR) friend_disabled_policy", p + c.MessageCurfewEnabled, p + c.Remark, p + c.PublishedAt, p + c.PublishedBy, p + c.CreatedAt, p + c.UpdatedAt}
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) List(ctx context.Context, in *sysin.ImSecurityPolicyListInp) (list []*sysin.ImSecurityPolicyModel, total int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
c := dao.ImSecurityPolicy.Columns()
|
||||
model := imtenant.RegistryDB().Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Fields(securityPolicyFields("")...).Where(c.TenantId, tenantID).WhereNull(c.DeletedAt)
|
||||
if in.PublishStatus != "" {
|
||||
model = model.Where(c.PublishStatus, in.PublishStatus)
|
||||
}
|
||||
err = model.Page(in.Page, in.PerPage).OrderDesc(c.PolicyVersion).ScanAndCount(&list, &total, false)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) View(ctx context.Context, in *sysin.ImSecurityPolicyViewInp) (res *sysin.ImSecurityPolicyModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
c := dao.ImSecurityPolicy.Columns()
|
||||
baseModel := imtenant.RegistryDB().Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Fields(securityPolicyFields("")...).WhereNull(c.DeletedAt)
|
||||
if in.Id > 0 {
|
||||
err = baseModel.Where(c.Id, in.Id).Scan(&res)
|
||||
} else {
|
||||
tenantID, _, resolveErr := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if resolveErr != nil {
|
||||
return nil, resolveErr
|
||||
}
|
||||
err = baseModel.Where(c.TenantId, tenantID).
|
||||
Where(c.PublishStatus, consts.ImSecurityPublishPublished).
|
||||
OrderDesc(c.PolicyVersion).Scan(&res)
|
||||
if err == nil && res == nil {
|
||||
err = baseModel.Where(c.TenantId, tenantID).OrderDesc(c.PolicyVersion).Scan(&res)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取安全策略失败")
|
||||
}
|
||||
if res == nil {
|
||||
return nil, gerror.New("安全策略不存在")
|
||||
}
|
||||
if _, _, err = resolveImPolicyTenant(ctx, res.TenantId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.CurfewRules, err = s.curfewRules(ctx, res.Id)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) curfewRules(ctx context.Context, policyID int64) (list []*sysin.ImMessageCurfewRuleModel, err error) {
|
||||
c := dao.ImMessageCurfewRules.Columns()
|
||||
err = imtenant.RegistryDB().Model(dao.ImMessageCurfewRules.Table()).Safe().Ctx(ctx).Fields(c.Id, c.RuleCode, c.RuleName, c.DateType, "CAST("+c.CustomDates+" AS CHAR) custom_dates", "CAST("+c.Weekdays+" AS CHAR) weekdays", c.Timezone, "TIME_FORMAT("+c.StartTime+",'%H:%i') start_time", "TIME_FORMAT("+c.EndTime+",'%H:%i') end_time", c.Scope, "CAST("+c.WhitelistUserIds+" AS CHAR) whitelist_user_ids", "CAST("+c.WhitelistGroupIds+" AS CHAR) whitelist_group_ids", c.Message, c.Status, c.Sort).Where(c.PolicyId, policyID).WhereNull(c.DeletedAt).OrderAsc(c.Sort).OrderAsc(c.Id).Scan(&list)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPolicySaveInp) (id int64, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
db := imtenant.RegistryDB()
|
||||
c := dao.ImSecurityPolicy.Columns()
|
||||
rc := dao.ImMessageCurfewRules.Columns()
|
||||
operator := contexts.GetUserId(ctx)
|
||||
now := gtime.Now()
|
||||
err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
version := uint(0)
|
||||
if in.Id > 0 {
|
||||
current, readErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One()
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
if current.IsEmpty() || current[c.TenantId].Int64() != tenantID {
|
||||
return gerror.New("安全策略不存在")
|
||||
}
|
||||
if current[c.PublishStatus].String() != consts.ImSecurityPublishDraft {
|
||||
return gerror.New("只有草稿状态的安全策略可以编辑")
|
||||
}
|
||||
version = current[c.PolicyVersion].Uint()
|
||||
id = in.Id
|
||||
} else {
|
||||
maxValue, maxErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.TenantId, tenantID).Max(c.PolicyVersion)
|
||||
if maxErr != nil {
|
||||
return maxErr
|
||||
}
|
||||
version = uint(maxValue) + 1
|
||||
}
|
||||
data := g.Map{c.TenantId: tenantID, c.PolicyVersion: version, c.PolicyName: in.PolicyName, c.PublishStatus: consts.ImSecurityPublishDraft, c.PasswordPolicy: policyJSON(in.PasswordPolicy, "{}"), c.LoginRateLimit: policyJSON(in.LoginRateLimit, "{}"), c.SensitiveWordPolicy: policyJSON(in.SensitiveWordPolicy, "{}"), c.FileReviewPolicy: policyJSON(in.FileReviewPolicy, "{}"), c.RealnamePolicy: policyJSON(in.RealnamePolicy, "{}"), c.RiskChallengePolicy: policyJSON(in.RiskChallengePolicy, "{}"), c.GlobalFriendEnabled: in.GlobalFriendEnabled, c.FriendDisabledPolicy: policyJSON(in.FriendDisabledPolicy, `{"hide_profile":true,"block_qrcode":true,"block_group_member_entry":true,"blocked_entries":["search","profile_card","group_member","qrcode","invite_auto_add","recommendation"],"message":"当前时段暂不允许添加好友"}`), c.MessageCurfewEnabled: in.MessageCurfewEnabled, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: operator}
|
||||
if in.Id > 0 {
|
||||
if _, updateErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update(); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
} else {
|
||||
data[c.CreatedAt] = now
|
||||
data[c.CreatedBy] = operator
|
||||
result, insertErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Data(data).Insert()
|
||||
if insertErr != nil {
|
||||
return insertErr
|
||||
}
|
||||
id, insertErr = result.LastInsertId()
|
||||
if insertErr != nil {
|
||||
return insertErr
|
||||
}
|
||||
}
|
||||
if _, deleteErr := tx.Model(dao.ImMessageCurfewRules.Table()).Safe().Ctx(ctx).Where(rc.PolicyId, id).Delete(); deleteErr != nil {
|
||||
return deleteErr
|
||||
}
|
||||
for _, rule := range in.CurfewRules {
|
||||
_, insertErr := tx.Model(dao.ImMessageCurfewRules.Table()).Safe().Ctx(ctx).Data(g.Map{rc.TenantId: tenantID, rc.PolicyId: id, rc.RuleCode: rule.RuleCode, rc.RuleName: rule.RuleName, rc.DateType: rule.DateType, rc.CustomDates: policyJSON(rule.CustomDates, "[]"), rc.Weekdays: policyJSON(rule.Weekdays, "[]"), rc.Timezone: rule.Timezone, rc.StartTime: rule.StartTime, rc.EndTime: rule.EndTime, rc.Scope: rule.Scope, rc.WhitelistUserIds: policyJSON(rule.WhitelistUserIds, "[]"), rc.WhitelistGroupIds: policyJSON(rule.WhitelistGroupIds, "[]"), rc.Message: rule.Message, rc.Status: rule.Status, rc.Sort: rule.Sort, rc.CreatedAt: now, rc.CreatedBy: operator, rc.UpdatedAt: now, rc.UpdatedBy: operator}).Insert()
|
||||
if insertErr != nil {
|
||||
return insertErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "保存安全策略草稿失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_security_policy_save", "im_security_policy", id, "success", g.Map{"global_friend_enabled": in.GlobalFriendEnabled, "message_curfew_enabled": in.MessageCurfewEnabled})
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) Publish(ctx context.Context, in *sysin.ImSecurityPolicyPublishInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
db := imtenant.RegistryDB()
|
||||
c := dao.ImSecurityPolicy.Columns()
|
||||
var row gdb.Record
|
||||
var err error
|
||||
if row, err = db.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() {
|
||||
return gerror.New("安全策略不存在")
|
||||
}
|
||||
if row[c.PublishStatus].String() != consts.ImSecurityPublishDraft {
|
||||
return gerror.New("只有草稿状态可以发布")
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, row[c.TenantId].Int64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
operator := contexts.GetUserId(ctx)
|
||||
now := gtime.Now()
|
||||
err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
if _, updateErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.TenantId, tenantID).Where(c.PublishStatus, consts.ImSecurityPublishPublished).WhereNot(c.Id, in.Id).WhereNull(c.DeletedAt).Data(g.Map{c.PublishStatus: consts.ImSecurityPublishArchived, c.UpdatedAt: now, c.UpdatedBy: operator}).Update(); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if _, updateErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(g.Map{c.PublishStatus: consts.ImSecurityPublishPublished, c.PublishedAt: now, c.PublishedBy: operator, c.UpdatedAt: now, c.UpdatedBy: operator}).Update(); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if tenantID > 0 {
|
||||
tc := dao.ImTenantConfig.Columns()
|
||||
if _, updateErr := tx.Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).Where(tc.TenantId, tenantID).Data(g.Map{tc.SecurityPolicyVersion: row[c.PolicyVersion].Uint(), tc.GlobalFriendEnabled: row[c.GlobalFriendEnabled].Int(), tc.MessageCurfewEnabled: row[c.MessageCurfewEnabled].Int(), tc.UpdatedAt: now, tc.UpdatedBy: operator}).Update(); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "发布安全策略失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_security_policy_publish", "im_security_policy", in.Id, "success", g.Map{"reason": in.Reason, "version": row[c.PolicyVersion].Uint()})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", row[c.PolicyVersion].Uint())
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) RestoreDefault(ctx context.Context, in *sysin.ImSecurityPolicyRestoreInp) (int64, error) {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c := dao.ImSecurityPolicy.Columns()
|
||||
var source *sysin.ImSecurityPolicyModel
|
||||
err = imtenant.RegistryDB().Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).
|
||||
Fields(securityPolicyFields("")...).Where(c.TenantId, 0).
|
||||
Where(c.PublishStatus, consts.ImSecurityPublishPublished).WhereNull(c.DeletedAt).
|
||||
OrderDesc(c.PolicyVersion).Scan(&source)
|
||||
if err != nil {
|
||||
return 0, gerror.Wrap(err, "读取全局默认安全策略失败")
|
||||
}
|
||||
if source == nil {
|
||||
return 0, gerror.New("全局默认安全策略不存在")
|
||||
}
|
||||
source.CurfewRules, err = s.curfewRules(ctx, source.Id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rules := make([]*sysin.ImMessageCurfewRuleInp, 0, len(source.CurfewRules))
|
||||
for _, rule := range source.CurfewRules {
|
||||
rules = append(rules, &sysin.ImMessageCurfewRuleInp{RuleCode: rule.RuleCode, RuleName: rule.RuleName, DateType: rule.DateType, CustomDates: gjson.New(rule.CustomDates), Weekdays: gjson.New(rule.Weekdays), Timezone: rule.Timezone, StartTime: rule.StartTime, EndTime: rule.EndTime, Scope: rule.Scope, WhitelistUserIds: gjson.New(rule.WhitelistUserIds), WhitelistGroupIds: gjson.New(rule.WhitelistGroupIds), Message: rule.Message, Status: rule.Status, Sort: rule.Sort})
|
||||
}
|
||||
id, err := s.Save(ctx, &sysin.ImSecurityPolicySaveInp{TenantId: tenantID, PolicyName: "恢复默认安全策略", PasswordPolicy: gjson.New(source.PasswordPolicy), LoginRateLimit: gjson.New(source.LoginRateLimit), SensitiveWordPolicy: gjson.New(source.SensitiveWordPolicy), FileReviewPolicy: gjson.New(source.FileReviewPolicy), RealnamePolicy: gjson.New(source.RealnamePolicy), RiskChallengePolicy: gjson.New(source.RiskChallengePolicy), GlobalFriendEnabled: source.GlobalFriendEnabled, FriendDisabledPolicy: gjson.New(source.FriendDisabledPolicy), MessageCurfewEnabled: source.MessageCurfewEnabled, Remark: in.Reason, CurfewRules: rules})
|
||||
if err == nil {
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_security_policy_restore_default", "im_security_policy", id, "success", g.Map{"reason": in.Reason})
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) IPRuleList(ctx context.Context, in *sysin.ImSecurityIPRuleListInp) (list []*sysin.ImSecurityIPRuleModel, total int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
c := dao.ImSecurityIpRules.Columns()
|
||||
model := imtenant.RegistryDB().Model(dao.ImSecurityIpRules.Table()).Safe().Ctx(ctx).Fields(c.Id, c.TenantId, c.RuleType, c.IpCidr, c.Action, "CAST("+c.SceneJson+" AS CHAR) scene_json", c.RiskLevel, c.Status, c.ExpiresAt, c.Remark, c.CreatedAt, c.UpdatedAt).Where(c.TenantId, tenantID).WhereNull(c.DeletedAt)
|
||||
if in.RuleType != "" {
|
||||
model = model.Where(c.RuleType, in.RuleType)
|
||||
}
|
||||
if in.IpCidr != "" {
|
||||
model = model.WhereLike(c.IpCidr, "%"+in.IpCidr+"%")
|
||||
}
|
||||
if in.Status != 0 {
|
||||
model = model.Where(c.Status, in.Status)
|
||||
}
|
||||
err = model.Page(in.Page, in.PerPage).OrderDesc(c.Id).ScanAndCount(&list, &total, false)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) IPRuleEdit(ctx context.Context, in *sysin.ImSecurityIPRuleEditInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if net.ParseIP(in.IpCidr) == nil {
|
||||
if _, _, err := net.ParseCIDR(in.IpCidr); err != nil {
|
||||
return gerror.New("IP或CIDR格式不合法")
|
||||
}
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := dao.ImSecurityIpRules.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
now := gtime.Now()
|
||||
data := g.Map{c.TenantId: tenantID, c.RuleType: in.RuleType, c.IpCidr: in.IpCidr, c.Action: in.Action, c.SceneJson: policyJSON(in.SceneJson, "[]"), c.RiskLevel: in.RiskLevel, c.Status: in.Status, c.ExpiresAt: in.ExpiresAt, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: contexts.GetUserId(ctx)}
|
||||
targetID := in.Id
|
||||
if in.Id > 0 {
|
||||
var row gdb.Record
|
||||
if row, err = db.Model(dao.ImSecurityIpRules.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() || row[c.TenantId].Int64() != tenantID {
|
||||
return gerror.New("IP规则不存在")
|
||||
}
|
||||
_, err = db.Model(dao.ImSecurityIpRules.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update()
|
||||
} else {
|
||||
data[c.CreatedAt] = now
|
||||
data[c.CreatedBy] = contexts.GetUserId(ctx)
|
||||
result, insertErr := db.Model(dao.ImSecurityIpRules.Table()).Safe().Ctx(ctx).Data(data).Insert()
|
||||
err = insertErr
|
||||
if err == nil {
|
||||
targetID, err = result.LastInsertId()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "保存IP规则失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_security_ip_rule_edit", "im_security_ip_rule", targetID, "success", g.Map{"ip_cidr": in.IpCidr, "rule_type": in.RuleType})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", uint(targetID))
|
||||
}
|
||||
|
||||
func (s *sSysImSecurityPolicy) IPRuleDelete(ctx context.Context, in *sysin.ImSecurityIPRuleDeleteInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
c := dao.ImSecurityIpRules.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
var row gdb.Record
|
||||
var err error
|
||||
if row, err = db.Model(dao.ImSecurityIpRules.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() {
|
||||
return gerror.New("IP规则不存在")
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, row[c.TenantId].Int64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Model(dao.ImSecurityIpRules.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(g.Map{c.Status: consts.StatusDisable, c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx), c.UpdatedAt: gtime.Now(), c.UpdatedBy: contexts.GetUserId(ctx)}).Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "删除IP规则失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_security_ip_rule_delete", "im_security_ip_rule", in.Id, "success", nil)
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", uint(in.Id))
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysImSensitiveWord struct{}
|
||||
|
||||
func NewSysImSensitiveWord() *sSysImSensitiveWord { return &sSysImSensitiveWord{} }
|
||||
func init() { service.RegisterSysImSensitiveWord(NewSysImSensitiveWord()) }
|
||||
|
||||
func normalizedSensitiveWord(value string) (string, string) {
|
||||
normalized := strings.ToLower(strings.TrimSpace(value))
|
||||
sum := sha256.Sum256([]byte(normalized))
|
||||
return normalized, hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func (s *sSysImSensitiveWord) List(ctx context.Context, in *sysin.ImSensitiveWordListInp) (list []*sysin.ImSensitiveWordListModel, total int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
c := dao.ImSensitiveWords.Columns()
|
||||
gc := dao.ImSensitiveWordGroups.Columns()
|
||||
model := imtenant.RegistryDB().Model(dao.ImSensitiveWords.Table()+" w").Safe().Ctx(ctx).LeftJoin(dao.ImSensitiveWordGroups.Table()+" g", "g."+gc.Id+"=w."+c.GroupId).Fields("w."+c.Id, "w."+c.TenantId, "w."+c.GroupId, "g."+gc.GroupName, "w."+c.Word, "w."+c.MatchType, "CAST(w."+c.SceneJson+" AS CHAR) scene_json", "w."+c.Action, "w."+c.Replacement, "w."+c.RiskLevel, "w."+c.Priority, "w."+c.Status, "w."+c.Remark, "w."+c.CreatedAt, "w."+c.UpdatedAt).Where("w."+c.TenantId, tenantID).WhereNull("w." + c.DeletedAt)
|
||||
if in.GroupId > 0 {
|
||||
model = model.Where("w."+c.GroupId, in.GroupId)
|
||||
}
|
||||
if in.Word != "" {
|
||||
model = model.WhereLike("w."+c.Word, "%"+in.Word+"%")
|
||||
}
|
||||
if in.MatchType != "" {
|
||||
model = model.Where("w."+c.MatchType, in.MatchType)
|
||||
}
|
||||
if in.Action != "" {
|
||||
model = model.Where("w."+c.Action, in.Action)
|
||||
}
|
||||
if in.RiskLevel != "" {
|
||||
model = model.Where("w."+c.RiskLevel, in.RiskLevel)
|
||||
}
|
||||
if in.Status != 0 {
|
||||
model = model.Where("w."+c.Status, in.Status)
|
||||
}
|
||||
err = model.Page(in.Page, in.PerPage).OrderDesc("w."+c.Priority).OrderDesc("w."+c.Id).ScanAndCount(&list, &total, false)
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "获取敏感词列表失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImSensitiveWord) Edit(ctx context.Context, in *sysin.ImSensitiveWordEditInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = validateSensitiveWordGroup(ctx, tenantID, in.GroupId); err != nil {
|
||||
return err
|
||||
}
|
||||
_, hash := normalizedSensitiveWord(in.Word)
|
||||
c := dao.ImSensitiveWords.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
now := gtime.Now()
|
||||
data := g.Map{c.TenantId: tenantID, c.GroupId: in.GroupId, c.Word: in.Word, c.WordHash: hash, c.MatchType: in.MatchType, c.SceneJson: policyJSON(in.SceneJson, "[]"), c.Action: in.Action, c.Replacement: in.Replacement, c.RiskLevel: in.RiskLevel, c.Priority: in.Priority, c.Status: in.Status, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: contexts.GetUserId(ctx)}
|
||||
targetID := in.Id
|
||||
if in.Id > 0 {
|
||||
var row gdb.Record
|
||||
if row, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() || row[c.TenantId].Int64() != tenantID {
|
||||
return gerror.New("敏感词不存在")
|
||||
}
|
||||
_, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update()
|
||||
} else {
|
||||
data[c.CreatedAt] = now
|
||||
data[c.CreatedBy] = contexts.GetUserId(ctx)
|
||||
result, insertErr := db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Data(data).Insert()
|
||||
err = insertErr
|
||||
if err == nil {
|
||||
targetID, err = result.LastInsertId()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "保存敏感词失败,请检查是否重复")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_sensitive_word_edit", "im_sensitive_word", targetID, "success", g.Map{"group_id": in.GroupId, "action": in.Action})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(targetID))
|
||||
}
|
||||
|
||||
func (s *sSysImSensitiveWord) Delete(ctx context.Context, in *sysin.ImSensitiveWordDeleteInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.updateWordState(ctx, in.Id, consts.StatusDisable, true, "im_sensitive_word_delete")
|
||||
}
|
||||
func (s *sSysImSensitiveWord) Status(ctx context.Context, in *sysin.ImSensitiveWordStatusInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.updateWordState(ctx, in.Id, in.Status, false, "im_sensitive_word_status")
|
||||
}
|
||||
func (s *sSysImSensitiveWord) updateWordState(ctx context.Context, id int64, status int, deleted bool, action string) error {
|
||||
c := dao.ImSensitiveWords.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
var row gdb.Record
|
||||
var err error
|
||||
if row, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() {
|
||||
return gerror.New("敏感词不存在")
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, row[c.TenantId].Int64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := g.Map{c.Status: status, c.UpdatedAt: gtime.Now(), c.UpdatedBy: contexts.GetUserId(ctx)}
|
||||
if deleted {
|
||||
data[c.DeletedAt] = gtime.Now()
|
||||
data[c.DeletedBy] = contexts.GetUserId(ctx)
|
||||
}
|
||||
if _, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, id).Data(data).Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新敏感词失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, action, "im_sensitive_word", id, "success", g.Map{"status": status})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(id))
|
||||
}
|
||||
|
||||
func (s *sSysImSensitiveWord) BatchImport(ctx context.Context, in *sysin.ImSensitiveWordBatchImportInp) (res *sysin.ImSensitiveWordBatchImportModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = validateSensitiveWordGroup(ctx, tenantID, in.GroupId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &sysin.ImSensitiveWordBatchImportModel{}
|
||||
db := imtenant.RegistryDB()
|
||||
c := dao.ImSensitiveWords.Columns()
|
||||
seen := map[string]struct{}{}
|
||||
err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
for _, raw := range in.Words {
|
||||
word, hash := normalizedSensitiveWord(raw)
|
||||
if word == "" {
|
||||
res.Skipped++
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[hash]; ok {
|
||||
res.Skipped++
|
||||
continue
|
||||
}
|
||||
seen[hash] = struct{}{}
|
||||
count, countErr := tx.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.TenantId, tenantID).Where(c.GroupId, in.GroupId).Where(c.WordHash, hash).WhereNull(c.DeletedAt).Count()
|
||||
if countErr != nil {
|
||||
return countErr
|
||||
}
|
||||
if count > 0 {
|
||||
res.Skipped++
|
||||
continue
|
||||
}
|
||||
now := gtime.Now()
|
||||
_, insertErr := tx.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Data(g.Map{c.TenantId: tenantID, c.GroupId: in.GroupId, c.Word: strings.TrimSpace(raw), c.WordHash: hash, c.MatchType: in.MatchType, c.SceneJson: policyJSON(in.SceneJson, "[]"), c.Action: in.Action, c.Replacement: in.Replacement, c.RiskLevel: in.RiskLevel, c.Status: consts.StatusEnabled, c.CreatedAt: now, c.CreatedBy: contexts.GetUserId(ctx), c.UpdatedAt: now, c.UpdatedBy: contexts.GetUserId(ctx)}).Insert()
|
||||
if insertErr != nil {
|
||||
return insertErr
|
||||
}
|
||||
res.Inserted++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "批量导入敏感词失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_sensitive_word_batch_import", "im_sensitive_word", 0, "success", g.Map{"inserted": res.Inserted, "skipped": res.Skipped})
|
||||
err = publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(res.Inserted))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImSensitiveWord) GroupList(ctx context.Context, in *sysin.ImSensitiveWordGroupListInp) (list []*sysin.ImSensitiveWordGroupModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := dao.ImSensitiveWordGroups.Columns()
|
||||
err = imtenant.RegistryDB().Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Fields(c.Id, c.TenantId, c.GroupCode, c.GroupName, c.Priority, c.Status, c.Remark, c.CreatedAt).Where(c.TenantId, tenantID).WhereNull(c.DeletedAt).OrderDesc(c.Priority).OrderAsc(c.GroupName).Scan(&list)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImSensitiveWord) GroupEdit(ctx context.Context, in *sysin.ImSensitiveWordGroupEditInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := dao.ImSensitiveWordGroups.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
now := gtime.Now()
|
||||
data := g.Map{c.TenantId: tenantID, c.GroupCode: in.GroupCode, c.GroupName: in.GroupName, c.Priority: in.Priority, c.Status: in.Status, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: contexts.GetUserId(ctx)}
|
||||
targetID := in.Id
|
||||
if in.Id > 0 {
|
||||
var row gdb.Record
|
||||
if row, err = db.Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() || row[c.TenantId].Int64() != tenantID {
|
||||
return gerror.New("敏感词分组不存在")
|
||||
}
|
||||
if row[c.GroupCode].String() != in.GroupCode {
|
||||
return gerror.New("分组编码创建后不能修改")
|
||||
}
|
||||
_, err = db.Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update()
|
||||
} else {
|
||||
data[c.CreatedAt] = now
|
||||
data[c.CreatedBy] = contexts.GetUserId(ctx)
|
||||
result, insertErr := db.Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Data(data).Insert()
|
||||
err = insertErr
|
||||
if err == nil {
|
||||
targetID, err = result.LastInsertId()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "保存敏感词分组失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_sensitive_word_group_edit", "im_sensitive_word_group", targetID, "success", nil)
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(targetID))
|
||||
}
|
||||
|
||||
func validateSensitiveWordGroup(ctx context.Context, tenantID, groupID int64) error {
|
||||
c := dao.ImSensitiveWordGroups.Columns()
|
||||
count, err := imtenant.RegistryDB().Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Where(c.Id, groupID).Where(c.TenantId, tenantID).WhereNull(c.DeletedAt).Count()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "检查敏感词分组失败")
|
||||
}
|
||||
if count == 0 {
|
||||
return gerror.New("敏感词分组不存在")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/dao"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/library/imtenant"
|
||||
"hotgo/internal/model/input/sysin"
|
||||
"hotgo/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysImUserPolicyGroup struct{}
|
||||
|
||||
func NewSysImUserPolicyGroup() *sSysImUserPolicyGroup { return &sSysImUserPolicyGroup{} }
|
||||
func init() { service.RegisterSysImUserPolicyGroup(NewSysImUserPolicyGroup()) }
|
||||
|
||||
func (s *sSysImUserPolicyGroup) List(ctx context.Context, in *sysin.ImUserPolicyGroupListInp) (list []*sysin.ImUserPolicyGroupListModel, total int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
c := dao.ImUserPolicyGroups.Columns()
|
||||
mc := dao.ImUserPolicyGroupMembers.Columns()
|
||||
model := imtenant.RegistryDB().Model(dao.ImUserPolicyGroups.Table()+" p").Safe().Ctx(ctx).
|
||||
Fields("p."+c.Id, "p."+c.TenantId, "p."+c.GroupCode, "p."+c.GroupName, "p."+c.Priority, "p."+c.Status,
|
||||
"CAST(p."+c.PolicyJson+" AS CHAR) policy_json", "CAST(p."+c.IpWhitelist+" AS CHAR) ip_whitelist",
|
||||
"CAST(p."+c.IpBlacklist+" AS CHAR) ip_blacklist", "p."+c.Version, "p."+c.Remark, "p."+c.CreatedAt, "p."+c.UpdatedAt,
|
||||
"(SELECT COUNT(*) FROM "+dao.ImUserPolicyGroupMembers.Table()+" m WHERE m."+mc.GroupId+"=p."+c.Id+" AND m."+mc.Status+"=1 AND m."+mc.DeletedAt+" IS NULL) member_count").
|
||||
Where("p."+c.TenantId, tenantID).WhereNull("p." + c.DeletedAt)
|
||||
if in.Keyword != "" {
|
||||
keyword := "%" + in.Keyword + "%"
|
||||
model = model.Where("(p."+c.GroupCode+" LIKE ? OR p."+c.GroupName+" LIKE ?)", keyword, keyword)
|
||||
}
|
||||
if in.Status != 0 {
|
||||
model = model.Where("p."+c.Status, in.Status)
|
||||
}
|
||||
err = model.Page(in.Page, in.PerPage).OrderDesc("p."+c.Priority).OrderDesc("p."+c.Id).ScanAndCount(&list, &total, false)
|
||||
if err != nil {
|
||||
err = gerror.Wrap(err, "获取用户策略分组失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImUserPolicyGroup) View(ctx context.Context, in *sysin.ImUserPolicyGroupViewInp) (res *sysin.ImUserPolicyGroupViewModel, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
c := dao.ImUserPolicyGroups.Columns()
|
||||
var row *sysin.ImUserPolicyGroupListModel
|
||||
err = imtenant.RegistryDB().Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).
|
||||
Fields(c.Id, c.TenantId, c.GroupCode, c.GroupName, c.Priority, c.Status, "CAST("+c.PolicyJson+" AS CHAR) policy_json", "CAST("+c.IpWhitelist+" AS CHAR) ip_whitelist", "CAST("+c.IpBlacklist+" AS CHAR) ip_blacklist", c.Version, c.Remark, c.CreatedAt, c.UpdatedAt).
|
||||
Where(c.Id, in.Id).WhereNull(c.DeletedAt).Scan(&row)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "获取用户策略分组详情失败")
|
||||
}
|
||||
if row == nil {
|
||||
return nil, gerror.New("用户策略分组不存在")
|
||||
}
|
||||
if _, _, err = resolveImPolicyTenant(ctx, row.TenantId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sysin.ImUserPolicyGroupViewModel{ImUserPolicyGroupListModel: *row}, nil
|
||||
}
|
||||
|
||||
func (s *sSysImUserPolicyGroup) Edit(ctx context.Context, in *sysin.ImUserPolicyGroupEditInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c := dao.ImUserPolicyGroups.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
now := gtime.Now()
|
||||
operator := contexts.GetUserId(ctx)
|
||||
data := g.Map{c.GroupName: in.GroupName, c.Priority: in.Priority, c.Status: in.Status, c.PolicyJson: policyJSON(in.PolicyJson, "{}"), c.IpWhitelist: policyJSON(in.IpWhitelist, "[]"), c.IpBlacklist: policyJSON(in.IpBlacklist, "[]"), c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: operator}
|
||||
var targetID int64
|
||||
if in.Id > 0 {
|
||||
var current gdb.Record
|
||||
if current, err = db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return gerror.Wrap(err, "读取用户策略分组失败")
|
||||
}
|
||||
if current.IsEmpty() || current[c.TenantId].Int64() != tenantID {
|
||||
return gerror.New("用户策略分组不存在")
|
||||
}
|
||||
if current[c.GroupCode].String() != in.GroupCode {
|
||||
return gerror.New("分组编码创建后不能修改")
|
||||
}
|
||||
data[c.Version] = gdb.Raw("version+1")
|
||||
if _, err = db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新用户策略分组失败")
|
||||
}
|
||||
targetID = in.Id
|
||||
} else {
|
||||
data[c.TenantId] = tenantID
|
||||
data[c.GroupCode] = in.GroupCode
|
||||
data[c.Version] = 1
|
||||
data[c.CreatedAt] = now
|
||||
data[c.CreatedBy] = operator
|
||||
result, insertErr := db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Data(data).Insert()
|
||||
if insertErr != nil {
|
||||
return gerror.Wrap(insertErr, "新增用户策略分组失败")
|
||||
}
|
||||
targetID, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "获取用户策略分组ID失败")
|
||||
}
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_edit", "im_user_policy_group", targetID, "success", g.Map{"group_code": in.GroupCode})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.updated", "user_policy_groups", uint(targetID))
|
||||
}
|
||||
|
||||
func (s *sSysImUserPolicyGroup) Delete(ctx context.Context, in *sysin.ImUserPolicyGroupDeleteInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
c := dao.ImUserPolicyGroups.Columns()
|
||||
mc := dao.ImUserPolicyGroupMembers.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
var row gdb.Record
|
||||
var err error
|
||||
if row, err = db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return gerror.Wrap(err, "读取用户策略分组失败")
|
||||
}
|
||||
if row.IsEmpty() {
|
||||
return gerror.New("用户策略分组不存在")
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, row[c.TenantId].Int64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count, err := db.Model(dao.ImUserPolicyGroupMembers.Table()).Safe().Ctx(ctx).Where(mc.GroupId, in.Id).Where(mc.Status, consts.StatusEnabled).WhereNull(mc.DeletedAt).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("请先移除分组内成员")
|
||||
}
|
||||
now := gtime.Now()
|
||||
_, err = db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(g.Map{c.DeletedAt: now, c.DeletedBy: contexts.GetUserId(ctx), c.Status: consts.StatusDisable}).Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "删除用户策略分组失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_delete", "im_user_policy_group", in.Id, "success", nil)
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.deleted", "user_policy_groups", uint(in.Id))
|
||||
}
|
||||
|
||||
func (s *sSysImUserPolicyGroup) Status(ctx context.Context, in *sysin.ImUserPolicyGroupStatusInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
c := dao.ImUserPolicyGroups.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
var row gdb.Record
|
||||
var err error
|
||||
if row, err = db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() {
|
||||
return gerror.New("用户策略分组不存在")
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, row[c.TenantId].Int64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(g.Map{c.Status: in.Status, c.Version: gdb.Raw("version+1"), c.UpdatedBy: contexts.GetUserId(ctx), c.UpdatedAt: gtime.Now()}).Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "更新用户策略分组状态失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_status", "im_user_policy_group", in.Id, "success", g.Map{"status": in.Status})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.updated", "user_policy_groups", uint(in.Id))
|
||||
}
|
||||
|
||||
func (s *sSysImUserPolicyGroup) MemberList(ctx context.Context, in *sysin.ImUserPolicyGroupMemberListInp) (list []*sysin.ImUserPolicyGroupMemberModel, total int, err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
gc := dao.ImUserPolicyGroups.Columns()
|
||||
mc := dao.ImUserPolicyGroupMembers.Columns()
|
||||
var group gdb.Record
|
||||
if group, err = imtenant.RegistryDB().Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(gc.Id, in.GroupId).WhereNull(gc.DeletedAt).One(); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if group.IsEmpty() {
|
||||
return nil, 0, gerror.New("用户策略分组不存在")
|
||||
}
|
||||
if _, _, err = resolveImPolicyTenant(ctx, group[gc.TenantId].Int64()); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
model := imtenant.RegistryDB().Model(dao.ImUserPolicyGroupMembers.Table()).Safe().Ctx(ctx).Fields(mc.Id, mc.GroupId, mc.TenantId, mc.UserId, mc.Status, mc.ValidFrom, mc.ValidUntil, mc.Remark, mc.CreatedAt).Where(mc.GroupId, in.GroupId).WhereNull(mc.DeletedAt)
|
||||
if in.UserId > 0 {
|
||||
model = model.Where(mc.UserId, in.UserId)
|
||||
}
|
||||
err = model.Page(in.Page, in.PerPage).OrderDesc(mc.Id).ScanAndCount(&list, &total, false)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysImUserPolicyGroup) MemberAdd(ctx context.Context, in *sysin.ImUserPolicyGroupMemberAddInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
gc := dao.ImUserPolicyGroups.Columns()
|
||||
mc := dao.ImUserPolicyGroupMembers.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
var group gdb.Record
|
||||
var err error
|
||||
if group, err = db.Model(dao.ImUserPolicyGroups.Table()).Safe().Ctx(ctx).Where(gc.Id, in.GroupId).WhereNull(gc.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if group.IsEmpty() {
|
||||
return gerror.New("用户策略分组不存在")
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, group[gc.TenantId].Int64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tenantID == 0 {
|
||||
return gerror.New("全局默认分组不能添加成员,请先选择具体租户")
|
||||
}
|
||||
tenantDB, _, err := imtenant.DB(ctx, tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userCount, err := tenantDB.Model("im_users").Safe().Ctx(ctx).Where("id", in.UserId).Count()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "检查租户用户失败")
|
||||
}
|
||||
if userCount == 0 {
|
||||
return gerror.New("用户不存在或不属于当前租户")
|
||||
}
|
||||
var current gdb.Record
|
||||
current, err = db.Model(dao.ImUserPolicyGroupMembers.Table()).Safe().Ctx(ctx).Where(mc.GroupId, in.GroupId).Where(mc.UserId, in.UserId).One()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := gtime.Now()
|
||||
data := g.Map{mc.TenantId: tenantID, mc.Status: consts.StatusEnabled, mc.ValidFrom: in.ValidFrom, mc.ValidUntil: in.ValidUntil, mc.Remark: in.Remark, mc.DeletedAt: nil, mc.DeletedBy: nil, mc.UpdatedAt: now, mc.UpdatedBy: contexts.GetUserId(ctx)}
|
||||
if current.IsEmpty() {
|
||||
data[mc.GroupId] = in.GroupId
|
||||
data[mc.UserId] = in.UserId
|
||||
data[mc.CreatedAt] = now
|
||||
data[mc.CreatedBy] = contexts.GetUserId(ctx)
|
||||
_, err = db.Model(dao.ImUserPolicyGroupMembers.Table()).Safe().Ctx(ctx).Data(data).Insert()
|
||||
} else {
|
||||
_, err = db.Model(dao.ImUserPolicyGroupMembers.Table()).Safe().Ctx(ctx).Where(mc.Id, current[mc.Id]).Data(data).Update()
|
||||
}
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "添加策略分组成员失败")
|
||||
}
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_member_add", "im_user_policy_group", in.GroupId, "success", g.Map{"user_id": in.UserId})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.updated", "user_policy_groups", uint(in.GroupId))
|
||||
}
|
||||
|
||||
func (s *sSysImUserPolicyGroup) MemberRemove(ctx context.Context, in *sysin.ImUserPolicyGroupMemberRemoveInp) error {
|
||||
if err := in.Filter(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
mc := dao.ImUserPolicyGroupMembers.Columns()
|
||||
db := imtenant.RegistryDB()
|
||||
var row gdb.Record
|
||||
var err error
|
||||
if row, err = db.Model(dao.ImUserPolicyGroupMembers.Table()).Safe().Ctx(ctx).Where(mc.Id, in.Id).WhereNull(mc.DeletedAt).One(); err != nil {
|
||||
return err
|
||||
}
|
||||
if row.IsEmpty() {
|
||||
return gerror.New("策略分组成员不存在")
|
||||
}
|
||||
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, row[mc.TenantId].Int64())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Model(dao.ImUserPolicyGroupMembers.Table()).Safe().Ctx(ctx).Where(mc.Id, in.Id).Data(g.Map{mc.Status: consts.StatusDisable, mc.DeletedAt: gtime.Now(), mc.DeletedBy: contexts.GetUserId(ctx), mc.UpdatedAt: gtime.Now(), mc.UpdatedBy: contexts.GetUserId(ctx)}).Update()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "移除策略分组成员失败")
|
||||
}
|
||||
groupID := row[mc.GroupId].Int64()
|
||||
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_member_remove", "im_user_policy_group", groupID, "success", g.Map{"user_id": row[mc.UserId].Int64()})
|
||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.updated", "user_policy_groups", uint(groupID))
|
||||
}
|
||||
Reference in New Issue
Block a user