- 后端 Go+Gin+GORM: 配置(OT_ env)/SQLite/Postgres 双驱动、用户体系(argon2id+JWT access/refresh)、 API Key(sk- 48位, 仅存 SHA-256 哈希) - 代理网关: /v1/chat/completions、/v1/responses、/v1/messages、/v1/models;错误按客户端协议返回 - 三协议互转(convert 包): Chat↔Messages↔Responses 请求/响应 + 流式 SSE 逐事件转换(直通优先) - 用量计费: 异步批量记账、余额扣减、balance_logs、usage_daily 日聚合 - 管理 API: 用户/渠道 CRUD+测试+模型导入/模型定价+绑定/统计/系统配置 - 前端 Vue3+TS+Tailwind(taste-skill 设计 tokens): Landing/登录注册/控制台/管理后台, 自建组件+Phosphor 图标+自建 SVG 趋势图, 已过 web-design-guidelines 复查 - mock 上游: OpenAI+Anthropic 双协议模拟(含流式) Co-Authored-By: Claude <noreply@anthropic.com>
73 lines
1.9 KiB
Go
73 lines
1.9 KiB
Go
// Package jwt 访问令牌(短时,存内存)与刷新令牌(HttpOnly Cookie)签发/校验。
|
||
package jwt
|
||
|
||
import (
|
||
"errors"
|
||
"time"
|
||
|
||
"github.com/golang-jwt/jwt/v5"
|
||
)
|
||
|
||
// Claims 用户声明;Subject 字段区分 "access" / "refresh"。
|
||
type Claims struct {
|
||
UserID uint64 `json:"uid"`
|
||
Username string `json:"uname"`
|
||
Role string `json:"role"`
|
||
jwt.RegisteredClaims
|
||
}
|
||
|
||
type Manager struct {
|
||
secret []byte
|
||
issuer string
|
||
accessTTL time.Duration
|
||
refreshTTL time.Duration
|
||
}
|
||
|
||
func NewManager(secret, issuer string, accessTTL, refreshTTL time.Duration) *Manager {
|
||
return &Manager{secret: []byte(secret), issuer: issuer, accessTTL: accessTTL, refreshTTL: refreshTTL}
|
||
}
|
||
|
||
func (m *Manager) AccessTTL() time.Duration { return m.accessTTL }
|
||
func (m *Manager) RefreshTTL() time.Duration { return m.refreshTTL }
|
||
|
||
// Sign 签发 token;typ 取 "access" / "refresh"。
|
||
func (m *Manager) Sign(userID uint64, username, role, typ string) (string, time.Time, error) {
|
||
ttl := m.accessTTL
|
||
if typ == "refresh" {
|
||
ttl = m.refreshTTL
|
||
}
|
||
now := time.Now()
|
||
exp := now.Add(ttl)
|
||
claims := Claims{
|
||
UserID: userID,
|
||
Username: username,
|
||
Role: role,
|
||
RegisteredClaims: jwt.RegisteredClaims{
|
||
Issuer: m.issuer,
|
||
Subject: typ,
|
||
IssuedAt: jwt.NewNumericDate(now),
|
||
ExpiresAt: jwt.NewNumericDate(exp),
|
||
},
|
||
}
|
||
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
s, err := tok.SignedString(m.secret)
|
||
return s, exp, err
|
||
}
|
||
|
||
var ErrInvalidToken = errors.New("invalid token")
|
||
|
||
// Parse 校验签名与有效期。
|
||
func (m *Manager) Parse(token string) (*Claims, error) {
|
||
claims := &Claims{}
|
||
tok, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
|
||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||
return nil, ErrInvalidToken
|
||
}
|
||
return m.secret, nil
|
||
})
|
||
if err != nil || !tok.Valid {
|
||
return nil, ErrInvalidToken
|
||
}
|
||
return claims, nil
|
||
}
|