package sys import ( "context" "fmt" "hotgo/internal/consts" "hotgo/internal/library/contexts" "hotgo/internal/library/imadmin" "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/frame/g" "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) Unlock(ctx context.Context, in *sysin.ImLoginFailureUnlockInp) error { if err := in.Filter(ctx); err != nil { return err } tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return err } path := "/internal/admin-query/tenants/" + url.PathEscape(tenant.Code) + "/login-lockouts/release" var out struct { AccountHash string `json:"account_hash"` Status string `json:"status"` } if err = imadmin.DefaultClient(ctx).InternalPost(ctx, path, map[string]string{"account_hash": in.AccountHash}, &out); err != nil { return err } g.Log().Infof(ctx, "登录锁定已解锁 tenantId:%d accountHash:%s adminId:%d status:%s", tenant.Id, in.AccountHash, contexts.GetUserId(ctx), out.Status) return nil } 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 }