6224ef5980
- Implemented a new Growth page to track practice time and trends. - Added API integration for fetching review data. - Created components for displaying practice statistics and trends. - Updated navigation titles for the main index and growth pages. - Removed unused styles from the index page. - Introduced a Projects management page for adding and editing practice projects. - Developed a Record form for logging practice sessions with validation. - Added utility functions for date manipulation and duration formatting. - Implemented error handling and session management in the practice service. - Created unit tests for the practice service to ensure reliability.
148 lines
10 KiB
Markdown
148 lines
10 KiB
Markdown
# Ballet Island
|
||
|
||
同一 Git 仓库中的两个独立项目,分别安装依赖、运行与构建。第一期提供课后练习登记、日历回看、成长统计和练习项目管理,通过微信身份关联 PostgreSQL 中的个人数据。
|
||
|
||
## 目录
|
||
|
||
```text
|
||
miniprogram/ Taro 4.2.1 + React 18 + TypeScript + Taroify 1.0.6
|
||
src/ 小程序源码
|
||
config/ 小程序构建配置
|
||
package.json 小程序依赖与命令
|
||
pnpm-lock.yaml 小程序依赖锁文件
|
||
node_modules/ 小程序本地依赖(不提交)
|
||
.nvmrc Node 版本
|
||
backend/ Go 1.26 + PostgreSQL 18
|
||
cmd/server/ 服务入口、数据库连接与优雅退出
|
||
internal/httpapi/ HTTP 路由与测试
|
||
internal/identity/ 微信身份核验适配器
|
||
internal/practice/ 会话、项目、记录与回顾业务
|
||
internal/database/ 内嵌版本迁移
|
||
go.mod / go.sum Go 模块与依赖
|
||
Dockerfile 后端镜像构建
|
||
compose.yaml API 与数据库编排
|
||
.env.example 后端环境变量示例
|
||
scripts/dev.sh 本机 Go 开发启动脚本
|
||
```
|
||
|
||
小程序的 Node、pnpm 配置和依赖均位于 `miniprogram/`。后端通过 Go、Docker 和 shell 独立运行。根目录保留 Git、说明文档和通用仓库约定。
|
||
|
||
## 小程序开发
|
||
|
||
需要 Node.js 22.12+(`miniprogram/.nvmrc` 指定 22.22.1)、pnpm 9.15.0,以及具有该小程序开发权限的微信开发者工具。
|
||
|
||
从仓库根目录进入小程序项目:
|
||
|
||
```sh
|
||
cd miniprogram
|
||
pnpm install --frozen-lockfile
|
||
pnpm dev
|
||
```
|
||
|
||
微信开发者工具导入 `miniprogram/`,AppID 为 `wx6dfbf1021db8aee0`,编译目录为 `dist/`。底部入口为“记录、日历、成长”。首次请求通过 `Taro.login` 取得 code,在后端核验后建立业务会话;登录失败或网络失败会显示重试入口,不会当作空记录。
|
||
|
||
默认 API 为 `http://127.0.0.1:8080`。使用本地 HTTP 地址时,在开发者工具的本地设置中开启“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,仅用于本地调试。真机需使用能够访问的接口地址,并按微信要求配置域名。
|
||
|
||
在 `miniprogram/` 中通过构建时环境变量指定接口地址,修改后重启构建:
|
||
|
||
```sh
|
||
TARO_APP_API_BASE_URL=https://api.example.com pnpm dev
|
||
TARO_APP_API_BASE_URL=https://api.example.com pnpm build
|
||
```
|
||
|
||
`TARO_APP_API_BASE_URL` 是公开接口地址,不自动读取后端 `.env`;未设置时使用本机默认地址。
|
||
|
||
首次登录原子创建八个可编辑项目,练习数据从零开始。表单支持补记、更正和误记删除;项目改名同步显示于历史,有记录的项目移除时归档。近期列表按最后登记或更正时间排序,使刚保存的补记也能立即看到;日历和统计使用实际练习日期,统一按 Asia/Shanghai 解释,周一开始一周。
|
||
|
||
项目名最多 40 个 Unicode 字符,笔记最多 2000 个字符;前后端使用相同校验。分钟为正整数,按 PostgreSQL integer 存储范围上限为 2147483647。笔记只作为普通文本显示。每次主动新增生成独立提交标识;结果未确认时保留该标识与完整输入,重试不重复累计。首期不提供离线保存队列。
|
||
|
||
## 后端开发
|
||
|
||
需要 Go 1.26 或更高版本,以及 Docker Compose。仅使用 Docker 运行后端时无需本机 Go。
|
||
|
||
另开终端,从仓库根目录进入后端项目:
|
||
|
||
```sh
|
||
cd backend
|
||
test -f .env || cp .env.example .env
|
||
docker compose up -d --wait postgres
|
||
./scripts/dev.sh
|
||
```
|
||
|
||
`backend/.env` 已被 Git 忽略。启动脚本读取其中的 PostgreSQL 标准 `PG*` 变量与 `HTTP_ADDR`,先构建再启动 Go 服务;修改代码后需重启命令。脚本只应读取可信的本地配置,`.env` 使用 shell 赋值语法,含空格或 `$` 等特殊字符的值须用单引号包裹。直接运行 Go 或二进制时需自行注入环境变量。
|
||
|
||
后端全程无需 Node.js 或 pnpm。
|
||
|
||
在可信的 `backend/.env` 中设置 `WECHAT_APP_ID` 和 `WECHAT_APP_SECRET`,AppID 必须与小程序一致。AppSecret 只存在于服务端配置中,不能放入小程序源码或构建变量。通过[微信官方 code2Session 接口](https://developers.weixin.qq.com/miniprogram/dev/server/API/user-login/api_code2session.html)核验身份,不接受客户端自报的用户标识;业务会话为随机 bearer token,有效期 30 天,数据库只存 token 摘要,过期后小程序自动重新登录一次。
|
||
|
||
服务启动会在一个带锁的事务中运行尚未应用的版本迁移,初始化用户、会话、项目、练习和幂等回执表。后续启动不会重置数据或补回已移除项目。迁移文件位于 `backend/internal/database/`,已经应用的迁移应保留不改,通过新增版本演进结构。没有微信凭据时健康检查仍可运行,登录返回 503;配置缺失不提供模拟身份或开发后门。
|
||
|
||
## Docker 运行与部署
|
||
|
||
以下命令在 `backend/` 中执行:
|
||
|
||
```sh
|
||
docker compose up -d --build --wait
|
||
curl http://127.0.0.1:8080/healthz
|
||
curl http://127.0.0.1:8080/readyz
|
||
docker compose logs -f
|
||
docker compose down
|
||
```
|
||
|
||
Compose 等待数据库健康后启动 API,并等待两个服务就绪。API 容器使用非 root 用户;数据库写入命名卷,`docker compose down` 保留数据。Compose 项目名固定为 `ballet-island`,沿用现有数据库卷。
|
||
|
||
默认仅向宿主机回环地址开放 API 和数据库。部署到服务器时,可让 HTTPS 反向代理连接 `127.0.0.1:${API_PORT}`;如确需其他机器直接访问 API,可在 `backend/.env` 调整 `API_BIND`。数据库端口始终只绑定本机。Compose 内部使用 `PGHOST=postgres`、`PGPORT=5432`,宿主机数据库端口由 `.env` 的 `PGPORT` 控制。
|
||
|
||
`API_PORT` 控制 Docker API 的宿主机端口,`HTTP_ADDR` 控制本机 Go 服务的监听地址;修改后应同步小程序的 API 地址。本机 Go 服务与 Docker API 不应同时占用同一端口。
|
||
|
||
正式上线需配置 HTTPS、微信 request 合法域名、环境密码与数据库备份。小程序由微信开发者工具发布,Docker 部署后端和数据库。
|
||
|
||
PostgreSQL 18 数据卷按[官方镜像说明](https://hub.docker.com/_/postgres)挂载到 `/var/lib/postgresql`。已有数据库卷的密码不会因修改 `.env` 自动更新;需按数据库流程修改,不要为更新密码删除数据卷。
|
||
|
||
## 验证与构建
|
||
|
||
在 `miniprogram/` 中:
|
||
|
||
```sh
|
||
pnpm typecheck
|
||
pnpm test:client
|
||
pnpm build
|
||
```
|
||
|
||
在 `backend/` 中:
|
||
|
||
```sh
|
||
go vet ./...
|
||
go test -race ./...
|
||
go build -o bin/api ./cmd/server
|
||
|
||
# 真实 PostgreSQL 18 HTTP 集成验证,自动创建和清理临时测试容器。
|
||
./scripts/test-integration.sh
|
||
```
|
||
|
||
集成脚本只使用独立、内存存储的 PostgreSQL 容器,不读取业务 `.env`,不挂载 Compose 数据卷。每个用例创建自己的 schema 并在结束后清理。已有独立测试数据库时可通过 `TEST_DATABASE_URL` 指定(用户须有创建 schema 的权限);`REQUIRE_TEST_DATABASE=1` 可让缺少配置直接失败。单独执行 `go test` 而未提供测试数据库时,会明确跳过数据库集成用例,不能据此宣称业务集成通过。
|
||
|
||
集成测试经过真实 HTTP handler、业务会话和 PostgreSQL,仅在微信网络核验处使用受控响应,覆盖首次初始化、重复登录、身份隔离、归档与更正、分钟汇总、周/月边界、分页、提交重试和并发。`pnpm test:client` 使用 Node 内置测试工具验证请求层重新登录、待确认输入与提交标识保留、失败反馈和成功写入通知;只替换 Taro 网络与本地存储调用,不引入小程序端到端框架。
|
||
|
||
保存未确认时不能通过表单取消;系统返回会提示,待确认输入只在当前小程序进程内暂存,再次登记会恢复原提交。完全退出小程序会失去该临时表单,重新进入后应先查看云端历史;这不提供离线队列或进程重启后的草稿持久化。微信开发者工具中的键盘/滚动/窄屏表现、真实微信首次登录、跨设备恢复及断网流程仍需独立验收,构建和模拟微信响应均不能替代这些验证。
|
||
|
||
产物分别在 `miniprogram/dist/` 和 `backend/bin/api`,均不进入版本控制。
|
||
|
||
| 接口 | 用途 | 响应 |
|
||
| --- | --- | --- |
|
||
| `GET /healthz` | HTTP 服务存活检查 | `200 {"status":"ok"}` |
|
||
| `GET /readyz` | 实际检查 PostgreSQL 连接 | 可用时 200,不可用时 `503 {"status":"unavailable"}` |
|
||
| `POST /v1/session` | 微信 code 换业务会话 | `{token, expiresAt}` |
|
||
| `GET /v1/projects` | 活跃项目;`includeArchived=true` 包含归档 | `{projects, today}` |
|
||
| `POST /v1/projects` / `PUT /v1/projects/{id}` | 新增 / 改名,JSON `{name}` | 最新项目 |
|
||
| `DELETE /v1/projects/{id}` | 删除未使用项目或归档 | `{action: "deleted" / "archived"}` |
|
||
| `GET /v1/records` | `from` / `to` 日期范围、`limit`(1–100,默认20)、`cursor` | `{records, nextCursor}` |
|
||
| `GET /v1/records/{id}` | 读取本人单条记录 | 项目当前名称、日期、分钟、笔记 |
|
||
| `POST /v1/records` / `PUT /v1/records/{id}` | 新增 / 更正 | `{projectId, date, minutes, note}`;新增另需 `requestId`(16–128位字母、数字、下划线或短横线) |
|
||
| `DELETE /v1/records/{id}` | 删除误记,可安全重试 | `{action: "deleted"}` |
|
||
| `GET /v1/review?period=week&date=YYYY-MM-DD` | `week` / `month`,日期默认业务今天 | 累计、选定周期、每日分钟与项目分布 |
|
||
|
||
除会话交换和健康检查外,接口均需 `Authorization: Bearer <token>`。失败以 `{error:{code,message}}` 返回:401 重新登录、400 输入无效、404 对象不可用、409 归档项目或提交内容冲突、410 已删除记录的旧提交、503 暂时性失败。错误响应不返回数据库细节、微信密钥或其他用户的数据。移除/记录写入在用户范围内串行化,防止并发删除项目损坏历史。
|
||
|
||
Go 服务启动时检查数据库连接,收到 `SIGINT` / `SIGTERM` 时停止接收请求并关闭连接池。
|