操作:
在 pom.xml 中新增以下依赖:
<!-- MyBatis 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
无需添加密码加密依赖 — 使用 Spring 内置 org.springframework.util.DigestUtils(MD5)
验证:mvn dependency:tree 无报错
操作:
在 src/main/java/com/campus/trade/ 下创建子包目录:
com.campus.trade
├── config
├── common
├── entity
├── dto
├── vo
├── mapper
├── service
│ └── admin
└── controller
└── admin
新建 src/main/java/com/campus/trade/ServerApplication.java:
package com.campus.trade;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.campus.trade")
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
}
保留旧 space.anyi 包下的入口作为备份,后续删除
验证:mvn compile 编译通过
操作:将 server/src/main/resources/application.yaml 更新为:
spring:
application:
name: CampusIdleItemTradingPlatform
datasource:
url: jdbc:mysql://localhost:3306/campus_idle_item_trading_platform?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
mvc:
static-path-pattern: /upload/**
static-locations: file:./upload/
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.campus.trade.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
enabled: true
packages-to-scan: com.campus.trade.controller
default-produces-media-type: application/json
server:
servlet:
context-path: /api
session:
timeout: 3600s
port: 8080
路径:src/main/java/com/campus/trade/common/Result.java
说明:统一 API 响应体,所有 Controller 返回此对象。
package com.campus.trade.common;
public class Result {
private int code;
private String message;
private Object data;
public Result() {}
public Result(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
public static Result success() {
return new Result(200, "success", null);
}
public static Result success(Object data) {
return new Result(200, "success", data);
}
public static Result error(int code, String message) {
return new Result(code, message, null);
}
// getters & setters
}
路径:src/main/java/com/campus/trade/common/StatusCode.java
说明:业务状态码常量。
package com.campus.trade.common;
public class StatusCode {
public static final int SUCCESS = 200;
public static final int BAD_REQUEST = 400;
public static final int UNAUTHORIZED = 401;
public static final int FORBIDDEN = 403;
public static final int NOT_FOUND = 404;
public static final int CONFLICT = 409;
public static final int SERVER_ERROR = 500;
}
路径:src/main/java/com/campus/trade/common/BusinessException.java
说明:自定义运行时异常,携带业务状态码。
package com.campus.trade.common;
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
路径:src/main/java/com/campus/trade/common/GlobalExceptionHandler.java
说明:全局异常捕获,统一返回 Result 格式。
package com.campus.trade.common;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.validation.FieldError;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusiness(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidation(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors()
.stream().map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(StatusCode.BAD_REQUEST, msg);
}
@ExceptionHandler(Exception.class)
public Result handleUnknown(Exception e) {
e.printStackTrace();
return Result.error(StatusCode.SERVER_ERROR, "服务器内部错误");
}
}
路径:src/main/java/com/campus/trade/common/FileUploadUtil.java
说明:上传文件到 ./upload/ 目录,校验图片格式。
package com.campus.trade.common;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.*;
@Component
public class FileUploadUtil {
@Value("${upload.dir:./upload}")
private String uploadDir;
// 上传单个文件,返回访问路径 /upload/{filename}
public String upload(MultipartFile file) {
if (file.isEmpty()) {
throw new BusinessException(StatusCode.BAD_REQUEST, "文件不能为空");
}
// 校验文件扩展名
String originalFilename = file.getOriginalFilename();
String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
List<String> allowed = Arrays.asList(".jpg", ".jpeg", ".png", ".gif", ".webp");
if (!allowed.contains(ext.toLowerCase())) {
throw new BusinessException(StatusCode.BAD_REQUEST, "不支持的图片格式");
}
// UUID 重命名防止覆盖
String filename = UUID.randomUUID() + ext;
File dest = new File(uploadDir, filename);
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
try {
file.transferTo(dest);
} catch (IOException e) {
throw new BusinessException(StatusCode.SERVER_ERROR, "文件上传失败");
}
return "/upload/" + filename;
}
// 批量上传
public List<String> uploadBatch(List<MultipartFile> files) {
if (files == null) return Collections.emptyList();
return files.stream().map(this::upload).collect(Collectors.toList());
}
}
路径:src/main/java/com/campus/trade/config/WebMvcConfig.java
说明:静态资源映射 + 注册拦截器。
package com.campus.trade.config;
import com.campus.trade.interceptor.AuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
// 映射 /upload/** 到本地 ./upload/ 目录
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceLocations("file:./upload/");
}
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**");
}
}
路径:src/main/java/com/campus/trade/config/MyBatisConfig.java
说明:PageHelper 分页插件配置。
package com.campus.trade.config;
import com.github.pagehelper.PageInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class MyBatisConfig {
@Bean
public PageInterceptor pageInterceptor() {
PageInterceptor interceptor = new PageInterceptor();
Properties props = new Properties();
props.setProperty("helperDialect", "mysql");
props.setProperty("reasonable", "true");
props.setProperty("supportMethodsArguments", "true");
interceptor.setProperties(props);
return interceptor;
}
}
路径:src/main/java/com/campus/trade/interceptor/AuthInterceptor.java
说明:Session 登录拦截器,白名单放行无需登录的路径。
package com.campus.trade.interceptor;
import com.campus.trade.common.Result;
import com.campus.trade.common.StatusCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Arrays;
import java.util.List;
@Component
public class AuthInterceptor implements HandlerInterceptor {
// 无需登录的白名单路径
private static final List<String> WHITE_LIST = Arrays.asList(
"/auth/register",
"/auth/login",
"/items",
"/items/search",
"/items/category/",
"/upload/",
"/v3/api-docs",
"/swagger-ui",
"/api-docs"
);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String uri = request.getRequestURI();
// 检查是否在白名单中
for (String white : WHITE_LIST) {
if (uri.startsWith(white)) {
return true;
}
}
// 检查 Session 中是否有 userId
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
response.setContentType("application/json;charset=utf-8");
ObjectMapper mapper = new ObjectMapper();
response.getWriter().write(
mapper.writeValueAsString(Result.error(StatusCode.UNAUTHORIZED, "请先登录"))
);
return false;
}
return true;
}
}
路径:src/main/java/com/campus/trade/common/OrderStatusMachine.java
说明:订单状态机,防止非法状态流转。
package com.campus.trade.common;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class OrderStatusMachine {
// 定义每个状态允许的下一个状态
private static final Map<String, List<String>> ALLOWED_TRANSITIONS = new HashMap<>();
static {
ALLOWED_TRANSITIONS.put("PENDING", Arrays.asList("CONFIRMED", "CANCELLED"));
ALLOWED_TRANSITIONS.put("CONFIRMED", Arrays.asList("DELIVERED", "CANCELLED"));
ALLOWED_TRANSITIONS.put("DELIVERED", Arrays.asList("COMPLETED", "CANCELLED"));
ALLOWED_TRANSITIONS.put("COMPLETED", Collections.emptyList());
ALLOWED_TRANSITIONS.put("CANCELLED", Collections.emptyList());
}
// 校验从当前状态到目标状态是否允许
public void validateTransition(String currentStatus, String targetStatus) {
List<String> allowed = ALLOWED_TRANSITIONS.get(currentStatus);
if (allowed == null || !allowed.contains(targetStatus)) {
throw new BusinessException(StatusCode.BAD_REQUEST,
"订单状态不允许从 " + currentStatus + " 转移到 " + targetStatus);
}
}
}
在 MySQL 中执行 6 张表的建表 SQL(DDL 内容详见 detailDesign.md 第 1.3 节,或执行 server/src/main/resources/init.sql)。
表清单:user、category、item、order、message、review
路径:src/main/java/com/campus/trade/entity/
| 步骤 | 类 | 说明 |
|---|---|---|
| 2.2 | User.java |
用户实体,含 id, studentId, name, passwordHash, nickname, avatarUrl, signature, campusCardUrl, authStatus, status, role |
| 2.3 | Item.java |
物品实体,含 id, sellerId, categoryId, title, description, price, images, tradeMethod, status |
| 2.4 | Order.java |
订单实体,含 id, itemId, buyerId, sellerId, tradeMethod, status |
| 2.5 | Message.java |
消息实体,含 id, fromUserId, toUserId, itemId, content, imageUrl, isRead |
| 2.6 | Review.java |
审核记录实体,含 id, adminId, reviewType, targetId, result, reason |
| 2.7 | Category.java |
分类实体,含 id, name, status, sortOrder |
要求:
// 注释 说明含义@Schema(description = "...")LocalDateTime 类型示例(User.java):
package com.campus.trade.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;
@Schema(description = "用户实体")
public class User {
private Long id; // 用户ID
private String studentId; // 学号
private String name; // 姓名
private String passwordHash; // 密码哈希(MD5)
private String nickname; // 昵称
private String avatarUrl; // 头像 URL
private String signature; // 个性签名
private String campusCardUrl; // 校园卡照片 URL
private String authStatus; // 认证状态:UNAUTHENTICATED/PENDING/AUTHENTICATED
private String status; // 账号状态:NORMAL/BANNED
private String role; // 角色:USER/ADMIN
private LocalDateTime createdAt; // 创建时间
private LocalDateTime updatedAt; // 更新时间
// getters & setters ...
}
路径:
| 步骤 | Mapper 接口 | 对应 XML | 核心方法 |
|---|---|---|---|
| 2.8 | mapper/UserMapper.java |
resources/mapper/UserMapper.xml |
selectByStudentId, selectByAuthStatus, updateAuthStatus, updatePassword |
| 2.9 | mapper/ItemMapper.java |
resources/mapper/ItemMapper.xml |
selectByStatus, searchByKeyword, selectByCategoryId, selectBySellerId, updateStatus |
| 2.10 | mapper/OrderMapper.java |
resources/mapper/OrderMapper.xml |
selectByBuyerId, selectBySellerId, updateStatus |
| 2.11 | mapper/MessageMapper.java |
resources/mapper/MessageMapper.xml |
selectConversations, selectHistory, markAsRead |
| 2.12 | mapper/ReviewMapper.java |
resources/mapper/ReviewMapper.xml |
insert, selectByTypeAndTarget |
| 2.13 | mapper/CategoryMapper.java |
resources/mapper/CategoryMapper.xml |
selectAll, selectEnabled, updateStatus |
要求:
@Mapper 注解或在启动类使用 @MapperScan#{} 参数绑定(防 SQL 注入)PageHelper.startPage(pageNum, pageSize) 在 Service 层调用示例(UserMapper.java):
package com.campus.trade.mapper;
import com.campus.trade.entity.User;
import java.util.List;
public interface UserMapper {
User selectByStudentId(String studentId); // 按学号查询用户
List<User> selectByAuthStatus(String authStatus); // 按认证状态查询用户列表
int insert(User user); // 新增用户
int updateAuthStatus(User user); // 更新认证状态
int updatePassword(User user); // 更新密码
User selectById(Long id); // 按ID查询
int updateProfile(User user); // 更新个人资料
}
示例(UserMapper.xml):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.campus.trade.mapper.UserMapper">
<select id="selectByStudentId" resultType="User">
SELECT * FROM user WHERE student_id = #{studentId}
</select>
<select id="selectByAuthStatus" resultType="User">
SELECT * FROM user WHERE auth_status = #{authStatus} ORDER BY created_at ASC
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (student_id, name, password_hash, nickname, avatar_url, signature,
campus_card_url, auth_status, status, role)
VALUES (#{studentId}, #{name}, #{passwordHash}, #{nickname}, #{avatarUrl}, #{signature},
#{campusCardUrl}, #{authStatus}, #{status}, #{role})
</insert>
<update id="updateAuthStatus">
UPDATE user SET auth_status = #{authStatus} WHERE id = #{id}
</update>
<update id="updatePassword">
UPDATE user SET password_hash = #{passwordHash} WHERE id = #{id}
</update>
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<update id="updateProfile">
UPDATE user SET nickname = #{nickname}, avatar_url = #{avatarUrl}, signature = #{signature}
WHERE id = #{id}
</update>
</mapper>
路径:src/main/java/com/campus/trade/dto/
| 步骤 | DTO | 校验注解 | 用途 |
|---|---|---|---|
| 3.1 | LoginRequest.java |
@NotBlank studentId, password |
登录 |
| 3.1 | RegisterRequest.java |
@NotBlank studentId, name, password + MultipartFile campusCardImage |
注册 |
| 3.1 | ChangePasswordRequest.java |
@NotBlank oldPassword, newPassword |
修改密码 |
| 3.1 | UserProfileRequest.java |
nickname, signature, MultipartFile avatar | 修改资料 |
| 3.2 | ItemPublishRequest.java |
@NotBlank title, tradeMethod @NotNull price + List images |
发布物品 |
| 3.2 | CreateOrderRequest.java |
@NotNull itemId |
创建订单 |
| 3.2 | SendMessageRequest.java |
@NotNull toUserId + content/imageUrl |
发送消息 |
| 3.3 | ReviewRequest.java |
@NotBlank result + reason |
审核操作 |
| 3.3 | CategoryRequest.java |
@NotBlank name + sortOrder |
分类管理 |
| 3.3 | StatusRequest.java |
@NotBlank status |
状态切换 |
要求:每个 DTO 添加 @Schema(description = "...") 和 @Schema(description = "...", example = "...") 字段注解。
路径:src/main/java/com/campus/trade/vo/
| 步骤 | VO | 说明 |
|---|---|---|
| 3.4 | UserProfileVO.java |
用户信息(不含密码) |
| 3.4 | ItemVO.java |
物品详情(含卖家昵称、头像、分类名) |
| 3.4 | OrderVO.java |
订单详情(含物品标题、图片、买卖双方信息) |
| 3.4 | MessageVO.java |
消息信息(含发送方/接收方昵称、头像) |
| 3.4 | ConversationVO.java |
会话列表项(含最后一条消息、未读数) |
| 3.5 | PageResult.java |
泛型分页结果 |
路径:src/main/java/com/campus/trade/service/ + service/admin/
| 步骤 | Service | 核心业务逻辑要点 |
|---|---|---|
| 3.7 | AuthService | 注册:学号查重 → 校园卡上传 → MD5 加密密码 → 写入 User(authStatus=PENDING);登录:学号查询 → 封禁检查 → MD5 密码比对 → 设置 Session |
| 3.8 | UserService | 查询/修改个人资料(昵称、签名、头像上传) |
| 3.8 | ItemService | 发布(认证检查 → 图片上传 → status=PENDING);列表(ON_SALE 分页倒序);搜索(LIKE 模糊);分类筛选;详情;下架(校验卖家身份);删除 |
| 3.9 | OrderService | 创建(校验物品 ON_SALE → 创建 PENDING 订单 → 物品变 SOLD);接单/拒单(校验卖家 + 状态机);交货(校验卖家 + CONFIRMED → DELIVERED);确认收货(校验买家 + DELIVERED → COMPLETED) |
| 3.9 | MessageService | 发送消息;会话列表(聚合查询含未读数);历史记录(分页 + 标记已读) |
| 3.10 | ReviewService | 实名审核(校验 PENDING → AUTHENTICATED/UNAUTHENTICATED + 记录 Review);物品审核(校验 PENDING → ON_SALE/OFF_SHELF + 记录 Review) |
| 3.10 | UserManageService | 用户列表;封禁/解封;重置密码(重置为默认 MD5 密码) |
| 3.10 | CategoryService | 分类 CRUD;启用/禁用 |
密码加密核心代码(AuthService):
// 注册时加密密码
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
// MD5 加密
String passwordHash = DigestUtils.md5DigestAsHex(
request.getPassword().getBytes(StandardCharsets.UTF_8)
);
// 登录时比对密码
boolean matched = DigestUtils.md5DigestAsHex(
password.getBytes(StandardCharsets.UTF_8)
).equals(user.getPasswordHash());
路径:src/main/java/com/campus/trade/controller/ + controller/admin/
| 步骤 | Controller | 类注解 | 方法数 | 关键路径 |
|---|---|---|---|---|
| 4.1 | AuthController.java |
@Tag(name="认证模块") |
4 | POST /auth/register, /auth/login, /auth/logout, /auth/change-password |
| 4.2 | UserController.java |
@Tag(name="用户模块") |
2 | GET/PUT /user/profile |
| 4.3 | ItemController.java |
@Tag(name="物品模块") |
8 | GET /items, /items/search, /items/category/{id}, /items/{id}; POST /items; GET /items/my; PUT /items/{id}/offline; DELETE /items/{id} |
| 4.4 | OrderController.java |
@Tag(name="订单模块") |
7 | POST /orders; GET /orders/bought, /orders/sold; PUT /orders/{id}/accept/reject/deliver/confirm |
| 4.5 | MessageController.java |
@Tag(name="消息模块") |
3 | GET /messages/conversations; GET /messages/{userId}; POST /messages |
| 4.6 | admin/ReviewController.java |
@Tag(name="审核模块") |
4 | GET /admin/review/users, /admin/review/items; POST /admin/review/user/{id}, /admin/review/item/{id} |
| 4.7 | admin/UserManageController.java |
@Tag(name="用户管理") |
4 | GET /admin/users; PUT /admin/users/{id}/ban, /admin/users/{id}/unban; POST /admin/users/{id}/reset-password |
| 4.8 | admin/CategoryController.java |
@Tag(name="分类管理") |
4 | GET/POST /admin/categories; PUT /admin/categories/{id}; PUT /admin/categories/{id}/status |
要求(遵循 AGENTS.md 规范):
@Tag(name = "...", description = "...")@Operation(summary = "...", description = "...")@ApiResponse(responseCode = "200", description = "成功")@Parameter(description = "...")HttpSession session → session.getAttribute("userId")示例(AuthController.java):
package com.campus.trade.controller;
import com.campus.trade.common.Result;
import com.campus.trade.dto.LoginRequest;
import com.campus.trade.dto.RegisterRequest;
import com.campus.trade.dto.ChangePasswordRequest;
import com.campus.trade.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
@Tag(name = "认证模块", description = "注册 / 登录 / 登出 / 修改密码")
public class AuthController {
@Autowired
private AuthService authService;
@Operation(summary = "用户注册", description = "学号+姓名+密码+校园卡照片,提交后进入待审核状态")
@PostMapping(value = "/register", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Result register(@Valid RegisterRequest req) {
return authService.register(req);
}
@Operation(summary = "用户登录", description = "学号+密码登录,创建 Session 会话")
@PostMapping("/login")
public Result login(@Valid @RequestBody LoginRequest req, HttpSession session) {
return authService.login(req, session);
}
@Operation(summary = "用户登出", description = "销毁当前 Session")
@PostMapping("/logout")
public Result logout(HttpSession session) {
session.invalidate();
return Result.success();
}
@Operation(summary = "修改密码", description = "需提供旧密码验证")
@PostMapping("/change-password")
public Result changePassword(@Valid @RequestBody ChangePasswordRequest req, HttpSession session) {
return authService.changePassword(req, session);
}
}
cd server
mvn spring-boot:run
验证项:
访问 http://localhost:8080/api/v3/api-docs
验证项:
summary、parameters、requestBody、responses 等完整信息访问 http://localhost:8080/api/swagger-ui.html
验证项:
| 测试用例 | 步骤 | 预期 |
|---|---|---|
| 注册 | POST /auth/register 提交注册信息+校园卡照片 |
返回 userId,authStatus=PENDING |
| 登录 | POST /auth/login 用刚注册的学号密码 |
返回用户信息,Session 创建成功 |
| 发布物品 | POST /items 提交物品信息+图片 |
返回 itemId,status=PENDING |
| 查看列表 | GET /items?pageNum=1&pageSize=10 |
返回在售物品分页列表 |
| 搜索物品 | GET /items/search?keyword=课本 |
返回匹配的物品列表 |
| 创建订单 | POST /orders { itemId } |
返回 orderId,status=PENDING |
| 接单 | PUT /orders/{id}/accept |
订单状态变为 CONFIRMED |
| 确认收货 | PUT /orders/{id}/confirm |
订单状态变为 COMPLETED |
| 发消息 | POST /messages { toUserId, content } |
返回 messageId |
| 会话列表 | GET /messages/conversations |
返回会话列表含未读数 |
| 阶段 | 新建 Java 文件数 | 新建 XML 文件数 | 修改配置文件 |
|---|---|---|---|
| 阶段零 | 1 | 0 | pom.xml, application.yaml |
| 阶段一 | 8 | 0 | — |
| 阶段二 | 12 | 6 | init.sql |
| 阶段三 | 16 | 0 | — |
| 阶段四 | 8 | 0 | — |
| 合计 | 45 | 6 | 2 |
com.campus.trade
├── ServerApplication.java
├── common/
│ ├── Result.java
│ ├── StatusCode.java
│ ├── BusinessException.java
│ ├── GlobalExceptionHandler.java
│ ├── FileUploadUtil.java
│ └── OrderStatusMachine.java
├── config/
│ ├── WebMvcConfig.java
│ └── MyBatisConfig.java
├── interceptor/
│ └── AuthInterceptor.java
├── entity/
│ ├── User.java
│ ├── Item.java
│ ├── Order.java
│ ├── Message.java
│ ├── Review.java
│ └── Category.java
├── dto/
│ ├── LoginRequest.java
│ ├── RegisterRequest.java
│ ├── ChangePasswordRequest.java
│ ├── UserProfileRequest.java
│ ├── ItemPublishRequest.java
│ ├── CreateOrderRequest.java
│ ├── SendMessageRequest.java
│ ├── ReviewRequest.java
│ ├── CategoryRequest.java
│ └── StatusRequest.java
├── vo/
│ ├── UserProfileVO.java
│ ├── ItemVO.java
│ ├── OrderVO.java
│ ├── MessageVO.java
│ ├── ConversationVO.java
│ └── PageResult.java
├── mapper/
│ ├── UserMapper.java
│ ├── ItemMapper.java
│ ├── OrderMapper.java
│ ├── MessageMapper.java
│ ├── ReviewMapper.java
│ └── CategoryMapper.java
├── service/
│ ├── AuthService.java
│ ├── UserService.java
│ ├── ItemService.java
│ ├── OrderService.java
│ ├── MessageService.java
│ └── admin/
│ ├── ReviewService.java
│ ├── UserManageService.java
│ └── CategoryService.java
└── controller/
├── AuthController.java
├── UserController.java
├── ItemController.java
├── OrderController.java
├── MessageController.java
└── admin/
├── ReviewController.java
├── UserManageController.java
└── CategoryController.java