Files
opsxsql/backend/internal/handler/auth.go
T
logan ca308e277d feat: v0.3.1 — Go/Gin 后端重写 + gh-ost 集成
架构变更:
- Python/FastAPI → Go/Gin 重写(Python 后端保留在 backend-python/)
- GORM + SQLite(兼容现有数据库表结构)
- JWT 认证 + RBAC 权限中间件

Go 后端结构:
- cmd/server/main.go — 入口,端口 9000
- config/config.go — 环境变量配置
- internal/model/ — GORM 模型(30张表,完全兼容 SQLite)
- internal/handler/ — auth/instance/ticket/dashboard/ghost handler
- internal/middleware/ — JWT 认证 + CORS
- internal/ghost/ — gh-ost 集成模块(同步/异步执行,任务管理)
- router/router.go — API 路由注册(/api/v1/...)

gh-ost 集成:
- RunGhOst() 同步执行
- RunGhOstAsync() 异步执行 + 进度追踪
- TaskManager 管理多个并发任务
- API: POST /ghost/dry-run, /ghost/execute, GET /ghost/status/:taskId

核心 API 已对齐:
- POST /auth/login (JWT)
- GET /dashboard/stats, /dashboard/my-tickets
- GET /tickets/statistics, CRUD + approve/execute
- GET/POST/PUT/DELETE /instances
- GET /users, /audit-rules, /announcements, /settings

已验证:
- 登录 admin/admin123 ✅
- Dashboard 99工单/110查询/4实例 ✅
- 工单统计按角色过滤 ✅
2026-05-25 21:40:54 +08:00

152 lines
3.9 KiB
Go

package handler
import (
"net/http"
"time"
"sql-platform/internal/middleware"
"sql-platform/internal/model"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
// POST /api/v1/auth/login
func Login(c *gin.Context) {
var req model.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "username and password required"})
return
}
var user model.User
if err := model.DB.Where("username = ?", req.Username).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"detail": "Invalid username or password"})
return
}
if user.IsActive != nil && !*user.IsActive {
c.JSON(http.StatusForbidden, gin.H{"detail": "Account is disabled"})
return
}
if err := bcrypt.CompareHashAndPassword([]byte(user.HashedPassword), []byte(req.Password)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"detail": "Invalid username or password"})
return
}
token, err := middleware.GenerateToken(&user)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "Failed to generate token"})
return
}
now := time.Now()
user.LastLogin = &now
model.DB.Save(&user)
c.JSON(http.StatusOK, model.LoginResponse{
AccessToken: token,
TokenType: "bearer",
User: user,
})
}
// GET /api/v1/auth/me
func GetMe(c *gin.Context) {
userID, _, _ := middleware.GetCurrentUser(c)
var user model.User
if err := model.DB.First(&user, userID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "User not found"})
return
}
c.JSON(http.StatusOK, user)
}
// GET /api/v1/users
func ListUsers(c *gin.Context) {
var users []model.User
model.DB.Order("id").Find(&users)
c.JSON(http.StatusOK, users)
}
// POST /api/v1/users
func CreateUser(c *gin.Context) {
var input struct {
Username string `json:"username" binding:"required"`
DisplayName string `json:"display_name" binding:"required"`
Password string `json:"password" binding:"required"`
Email *string `json:"email"`
Phone *string `json:"phone"`
Role string `json:"role" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
hashed, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": "Failed to hash password"})
return
}
isActive := true
user := model.User{
Username: input.Username,
DisplayName: input.DisplayName,
HashedPassword: string(hashed),
Email: input.Email,
Phone: input.Phone,
Role: input.Role,
IsActive: &isActive,
}
if err := model.DB.Create(&user).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "Username already exists"})
return
}
c.JSON(http.StatusCreated, user)
}
// PUT /api/v1/users/:id
func UpdateUser(c *gin.Context) {
id := c.Param("id")
var user model.User
if err := model.DB.First(&user, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "User not found"})
return
}
var input map[string]interface{}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
if pw, ok := input["password"].(string); ok && pw != "" {
hashed, _ := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
input["hashed_password"] = string(hashed)
}
delete(input, "password")
if err := model.DB.Model(&user).Updates(input).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
return
}
model.DB.First(&user, id)
c.JSON(http.StatusOK, user)
}
// DELETE /api/v1/users/:id
func DeleteUser(c *gin.Context) {
id := c.Param("id")
if err := model.DB.Delete(&model.User{}, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "User deleted"})
}