spark-server/.cursor/skills/spark-im-v3/SKILL.md
2026-09-29 10:22:28 +08:00

6.9 KiB
Raw Blame History

name description
spark-im-v3 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 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 — t_ table prefix, replay-all migrate, run migrate after SQL edits.

Layout

cmd/server                       process entry
cmd/migrate                      apply migrations/*.sql (re-runs all files)
internal/bootstrap               config, lifecycle, wire model → logic
internal/transport/http          Gin engine, REST groups, middleware
internal/transport/http/handler  thin MVC controllers (no business)
internal/transport/http/respond  BindJSON + envelope write
internal/transport/tcp           gnet EventLoop + TaskPool + Frame
internal/module/{biz}            entity / model / logic (see Layers)
internal/pkg                     process-agnostic helpers (no DSN / pools)
internal/infrastructure          process adapters (mysql, redis, id, logger, push)

HTTP: read http.md — validator keys, reqctx.RealIP, /v1/{resource} groups, MVC login. i18n: read i18n.md — request language, error msg, notification types. Enums: read enums.md — typed entity enums, OpenAPI $ref, oneof bound to the same numbers.

Layers

Each internal/module/{biz} is three packages. Do not put entity, GORM rows, and use-cases in one directory.

Layer Path Owns
entity {biz}/entity Domain types, message payloads. No I/O.
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.

pkg vs infrastructure

Do not move infrastructure into pkg (or a public repo-root pkg/).

Path Owns May import
internal/pkg errs, i18n, validate, reqctx, reply, in-memory ratelimit stdlib + other pkg only
internal/infrastructure MySQL pool, Redis client, Snowflake id, zap logger, push, metrics pkg + vendor drivers

pkg has no connection lifecycle and must not know DSN, Redis AUTH, or snowflake_worker_id. infrastructure is opened/closed by bootstrap. Direction: infrastructure → pkg, never the reverse.

IDs

Not every PK is Snowflake. Do not AUTO_INCREMENT Snowflake business ids (users.id, messages.id, conversations.id).

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 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.

Send path (must keep this order)

  1. TaskPool (not EventLoop)
  2. Permission (friend / block / group mute) — fail before INSERT
  3. Snowflake MsgId (or client MsgId within ±5m)
  4. MySQL txn: messages + conversations.last_msg_id
  5. DeliverQueue.Push (reliable list)
  6. SEND_ACK

If step 5 fails, do not ACK. Client retries the same client_msg_id.

EventLoop vs pools

Pool Owns
gnet EventLoop PROXY, TLS, Frame, heartbeat, PUSH_ACK, Submit
TaskPool AUTH, SEND, SYNC, READ, RECALL, MySQL
Deliver Worker ZSET inbox, Session.Send, mailbox on push fail, PushNotifier

Commands

AUTH HEARTBEAT GENERATE_MSG_ID SEND_MESSAGE SEND_ACK MESSAGE PUSH_ACK READ RECALL SYNC_INBOX SYNC_MESSAGES KICK

Media: HTTP upload credential, message content is object key only. Frame max 64KB. Compress body over 512B. Message types: text / image / voice / video / emoji / gif / file / location / system (message/entity.Type 1–9). messages.content stores serialized JSON bytes (BLOB), never a raw string. Closed type/status values: enums.md.

TCP body is JSON envelope {code,msg,request_id,data}. AUTH request is {"token","device_id","app_version","language"} (not protobuf). Public TLS is Nginx :443 SNI: HTTP cert on loopback :8443 (sparkapi + sparkr/RustFS), IM cert on loopback :19001 then PROXY to gnet :9090 (host :9000 is rustfs). See deploy/nginx/. AUTH/login rate-limit uses reqctx.RealIP. TaskPool workers recover panics. Deliver workers start with RequeueStale + SETNX deliver:done:{msgId}.

When adding a feature

  • HTTP writes must enqueue deliver jobs for online devices.
  • List/unread go to Redis ZSET, not MySQL row-per-member.
  • New goroutine I/O does not belong in OnTraffic.
  • 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).
  • 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/GORM: mysql.md (COMMENT, t_*, migrate).

Verify before done

Follow .cursor/rules/verify-before-done.mdc: from repo root, make test (depends on check-dev, then lint) must exit 0; logic packages need ≥65% coverage (LOGIC_COVER_MIN). No Go toolchain → make test errors; install Go 1.26+ and golangci-lint (make setup). make deploy depends on make test.

Additional resources