serverDesign.md 19 KB

校园二手书交易系统 — 服务端详细设计文档

版本:1.0
对应设计:design.md 第 4 章


1. 分层架构

space.anyi.server
├── ServerApplication.java          — Spring Boot 入口
├── config/                         — 配置层
│   ├── WebConfig.java              — CORS 跨域配置
│   ├── MyBatisPlusConfig.java      — MyBatis-Plus 分页插件
│   └── GlobalExceptionHandler.java — 全局异常处理
├── controller/                     — Controller 层(REST 接口)
│   ├── UserController.java
│   ├── BookController.java
│   ├── TransactionController.java
│   ├── StatisticsController.java
│   └── AiController.java
├── service/                        — Service 接口层
│   ├── UserService.java
│   ├── BookService.java
│   ├── TransactionService.java
│   ├── StatisticsService.java
│   └── AiService.java
├── service/impl/                   — Service 实现层
│   ├── UserServiceImpl.java
│   ├── BookServiceImpl.java
│   ├── TransactionServiceImpl.java
│   ├── StatisticsServiceImpl.java
│   └── AiServiceImpl.java
└── mapper/                         — Mapper 层(数据访问)
    ├── UserMapper.java
    ├── BookMapper.java
    ├── TransactionRecordMapper.java
    └── StatisticsMapper.java

1.1 各层职责

层次 职责
Config CORS 跨域、MyBatis-Plus 分页拦截器、全局异常处理
Controller @RestController,请求映射、参数解析、调用 Service 并封装 R<T> 响应
Service 接口 业务逻辑抽象,继承 IService 获得通用 CRUD
Service 实现 核心业务逻辑、事务控制(@Transactional)、组合 Mapper 调用
Mapper BaseMapper 扩展提供 CRUD,@Select 自定义 SQL 提供统计查询

1.2 依赖关系

Controller → Service → Mapper → MySQL

所有 API 返回统一响应格式 R<T>(定义在 common 模块)。

1.3 Maven 依赖(server/pom.xml)

依赖 说明
spring-boot-starter-web Spring Boot Web 容器
mybatis-plus-spring-boot3-starter MyBatis-Plus ORM(适配 Spring Boot 3)
mybatis-plus-jsqlparser MyBatis-Plus SQL 解析器(分页插件依赖)
mysql-connector-j MySQL JDBC 驱动
lombok 编译期注解生成 getter/setter
common 项目共享模块(实体、DTO、枚举)

2. 配置层

2.1 WebConfig.java — CORS 跨域

允许所有来源的跨域请求,用于开发环境 JavaFX 客户端直连服务端。

registry.addMapping("/**")
        .allowedOriginPatterns("*")
        .allowedMethods("*")
        .allowedHeaders("*")
        .allowCredentials(true);

2.2 MyBatisPlusConfig.java — 分页插件

注册 PaginationInnerInterceptor(DbType.MYSQL),自动拦截并改写分页 SQL。

配合 application.yml 配置:

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      id-type: auto

2.3 GlobalExceptionHandler.java — 全局异常处理

异常 响应
DuplicateKeyException R.error("用户名已存在") → code=400
IllegalArgumentException R.error(e.getMessage()) → code=400
Exception(兜底) R.error("服务器内部错误: " + e.getMessage()) → code=500

3. 公共模块(common)

3.1 实体类(space.anyi.common.entity

User

字段 类型 说明
userid Integer PK,自增
username String 用户名
password String 密码
nickname String 昵称
email String 邮箱
phone String 电话
dept String 院系
credit Integer 信用分,默认 100
role Integer 角色(0=学生, 1=管理员)
isSeller Integer @TableField("is_seller"),是否为卖家(0=否, 1=是)
createdAt LocalDateTime 注册时间

Book

字段 类型 说明
bookId Integer PK,自增
sellerId Integer 卖家 ID(逻辑外键 → user.userid)
title String 书名
author String 作者
category String 分类
originalPrice BigDecimal 原价
sellingPrice BigDecimal 售价
imageUrl String 图片 URL(预留)
createdAt LocalDateTime 上架时间
stock Integer 库存数量
status Integer @TableField("status"),0=待审核, 1=已上架, 2=已下架

TransactionRecord

字段 类型 说明
transId Integer PK,自增
bookId Integer 书籍 ID(逻辑外键 → book.book_id)
buyerId Integer 买家 ID(逻辑外键 → user.userid)
transactionNum Integer 购买数量
transactionPrice BigDecimal 交易总价(= sellingPrice × quantity)
transactionTime LocalDateTime 交易时间

3.2 DTO 类(space.anyi.common.dto

R<T> — 统一响应

{"code": 200, "message": "success", "data": ...}
静态方法 说明
R.ok(data) 成功,code=200
R.ok() 成功无数据
R.error(message) 错误,code=400
R.error(code, message) 自定义错误码

PageResult<T> — 分页结果

{"records": [...], "total": 100, "size": 20, "current": 1}

LoginRequest

{"username": "string", "password": "string"}

RegisterRequest

{"username": "string", "password": "string", "nickname": "string", "email": "string", "phone": "string", "dept": "string"}

SalesRecordDTO

字段 类型 说明
bookId Integer 书籍ID
title String 书名
buyerId Integer 买家ID
buyerName String 买家昵称(JOIN user 表)
quantity Integer 购买数量
totalPrice BigDecimal 成交金额
transactionTime LocalDateTime 交易时间

StatisticsSummary

字段 类型 说明
totalAmount BigDecimal 交易总金额
totalCount long 交易总笔数

DeptSalesDTO

字段 类型 说明
dept String 院系名称
salesAmount BigDecimal 销售额

HotBookDTO

字段 类型 说明
bookId Integer 书籍ID
title String 书名
author String 作者
totalSold int 总销量

DeptDiscountDTO

字段 类型 说明
dept String 院系名称
avgDiscountRate BigDecimal 平均折扣率(百分比)

3.3 枚举类(space.anyi.common.enums

UserRole

常量 说明
STUDENT 0 学生
ADMIN 1 管理员

提供 fromValue(int) 工厂方法。


4. RESTful API 接口定义

所有接口基路径:/api(通过 server.servlet.context-path 配置)

4.1 用户模块 — /api/user

方法 路径 请求 响应 说明
POST /api/user/login LoginRequest JSON R<User> 登录认证
POST /api/user/register RegisterRequest JSON R<User> 注册新用户
GET /api/user ?page=&size= R<IPage<User>> 分页获取用户列表(Admin)
GET /api/user/{id} R<User> 获取单个用户
PUT /api/user/{id} User JSON R<User> 更新用户信息
DELETE /api/user/{id} R<Void> 删除用户(Admin)

Controller: UserController.java

  • login(): 调用 userService.login(username, password),匹配则返回 User,否则 R.error
  • register(): 创建 User 对象,调用 userService.register(user)
  • listAll(): MyBatis-Plus page() 分页查询
  • getById(): 按主键查询,不存在则返回错误
  • update(): 设置 userId 后 updateById(),再返回最新数据
  • delete(): removeById() 物理删除

4.2 书籍模块 — /api/book

方法 路径 参数 响应 说明
GET /api/book keyword, category, page, size R<PageResult<Book>> 搜索书籍(仅返回 status=1)
GET /api/book/all R<List<Book>> 获取所有书籍(不分页)
GET /api/book/{id} R<Book> 获取书籍详情
GET /api/book/my sellerId R<List<Book>> 卖家查看自己的书籍
GET /api/book/{id}/sales R<List<SalesRecordDTO>> 查看书籍销售记录(卖家)
GET /api/book/pending R<List<Book>> 查看待审核书籍列表(Admin)
POST /api/book Book JSON R<Book> 创建/上架书籍(默认 status=0)
PUT /api/book/{id} Book JSON R<Book> 更新书籍
PUT /api/book/{id}/audit ?status= R<Book> 审核书籍(Admin:0 待审/1 通过/2 拒绝)
DELETE /api/book/{id} R<Void> 删除书籍

Controller: BookController.java

  • search(): 调用 bookService.searchBooks(),返回 PageResult<Book>
  • listAll(): 返回所有书籍(按创建时间降序)
  • getById(): 按主键查询
  • getMyBooks(): 按 sellerId 查询
  • getSales(): JOIN 查询销售记录,返回 List<SalesRecordDTO>
  • getPendingBooks(): 查询 status=0 的书籍
  • create(): 设置 createdAt=now, status=0,保存后返回
  • update(): 设置 bookId 后 updateById()
  • audit(): 查询书籍 → 设置新 status → 更新
  • delete(): removeById() 物理删除

Service 方法: BookServiceImpl.java

方法 说明
searchBooks(keyword, category, page, size) 动态条件:keyword 模糊匹配 title 或 author;category 精确匹配;仅返回 status=1;按 created_at DESC 排序
getBySellerId(sellerId) 按卖家 ID 查询,按创建时间降序
listAll() 返回所有书籍,按创建时间降序
getSalesByBookId(bookId) 委托 bookMapper.selectSalesByBookId() JOIN 查询
getPendingBooks() 查询 status=0 的书籍

4.3 交易模块 — /api/transaction

方法 路径 请求 响应 说明
POST /api/transaction {bookId, buyerId, quantity} R<TransactionRecord> 购买(需事务)
GET /api/transaction ?page=&size= R<IPage<TransactionRecord>> 分页查询所有交易
GET /api/transaction/{id} R<TransactionRecord> 交易详情
GET /api/transaction/user/{userId} R<List<TransactionRecord>> 用户交易记录
PUT /api/transaction/{id} TransactionRecord JSON R<TransactionRecord> 更新交易(预留)
DELETE /api/transaction/{id} R<Void> 删除交易

Controller: TransactionController.java

  • purchase(): 从 Map 中提取 bookId/buyerId/quantity,调用 transactionService.purchase(),捕获运行时异常返回错误
  • listAll(): MyBatis-Plus 分页查询
  • getById(): 按主键查询
  • getByUser(): 调用 transactionService.getByUserId()
  • update()/delete(): MyBatis-Plus 通用操作

4.4 统计模块 — /api/statistics

方法 路径 响应 说明
GET /api/statistics/summary R<StatisticsSummary> 交易总金额和总笔数
GET /api/statistics/top-departments R<List<DeptSalesDTO>> 销售额前三的院系
GET /api/statistics/top-books R<List<HotBookDTO>> 销量前五的书籍
GET /api/statistics/discount-rates R<List<DeptDiscountDTO>> 各院系平均折扣率

Controller: StatisticsController.java — 直接委托 StatisticsService 的四个方法。

4.5 AI 分析模块 — /api/ai

方法 路径 响应 说明
GET /api/ai/analysis R<String> 触发服务端固定分析流程,返回文本分析报告

Controller: AiController.java — 调用 aiService.generateAnalysis()

Service 实现: AiServiceImpl.java

流程:

  1. 调用 StatisticsService 获取全部统计指标(summary、topDepartments、topBooks、discountRates)
  2. 格式化拼接为纯文本分析报告
  3. 报告包含:交易总览、热门院系 Top 3、热门书籍 Top 5、各院系折扣率、分析结论

5. 核心业务逻辑

5.1 注册流程

  1. Controller 接收 RegisterRequest
  2. 构造 User 对象,设置默认值:credit=100, role=0, isSeller=0, createdAt=now
  3. 调用 userService.register()save(user)
  4. 返回 R.ok(user)(含自增 ID)

服务端代码位置: UserServiceImpl.register()

5.2 登录流程

  1. Controller 接收 LoginRequest
  2. 调用 userService.login(username, password)
  3. 构建 LambdaQueryWrappereq(username) AND eq(password)
  4. 匹配则返回 R.ok(user),否则返回 R.error("Invalid username or password")

服务端代码位置: UserServiceImpl.login()

5.3 购买流程(事务)

@Transactional 开启事务
  1. bookService.getById(bookId) → 查询书籍
  2. 校验 stock >= quantity,否则抛异常回滚
  3. book.setStock(stock - quantity) → bookService.updateById()
  4. 创建 TransactionRecord:
     - bookId, buyerId, transactionNum
     - transactionPrice = sellingPrice × quantity
     - transactionTime = now
  5. save(record)
提交事务

事务边界: TransactionServiceImpl.purchase() 方法标注 @Transactional

5.4 搜索书籍流程

  1. Controller 接收 keyword、category、page、size
  2. 构建 LambdaQueryWrapper<Book>
    • 固定条件:eq(Book::getStatus, 1)(仅返回已上架书籍)
    • keyword 非空 → like(title, keyword) OR like(author, keyword)
    • category 非空 → eq(category)
    • 排序:orderByDesc(createdAt)
  3. 调用 page(new Page<>(page, size), wrapper) 分页查询
  4. 转换为 PageResult<Book> 返回

服务端代码位置: BookServiceImpl.searchBooks()


6. 数据访问层

6.1 Mapper 结构

Mapper 父类 自定义方法 SQL 类型
UserMapper BaseMapper<User>
BookMapper BaseMapper<Book> selectSalesByBookId() @Select JOIN 查询
TransactionRecordMapper BaseMapper<TransactionRecord>
StatisticsMapper 无(不是实体 Mapper) 4 个统计方法 全部 @Select 自定义 SQL

6.2 自定义 SQL

BookMapper.selectSalesByBookId()

SELECT b.book_id, b.title, t.buyer_id, u.nickname AS buyerName,
       t.transaction_num AS quantity, t.transaction_price AS totalPrice,
       t.transaction_time
FROM transaction_record t
JOIN book b ON t.book_id = b.book_id
JOIN user u ON t.buyer_id = u.userid
WHERE b.book_id = #{bookId}
ORDER BY t.transaction_time DESC

StatisticsMapper

方法 SQL
selectSummary() SELECT COALESCE(SUM(transaction_price), 0), COUNT(*) FROM transaction_record
selectTopDepartments() JOIN transaction_record + book + user,按 user.dept 分组,销售额前 3
selectTopBooks() JOIN transaction_record + book,按 book 分组,销量前 5
selectDeptDiscountRates() AVG((original_price - unit_price) / original_price * 100),按 dept 分组

7. 统一响应与分页

7.1 响应格式

所有 API 返回统一 JSON:

{
  "code": 200,
  "message": "success",
  "data": { ... }
}
code 含义
200 成功
400 业务错误(参数错误、库存不足、用户名已存在等)
500 服务器内部错误

7.2 分页机制

Client → GET /api/book?keyword=&category=&page=1&size=20
Server → MyBatis-Plus Page + LambdaQueryWrapper
       → PaginationInnerInterceptor 自动拦截改写 SQL
       → 返回 IPage<Book> → 转换为 PageResult<Book>
Client → FXCollections.observableArrayList(records) → TableView

分页查询步骤:

  1. Controller 接收 page 和 size 参数(默认 page=1, size=20)
  2. 构建 Page<T> 对象 + LambdaQueryWrapper 条件
  3. MyBatis-Plus 分页插件自动添加 LIMITCOUNT 子句
  4. 返回 IPage<T>,包含 records、total、size、current

8. 运行配置

8.1 application.yml

server:
  port: 8080
  servlet:
    context-path: /api

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/campus_book_trade?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      id-type: auto

8.2 启动入口

ServerApplication.java

  • @SpringBootApplication + @MapperScan("space.anyi.server.mapper")
  • 运行 main() 监听 8080 端口

8.3 构建与运行

# 全量构建
JAVA_HOME=/home/yangyi/.jdks/corretto-23.0.2 mvn clean install

# 单独启动服务端
mvn spring-boot:run -pl server

9. 已实现功能清单与代码位置

9.1 基础功能

功能 Controller Service 实现 Mapper
登录 UserController.java:22 UserServiceImpl.java:15 UserMapper (BaseMapper)
注册 UserController.java:31 UserServiceImpl.java:22 UserMapper (BaseMapper)
用户 CRUD UserController.java:44-70 ServiceImpl 继承 UserMapper (BaseMapper)
书籍搜索 BookController.java:22 BookServiceImpl.java:24 BookMapper (BaseMapper)
书籍 CRUD BookController.java:38-98 ServiceImpl 继承 BookMapper (BaseMapper)
购买交易 TransactionController.java:22 TransactionServiceImpl.java:26 TransactionRecordMapper + BookMapper
交易 CRUD TransactionController.java:35-66 ServiceImpl 继承 TransactionRecordMapper (BaseMapper)
统计数据 StatisticsController.java StatisticsServiceImpl.java StatisticsMapper.java(4 个 @Select

9.2 扩展功能

功能 Controller Service 实现 Mapper
销售记录 BookController.java:56 BookServiceImpl.java:52 BookMapper.selectSalesByBookId()
待审核书籍 BookController.java:61 BookServiceImpl.java:57 BookMapper (BaseMapper)
审核书籍 BookController.java:66 —(直接在 Controller 中操作) BookMapper (BaseMapper)
AI 分析 AiController.java AiServiceImpl.java —(依赖 StatisticsService)

10. 设计补充说明

10.1 数据库约束

  • 未显式声明外键约束,由应用层保证数据一致性
  • transaction_recordbookprice 字段使用 decimal(10,2) 确保精度

10.2 安全性

  • 当前无 Token/JWT 机制,userId 通过请求体或参数明文传输
  • 密码明文存储,后续建议引入加密和 Spring Security

10.3 待扩展

  • 分页返回类型不一致:用户模块使用 IPage<User>,书籍模块使用 PageResult<Book>,建议统一
  • @Transactional 仅应用于购买流程,其他批量操作未加事务
  • 服务端未实现权限拦截,所有 API 均可被任意客户端调用