小程序一些功能更新

This commit is contained in:
李琦
2026-09-29 10:57:01 +08:00
parent 0cb70a7618
commit 3bb7fa8db3
96 changed files with 13890 additions and 213 deletions

96
api/user/v1/event.go Normal file
View File

@@ -0,0 +1,96 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
// ============================================================================
// 小程序端「年会」公开接口(免登录:/event/info、/event/seat/query)
// 依据:架构 §5.1 E1/E2、§5.3 骨架、§4.4 图④;PRD-02 §4.2.3 隐私边界。
//
// 结果最小化红线:返回体**只含本人**的桌号/桌名/座位号,以及「本人所在桌」的示意
// (各座位是否有人,但**不含任何他人姓名/手机号/全厅名单/统计**)。SeatCell 刻意只有
// seat_no / is_self / filled 三个字段。
// ============================================================================
// SeatCell 仅用于「本人所在桌」示意 —— 只含座位号与是否本人,不含任何他人信息
type SeatCell struct {
SeatNo int `json:"seat_no"`
IsSelf bool `json:"is_self"`
Filled bool `json:"filled"` // 是否有人(不返回是谁)
}
type StageOut struct {
X int `json:"x"`
Y int `json:"y"`
W int `json:"w"`
H int `json:"h"`
Shape string `json:"shape"`
Rotation int `json:"rotation"`
Color string `json:"color"`
}
type EventInfoReq struct {
g.Meta `path:"/event/info" method:"get" tags:"event" summary:"活动公开信息(查座页顶部)"`
EventId int64 `v:"required#活动不存在" json:"event_id"`
}
type EventInfoRes struct {
Title string `json:"title"`
Venue string `json:"venue"`
EventTime string `json:"event_time"`
Status string `json:"status"`
CanQuery bool `json:"can_query"` // status==published
Stage *StageOut `json:"stage"` // 用于定位,不含个人信息
}
// EventSeatQueryReq 姓名 + 手机号双因子精确匹配(都填且都对才返回)
type EventSeatQueryReq struct {
g.Meta `path:"/event/seat/query" method:"post" tags:"event" summary:"扫码查座(免登录)"`
EventId int64 `v:"required#活动不存在" json:"event_id"`
Name string `json:"name"`
Phone string `json:"phone"`
}
// EventSeatQueryRes 结果最小化:只有本人桌座 + 本人所在桌示意。
//
// Found=false 表示未查询到(双因子不匹配/未录入/未分配座位),message 为统一模糊文案,
// 前端据 Found 判定;找不到时所有桌座字段为空,不透出任何他人信息。
type EventSeatQueryRes struct {
Found bool `json:"found"`
Message string `json:"message"`
TableNo string `json:"table_no"`
TableName string `json:"table_name"`
SeatNo int `json:"seat_no"`
Capacity int `json:"capacity"`
Seats []SeatCell `json:"seats"` // 本人所在桌,按座位号;Filled 不含身份
Stage *StageOut `json:"stage"`
// 本人桌圆心 x(大厅逻辑坐标系)。**仅 Found=true 时有意义,未命中为 0**。
TableX int `json:"table_x"`
// 本人桌圆心 y(大厅逻辑坐标系)。**仅 Found=true 时有意义,未命中为 0**。
TableY int `json:"table_y"`
// 大厅逻辑宽(取自 hall_layout.canvas.w,缺省 1600)。**仅 Found=true 时有意义,未命中为 0**。
HallW int `json:"hall_w"`
// 大厅逻辑高(取自 hall_layout.canvas.h,缺省 1200)。**仅 Found=true 时有意义,未命中为 0**。
HallH int `json:"hall_h"`
}
// ============================================================================
// 公开「可查询活动列表」(N4-A / E-list,免登录)
//
// 只返回已发布(can_query=true)的活动,仅暴露最小字段(event_id / title / venue / event_time);
// 无已发布活动 → {list:[]}(**非报错**,前端据此渲染空态);草稿 / 已结束**不出现**。
// ============================================================================
// EventPublicItem 公开活动条目(最小字段,不含任何组织/名单信息)
type EventPublicItem struct {
EventId int64 `json:"event_id"`
Title string `json:"title"`
Venue string `json:"venue"`
EventTime string `json:"event_time"`
}
// EventPublicListReq 公开可查询活动列表
type EventPublicListReq struct {
g.Meta `path:"/event/list" method:"get" tags:"event" summary:"公开可查询活动列表"`
}
type EventPublicListRes struct {
List []EventPublicItem `json:"list"`
}

View File

@@ -52,22 +52,26 @@ type MemberCenterRes struct {
PeriodDays int `json:"period_days"` // 当前生效的额度重置周期
}
// MemberOrderCreateReq 创建会员订单
// MemberOrderCreateReq 创建会员订单(**只建单**,N1-1/2)。
//
// Code 为 wx.login 获取的临时登录凭证 —— 服务端用它换 session_key,
// session_key 是「用户态签名 signature」的密钥;同时校验支付人与登录人一致。
// Code 保留以兼容旧客户端但**非必传**:建单不再校验「支付人 = 登录人」(该校验前移到
// POST /member/order/pay)。两步式的第一步只负责落一条待支付订单。
type MemberOrderCreateReq struct {
g.Meta `path:"/member/order" method:"post" tags:"member" summary:"创建会员订单(返回支付参数)"`
g.Meta `path:"/member/order" method:"post" tags:"member" summary:"创建会员订单(待支付,不返回支付参数)"`
PlanKey string `v:"required#请选择会员套餐" json:"plan_key"`
Code string `json:"code"`
}
// MemberOrderCreateRes 创建会员订单响应(只建单,**不返回支付参数**;N1-1/2)。
//
// 支付参数由独立端点 POST /member/order/pay 构建(见 order.go 的 MemberOrderPayRes)。
// Reused=true 表示命中「同用户 + 同套餐 + 待支付」的既有单(N1-10),保证待支付单恒为 1 条。
type MemberOrderCreateRes struct {
OutTradeNo string `json:"out_trade_no"`
Mode string `json:"mode"` // 固定 short_series_goods
SignData string `json:"signData"` // JSON 字符串,必须原样传给 wx.requestVirtualPayment
PaySig string `json:"paySig"`
Signature string `json:"signature"`
Env int `json:"env"`
OutTradeNo string `json:"out_trade_no"`
Reused bool `json:"reused"`
OriginPriceCents int64 `json:"origin_price_cents"`
PaidPriceCents int64 `json:"paid_price_cents"`
Status int `json:"status"`
}
// MemberOrderCheckReq 查询订单状态(前端支付成功回调后轮询;同时承担兜底发货)
@@ -91,6 +95,7 @@ type MemberOrderOut struct {
OrderType int `json:"order_type"` // 1=会员套餐 2=次数包
PlanKey string `json:"plan_key"`
PlanName string `json:"plan_name"`
PackKey string `json:"pack_key"`
Title string `json:"title"` // 统一展示标题(套餐名 / 次数包名)
ToolKey string `json:"tool_key"`
ToolName string `json:"tool_name"`
@@ -101,6 +106,12 @@ type MemberOrderOut struct {
PayChannel string `json:"pay_channel"`
CreatedAt string `json:"created_at"`
DeliveredAt string `json:"delivered_at"`
// ===== T04.6 优惠凭证(响应 snake_case)=====
OriginPriceCents int64 `json:"origin_price_cents"`
PaidPriceCents int64 `json:"paid_price_cents"`
PromoCode string `json:"promo_code"`
DiscountCents int64 `json:"discount_cents"`
PromoKind int `json:"promo_kind"`
}
type MemberOrdersRes struct {
List []MemberOrderOut `json:"list"`

85
api/user/v1/note.go Normal file
View File

@@ -0,0 +1,85 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
// NoteOut 笔记列表项
type NoteOut struct {
Id int64 `json:"id"`
Title string `json:"title"`
Summary string `json:"summary"` // 正文摘要(列表不返回全文)
Tags []string `json:"tags"`
IsPinned bool `json:"is_pinned"`
RemindAt string `json:"remind_at"` // 空串=未设置
RemindStatus int `json:"remind_status"` // 0..4
UpdatedAt string `json:"updated_at"`
}
type NoteListReq struct {
g.Meta `path:"/note/list" method:"get" tags:"note" summary:"笔记列表"`
Keyword string `json:"keyword"`
Tag string `json:"tag"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
type NoteListRes struct {
List []NoteOut `json:"list"`
Total int `json:"total"`
ActiveCount int `json:"active_count"` // 活跃笔记数(不含回收站)
Limit int `json:"limit"` // 免费上限 50;会员为 0(表示不限)
IsMember bool `json:"is_member"`
}
type NoteDetailReq struct {
g.Meta `path:"/note/detail" method:"get" tags:"note" summary:"笔记详情"`
Id int64 `v:"required|min:1#笔记不存在" json:"id"`
}
type NoteDetailRes struct {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Tags []string `json:"tags"`
IsPinned bool `json:"is_pinned"`
RemindAt string `json:"remind_at"`
RemindStatus int `json:"remind_status"`
}
// NoteSaveReq id=0 表示新建;新建计入额度、编辑不计入
type NoteSaveReq struct {
g.Meta `path:"/note/save" method:"post" tags:"note" summary:"新建/编辑笔记"`
Id int64 `json:"id"`
Title string `v:"required|length:1,128#标题不能为空|标题最长 128 字" json:"title"`
Content string `v:"length:0,20000#正文过长" json:"content"`
Tags []string `json:"tags"`
IsPinned bool `json:"is_pinned"`
RemindAt string `v:"datetime#提醒时间格式不正确" json:"remind_at"` // 空串=取消提醒
}
type NoteSaveRes struct {
Id int64 `json:"id"`
ActiveCount int `json:"active_count"`
}
type NoteDeleteReq struct {
g.Meta `path:"/note/delete" method:"post" tags:"note" summary:"删除笔记(软删,释放额度)"`
Id int64 `v:"required" json:"id"`
}
type NoteDeleteRes struct{}
type NoteRestoreReq struct {
g.Meta `path:"/note/restore" method:"post" tags:"note" summary:"恢复笔记(P2)"`
Id int64 `v:"required" json:"id"`
}
type NoteRestoreRes = NoteSaveRes
type NoteRemindCancelReq struct {
g.Meta `path:"/note/remind-cancel" method:"post" tags:"note" summary:"取消提醒"`
Id int64 `v:"required" json:"id"`
}
type NoteRemindCancelRes struct{}
// NoteSubscribeReportReq 前端 requestSubscribeMessage 授权成功后回传(一次授权=一次可发送)
type NoteSubscribeReportReq struct {
g.Meta `path:"/note/subscribe-report" method:"post" tags:"note" summary:"上报订阅授权次数"`
NoteId int64 `v:"required" json:"note_id"`
Count int `v:"min:1#授权次数异常" json:"count"`
}
type NoteSubscribeReportRes struct{}

94
api/user/v1/order.go Normal file
View File

@@ -0,0 +1,94 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
// ============================================================================
// 用户端 · 会员 / 次数包订单(N1 两步式:建单 / 建签名 / 详情 / 应用优惠)
//
// 铁律 S1:响应一律 snake_case。请求沿用 member / quota 组既有字段名(snake_case)。
//
// 正当例外(非豁免):微信虚拟支付规定字段 signData / paySig / signature / env 由客户端
// 原样交给 wx.requestVirtualPayment,**必须原样透传、禁止改名**(改名会导致收银台直接失败)。
// 此类「外部平台规定」的命名不受 S1 约束(见 MemberOrderPayRes)。
// ============================================================================
// MemberOrderPayReq 对已建订单构建支付参数(O2)
type MemberOrderPayReq struct {
g.Meta `path:"/member/order/pay" method:"post" tags:"member" summary:"对已建订单构建支付参数"`
OutTradeNo string `v:"required#订单号不能为空" json:"out_trade_no"`
Code string `v:"required#缺少微信登录凭证" json:"code"`
}
// MemberOrderPayRes 虚拟支付参数(O2 / O6)。
//
// ⚠️ SignData / PaySig / Signature / Env 的字段名由微信 wx.requestVirtualPayment 规定,
// 必须原样透传、禁止改动(改名会导致收银台直接失败)。
type MemberOrderPayRes struct {
OutTradeNo string `json:"out_trade_no"`
Mode string `json:"mode"` // 固定 short_series_goods
SignData string `json:"signData"`
PaySig string `json:"paySig"`
Signature string `json:"signature"`
Env int `json:"env"`
}
// QuotaOrderPayReq 次数包订单构建支付参数(O6,与会员完全同构)
type QuotaOrderPayReq struct {
g.Meta `path:"/quota/order/pay" method:"post" tags:"quota" summary:"次数包订单构建支付参数"`
OutTradeNo string `v:"required#订单号不能为空" json:"out_trade_no"`
Code string `v:"required#缺少微信登录凭证" json:"code"`
}
// QuotaOrderPayRes 次数包支付参数(与会员完全同构)
type QuotaOrderPayRes = MemberOrderPayRes
// MemberOrderDetailReq 订单详情(O3)
type MemberOrderDetailReq struct {
g.Meta `path:"/member/order/detail" method:"get" tags:"member" summary:"订单详情"`
OutTradeNo string `v:"required#订单号不能为空" json:"out_trade_no"`
}
// MemberOrderDetailRes 订单详情(O3,响应 snake_case)。
type MemberOrderDetailRes struct {
OutTradeNo string `json:"out_trade_no"`
OrderType int `json:"order_type"` // 1 套餐 2 次数包
Title string `json:"title"` // 套餐名 / 次数包名
PlanKey string `json:"plan_key"`
PlanName string `json:"plan_name"`
PackKey string `json:"pack_key"` // 次数包 key(order_type=2 时用于查询可选免单券)
ToolKey string `json:"tool_key"`
ToolName string `json:"tool_name"`
Times int `json:"times"`
OriginPriceCents int64 `json:"origin_price_cents"`
PaidPriceCents int64 `json:"paid_price_cents"`
DiscountCents int64 `json:"discount_cents"`
PromoKind int `json:"promo_kind"` // 0 无 1 优惠码 2 免单券
PromoCode string `json:"promo_code"`
UserCouponId int64 `json:"user_coupon_id"`
Status int `json:"status"` // 0 待支付 1 已发货 2 已退款 3 已关闭
StatusText string `json:"status_text"`
CreatedAt string `json:"created_at"`
DeliveredAt string `json:"delivered_at"`
}
// MemberOrderApplyVoucherReq 应用 / 更换 / 清除优惠(O4)。
//
// PromoCode 与 UserCouponId **互斥**:同时传 → 4010(后端校验,不靠前端隐藏);两者皆空 = 清除优惠。
type MemberOrderApplyVoucherReq struct {
g.Meta `path:"/member/order/apply-voucher" method:"post" tags:"member" summary:"应用/更换/清除优惠"`
OutTradeNo string `v:"required#订单号不能为空" json:"out_trade_no"`
PromoCode string `json:"promo_code"`
UserCouponId int64 `json:"user_coupon_id"`
}
// MemberOrderApplyVoucherRes 应用优惠结果(O4,响应 snake_case)。
type MemberOrderApplyVoucherRes struct {
Valid bool `json:"valid"`
OriginPriceCents int64 `json:"origin_price_cents"`
PaidPriceCents int64 `json:"paid_price_cents"`
DiscountCents int64 `json:"discount_cents"`
PromoKind int `json:"promo_kind"`
PromoCode string `json:"promo_code"`
UserCouponId int64 `json:"user_coupon_id"`
Message string `json:"message"`
}

107
api/user/v1/promo.go Normal file
View File

@@ -0,0 +1,107 @@
package v1
import "github.com/gogf/gf/v2/frame/g"
// ============================================================================
// 用户端优惠凭证域契约(优惠码校验 / 我的券 / 券可用性校验 / 当前订单可选用券)
//
// 约定(铁律 S1):请求 camelCase / 响应 **snake_case**。
// (架构文档 §4.4.3 此处曾把若干出参写成驼峰,已按 S1 纠正。)
// ============================================================================
// ===== P1 优惠码校验(服务端为准,前端只展示)=====
// PromoValidateReq 校验优惠码
type PromoValidateReq struct {
g.Meta `path:"/promo/validate" method:"post" tags:"promo" summary:"校验优惠码"`
Code string `v:"required#请输入优惠码" json:"code"`
OrderType int `v:"required|in:1,2#订单类型非法" json:"orderType"` // 1会员套餐 2次数包
TargetKey string `json:"targetKey"` // plan_key 或 pack_key
}
type PromoValidateRes struct {
Valid bool `json:"valid"`
OriginPriceCents int64 `json:"origin_price_cents"`
PaidPriceCents int64 `json:"paid_price_cents"`
DiscountCents int64 `json:"discount_cents"`
ProductId string `json:"product_id"`
Message string `json:"message"`
}
// ===== P5 我的优惠券 =====
// CouponMyReq 我的优惠券
type CouponMyReq struct {
g.Meta `path:"/coupon/my" method:"get" tags:"coupon" summary:"我的优惠券"`
Status *int `json:"status"` // 可空:0可用 1已用 2已过期
}
type CouponMySummary struct {
Usable int `json:"usable"`
Freebie int `json:"freebie"` // 可抵扣券(仅免单券)
ExpiringSoon int `json:"expiring_soon"` // 本月将过期
Used int `json:"used"`
Expired int `json:"expired"`
}
type UserCouponItem struct {
Id int64 `json:"id"`
CouponId int64 `json:"coupon_id"`
Title string `json:"title"`
SubTitle string `json:"sub_title"`
CouponType int `json:"coupon_type"` // 1免单券 2展示券
Kind int `json:"kind"`
Value int `json:"value"`
ThresholdCents int `json:"threshold_cents"`
Status int `json:"status"` // 0未使用 1已使用 2已过期 3已作废
ExpireAt string `json:"expire_at"`
GrantedAt string `json:"granted_at"`
UsedAt string `json:"used_at"`
}
type CouponMyRes struct {
Summary CouponMySummary `json:"summary"`
List []UserCouponItem `json:"list"`
}
// ===== P6 下单前可用性校验(只读,不占用)=====
// CouponValidateReq 校验免单券可用性
type CouponValidateReq struct {
g.Meta `path:"/coupon/validate" method:"post" tags:"coupon" summary:"校验免单券可用性"`
UserCouponId int64 `v:"required" json:"userCouponId"`
OrderType int `v:"required|in:1,2" json:"orderType"`
TargetKey string `json:"targetKey"`
}
type CouponValidateRes struct {
Valid bool `json:"valid"`
CouponType int `json:"coupon_type"`
IsFreebie bool `json:"is_freebie"`
ProductId string `json:"product_id"`
PriceCents int64 `json:"price_cents"`
OriginPriceCents int64 `json:"origin_price_cents"`
PaidPriceCents int64 `json:"paid_price_cents"`
Message string `json:"message"`
}
// ===== P7 当前订单可选用的券(只返免单券)=====
// CouponUsableReq 当前订单可选用的券
type CouponUsableReq struct {
g.Meta `path:"/coupon/usable" method:"get" tags:"coupon" summary:"当前订单可选用券"`
OrderType int `v:"required|in:1,2" json:"orderType"`
TargetKey string `json:"targetKey"`
// OutTradeNo 可选(向后兼容):传入本人订单号时,响应会**保底**返回该订单当前
// 已选中的那张免单券并标记 applied=true。原因:下单占用后该券 status 变 1,
// 会被「仅返未使用券」的过滤排除,导致用户只有一张券时列表变空、既看不到也换不了。
OutTradeNo string `json:"outTradeNo"`
}
type UsableCouponItem struct {
UserCouponId int64 `json:"user_coupon_id"`
Title string `json:"title"`
CouponType int `json:"coupon_type"`
IsFreebie bool `json:"is_freebie"`
ExpireAt string `json:"expire_at"`
EstimatedPaidPriceCents int64 `json:"estimated_paid_price_cents"`
// Applied 是否为「当前订单已选中」的券(默认 false;仅当请求带 outTradeNo 时可能为 true)。
Applied bool `json:"applied"`
}
type CouponUsableRes struct {
List []UsableCouponItem `json:"list"`
}

View File

@@ -87,22 +87,28 @@ type QuotaPacksRes struct {
List []QuotaPackOut `json:"list"`
}
// QuotaOrderCreateReq 创建次数包订单(支付参数与会员套餐完全同构)
// QuotaOrderCreateReq 创建次数包订单(**只建单**,N1:与会员套餐两步式同款)
//
// Code 保留以兼容旧客户端但**非必传**:建单不再校验「支付人 = 登录人」(该校验前移到
// POST /quota/order/pay)。两步式的第一步只负责落一条待支付订单。
type QuotaOrderCreateReq struct {
g.Meta `path:"/quota/order" method:"post" tags:"quota" summary:"创建次数包订单(返回支付参数)"`
g.Meta `path:"/quota/order" method:"post" tags:"quota" summary:"创建次数包订单(待支付,不返回支付参数)"`
PackKey string `v:"required#请选择次数包" json:"pack_key"`
ToolKey string `v:"required#请选择要购买的次数包工具" json:"tool_key"`
Code string `json:"code"`
}
// QuotaOrderCreateRes 创建次数包订单响应(只建单,不返回支付参数)。
//
// 支付参数由 POST /quota/order/pay 构建(见 order.go 的 QuotaOrderPayRes)。
type QuotaOrderCreateRes struct {
OutTradeNo string `json:"out_trade_no"`
Mode string `json:"mode"`
SignData string `json:"signData"`
PaySig string `json:"paySig"`
Signature string `json:"signature"`
Env int `json:"env"`
PackKey string `json:"pack_key"`
ToolKey string `json:"tool_key"`
Times int `json:"times"`
ValidDays int `json:"valid_days"`
OutTradeNo string `json:"out_trade_no"`
Reused bool `json:"reused"`
OriginPriceCents int64 `json:"origin_price_cents"`
PaidPriceCents int64 `json:"paid_price_cents"`
PackKey string `json:"pack_key"`
ToolKey string `json:"tool_key"`
Times int `json:"times"`
ValidDays int `json:"valid_days"`
Status int `json:"status"`
}

View File

@@ -76,6 +76,18 @@ type ProfileUpdateRes struct {
AvatarUrl string `json:"avatar_url"`
}
// ===== 头像上传(N3)=====
// AvatarUploadReq 头像上传(multipart/form-data,文件字段名 file;≤5MB,jpg / png / webp)。
//
// 返回的 avatar_url 为「浏览器可直接打开」的绝对地址(<scheme>://<host>/static/avatar/...)。
type AvatarUploadReq struct {
g.Meta `path:"/user/avatar" method:"post" mime:"multipart/form-data" tags:"user" summary:"上传头像"`
}
type AvatarUploadRes struct {
AvatarUrl string `json:"avatar_url"`
}
// ===== 等级 =====
type LevelReq struct {