第 5 阶段:纠纷、通知与后台-1
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Notification Module
|
||||
|
||||
站内信列表、已读状态和订单流程消息。
|
||||
@@ -0,0 +1,15 @@
|
||||
package notification
|
||||
|
||||
import "time"
|
||||
|
||||
type NotificationDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
BizType string `json:"biz_type"`
|
||||
BizID *uint64 `json:"biz_id"`
|
||||
ReadAt *time.Time `json:"read_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"hfb_sys/backend/internal/middleware"
|
||||
"hfb_sys/backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
func (h *Handler) List(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
items, err := h.service.List(userID)
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
func (h *Handler) MarkRead(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "ID 不正确")
|
||||
return
|
||||
}
|
||||
if err := h.service.MarkRead(userID, id); err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"read": true})
|
||||
}
|
||||
|
||||
func currentUserID(c *gin.Context) (uint64, bool) {
|
||||
value, ok := c.Get(middleware.ContextUserID)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
userID, ok := value.(uint64)
|
||||
return userID, ok
|
||||
}
|
||||
|
||||
func writeNotificationError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
default:
|
||||
response.ServiceUnavailable(c, "通知服务暂时不可用")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
UserID uint64
|
||||
Type string
|
||||
Title string
|
||||
Content string
|
||||
BizType string
|
||||
BizID *uint64
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
|
||||
func (r *Repository) List(userID uint64) ([]NotificationDTO, error) {
|
||||
var rows []model.Notification
|
||||
if err := r.db.Where("user_id = ?", userID).Order("id DESC").Limit(100).Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]NotificationDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, toDTO(row))
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *Repository) MarkRead(userID uint64, id uint64) error {
|
||||
now := time.Now()
|
||||
return r.db.Model(&model.Notification{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Update("read_at", now).Error
|
||||
}
|
||||
|
||||
func Append(tx *gorm.DB, entries ...Entry) error {
|
||||
for _, entry := range entries {
|
||||
if entry.UserID == 0 || entry.Title == "" {
|
||||
continue
|
||||
}
|
||||
row := model.Notification{
|
||||
UserID: entry.UserID,
|
||||
Type: entry.Type,
|
||||
Title: entry.Title,
|
||||
Content: entry.Content,
|
||||
BizType: entry.BizType,
|
||||
BizID: entry.BizID,
|
||||
}
|
||||
if row.Type == "" {
|
||||
row.Type = "system"
|
||||
}
|
||||
if err := tx.Create(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toDTO(row model.Notification) NotificationDTO {
|
||||
return NotificationDTO{
|
||||
ID: row.ID,
|
||||
UserID: row.UserID,
|
||||
Type: row.Type,
|
||||
Title: row.Title,
|
||||
Content: row.Content,
|
||||
BizType: row.BizType,
|
||||
BizID: row.BizID,
|
||||
ReadAt: row.ReadAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package notification
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
}
|
||||
|
||||
func NewService(repo *Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) List(userID uint64) ([]NotificationDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.List(userID)
|
||||
}
|
||||
|
||||
func (s *Service) MarkRead(userID uint64, id uint64) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.MarkRead(userID, id)
|
||||
}
|
||||
Reference in New Issue
Block a user