add: some user api and all api desc
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
ChatSession {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"` // order, consultation
|
||||
OrderId string `json:"orderId,optional"`
|
||||
Participants []SimpleUser `json:"participants"`
|
||||
LastMessage string `json:"lastMessage"`
|
||||
UnreadCount int `json:"unreadCount"`
|
||||
}
|
||||
|
||||
ChatSessionListResp {
|
||||
Items []ChatSession `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
ChatMessage {
|
||||
Id string `json:"id"`
|
||||
SessionId string `json:"sessionId"`
|
||||
SenderId string `json:"senderId"`
|
||||
Type string `json:"type"` // text, image, system
|
||||
Content string `json:"content"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
ChatMessageListResp {
|
||||
Items []ChatMessage `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
SendMessageReq {
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1/chat
|
||||
group: chat
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取会话列表"
|
||||
@handler ListSessions
|
||||
get /sessions (PageReq) returns (ChatSessionListResp)
|
||||
|
||||
@doc "获取会话详情"
|
||||
@handler GetSession
|
||||
get /sessions/:id (EmptyResp) returns (ChatSession)
|
||||
|
||||
@doc "获取消息历史"
|
||||
@handler ListMessages
|
||||
get /sessions/:id/messages (PageReq) returns (ChatMessageListResp)
|
||||
|
||||
@doc "发送消息"
|
||||
@handler SendMessage
|
||||
post /sessions/:id/messages (SendMessageReq) returns (ChatMessage)
|
||||
|
||||
@doc "创建/获取订单会话"
|
||||
@handler EnsureOrderSession
|
||||
post /sessions/order (EmptyResp) returns (ChatSession)
|
||||
|
||||
@doc "创建咨询会话"
|
||||
@handler CreateConsultation
|
||||
post /sessions/consultation (EmptyResp) returns (ChatSession)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
syntax = "v1"
|
||||
|
||||
info(
|
||||
title: "公共定义"
|
||||
desc: "Common structures"
|
||||
author: "Juwan Team"
|
||||
version: "1.0"
|
||||
)
|
||||
|
||||
type (
|
||||
// 分页请求基础
|
||||
PageReq {
|
||||
Offset int64 `form:"offset,default=0"`
|
||||
Limit int64 `form:"limit,default=20"`
|
||||
}
|
||||
|
||||
// 分页响应元数据
|
||||
PageMeta {
|
||||
Total int64 `json:"total"`
|
||||
Offset int64 `json:"offset"`
|
||||
Limit int64 `json:"limit"`
|
||||
}
|
||||
|
||||
// 空响应
|
||||
EmptyResp {}
|
||||
|
||||
// 核心用户画像 (被 Auth, Community, Shop 等多个服务引用)
|
||||
UserProfile {
|
||||
Id string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Role string `json:"role"` // consumer, player, owner, admin
|
||||
VerifiedRoles []string `json:"verifiedRoles"`
|
||||
VerificationStatus map[string]string `json:"verificationStatus"`
|
||||
Phone string `json:"phone,optional"`
|
||||
Bio string `json:"bio,optional"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
// 简略用户信息 (用于列表、聊天头像等)
|
||||
SimpleUser {
|
||||
Id string `json:"id"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,115 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
Post {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Images []string `json:"images"`
|
||||
Tags []string `json:"tags"`
|
||||
LikeCount int64 `json:"likeCount"`
|
||||
CommentCount int64 `json:"commentCount"`
|
||||
Liked bool `json:"liked"`
|
||||
Author UserProfile `json:"author"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
CreatePostReq {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Images []string `json:"images"`
|
||||
Tags []string `json:"tags"`
|
||||
LinkedOrderId string `json:"linkedOrderId,optional"`
|
||||
}
|
||||
|
||||
PostListReq {
|
||||
PageReq
|
||||
Tags string `form:"tags,optional"`
|
||||
SortBy string `form:"sortBy,optional"`
|
||||
}
|
||||
|
||||
PostListResp {
|
||||
Items []Post `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
Comment {
|
||||
Id string `json:"id"`
|
||||
Content string `json:"content"`
|
||||
Author UserProfile `json:"author"`
|
||||
LikeCount int64 `json:"likeCount"`
|
||||
Liked bool `json:"liked"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
CommentListResp {
|
||||
Items []Comment `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
CreateCommentReq {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: community
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取帖子列表"
|
||||
@handler ListPosts
|
||||
get /posts (PostListReq) returns (PostListResp)
|
||||
|
||||
@doc "获取帖子详情"
|
||||
@handler GetPost
|
||||
get /posts/:id (EmptyResp) returns (Post)
|
||||
|
||||
@doc "获取帖子评论"
|
||||
@handler ListComments
|
||||
get /posts/:id/comments (PageReq) returns (CommentListResp)
|
||||
|
||||
@doc "获取用户帖子"
|
||||
@handler ListUserPosts
|
||||
get /users/:id/posts (PageReq) returns (PostListResp)
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: community
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "发布帖子"
|
||||
@handler CreatePost
|
||||
post /posts (CreatePostReq) returns (Post)
|
||||
|
||||
@doc "点赞帖子"
|
||||
@handler LikePost
|
||||
post /posts/:id/like (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "取消点赞帖子"
|
||||
@handler UnlikePost
|
||||
delete /posts/:id/like (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "置顶帖子"
|
||||
@handler PinPost
|
||||
post /posts/:id/pin (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "取消置顶"
|
||||
@handler UnpinPost
|
||||
delete /posts/:id/pin (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "发表评论"
|
||||
@handler CreateComment
|
||||
post /posts/:id/comments (CreateCommentReq) returns (Comment)
|
||||
|
||||
@doc "点赞评论"
|
||||
@handler LikeComment
|
||||
post /comments/:id/like (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "取消点赞评论"
|
||||
@handler UnlikeComment
|
||||
delete /comments/:id/like (EmptyResp) returns (EmptyResp)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
Dispute {
|
||||
Id string `json:"id"`
|
||||
OrderId string `json:"orderId"`
|
||||
Reason string `json:"reason"`
|
||||
Status string `json:"status"`
|
||||
Evidence []string `json:"evidence"`
|
||||
Result string `json:"result,optional"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
DisputeListResp {
|
||||
Items []Dispute `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
CreateDisputeReq {
|
||||
Reason string `json:"reason"`
|
||||
Evidence []string `json:"evidence"`
|
||||
}
|
||||
|
||||
DisputeResponseReq {
|
||||
Reason string `json:"reason"`
|
||||
Evidence []string `json:"evidence"`
|
||||
}
|
||||
|
||||
AppealReq {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: dispute
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取争议列表"
|
||||
@handler ListDisputes
|
||||
get /disputes (PageReq) returns (DisputeListResp)
|
||||
|
||||
@doc "获取订单争议"
|
||||
@handler GetOrderDispute
|
||||
get /orders/:id/dispute (EmptyResp) returns (Dispute)
|
||||
|
||||
@doc "发起争议"
|
||||
@handler CreateDispute
|
||||
post /orders/:id/dispute (CreateDisputeReq) returns (EmptyResp)
|
||||
|
||||
@doc "回应争议"
|
||||
@handler RespondDispute
|
||||
post /disputes/:id/response (DisputeResponseReq) returns (EmptyResp)
|
||||
|
||||
@doc "申诉"
|
||||
@handler AppealDispute
|
||||
post /disputes/:id/appeal (AppealReq) returns (EmptyResp)
|
||||
}
|
||||
+3
-6
@@ -7,7 +7,7 @@ info (
|
||||
)
|
||||
|
||||
type (
|
||||
EmptyResp {}
|
||||
EmptyResp {}
|
||||
ForgotPasswordReq {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
@@ -24,7 +24,7 @@ type (
|
||||
|
||||
@server (
|
||||
group: email
|
||||
prefix: /api/email
|
||||
prefix: /api/v1/email
|
||||
middleware: Logger
|
||||
)
|
||||
service email-api {
|
||||
@@ -34,14 +34,11 @@ service email-api {
|
||||
)
|
||||
@handler SendVerificationCode
|
||||
post /verification-code/send (SendVerificationCodeReq) returns (SendVerificationCodeResp)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@server (
|
||||
group: user
|
||||
group: email
|
||||
prefix: /api/v1/auth
|
||||
|
||||
)
|
||||
service email-api {
|
||||
@doc "忘记密码-发送验证码"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
Game {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
GameListResp {
|
||||
Items []Game `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1/games
|
||||
group: game
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取游戏列表"
|
||||
@handler ListGames
|
||||
get / (PageReq) returns (GameListResp)
|
||||
|
||||
@doc "获取游戏详情"
|
||||
@handler GetGame
|
||||
get /:id (EmptyResp) returns (Game)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
Notification {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Read bool `json:"read"`
|
||||
Link string `json:"link,optional"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
NotificationListResp {
|
||||
Items []Notification `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: notification
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取通知列表"
|
||||
@handler ListNotifications
|
||||
get /notifications (PageReq) returns (NotificationListResp)
|
||||
|
||||
@doc "标记已读"
|
||||
@handler ReadNotification
|
||||
put /notifications/:id/read (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "全部已读"
|
||||
@handler ReadAllNotifications
|
||||
put /notifications/read-all (EmptyResp) returns (EmptyResp)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "文件服务"
|
||||
desc: "处理文件上传与获取"
|
||||
author: "Asadz"
|
||||
version: "v1"
|
||||
)
|
||||
|
||||
type (
|
||||
// 上传请求参数(File文件流在Handler中通过 r.FormFile 获取)
|
||||
UploadReq {
|
||||
Type string `form:"type,options=avatar|chat|post|verification|dispute"` // 文件类型限制
|
||||
}
|
||||
// 上传响应
|
||||
UploadResp {
|
||||
Url string `json:"url"` // 返回 CDN 地址或访问地址
|
||||
}
|
||||
// 获取文件请求(用于私有文件或代理访问)
|
||||
GetFileReq {
|
||||
FileId string `path:"fileId"`
|
||||
}
|
||||
)
|
||||
|
||||
@server (
|
||||
prefix: /api/v1
|
||||
group: file
|
||||
jwt: Logger
|
||||
middleware: FileSizeLimit // 建议添加中间件限制文件大小
|
||||
)
|
||||
service file-api {
|
||||
@doc "文件上传接口"
|
||||
@handler Upload
|
||||
post /upload (UploadReq) returns (UploadResp)
|
||||
|
||||
@doc "文件获取接口 (如果是私有文件,通过此接口获取或重定向)"
|
||||
@handler GetFile
|
||||
get /files/:fileId (GetFileReq)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
import "player.api" // 为了使用 PlayerService 定义
|
||||
|
||||
type (
|
||||
Order {
|
||||
Id string `json:"id"`
|
||||
ConsumerId string `json:"consumerId"`
|
||||
ConsumerName string `json:"consumerName"`
|
||||
PlayerId string `json:"playerId"`
|
||||
PlayerName string `json:"playerName"`
|
||||
ShopId string `json:"shopId,optional"`
|
||||
ShopName string `json:"shopName,optional"`
|
||||
Service PlayerService `json:"service"`
|
||||
Status string `json:"status"`
|
||||
TotalPrice float64 `json:"totalPrice"`
|
||||
Note string `json:"note,optional"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
AcceptedAt string `json:"acceptedAt,optional"`
|
||||
CompletedAt string `json:"completedAt,optional"`
|
||||
}
|
||||
|
||||
OrderListReq {
|
||||
PageReq
|
||||
Role string `form:"role"` // consumer, player, owner
|
||||
Status string `form:"status,optional"`
|
||||
}
|
||||
|
||||
OrderListResp {
|
||||
Items []Order `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
CreateOrderReq {
|
||||
PlayerId string `json:"playerId"`
|
||||
ShopId string `json:"shopId,optional"`
|
||||
ServiceId string `json:"serviceId"`
|
||||
Quantity int `json:"quantity"`
|
||||
Note string `json:"note,optional"`
|
||||
}
|
||||
|
||||
CreateOrderResp {
|
||||
Ok bool `json:"ok"`
|
||||
Order Order `json:"order"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1/orders
|
||||
group: order
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取订单列表"
|
||||
@handler ListOrders
|
||||
get / (OrderListReq) returns (OrderListResp)
|
||||
|
||||
@doc "获取订单详情"
|
||||
@handler GetOrder
|
||||
get /:id (EmptyResp) returns (Order)
|
||||
|
||||
@doc "创建订单"
|
||||
@handler CreateOrder
|
||||
post / (CreateOrderReq) returns (CreateOrderResp)
|
||||
|
||||
@doc "创建并支付订单"
|
||||
@handler CreateAndPayOrder
|
||||
post /paid (CreateOrderReq) returns (CreateOrderResp)
|
||||
|
||||
@doc "支付订单"
|
||||
@handler PayOrder
|
||||
post /:id/pay (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "接单"
|
||||
@handler AcceptOrder
|
||||
post /:id/accept (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "申请结算"
|
||||
@handler RequestCloseOrder
|
||||
post /:id/request-close (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "确认结算"
|
||||
@handler ConfirmCloseOrder
|
||||
post /:id/confirm-close (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "取消订单"
|
||||
@handler CancelOrder
|
||||
post /:id/cancel (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "再来一单"
|
||||
@handler Reorder
|
||||
post /:id/reorder (EmptyResp) returns (CreateOrderResp)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
PlayerService {
|
||||
Id string `json:"id"`
|
||||
PlayerId string `json:"playerId"`
|
||||
GameId string `json:"gameId"`
|
||||
GameName string `json:"gameName"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Price float64 `json:"price"`
|
||||
Unit string `json:"unit"`
|
||||
RankRange string `json:"rankRange,optional"`
|
||||
Availability []string `json:"availability"`
|
||||
}
|
||||
|
||||
PlayerServiceListResp {
|
||||
Items []PlayerService `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
CreateServiceReq {
|
||||
GameId string `json:"gameId"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,optional"`
|
||||
Price float64 `json:"price"`
|
||||
Unit string `json:"unit"`
|
||||
RankRange string `json:"rankRange,optional"`
|
||||
Availability []string `json:"availability,optional"`
|
||||
}
|
||||
|
||||
PlayerProfile {
|
||||
Id string `json:"id"`
|
||||
User UserProfile `json:"user"`
|
||||
Rating float64 `json:"rating"`
|
||||
TotalOrders int64 `json:"totalOrders"`
|
||||
CompletionRate float64 `json:"completionRate"`
|
||||
Status string `json:"status"`
|
||||
Games []string `json:"games"`
|
||||
Services []PlayerService `json:"services"`
|
||||
ShopId string `json:"shopId,optional"`
|
||||
ShopName string `json:"shopName,optional"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
PlayerListReq {
|
||||
PageReq
|
||||
GameId string `form:"gameId,optional"`
|
||||
Gender int `form:"gender,optional"`
|
||||
}
|
||||
|
||||
PlayerListResp {
|
||||
Items []PlayerProfile `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
UpdatePlayerStatusReq {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: player
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取打手列表"
|
||||
@handler ListPlayers
|
||||
get /players (PlayerListReq) returns (PlayerListResp)
|
||||
|
||||
@doc "获取打手详情"
|
||||
@handler GetPlayer
|
||||
get /players/:id (EmptyResp) returns (PlayerProfile)
|
||||
|
||||
@doc "获取所有服务列表"
|
||||
@handler ListServices
|
||||
get /services (PageReq) returns (PlayerServiceListResp)
|
||||
|
||||
@doc "获取服务详情"
|
||||
@handler GetService
|
||||
get /services/:id (EmptyResp) returns (PlayerService)
|
||||
|
||||
@doc "获取指定打手的服务列表"
|
||||
@handler ListPlayerServices
|
||||
get /players/:id/services (PageReq) returns (PlayerServiceListResp)
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: player
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "更新接单状态"
|
||||
@handler UpdatePlayerStatus
|
||||
put /players/me/status (UpdatePlayerStatusReq) returns (EmptyResp)
|
||||
|
||||
@doc "创建服务"
|
||||
@handler CreateService
|
||||
post /services (CreateServiceReq) returns (PlayerService)
|
||||
|
||||
@doc "更新服务"
|
||||
@handler UpdateService
|
||||
put /services/:id (CreateServiceReq) returns (PlayerService)
|
||||
|
||||
@doc "删除服务"
|
||||
@handler DeleteService
|
||||
delete /services/:id (EmptyResp) returns (EmptyResp)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
Review {
|
||||
Id string `json:"id"`
|
||||
OrderId string `json:"orderId"`
|
||||
FromUserId string `json:"fromUserId"`
|
||||
FromUserName string `json:"fromUserName"`
|
||||
Rating int `json:"rating"`
|
||||
Content string `json:"content"`
|
||||
Sealed bool `json:"sealed"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
ReviewListResp {
|
||||
Items []Review `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
SubmitReviewReq {
|
||||
Rating int `json:"rating"`
|
||||
Content string `json:"content,optional"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: review
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "提交评价"
|
||||
@handler SubmitReview
|
||||
post /orders/:id/review (SubmitReviewReq) returns (EmptyResp)
|
||||
|
||||
@doc "获取订单评价"
|
||||
@handler GetOrderReviews
|
||||
get /orders/:id/reviews (EmptyResp) returns ([]Review)
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: review
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取公开评价列表"
|
||||
@handler ListReviews
|
||||
get /reviews (PageReq) returns (ReviewListResp)
|
||||
|
||||
@doc "获取用户收到的评价"
|
||||
@handler ListUserReviews
|
||||
get /users/:id/reviews (PageReq) returns (ReviewListResp)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
SearchReq {
|
||||
PageReq
|
||||
Q string `form:"q"`
|
||||
MinPrice float64 `form:"min,optional"`
|
||||
MaxPrice float64 `form:"max,optional"`
|
||||
OnlyOnline bool `form:"onlyOnline,optional"`
|
||||
Sort string `form:"sort,optional"`
|
||||
}
|
||||
|
||||
SearchResp {
|
||||
Items []interface{} `json:"items"` // Mixed items
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
FavoriteReq {
|
||||
TargetType string `json:"targetType"` // player, shop
|
||||
TargetId string `json:"targetId"`
|
||||
}
|
||||
|
||||
FavoriteCheckReq {
|
||||
TargetType string `form:"targetType"`
|
||||
TargetId string `form:"targetId"`
|
||||
}
|
||||
|
||||
FavoriteCheckResp {
|
||||
Favorited bool `json:"favorited"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: search
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "统一搜索"
|
||||
@handler Search
|
||||
get /search (SearchReq) returns (SearchResp)
|
||||
|
||||
@doc "首页推荐"
|
||||
@handler Recommendations
|
||||
get /recommendations/home (PageReq) returns (SearchResp)
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: favorites
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取收藏列表"
|
||||
@handler ListFavorites
|
||||
get /favorites (PageReq) returns (SearchResp)
|
||||
|
||||
@doc "添加收藏"
|
||||
@handler AddFavorite
|
||||
post /favorites (FavoriteReq) returns (EmptyResp)
|
||||
|
||||
@doc "取消收藏"
|
||||
@handler RemoveFavorite
|
||||
delete /favorites/:id (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "检查收藏状态"
|
||||
@handler CheckFavorite
|
||||
get /users/:id/favorites/check (FavoriteCheckReq) returns (FavoriteCheckResp)
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
ShopProfile {
|
||||
Id string `json:"id"`
|
||||
Owner UserProfile `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
Banner string `json:"banner,optional"`
|
||||
Description string `json:"description"`
|
||||
Rating float64 `json:"rating"`
|
||||
TotalOrders int64 `json:"totalOrders"`
|
||||
PlayerCount int64 `json:"playerCount"`
|
||||
CommissionType string `json:"commissionType"`
|
||||
CommissionValue float64 `json:"commissionValue"`
|
||||
Announcements []string `json:"announcements"`
|
||||
TemplateConfig interface{} `json:"templateConfig"`
|
||||
}
|
||||
|
||||
ShopListResp {
|
||||
Items []ShopProfile `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
CreateShopReq {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
CommissionType string `json:"commissionType"`
|
||||
CommissionValue float64 `json:"commissionValue"`
|
||||
}
|
||||
|
||||
UpdateShopReq {
|
||||
Name string `json:"name,optional"`
|
||||
Description string `json:"description,optional"`
|
||||
CommissionType string `json:"commissionType,optional"`
|
||||
CommissionValue float64 `json:"commissionValue,optional"`
|
||||
AllowMultiShop bool `json:"allowMultiShop,optional"`
|
||||
AllowIndependentOrders bool `json:"allowIndependentOrders,optional"`
|
||||
DispatchMode string `json:"dispatchMode,optional"`
|
||||
}
|
||||
|
||||
UpdateTemplateReq {
|
||||
Sections interface{} `json:"sections"`
|
||||
}
|
||||
|
||||
AnnouncementReq {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
IncomeStatsResp {
|
||||
MonthlyIncome float64 `json:"monthlyIncome"`
|
||||
PendingSettlement float64 `json:"pendingSettlement"`
|
||||
TotalWithdrawn float64 `json:"totalWithdrawn"`
|
||||
TotalOrders int64 `json:"totalOrders"`
|
||||
CompletedOrders int64 `json:"completedOrders"`
|
||||
}
|
||||
|
||||
InvitationReq {
|
||||
PlayerId string `json:"playerId"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: shop
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取店铺列表"
|
||||
@handler ListShops
|
||||
get /shops (PageReq) returns (ShopListResp)
|
||||
|
||||
@doc "获取店铺详情"
|
||||
@handler GetShop
|
||||
get /shops/:id (EmptyResp) returns (ShopProfile)
|
||||
|
||||
@doc "获取店长的店铺"
|
||||
@handler GetUserShop
|
||||
get /users/:id/shop (EmptyResp) returns (ShopProfile)
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: api/v1
|
||||
group: shop
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "创建店铺"
|
||||
@handler CreateShop
|
||||
post /shops (CreateShopReq) returns (ShopProfile)
|
||||
|
||||
@doc "获取当前用户的店铺"
|
||||
@handler GetMyShop
|
||||
get /shops/mine (EmptyResp) returns (ShopProfile)
|
||||
|
||||
@doc "更新店铺信息"
|
||||
@handler UpdateShop
|
||||
put /shops/:id (UpdateShopReq) returns (ShopProfile)
|
||||
|
||||
@doc "更新店铺模板"
|
||||
@handler UpdateShopTemplate
|
||||
put /shops/:id/template (UpdateTemplateReq) returns (EmptyResp)
|
||||
|
||||
@doc "新增店铺公告"
|
||||
@handler AddAnnouncement
|
||||
post /shops/:id/announcements (AnnouncementReq) returns (EmptyResp)
|
||||
|
||||
@doc "删除店铺公告"
|
||||
@handler DeleteAnnouncement
|
||||
delete /shops/:id/announcements/:index (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "邀请打手"
|
||||
@handler InvitePlayer
|
||||
post /shops/:id/invitations (InvitationReq) returns (EmptyResp)
|
||||
|
||||
@doc "接受邀请"
|
||||
@handler AcceptInvitation
|
||||
post /shops/invitations/:id/accept (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "拒绝邀请"
|
||||
@handler RejectInvitation
|
||||
delete /shops/invitations/:id (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "移除打手"
|
||||
@handler RemovePlayer
|
||||
delete /shops/:id/players/:playerId (EmptyResp) returns (EmptyResp)
|
||||
|
||||
@doc "获取收入统计"
|
||||
@handler GetShopIncomeStats
|
||||
get /shops/:id/income-stats (EmptyResp) returns (IncomeStatsResp)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
syntax = "v1"
|
||||
|
||||
info (
|
||||
title: "聚玩认证审核服务"
|
||||
desc: "处理用户角色认证申请(打手/店长)及管理员审核流程"
|
||||
author: "Asadz"
|
||||
version: "1.0"
|
||||
)
|
||||
|
||||
// =================================================================================
|
||||
// 数据结构定义 (Data Structures)
|
||||
// =================================================================================
|
||||
|
||||
// =================================================================================
|
||||
// 用户端接口 (User Side)
|
||||
// 路径前缀: /api/v1/users
|
||||
// =================================================================================
|
||||
@server (
|
||||
group: verification_user
|
||||
prefix: /api/v1/users
|
||||
jwt: Auth // 必须登录
|
||||
)
|
||||
service verification-api {
|
||||
@doc "提交或修改角色认证申请 (支持幂等更新)"
|
||||
@handler ApplyVerification
|
||||
post /me/verification (ApplyVerificationReq) returns (VerificationEmptyResp)
|
||||
|
||||
@doc "获取我的所有认证状态"
|
||||
@handler GetMyVerifications
|
||||
get /me/verification returns (GetMyVerificationsResp)
|
||||
}
|
||||
|
||||
// =================================================================================
|
||||
// 管理端接口 (Admin Side)
|
||||
// 路径前缀: /api/v1/admin
|
||||
// =================================================================================
|
||||
@server (
|
||||
group: verification_admin
|
||||
prefix: /api/v1/admin
|
||||
jwt: Auth // 需要登录,且 Logic 层需校验 IsAdmin
|
||||
)
|
||||
service verification-api {
|
||||
@doc "管理员获取认证申请列表 (分页)"
|
||||
@handler GetVerifications
|
||||
get /verifications (GetPendingListReq) returns (GetPendingListResp)
|
||||
|
||||
@doc "管理员通过申请"
|
||||
@handler ApproveVerification
|
||||
post /verifications/:id/approve (VerificationIdReq) returns (VerificationEmptyResp)
|
||||
|
||||
@doc "管理员驳回申请"
|
||||
@handler RejectVerification
|
||||
post /verifications/:id/reject (RejectVerificationReq) returns (VerificationEmptyResp)
|
||||
}
|
||||
|
||||
+251
-146
@@ -1,164 +1,269 @@
|
||||
syntax = "v1"
|
||||
|
||||
info(
|
||||
author: "Asadz"
|
||||
date: "2024-06-19"
|
||||
version: "1.0"
|
||||
info (
|
||||
title: "聚玩用户服务"
|
||||
desc: "处理用户注册、登录、个人信息管理及关注系统"
|
||||
author: "Asadz"
|
||||
version: "1.0"
|
||||
)
|
||||
|
||||
type (
|
||||
EmptyResp {
|
||||
}
|
||||
SearchUserResp {
|
||||
Username string `json:"username"`
|
||||
UserId int64 `json:"userId"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Bio string `json:"bio"`
|
||||
Page int64 `json:"page"`
|
||||
Limit int64 `json:"limit"`
|
||||
}
|
||||
ResetPasswordByVcode {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
Vcode string `json:"vcode"`
|
||||
}
|
||||
RegisterReq {
|
||||
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||
Password string `json:"password" binding:"required,min=6,max=128"`
|
||||
Email string `json:"email,omitempty" binding:"omitempty,email"`
|
||||
Phone string `json:"phone,omitempty" binding:"omitempty,len=11"`
|
||||
Vcode int32 `json:"vcode"`
|
||||
}
|
||||
RegisterResp {
|
||||
UserId int64 `json:"userId"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
LoginReq {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
LoginResp {
|
||||
UserId int64 `json:"userId"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Token string `json:"token"`
|
||||
Expires int64 `json:"expires"`
|
||||
}
|
||||
GetUserInfoReq {
|
||||
UserId int64 `path:"userId" binding:"required,gt=0"`
|
||||
}
|
||||
UserInfo {
|
||||
UserId int64 `json:"userId"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Avatar string `json:"avatar"`
|
||||
Status int `json:"status"`
|
||||
CreateAt int64 `json:"createAt"`
|
||||
UpdateAt int64 `json:"updateAt"`
|
||||
}
|
||||
UpdateUserInfoReq {
|
||||
Nickname *string `json:"nickname,omitempty"`
|
||||
Avatar *string `json:"avatar,omitempty"`
|
||||
Bio *string `json:"bio,omitempty"`
|
||||
}
|
||||
UpdateUserInfoResp {
|
||||
UserId int64 `json:"userId"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
UpdatePasswordReq {
|
||||
UserId int64 `path:"userId" binding:"required,gt=0"`
|
||||
OldPassword string `json:"oldPassword" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required,min=6,max=128"`
|
||||
}
|
||||
UpdatePasswordResp {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
LogoutReq {
|
||||
UserId int64 `path:"userId" binding:"required,gt=0"`
|
||||
Token string `header:"Authorization" binding:"required"`
|
||||
}
|
||||
LogoutResp {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
ErrorResp {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
// 认证记录展示对象
|
||||
// 对应数据库 user_verifications 表
|
||||
VerificationItem {
|
||||
Id int64 `json:"id"` // 认证记录ID (主键,用于管理员操作)
|
||||
UserId int64 `json:"userId"` // 申请人ID (外键)
|
||||
UserNickname string `json:"userNickname"` // 冗余显示,方便前端展示
|
||||
Role string `json:"role"` // 申请角色: player, owner
|
||||
Status string `json:"status"` // pending, approved, rejected
|
||||
Materials map[string]string `json:"materials"` // 核心字段:对应 DB 的 JSONB
|
||||
RejectReason string `json:"rejectReason"` // 驳回原因
|
||||
CreatedAt string `json:"createdAt"` // 申请时间
|
||||
ReviewedAt string `json:"reviewedAt"` // 审核时间
|
||||
}
|
||||
// 提交申请请求
|
||||
ApplyVerificationReq {
|
||||
Role string `json:"role"` // 申请什么角色
|
||||
Materials map[string]string `json:"materials"` // 证明材料键值对 {"idCardFront": "http...", "license": "http..."}
|
||||
}
|
||||
// 获取我的申请记录响应
|
||||
GetMyVerificationsResp {
|
||||
List []VerificationItem `json:"list"`
|
||||
}
|
||||
// 管理员:获取待审核列表请求
|
||||
GetPendingListReq {
|
||||
Page int64 `form:"page,default=1"`
|
||||
Size int64 `form:"size,default=20"`
|
||||
Role string `form:"role,optional"` // 筛选角色
|
||||
Status string `form:"status,optional"` // 筛选状态,默认 pending
|
||||
}
|
||||
// 管理员:列表响应
|
||||
GetPendingListResp {
|
||||
List []VerificationItem `json:"list"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
// 管理员:审核操作请求 (只包含 ID 路径参数)
|
||||
VerificationIdReq {
|
||||
Id int64 `path:"id"` // 注意:这是 user_verifications.id
|
||||
}
|
||||
// 管理员:驳回请求
|
||||
RejectVerificationReq {
|
||||
Id int64 `path:"id"`
|
||||
Reason string `json:"reason"` // 必填:驳回原因
|
||||
}
|
||||
// 通用空响应
|
||||
VerificationEmptyResp {}
|
||||
)
|
||||
|
||||
@server(
|
||||
group: user
|
||||
prefix: /api/v1/auth
|
||||
middleware: Logger
|
||||
// =================================================================================
|
||||
// 基础数据模型 (Data Models)
|
||||
// =================================================================================
|
||||
type (
|
||||
// 通用空响应
|
||||
EmptyResp {}
|
||||
// 用户信息核心模型 (User)
|
||||
User {
|
||||
Id int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avatar string `json:"avatar"`
|
||||
Role string `json:"role"` // consumer, player, owner, admin
|
||||
VerifiedRoles []string `json:"verifiedRoles"` // e.g. ["consumer", "player"]
|
||||
VerificationStatus map[string]string `json:"verificationStatus"` // e.g. {"player": "approved"}
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Bio string `json:"bio,omitempty"`
|
||||
CreatedAt string `json:"createdAt"` // ISO 8601
|
||||
}
|
||||
)
|
||||
|
||||
// =================================================================================
|
||||
// 认证相关 (Auth)
|
||||
// =================================================================================
|
||||
type (
|
||||
RegisterReq {
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Vcode string `json:"vcode,omitempty"` // 验证码
|
||||
}
|
||||
RegisterResp {
|
||||
AccessToken string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
User User `json:"user"`
|
||||
}
|
||||
LoginReq {
|
||||
Phone string `json:"phone,omitempty"` // 手机号登录
|
||||
Username string `json:"username,omitempty"` // 或用户名登录
|
||||
Password string `json:"password"`
|
||||
Remember bool `json:"remember,optional"`
|
||||
}
|
||||
LoginResp {
|
||||
AccessToken string `json:"accessToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
User User `json:"user"`
|
||||
}
|
||||
LogoutReq {}
|
||||
ForgotPasswordReq {
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
ResetPasswordReq {
|
||||
Phone string `json:"phone,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Vcode string `json:"vcode"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
)
|
||||
|
||||
// =================================================================================
|
||||
// 用户个人中心 (User Me)
|
||||
// =================================================================================
|
||||
type (
|
||||
UpdateUserProfileReq {
|
||||
Nickname string `json:"nickname,optional"`
|
||||
Avatar string `json:"avatar,optional"`
|
||||
Bio string `json:"bio,optional"`
|
||||
}
|
||||
SwitchRoleReq {
|
||||
Role string `json:"role"` // 目标角色
|
||||
}
|
||||
UpdateNotifySettingsReq {
|
||||
Order bool `json:"order,optional"`
|
||||
Community bool `json:"community,optional"`
|
||||
System bool `json:"system,optional"`
|
||||
}
|
||||
UpdateThemeSettingsReq {
|
||||
Theme string `json:"theme"` // dark, light
|
||||
}
|
||||
)
|
||||
|
||||
// =================================================================================
|
||||
// 公开用户信息与社交 (Public User & Social)
|
||||
// =================================================================================
|
||||
type (
|
||||
GetUserReq {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
FollowUserReq {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
UnfollowUserReq {
|
||||
Id int64 `path:"id"`
|
||||
}
|
||||
)
|
||||
|
||||
// =================================================================================
|
||||
// 服务定义
|
||||
// =================================================================================
|
||||
@server (
|
||||
group: auth
|
||||
prefix: /api/v1/auth
|
||||
)
|
||||
service user-api {
|
||||
@doc(
|
||||
summary: "用户注册接口"
|
||||
description: "通过用户名、密码、邮箱、电话号码注册新用户账户"
|
||||
)
|
||||
@handler Register
|
||||
post /register (RegisterReq) returns (RegisterResp)
|
||||
@doc "用户注册"
|
||||
@handler Register
|
||||
post /register (RegisterReq) returns (RegisterResp)
|
||||
|
||||
@doc(
|
||||
summary: "用户登录接口"
|
||||
description: "使用用户名和密码进行登录,返回访问令牌和用户信息"
|
||||
)
|
||||
@handler Login
|
||||
post /login (LoginReq) returns (LoginResp)
|
||||
@doc "用户登录"
|
||||
@handler Login
|
||||
post /login (LoginReq) returns (LoginResp)
|
||||
|
||||
@doc(
|
||||
summary: "修改用户密码"
|
||||
description: "验证旧密码后修改为新密码,需要提供原密码"
|
||||
)
|
||||
@handler UpdatePassword
|
||||
put /:userId/password (UpdatePasswordReq) returns (UpdatePasswordResp)
|
||||
@doc "忘记密码-发送验证码"
|
||||
@handler ForgotPassword
|
||||
post /forgot-password (ForgotPasswordReq) returns (EmptyResp)
|
||||
|
||||
@doc(
|
||||
summary: "用户登出"
|
||||
description: "需要携带 Authorization 令牌,清除用户会话并使令牌失效"
|
||||
)
|
||||
@handler Logout
|
||||
post /:userId/logout (LogoutReq) returns (LogoutResp)
|
||||
|
||||
@doc "修改密码-使用验证码"
|
||||
@handler UpdatePasswordByVcode
|
||||
put /forgot-password/reset (ResetPasswordByVcode) returns (EmptyResp)
|
||||
@doc "重置密码"
|
||||
@handler ResetPassword
|
||||
post /reset-password (ResetPasswordReq) returns (EmptyResp)
|
||||
}
|
||||
|
||||
@server(
|
||||
group: user
|
||||
prefix: /api/v1/user
|
||||
middleware: Logger
|
||||
@server (
|
||||
group: auth
|
||||
prefix: /api/v1/auth
|
||||
)
|
||||
service user-api {
|
||||
@doc "获取当前登录用户信息"
|
||||
@handler GetMe
|
||||
get /me returns (UserInfo)
|
||||
|
||||
@doc "通过用户名搜索用户"
|
||||
@handler SearchUsersByUsername
|
||||
get /search () returns ([]UserInfo)
|
||||
|
||||
@doc "更改当前登录用户信息"
|
||||
@handler UpdateMe
|
||||
put /me (UpdateUserInfoReq) returns (UserInfo)
|
||||
|
||||
@doc(
|
||||
summary: "获取用户信息"
|
||||
description: "根据用户ID获取用户的详细信息,包含个人资料和账户状态"
|
||||
)
|
||||
@handler GetUserInfo
|
||||
get /:userId (GetUserInfoReq) returns (UserInfo)
|
||||
|
||||
@doc(
|
||||
summary: "修改用户信息"
|
||||
description: "修改用户的邮箱、电话号码、头像等信息"
|
||||
)
|
||||
@handler UpdateUserInfo
|
||||
put /:userId (UpdateUserInfoReq) returns (UpdateUserInfoResp)
|
||||
@doc "退出登录"
|
||||
@handler Logout
|
||||
post /logout (LogoutReq) returns (EmptyResp)
|
||||
}
|
||||
|
||||
@server (
|
||||
group: user
|
||||
prefix: /api/v1/users
|
||||
middleware: Logger
|
||||
)
|
||||
service user-api {
|
||||
@doc "获取当前登录用户信息"
|
||||
@handler GetMe
|
||||
get /me returns (User)
|
||||
|
||||
@doc "更新个人资料"
|
||||
@handler UpdateMe
|
||||
put /me (UpdateUserProfileReq) returns (User)
|
||||
|
||||
@doc "切换当前激活角色"
|
||||
@handler SwitchRole
|
||||
post /me/switch-role (SwitchRoleReq) returns (EmptyResp)
|
||||
|
||||
@doc "更新通知偏好"
|
||||
@handler UpdateNotificationSettings
|
||||
put /me/preferences/notifications (UpdateNotifySettingsReq) returns (EmptyResp)
|
||||
|
||||
@doc "更新主题偏好"
|
||||
@handler UpdateThemeSettings
|
||||
put /me/preferences/theme (UpdateThemeSettingsReq) returns (EmptyResp)
|
||||
|
||||
@doc "关注用户"
|
||||
@handler FollowUser
|
||||
post /:id/follow (FollowUserReq) returns (EmptyResp)
|
||||
|
||||
@doc "取消关注用户"
|
||||
@handler UnfollowUser
|
||||
delete /:id/follow (UnfollowUserReq) returns (EmptyResp)
|
||||
}
|
||||
|
||||
// 公开接口,不需要 JWT
|
||||
@server (
|
||||
group: user
|
||||
prefix: /api/v1/users
|
||||
)
|
||||
service user-api {
|
||||
@doc "获取指定用户信息"
|
||||
@handler GetUserInfo
|
||||
get /:id (GetUserReq) returns (User)
|
||||
}
|
||||
|
||||
@server (
|
||||
group: verification_user
|
||||
prefix: /api/v1/users
|
||||
middleware: Logger // 必须登录
|
||||
)
|
||||
service user-api {
|
||||
@doc "提交或修改角色认证申请 (支持幂等更新)"
|
||||
@handler ApplyVerification
|
||||
post /me/verification (ApplyVerificationReq) returns (VerificationEmptyResp)
|
||||
|
||||
@doc "获取我的所有认证状态"
|
||||
@handler GetMyVerifications
|
||||
get /me/verification returns (GetMyVerificationsResp)
|
||||
}
|
||||
|
||||
@server (
|
||||
group: verification_admin
|
||||
prefix: /api/v1/admin
|
||||
)
|
||||
service user-api {
|
||||
@doc "管理员获取认证申请列表 (分页)"
|
||||
@handler GetVerifications
|
||||
get /verifications (GetPendingListReq) returns (GetPendingListResp)
|
||||
|
||||
@doc "管理员通过申请"
|
||||
@handler ApproveVerification
|
||||
post /verifications/:id/approve (VerificationIdReq) returns (VerificationEmptyResp)
|
||||
|
||||
@doc "管理员驳回申请"
|
||||
@handler RejectVerification
|
||||
post /verifications/:id/reject (RejectVerificationReq) returns (VerificationEmptyResp)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
syntax = "v1"
|
||||
import "common.api"
|
||||
|
||||
type (
|
||||
WalletBalance {
|
||||
Balance float64 `json:"balance"`
|
||||
FrozenBalance float64 `json:"frozenBalance"`
|
||||
}
|
||||
|
||||
Transaction {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Amount float64 `json:"amount"`
|
||||
Description string `json:"description"`
|
||||
OrderId string `json:"orderId,optional"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
}
|
||||
|
||||
TransactionListResp {
|
||||
Items []Transaction `json:"items"`
|
||||
Meta PageMeta `json:"meta"`
|
||||
}
|
||||
|
||||
TopupReq {
|
||||
Amount float64 `json:"amount"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
)
|
||||
|
||||
@server(
|
||||
prefix: api/v1/wallet
|
||||
group: wallet
|
||||
jwt: Auth
|
||||
)
|
||||
service juwan-api {
|
||||
@doc "获取余额"
|
||||
@handler GetBalance
|
||||
get /balance (EmptyResp) returns (WalletBalance)
|
||||
|
||||
@doc "获取流水"
|
||||
@handler ListTransactions
|
||||
get /transactions (PageReq) returns (TransactionListResp)
|
||||
|
||||
@doc "充值"
|
||||
@handler Topup
|
||||
post /topup (TopupReq) returns (EmptyResp)
|
||||
|
||||
@doc "提现"
|
||||
@handler Withdraw
|
||||
post /withdraw (TopupReq) returns (EmptyResp)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package file;
|
||||
option go_package = "./pb";
|
||||
|
||||
// 文件上传的元数据信息
|
||||
message UploadFileMetadataReq {
|
||||
string fileName = 1;
|
||||
int64 fileSize = 2;
|
||||
string fileType = 3; // avatar, chat, etc.
|
||||
string userId = 4;
|
||||
bytes fileData = 5; // 如果文件很小可以直接传,大文件建议API层直接传S3,RPC只传元数据
|
||||
}
|
||||
|
||||
message UploadFileResp {
|
||||
string url = 1;
|
||||
string fileId = 2;
|
||||
}
|
||||
|
||||
message GetFileUrlReq {
|
||||
string fileId = 1;
|
||||
string userId = 2; // 用于鉴权
|
||||
}
|
||||
|
||||
message GetFileUrlResp {
|
||||
string url = 1; // 可能是带签名的临时 URL
|
||||
}
|
||||
|
||||
service FileService {
|
||||
// 简单上传(适合小文件,或保存元数据)
|
||||
rpc Upload(UploadFileMetadataReq) returns (UploadFileResp);
|
||||
// 获取文件访问链接(处理私有文件的鉴权)
|
||||
rpc GetFileUrl(GetFileUrlReq) returns (GetFileUrlResp);
|
||||
}
|
||||
@@ -39,11 +39,11 @@ message AddUserVerificationsResp {
|
||||
|
||||
message UpdateUserVerificationsReq {
|
||||
int64 id = 1; //id
|
||||
int64 userId = 2; //userId
|
||||
string role = 3; //role
|
||||
string status = 4; //status
|
||||
string materials = 5; //materials
|
||||
string rejectReason = 6; //rejectReason
|
||||
optional int64 userId = 2; //userId
|
||||
optional string role = 3; //role
|
||||
optional string status = 4; //status
|
||||
optional string materials = 5; //materials
|
||||
optional string rejectReason = 6; //rejectReason
|
||||
int64 reviewedBy = 7; //reviewedBy
|
||||
int64 reviewedAt = 8; //reviewedAt
|
||||
int64 createdAt = 9; //createdAt
|
||||
|
||||
@@ -155,7 +155,7 @@ message RegisterReq {
|
||||
string username = 1;
|
||||
string passwd = 2;
|
||||
string phone = 3;
|
||||
int32 vcode = 4;
|
||||
string vcode = 4;
|
||||
string email = 5;
|
||||
string requestId = 6;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE comment_likes (
|
||||
comment_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
PRIMARY KEY (comment_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_comment_likes_lookup ON comment_likes(user_id, comment_id);
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE comments (
|
||||
id BIGINT PRIMARY KEY,
|
||||
post_id BIGINT NOT NULL REFERENCES posts(id),
|
||||
author_id BIGINT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
like_count INT DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_comments_post ON comments(post_id, created_at) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_comments_author ON comments(author_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||
|
||||
-- 三元组索引用于评论内容搜索
|
||||
CREATE INDEX idx_comments_content_trgm ON comments USING gin(content gin_trgm_ops)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- 热门评论索引
|
||||
CREATE INDEX idx_comments_post_likes ON comments(post_id, like_count DESC, created_at)
|
||||
WHERE deleted_at IS NULL;
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE post_likes (
|
||||
post_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- 复合主键,防止重复点赞
|
||||
PRIMARY KEY (post_id, user_id)
|
||||
);
|
||||
|
||||
-- [核心索引] 优化 "feed流中判断我是否已赞" (user_id = ? AND post_id IN (...))
|
||||
CREATE INDEX idx_post_likes_lookup ON post_likes(user_id, post_id);
|
||||
|
||||
-- [核心索引] 优化 "我赞过的帖子" 列表
|
||||
CREATE INDEX idx_post_likes_user_timeline ON post_likes(user_id, created_at DESC);
|
||||
@@ -0,0 +1,58 @@
|
||||
CREATE TABLE posts (
|
||||
id BIGINT PRIMARY KEY,
|
||||
author_id BIGINT NOT NULL,
|
||||
author_role VARCHAR(20) NOT NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
images TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
tags TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
linked_order_id BIGINT,
|
||||
quoted_post_id BIGINT REFERENCES posts(id),
|
||||
like_count INT DEFAULT 0,
|
||||
comment_count INT DEFAULT 0,
|
||||
pinned BOOLEAN DEFAULT FALSE,
|
||||
search_text TEXT GENERATED ALWAYS AS (
|
||||
title || ' ' || content
|
||||
) STORED,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_posts_author ON posts(author_id, created_at DESC) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX idx_posts_created ON posts(created_at DESC) WHERE deleted_at IS NULL;
|
||||
|
||||
-- 三元组索引用于帖子内容搜索
|
||||
CREATE INDEX idx_posts_search_trgm ON posts USING gin(search_text gin_trgm_ops)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- 全文搜索索引
|
||||
CREATE INDEX idx_posts_fulltext ON posts USING gin(
|
||||
to_tsvector('simple', title || ' ' || content)
|
||||
) WHERE deleted_at IS NULL;
|
||||
|
||||
-- 标签 GIN 索引
|
||||
CREATE INDEX idx_posts_tags_gin ON posts USING gin(tags) WHERE deleted_at IS NULL;
|
||||
|
||||
-- 复合索引优化热门排序
|
||||
CREATE INDEX idx_posts_hot_score ON posts(
|
||||
(like_count * 2 + comment_count) DESC,
|
||||
created_at DESC
|
||||
) WHERE deleted_at IS NULL;
|
||||
|
||||
-- 置顶+时间索引
|
||||
CREATE INDEX idx_posts_pinned_created ON posts(author_id, pinned DESC, created_at DESC)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- 关联订单索引
|
||||
CREATE INDEX idx_posts_linked_order ON posts(linked_order_id)
|
||||
WHERE linked_order_id IS NOT NULL AND deleted_at IS NULL;
|
||||
|
||||
-- 图片数组索引
|
||||
CREATE INDEX idx_posts_images ON posts USING gin(images) WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE TRIGGER trigger_posts_updated_at
|
||||
BEFORE UPDATE ON posts
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE dispute_timeline (
|
||||
id BIGINT PRIMARY KEY,
|
||||
dispute_id BIGINT NOT NULL REFERENCES disputes(id),
|
||||
event_type VARCHAR(30) NOT NULL,
|
||||
actor_id BIGINT,
|
||||
actor_name VARCHAR(100),
|
||||
details JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_event_type CHECK (event_type IN (
|
||||
'created', 'response', 'reviewing', 'resolved', 'appealed'
|
||||
))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_timeline_dispute ON dispute_timeline(dispute_id, created_at);
|
||||
CREATE INDEX idx_timeline_dispute_created ON dispute_timeline(dispute_id, created_at);
|
||||
CREATE INDEX idx_timeline_details ON dispute_timeline USING gin(details);
|
||||
@@ -0,0 +1,49 @@
|
||||
CREATE TABLE disputes (
|
||||
id BIGINT PRIMARY KEY,
|
||||
order_id BIGINT NOT NULL UNIQUE,
|
||||
initiator_id BIGINT NOT NULL,
|
||||
initiator_name VARCHAR(100) NOT NULL,
|
||||
respondent_id BIGINT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
evidence TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'open',
|
||||
result VARCHAR(30),
|
||||
respondent_reason TEXT,
|
||||
respondent_evidence TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
appeal_reason TEXT,
|
||||
appealed_at TIMESTAMPTZ,
|
||||
resolved_by BIGINT,
|
||||
resolved_at TIMESTAMPTZ,
|
||||
search_text TEXT GENERATED ALWAYS AS (
|
||||
initiator_name || ' ' || reason || ' ' || coalesce(respondent_reason, '') || ' ' || coalesce(appeal_reason, '')
|
||||
) STORED,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_dispute_status CHECK (status IN ('open', 'reviewing', 'resolved', 'appealed')),
|
||||
CONSTRAINT chk_dispute_result CHECK (result IS NULL OR result IN ('full_refund', 'full_payment', 'partial_refund'))
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_disputes_order ON disputes(order_id);
|
||||
CREATE INDEX idx_disputes_status ON disputes(status, created_at DESC);
|
||||
CREATE INDEX idx_disputes_initiator ON disputes(initiator_id);
|
||||
|
||||
-- 三元组索引用于争议内容搜索
|
||||
CREATE INDEX idx_disputes_search_trgm ON disputes USING gin(search_text gin_trgm_ops);
|
||||
|
||||
-- 复合索引优化状态查询
|
||||
CREATE INDEX idx_disputes_status_created ON disputes(status, created_at DESC);
|
||||
|
||||
-- 参与者索引
|
||||
CREATE INDEX idx_disputes_initiator_status ON disputes(initiator_id, status, created_at DESC);
|
||||
CREATE INDEX idx_disputes_respondent_status ON disputes(respondent_id, status, created_at DESC);
|
||||
|
||||
-- 数组索引优化证据查询
|
||||
CREATE INDEX idx_disputes_evidence ON disputes USING gin(evidence);
|
||||
CREATE INDEX idx_disputes_respondent_evidence ON disputes USING gin(respondent_evidence);
|
||||
|
||||
CREATE TRIGGER trigger_disputes_updated_at
|
||||
BEFORE UPDATE ON disputes
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,30 @@
|
||||
CREATE TABLE games (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL UNIQUE,
|
||||
icon TEXT NOT NULL,
|
||||
category VARCHAR(50) NOT NULL,
|
||||
sort_order INT DEFAULT 0,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_games_category ON games(category) WHERE is_active = TRUE;
|
||||
CREATE INDEX idx_games_sort ON games(sort_order, id) WHERE is_active = TRUE;
|
||||
|
||||
-- 三元组索引用于游戏名称模糊搜索
|
||||
CREATE INDEX idx_games_name_trgm ON games USING gin(name gin_trgm_ops) WHERE is_active = TRUE;
|
||||
|
||||
-- 复合索引优化分类+排序查询
|
||||
CREATE INDEX idx_games_category_sort ON games(category, sort_order, id) WHERE is_active = TRUE;
|
||||
|
||||
-- 全文搜索索引
|
||||
CREATE INDEX idx_games_fulltext ON games USING gin(
|
||||
to_tsvector('simple', name || ' ' || category)
|
||||
) WHERE is_active = TRUE;
|
||||
|
||||
CREATE TRIGGER trigger_games_updated_at
|
||||
BEFORE UPDATE ON games
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,49 @@
|
||||
CREATE TABLE player_services (
|
||||
id BIGINT PRIMARY KEY,
|
||||
player_id BIGINT NOT NULL REFERENCES players(id),
|
||||
game_id BIGINT NOT NULL,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
unit VARCHAR(20) NOT NULL,
|
||||
rank_range VARCHAR(100),
|
||||
availability TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
rating DECIMAL(3,2) DEFAULT 5.00,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_price_positive CHECK (price > 0),
|
||||
CONSTRAINT chk_service_rating CHECK (rating >= 0 AND rating <= 5)
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_services_player ON player_services(player_id) WHERE is_active = TRUE;
|
||||
CREATE INDEX idx_services_game ON player_services(game_id) WHERE is_active = TRUE;
|
||||
CREATE INDEX idx_services_price ON player_services(price);
|
||||
|
||||
-- 三元组索引用于服务标题模糊搜索
|
||||
CREATE INDEX idx_services_title_trgm ON player_services USING gin(title gin_trgm_ops)
|
||||
WHERE is_active = TRUE;
|
||||
|
||||
-- 全文搜索索引
|
||||
CREATE INDEX idx_services_fulltext ON player_services USING gin(
|
||||
to_tsvector('simple', title || ' ' || coalesce(description, ''))
|
||||
) WHERE is_active = TRUE;
|
||||
|
||||
-- 复合索引优化价格区间查询
|
||||
CREATE INDEX idx_services_game_price ON player_services(game_id, price, rating DESC)
|
||||
WHERE is_active = TRUE;
|
||||
|
||||
-- 打手+游戏复合索引
|
||||
CREATE INDEX idx_services_player_game ON player_services(player_id, game_id)
|
||||
WHERE is_active = TRUE;
|
||||
|
||||
-- GIN 索引优化时间段查询
|
||||
CREATE INDEX idx_services_availability ON player_services USING gin(availability)
|
||||
WHERE is_active = TRUE;
|
||||
|
||||
CREATE TRIGGER trigger_services_updated_at
|
||||
BEFORE UPDATE ON player_services
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,40 @@
|
||||
CREATE TABLE players
|
||||
(
|
||||
id BIGINT PRIMARY KEY,
|
||||
CREATE TABLE players (
|
||||
id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL UNIQUE,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'offline',
|
||||
rating DECIMAL(3,2) DEFAULT 5.00,
|
||||
total_orders INT DEFAULT 0,
|
||||
completed_orders INT DEFAULT 0,
|
||||
|
||||
-- [注意] 此字段为冗余缓存,通过消息队列与 shop_players 表保持一致
|
||||
shop_id BIGINT,
|
||||
|
||||
tags TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
games BIGINT[] DEFAULT ARRAY[]::BIGINT[],
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_players_user ON players (user_id);
|
||||
CREATE INDEX idx_players_shop ON players (shop_id) WHERE shop_id IS NOT NULL;
|
||||
CREATE INDEX idx_players_status ON players (status);
|
||||
CREATE INDEX idx_players_rating ON players (rating DESC);
|
||||
|
||||
-- 复合索引优化多条件筛选
|
||||
CREATE INDEX idx_players_status_rating ON players (status, rating DESC) WHERE status IN ('available', 'busy');
|
||||
|
||||
-- GIN 索引优化数组查询
|
||||
CREATE INDEX idx_players_games_gin ON players USING gin(games);
|
||||
CREATE INDEX idx_players_tags_gin ON players USING gin(tags);
|
||||
|
||||
-- 店铺+状态复合索引
|
||||
CREATE INDEX idx_players_shop_status ON players (shop_id, status, rating DESC) WHERE shop_id IS NOT NULL;
|
||||
|
||||
-- CREATE TRIGGER trigger_players_updated_at
|
||||
-- BEFORE UPDATE
|
||||
-- ON players
|
||||
-- FOR EACH ROW
|
||||
-- EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,49 @@
|
||||
CREATE TABLE shops (
|
||||
id BIGINT PRIMARY KEY,
|
||||
owner_id BIGINT NOT NULL UNIQUE,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
banner TEXT,
|
||||
description TEXT,
|
||||
rating DECIMAL(3,2) DEFAULT 5.00,
|
||||
total_orders INT DEFAULT 0,
|
||||
player_count INT DEFAULT 0,
|
||||
commission_type VARCHAR(20) NOT NULL DEFAULT 'percentage',
|
||||
commission_value DECIMAL(10,2) NOT NULL,
|
||||
allow_multi_shop BOOLEAN DEFAULT FALSE,
|
||||
allow_independent_orders BOOLEAN DEFAULT TRUE,
|
||||
dispatch_mode VARCHAR(20) NOT NULL DEFAULT 'manual',
|
||||
announcements TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
template_config JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_commission_type CHECK (commission_type IN ('fixed', 'percentage')),
|
||||
CONSTRAINT chk_dispatch_mode CHECK (dispatch_mode IN ('manual', 'auto')),
|
||||
CONSTRAINT chk_rating_range CHECK (rating >= 0 AND rating <= 5)
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_shops_owner ON shops(owner_id);
|
||||
CREATE INDEX idx_shops_rating ON shops(rating DESC);
|
||||
|
||||
-- 三元组索引用于店铺名称模糊搜索
|
||||
CREATE INDEX idx_shops_name_trgm ON shops USING gin(name gin_trgm_ops);
|
||||
|
||||
-- 全文搜索索引
|
||||
CREATE INDEX idx_shops_fulltext ON shops USING gin(
|
||||
to_tsvector('simple', name || ' ' || coalesce(description, ''))
|
||||
);
|
||||
|
||||
-- 复合索引优化排序查询
|
||||
CREATE INDEX idx_shops_rating_orders ON shops(rating DESC, total_orders DESC);
|
||||
|
||||
-- 公告数组索引
|
||||
CREATE INDEX idx_shops_announcements ON shops USING gin(announcements);
|
||||
|
||||
-- JSONB 索引优化模板配置查询
|
||||
CREATE INDEX idx_shops_template ON shops USING gin(template_config);
|
||||
|
||||
CREATE TRIGGER trigger_shops_updated_at
|
||||
BEFORE UPDATE ON shops
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE shop_invitations (
|
||||
id BIGINT PRIMARY KEY,
|
||||
shop_id BIGINT NOT NULL REFERENCES shops(id),
|
||||
player_id BIGINT NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
invited_by BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
responded_at TIMESTAMPTZ,
|
||||
|
||||
CONSTRAINT chk_invitation_status CHECK (status IN ('pending', 'accepted', 'rejected', 'cancelled')),
|
||||
UNIQUE(shop_id, player_id, status) WHERE status = 'pending'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_invitations_shop ON shop_invitations(shop_id);
|
||||
CREATE INDEX idx_invitations_player ON shop_invitations(player_id) WHERE status = 'pending';
|
||||
CREATE INDEX idx_invitations_player_status ON shop_invitations(player_id, status, created_at DESC);
|
||||
CREATE INDEX idx_invitations_shop_status ON shop_invitations(shop_id, status, created_at DESC);
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE shop_players
|
||||
(
|
||||
shop_id BIGINT NOT NULL REFERENCES shops (id),
|
||||
player_id BIGINT NOT NULL,
|
||||
|
||||
-- [新增] 标记是否为主店铺。用于个人主页展示和 players.shop_id 缓存源
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
|
||||
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
left_at TIMESTAMPTZ, -- 软删除,表示已离职
|
||||
|
||||
PRIMARY KEY (shop_id, player_id)
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX idx_shop_players_player ON shop_players (player_id) WHERE left_at IS NULL;
|
||||
CREATE INDEX idx_shop_players_shop_active ON shop_players (shop_id, joined_at DESC) WHERE left_at IS NULL;
|
||||
|
||||
-- [新增] 唯一索引:确保一个打手同一时间只能有一个主店铺
|
||||
CREATE UNIQUE INDEX idx_shop_players_one_primary
|
||||
ON shop_players (player_id) WHERE is_primary = TRUE AND left_at IS NULL;
|
||||
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE order_state_logs (
|
||||
id BIGINT PRIMARY KEY,
|
||||
order_id BIGINT NOT NULL REFERENCES orders(id),
|
||||
from_status VARCHAR(30),
|
||||
to_status VARCHAR(30) NOT NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
actor_id BIGINT NOT NULL,
|
||||
actor_role VARCHAR(20) NOT NULL,
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_state_logs_order ON order_state_logs(order_id, created_at);
|
||||
CREATE INDEX idx_state_logs_order_created ON order_state_logs(order_id, created_at DESC);
|
||||
CREATE INDEX idx_state_logs_actor ON order_state_logs(actor_id, created_at DESC);
|
||||
@@ -0,0 +1,65 @@
|
||||
CREATE TABLE orders (
|
||||
id BIGINT PRIMARY KEY,
|
||||
consumer_id BIGINT NOT NULL,
|
||||
consumer_name VARCHAR(100) NOT NULL,
|
||||
player_id BIGINT NOT NULL,
|
||||
player_name VARCHAR(100) NOT NULL,
|
||||
shop_id BIGINT,
|
||||
shop_name VARCHAR(200),
|
||||
service_snapshot JSONB NOT NULL,
|
||||
status VARCHAR(30) NOT NULL DEFAULT 'pending_payment',
|
||||
total_price DECIMAL(10,2) NOT NULL,
|
||||
note TEXT,
|
||||
version INT NOT NULL DEFAULT 1,
|
||||
timeout_job_id VARCHAR(100),
|
||||
search_text TEXT GENERATED ALWAYS AS (
|
||||
consumer_name || ' ' || player_name || ' ' || coalesce(shop_name, '') || ' ' || coalesce(note, '')
|
||||
) STORED,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
accepted_at TIMESTAMPTZ,
|
||||
closed_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
cancelled_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_order_status CHECK (status IN (
|
||||
'pending_payment', 'pending_accept', 'in_progress',
|
||||
'pending_close', 'pending_review', 'disputed',
|
||||
'completed', 'cancelled'
|
||||
)),
|
||||
CONSTRAINT chk_price_positive CHECK (total_price > 0)
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_orders_consumer ON orders(consumer_id, created_at DESC);
|
||||
CREATE INDEX idx_orders_player ON orders(player_id, created_at DESC);
|
||||
CREATE INDEX idx_orders_shop ON orders(shop_id, created_at DESC) WHERE shop_id IS NOT NULL;
|
||||
CREATE INDEX idx_orders_status ON orders(status, created_at DESC);
|
||||
CREATE INDEX idx_orders_timeout ON orders(timeout_job_id) WHERE timeout_job_id IS NOT NULL;
|
||||
|
||||
-- 三元组索引用于订单搜索
|
||||
CREATE INDEX idx_orders_search_trgm ON orders USING gin(search_text gin_trgm_ops);
|
||||
|
||||
-- 复合索引优化多条件查询
|
||||
CREATE INDEX idx_orders_consumer_status_created ON orders(consumer_id, status, created_at DESC);
|
||||
CREATE INDEX idx_orders_player_status_created ON orders(player_id, status, created_at DESC);
|
||||
CREATE INDEX idx_orders_shop_status_created ON orders(shop_id, status, created_at DESC)
|
||||
WHERE shop_id IS NOT NULL;
|
||||
|
||||
-- 状态+时间复合索引 (用于超时任务扫描)
|
||||
CREATE INDEX idx_orders_status_timeout ON orders(status, created_at)
|
||||
WHERE status IN ('pending_accept', 'pending_close', 'pending_review');
|
||||
|
||||
-- JSONB 索引优化服务快照查询
|
||||
CREATE INDEX idx_orders_service_snapshot ON orders USING gin(service_snapshot);
|
||||
|
||||
-- 价格区间索引
|
||||
CREATE INDEX idx_orders_price ON orders(total_price) WHERE status = 'completed';
|
||||
|
||||
-- 时间范围索引 (用于统计)
|
||||
CREATE INDEX idx_orders_completed_at ON orders(completed_at DESC) WHERE completed_at IS NOT NULL;
|
||||
|
||||
CREATE TRIGGER trigger_orders_updated_at
|
||||
BEFORE UPDATE ON orders
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,37 @@
|
||||
CREATE TABLE reviews (
|
||||
id BIGINT PRIMARY KEY,
|
||||
order_id BIGINT NOT NULL,
|
||||
from_user_id BIGINT NOT NULL,
|
||||
from_user_name VARCHAR(100) NOT NULL,
|
||||
from_user_avatar TEXT,
|
||||
to_user_id BIGINT NOT NULL,
|
||||
rating SMALLINT NOT NULL,
|
||||
content TEXT,
|
||||
sealed BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
unsealed_at TIMESTAMPTZ,
|
||||
|
||||
CONSTRAINT chk_rating_range CHECK (rating >= 1 AND rating <= 5),
|
||||
UNIQUE(order_id, from_user_id)
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_reviews_order ON reviews(order_id);
|
||||
CREATE INDEX idx_reviews_to_user ON reviews(to_user_id, created_at
|
||||
|
||||
CREATE INDEX idx_reviews_to_user ON reviews(to_user_id, created_at DESC) WHERE sealed = FALSE;
|
||||
CREATE INDEX idx_reviews_from_user ON reviews(from_user_id);
|
||||
|
||||
-- 三元组索引用于评价内容搜索
|
||||
CREATE INDEX idx_reviews_content_trgm ON reviews USING gin(content gin_trgm_ops)
|
||||
WHERE sealed = FALSE AND content IS NOT NULL;
|
||||
|
||||
-- 复合索引优化用户评价列表
|
||||
CREATE INDEX idx_reviews_to_user_rating ON reviews(to_user_id, rating DESC, created_at DESC)
|
||||
WHERE sealed = FALSE;
|
||||
|
||||
-- 复合索引优化订单评价查询
|
||||
CREATE INDEX idx_reviews_order_sealed ON reviews(order_id, sealed);
|
||||
|
||||
-- 评分统计索引
|
||||
CREATE INDEX idx_reviews_rating_created ON reviews(rating, created_at DESC) WHERE sealed = FALSE;
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE favorites (
|
||||
id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
target_type VARCHAR(20) NOT NULL,
|
||||
target_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_target_type CHECK (target_type IN ('player', 'shop')),
|
||||
UNIQUE(user_id, target_type, target_id)
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_favorites_user ON favorites(user_id, created_at DESC);
|
||||
CREATE INDEX idx_favorites_target ON favorites(target_type, target_id);
|
||||
|
||||
-- 复合索引优化收藏列表查询
|
||||
CREATE INDEX idx_favorites_user_type_created ON favorites(user_id, target_type, created_at DESC);
|
||||
|
||||
-- 目标反查索引(统计收藏数)
|
||||
CREATE INDEX idx_favorites_target_count ON favorites(target_type, target_id);
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE user_follows (
|
||||
id BIGINT PRIMARY KEY,
|
||||
follower_id BIGINT NOT NULL REFERENCES users(id),
|
||||
followee_id BIGINT NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE(follower_id, followee_id),
|
||||
CONSTRAINT chk_no_self_follow CHECK (follower_id != followee_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_follows_follower ON user_follows(follower_id);
|
||||
CREATE INDEX idx_follows_followee ON user_follows(followee_id);
|
||||
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE user_preferences (
|
||||
user_id BIGINT PRIMARY KEY REFERENCES users(id),
|
||||
notification_order BOOLEAN DEFAULT TRUE,
|
||||
notification_community BOOLEAN DEFAULT TRUE,
|
||||
notification_system BOOLEAN DEFAULT TRUE,
|
||||
theme VARCHAR(20) DEFAULT 'light',
|
||||
language VARCHAR(10) DEFAULT 'zh-CN',
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_theme CHECK (theme IN ('light', 'dark', 'auto'))
|
||||
);
|
||||
|
||||
CREATE TRIGGER trigger_preferences_updated_at
|
||||
BEFORE UPDATE ON user_preferences
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -1,35 +1,52 @@
|
||||
CREATE TABLE user_verifications (
|
||||
id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||
role VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
materials JSONB NOT NULL,
|
||||
reject_reason TEXT,
|
||||
reviewed_by BIGINT,
|
||||
reviewed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CREATE TABLE user_verifications
|
||||
(
|
||||
id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users (id),
|
||||
role VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
materials JSONB NOT NULL,
|
||||
reject_reason TEXT,
|
||||
reviewed_by BIGINT,
|
||||
reviewed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_verification_status CHECK (status IN ('pending', 'approved', 'rejected')),
|
||||
UNIQUE(user_id, role) -- 每个角色只有一条最新的认证记录
|
||||
CONSTRAINT chk_verification_status CHECK (status IN ('pending', 'approved', 'rejected')),
|
||||
UNIQUE (user_id, role) -- 每个角色只有一条最新的认证记录
|
||||
);
|
||||
|
||||
-- Note:Auto update verification_status of users table
|
||||
CREATE OR REPLACE FUNCTION sync_user_verification_status()
|
||||
RETURNS TRIGGER AS $$
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
DECLARE
|
||||
status_json JSONB;
|
||||
status_json JSONB;
|
||||
BEGIN
|
||||
SELECT jsonb_object_agg(role, status)
|
||||
INTO status_json
|
||||
FROM user_verifications
|
||||
WHERE user_id = NEW.user_id;
|
||||
SELECT jsonb_object_agg(role, status)
|
||||
INTO status_json
|
||||
FROM user_verifications
|
||||
WHERE user_id = NEW.user_id;
|
||||
|
||||
UPDATE users SET verification_status = status_json WHERE id = NEW.user_id;
|
||||
RETURN NEW;
|
||||
UPDATE users SET verification_status = status_json WHERE id = NEW.user_id;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trigger_sync_verifications
|
||||
AFTER INSERT OR UPDATE ON user_verifications
|
||||
FOR EACH ROW EXECUTE FUNCTION sync_user_verification_status();
|
||||
AFTER INSERT OR UPDATE
|
||||
ON user_verifications
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION sync_user_verification_status();
|
||||
|
||||
-- | 字段名 | 类型 | 技术含义 | 业务目的与设计思考 |
|
||||
-- | :--- | :--- | :--- | :--- |
|
||||
-- | **`id`** | BIGINT | **主键 (Primary Key)** | **这张认证申请单的唯一编号**。<br>后端代码在处理“审核通过”这个动作时,操作的是这个 ID(例如:`Approve(verificationId)`)。 |
|
||||
-- | **`user_id`** | BIGINT | **外键 (Foreign Key)** | **谁在申请?**<br>关联到 `users` 表。通过这个字段,我们可以查到申请人的昵称、手机号等信息。 |
|
||||
-- | **`role`** | VARCHAR | 普通字段 | **申请什么身份?**<br>枚举值:`player` (打手), `owner` (店长)。<br>因为一个用户可以同时是打手和店长,所以需要区分这条记录是申请哪个身份的。 |
|
||||
-- | **`status`** | VARCHAR | 状态字段 | **当前进度如何?**<br>枚举值:`pending` (待审核), `approved` (已通过), `rejected` (已驳回)。 |
|
||||
-- | **`materials`** | **JSONB** | **非结构化数据** | **提交了什么证明材料?**<br>**精华设计**:这里不使用多张表或多个字段,而是用 JSON 存。因为打手需要传“身份证+段位图”,店长需要传“营业执照”。不同角色的材料结构不同,用 JSONB 最灵活,以后改规则不需要改表结构。 |
|
||||
-- | **`reject_reason`** | TEXT | 文本 | **驳回理由**。<br>只有当 `status` = 'rejected' 时才有值,告诉用户哪里填错了。 |
|
||||
-- | **`reviewed_by`** | BIGINT | 审计字段 | **谁审核的?**<br>记录是哪个管理员(Admin ID)点击了通过或拒绝。用于内部追责和审计。 |
|
||||
-- | **`reviewed_at`** | TIMESTAMPTZ | 时间字段 | **什么时候审核的?**<br>用于统计管理员的工作效率,或者展示给用户“审核耗时”。 |
|
||||
-- | **`created_at`** | TIMESTAMPTZ | 时间字段 | **申请提交时间**。 |
|
||||
-- | **`updated_at`** | TIMESTAMPTZ | 时间字段 | **最后更新时间**。<br>比如用户重新上传了图片,这个时间会变。 |
|
||||
@@ -0,0 +1,33 @@
|
||||
CREATE TABLE wallet_transactions (
|
||||
id BIGINT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
type VARCHAR(20) NOT NULL,
|
||||
amount DECIMAL(12,2) NOT NULL,
|
||||
balance_after DECIMAL(12,2) NOT NULL,
|
||||
description VARCHAR(500) NOT NULL,
|
||||
order_id BIGINT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
search_text TEXT GENERATED ALWAYS AS (
|
||||
type || ' ' || description
|
||||
) STORED,
|
||||
|
||||
CONSTRAINT chk_transaction_type CHECK (type IN ('topup', 'payment', 'income', 'withdrawal', 'refund'))
|
||||
);
|
||||
|
||||
-- 基础索引
|
||||
CREATE INDEX idx_transactions_user ON wallet_transactions(user_id, created_at DESC);
|
||||
CREATE INDEX idx_transactions_order ON wallet_transactions(order_id) WHERE order_id IS NOT NULL;
|
||||
CREATE INDEX idx_transactions_type ON wallet_transactions(user_id, type, created_at DESC);
|
||||
|
||||
-- 三元组索引用于交易描述搜索
|
||||
CREATE INDEX idx_transactions_search_trgm ON wallet_transactions USING gin(search_text gin_trgm_ops);
|
||||
|
||||
-- 复合索引优化用户交易查询
|
||||
CREATE INDEX idx_transactions_user_type_created ON wallet_transactions(user_id, type, created_at DESC);
|
||||
|
||||
-- 时间范围索引 (用于统计)
|
||||
CREATE INDEX idx_transactions_created_amount ON wallet_transactions(created_at DESC, amount);
|
||||
|
||||
-- 订单关联索引
|
||||
CREATE INDEX idx_transactions_order_type ON wallet_transactions(order_id, type)
|
||||
WHERE order_id IS NOT NULL;
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE wallets (
|
||||
user_id BIGINT PRIMARY KEY,
|
||||
balance DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||
frozen_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||
version INT NOT NULL DEFAULT 1, -- 必须使用乐观锁
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT chk_balance_non_negative CHECK (balance >= 0),
|
||||
CONSTRAINT chk_frozen_non_negative CHECK (frozen_balance >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_wallets_updated ON wallets(updated_at DESC);
|
||||
|
||||
CREATE TRIGGER trigger_wallets_updated_at
|
||||
BEFORE UPDATE ON wallets
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
Reference in New Issue
Block a user