前阵子帮一位读者远程看毕设项目他用了“SpringBoot SSM Thymeleaf MySQL”这套组合做了一个兼职平台碰到一堆版本、依赖、AJAX数据解析的坑。帮他调完我就想很多人选型时会低估这套组合的细节复杂度搜索引擎一看全是“基于javaweb和mysql的springboot兼职平台系统”标题等真正跑起来才发现光是把SpringBoot和MyBatis的Mapper扫描规则、Thymeleaf模板渲染、AJAX返回JSON这三件事串起来就能卡掉一批人。这篇文章我就用“SpringBoot SSM MySQL Thymeleaf Maven AJAX”这套技术栈做一个健壮可跑的兼职平台系统从功能边界、数据库设计、核心代码、AJAX改造到部署自测全流程拆开讲。适合三类人看准备做毕设/课设的在校生、想把SSM整合熟了的初级开发者以及接到类似“兼职/招聘/二手交易”项目但没时间从头理需求的技术朋友。1. 标题里的每个技术名词在项目里到底干什么1.1 先定功能边界兼职平台最少要有哪些页面和流程拿到标题先别急着敲代码。任何“平台类”系统的核心价值是连接两类用户兼职平台连的是“找兼职的学生/用户”和“发兼职的企业”。基于这个场景我按最典型的链路补全需求这是做类似项目最常见的功能切分方式也是面试官/答辩老师最容易问到的点用户端学生注册登录、浏览兼职列表、关键词搜索、申请兼职、查看我的申请记录企业端注册登录、发布兼职、管理自己发布的兼职、审核申请的学生公共模块验证码/登录态拦截、统一返回格式、异常处理页面就顺势出来了首页/列表页、登录页、注册页、企业发布页、申请记录页。复杂一点可以加管理后台但对一个“平台系统”来说先跑通上面的闭环其他都是加分项。1.2 技术名词逐个翻译成代码里的位置“SpringBootSSM”是一个很容易被混淆的说法。严格说SSM是指Spring SpringMVC MyBatis这套经典三层框架而SpringBoot是一个“把Spring生态的东西自动配置好”的脚手架。所以这个项目实际是用Maven做构建用SpringBoot做底盘底盘中包含了Spring容器、SpringMVC控制器、MyBatis持久层页面渲染交给Thymeleaf模板引擎前端交互用到AJAX数据最终落进MySQL。技术名词在项目里的实际位置作用SpringBoot启动类、application.yml、自动配置负责把整个项目“拧成一股绳”SpringMVCController层、拦截器、参数绑定接收HTTP请求、返回页面/JSONMyBatisMapper接口 XML文件操作MySQL写SQLMySQL本地数据库存储用户、企业、兼职、申请记录Thymeleaftemplates目录下的HTML模板服务端渲染列表、用户昵称等AJAX前端JavaScript发异步请求申请兼职、局部刷新、不整页跳转Mavenpom.xml管理依赖统一各类jar包版本1.3 一条数据流走到底申请兼职请求经历了什么我强烈建议任何人在写代码前先学会“跟着一条请求走”。以“学生点击申请按钮”为例完整路径是前端JS拿到兼职ID用AJAX POST到/job/applySpringMVC拦截器先检查用户是否登录没登录返回“请先登录”Controller接收参数调用ServiceService先查兼职是否存在、状态是否为“已发布”再查当前用户是否重复申请过通过后插入一条申请记录状态为“待审核”返回一个Result对象JSON格式给前端前端根据返回code决定弹提示还是刷新申请按钮后面第5章会把这个链路展开成真实代码。先记住一点这个项目本质上就是“用户、兼职、申请记录”三张表在状态流转想清楚状态代码就不容易乱。2. 技术选型背后的逻辑为什么这套组合能常驻十年2.1 三层架构演进SSM为什么成了SpringBoot的底色很多初学者写SpringBoot项目时觉得SSM已经过时了——其实不是。SpringBoot的spring-boot-starter-web内部就是SpringMVCMyBatis要自己引入starter整合所以写出来的代码依然是Controller、Service、Mapper三层。SSM当年的痛点是配置地狱web.xml、Spring配置文件、SpringMVC配置文件、MyBatis配置文件四个文件互相引用新人光配置就要折腾一两天。SpringBoot把公共配置收敛到application.yml里例如数据源、端口、SQL映射位置都成了几行配置。这也是为什么现在的课程设计主流是“SpringBootSSM”而非纯SSM——代码结构没变门槛低了。个人习惯是保留MyBatis的XML写SQL。虽然MyBatis支持注解写SQL但复杂查询比如兼职列表的分页搜索统计用XML维护起来清楚得多这是经验之谈后面章节会体现。2.2 Thymeleaf vs JSP vs 前后端分离毕设场景的最优解JSPSpringBoot对JSP支持不太友好内嵌Tomcat跑JSP需要额外配置spring-boot-starter-tomcat-jasper部署时也容易有问题我基本不推荐。前后端分离Vue SpringBoot是很多商业项目的标准做法但对课程设计、毕设来说要单独部署前端、处理跨域、Mock接口工作量翻倍答辩时老师如果只关注业务逻辑这套反而显得“杀鸡用牛刀”。Thymeleaf它和HTML长得很像浏览器直接打开静态文件也能看到大致结构后端填充数据后正常渲染。再加上项目标题里专门写了AJAX所以最合理的方案是页面基本结构用服务端渲染交互性强的操作申请兼职、状态变更、搜索防抖用AJAX局部刷新。这套组合还有一个隐性优点Thymeleaf能和Bootstrap等CSS框架无缝配合你不需要学Vue写出来的页面也不丑。2.3 版本搭配是最大的隐藏门槛标题定的技术栈常见但坑也在常见里。最典型的JDK8 SpringBoot 2.7.x mybatis-spring-boot-starter 2.3.x这是最稳的组合网上资料最多毕设答辩演示不会莫名翻车。SpringBoot 3.x JDK17如果你新建项目时选了最新版SpringBoot 3.2它要求JDK17而很多人电脑里是JDK8启动直接报错。还有javax包名变成了jakarta代码里import javax.servlet.*要改成import jakarta.servlet.*新手在这里卡到怀疑人生。我的结论如果是为了毕业设计、课程设计老老实实用SpringBoot 2.7.18 JDK8。后面章节的依赖和代码我都按这个配套来写。3. 数据库设计兼职平台的核心是状态而不是表3.1 四张核心表的设计思路平台系统的数据库设计有一个原则能用“角色区分”就别拆数据表。用户和企业不需要分成student和company两套用户体系而是用一张user_info表加一个role字段1-学生2-企业。企业特有的公司资料名称、行业、地址单独放一张company_info表通过用户ID关联。另外两张核心表是job_info兼职信息和job_application申请记录。字段控制在合理范围别一上来就加十几个业务冗余字段。表名核心字段作用user_infoid, username, password, role, phone, create_time登录身份不含杂项company_infoid, user_id, company_name, industry, description企业资料一人一条job_infoid, company_id, title, description, salary_min, salary_max, work_time, location, status, deadline兼职信息主体是企业发的内容job_applicationid, job_id, user_id, status, apply_time关联申请关系状态流转3.2 为什么状态字段用int而不是用字符串这是很多做毕设的人不会细想但答辩老师会问的设计点。job_info.status和job_application.status我不建议直接存“已发布”“待审核”这种中文原因有三中文占空间且乱码风险高MySQL连接配置没写好时第一个挂的就是中文。业务判断写起来麻烦代码全是魔法字符串错误拼写还不报编译错。你需要在代码里维护一层“字典语义”比如/** * 兼职状态0-草稿 1-审核中 2-已发布 3-已下架 */ public class JobStatus { public static final int DRAFT 0; public static final int PENDING 1; public static final int PUBLISHED 2; public static final int OFFLINE 3; }前端Thymeleaf渲染时再根据状态值映射成中文标签和徽标颜色这才是清晰的做法。3.3 建表SQL与外键取舍开发环境我直接写init.sql手动初始化不用等着导出。下面给最简可用的三张表SQL公众号或笔记里粘贴时注意MySQL 8默认字符集utf8mb4CREATE DATABASE IF NOT EXISTS parttime_job DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE parttime_job; CREATE TABLE user_info ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, phone VARCHAR(20), role TINYINT NOT NULL DEFAULT 1 COMMENT 1-学生 2-企业, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE company_info ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, company_name VARCHAR(100) NOT NULL, industry VARCHAR(50), address VARCHAR(255), description TEXT, UNIQUE KEY uk_user_id (user_id) ); CREATE TABLE job_info ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, title VARCHAR(100) NOT NULL, description TEXT, salary_min INT, salary_max INT, work_time VARCHAR(100), location VARCHAR(100), status TINYINT DEFAULT 0, deadline DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_status (status), KEY idx_create_time (create_time) ); CREATE TABLE job_application ( id INT AUTO_INCREMENT PRIMARY KEY, job_id INT NOT NULL, user_id INT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0-申请中 1-已通过 2-已拒绝, apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_job_user (job_id, user_id) );job_application上的uk_job_user联合唯一键非常关键这是防止学生重复申请兼职的数据库层兜底。就算代码里忘了判断数据库也会拒绝重复插入。4. Maven依赖与配置文件项目跑起来的第一道关口4.1 pom.xml 关键依赖说明新建项目时Spring Initializr会给你一堆依赖。但要跑通这个项目核心只有这几个parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web内含SpringMVC是SSM的“M”层 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Thymeleaf服务端模板引擎 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- MyBatis整合SpringBoot -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- Lombok简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies特别注意如果你在SpringBoot 2.7.18的parent下不写MySQL驱动版本它默认给你带的是mysql-connector-java8.0.33能用。但SpringBoot 3.x里驱动坐标变成了com.mysql:mysql-connector-j如果网上抄配置抄混了Maven会飘红。4.2 application.yml 配置项逐个解析server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/parttime_job?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 thymeleaf: cache: false suffix: .html mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.parttime.entity configuration: map-underscore-to-camel-case: true三个最容易被坑的地方driver-class-name必须写com.mysql.cj.jdbc.Driver这是MySQL 8的驱动类旧版的com.mysql.jdbc.Driver会报警告甚至直接失败。URL里的serverTimezoneAsia/Shanghai不能省否则连MySQL时区报错英文报错里会出现The server time zone value ... is unrecognized字样。map-underscore-to-camel-case: true开启后数据库的create_time字段才能自动映射到Java实体的createTime属性否则查出来的时间为null。4.3 MyBatis的mapper-locations细节MyBatis的XML文件默认不会自动扫描必须靠mybatis.mapper-locations指定常见写法是classpath:mapper/*.xml。这意味着你的XML文件要放在src/main/resources/mapper/目录下而不是放在Java包里。新手最常犯的错误是把UserMapper.xml塞进com.parttime.mapper包下结果启动报Invalid bound statement (not found)。同理Mapper接口要能被扫描到启动类上记得加MapperScan(com.parttime.mapper)或者在每个Mapper接口上加Mapper注解。我习惯用MapperScan省得每个接口都写。5. 核心功能代码落地登录拦截、发布兼职、申请状态流转5.1 统一返回结果ResultAJAX交互的地基因为有AJAX参与后端的接口不能只返回StringThymeleaf视图名而要有“返回数据”和“返回视图”两种能力。AJAX接的正规沟通格式是这样Data public class ResultT { private int code; // 200成功400业务错误401未登录 private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.message 操作成功; r.data data; return r; } public static T ResultT error(String message) { ResultT r new Result(); r.code 400; r.message message; return r; } public static T ResultT notLogin() { ResultT r new Result(); r.code 401; r.message 请先登录; return r; } }后面的Controller里凡是要被AJAX访问的接口统一返回ResultSpringBoot会自动序列化成JSON。而普通页面跳转的接口还是返回视图名。这个区分一定要想明白返回JSON靠RestController或ResponseBody返回视图靠Controller返回字符串不少人的接口被前端拿到了一整个HTML页面就是没搞清楚这个。5.2 登录拦截器页面都不想让没登录的人看到平台系统里除了登录页、注册页、首页列表大部分业务接口都要求登录。用SpringMVC拦截器做最合适public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); // 权限特殊处理登录页、注册页、静态资源放行 if (user null) { // AJAX请求判定返回JSON而不是跳转页面 String xRequestedWith request.getHeader(X-Requested-With); if (XMLHttpRequest.equals(xRequestedWith)) { response.setContentType(application/json;charsetutf-8); response.getWriter().write(new ObjectMapper().writeValueAsString(Result.notLogin())); } else { response.sendRedirect(/login); } return false; } return true; } }注册拦截器时注意放行规则。千万别把Controller里的业务路径也放行了我发现有同学图省事直接放行所有/job/**结果学生没登录也能点“申请兼职”后端根据session拿不到用户就空指针这个坑太典型了。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /register, /job/list, /css/**, /js/**, /images/**); } }拦截器里的一个关键点AJAX请求的sendRedirect(/login)不好使因为浏览器拿到302后自己跳走了前端JS根本接不到返回值。所以拦截器必须判断X-Requested-With请求头AJAX场景下直接输出JSON给前端让前端处理跳转。5.3 发布兼职与分页查询Service怎么写才不臃肿以发布兼职为例Controller不要写业务逻辑Service只做一件事把前端传来的参数封装成JobInfo对象状态设成PENDING0草稿可以先不暴露给用户直接1审核中或2已发布看你的后台流程然后调用Mapper插入。为了演示状态流转我按“企业发布后直接上架为已发布”处理Service public class JobServiceImpl implements JobService { Autowired private JobInfoMapper jobInfoMapper; Autowired private CompanyInfoMapper companyInfoMapper; Override Transactional public int publishJob(JobInfo job, Integer userId) { CompanyInfo company companyInfoMapper.findByUserId(userId); if (company null) { throw new ServiceException(请先完善企业资料); } job.setCompanyId(company.getId()); job.setStatus(JobStatus.PUBLISHED); return jobInfoMapper.insert(job); } }分页查询用MyBatis的PageHelper组件是最省事的但为了减少依赖很多课程设计项目会自己写一个简易分页先count查总数再limit offset, size查当前页。面试时能把LIMIT #{offset}, #{pageSize}这种逻辑讲清楚比背一个PageHelper.startPage()有价值得多。5.4 申请兼职事务与状态校验申请兼职是典型的“先查后插”逻辑必须做两层防御。第一层查兼职是否存在且已发布第二层查当前用户是否已申请过DB层有唯一键兜底。两个查询之间是线程不安全的单机演示问题不大但代码里养成事务习惯Override Transactional public ResultVoid applyJob(Integer jobId, Integer userId) { JobInfo job jobInfoMapper.findById(jobId); if (job null || job.getStatus() ! JobStatus.PUBLISHED) { return Result.error(该兼职不存在或已下架); } Integer count jobApplicationMapper.countByJobIdAndUserId(jobId, userId); if (count ! null count 0) { return Result.error(您已申请过该兼职请勿重复提交); } JobApplication application new JobApplication(); application.setJobId(jobId); application.setUserId(userId); application.setStatus(ApplicationStatus.PENDING); jobApplicationMapper.insert(application); return Result.success(null); }Transactional加在这里保证“查重和插入”要么都成功要么都失败。另外Service里抛业务异常时我建议自定义ServiceException配合SpringBoot的RestControllerAdvice全局异常处理器统一转成Result.error前端就不会在AJAX回调里看到一堆堆栈信息。6. AJAX局部刷新的改造思路一个按钮如何不用跳转完成申请6.1 页面基础Thymeleaf循环渲染兼职列表先看首页兼职列表。用Thymeleaf的th:each循环出一张卡片列表每条卡片里嵌一个“立即申请”按钮。div classjob-list div classjob-card th:eachjob : ${jobList} h3 th:text${job.title}前端实习生/h3 p th:text薪资 ${job.salaryMin} - ${job.salaryMax} 元/天薪资/p p th:text${job.location}工作地点/p button classbtn-apply th:attrdata-job-id${job.id} th:data-apply-status${job.applyStatus ! null ? job.applyStatus : 0} 立即申请 /button /div /divth:attrdata-job-id${job.id}这种写法能把数据“挂”在按钮上前端JS通过>$(document).on(click, .btn-apply, function () { var jobId $(this).data(job-id); var status parseInt($(this).data(apply-status)); if (status ! 0) { alert(你已经申请过了); return; } $.ajax({ url: /job/apply, type: POST, data: { jobId: jobId }, dataType: json, success: function (res) { if (res.code 200) { alert(申请成功等待企业审核); location.reload(); } else if (res.code 401) { alert(请先登录); window.location.href /login; } else { alert(res.message); } }, error: function () { alert(网络异常请稍后再试); } }); });这里有一个容易被忽略的细节data: { jobId: jobId }默认是application/x-www-form-urlencoded格式后端Controller直接写普通参数接收就行不需要RequestBody。只有contentType: application/json时后端才必须用RequestBody接收两边格式不一致最常见的表现就是Controller方法根本没被调用。对应的Controller代码PostMapping(/job/apply) ResponseBody public ResultVoid apply(RequestParam Integer jobId) { UserInfo user (UserInfo) session.getAttribute(loginUser); return jobService.applyJob(jobId, user.getId()); }6.3 AJAX常见失败404、500、跳转登录页的原因把这一节单独拎出来是因为标题里专门写了ajax。我解决过太多类似问题归纳一下404但浏览器直接访问URL是好的多半是PostMapping写成了GetMapping前端POST请求打不到或者URL拼写多了一个/。500且控制台提示thymeleaf模板解析错误你把返回JSON的接口放在了Controller类里方法又没加ResponseBodySpringBoot想把Result对象当成视图名去templates里找模板于是报view name相关异常。AJAX请求被拦截器重定向到登录页参见5.2后端需要判断X-Requested-With给JS返回JSON而不是302。前端拿到的res是字符串而非对象检查SpringBoot返回的content-type正常JSON应答应该是application/json。如果接口被全局异常处理器包了一层HTML错误页也可能出现这个问题。7. 从数据库初始化到本地部署的自测清单与踩坑记录7.1 一套可复现的完整自测流程项目写完之后别急着打包按下面的流程手动过一遍。这比直接截图给导师要稳得多每个环节都对应一个测试点启动MySQL执行init.sql确认四张表创建成功SHOW TABLES;看表名。修改application.yml里的数据库密码启动SpringBoot项目控制台无红色报错。浏览器访问http://localhost:8080/能看到首页兼职列表未登录也会展示该看的内容。注册一个学生账号登录再注册一个企业账号登录确认两个角色进入系统后看到的入口不同。用企业账号发布一条兼职切到学生账号在列表页搜到这条兼职并申请。切回企业账号在自己的企业后台看到申请记录点击“通过”。再切到学生账号查看申请记录状态显示“已通过”。最后测试重复申请确认前端按钮置灰、后端数据库唯一键双重拦截。上面跑通核心闭环就成立了。剩下的优化都在这条主链上做加法。7.2 踩坑记录一MySQL 8驱动与时区问题有次帮人配环境SpringBoot启动时报错The server time zone value Öйú±ê׼ʱ¼ä is unrecognized翻译过来就是MySQL 8的驱动要求明确时区。解决方案就是4.2里那行URL参数serverTimezoneAsia/Shanghai。顺带说一句useSSLfalse也值得保留本地开发没有配置SSL证书驱动默认去验证时容易报ssl连接警告。还有人在MySQL 8下用旧驱动com.mysql.jdbc.Driver会直接提示Loading class ... is deprecated虽然能跑通但迟早出问题不如一开始就写新的。7.3 踩坑记录二Thymeleaf热更新不生效开发时每改一个HTML都要重启项目这体验太糟糕了。正确做法是spring.thymeleaf.cachefalse然后配合CtrlF9重新编译模板。但要注意如果你改的是static目录下的CSS、JS浏览器强刷CtrlShiftR才行因为浏览器也有缓存。我经常遇到“为什么我改了页面没反应”最后发现是浏览器缓存调了半天后端。另外一个坑是Thymeleaf语法报错定位不准。模板里一个th:if写错前端整页报500。排错时先看控制台日志的具体异常行比如Could not parse as expression再去对应HTML行检查别盲目改代码。7.4 踩坑记录三Maven依赖冲突与SpringBoot版本过高依赖冲突在多模块项目里常见但这个单模块项目也有典型问题如果你同时引入了spring-boot-starter-data-jpa和mybatis-spring-boot-starterSpringBoot会自动配置两套数据访问层启动时偶尔出现Bean冲突。解决思路很简单项目只需要MyBatis不要混引JPA。凡是网上示例里看到JPA依赖直接删掉。版本过高的问题在5.2已经说过SpringBoot 3.x要求JDK17。如果你本机是JDK8却建了SpringBoot 3.x项目启动会报UnsupportedClassVersionError。这里给个判断方法看pom.xml里java.version是否与本地java -version一致。如果一致还报错检查IDEA的Project SDK和Maven Runner的JDK设置是否统一。这个坑90%是IDEA里Project Structure和Settings两处JDK不一致导致的。7.5 上线部署前的最后检查清单如果做完后想部署到服务器或云主机演示最后检查这几项数据库密码不要直接硬编码明文本地开发可以但要讲清楚这是开发配置正式一点的方案是环境变量注入${DB_PASSWORD}。MySQL所在服务器的3306端口防火墙是否放行很多人在本地跑得欢部署后连不上数据库排查半天其实是被防火墙拦了。打包命令用mvn clean package打出的jar包在target目录运行命令是java -jar parttime-job-0.0.1-SNAPSHOT.jar。记得在application-prod.yml里关闭Thymeleaf缓存否则部署后改了模板不生效。静态资源路径用/static/**如果打包时发现前端引用的图片404别急先看target/classes/static下有没有对应文件Maven没有过滤掉资源才是对的。结尾我实际做完这套系统后的一点体会这个项目做完我个人感触最深的一点是毕设级别的项目最怕的不是功能少而是把状态流转做复杂。最开始我也想把兼职审核流程做成多级审批那种结果发现一大半时间都花在“状态机”和各种if判断上页面倒是没几个。后来收敛成三个角色、两类核心状态兼职状态申请状态整个代码清晰了很多。如果以后想给这个系统加亮点我建议先别急着加花哨功能而是把热门兼职加一层Redis缓存、把企业头像和兼职封面用对象存储存起来这两个方向都比单纯加功能更能体现工程意识面试也能多聊几句。尤其是如果你打算基于这套代码去面试建议把第5章的拦截器逻辑和第6章的AJAX请求头判断拿出来多讲这是区分“会写增删改查”和“懂Web底层交互”的一个明显分水岭。至于前端页面Thymeleaf加Bootstrap足够漂亮了不必非要去啃Vue先把这套后端打扎实后面做什么都快。