Files
91-api-server/internal/handler/admin_handler.go
yuqianhe 3e27017e42 feat: add auth, db, gateway, email, ratelimit, geoblock modules and new handlers
- New CLI commands: key, user
- New internal modules: auth (middleware, password, user), db (db, logs, products), email, gateway (gateway, hooks, middlewares), handler (admin, auth, dev, log, product, settings), middleware/geoblock, ratelimit
- New demo frontend page
- Updated config, server, analytics, stats, and existing frontend pages
2026-06-26 21:32:51 +09:00

465 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"net/http"
"os"
"path/filepath"
"strconv"
"api-server/internal/auth"
"api-server/internal/db"
"github.com/gin-gonic/gin"
)
// AdminHandler 管理后台处理器
type AdminHandler struct {
db *db.DB
}
// NewAdminHandler 创建
func NewAdminHandler(database *db.DB) *AdminHandler {
return &AdminHandler{db: database}
}
// RegisterRoutes 注册路由
func (h *AdminHandler) RegisterRoutes(r *gin.RouterGroup) {
admin := r.Group("/admin")
admin.Use(auth.RequireAuth(h.db), auth.RequireAdmin())
{
admin.GET("/users", h.ListUsers)
admin.POST("/users", h.CreateUser)
admin.PUT("/users/:id", h.UpdateUser)
admin.DELETE("/users/:id", h.DeleteUser)
admin.PUT("/users/:id/role", h.UpdateUserRole)
admin.PUT("/users/:id/disable", h.ToggleUserDisable)
admin.PUT("/users/:id/password", h.ResetUserPassword)
admin.GET("/users/roles", h.UserRoles)
admin.POST("/users/:id/points", h.AdjustUserPoints)
admin.GET("/users/:id/points/transactions", h.UserPointTransactions)
admin.GET("/points/config", h.GetPointsConfig)
admin.PUT("/points/config", h.SetPointsConfig)
admin.GET("/keys", h.ListAllKeys)
admin.PATCH("/keys/:key", h.ToggleKey)
admin.DELETE("/keys/:key", h.RevokeKey)
admin.DELETE("/keys/:key/force", h.DeleteKey)
admin.PATCH("/keys/:key/quota", h.SetKeyQuota)
admin.GET("/config", h.GetHomeConfig)
admin.PUT("/config", h.SetHomeConfig)
}
}
// ListUsers 列出所有用户
func (h *AdminHandler) ListUsers(c *gin.Context) {
users, err := h.db.ListUsers()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": users})
}
// ── 积分管理 ──
// GetPointsConfig 获取积分定价配置
func (h *AdminHandler) GetPointsConfig(c *gin.Context) {
price, _ := h.db.GetSystemSetting("direct_call_price")
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{
"direct_call_price": price,
}})
}
// SetPointsConfig 设置积分定价配置
func (h *AdminHandler) SetPointsConfig(c *gin.Context) {
var req struct {
DirectCallPrice string `json:"direct_call_price"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if err := h.db.SetSystemSetting("direct_call_price", req.DirectCallPrice); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "已保存"})
}
// AdjustUserPoints 调整用户积分amount 为正数增加,负数扣除)
func (h *AdminHandler) AdjustUserPoints(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
var req struct {
Amount int64 `json:"amount"`
Reason string `json:"reason"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if req.Reason == "" {
req.Reason = "管理员调整"
}
if req.Amount >= 0 {
if err := h.db.AddPoints(id, req.Amount, req.Reason); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
} else {
if _, err := h.db.DeductPoints(id, -req.Amount, req.Reason); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": err.Error()})
return
}
}
user, _ := h.db.GetUserByID(id)
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{"points": user.Points}, "msg": "积分已更新"})
}
// UserPointTransactions 获取用户积分交易记录
func (h *AdminHandler) UserPointTransactions(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
txs, err := h.db.GetPointTransactionsByUser(id, 100, 0)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
if txs == nil {
txs = []db.PointTransaction{}
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": txs})
}
// CreateUser 管理员创建用户
func (h *AdminHandler) CreateUser(c *gin.Context) {
var req struct {
Email string `json:"email" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
Name string `json:"name"`
Role string `json:"role"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误: " + err.Error()})
return
}
if req.Role == "" {
req.Role = "user"
}
if req.Role != "user" && req.Role != "admin" {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "无效的角色,仅支持 user/admin"})
return
}
hash, err := auth.HashPassword(req.Password)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "密码加密失败"})
return
}
user, err := h.db.AdminCreateUser(req.Email, hash, req.Name, req.Role)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": user, "msg": "用户创建成功"})
}
// UpdateUser 更新用户信息(名称、邮箱)
func (h *AdminHandler) UpdateUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if req.Name != "" {
if err := h.db.UpdateUser(id, req.Name); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
}
if req.Email != "" {
if err := h.db.UpdateUserEmail(id, req.Email); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
}
user, err := h.db.GetUserByID(id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": user, "msg": "已更新"})
}
// DeleteUser 管理员删除用户
func (h *AdminHandler) DeleteUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
currentUser := auth.GetUser(c)
if currentUser != nil && currentUser.ID == id {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "不能删除自己"})
return
}
if err := h.db.DeleteUserByID(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "用户已删除"})
}
// ToggleUserDisable 禁用/启用用户
func (h *AdminHandler) ToggleUserDisable(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
currentUser := auth.GetUser(c)
if currentUser != nil && currentUser.ID == id {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "不能禁用自己"})
return
}
var req struct {
Disabled bool `json:"disabled"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if err := h.db.SetUserDisabled(id, req.Disabled); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
label := "已启用"
if req.Disabled {
label = "已禁用"
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": label})
}
// ResetUserPassword 管理员重置用户密码
func (h *AdminHandler) ResetUserPassword(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
var req struct {
Password string `json:"password" binding:"required,min=6"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "密码至少6位"})
return
}
user, err := h.db.GetUserByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"code": 1, "msg": "用户不存在"})
return
}
hash, err := auth.HashPassword(req.Password)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "密码加密失败"})
return
}
if err := h.db.UpdatePassword(user.Email, hash); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "密码已重置"})
}
// UserRoles 获取角色分布统计
func (h *AdminHandler) UserRoles(c *gin.Context) {
roleCounts, err := h.db.CountUsersByRole()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
// 构建角色详情列表
type roleInfo struct {
Role string `json:"role"`
UserCount int `json:"user_count"`
Permission string `json:"permission"`
}
roles := []roleInfo{
{Role: "admin", Permission: "全部权限,包括用户管理、系统设置、产品管理等"},
{Role: "user", Permission: "基本权限API 调用、工作台、个人设置"},
}
for i := range roles {
roles[i].UserCount = roleCounts[roles[i].Role]
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": roles})
}
// UpdateUserRole 更新用户角色
func (h *AdminHandler) UpdateUserRole(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
var req struct {
Role string `json:"role"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if req.Role != "user" && req.Role != "admin" {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "无效的角色,仅支持 user/admin"})
return
}
if err := h.db.UpdateUserRole(id, req.Role); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "角色已更新"})
}
// ListAllKeys 列出所有 API Key
func (h *AdminHandler) ListAllKeys(c *gin.Context) {
keys, err := h.db.ListAllAPIKeys()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
type keyInfo struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Key string `json:"key"`
Name string `json:"name"`
Quota int64 `json:"quota"`
Used int64 `json:"used"`
Active bool `json:"active"`
QuotaLocked bool `json:"quota_locked"`
}
list := make([]keyInfo, 0, len(keys))
for _, k := range keys {
list = append(list, keyInfo{
ID: k.ID,
UserID: k.UserID,
Key: k.Key,
Name: k.Name,
Quota: k.Quota,
Used: k.Used,
Active: k.Active,
QuotaLocked: k.QuotaLocked,
})
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": list})
}
// ToggleKey 启用/禁用 API Key
func (h *AdminHandler) ToggleKey(c *gin.Context) {
key := c.Param("key")
var req struct {
Active bool `json:"active"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if err := h.db.SetAPIKeyActive(key, req.Active); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "已更新"})
}
// RevokeKey 吊销 API Key
func (h *AdminHandler) RevokeKey(c *gin.Context) {
key := c.Param("key")
if err := h.db.RevokeAPIKey(key); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "已吊销"})
}
// DeleteKey 物理删除 API Key管理员
func (h *AdminHandler) DeleteKey(c *gin.Context) {
key := c.Param("key")
if err := h.db.DeleteAPIKey(key); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "已删除"})
}
// SetKeyQuota 修改配额
func (h *AdminHandler) SetKeyQuota(c *gin.Context) {
key := c.Param("key")
var req struct {
Quota int64 `json:"quota"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
if err := h.db.SetAPIKeyQuota(key, req.Quota); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "配额已更新"})
}
// GetHomeConfig 获取首页配置
func (h *AdminHandler) GetHomeConfig(c *gin.Context) {
cfg, err := h.db.GetHomeConfig()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
for _, fk := range []string{"content_file", "dashboard_content_file", "dashboard_quickstart_file"} {
if fpath, ok := cfg[fk]; ok && fpath != "" {
abs := fpath
if !filepath.IsAbs(fpath) {
abs = filepath.Join(".", fpath)
}
data, err := os.ReadFile(abs)
if err == nil {
cfg[fk+"_content"] = string(data)
}
}
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": cfg})
}
// SetHomeConfig 更新首页配置
func (h *AdminHandler) SetHomeConfig(c *gin.Context) {
var req map[string]string
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"})
return
}
for k, v := range req {
if err := h.db.SetHomeConfig(k, v); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()})
return
}
}
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "配置已更新"})
}