算了,后端我自己写吧

This commit is contained in:
2026-04-01 12:09:02 +08:00
parent 4138340f53
commit 1a0a01a56d
69 changed files with 1949 additions and 102 deletions
@@ -0,0 +1,345 @@
package handler
import (
"errors"
"strings"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"gorm.io/gorm"
"ops/internal/service"
"ops/pkg/response"
)
// AuthHandler 用户认证处理器
type AuthHandler struct {
authService *service.AuthService
validate *validator.Validate
}
// LoginRequest 登录请求结构
type LoginRequest struct {
Name string `json:"name" binding:"required,min=3,max=50"`
Password string `json:"password" binding:"required,min=6,max=50"`
DeviceID string `json:"deviceID"`
IP string `json:"ip"`
Remember string `json:"remember"`
}
// LoginResponse 登录响应结构
type LoginResponse struct {
UserID uint `json:"userID"`
Name string `json:"name"`
AvatarURL string `json:"avatarURL"`
CookieValue string `json:"cookieValue"`
CookieExpireDate string `json:"cookieExpireDate"`
}
// RegisterRequest 注册请求结构
type RegisterRequest struct {
Name string `json:"name" binding:"required,min=3,max=50"`
Password string `json:"password" binding:"required,min=6,max=50"`
Email string `json:"email" binding:"omitempty,email"`
Phone string `json:"phone" binding:"omitempty,len=11"`
}
// RegisterResponse 注册响应结构
type RegisterResponse struct {
UserID uint `json:"userID"`
Name string `json:"name"`
CookieValue string `json:"cookieValue"`
}
// ForgotPasswordRequest 忘记密码请求
type ForgotPasswordRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"omitempty,email"`
Phone string `json:"phone" binding:"omitempty,len=11"`
}
// ResetPasswordRequest 重置密码请求
type ResetPasswordRequest struct {
Token string `json:"token" binding:"required"`
NewPassword string `json:"newPassword" binding:"required,min=6,max=50"`
}
// LogoutRequest 退出登录请求
type LogoutRequest struct {
CookieValue string `json:"cookieValue" binding:"required"`
DeviceID string `json:"deviceID"`
}
// NewAuthHandler 创建认证处理器
func NewAuthHandler(db *gorm.DB) *AuthHandler {
return &AuthHandler{
authService: service.NewAuthService(db),
validate: validator.New(),
}
}
// UserLogin 用户登录
func (h *AuthHandler) UserLogin(c *gin.Context) {
var req LoginRequest
// 绑定和验证请求
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request format")
return
}
// 验证请求参数
if err := h.validate.Struct(req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 调用服务层
user, cookie, err := h.authService.Login(req.Name, req.Password, req.DeviceID, req.IP, req.Remember)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.Error(c, "-5", "User not found")
return
}
if strings.Contains(err.Error(), "password") {
response.Error(c, "-42", "Invalid password")
return
}
response.InternalError(c, err)
return
}
// 构建响应
resp := LoginResponse{
UserID: user.UserID,
Name: user.Name,
AvatarURL: user.AvatarURL,
CookieValue: cookie.Value,
CookieExpireDate: cookie.ExpireDate.Format("2006-01-02 15:04:05"),
}
response.Success(c, resp)
}
// UserRegister 用户注册
func (h *AuthHandler) UserRegister(c *gin.Context) {
var req RegisterRequest
// 绑定和验证请求
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request format")
return
}
// 验证请求参数
if err := h.validate.Struct(req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 调用服务层
user, cookie, err := h.authService.Register(req.Name, req.Password, req.Email, req.Phone)
if err != nil {
if strings.Contains(err.Error(), "duplicate") || strings.Contains(err.Error(), "unique") {
response.Error(c, "-4", "Username already exists")
return
}
response.InternalError(c, err)
return
}
// 构建响应
resp := RegisterResponse{
UserID: user.UserID,
Name: user.Name,
CookieValue: cookie.Value,
}
response.Success(c, resp)
}
// UserForgotPassword 忘记密码
func (h *AuthHandler) UserForgotPassword(c *gin.Context) {
var req ForgotPasswordRequest
// 绑定和验证请求
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request format")
return
}
// 至少需要邮箱或手机号之一
if req.Email == "" && req.Phone == "" {
response.BadRequest(c, "Email or phone number is required")
return
}
// 验证请求参数
if err := h.validate.Struct(req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 调用服务层
token, err := h.authService.ForgotPassword(req.Name, req.Email, req.Phone)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.Error(c, "-5", "User not found")
return
}
response.InternalError(c, err)
return
}
// 构建响应
response.Success(c, gin.H{
"resetToken": token,
"message": "Password reset instructions have been sent",
})
}
// UserResetPassword 重置密码
func (h *AuthHandler) UserResetPassword(c *gin.Context) {
var req ResetPasswordRequest
// 绑定和验证请求
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request format")
return
}
// 验证请求参数
if err := h.validate.Struct(req); err != nil {
response.BadRequest(c, err.Error())
return
}
// 调用服务层
err := h.authService.ResetPassword(req.Token, req.NewPassword)
if err != nil {
if strings.Contains(err.Error(), "invalid") || strings.Contains(err.Error(), "expired") {
response.Error(c, "-2", "Reset token is invalid or expired")
return
}
if errors.Is(err, gorm.ErrRecordNotFound) {
response.Error(c, "-5", "User not found")
return
}
response.InternalError(c, err)
return
}
response.Success(c, gin.H{
"message": "Password has been reset successfully",
})
}
// UserLogout 用户退出登录
func (h *AuthHandler) UserLogout(c *gin.Context) {
var req LogoutRequest
// 从认证中间件获取cookie值
cookieValue := getCookieFromContext(c)
if cookieValue == "" {
// 尝试从请求body获取
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request format")
return
}
cookieValue = req.CookieValue
}
if cookieValue == "" {
response.BadRequest(c, "Cookie value is required")
return
}
// 从请求中获取设备ID
deviceID := c.GetHeader("X-Device-ID")
if deviceID == "" && req.DeviceID != "" {
deviceID = req.DeviceID
}
// 调用服务层
err := h.authService.Logout(cookieValue, deviceID)
if err != nil {
response.InternalError(c, err)
return
}
response.Success(c, gin.H{
"message": "Logged out successfully",
})
}
// UserProfile 获取用户信息
func (h *AuthHandler) UserProfile(c *gin.Context) {
// 从认证中间件获取用户ID或名称
userID := getUserIDFromContext(c)
if userID == 0 {
response.Unauthorized(c)
return
}
// 调用服务层
user, err := h.authService.GetProfile(userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
response.Error(c, "-5", "User not found")
return
}
response.InternalError(c, err)
return
}
response.Success(c, user)
}
// UserUpdateProfile 更新用户信息
func (h *AuthHandler) UserUpdateProfile(c *gin.Context) {
// 从认证中间件获取用户ID
userID := getUserIDFromContext(c)
if userID == 0 {
response.Unauthorized(c)
return
}
// 解析更新请求
var updateData map[string]interface{}
if err := c.ShouldBindJSON(&updateData); err != nil {
response.BadRequest(c, "Invalid request format")
return
}
// 禁止更新某些字段
delete(updateData, "id")
delete(updateData, "name")
delete(updateData, "password")
delete(updateData, "createdAt")
// 调用服务层
user, err := h.authService.UpdateProfile(userID, updateData)
if err != nil {
response.InternalError(c, err)
return
}
response.Success(c, user)
}
// 辅助函数
func getCookieFromContext(c *gin.Context) string {
if cookie, exists := c.Get("userCookieValue"); exists && cookie != "" {
return cookie.(string)
}
return ""
}
func getUserIDFromContext(c *gin.Context) uint {
if userID, exists := c.Get("userID"); exists {
if id, ok := userID.(uint); ok {
return id
}
}
return 0
}
@@ -0,0 +1,241 @@
package handler
import (
"ops/internal/service"
"ops/pkg/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type FileHandler struct {
service service.FileService
}
func NewFileHandler(db *gorm.DB) *FileHandler {
return &FileHandler{
service: service.NewFileService(db),
}
}
// UploadFile 上传文件
// @Summary 上传文件
// @Description 上传文件到服务器,支持图片、文档等多种类型
// @Tags 文件管理
// @Accept multipart/form-data
// @Produce json
// @Param userID header string false "用户ID" default("")
// @Param file formData file true "文件内容"
// @Param type formData string false "文件类型" default(image)
// @Param description formData string false "文件描述"
// @Success 200 {object} response.StandardResponse "成功"
// @Failure 400 {object} response.StandardResponse "参数错误"
// @Failure 401 {object} response.StandardResponse "未授权"
// @Failure 413 {object} response.StandardResponse "文件过大"
// @Failure 415 {object} response.StandardResponse "文件类型不支持"
// @Failure 500 {object} response.StandardResponse "服务器错误"
// @Router /api/v1/files/upload [post]
func (h *FileHandler) UploadFile(c *gin.Context) {
// 从上下文中获取用户ID
userID, exists := c.Get("userID")
if !exists {
response.Unauthorized(c)
return
}
// 获取文件类型参数
fileType := c.PostForm("type")
if fileType == "" {
fileType = "image" // 默认类型为图片
}
// 获取文件描述
description := c.PostForm("description")
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
response.BadRequest(c, "请选择要上传的文件")
return
}
// 调用Service上传文件
uploadResponse, success := h.service.UploadFile(c, userID.(uint), file, fileType, description)
if !success {
response.BadRequest(c, "文件上传失败,请检查文件格式和大小")
return
}
response.Success(c, uploadResponse)
}
// GetFileList 获取文件列表
// @Summary 获取文件列表
// @Description 获取当前用户上传的文件列表
// @Tags 文件管理
// @Accept json
// @Produce json
// @Param userID header string false "用户ID" default("")
// @Param type query string false "文件类型过滤"
// @Param page query int false "页码" default(1)
// @Param entries query int false "每页数量" default(20)
// @Success 200 {object} response.StandardResponse "成功"
// @Failure 400 {object} response.StandardResponse "参数错误"
// @Failure 401 {object} response.StandardResponse "未授权"
// @Router /api/v1/files/list [get]
func (h *FileHandler) GetFileList(c *gin.Context) {
// 从上下文中获取用户ID
userID, exists := c.Get("userID")
if !exists {
response.Unauthorized(c)
return
}
// 获取查询参数
fileType := c.Query("type")
page := GetIntParam(c, "page", 1)
entries := GetIntParam(c, "entries", 20)
// 调用Service获取文件列表
fileListResponse, success := h.service.GetFileList(userID.(uint), fileType, page, entries)
if !success {
response.BadRequest(c, "参数错误")
return
}
response.Success(c, fileListResponse)
}
// GetFileByID 获取文件信息
// @Summary 获取文件信息
// @Description 根据文件ID获取文件详细信息
// @Tags 文件管理
// @Accept json
// @Produce json
// @Param userID header string false "用户ID" default("")
// @Param id path int true "文件ID"
// @Success 200 {object} response.StandardResponse "成功"
// @Failure 401 {object} response.StandardResponse "未授权"
// @Failure 404 {object} response.StandardResponse "文件不存在"
// @Router /api/v1/files/{id} [get]
func (h *FileHandler) GetFileByID(c *gin.Context) {
// 从上下文中获取用户ID
userID, exists := c.Get("userID")
if !exists {
response.Unauthorized(c)
return
}
// 获取文件ID
fileID := GetUintParam(c, "id")
if fileID == 0 {
response.BadRequest(c, "文件ID无效")
return
}
// 调用Service获取文件信息
file, success := h.service.GetFileByID(fileID, userID.(uint))
if !success {
response.Error(c, "-100", "文件不存在或无权限访问")
return
}
response.Success(c, gin.H{
"file_id": file.ID,
"name": file.Name,
"sha256": file.Sha256,
"mime": file.Mime,
"type": file.Type,
"size": file.Const, // 注意:这里const字段实际上存储的是使用次数,需要确认实际字段
"created_at": file.Date.Format("2006-01-02T15:04:05Z"),
"path": file.Path,
})
}
// DeleteFile 删除文件
// @Summary 删除文件
// @Description 删除用户上传的文件
// @Tags 文件管理
// @Accept json
// @Produce json
// @Param userID header string false "用户ID" default("")
// @Param id path int true "文件ID"
// @Success 200 {object} response.StandardResponse "成功"
// @Failure 401 {object} response.StandardResponse "未授权"
// @Failure 404 {object} response.StandardResponse "文件不存在"
// @Router /api/v1/files/{id} [delete]
func (h *FileHandler) DeleteFile(c *gin.Context) {
// 从上下文中获取用户ID
userID, exists := c.Get("userID")
if !exists {
response.Unauthorized(c)
return
}
// 获取文件ID
fileID := GetUintParam(c, "id")
if fileID == 0 {
response.BadRequest(c, "文件ID无效")
return
}
// 调用Service删除文件
success := h.service.DeleteFile(fileID, userID.(uint))
if !success {
response.Error(c, "-100", "文件删除失败,文件不存在或无权限")
return
}
response.Success(c, gin.H{"message": "文件删除成功"})
}
// DownloadFile 下载文件
// @Summary 下载文件
// @Description 下载文件内容(直接下载)
// @Tags 文件管理
// @Accept json
// @Produce application/octet-stream
// @Param hash path string true "文件SHA256哈希值"
// @Success 200 {file} binary "文件内容"
// @Failure 404 {object} response.StandardResponse "文件不存在"
// @Router /api/v1/files/download/{hash} [get]
func (h *FileHandler) DownloadFile(c *gin.Context) {
hash := c.Param("hash")
if hash == "" {
response.BadRequest(c, "文件哈希值无效")
return
}
// 调用Service下载文件
success := h.service.DownloadFile(c, hash, true)
if !success {
response.Error(c, "-100", "文件不存在")
return
}
}
// GetFile 获取文件(预览)
// @Summary 获取文件(预览)
// @Description 获取文件内容(浏览器预览)
// @Tags 文件管理
// @Accept json
// @Produce *
// @Param hash path string true "文件SHA256哈希值"
// @Success 200 {file} binary "文件内容"
// @Failure 404 {object} response.StandardResponse "文件不存在"
// @Router /api/v1/files/get/{hash} [get]
func (h *FileHandler) GetFile(c *gin.Context) {
hash := c.Param("hash")
if hash == "" {
response.BadRequest(c, "文件哈希值无效")
return
}
// 调用Service获取文件(预览模式)
success := h.service.DownloadFile(c, hash, false)
if !success {
response.Error(c, "-100", "文件不存在")
return
}
}
@@ -0,0 +1,155 @@
package handler
import (
"ops/internal/service"
"ops/pkg/response"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"gorm.io/gorm"
)
type PurchaseHandler struct {
service service.PurchaseService
}
func NewPurchaseHandler(db *gorm.DB) *PurchaseHandler {
return &PurchaseHandler{
service: service.NewPurchaseService(db),
}
}
// GetOrders 获取采购订单列表
// @Summary 获取采购订单列表
// @Description 获取用户采购订单列表,支持搜索和分页
// @Tags 采购管理
// @Accept json
// @Produce json
// @Param userID header string false "用户ID" default("")
// @Param search query string false "搜索关键词"
// @Param page query int true "页码" default(1)
// @Param entries query int true "每页数量" default(20)
// @Success 200 {object} response.StandardResponse "成功"
// @Failure 400 {object} response.StandardResponse "参数错误"
// @Failure 401 {object} response.StandardResponse "未授权"
// @Failure 500 {object} response.StandardResponse "服务器错误"
// @Router /api/v1/purchase/orders [get]
func (h *PurchaseHandler) GetOrders(c *gin.Context) {
// 从上下文中获取用户ID
userID, exists := c.Get("userID")
if !exists {
response.Unauthorized(c)
return
}
// 获取查询参数
search := c.Query("search")
page := GetIntParam(c, "page", 1)
entries := GetIntParam(c, "entries", 20)
// 调用Service
result, success := h.service.GetOrders(c, userID.(uint), search, page, entries)
if !success {
response.BadRequest(c, "参数错误")
return
}
response.Success(c, result)
}
// CreateOrder 创建采购订单
// @Summary 创建采购订单
// @Description 创建新的采购订单
// @Tags 采购管理
// @Accept json
// @Produce json
// @Param userID header string false "用户ID" default("")
// @Param request body service.CreateOrderRequest true "订单信息"
// @Success 200 {object} response.StandardResponse "成功"
// @Failure 400 {object} response.StandardResponse "参数错误"
// @Failure 401 {object} response.StandardResponse "未授权"
// @Failure 500 {object} response.StandardResponse "服务器错误"
// @Router /api/v1/purchase/orders [post]
func (h *PurchaseHandler) CreateOrder(c *gin.Context) {
// 从上下文中获取用户ID
userID, exists := c.Get("userID")
if !exists {
response.Unauthorized(c)
return
}
// 解析请求体
var request service.CreateOrderRequest
if err := c.ShouldBindJSON(&request); err != nil {
var validationErrors []string
for _, err := range err.(validator.ValidationErrors) {
validationErrors = append(validationErrors, err.Field()+" "+err.Tag())
}
if len(validationErrors) > 0 {
response.BadRequest(c, "参数错误: "+validationErrors[0])
} else {
response.BadRequest(c, "请求格式错误")
}
return
}
// 调用Service
success := h.service.CreateOrder(c, userID.(uint), request)
if !success {
response.BadRequest(c, "创建订单失败,请检查数据")
return
}
response.Success(c, gin.H{"message": "订单创建成功"})
}
// GetOrderDetails 获取订单详情
// @Summary 获取订单详情
// @Description 获取采购订单的详细信息
// @Tags 采购管理
// @Accept json
// @Produce json
// @Param userID header string false "用户ID" default("")
// @Param id path int true "订单ID"
// @Success 200 {object} response.StandardResponse "成功"
// @Failure 401 {object} response.StandardResponse "未授权"
// @Failure 404 {object} response.StandardResponse "订单不存在"
// @Failure 500 {object} response.StandardResponse "服务器错误"
// @Router /api/v1/purchase/orders/{id} [get]
func (h *PurchaseHandler) GetOrderDetails(c *gin.Context) {
// 从上下文中获取用户ID
userID, exists := c.Get("userID")
if !exists {
response.Unauthorized(c)
return
}
// 获取订单ID
orderID := GetUintParam(c, "id")
if orderID == 0 {
response.BadRequest(c, "订单ID无效")
return
}
// 调用Service
order, costs, err := h.service.GetOrderDetails(orderID)
if err != nil {
if err == gorm.ErrRecordNotFound {
response.Error(c, "-5", "订单不存在")
} else {
response.InternalError(c, err)
}
return
}
// 检查订单所属用户
if order.UserID != userID.(uint) {
response.Unauthorized(c)
return
}
response.Success(c, gin.H{
"order": order,
"costs": costs,
})
}
+35
View File
@@ -0,0 +1,35 @@
package handler
import (
"strconv"
"github.com/gin-gonic/gin"
)
// GetIntParam 获取整数参数
func GetIntParam(c *gin.Context, key string, defaultValue int) int {
value := c.Query(key)
if value == "" {
return defaultValue
}
intValue, err := strconv.Atoi(value)
if err != nil {
return defaultValue
}
return intValue
}
// GetUintParam 获取uint参数
func GetUintParam(c *gin.Context, key string) uint {
value := c.Param(key)
if value == "" {
return 0
}
intValue, err := strconv.Atoi(value)
if err != nil {
return 0
}
return uint(intValue)
}