SpringBoot+Vue汽车4S店车辆管理系统源码解析:从启动到改造实战

发布时间:2026/10/8 11:23:41
SpringBoot+Vue汽车4S店车辆管理系统源码解析:从启动到改造实战
简介一套基于SpringBootVue的汽车4S店车辆管理系统源码与配套数据库面向计算机相关专业毕业设计学生和Java实战学习者覆盖管理员、销售员、维修员三类角色业务涵盖客户管理、车辆销售、车辆维修、供应商信息与保险公司管理等模块。资源包共450个文件以Java后端源码、Vue前端页面、SVG图标、SQL数据库脚本、开发说明文档、答辩PPT及演示视频为主其中131个Java文件、49个Vue文件支撑系统前后端主体功能整体压缩包约17.91MB目录结构清晰便于按模块学习。项目经过严格调试可直接运行附带数据库脚本、开发文档、LW报告和答辩PPT能帮助读者快速理解前后端交互逻辑与业务流程。同时管理员、销售员、维修员三种视角下的功能划分清晰适合作为课程设计、期末大作业或正式毕业设计的参考模板。目前已有410人浏览学习。1. 拿到这份SpringBootVue的汽车4S店车辆管理系统源码先别急着点运行如果你手里正好有一份基于SpringBootVue的汽车4S店车辆管理系统毕业设计源码我的建议是先别急着双击启动。这个课题是前后端分离项目的标准模板SpringBoot在后台提供REST接口Vue负责页面渲染MySQL存放车辆、客户、订单和售后数据。对毕业设计来说它的价值不在于“能跑起来”而在于你能把每一张表、每一个接口和每一次页面跳转背后的业务逻辑讲清楚。我见过不少同学卡在第一步数据库没导、端口没配、跨域没开项目文件明明完整却一步都跑不起来。这篇文章就顺着源码的骨架把技术选型、启动步骤、核心模块改造和常见坑一次性讲透。适合选了车辆管理、进销存、企业后台这类课题的学生也适合想快速搭一套管理系统的开发者参考。2. 技术栈拆解SpringBootVue这套“毕业设计黄金组合”应该怎么分工2.1 后端SpringBoot的三层架构与自动配置边界拿到源码后第一件事不是看页面长什么样而是先找到后端项目的包结构。这套系统的后端几乎都是标准的三层架构Controller接收前端请求Service处理业务逻辑Mapper操作数据库中间再夹一层实体类。SpringBoot在这里的“自动配置”体现在哪里它帮你内置了Tomcat、帮你注册了Spring MVC的DispatcherServlet、帮你把MyBatis的数据源初始化好了。你的代码要改的是业务逻辑本身而不是那些基础设施配置。这就引出一个答辩必问的问题“你这个项目分层了吗”如果你把Controller-Service-Mapper这层关系一句话说清楚再指出哪个Controller调用了哪个Service方法老师基本就会点头。大多数毕业设计源码的Controller层会写成这样RestController RequestMapping(/api/vehicle) public class VehicleController { Autowired private VehicleService vehicleService; GetMapping(/page) public ResultPageResultVehicle page( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword) { return Result.success(vehicleService.pageQuery(pageNum, pageSize, keyword)); } PostMapping public ResultBoolean add(RequestBody Vehicle vehicle) { vehicleService.addVehicle(vehicle); return Result.success(true); } }这段代码说明了三件事第一RestController让返回对象直接变成JSON前端拿到的就是{code: 200, data: {...}}这种结构第二分页参数用RequestParam接收能够支持前端表格的页码跳转第三新增车辆时用RequestBody接收JSON前端表单提交时不用拼接字符串。参数说明里最需要注意的是RequestParam(required false)它决定了搜索关键字可以不传如果写死required true前端不传keyword时接口直接报400。你的改造重点也应该在这一层。如果源码里Controller里写了一大堆业务判断比如在Controller里直接调用Mapper答辩时一定会被追问“业务和接口为什么不分离”。最好把校验逻辑下沉到ServiceController只负责参数接收和结果封装这是和源码默认设计最契合的一种改动也最容易在论文里写清楚。2.2 前端Vue与Element UI页面是现成的但组件化逻辑可以重写前端部分的源码结构通常是src/views放页面、src/api放接口请求、src/router放路由。Element UI把表格、表单、弹窗、分页这些组件都封装好了业务页面里的代码大量使用el-table、el-form、el-dialog开发效率确实高。但很多二手源码的页面组件写得很粗糙——一个大文件塞了几百行弹窗表单和列表全混在一起。你要做的是把它拆开。最常见的拆分方式是把“新增/编辑车辆”的弹窗提取成一个子组件父组件通过props把要编辑的数据传进去子组件通过$emit通知父组件刷新列表。这样代码结构一拆答辩时的“可维护性”就能讲出东西来。代码大概是这个意思// VehicleFormDialog.vue export default { name: VehicleFormDialog, props: { visible: Boolean, formData: { type: Object, default: () ({}) } }, emits: [refresh], methods: { submit() { // 调用新增或更新接口 this.$emit(refresh) } } }这段代码里的props接收父组件传过来的弹窗开关和表单初始值emits声明自定义事件refresh。很多新手看不懂为什么表单提交后列表会刷新其实就是子组件提交成功后触发了refresh父组件监听这个事件再去调一次分页查询接口。使用Element UI时还要注意el-dialog的destroy-on-close属性不加它的话第二次打开弹窗时表单里往往还残留上一次的数据这是实际开发里很常见的“玄学”问题其实就是组件实例没有销毁。2.3 3张核心表与5个接口别把车辆管理系统做成增删改查Demo判断一份车辆管理系统源码质量高低先看数据库表设计。很多Demo项目只有一张车辆表字段就是id、name、price这种项目答辩时会被一眼看穿。真正能落地的4S店车辆管理至少要包含三张核心表车辆信息表、客户表和订单表。车辆信息表要有品牌、车型、颜色、车架号、指导价、成交价、入库时间、车辆状态这些字段其中“状态”字段决定了整个系统的业务复杂度。订单表则记录哪辆车卖给了哪个客户、成交价是指导价的几折、是现金还是分期。车辆状态字段如果设计成数字字典比如0在库、1预定、2已售、3维修中那系统就不再是单纯增删改查而是有了业务流转。下面这段建表语句是我建议的最小设计CREATE TABLE vehicle ( id int NOT NULL AUTO_INCREMENT, brand varchar(50) DEFAULT NULL COMMENT 品牌, model varchar(100) DEFAULT NULL COMMENT 车型, vin varchar(50) DEFAULT NULL COMMENT 车架号, price decimal(10,2) DEFAULT NULL COMMENT 指导价, status tinyint DEFAULT 0 COMMENT 0在库 1预定 2已售 3维修中, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_status (status), KEY idx_brand (brand) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT车辆信息表;建表时给status和brand加索引很有必要因为后面查询“在库车辆列表”和“按品牌统计库存”都会走这两个字段没有索引的话数据量一上来查询就变慢答辩时如果评委问“你这表怎么优化”至少能说出索引这一层。对应这辆系统的“最小可用接口”应该有车辆分页查询、新增车辆、修改车辆状态、客户分页查询、创建订单。如果源码只有增删改查你就需要自己把订单和客户这两块补上。3. 本地把项目跑起来环境版本、建库脚本和前后端联调完整命令3.1 环境与版本匹配JDK、Maven、MySQL、Node该怎么选跑这套源码之前先把环境统一好版本不匹配是运行时翻车的第一大原因。SpringBoot 2.x配JDK 1.8是最稳的组合MySQL用5.7或8.0都能跑Node建议用14或16Maven用3.6以上。版本高了真不一定就好比如SpringBoot 3.x把javax换成jakarta很多旧源码拿过来直接编译失败盲目追求最新版只会浪费半天时间。我把常用版本组合列成一张表直接照抄即可组件建议版本说明JDK1.8SpringBoot 2.x的默认编译目标Maven3.6.3配合IDEA内置Maven也可MySQL5.7 / 8.08.0要换驱动类名Node.js14.x / 16.x太高版本npm install可能报错npm6.x / 8.x随Node版本自带MySQL 8.0的用户要注意JDBC驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.Driver连接URL里还要加serverTimezoneAsia/Shanghai否则时区报错。这些细节在下面配置里都有体现。如果是Maven项目先看pom.xml里spring-boot-starter-parent的版本号如果写的是3.x大概率需要降级到2.7.x再跑。3.2 后端启动步骤修改SpringBoot配置并导入数据库脚本后端启动的完整步骤一般是五步。第一步把zip解压确认目录里有前端、后端和SQL脚本第二步用IDEA打开后端目录等待Maven把依赖下载完第三步在本地MySQL里创建一个数据库执行SQL脚本导入表结构和初始数据第四步修改application.yml里的数据库账号密码第五步找到启动类运行。大部分源码的启动类都在src/main/java下类名通常是Application或CarSystemApplication。application.yml是springboot配置的核心文件也是你最需要看懂的文件。一段典型配置长这样server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/car_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.carsystem.entity这里最要命的是url里的数据库名car_system它必须和你本地建的库名完全一致否则启动时直接报Unknown database。characterEncodingutf8解决中文乱码丢了它的话页面列表里全是问号。serverTimezoneAsia/Shanghai解决MySQL 8.0的时区差8小时问题。mapper-locations指定了MyBatis的XML文件位置如果源码里Mapper接口用的是注解SQL这项也可以不写写了反而可能因为找不到XML目录而报错。启动后看到Tomcat started on port(s): 8080并不代表接口都能访问还要看有没有Mapper相关的报错。我习惯先用浏览器直接访问http://localhost:8080/api/vehicle/page如果返还JSON就说明后端通了这一步能帮你把后端问题和前端问题隔离开来。3.3 前端启动步骤npm install与Vue项目运行前端部分启动相对简单但坑也不少。在Vue项目根目录下依次执行安装依赖和启动命令npm install npm run servenpm install会下载依赖到本地node_modules目录一般需要几分钟。如果中途报错优先检查Node版本是不是太高我遇到过Node 18上某些旧依赖直接编译失败的情况切换回Node 16就正常了。npm run serve是开发服务器命令默认端口是8080如果后端已经占用了8080前端会询问是否换端口这时要么让前端换到8081要么把后端的server.port改成8081前后端必须不同端口才能同时运行。很多时候前端启动成功了但页面一直转圈、数据加载不出来问题出在接口地址。前端源码里通常会有一个封装好的axios实例你需要把请求的baseURL改成后端地址// src/api/request.js import axios from axios const service axios.create({ baseURL: http://localhost:8080/api, timeout: 10000 }) export default service注意baseURL里的/api前缀不是随便写的它要和后端RequestMapping(/api)的路径匹配。如果后端Controller类上是RequestMapping(/vehicle)前端就要写成/api/vehicle两个地方只要拼接起来能对得上就行。这种“接口路径对不上”的问题排查起来很简单打开浏览器F12看Network面板里的请求URL是什么和后端实际路径比一比就清楚了。3.4 前后端联调跨域配置与接口连通性排查前后端分离项目最经典的拦路虎是跨域。前端跑在http://localhost:8081后端跑在http://localhost:8080端口不同就产生了跨域请求浏览器会拦截。后端解决跨域最省事的办法是加一个全局配置类Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这段配置里有个细节容易踩坑setAllowCredentials(true)表示允许携带Cookie但使用Credentials时addAllowedOrigin不能直接加*必须用addAllowedOriginPattern(*)写法否则浏览器会报“Cannot use wildcard pattern with credentials”。如果源码里已经有跨域配置就别重复添加两个配置类叠加反而可能出现冲突。联调排错我习惯按这个顺序来先确认后端单独能访问接口再用curl命令测一下接口是否可访问最后才是打开前端页面。用curl测接口时直接看返回的HTTP状态码和JSON内容比浏览器直观得多。如果curl有返回但前端报错那问题十有八九出在前端axios封装或跨域配置上而不是后端接口本身。4. 把“毕业设计”改成“能答辩的项目”核心模块的扩展点与源码改造4.1 车辆状态管理在增删改查之上补一个业务状态机很多车辆管理系统源码的问题在于“车辆表只是一个小字典”没有状态流转。但真正开过4S店的人都知道一台车从入库到卖给客户中间可能经历预约看车、预定、已售、售后维修等多个状态。你把这层逻辑补上去系统就从“增删改查Demo”变成“有业务逻辑的系统”了。改造点很明确在VehicleService里加一个更新状态的方法并且做前置状态校验。比如一辆“在库”的车可以直接转成“预定”但一辆“维修中”的车不能直接跳成“已售”这种约束写在Controller里会越写越乱统一放在Service层才合理public boolean updateVehicleStatus(Integer id, Integer targetStatus) { Vehicle vehicle vehicleMapper.selectById(id); if (vehicle null) { throw new BizException(车辆不存在); } Integer current vehicle.getStatus(); if (VehicleStatus.SOLD.equals(targetStatus) !(VehicleStatus.ONSALE.equals(current) || VehicleStatus.BOOKED.equals(current))) { throw new BizException(当前状态不允许直接售出); } return vehicleMapper.updateStatus(id, targetStatus) 0; }这段代码的关键不在updateStatus那一行而在于前面那两个if判断。BizException是业务异常需要在全局异常处理器里捕获并返回友好的错误提示不然前端拿到异常堆栈很难看。如果你想让答辩更有亮点还可以把状态流转规则定义成一个枚举或者Map比如“在库可转预定、预定可转已售、已售可转维修”这样一组规则答辩时你直接说“我用状态机约束了业务流程”比单纯说“我封装了一个更新方法”要有说服力得多。4.2 权限控制用拦截器给管理系统补上登录校验管理系统的源码里如果只有一个登录页但所有接口都能匿名访问那这个系统等于没做权限。补权限控制有两个选择用Spring Security或者用拦截器。对毕业设计而言我建议用拦截器理由很现实——Spring Security的过滤器链配置对新手不友好一旦配错接口要么全部拒绝、要么全部放行而且答辩时很难讲清楚原理。拦截器的思路简单直接登录成功后返回一个token前端每次请求都把token放在请求头里后端拦截器校验token是否存在、是否有效。核心代码就一个类public class LoginInterceptor 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(token); if (token null || token.isEmpty()) { response.setStatus(401); response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:401,\msg\:\未登录\}); return false; } // TODO: 去数据库或Redis校验token return true; } }这段代码有几个坑要说清楚。第一跨域预检请求的类型是OPTIONS不直接放行会导致前端连跨域配置都触发不了第二response.setStatus(401)之后还要write一段JSON否则前端拿到的是空响应看不懂为什么失败第三拦截器写好之后必须注册到WebMvcConfigurer里否则写了等于白写。注册时还可以顺便配置放行路径比如/api/login不需要登录其他/api/**都要校验。建议把“管理员”和“普通员工”两种角色字段加在用户表里拦截器里顺手判断一下角色权限这就是比较完整的权限控制了。4.3 前端Route与Axios拦截路由守卫和token自动携带后端加了拦截器前端也要配合改造。否则用户没登录也能打开首页只是接口返回401页面体验很差。前端要做两件事路由守卫控制页面跳转axios请求拦截器自动携带token。路由守卫的写法在src/router/index.js里加一段beforeEach逻辑router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })这里的关键是配合路由元信息meta.requiresAuth使用。需要登录才能访问的页面在定义路由时加上meta: { requiresAuth: true }不需要登录的登录页就不加。这样一个守卫逻辑就能管理所有页面不用每个页面单独判断有没有token。如果你不太理解vue-router的工作流程可以在守卫里加一句console.log(to.path)实测一下每次跳转时拦截器走了哪些路径比看文档快得多。axios请求拦截器则是把token自动加到每个请求头上不需要在每个接口调用处手动传参。这个改造对代码侵入很小但能让所有接口统一带上token和后端拦截器形成闭环。把这两段代码加进去后这部分前端改造就完成了系统也真正有了“登录就能用”的完整观感。5. 避坑这份源码最常见的五个“跑不起来”现象、原因和解决办法5.1 前端页面白屏控制台报Missing required prop现象启动前端后页面是空白打开控制台有一条红色的Vue警告提示某个组件缺少required prop。原因源码里某个自定义组件或者Element UI组件的props被声明成required: true但父组件传给它的数据是从后端接口异步获取的。页面初始化时接口还没返回data字段是undefined组件一渲染就报错。解决给props设置默认值或者在父组件模板里用v-if判断数据到位后再渲染子组件。比如列表数据接口没有返回之前先用v-iflist.length 0包住表格等数据加载完再展示。这样改既不影响功能还能避免控制台报错。这也说明拿到源码先看控制台是个好习惯前端很多问题不是代码逻辑错了而是数据时序的问题。5.2 后端启动失败springboot版本太高导致编译直接报错现象IDEA里运行启动类控制台抛出UnsupportedClassVersionError提示class file has wrong version或者大量ClassNotFoundException代码根本没进入Spring Boot启动流程。原因pom里SpringBoot的版本是3.x但你的JDK还是1.8。SpringBoot 3.x要求JDK17以上同时包名从javax换成了jakarta源码里的javax.annotation.Resource等引用全部编译不过。这个坑特别容易出现在新下载的源码里很多开源项目已经把SpringBoot升级到3.x了但毕业设计论文和本地环境还是按2.x写的。解决把spring-boot-starter-parent的版本降到2.7.x然后把所有jakarta.*的import重新替换回javax.*。如果搞不清楚源码里到底用了哪些新包名最稳妥的办法是直接新建一个SpringBoot 2.7.x的空项目把源码里的src目录整体复制过来再按新项目结构调整pom依赖。别硬着头皮升级JDK到17SpringBoot 2.x在JDK17下反而会有CGLIB相关的报错。5.3 数据库导入后中文乱码表格里全是问号现象SQL脚本导入成功但打开页面看到车辆品牌的字段全是??或者登录账号直接提示找不到用户。原因创建数据库时没有指定字符集MySQL默认用了latin1中文存不进去。另一种可能是在MySQL命令行里执行SQL脚本时没有加--default-character-setutf8mb4。解决重建数据库建库时显式指定字符集这一步建议直接在SQL脚本最前面加上CREATE DATABASE IF NOT EXISTS car_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;注意如果在原脚本里已经有了CREATE DATABASE语句你要做的是把这句话改成上面这句再重新执行而不是再创建一次同名库。执行之前把库删掉重建最省心否则表结构已经存在CREATE TABLE会报表已存在的错误。除此之外application.yml里的连接URL也要带characterEncodingutf8数据库字符集和连接字符集任何一边不对都会乱码。5.4 vue打包放进springboot中后刷新页面404且静态资源路径错现象npm run build把dist目录放到SpringBoot项目的static目录下打包启动后首页能打开但在浏览器里直接访问某个路由地址或者按F5刷新页面变成404。原因vue-router默认使用createWebHistory模式也就是HTML5 History模式。路由跳转全靠前端的History API刷新时浏览器以为访问的是后端路径后端没有这个Controller自然返回404。另外打包后的JS/CSS如果用了绝对路径/js/app.js部署到子路径时也会加载失败。解决开发时用createWebHistory方便但打包后最好改成createWebHashHistory地址栏会多一个#号刷新时不会请求后端路径。同时修改vue.config.jsmodule.exports { publicPath: ./ }publicPath: ./的意思是把静态资源引用从根路径改成相对路径这样部署到任意目录都能加载CSS和JS。如果答辩老师问“为什么部署的时候要改这两个地方”你能把HTML5 History和hash模式的差别讲清楚这就是个加分项。不改的话项目本地开发没问题部署上去全是坑。5.5 后端能启动但接口超时端口占用、连接池满和慢查询现象后端启动日志显示Tomcat已启动但前端页面一直转圈Network面板里接口pending很久最后超时或报500。原因第一种可能是端口被其他程序占用SpringBoot虽然启动了但实际监听端口不是你以为的8080日志里通常能找到Port 8080 was already in use提示第二种是MySQL连接池被占满代码里获取连接后没有释放第三种是某个查询SQL走了全表扫描数据量大时很慢。解决先看日志不要瞎猜。端口冲突就用netstat -ano | findstr 8080Windows或lsof -i:8080Mac/Linux查占用进程改SpringBoot端口也行换一个空闲端口最快。连接池占满时检查Service里是否把SqlSession或者Connection放在循环里没有关闭MyBatis-Plus一般不用手动管理连接但原生JDBC会出现这个问题。慢查询则是在MySQL里执行EXPLAIN SELECT ...看是否走了索引我在2.3节里建议给status字段加索引就是为了避免这种问题。6. 给源码做“加分改造”测试用例、操作日志与Redis缓存答辩能多讲三个点6.1 给车辆接口补一个JUnit集成测试答辩时最尴尬的一件事是评委问“你这接口测试过吗”你只能说“我试过了”。但如果你在源码里写了一个正式的集成测试类观感完全不一样。在src/test/java目录下写一个测试类用SpringBootTest拉起整个Spring容器再用MockMvc模拟HTTP请求SpringBootTest AutoConfigureMockMvc class VehicleControllerTest { Autowired private MockMvc mockMvc; Test void shouldReturnVehiclePage() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get(/api/vehicle/page) .param(pageNum, 1) .param(pageSize, 10)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath($.data.total).exists()); } }这个测试类的价值在于它不只是跑通一个接口还校验了返回的JSON里有没有data.total这个字段。如果表结构或者SQL改了导致查询结果不对测试会直接失败。你可以在论文里写“使用Spring Boot Test框架对核心接口做了集成测试”这句话比空口说“测试过了”有份量得多。6.2 用logback把关键操作留痕面试或答辩时把idea控制台打印的日志展示给评委看效果比截图好得多。给Service层的关键操作加上日志输出比如车辆状态变更、订单创建、用户登录用logback输出到文件。代码里加一行log.info(用户{}将车辆{}状态从{}变更为{}, operator, vehicleId, before, after)运行系统后打开日志文件能清晰地看到一个人在什么时间干了什么事。这个改造的额外好处是如果后端接口出了问题你可以直接查日志定位不用再靠print和猜。日志级别也要控制好开发环境用DEBUG生产环境用INFO日志文件按日期滚动别让一个文件无限增长。答辩时你可以指着日志说“这里记录了状态变更的原始凭证”老师会觉得你考虑到了运维层面的事。6.3 给热门车辆列表加一层Redis缓存车辆列表接口如果每次查询都直接访问MySQL数据量小的时候看不出问题但答辩时你主动提到“接口性能优化”就能体现出性能意识。最简单的做法是用StringRedisTemplate缓存热门车辆列表key设为vehicle:hot:listvalue存JSON字符串查询时先看缓存缓存没有再去数据库查并把结果放回缓存。需要注意缓存和数据库的一致性车辆状态变更、新增车辆时必须手动删掉对应缓存否则用户看到的列表永远是旧数据。给缓存设置过期时间比如30分钟就算忘记删缓存数据最多延迟半小时更新。这段逻辑不要写复杂够讲清楚“缓存穿透和缓存更新”就足够了真引入Redis还需要在pom里加依赖、在application.yml里配置连接地址属于一套完整的改造流程。我做车辆管理系统时最深的教训是刚开始只关心CRUD能不能跑通结果做到了订单模块才发现车辆状态字段缺失又回头去改表结构和所有相关Service。如果你打算认真答辩建议把第四章和第六章的改造都做上这样才能从“源码使用者”变成“项目开发者”。重写代码不可怕可怕的是连重写思路都没有。希望帮到你。本文还有配套的精品资源点击获取