ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

社区商铺管理系统:Spring Boot+Vue+MySQL全栈实战解析

社区商铺管理系统:Spring Boot+Vue+MySQL全栈实战解析

社区商铺管理系统,这类毕设题目每年都能见到不少,但真正做扎实的其实不多。我前后参与评审和带过几个类似的毕业设计项目,这套“社区商铺管理系统”算是比较典型的一套完整方案,后端是Spring Boot,前端是Vue加Element UI,数据库用的MySQL,覆盖商铺档案、租户管理、合同台账、费用收缴、报修跟踪等核心模块。不管你是正在做毕设需要参考,还是想快速搭一套轻量级的商铺管理后台,这篇文章会把设计思路、数据库结构、关键实现、部署步骤和答辩常见追问一次讲清楚。

这系统解决的核心问题很具体:社区里几十上百间商铺,如果只靠Excel表格管理,租期、租金、水电费动不动就对不上账。把商铺信息、租户关系、合同状态、费用记录全部落到一个系统里,谁在租、租到什么时候、欠了多少钱、哪间铺子空着,打开后台一眼就能看清。对做毕设的同学来说,这类系统的业务逻辑不复杂但链条完整,特别适合用来展示从需求分析到数据库设计再到前后端编码的完整能力。下面我按实际动手做这个项目的思路来讲,不绕弯子。

1. 项目定位与需求拆解

1.1 这类系统到底在管什么

先说场景。社区商铺的形态很多,有小区底商、社区商业街、产业园区配套商业区,少则十几间,多则上百间。管理方往往是物业公司或者社区运营方,他们日常要处理的不是“卖东西”,而是“管铺子”:哪间铺子租出去了、租给了谁、合同什么时候到期、这个月该收多少租金和水电费、租户提交的报修单处理到什么程度。这些信息如果散落在Excel和微信聊天记录里,时间一长必定对不上账,尤其是同时存在“已退租未结清”“合同已到期但租户还在用”这类特殊情况时,纯粹靠人脑记根本没有可行性。

所以这套系统的核心价值就是把商铺全生命周期管起来:从录入一间铺子的基本信息开始,到找到租户、签合同收押金、每月自动生成应收账单、收款登记、续租或退租结算,整个链条每个环节都有对应的数据记录和状态变更。对做毕设而言,这个业务域特别合适,因为它不需要复杂算法,但涉及多个实体的关联、状态的扭转、统计报表的输出,能很好地体现一个开发者对业务建模和工程化的理解。

1.2 核心功能模块与业务流程

我拆解这个系统时,习惯先画业务流程图再动手写代码。整体上分为六大模块:

  • 商铺管理:维护商铺档案,包括铺号、楼层、面积、租金单价、物业费单价、当前状态(空闲/已租/装修中/停用)。
  • 租户管理:管理租户基本信息,包括姓名、手机号、证件号、紧急联系人,支持按名称或手机号快速检索。
  • 合同管理:商铺与租户之间签约、续租、退租、到期提醒。合同里记录租期、月租金、押金、递增规则。
  • 收费管理:按合同生成月度账单,收费类型涵盖租金、物业费、水费、电费、滞纳金;支持线下收款后登记核销、逾期标记。
  • 报修管理:租户发起报修,管理员派单、回访、完结,记录处理人、费用。
  • 系统管理:登录认证、用户管理、角色权限、操作日志。

业务流程的主线是“建档→出租→收费→结算”。一间新铺子先由管理员建档,状态设为“空闲”;找到租户后签订合同,合同一旦生效,系统自动把商铺状态改为“已租”,同时根据合同起止日期生成首月的应收账单;此后每月由定时任务或管理员手动触发生成当月的租金加物业费账单;租户缴费后做收款登记,账单状态变为“已缴”;合同到期时如果续签,就延长合同并调整租金,如果不续签,办理退租结算,把押金抵扣未缴费用后退还,商铺状态改回“空闲”。这条链路把核心业务全部串起来了,也是后面设计表结构和接口的主线。

1.3 选择这个题目的优势与难点

从毕设选题角度看,“社区商铺管理系统”的优势很明显:业务不冷门,任何人一看题目就知道要做什么;技术栈可以选主流水准,前端Vue、后端Spring Boot、数据库MySQL,工作量大但完全可控;演示效果好,后台界面上有表格、表单、图表,甚至可以做ECharts出租率趋势图,答辩时能展示出很完整的项目成果。

难度则集中在几处:一是合同与账单的关联逻辑,尤其是跨月、跨年的账单生成和费用统计,容易算错;二是权限控制要做到不同角色看不到不同菜单,而不是只在页面里隐藏按钮;三是时间字段的处理容易踩时区坑,后面我会单独讲。明确这三个难点之后,开发时就能有意识地避开。

2. 技术选型与架构设计

2.1 为什么选Spring Boot加Vue前后端分离

毕业设计的主流技术方案我对比过几类:纯Servlet加JSP的传统模式、SSM配合JSP、Spring Boot加Thymeleaf服务端渲染、Spring Boot加Vue前后端分离。我的建议是直接用Spring Boot加Vue这套,理由很实际。

第一,前后端分离让代码组织更清晰。后端只暴露REST接口,前端只做页面交互,两边都能单独调试、单独部署。答辩时你能够把前后端分开展示,讲接口设计、讲页面组件,每个部分都有自己的工作量,不会被质疑“是不是只有CRUD”。

第二,Spring Boot把配置简化了很多。内嵌Tomcat,不用单独装容器,一个jar包就能跑起来;数据库连接池、MyBatis、校验框架通过starter引入即可,省去大量XML配置。对一个半年周期内还要写论文、画图、调格式的毕设来说,时间要花在业务上,而不是环境折腾上。

第三,Vue的生态成熟,Element UI的组件覆盖了后台管理系统需要的表单、表格、弹窗、分页、菜单、面包屑,套上就能出一套像样的界面。对非设计出身的开发者来说,这是性价比最高的选择。

当然,如果你对Java不熟,备选方案是Python的Django/Flask搭配Vue,或者Node.js的Express前后端同构,但考虑到答辩时老师普遍更认可Java系列,而且社区里能找到的参考源码和文档最多,我还是推荐Spring Boot。

2.2 整体架构与请求链路

系统整体采用标准的前后端分离架构。前端是Vue加Vue Router加Vuex加Axios加Element UI,使用Vite构建;后端是Spring Boot加MyBatis-Plus加MySQL,接口遵循RESTful风格;开发阶段前端通过Vite代理转发请求到后端的8080端口,生产环境则把前后端分别部署。

一条典型的请求链路是这样:用户打开登录页输入账号密码,前端调用/api/login接口,后端认证成功后返回一个Token;前端把Token存到localStorage,Axios拦截器里每次请求都带上Authorization头;后端通过拦截器校验Token,解析出当前用户和角色;再到Service层做业务判断,Mapper层执行SQL,最终把结果以统一返回结构{ code, message, data }返回给前端。这个统一返回结构很重要,前端可以直接根据code判断成功与失败,不需要在每一个请求里单独处理异常。

架构上分层做得很清楚:Controller层只做参数接收和结果封装,不写业务;Service层写核心逻辑,比如账单生成、合同状态流转;Mapper层只负责SQL与实体映射。这样做的好处是答辩时老师问“你的事务怎么控制的”,你就能直接说账单生成是多表操作,在Service上加@Transactional,保证合同状态和账单数据不会出现一半成功一半失败。

2.3 数据库设计思路与核心表结构

数据库设计是整个项目的地基,我把核心表结构列出来,并把设计原因讲清楚。

商铺表shop:

字段类型说明
idbigint主键自增
shop_novarchar(20)铺号,唯一索引
shop_namevarchar(100)商铺名称
floorvarchar(10)所在楼层
areadecimal(10,2)建筑面积(平方米)
rent_unit_pricedecimal(10,2)月租金单价(元/平米)
property_fee_unit_pricedecimal(10,2)物业费单价(元/平米/月)
statustinyint状态(0空闲 1已租 2装修中 3停用)
remarkvarchar(255)备注

租户表tenant:id、tenant_name、phone、id_card、emergency_contact、emergency_phone、create_time。合同表contract设计时要注意冗余字段:id、contract_no、shop_id、tenant_id、start_date、end_date、monthly_rent、monthly_property_fee、deposit、rent_increase_rate、status(1生效 2已到期 3已解约)、sign_date、remark。

收费记录表charge_record:id、contract_id、charge_type(1租金 2物业费 3水费 4电费 5滞纳金)、amount、period(账期,如2025-03)、status(0未缴 1已缴 2逾期)、pay_date、pay_method、operator_id。报修表repair_record、公告表notice、用户表sys_user、角色表sys_role、用户角色关联表sys_user_role、操作日志表sys_log。

设计上的几个关键点要单独讲:

外键关系和查询效率。业务表之间有关联,比如合同表关联shop_id和tenant_id,但我在数据库层面不建物理外键,而是在代码里保证引用关系。物理外键在删数据时会带来一堆约束麻烦,线上系统也普遍不推荐用,关键是索引要建对。对shop_id、tenant_id、contract_id这些关联字段都要建普通索引,否则数据量上来后联表查询会变慢。

金额字段一律用decimal而不是float/double。Java后端用BigDecimal接收,数据库用decimal(10,2)。浮点数算金额,0.1加0.2不等于0.3的问题在答辩现场非常尴尬,这一点一定要提前规避。

时间字段用datetime,所有表都带上create_time和update_time。MyBatis-Plus的自动填充功能可以让插入和更新时自动填这两个字段,省去手动维护。

账期period字段为什么不直接用日期?因为账单是按自然月生成的,2025-03这个字段在统计月度收入时直接group by period就可以,不需要再转换日期格式。这个设计是业务驱动出来的,不是拍脑袋决定的。

3. 核心模块实现深度解析

3.1 商铺管理模块:状态流转是关键

商铺管理表面上是增删改查,核心是状态流转。一间商铺可以从空闲变成已租、从已租变成装修中、再重新变成空闲,也可以被停用。我在后端定义一个枚举ShopStatus,在Service层做流转校验:只有“空闲”状态的商铺才能签新合同变成“已租”;“已租”状态的商铺不允许直接删除,只能走退租流程;“停用”状态的商铺不参与出租率统计。

这里有一个好用的技巧:凡是需要和业务状态打交道的修改操作,都写专门的Service方法,而不是让前端直接调通用update接口传一个status字段。比如签约操作是createContract(),里面做了三件事:校验商铺状态、插入合同记录、更新商铺状态为已租。这样状态流转的逻辑只在服务端,前端只能通过业务接口触发,避免了有人绕过校验直接改状态的情况。代码里用@Transactional包住整个方法,任何一个环节出错都回滚。核心方法大概长这样:

@Transactional(rollbackFor = Exception.class) public void signContract(SignContractRequest req) { Shop shop = shopMapper.selectById(req.getShopId()); if (shop == null || !ShopStatus.FREE.equals(shop.getStatus())) { throw new BusinessException("该商铺当前不可出租"); } Contract contract = new Contract(); contract.setShopId(req.getShopId()); contract.setTenantId(req.getTenantId()); contract.setStartDate(req.getStartDate()); contract.setEndDate(req.getEndDate()); contract.setMonthlyRent(req.getMonthlyRent()); contract.setDeposit(req.getDeposit()); contract.setStatus(ContractStatus.ACTIVE); contractMapper.insert(contract); shop.setStatus(ShopStatus.RENTED); shopMapper.updateById(shop); generateFirstMonthBill(contract.getId()); }

3.2 合同管理模块:日期与关联状态的处理

合同管理是这个项目里业务逻辑最重的部分,我踩过不少坑。先说签约时租金怎么算。

商铺表里存的是“租金单价(元/平米/月)”,但合同里存的是“月租金(元)”。签约时要根据面积乘以单价算出来,比如一间50平米的铺子,单价80元/平,月租金就是4000元。为什么合同里要冗余一个月的租金总价而不是每次现算?因为将来可能单独对某个合同调价,比如商圈整体涨价后给老租户打折,如果只存单价,历史合同的金额会跟着变化,财务核算就乱了。所以合同一旦生成,月租金就以合同为准,商铺的单价只作为新建合同的默认值,互不影响。

日期处理也是重点。合同的start_date和end_date用Java的LocalDate来接收,向前端返回时格式化成yyyy-MM-dd。计算租期内到底要生成几个月的账单,有一种常见做法是用ChronoUnit.MONTHS.between(start, end)再加1,比如3月1日到5月31日是3个月。这里有个坑:ChronoUnit计算的是两个日期之间的完整月数,如果合同是3月15日到5月14日,算出来是2个月,但实际按自然月算账单可能是跨了3、4、5三个月的部分月份。所以我在生成账单时不用这个API,而是按月从合同开始月份循环到结束月份,每个月生成一条账期记录,金额按整月收。虽然部分月份实际不是整月,但社区商铺的结算习惯通常都是按月整收,这种约定俗成的规则在计费逻辑里要先和需求方确认清楚。

合同到期提醒功能也建议做。可以每天跑一个定时任务,扫一遍状态为生效、结束日期在30天内的合同,把记录写入提醒列表,让管理员在首页看到。这个功能不复杂,但对“系统有没有用”的感知提升很明显。

3.3 收费管理模块:账单生成与核销

收费模块是核心中的核心。我设计了一个计费方法generateMonthlyBill(contractId, yearMonth),逻辑是:

  1. 查询合同是否在指定月份处于生效状态;
  2. 检查该月份是否已经生成过账单,有则跳过,避免重复生成;
  3. 根据合同中的月租金和月物业费,生成两条charge_record记录,一条charge_type等于1租金,一条等于2物业费;
  4. 如果有水电费抄表记录,再生成对应的水费、电费记录。

账单生成时机有两种做法:一种是在合同生效时一次性生成整个租期的所有账单;另一种是每个月生成当月的。我推荐后者,因为合同可能中途变更、退租或者调价,一次性生成一堆账单后面要维护很麻烦。每个月由管理员点一下“生成本月账单”,或者配置一个定时任务每天跑一次检查本月是否已生成,就能覆盖绝大多数场景。SQL里用唯一索引unique key uk_contract_period_type(contract_id, period, charge_type)来保证一笔费用在一个账期内只能有一条记录,这是数据库层面的兜底。

收款核销的流程是:管理员看到某条账单状态未缴,点击“收款”,弹出收款窗口,记录收款方式、实收金额(允许部分减免并把减免原因写入备注)、收款时间,然后更新账单状态为已缴。这里业务上有个处理要点:拖欠缴费时要不要自动产生滞纳金。社区商铺通常按合同约定滞纳金比例,比如每日千分之三,但计算起来比较复杂,我的做法是在退租结算时统一计算未缴账单的滞纳金,日常不自动累加,这样既满足业务预期又减少了定时计算的负担。

3.4 统计报表与可视化

统计报表是答辩时的加分项,我建议至少做三个视图。

出租率趋势图:按月统计每个月的出租商铺数除以总商铺数,前端用ECharts画折线图。SQL上可以从合同表里查出每个月份有效合同的数量,也可以用商铺表中status等于已租的数量除以总数。前一种能看历史趋势,后一种只能看当前快照,答辩展示建议用前一种,更有“数据感”。

月度收入趋势图:按实收月份统计charge_record里status等于已缴的账单金额合计,能看出收租高峰和低谷。这里注意统计口径是“实收月份”而不是“账期月份”,因为可能存在补缴以前月份款项的情况,两个口径的差别在答辩时讲清楚了,老师会认可你对统计口径的敏感度。

费用类型占比饼图:统计一个时间段内租金、物业费、水电费各自的总额和占比。这个图最直观,非技术背景的人也能一眼看懂。

做报表时SQL层建议直接写分组统计,不要先把明细查出来然后在Java里for循环累加。虽然对数据量小的毕设来说性能差别不大,但写一手“用SQL完成聚合”的代码在答辩时更显得有工程素养。例如当月收入统计:

select DATE_FORMAT(pay_date, '%Y-%m') as month, sum(amount) as total_income from charge_record where status = 1 and pay_date is not null group by DATE_FORMAT(pay_date, '%Y-%m') order by month desc;

4. 实操过程与部署实施

4.1 从零初始化项目的环境准备

在开始写代码前,先把环境准备好,我列一个清单:

  • JDK 1.8或11(我用1.8,兼容性最好)
  • Maven 3.6以上
  • MySQL 5.7或8.0(我用5.7,如果8.0要注意驱动和时区配置)
  • Node.js 14以上
  • IDEA、Navicat或DBeaver

数据库初始化时,建一个名为community_shop的库,字符集选utf8mb4。然后执行建表脚本,我习惯把所有建表语句放进一个schema.sql里统一管理,再插一批测试数据。测试数据很重要,不只是为了演示效果,更是为了让自己在开发阶段就面对真实数据形态,比如有的商铺已经出租多年、有的上个月刚退租、有些账单逾期未缴,这样页面上的图表才不会显得空洞。

后端项目用Spring Initializr生成,引入spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、spring-boot-starter-validation、jjwt这些依赖。application.yml里配置数据源、MyBatis-Plus的mapper扫描、JWT密钥和过期时间。注意数据库连接URL里一定要带serverTimezone=Asia/Shanghai,否则连上库以后时间字段会差8个小时,这是个特别经典的坑。配置示例:

spring: datasource: url: jdbc:mysql://localhost:3306/community_shop?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-secret-key expire-hours: 24

4.2 核心接口与前后端联调

后端接口统一以/api开头,通过JWT拦截器做认证。登录接口、查询商铺列表接口、生成账单接口分别对应不同的权限点。我把主要接口列出来:

  • 商铺相关:GET /api/shop/page、POST /api/shop、PUT /api/shop、DELETE /api/shop/{id}、PUT /api/shop/{id}/status
  • 租户相关:GET /api/tenant/page、POST /api/tenant、PUT /api/tenant
  • 合同相关:POST /api/contract/sign、GET /api/contract/page、PUT /api/contract/renew、PUT /api/contract/terminate
  • 收费相关:POST /api/charge/generateMonthly、GET /api/charge/page、POST /api/charge/pay/{id}
  • 统计相关:GET /api/stats/rentRate、GET /api/stats/monthlyIncome、GET /api/stats/incomeType

前后端联调时的重点在Token传递。前端Axios的request拦截器里加Authorization头,response拦截器里对code等于401的统一跳转登录页。一开始最容易出的问题是跨域,开发环境我是在Vite的配置里加了proxy,把/api前缀的请求代理到http://localhost:8080,这样浏览器的请求是同源的,就不需要后端额外开放CORS。这个方法推荐优先用,比在Spring里配@CrossOrigin更干净,也不会影响生产部署。

另外,“导出Excel”功能我强烈建议做进去。社区商铺管理方经常要把账单表发出去对账,一个导出按钮能生成Excel文件,在毕设里既是一个实用亮点,又能在答辩时现场演示,成本却很低。做法是后端引入easypoi依赖,把查出来的收费记录列表直接转成Excel响应给前端,前端用window.open下载或者axios接收blob。这个功能属于“投入小、产出高”的典型代表。

4.3 本地运行与打包上线的完整流程

整个项目在本地跑起来只需要几步:

  1. 启动MySQL,执行初始化脚本导入表结构和测试数据;
  2. 修改application.yml里的数据库账号密码;
  3. 在IDEA里启动Spring Boot后端,看到Started日志说明成功,默认端口8080;
  4. 在前端目录执行npm install安装依赖,再npm run dev,Vite默认5173端口会打开登录页面;
  5. 用管理员账号登录,进入后逐个模块点一遍,确认接口和数据都正常。

如果要把项目打包部署到服务器,后端执行mvn package,会生成一个可执行的jar包,把jar、MySQL、前端构建产物放到同一台服务器上;前端执行npm run build,产物是一个dist目录,用Nginx做静态托管,并把/api路径反向代理到Spring Boot的8080端口。Nginx配置里server_name、root、location /api的proxy_pass是关键。前端路由如果是history模式,需要在Nginx里配置try_files,否则刷新页面就会404,这个坑几乎每个第一次部署前后端分离项目的人都会遇到一次。Nginx核心配置:

server { listen 80; server_name your-domain.com; root /opt/community-shop/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }

5. 常见问题与排查技巧实录

5.1 高频问题速查表

我在开发和带学生做类似项目时,遇到最多的问题基本集中在下面这些,整理成一张速查表:

现象可能原因解决方案
登录页验证码不显示或接口404后端没启动或路径不对检查控制台日志,确认端口和上下文路径
前端请求接口报CORS错误未配置代理或未开CORS用Vite proxy代理,或后端配置跨域过滤器
数据库时间差8小时连接URL缺serverTimezone加上serverTimezone=Asia/Shanghai
中文乱码连接字符集不是utf8mb4连接URL加characterEncoding=utf8,库表字符集统一
npm install卡住依赖源太慢设置镜像源加速
账单生成重复唯一索引没建对contract_id、period、charge_type加唯一索引
刷新页面404前端history路由部署未配置Nginx加try_files规则
导出Excel中文乱码浏览器下载文件名编码文件名URLEncode后放入Content-Disposition
修改数据后列表不刷新前端缓存或查询条件问题检查分页参数和响应数据,清缓存调试

5.2 答辩时如何把项目讲清楚

毕业设计的最后一道坎是答辩。很多同学代码写完了,但讲的时候只能念PPT,很吃亏。我给几个建议:

一定要准备一张“业务流程图”或者“系统功能结构图”,答辩时第一件事就是讲清楚这个系统是给谁用的、解决什么问题,然后顺着“商铺建档→签约→月度收费→退租结算”这条主线讲,把每个环节对应的页面和接口指出来。时间控制在5分钟内,重点是体现出你理解业务,而不是只会敲代码。

准备几个“为什么”答案。老师必问的问题包括:为什么选MySQL不选Oracle?为什么金额用decimal?为什么不用物理外键?合同到期提醒是怎么做的?权限控制怎么实现的?这些问题我在前面的内容里都已经把答案给了,关键是要自己理解、用自己的话表达出来。

还有一个小技巧:答辩演示时准备好一组完整的演示数据,包括一间已经出租一年多的商铺、一间空铺、几条不同状态的电费账单,以及两个不同权限的账号。演示时先讲普通操作,再登录管理员账号演示权限差异,最后打开统计图表页面,连续操作下来,整套系统给人的印象就是完整、有深度的,而不是只做了个登录加两个列表的“半成品”。

5.3 实际开发过程中我踩过的三个坑

第一个坑是分页插件导致的SQL异常。MyBatis-Plus的分页查询需要单独配置一个分页拦截器,如果没配置,Page对象返回的total始终为0,你以为是自己代码写错了,其实只是少装了一个插件。第二个坑是Element UI的日期选择器返回的格式问题,如果你把DatePicker的value-format不设成yyyy-MM-dd,前端拿到的就是标准时间对象,直接传给后端会导致格式解析报错。第三个坑是权限框架的引入顺序,一旦引入拦截器后发现静态资源和登录接口都被拦截,排查的思路是先看拦截器排除列表是否包含了登录接口和静态路径,这个顺序问题我在做项目时调试了整整一个下午。

排查思路分享:遇到问题不要慌,先看后端日志,再看浏览器Network面板。后端日志定位是参数问题、SQL问题还是业务异常,Network面板确认请求有没有发出去、响应码是多少、返回体是什么。90%的联调问题靠这两个工具就能解决。

6. 经验总结与扩展建议

整套系统做下来,我个人最大的体会是:毕设项目的难不在“代码量”,而在“业务想清楚”。社区商铺管理系统的每个模块单看都不难,但把它们连成一条完整的业务链,让数据在商铺、租户、合同、账单之间保持一致,这个过程才是真正的锻炼。

如果这个项目你想继续扩展,方向其实很多。可以加一个租户端小程序,让租户在手机上查看账单并在线缴费、提交报修申请;可以在收费模块里加入对接微信支付或支付宝的沙箱环境,做成在线支付闭环;也可以加入更细的权限粒度,比如每个管理员只能管理自己负责的楼栋。想走数据分析方向的话,还可以把出租率、回款率、空置率做成大屏看板,展示效果会更有冲击力。

最后分享一个做毕设时很实用的小习惯:每写完一个模块,就写一小段测试代码或者用Postman把接口验证一遍,并把验证记录贴在项目的README里。这样到写论文的时候,“系统测试”这一章的内容自然就有了,不用最后几天熬夜补材料。希望这篇文章能帮你少走一些弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进