- 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
465 lines
14 KiB
Go
465 lines
14 KiB
Go
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": "配置已更新"})
|
||
}
|