feat(expiry): 完成 #25 用户设置接口实现

This commit is contained in:
root
2026-03-04 17:12:36 +08:00
parent 6bee50c170
commit cc16b342d7
5 changed files with 161 additions and 1 deletions
+2
View File
@@ -52,6 +52,8 @@ func main() {
&model.MiniProgram{},
&model.User{},
&model.UserMembership{},
&expiry.ExpiryItem{},
&expiry.ExpiryUserSettings{},
&membershipmodel.MembershipRedeemCode{},
&membershipmodel.MembershipRedemption{},
&rmmodel.VideoParseLog{},
+42 -1
View File
@@ -49,6 +49,10 @@ type updateStatusRequest struct {
Status string `json:"status"`
}
type updateSettingsRequest struct {
RemindDays []int `json:"remind_days"`
}
const expiryDateLayout = "2006-01-02"
// GetSummary 获取首页汇总统计。
@@ -216,6 +220,42 @@ func (h *Handler) UpdateStatus(c *gin.Context) {
})
}
// 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) (CreateItemRequest, error) {
productionDate, err := parseDateString(req.ProductionDate)
if err != nil {
@@ -289,7 +329,8 @@ func isExpiryBadRequestError(err error) bool {
errors.Is(err, ErrExpiryFilterStatusInvalid) ||
errors.Is(err, ErrExpiryFilterCategoryInvalid) ||
errors.Is(err, ErrExpiryFilterSortInvalid) ||
errors.Is(err, ErrExpiryStatusInvalid)
errors.Is(err, ErrExpiryStatusInvalid) ||
errors.Is(err, ErrExpiryRemindDaysInvalid)
}
func writeExpirySuccess(c *gin.Context, message string, data interface{}) {
+49
View File
@@ -217,3 +217,52 @@ func (r *Repository) UpdateStatus(id, userID uint, status string) error {
}
return nil
}
// GetSettings 查询用户提醒设置。
func (r *Repository) GetSettings(userID uint) (*ExpiryUserSettings, error) {
var settings ExpiryUserSettings
err := r.db.Where("user_id = ?", userID).First(&settings).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, fmt.Errorf("find expiry settings: %w", err)
}
return &settings, nil
}
// UpdateSettings 更新用户提醒设置(不存在则创建)。
func (r *Repository) UpdateSettings(userID uint, remindDays []int) (*ExpiryUserSettings, error) {
settings, err := r.GetSettings(userID)
if err != nil {
return nil, err
}
if settings == nil {
settings = &ExpiryUserSettings{
UserID: userID,
RemindDays: copyIntSlice(remindDays),
}
if err := r.db.Create(settings).Error; err != nil {
return nil, fmt.Errorf("create expiry settings: %w", err)
}
return settings, nil
}
settings.RemindDays = copyIntSlice(remindDays)
if err := r.db.Model(&ExpiryUserSettings{}).
Where("user_id = ?", userID).
Update("remind_days", settings.RemindDays).Error; err != nil {
return nil, fmt.Errorf("update expiry settings: %w", err)
}
return settings, nil
}
func copyIntSlice(values []int) []int {
if len(values) == 0 {
return nil
}
copied := make([]int, len(values))
copy(copied, values)
return copied
}
+66
View File
@@ -19,6 +19,7 @@ var (
ErrExpiryFilterCategoryInvalid = errors.New("category 参数无效")
ErrExpiryFilterSortInvalid = errors.New("sort 参数无效")
ErrExpiryStatusInvalid = errors.New("状态无效,仅支持 used/discarded")
ErrExpiryRemindDaysInvalid = errors.New("remind_days 必须是 1-30 的整数,数量 1-5 个")
)
// Service 封装保质期模块业务逻辑。
@@ -86,6 +87,11 @@ type SummaryResponse struct {
Discarded int `json:"discarded"`
}
// SettingsResponse 用户提醒设置返回结构。
type SettingsResponse struct {
RemindDays []int `json:"remind_days"`
}
func NewService(repo *Repository) *Service {
return &Service{repo: repo}
}
@@ -255,6 +261,41 @@ func (s *Service) UpdateItemStatus(id, userID uint, status string) error {
return s.repo.UpdateStatus(id, userID, status)
}
// GetSettings 获取用户提醒设置;若未配置则返回默认值。
func (s *Service) GetSettings(userID uint) (*SettingsResponse, error) {
settings, err := s.repo.GetSettings(userID)
if err != nil {
return nil, err
}
if settings == nil || len(settings.RemindDays) == 0 {
return &SettingsResponse{
RemindDays: copyIntSlice(defaultRemindDays),
}, nil
}
return &SettingsResponse{
RemindDays: copyIntSlice(settings.RemindDays),
}, nil
}
// UpdateSettings 更新用户提醒设置。
func (s *Service) UpdateSettings(userID uint, remindDays []int) (*SettingsResponse, error) {
validated, err := validateRemindDays(remindDays)
if err != nil {
return nil, err
}
settings, err := s.repo.UpdateSettings(userID, validated)
if err != nil {
return nil, err
}
return &SettingsResponse{
RemindDays: copyIntSlice(settings.RemindDays),
}, nil
}
func validateBaseFields(name, category string, quantity int, location, remark string) error {
name = strings.TrimSpace(name)
category = strings.TrimSpace(category)
@@ -367,3 +408,28 @@ func toItemView(item ExpiryItem) ItemView {
UpdatedAt: item.UpdatedAt,
}
}
func validateRemindDays(days []int) ([]int, error) {
if len(days) == 0 || len(days) > 5 {
return nil, ErrExpiryRemindDaysInvalid
}
seen := make(map[int]struct{}, len(days))
result := make([]int, 0, len(days))
for _, day := range days {
if day < 1 || day > 30 {
return nil, ErrExpiryRemindDaysInvalid
}
if _, ok := seen[day]; ok {
continue
}
seen[day] = struct{}{}
result = append(result, day)
}
if len(result) == 0 || len(result) > 5 {
return nil, ErrExpiryRemindDaysInvalid
}
return result, nil
}
+2
View File
@@ -20,5 +20,7 @@ func registerExpiryRoutes(protected *gin.RouterGroup, expiryHandler *expiryhandl
expiry.PUT("/items/:id", expiryHandler.UpdateItem)
expiry.DELETE("/items/:id", expiryHandler.DeleteItem)
expiry.POST("/items/:id/status", expiryHandler.UpdateStatus)
expiry.GET("/settings", expiryHandler.GetSettings)
expiry.POST("/settings", expiryHandler.UpdateSettings)
}
}