spark-server/.cursor/skills/spark-im-v3/enums.md
2026-09-18 14:50:41 +08:00

96 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Enums: typed closed sets for JSON, TCP, and SQL
Closed type/status values live in `internal/module/{biz}/entity` as named types. OpenAPI names the same set once and `$ref`s it. Do not invent a parallel string vocabulary.
`GET /v1/meta/enums` is the client bootstrap (no auth). Human pages: `/docs/enums` (jumpable sets) and `/docs/errors` (`envelope_code`). `value` is the wire number or string; `name` is a stable English token; `label` is i18n for the request language. Values are built from entity consts — do not hand-copy numbers into the handler.
Wire JSON and TCP stay **numbers** (or the existing strings below). Do not turn message types into strings. Do not change persisted numeric values.
## Template (`message/entity.Type`)
```go
type Type int16
const (
TypeText Type = 1
// ...
TypeSystem Type = 9
)
func (t Type) Valid() bool {
return t >= TypeText && t <= TypeSystem
}
```
Input DTOs that bind the field directly use the same numbers in `validate:"oneof=..."`:
```go
Gender *entity.Gender `json:"gender" validate:"omitempty,oneof=0 1 2" label:"field.gender" errmsg:"oneof:validate.user.gender_invalid"`
```
Nested JSON (e.g. `content.t`) is not a validator field; gate it with `Valid()` inside `UnmarshalContent` / `Content.Validate`.
## Layers
| Layer | Rule |
|-------|------|
| entity | `type X int` / `int16` / `string` + const + `Valid()`. Domain structs use the typed field. |
| model | GORM rows stay `int` / `int16` / `string`. Convert at `entity()` / `rowFrom`. Never store a Go typed enum in a GORM struct if the column is numeric. |
| logic JSON | Response/request DTOs use the entity type so encoding/json emits the number (or the string). |
| HTTP validator | `oneof` lists the **same** numbers or strings as the consts. i18n `errmsg` for `oneof`. |
| OpenAPI | One named schema in `components/schemas`; fields `$ref` it. Do not inline `enum:` on the field. |
| HTTP catalog | Add the set to `meta/logic.Catalog` and i18n `enum.{set}.{name}`. Served by `GET /v1/meta/enums`. |
Name clashes: the JSON viewer `relation` is `RelationKind`; the table column is `EdgeStatus`. Do not name both `Status` in the same package if two tables differ.
## Numeric catalog
Values are stable. Do not reuse a retired number.
| OpenAPI schema | Go | JSON / TCP | SQL | Values |
|----------------|----|------------|-----|--------|
| `Gender` | `user/entity.Gender` | `gender` | `users.gender` | 0未知 1男 2女 |
| `UserStatus` | `user/entity.Status` | `status` | `users.status` | 0停用 1正常 |
| `ConversationType` | `conversation/entity.Type` | (internal / conv type) | `conversations.type` | 1单聊 2群聊 |
| `MessageKind` | `message/entity.Kind` | `kind` | not a column; selects `private_message` vs `group_message` | 1 / 2, **same numbers as ConversationType** |
| `MessageType` | `message/entity.Type` | `message_type`, `content.t` | `*_message.message_type` | 1文本 2图片 3语音 4视频 5表情 6GIF 7文件 8位置 9系统 |
| `MessageStatus` | `message/entity.Status` | `status` | `*_message.status` | 1正常 2已撤回 3审核隐藏 |
| `RelationKind` | `friend/entity.RelationKind` | `relation` | not a column (viewer-centric) | 0无 1好友 2已拉黑 |
| — | `friend/entity.EdgeStatus` | not on the wire | `friend_relations.status` | 0非好友 1好友 |
| `FriendRequestStatus` | `friend/entity.RequestStatus` | apply `status` | `friend_requests.status` | 1待处理 2已同意 3已拒绝 |
| `FriendSource` | `friend/entity.Source` | `source` | request + edge `source` | 0未知 1二维码 2邮箱 3spark_id |
| `ReportReason` | `friend/entity.ReportReason` | `reason` | `friend_reports.reason` | 1垃圾 2骚扰 3不实身份 4其他 |
| `GroupStatus` | `group/entity.Status` | (internal) | `groups.status` | 1正常 2已解散 |
| `GroupRole` | `group/entity.Role` | `role` | `group_members.role` | 1群主 2管理员 3成员。PATCH 只能 2 或 3 |
| `MailboxEventType` | `sync/entity.EventType` | mailbox `event_type` | `mailbox_events.event_type` | 1漏推 2撤回 3删除 |
| `TcpCommand` | `tcp/protocol.Cmd*` | Frame.Command | — | 1–15 see protocol |
| `EnvelopeCode` | `pkg/errs` codes | envelope `code` | — | `"0"` + `40001`… |
`POST /v1/friends/requests` returns `FriendApplyData.status` as **1 or 2**, not `"pending"` / `"accepted"`.
Compare `MessageKind` with `conversation.entity.Type`, never with `message.entity.Type`.
## String catalog (keep as strings)
Do not convert these to integers.
| OpenAPI schema | Go | JSON | Values |
|----------------|----|------|--------|
| `DeviceType` | login DTO `string` + `oneof` | `device_type` / `platform` | `ios android harmony windows macos web` |
| `Language` | `i18n.Lang` | `language`, `Accept-Language`, `X-Language` | `zh-CN` `en` `zh-TW` (default `zh-CN`) |
| `UploadPurpose` | `upload/entity.Purpose*` consts + `LookupSpec` | `purpose` | `avatar group_avatar chat_image chat_voice chat_video chat_gif chat_file` |
| `SystemKind` | `friend/entity.SystemKind*` / `group/entity.SystemKind*` | `content.system.kind` | `friend.became` `group.created` `group.invite` `group.leave` `group.kick` `group.dissolve` `group.transfer` |
Health `status` (`ok` / `not_ready`) is process liveness, not a domain enum.
## Adding a closed set
1. Put `type X int` (or keep a string const set) in the owning module `entity`. Add `Valid()`.
2. Use `X` on entity + JSON DTOs. Convert at the GORM boundary.
3. Bind request fields with `oneof=` listing the same values; add i18n `errmsg`.
4. Add a named schema in `internal/transport/http/docs/openapi.yaml` and `$ref` every field.
5. Add the set to `GET /v1/meta/enums` (`meta/logic`) and i18n labels `enum.{set}.{name}` (zh-CN / en / zh-TW).
6. Update this catalog.
Do **not**: change existing numbers; stringify a numeric set; duplicate enum lists in handlers.