- DB Migration 081: 新增 account_behavior_hourly / account_risk_scores 表 - 行为采集:Gateway/OpenAI Gateway RecordUsage 注入 fire-and-forget CollectBehaviorAsync - SQL 打分引擎:CTE 加权特征向量 → risk_score [0-1],UPSERT 保留 idle_override - RiskSettings:Redis 缓存 → DB fallback → 默认值(observe 模式) - REST API:/admin/risk/summary|accounts|accounts/:id|settings - 前端:Pinia store + RiskControlView + 6 子组件(donut/radar/line 纯 SVG 图表) - 侧边栏新增 Risk Control 入口(ShieldExclamationIcon) - 反风控优化:移除 Antigravity 后台定时刷新,改为按需刷新避免 idle 封号
115 lines
2.6 KiB
Go
115 lines
2.6 KiB
Go
package admin
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type RiskHandler struct {
|
|
service *service.RiskService
|
|
}
|
|
|
|
func NewRiskHandler(svc *service.RiskService) *RiskHandler {
|
|
return &RiskHandler{service: svc}
|
|
}
|
|
|
|
func (h *RiskHandler) GetSummary(c *gin.Context) {
|
|
summary, err := h.service.GetSummary(c.Request.Context())
|
|
if err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
response.Success(c, summary)
|
|
}
|
|
|
|
func (h *RiskHandler) ListAccounts(c *gin.Context) {
|
|
filter := service.RiskAccountFilter{
|
|
Level: c.Query("risk_level"),
|
|
Platform: c.Query("platform"),
|
|
}
|
|
if p := c.Query("page"); p != "" {
|
|
if v, err := strconv.Atoi(p); err == nil {
|
|
filter.Page = v
|
|
}
|
|
}
|
|
if l := c.Query("limit"); l != "" {
|
|
if v, err := strconv.Atoi(l); err == nil {
|
|
filter.PageSize = v
|
|
}
|
|
}
|
|
|
|
list, err := h.service.ListAccounts(c.Request.Context(), filter)
|
|
if err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
response.Success(c, list)
|
|
}
|
|
|
|
func (h *RiskHandler) GetAccountDetail(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
response.ErrorFrom(c, service.ErrRiskAccountNotFound)
|
|
return
|
|
}
|
|
detail, err := h.service.GetAccountDetail(c.Request.Context(), id)
|
|
if err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
response.Success(c, detail)
|
|
}
|
|
|
|
type overrideRiskLevelRequest struct {
|
|
Level string `json:"level" binding:"required"`
|
|
Reason string `json:"reason" binding:"required"`
|
|
}
|
|
|
|
func (h *RiskHandler) OverrideRiskLevel(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
response.ErrorFrom(c, service.ErrRiskAccountNotFound)
|
|
return
|
|
}
|
|
|
|
var req overrideRiskLevelRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
|
|
if err := h.service.OverrideRiskLevel(c.Request.Context(), id, req.Level, req.Reason); err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
response.Success(c, nil)
|
|
}
|
|
|
|
func (h *RiskHandler) GetSettings(c *gin.Context) {
|
|
settings, err := h.service.GetSettings(c.Request.Context())
|
|
if err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
response.Success(c, settings)
|
|
}
|
|
|
|
func (h *RiskHandler) UpdateSettings(c *gin.Context) {
|
|
var req service.RiskSettings
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
updated, err := h.service.UpdateSettings(c.Request.Context(), &req)
|
|
if err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
response.Success(c, updated)
|
|
}
|