package sysin import ( "context" "strings" "time" "hotgo/internal/consts" "hotgo/internal/library/dict" "hotgo/internal/model/input/form" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" ) type ImSecurityPolicyListInp struct { form.PageReq TenantId int64 `json:"tenantId"` PublishStatus string `json:"publishStatus"` } func (in *ImSecurityPolicyListInp) Filter(ctx context.Context) error { if in.PublishStatus != "" && !dict.HasOptionKey(consts.ImSecurityPublishStatusOptions, in.PublishStatus) { return gerror.New("发布状态不合法") } return nil } type ImMessageCurfewRuleModel struct { Id int64 `json:"id"` RuleCode string `json:"ruleCode"` RuleName string `json:"ruleName"` DateType string `json:"dateType"` CustomDates string `json:"customDates"` Weekdays string `json:"weekdays"` Timezone string `json:"timezone"` StartTime string `json:"startTime"` EndTime string `json:"endTime"` Scope string `json:"scope"` WhitelistUserIds string `json:"whitelistUserIds"` WhitelistGroupIds string `json:"whitelistGroupIds"` Message string `json:"message"` Status int `json:"status"` Sort int `json:"sort"` } type ImSecurityPolicyModel struct { Id int64 `json:"id"` TenantId int64 `json:"tenantId"` PolicyVersion uint `json:"policyVersion"` PolicyName string `json:"policyName"` PublishStatus string `json:"publishStatus"` PasswordPolicy string `json:"passwordPolicy"` LoginRateLimit string `json:"loginRateLimit"` SensitiveWordPolicy string `json:"sensitiveWordPolicy"` FileReviewPolicy string `json:"fileReviewPolicy"` RealnamePolicy string `json:"realnamePolicy"` RiskChallengePolicy string `json:"riskChallengePolicy"` GlobalFriendEnabled int `json:"globalFriendEnabled"` FriendDisabledPolicy string `json:"friendDisabledPolicy"` MessageCurfewEnabled int `json:"messageCurfewEnabled"` Remark string `json:"remark"` PublishedAt *gtime.Time `json:"publishedAt"` PublishedBy int64 `json:"publishedBy"` CreatedAt *gtime.Time `json:"createdAt"` UpdatedAt *gtime.Time `json:"updatedAt"` CurfewRules []*ImMessageCurfewRuleModel `json:"curfewRules"` } type ImSecurityPolicyViewInp struct { Id int64 `json:"id"` TenantId int64 `json:"tenantId"` } func (in *ImSecurityPolicyViewInp) Filter(ctx context.Context) error { if in.Id <= 0 && in.TenantId < 0 { return gerror.New("策略ID或租户ID不合法") } return nil } type ImMessageCurfewRuleInp struct { RuleCode string `json:"ruleCode"` RuleName string `json:"ruleName"` DateType string `json:"dateType"` CustomDates *gjson.Json `json:"customDates"` Weekdays *gjson.Json `json:"weekdays"` Timezone string `json:"timezone"` StartTime string `json:"startTime"` EndTime string `json:"endTime"` Scope string `json:"scope"` WhitelistUserIds *gjson.Json `json:"whitelistUserIds"` WhitelistGroupIds *gjson.Json `json:"whitelistGroupIds"` Message string `json:"message"` Status int `json:"status"` Sort int `json:"sort"` } func (in *ImMessageCurfewRuleInp) Filter(ctx context.Context) error { in.RuleCode = strings.TrimSpace(in.RuleCode) in.RuleName = strings.TrimSpace(in.RuleName) in.Timezone = strings.TrimSpace(in.Timezone) in.Message = strings.TrimSpace(in.Message) if !imPolicyGroupCodePattern.MatchString(in.RuleCode) { return gerror.New("宵禁规则编码格式不合法") } if in.RuleName == "" { return gerror.New("宵禁规则名称不能为空") } if !dict.HasOptionKey(consts.ImCurfewDateTypeOptions, in.DateType) { return gerror.New("宵禁日期类型不合法") } if !dict.HasOptionKey(consts.ImCurfewScopeOptions, in.Scope) { return gerror.New("宵禁范围不合法") } if len(in.StartTime) != 5 || len(in.EndTime) != 5 { return gerror.New("宵禁时间必须使用HH:mm格式") } if _, err := time.Parse("15:04", in.StartTime); err != nil { return gerror.New("宵禁开始时间不合法") } if _, err := time.Parse("15:04", in.EndTime); err != nil { return gerror.New("宵禁结束时间不合法") } if in.Timezone == "" { in.Timezone = "Asia/Shanghai" } if in.Message == "" { in.Message = "当前时段暂不能发送消息" } if in.Status == 0 { in.Status = consts.StatusEnabled } if in.Status != consts.StatusEnabled && in.Status != consts.StatusDisable { return gerror.New("宵禁规则状态不合法") } return nil } type ImSecurityPolicySaveInp struct { Id int64 `json:"id"` TenantId int64 `json:"tenantId"` PolicyName string `json:"policyName" v:"required#策略名称不能为空"` PasswordPolicy *gjson.Json `json:"passwordPolicy"` LoginRateLimit *gjson.Json `json:"loginRateLimit"` SensitiveWordPolicy *gjson.Json `json:"sensitiveWordPolicy"` FileReviewPolicy *gjson.Json `json:"fileReviewPolicy"` RealnamePolicy *gjson.Json `json:"realnamePolicy"` RiskChallengePolicy *gjson.Json `json:"riskChallengePolicy"` GlobalFriendEnabled int `json:"globalFriendEnabled"` FriendDisabledPolicy *gjson.Json `json:"friendDisabledPolicy"` MessageCurfewEnabled int `json:"messageCurfewEnabled"` Remark string `json:"remark"` CurfewRules []*ImMessageCurfewRuleInp `json:"curfewRules"` } func (in *ImSecurityPolicySaveInp) Filter(ctx context.Context) error { in.PolicyName = strings.TrimSpace(in.PolicyName) in.Remark = strings.TrimSpace(in.Remark) if in.PolicyName == "" || len([]rune(in.PolicyName)) > 128 { return gerror.New("策略名称长度必须为1至128个字符") } if in.GlobalFriendEnabled != 0 && in.GlobalFriendEnabled != 1 { return gerror.New("全局好友开关不合法") } if in.LoginRateLimit != nil { normalized, err := normalizeLoginRateLimit(in.LoginRateLimit) if err != nil { return err } in.LoginRateLimit = normalized } if in.MessageCurfewEnabled != 0 && in.MessageCurfewEnabled != 1 { return gerror.New("宵禁开关不合法") } if in.GlobalFriendEnabled == 0 { policy := make(map[string]interface{}) if in.FriendDisabledPolicy != nil { policy = in.FriendDisabledPolicy.Map() } policy["hide_profile"] = true policy["block_qrcode"] = true policy["block_group_member_entry"] = true policy["blocked_entries"] = []string{ "search", "profile_card", "group_member", "qrcode", "invite_auto_add", "recommendation", } message, ok := policy["message"].(string) if !ok || strings.TrimSpace(message) == "" { policy["message"] = "当前时段暂不允许添加好友" } in.FriendDisabledPolicy = gjson.New(policy) } if in.MessageCurfewEnabled == 1 && len(in.CurfewRules) == 0 { return gerror.New("启用宵禁时至少配置一条宵禁规则") } seen := make(map[string]struct{}, len(in.CurfewRules)) hasPrivateMessageRule := false for _, rule := range in.CurfewRules { if rule == nil { return gerror.New("宵禁规则不能为空") } if err := rule.Filter(ctx); err != nil { return err } if _, ok := seen[rule.RuleCode]; ok { return gerror.New("宵禁规则编码不能重复") } seen[rule.RuleCode] = struct{}{} if rule.Scope == consts.ImCurfewScopePrivate && rule.Status == consts.StatusEnabled { hasPrivateMessageRule = true } } if in.MessageCurfewEnabled == 1 && !hasPrivateMessageRule { return gerror.New("启用宵禁时至少配置一条启用的单聊/私聊规则") } return nil } // normalizeLoginRateLimit 校验并归一化登录失败锁定配置: // {enabled, password_max_attempts, lock_seconds, window_seconds}。 // enabled=false 时输出仅含 enabled,行为与未配置一致。 func normalizeLoginRateLimit(value *gjson.Json) (*gjson.Json, error) { policy := map[string]any{"enabled": false, "password_max_attempts": 5, "lock_seconds": 86400, "window_seconds": 3600} if value != nil && value.String() != "" && value.String() != "null" { for key, item := range value.Map() { policy[key] = item } } if enabled, _ := policy["enabled"].(bool); !enabled { return gjson.New(map[string]any{"enabled": false}), nil } maxAttempts := gconv.Int(policy["password_max_attempts"]) if maxAttempts == 0 { maxAttempts = 5 } if maxAttempts < 1 || maxAttempts > 20 { return nil, gerror.New("登录失败最大次数必须在1至20之间") } lockSeconds := gconv.Int(policy["lock_seconds"]) if lockSeconds == 0 { lockSeconds = 86400 } if lockSeconds < 60 || lockSeconds > 86400 { return nil, gerror.New("账号锁定时长必须在1至1440分钟之间") } windowSeconds := gconv.Int(policy["window_seconds"]) if windowSeconds == 0 { windowSeconds = 3600 } if windowSeconds < 60 || windowSeconds > 14400 { return nil, gerror.New("失败计数窗口必须在1至240分钟之间") } return gjson.New(map[string]any{"enabled": true, "password_max_attempts": maxAttempts, "lock_seconds": lockSeconds, "window_seconds": windowSeconds}), nil } type ImSecurityPolicyPublishInp struct { Id int64 `json:"id" v:"required|min:1#策略ID不能为空|策略ID不合法"` Reason string `json:"reason" v:"required#发布原因不能为空"` } func (in *ImSecurityPolicyPublishInp) Filter(ctx context.Context) error { in.Reason = strings.TrimSpace(in.Reason) if in.Id <= 0 { return gerror.New("策略ID不合法") } if in.Reason == "" { return gerror.New("发布原因不能为空") } if len([]rune(in.Reason)) > 500 { return gerror.New("发布原因不能超过500个字符") } return nil } type ImSecurityPolicyRestoreInp struct { TenantId int64 `json:"tenantId"` Reason string `json:"reason" v:"required#恢复原因不能为空"` } func (in *ImSecurityPolicyRestoreInp) Filter(ctx context.Context) error { in.Reason = strings.TrimSpace(in.Reason) if in.TenantId < 0 { return gerror.New("租户ID不合法") } if in.Reason == "" { return gerror.New("恢复原因不能为空") } if len([]rune(in.Reason)) > 500 { return gerror.New("恢复原因不能超过500个字符") } return nil } type ImLoginLockoutConfigModel struct { Enabled bool `json:"enabled"` PasswordMaxAttempts int `json:"passwordMaxAttempts"` LockMinutes int `json:"lockMinutes"` WindowMinutes int `json:"windowMinutes"` PolicyId int64 `json:"policyId"` PolicyVersion uint `json:"policyVersion"` } type ImLoginLockoutConfigGetInp struct { TenantId int64 `json:"tenantId"` } func (in *ImLoginLockoutConfigGetInp) Filter(ctx context.Context) error { if in.TenantId < 0 { return gerror.New("租户ID不合法") } return nil } type ImLoginLockoutConfigSaveInp struct { TenantId int64 `json:"tenantId"` Enabled bool `json:"enabled"` PasswordMaxAttempts int `json:"passwordMaxAttempts"` LockMinutes int `json:"lockMinutes"` WindowMinutes int `json:"windowMinutes"` } func (in *ImLoginLockoutConfigSaveInp) Filter(ctx context.Context) error { if in.TenantId < 0 { return gerror.New("租户ID不合法") } if !in.Enabled { return nil } if in.PasswordMaxAttempts < 1 || in.PasswordMaxAttempts > 20 { return gerror.New("登录失败最大次数必须在1至20之间") } if in.LockMinutes < 1 || in.LockMinutes > 1440 { return gerror.New("账号锁定时长必须在1至1440分钟之间") } if in.WindowMinutes < 1 || in.WindowMinutes > 240 { return gerror.New("失败计数窗口必须在1至240分钟之间") } return nil } type ImSecurityIPRuleListInp struct { form.PageReq TenantId int64 `json:"tenantId"` RuleType string `json:"ruleType"` IpCidr string `json:"ipCidr"` Status int `json:"status"` } func (in *ImSecurityIPRuleListInp) Filter(ctx context.Context) error { in.IpCidr = strings.TrimSpace(in.IpCidr) if in.RuleType != "" && in.RuleType != "whitelist" && in.RuleType != "blacklist" { return gerror.New("IP规则类型不合法") } return nil } type ImSecurityIPRuleModel struct { Id int64 `json:"id"` TenantId int64 `json:"tenantId"` RuleType string `json:"ruleType"` IpCidr string `json:"ipCidr"` Action string `json:"action"` SceneJson string `json:"sceneJson"` RiskLevel string `json:"riskLevel"` Status int `json:"status"` ExpiresAt *gtime.Time `json:"expiresAt"` Remark string `json:"remark"` CreatedAt *gtime.Time `json:"createdAt"` UpdatedAt *gtime.Time `json:"updatedAt"` } type ImSecurityIPRuleEditInp struct { Id int64 `json:"id"` TenantId int64 `json:"tenantId"` RuleType string `json:"ruleType"` IpCidr string `json:"ipCidr"` Action string `json:"action"` SceneJson *gjson.Json `json:"sceneJson"` RiskLevel string `json:"riskLevel"` Status int `json:"status"` ExpiresAt *gtime.Time `json:"expiresAt"` Remark string `json:"remark"` } func (in *ImSecurityIPRuleEditInp) Filter(ctx context.Context) error { in.IpCidr = strings.TrimSpace(in.IpCidr) if in.RuleType != "whitelist" && in.RuleType != "blacklist" { return gerror.New("IP规则类型不合法") } if in.IpCidr == "" { return gerror.New("IP或CIDR不能为空") } if !dict.HasOptionKey(consts.ImSecurityActionOptions, in.Action) { return gerror.New("规则动作不合法") } if !dict.HasOptionKey(consts.ImRiskLevelOptions, in.RiskLevel) { return gerror.New("风险等级不合法") } if in.Status == 0 { in.Status = consts.StatusEnabled } if in.Status != consts.StatusEnabled && in.Status != consts.StatusDisable { return gerror.New("状态不合法") } if in.SceneJson == nil || in.SceneJson.String() == "" || in.SceneJson.String() == "null" { return gerror.New("生效场景不能为空") } return nil } type ImSecurityIPRuleDeleteInp struct { Id int64 `json:"id" v:"required|min:1#IP规则ID不能为空|IP规则ID不合法"` } func (in *ImSecurityIPRuleDeleteInp) Filter(ctx context.Context) error { return nil }