考勤管理系统在毕设选题里算是"常青树",但正因为它常见,想做出区分度反而更难。如果你正在纠结 SpringBoot 考勤管理系统怎么做、或者已经开工但卡在业务设计上,这篇文章基本能覆盖你从选题到答辩的完整链路。我会从一个把这套系统完整做完并顺利通过答辩的人的角度,把技术选型、数据库设计、核心代码实现、前后端联调、部署演示这些环节里真正坑过我的地方都摊开讲。
1. 选题之前,先搞清楚小型企业的考勤到底难在哪里
很多人选考勤管理系统当毕设,是觉得"打卡嘛,存一下时间就行"。但我把需求文档写了三版之后才意识到,考勤系统真正的复杂点根本不在打卡本身,而在"规则"。小型企业的考勤需求往往比大型公司更碎、更灵活,比如:不同部门上班时间不一样、有人要上夜班、有人经常外勤、请假要审批、加班要关联调休。这些需求如果不在设计阶段理清楚,后面写代码就是不停打补丁。
我当初自己在选题的时候,导师一句话点醒了我,他说:"考勤系统人人都做过,但大部分人做的只是一个记录表,不是管理系统。你要让老师看到你理解'管理'这两个字。"这句话直接影响了我后面所有的功能设计。
所以这个项目在动手之前,我先把需求拆成了几个维度:
- 基础数据维度:员工信息、部门信息、岗位信息,这些是系统运转的地基。
- 规则维度:上下班时间、午休时长、迟到/早退/旷工判定阈值、是否弹性打卡,这些是考勤能不能算准的关键。
- 流程维度:请假、加班、外勤、补卡审批,这些是让系统真正能落地使用的保障。
- 统计维度:日报、月报、异常记录、导出 Excel,这是使用者(尤其老板或人事)最关心的部分。
提示:如果你的毕设题名叫"xx管理系统",但需求里只有增删改查,大概率会被答辩老师问住。管理系统的核心是"流程+规则+统计",而不是界面多花哨。
把需求理到这个颗粒度,再去设计数据库和代码结构,基本就不会出现推翻重来的情况了。这一步看着不产代码,但节省的时间远超你的想象。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与工程初始化:SpringBoot 版本、JDK 和前后端分离的取舍
技术选型决定你接下来两三个月的开发体验。我的选择是:后端 Spring Boot 2.7.18 + MyBatis-Plus 3.5.x + MySQL 8.0,前端 Vue 2 + Element UI,用前后端分离的方式开发。这套组合是当前毕设项目里最稳的搭配,没有之一。
2.1 为什么是 Spring Boot 而不是 Spring MVC 或 SSM
Spring Boot 最大的价值是"约定优于配置",不需要你手动写一堆 XML 配置。但这也带来了一个问题:答辩老师很喜欢追问"Spring Boot 自动装配原理是什么",这部分你必须提前准备。简单说,Spring Boot 之所以能"零配置"启动,核心是 @SpringBootApplication 中组合了 @EnableAutoConfiguration,它会通过 spring.factories 或 AutoConfiguration.imports 文件加载大量 AutoConfiguration 类,再根据 @ConditionalOnClass、@ConditionalOnProperty 等条件注解决定哪些配置类生效。
因为考勤系统消息提醒、缓存这类功能暂时用不上,我建议:
- 数据库访问用 MyBatis-Plus,因为它内置了单表 CRUD 和分页插件,能把重复劳动砍掉一半。
- 权限认证用 Sa-Token 或 Spring Security + JWT。如果不想在权限上花太多时间,Sa-Token 的学习成本更低。
- 项目构建用 Maven,版本管理清晰,换机器也能快速
mvn clean package打 jar 包。
2.2 环境版本的一个关键教训:JDK 1.8 仍然是毕设项目的最优解
热搜词里出现了"springboot jdk1.8打包到docker desktop"和"springboot版本太高",这其实反应了一个很真实的场景:新手在环境问题上浪费了大量时间。
我的建议是用 JDK 1.8,不要用 JDK 17。原因很简单,Spring Boot 2.7.x 对 JDK 1.8 的支持非常成熟,MyBatis-Plus、各种工具类的兼容性也都默认按 1.8 适配。如果你用了 JDK 17,Spring Boot 2.x 还能跑,但 Lombok、MyBatis-Plus 低版本可能报反射相关错误;如果你直接上 Spring Boot 3.x,就会出现 javax 到 jakarta 的包名变更问题。
创建项目的时候按下面这套来:
- Spring Boot 2.7.18
- JDK 1.8
- MyBatis-Plus 3.5.3.1
- MySQL 8.0(或 5.7 也行)
- Lombok、Hutool(工具库,能省很多代码)、EasyExcel(导出报表用)
提示:如果你用的是 IDEA,创建 Spring Boot 项目时默认可能选最新版本,一定要手动改成 2.7.x。另外确保 IDEA 的 Project Structure 里 Project SDK 和 Modules 的 Language Level 都设置为 8。
2.3 前端选型:Vue 2 + Element UI 为什么是毕设最优解
前端这块,很多同学纠结要不要用 Vue 3 + Element Plus。我的判断是:除非你已经很熟悉 Vue 3,否则选 Vue 2 + Element UI 会顺利很多。原因很现实:
- 毕设系统的核心是后台管理界面,Element UI 的表格、表单、日期选择器、对话框组件非常成熟,直接查文档就能用。
- Vue 2 相关的问题解决方案在网上一搜一大把,碰到报错基本都能找到案例。
- Vue 3 + Element Plus 虽然新,但在某些依赖版本配合上会出现兼容坑,不值得在毕设阶段冒险。
一个很实用的初始化思路:用 Vue CLI 先搭出项目框架,然后从路由层面把页面划分好,比如登录页、首页布局、员工管理、考勤管理、请假管理、统计报表等模块。不用急着写业务逻辑,先把页面跳转跑通,成就感上来之后,后面的开发动力会足很多。
3. 数据库建模:考勤系统最少需要几张表,每张表为什么这么设计
考勤系统数据库设计算是一个重点,答辩老师大概率会盯着看表关系。我最终设计了 7 张核心表,不是凭空想的,是从需求一步步推演出来的。
3.1 表结构与字段
员工表 sys_user
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| username | varchar | 登录账号 |
| password | varchar | 密码(BCrypt 加密) |
| name | varchar | 姓名 |
| dept_id | bigint | 部门 ID |
| position | varchar | 岗位 |
| phone | varchar | 手机号 |
| varchar | 邮箱 | |
| status | tinyint | 0 禁用,1 启用 |
| create_time | datetime | 创建时间 |
部门表 sys_dept
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| dept_name | varchar | 部门名称 |
| parent_id | bigint | 父级部门 ID,支持树形结构 |
| sort | int | 排序号 |
考勤规则表 attendance_rule
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| rule_name | varchar | 规则名称,如"行政班" |
| work_start_time | time | 上班时间 |
| work_end_time | time | 下班时间 |
| late_threshold_minutes | int | 迟到多少分钟内算轻微迟到 |
| absent_threshold_minutes | int | 迟到超过多少分钟算旷工 |
| workdays | varchar | 周一至周五,用逗号分隔 |
| enabled | tinyint | 是否启用 |
员工排班表 attendance_schedule
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 员工 ID |
| rule_id | bigint | 考勤规则 ID |
| schedule_date | date | 排班日期 |
打卡记录表 attendance_record
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 员工 ID |
| clock_in_time | datetime | 上班打卡时间(可空) |
| clock_out_time | datetime | 下班打卡时间(可空) |
| clock_in_status | varchar | NORMAL, LATE, MISSING |
| clock_out_status | varchar | NORMAL, EARLY, MISSING |
| work_date | date | 考勤日期 |
| remark | varchar | 备注 |
请假申请表 leave_request
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 申请人 ID |
| leave_type | varchar | SICK, PERSONAL, ANNUAL, OTHER |
| start_time | datetime | 开始时间 |
| end_time | datetime | 结束时间 |
| reason | varchar | 事由 |
| status | tinyint | 0 待审批,1 通过,2 驳回 |
| approver_id | bigint | 审批人 ID |
加班申请表 overtime_record
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 员工 ID |
| apply_date | date | 加班日期 |
| start_time | datetime | 开始时间 |
| end_time | datetime | 结束时间 |
| duration_hours | decimal | 加班时长(小时) |
| status | tinyint | 审批状态 |
| remark | varchar | 备注 |
考勤汇总表 attendance_summary
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 员工 ID |
| summary_month | varchar | 统计月份,格式 yyyy-MM |
| work_days | int | 应出勤天数 |
| actual_days | int | 实际出勤天数 |
| late_count | int | 迟到次数 |
| early_count | int | 早退次数 |
| missing_count | int | 缺卡次数 |
| leave_days | decimal | 请假天数 |
| overtime_hours | decimal | 加班总时长 |
3.2 为什么需要排班表
这是我最想强调的一点。很多考勤系统直接把考勤规则挂在员工表上,这样不是不行,但遇到"下个月某个部门整体换班"这种需求,就得改员工表,非常被动。
排班表相当于一个"中间层":员工和日期确定一条排班记录,排班记录关联考勤规则。这样,同一个员工不同日期可以有不同的上下班时间,也支持临时调整某一天的值班安排。你在答辩时提到"通过排班表实现弹性考勤规则",这本身就是加分的点。
3.3 打卡记录与汇总表分离
打卡记录是流水数据,只增不删,数据量会持续上涨;汇总表是统计结果,可以按月定时生成。两张表分开之后,好处很明显:列表页展示用汇总表非常快,不会因为关联和计算拖慢;排查某一天异常时再去查明细表。这也符合实际开发中常见的"读模型与写模型分离"思路,虽然听起来有点重,但在考勤场景下很有价值。
4. 核心业务实现:从打卡到月度统计这条主链路是怎么走通的
这一节是整个系统含金量最高的部分,我会把实际开发中主链路的关键代码和判断逻辑完整拆开。你要能把这个链路讲清楚,答辩状态基本稳了。
4.1 打卡接口的设计:不止是往数据库插一条时间
打卡模块表面上只是维护"上班打卡""下班打卡"两个动作,但真正实现的时候要考虑三类问题:
- 怎么区分这次打卡是上班还是下班?
- 怎么判定异常状态?
- 怎么处理"重复打卡"和"数据覆盖"?
我的方案是:上班打卡和下班打卡共用一张记录表,用 work_date(考勤日期)定位。工作日的考勤日期是当天;但考虑到跨天班次,考勤日期也可以单独由规则配置生成。
核心逻辑是这样:
java复制@Transactional
public ClockResult clock(ClockRequest request) {
Long userId = request.getUserId();
LocalDate today = LocalDate.now();
// 1. 获取当前用户的排班和规则
AttendanceSchedule schedule = this.getTodaySchedule(userId, today);
if (schedule == null) {
throw new ServiceException("今日无排班,请先联系管理员设置排班");
}
AttendanceRule rule = schedule.getRule();
// 2. 查询当日打卡记录,判断是上班还是下班
LambdaQueryWrapper<AttendanceRecord> wrapper = Wrappers.lambdaQuery();
wrapper.eq(AttendanceRecord::getUserId, userId)
.eq(AttendanceRecord::getWorkDate, today);
AttendanceRecord record = attendanceRecordMapper.selectOne(wrapper);
if (record == null) {
record = new AttendanceRecord();
record.setUserId(userId);
record.setWorkDate(today);
LocalTime now = LocalTime.now();
// 3. 上班打卡判断
record.setClockInTime(LocalDateTime.now());
if (now.isAfter(rule.getWorkStartTime().plusMinutes(
rule.getLateThresholdMinutes()))) {
record.setClockInStatus(ClockStatus.LATE.name());
} else {
record.setClockInStatus(ClockStatus.NORMAL.name());
}
attendanceRecordMapper.insert(record);
return ClockResult.of(ClockType.IN, record.getClockInStatus());
}
// 4. 已存在上班打卡,则记录下班
if (record.getClockOutTime() != null) {
throw new ServiceException("今日已打卡完成,请勿重复操作");
}
LocalTime now = LocalTime.now();
record.setClockOutTime(LocalDateTime.now());
LocalTime endTime = rule.getWorkEndTime();
// 如果存在午休时间,可以在这里扣除
if (now.isBefore(endTime.minusMinutes(rule.getEarlyThresholdMinutes()))) {
record.setClockOutStatus(ClockStatus.EARLY.name());
} else {
record.setClockOutStatus(ClockStatus.NORMAL.name());
}
attendanceRecordMapper.updateById(record);
return ClockResult.of(ClockType.OUT, record.getClockOutStatus());
}
注意:打卡判断"是否迟到"应该基于该员工当天的排班规则,而不是用固定时间。这也是为什么排班表能成为系统亮点,因为它保证了多人多班次场景下打卡逻辑的准确性。
4.2 异常状态怎么定:LATE、EARLY、MISSING 与旷工
考勤状态的判定逻辑,要解释清楚"从打卡时间到状态"的映射关系:
- 正常(NORMAL):上班打卡时间在规则上班时间或之后的一小段容忍范围内,下班打卡时间在规则下班时间或之后的容忍范围内。
- 迟到(LATE):上班打卡时间超过规则上班时间,但尚未达到旷工阈值。
- 早退(EARLY):下班打卡时间早于规则下班时间减去早退阈值。
- 缺卡(MISSING):缺少上班卡或下班卡其中任意一条。
在统计逻辑里,我会把"缺卡"再汇总统计,因为缺卡不等于旷工,可能只是忘记打卡,需要通过补卡流程修正。
4.3 定时任务生成月度汇总
考勤记录是流水,汇总表是月结。我用 Spring Boot 自带的 @Scheduled 定时任务,在每月 1 日凌晨执行一次上月汇总。这一步也是完全可以展开讲的一个技术细节。
java复制@Component
public class AttendanceSummaryTask {
private final AttendanceRecordMapper recordMapper;
private final AttendanceSummaryMapper summaryMapper;
private final UserMapper userMapper;
@Scheduled(cron = "0 0 1 1 * ?")
public void generateLastMonthSummary() {
YearMonth lastMonth = YearMonth.now().minusMonths(1);
String month = lastMonth.toString();
// 删除旧汇总,避免重复生成
LambdaQueryWrapper<AttendanceSummary> deleteWrapper = Wrappers.lambdaQuery();
deleteWrapper.eq(AttendanceSummary::getSummaryMonth, month);
summaryMapper.delete(deleteWrapper);
List<SysUser> users = userMapper.selectList(null);
for (SysUser user : users) {
AttendanceSummary summary = buildSummary(user.getId(), month);
summaryMapper.insert(summary);
}
}
private AttendanceSummary buildSummary(Long userId, String month) {
AttendanceSummary summary = new AttendanceSummary();
summary.setUserId(userId);
summary.setSummaryMonth(month);
// 统计应出勤天数:来自排班表+规则表
Integer workDays = scheduleMapper.countWorkDays(userId, month);
summary.setWorkDays(workDays);
// 统计实际出勤:打上班卡且状态不是 MISSING 的天数
Integer actualDays = recordMapper.countActualDays(userId, month);
summary.setActualDays(actualDays);
// 统计迟到、早退、缺卡次数
summary.setLateCount(recordMapper.countLate(userId, month));
summary.setEarlyCount(recordMapper.countEarly(userId, month));
summary.setMissingCount(recordMapper.countMissing(userId, month));
// 请假天数与加班时长
summary.setLeaveDays(leaveRequestMapper.sumDays(userId, month));
summary.setOvertimeHours(overtimeMapper.sumHours(userId, month));
return summary;
}
}
这套代码的思路是"先把所有员工遍历一遍,再按月份条件汇总"。如果员工很多,可以改成按部门循环批量处理,但毕设规模用遍历完全没问题。在答辩时,你至少可以说清楚"为什么用定时任务而不是实时统计"——因为汇总数据不需要实时,定时生成能大幅减轻数据库压力。
4.4 请假、加班与补卡审批流程
审批流程是系统迈入"管理"层面的关键。我设计了一个统一的流程字段 status,并用一个简单的 审批状态 枚举来维护:
- 0 待审批
- 1 已通过
- 2 已驳回
在代码里用"申请人提交 -> 审批人查询待办 -> 审批人通过/驳回"三步来实现。不需要引入 Flowable 这种重量级工作流引擎,理由很简单:小型企业考勤审批是单级审批,用状态字段足够,引入工作流反而会把答辩复杂度抬高。如果你确实对"springboot使用flowable"这类技术感兴趣,可以在项目展望部分提一嘴,作为扩展方向,而不是落进核心代码里。
审批相关接口,我会拆成员工端和管理端两个维度:
- 员工端:提交请假申请、查看我的申请列表、查看审批进度。
- 管理端:待审批列表、通过/驳回操作、按部门查看请假汇总。
4.5 补卡与异常处理:这个功能是加分项
真实考勤里,员工漏打卡很常见。系统里我加了"补卡申请"功能:员工提交某天的补卡说明,审批通过后,管理员可以在后台把该天的 MISSING 状态修正为 NORMAL,并在 remark 里标记为补卡。这个小功能花不了多少时间,但能让你的系统在答辩老师眼里"有业务思维"。
5. 前后端联调与报表导出:真正磨人的都是小细节
前后端分离的项目,后端接口写得再漂亮,联调阶段也会遇到一堆看似不起眼但特别耗时的问题。我把自己实际踩过的坑列一下,省得你再走一遍。
5.1 时间格式统一问题
LocalDateTime 默认序列化之后是一串带 T 的格式,前端 date-picker 组件很多时候读取不了。解决方式是在后端做全局配置:
java复制@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.serializers(new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.deserializers(new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.serializers(new LocalDateSerializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd")));
};
}
}
另外,前端的日期选择器传值到后端,如果 @RequestBody 里的日期字段类型没对齐,大概率也会报 400。建议前端的日期组件统一使用 value-format="yyyy-MM-dd HH:mm:ss",保持跟后端格式一致。
5.2 跨域配置
前后端分离开发时,前端跑在 8080 端口,后端在 8081,跨域问题一定会出现。我的做法是写一个全局 CORS 配置类:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
注意:如果后端用了 Sa-Token 或 JWT 这类基于 header 的认证方式,跨域配置里必须把
allowedHeaders设为*,否则前端请求带不上Authorization或satokenheader,登录接口一调就 401。
5.3 报表导出:EasyExcel 让导出变得很简单
月度汇总表导出 Excel,我用的是 EasyExcel。相比 POI 直接写,EasyExcel 的 API 更友好,写起来速度快,也支持大数据量导出。核心代码是这样:
java复制public void exportSummary(String month, HttpServletResponse response) throws IOException {
List<AttendanceSummaryVO> list = summaryMapper.selectSummaryWithUser(month);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("考勤汇总_" + month, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), AttendanceSummaryVO.class)
.sheet("考勤汇总")
.doWrite(list);
}
5.4 前端接口封装与请求拦截
前端联调阶段,建议统一封装 axios,在 request 拦截器里带上 token,在 response 拦截器里统一处理错误码。这样后端返回"未登录"或"无权限"时,前端会自动跳转登录页或弹出提示,不会出现一个接口调不通就卡住整个页面的情况。
javascript复制import axios from 'axios'
import { Message } from 'element-ui'
import router from '@/router'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 10000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = 'Bearer ' + token
}
return config
}, error => Promise.reject(error))
service.interceptors.response.use(response => {
const res = response.data
if (res.code === 200) {
return res
}
if (res.code === 401) {
localStorage.removeItem('token')
router.push('/login')
}
Message.error(res.msg || '系统异常')
return Promise.reject(new Error(res.msg || 'Error'))
}, error => Promise.reject(error))
export default service
6. 从能跑到能演示:打包部署、答辩演示和项目亮点包装
开发完不等于毕设完成,你得让系统在答辩现场稳定跑起来,并且能在几分钟内讲清楚系统的核心价值。
6.1 后端打包与部署
后端打包推荐用 Maven 的 package 命令,生成 jar 包后,用 nohup java -jar attendance-system.jar --server.port=8081 & 启动。注意打包前要检查配置文件 application.yml 里的数据库连接、端口、文件上传路径这些环境相关的配置,避免换了一台机器启动就报错。
如果你有 Docker 环境,也可以用 Dockerfile 打镜像,但毕设答辩现场建议别在 Docker 上冒险,直接 jar 包跑最稳。等答辩过了再研究 Docker 部署也不迟。
6.2 前端打包与部署
前端用 Vue CLI 构建,执行 npm run build 生成 dist 目录。最简单的部署方式是用 Nginx 托管 dist,并把 /api 反向代理到后端:
nginx复制server {
listen 8080;
server_name localhost;
location / {
root /usr/share/nginx/html/attendance-front;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://localhost:8081/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6.3 答辩演示脚本怎么准备
演示的时候别一上来就点菜单。我当时的顺序是:
- 登录,进入首页,先看仪表盘里的统计卡片(出勤率、迟到趋势)。
- 进入员工管理,新建一个员工,再给该员工排班,演示"基础数据如何影响考勤"。
- 切换成该员工账号(或者直接演示打卡页面),现场打一次卡,演示正常打卡和迟到打卡的区别。
- 查看今日考勤记录,确认状态是否正确。
- 提交一个请假申请,管理员审批,再回到考勤列表,确认请假期间的考勤状态。
- 进入月度汇总,点击导出 Excel,展示报表。
- 最后打开数据库表,简单展示几张表的记录关系。
这样一条链路走下来,老师能理解"系统不是一堆页面拼凑,而是完整的业务闭环"。这个演示逻辑比任何花哨的特效都有说服力。
6.4 项目亮点和扩展方向
说到最后,我觉得做毕设最大的收获不是那几十个接口,而是养成"先想清楚再动手"的习惯。考勤系统看似简单,但它在业务上覆盖了规则引擎、审批流、定时任务、报表导出、权限控制这些很典型的系统设计元素,是一个投入产出比很高的题目。
如果你做完基础版还想加亮点,我建议顺着这几个方向:加入 Flowable 工作流引擎支撑多级审批、接入 Redis 做打卡状态的缓存、用 ECharts 做更丰富的考勤趋势图表、支持小程序端打卡。这些方向在热搜词里也是高频概念,答辩时提出来会很自然。
最后再分享一个我踩过的坑:别把登录模块做到最后才补。几乎所有管理系统都有登录需求,但很多同学一开始光顾着做业务表,结果联调的时候发现没有登录态,所有接口都裸奔。先花半天把基于 Sa-Token 或 JWT 的登录认证跑通,后面的接口开发都会舒服很多。
