
做课程设计或者毕业设计选“交叉路口行人非机动车流量调查统计分析”这个题目的人我猜多半是被城市交通里那些“数人头”的破事逼过——路口的行人、电动车、自行车到底有多少早高峰集中在几点哪个方向压力最大这些问题要是没有系统全靠人工蹲点计时拿着一张纸质表格手写记录回去再对着Excel敲半天。等数据汇总完早高峰已经变成了过期高峰报告出来的时候又该重新调查了。我这次做的这套系统正是把线下这套流程搬到线上后端用SpringBoot数据存MySQL前端用ECharts出图表。管理员维护路口信息和用户账号调查员按时间段、按方向录入行人流量和非机动车流量系统自动完成按小时、按早晚高峰、按路口维度的统计分析和可视化展示顺便还支持Excel导出和数据大屏演示。整套东西做完从录入到出报告时间压缩到原来的十分之一而且是随查随出不用等人加班汇总。这篇文章适合三类人细看一是正在做课程设计或毕业设计的学生二是想快速搭一个流量调查小工具的课题组或交管外协人员三是想入门SpringBoot全栈开发、需要一套完整业务闭环案例的初学者。下面我就从选题拆解开始把系统的设计思路、数据库建模、核心代码、踩坑记录和交付经验完整过一遍尽量把能避开的问题都提前说清楚。1. 选题价值与系统边界交叉路口流量调查到底要解决什么问题1.1 人工统计模式的真实痛点流量调查这事儿听起来简单做起来全是细节。传统做法是找一个路口安排两到三个人分别盯行人、非机动车、机动车手里拿着计数器或者纸质表格按15分钟或者1小时为单位记录每个进口方向的通行量。一天下来纸质记录表能攒一摞。问题出在后面这些环节数据口径不统一。有人记“行人流量”只管步行的人有人把推着自行车走的人也算了进去还有人把电动车和自行车混在一起记。等到汇总的时候两个调查员的数据根本对不上。手工录入效率低。一天的量录入Excel至少要两三个小时还容易敲错数字。统计维度太死板。领导临时要一个“周一早高峰东进口的行人流量”负责汇总的人就得重新翻原始表用筛选器慢慢拉运气好几分钟运气不好半小时。纸质表容易丢。调查表本身没有备份机制丢一张表这一天的数据就废了。这套系统的第一个价值就是把“现场记录—手工录入—手动统计—出报告”这条链路缩短。调查员在系统里直接录入数据进库即完成归档统计分析由程序自动跑一分钟出图表、出Excel。说白了计算机干的就是这种脏活累活。1.2 功能需求清单与角色设计一个能说服答辩老师、也能应付真实场景的系统功能至少要覆盖三个层次基础数据维护、流量数据采集、统计分析输出。我整理出的功能清单如下模块功能点说明登录与权限用户登录、退出、密码修改管理员与调查员分角色控制路口管理路口的增删改查记录路口名称、编码、所属区域、经度纬度用户管理管理员维护调查员账号重置密码、禁用账号流量录入手动录入单条流量数据路口、日期、时段、方向、行人/非机动车量批量录入按路口和日期批量录入多条减少重复选择路口的时间统计报表按路口/日期/时段/方向多维度统计小时趋势、方向占比、峰谷值可视化大屏综合展示核心指标适合答辩演示也是加分项Excel导出导出明细表和统计表替代手工Excel汇总角色就设计两个不搞花里胡哨的东西管理员拥有全部模块权限负责维护路口、管理用户、查看所有流量统计。调查员只能录入流量数据、查看和导出统计数据不能动基础配置。两个角色加一个登录页足够说明白“权限控制”这个考点又不会让代码量失控。对课程设计来说这个规模刚刚好。1.3 明确项目边界哪些功能刻意不做很多学生做毕设容易跑偏恨不得把视频识别、信号灯联动、地图导航全部塞进去最后代码写不完演示还翻车。我在这个项目里刻意砍掉了几块内容在论文的需求分析里也写清楚了不做视频监控与AI识别。行人非机动车检测涉及目标检测算法训练数据和算力成本高不是课程设计阶段能稳定交付的。现场人工调查仍是行业里一种基础的数据采集方式。不做实时信号灯联动。那需要和交管信号控制设备对接涉及协议和设备权限普通项目碰不了。不接入地图在线服务。在线地图依赖外网接口答辩现场网络环境不稳定万一断网就是事故。路口经纬度我只做普通字段存储。不采集个人隐私数据。系统只统计“流量数字”不记录行人面部、车牌等信息这也符合数据合规的基本要求。把边界划清楚答辩老师问“你为什么不做AI识别”的时候你就可以有理有据地说题目定位是“调查统计分析系统”统计分析的完整闭环才是核心AI识别是另一个技术栈的问题不属于本项目范围。这种取舍能力本身就是评分点。2. 技术选型取舍SpringBoot、MySQL、ECharts这套组合为什么够用2.1 SpringBoot版本与JDK怎么定后端技术选型我的结论是用SpringBoot 2.7.x JDK 8。别一上来就追SpringBoot 3也别用JDK 17原因很简单学校机房电脑、你宿舍笔记本、答辩教室的投影机最稳的环境就是JDK 8。JDK 8配SpringBoot 2.7生态成熟网上资料多遇到问题搜索一下基本都能解决。SpringBoot 3要求JDK 17确实新但很多老教程和工具链还没跟上对一个课程设计来说没必要冒这个险。Maven项目结构上我用的是标准三层分包com.campus.traffic ├── controller // 接收请求、参数校验 ├── service // 业务逻辑 ├── mapper // MyBatis接口 ├── entity // 实体类 ├── config // 配置类拦截器、跨域、JSON序列化 └── common // 公共返回结果、异常处理、工具类2.2 ORM选MyBatis-Plus而不是JPAORM框架我对比过MyBatis-Plus和Spring Data JPA最终选了MyBatis-Plus。理由很实际对比项MyBatis-PlusSpring Data JPA单表CRUD内置BaseMapper零SQL内置接口零SQL复杂统计SQL手写SQL完全可控JPQL或原生SQL绕来绕去分页分页插件一行配置Pageable但复杂查询配置麻烦学习成本会MyBatis基本秒上手要理解实体关系映射坑多流量统计这个项目核心是大量分组聚合查询比如“按小时分组求总和”“按方向分组求平均值”。这种SQL用MyBatis直接写最直白一眼能看懂出问题也好排查。JPA做简单增删改查很爽一旦涉及这种多表分组统计生成的SQL绕得你怀疑人生。2.3 前端方案服务端渲染兜底能少一半麻烦前端我见过两种路线完全前后端分离Vue3 Element Plus Axios Vite。服务端渲染SpringBoot Thymeleaf模板 AdminLTE后台模板 ECharts。我实际用的是Thymeleaf AdminLTE ECharts这套。原因很现实课程设计不要求你证明“会搭前端工程”要求的是“系统能跑、逻辑完整”。服务端渲染直接打成jar包一个进程全搞定不用部署Nginx、不用处理跨域、不用愁前端打包产物放哪。对于答辩演示稳定是第一位的。ECharts是图表库的主力折线图、柱状图、饼图都靠它。数据接口我设计成返回统一的JSON结构页面通过Ajax拉取后塞给图表。2.4 项目名叫“大数据”真的需要上Hadoop吗这个问题答辩的时候一定会被问干脆自己先说清楚。项目名里有“大数据”三个字本质上是因为流量数据在时间、路口、方向、类型多个维度下展开后记录条数积累速度快统计分析具有“大数据处理”的思路和特征。但课程设计的数据量撑死几千上万条MySQL单机完全能扛不需要分布式存储和计算。打个比方你不能因为公司叫“环球贸易”就要求实习生去开航空母舰。真正能体现水平的地方在于统计口径严谨、聚合逻辑正确、查询效率合理而不是套一个Hadoop外壳。如果将来数据量真的涨到百万级别这个项目的统计SQL配合索引、分区表、定时汇总表也能撑住再往后才需要考虑引入离线数仓那套东西。在论文里把这个逻辑写明白技术选型这一节就立住了。3. 流量数据建模表结构设计、统计口径与核心查询3.1 五张核心表先把关系理清数据库设计是整个系统能不能立的根基。我最终设计了五张表外加一张字典表关系不复杂但每张都有用途表名用途关键字段sys_user用户表id, username, password, real_name, role, statusintersection交叉路口表id, inter_code, inter_name, region, longitude, latitudetraffic_flow流量记录表id, inter_id, flow_date, period, direction, ped_count, non_motor_countsys_role角色表可选id, role_code, role_nameflow_dict流量类型字典可选id, type_code, type_name实际上角色字段我直接放进了用户表用role字段区分ADMIN和SURVEYOR省一张表。字典表是给流量类型扩展用的目前系统里分“行人”和“非机动车”两大类但以后想增加“摩托车”“三轮车”加字典记录就行不用改表结构。traffic_flow表是整个系统的核心设计时要特别注意字段含义字段名类型说明flow_datedate流量统计日期精确到天periodvarchar(32)时段标识如“07:00-08:00”directionvarchar(16)方向东/南/西/北或NE/SE/SW/NWped_countint行人流量单位人次non_motor_countint非机动车流量单位辆次这里的每一行代表某个路口某一天某一个小时内、某一个方向、行人多少、非机动车多少。这个粒度定得合适既能支持小时级分析也能通过SQL聚合出早高峰、晚高峰的数据不用再拆表。3.2 统计口径方向、时段、折算系数怎么约定统计口径是最容易被忽略、却又最影响系统可信度的地方。我在设计文档里写死了三条规则流量单位行人按“人次”计非机动车按“辆次”计。一个人走过马路就算1人次同一人往返算2人次不做去重。因为流量调查本身关注的是通行压力不是精准到人的身份识别。方向约定以路口中心为参照按“东进口”“南进口”“西进口”“北进口”四个主方向记录。如果是五岔路口、环形路口用“东北”“西南”等辅助方向direction字段设计成varchar就是为了兼容扩展。时段折算现场调查常以15分钟为一个计数周期。如果录入的是15分钟数据系统按系数4折算成小时流量如果直接录入1小时数据系数就是1。我在页面里加了一个“录入周期”下拉框选了“15分钟”保存时自动乘4。这个细节在论文里写出来老师会觉得你懂业务。示例说明 某路口早高峰07:00-09:00东进口行人数 07:00-08:00 记录 320 人次 08:00-09:00 记录 405 人次 则该路口该方向早高峰行人总流量 320 405 725 人次3.3 核心统计SQL聚合查询就是这么写出来的流量统计分析靠的是几条聚合SQL这里给出最关键的几个直接贴在Mapper里就能用。查询某路口某天各时段行人流量变化折线图数据源SELECT period, SUM(ped_count) AS total_ped, SUM(non_motor_count) AS total_non_motor FROM traffic_flow WHERE inter_id #{interId} AND flow_date #{flowDate} GROUP BY period ORDER BY period;查询某路口某天各方向流量占比饼图数据源SELECT direction, SUM(ped_count) AS total_ped, SUM(non_motor_count) AS total_non_motor FROM traffic_flow WHERE inter_id #{interId} AND flow_date #{flowDate} GROUP BY direction;查询所有路口的流量排行柱状图数据源SELECT i.id, i.inter_name, SUM(t.ped_count) AS total_ped, SUM(t.non_motor_count) AS total_non_motor FROM intersection i LEFT JOIN traffic_flow t ON i.id t.inter_id WHERE t.flow_date BETWEEN #{startDate} AND #{endDate} GROUP BY i.id, i.inter_name ORDER BY total_ped DESC;这几条SQL写出来统计模块的大半功能就有底了。剩下的“早高峰对比”“周同比”无非是在WHERE条件里再加日期范围、在SELECT里再加聚合条件的事。3.4 索引与查询性能毕设也得讲基本法流量记录表是数据增长最快的表索引设计不能马虎。我建了两个联合索引ALTER TABLE traffic_flow ADD INDEX idx_inter_date (inter_id, flow_date); ALTER TABLE traffic_flow ADD INDEX idx_date (flow_date);第一个索引用于“按路口日期”查询这是最常见的统计维度查询时能直接命中索引缩小范围。第二个索引用于跨路口的时间段统计。方向、时段这些字段基数太小单独建索引意义不大反而浪费空间。至于数据量毕设阶段用个几千条模拟数据跑统计SQL都毫秒级返回。但为了展示“大数据”的思维方式我在论文里提了一嘴当单表超过百万记录时可以按月做分区表统计SQL只扫当月分区性能依旧可控。这一步属于“设计思想的完备性”代码不用真写。4. 核心功能代码落地登录、录入、统计、导出一条龙实现4.1 登录认证与拦截器JWT加个拦截器就够了用户登录我用的JWT方案逻辑很简单登录成功生成一个带用户名和角色信息的token返回给前端。前端存到localStorage每次请求带上后端拦截器校验token合法性。生成token的代码用一个工具类搞定public class JwtUtil { private static final SecretKey KEY Keys.hmacShaKeyFor( campus-traffic-secret-key-2024.getBytes(StandardCharsets.UTF_8)); public static String generateToken(String username, String role) { return Jwts.builder() .setSubject(username) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 24 * 3600 * 1000)) .signWith(KEY, SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder().setSigningKey(KEY).build() .parseClaimsJws(token).getBody(); } }拦截器配置Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /logout, /css/**, /js/**, /img/**, /lib/**); }密码存储用MD5加盐虽然现在流行BCrypt但课程设计里写清楚“MD5盐”的局限性、再指出生产环境应该用BCrypt比单纯追新显得更懂行。4.2 MyBatis-Plus分页配置一行都不能省分页插件一定要在配置类里注册否则调用Page方法时数据会全部返回表面看“没报错”实际统计结果却是错的。这个坑我第5章还会细讲先贴正确配置Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }分页查询代码PageTrafficFlow page new Page(current, size); LambdaQueryWrapperTrafficFlow wrapper new LambdaQueryWrapper(); wrapper.eq(TrafficFlow::getInterId, interId) .eq(TrafficFlow::getFlowDate, flowDate) .orderByAsc(TrafficFlow::getPeriod); trafficFlowMapper.selectPage(page, wrapper);4.3 流量录入的交互细节减少重复操作才是好设计流量录入如果设计成“每次只能录一个方向一个时段”调查员录一天的数据要点几十次保存按钮会烦死。我在录入页面做了两层优化页面默认带出路口和日期用户只需要选时段、方向填数字自动保存后表格实时刷新。批量录入模式一个页面用表格展示24个时段 × 4个方向用户横着一行行填最后点一次“保存本页数据”。后台接收一个列表事务插入。后台批量保存代码Transactional public void batchSave(ListTrafficFlow flowList) { for (TrafficFlow flow : flowList) { if (flow.getPedCount() null flow.getNonMotorCount() null) { continue; // 跳过空行 } trafficFlowMapper.insert(flow); } }4.4 统计Service层设计把业务逻辑从Controller里解放出来统计模块我单列了一个TrafficStatsService避免Controller里塞一堆SQL招人烦。核心方法设计public interface TrafficStatsService { // 获取某路口某天各时段流量趋势 ListMapString, Object getHourlyTrend(Long interId, LocalDate date); // 获取方向分布 ListMapString, Object getDirectionDistribute(Long interId, LocalDate date); // 获取路口流量排行榜 ListMapString, Object getIntersectionRank(LocalDate start, LocalDate end); // 获取早晚高峰对比 MapString, Object getPeakCompare(Long interId, LocalDate date); }每个方法内部三步走拼参数→调Mapper执行聚合SQL→把结果构造成前端图表需要的JSON结构。比如折线图需要{ periods: [...], pedData: [...], nonMotorData: [...] }这个结构在Service里组装Controller只负责转发。4.5 Excel导出EasyExcel让代码量骤减Excel导出我用的EasyExcel相比原生的Apache POIAPI更友好内存占用还低。导出统计表的核心代码GetMapping(/export) public void export(HttpServletResponse response, RequestParam Long interId, RequestParam String startDate, RequestParam String endDate) throws IOException { ListTrafficFlowStatVo list statsService.getStatForExport(interId, startDate, endDate); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(流量统计表, UTF-8); response.setHeader(Content-disposition, attachment;filename fileName .xlsx); EasyExcel.write(response.getOutputStream(), TrafficFlowStatVo.class) .sheet(流量统计) .doWrite(list); }TrafficFlowStatVo里用ExcelProperty(路口名称)注解标注列名导出的表头就自动对应中文不用手工拼。4.6 全局异常与统一返回结果REST接口我统一返回ResultT结构告诉前端成功还是失败、返回什么数据、带什么提示信息。再配一个RestControllerAdvice全局异常处理器把SQL异常、参数异常统一拦截前端不用每处都写try-catch。这个设计在论文“系统非功能性设计”里能凑一节在答辩“你项目里有什么亮点”里也能讲。public class ResultT { private Integer code; // 200成功500失败 private String message; private T data; }5. 实测运行与高频踩坑答辩前你最好先过一遍这些问题5.1 启动即报错时区问题几乎人人遇到第一次启动SpringBoot项目连MySQL时直接报The server time zone value ... is unrecognized。原因很简单MySQL 8.0默认时区不是中国的JDBC驱动要明确告诉它。解决方法是连接串加上参数jdbc:mysql://localhost:3306/traffic_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue另外MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver跟MySQL 5.x的com.mysql.jdbc.Driver不一样别照老教程抄错了。5.2 分页功能形同虚设MyBatis-Plus的经典坑我在前期测试的时候发现调用selectPage后返回的total竟然是全部记录数每页数据也没按条数截断。查了一下就是分页拦截器没生效。原因MyBatis-Plus的分页插件需要手动注册到MyBatis拦截器链中光引入依赖不注册等于没用。第4章里那段MybatisPlusConfig就是解决方案。这个问题典型到几乎每个班级都会有人遇到写在论文测试章节能体现你踩过坑、会排查。5.3 前后端跨域问题服务端渲染绕开了分离开发绕不开如果用Thymeleaf服务端渲染Ajax请求同源没有跨域问题。但如果你分了前后端、前端跑在8081端口后端在8080端口那Ajax请求必然跨域。解决办法是在后端配置CorsFilter允许指定来源访问Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOrigin(http://localhost:8081); config.addAllowedMethod(*); config.addAllowedHeader(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }5.4 LocalDateTime返回给前端变成数组Jackson序列化没配不配置JackSon时LocalDateTime会默认序列化成[2024, 5, 20, 14, 30, 25]这种数组形式前端拿到的不是想要的2024-05-20 14:30:25。要加JavaTimeModule配置并指定格式Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder - { builder.serializers(new LocalDateTimeSerializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializers(new LocalDateTimeDeserializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; } }5.5 ECharts图表不渲染先看数据再看DOMECharts图表空白九成问题不出在ECharts本身而是出在数据上。我排查的顺序是浏览器F12打开Network看接口返回是不是200返回数据里period字段和图表需要的字段名是否一致。打开Console看有没有JS报错最常见的是Cannot read properties of undefined (reading length)说明数据是空的。第三步才检查初始化时机图表容器是否已经渲染完毕。如果有display:none的Tab页里放图表需要等Tab切换后再调用echarts.init和setOption。5.6 中文乱码三件套中文乱码问题通常出现在三个地方位置表现解决数据库存储表字段变成问号建库用utf8mb4连接串加characterEncodingutf8响应返回接口中文乱码response.setCharacterEncoding(utf-8)Excel导出文件名乱码URLEncoder.encode处理文件名这个问题看似小一旦出现答辩演示时特别尴尬。提前检查一遍别等演示翻车。6. 源码、数据库脚本与万字文档顺利交付的关键经验6.1 源码怎么组织才不会在答辩时找不到文件源码组织有两点建议一是package命名规范点大写驼峰和全小写混乱很掉印象分二是关键代码加注释不用多每个类顶部两三行“这个类干什么”即可。我建议在项目根目录放一个README.md写清楚JDK版本、Maven版本、MySQL版本数据库初始化步骤启动命令mvn spring-boot:run或打包后运行初始管理员账号到时候答辩老师拷走代码自己跑不起来问题全出在README没写清楚。README写好了印象分直接拉满。6.2 数据库脚本要交付什么交付数据库脚本至少要给两个文件init.sql完整建库建表语句包括索引、初始管理员账号。seed.sql模拟数据。没有模拟数据系统跑起来图表是空的演示效果大打折扣。模拟数据生成建议用存储过程循环插入DELIMITER // CREATE PROCEDURE generate_flow_data() BEGIN DECLARE i INT DEFAULT 1; DECLARE d DATE; SET d 2024-05-01; WHILE i 1000 DO INSERT INTO traffic_flow (inter_id, flow_date, period, direction, ped_count, non_motor_count) VALUES ( FLOOR(1 RAND() * 4), d, CONCAT(LPAD(FLOOR(7 RAND() * 14), 2, 0), :00-, LPAD(FLOOR(8 RAND() * 14), 2, 0), :00), ELT(FLOOR(1 RAND() * 4), 东, 南, 西, 北), FLOOR(20 RAND() * 400), FLOOR(10 RAND() * 200) ); SET i i 1; IF i % 14 0 THEN SET d DATE_ADD(d, INTERVAL 1 DAY); END IF; END WHILE; END// DELIMITER ; CALL generate_flow_data();千万记得模拟数据得有起伏早高峰数据要明显高于中午别全部随机平铺直叙。否则图表上看不出趋势统计模块“早高峰识别”就没法展示。6.3 万字文档怎么写从目录到答辩老师最关心的章节毕业设计文档一般要写到8000到12000字字数不是问题结构才是。我推荐的目录顺序章节写作重点容易丢分的地方摘要300字左右写清背景、方法、成果写成“本文介绍了…”的套话绪论选题背景、国内外现状缺少对现有调查方式的不足分析需求分析功能需求、非功能需求、用例图没有业务流程图系统设计架构图、技术选型理由只贴代码不解释设计原因数据库设计E-R图、表结构、字段说明缺少索引和口径说明系统实现核心功能代码界面截图全是代码没有文字说明系统测试测试用例表、测试结果没有边界条件测试总结展望项目完成情况、不足“本公司系统…”“具有一定的推广价值”这里特别提示数据库设计那一章把“统计口径”单独写一小节说清楚方向怎么定义、时段怎么折算、单位怎么统一。这是调查统计类系统最容易出彩、也最容易翻车的细节。6.4 答辩演示预演与高频追问答辩演示不要现场敲代码。提前录好操作视频或者准备三到五个演示步骤登录 → 查看大屏 → 录入一条数据 → 刷新统计图表 → 导出Excel。全程控制在五分钟内展示了主要功能又留出时间回答问题。高频答辩问题我按项目实际给你列个清单问为什么选SpringBoot答简化配置、内嵌服务器、生态成熟适合快速开发中小型业务系统对比SSM省去了大量XML配置。问流量数据怎么保证准确性答录入时做范围校验统计口径在文档里明确约定提供导出后人工抽查的机制。问数据量大了怎么办答目前用索引保证查询效率设计上预留分区表方案再大可以引入离线统计与缓存。问行人和非机动车数据分开统计的意义答两类交通参与者的出行规律不同分开统计有助于分方向优化信号配时和非机动车道设置。问这个系统实际能用吗答已经跑通了完整业务流程真实路口数据可以直接投入使用与信号灯联动和AI识别是下一步的扩展方向。6.5 交付包怎么整理最后交付的时候压缩包我按这个结构整理├── source/ // 完整工程源码 ├── sql/ │ ├── init.sql │ └── seed.sql ├── doc/ │ └── 系统设计文档.docx ├── README.md // 运行说明 └── 演示视频.mp4可选README放最外层别人解压后第一个看到按步骤几个命令就能把系统跑起来这份交付才算真正完整。最后说一个我做这个项目时体会最深的地方流量数据录入页面一定要在后台做“时段重复校验”。同一个路口、同一天、同一个时段、同一个方向如果录了两次统计时会直接翻倍而且很难排查。我加了一个联合唯一索引把inter_id flow_date period direction设成唯一键插入重复数据直接报错前台再提示“该时段已录入请检查”。这个设计既能体现在数据库层面的严谨性也是实实在在避免数据翻倍的兜底方案。这个题目做完你再回头看其实就是“一个业务闭环 一套常规技术栈 几个踩坑细节”。如果你正卡在某个环节里希望这些经验能省你几个通宵。