最近总有人问我,AI辅助编程到底是噱头还是真能落地干活。我手上的答案很简单:拿一个真实项目走一遍就知道。这篇文章我就以“学生成绩管理系统”为例,把AI怎么辅助我完成一个Web项目从零到能跑的全过程捋一遍——包括业务需求拆解、技术选型、数据库设计、提示词怎么写、代码怎么生成、联调怎么测、踩了哪些坑。如果你正好在做Web开发、准备毕设,或者想体验一下AI助写代码的真实工作流,这篇应该能让你少走不少弯路。
1. 项目定位与核心需求拆解:学生成绩管理系统到底在管什么
1.1 一个成绩管理系统背后的真实业务场景
很多第一次接触这个项目的人会想:不就是记录学生成绩嘛,Excel都能干,为什么要做个Web系统?但把场景放大就不一样了。假设一个系里有12个班、每年级6个班,每个班40人,每学期开8门课,光一次期末考试就会产生3840条成绩记录。这还只是一个年级的。教务老师要在这些数据里查某个学生的历次成绩变化、出一份班级平均分对比、筛选不及格名单、给家长打印成绩单——用Excel做这些事,重复劳动量大得吓人,而且特别容易出错。
所以学生成绩管理系统要解决的,不是“能不能存成绩”的问题,而是“怎么高效地管成绩、查成绩、用成绩”。我给它拆成了四个核心需求点:
- 学生信息和班级信息的管理,要能快速增删改查和模糊搜索;
- 课程信息和成绩信息的关联存储,一门课对应多个学生的成绩,一个学生也有多门课成绩,这是一对多的典型场景;
- 多维度的成绩统计,包括个人总分、排名、平均分、及格率、分数段分布;
- 成绩单的导出与打印,这是教务场景里最常被忽略但使用频率极高的功能。
把这些需求想清楚之后,再去看AI辅助开发就有了靶子。AI擅长的是代码生成和结构化实现,但前提是你得先把业务逻辑想明白,否则问出来的代码一定是四不像。
1.2 AI在这个项目里能帮什么、不能帮什么
我对AI辅助编程的态度一直比较务实:它不是一个能替你思考的架构师,而是一个响应速度极快、知识面极广的结对程序员。你负责把“做什么”和“怎么做”的框架定清楚,它负责把“具体代码长什么样”快速输出出来。
具体到学生成绩管理系统,AI能帮的环节非常明确:
- 根据你给的数据库字段描述,生成建表SQL和对应的实体类;
- 根据一个接口的入参和返回结构,生成完整的Controller-Service-Mapper三层代码;
- 根据页面截图或功能描述,生成Bootstrap或Vue风格的表格、表单、弹窗代码;
- 在你报错的时候,快速定位异常原因并给出修复方案。
但它不能替你做的也很明显:它不知道你的教务流程里影响最终成绩的平时分和期末考占比各是多少,不知道班级管理员的权限边界是什么,也不知道你想要成绩单上显示哪些字段。这些业务规则,必须由你在提示词里写清楚,或者在对话过程中逐步补充修正。
所以,AI助写这个项目的正确打开方式是:先自己做需求分析和表结构设计,然后让AI去落地编码,再通过人工审查和联调把代码质量控住。这个思路我后面会反复用到。
2. 技术选型定盘:AI辅助下如何快速锁定一套稳定方案
2.1 技术栈选择的底层逻辑:成熟优先于新颖
在AI辅助开发的场景下,技术栈选择有一个特别容易被忽略的原则:必须选AI训练语料里出现频率最高的方案。你选一个小众冷门框架,AI生成出来的代码大概率是幻觉连篇;你选Spring Boot + Vue这套国内Web开发最常见的组合,AI输出的质量和稳定性就会高很多,因为它见过的相关代码可能是百万级以上的。
我当时定下来的技术栈是:
- 后端:Java 17 + Spring Boot 3.x + MyBatis-Plus + MySQL 8.0
- 前端:Vue 3 + Vite + Element Plus(组件库成熟,AI生成的表单和表格代码非常稳定)
- 中间件:使用Maven管理依赖,使用Lombok简化实体类代码,使用Druid作为数据库连接池
- 部署形态:前后端分离,后端打包成Jar包运行在8080端口,前端通过Nginx或Vite代理转发请求
为什么会选这套方案?一方面是这套组合在国内Web项目中太常见,AI训练语料充足,生成出来的代码基本不需要大改;另一方面是MyBatis-Plus帮我们把大量单表CRUD操作简化成了框架内置方法,让AI生成的Service层代码不至于冗余。
还有一点值得说,我看到热搜词里有人提到Spring Boot集成WebSocket的yml配置,这个如果做成成绩实时通知、考试开始提醒之类的功能是可以加的,但对一个标准的学生成绩管理系统来说属于增量需求,不是核心。我的建议是先跑通核心CRUD再加这类功能,别在一开始就引入复杂度。
2.2 数据库设计:先定表结构再让AI写实体
数据库是整个系统最不能依赖AI的部分。我见过不少人让AI“帮忙设计表结构”,生成出来的表要么缺字段,要么字段类型不合理。我的做法是先把表结构设计好,再让AI根据表结构生成实体类和Mapper。
学生成绩管理系统的核心表我设计了五张:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| student | 学生信息表 | id, student_no(学号), name, gender, phone, class_id |
| class_info | 班级表 | id, class_name, grade(年级) |
| course | 课程表 | id, course_name, credit(学分), teacher |
| score | 成绩表 | id, student_id, course_id, score, exam_type(平时/期中/期末), exam_date |
| sys_user | 系统用户表 | id, username, password, role(角色) |
以score表为例,我故意把exam_type设计成字段而不是分成平时成绩表、期中成绩表、期末成绩表,原因在于:同一门课的成绩记录放到同一张表里,统计平均分和加权计算时会简单非常多。这个表设计细节直接决定了后面AI生成的SQL查询语句写起来顺不顺。
表结构确定后,我会把建表DDL直接丢给AI,让它生成对应的实体类和Mapper接口。比如:
CREATE TABLE `student` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `student_no` varchar(20) NOT NULL COMMENT '学号', `name` varchar(50) NOT NULL COMMENT '姓名', `gender` tinyint DEFAULT NULL COMMENT '性别:1男 2女', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', `class_id` bigint DEFAULT NULL COMMENT '班级ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_student_no` (`student_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表';这个DDL一旦给到AI,它生成的实体类基本就是照着字段映射,不会有错。
3. AI助写的实操流程:从提示词到可运行代码的完整链路
3.1 高质量AI编程提示词的设计方法
很多人用AI写代码效果差,问题真不在AI,而在提示词写得太随意。比如有人直接问“给我写一个学生成绩管理系统”,这种问题AI只能给你一个飘在天上的大纲,根本落不了地。
我总结出一套适合Web项目开发的提示词结构,叫“角色+任务+输入+约束+输出格式”五件套。举个例子,我要生成后端成绩列表查询的Controller,提示词是这样写的:
你是一名有10年经验的Java高级工程师,擅长Spring Boot开发。 现在需要为学生成绩管理系统编写一个成绩管理模块。 已有实体类 Score,包含字段:id、studentId、courseId、score、examType、examDate。 请编写Controller层代码,实现以下功能: 1. 分页查询成绩列表,支持按学生姓名、课程名称、考试类型筛选; 2. 新增成绩记录; 3. 修改成绩记录; 4. 删除成绩记录。 要求:基于MyBatis-Plus实现,使用统一的返回结果类Result,包含code、message、data三个字段。 请直接输出完整代码,并附上关键注释。注意这里我给了角色设定,给了明确的任务边界,给了已有的实体类信息,给了技术约束,还指定了返回结果格式。AI拿到这种提示词之后,生成的Controller基本是可用的,不会出现“自己发明实体字段”的情况。
3.2 后端代码生成:分层生成的效率远高于一次生成
我实测下来,AI一次生成整个项目的代码风险高、改动成本也大,远远不如按层生成来得靠谱。我习惯按这条路走一遍:
先让AI生成Controller层代码,确认接口路径和参数设计没问题,再让它生成Service接口和实现类。Service是业务逻辑的核心,需要把复杂查询的SQL逻辑在这里定义清楚。最后生成Mapper层代码,这部分如果用了MyBatis-Plus,几乎不需要写SQL,基础的增删改查都内置了。真正的复杂查询,比如按班级统计平均分、按课程统计各分数段人数,才需要自己写SQL或让AI帮忙生成。
这里我想特别说一个经验:给AI报需求的时候,一定要带上表结构的字段名。因为AI不认识你的数据库,只能根据你提供的字段名来拼SQL。聪明一点的AI会编字段,但字段名跟数据库对不上,代码跑起来就是一片红。所以宁可每次提示词里多复制几行字段说明,也不要让AI自由发挥。
3.3 前端页面生成:从首页到详情的渐进式迭代
后端做得差不多了,再让AI生成前端页面。学生成绩管理系统的前端页面主要包括登录页、首页Dashboard、学生管理页、课程管理页、成绩管理页、成绩统计页。我通常会先让AI生成一个基于Element Plus的“成绩管理页”,包含一个表格、一个搜索表单、一个新增/编辑对话框,然后截图或描述让AI继续调整样式。
AI生成前端代码有个小技巧:把接口的数据结构先给AI看。比如你告诉它成绩列表接口返回的数据是[{id: 1, studentName: '张三', courseName: '高数', score: 89, examType: '期末'}],AI生成的el-table列就能直接绑对字段,省掉大量的调试时间。
至于风格,我让AI统一使用Element Plus的Table、Form、Dialog、Message组件,这组组件在AI训练语料里的代码足够多,生成出来的交互效果相对统一。一个能跑的界面比一个花哨但README写得天花乱坠的界面有用得多。
4. 核心功能模块实现详解:从CRUD到统计报表
4.1 成绩管理模块:背后的数据关联是核心
成绩管理这个模块表面看就是一个成绩表的增删改查,但实际写起来门槛在数据关联。你要在成绩列表里显示的是“张三 93分 高数”,而不是“studentId=1 courseId=3 score=93”这种开发人员才看得懂的原始数据。
这就需要在Mapper层做关联查询。用MyBatis-Plus的话,可以配合自定义SQL实现:
<select id="selectScorePage" resultType="com.example.vo.ScoreVO"> SELECT s.id, st.student_no AS studentNo, st.name AS studentName, c.course_name AS courseName, s.score, s.exam_type AS examType, s.exam_date AS examDate FROM score s LEFT JOIN student st ON s.student_id = st.id LEFT JOIN course c ON s.course_id = c.id <where> <if test="studentName != null and studentName != ''"> AND st.name LIKE CONCAT('%', #{studentName}, '%') </if> <if test="courseName != null and courseName != ''"> AND c.course_name LIKE CONCAT('%', #{courseName}, '%') </if> <if test="examType != null and examType != ''"> AND s.exam_type = #{examType} </if> </where> ORDER BY s.create_time DESC </select>这种带LEFT JOIN和动态查询条件的SQL,让AI生成时最容易出的问题是<where>标签写错位置或漏了<if>里的条件判断。所以我是先搭好SQL骨架,再让AI帮我把重复度高、模式固定的部分填上。
4.2 统计报表模块:一个JOIN加GROUP BY就能搞定的重头戏
成绩统计是这系统里最有价值的部分,因为教务老师真正看重的不是一条条的成绩记录,而是汇总之后的信息:这个班这次期末平均分多少,哪门课挂了最多人,全年级前20名是哪些学生。
用SQL表达的话,班级平均分统计可以这样写:
SELECT c.class_name AS className, ROUND(AVG(sc.score), 2) AS avgScore, COUNT(sc.id) AS examCount, SUM(CASE WHEN sc.score < 60 THEN 1 ELSE 0 END) AS failCount FROM score sc LEFT JOIN student st ON sc.student_id = st.id LEFT JOIN class_info c ON st.class_id = c.id WHERE sc.exam_type = '期末' GROUP BY c.id ORDER BY avgScore DESC这个查询在AI辅助下的生成速度非常快,但我强调过一件事:AVG函数、SUM函数配合CASE WHEN的条件计数,一定要先确认数据库字段类型。如果score字段不小心被设计成了varchar,那么AVG出来的结果会让你怀疑人生。所以建表时成绩字段必须是DECIMAL(5,2)或INT型,这个属于数据规范和表结构设计的问题,AI帮不了你。
统计报表的后端接口返回给前端后,前端需要把它渲染成图表。这一步我让AI生成一个基于ECharts的柱状图组件,大概逻辑就是接受后端返回的班级名称数组和平均分数组,然后渲染成图表。实测下来,ECharts的option配置在AI眼里简直是训练集里面的老熟人,生成出来的代码基本不用改。
4.3 页面PDF打印:成绩单怎么实现Web端直出PDF
热搜词里有人搜“web页面pdf打印”,这正好是成绩管理系统中一个实用的场景:给每个学生打印成绩单。实现思路通常有三种:
- 前端用浏览器自带的
window.print()打印指定区域,好处是零依赖,坏处是样式控制能力弱; - 前端引入html2canvas把页面区域转成图片后再打印或导出,实现简单但清晰度一般;
- 后端用iText或POI生成PDF文件,前端直接下载,样式可控性最强,但开发量稍大。
对于学生成绩管理系统,我认为最务实的方案是第一种配合一点样式微调。把要打印的区域包在一个div中,打印时把其他区域隐藏:
function printScoreSheet() { const printContent = document.getElementById('print-area'); const originalTitle = document.body.innerHTML; document.body.innerHTML = printContent.innerHTML; window.print(); document.body.innerHTML = originalTitle; window.location.reload(); }AI生成这种经典JS函数几乎是秒出,但要提醒你的是:DOM操作重置之后,Vue的响应式数据可能丢失,所以这个函数适合用在纯展示页面上,比如成绩单预览页。如果表单页面也这样做,保存后再恢复页面就需要window.location.reload()兜底。这个坑我是踩过的,特意把reload写进代码里就是为了避免页面状态错乱。
5. 实操过程记录:IDEA 2024环境下从零搭建整个项目
5.1 Spring Boot项目初始化与关键依赖配置
我用的是IDEA 2024版本,新建项目时可以直接选择Spring Initializr,然后勾选Web、MySQL Driver、Lombok等依赖。这一步有一个容易被AI辅助搞混的地方:AI生成代码时会默认你有一整套pom.xml,但新项目的依赖是你自己勾的,所以提示词里最好带上你现有的依赖列表,让AI在这套基础上补充。
核心的pom.xml依赖长这样:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> </dependency>如果你的项目里tomcat和数据库驱动版本不对,启动时报“Failed to load plugin”之类的问题很常见。实际上这种报错大多是版本冲突,不要盯着一长串报错信息发愁,先检查pom里有没有引入两个冲突的依赖。
5.2 application.yml配置与MyBatis-Plus集成
配置文件是项目能跑起来的地基,我通常会这样写:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/grade_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: root type: com.alibaba.druid.pool.DruidDataSource mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0有几个细节值得展开说。url里的serverTimezone=Asia/Shanghai是必须的,否则会报时区异常;map-underscore-to-camel-case要开启,否则数据库字段student_no映射不到实体的studentNo属性上。如果你用了逻辑删除,记得配好logic-delete相关参数,AI生成的查询语句会自动带上deleted过滤条件。
5.3 前端工程创建与Vue页面跑通
前端我用Vite来创建Vue3项目,命令很简单:
npm create vue@latest npm install element-plus npm install axios npm install echarts然后配置axios请求代理,把前端的/api请求转发到后端8080端口。这个配置在vite.config.js里写:
server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }前端页面写完之后的联调阶段,有一个高频问题:前后端联调时接口返回403或404。403大概率是跨域没配好,那就需要在后端加一个CorsConfig或者在Controller上加@CrossOrigin注解。404则是代理路径和Controller的RequestMapping对不上。我习惯在前端请求时统一加/api前缀,后端Controller的RequestMapping里就别再写/api了,避免重复。
5.4 联调测试的完整流程记录
联调阶段我通常按这个顺序走:先测登录,再测学生管理,最后测成绩和统计。登录测试通过说明数据库连接和用户表没问题;学生管理通了说明基础CRUD链路没问题;性能和复杂的统计放在最后。每测一个接口,我会先用Postman把后端接口调通,然后在前端页面上操作一遍。这个习惯帮我节省了大量排查前端还是后端问题的时间。
一个真实的测试场景是这样的:我让AI生成了成绩列表的分页查询接口,结果前端一直拿不到第二页的数据。后来发现AI生成的SQL里LIMIT参数的顺序写反了,LIMIT 1, 10和LIMIT 10, 1在语义上完全不一样。我直接在Postman里测试后端接口,就快速锁定了问题不在前端而在后端SQL。所以我说,AI能生成代码,但联调这个环节还是得自己动手跑一遍,因为你比AI更清楚业务上的正确行为是什么。
6. 踩坑实录:AI生成代码的典型问题与排查技巧
6.1 常见问题速查表
我在这套学生成绩管理系统实际开发过程中,遇到的最典型的问题整理成了一张某种意义上可以直接抄作业的速查表:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 接口返回500,线上日志提示Unknown column | AI生成的查询条件里有数据库不存在的字段 | 核对实体类字段和表字段映射关系 |
| 前端表单提交报400 Bad Request | JSON字段名与后端实体属性名对不上 | 在实体类上加@JsonProperty或改前端字段名 |
| MyBatis-Plus分页不生效,始终返回全量数据 | 缺少分页插件配置 | 新建MybatisPlusInterceptor并添加PaginationInnerInterceptor |
| 日期格式化后前端显示时间戳 | 后端没做Jackson日期格式配置 | yml中配置spring.jackson.date-format |
| 页面PDF打印时内容和背景丢失 | 打印样式未加载 | 在@media print中单独设置背景色和裁剪区域 |
| ECharts图表不显示 | 容器div没有高度 | 给图表容器设置固定或百分比高度 |
这里我想特别说一下分页插件的问题。MyBatis-Plus自带的CRUD方法支持分页,但必须在配置类里显式注册分页拦截器,否则Page对象查出来的永远是全量数据。AI生成这类配置类经常漏,因为它的上下文里没有你项目的包结构。所以拿到AI生成的代码,第一件事就是检查有没有配置类被漏掉了。
6.2 提示词迭代修正的核心经验
AI写代码很少一次成功,但很多人不知道如何有效修正AI的输出。我的做法是把错误日志或者前端报错信息原封不动粘给AI,再补充说明期望的正确行为是什么。不要只说“你这个代码写错了”,更好的说法是“当前返回结果出现了null字段,期望返回成绩对应的学生姓名,请检查LEFT JOIN条件中student表的关联字段”。
还有一种情况:AI生成了两套实现方式,比如把删除设计为物理删除,而你的需求是逻辑删除。这时需要在提示词里明确描述“系统要求数据不可物理删除,使用MyBatis-Plus逻辑删除”,让AI基于这个约束重新生成。提示词的修正过程本身就是在锻炼你对自己的业务逻辑的表达能力,这是AI辅助开发中很有价值的副产品。
6.3 如何判断AI生成的代码可不可用
最后分享一个筛选AI生成代码是否值得使用的判据。拿到一段代码后,我会先看三处:第一,实体类和数据库字段是否一一对应;第二,事务注解有没有加对位置——一般写在ServiceImpl类上而不是Controller上;第三,异常处理是不是被吞掉了——最好不要catch之后什么都不做,至少要打日志。
这三处过关了,这个代码大概率能跑。如果这三处都不过关,那还不如自己手写。AI助写的意义是把80分的代码快速产出,后续的20分属于人工打磨。
7. 一点个人体会
如果你要问我这个项目做完之后最大的感受,那就是:AI助写代码这件事,重心从来不在AI,而在“你怎么定义需求、怎么拆解任务、怎么验证结果”。学生成绩管理系统本身不复杂,但它把AI辅助开发的全流程完完整整走了一遍——从SQL到后端接口再到前端页面,每个环节AI都参与了,每个环节的产物我都做了审查和调整。
我建议你动手做类似项目时,不要直接甩给AI一句“帮我写一个成绩管理系统”就坐等结果。先花半小时画一画业务流程,写清楚要几张表,想明白哪些功能是必须的,然后再打开AI对话窗口。把问题想得越清楚,AI给你回报的效率就越高。
另外一个特别想提醒的点是:AI生成的代码一定要自己跑测试,尤其是涉及金额、分数、日期这类精度敏感的数据。成绩管理系统的score字段哪怕精度错了0.5分,在统计报表里都会被放大。顺手写一个基础的单元测试或者用手工数据验证一遍,比什么都可靠。
这个项目后续要扩展的话,可以加WebSocket主动推送成绩发布通知,也可以接一个简单的权限系统区分管理员和教师角色,基础做好了之后这些都不会太难。