daShangDao_psiServer/controllers/product.go
Administrator a2ea0c3a40 1.在这个接口里 /api/product/updateNameAndImages,添加多张图片时,并未覆盖原来的多张图片。(Y)
2.从系统导出的excel数据,在外部对excel某一列进行更改时,新增的要回传到原来的地方;并对改动的地方进行覆盖。
3.销售单管理、出库管理、发货单三个接口里面展示第三方订单编号和快递单号
4.选择多个仓库时,只要选择发货单子就会报错
5.在这个/api/split-account-deduction-log/create接口里,当传参时,如果参数 total_amount 是0,则会报错 {"code":204,"data":{},"msg":"TotalAmount不能为空"} 0是金额数字,不能当空值进行判断(T)
传递参数created_by,没有往数据表里写入
6.商品销毁的同时写入日志,也能通过读取这个日志,还原销毁的商品。传出这个新增的接口
7.新增一个不需要签名认证的分帐扣钱日志列表接口,新增一个返回字段buniness_no,并对这个字段进行模糊查询。
测试接口:/open/split-account-deduction-log/list
8.增加个新接口:首先 调用 /api/sales-order/create 创建销售订单的时候会锁定库存,
现在我需要一个解锁库存的接口,传递参数是订单编号
POST /api/sales-order/unlock-inventory // 解锁销售订单库存
/api/split-account-deduction-log/update /api/sales-order/unlock-inventory 在这两个接口里不需要签名认证
/api/sales-order/unlock-inventory 在这个接口里面返回解锁的所有商品信息
/api/split-account-deduction-log/update  在这个接口里面的status也需要更改,status没有变化
2026-06-24 09:41:12 +08:00

726 lines
20 KiB
Go
Raw 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 controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"io"
"net/http"
"net/url"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
"strconv"
"strings"
)
type ProductApi struct{}
var productService = service.ProductService{}
// GetProductList 获取商品列表
func (r *ProductApi) GetProductList(c *gin.Context) {
var req systemReq.GetProductListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "商品列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.IDs) == 0 {
ids, err := parseIds(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.IDs = ids
}
result, err := productService.GetProductList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetDistributionProductList 获取分销商品列表
func (r *ProductApi) GetDistributionProductList(c *gin.Context) {
var req systemReq.GetDistributionProductListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "分销商品列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
// Name支持双重编码如果绑定后仍含%编码字符,再解码一次
if req.Name != "" && strings.Contains(req.Name, "%") {
if decoded, err := url.QueryUnescape(req.Name); err == nil {
req.Name = decoded
}
}
// StockOperator支持双重编码
if req.StockOperator != "" && strings.Contains(req.StockOperator, "%") {
if decoded, err := url.QueryUnescape(req.StockOperator); err == nil {
req.StockOperator = decoded
}
}
// SalePriceOperator支持双重编码
if req.SalePriceOperator != "" && strings.Contains(req.SalePriceOperator, "%") {
if decoded, err := url.QueryUnescape(req.SalePriceOperator); err == nil {
req.SalePriceOperator = decoded
}
}
result, err := productService.GetDistributionProductList(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "分销商品列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetProductDetail 获取商品详情
func (r *ProductApi) GetProductDetail(c *gin.Context) {
var req systemReq.GetProductDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "商品详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetProductDetail(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品详情异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetProductFullInfo 获取商品完整信息(无需签名认证) - 从租户分库查询
func (r *ProductApi) GetProductFullInfo(c *gin.Context) {
var req systemReq.GetProductFullInfoRequest
fmt.Printf("========== 【接口入口】获取商品完整信息 ==========\n")
if err := c.ShouldBindQuery(&req); err != nil {
fmt.Printf("【参数验证失败】%v\n", err)
utils.InfoLog(constant.LoggerChannelRequest, map[string]interface{}{
"action": "获取商品完整信息参数验证失败",
"error": err.Error(),
})
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
fmt.Printf("【参数验证通过】UserID(租户): %d, ProductID: %d\n", req.UserID, req.ProductID)
result, err := productService.GetProductFullInfo(req, database.GetDB(c))
if err != nil {
fmt.Printf("【查询失败】%v\n", err)
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取商品完整信息异常", err, c, req)
return
}
fmt.Printf("【查询成功】返回商品完整信息\n")
fmt.Printf("========== 【接口完成】 ==========\n")
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// SaveProduct 保存商品(添加或修改)
func (r *ProductApi) SaveProduct(c *gin.Context) {
var req systemReq.ProductRequest
fmt.Printf("【断点1】接收到保存商品请求\n")
if err := c.ShouldBind(&req); err != nil {
fmt.Printf("【断点2】参数绑定失败: %v\n", err)
ValidAndFail(constant.LoggerChannelRequest, "保存商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
fmt.Printf("【断点3】参数绑定成功, 商品信息: %+v\n", req)
if len(req.LiveImage) == 0 {
image, err := parseImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.LiveImage = image
}
id, err := productService.SaveProduct(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "保存商品异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "操作成功", c)
}
// UpdatePrice 修改商品售价
func (r *ProductApi) UpdatePrice(c *gin.Context) {
var req systemReq.UpdatePriceRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "修改售价请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.UpdatePrice(req); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "修改售价异常", err, c, req)
return
}
systemRes.OkWithMessage("售价修改成功", c)
}
// DeleteProduct 删除商品
func (r *ProductApi) DeleteProduct(c *gin.Context) {
var req systemReq.DeleteProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := productService.DeleteProduct(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除商品异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}
// GetProductInventory 获取商品库存数量
func (r *ProductApi) GetProductInventory(c *gin.Context) {
var req systemReq.GetProductInventoryRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取商品库存请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetProductInventory(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取商品库存异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// RetryOutTask 重试失败的外部任务
func (r *ProductApi) RetryOutTask(c *gin.Context) {
var req systemReq.RetryOutTaskRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "重试任务请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.RetryOutTask(req, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "重试任务异常", err, c, req)
return
}
systemRes.OkWithMessage("重试任务已提交", c)
}
// ExportProducts 导出商品到Excel
func (r *ProductApi) ExportProducts(c *gin.Context) {
var req systemReq.ExportProductRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "导出商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.ExportProducts(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "导出商品异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// PushProductToShop 上架到商铺:创建商品、库存记录,并推送到商铺
func (r *ProductApi) PushProductToShop(c *gin.Context) {
var req systemReq.PushProductToShopRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "上架到商铺请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
// 处理photos参数: 如果表单未绑定,尝试从原始表单解析
if len(req.Photos) == 0 {
var photos []string
for i := 0; i < 20; i++ {
photoStr := c.PostForm(fmt.Sprintf("photos[%d]", i))
if photoStr == "" {
break
}
photos = append(photos, photoStr)
}
req.Photos = photos
}
result, err := productService.PushProductToShop(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "上架到商铺异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// BatchPushProducts 批量推送商品到多个店铺
func (r *ProductApi) BatchPushProducts(c *gin.Context) {
var req systemReq.BatchPushProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "批量推送请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.BatchPushProducts(req); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "批量推送异常", err, c, req)
return
}
systemRes.OkWithMessage("批量推送完成", c)
}
/*func (r *ProductApi) BatchPushProducts(c *gin.Context) {
var req systemReq.BatchPushProductRequest
if err := c.ShouldBind(&req); err != nil {
utils.InfoLog(constant.LoggerChannelRequest, map[string]interface{}{
"action": "批量推送参数验证失败",
"error": err.Error(),
})
ValidAndFail(constant.LoggerChannelRequest, "批量推送请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
utils.InfoLog(constant.LoggerChannelWork, map[string]interface{}{
"action": "开始批量推送商品",
"shop_ids": req.ShopIDs,
"product_id": req.ProductID,
})
if err := productService.BatchPushProducts(req); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "批量推送异常", err, c, req)
return
}
utils.InfoLog(constant.LoggerChannelWork, map[string]interface{}{
"action": "批量推送完成",
"product_id": req.ProductID,
"success": true,
})
systemRes.OkWithMessage("批量推送完成", c)*/
// GetProductLogList 商品日志列表
func (r *ProductApi) GetProductLogList(c *gin.Context) {
var req systemReq.GetProductLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "商品日志列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := productService.GetProductLogList(req, userInfo.AboutID)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品日志列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// SaveProductLog 保存商品日志(添加或修改)
func (r *ProductApi) SaveProductLog(c *gin.Context) {
var req systemReq.ProductLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "保存商品日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.OldLiveImage) == 0 {
image, err := parseOldImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.OldLiveImage = image
}
if len(req.NewLiveImage) == 0 {
image, err := parseNewImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.NewLiveImage = image
}
userInfo := utils.GetUserInfo(c)
id, err := productService.SaveProductLog(req, userInfo.ID)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "保存商品日志异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "操作成功", c)
}
// AuditProductLog 审核商品日志
func (r *ProductApi) AuditProductLog(c *gin.Context) {
var req systemReq.AuditProductLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "审核商品日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.NewLiveImage) == 0 {
image, err := parseNewImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.NewLiveImage = image
}
userInfo := utils.GetUserInfo(c)
if err := productService.AuditProductLog(req, userInfo.ID); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "审核商品日志异常", err, c, req)
return
}
systemRes.OkWithMessage("审核成功", c)
}
// DeleteProductLog 删除商品日志
func (r *ProductApi) DeleteProductLog(c *gin.Context) {
var req systemReq.DeleteProductLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除商品日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.DeleteProductLog(req); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除商品日志异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}
// GetShopProductDetail 获取店铺商品详情(包含商品数据和发送记录)
func (r *ProductApi) GetShopProductDetail(c *gin.Context) {
var req systemReq.GetShopProductDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取店铺商品详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetShopProductDetail(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取店铺商品详情异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// parseIds 解析ID列表
func parseIds(c *gin.Context) ([]int64, error) {
var ids []int64
for i := 0; i < 100; i++ {
idStr := c.Query(fmt.Sprintf("ids[%d]", i))
if idStr == "" {
break
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
return nil, fmt.Errorf("第%d个ID格式错误", i)
}
if id <= 0 {
return nil, fmt.Errorf("第%d个ID必须大于0", i)
}
ids = append(ids, id)
}
return ids, nil
}
// parseImageFromForm 解析图片列表
func parseImageFromForm(c *gin.Context) ([]string, error) {
var images []string
for i := 0; i < 10; i++ {
imageStr := c.PostForm(fmt.Sprintf("live_image[%d]", i))
if imageStr == "" {
break
}
images = append(images, imageStr)
}
// 如果解析到了数组格式,直接返回
if len(images) > 0 {
return images, nil
}
// 方式2: 尝试解析 liveimage 逗号分隔格式
liveimageStr := c.PostForm("liveimage")
if liveimageStr != "" {
// 按逗号分割
parts := strings.Split(liveimageStr, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
images = append(images, part)
}
}
return images, nil
}
// 方式3: 尝试解析 live_image 逗号分隔格式
liveImageStr := c.PostForm("live_image")
if liveImageStr != "" {
// 按逗号分割
parts := strings.Split(liveImageStr, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
images = append(images, part)
}
}
return images, nil
}
return images, nil
}
// parseOldImageFromForm 解析图片列表
func parseOldImageFromForm(c *gin.Context) ([]string, error) {
var images []string
for i := 0; i < 10; i++ {
imageStr := c.PostForm(fmt.Sprintf("old_live_image[%d]", i))
if imageStr == "" {
break
}
images = append(images, imageStr)
}
return images, nil
}
// parseNewImageFromForm 解析图片列表
func parseNewImageFromForm(c *gin.Context) ([]string, error) {
var images []string
for i := 0; i < 10; i++ {
imageStr := c.PostForm(fmt.Sprintf("new_live_image[%d]", i))
if imageStr == "" {
break
}
images = append(images, imageStr)
}
return images, nil
}
// UpdateProductNameAndImages 修改商品名称和实拍图
func (r *ProductApi) UpdateProductNameAndImages(c *gin.Context) {
var req systemReq.UpdateProductNameAndImagesRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "修改商品信息请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
// 始终从表单解析图片参数,确保能接收到空数组或新图片
image, err := parseImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
// 如果解析到了图片,使用解析的结果;否则保留ShouldBind的结果
if len(image) > 0 || c.PostForm("live_image[0]") != "" {
req.LiveImage = image
}
fmt.Printf("【UpdateProductNameAndImages】最终请求参数 - ProductID: %d, Name: %s, LiveImage数量: %d, LiveImage: %+v\n",
req.ProductID, req.Name, len(req.LiveImage), req.LiveImage)
if err := productService.UpdateProductNameAndImages(req, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "修改商品信息异常", err, c, req)
return
}
systemRes.OkWithMessage("修改成功", c)
}
// ReimportProducts 将导出的Excel修改后重新导入覆盖更新或新增
func (r *ProductApi) ReimportProducts(c *gin.Context) {
warehouseIDStr := c.PostForm("warehouse_id")
if warehouseIDStr == "" {
systemRes.FailWithValidateMessage("warehouse_id不能为空", c)
return
}
warehouseID, err := strconv.ParseInt(warehouseIDStr, 10, 64)
if err != nil || warehouseID <= 0 {
systemRes.FailWithValidateMessage("warehouse_id格式错误", c)
return
}
file, _, err := c.Request.FormFile("file")
if err != nil {
systemRes.FailWithValidateMessage("请上传Excel文件", c)
return
}
defer file.Close()
fileBytes, err := io.ReadAll(file)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品回传导入-读取文件失败", err, c, nil)
return
}
result, err := productService.ReimportProducts(fileBytes, warehouseID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品回传导入异常", err, c, nil)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "导入完成",
"data": result,
})
}
// DestroyProduct 销毁商品
func (r *ProductApi) DestroyProduct(c *gin.Context) {
var req systemReq.DestroyProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销毁商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
logID, err := productService.DestroyProduct(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销毁商品异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"destroy_log_id": logID}, "商品已销毁", c)
}
// RestoreProduct 还原商品
func (r *ProductApi) RestoreProduct(c *gin.Context) {
var req systemReq.RestoreProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "还原商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := productService.RestoreProduct(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "还原商品异常", err, c, req)
return
}
systemRes.OkWithMessage("商品已还原", c)
}
// GetDestroyLogList 获取销毁日志列表
func (r *ProductApi) GetDestroyLogList(c *gin.Context) {
var req systemReq.GetDestroyLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销毁日志列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetDestroyLogList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销毁日志列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetDestroyLogDetail 获取销毁日志详情
func (r *ProductApi) GetDestroyLogDetail(c *gin.Context) {
var req systemReq.GetDestroyLogDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销毁日志详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetDestroyLogDetail(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销毁日志详情异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}