1
前端环境准备
1 | npm create vite@latest frontend -- --template vue |
第一个填y,然后一路回车
1 | cd frontend |
1 | frontend/ |
把json文档导入apifox:导入之前打开文件看版本
前端让ai做没对应好,需要自己学习一下再调
1 | -- 比赛表 |
现学一遍数据库
每一行称为记录(Record)
每一列称为字段(Column)
主键id或uuid,
外键一对多
1 | alter table 表名 |
创建索引
1 | alter table 表名 |
投影查询
使用SELECT 列1, 列2, 列3 FROM ...时,还可以给每一列起个别名,这样,结果集的列名就可以与原表的列名不同。它的语法是SELECT 列1 别名1, 列2 别名2, 列3 别名3 FROM ...。
排序
order by desc asc
使用ORDER BY score DESC, gender表示先按score列倒序,如果有相同分数的,再按gender列排序:
分页查询
我试了好多次,而且环境一直配不好,配了一个springbot4的版本5结果一直报错“找不到jdk发行版本24”,后来发现是spring版本太新了,与我的jdk8不兼容,然后我配了个21,还是不行,又配了个17,依旧不行,很费劲了,中途maven又出了问题,好像是依赖加载不上来,然后配了一个阿里镜像,没配仓库,最后idea里面便捷下载了一个ms17的,前一天终于打印出helloworld了,第二天又不行了。。。
5天速通创建一个项目对我来说还是太难了,完全不知道从哪开始,所以我“借鉴”苍穹外卖了
苍穹外卖采用父项目下三个子项目结构
采用2.7.3的springbot框架
父项目的项目全局依赖有
1 | <!--版本变量--> |
注释了微信支付
这里有个遗漏点,我pom没加,识别不出父类,还有原执行代码是java,这里又调了两小时
常用组件,简单说下作用,帮助理解项目定位:
| 依赖名 | 用途 |
|---|---|
| mybatis-spring-boot | MyBatis 持久层框架(操作数据库) |
| lombok | 简化实体类代码(不用写 getter/setter) |
| fastjson | JSON 序列化 / 反序列化工具 |
| druid | 阿里的数据库连接池 |
| pagehelper | 分页插件 |
| knife4j | 接口文档生成工具(基于 Swagger) |
| aspectj | AOP 面向切面编程(比如日志、权限) |
| jjwt | JWT 令牌生成(用户登录认证) |
| aliyun-sdk-oss | 阿里云 OSS 对象存储(上传文件) |
| poi/poi-ooxml | Excel 导入导出 |
| wechatpay-apache-httpclient | 微信支付对接工具 |
采用版本变量定义,升级版本只需在<properties> 里改一处
也就是说${mybatis.spring}里面的变量是<mybatis.spring>2.2.0</mybatis.spring>里面的2.2.0
父工程下要加子工程的名字
1 | <modules> |
聚合父项目的职责是:
- 统一管理所有子模块的依赖版本(通过
<dependencyManagement>) - 定义公共的版本常量(
<properties>) - 聚合子模块的构建流程
没有<java.version>17</java.version>
聚合父工程本身不编译代码(只是聚合模块),所以不需要配置;
子模块中需要把
1 | <parent> |
改成
1 | <parent> |
然后删去
1 | <groupId>com.julien</groupId> |
再加上依赖
common模块定位
通用工具模块(比如存放工具类、常量、全局异常处理、注解等),不是可独立运行的 Spring Boot 应用,而是给其他模块(如sky-server)依赖的 “工具包”。这个定位决定了它不需要很多业务模块的配置。
项目元数据配置,不是功能必需项,只有满足特定场景才需要:
url:项目的官网 / 仓库地址,非核心配置,个人 / 企业内部项目通常省略;licenses:开源许可证(如 Apache 2.0),仅开源项目需要声明,内部项目无需配置;developers:开发者信息,团队协作时可配,但非强制,小型项目 / 教程项目通常省略;scm:版本控制信息(如 Git 仓库地址),自动化部署 / 发布时可能用到,普通开发场景不需要。
java.version?
java.version是用来指定Java 编译版本的,这个配置可以在父工程(sky-take-out)中统一配置,子模块会自动继承;- 即使父工程没配,Spring Boot 父工程(
spring-boot-starter-parent)也有默认值(比如 2.7.3 默认 Java 8,4.0.x 默认 Java 17),无需在每个子模块重复写;
以下是common模块应用依赖
1 | <dependencies> |
理解:
| 依赖名称 | 核心作用 | 典型应用场景 |
|---|---|---|
| Lombok | 通过注解简化 Java 代码,消除 getter/setter、构造方法等冗余模板代码 | 1. @Data注解替代 POJO 类的 getter/setter2. @Slf4j快速注入日志对象3. @NoArgsConstructor生成无参构造 |
| Fastjson | 阿里巴巴开源的高性能 JSON 序列化 / 反序列化工具 | 1. Java 对象 ↔ JSON 字符串互转2. 处理第三方接口的 JSON 请求 / 响应3. 自定义 JSON 字段过滤 |
| Commons-lang | Apache 开源的 Java 基础工具类库,补充 JDK 原生 API 不足 | 1. StringUtils.isEmpty()判空字符串2. DateUtils处理日期3. 数组 / 对象通用操作 |
| Spring Boot Starter JSON | Spring Boot 内置的 JSON 处理核心依赖(默认集成 Jackson) | 1. Spring MVC 接口自动返回 JSON 格式数据2. 接收 JSON 请求体并反序列化为 Java 对象3. 自定义 JSON 日期格式化 |
| JJWT | Java JWT 工具库,用于生成、解析、验证 JWT 令牌 | 1. 生成登录用户的身份令牌2. 解析前端传入的 JWT 并验证有效性3. 从 JWT 中提取用户信息 |
| Spring Boot Configuration Processor | 生成配置元数据,让 IDE 对 yml/properties 配置文件提供自动提示 | 1. 自定义配置类(@ConfigurationProperties)时,yml 文件提示配置项2. 提升配置开发体验(可选依赖,不传递) |
| 阿里云 OSS SDK | 操作阿里云对象存储(OSS)的 Java SDK | 1. 上传用户头像、订单小票到 OSS2. 下载 OSS 中的文件3. 生成文件临时访问链接 |
| JAXB API | XML 与 Java 对象的绑定转换工具(JDK9 + 需手动引入) | 1. 兼容阿里云 OSS SDK 的内部依赖2. 处理 XML 格式的数据(如部分第三方接口) |
| 微信支付 HTTP 客户端(v3) | 微信支付 v3 版本官方 HTTP 客户端,封装签名、验签、请求发送逻辑 | 1. 发起微信支付下单请求2. 验证微信支付回调通知的合法性3. 查询订单支付状态、发起退款 |
common的子包详解
| 包名 | 作用 | 典型内容示例 |
|---|---|---|
| constant | 存放常量定义 | 如 API 路径、错误码、状态码、固定字符串等,避免硬编码 |
| context | 管理上下文信息 | 如用户会话、请求上下文、全局配置、线程本地存储等 |
| enumeration | 存放枚举类型 | 如订单状态、用户角色、操作类型等,提高代码可读性和类型安全 |
| exception | 自定义异常体系 | 如 BusinessException、AuthFailedException,统一错误处理和返回 |
| json | JSON 序列化 / 反序列化工具 | 如自定义 Gson/Jackson 适配器、JSON 工具类 |
| properties | 配置属性类 | 如 AppProperties,绑定 application.yml 中的配置项 |
| result | 统一响应结果封装 | 如 Result<T>、PageResult,规范 API 返回格式 |
| utils | 通用工具类 | 如日期处理、字符串工具、加密解密、文件操作等 |
- 业务逻辑和技术细节分开
- 常量、枚举、异常等基础设施集中管理
- 工具类和通用组件复用性高
- 便于团队协作和代码维护
这里新学一个 枚举 优雅写法
1 | // 第一步:定义枚举类(固定的订单状态选项) |
然后再补充面向切面AOP
AOP 的核心价值是将通用、非业务核心的逻辑(如日志、权限、数据校验、事务、保密控制等)抽离出来,与业务逻辑解耦。
打算使用的业务点(只是打算,后面万一又学到更先进方便的技术)
一、权限校验切面(最核心的应用)
应用场景
- 队长只能操作自己队伍的信息,不能修改其他队伍的数据;
- 二级管理员只能管理本院的队伍,超级管理员可操作所有数据;
- 评委只能给分配给自己的项目打分,且打分功能仅在比赛评分阶段开放。
二、操作日志切面
应用场景
- 记录队长上传队伍信息、附件的操作;
- 记录二级管理员审核队伍、分发账号的操作;
- 记录超级管理员创建二级管理员、配置比赛的操作;
- 记录评委打分、提交评语的操作。
三、评分数据保密切面
应用场景
- 评委打分后,在比赛评分阶段结束、超级管理员未解锁统计前,禁止队长 / 二级管理员查询评分数据;
- 仅超级管理员和评委本人可查看未公开的评分记录(评委仅能看自己的)。
⼈员信息管理:队⻓上传并管理队伍成员的个⼈信息,包括成员姓名、学号、学院等。
模仿苍穹外卖新增员工模块
t1:产品设计需求分析
t2:设计接口:a.请求方式:post/get b.提交格式:json/… c.后端返回数据格式(整体封装成result对象) d.
实体设计是定义 “数据长什么样”,接口文档是定义 “数据怎么传”
再补充方法
当前线程上下文工具类,核心用于在多线程环境下存储和获取当前登录用户的 ID,是外卖 / 比赛管理这类系统中非常通用的核心工具类。
ThreadLocal的核心特性:一个用户的请求对应一个独立的线程,在这个线程的整个处理流程中(从控制器→服务层→持久层),都能通过BaseContext获取到该用户的 ID,且不同用户的请求(不同线程)之间数据互不干扰。
再复习泛型
1 | // 泛型类:只写一次,适配所有返回类型 |
vo 前后端数据交互载体
凡是需要给前端(如 Vue/React/ 小程序)提供数据接口的场景,都会用到 @RestController,比如:
- 员工登录接口(返回登录结果);
- 查询用户列表接口(返回用户数据 JSON);
- 新增 / 修改 / 删除数据接口(返回操作结果)。
配置日志调试
1 | logging: |
加了 @EnableTransactionManagement:@Transactional 注解才能正常工作,实现事务控制。
@transactional保证所有事物成功执行,中途失败立马回滚
new HashMap<>(); 创建一个键值对集合
使用Builder创建对象(链式调用,无需关心参数顺序)
Entity 应该根据数据库表结构生成
“接口” 和 “数据模型”(Schemas),对应的是后端开发中的 DTO/VO/Request/Response 等对象
DTO/Request:接收前端传来的参数,比如登录接口的 EmployeeLoginDTO。
VO/Response:返回给前端的数据,比如登录成功后返回的 EmployeeLoginVO。
这些对象的字段是根据接口的业务需求来设计的,可能只包含 Entity 中的部分字段,或者组合了多个 Entity 的数据。
正确的开发流程
- 设计数据库表:根据业务需求,设计好所有的表结构(如
user,team,activity)。 - 生成 Entity:根据表结构,生成对应的 Entity 类,字段与表列一一对应。
- 设计接口:根据前端交互需求,设计接口的请求(DTO)和响应(VO)数据模型。
- 业务转换:在 Service 层,将 Entity 转换为 DTO/VO,或者将 DTO 转换为 Entity 进行数据库操作。
这里我都没做,直接写控制层了,理所当然的认为直接从控制层倒退服务层和接口层,中途写一个补一个,但是不是这样的,接口文档都给我了,我应该在创建数据库后先设计 Entity → 再设计 Common 通用分页组件 → 接着设计 DTO → 最后设计 VO
Entity 是所有业务的 “数据底层”,对应数据库表结构,是 DTO/VO 设计的基础(字段类型、业务含义都依赖 Entity)。
如果先设计 DTO/VO,会因为不清楚数据库的字段类型 / 约束(如 status 是 TINYINT 对应 Byte)导致字段类型设计错误,后期需要返工。
common 的设计思路(统一“规矩”)
common 不是业务,而是“全项目都遵守的一套规范”。
1) 统一返回结构(最重要)
所有接口都返回同一形状,前端永远按同一方式处理成功/失败。
ApiResponse<T>:success / code / message / data- 好处:前端不用每个接口写不同判断;后端错误也能统一落到
code/message
2) 统一错误体系
ErrorCode(枚举/常量):例如PARAM_ERROR(40001) / UNAUTHORIZED(40100) / FORBIDDEN(40300) / NOT_FOUND(40400)BizException(code, message):业务异常直接抛GlobalExceptionHandler:把异常转成ApiResponse.fail(...)
这样 controller/service 不用到处 try-catch,也不会出现“有的接口返回字符串、有的返回 JSON”的混乱。
3) 通用能力(可选但推荐)
- 分页:
PageRequest(pageNo,pageSize)+PageResult<T>(total,list) - 时间/ID:
IdVO、BatchIdReq、DateRangeQuery这种复用模型 - 登录上下文:
UserContext(从 JWT/Session 里解析出 userCode/role/academyId/comId) - 工具类:
AssertUtil、BeanCopyUtil(少写重复校验/转换)
common 的原则:只放“全项目通用”、不掺业务字段。
DTO 的设计思路(输入模型)
DTO = Request Model。它描述“这个接口允许用户传什么”。
1) DTO 只服务“接口入参”,不要复用 entity
- entity 是数据库结构,会变、字段多且有敏感字段
- DTO 是 API 契约,要稳定、精简、只包含需要的字段
2) 强烈建议按“用例/接口”来拆 DTO
不要按表拆 DTO(TeamDTO/MemberDTO 一把梭),而是按动作拆:
CreateCompetitionReqUpdateTeamReqAddMemberReqLoginReq
原因:不同接口需要的字段不同。按动作拆能避免:
- DTO 里一堆字段对某些接口无意义(前端也迷惑)
- 校验注解没法写清楚(某字段有时必填有时非必填)
3) DTO 放校验(Validation)
DTO 是最适合做参数校验的地方:
@NotBlank @NotNull @Min @Max @Size @Pattern- 复杂校验(开始时间 < 结束时间)可以用自定义校验器或 service 校验
4) DTO 的边界
- DTO 可以包含“前端传来的 ID/枚举/列表”
- DTO 不应该包含:
- 数据库自增字段(除非 update 必须传 id)
- createTime/updateTime(由后端生成)
- 任何敏感信息(比如 passwordHash)
VO 的设计思路(输出模型)
VO = Response/View Object。它描述“前端最终要展示/使用什么”。
1) VO 不等于 entity
同样理由:entity 是内部实现,VO 是对外契约。
VO 通常会:
- 字段更少、更贴近页面展示
- 有“组合/聚合”的结构(比如一个 TeamDetailVO 带 members 列表)
- 有一些“展示友好字段”
status+statusTextcreatedAt格式化后的字符串(或统一 ISO 字符串)
2) 列表 VO 和 详情 VO 分开
典型做法:
TeamListItemVO:列表只要摘要(id/name/status/memberCount)TeamDetailVO:详情再带 members、附件等
好处:列表接口不会很重,性能好;前端也清楚用哪个。
3) VO 允许“字段再加工”
前端不应该反复推导字段,后端可以在 VO 做:
- 状态码转中文:
statusText - count:
memberCount - 是否可操作:
canEdit、canScore(结合权限/时间窗口)
VO 的原则:面向页面/使用场景,而不是面向数据库。
三者怎么配合(推荐工作流)
- 先定 API(Apifox/接口文档):每个接口的 request/response 示例
- 对每个接口:
- 写 DTO(入参)+ 校验
- 写 VO(出参)+ 是否需要聚合
- service 内部用 entity/DO 做持久化
- service 返回 VO(或 assembler 转换)
- controller 只负责:接收 DTO -> 调 service -> 返回 ApiResponse
ai含量有点高了,然后,最后的最后,我tm没测项目能否启动,前面的也没法注释了,很害怕明天填完所有出现环境问题又跑不动的情况,唉第一次真的啥都不知道,心急吃不了热豆腐哎哎哎
核心区别总结
| 维度 | Team(数据实体) |
TeamModel(视图模型) |
|---|---|---|
| 核心用途 | 映射数据库表,用于数据存储和内部处理 | 适配前端展示,用于接口返回 |
| 数据内容 | 存储 ID 等结构化数据(如 memberIds) |
存储名称等可读数据(如 memberNames) |
| 数据来源 | 直接从数据库查询得到 | 由 Team、Member、Instructor 等多个实体组装而成 |
| 字段设计 | 与数据库表一一对应,无冗余 | 按需设计,包含前端需要的所有展示字段 |
4. 实际开发中的使用流程
- 查询数据:后端从数据库查询
Team实体,同时关联查询Member和Instructor表,获取队员和指导教师的姓名。 - 组装视图:将
Team的 ID 列表(memberIds、instructorIds)转换为名称字符串(memberNames、instructorNames),封装成TeamModel。 - 返回前端:将
TeamModel作为接口的data字段返回,前端直接渲染名称,无需再根据 ID 去查询姓名。
entity命名是对应一个人名
dto和vo是个动作,对应一个短语
common 该怎么设计(统一返回、错误码、分页、登录态)
分页
如果不分页,接口会一次性返回 100 条数据,前端渲染慢、后端查库耗时;分页后每次只返回 10 条,性能大幅提升。
分页的核心参数(前后端通用)
| 参数名 | 含义 | 示例值 | 说明 |
|---|---|---|---|
pageNum |
当前页码 | 1 | 前端传入,默认值建议设 1 |
pageSize |
每页展示的条数 | 10 | 前端传入,默认值建议设 10 |
total |
符合条件的总记录数 | 100 | 后端计算后返回,用于前端显示 “共 100 条” |
records |
当前页的数据列表 | [队伍 1, 队伍 2,…] | 后端查询后返回的当前页数据 |
通用化抽离 → 业务化扩展 → 标准化返回
最后依旧感谢群友提醒极大减少了我的弯路
如有错误,多多指教