做课程设计或者毕业设计选“交叉路口行人非机动车流量调查统计分析”这个题目的人,我猜多半是被城市交通里那些“数人头”的破事逼过——路口的行人、电动车、自行车到底有多少?早高峰集中在几点?哪个方向压力最大?这些问题要是没有系统,全靠人工蹲点计时,拿着一张纸质表格手写记录,回去再对着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而不是JPA
ORM框架我对比过MyBatis-Plus和Spring Data JPA,最终选了MyBatis-Plus。理由很实际:
| 对比项 | MyBatis-Plus | Spring 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, status |
| intersection | 交叉路口表 | id, inter_code, inter_name, region, longitude, latitude |
| traffic_flow | 流量记录表 | id, inter_id, flow_date, period, direction, ped_count, non_motor_count |
| sys_role | 角色表(可选) | id, role_code, role_name |
| flow_dict | 流量类型字典(可选) | id, type_code, type_name |
实际上角色字段我直接放进了用户表,用role字段区分ADMIN和SURVEYOR,省一张表。字典表是给流量类型扩展用的,目前系统里分“行人”和“非机动车”两大类,但以后想增加“摩托车”“三轮车”,加字典记录就行,不用改表结构。
traffic_flow表是整个系统的核心,设计时要特别注意字段含义:
| 字段名 | 类型 | 说明 |
|---|---|---|
| flow_date | date | 流量统计日期,精确到天 |
| period | varchar(32) | 时段标识,如“07:00-08:00” |
| direction | varchar(16) | 方向,东/南/西/北(或NE/SE/SW/NW) |
| ped_count | int | 行人流量,单位:人次 |
| non_motor_count | int | 非机动车流量,单位:辆次 |
这里的每一行代表:某个路口某一天某一个小时内、某一个方向、行人多少、非机动车多少。这个粒度定得合适,既能支持小时级分析,也能通过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; } }分页查询代码:
Page<TrafficFlow> page = new Page<>(current, size); LambdaQueryWrapper<TrafficFlow> 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(List<TrafficFlow> 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 { // 获取某路口某天各时段流量趋势 List<Map<String, Object>> getHourlyTrend(Long interId, LocalDate date); // 获取方向分布 List<Map<String, Object>> getDirectionDistribute(Long interId, LocalDate date); // 获取路口流量排行榜 List<Map<String, Object>> getIntersectionRank(LocalDate start, LocalDate end); // 获取早晚高峰对比 Map<String, Object> getPeakCompare(Long interId, LocalDate date); }每个方法内部三步走:拼参数→调Mapper执行聚合SQL→把结果构造成前端图表需要的JSON结构。比如折线图需要{ periods: [...], pedData: [...], nonMotorData: [...] },这个结构在Service里组装,Controller只负责转发。
4.5 Excel导出:EasyExcel让代码量骤减
Excel导出我用的EasyExcel,相比原生的Apache POI,API更友好,内存占用还低。导出统计表的核心代码:
@GetMapping("/export") public void export(HttpServletResponse response, @RequestParam Long interId, @RequestParam String startDate, @RequestParam String endDate) throws IOException { List<TrafficFlowStatVo> 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接口我统一返回Result<T>结构,告诉前端:成功还是失败、返回什么数据、带什么提示信息。再配一个@RestControllerAdvice全局异常处理器,把SQL异常、参数异常统一拦截,前端不用每处都写try-catch。这个设计在论文“系统非功能性设计”里能凑一节,在答辩“你项目里有什么亮点”里也能讲。
public class Result<T> { 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?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true另外,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图表不渲染:先看数据再看DOM
ECharts图表空白,九成问题不出在ECharts本身,而是出在数据上。我排查的顺序是:
- 浏览器F12打开Network,看接口返回是不是200,返回数据里period字段和图表需要的字段名是否一致。
- 打开Console看有没有JS报错,最常见的是
Cannot read properties of undefined (reading 'length'),说明数据是空的。 - 第三步才检查初始化时机:图表容器是否已经渲染完毕。如果有
display:none的Tab页里放图表,需要等Tab切换后再调用echarts.init和setOption。
5.6 中文乱码三件套
中文乱码问题通常出现在三个地方:
| 位置 | 表现 | 解决 |
|---|---|---|
| 数据库存储 | 表字段变成问号 | 建库用utf8mb4,连接串加characterEncoding=utf8 |
| 响应返回 | 接口中文乱码 | 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设成唯一键,插入重复数据直接报错,前台再提示“该时段已录入,请检查”。这个设计既能体现在数据库层面的严谨性,也是实实在在避免数据翻倍的兜底方案。
这个题目做完,你再回头看,其实就是“一个业务闭环 + 一套常规技术栈 + 几个踩坑细节”。如果你正卡在某个环节里,希望这些经验能省你几个通宵。