docs(cursor): 优化 spark-im-v3 skill 与 Agent 说明

新增 mysql.md(t_ 表名与 migrate 流程),补全 /v1/music·square 与 admin 路由说明;description 与 AGENTS/rules 对齐 make test→lint 与 deploy 门禁,减少与 verify-before-done 重复。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhr 2026-09-24 11:09:15 +08:00
parent 59c79c7d32
commit 8870c59e2d
8 changed files with 75 additions and 30 deletions

View File

@ -11,7 +11,7 @@ Each business module under `internal/module/{biz}` is three packages:
| Layer | Path | Owns |
|-------|------|------|
| entity | `{biz}/entity` | Domain structs, message payload types, no I/O |
| model | `{biz}/model` | One Go file per MySQL table (GORM + COMMENT tags), Redis adapters |
| model | `{biz}/model` | One Go file per MySQL table (`TableName()` → `t_*`, GORM + COMMENT tags), Redis adapters |
| logic | `{biz}/logic` | One file per **function** (profile, login, send, inbox sync). Not one file per table. |
`logic` must not import `model`. Wire implementations in `internal/bootstrap`. Auth has no tables (entity + logic only).

View File

@ -7,11 +7,10 @@ alwaysApply: true
After **any** change to Go code, configs, migrations, OpenAPI, or i18n catalogs in this repo:
1. Run `make lint` from the repo root (`golangci-lint`; see `.golangci.yaml`).
2. Run `make test` from the repo root (full `go test ./...` plus logic-package coverage gate).
1. Run `make test` from the repo root — it depends on `make lint` (`golangci-lint`; see `.golangci.yaml`) then `go test ./...` plus logic-package coverage (≥65%).
**Do not** tell the user the task is complete, ready to merge, or OK to deploy until **both** commands exit 0.
**Do not** tell the user the task is complete, ready to merge, or OK to deploy until `make test` exits 0.
If either fails, fix the failures and re-run both. Do not skip hooks or substitute partial checks (e.g. only `go test` on one package) unless the user explicitly asked for a scoped check.
If it fails, fix and re-run. Do not skip hooks or substitute partial checks (e.g. only `go test` on one package) unless the user explicitly asked for a scoped check. Use `make lint` alone only when debugging lint without the full test run.
Docs-only or comment-only edits with zero build/test surface may skip verification; anything that can affect compile, lint, or tests must run both targets.

View File

@ -1,18 +1,20 @@
---
name: spark-im-v3
description: >-
Implements Spark IM modular monolith per architecture V3. Use when writing or
reviewing spark-server Go code, TCP/gnet, Gin HTTP, REST /v1 groups, MVC
handlers, go-playground validator, RealIP, messages, deliver queue, inbox
ZSET, Nginx 443 SNI, PROXY protocol, or SEND_ACK. Trigger terms: IM, MsgId,
TaskPool, deliver:queue, SYNC_INBOX, gnet, RealIP, validator, LoginService,
i18n, Accept-Language, zh-CN, uid, spark_id, infrastructure, entity, model, logic,
enum, MessageType, oneof.
Implements Spark IM modular monolith per architecture V3 in spark-server. Use
when writing or reviewing Go, migrations/SQL, OpenAPI, TCP/gnet, Gin HTTP,
admin `/api/admin`, REST `/v1`, MVC handlers, validator, RealIP, messages,
deliver queue, inbox ZSET, music/square modules, Makefile lint/test/deploy,
or t_* MySQL tables. Triggers: IM, MsgId, TaskPool, deliver:queue, migrations,
t_users, golangci-lint, make test, SYNC_INBOX, gnet, RealIP, i18n, entity,
model, logic, enum, oneof, SEND_ACK.
---
# Spark IM V3
Read [invariants.md](invariants.md) before changing send/sync/transport. Full design: `docs/architecture-v3.md`. Lint: `.golangci.yaml`.
Read [invariants.md](invariants.md) before changing send/sync/transport. Full design: `docs/architecture-v3.md`. Lint: `.golangci.yaml` (use `GOLANGCI_LINT := golangci-lint` in Makefile — not Make's builtin `LINT`).
MySQL / migrations: [mysql.md](mysql.md) — **`t_` table prefix**, replay-all migrate, run migrate after SQL edits.
## Layout
@ -40,7 +42,7 @@ Each `internal/module/{biz}` is three packages. Do not put entity, GORM rows, an
| Layer | Path | Owns |
|-------|------|------|
| entity | `{biz}/entity` | Domain types, message payloads. No I/O. |
| model | `{biz}/model` | One Go file per MySQL table (GORM + COMMENT). Redis adapters. |
| model | `{biz}/model` | One Go file per MySQL table (`TableName()` → `t_*`, GORM + COMMENT). Redis adapters. |
| logic | `{biz}/logic` | One file per **function** (profile, login, send). Not per table. |
`logic` must not import `model`. Wire implementations in `internal/bootstrap`. Auth has no tables (entity + logic only). HTTP/TCP both call `logic`; Gin handlers only bind and write the envelope.
@ -63,7 +65,7 @@ Not every PK is Snowflake. Do not AUTO_INCREMENT Snowflake business ids (`users.
| Kind | Examples | Rule |
|------|----------|------|
| Snowflake | `users.id` (uid), `messages.id` (MsgId), `conversations.id` | Time-ordered business IDs. One `id.Generator` per process; `snowflake_worker_id` unique per process. Clock rewind fails issuance for the whole process. |
| AUTO_INCREMENT | `groups.id` (群号); row id on `user_devices`, `friend_relations`, `user_login_logs`, `user_emails`, `p2p_conversations`, `conversation_members`, `group_members`, `device_sync_cursors`, `message_deletes`, `moment_audience`, `moment_likes`, `moment_settings`, `moment_hides` | Table-local row id. Business uniqueness stays UNIQUE (email, P2P pair, members, cursors). Do not AUTO_INCREMENT Snowflake columns. |
| AUTO_INCREMENT | `t_groups.id` (群号); row id on `t_user_devices`, `t_friend_relations`, … (see entity docs) | Table-local row id. Business uniqueness stays UNIQUE (email, P2P pair, members, cursors). Do not AUTO_INCREMENT Snowflake columns. |
`device_id` is **client-generated**, stored on the device, written to JWT `did`. The server does not allocate it. `uid` in JSON **is** `users.id`; do not add a second uid column.
@ -105,23 +107,16 @@ AUTH/login rate-limit uses `reqctx.RealIP`. TaskPool workers recover panics. Del
- HTTP: thin handler + module `logic`; REST group by business; validate with i18n `label`/`errmsg` keys; put RealIP and language on context.
- Closed type/status: entity typed enum + `Valid()` + OpenAPI named `$ref` + validator `oneof` on the same values ([enums.md](enums.md)).
- Login is email OTP (`POST /v1/auth/login`). `uid` is Snowflake `users.id`; public `spark_id` is unique and mutable. Email↔user lives in `user_emails`. Login upserts `user_devices` (client `device_id`). Logout is `POST /v1/auth/logout` (current device). Account cancellation is `DELETE /v1/users/me` (archive to `user_delete`, purge friends/moments/related).
- SQL tables and GORM models carry COMMENT on the table and every column.
- After adding or changing `migrations/*.sql`, run `go run ./cmd/migrate -config configs/config.yaml` in this session. Do not leave it for deploy.
- SQL/GORM: [mysql.md](mysql.md) (COMMENT, `t_*`, migrate).
## Before declaring done (required)
## Verify before done
After modifying this repo, run from the project root:
```bash
make lint
make test
```
Both must exit 0. `make test` runs all packages and enforces logic-layer coverage (see root `Makefile`). Do not mark work complete or hand off to the user until lint and test pass. Fix failures and re-run.
Follow `.cursor/rules/verify-before-done.mdc`: from repo root, `make test` (depends on `lint`) must exit 0; logic packages need ≥65% coverage (`LOGIC_COVER_MIN`). `make deploy` depends on `make test`. Docs-only edits with no compile surface may skip.
## Additional resources
- [invariants.md](invariants.md)
- [mysql.md](mysql.md)
- [send-path.md](send-path.md)
- [http.md](http.md)
- [i18n.md](i18n.md)

View File

@ -6,6 +6,8 @@ Closed type/status values live in `internal/module/{biz}/entity` as named types.
Wire JSON and TCP stay **numbers** (or the existing strings below). Do not turn message types into strings. Do not change persisted numeric values.
Physical MySQL tables are **`t_*`**; catalog column names below use logical names (`users.gender` → table `t_users`).
## Template (`message/entity.Type`)
```go

View File

@ -89,6 +89,8 @@ Public HTTP is `/v1/{resource}/...`. Register with Gin groups in `internal/trans
| files | `/v1/files` | upload credential |
| favorites | `/v1/favorites` | 收藏聊天消息 |
| moments | `/v1/moments` | 朋友圈 Feed、相册、赞评、设置 |
| square | `/v1/square` | 广场帖子、赞评、通知 |
| music | `/v1/music` | 公开 playlist / categories / settings(无需登录) |
| meta | `/v1/meta` | `GET /v1/meta/enums`(无需登录) |
Rules:
@ -107,6 +109,19 @@ Documented in **`docs/sms.md`**. Implementation:
Both return **`errs.ErrRateLimited` (`42902`)**. UI countdown should follow **60s** target cooldown, not `expires_in`.
- OpenAPI `tags` must match these groups. Docs jump tags `enums` / `errors` sit above `system` and open `/docs/enums`、`/docs/errors`.
## Admin HTTP (separate from `/v1`)
Registered in `router_admin.go`. Bearer admin token (`AdminAuth`). Prefixes:
| Prefix | Use |
|--------|-----|
| `/api/admin` | Shared user lookup / ban ops (popadmin host routing) |
| `/api/admin/spark`, `/api/admin/pop` | IM stats, groups, complaints, rankings, **music CRUD** + uploads |
Music admin: `GET/POST/PATCH/DELETE …/music`, categories tree, `PUT …/music/:id/categories`, cover/background/audio upload. Client-facing music stays on `/v1/music`.
When adding admin routes: handler under `handler/admin`, register in `router_admin.go`, document in OpenAPI admin section if applicable.
## Checklist when adding an HTTP API
1. Input struct in `internal/module/{biz}` with `validate` / `label` / `errmsg` **keys**.

View File

@ -0,0 +1,34 @@
# MySQL: `t_` tables, migrations, GORM
## Table names
All application tables use the **`t_` prefix** (`t_users`, `t_messages`, `t_music_tracks`, …).
| Layer | Rule |
|-------|------|
| SQL migrations | `CREATE TABLE t_*`, `RENAME TABLE … TO t_*` |
| GORM | `func (Row) TableName() string { return "t_…" }` on every row struct |
| Raw SQL in logic/model | Use `t_*` names; do not reference unprefixed legacy names |
Legacy DBs: `migrations/0000_table_prefix.sql` renames old tables before `0001+` (no-op if already prefixed). New environments rely on `CREATE t_*` in later files.
## Migrations workflow
- Files live in `migrations/*.sql`; **`cmd/migrate` replays every file** on each run (no version table). Order matters: `0000` before creates.
- Every new/changed migration: run in the agent session:
```bash
go run ./cmd/migrate -config configs/config.yaml
```
Do not tell the user to apply SQL manually on deploy unless they asked for ops-only steps.
## Schema documentation
- Table-level and column-level **`COMMENT`** in SQL; mirror intent in GORM struct tags where the team already uses comments.
- Closed numeric/string sets: entity enum + OpenAPI `$ref` ([enums.md](enums.md)), not ad hoc magic numbers in SQL.
## Logic tests vs DB
- `internal/module/*/logic` tests use stubs/fakes; they must **not** require MySQL.
- Model/repo integration belongs in transport tests or dedicated DB tests if added later.

View File

@ -25,7 +25,7 @@ _ = pool.Submit(ctx, func() { router.Dispatch(ctx, frame) })
## Sync
```sql
SELECT * FROM messages
SELECT * FROM t_messages
WHERE conversation_id = ? AND id > ?
ORDER BY id ASC LIMIT 200;
```

View File

@ -7,8 +7,8 @@ Before writing code:
1. Load `.cursor/skills/spark-im-v3/SKILL.md`
2. Follow `.cursor/rules/` (always-on V3 invariants + file-scoped rules)
3. Keep functions within `.golangci.yaml` (`funlen` 100, `lll` 240)
4. HTTP and TCP share Application services under `internal/module/{biz}/logic`. Each module is `entity` / `model` (one file per table) / `logic` (one file per function, not per table). Gin handlers only bind and call those services (MVC). REST paths are `/v1/{resource}` grouped by business. Request DTOs use go-playground/validator (`label` / `errmsg` i18n keys). Context must carry `reqctx.RealIP` and the user language (`zh-CN` / `en` / `zh-TW`). Error `msg` and notification copy come from `internal/pkg/i18n`. Table and column COMMENT is required. Message `content` is serialized bytes, not a string.
5. After adding or changing `migrations/*.sql`, run `go run ./cmd/migrate -config configs/config.yaml` yourself. Do not ask the user to apply it later.
6. Before finishing any code change, run `make lint` and `make test` at the repo root; both must pass. Do not report the task as OK until they do.
4. HTTP and TCP share Application services under `internal/module/{biz}/logic`. Each module is `entity` / `model` (one file per table, GORM `TableName()` → `t_*`) / `logic` (one file per function, not per table). Gin handlers only bind and call those services (MVC). REST paths are `/v1/{resource}` grouped by business; admin ops use `/api/admin/…` (see skill `http.md`). Request DTOs use go-playground/validator (`label` / `errmsg` i18n keys). Context must carry `reqctx.RealIP` and the user language (`zh-CN` / `en` / `zh-TW`). Error `msg` and notification copy come from `internal/pkg/i18n`. Table and column COMMENT is required. Message `content` is serialized bytes, not a string.
5. MySQL: all tables `t_*`; migrations replay-all — details in `.cursor/skills/spark-im-v3/mysql.md`. After changing `migrations/*.sql`, run `go run ./cmd/migrate -config configs/config.yaml` yourself.
6. Before finishing any code change, run `make test` at the repo root (runs `lint` first, logic coverage ≥65%). Do not report the task as OK until it passes. `make deploy` chains through `test`.
Do not introduce Kafka, K8s, microservices, ConversationSeq, application-layer handshake crypto, or public gnet ports.