dudu MVP:五端语音输入法初始提交
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调 - desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导) - android:Compose 主 App + IME(键盘内录音直传) - ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写) - design/design-pipeline:设计系统 + token 导出 iOS/Android 主题 - doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
// Package store 数据模型与迁移。schema 设计见 doc/backend-architecture.html 第六章。
|
||||
package store
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
Nickname string `gorm:"size:64"`
|
||||
AvatarURL string `gorm:"size:512"`
|
||||
BalanceSeconds int64 `gorm:"not null;default:0"` // 冗余列,与 balance_ledger 同事务更新
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type WechatIdentity struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
UserID string `gorm:"size:32;index;not null"`
|
||||
OpenID string `gorm:"size:64;uniqueIndex;not null"`
|
||||
UnionID string `gorm:"size:64;index"`
|
||||
AppType string `gorm:"size:16;not null"` // web | mobile
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type DurationPack struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
Minutes int `gorm:"not null"`
|
||||
PriceCents int `gorm:"not null"`
|
||||
UnitDesc string `gorm:"size:64"`
|
||||
Tag string `gorm:"size:32"`
|
||||
Sort int
|
||||
Active bool `gorm:"not null;default:true"`
|
||||
}
|
||||
|
||||
const (
|
||||
OrderPending = "pending"
|
||||
OrderPaid = "paid"
|
||||
OrderClosed = "closed"
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
UserID string `gorm:"size:32;index;not null"`
|
||||
PackID string `gorm:"size:32;not null"`
|
||||
PriceCents int `gorm:"not null"`
|
||||
Channel string `gorm:"size:16;not null"` // native | app
|
||||
Status string `gorm:"size:16;not null;index"` // pending | paid | closed
|
||||
TransactionID *string `gorm:"size:64;uniqueIndex"` // 微信支付单号,幂等去重
|
||||
PaidAt *time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
LedgerPurchase = "purchase"
|
||||
LedgerUsage = "usage"
|
||||
LedgerGift = "gift"
|
||||
)
|
||||
|
||||
// BalanceLedger 时长账本:只追加不修改,余额 = SUM(delta_seconds)。
|
||||
type BalanceLedger struct {
|
||||
ID uint `gorm:"primaryKey"`
|
||||
UserID string `gorm:"size:32;index;not null"`
|
||||
DeltaSeconds int64 `gorm:"not null"` // + 购买/赠送,− 用量
|
||||
Reason string `gorm:"size:16;not null"`
|
||||
OrderID *string `gorm:"size:32"`
|
||||
SessionID *string `gorm:"size:64"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// TrialUsage 每日免费试用:自然日(Asia/Shanghai)为键,无记录即未使用。
|
||||
type TrialUsage struct {
|
||||
UserID string `gorm:"primaryKey;size:32"`
|
||||
Date string `gorm:"primaryKey;size:10"` // YYYY-MM-DD
|
||||
UsedSeconds int `gorm:"not null;default:0"`
|
||||
}
|
||||
|
||||
// ASRSession 识别会话用量明细(计费拆分 + 排障)。
|
||||
type ASRSession struct {
|
||||
ID string `gorm:"primaryKey;size:64"`
|
||||
UserID string `gorm:"size:32;index;not null"`
|
||||
DeviceID string `gorm:"size:64"`
|
||||
AudioSeconds int `gorm:"not null"`
|
||||
TrialPart int `gorm:"not null"` // 试用扣减部分
|
||||
BalancePart int `gorm:"not null"` // 余额扣减部分
|
||||
ProviderMs int64 // Provider 报告的识别时长(ms),与 AudioSeconds 交叉校验/审计
|
||||
Provider string `gorm:"size:16"`
|
||||
Canceled bool
|
||||
CreatedAt time.Time `gorm:"index"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ID string `gorm:"primaryKey;size:64"`
|
||||
UserID string `gorm:"size:32;index"`
|
||||
Platform string `gorm:"size:16"` // mac | win | ios | android
|
||||
AppVersion string `gorm:"size:32"`
|
||||
LastSeen time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
FeedbackNew = "new"
|
||||
FeedbackTriaged = "triaged"
|
||||
FeedbackResolved = "resolved"
|
||||
)
|
||||
|
||||
// Feedback 用户反馈(v1.1)。
|
||||
type Feedback struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
UserID string `gorm:"size:32;index;not null"`
|
||||
Content string `gorm:"type:text;not null"`
|
||||
Images datatypes.JSON // OSS keys: ["fb/{id}/1.png", ...]
|
||||
Diagnostics datatypes.JSON
|
||||
Platform string `gorm:"size:16"`
|
||||
AppVersion string `gorm:"size:32"`
|
||||
Status string `gorm:"size:16;not null;default:new;index"`
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// MetricEvent 客户端打点原始事件(v1.1,保留 90 天定期清理)。
|
||||
type MetricEvent struct {
|
||||
ID uint64 `gorm:"primaryKey"`
|
||||
UserID *string `gorm:"size:32"`
|
||||
DeviceID string `gorm:"size:64;not null"`
|
||||
Platform string `gorm:"size:16"`
|
||||
AppVersion string `gorm:"size:32"`
|
||||
OSVersion string `gorm:"size:64"`
|
||||
Event string `gorm:"size:64;not null;index"`
|
||||
Props datatypes.JSON
|
||||
ClientTs int64
|
||||
ReceivedAt time.Time // BRIN 索引在 Open() 中按 PG 专属 DDL 创建
|
||||
}
|
||||
|
||||
// MetricDaily 每日聚合(event × platform × date)。
|
||||
type MetricDaily struct {
|
||||
Date string `gorm:"primaryKey;size:10"`
|
||||
Event string `gorm:"primaryKey;size:64"`
|
||||
Platform string `gorm:"primaryKey;size:16"`
|
||||
Count int64 `gorm:"not null"`
|
||||
P50Ms float64 `gorm:"not null;default:0"`
|
||||
P95Ms float64 `gorm:"not null;default:0"`
|
||||
}
|
||||
|
||||
// AllModels 迁移清单(11 张表)。
|
||||
func AllModels() []any {
|
||||
return []any{
|
||||
&User{}, &WechatIdentity{}, &DurationPack{}, &Order{}, &BalanceLedger{},
|
||||
&TrialUsage{}, &ASRSession{}, &Device{}, &Feedback{}, &MetricEvent{}, &MetricDaily{},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user