232 lines
6.6 KiB
Go
232 lines
6.6 KiB
Go
package appuser
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"hyapp-admin-server/internal/appctx"
|
|
)
|
|
|
|
type AppLoginLog struct {
|
|
ID int64 `json:"id"`
|
|
RequestID string `json:"requestId"`
|
|
UserID string `json:"userId"`
|
|
DisplayUserID string `json:"displayUserId"`
|
|
Username string `json:"username"`
|
|
Avatar string `json:"avatar"`
|
|
Country string `json:"country"`
|
|
RegionID int64 `json:"regionId"`
|
|
RegionName string `json:"regionName"`
|
|
LoginIP string `json:"loginIp"`
|
|
IPCountryCode string `json:"ipCountryCode"`
|
|
Channel string `json:"channel"`
|
|
Platform string `json:"platform"`
|
|
Result string `json:"result"`
|
|
FailureCode string `json:"failureCode"`
|
|
Blocked bool `json:"blocked"`
|
|
BlockReason string `json:"blockReason"`
|
|
RiskHighlighted bool `json:"riskHighlighted"`
|
|
LoginType string `json:"loginType"`
|
|
Provider string `json:"provider"`
|
|
CreatedAtMs int64 `json:"createdAtMs"`
|
|
}
|
|
|
|
func (s *Service) ListLoginLogs(ctx context.Context, query loginLogQuery) ([]AppLoginLog, int64, error) {
|
|
if s.userDB == nil {
|
|
return nil, 0, fmt.Errorf("user mysql is not configured")
|
|
}
|
|
query = normalizeLoginLogQuery(query)
|
|
whereSQL, args := loginLogWhereSQL(ctx, query)
|
|
total, err := countRows(ctx, s.userDB, whereSQL, args...)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
rows, err := s.userDB.QueryContext(ctx, fmt.Sprintf(`
|
|
SELECT la.id,
|
|
la.request_id,
|
|
COALESCE(la.user_id, 0),
|
|
COALESCE(u.current_display_user_id, ''),
|
|
COALESCE(u.username, ''),
|
|
COALESCE(u.avatar, ''),
|
|
COALESCE(u.country, ''),
|
|
COALESCE(u.region_id, 0),
|
|
CASE
|
|
WHEN COALESCE(u.region_id, 0) = 0 THEN 'GLOBAL'
|
|
ELSE COALESCE((SELECT rg.name FROM regions rg WHERE rg.app_code = u.app_code AND rg.region_id = u.region_id LIMIT 1), '')
|
|
END,
|
|
COALESCE(la.client_ip, ''),
|
|
COALESCE(la.ip_country_code, ''),
|
|
COALESCE(NULLIF(la.channel, ''), CASE WHEN la.login_type = 'password' THEN 'account' ELSE COALESCE(la.provider, '') END),
|
|
COALESCE(la.platform, ''),
|
|
la.result,
|
|
COALESCE(la.failure_code, ''),
|
|
la.blocked,
|
|
la.block_reason,
|
|
la.login_type,
|
|
COALESCE(la.provider, ''),
|
|
la.created_at_ms
|
|
%s
|
|
ORDER BY la.created_at_ms DESC, la.id DESC
|
|
LIMIT ? OFFSET ?
|
|
`, whereSQL), append(args, query.PageSize, offset(query.Page, query.PageSize))...)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := make([]AppLoginLog, 0, query.PageSize)
|
|
for rows.Next() {
|
|
item, err := scanAppLoginLog(rows)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return items, total, nil
|
|
}
|
|
|
|
func loginLogWhereSQL(ctx context.Context, query loginLogQuery) (string, []any) {
|
|
whereSQL := `
|
|
FROM login_audit la
|
|
LEFT JOIN users u ON u.app_code = la.app_code AND u.user_id = la.user_id
|
|
WHERE la.app_code = ?`
|
|
args := []any{appctx.FromContext(ctx)}
|
|
if query.UserID > 0 {
|
|
whereSQL += " AND la.user_id = ?"
|
|
args = append(args, query.UserID)
|
|
}
|
|
if query.DisplayUserID != "" {
|
|
whereSQL += " AND u.current_display_user_id = ?"
|
|
args = append(args, query.DisplayUserID)
|
|
}
|
|
if query.IP != "" {
|
|
whereSQL += " AND la.client_ip = ?"
|
|
args = append(args, query.IP)
|
|
}
|
|
if query.IPCountryCode != "" {
|
|
whereSQL += " AND la.ip_country_code = ?"
|
|
args = append(args, query.IPCountryCode)
|
|
}
|
|
if query.Channel != "" {
|
|
whereSQL += " AND la.channel = ?"
|
|
args = append(args, query.Channel)
|
|
}
|
|
if query.Platform != "" {
|
|
whereSQL += " AND la.platform = ?"
|
|
args = append(args, query.Platform)
|
|
}
|
|
if query.RegionIDSet {
|
|
whereSQL += " AND COALESCE(u.region_id, 0) = ?"
|
|
args = append(args, query.RegionID)
|
|
}
|
|
switch query.Blocked {
|
|
case "blocked":
|
|
whereSQL += " AND (la.blocked = 1 OR la.failure_code = 'AUTH_LOGIN_BLOCKED')"
|
|
case "not_blocked":
|
|
whereSQL += " AND la.blocked = 0 AND COALESCE(la.failure_code, '') <> 'AUTH_LOGIN_BLOCKED'"
|
|
}
|
|
if query.Result != "" {
|
|
whereSQL += " AND la.result = ?"
|
|
args = append(args, query.Result)
|
|
}
|
|
if query.StartMs > 0 {
|
|
whereSQL += " AND la.created_at_ms >= ?"
|
|
args = append(args, query.StartMs)
|
|
}
|
|
if query.EndMs > 0 {
|
|
whereSQL += " AND la.created_at_ms <= ?"
|
|
args = append(args, query.EndMs)
|
|
}
|
|
if query.Keyword != "" {
|
|
like := "%" + query.Keyword + "%"
|
|
whereSQL += " AND (CAST(la.user_id AS CHAR) LIKE ? OR u.current_display_user_id LIKE ? OR u.username LIKE ? OR la.client_ip LIKE ?)"
|
|
args = append(args, like, like, like, like)
|
|
}
|
|
return whereSQL, args
|
|
}
|
|
|
|
func scanAppLoginLog(rows *sql.Rows) (AppLoginLog, error) {
|
|
var item AppLoginLog
|
|
var userID int64
|
|
if err := rows.Scan(
|
|
&item.ID,
|
|
&item.RequestID,
|
|
&userID,
|
|
&item.DisplayUserID,
|
|
&item.Username,
|
|
&item.Avatar,
|
|
&item.Country,
|
|
&item.RegionID,
|
|
&item.RegionName,
|
|
&item.LoginIP,
|
|
&item.IPCountryCode,
|
|
&item.Channel,
|
|
&item.Platform,
|
|
&item.Result,
|
|
&item.FailureCode,
|
|
&item.Blocked,
|
|
&item.BlockReason,
|
|
&item.LoginType,
|
|
&item.Provider,
|
|
&item.CreatedAtMs,
|
|
); err != nil {
|
|
return AppLoginLog{}, err
|
|
}
|
|
if userID > 0 {
|
|
item.UserID = strconv.FormatInt(userID, 10)
|
|
}
|
|
item.Result = normalizeLoginLogResult(item.Result, item.FailureCode, item.Blocked)
|
|
item.RiskHighlighted = item.Blocked || item.FailureCode == "AUTH_LOGIN_BLOCKED"
|
|
return item, nil
|
|
}
|
|
|
|
func normalizeLoginLogResult(result string, failureCode string, blocked bool) string {
|
|
result = strings.TrimSpace(result)
|
|
if result != "" {
|
|
return result
|
|
}
|
|
if blocked || failureCode == "AUTH_LOGIN_BLOCKED" {
|
|
return "blocked"
|
|
}
|
|
return "failed"
|
|
}
|
|
|
|
func normalizeLoginLogQuery(query loginLogQuery) loginLogQuery {
|
|
if query.Page < 1 {
|
|
query.Page = 1
|
|
}
|
|
if query.PageSize < 1 {
|
|
query.PageSize = 20
|
|
}
|
|
if query.PageSize > 100 {
|
|
query.PageSize = 100
|
|
}
|
|
query.Keyword = strings.TrimSpace(query.Keyword)
|
|
query.DisplayUserID = strings.TrimSpace(query.DisplayUserID)
|
|
query.IP = strings.TrimSpace(query.IP)
|
|
query.IPCountryCode = normalizeCountryCode(query.IPCountryCode)
|
|
query.Channel = strings.ToLower(strings.TrimSpace(query.Channel))
|
|
query.Platform = strings.ToLower(strings.TrimSpace(query.Platform))
|
|
query.Blocked = strings.ToLower(strings.TrimSpace(query.Blocked))
|
|
query.Result = strings.ToLower(strings.TrimSpace(query.Result))
|
|
if query.Result == "blocked" {
|
|
query.Blocked = "blocked"
|
|
query.Result = ""
|
|
}
|
|
if query.Blocked != "blocked" && query.Blocked != "not_blocked" {
|
|
query.Blocked = "all"
|
|
}
|
|
switch query.Result {
|
|
case "", "failed", "revoked", "success":
|
|
default:
|
|
query.Result = ""
|
|
}
|
|
return query
|
|
}
|