新增 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>
6.3 KiB
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 $refs 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.
Physical MySQL tables are t_*; catalog column names below use logical names (users.gender → table t_users).
Template (message/entity.Type)
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=...":
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 uint8 min=1,max=6 |
device_type / platform |
meta device_type 1–6 |
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 moment_image moment_video moment_cover |
MomentKind |
moment/entity.Kind |
kind |
1纯文 2图 3视频 |
MomentVisibility |
moment/entity.Visibility |
visibility |
0好友 1私密 2白名单 3黑名单 |
MomentStatus |
moment/entity.Status |
0正常 1已删 | |
MomentNoticeKind |
moment/entity.NoticeKind |
kind |
1赞 2评论 3回复 |
MomentVisibleDays |
moment/entity.VisibleDays |
visible_days |
0 / 3 / 30 / 180 |
SystemKind |
friend/entity.SystemKind* / user/entity.SystemKind* / group/entity.SystemKind* |
content.system.kind |
friend.became system.welcome 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
- Put
type X int(or keep a string const set) in the owning moduleentity. AddValid(). - Use
Xon entity + JSON DTOs. Convert at the GORM boundary. - Bind request fields with
oneof=listing the same values; add i18nerrmsg. - Add a named schema in
internal/transport/http/docs/openapi.yamland$refevery field. - Add the set to
GET /v1/meta/enums(meta/logic) and i18n labelsenum.{set}.{name}(zh-CN / en / zh-TW). - Update this catalog.
Do not: change existing numbers; stringify a numeric set; duplicate enum lists in handlers.