一. 需求

要完成 “Tlias 智能学习辅助系统” 的实战项目,我们可以从业务场景、功能模块、技术需求三个维度进行需求分析:
1.业务场景分析
该系统定位为企业级学习辅助与管理平台,核心服务于企业内部的员工培训、部门管理、数据统计等场景,帮助企业高效管理员工信息、培训资源,并通过数据报表优化学习策略。
2.功能模块需求拆解
结合界面和右侧功能列表,系统可拆分为以下核心模块:
1)部门管理模块
- 功能:对企业部门进行增删改查
-
- 查询:支持列表展示所有部门,可按部门名称、ID 筛选
-
- 新增:录入部门名称、上级部门等信息
-
- 修改:更新部门基本信息
-
- 删除:移除无效部门(需处理关联员工的级联逻辑)
2)员工管理模块
- 功能:对企业员工进行全生命周期管理
-
- 增删改查:支持员工信息(ID、姓名、年龄、部门、职位等)的添加、修改、删除、列表查询
-
- 文件上传:支持员工证件、培训材料等附件的上传与管理
3)报表统计模块
- 功能:通过可视化图表呈现企业学习与人员数据
-
- 员工职位统计:柱状图展示各职位的员工分布
-
- 员工性别统计:饼图展示性别比例
-
- 班级人数统计:柱状图展示各班级学员数量
-
- 学员学历统计:环形图展示学员学历分布
4)登录认证模块
- 功能:保障系统访问安全
-
- 登录:员工通过账号密码登录系统
-
- 修改密码:支持登录后修改个人密码
-
- 退出登录:安全登出系统
5)日志管理模块
- 功能:记录系统操作轨迹
-
- 操作日志:记录员工的系统操作(如新增部门、修改员工信息等)
-
- 登录日志:记录员工的登录时间、IP 等信息
6) 班级与学员管理模块(实战核心)
- 功能:管理企业培训班级和学员
-
- 班级管理:班级的增删改查(名称、课程、班主任等)
-
- 学员管理:学员信息的增删改查(关联班级、学历、培训进度等)
3.技术需求分析
结合 “Web 前端 + 后端” 的技术栈,需明确以下技术实现要求:
1)前端技术需求
- 页面布局:采用侧边栏导航 + 顶部栏功能区 + 主体内容区的经典后台布局
- 交互要求:
-
- 表单验证:新增 / 修改数据时需做前端校验(如必填项、格式校验)
-
- 分页查询:列表页支持分页、排序、筛选
-
- 图表可视化:使用 ECharts 实现柱状图、饼图、环形图等数据可视化
-
- 文件上传:支持多文件上传、进度条展示、文件预览
2)后端技术需求
- 架构:采用前后端分离架构,前端通过接口与后端交互
- 数据库:设计部门表、员工表、班级表、学员表、日志表等,需关注表间关联(如部门与员工的一对多关系)
- 接口设计:每个功能模块需提供 RESTful 风格接口(如/api/dept/list查询部门列表)
- 权限控制:基于登录态实现接口鉴权,不同角色(如管理员、普通员工)可访问的功能不同
- 文件存储:实现文件上传接口,支持将文件存储到服务器本地或云存储(如 MinIO)
4.技术栈选型建议
- 前端:HTML、CSS、JavaScript(或 Vue/React 框架)+ ECharts(图表)+ Axios(接口请求)
- 后端:Java + Spring Boot + MyBatis(或 MyBatis-Plus)+ MySQL(数据库)
- 部署:可部署在 Tomcat 容器,结合 Nginx 做前端静态资源代理
二.准备工作
1.开发模式
基于当前主流的前后端混合开发

2.开发规范
在案例进行开发的时候,采用的架构风格是Restful来进行前端和服务器端的交互

注:
1.REST是风格,是约定方式,约定不是规定,可以被打破。
2.描述功能模块通常使用复数形式(加s),表示此类资源,而非单个资源。如:user、books
思考:
前后端都在并行开发,后端开发完对应的接口之后,如何对接口进行请求测试?前端开发过程中,如何获取到数据,测试页面的渲染展示?
这里可以使用Apifox来解决这个问题,因为浏览器地址栏发起的请求,都是GET方式的请求,如果我们需要POST、PUT、DELETE方式的请求,就需要借助这类工具。

3.工程搭建

困惑点:三层架构、解耦、mapper
三.部门管理
1.列表查询-接口开发

困惑点:这里我遇到了一个坑,在正常写完查询数据代码后会碰到403或者500的问题,这个是因为Lombok引入后但是@Data注解没有正常生效,导致前后端返回的数据格式要求不一致,前端要求返回的是一个json格式,但是后端服务器返回的是一个html格式
解决办法:先打开pom.xml,然后找到Lombok所在位置,根据spring创建的Lombok版本,手动改一下版本即可

负载均衡:意思就是说前端给后端发送请求,代理服务器会把每次请求分批次给不同的服务器,比如第一次请求给第一台服务器,第二次请求给第二台。

这个图展示的是 Nginx 反向代理 + 路径重写的核心流程,简单说就是:Nginx 接收前端请求,先修改请求路径,再把请求转发给后端 Tomcat 服务器。
1) listen 90;
- 作用:Nginx 启动后,会监听服务器的 90 端口,所有发往
http://localhost:90的请求都会被这个server块处理。
2.)location ^~ /api/ { ... }
location:Nginx 用于「匹配请求路径」的指令,不同的路径可以走不同的处理规则;^~ /api/:路径匹配规则(^~表示 “精确前缀匹配”),意思是:只处理 “以 /api/ 开头” 的请求(比如http://localhost:90/api/depts、http://localhost:90/api/emps会匹配,http://localhost:90/test不会匹配)。
3)rewrite ^/api/(.*)$ /$1 break;(路径重写)
这是核心中的核心,作用是「修改请求路径」,用正则表达式实现:
- 正则部分
^/api/(.*)$:^:匹配路径开头;/api/:匹配固定前缀/api/;(.*):匹配/api/后面的所有内容(用括号包裹,作为 “分组”,后续用$1引用);$:匹配路径结尾;
- 替换部分
/$$1(图中是/$$1,实际是/+$1):把原路径替换成/$1($1就是前面括号里匹配到的内容); break:重写完成后,停止后续的重写规则,直接执行后面的proxy_pass。
举例子(对应图中的请求):原请求路径是 /api/depts,经过 rewrite 后:
- 原路径
^/api/(.*)$中的(.*)匹配到的是depts; - 替换成
/$$1→ 最终路径变成/depts。
4) proxy_pass http://localhost:8080;(反向代理)
- 作用:把重写后的请求转发给指定的后端服务器(这里是 Tomcat,地址是
http://localhost:8080); - 结合前面的路径重写,最终转发给 Tomcat 的请求是:
http://localhost:8080/depts(对应图中右侧的箭头)。
完整流程(从前端请求到后端响应):
前端发送请求:http://localhost:90/api/depts(访问 Nginx 的 90 端口);
Nginx 监听 90 端口,收到请求;
Nginx 匹配 location ^~ /api/(因为请求路径以 /api/ 开头);
执行 rewrite:把请求路径从 /api/depts 改成 /depts;
执行 proxy_pass:把修改后的请求 http://localhost:8080/depts 转发给 Tomcat;
Tomcat 处理 /depts 请求(比如返回部门列表数据),并把响应结果返回给 Nginx;
Nginx 把 Tomcat 的响应结果转发给前端。
2.删除部门-接口开发
1)思路分析

2)Controller接收参数


注意事项:@RequestParam注解required属性默认为true,代表该参数必须传递,如果不传递将报错。如果参数可选,可以将属性设置为false。
/*
* 根据ID删除部门
* */
@Delete("delete from dept where id = #{id}")//#{id}:表示占位符,表示id参数的值,占位符的值会从方法参数中获取,占位符的值会替换掉#{id},表示预编译的sql
void deleteById(Integer id);
3.新增部门-接口开发
1)思路分析

JSON格式的参数,通常会使用一个实体对象进行接受
规则:JSON数据的键名与方法形参对象的属性名相同,并需要使用@RequestBody注解标识
2)Controller接受参数

注:@RequestBody注解可以将一个json格式的请求参数,直接封装到一个对象当中。
困惑点:这里有一个之前学习的时候忽略,所以一直没搞懂的点,就是为啥Dept为啥能直接设置成数据类型?
这里的 Dept dept 能直接作为方法参数,核心原因是 Dept 是一个「类」(实体类),而 dept 是这个类的「对象引用变量」—— 在 Java 中,类本身就可以作为数据类型(引用类型)来使用,用于声明变量、作为方法参数或返回值。
- Dept 是一个 Java 类(用 class 关键字定义),它是对 “部门” 这个实体的抽象,包含了部门的属性(id、name 等);
- 在 Java 中,类是 “引用数据类型” 的一种,和 String、List 一样,都可以用来声明变量或作为方法参数类型。
为什么 Dept dept 能作为方法参数?
1. Dept 作为 “数据类型”
Java 中的数据类型分为两种:
- 基本数据类型:int、double、boolean 等,直接存储值;
- 引用数据类型:类(Dept、String)、接口、数组等,存储的是对象的引用(内存地址)。
Dept 作为一个类,属于引用数据类型,因此可以像 String name 一样,用 Dept dept 来声明变量 —— 这里的 Dept 是 “类型”,dept 是 “变量名”,表示这个变量可以指向一个 Dept 类型的对象。
注:json格式的请求参数适用场景主要在POST、PUT请求中,在请求体传递请求参数。
4.修改部门-查询回显
1)查询回显
查询回显是指在前后端交互过程中,前端发送查询请求到后端,后端处理请求后将结果返回给前端,前端将返回的数据展示给用户的过程。这一过程涵盖了数据查询、传输和展示三个核心环节。
常见应用场景
表单提交后显示查询结果、实时搜索建议、数据表格的分页加载等场景均依赖查询回显。例如电商网站的搜索功能,用户输入关键词后,前端实时显示后端返回的商品列表。
2)思路分析

3)Controller接收参数

注:用@PathVariable注解来声明获取的是路径参数
注:在url中可以携带多个路径参数,如:/depts/1/0

4)修改部门数据

5)@RequestMapping方法抽取(代码优化)

注:@RequestMapping("/depts") 加在Controller 类上时,作用是为当前类中所有接口方法统一指定请求路径的前缀。
困惑点:没有搞清楚在Java中方法和构造器到底有什么区别?
| 概念 | 定义 | 核心作用 |
| 构造器 | 与类名同名、没有返回值(连void都不能写)的特殊 “函数” | 创建对象时初始化对象的属性(比如给name、age赋值) |
| 方法 | 有方法名、返回值类型(可以是void)、参数列表的普通函数 | 封装业务逻辑(比如计算、打印、查询数据) |
语法区别(代码示例对比)
以Dept类为例:
1. 构造器的语法
public class Dept {
private Integer id;
private String name;
// 构造器:与类名Dept同名,无返回值
public Dept(Integer id, String name) {
this.id = id; // 创建对象时初始化id属性
this.name = name; // 创建对象时初始化name属性
}
}
2. 方法的语法
public class Dept {
private Integer id;
private String name;
// 方法:有返回值(String)、方法名(getInfo)
public String getInfo() {
return "部门ID:" + this.id + ",部门名称:" + this.name; // 封装业务逻辑
}
}
调用时机与方式区别
1. 构造器的调用时机
只能在创建对象时通过new关键字调用,且每个对象创建时必须调用一次构造器(即使你没写,Java 会提供默认无参构造器):
// 创建Dept对象时,自动调用构造器初始化属性
Dept dept = new Dept(1, "教研部");
2. 方法的调用时机
必须在对象创建完成后,通过 “对象。方法名 ()” 调用,可以调用任意次数:
Dept dept = new Dept(1, "教研部");
// 对象创建后,调用方法
String info = dept.getInfo();
System.out.println(info); // 输出:部门ID:1,部门名称:教研部
核心结论
“构造器” 和 “方法” 完全不是一回事:
- 构造器是对象创建的 “初始化工具”,负责给对象 “初始化属性”;
- 方法是对象的 “功能工具”,负责封装 “业务逻辑”。
简单记:new对象时用构造器初始化,对象创建后用方法做事情。
四.日志技术

1.日志技术


2.Logback快速入门程序

3.配置文件详解

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%logger显示日志记录器的名称, %msg表示日志消息,%n表示换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
</appender>
<!-- 系统文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志文件输出的文件名, %i表示序号 -->
<FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
<!-- 最多保留的历史日志文件数量 -->
<MaxHistory>30</MaxHistory>
<!-- 最大文件大小,超过这个大小会触发滚动到新文件,默认为 10MB -->
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="ALL">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
4.日志级别


用@Slf4j注解,将日志记录功能封装到类中,不用再写日志记录代码,简化下面一长串代码
private static final org.slf4j.Logger log = LoggerFactory.getLogger(DeptController.class);
注:不建议用日志级别调的太低,不然日志会过多,影响日志查看!
1564

被折叠的 条评论
为什么被折叠?



