通过Github Copilot开发项目——复现我曾经的农产品交易项目——Day 3
目录
为什么我的项目开发过程被认为是敏捷开发?(Why is my project process considered as Agile development)
昨天由于导师开组会的原因,本次的Blog发布时间就推迟了,今天我们抓紧时间继续开始对我们的项目进行重构。后续的话没有特殊情况我尽量也会确保一周至少发布四篇的本专题的Blog吧。
敏捷开发(Agile)
不知道你们有没有发现,我在DAY1和DAY2里,AI所生成的Prompt里,AI把每一次的工作任务称为Sprint。抱着求知的心态,我去查询了一下Sprint这个词本身的含义,以及是在什么场景下出现的。不出所料,Sprint的出现是由于AI把我的项目实现的过程认为成了敏捷开发(agile)的过程。而agile开发往往带有一系列的Scrum词汇:Sprint(一个固定周期的开发迭代)、Increment:本次迭代可交付的成果、Product Backlog(所有需求的总清单)等等。

什么是敏捷开发?(What's Agile?)
敏捷开发(Agile)是一种软件开发的方法,核心是:小步快跑、持续交付、快速反馈、不断改进,而不是一开始把所有需求一次性定死。 可以把它理解为:先做一个可用的小版本之后,直接交给用户试用,然后再根据反馈继续迭代,最后通过不断的迭代,多次小版本的更新指导获得最终的开发成果。
为什么我的项目开发过程被认为是敏捷开发?(Why is my project process considered as Agile development)
传统的敏捷开发是强调以人为中心的协作方式,以可跑通的代码为优先,通过与客户持续合作,获得反馈,并且在这个过程中拥抱变化的灵活开发过程。而在现如今的AI时代,传统的人与人之间、开发者与客户之间的关系就发生了改变。在AI背景下(以我所使用的Copilot为例),我就是客户,而Copilot往往是开发者的一个身份,我通过一次次的Prompt向Copilot提出需求,并让Copilot根据需求不断给我的版本进行迭代,这就是很明显的敏捷开发的流程。稍微不同的是,由于Copilot并不能非常完美的完成任务,对于部分情况,还需要身为客户的我同时兼顾开发者的身份,去对项目进行修改,进行微调。(本文后续就会介绍下目前我所遇到的Copilot所不能解决的问题)
从具体的地方来分析,从我第一个prompt对AI提出的需求:由于token长度的限制,我要求AI每一次只完成一定工作量的任务,而不需要贪与求成一口气把多个模块都完善的输出出来。(事实上我曾经尝试过这么去做,结果就是AI的“幻觉”现象会特别的严重,特别是当模块之间出现互相调用,逻辑上互通的情况:AI往往很难把不同模块之间的关联串到一起。最离谱的时候,甚至在A模块和B模块里一个同样的参数,在AB模块里却定义出了两种不同的变量,最后直接在变量传递的时候就报错了,而AI还试图说服我们:它已经跑通了所有的基本测试...
因此不难看出,敏捷开发的模式,特别适合被用在Copilot来开发项目的情况,我甚至让Copilot在这个过程中客串了客户的身份:在每一次的Session结束后,根据此刻的项目情况,以及前几次Prompt的描述,自己推出一个合理的下一次执行的需求项,并根据需求项生成合适的Prompt。虽然还会出现一些问题,但这些问题相对比较好进行人工排查,人工维护,相比于不同模块之间出现的问题来说,已经算是小巫见大巫了。特别是Copilot维护的项目是建立在Github仓库的基础上的,Github仓库本身的功能(版本回滚,分支测试...)也非常适配敏捷开发的场景。
第二次的工作(Session 2)
回到正题,前一天我们完成了第一次的Session,并且对Copilot的思考与工作的流程进行了基本的介绍,这一次的Blog我将继续前一天的工作进行,此时此刻我已经把第二次的Prompt喂给Copilot了...让我们期待它的回复。
提示词(Prompt of Session 2)
需要特别提到的是,由于我让Copilot生成的是.md文件在项目本地。(至于为什么要生成.md文件我在Day 1介绍过了,这里就不展开说明了)而Github仓库默认的预览器是会把.md的格式编译成人们易于阅读的形式。但是如果在这种情况下我们直接去进行复制的话,是得不到.md的纯净文件的,我们也不可以把.md文件发给Copilot,因为Copilot的Agent模式无法接收文件。这种情况我给你们支个招,在Github仓库打开Prompt文件后,可以注意到右上角有个Raw的选项,点开这个选项后我们就可以得到纯净的.md的格式了,这时候我们就可以直接复制给Copilot进行开发了。

# Sprint 2 输入 Prompt
> 📌 历史存档文件:`/prompt/history/sprint2_next.md`。如需继续迭代,请将下述 Prompt 内容发送给 AI 并按当前规范保存下一轮 Prompt。
---
你是一位资深的全栈开发工程师,正在与我共同开发"汇农亩场"农产品交易平台。我们已完成 Sprint 1(项目初始化与基础接口跑通),现在进入 **Sprint 2:数据库连接与用户模型设计**。
## 项目背景(延续 Sprint 1)
- **技术栈**:FastAPI(后端)+ Vue 3(前端)+ MySQL(数据库)
- **Sprint 1 成果**:
- 后端目录结构已建立,`/api/health` 和 `/api/products`(Mock 数据)接口已就绪。
- 前端页面可以调用后端接口并渲染商品卡片列表。
- 项目结构如下:
```
backend/
├── main.py
├── requirements.txt
└── app/
├── models/product.py
└── routers/{health,products}.py
frontend/
└── src/{App.vue, components/ProductList.vue, api/index.js}
```
## Sprint 2 任务目标
请完成以下内容:
### 1. 数据库连接配置
- 使用 **SQLAlchemy**(异步版本 `asyncmy` 或 `aiomysql`)连接 MySQL。
- 配置文件使用 `backend/app/core/config.py`,通过 `.env` 文件管理数据库连接信息(`DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`)。
- 在 `backend/app/database.py` 中创建 `AsyncEngine` 和 `AsyncSession`。
### 2. 用户模型设计
设计并实现以下数据库表(ORM 模型,放在 `backend/app/models/user.py`):
| 字段名 | 类型 | 说明 |
|------------|------------|----------------------------|
| id | INT PK AI | 主键,自增 |
| username | VARCHAR(50)| 用户名,唯一 |
| email | VARCHAR(100)| 邮箱,唯一 |
| hashed_password | VARCHAR(128) | 加密后的密码 |
| role | ENUM | 用户角色:`consumer` / `seller` |
| is_active | BOOLEAN | 是否激活,默认 True |
| created_at | DATETIME | 注册时间 |
### 3. 用户注册 / 登录接口
- `POST /api/auth/register`:接收 `username`, `email`, `password`, `role`,注册新用户(密码用 `bcrypt` 加密)。
- `POST /api/auth/login`:接收 `email` + `password`,验证成功后返回 JWT Token。
- 使用 `python-jose` 生成 JWT,有效期 30 分钟。
- 新增 `backend/app/routers/auth.py` 路由文件。
### 4. 前端对接(可选,优先后端)
- 在前端添加一个简单的**登录表单**组件 `frontend/src/components/LoginForm.vue`。
- 登录成功后将 JWT 存入 `localStorage`,并在页面顶部展示用户名。
## 输出要求
1. 提供所有新增/修改文件的完整代码,并在代码块上方注明文件路径。
2. 提供数据库初始化 SQL(或 Alembic migration 命令)。
3. 提供更新后的 `requirements.txt`。
4. 更新 Sprint 3 的 Prompt,保存到 `/prompt/output/sprint3_next.md`,内容聚焦于**商品模型持久化与分类管理**。
运行结果(Result of Session 2)

可以看到,相比Session1的页面,Session2多了一个用户登录的模块,这正好对应我们Prompt里的第三点。并且第一点与第二点的工作也完成的非常完美,我们可以在服务器后台属于mysql相关的查询语句:
service mysql start
mysql;
use harvesthub;
select * from user;

hased_password的生成也很完美,第一次Session的任务也圆满完成了。
遇到的问题(Problems encountered)
到目前你是不是觉得Copilot做的工作都非常完美,但你是否还记得我在最开始提到,我在后文会详细说明Copilot在开发过程中遇到的问题吗,这不就来了。

这个问题我其实在Day1的时候就发现了,但是碍于文字排版,因此打算总结到今天的Blog来对已经发现的问题汇总的来进行说明。这里是直接从Copilit完成工作后生成的总结里截取出来的一段,不知道你能不能发现问题。我当时是没有发现的,直到我在服务器上运行了requirement的安装... 直接卡在了aiomysql的安装,后续原因查询到了,我的服务器装的Python是3.8版本的,而我的Copilot没有指定我的py版本,3.8版本的python最多支持到0.2.0版本的aiomysql。当时的我就进行了妥协,直接安装了我所能安装的最新0.2版本,后续深入去分析这个问题的时候,才发现我需要将python更新至3.11版本(新建了一个python虚拟环境)才能够安装成功0.3版本的aiomysql。这也是为什么我在Day1的Blog里提到python的版本最好大于3.11,不然救会在装环境依赖项的时候出现最低级的版本问题。这里就可以看出来Copilot虽然是站在宏观角度的,但是我们在Prompt里没办法完整的提到我们运行项目的机器环境,类似的情况还有很多。所以我并不是完全把项目就交给了Copilot,而是需要和Copilot协作一起去开发项目。
除了上述问题之外,不知道你是否还记得我在Day1内提到的一个问题,当时的我对Copilot发出了一个疑问,觉得随着项目的不断迭代,复杂度不断增加,模块之间的联系越来越多后,Copilot会慢慢开始出现所谓的“幻觉”现象,即便我已经采用了Agile开发的方式。结果你猜怎么着,这才第二次的Session,Copilot在逻辑上就已经犯了最基础的错误,在不同模块数据对接的情况出现了问题:

上面这张图是我截取的项目的数据库里存放用户信息的table的内容,id为1的数据是Copilot写好的项目自动生成的,而id为3的数据是我人工手动加上的。如果我们在项目前端去尝试登录这两个用户,会发生什么呢?
我们输入seed_seller的邮箱密码,可以看到前端就给我们直接返回了一个错误信息,翻译过来的意思就是:我们所提供的邮箱类型@harvesthub.local并不是所支持的邮箱类型。也就是说Copilot在写邮箱验证模块的时候,考虑到了正规的邮箱格式类型。但是却在新建一个seed_seller用户的测试模块的时候,没有考虑到去采用正规的邮箱格式。(或者还有可能是Copilot为了避免Prompt攻击,杜绝了这种可能会获取真实的邮箱地址的情况,类似的情况还有用Prompt试图让AI告诉我一个有效的Win11正版激活码,不过考虑到只是伪造的数据,我个人还是更倾向于理解成Copilot没有协调好模块之间数据传输的统一规格。)

接下来我们手动添加一个新的new_seller,确保采用了正规的@gmail.com的邮箱格式,再来尝试一下是否能够成功登录:

可以看到,我们已经成功登录了,并且在右上角出现了用户的信息。
第三次的工作(Session 3)
提示词(Prompt of Session 3)
# Sprint 3 输入 Prompt
> 📌 历史存档文件:`/prompt/history/sprint3_next.md`。如需继续迭代,请将下述 Prompt 内容发送给 AI 并按当前规范保存下一轮 Prompt。
---
你是一位资深的全栈开发工程师,正在与我共同开发"汇农亩场"农产品交易平台。我们已完成 **Sprint 2(数据库连接与用户认证)**,现在进入 **Sprint 3:商品模型持久化与分类管理**。
## 项目背景(延续 Sprint 2)
- **技术栈**:FastAPI(后端)+ Vue 3(前端)+ MySQL(数据库)
- **Sprint 2 成果**:
- 后端已接入 MySQL(SQLAlchemy Async + aiomysql)
- 已有 `users` 用户表与注册/登录接口(JWT)
- 前端已支持登录并保存 Token
## Sprint 3 任务目标
请完成以下内容:
### 1. 商品模型持久化(替换 Mock 数据)
- 在后端新增并实现商品 ORM 模型(如 `backend/app/models/product.py`)并映射到 MySQL 表。
- 将现有 `/api/products` 与 `/api/products/{id}` 从 Mock 列表改为数据库查询。
- 字段至少包含:
- `id`、`name`、`description`、`price`、`unit`、`stock`、`image_url`、`seller_id`、`category_id`、`created_at`。
### 2. 分类管理模块
- 新增商品分类模型(`Category`),支持层级前先做一级分类:
- `id`、`name`(唯一)、`description`、`is_active`、`created_at`。
- 提供分类接口:
- `GET /api/categories`:查询分类列表
- `POST /api/categories`:新增分类(仅 seller 或管理员可扩展)
### 3. 商品 CRUD 接口
- 至少实现:
- `POST /api/products`:创建商品
- `GET /api/products`:商品列表(支持分页、按分类筛选)
- `GET /api/products/{id}`:商品详情
- `PUT /api/products/{id}`:更新商品
- `DELETE /api/products/{id}`:下架/删除商品
- 保持响应结构统一,补充必要的请求/响应 Schema。
### 4. 数据初始化与迁移
- 提供建表 SQL 或 Alembic migration。
- 准备最少 5 条商品与 3 条分类的初始化数据(seed)。
### 5. 前端基础对接
- 前端分类筛选下拉框接入 `GET /api/categories`。
- 商品列表改为真实后端分页数据。
- 新增一个简易“发布商品”表单(可先不做完整权限页)。
## 输出要求
1. 提供所有新增/修改文件的完整代码,并在代码块上方注明文件路径。
2. 给出数据库迁移/初始化命令与示例数据导入方式。
3. 说明接口调试方式(可用 curl 或 Swagger)。
4. 保持与现有代码风格一致,确保可直接运行。
从第三次生成的提示词可以看出,我们在第一次所发出的提示词的大体格式、描述风格都延续了我们第一次提示词的情况,这样就有利于减少我们人工维护Prompt的开销了。但是这里却在Prompt里出现了一个致命的问题,不知道各位能不能发现,我会在本部分的遇到的问题进行展开说明。
运行结果(Result of Session 3)

可以看到,我们本次的运行结果,出现了一个新的模块,我们作为农产品经销商可以手动去添加我们所需要出售的农产品了,我们来试着添加一下。


可以看到已经成功进行添加了,不过这一块也存在着一些小问题,在遇到的问题部分我也会对问题展开来说明。
遇到的问题(Problems encountered)
好的,这里就来对本次Session3遇到的问题来进行具体的说明吧。首先先从一个比较小的问题说起:如果你仔细观察我前面给出的发布商品模块,不难发现。这个模块的交互做的并不好,一部分的待填项目给出了提示词,一部分却没有。商品名称和单位右侧的两个空,我们并不知道要填什么。且现阶段的添加商品缺乏审核机制,我们可以随意添加任意的商品,就比如我第一次去添加商品的时候,我并不知道这两个空代表的含义,就随便填入了两个数字,最终得到了我不想要的数据,并且污染了数据库。这里的问题总结来说就是Copilot完成的部分,有时候会出现难以交互,甚至无法交互的情况,我们对于项目的UI需要进一步的去进行人工干预,人为优化。我们尝试去找到前端对应模块的代码:
<template>
<div class="publish-form">
<h3>发布商品</h3>
<form @submit.prevent="submitForm">
<div class="row">
<input v-model="form.name" placeholder="商品名称" required />
<input v-model.number="form.price" type="number" min="0.01" step="0.01" placeholder="价格" required />
</div>
<div class="row">
<input v-model="form.unit" placeholder="单位(如:斤)" required />
<input v-model.number="form.stock" type="number" min="0" placeholder="库存" required />
</div>
<div class="row">
<select v-model.number="form.category_id" required>
<option :value="0" disabled>请选择分类</option>
<option v-for="category in categories" :key="category.id" :value="category.id">{{ category.name }}</option>
</select>
<input v-model="form.image_url" placeholder="图片地址(可选)" />
</div>
<textarea v-model="form.description" placeholder="商品描述"></textarea>
<button type="submit" :disabled="submitting">{{ submitting ? '发布中...' : '发布商品' }}</button>
<p v-if="message" class="message">{{ message }}</p>
<p v-if="error" class="error">{{ error }}</p>
</form>
</div>
</template>
从代码中可以看到,placehoder对应的部分就是所谓提示词的部分,但是我们的第二和第四个空对应的位置Copilot已经写好了placehoder=价格和库存,但是却被两个0给覆盖了。(这是由于v-model数值类型的初始化的时候默认赋值为了0,后续就是我手动将这两个值赋值为null解决了这个问题。)
讲完上面这个问题,我们来进一步说明一下之前在Prompt部分提到个一个非常致命的问题,我们从第一次的Prompt开始就规范好了敏捷开发的流程,并且在其后的第二次Prompt也依照着这个流程来进行。但是在第三次的Prompt问题就出现了:第三次的Prompt并没有对第四次的Session工作进行指导并生成对应规范的Prompt。如果是在一个规定好的Agent工作流里,我们去用敏捷开发的方式迭代项目版本就一定不会出现这种问题,因为我们是从底层就规定好了我们的工作流程,我们到这一步就一定会有一个MCP节点的Agent去严格执行生成Prompt的工作。但是Copilot,我们通过Prompt去约束这个行为,就会随着Session轮次的增加而渐渐弱化我们的人为约束。因此这就需要我们去人工对Prompt进行审查和维护。幸好这次的问题直接导致了整体的开发过程停滞了,因为没有下一次的Prompt,Copilot就没办法进一步工作。而不是隐藏在Prompt里的其他致命问题:例如Agent完全曲解了最开始整个项目的宏观目标,而是随着Prompt的进行在某个小方向上越走越深越走越偏,没有办法跳脱出来跑去执行其他的大目标(说不定后续还真会出现这种情况)。

Copilot在本轮工作完成之后,最后一步自我审查并输出总结内容的时候,即便是二次的审查也没能让他自己发现这个最关键的缺失...
在这之后我就重新自己手动编写Prompt,指导Copilot根据前三次的Prompt去生成第四次的Prompt,顺带再把项目的结构优化了一下。
一些简单的总结(Some Conclusion)
Copilot除了上面提到的:版本兼容问题、模块间的数据格式问题、UI交互问题之外,还出现了最致命的遗忘了我们所规范的Prompt格式的问题。这给我们提了个醒,即便Copilot的出现解决了我们繁琐的代码重复搬运的任务,但是在每次的工作结束后,我们还需要介入人工的力量去进行额外的审查,甚至是手动从代码的层面进行修改。我们可以对之前Copilot开发项目出现的问题做一个总结表单,每次Copilot完成工作之后,我们可以依循这个表单逐个排查项目潜在的问题。也因此,我们不能过于依赖Copilot的自动化,一股脑的生成好几轮的Session,这也直接印证了我们去采用敏捷开发的模式使用Copilot的重要性。
原本打算把Session4的内容也放进来的,但是限于篇幅,就放到下次的Blog吧。经过了三次的Session,我们的项目框架搭建完后,搭建好了数据库,同时实现了基本的数据添加等功能。但是对于一个完整的项目来说依然还是任重道远。期待后续的Copilot又会如何开发项目呢?是否会出现我刚刚提到的在某个小方向上越走越远越走越偏的情况呢?让我们拭目以待。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)