server: Go 服务器(REST+WSS 网关+JWT+Argon2+配对+素材直链+任务状态机);修复并发下线 send-on-closed-channel、下发查询 SQL 优先级、配对码原子占用、上传体积上限、JWT 默认密钥告警
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"everypublish/server/internal/api/response"
|
||||
"everypublish/server/internal/models"
|
||||
"everypublish/server/internal/ws"
|
||||
)
|
||||
|
||||
// AdminHandler 平台管理处理器(AdminRequired 守卫)
|
||||
type AdminHandler struct {
|
||||
DB *gorm.DB
|
||||
Hub *ws.Hub
|
||||
}
|
||||
|
||||
type tenantRow struct {
|
||||
models.Workspace
|
||||
OwnerEmail string `json:"ownerEmail"`
|
||||
MemberCount int64 `json:"memberCount"`
|
||||
DeviceCount int64 `json:"deviceCount"`
|
||||
}
|
||||
|
||||
// Tenants 租户列表(含 owner 邮箱与成员/设备数)
|
||||
func (h *AdminHandler) Tenants(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 || size > 100 {
|
||||
size = 20
|
||||
}
|
||||
q := h.DB.Model(&models.Workspace{})
|
||||
if s := c.Query("status"); s != "" {
|
||||
q = q.Where("status = ?", s)
|
||||
}
|
||||
var total int64
|
||||
q.Count(&total)
|
||||
var wsList []models.Workspace
|
||||
q.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&wsList)
|
||||
rows := make([]tenantRow, 0, len(wsList))
|
||||
for _, ws := range wsList {
|
||||
row := tenantRow{Workspace: ws}
|
||||
var owner models.User
|
||||
if err := h.DB.First(&owner, ws.OwnerID).Error; err == nil {
|
||||
row.OwnerEmail = owner.Email
|
||||
}
|
||||
h.DB.Model(&models.Member{}).Where("workspace_id = ?", ws.ID).Count(&row.MemberCount)
|
||||
h.DB.Model(&models.AgentDevice{}).Where("workspace_id = ?", ws.ID).Count(&row.DeviceCount)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
response.OK(c, gin.H{"list": rows, "total": total, "page": page, "size": size})
|
||||
}
|
||||
|
||||
// TenantStatus 启用/停用租户
|
||||
func (h *AdminHandler) TenantStatus(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Status string `json:"status" binding:"required"`
|
||||
}
|
||||
if err = c.ShouldBindJSON(&req); err != nil {
|
||||
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
|
||||
return
|
||||
}
|
||||
if req.Status != "active" && req.Status != "suspended" {
|
||||
response.Fail(c, http.StatusBadRequest, 1001, "状态不合法")
|
||||
return
|
||||
}
|
||||
var ws models.Workspace
|
||||
if err = h.DB.First(&ws, id).Error; err != nil {
|
||||
response.Fail(c, http.StatusNotFound, 1004, "租户不存在")
|
||||
return
|
||||
}
|
||||
if err = h.DB.Model(&ws).Update("status", req.Status).Error; err != nil {
|
||||
response.Fail(c, http.StatusInternalServerError, 5000, "更新失败")
|
||||
return
|
||||
}
|
||||
response.Audit(c, "admin.tenant.status", "workspace:"+itoa(id), gin.H{"status": req.Status})
|
||||
response.OK(c, nil)
|
||||
}
|
||||
|
||||
type agentRow struct {
|
||||
models.AgentDevice
|
||||
WorkspaceName string `json:"workspaceName"`
|
||||
}
|
||||
|
||||
// Agents 全局 Agent 总览
|
||||
func (h *AdminHandler) Agents(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 || size > 100 {
|
||||
size = 20
|
||||
}
|
||||
var total int64
|
||||
h.DB.Model(&models.AgentDevice{}).Count(&total)
|
||||
var devices []models.AgentDevice
|
||||
h.DB.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&devices)
|
||||
rows := make([]agentRow, 0, len(devices))
|
||||
for _, d := range devices {
|
||||
row := agentRow{AgentDevice: d}
|
||||
var ws models.Workspace
|
||||
if err := h.DB.First(&ws, d.WorkspaceID).Error; err == nil {
|
||||
row.WorkspaceName = ws.Name
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
response.OK(c, gin.H{"list": rows, "total": total, "page": page, "size": size})
|
||||
}
|
||||
|
||||
// AgentRevoke 全局强制吊销
|
||||
func (h *AdminHandler) AgentRevoke(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.Fail(c, http.StatusBadRequest, 1001, "参数不合法")
|
||||
return
|
||||
}
|
||||
var device models.AgentDevice
|
||||
if err = h.DB.First(&device, id).Error; err != nil {
|
||||
response.Fail(c, http.StatusNotFound, 1004, "设备不存在")
|
||||
return
|
||||
}
|
||||
if err = h.DB.Model(&device).Updates(map[string]interface{}{"revoked": true, "status": "offline"}).Error; err != nil {
|
||||
response.Fail(c, http.StatusInternalServerError, 5000, "吊销失败")
|
||||
return
|
||||
}
|
||||
if h.Hub != nil {
|
||||
h.Hub.KickAgent(device.ID)
|
||||
}
|
||||
response.Audit(c, "admin.agent.revoke", "device:"+itoa(id), nil)
|
||||
response.OK(c, nil)
|
||||
}
|
||||
Reference in New Issue
Block a user