毕业设计被分到一个“高校宣讲会管理系统”这类题目很多人的第一反应是“这不就是个CRUD”。说实话第一眼确实平平无奇但真的把需求捋清楚、把前后端跑通、再把论文和答辩撑住这里面的水反而挺深。我前后折腾了一个多月从数据库设计到前端页面从本地联调到服务器部署踩了不少坑也总结出一些能直接复用的套路。这篇文章就把整个项目的核心设计、关键实现、部署流程和排查经验完整复盘一遍给正在做类似题目的同学一个能抄作业的路线图。1. 项目定位与整体设计思路1.1 需求背后的真实场景宣讲会校园招聘宣讲会这件事在学生眼里是“哪里开、几点开、谁来讲”但在就业办老师和企业HR眼里是一整套流程企业申请场地、发布宣讲信息、学生浏览报名、现场签到、会后反馈统计。如果全靠人工老师每天要接无数个电话展会当天签到表乱成一团会后统计出勤率更是灾难。所以这个系统的核心价值是把“信息发布—学生报名—现场签到—数据统计”这条链路线上化。基于这个定位典型角色划分非常清晰管理员审核企业、审核宣讲会、管理公告、查看统计数据企业用户注册入驻、申请/发布宣讲会、查看报名学生列表、查看签到情况学生用户浏览宣讲会、按条件筛选、在线报名/取消报名、现场签到、填写反馈我当时拿到题目以后没有急着写代码先花了一天把角色和功能清单列出来这一步很有必要。很多同学的毕业设计代码量不小但答辩老师一问“你系统解决了什么业务问题”就哑火根本原因就是没从场景出发去设计功能。1.2 技术栈选择的理由这个题目技术栈是固定的组合SpringBoot Vue MySQL。这个组合能成为绝大多数高校毕设的标准配置不是因为它最先进而是因为它生态成熟、资料丰富、前后端职责清晰非常适合在有限时间内交付完整项目。SpringBoot负责后端接口内置Tomcat省掉大量SSM阶段繁琐的XML配置开发效率高Vue做前端SPA单页应用页面交互流畅组件化开发方便后期维护MySQL做数据持久化配合MyBatis-Plus能减少大量重复SQL认证方案我用的是JWT 拦截器比Spring Security配置简单也方便答辩时讲清楚原理另外我补充了一个细节前端用Vue 2 Element UI。选Vue 2不是因为新而是Element UI在Vue 2下最稳定网上类似的管理系统案例最多遇到问题基本都是搜得到答案的Vue 3对应的是Element Plus虽然也不错但如果你是第一次做完整项目Vue 2的学习曲线更平缓。1.3 功能模块划分整个系统在功能上分成前台和后台两条线这个思路也是毕设论文里“系统设计”章节的主干前台学生端注册登录、宣讲会列表带筛选分页、宣讲会详情、在线报名、我的报名、签到、公告通知后台管理端企业审核、宣讲会管理发布/修改/下线、报名审核、签到管理、公告管理、用户管理、数据统计看板把功能分清楚以后后面的数据库设计就顺了——有业务动作就需要表和接口。我最终建了6张核心表下面详细拆解。2. 数据库设计核心表与字段拆解2.1 实体关系梳理数据库设计是这类项目的命根子答辩老师最常翻的就是ER图和表结构。我的做法是先识别实体关系一个企业可以发布多条宣讲会一条宣讲会可以被多个学生报名一个学生也可以报名多条宣讲会——所以用户、宣讲会、报名记录之间天然是多对多关系必须拆出关联表。最终的实体集合如下用户表sys_user统一存放管理员、企业、学生三种角色用role字段区分企业信息表company存放企业名称、简介、行业、联系人等资料宣讲会表lecture存放宣讲会主题、时间、地点、企业ID、状态等报名记录表registration一条报名记录外键关联学生用户和宣讲会签到记录表checkin记录宣讲会现场的签到情况公告表notice存放系统公告、通知2.2 核心表结构与字段说明讲一下几个关键表和最终确定的字段。用户表是基础所有登录都靠它我直接给出一版可用的建表SQLCREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码(BCrypt加密), real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL COMMENT 手机号, role tinyint(4) NOT NULL DEFAULT 2 COMMENT 角色:0管理员 1企业 2学生, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态:1正常 0禁用, company_id bigint(20) DEFAULT NULL COMMENT 关联企业ID(企业角色用), create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;宣讲会表是整个系统的业务核心字段设计一定要把“筛选条件”考虑进去否则做前端筛选时SQL会写得很难看CREATE TABLE lecture ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键, title varchar(200) NOT NULL COMMENT 宣讲会标题, company_id bigint(20) NOT NULL COMMENT 归属企业ID, content text COMMENT 宣讲会内容详情, location varchar(200) DEFAULT NULL COMMENT 宣讲地点, start_time datetime DEFAULT NULL COMMENT 开始时间, end_time datetime DEFAULT NULL COMMENT 结束时间, capacity int(11) DEFAULT 100 COMMENT 可报名人数上限, status tinyint(4) DEFAULT 0 COMMENT 状态:0待审核 1已发布 2已取消 3已结束, view_count int(11) DEFAULT 0 COMMENT 浏览数, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_company (company_id), KEY idx_start_time (start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宣讲会表;这里的status字段是业务的关键我在实现时给它定义了四种状态前端按钮发布、取消、结束都是根据这个状态来切换的。不要用String存状态用tinyint加注释既省空间又清晰。2.3 表关系设计中的几个坑第一报名记录表要加唯一约束。这是整个系统最容易出bug的地方——学生重复报名。我在registration表上加了UNIQUE KEY uk_student_lecture (student_id, lecture_id)这样即使后端逻辑漏判数据库层面也会拦一次。这种“双保险”思路在毕设答辩时说出来很加分。第二签到表和报名表不要合并。有些同学嫌表多麻烦把签到状态存到报名表里。短期看省事但业务上报名和签到是两个动作、两个时间点拆开更清晰——报名可能在宣讲会前一周签到在当天而且一个报名被取消后签到记录应该保留合并反而让逻辑变复杂。第三企业表和用户表分离。企业账号注册后企业信息联系人、公司介绍和登录账号信息变动频率是不一样的分开存便于维护。当然也有方案把企业字段直接冗余进user表但扩展性很差我最终选择了拆表。3. 后端实现SpringBoot接口与核心逻辑3.1 工程分层与目录结构后端我用的是经典的三层结构Controller接口层、Service业务层、Mapper数据访问层加上entity、dto、common、config几个辅助包。目录结构是这样的src/main/java/com/example/lecture ├── controller # 接口入口只做参数接收和结果返回 │ ├── AuthController.java │ ├── LectureController.java │ └── StudentController.java ├── service # 业务逻辑层核心判断都放这里 ├── mapper # MyBatis-Plus Mapper接口 ├── entity # 数据库实体类 ├── dto # 请求/响应对象避免实体直接暴露 ├── common # 统一返回结果、异常处理、常量 ├── config # 拦截器、跨域等配置 └── util # JWT工具类等这里要强调一个习惯Controller里不要写业务代码。我见过不少同学的毕设Controller里直接拼SQL、写if else判断答辩时被老师一问逻辑就乱了。业务判断比如“报名时检查是否已满”“审核时检查状态是否合法”全部下沉到ServiceController只负责接收参数和调用Service这样代码结构清晰论文里的“系统设计”章节也好写。3.2 统一返回结果与异常处理前后端分离项目接口返回格式必须统一否则前端处理异常会非常痛苦。我定义了一个ResultT类结构如下{ code: 200, message: 操作成功, data: { ... } }配合一个RestControllerAdvice全局异常处理器把业务异常如“报名人数已满”和系统异常统一转成这个格式。这样前端Axios拦截器只需要判断code是否为200其余全部交给统一的错误提示。这个设计是答辩高频考点建议认真实现并写进论文。3.3 JWT认证与拦截器登录认证我选了JWT方案整个流程是用户输入账号密码后端用BCrypt校验密码注册时加密存储校验通过后生成Token包含用户ID、用户名、角色返回给前端前端把Token存在localStorage每次请求在请求头里带上Authorization: Bearer token后端写一个拦截器拦截/api/**下除登录注册外的所有请求解析并校验Token拦截器里的关键代码大致是这样public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } try { // 解析token拿到用户信息存入ThreadLocal Integer userId JwtUtil.parseToken(token); UserContext.set(userId); return true; } catch (Exception e) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; } } }注意两个细节跨域预检请求OPTIONS必须放行否则前端调接口时会一直报跨域错误解析完Token要把用户ID存起来后面查询“我的报名”“我发布的宣讲会”时直接取不要再传参。这两个细节是我自己踩坑后加上的很多人忽略了所以联调时卡很久。3.4 核心业务接口与分页实现系统核心接口大约17个我把它们按角色归类模块接口路径说明认证/api/auth/login登录返回Token认证/api/auth/register学生注册、企业注册申请宣讲会/api/lecture/page分页条件查询宣讲会宣讲会/api/lecture/save企业发布宣讲会宣讲会/api/lecture/audit管理员审核报名/api/registration/apply学生报名报名/api/registration/cancel取消报名报名/api/registration/list查看我的报名报名/api/registration/students企业查看报名学生名单签到/api/checkin/submit现场签到分页查询用的是MyBatis-Plus的Page对象配合LambdaQueryWrapper做条件拼接。以宣讲会列表为例前端传keyword标题模糊、companyId、status等参数后端这样处理public PageLecture pageLecture(LectureQuery query) { LambdaQueryWrapperLecture wrapper new LambdaQueryWrapper(); // 关键字模糊查询 if (StrUtil.isNotBlank(query.getKeyword())) { wrapper.like(Lecture::getTitle, query.getKeyword()); } // 状态过滤 if (query.getStatus() ! null) { wrapper.eq(Lecture::getStatus, query.getStatus()); } // 时间排序最新的在前面 wrapper.orderByDesc(Lecture::getCreateTime); return lectureMapper.selectPage(new Page(query.getPageNum(), query.getPageSize()), wrapper); }这里要提醒MyBatis-Plus的selectPage会自动拼limit但count查询在数据量大时会慢。毕设数据量小无所谓但如果你论文里写了“性能优化”可以把这个点作为切入点写分页优化方案。3.5 报名与签到的并发控制报名逻辑看似简单但隐藏一个典型问题并发下超卖。比如宣讲会容量100人最后一个名额如果两个学生同时提交两条SQL都读到当前报名人数99然后都insert成功就会多出一个人。真实场景并发量不大但考研式的思考还是要有。我的处理方案是报名前先SELECT COUNT(*)判断是否已满然后在registration表插入时依赖2.3节说的唯一约束兜底更进一步的做法是在lecture表上加一个current_count字段用UPDATE lecture SET current_count current_count 1 WHERE id ? AND current_count capacity这样的原子更新语句来判断是否成功。这个方案在答辩时讲出来老师会觉得你真的考虑过生产问题。签到逻辑相对简单——管理员在后台点“签到”或者现场学生扫码毕设简化成输入学号后端先判断学生是否已报名该宣讲会再判断是否已经签到最后插入签到记录。判断“是否已报名”这步千万别漏否则没报名的学生也能签到业务就乱了。4. 前端实现Vue页面与前后端联调4.1 Vue工程项目结构前端我用Vue 2 Vue Router Vuex Element UI Axios。工程初始化用Vue CLI项目结构大致如下src ├── api # 所有接口请求封装按模块拆文件 │ ├── auth.js │ ├── lecture.js │ └── registration.js ├── router # 路由配置带导航守卫 ├── store # Vuex状态管理存用户信息 ├── views # 页面组件 │ ├── Home.vue │ ├── LectureDetail.vue │ └── admin/ # 后台管理页面 ├── utils │ ├── request.js # Axios封装 │ └── auth.js # Token存取工具 └── main.js我把接口请求全部集中在api目录不在页面里直接写axios。这样做的直接好处是后端接口改了路径只需要改一个文件答辩演示时代码也显得规范。实际上我后期改了两版接口全靠这个习惯省了大量时间。4.2 Axios封装与Token注入前端联调阶段最常见的两个问题就是跨域和Token丢失。Axios封装里把这两个问题一并解决// utils/request.js import axios from axios import { Message } from element-ui import { getToken, clearToken } from ./auth const service axios.create({ baseURL: /api, // 开发环境走Vue代理生产环境走Nginx代理 timeout: 10000 }) // 请求拦截器自动带Token service.interceptors.request.use(config { const token getToken() if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截器统一处理错误码 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { Message.error(登录已过期请重新登录) clearToken() window.location.href /login } return Promise.reject(error) } )baseURL: /api这个写法很关键。开发环境在vue.config.js里配置代理把/api转发到http://localhost:8080规避跨域生产环境把前端打包后丢给NginxNginx把/api反向代理到后端服务。这样前端代码里永远不用写后端的真实地址用什么环境都能跑。4.3 路由守卫与权限控制前端权限控制的核心是路由导航守卫。因为我的系统有三类角色所以路由表里给每条记录打了roles标签导航守卫里校验当前用户角色是否匹配router.beforeEach((to, from, next) { const token getToken() // 本地没有Token强制去登录页 if (!token to.path ! /login) { next(/login) return } // 已登录但访问登录页跳回首页 if (token to.path /login) { next(/) return } // 角色校验admin相关页面只有管理员能进 if (to.meta.roles !to.meta.roles.includes(store.state.user.role)) { next(/403) return } next() })这里有个毕设常见的坑后端接口也要做权限校验不能只靠前端隐藏按钮。很多同学前端把“审核按钮”藏起来就认为安全了实际上用户完全可以直接请求/api/lecture/audit接口。我的做法是在后端的审核接口里判断当前用户角色是不是管理员不是直接返回403。前端的角色控制只是体验优化后端校验才是安全边界这个点一定要在论文里写明。4.4 核心页面流程与关键交互宣讲会列表页是最核心的页面。我用Element UI的el-card做卡片式布局展示企业logo占位、标题、时间、地点、报名进度已报/容量右上角根据登录状态显示“报名/已报名/名额已满”按钮。筛选器放在页面顶部标题关键字输入框、下拉选择“状态”、分页器放在底部。这些组件Element UI都现成主要是把数据流转接顺。宣讲会详情页进入时调详情接口后端返回基本信息企业信息报名人数前端把富文本内容渲染出来。这里提醒一下如果宣讲会content存的是普通文本直接用{{}}插值就行如果要支持富文本后端返回HTML字符串后前端要加v-html但注意XSS风险典型做法是对内容做过滤。我为了演示效果用了富文本编辑器wangEditor同时在后端对内容做了简单的脚本标签清洗。后台管理页面就是经典的管理端布局左侧菜单宣讲会审核、企业审核、公告管理、数据统计右侧内容区。表格用el-table配合对话框做审核驳回。数据统计页我用ECharts画了“每月宣讲会数量柱状图”和“各企业宣讲会占比饼图”这两张图撑起了论文里“系统测试与运行结果”章节的半边天。4.5 前后端联调疑问速查联调阶段我整理了三个高频问题直接给结论问题一前端请求一直报跨域。先分清是开发环境还是生产环境。开发环境检查vue.config.js的proxy配置是否正确生产环境检查Nginx的location /api代理是否生效。还有一个隐蔽问题后端控制器没加CrossOrigin或全局CORS配置时开发环境的预检请求也会挂但我用代理方案就不存在这个问题所以推荐优先用代理。问题二接口请求到了但返回404。大概率是路径不匹配或者前端baseURL配了完整域名导致代理没命中。检查浏览器Network里请求的URL如果是http://localhost:8080/api/xxx这种那就说明代理没问题如果请求到了http://localhost:8080/xxx少了/api说明baseURL配错了。问题三表格数据渲染不出来。先看Network响应体确认是否返回了JSON再确认前端取的字段名是否和后端一致——Java的驼峰命名createTime和数据库下划线create_time在MyBatis-Plus开启驼峰映射后会自动转换但如果你手写了Map返回key可能就变成下划线了所以统一用实体类传输最稳。5. 数据库初始化与本地运行5.1 数据库环境准备MySQL安装算是本项目的“第一道坎”尤其是Windows环境。我试下来最靠谱的方式是去官网下载MySQL Community Server或者用安装包安装安装时选择“Developer Default”记得设置UTF-8字符集。安装完以后验证一下mysql --version如果报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock这类错误在Windows上是服务没启动去“服务”里启动MySQL服务在Linux上先确认mysqld进程是否存在或者直接systemctl start mysqld同时检查/etc/my.cnf里的socket路径是否一致。5.2 建库与初始数据导入项目包里自带一个lecture_system.sql文件。导入方式有两种我用命令行mysql -u root -p lecture_system.sql导入成功后检查一下SHOW DATABASES; USE lecture_system; SHOW TABLES;如果导入时报字符集或编码错误先把SQL文件用文本编辑器转成UTF-8无BOM格式再重试。这个文件里除了表结构还有一条管理员初始账号admin/admin123和几条测试用宣讲会数据负责演示时不用自己造数据。5.3 后端配置调整确保本地能跑起来的关键一步是配置application.yml里的数据源。我用的是MySQL 8.x驱动需要带cj的版本配置如下spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/lecture_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码serverTimezoneAsia/Shanghai这个参数一定要加否则默认时区会导致时间字段差8个小时这在做宣讲会时间显示的时候特别明显。启动后端后访问http://localhost:8080/api/lecture/page如果能返回JSON说明环境已经通了。5.4 前端依赖安装与启动前端需要Node环境我用的是Node 14配合Vue CLI 4。这类项目最常踩的坑是依赖安装失败常见原因和解决办法npm源太慢npm config set registry https://registry.npmmirror.com依赖版本冲突删除node_modules和package-lock.json重新npm installNode版本过高导致node-sass报错如果项目里用了sassNode 16以上容易出问题我当时干脆放弃了scss直接用Element UI自带的样式。依赖装好后直接npm run serve开发服务器默认跑在8081端口Vue CLI自动避让后端的8080打开页面就能看到登录界面了。6. 服务器部署与上线流程6.1 后端打包与运行本地跑通以后部署到Linux服务器是毕设加分项。我用一台CentOS 7服务器做演示目标是把后端jar包直接丢上去运行。后端先打成jar包。在项目根目录执行mvn clean package -DskipTests打包产物在target/lecture-api.jar。上传到服务器后用nohup启动nohup java -jar lecture-api.jar --spring.profiles.activeprod app.log 21 这里我给生产环境单独建了一个application-prod.yml把数据库地址改成服务器的地址。有一个坑要提醒服务器防火墙和云安全组都要放行8080端口否则浏览器永远访问不到这个我排查了半小时才反应过来属于“基础但致命”的问题。6.2 Nginx配置前端与反向代理前端打包npm run build打包出的dist目录上传到服务器然后配置Nginx。核心配置如下server { listen 80; server_name 你的服务器IP或域名; # 前端静态文件 location / { root /usr/share/nginx/lecture-web; index index.html; try_files $uri $uri/ /index.html; # 关键支持Vue Router的history模式 } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files $uri $uri/ /index.html这行是关键中的关键。Vue Router如果用了history模式刷新页面时浏览器会直接请求某个前端路由路径比如刷新/lecture/3Nginx如果不做try_files回退会返回404。配了这一行以后所有前端路径都回退到index.html由Vue自己解析路由。6.3 部署后的验证清单部署完成后我按这个顺序自检浏览器访问http://IP/能看到登录页用初始账号登录能正常跳转访问宣讲会列表接口数据正常返回刷新一个二级页面路径不出现404查看后端日志确认没有数据库连接异常如果第2步失败但第1步成功基本是后端服务没起来或者/api代理没生效如果第3步失败优先看后端日志app.log数据库账号权限错误是高频原因。7. 常见问题与排查技巧整合7.1 高频问题速查表把我在这类项目里遇到的高频问题汇总成一张表方便你快速对照现象可能原因排查思路与解决前端请求报405 Method Not Allowed请求方式与后端接口不一致POST写成GET查看后端Controller的PostMapping/GetMapping对照Network请求方式登录成功但列表请求401刷新后Token丢失检查请求拦截器是否从localStorage取Token检查Token过期时间把有效时长设为7天数据库中文乱码连接URL没指定字符集url里加characterEncodingutf8建表统一utf8mb4端口被占用上次进程没关Linux用netstat -tlnp | grep 8080查PIDkill -9前端页面白屏路由配置错误或JS加载失败打开浏览器Console看报错确认publicPath是否配置正确时间显示差8小时MySQL时区问题连接URL加serverTimezoneAsia/Shanghai数据显示但不更新浏览器缓存强刷页面CtrlF5前端打包加哈希文件命名7.2 我一定会主动检查的隐蔽问题第一个隐蔽问题是数据库密码含特殊字符。如果你数据库密码里有或#写在application.yml里可能会被YAML解析成注释或特殊字符串导致连接失败。解决办法是用application.properties的url格式或者对密码做URL编码。第二个隐蔽问题是上传文件目录没有写权限。系统里有公告图片上传功能如果上传目录是非root用户可写路径Nginx运行用户nginx又无权读取就会出现“图片上传成功但页面加载403”。我最后的处理是把上传目录放到/usr/share/nginx/lecture-web/upload下统一由Nginx管理权限就没有这种分裂问题了。第三个隐蔽问题是JWT密钥硬编码。开发时JWT密钥写死在工具类里上线前应该挪到application-prod.yml配置里答辩时可以主动讲“我把密钥外置到配置文件避免源码泄露风险”这是加分表达。7.3 论文与答辩准备的几点建议这个项目包里有现成的论文模板但直接照抄很难过。我的建议是把论文的第三章和第四章当重点《系统设计》架构图、功能模块图、数据库ER图、表结构和《系统实现》核心代码片段运行截图。截图一定要用自己真实运行的系统界面演示时也要用自己导入的数据不要拿着说明书念代码。答辩演示时我建议提前准备好三个闭环场景学生闭环注册→浏览宣讲会→报名→管理员模拟签到→查看报名状态企业闭环注册→发布宣讲会→查看报名学生名单管理员闭环审核企业→审核宣讲会→统计看板这三个流程走下来基本上覆盖了系统80%的功能也对应论文里的核心测试用例。演示踩坑的教训是提前把测试账号准备好现场不要输入注册流程注册验证等逻辑用手机演示既费时又容易出意外。写在最后回头来看这个项目技术上并没有多高深无非是CRUD加上一套权限、一份分页、两次状态流转。但“做完”和“做明白”是两回事数据库约束兜底、后端角色校验、Token状态管理、Nginx路由回退这些藏在细节里的问题才是真正拉开差距的地方。我最大的体会有两个第一先花时间把业务链路和表结构想清楚比急着写代码有用得多第二毕设的意义不在于项目本身多复杂而在于你能不能把每一处设计决策背后的原因讲明白哪怕只是“为什么用tinyint存状态”“为什么报名表要加唯一约束”只要是自己实践后的结论答辩时就站得住脚。如果你正在做这个题目希望这篇复盘能帮你少踩几个坑尤其是部署和联调阶段那几道坎提前绕过去比卡住一天再搜答案舒服得多。
