添加转岗学习计划:总纲、第 1-4 周、Python 补课和迷你多 Agent 项目
This commit is contained in:
commit
f8a25cb765
9 files changed
+2362
No files matched your search
@@ -0,0 +1,229 @@
|
||||
# 转岗计划 · 第 2 周学习计划(Gin 写 HTTP 接口 + 接前端)
|
||||
|
||||
> 所属阶段:第 1 个月 Go 全栈基础。总纲见同目录 `README.md`,下周计划见 `week03-postgresql.md`。
|
||||
|
||||
目标:把第 1 周的命令行任务清单改造成一个 Web 应用。后端用 Gin 提供 REST 接口,前端用你熟悉的 React 或 Vue 调用接口。本周数据先存在内存里,第 3 周再换成 PostgreSQL。
|
||||
|
||||
前置条件:第 1 周的 `todo` 项目已完成,`go test ./...` 能通过。
|
||||
|
||||
参考资料:
|
||||
- Gin 官方文档(中文):https://gin-gonic.com/zh-cn/docs/
|
||||
- Go 标准库 `net/http`:https://pkg.go.dev/net/http
|
||||
|
||||
---
|
||||
|
||||
## 最终项目结构
|
||||
|
||||
```
|
||||
todo-api/
|
||||
├── backend/
|
||||
│ ├── go.mod
|
||||
│ ├── main.go # 程序入口:读配置、组装依赖、启动服务
|
||||
│ ├── internal/
|
||||
│ │ ├── model/
|
||||
│ │ │ └── task.go # Task 结构体和校验
|
||||
│ │ ├── store/
|
||||
│ │ │ ├── store.go # TaskStore 接口定义
|
||||
│ │ │ └── memory_store.go # 内存实现(并发安全)
|
||||
│ │ ├── handler/
|
||||
│ │ │ ├── task_handler.go # 任务相关接口
|
||||
│ │ │ ├── task_handler_test.go # 接口测试
|
||||
│ │ │ └── response.go # 统一响应格式
|
||||
│ │ ├── middleware/
|
||||
│ │ │ ├── logger.go # 请求日志
|
||||
│ │ │ ├── cors.go # 跨域
|
||||
│ │ │ └── recovery.go # 捕获 panic
|
||||
│ │ └── router/
|
||||
│ │ └── router.go # 路由注册
|
||||
│ └── api.http # 手动测试接口用的请求文件
|
||||
└── frontend/
|
||||
├── package.json
|
||||
├── .env.development # 接口地址配置
|
||||
└── src/
|
||||
├── api/
|
||||
│ └── tasks.ts # 封装所有任务接口请求
|
||||
├── types/
|
||||
│ └── task.ts # Task 类型,和后端保持一致
|
||||
├── components/
|
||||
│ ├── TaskForm.tsx # 新增任务表单
|
||||
│ ├── TaskList.tsx # 任务列表
|
||||
│ └── TaskItem.tsx # 单条任务(完成、删除、编辑)
|
||||
└── App.tsx
|
||||
```
|
||||
|
||||
如果前端用 Vue,把 `.tsx` 换成 `.vue`,结构不变。
|
||||
|
||||
## 接口设计(本周要实现的全部接口)
|
||||
|
||||
| 方法 | 路径 | 说明 | 成功状态码 |
|
||||
| --- | --- | --- | --- |
|
||||
| GET | `/api/health` | 健康检查 | 200 |
|
||||
| GET | `/api/tasks` | 获取任务列表,支持 `?done=true/false` 过滤 | 200 |
|
||||
| GET | `/api/tasks/:id` | 获取单个任务 | 200 |
|
||||
| POST | `/api/tasks` | 新增任务,请求体 `{"title": "..."}` | 201 |
|
||||
| PATCH | `/api/tasks/:id` | 修改标题或完成状态 | 200 |
|
||||
| DELETE | `/api/tasks/:id` | 删除任务 | 204 |
|
||||
|
||||
统一响应格式:
|
||||
|
||||
```json
|
||||
{ "code": 0, "message": "ok", "data": { } }
|
||||
```
|
||||
|
||||
出错时 `code` 为非 0,`message` 写清楚原因,例如 `{"code": 40401, "message": "task not found", "data": null}`。
|
||||
|
||||
---
|
||||
|
||||
## 第 1 天:用标准库理解 HTTP,再上手 Gin
|
||||
|
||||
**学习内容**
|
||||
- 先用标准库 `net/http` 写一个最简单的服务器,理解 `http.Handler`、`ResponseWriter`、`Request`。
|
||||
- 再用 Gin 改写,对比两者差别,理解 Gin 帮你省了什么。
|
||||
- 安装 Gin:`go get github.com/gin-gonic/gin`。
|
||||
|
||||
**要建的文件**
|
||||
- `backend/go.mod`:运行 `go mod init todo-api`。
|
||||
- `backend/main.go`:
|
||||
- 先写标准库版本:`/hello` 返回 `Hello, HTTP`,跑通后注释掉。
|
||||
- 再写 Gin 版本:`GET /api/health` 返回 `{"status": "ok"}`,监听 `8080` 端口。
|
||||
- `backend/api.http`:写好 `GET http://localhost:8080/api/health` 请求,用编辑器的 REST Client 插件或 `curl` 测试。
|
||||
|
||||
**运行方式**:`cd backend && go run .`,另开终端运行 `curl localhost:8080/api/health`
|
||||
|
||||
**完成标准**:能说清楚一个 HTTP 请求从进入 Gin 到返回响应经过了哪些步骤。
|
||||
|
||||
---
|
||||
|
||||
## 第 2 天:数据模型和存储层
|
||||
|
||||
**学习内容**
|
||||
- 用接口隔离存储层,让接口代码不关心数据存在哪儿(下周换数据库时只换实现)。
|
||||
- 用 `sync.RWMutex` 让内存存储在并发请求下安全。
|
||||
|
||||
**要建的文件**
|
||||
- `internal/model/task.go`:
|
||||
- `Task` 结构体:`ID int64`、`Title string`、`Done bool`、`CreatedAt time.Time`、`UpdatedAt time.Time`,带 JSON 标签。
|
||||
- `func (t Task) Validate() error`:标题不能为空,长度不超过 100 字。
|
||||
- `internal/store/store.go`:
|
||||
- `var ErrNotFound = errors.New("task not found")`
|
||||
- 定义 `TaskStore` 接口:`List(done *bool) ([]model.Task, error)`、`Get(id int64)`、`Create(t model.Task)`、`Update(t model.Task)`、`Delete(id int64)`。
|
||||
- `internal/store/memory_store.go`:
|
||||
- `MemoryStore` 结构体:内部用 `map[int64]model.Task` 存数据,用自增 ID,用 `sync.RWMutex` 加锁。
|
||||
- 实现 `TaskStore` 接口的全部方法,找不到时返回 `ErrNotFound`。
|
||||
|
||||
**完成标准**:写一个小测试,启动 100 个 goroutine 同时调用 `Create`,最后任务数正好是 100,用 `go test -race ./...` 检查没有数据竞争。
|
||||
|
||||
---
|
||||
|
||||
## 第 3 天:任务接口(增删改查)
|
||||
|
||||
**学习内容**
|
||||
- Gin 的路由分组、路径参数 `c.Param`、查询参数 `c.Query`。
|
||||
- 请求体绑定 `c.ShouldBindJSON` 和参数校验标签 `binding:"required"`。
|
||||
- 正确使用 HTTP 状态码。
|
||||
|
||||
**要建的文件**
|
||||
- `internal/handler/response.go`:
|
||||
- `func OK(c *gin.Context, data any)`、`func Created(c *gin.Context, data any)`、`func Fail(c *gin.Context, httpStatus int, code int, msg string)`,统一响应格式。
|
||||
- `internal/handler/task_handler.go`:
|
||||
- `TaskHandler` 结构体,字段是 `store.TaskStore` 接口。
|
||||
- 方法 `List`、`Get`、`Create`、`Update`、`Delete`,对应上面的接口表。
|
||||
- 请求结构体 `CreateTaskRequest`、`UpdateTaskRequest`(`Update` 里的字段用指针,区分“没传”和“传了空值”)。
|
||||
- `store.ErrNotFound` 转成 404,校验失败转成 400。
|
||||
- `internal/router/router.go`:
|
||||
- `func New(h *handler.TaskHandler) *gin.Engine`,注册 `/api` 路由组下的全部接口。
|
||||
- 更新 `main.go`:创建 `MemoryStore`、`TaskHandler`、路由,启动服务。
|
||||
- 更新 `api.http`:为 6 个接口都写好测试请求,包括错误情况(空标题、不存在的 ID)。
|
||||
|
||||
**完成标准**:用 `api.http` 或 `curl` 把每个接口的正常和出错情况都测一遍,状态码和响应格式都正确。
|
||||
|
||||
---
|
||||
|
||||
## 第 4 天:中间件
|
||||
|
||||
**学习内容**
|
||||
- Gin 中间件原理:`c.Next()` 前后分别执行什么。
|
||||
- 跨域(CORS)为什么会出现,前端开发时怎么处理。
|
||||
|
||||
**要建的文件**
|
||||
- `internal/middleware/logger.go`:记录每个请求的方法、路径、状态码、耗时,用标准库 `log/slog` 输出。
|
||||
- `internal/middleware/cors.go`:允许前端开发地址(如 `http://localhost:5173`)跨域访问,处理 `OPTIONS` 预检请求。
|
||||
- `internal/middleware/recovery.go`:捕获 panic,返回统一的 500 错误响应,并记录日志。
|
||||
- 更新 `router.go`:用 `gin.New()` 替代 `gin.Default()`,手动挂上这三个中间件。
|
||||
|
||||
**完成标准**:故意在一个接口里写 `panic`,服务不会挂掉,返回 500 和统一格式;日志里能看到每个请求的耗时。
|
||||
|
||||
---
|
||||
|
||||
## 第 5 天:接口测试
|
||||
|
||||
**学习内容**
|
||||
- 用 `net/http/httptest` 在不启动服务的情况下测试接口。
|
||||
- 表格驱动测试覆盖多种请求。
|
||||
|
||||
**要建的文件**
|
||||
- `internal/handler/task_handler_test.go`:
|
||||
- 写一个 `setupRouter()` 辅助函数,用 `MemoryStore` 组装路由。
|
||||
- 测试用例至少包括:创建成功返回 201、空标题返回 400、获取不存在的任务返回 404、修改完成状态、删除后再获取返回 404、按 `done` 过滤列表。
|
||||
|
||||
**运行方式**:`go test ./... -v -race`
|
||||
|
||||
**完成标准**:全部测试通过,没有数据竞争警告。
|
||||
|
||||
---
|
||||
|
||||
## 第 6 天:前端接入
|
||||
|
||||
**学习内容**
|
||||
- 前后端分离的联调方式:开发时用环境变量配置接口地址,或用 Vite 代理。
|
||||
- 前端类型和后端 JSON 保持一致。
|
||||
|
||||
**要建的文件**
|
||||
- 用 Vite 创建前端:`npm create vite@latest frontend -- --template react-ts`(Vue 用 `vue-ts`)。
|
||||
- `frontend/.env.development`:`VITE_API_BASE=http://localhost:8080/api`
|
||||
- `src/types/task.ts`:`Task` 类型,字段名和后端 JSON 标签一致。
|
||||
- `src/api/tasks.ts`:封装 `listTasks`、`createTask`、`updateTask`、`deleteTask`,统一处理响应格式,`code` 非 0 时抛出错误。
|
||||
- `src/components/TaskForm.tsx`:输入标题,回车或点按钮新增。
|
||||
- `src/components/TaskItem.tsx`:显示单条任务,可勾选完成、删除、双击编辑标题。
|
||||
- `src/components/TaskList.tsx`:显示列表,带“全部 / 未完成 / 已完成”过滤。
|
||||
- `src/App.tsx`:组合以上组件,处理加载状态和错误提示。
|
||||
|
||||
**运行方式**:后端 `go run .`,前端 `npm run dev`,浏览器打开前端地址。
|
||||
|
||||
**完成标准**:在页面上能完成增删改查和过滤,后端日志能看到对应请求。
|
||||
|
||||
---
|
||||
|
||||
## 第 7 天:配置、优雅关机和收尾
|
||||
|
||||
**学习内容**
|
||||
- 用环境变量配置端口等参数,不把配置写死在代码里。
|
||||
- 优雅关机:收到 `Ctrl+C` 时,等正在处理的请求完成再退出。
|
||||
|
||||
**要建的文件和修改**
|
||||
- 更新 `main.go`:
|
||||
- 从环境变量 `PORT` 读端口,没设置时默认 `8080`。
|
||||
- 用 `http.Server` 包装 Gin,监听 `os.Interrupt` 和 `syscall.SIGTERM` 信号,调用 `server.Shutdown` 优雅关机,超时 5 秒。
|
||||
- `todo-api/README.md`:写清楚项目功能、技术栈、接口列表、本地运行步骤。
|
||||
- 提交并推送到 `zhuangang` 仓库(路径 `todo-api/`)。
|
||||
|
||||
**完成标准**
|
||||
- `go vet ./...` 没有警告,`gofmt -l .` 没有输出。
|
||||
- `go test ./... -race` 全部通过。
|
||||
- 前后端联调正常,README 别人照着能跑起来。
|
||||
- 在总纲 `README.md` 的进度记录里勾掉第 2 周。
|
||||
|
||||
---
|
||||
|
||||
## 本周自查清单
|
||||
|
||||
- [ ] 能说清楚 Gin 处理一个请求的流程
|
||||
- [ ] 理解为什么存储层要用接口隔离
|
||||
- [ ] 会用 `RWMutex` 写并发安全的结构
|
||||
- [ ] 会写 REST 接口,状态码使用正确
|
||||
- [ ] 会写自定义中间件
|
||||
- [ ] 会用 `httptest` 写接口测试
|
||||
- [ ] 前后端联调完成
|
||||
- [ ] 会做配置读取和优雅关机
|
||||
|
||||
下周预告:第 3 周把 `MemoryStore` 换成 PostgreSQL,学表结构设计、SQL、索引和事务,用 sqlc 生成数据访问代码。因为本周用接口隔离了存储层,下周只需要新写一个实现,接口代码基本不用动。
|
||||
Reference in new issue
Block a user