Java道经 - 项目 - MyItem - 项目简介
文章目录
- [S01. 项目简介](#S01. 项目简介)
-
- [E01. 基本介绍](#E01. 基本介绍)
-
- [1. 用户权限](#1. 用户权限)
- [2. 技术选型](#2. 技术选型)
- [E02. 通用环境](#E02. 通用环境)
-
- [1. MySQL数据库](#1. MySQL数据库)
- [2. MinIO文件数据库](#2. MinIO文件数据库)
- [3. Redis缓存数据库](#3. Redis缓存数据库)
- [4. ELK日志收集系统](#4. ELK日志收集系统)
- [5. Nginx代理服务器](#5. Nginx代理服务器)
- [E03. 数据信息](#E03. 数据信息)
-
- [1. 用户表user](#1. 用户表user)
- [2. 物品分类表item_category](#2. 物品分类表item_category)
- [3. 房间表room](#3. 房间表room)
- [4. 位置表location](#4. 位置表location)
- [5. 物品表item](#5. 物品表item)
- [S02. 项目搭建](#S02. 项目搭建)
-
- [E01. 开发全局通用类](#E01. 开发全局通用类)
-
- [1. 响应实体类Result](#1. 响应实体类Result)
- [2. 自定义异常类ServiceException](#2. 自定义异常类ServiceException)
- [3. 通用实体类PageVO](#3. 通用实体类PageVO)
- [E02. 开发通用组件类](#E02. 开发通用组件类)
-
- [1. 异常处理类ExceptionAdvice](#1. 异常处理类ExceptionAdvice)
- [2. 业务切面类ServiceAspect](#2. 业务切面类ServiceAspect)
- [3. 响应处理类ResultAdvice](#3. 响应处理类ResultAdvice)
- [4. 缓存工具类MyRedis](#4. 缓存工具类MyRedis)
- [E03. 开发通用配置类](#E03. 开发通用配置类)
-
- [1. 文档配置类SpringDocConfig](#1. 文档配置类SpringDocConfig)
- [2. 跨域配置类CorsConfig](#2. 跨域配置类CorsConfig)
- [E04. 开发通用工具类](#E04. 开发通用工具类)
-
- [1. 报表打印工具EasyExcelUtil](#1. 报表打印工具EasyExcelUtil)
- [2. 文件上传工具MinioUtil](#2. 文件上传工具MinioUtil)
- [3. 正则匹配工具RegexUtil](#3. 正则匹配工具RegexUtil)
- [4. 代码生成工具MyBatisFlexGenerator](#4. 代码生成工具MyBatisFlexGenerator)
心法:本章使用 Maven 结构项目(非父子结构)进行开发。
项目结构:
shell
|_ my-item-backend # 项目后端,端口 23201
|_ docs # 项目文档
|_ sql # 数据库文件
|_ src # 源代码
|_ README.md # 项目说明
|_ my-item-frontend # 项目前端,端口 23299
S01. 项目简介
心法:MyItem 项目简介。
MyItem 项目是仿照《有数》APP 制作的个人物品管理平台,前后端分离,接入了 AI 功能。
E01. 基本介绍
心法:MyItem 项目中的角色分为 "普通用户" 和 "VIP 用户"(暂不开放) 两种。
1. 用户权限
| 权限 | 描述 |
|---|---|
| 个人信息管理 | 包括注册,登录,修改头像,修改个人信息,修改密码等 |
| 物品分类管理 | 包括添加物品分类,修改物品分类,查询物品分类,删除物品分类等 |
| 房间管理 | 包括添加房间,修改房间,查询房间,删除房间等 |
| 位置管理 | 包括添加位置,修改位置,查询位置,删除位置等 |
| 物品管理 | 包括添加物品,修改物品,查询物品,删除物品等 |
2. 技术选型
后端技术选型:
| 技术选型 | 描述 | 版本 |
|---|---|---|
| 单元测试 | junit | 4.13.2 |
| 代码简化 | lombok | 1.18.24 |
| 通用工具 | hutool | 5.8.25 |
| 序列化工具 | jackson | 2.17.0 |
| 控制层技术 | spring-boot-starter-web | 3.2.5 |
| 测试层技术 | spring-boot-starter-test | 3.2.5 |
| 切面层技术 | spring-boot-starter-aop | 3.2.5 |
| 日志收集 | logstash-logback-encoder | 8.0 |
| 数据库驱动 | mysql-connector-j | 8.2.0 |
| 持久层框架 | mybatis-flex-spring-boot3-starter | 1.10.2 |
| 数据库连接池 | HikariCP | 5.0.1 |
| 代码生成器 | mybatis-flex-codegen | 1.10.1 |
| 文档工具 | knife4j-openapi3-jakarta-spring-boot-starter | 4.4.0 |
| 缓存中间件 | redisson-spring-boot-starter | 3.33.0 |
| 参数校验 | hibernate-validator | 8.0.1.Final |
| 缓存工具 | spring-boot-starter-cache | 3.2.5 |
| 对象存储 | minio | 3.0.10 |
| 报表打印 | easyexcel | 3.3.4 |
| 大模型 | spring-ai-alibaba-starter-dashscope | 1.1.2.0 |
| Agent | spring-ai-alibaba-agent-framework | 1.1.2.0 |
| Agent调试台 | spring-ai-alibaba-studio | 1.1.2.2 |
| 向量数据库 | spring-ai-starter-vector-store-redis | 1.1.2 |
| ETL技术 | spring-ai-tika-document-reader | 1.1.2 |
前端技术选型:
| 技术选型 | 描述 | 版本 |
|---|---|---|
| 页面布局 | HTML | 5 |
| 页面美化 | CSS | 3 |
| 脚本功能 | ECMAScript | 6 |
| 前端服务器 | node | 20.12.0 |
| Vue脚手架 | vite | 5.5.1 |
| Vue路由 | vue-router | 4.0.3 |
| Vue样式预处理器 | sass-embedded | 1.77.8 |
| AJAX产品 | axios | 1.6.7 |
| WEB框架 | element-plus | 2.5.3 |
| 图表库 | ApacheEcharts | 5.4.3 |
E02. 通用环境
| 环境&工具 | 技术选型 | 版本 | 容器名称 | 端口占用 |
|---|---|---|---|---|
| 开发工具箱 | JDK | 17.0.9 | - | |
| 集成开发环境 | IntelliJ IDEA | 2023.3.3.win | - | |
| 项目管理工具 | Maven | 3.9.9 | - | |
| 版本控制工具 | Git | 2.28.0.windows.1 | - | |
| 虚拟机管理工具 | VmWare | 17.5.1 build-23298084 | - | |
| 虚拟操作系统 | OpenEuler | 24.03-LTS | - | |
| 容器化管理引擎 | Docker | 18.09.0 | - | |
| 数据库服务 | MySQL | 8.0.27 | mysql | 3306 |
| 对象存储服务 | MinIO | RELEASE.2023-08-31T15-31-16Z | minio | 9000 |
| 分布式缓存服务 | Redis | 7.0.5 | redis | 6379 |
| 向量数据库 | RedisStack | 7.0.5 | redis | 5379 |
| 反向代理服务 | Nginx | 1.25.2 | nginx | 86 |
| 搜索引擎服务 | ElasticSearch | 8.4.0 | elasticsearch | 9200 |
| 搜索引擎管控台服务 | Kibana | 8.4.0 | kibana | 5601 |
| 日志收集引擎 | Logstash | 8.4.0 | logstash-my-item | 4860 |
最小启动命令:
shell
docker start mysql minio redis redis-stack
最大启动命令:
shell
docker start mysql minio redis redis-stack nginx elasticsearch kibana logstash-my-item
1. MySQL数据库
武技:参考 JB2-3-MySQL(一)- S01E02 搭建 MySQL 单机容器,端口使用 3306 即可。
- 开发数据库:
sql
create database myitem default charset utf8mb4;
2. MinIO文件数据库
武技:参考 JB3-4-SpringBoot(三)- S04E01 搭建 MinIO 单机容器,端口使用 9000 即可。
访问 MinIO 管控台 http://192.168.40.77:9000,使用 minioadmin/minioadmin 登录,然后:
- 创建数据桶 myitem 并修改为 Public 访问权限。
- 创建 avatar 目录并上传 default-avatar.png 图片(图片随意)。
- 创建 itemPicture 目录并上传 default-item-picture.png 图片(图片随意)。
3. Redis缓存数据库
武技:参考 JB3-5-Redis(一) - S01E01 搭建 Redis 单机容器,端口使用 6379 即可。
4. ELK日志收集系统
武技:搭建 ELK 系统收集项目日志。
- 参考 JB3-6-ELK - S01E01 搭建 ElasticSearch 单机容器,容器名 elasticsearch,端口使用 9200 即可。
- 参考 JB3-6-ELK - S02E01 搭建 Kibana 单机容器,容器名 kibana,端口使用 5601 即可。
- 参考 JB3-6-ELK - S03E01 搭建 Logstash 单机容器,容器名 logstash-my-item,内部端口使用 4860,日志通信端口使用 5001 即可。
5. Nginx代理服务器
武技:参考 JB3-9-Nginx - S01E01.4 搭建 Nginx 单机容器,端口使用 85 即可。
E03. 数据信息
要求所有的表都必须添加如下 5 个字段:
| 字段名 | 类型 | 注释 | 约束 | 默认值 | 其它 |
|---|---|---|---|---|---|
| id | bigint | 主键 | 主键 | 自增 | |
| version | int | 数据版本(乐观锁版本号) | 非空 | 0 | |
| is_deleted | tinyint | 逻辑删除(0=否 非0=是) | 非空 | 0 | 删除时填充ID值 |
| create_time | datetime(3) | 创建时间 | 非空 | current_timestamp(3) | 毫秒级 |
| update_time | datetime(3) | 更新时间 | 非空 | current_timestamp(3) | 毫秒级 |
1. 用户表user
心法:用户表 user 中存放的是系统中每个用户的信息。
| 字段名 | 类型 | 注释 | 约束 | 默认值 | 其它 | |
|---|---|---|---|---|---|---|
| 1 | username | varchar(64) | 登录账号 | 非空 | ||
| 2 | password_hash | varchar(512) | 登录密码(哈希值) | 非空 | ||
| 3 | nickname | varchar(128) | 用户昵称 | 默认使用账号作为昵称 | ||
| 4 | avatar_url | varchar(2048) | 用户头像(URL地址) | |||
| 5 | summary | text | 用户简介 |
武技:开发用户表。
sql
-- table
create table `user`
(
`id` bigint auto_increment comment '主键',
`username` varchar(64) not null comment '登录账号',
`password_hash` varchar(512) not null comment '登录密码(哈希值)',
`nickname` varchar(128) null comment '用户昵称',
`avatar_url` varchar(2048) null comment '用户头像(URL地址)',
`summary` text null comment '用户简介',
`version` int not null default 0 comment '数据版本(乐观锁版本号)',
`is_deleted` tinyint not null default 0 comment '逻辑删除(0=否 1=是)',
`create_time` datetime(3) not null default current_timestamp(3) comment '创建时间',
`update_time` datetime(3) not null default current_timestamp(3) comment '更新时间',
primary key (`id`),
unique key `uk_username_deleted` (`username`, `is_deleted`),
key idx_is_deleted_create_time (`is_deleted`, `create_time`)
) engine = InnoDB
default charset = utf8mb4
collate = utf8mb4_unicode_ci
comment = '用户表';
2. 物品分类表item_category
心法:物品分类表 item_category 中存放的是每个物品的分类信息,如电子产品,服装鞋帽等:
物品分类 → 用户 = (1:1) = 每个物品分类只属于 1 个用户。
| 字段名 | 类型 | 注释 | 约束 | 默认值 | ||
|---|---|---|---|---|---|---|
| 1 | user_id | bigint | 外键(用户表) | 非空 | ||
| 2 | category_name | varchar(128) | 物品分类名称 | 非空 | ||
| 3 | emoji | varchar(32) | 物品分类图标(Emoji) | |||
| 4 | sort | int | 排序值(越小越靠前) | 非空 | 0 | |
| 5 | summary | text | 物品分类简介 |
武技:开发物品分类表。
sql
-- table
create table `item_category`
(
`id` bigint auto_increment comment '主键',
`user_id` bigint not null comment '外键(用户表)',
`category_name` varchar(128) not null comment '物品分类名称',
`emoji` varchar(32) null comment '物品分类图标(Emoji)',
`sort` int not null default 0 comment '排序值(越小越靠前)',
`summary` text null comment '物品分类简介',
`version` int not null default 0 comment '数据版本(乐观锁版本号)',
`is_deleted` tinyint not null default 0 comment '逻辑删除(0=否 1=是)',
`create_time` datetime(3) not null default current_timestamp(3) comment '创建时间',
`update_time` datetime(3) not null default current_timestamp(3) comment '更新时间',
primary key (`id`),
unique key `uk_user_category` (`user_id`, `category_name`, `is_deleted`),
key `idx_user_id_deleted` (`user_id`, `is_deleted`),
key `idx_deleted_sort` (`is_deleted`, `sort`)
) engine = InnoDB
default charset = utf8mb4
collate = utf8mb4_unicode_ci
comment = '物品分类表';
-- row
insert into `item_category` (`user_id`, `category_name`, `emoji`, `sort`, `summary`)
values (1, '电子产品', '📱', 1, '手机、电脑、耳机等电子设备'),
(1, '服装鞋帽', '👕', 2, '上衣、裤子、鞋子、配饰'),
(1, '厨房用品', '🍳', 3, '锅碗瓢盆、厨具餐具'),
(1, '家居饰品', '🏠', 4, '装饰画、摆件、布艺'),
(1, '美妆护肤', '💄', 5, '化妆品、护肤品、香水'),
(1, '图书文具', '📚', 6, '书籍、笔记本、文具'),
(1, '运动器材', '🏃', 7, '瑜伽垫、哑铃、运动装备'),
(1, '食品零食', '🍎', 8, '零食、饮料、食材'),
(1, '医疗器械', '💊', 9, '药品、体温计、血压计'),
(1, '工具配件', '🔧', 10, '螺丝刀、扳手、维修工具');
3. 房间表room
心法:房间表 room 中存放的是房间信息,如主卧,次卧,客厅等:
房间 → 用户 = (1:1) = 每个房间只属于 1 个用户。
| 字段名 | 类型 | 注释 | 约束 | 默认值 | |
|---|---|---|---|---|---|
| 1 | user_id | bigint | 外键(用户表) | 非空 | |
| 2 | room_name | varchar(128) | 房间名称 | 非空 | |
| 3 | emoji | varchar(32) | 房间图标(Emoji) | ||
| 4 | sort | int | 排序值(越小越靠前) | 非空 | 0 |
| 5 | summary | text | 房间简介 |
武技:开发房间表。
sql
-- table
create table `room`
(
`id` bigint auto_increment comment '主键',
`user_id` bigint not null comment '外键(用户表)',
`room_name` varchar(128) not null comment '房间名称',
`emoji` varchar(32) null comment '房间图标(Emoji)',
`sort` int not null default 0 comment '排序值(越小越靠前)',
`summary` text comment '房间简介',
`version` int not null default 0 comment '数据版本(乐观锁版本号)',
`is_deleted` tinyint not null default 0 comment '逻辑删除(0=否 1=是)',
`create_time` datetime(3) not null default current_timestamp(3) comment '创建时间',
`update_time` datetime(3) not null default current_timestamp(3) comment '更新时间',
primary key (`id`),
unique key `uk_user_room` (`user_id`, `room_name`, `is_deleted`),
key `idx_user_id_deleted` (`user_id`, `is_deleted`)
) engine = InnoDB
default charset = utf8mb4
collate = utf8mb4_unicode_ci
comment = '房间表';
-- row
insert into `room` (`user_id`, `room_name`, `emoji`, `sort`, `summary`)
values (1, '主卧', '🛏️', 1, '主卧室'),
(1, '次卧', '🛏️', 2, '次卧室'),
(1, '卫生间', '🚽', 3, '洗手间'),
(1, '衣帽间', '👗', 4, '衣物收纳间'),
(1, '阳台', '🌞', 5, '休闲阳台'),
(1, '客厅', '🛋️', 6, '起居室'),
(1, '厨房', '🍳', 7, '烹饪厨房');
4. 位置表location
心法:位置表 location 中存放的是房间中的位置信息,如第一个抽屉,第二个柜子等:
位置 → 房间 = (1:1) = 每个位置只属于 1 个房间。
| 字段名 | 类型 | 注释 | 约束 | 默认值 | |
|---|---|---|---|---|---|
| 1 | room_id | bigint | 外键(房间表) | 非空 | |
| 2 | location_name | varchar(128) | 房间位置名称 | 非空 | |
| 3 | emoji | varchar(32) | 房间位置图标(Emoji) | ||
| 4 | sort | int | 排序值(越小越靠前) | 非空 | 0 |
| 5 | summary | text | 房间位置简介 |
武技:开发房间位置表。
sql
-- table
create table `location`
(
`id` bigint auto_increment comment '主键',
`room_id` bigint not null comment '外键(房间表)',
`location_name` varchar(128) not null comment '位置名称',
`emoji` varchar(32) null comment '位置图标(Emoji)',
`sort` int not null default 0 comment '排序值(越小越靠前)',
`summary` text null comment '位置简介',
`version` int not null default 0 comment '数据版本(乐观锁版本号)',
`is_deleted` tinyint not null default 0 comment '逻辑删除(0=否 1=是)',
`create_time` datetime(3) not null default current_timestamp(3) comment '创建时间',
`update_time` datetime(3) not null default current_timestamp(3) comment '更新时间',
primary key (`id`),
unique key `uk_location` (`room_id`, `location_name`, `is_deleted`),
key `idx_room_id_deleted` (`room_id`, `is_deleted`)
) engine = InnoDB
default charset = utf8mb4
collate = utf8mb4_unicode_ci
comment = '位置表';
-- row
insert into `location` (`room_id`, `location_name`, `emoji`, `sort`, `summary`)
values
-- 主卧 (room_id=1)
(1, '主卧大衣柜', '🗄️', 1, '主卧衣物柜'),
(1, '主卧床头柜', '🛒', 2, '床头收纳'),
(1, '主卧梳妆台', '💄', 3, '化妆台'),
(1, '主卧抽屉柜', '🗄️', 4, '多层抽屉'),
(1, '主卧床底收纳', '🛏️', 5, '床底储物'),
-- 次卧 (room_id=2)
(2, '次卧衣柜', '🗄️', 1, '次卧衣物柜'),
(2, '次卧书桌', '📚', 2, '学习书桌'),
(2, '次卧书架', '📖', 3, '书籍收纳'),
(2, '次卧储物柜', '📦', 4, '杂物收纳'),
-- 卫生间 (room_id=3)
(3, '浴室柜', '🚿', 1, '洗漱台柜'),
(3, '卫生间置物架', '🧴', 2, '洗护用品架'),
(3, '卫生间壁柜', '🪟', 3, '墙面收纳柜'),
(3, '卫生间收纳篮', '🧺', 4, '脏衣收纳'),
-- 衣帽间 (room_id=4)
(4, '大衣区', '👔', 1, '长款衣物'),
(4, '裤子区', '👖', 2, '裤子收纳'),
(4, '鞋包区', '👠', 3, '鞋子包包'),
(4, '配饰区', '💍', 4, '首饰配饰'),
(4, '内衣区', '🩲', 5, '贴身衣物'),
(4, '换季收纳箱', '📦', 6, '过季衣物'),
-- 阳台 (room_id=5)
(5, '阳台储物柜', '🗄️', 1, '阳台杂物柜'),
(5, '阳台花架', '🌱', 2, '绿植摆放'),
(5, '阳台收纳柜', '🧺', 3, '清洁用品'),
(5, '阳台吊柜', '🪟', 4, '墙面吊柜'),
-- 客厅 (room_id=6)
(6, '客厅电视柜', '📺', 1, '电视收纳柜'),
(6, '客厅茶几', '☕', 2, '茶几收纳'),
(6, '客厅展示柜', '🏺', 3, '摆件展示'),
(6, '客厅边柜', '🪑', 4, '侧边收纳'),
(6, '客厅书架', '📚', 5, '客厅书柜'),
-- 厨房 (room_id=7)
(7, '厨房吊柜', '🪟', 1, '上方橱柜'),
(7, '厨房地柜', '🍽️', 2, '下方橱柜'),
(7, '厨房抽屉', '🔪', 3, '刀具餐具'),
(7, '厨房置物架', '🍄', 4, '调料架'),
(7, '厨房冰箱', '🧊', 5, '冰箱收纳'),
(7, '厨房餐边柜', '🍷', 6, '酒水收纳'),
(7, '厨房消毒柜', '🦠', 7, '消毒收纳');
5. 物品表item
心法:物品表 item 中存放的是物品信息,如手机,外套等:
物品 → 用户 = (1:1) = 每个物品只属于 1 个用户。
物品 → 物品分类 = (1:1) = 每个物品只属于 1 个物品分类。
物品 → 地址 = (1:1) = 每个物品只属于 1 个地址。
| 字段名 | 类型 | 注释 | 约束 | 默认值 | |
|---|---|---|---|---|---|
| 1 | user_id | bigint | 外键(用户表) | 非空 | |
| 2 | item_category_id | bigint | 外键(物品分类表) | 非空 | |
| 3 | location_id | bigint | 外键(位置表) | 非空 | |
| 4 | item_name | varchar(128) | 物品名称 | 非空 | |
| 5 | picture_url | varchar(2048) | 物品图片(URL地址) | ||
| 6 | tags | varchar(2048) | 物品标签(逗号分隔) | ||
| 7 | sort | int | 排序值(越小越靠前) | 非空 | 0 |
| 8 | summary | text | 物品简介(用户对物品的介绍) | ||
| 9 | remark | text | 物品备注(系统生成的AI标识) | ||
| 10 | price | decimal(10, 2) | 物品价格 | 非空 | 0.00 |
| 11 | count | int | 物品数量 | 非空 | 0 |
| 12 | usage_score | tinyint | 物品使用率评分(1~5颗星,0表示未评级) | 非空 | 0 |
| 13 | value_score | tinyint | 物品性价比评分(1~5颗星,0表示未评级) | 非空 | 0 |
| 14 | item_status | tinyint | 物品状态 0=服役中 1=闲置中 2=出借中 3=维修中 | 非空 | 0 |
| 15 | item_outcome | tinyint | 物品去向 0=持有中 1=已消耗 2=已丢弃 3=已丢失 4=已转赠 5=已售出 | 非空 | 0 |
| 16 | buy_date | date | 购入时间 | ||
| 17 | status_change_time | datetime | 状态变更时间 | ||
| 18 | outcome_change_time | datetime | 去向变更时间 | ||
| 19 | expire_time | datetime | 过期时间 |
武技:开发物品表。
sql
-- table
create table `item`
(
`id` bigint auto_increment comment '主键',
`user_id` bigint not null comment '外键(用户表)',
`item_category_id` bigint not null comment '外键(物品分类表)',
`location_id` bigint not null comment '外键(位置表)',
`item_name` varchar(128) not null comment '物品名称',
`picture_url` varchar(2048) null comment '物品图片(URL地址)',
`tags` varchar(2048) null comment '物品标签(逗号分隔)',
`sort` int not null default 0 comment '排序值(越小越靠前)',
`summary` text null comment '物品简介(用户对物品的介绍)',
`remark` text null comment '物品备注(系统生成的AI标识)',
`price` decimal(10, 2) not null default 0.00 comment '物品价格',
`count` int not null default 0 comment '物品数量',
`usage_score` tinyint not null default 0 comment '物品使用率评分(1~5颗星,0表示未评级)',
`value_score` tinyint not null default 0 comment '物品性价比评分(1~5颗星,0表示未评级)',
`item_status` tinyint not null default 0 comment '物品状态(0=服役中 1=闲置中 2=出借中 3=维修中)',
`item_outcome` tinyint not null default 0 comment '物品去向(0=持有中 1=已消耗 2=已丢弃 3=已丢失 4=已转赠 5=已售出)',
`buy_date` date null comment '购入时间',
`status_change_time` datetime(3) null comment '状态变更时间',
`outcome_change_time` datetime(3) null comment '去向变更时间',
`expire_date` date null comment '过期时间',
`version` int not null default 0 comment '数据版本(乐观锁版本号)',
`is_deleted` tinyint not null default 0 comment '逻辑删除(0=否 1=是)',
`create_time` datetime(3) not null default current_timestamp(3) comment '创建时间',
`update_time` datetime(3) not null default current_timestamp(3) comment '更新时间',
primary key (`id`),
unique key `uk_user_item_deleted` (`user_id`, `item_name`, `is_deleted`),
key `idx_user_id_deleted` (`user_id`, `is_deleted`),
key `idx_category_id_deleted` (`item_category_id`, `is_deleted`),
key `idx_location_id_deleted` (`location_id`, `is_deleted`),
key `idx_user_deleted_create_time` (`user_id`, `is_deleted`, `create_time`)
) engine = InnoDB
default charset = utf8mb4
collate = utf8mb4_unicode_ci
comment = '物品表';
-- row
insert into `item` (`user_id`, `item_category_id`, `location_id`, `item_name`,
`picture_url`, `tags`, `sort`, `summary`, `remark`, `price`,
`count`, `usage_score`, `value_score`)
values
-- 电子产品
(1, 1, 6, 'iphone 15', 'default-item-picture.png', '手机,苹果,通讯', 1, '苹果智能手机', '其它', 5999.00, 1, 5, 4),
(1, 1, 6, '华为笔记本', 'default-item-picture.png', '电脑,办公,华为', 2, '轻薄办公本', '其它', 6299.00, 1, 2, 4),
(1, 1, 1, 'airpods pro', 'default-item-picture.png', '耳机,蓝牙,苹果', 3, '无线降噪耳机', '其它', 1799.00, 1, 3, 4),
(1, 1, 23, '小米电视', 'default-item-picture.png', '电视,小米,影音', 4, '55寸智能电视', '其它', 3299.00, 1, 3, 4),
(1, 1, 1, 'ipad pro', 'default-item-picture.png', '平板,苹果,娱乐', 5, '平板电脑', '其它', 6999.00, 1, 5, 5),
-- 服装鞋帽
(1, 2, 15, '耐克运动鞋', 'default-item-picture.png', '鞋子,运动,耐克', 1, '跑步运动鞋', '其它', 699.00, 1, 5, 4),
(1, 2, 14, '牛仔裤', 'default-item-picture.png', '裤子,休闲,百搭', 2, '直筒牛仔裤', '其它', 299.00, 1, 5, 1),
(1, 2, 13, '羽绒服', 'default-item-picture.png', '外套,冬季,保暖', 3, '加厚羽绒服', '其它', 899.00, 1, 3, 5),
(1, 2, 16, '项链', 'default-item-picture.png', '配饰,首饰,百搭', 4, '银项链', '其它', 199.00, 1, 3, 3),
(1, 2, 15, '皮鞋', 'default-item-picture.png', '鞋子,正装,商务', 5, '商务皮鞋', '其它', 499.00, 1, 3, 3),
-- 厨房用品
(1, 3, 30, '不粘锅', 'default-item-picture.png', '厨具,烹饪,不粘锅', 1, '炒菜不粘锅', '其它', 199.00, 1, 2, 3),
(1, 3, 29, '陶瓷碗套装', 'default-item-picture.png', '餐具,碗碟,陶瓷', 2, '家用碗套装', '其它', 129.00, 1, 1, 3),
(1, 3, 31, '菜刀套装', 'default-item-picture.png', '刀具,厨房,锋利', 3, '不锈钢刀具', '其它', 159.00, 1, 5, 3),
(1, 3, 32, '调料盒', 'default-item-picture.png', '调料,收纳,厨房', 4, '调味收纳盒', '其它', 29.90, 1, 1, 3),
(1, 3, 33, '冰箱收纳盒', 'default-item-picture.png', '冰箱,收纳,保鲜', 5, '食物保鲜盒', '其它', 49.90, 1, 2, 2);
S02. 项目搭建
武技:创建项目 my-item-backend,并完成初始化工作。
- 锁定依赖版本:
xml
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.13.2</junit.version>
<lombok.version>1.18.24</lombok.version>
<hutool-all.version>5.8.25</hutool-all.version>
<jackson.version>2.17.0</jackson.version>
<spring-boot.version>3.2.5</spring-boot.version>
<logstash-logback-encoder.version>8.0</logstash-logback-encoder.version>
<mysql-connector-j.version>8.2.0</mysql-connector-j.version>
<knife4j-openapi3-jakarta-spring-boot-starter.version>4.4.0
</knife4j-openapi3-jakarta-spring-boot-starter.version>
<hibernate-validator.version>8.0.1.Final</hibernate-validator.version>
<minio.version>3.0.10</minio.version>
<okio.version>3.5.0</okio.version>
<easyexcel.version>3.3.4</easyexcel.version>
<mybatis-flex.version>1.10.2</mybatis-flex.version>
<mybatis-flex-codegen.version>1.10.1</mybatis-flex-codegen.version>
<redisson-spring-boot-starter.version>3.33.0</redisson-spring-boot-starter.version>
<spring-ai.version>1.1.2</spring-ai.version>
<spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
<spring-ai-alibaba-extensions.version>1.1.2.0</spring-ai-alibaba-extensions.version>
<ST4.version>4.3.4</ST4.version>
<spring-ai-alibaba-studio.version>1.1.2.2</spring-ai-alibaba-studio.version>
<commons-io.version>2.18.0</commons-io.version>
</properties>
- 指定仓库:
xml
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
- 管理依赖清单:
xml
<dependencyManagement>
<dependencies>
<!--spring-boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-ai-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-ai-alibaba-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${spring-ai-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-ai-alibaba-extensions-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-extensions-bom</artifactId>
<version>${spring-ai-alibaba-extensions.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 添加三方依赖:
xml
<dependencies>
<!--junit(单元测试)-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--lombok(代码简化)-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!--hutool-all(工具类)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
<!--jackson-databind(JSON处理)-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!--jackson-core(JSON处理)-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<!--jackson-annotations(JSON处理)-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<!--spring-boot-starter-web(Web应用)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-test(SpringBoot测试)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--spring-boot-starter-aop(切面)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--logstash-logback-encoder-->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>${logstash-logback-encoder.version}</version>
</dependency>
<!--mysql-connector-j(数据库驱动)-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
<scope>runtime</scope>
</dependency>
<!--mybatis-flex-spring-boot3-starter(MyBatisFlex框架)-->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>${mybatis-flex.version}</version>
</dependency>
<!--HikariCP(数据库连接池)-->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!--mybatis-flex-codegen(MyBatisFlex代码生成器)-->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-codegen</artifactId>
<version>${mybatis-flex-codegen.version}</version>
</dependency>
<!--knife4j-openapi3-jakarta-spring-boot-starter(OpenAPI3文档)-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j-openapi3-jakarta-spring-boot-starter.version}</version>
</dependency>
<!--redisson-spring-boot-starter(Redisson)-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson-spring-boot-starter.version}</version>
</dependency>
<!--hibernate-validator-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<!--minio(对象存储)-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!--okio(解决minio的依赖冲突)-->
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>${okio.version}</version>
<optional>true</optional>
</dependency>
<!--easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!--spring-ai-alibaba-starter-dashscope-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<!--spring-ai-alibaba-agent-framework-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
</dependency>
<!--spring-ai-starter-mcp-client-webflux-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<!--spring-ai-alibaba-studio-->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-studio</artifactId>
<version>${spring-ai-alibaba-studio.version}</version>
</dependency>
<!--spring-ai-advisors-vector-store-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<!--spring-ai-starter-vector-store-redis-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
</dependency>
<!--spring-ai-tika-document-reader-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!--commons-io(解决 spring-ai-tika-document-reader 中对应依赖版本兼容问题)-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
</dependencies>
- 开发主配文件:
application.yml:
yml
server:
port: 23201 # 端口号
spring:
main:
banner-mode: off # 不打印 banner
# minio
servlet:
multipart:
max-file-size: 1000MB # 文件上传最大限制
max-request-size: 100MB # 每个文件大小限制
# mysql
datasource:
url: jdbc:mysql://192.168.40.77:3306/myitem?
serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8 # 连接
username: root # 账号
password: root # 密码
type: com.zaxxer.hikari.HikariDataSource # 连接池
# redis
data:
redis:
host: 192.168.40.77 # 服务器IP
port: 6379 # 端口号
timeout: 3000ms # 超时毫秒数
jedis:
pool:
max-active: 8 # 最大连接
max-wait: -1ms # 最大阻塞时间
max-idle: 8 # 最大空闲
min-idle: 0 # 最小空闲
# Spring AI
ai:
dashscope:
api-key: ${DASHSCOPE_API_KEY} # 阿里云百炼 API_KEY
read-timeout: 100000 # 读取超时时间(毫秒)
chat:
options:
model: qwen-plus # 基础对话模型
max-tokens: 2000 # Token 限制
temperature: 0.7 # 采样温度
top-k: 50 # 候选池大小
top-p: 0.9 # 候选池门槛
embedding:
options:
model: text-embedding-v1 # 向量模型,阿里云百炼默认模型,不能修改
dimensions: 1536 # 向量维度:阿里云该模型固定 1536,不能修改
# mybatis-flex
mybatis-flex:
global-config:
print-banner: false # 不打印 banner
logic-delete-column: isDeleted # 逻辑删除字段
normal-value-of-logic-delete: 0 # 未删除值
deleted-value-of-logic-delete: 1 # 已删除值(会被业务层覆盖为动态的 ID 值)
configuration:
map-underscore-to-camel-case: true # 下划线转驼峰
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台SQL
type-aliases-package: com.joezhou.entity # 别名包扫描
# logback
logging:
level:
org.springframework.ai: DEBUG # AI 基础日志
io.modelcontextprotocol: DEBUG # MCP 调用日志
org.springframework.ai.tool: DEBUG # 本地 Tool 工具调用日志
# 自定义配置
myitem:
springdoc:
author: 周航宇
url: https://localhost:23202/index.html
title: my-item-backend
description: my-item-backend的API文档
version: 1.0.0
minio:
bucket-name: myitem
avatar-dir: avatar
default-avatar: default-avatar.png
item-picture-dir: itemPicture
default-item-picture: default-item-picture.png
token:
access-token-prefix: "login:access:"
refresh-token-prefix: "login:refresh:"
access-token-map-prefix: "login:access-map:"
refresh-token-map-prefix: "login:refresh-map:"
access-token-expire: 600 # 访问令牌过期时间(分钟)
refresh-token-expire: 7 # 刷新令牌过期时间(天)
- 开发启动类:
java
package com.joezhou;
/** @author 周航宇 */
@SpringBootApplication
@MapperScan("com.joezhou.mapper")
public class MyItemApp {
public static void main(String[] args) {
SpringApplication.run(MyItemApp.class, args);
}
}
- 参考 JB3-6-ELK - S03E01 搭建 ELK 收集日志系统:
classpath:logback.xml 最终内容如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--定义通用日志格式模板-->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %4.15(%t) %c{1}.%M:%L %msg%n"/>
<!--控制台 Appender: 将日志输出在控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--配置日志格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--LogStashAppender: 将日志输出到指定的 LogStash 中-->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!--Logstash 服务地址(端口为 TCP 连接的端口号)-->
<destination>192.168.40.77:5001</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
<!--自定义字段-->
<customFields>{"app": "my-item-backend"}</customFields>
</encoder>
<!--过滤器:阻止 INFO 级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
</appender>
<!--根日志记录器: 对全局的启动日志和运行时日志进行记录,最低级别为 INFO-->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="LOGSTASH"/>
</root>
</configuration>
E01. 开发全局通用类
1. 响应实体类Result
武技:参考 JB3-3-SpringMVC(一)- S01E03.4 封装响应类。
- 将 ResultCode 类扩展如下:
java
package com.joezhou.result;
/** @author 周航宇 */
@Getter
public enum ResultCode {
// 成功
SUCCESS(1000, "请求成功"),
// 服务器错误
SERVER_ERROR(2000, "服务器异常"),
MYSQL_ERROR(2001, "数据操作失败"),
MINIO_ERROR(2002, "文件操作失败"),
// 请求参数错误
ILLEGAL_PARAM(3000, "请求参数错误"),
ACCOUNT_ILLEGAL(3001, "账号密码错误"),
OLD_PASSWORD_ILLEGAL(3002, "旧密码错误"),
DATETIME_ILLEGAL(3003, "时间设定错误"),
// 记录重复
REPEAT_RECORD(4000, "记录重复"),
USERNAME_REPEAT(4001, "登录账号重复"),
CATEGORY_NAME_REPEAT(4002, "物品分类名称重复"),
ITEM_NAME_REPEAT(4003, "物品名称重复"),
ROOM_NAME_REPEAT(4004, "房间名称重复"),
LOCATION_NAME_REPEAT(4005, "位置名称重复"),
// 记录不存在
RECORD_NOT_FOUND(5000, "记录不存在"),
USER_NOT_FOUND(5001, "用户不存在"),
ITEM_CATEGORY_NOT_FOUND(5002, "物品分类不存在"),
ROOM_NOT_FOUND(5003, "房间不存在"),
LOCATION_NOT_FOUND(5004, "位置不存在"),
ITEM_NOT_FOUND(5005, "物品不存在"),
// 权限不足
PERMISSION_DENIED(6000, "权限不足");
private final int CODE;
private final String MESSAGE;
ResultCode(int code, String msg) {
this.CODE = code;
this.MESSAGE = msg;
}
}
- 开发 Result 响应类。
2. 自定义异常类ServiceException
心法:自定义异常类作用于业务层的逻辑处理阶段,不同的场景抛出不同的异常类,然后统一由全局异常处理器类捕捉并向请求方响应一个标准格式的异常信息。
当业务层代码需要抛出异常时,建议抛出自定义异常 ServiceException,此时需要传递一个用于表明异常类型的 ResultCode 枚举以及一个提供给开发人员的开发信息 coderMessage,使用示例如下:
java
if(条件) {
throw new ServiceException(ResultCode.ILLEGAL_PARAM, "业务方法中存在null值");
}
武技:开发自定义的全局异常类。
java
package com.joezhou.exception;
/** @author 周航宇 */
@Getter
public class ServiceException extends RuntimeException {
private final ResultCode resultCode;
public ServiceException(ResultCode resultCode, String coderMessage) {
super(coderMessage);
this.resultCode = resultCode;
}
}
3. 通用实体类PageVO
java
package com.joezhou.vo;
/** @author 周航宇 */
@Data
public class PageVO<T> implements Serializable {
private Long pageNumber;
private Long pageSize;
private Long totalRow;
private Long totalPage;
private List<T> records;
}
E02. 开发通用组件类
心法:使用 @Component 标记的类统一称为 "组件类",个人行为,非业界标准规范。
@RestControllerAdvice 注解的底层也是 @Component 注解。
1. 异常处理类ExceptionAdvice
心法:全局异常处理类用于对业务层代码进行增强,对每个业务方法抛出的异常进行统一的捕捉和处理(处理成 Result 类型的数据并返回)。
武技:开发全局异常处理类。
java
package com.joezhou.component;
import com.joezhou.exception.*;
import com.joezhou.result.*;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
/** @author 周航宇 */
@Slf4j
@RestControllerAdvice(basePackages = {"com.joezhou"})
public class ExceptionAdvice {
@ExceptionHandler(ServiceException.class)
public Object serviceException(ServiceException e) {
String coderMessage = e.getMessage();
log.error("❌业务层异常: " + coderMessage);
return new Result<>(e.getResultCode(), coderMessage);
}
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public Object hibernateValidatorException(BindException e) {
// 获取BindingResult
BindingResult bindingResult = e.getBindingResult();
// 获取BindingResult中所有属性错误信息集合中的第一个属性错误
FieldError firstFieldError = bindingResult.getFieldErrors().get(0);
// 异常信息 : "xxx实例的xxx属性校验失败: xxx异常信息"
String coderMessage = String.format("%s实例的%s属性校验失败: %s",
firstFieldError.getObjectName(),
firstFieldError.getField(),
firstFieldError.getDefaultMessage());
// 记录日志
log.error("❌控制层异常:" + coderMessage);
// 响应
return new Result<>(ResultCode.ILLEGAL_PARAM, firstFieldError.getDefaultMessage());
}
@ExceptionHandler(Exception.class)
public Object exception(Exception e) {
String coderMessage = e.getMessage();
log.error("❌其他异常: " + coderMessage);
return new Result<>(ResultCode.SERVER_ERROR, coderMessage);
}
}
2. 业务切面类ServiceAspect
心法:业务切面类利用 SpringAOP 技术对业务层代码进行增强。
- 前置通知:判断业务方法参数中是否存在 null 值,存在则直接抛出异常。
- 后置通知:记录业务方法的调用日志。
- 返回通知:暂无使用。
- 异常通知:AOP 中的异常通知比全局异常处理类优先级高,建议将处理异常的工作让给全局异常处理类来做。
武技:开发业务层切面类。
java
package com.joezhou.component;
/** @author 周航宇 */
@Slf4j
@Aspect
@Component
public class ServiceAspect {
@Around("execution(public * com.joezhou.service.impl.*.*(..))")
@SneakyThrows
public Object aroundAdvice(ProceedingJoinPoint pjp) {
// 获取方法参数
Object[] args = pjp.getArgs();
// 组装完整业务方法名:类名.方法名()
String className = pjp.getTarget().toString();
className = className.substring(className.lastIndexOf('.') + 1, className.indexOf('@'));
String methodName = pjp.getSignature().getName();
methodName = className + "." + methodName + "()";
// 前置通知: 检查参数中是否存在空值
if (ObjectUtil.hasNull(args)) {
throw new ServiceException(ResultCode.ILLEGAL_PARAM, "业务方法 " + methodName + "中存在null值参数");
}
// 调用目标方法
Object returnValue = pjp.proceed(args);
// 后置通知: 记录业务层调用日志
log.info(String.format("""
========== 业务日志 ==========
业务方法: %s
业务参数: %s
响应数据: %s
""",
methodName, JSONUtil.toJsonStr(args), JSONUtil.toJsonStr(returnValue)));
// 返回目标方法的返回值
return returnValue;
}
}
3. 响应处理类ResultAdvice
java
package com.joezhou.component;
import com.joezhou.result.Result;
/** @author 周航宇 */
@RestControllerAdvice(basePackages = "com.joezhou.controller")
public class ResultAdvice implements ResponseBodyAdvice<Object> {
/**
* @param methodParameter 控制方法全名
* @param aClass 转换器类型,以下两种均为 HttpMessageConverter 的子类
* 字符串型数据使用 StringHttpMessageConverter转换,
* 其他类型数据使用 MappingJackson2HttpMessageConverter转换。
* @return true 表示继续调用 beforeBodyWrite() 方法,false 表示不处理数据,直接返回。
*/
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
// 放行到 beforeBodyWrite() 方法
return true;
}
/**
* @param o 响应数据
* @param methodParameter 控制方法全名
* @param mediaType 响应类型,如 application/json 等
* @param serverHttpRequest 请求对象
* @param serverHttpResponse 响应对象
* @param aClass 转换器类型,以下两种均为 HttpMessageConverter 的子类
* 字符串型数据使用 StringHttpMessageConverter转换,
* 其他类型数据使用 MappingJackson2HttpMessageConverter转换。
*/
@Override
public Object beforeBodyWrite(Object o,
MethodParameter methodParameter,
MediaType mediaType,
Class aClass,
ServerHttpRequest serverHttpRequest,
ServerHttpResponse serverHttpResponse) {
// 若Jackson使用字符串转换器,使用JSON封装一下再直接返回字符串,避免爆发转换异常
if (aClass == StringHttpMessageConverter.class) {
return JSONUtil.toJsonStr(new Result<>(o));
}
// 若返回值已经是Result类型则直接返回
if (o instanceof Result) {
return o;
}
// 统一将Controller的返回值封装为Result类型
return new Result<>(o);
}
}
4. 缓存工具类MyRedis
武技:参考 附件_封装MyRedis工具 笔记,封装 MyRedis 工具类。
E03. 开发通用配置类
心法:使用 @Configuration 标记的类统一称为 "配置类",个人行为,非业界标准规范。
1. 文档配置类SpringDocConfig
java
package com.joezhou.config;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.beans.factory.annotation.Value;
/** @author 周航宇 */
@Configuration
public class SpringDocConfig {
@Value("${myitem.springdoc.author}")
private String author;
@Value("${myitem.springdoc.url}")
private String url;
@Value("${myitem.springdoc.title}")
private String title;
@Value("${myitem.springdoc.description}")
private String description;
@Value("${myitem.springdoc.version}")
private String version;
/** 通用信息Bean */
@Bean
public OpenAPI commonInfo() {
return new OpenAPI().info(new Info()
.title(title)
.description(description)
.version(version)
.contact(new Contact().name(author).url(url)));
}
}
2. 跨域配置类CorsConfig
java
package com.joezhou.config;
/** @author 周航宇 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/v1/**") // 匹配接口路径
.allowedOrigins("http://localhost:23299") // 前端地址
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
E04. 开发通用工具类
1. 报表打印工具EasyExcelUtil
武技:参考 JB3-4-SpringBoot(二)- S02E05 笔记。
- 开发 Excel 转换器 TimestampConverter。
- 开发 Excel 工具类 EasyExcelUtil。
2. 文件上传工具MinioUtil
武技:参考 JB3-4-SpringBoot(三)- S04E02 笔记。
- 开发 MinIO 工具类 MinioUtil。
3. 正则匹配工具RegexUtil
武技:开发一个接口类,用于规范项目中用到的正则表达式和消息提示。
java
package com.joezhou.util;
/** @author 周航宇 */
public interface RegexUtil {
String USERNAME_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{3,63}$";
String USERNAME_REGEX_MSG = "登录账号必须以字母开头,只能包含字母、数字、下划线";
String NICKNAME_REGEX = "^[\\u4e00-\\u9fa5a-zA-Z0-9_\\s]{1,128}$";
String NICKNAME_REGEX_MSG = "昵称只能包含中文、字母、数字、下划线和空格";
String CATEGORY_NAME_REGEX = "^[\\u4e00-\\u9fa5a-zA-Z0-9_\\s]{1,128}$";
String CATEGORY_NAME_REGEX_MSG = "物品分类名称只能包含中文、字母、数字、下划线和空格,长度1-128个字符";
String ROOM_NAME_REGEX = "^[\\u4e00-\\u9fa5a-zA-Z0-9_\\s]{1,128}$";
String ROOM_NAME_REGEX_MSG = "房间名称只能包含中文、字母、数字、下划线和空格,长度1-128个字符";
String LOCATION_NAME_REGEX = "^[\\u4e00-\\u9fa5a-zA-Z0-9_\\s]{1,128}$";
String LOCATION_NAME_REGEX_MSG = "位置名称只能包含中文、字母、数字、下划线和空格,长度1-128个字符";
String ITEM_NAME_REGEX = "^[\\u4e00-\\u9fa5a-zA-Z0-9_\\s]{1,128}$";
String ITEM_NAME_REGEX_MSG = "物品名称只能包含中文、字母、数字、下划线和空格,长度1-128个字符";
}
4. 代码生成工具MyBatisFlexGenerator
武技:参考 JB3-4-SpringBoot(五)- S10E01.1 笔记。
- 开发 MyBatisFlex 生成器类 MyBatisFlexGenerator。
- 使用 MyBatisFlex 生成器类生成全部表的对应代码。
Java道经 - 项目 - MyItem - 项目简介