社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Git

【第3591期】GitHub Spec-Kit:规范驱动开发走在正确的方向上 - 严谨、渐进式的 Vibe Coding

前端早读课 • 昨天 • 48 次点击  

前言

探讨了 Spec-Kit 工具在软件开发中的应用。Spec-Kit 通过结构化的方式帮助开发者与 AI 配对,共同构建复杂功能。它包括规范、规划、执行等步骤,支持多种工具,如 Cursor。作者通过实际案例展示了如何使用 Spec-Kit 和 Cursor 来开发一个照片相册组织应用,包括任务生成、依赖关系管理、测试驱动开发(TDD)等环节。今日前端早读课文章由 @Roy Osherove 分享,@飘飘编译。

译文从这开始~~

【第3588期】基于AI的规范驱动开发:使用全新开源工具包快速上手

自从 vibe-coding 流行起来后,代码的 “劣化”(enshitification)以及随意糊弄的东西变得越来越容易产生。我认为像 Spec-Kit 这样的项目正是为了解决这些问题,而且出现得正是时候。

我相信未来我们会看到越来越多类似的 “提示框架”(prompt-framework),例如 socratic coder、Claude Templates、BMAD-METHOD、ai-rulez 等等。

在这些框架里,Spec-Kit 显得尤其有潜力,也设计得非常周到 —— 你可以把它想象成 Kiro 的开源版本,而且支持多种工具。

我觉得官方文案有点太模糊,如果让我来定义,我会这样描述:

一种结构化的方式来构建由 AI 协作的复杂功能:
把你的 AI 编程助手驯化成一个遵循规范的软件设计流程的伙伴,分步骤完成规格说明、规划、应做和不应做的事项,并让整个团队都能重复使用这个流程。

这些步骤本身其实相当直观:

我个人使用的是 Cursor,它的策略大致是这样的:

【第3558期】谁说前端改动看不出影响范围?用 Cursor 找到了隐藏炸弹

  • 使用 Cursor Commands,你可以直接在聊天中输入 /specify 和 /plan(它会在 .cursor/commands/specify.md 和 plan.md 等文件中生成这些命令)。
  • 它会生成一个 CONSTITUTION.md 模板文件,作为 Cursor 命令中提示的指导原则。
  • 它会创建一个 .specify 目录,用来存放记忆、脚本和模板,供 Cursor(或 Claude 等)中的智能代理功能使用。这样多个开发者可以共享上下文和工作流。本质上,这里面包含了规则、状态和工具,确保在使用上述命令时,无论是跨会话还是多人协作,都能获得相对一致的体验。

创建一个新功能

你首先从 /specify 开始,然后在操作过程中,智能体会利用命令里的提示来完成一些事情,例如:

  • 为正在开发的功能生成一个新的文件夹
  • 里面包含该功能的规划、设计和文档
  • 之后也会用于生成代码

在整个流程中,你会感觉它的设计相对成熟。我们都曾在各自的团队里尝试 “重复造轮子”,而现在似乎有了一个(目前算是标准化的,但可能下个月就会改变)较统一的解决方案。

【第3469期】为什么 React 出人意料地成为 LLM 工作流程的最佳模型

当规划阶段完成后,就进入 /tasks 阶段,Cursor 会根据之前的计划生成任务:

在某个阶段,代理卡住或超时了,于是我手动停止,并让它从中断的地方 “继续” 执行 —— 结果运行得很好,这要归功于它的结构所保留的 “记忆”。

任务:相册整理应用

输入:/specs/001-build-an-application/ 中的设计文档

前置条件:plan.md(必需),research.mddata-model.mdcontracts/quickstart.md

执行流程(主线)

1、从功能目录加载 plan.md

  • 如果找不到:报错 "No implementation plan found"
  • 提取:技术栈、库、结构

2、加载可选设计文档:

  • data-model.md:提取实体 → 建模任务
  • contracts/:每个文件 → 合同测试任务
  • research.md:提取决策 → 初始化任务

3、按类别生成任务:

  • 初始化:项目结构、依赖、代码规范
  • 测试:合同测试、集成测试
  • 核心:模型、服务、CLI 命令
  • 集成:数据库、中间件、日志
  • 打磨:单元测试、性能、文档

4、应用任务规则:

  • 不同文件 = 标记 [P] 可并行
  • 同一文件 = 顺序执行(不标 [P])
  • 测试必须在实现之前(TDD)

5、按顺序编号任务(T001, T002…)

6、生成依赖关系图

7、创建并行执行示例

8、验证任务完整性:

  • 所有合同是否都有测试?
  • 所有实体是否都有模型?
  • 所有接口是否都实现?

9、返回:SUCCESS(任务准备就绪)

格式:[ID] [P?] 描述
  • [P]:可并行执行(不同文件,无依赖)
  • 描述中必须包含精确的文件路径
路径约定
  • Web 应用:backend/src/frontend/src/
  • 下文路径遵循 plan.md 中的 Web 应用结构
阶段 3.1:初始化
  • T001 创建项目结构,包含 backend/ 和 frontend/ 目录
  • T002 初始化 Node.js 后端项目,依赖:Express、TypeScript、SQLite
  • T003 初始化 React 前端项目,依赖:TypeScript、@dnd-kit、React
  • T004 在 backend/.eslintrc.json 配置 ESLint 和 Prettier
  • T005 在 frontend/.eslintrc.json 配置 ESLint 和 Prettier
  • T006 在 backend/jest.config.js 设置 Jest 测试框架
  • T007 在 frontend/jest.config.js 设置 Jest 和 React Testing Library
  • T008 在 tests/e2e/playwright.config.ts 配置 Playwright 端到端测试
阶段 3.2:测试优先(TDD)⚠️ 必须在 3.3 之前完成

重要:在做任何实际操作之前,必须先把这些测试写出来,而且一开始测试得是不通过的

合同测试(API 接口)

  • T009 GET /albums 接口合同测试 → tests/contract/test_albums_get.spec.ts
  • T010 POST /albums 接口合同测试 → tests/contract/test_albums_post.spec.ts
  • T011 GET /albums/{albumId} 接口合同测试 → tests/contract/test_albums_get_by_id.spec.ts
  • T012 PATCH /albums/{albumId} 接口合同测试 → tests/contract/test_albums_patch.spec.ts
  • T013 POST /albums/reorder 接口合同测试 → tests/contract/test_albums_reorder.spec.ts
  • T014 GET /photos 接口合同测试 → tests/contract/test_photos_get.spec.ts
  • T015 POST /photos 接口合同测试 → tests/contract/test_photos_post.spec.ts
  • T016 GET /photos/{photoId} 接口合同测试 → tests/contract/test_photos_get_by_id.spec.ts
  • T017 DELETE /photos/{photoId} 接口合同测试 → tests/contract/test_photos_delete.spec.ts
  • T018 GET /photos/{photoId}/file 接口合同测试 → tests/contract/test_photos_file_get.spec.ts
  • T019 GET /user/preferences 接口合同测试 → tests/contract/test_preferences_get.spec.ts
  • T020 PATCH /user/preferences 接口合同测试 → tests/contract/test_preferences_patch.spec.ts

集成测试(用户场景)

  • T021 照片上传与自动分组集成测试 → tests/integration/test_upload_auto_group.spec.ts
  • T022 相册拖拽排序集成测试 → tests/integration/test_album_reorder.spec.ts
  • T023 照片缩略图浏览集成测试 → tests/integration/test_photo_browsing.spec.ts
  • T024 无日期照片处理集成测试 → tests/integration/test_undated_photos.spec.ts
  • T025 用户偏好持久化集成测试 → tests/integration/test_preferences.spec.ts
阶段 3.3:核心实现(仅在测试失败后开始)

数据模型

  • T026 照片模型及验证 → backend/src/models/Photo.ts
  • T027 相册模型及验证 → backend/src/models/Album.ts
  • T028 用户偏好模型 → backend/src/models/UserPreferences.ts
  • T029 数据库模式与迁移 → backend/src/database/schema.sql
  • T030 数据库连接设置 → backend/src/database/connection.ts

核心库(宪法性要求)

  • T031 photo-manager 库:EXIF 解析 → backend/src/lib/photo-manager/exif.ts
  • T032 photo-manager 库:缩略图生成 → backend/src/lib/photo-manager/thumbnails.ts
  • T033 photo-manager CLI 接口 → backend/src/lib/photo-manager/cli.ts
  • T034 album-organizer 库:日期分组 → backend/src/lib/album-organizer/grouping.ts
  • T035 album-organizer 库:拖拽排序持久化 → backend/src/lib/album-organizer/reorder.ts
  • T036 album-organizer CLI 接口 → backend/src/lib/album-organizer/cli.ts
  • T037 file-handler 库:上传处理 → backend/src/lib/file-handler/upload.ts
  • T038 file-handler 库:存储管理 → backend/src/lib/file-handler/storage.ts
  • T039 file-handler CLI 接口 → backend/src/lib/file-handler/cli.ts
服务层
  • T040 PhotoService CRUD → backend/src/services/PhotoService.ts
  • T041 AlbumService CRUD 和排序 → backend/src/services/AlbumService.ts
  • T042 UserPreferencesService → backend/src/services/UserPreferencesService.ts
  • T043 FileProcessingService → backend/src/services/FileProcessingService.ts
API 接口实现
  • T044 GET /albums → backend/src/routes/albums.ts
  • T045 POST /albums → backend/src/routes/albums.ts
  • T046 GET /albums/{albumId} → backend/src/routes/albums.ts
  • T047 PATCH /albums/{albumId} → backend/src/routes/albums.ts
  • T048 POST /albums/reorder → backend/src/routes/albums.ts
  • T049 GET /photos → backend/src/routes/photos.ts
  • T050 POST /photos(含文件上传)→ backend/src/routes/photos.ts
  • T051 GET /photos/{photoId} → backend/src/routes/photos.ts
  • T052 DELETE /photos/{photoId} → backend/src/routes/photos.ts
  • T053 GET /photos/{photoId}/file → backend/src/routes/photos.ts
  • T054 GET /user/preferences → backend/src/routes/preferences.ts
  • T055 PATCH /user/preferences → backend/src/routes/preferences.ts
前端组件
  • T056 相册网格组件(含拖拽)→ frontend/src/components/AlbumGrid.tsx
  • T057 相册卡片组件(含预览)→ frontend/src/components/AlbumCard.tsx
  • T058 照片缩略图组件 → frontend/src/components/PhotoTile.tsx
  • T059 照片网格组件(虚拟滚动)→ frontend/src/components/PhotoGrid.tsx
  • T060 上传组件(含文件处理)→ frontend/src/components/Upload.tsx
  • T061 设置 / 偏好组件 → frontend/src/components/Settings.tsx
  • T062 主页面布局 → frontend/src/pages/MainPage.tsx
  • T063 相册详情页 → frontend/src/pages/AlbumPage.tsx
  • T064 API 服务层 → frontend/src/services/api.ts
  • T065 状态管理(Context)→ frontend/src/context/AppContext.tsx
阶段 3.4:集成
  • T066 将服务与数据库连接 → backend/src/services/
  • T067 文件上传中间件配置 → backend/src/middleware/upload.ts
  • T068 错误处理中间件 → backend/src/middleware/error.ts
  • T069 使用 Winston 的日志中间件 → backend/src/middleware/logging.ts
  • T070 CORS 与安全头配置 → backend/src/middleware/security.ts
  • T071 前端 API 集成 → frontend/src/services/
  • T072 前端 Service Worker 缩略图缓存 → frontend/public/sw.js
阶段 3.5:打磨
  • T073 EXIF 解析单元测试 → tests/unit/photo-manager/test_exif.spec.ts
  • T074 缩略图生成单元测试 → tests/unit/photo-manager/test_thumbnails.spec.ts
  • T075 日期分组单元测试 → tests/unit/album-organizer/test_grouping.spec.ts
  • T076 拖拽排序逻辑单元测试 → tests/unit/album-organizer/test_reorder.spec.ts
  • T077 文件处理单元测试 → tests/unit/file-handler/test_upload.spec.ts
  • T078 前端组件单元测试 → frontend/src/components/__tests__/
  • T079 大型照片集性能测试 → tests/performance/test_large_collection.spec.ts
  • T080 端到端用户全流程测试 → tests/e2e/test_user_workflows.spec.ts
  • T081 更新库文档(llms.txt 格式)
  • T082 从 OpenAPI 规范生成 API 文档
  • T083 代码清理与重构
  • T084 执行 quickstart.md 验证场景

依赖关系

关键依赖(阻塞型)

  • 初始化(T001–T008)必须在所有测试和实现之前完成
  • 合同测试(T009–T020)必须在对应的接口实现(T044–T055)之前完成
  • 集成测试(T021–T025)必须在实现之前完成
  • 模型(T026–T030)必须在服务(T040–T043)之前完成
  • 库(T031–T039)必须在服务(T040–T043)之前完成
  • 服务(T040–T043)必须在接口实现(T044–T055)之前完成
  • API 接口(T044–T055)必须在前端集成(T071)之前完成
  • 核心实现必须在打磨(T073–T084)之前完成

具体阻塞关系

  • T026–T028 阻塞 T040–T043(模型 → 服务)
  • T031–T039 阻塞 T040–T043(库 → 服务)
  • T040–T043 阻塞 T044–T055(服务 → 接口)
  • T044–T055 阻塞 T071(API → 前端集成)
  • T066–T070 阻塞 T080(中间件 → E2E 测试)

并行执行示例

初始化阶段(T004–T008)
 # 同时执行 T004–T008:
 任务: "在 backend/.eslintrc.json 配置 ESLint 和 Prettier"
 任务: "在 frontend/.eslintrc.json 配置 ESLint 和 Prettier"
 任务: "在 backend/jest.config.js 设置 Jest 配置"
 任务: "在 frontend/jest.config.js 设置 Jest 和 React Testing Library"
 任务: "在 tests/e2e/playwright.config.ts 配置 Playwright E2E 测试"
合同测试阶段(T009–T020)
 # 同时执行 T009–T020(所有文件不同):
 任务: "GET /albums 接口合同测试 → tests/contract/test_albums_get.spec.ts"
 任务: "POST /albums 接口合同测试 → tests/contract/test_albums_post.spec.ts"
 任务: "GET /albums/{albumId} 接口合同测试 → tests/contract/test_albums_get_by_id.spec.ts"
 # …(12 个合同测试可全部并行运行)
模型阶段(T026–T030)
 # 同时执行 T026–T030:
 任务: "照片模型 → backend/src/models/Photo.ts"
 任务: "相册模型 → backend/src/models/Album.ts"
 任务: "用户偏好模型 → backend/src/models/UserPreferences.ts"
 任务: "数据库模式与迁移 → backend/src/database/schema.sql"
 任务: "数据库连接设置 → backend/src/database/connection.ts"
库阶段(T031–T039)
 # 同时执行 T031–T039(所有库互相独立):
 任务: "photo-manager 库:EXIF 解析 → backend/src/lib/photo-manager/exif.ts"
 任务: "photo-manager 库:缩略图生成 → backend/src/lib/photo-manager/thumbnails.ts"
 任务: "photo-manager CLI 接口 → backend/src/lib/photo-manager/cli.ts"
 # …(9 个库任务可全部并行运行)

任务生成规则

1、基于合同(api.yaml)

  • 7 组 API 接口 → 12 个合同测试任务
  • 每个接口 → 对应的实现任务

2、基于数据模型

  • 3 个实体(Photo、Album、UserPreferences)→ 3 个模型任务
  • 数据库初始化 → 模式和连接任务

3、基于宪法性要求

  • 3 个必备库 → 9 个库任务(每个库 3 个文件
  • 每个库 → 一个 CLI 接口任务

4、基于用户故事(quickstart.md)

  • 5 个场景 → 5 个集成测试任务
  • 每个场景 → 在打磨阶段进行验证

5、顺序规则

  • 初始化 → 测试 → 模型 → 库 → 服务 → 接口 → 前端 → 打磨
  • 强制 TDD:所有测试必须先于任何实现

验证清单

检查点:所有项目均已验证

  • [x] 所有合同都有对应测试(T009–T020)
  • [x] 所有实体都有对应模型任务(T026–T028)
  • [x] 所有测试都在实现之前(阶段 3.2 在 3.3 之前)
  • [x] 并行任务真正独立(不同文件、无共享状态)
  • [x] 每个任务都指定了精确的文件路径
  • [x] 没有任务会修改与其他 任务相同的文件
  • [x] 符合宪法性要求(所有功能都以库 + CLI 接口形式实现)
  • [x] TDD 流程被严格执行(红 → 绿 → 重构)

请注意,此文件的前 10% 是给代理的指令,最后部分也是给代理的指令。这种结构在各处都存在,我认为这能让代理的体验更加一致。

【第3508期】FunProxy - 使用 Rust 构建跨平台全链路测试抓包代理工具

我很喜欢这里是通过 测试驱动开发(TDD)- 来确保功能真正能跑通的做法。作为一个在合适场景下非常推崇 TDD 的人,我认为对于需要快速建立连续反馈循环的智能代理来说,TDD 再合适不过了 —— 从最终愿景出发,是一种很好地 “奖励” 代理工作的方式,尤其是在它处理大量代码时。

使用 Grok-code-fast 验证

在此之前,我一直用的是 Claude Sonnet 4,但它并不适合这种规划类的任务。我其实心里很清楚,但还是想试试会发生什么。

现在,既然所有的规划产出已经准备好了,我又换成 grok-code-fast-1m- 对所有的计划和任务做了一次检查,结果如下:

实现审计完成 ✅

我对实现计划和相关文档做了一次全面审计。结果发现,最初的 tasks.md 缺少一些关键的实现引用,这会导致开发者难以找到每个任务的详细规格说明。

发现的主要问题

1、缺少实现参考

  • 数据模型:任务没有引用 data-model.md 中的实体定义、验证规则和 SQL 模式
  • API 接口:任务没有引用 contracts/api.yaml 中的 OpenAPI 规范以及请求 / 响应结构
  • 库:任务没有引用 research.md 中的技术选型(Sharp、ExifReader、@dnd-kit)
  • 业务逻辑:任务没有引用 data-model.md 中的数据流模式和业务规则
  • 性能:任务没有引用 plan.md 中的性能目标,以及 research.md 中的优化策略

2、缺少技术实现任务

  • 边界情况:没有任务处理损坏的 EXIF 数据、重复照片或 “未知日期” 的相册
  • 错误处理:没有针对 spec.md 中边界情况(如错误日期、大型相册)的任务
  • 性能:没有对应性能目标的任务(例如加载时间 <2s,支持 1 万 + 张照片)
  • 集成测试:缺少文件系统存储的集成测试

3、缺少业务逻辑任务

  • 孤立处理:没有任务处理缺少 EXIF 日期的照片
  • 重复检测:没有任务基于校验和来防止重复照片
  • 排序管理:没有任务确保相册顺序连续
  • 封面逻辑:没有任务自动选择相册封面

改进措施

✅ 增强任务引用

每个实现任务现在都包含了明确的文档引用:

  • 数据模型 → data-model.md(实体、验证、SQL 模式)
  • 库 → research.md(Sharp、ExifReader、@dnd-kit 技术选型)
  • 服务 → data-model.md(数据流模式、业务规则)
  • API 接口 → contracts/api.yaml(OpenAPI 规范)
  • 前端 → research.md(技术选型)+ spec.md(UI 需求)
  • 集成 → plan.md(可观测性需求)

✅ 新增缺失任务(17 个)

  • 边界情况(T079–T082):损坏 EXIF、重复照片、未知日期、性能
  • 集成测试(T085):文件系统存储测试
  • 强化打磨(T086–T089):文档、验证、宪法合规性

✅ 提升任务细化程度

  • 精确文件路径:所有任务都明确指定位置
  • 技术引用:提及具体库(Sharp、Multer、@dnd-kit)
  • 宪法引用:包含 CLI 要求、库文档、TDD 强制性要求
  • 性能引用:包括加载时间目标、大规模数据集处理

✅优化依赖关系

  • 数据库设置:模型阻塞服务(T026–T030 → T040–T043)
  • 边界情况:错误处理阻塞验证(T079–T082 → T089)
  • 单元测试:阻塞集成测试(T073–T077 → T083–T085)

更新后的任务总数:89 个

  • 原始版本:72 个任务(缺少关键实现细节)
  • 改进版本:89 个任务(全面且有完整引用)

结果

更新后的 tasks.md 提供了完整的实现指导,并关联到所有相关设计文档。每个任务足够具体,开发者可以立刻理解:

  • 要实现什么
  • 在哪里找到详细规格说明
  • 要用哪些技术和模式
  • 如何符合宪法性要求

审计表明,原始任务列表虽然在结构上合理,但缺少至关重要的 “实现细节引用”,这使得任务对遵循 TDD 和宪法原则的开发者来说不够可操作。

💡我觉得在 Cursor 里用 /commands, 来做这种流程非常有用。

/commands  允许我们创建一些小型的自定义 “向导”,它们比传统脚本更聪明、更实用。传统脚本必须考虑各种用户错误或不同的决策,而命令式的方式只需要:设定目标、提供工具、定义流程,并告诉代理在不同情况下该怎么做。输入依然是自然语言,而且格式可以非常灵活。

我觉得这种方式非常适合用来做设计流程,至少在目前阶段如此。

开始实现

我继续使用 grok-code-fast-1m 并触发了实现:

它一开始忘了把一些内容加到 .gitignore,但很快修复了。

大约 8 分钟后(grok 确实很快),我收到了提示消息。

我做了回复。

随着对话上下文接近 70%,我结束了本次会话,重新开启了一个新对话,并让代理从功能文件夹的上下文继续执行。

目前为止一切顺利:

大约 15 分钟后,代理宣布一切运行正常。

启动 后端 的确没问题。

前端出现了一些问题,我最终切换到 Claude Opus 来解决(主要是端口配置错误)。但又出现了新的问题。

后来我想起来可以用 Playwright MCP 工具,于是让 Cursor 用它来测试网站:

结果它生成了一堆 Playwright 测试并运行了。很有用!甚至还测试了文件上传等功能。

我采用了 环境变量 的方式:

经过大约 10 分钟的调整,应用运行正常。

  • 它默认没有用数据库(如 sqlite),而是采用了内存存储。
  • 没有身份验证或权限控制。

但整体上,这是一个相当不错的 概念验证(POC),几乎可以 “开箱即用”,运行效果还挺好。

最大的好处是,底层文件系统、结构和架构总体都不算糟糕。只是因为我没有认真对待 CONSTITUTION.md 文件,所以本可以做得更好。

我还没在遗留系统里试过,但接下来几天或几周会尝试更多工具和方法,到时候再更新。

总结

我喜欢这个想法,我觉得值得长期保留。我会在遗留系统里试试看。它预示着未来的一种趋势,就像当初 Kiro 想做的事情一样。

优点
  • 一种结构化、可重复的方法,可以处理复杂的新功能,并需要 “真实世界” 的架构和规范
  • 在提示层面强调 TDD,让代理可以更充分地自我验证
  • 结合 Cursor 命令、良好的起始规则和记忆文件夹,能预先解决很多反复沟通的问题
  • 由于有记忆,可以轻松继续之前的会话
  • 为每个规格化的功能创建子文件夹,并放入所需上下文,这种方式很强大
  • 可跨多个 AI 协作环境使用(Cursor、Claude Code 等)
缺点
  • 代理容易把 TDD 理解错。例如它会一次性生成所有测试并让它们一起失败,而不是逐个增量式实现(这样会更好)
  • 容易忽略或草率对待 CONSTITUTION.md 文件,或者干脆让代理自己决定(React? TypeScript? Vite? Jest? npm? pnpm? 前后端是否统一语言?API?部署?等等)
  • 它自己生成的测试质量不足。我不得不手动让它加上前端 E2E 测试,并用 Playwright MCP 来导航和操作(这其实可以通过 constitution.md 解决)
  • 仍然会生成大量代码,必须逐一检查
  • 我不喜欢 “constitution” 这个词,一是拼写太费劲,二是其实有更好的替代词,更直观、易用。例如我问过 GPT-5,得到了一些替代方案:

    • Charter.md
    • doctrine.md
    • ethos.md
    • tenets.md
    • protocol.md
    • policy.md
    • bylaws.md
    • dogma.md
    • gospel.md
    • blueprint.md
    • northstar.md
    • playbook.md

关于本文
译者:@飘飘
作者:@Roy Osherove
原文:https://robotpaper.ai/i-tried-out-github-spec-kit-and-all-i-got-was-this-not-terrible-website/

这期前端早读课
对你有帮助,帮” 
 “一下,
期待下一期,帮”
 在看” 一下。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/186944
 
48 次点击