chore: 初始化 monorepo 脚手架与代码骨架

- Bun workspaces:apps/web(Vue 3.5 + Vite 8 + naive-ui)· apps/server(Hono 4.13 + Drizzle)· packages/shared(契约类型)
- 代码骨架:四表 Drizzle schema · 11 个端点占位(501)· 管理面口令中间件 · 同步引擎占位(5 分钟 tick + 防重入)
- 命令链:dev / build / test / lint / type-check / db:*(见 AGENTS.md)
This commit is contained in:
2026-09-14 23:54:58 +08:00
parent 45b312b357
commit 138a1b7685
50 changed files with 2739 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
/**
* 契约常量 —— 与 `docs/design/API契约.md` 保持同步。
*/
/** 明细分页:默认每页条数(契约 §2.1 请求参数) */
export const LOGS_DEFAULT_LIMIT = 50
/** 明细分页:每页条数上限 */
export const LOGS_MAX_LIMIT = 200
/** 每页条数选择器的可选值 */
export const LOGS_LIMIT_OPTIONS = [20, 50, 100, 200] as const
/** 单次查询的 key 集合上限(初值,技术稿可调) */
export const KEYS_MAX = 20
/** 日志类型(上游 model/log.go;常量注释明确「刻意不用 iota」,枚举值永不改变) */
export const LOG_TYPE = {
Unknown: 0,
Topup: 1,
Consume: 2,
Manage: 3,
System: 4,
Error: 5,
Refund: 6,
Login: 7,
} as const
/** 日志类型的中文标签(展示层用;未知值原样显示数字) */
export const LOG_TYPE_LABEL: Record<number, string> = {
[LOG_TYPE.Unknown]: '未知',
[LOG_TYPE.Topup]: '充值',
[LOG_TYPE.Consume]: '消费',
[LOG_TYPE.Manage]: '管理',
[LOG_TYPE.System]: '系统',
[LOG_TYPE.Error]: '错误',
[LOG_TYPE.Refund]: '退款',
[LOG_TYPE.Login]: '登录',
}
/** 额度美元换算的出厂默认(仅兜底展示;实际取值一律从 qpu_history 时间线来) */
export const DEFAULT_QUOTA_PER_UNIT = 500000
/** 管理面请求头(契约 约定 6) */
export const MANAGE_TOKEN_HEADER = 'x-manage-token'
+2
View File
@@ -0,0 +1,2 @@
export * from './types'
export * from './constants'
+223
View File
@@ -0,0 +1,223 @@
/**
* API 契约类型 —— 与 `docs/design/API契约.md` 保持同步(全链路 snake_case,契约 约定 3)。
* 字段集稳定:可空列返回 `null` 而非省略(约定「字段集稳定」)。
*/
// ---------- 通用:错误与元信息 ----------
/** 错误码枚举(契约 约定 21) */
export type ApiErrorCode =
| 'invalid_params'
| 'manage_unauthorized'
| 'not_found'
| 'internal'
| 'key_rejected'
| 'already_registered'
| 'upstream_unavailable'
/** 失败响应体(约定 20:非 2xx + error 信封) */
export interface ApiErrorBody {
error: {
code: ApiErrorCode | (string & {})
message: string
}
}
/** 查看面查询响应统一携带的 meta(约定 22) */
export interface QueryMetaKeyRegistered {
key_id: number
token_name: string
registered: true
status: 'active' | 'disabled'
/** 该 key 自己的同步进度(新登记未首同步时为 null) */
log_synced_at: string | null
}
export interface QueryMetaKeyUnregistered {
registered: false
/** 即时生成、不落库:`sk-…a1b2` */
key_hint: string
}
export type QueryMetaKey = QueryMetaKeyRegistered | QueryMetaKeyUnregistered
export interface QueryMeta {
/** 参与 key 中最慢的 log_synced_at(取非 null 的最慢值);全部未同步时为 null */
data_until: string | null
served_at: string
keys: QueryMetaKey[]
}
// ---------- /usage-log ----------
/** 日志类型(上游 model/log.go 的稳定枚举,照存不过滤) */
export type LogType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
/** 日志行的 `other`(实测 26 键中的常用项;完整键集合见 docs/upstream 实测记录) */
export interface LogOther {
billing_source?: 'subscription' | 'wallet'
cache_tokens?: number
model_ratio?: number
request_path?: string
subscription_id?: number
subscription_plan_title?: string
subscription_total?: number
subscription_used?: number
subscription_remain?: number
[key: string]: unknown
}
/** 明细行 = `usage_logs` 表 1:1(除内部列 `dedupe_key`)+ 计算字段 */
export interface UsageLogRow {
id: number
key_id: number
user_id: number
username: string
created_at: string
type: LogType
content: string
/** 当前名(join api_keys),非日志快照名 */
token_name: string
model_name: string
quota: number
prompt_tokens: number
completion_tokens: number
use_time: number
is_stream: boolean
channel: number
channel_name: string
token_id: number
group_name: string
ip: string
request_id: string
upstream_request_id: string | null
/** 该行折算实付人民币(错误行 quota = 0 → 0) */
amount_rmb: number
other: LogOther
}
export interface LogsQueryRequest {
/** 必填;去重后最多 KEYS_MAX 个 */
keys: string[]
/** 必填;ISO 8601(输出 +08:00,输入宽容),左闭右开 [start, end) */
start: string
end: string
/** 可选;缺省 = 全部类型;只作用于本端点 */
type?: number[]
/** 可选;默认 50,上限 200 */
limit?: number
offset?: number
}
export interface LogsQueryResponse {
items: UsageLogRow[]
total: number
meta: QueryMeta
}
export interface SummaryRequest {
keys: string[]
start: string
end: string
}
export interface SummaryResponse {
request_count: number
consume_count: number
error_count: number
prompt_tokens: number
completion_tokens: number
cache_tokens: number
/** quota 合计(原值,供核对) */
quota: number
/** 折算实付人民币(跨期按购买期间分段累加) */
amount_rmb: number
meta: QueryMeta
}
// ---------- /manage/api-keys ----------
/** 清单行 = `api_keys` 表 1:1(除 key_hash / key_ciphertext 两个永不出接口的敏感列) */
export interface ApiKeyRow {
id: number
token_name: string
status: 'active' | 'disabled'
/** 停用来源(可辨「已失效」与「已停用」) */
disabled_source: 'manual' | 'auto_401' | null
consecutive_401s: number
registered_at: string
log_synced_at: string | null
usage_synced_at: string | null
total_granted: number
total_used: number
total_available: number
unlimited_quota: boolean
model_limits: Record<string, unknown>
model_limits_enabled: boolean
expires_at: string | null
}
export interface RegisterApiKeyRequest {
key: string
}
export interface PatchApiKeyRequest {
status: 'active' | 'disabled'
}
// ---------- /manage/purchases ----------
export type PurchaseKind = 'subscription' | 'topup'
/** 清单行 = `purchases` 表 1:1(9 列) */
export interface PurchaseRow {
id: number
kind: PurchaseKind
paid_at: string
paid_amount: number
note: string | null
period_start: string | null
period_end: string | null
subscription_id: number | null
quota_granted: number | null
}
/** 登记请求(必填:kind / paid_at / paid_amount / quota_granted) */
export interface PurchaseCreateRequest {
kind: PurchaseKind
paid_at: string
paid_amount: number
quota_granted: number
note?: string | null
period_start?: string | null
period_end?: string | null
subscription_id?: number | null
}
/** 编辑请求:部分更新语义(提交哪些改哪些) */
export type PurchasePatchRequest = Partial<PurchaseCreateRequest>
/** 观测项(核对提示的数据面) */
export interface ObservationItem {
subscription_id: number
plan_title: string | null
observed_quota_total: number | null
first_seen_at: string
last_seen_at: string
log_count: number
/** null = 观测到但未登记(补录引导的目标) */
matched_purchase_id: number | null
}
export interface ObservationsResponse {
items: ObservationItem[]
}
// ---------- /manage/qpu ----------
/** 时间线行 = `qpu_history` 表 1:1(3 列,纯只读) */
export interface QpuRow {
id: number
qpu: number
effective_from: string
}