校园内二手教材流转需求旺盛,但缺乏统一的线上交易平台。本项目旨在构建一个 C/S 架构的校园二手书交易系统,为学生提供便捷的书籍买卖渠道,为管理员提供数据监控与统计能力。
本系统面向高校在校学生与管理人员,部署于校园局域网环境。
| 术语 | 说明 |
|---|---|
| Buyer | 买家/普通学生用户 |
| Seller | 卖家(买家身份的权限扩展) |
| Admin | 系统管理员 |
| C/S | Client/Server 架构 |
| MyBatis-Plus | MyBatis 增强工具 |
| LLM | Large Language Model,大语言模型 |
本系统采用经典 C/S 两层架构:
flowchart TB
subgraph Client [JavaFX 客户端 space.anyi.client]
VIEW[FXML 视图层]
CTRL[Controller 层]
HSVC[HTTP Service 层]
end
subgraph Server [Spring Boot 服务端 space.anyi.server]
CTL[Controller 层]
SVC[Service 层]
MPR[Mapper 层]
end
subgraph DB [MySQL 8.0]
D[(campus_book_trade)]
end
Client <-->|HTTP/JSON RESTful API| Server
MPR <--> D
客户端与服务端之间仅通过 HTTP RESTful API 通信,无直接依赖。共享模块 common 提供实体类、DTO 和工具类。
| 层次 | 技术 | 版本 |
|---|---|---|
| 客户端 UI | JavaFX + FXML + Scene Builder | 23.0.2 |
| 客户端 HTTP | java.net.http.HttpClient |
JDK 内置 |
| JSON 序列化 | Jackson (jackson-databind + jackson-datatype-jsr310) |
2.17.3 |
| 服务端框架 | Spring Boot Web | 3.4.4 |
| ORM | MyBatis-Plus (mybatis-plus-spring-boot3-starter) |
3.5.9 |
| 数据库 | MySQL 8.0+ (Connector 8.4.0) | |
| 构建工具 | Maven (3.9+) | |
| Java | Corretto-23 | 23 |
CampusUsedBookTradingSystem/
├── common/ 共享模块 — 实体、DTO、枚举
├── server/ Spring Boot 服务端
│ ├── config/ 配置类(CORS、MyBatis-Plus、异常处理)
│ ├── controller/ REST 控制器
│ ├── service/ 业务接口
│ ├── service/impl/ 业务实现
│ └── mapper/ MyBatis-Plus Mapper + XML
├── client/ JavaFX 客户端
│ ├── controller/ JavaFX 控制器
│ ├── service/ HTTP 调用封装
│ └── util/ 工具类
└── sql/ 数据库建表脚本
依赖关系:server → common,client → common。
space.anyi
├── common
│ ├── entity/ User, Book, TransactionRecord
│ ├── dto/ LoginRequest, RegisterRequest, R<T>, PageResult<T>,
│ │ StatisticsSummary, DeptSalesDTO, DeptDiscountDTO, HotBookDTO
│ └── enums/ UserRole
├── server
│ ├── ServerApplication.java
│ ├── config/ WebConfig, MyBatisPlusConfig, GlobalExceptionHandler
│ ├── controller/ UserController, BookController, TransactionController, StatisticsController
│ ├── service/ UserService, BookService, TransactionService, StatisticsService
│ ├── service/impl/ UserServiceImpl, BookServiceImpl, TransactionServiceImpl, StatisticsServiceImpl
│ └── mapper/ UserMapper, BookMapper, TransactionRecordMapper, StatisticsMapper
└── client
├── App.java, Client.java
├── controller/ LoginController, RegisterController, MainController
├── service/ UserService, BookService, TransactionService, StatisticsService
└── util/ HttpClientUtil, LoginUserHolder
erDiagram
USER {
int userid PK
varchar username
varchar password
varchar nickname
varchar email
varchar phone
varchar dept
int credit
int role
int is_seller
datetime created_at
}
BOOK {
int book_id PK
int seller_id FK
varchar title
varchar author
varchar category
decimal original_price
decimal selling_price
varchar image_url
datetime created_at
int stock
}
TRANSACTION_RECORD {
int trans_id PK
int book_id FK
int buyer_id FK
int transaction_num
decimal transaction_price
datetime transaction_time
}
USER ||--o{ BOOK : "seller"
USER ||--o{ TRANSACTION_RECORD : "buyer"
BOOK ||--o{ TRANSACTION_RECORD : "contains"
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| userid | int | PK, AUTO_INCREMENT | 用户ID |
| username | varchar(255) | 用户名 | |
| password | varchar(255) | 密码(明文存储,后续改进) | |
| nickname | varchar(255) | 昵称 | |
| varchar(255) | 邮箱 | ||
| phone | varchar(255) | 电话 | |
| dept | varchar(255) | 院系 | |
| credit | int | DEFAULT 100 | 信用分 |
| role | int | DEFAULT 0 | 角色(0=学生, 1=管理员) |
| is_seller | int | DEFAULT 0 | 是否为卖家(0=否, 1=是) |
| created_at | datetime | 注册时间 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| book_id | int | PK, AUTO_INCREMENT | 书籍ID |
| seller_id | int | 卖家ID(逻辑外键→user.userid) | |
| title | varchar(255) | 书名 | |
| author | varchar(255) | 作者 | |
| category | varchar(255) | 分类 | |
| original_price | decimal(10,2) | 原价 | |
| selling_price | decimal(10,2) | 售价 | |
| image_url | varchar(255) | 图片URL(预留) | |
| created_at | datetime | 上架时间 | |
| stock | int | 库存数量 |
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| trans_id | int | PK, AUTO_INCREMENT | 交易ID |
| book_id | int | 书籍ID(逻辑外键→book.book_id) | |
| buyer_id | int | 买家ID(逻辑外键→user.userid) | |
| transaction_num | int | 购买数量 | |
| transaction_price | decimal(10,2) | 交易总价 | |
| transaction_time | datetime | 交易时间 |
book.seller_id → user.userid:一个卖家可发布多本书(一对多)transaction_record.book_id → book.book_id:一本书可被多次交易(一对多)transaction_record.buyer_id → user.userid:一个买家可有多笔交易(一对多)当前 DDL 中未显式声明外键约束,由应用层(事务)保证数据一致性。
建库脚本位于 sql/campus_book_trade.sql,包含完整 CREATE TABLE 和示例初始数据。字符集采用 utf8mb4。
flowchart LR
subgraph Controller [Controller 层]
direction LR
UC[UserController]
BC[BookController]
TC[TransactionController]
SC[StatisticsController]
end
subgraph Service [Service 层]
direction LR
US[UserService]
BS[BookService]
TS[TransactionService]
SS[StatisticsService]
end
subgraph Mapper [Mapper 层]
direction LR
UM[UserMapper]
BM[BookMapper]
TM[TransactionRecordMapper]
SM[StatisticsMapper]
end
DB[(MySQL)]
Controller --> Service
Service --> Mapper
Mapper --> DB
各层职责:
| 层次 | 职责 | |||
|---|---|---|---|---|
| Controller | 请求映射、参数解析校验、调用 Service、封装 R 响应 | |||
| Service | 核心业务逻辑、事务控制、组合操作 | |||
| Mapper | 数据访问(继承 BaseMapper 提供 CRUD,自定义 SQL 提供统计) | |||
| Config | CORS 跨域配置、MyBatis-Plus 分页插件、全局异常处理 |
| 方法 | 路径 | 请求体/参数 | 响应 | 说明 |
|---|---|---|---|---|
| POST | /api/user/login |
LoginRequest |
R<User> |
登录认证 |
| POST | /api/user/register |
RegisterRequest |
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) |
LoginRequest:
{"username": "string", "password": "string"}
RegisterRequest:
{"username": "string", "password": "string", "nickname": "string", "email": "string", "phone": "string", "dept": "string"}
/api/book| 方法 | 路径 | 参数 | 响应 | 说明 |
|---|---|---|---|---|
| GET | /api/book |
keyword, category, page, size | R<PageResult<Book>> |
搜索书籍(分页) |
| 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> |
删除书籍 |
/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> |
删除交易 |
/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>> |
各院系平均折扣率 |
/api/ai| 方法 | 路径 | 响应 | 说明 |
|---|---|---|---|
| GET | /api/ai/analysis |
R<String> |
触发服务端固定分析流程,返回文本分析报告 |
所有 API 返回统一结构:
{
"code": 200,
"message": "success",
"data": { ... }
}
| code | 含义 |
|---|---|
| 200 | 成功 |
| 400 | 业务错误(参数错误、库存不足等) |
| 500 | 服务器内部错误 |
sequenceDiagram
participant Client as JavaFX 客户端
participant Server as Spring Boot 服务端
participant DB as MySQL
Client->>Server: GET /api/book?keyword=&category=&page=1&size=20
Server->>DB: MyBatis-Plus Page + LambdaQueryWrapper
DB-->>Server: 分页数据 + 总数
Server->>Server: 转换为 PageResult<Book>
Server-->>Client: R<PageResult<Book>>
Client->>Client: FXCollections.observableArrayList(records) → TableView
服务端使用 MyBatis-Plus IPage 分页,通过 PageResult<T> DTO 传输给客户端。MyBatis-Plus PaginationInnerInterceptor 自动拦截并改写分页 SQL。
允许跨域访问(开发环境 JavaFX 直连服务端,无代理层)。
PaginationInnerInterceptor(DbType.MYSQL)map-underscore-to-camel-case: trueid-type: auto(数据库自增)| 异常 | 响应 |
|---|---|
DuplicateKeyException |
R.error("用户名已存在") |
IllegalArgumentException |
R.error(异常消息) |
Exception(兜底) |
R.error("服务器内部错误: " + e.getMessage()) |
sequenceDiagram
participant Client as JavaFX 客户端
participant Server as 服务端
participant DB as MySQL
Client->>Server: POST /api/user/register (RegisterRequest)
Server->>Server: 创建 User 对象
Note over Server: 设置默认值: <br/>credit=100, role=0, <br/>isSeller=0, createdAt=now
Server->>DB: INSERT INTO user
DB-->>Server: User (含自增ID)
Server-->>Client: R.ok(user)
sequenceDiagram
participant Client as JavaFX 客户端
participant Server as 服务端
participant DB as MySQL
Client->>Server: POST /api/user/login (LoginRequest)
Server->>DB: SELECT * FROM user WHERE username=? AND password=?
DB-->>Server: User / null
alt user == null
Server-->>Client: R.error("Invalid username or password")
else
Server-->>Client: R.ok(user)
Client->>Client: LoginUserHolder.set(user)
Client->>Client: App.showMainScene()
end
sequenceDiagram
participant Client as JavaFX 客户端
participant Server as 服务端
participant DB as MySQL
Client->>Server: POST /api/transaction {bookId, buyerId, quantity}
Server->>Server: @Transactional 开启事务
Server->>DB: SELECT * FROM book WHERE book_id=?
DB-->>Server: {stock=N, sellingPrice=P}
alt stock < quantity
Server-->>Client: R.error("Insufficient stock")
Server->>Server: 回滚事务
else
Server->>DB: UPDATE book SET stock = stock - quantity
Server->>DB: INSERT INTO transaction_record (bookId, buyerId, transactionNum, transactionPrice, transactionTime)
Server->>Server: 提交事务
Server-->>Client: R.ok(transactionRecord)
end
事务边界(TransactionServiceImpl.purchase()):
book.stock -= quantitysellingPrice × quantity)@Transactional 回滚1. 客户端组装参数:keyword + category + page + size
2. GET /api/book?keyword=&category=&page=&size=
3. 服务端构建动态查询条件:
- keyword → LIKE title OR LIKE author
- category → EQ category
- 按 created_at DESC 排序
4. MyBatis-Plus 分页查询 + 返回 PageResult<Book>
统计模块全部使用自定义 SQL 在 StatisticsMapper 中通过 @Select 注解实现:
| 统计项 | SQL 概要 |
|---|---|
| summary | SELECT COALESCE(SUM(transaction_price),0), COUNT(*) FROM transaction_record |
| top-departments | JOIN transaction_record + book + user,按 user.dept 分组,销售额前3 |
| top-books | JOIN transaction_record + book,按 book 分组,销量前5 |
| discount-rates | AVG((original_price - unit_price) / original_price * 100),按 dept 分组 |
flowchart LR
A[login.fxml] -->|登录成功| C[main.fxml]
A -->|点击注册链接| B[register.fxml]
B -->|注册成功| A
B -->|返回登录| A
C -->|退出登录| A
App.java 作为 JavaFX Application 入口,通过静态方法实现场景切换。Client.java 为启动入口(Application.launch(App.class, args))。
VBox (400×350)
├── Label "校园书市"(标题,22px bold)
├── TextField #usernameField(用户名输入)
├── PasswordField #passwordField(密码输入)
├── Button "登录" → #handleLogin(btn-primary)
└── Hyperlink "没有账号?立即注册" → #handleRegister
VBox (400×500)
├── Label "创建账号"(标题,20px bold)
├── TextField #usernameField(用户名 *)
├── PasswordField #passwordField(密码 *)
├── TextField #nicknameField(昵称)
├── TextField #emailField(邮箱)
├── TextField #phoneField(电话)
├── TextField #deptField(院系)
├── Button "注册" → #handleRegister(btn-primary)
└── Hyperlink "已有账号?去登录" → #handleBack
BorderPane (1000×700)
├── Top: HBox 顶部标题栏
│ ├── Label #welcomeLabel "欢迎,xxx"
│ └── Button "退出登录" → #handleLogout(btn-danger)
│
└── Center: TabPane(6个Tab)
│
├── Tab "浏览书籍"(买家/卖家共有的 browse tab)
│ ├── HBox 搜索栏: TextField #searchField + ComboBox #categoryCombo + Button "搜索"
│ └── SplitPane (0.6)
│ ├── TableView #bookTable [编号,书名,作者,分类,价格,库存]
│ └── VBox: 详情 Label 组 + TextField #quantityField + Button "购买"
│
├── Tab "我的交易"(买家/卖家)
│ ├── Button "刷新" → #handleLoadTransactions
│ └── TableView #transTable [交易编号,书籍编号,数量,总价,时间]
│
├── Tab "个人信息"(买家/卖家)
│ └── VBox: #profileUsername(只读), #profileNickname, #profileEmail, #profilePhone, #profileDept, #profileCredit(只读) + Button "保存修改"
│
├── Tab "卖家中心"(isSeller=1 启用)
│ ├── Label "我的书籍"
│ ├── Button "刷新" → #handleLoadMyBooks
│ ├── TableView #myBookTable [编号,书名,价格,库存]
│ ├── Button "删除选中" → #handleDeleteBook
│ └── TitledPane "添加新书"
│ └── GridPane: 书名/作者/分类/原价/售价/库存 输入 + Button "添加书籍"
│
├── Tab "数据统计"(所有角色可见)
│ └── ScrollPane > VBox
│ ├── Button "刷新数据" → #handleLoadStatistics
│ ├── TitledPane "总览": statTotalAmount + statTotalCount
│ ├── TitledPane "热门院系(销量前三)": TableView #deptTable
│ ├── TitledPane "热门书籍(前五)": TableView #hotBookTable
│ └── TitledPane "各院系平均折扣率": TableView #discountTable
│
└── Tab "管理"(role=1 启用)
├── Label "用户管理"
├── Button "刷新用户" → #handleLoadAllUsers
├── TableView #userTable [编号,用户名,昵称,院系,信用分,角色,卖家]
├── Button "删除选中用户" → #handleDeleteUser
├── Label "全部交易"
├── Button "刷新交易" → #handleLoadAllTransactions
├── TableView #allTransTable [交易编号,书籍编号,买家编号,数量,价格,时间]
└── Button "删除选中交易" → #handleDeleteTransaction
| 字段/方法 | 类型 | 说明 |
|---|---|---|
| usernameField | TextField | 用户名输入框 |
| passwordField | PasswordField | 密码输入框 |
| userService | UserService | HTTP 服务层 |
| handleLogin() | void | 校验非空 → 调用登录 API → 存入会话 → 跳转主界面 |
| handleRegister() | void | 跳转到注册界面 |
| 字段/方法 | 类型 | 说明 |
|---|---|---|
| usernameField ~ deptField | TextField / PasswordField | 注册表单字段 |
| userService | UserService | HTTP 服务层 |
| handleRegister() | void | 校验必填 → 调用注册 API → 提示 → 跳回登录 |
| handleBack() | void | 返回登录界面 |
| Tab | 核心方法 | 说明 |
|---|---|---|
| 浏览书籍 | handleSearch() |
搜索 + 刷新列表 |
handlePurchase() |
校验 → 购买 → 刷新 | |
initialize() |
绑定表格列、设置角色权限、加载数据 | |
| 我的交易 | handleLoadTransactions() |
加载当前用户交易记录 |
| 个人信息 | handleUpdateProfile() |
保存修改 + 更新会话 |
| 卖家中心 | handleLoadMyBooks() |
加载卖家自己的书籍 |
handleAddBook() |
上架新书 | |
handleDeleteBook() |
删除选中书籍 | |
| 数据统计 | handleLoadStatistics() |
加载4个统计指标 |
| 管理 | handleLoadAllUsers() |
加载所有用户 |
handleDeleteUser() |
删除选中用户 | |
handleLoadAllTransactions() |
加载所有交易 | |
handleDeleteTransaction() |
删除选中交易 |
会话初始化(initialize() 方法):
LoginUserHolder 获取当前用户isSeller 和 role 控制 sellerTab 和 adminTab 的启用状态单例模式的 HttpClient + Jackson ObjectMapper,提供泛型方法:
| 方法 | 说明 |
|---|---|
get(path, Class<T>) |
GET 请求,简单类型反序列化 |
get(path, TypeReference) |
GET 请求,泛型类型反序列化 |
post(path, body, Class<T>) |
POST 请求 |
put(path, body, Class<T>) |
PUT 请求 |
delete(path, Class<T>) |
DELETE 请求 |
基 URL = http://localhost:8080,错误时返回 code=500。
| Service | 封装方法 | 对应 API |
|---|---|---|
| UserService | login, register, getById, update, delete | /api/user/* |
| BookService | search, getById, getMyBooks, listAll, create, update, delete | /api/book/* |
| TransactionService | purchase, getById, getByUser, update, delete | /api/transaction/* |
| StatisticsService | getSummary, getTopDepartments, getTopBooks, getDiscountRates | /api/statistics/* |
LoginUserHolder:基于内存的静态会话管理。
LoginUserHolder.set(user); // 登录成功后存入
LoginUserHolder.get(); // 获取当前用户
LoginUserHolder.isSeller(); // 判断是否为卖家
LoginUserHolder.clear(); // 退出时清除
当前无 Token/JWT 机制,userId 通过请求参数/体明文传输。
引入 org.kordamp.bootstrapfx:bootstrapfx-core:0.4.0,在场景加载时添加 bootstrapfx.css。额外定制 css/style.css:全局字体、按钮手型光标、表格边框圆角。
用户角色由两个字段组合定义:
| role | isSeller | 角色 |
|---|---|---|
| 0 | 0 | 普通学生(买家) |
| 0 | 1 | 卖家(同时具备买家权限) |
| 1 | 0/1 | 管理员 |
UserRole.java 枚举仅定义 STUDENT(0) 和 ADMIN(1),卖家用 isSeller 标志区分。
| 功能模块 | 功能点 | 买家 | 卖家 | 管理员 |
|---|---|---|---|---|
| 账号 | 注册 | ✅ | ✅ | ✅ |
| 账号 | 登录 | ✅ | ✅ | ✅ |
| 书籍 | 浏览/搜索 | ✅ | ✅ | ✅ |
| 书籍 | 查看详情 | ✅ | ✅ | ✅ |
| 书籍 | 购买 | ✅ | ✅ | ❌ |
| 书籍 | 上架 | ❌ | ✅ | ✅ |
| 书籍 | 编辑 | ❌ | ✅ | ✅ |
| 书籍 | 下架/删除 | ❌ | ✅ | ✅ |
| 书籍 | 查看自己的发布 | ❌ | ✅ | ✅ |
| 书籍 | 审核(待实现) | ❌ | ❌ | ✅ |
| 订单 | 查看自己的订单 | ✅ | ✅ | ✅ |
| 订单 | 查看全部订单 | ❌ | ❌ | ✅ |
| 订单 | 删除订单 | ❌ | ❌ | ✅ |
| 用户 | CRUD | ❌ | ❌ | ✅ |
| 用户 | 重置密码 | ❌ | ❌ | ✅ |
| 统计 | 查看统计数据 | ✅ | ✅ | ✅ |
| AI | 分析报告(待实现) | ❌ | ❌ | ✅ |
在 MainController.initialize() 中:
if (user.getIsSeller() != 1) sellerTab.setDisable(true); // 非卖家禁用卖家中心
if (user.getRole() != 1) adminTab.setDisable(true); // 非管理员禁用管理Tab
Tab 被禁用后用户无法点击,但 FXML 元素仍保留。
当前服务端未实现权限拦截,所有 API 均可被任意客户端调用。生产环境建议引入 Spring Security 或自定义拦截器。
sequenceDiagram
actor User as 用户
participant Login as 登录界面
participant Reg as 注册界面
participant Main as 主界面
participant Svc as 客户端Service
participant API as 服务端API
participant DB as 数据库
Note over User,DB: === 登录流程 ===
User->>Login: 输入用户名+密码
Login->>Svc: userService.login(username, password)
Svc->>API: POST /api/user/login
API->>DB: SELECT * FROM user WHERE username=? AND password=?
DB-->>API: User
API-->>Svc: R.ok(user)
Svc-->>Login: R<User>
Login->>Login: LoginUserHolder.set(user)
Login->>Main: App.showMainScene()
Note over User,DB: === 注册流程 ===
User->>Reg: 填写表单
Reg->>Svc: userService.register(RegisterRequest)
Svc->>API: POST /api/user/register
API->>API: 设置默认值 (credit=100,role=0,isSeller=0)
API->>DB: INSERT INTO user
DB-->>API: User
API-->>Svc: R.ok(user)
Svc-->>Reg: 注册成功
Reg->>Login: App.showLoginScene()
sequenceDiagram
actor Buyer as 买家
participant Browse as 浏览Tab
participant Svc as TransactionService
participant API as 服务端
participant DB as 数据库
Buyer->>Browse: 选择书籍 → 输入数量 → 点击购买
Browse->>Svc: purchase(bookId, buyerId, quantity)
Svc->>API: POST /api/transaction
API->>API: @Transactional 开启
API->>DB: SELECT stock FROM book WHERE book_id=?
DB-->>API: stock=N
alt N < quantity
API-->>Svc: R.error("Insufficient stock")
Svc-->>Browse: Alert 警告
else
API->>DB: UPDATE book SET stock = stock - quantity
API->>DB: INSERT INTO transaction_record
API->>API: 提交事务
API-->>Svc: R.ok(record)
Svc-->>Browse: Alert 成功 + 刷新列表
end
sequenceDiagram
actor User as 用户
participant Browse as 浏览Tab
participant Svc as BookService
participant API as 服务端
participant DB as 数据库
User->>Browse: 输入关键字 / 选择分类
User->>Browse: 点击"搜索"
Browse->>Svc: search(keyword, category, page, size)
Svc->>API: GET /api/book?keyword=&category=
API->>API: LambdaQueryWrapper 动态条件
API->>DB: SELECT ... WHERE title LIKE ? OR author LIKE ? AND category=? ORDER BY created_at DESC
DB-->>API: 分页结果
API-->>Svc: R.ok(PageResult<Book>)
Svc-->>Browse: 数据列表
Browse->>Browse: bookTable.setItems(...)
以下列出所有基于 demand.md 需求已实现的功能及其对应代码位置:
| 组件 | 说明 |
|---|---|
| FXML | 卖家中心 Tab 新增"编辑选中"按钮 (main.fxml:92) |
| Controller | handleEditBook() — 选中书籍回填表单,切换为编辑模式 (MainController.java) |
| 复用 | handleAddBook() — 检测 editingBook 字段,区分新增/更新逻辑 |
| 重置 | clearBookForm() — 编辑完成后重置表单状态 |
| 组件 | 说明 |
|---|---|
| API | GET /api/book/{id}/sales — 返回 List<SalesRecordDTO> (BookController.java:56) |
| DTO | SalesRecordDTO — bookId, title, buyerId, buyerName, quantity, totalPrice, transactionTime |
| Mapper | BookMapper.selectSalesByBookId() — JOIN transaction_record + book + user |
| 客户端 | handleViewSales() — 弹出 Dialog 展示销售记录表格 (MainController.java) |
| 组件 | 说明 |
|---|---|
| 编辑用户 | handleEditUser() — 选中用户回填表单,支持修改 (MainController.java) |
| 添加用户 | handleSaveUser() — 创建用户(调用 Register API) |
| 重置密码 | handleResetPassword() — 密码重置为 123456 |
| FXML | 用户表单 TitledPane (main.fxml) |
| 组件 | 说明 |
|---|---|
| TableView | adminBookTable — 显示所有书籍(编号、书名、作者、分类、售价、库存、卖家ID) |
| 刷新 | handleLoadAllBooksAdmin() — 调用 GET /api/book/all |
| 删除 | handleDeleteBookAdmin() — 删除选中书籍 |
| 组件 | 说明 |
|---|---|
| Schema | book 表增加 status 字段: 0=待审核, 1=已上架, 2=已下架 |
| Entity | Book.java 增加 @TableField("status") private Integer status; |
| API | GET /api/book/pending — 获取待审核书籍 |
| API | PUT /api/book/{id}/audit?status= — 审核通过/拒绝 |
| 搜索过滤 | searchBooks() 仅返回 status=1 的书籍 |
| 新书上架 | create() 默认 status=0(待审核) |
| 客户端 | pendingBookTable + "通过"/"拒绝" 按钮 |
| 组件 | 说明 |
|---|---|
| Service | AiServiceImpl.generateAnalysis() — 汇总统计数据 → 格式化分析报告 |
| Controller | GET /api/ai/analysis — 返回 R<String> 文本报告 |
| 客户端 | AiService — HTTP 调用封装 |
| UI | 管理 Tab 增加"AI 分析"区域: "生成分析报告"按钮 + TextArea 展示区 |
| 后续 | LLM 接入方案(API Key、模型选择、固定提示词模板)待补充 |
| 组件 | 说明 |
|---|---|
| UI | 管理 Tab 用户管理区增加"修改信用分"按钮 |
| 交互 | TextInputDialog 输入新分值 → 调用 PUT /api/user/{id} 更新 |
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
# 全量构建(需 JDK 23)
JAVA_HOME=/path/to/corretto-23 mvn clean install
# 运行服务端
mvn spring-boot:run -pl server
# 运行客户端
mvn javafx:run -pl client