- 新增 utils.InternalMsg:内部错误详情只进服务端日志,对外统一脱敏 消息,不再向客户端泄露 GORM/SQL 实现细节;九个 controller 的 err.Error() 出口全部切换 - 新增 .github/workflows/ci.yml:go build/vet/test(backend,go 1.25)
166 lines
5.5 KiB
Go
166 lines
5.5 KiB
Go
package product
|
|
|
|
import (
|
|
"backend/internal/dto"
|
|
planservice "backend/internal/service/plan"
|
|
productservice "backend/internal/service/product"
|
|
"backend/utils"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ProductController 产品控制器 // Logan.Li
|
|
type ProductController struct {
|
|
svc *productservice.ProductService
|
|
planSvc *planservice.PlanService
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewProductController 创建控制器实例 // Logan.Li
|
|
func NewProductController(db *gorm.DB) *ProductController {
|
|
return &ProductController{
|
|
svc: productservice.NewProductService(db),
|
|
planSvc: planservice.NewPlanService(db),
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// List 产品列表 // Logan.Li
|
|
func (ctrl *ProductController) List(c *gin.Context) {
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
|
keyword := c.Query("keyword")
|
|
|
|
products, total, err := ctrl.svc.List(page, pageSize, keyword)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{
|
|
"list": products, "total": total, "page": page, "page_size": pageSize,
|
|
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
|
}})
|
|
}
|
|
|
|
// GetAll 全部产品(下拉用) // Logan.Li
|
|
func (ctrl *ProductController) GetAll(c *gin.Context) {
|
|
products, err := ctrl.svc.GetAll()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "data": products})
|
|
}
|
|
|
|
// Get 产品详情 // Logan.Li
|
|
func (ctrl *ProductController) Get(c *gin.Context) {
|
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
product, err := ctrl.svc.GetByID(uint(id))
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 404, "message": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "data": product})
|
|
}
|
|
|
|
// GetByCode 按产品编码返回产品及上架计划(公开接口) // Logan.Li
|
|
func (ctrl *ProductController) GetByCode(c *gin.Context) {
|
|
code := c.Param("code")
|
|
product, err := ctrl.svc.GetByCode(code)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 404, "message": err.Error()})
|
|
return
|
|
}
|
|
plans, err := ctrl.planSvc.GetByProduct(product.ID)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "message": utils.InternalMsg(err)})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "data": dto.ProductWithPlansResponse{
|
|
Product: *product,
|
|
Plans: plans,
|
|
}})
|
|
}
|
|
|
|
// Create 创建产品 // Logan.Li
|
|
func (ctrl *ProductController) Create(c *gin.Context) {
|
|
var req dto.ProductCreate
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误: " + err.Error()})
|
|
return
|
|
}
|
|
product, err := ctrl.svc.Create(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "message": utils.InternalMsg(err)})
|
|
return
|
|
}
|
|
utils.LogAction(ctrl.db, c, "create", "product", product.ID, product.Name, "", "", "创建产品")
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "创建成功", "data": product})
|
|
}
|
|
|
|
// Update 更新产品 // Logan.Li
|
|
func (ctrl *ProductController) Update(c *gin.Context) {
|
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
var req dto.ProductUpdate
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"})
|
|
return
|
|
}
|
|
product, err := ctrl.svc.Update(uint(id), req)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "message": utils.InternalMsg(err)})
|
|
return
|
|
}
|
|
utils.LogAction(ctrl.db, c, "update", "product", product.ID, product.Name, "", "", "更新产品")
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "更新成功", "data": product})
|
|
}
|
|
|
|
// Delete 删除产品 // Logan.Li
|
|
func (ctrl *ProductController) Delete(c *gin.Context) {
|
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err := ctrl.svc.Delete(uint(id)); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "message": utils.InternalMsg(err)})
|
|
return
|
|
}
|
|
utils.LogAction(ctrl.db, c, "delete", "product", uint(id), "", "", "", "删除产品")
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "删除成功"})
|
|
}
|
|
|
|
// UpdateStatus 切换上下架 // Logan.Li
|
|
func (ctrl *ProductController) UpdateStatus(c *gin.Context) {
|
|
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
var req struct {
|
|
Status int `json:"status"`
|
|
}
|
|
c.ShouldBindJSON(&req)
|
|
if err := ctrl.svc.UpdateStatus(uint(id), req.Status); err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "message": utils.InternalMsg(err)})
|
|
return
|
|
}
|
|
utils.LogAction(ctrl.db, c, "update_status", "product", uint(id), "", "", "", fmt.Sprintf("切换状态为 %d", req.Status))
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "状态更新成功"})
|
|
}
|
|
|
|
// BatchDelete 批量删除产品(被 License 引用的自动跳过) // Logan.Li
|
|
func (ctrl *ProductController) BatchDelete(c *gin.Context) {
|
|
var req struct {
|
|
IDs []uint `json:"ids" binding:"required"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"})
|
|
return
|
|
}
|
|
result, err := ctrl.svc.BatchDelete(req.IDs)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, gin.H{"code": 1, "message": utils.InternalMsg(err)})
|
|
return
|
|
}
|
|
utils.LogAction(ctrl.db, c, "batch_delete", "product", 0, "", "", "",
|
|
fmt.Sprintf("批量删除产品 %d 条,跳过 %d 条", result.Deleted, result.Skipped))
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "批量删除完成", "data": result})
|
|
}
|