refactor: expiry 模块按 handler/model/service 分层
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
expiryrepo "wx_service/internal/expiry/repository"
|
||||
expiryservice "wx_service/internal/expiry/service"
|
||||
"wx_service/internal/middleware"
|
||||
)
|
||||
|
||||
// Handler 负责 HTTP 层处理。
|
||||
type Handler struct {
|
||||
service *expiryservice.Service
|
||||
}
|
||||
|
||||
func NewHandler(service *expiryservice.Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
// Healthz 用于 expiry 模块健康检查。
|
||||
func (h *Handler) Healthz(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": gin.H{
|
||||
"module": "expiry",
|
||||
"status": "ok",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type createOrUpdateItemRequest struct {
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
ProductionDate string `json:"production_date"`
|
||||
ExpiryDate string `json:"expiry_date"`
|
||||
ShelfLifeDays *int `json:"shelf_life_days"`
|
||||
Quantity *int `json:"quantity"`
|
||||
Location string `json:"location"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type updateStatusRequest struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type updateSettingsRequest struct {
|
||||
RemindDays []int `json:"remind_days"`
|
||||
}
|
||||
|
||||
const expiryDateLayout = "2006-01-02"
|
||||
|
||||
// GetSummary 获取首页汇总统计。
|
||||
func (h *Handler) GetSummary(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
resp, err := h.service.GetSummary(user.ID)
|
||||
if err != nil {
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
writeExpirySuccess(c, "success", resp)
|
||||
}
|
||||
|
||||
// GetItems 获取物品列表。
|
||||
func (h *Handler) GetItems(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
|
||||
page := parseIntWithDefault(c.Query("page"), 1)
|
||||
pageSize := parseIntWithDefault(c.Query("page_size"), 20)
|
||||
filters := expiryservice.ItemFilters{
|
||||
Status: strings.TrimSpace(c.DefaultQuery("status", "all")),
|
||||
Category: strings.TrimSpace(c.DefaultQuery("category", "all")),
|
||||
Sort: strings.TrimSpace(c.DefaultQuery("sort", "expiry_date")),
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
|
||||
resp, err := h.service.GetItems(user.ID, filters)
|
||||
if err != nil {
|
||||
if isExpiryBadRequestError(err) {
|
||||
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
writeExpirySuccess(c, "success", resp)
|
||||
}
|
||||
|
||||
// CreateItem 添加物品。
|
||||
func (h *Handler) CreateItem(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
|
||||
var req createOrUpdateItemRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
writeExpiryError(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
serviceReq, err := h.toCreateItemRequest(user.MiniProgramID, req)
|
||||
if err != nil {
|
||||
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
item, err := h.service.CreateItem(user.ID, serviceReq)
|
||||
if err != nil {
|
||||
if isExpiryBadRequestError(err) {
|
||||
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
writeExpirySuccess(c, "添加成功", expiryservice.ToItemView(*item))
|
||||
}
|
||||
|
||||
// UpdateItem 更新物品。
|
||||
func (h *Handler) UpdateItem(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
id, ok := parseItemID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req createOrUpdateItemRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
writeExpiryError(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
serviceReq, err := h.toCreateItemRequest(user.MiniProgramID, req)
|
||||
if err != nil {
|
||||
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
item, err := h.service.UpdateItem(id, user.ID, serviceReq)
|
||||
if err != nil {
|
||||
if errors.Is(err, expiryrepo.ErrExpiryItemNotFound) {
|
||||
writeExpiryError(c, http.StatusNotFound, "物品不存在")
|
||||
return
|
||||
}
|
||||
if isExpiryBadRequestError(err) {
|
||||
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
writeExpirySuccess(c, "更新成功", expiryservice.ToItemView(*item))
|
||||
}
|
||||
|
||||
// DeleteItem 删除物品。
|
||||
func (h *Handler) DeleteItem(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
id, ok := parseItemID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := h.service.DeleteItem(id, user.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, expiryrepo.ErrExpiryItemNotFound) {
|
||||
writeExpiryError(c, http.StatusNotFound, "物品不存在")
|
||||
return
|
||||
}
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
writeExpirySuccess(c, "删除成功", nil)
|
||||
}
|
||||
|
||||
// UpdateStatus 标记物品状态(used/discarded)。
|
||||
func (h *Handler) UpdateStatus(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
id, ok := parseItemID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req updateStatusRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
|
||||
writeExpiryError(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.service.UpdateItemStatus(id, user.ID, strings.TrimSpace(req.Status)); err != nil {
|
||||
if errors.Is(err, expiryrepo.ErrExpiryItemNotFound) {
|
||||
writeExpiryError(c, http.StatusNotFound, "物品不存在")
|
||||
return
|
||||
}
|
||||
if isExpiryBadRequestError(err) {
|
||||
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
item, err := h.service.GetItem(id, user.ID)
|
||||
if err != nil {
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
writeExpirySuccess(c, "标记成功", gin.H{
|
||||
"id": item.ID,
|
||||
"status": item.Status,
|
||||
"updated_at": item.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// GetSettings 获取用户提醒设置。
|
||||
func (h *Handler) GetSettings(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
|
||||
resp, err := h.service.GetSettings(user.ID)
|
||||
if err != nil {
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
writeExpirySuccess(c, "success", resp)
|
||||
}
|
||||
|
||||
// UpdateSettings 更新用户提醒设置。
|
||||
func (h *Handler) UpdateSettings(c *gin.Context) {
|
||||
user := middleware.MustCurrentUser(c)
|
||||
|
||||
var req updateSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
writeExpiryError(c, http.StatusBadRequest, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.UpdateSettings(user.ID, req.RemindDays)
|
||||
if err != nil {
|
||||
if isExpiryBadRequestError(err) {
|
||||
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeExpiryServerError(c)
|
||||
return
|
||||
}
|
||||
|
||||
writeExpirySuccess(c, "更新成功", resp)
|
||||
}
|
||||
|
||||
func (h *Handler) toCreateItemRequest(miniProgramID uint, req createOrUpdateItemRequest) (expiryservice.CreateItemRequest, error) {
|
||||
productionDate, err := parseDateString(req.ProductionDate)
|
||||
if err != nil {
|
||||
return expiryservice.CreateItemRequest{}, errors.New("production_date 格式错误,应为 YYYY-MM-DD")
|
||||
}
|
||||
|
||||
expiryDate, err := parseDateString(req.ExpiryDate)
|
||||
if err != nil {
|
||||
return expiryservice.CreateItemRequest{}, errors.New("expiry_date 格式错误,应为 YYYY-MM-DD")
|
||||
}
|
||||
|
||||
quantity := 1
|
||||
if req.Quantity != nil {
|
||||
quantity = *req.Quantity
|
||||
}
|
||||
|
||||
return expiryservice.CreateItemRequest{
|
||||
MiniProgramID: miniProgramID,
|
||||
Name: req.Name,
|
||||
Category: req.Category,
|
||||
ProductionDate: productionDate,
|
||||
ExpiryDate: expiryDate,
|
||||
ShelfLifeDays: req.ShelfLifeDays,
|
||||
Quantity: quantity,
|
||||
Location: req.Location,
|
||||
Remark: req.Remark,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseDateString(v string) (*time.Time, error) {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parsed, err := time.ParseInLocation(expiryDateLayout, v, time.Local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &parsed, nil
|
||||
}
|
||||
|
||||
func parseIntWithDefault(v string, defaultValue int) int {
|
||||
if strings.TrimSpace(v) == "" {
|
||||
return defaultValue
|
||||
}
|
||||
parsed, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func parseItemID(c *gin.Context) (uint, bool) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil || id <= 0 {
|
||||
writeExpiryError(c, http.StatusBadRequest, "id 参数错误")
|
||||
return 0, false
|
||||
}
|
||||
return uint(id), true
|
||||
}
|
||||
|
||||
func isExpiryBadRequestError(err error) bool {
|
||||
return errors.Is(err, expiryservice.ErrExpiryNameInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryCategoryInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryMiniProgramRequired) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryQuantityInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryLocationTooLong) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryRemarkTooLong) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryDateRequired) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryShelfLifeDaysInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryFilterStatusInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryFilterCategoryInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryFilterSortInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryStatusInvalid) ||
|
||||
errors.Is(err, expiryservice.ErrExpiryRemindDaysInvalid)
|
||||
}
|
||||
|
||||
func writeExpirySuccess(c *gin.Context, message string, data interface{}) {
|
||||
resp := gin.H{
|
||||
"code": 0,
|
||||
"message": message,
|
||||
}
|
||||
if data != nil {
|
||||
resp["data"] = data
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func writeExpiryError(c *gin.Context, code int, message string) {
|
||||
c.JSON(code, gin.H{
|
||||
"code": code,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func writeExpiryServerError(c *gin.Context) {
|
||||
writeExpiryError(c, http.StatusInternalServerError, "服务器错误")
|
||||
}
|
||||
Reference in New Issue
Block a user