AI辅助开发实战:用Flask+SQLite构建学生成绩管理系统
最近把一个攒了很久的需求翻了出来——给一个课外培训机构做学生成绩管理。一开始想直接套个现成的教务系统但实际需求实在不大一百多个学生、十来门课要管学生档案、录成绩、做点查询统计。上大系统是杀鸡用牛刀外包又犯不着正好这段时间我在集中研究AI辅助开发干脆就把它当成试验田试试用AI从零写一套Web页面到底靠不靠谱。做完之后的结论先摆在这AI写代码绝对不是“你提需求、它全包圆”而是“你把架构和验收标准想清楚它负责把重复劳动快速变现”。这篇就把整个过程中的项目定位、数据模型设计、提示词编写、逐模块让AI产出代码、最后修掉一堆隐蔽问题再上线的完整流程复盘一遍。如果你也想拿AI练手做个web项目这篇应该能帮你省下不少弯路。1. 为什么拿“学生成绩管理系统”当AI协作练手项目1.1 麻雀虽小五脏俱全我选这个项目不是因为业务有多复杂而是因为它把Web开发里常见的基础能力都覆盖了账号登录与权限识别、学生信息的增删改查、成绩的录入和修改、按课程和班级做的条件查询、平均分及格率的统计再加上前端那一堆表格、表单、弹窗提示。这些场景几乎就是日常业务系统里百分之八十的常用功能。对AI协作来说这个规模特别合适。太小的项目试不出问题比如“帮我写个登录页”AI一次能搞定但你根本看不出协作流程里的坑太复杂的项目又容易失控比如电商系统、带高并发的平台那种项目里架构设计和性能瓶颈才是核心AI生成的初稿大概率连跑都跑不起来。学生成绩管理系统刚好卡在中间数据模型清晰、功能模块边界明确AI生成的内容“能跑”的概率高同时也足够让你暴露问题、总结经验。1.2 人管业务规则AI管重复劳动开工前我给自己划了条线AI的产出只当“初稿”业务规则和最终判断必须由我把关。举个例子“成绩”这个字段到底是0到100的整数还是允许小数补考成绩怎么记录一个学生能不能在同一学期重复修同一门课这些业务决策AI猜不出来猜出来也未必对。我见过不少朋友第一次用AI做项目开口就是“帮我写一个完整的成绩管理系统”结果AI真给你生成一大坨代码但跑起来全是问题。原因就在于需求里的规则没有被拆解。后来我调整了协作方式复杂的业务规则我自己定AI负责翻译成代码。实际操作下来效率确实高了不少项目本身成为了理想AI工作流的分工基准。1.3 预期管理别指望“一次成型”还有一点必须提前说AI生成的代码大概率是“形似而神不似”。表面上功能齐全实际上边界条件没处理、异常没捕获、甚至调用了不存在的接口。这种情况太常见了。我的预期是第一版架构构建好第二版功能跑通第三版补漏洞完全靠AI一次写完并完美运行至少目前还不现实。2. 先定技术栈和数据模型再让AI动手很多人让AI写代码上来就把技术栈甩给它这是本末倒置。技术选型这件事必须人先想清楚因为AI只会顺着你的选择往下走它不会替你考虑“这个方案适不适合你”。2.1 技术栈Flask SQLite Jinja2怎么简单怎么来我最终选的是Flask SQLite Jinja2模板 少量原生JavaScript没上前后端分离也没用Vue/React这种前端框架。理由非常实际这个系统用户量很小就是个内部工具不需要微服务、不需要Redis缓存、不需要消息队列。前后端分离意味着要维护两套工程还要处理跨域、鉴权、接口约定在一个百人级的小系统里纯粹是给自己找麻烦。Flask SQLite是单机服务级方案中最易跑的一个Python文件加几个HTML模板就能把整个后端和前段逻辑串起来调试成本极低。技术栈对比其实可以放在一张表里看方案优点缺点适合场景Flask Jinja2 SQLite启动快、调试简单、代码量少不适合高并发、大规模应用内部工具、小型业务系统、快速原型Spring Boot Vue架构规范、扩展性强环境配置复杂、工程体积大团队协作、中大型系统Node.js Express MongoDBJS全栈统一生态碎片化、异步模型要理解前后端同语言团队选好了技术栈我交付给AI的提示词里就必须写清楚“基于Flask SQLite Jinja2”不然它很可能自作主张引入React之后的代码跨几个框架你根本接不住。2.2 数据库设计三张表打天下数据模型是整个系统的地基。我花了半小时把表结构理清楚之后所有AI提示词都是围绕这三张表展开的from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() class Student(db.Model): __tablename__ student id db.Column(db.Integer, primary_keyTrue) student_no db.Column(db.String(20), uniqueTrue, nullableFalse) # 学号 name db.Column(db.String(50), nullableFalse) gender db.Column(db.String(4), nullableTrue) class_name db.Column(db.String(50), nullableTrue) # 班级 class Course(db.Model): __tablename__ course id db.Column(db.Integer, primary_keyTrue) course_name db.Column(db.String(50), uniqueTrue, nullableFalse) class Score(db.Model): __tablename__ score id db.Column(db.Integer, primary_keyTrue) student_id db.Column(db.Integer, db.ForeignKey(student.id), nullableFalse) course_id db.Column(db.Integer, db.ForeignKey(course.id), nullableFalse) score_value db.Column(db.Float, nullableFalse) # 成绩 semester db.Column(db.String(20), nullableFalse) # 学期例如 2025-01这里有个容易被忽略的点成绩表为什么要单独建而不是在学生表里加一堆成绩字段因为一个学生对应多门课程、多个学期成绩数据天然是一对多的关系。如果做成学生表里的“语文成绩”“数学成绩”这种字段后期每加一门课都要改表结构。我选择把业务关系梳理清楚后再写提示词AI生成的数据模型基本一次通过没有返工。2.3 功能模块拆解人和AI协作最忌讳的就是把一个复杂系统当成黑盒丢给AI。我在动笔前先把系统拆成了四个模块每个模块有明确的输入输出模块核心功能核心页面登录认证账号密码登录、会话保持login.html学生管理学生信息的增删改查、分页students.html成绩管理录入成绩、修改成绩、按课程筛选scores.html查询统计按学生/课程/班级查询平均分及及格率statistics.html这四个模块是我后续提示词的基本单位每次只让AI处理一个模块绝不一次性要求它产出整个系统。3. 提示词设计把需求翻译成AI能执行的“自然语言代码”如果AI协作开发有百分之五十的功夫要花在提示词上这话真不夸张。提示词质量决定了AI产出代码的质量而提示词写作是有套路可循的。3.1 三段式提示词结构我总结的模板是角色定义 任务拆解 约束条件。角色定义让AI进入“专家状态”比如“你是一名熟悉Flask开发的高级工程师”。任务拆解把具体要实现的页面或接口一条条列清楚越具体越好。约束条件写明技术栈、代码风格、异常处理要求以及禁止事项。实际用下来效果最直观的对比是这样的❌ 坏提示帮我写一个成绩录入页面。 ✅ 好提示 【角色】你是一名熟悉Flask SQLite的中级后端工程师代码风格清晰简洁。 【任务】请实现成绩录入功能具体要求 1. 提供成绩录入页面scores.html按“课程 学期”筛选出学生列表并展示每个学生的姓名和学号。 2. 目前默认只有score_value字段页面里对每个学生有一个成绩输入框支持小数。 3. 提交后通过POST请求将数据提交到/score/save接口用事务批量写入score表学号不存在则报错。 4. 表单提交前要前端校验成绩范围是0~100后端也要做同样校验。 【约束】 - 使用Flask蓝图组织路由 - 所有数据库操作放在model层 - 需要在代码关键处写中文注释 - Python版本3.10 【输出】给出完整代码包含路由、模板和数据库交互部分。这种提示词为什么有效因为它把“做什么”和“怎么做”都说明白了AI不需要猜意图直接翻译成代码即可。而坏提示词里AI会自行脑补出一整套功能最后你拿到的代码基本不能用。3.2 让AI输出什么格式得明确大多数人跟AI协作的失败源于让AI一次性生成“全部代码”。我自己的经历第一次让AI写整个系统它甩给我一个两千行的main.py看着齐全实则任何一个文件都没法独立运行模板路径、静态资源引用全都是错的。正确的做法是让AI分文件输出并且在提示词里明确输出粒度。比如“请生成app.py的全部代码不含HTML模板。”“请生成templates/students.html页面的完整HTML代码样式依赖Bootstrap 5 CDN。”“请生成models.py定义三个数据模型。”按文件粒度拆分后每一个输出都是可验证的我可以跑一遍看看有没有语法错误再进入下一步。3.3 用“带例子”的方式让AI理解业务当业务规则比较复杂时光用文字描述是远远不够的。比如“及格率”的计算方式不同系统可能定义不同是按参考人数算还是按总人数算不及格补考算不算这种歧义会让AI生成完全错误逻辑中间隔着一次反复试错。较好的方式是给AI一个具体输入输出示例直接复制到提示词里班级统计页面需要展示如下内容 输入选择班级三年级一班、学期2025-01 输出表格 班级 课程 参考人数 平均分 及格率 三年级一班 语文 32 87.5 93.75% 三年级一班 数学 32 79.2 84.38% 其中及格率成绩60的人数 / 参考人数保留两位小数。这种“人话示例”极大降低了AI的理解偏差它输出的代码基本能直接对齐预期比我用三层文字描述都有效。4. 逐模块落地从登录页到成绩统计的构建过程实录技术栈定了、数据模型定了、模块拆好了、提示词模板也有了。接下来就是真刀真枪让AI产出代码的过程。我按“骨架→学生模块→成绩模块→统计模块”的顺序逐个落地整个过程差不多花了一个周末。4.1 先让AI搭项目骨架我把项目骨架的生成放在第一步目的是先把工程目录、入口文件、数据库初始化这些基础打好# 项目结构AI根据提示词生成 grade_system/ ├── app.py # Flask应用入口 ├── models.py # 数据库模型 ├── views/ # 蓝图路由 │ ├── __init__.py │ ├── auth.py │ ├── student.py │ └── score.py ├── templates/ # HTML模板 ├── static/ # CSS/JS └── requirements.txt # 依赖骨架提示词的关键点是要求AI“初始化一个可运行的Flask应用”并给出验证方式请生成Flask项目的骨架代码要求包含 1. app.py中初始化app、db、注册蓝图并支持flask run直接启动。 2. models.py定义Student、Course、Score三个模型字段与下面给出的SQL一致。 3. 创建一条./init_db.py脚本用于初始化SQLite数据库并插入初始管理员账号。 4. requirements.txt列出所有依赖。AI给的骨架基本能跑。唯一需要手动调整的是管理员账号的密码生成AI默认用了明文我让它改成Werkzeug的密码哈希函数处理这个细节直接关系到系统安全性后面还会展开。4.2 学生信息管理模块骨架搭好之后的下一站就是学生管理因为成绩模块依赖学生和课程数据。我给AI的提示词里要求学生列表提供分页、支持按班级和姓名筛选同时提供新增、编辑、删除三个操作。AI输出的students.html用了表格加Bootstrap样式分页逻辑在路由层实现。# 学生列表分页接口AI生成我做了微调 app.route(/students) def student_list(): page request.args.get(page, 1, typeint) per_page request.args.get(per_page, 10, typeint) kw request.args.get(kw, ).strip() query Student.query if kw: query query.filter( db.or_(Student.name.like(f%{kw}%), Student.student_no.like(f%{kw}%)) ) return render_template(students.html, paginationquery.paginate(pagepage, per_pageper_page), kwkw)这里微调了什么AI原来用的是Student.name.contains(kw)在SQLite下没问题但如果将来切到MySQL转义可能出问题改成like模式更稳妥。这类细节AI不会替你考虑但它生成的主体逻辑可用。4.3 成绩录入模块有点小波折成绩录入页面是系统里交互最复杂的页面它需要先选择课程和学期再列出该课程对应的所有学生名单逐个输入成绩后提交。AI生成的版本功能完整但第一次跑就报了一个经典错误sqlalchemy.exc.IntegrityError: NOT NULL constraint failed: score.semester原因一目了然AI在保存Score表记录时没有从表单里读取semester字段。我把错误信息贴回给AI让它排查十秒钟后它给出了补丁代码改成从表单读学期参数。这个环节让我意识到AI写代码有个显著好处报错信息直接贴给它它通常能快速定位。所以你在用AI协作开发时千万不要自己手动一条条读堆栈让AI读效率完全不同。4.4 查询统计用SQL聚合让AI干活统计模块的核心是平均分、及格率和参考人数。按课程维度聚合的SQL我直接用自然语言描述给AI它生成的结果贴合我的预期。SELECT course_name, COUNT(score.id) AS exam_count, ROUND(AVG(score.score_value), 2) AS avg_score, ROUND(SUM(CASE WHEN score.score_value 60 THEN 1 ELSE 0 END) * 100.0 / COUNT(score.id), 2) AS pass_rate FROM score JOIN course ON score.course_id course.id WHERE score.semester :sem GROUP BY course.id, course.course_nameAI帮我改进了及格率SQL的写法以前我习惯在Python层循环计算现在直接在SQL里用CASE WHEN聚合完成代码简洁运行还快。即使数据量小这一层优化也有价值因为教育场景下老师很可能需要按学期整体导出统计数据SQL聚合比Python层循环稳定得多。4.5 前端交互的细节让AI补齐纯后端功能跑通以后我开始打磨前端体验。我做的系统页面数量不多但交互细节不少成绩输入框要支持回车跳到下一个输入框、删除操作要弹确认提示、如录入出错要显示全局错误信息而不仅是弹窗。我把这些要求单独发给AI让它批量补丁。这个阶段AI的作用体现得很明显交互细节对应的代码其实就是几十行JavaScript和CSS但自己写又会占掉大量时间让AI生成后稍微改改就能用。最终效果是页面整体风格统一操作流顺畅完全不像一个“AI写的半成品”。5. 排坑记AI生成代码里最典型的“隐性炸弹”如果只是让系统跑起来第4节的内容已经够了。但作为一个负责任的开发者我不能交付一个“能跑但不敢用”的烂产品。这一节说重点我在验收AI生成的代码时发现了五类隐蔽问题每个都有可能导致安全事故或数据错误。5.1 字符串拼接SQL注入这是最触目惊心的一个。搜索学生功能AI第一版代码是这么写的# 危险写法AI第一版真的这么干了 app.route(/students/search) def search(): kw request.args.get(kw, ) sql fSELECT * FROM student WHERE name LIKE %{kw}% result db.session.execute(text(sql)).fetchall() return render_template(students.html, studentsresult)用拼接方式把用户输入直接插入SQL只要有人在校名参数里填入 or 11 --就能把整个学生表导出来。此外如果是按名称匹配这种写法在模糊搜索特殊字符时还会各种出问题。正确做法是让AI改用参数化查询# 安全写法参数化查询 app.route(/students/search) def search(): kw request.args.get(kw, ) query Student.query.filter(Student.name.like(f%{kw}%)) students query.all() return render_template(students.html, studentsstudents)这里还是要夸一句SQLAlchemy默认参数化从根源上避免了注入风险。但如果AI生成的是裸SQL拼接你必须自己兜底检查。我的经验是在给AI的提示词约束里直接写上“所有数据库查询必须使用SQLAlchemy ORM或参数化SQL禁止字符串拼接”它能避免绝大多数低级安全问题。5.2 表单校验形同虚设AI默认生成的表单校验基本都是“前端校验”。前端校验只对正常用户有意义恶意用户可以直接绕过所以后端必须重复校验。我给AI的提示词里写了“前端和后端都校验成绩0-100”结果它前端做了、后端漏了。后来我在成绩保存接口发现了这个问题成绩字段传“999”也能写进数据库。修复方式是在路由函数里增加明确校验逻辑# 后端校验 try: score_value float(request.form.get(score_value)) except (TypeError, ValueError): return 成绩必须是数字, 400 if not 0 score_value 100: return 成绩必须在0-100之间, 400这种问题不跑极端数据很难发现但一旦出现数据就会被污染。AI不会主动考虑恶意输入和异常输入这部分只能靠人验收。5.3 会话管理没有过期和权限控制系统默认只有一个管理员角色但AI生成时没区分不同登录状态下的页面访问权限。登录后没有任何会话过期时间关闭浏览器再打开cookie还在等于是一个永不退出的系统。对内部工具来说密码随便放在共享电脑上确实很危险。修复方式很简单在Flask应用配置里加会话过期时间app.config[PERMANENT_SESSION_LIFETIME] timedelta(hours2)同时在每个需要权限的页面前加login_required装饰器AI默认只给部分页面加了。我检查时把每个路由都过了一遍确保未登录跳转、登录后过期重新登录都需要明确的逻辑。5.4 核心操作缺少二次确认删除学生信息、清空成绩这类不可逆操作界面上一没有二次确认弹窗二不需要再次输入密码确认。AI生成的版本里删除按钮只是一个简单的链接点一下就删。这里倒不完全是安全问题而是操作习惯问题。如果老师不小心点到删除又没有二次确认数据直接没了。要求AI在所有删除操作前增加JS确认弹窗并且在成绩批量导入时要求上传前二次确认。工具系统虽然简单但数据安全习惯不能省。5.5 AI“幻觉API”问题AI生成代码时会引用不存在的库或者不存在的API方法。比如它有一次用了一个db.mass_delete()我查了SQLAlchemy文档根本没这个方法。还有一次引入了一个Flask插件requirements里没写运行时直接报ModuleNotFoundError。这类问题的排查思路很简单让AI生成代码后先跑一遍把报错信息原封不动贴回AI让它自己修正。这种方法通常一次就能解决。如果反复报错说明AI陷入了“幻觉循环”此时我会手动检查引用的库是否真实存在必要时在提示词里点明“不要使用未安装的第三方库”。5.6 排坑方法论把修复流程嵌入工作流经过这个项目我总结出一套与AI协作的排错流程让AI生成初稿跑通主流程遇到报错直接贴给AI修复。检查安全边界SQL注入看是否有拼接、权限校验每个路由是否登录、输入校验后端是否有。检查数据一致性删除关联数据时的外键处理、批量操作的事务处理。检查前端交互操作确认、加载状态、错误提示。这个流程每次必走即使AI生成的代码百分之九十没问题那百分之十的风险也应该由人来兜底。6. 收尾心得怎么让AI持续迭代而不跑偏系统最终状态我是满意的统计页面能按班级、学期、课程分别查看平均分和及格率学生管理支持分页、搜索、编辑成绩录入有前端校验后端也校验删除操作有二次确认SQL注入这类低级漏洞在验收过程中全部堵死。整体从“AI生成的毛坯房”变成了“能正经交付的竣工房”。6.1 让AI按迭代清单继续优化最初版跑通后我还整理了第二轮迭代清单导出Excel成绩单、批量导入成绩模板、密码修改页、IP黑白名单虽然没真正用上。每次迭代都用明确指令描述改动范围【当前状态】系统已能正常录入成绩和管理学生以下功能需要追加。 【任务】请在现有代码基础上增加“按班级导出Excel成绩单”功能使用openpyxl库。 1. 页面新增“导出当前列表”按钮。 2. 点击后下载excel文件不弹出新页面。 3. 导出内容包含学号、姓名、班级、各科成绩、平均分。 【注意】不要改动现有路由和数据库结构。这种“增量迭代”提示词的优势是不会把已有功能搞崩。AI会提供带更清晰的导出函数我只需要在路由层调用导出Excel功能半小时内完成。6.2 人的工作反而是最关键的验收环节如果把AI协作开发的过程比作开高速AI是发动机我是方向盘和刹车。发动机再快方向盘打错了方向刹车踩晚了结果都是灾难。学生成绩管理系统本身不是什么大项目但它的完整落地让我对AI辅助开发的边界有了更深理解AI适合干“从1到10”的活不适合干“从0到1”的活。“从0到1”是指需求分析、技术选型、数据建模、安全基线这些都必须人先定好。6.3 后续还能怎么扩展这个系统现在跑在我一台旧服务器上实测并发二三十个请求毫无压力。后面如果再想扩展可以按同样模式让AI继续加功能比如家长端查询成绩只读权限、成绩趋势折线图、按Excel模板批量导入、钉钉或企业微信推送成绩提醒。技术栈完全不用换数据库升级成MySQL也只是改一行连接配置的事。回头再看这个项目的意义我倒觉得最大的收获不是那套代码而是彻底摸清了人机协作的节奏需求拆得越细AI产出越准验收做得越严系统越敢用。这套方法在下一个项目里依然能复制你也完全可以拿它去试水自己的第一个AI辅助Web项目。