feat: 删除JSP视图层,完善评价和通知系统,新增拼团模块

- 删除所有 JSP 页面(20个文件),前端完全迁移至 Vue 3 SPA
- 完善评价系统:ReviewDialog 组件、用户评价历史页、评价状态检查API
- 新增通知系统:Notification 实体/仓库/服务/控制器,NotificationCenter 接入真实API
- 新增拼团模块:GroupBuying 全套后端和前端页面
- 修复 review check API 参数双重包装导致请求格式错误
- 修复通知 API 路径缺少 /api 前缀和响应格式处理
- MessageListenerService 集成 NotificationService 创建持久化通知

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 16:40:26 +08:00
parent b684ea38d4
commit c4582655d9
115 changed files with 5968 additions and 12623 deletions

View File

@@ -0,0 +1,98 @@
package com.org.flashsalesystem.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "group_buying")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GroupBuying {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "product_id", nullable = false)
private Long productId;
@Column(name = "group_price", nullable = false, precision = 10, scale = 2)
private BigDecimal groupPrice;
@Column(name = "required_members", nullable = false)
private Integer requiredMembers = 2;
@Column(name = "duration_minutes", nullable = false)
private Integer durationMinutes = 1440;
@Column(name = "total_stock", nullable = false)
private Integer totalStock;
@Column(name = "remaining_stock", nullable = false)
private Integer remainingStock;
@Column(name = "max_per_user", nullable = false)
private Integer maxPerUser = 1;
/**
* 状态0-草稿 1-未开始 2-进行中 3-已结束
*/
@Column(nullable = false)
private Integer status = 0;
@Column(name = "start_time", nullable = false)
private LocalDateTime startTime;
@Column(name = "end_time", nullable = false)
private LocalDateTime endTime;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", insertable = false, updatable = false)
private Product product;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
public boolean isActive() {
LocalDateTime now = LocalDateTime.now();
return now.isAfter(startTime) && now.isBefore(endTime) && status == 2;
}
public enum GroupBuyingStatus {
DRAFT(0, "草稿"),
PENDING(1, "未开始"),
ACTIVE(2, "进行中"),
ENDED(3, "已结束");
private final int code;
private final String description;
GroupBuyingStatus(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() { return code; }
public String getDescription() { return description; }
}
}

View File

@@ -0,0 +1,80 @@
package com.org.flashsalesystem.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "group_buying_group")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GroupBuyingGroup {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "group_no", nullable = false, unique = true, length = 64)
private String groupNo;
@Column(name = "group_buying_id", nullable = false)
private Long groupBuyingId;
@Column(name = "leader_user_id", nullable = false)
private Long leaderUserId;
@Column(name = "required_members", nullable = false)
private Integer requiredMembers;
@Column(name = "current_members", nullable = false)
private Integer currentMembers = 1;
/**
* 状态1-拼团中 2-已成团 3-已失败(超时)
*/
@Column(nullable = false)
private Integer status = 1;
@Column(name = "expire_time", nullable = false)
private LocalDateTime expireTime;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "completed_at")
private LocalDateTime completedAt;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_buying_id", insertable = false, updatable = false)
private GroupBuying groupBuying;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "leader_user_id", insertable = false, updatable = false)
private User leader;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
public enum GroupStatus {
FORMING(1, "拼团中"),
SUCCESS(2, "已成团"),
FAILED(3, "已失败");
private final int code;
private final String description;
GroupStatus(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() { return code; }
public String getDescription() { return description; }
}
}

View File

@@ -0,0 +1,53 @@
package com.org.flashsalesystem.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "group_buying_member", uniqueConstraints = {
@UniqueConstraint(name = "uk_group_user", columnNames = {"group_id", "user_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GroupBuyingMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "group_id", nullable = false)
private Long groupId;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "order_id")
private Long orderId;
/**
* 状态1-已加入 2-已成团 3-已退出
*/
@Column(nullable = false)
private Integer status = 1;
@Column(name = "joined_at", nullable = false, updatable = false)
private LocalDateTime joinedAt;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_id", insertable = false, updatable = false)
private GroupBuyingGroup group;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user;
@PrePersist
protected void onCreate() {
joinedAt = LocalDateTime.now();
}
}

View File

@@ -0,0 +1,49 @@
package com.org.flashsalesystem.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "notifications", indexes = {
@Index(name = "idx_notification_user_read", columnList = "user_id, is_read"),
@Index(name = "idx_notification_user_created", columnList = "user_id, created_at")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Notification {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(nullable = false, length = 32)
private String type;
@Column(nullable = false)
private String title;
@Column(nullable = false, columnDefinition = "TEXT")
private String message;
@Column(length = 255)
private String link;
@Column(name = "is_read", nullable = false)
private Boolean read = false;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
}

View File

@@ -40,6 +40,12 @@ public class Order {
@Column(name = "product_id", nullable = false)
private Long productId;
@Column(name = "flash_sale_id")
private Long flashSaleId;
@Column(name = "group_buying_group_id")
private Long groupBuyingGroupId;
@Min(value = 1, message = "商品数量必须大于0")
@Column(nullable = false)
private Integer quantity;
@@ -145,7 +151,8 @@ public class Order {
*/
public enum OrderType {
NORMAL(1, "普通订单"),
FLASH_SALE(2, "秒杀订单");
FLASH_SALE(2, "秒杀订单"),
GROUP_BUYING(3, "拼团订单");
private final int code;
private final String description;