-
Notifications
You must be signed in to change notification settings - Fork 19
Add: KimmyXYC LLM Backend #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough新增完整后端与示例前端原型:包括 Go 模块与依赖、数据库模型与连接、JWT 鉴权与中间件、聊天服务、可插拔 LLM 提供方(Mock/OpenAI)、HTTP 路由与 SSE 流式接口、README/API 文档、示例 .env 及前端静态资源与脚本。 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Env as .env / 环境
participant Main as cmd/server/main.go
participant DB as GORM/Postgres
participant Prov as Provider 选择
participant Router as Gin 路由
participant HTTP as HTTP 服务
Env->>Main: 可选加载 .env
Main->>DB: Connect(DATABASE_URL)
DB-->>Main: 返回 *gorm.DB 或 错误
Main->>DB: AutoMigrate(User, Conversation, Message)
Main->>Prov: NewProviderFromEnv()
Prov-->>Main: 返回 LLMProvider (Mock 或 OpenAI)
Main->>Router: NewRouter(db, llm)
Main->>HTTP: ListenAndServe(ADDR)
HTTP-->>Main: 运行或报告错误
sequenceDiagram
autonumber
participant FE as 前端
participant API as /api/chat
participant Auth as 中间件(AuthRequired/ModelAccess)
participant Chat as ChatService
participant LLM as LLMProvider
participant DB as 数据库
FE->>API: POST /api/chat (?stream=1) + Bearer token
API->>Auth: 验证 JWT 并检查模型访问
Auth-->>API: 通过 或 返回 401/403
API->>Chat: SendMessage(ctx, userID, convID, model, text, streamCb)
Chat->>DB: EnsureConversation / 保存用户消息
Chat->>DB: 读取最近历史消息
Chat->>LLM: ChatCompletionStream(ctx, model, messages)
loop 流式生成
LLM-->>Chat: StreamChunk(content / done / err)
Chat-->>API: 写入 SSE 数据块(data: ...)
end
Chat->>DB: 保存助手最终消息并更新会话
API-->>FE: SSE done 事件(包含 conversation_id)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (5)
KimmyXYC/web/js/auth.js (1)
20-20: 密码输入未trim,与邮箱处理不一致第20行和第35行中,邮箱使用了
.trim()处理前后空格,但密码字段未做相同处理。虽然密码可能有意保留前后空格,但这种不一致性可能导致用户体验问题(例如用户无意中输入了空格)。建议统一处理方式:
- const resp = await login(loginEmail.value.trim(), loginPassword.value); + const resp = await login(loginEmail.value.trim(), loginPassword.value.trim());- const resp = await register(regEmail.value.trim(), regPassword.value, regRole.value); + const resp = await register(regEmail.value.trim(), regPassword.value.trim(), regRole.value);Also applies to: 35-35
KimmyXYC/internal/models/models.go (1)
28-32: 考虑显式定义外键约束虽然GORM会根据约定自动处理
UserID外键关系,但显式定义外键约束可以提高代码可读性,并确保数据库层面的引用完整性。可选改进:
type Conversation struct { ID uint `gorm:"primaryKey" json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - UserID uint `gorm:"index" json:"user_id"` + UserID uint `gorm:"index;constraint:OnDelete:CASCADE" json:"user_id"` Title string `gorm:"size:255" json:"title"` Model string `gorm:"size:100" json:"model"` Messages []Message `json:"messages"` }类似地,在
Message结构体中也可以为ConversationID添加约束。KimmyXYC/internal/provider/provider.go (2)
35-35: 移除未使用的环境变量读取第 35 行读取
VOLC_API_KEY但未使用它。即使是为未来预留的,空读取会产生不必要的开销。建议在实际需要时再添加。应用此 diff 移除未使用的代码:
- _ = os.Getenv("VOLC_API_KEY") // reserved for future real provider
65-65: 简化内联函数提高可读性第 65 行的匿名函数过于复杂,可以提取为独立变量以提高代码清晰度。
应用此 diff 重构代码:
for i, w := range words { + chunk := w + if i > 0 { + chunk = " " + w + } select { case <-ctx.Done(): ch <- StreamChunk{Err: ctx.Err()} return - case ch <- StreamChunk{Content: func() string { if i == 0 { return w } ; return " " + w }()}: + case ch <- StreamChunk{Content: chunk}: time.Sleep(50 * time.Millisecond) }KimmyXYC/web/js/api.js (1)
111-115: 前后端模型列表重复定义
AllowedModelsByRole与后端KimmyXYC/pkg/middleware/auth.go中的定义完全重复。这种重复可能导致维护问题,当后端更新模型列表时需要手动同步前端。考虑以下改进方案:
- 通过 API 端点从后端动态获取模型列表
- 使用代码生成工具保持前后端同步
- 如果选择保持当前实现,请在两处添加注释说明需要同步更新
基于 learnings(前端需要客户端验证以改善用户体验)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
KimmyXYC/.env.example(1 hunks)KimmyXYC/README.md(1 hunks)KimmyXYC/cmd/server/main.go(1 hunks)KimmyXYC/docs/api.md(1 hunks)KimmyXYC/go.mod(1 hunks)KimmyXYC/internal/db/db.go(1 hunks)KimmyXYC/internal/httpserver/router.go(1 hunks)KimmyXYC/internal/models/models.go(1 hunks)KimmyXYC/internal/provider/openai.go(1 hunks)KimmyXYC/internal/provider/provider.go(1 hunks)KimmyXYC/internal/services/auth_service.go(1 hunks)KimmyXYC/internal/services/chat_service.go(1 hunks)KimmyXYC/pkg/auth/token.go(1 hunks)KimmyXYC/pkg/middleware/auth.go(1 hunks)KimmyXYC/web/css/styles.css(1 hunks)KimmyXYC/web/index.html(1 hunks)KimmyXYC/web/js/api.js(1 hunks)KimmyXYC/web/js/auth.js(1 hunks)KimmyXYC/web/js/chat.js(1 hunks)KimmyXYC/web/js/main.js(1 hunks)KimmyXYC/web/js/state.js(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
KimmyXYC/web/js/main.js (4)
KimmyXYC/web/js/chat.js (1)
initChatUI(4-140)KimmyXYC/web/js/auth.js (1)
initAuthUI(4-44)KimmyXYC/web/js/state.js (4)
clearToken(12-14)clearUser(26-28)isLoggedIn(30-32)setUser(22-24)KimmyXYC/web/js/api.js (1)
me(34-36)
KimmyXYC/web/js/auth.js (3)
KimmyXYC/web/js/api.js (3)
login(26-32)me(34-36)register(18-24)KimmyXYC/web/js/state.js (2)
setToken(8-10)setUser(22-24)KimmyXYC/web/js/main.js (1)
profile(38-38)
KimmyXYC/pkg/middleware/auth.go (2)
KimmyXYC/web/js/api.js (2)
AllowedModelsByRole(111-115)AllowedModelsByRole(111-115)KimmyXYC/pkg/auth/token.go (1)
ParseToken(44-59)
KimmyXYC/internal/provider/openai.go (1)
KimmyXYC/internal/provider/provider.go (2)
ChatMessage(11-14)StreamChunk(17-21)
KimmyXYC/web/js/chat.js (2)
KimmyXYC/web/js/state.js (1)
getUser(16-20)KimmyXYC/web/js/api.js (6)
roleAllowsModel(117-121)listConversations(38-40)convId(73-73)getMessages(42-44)sendChat(46-52)chatStream(55-109)
KimmyXYC/cmd/server/main.go (3)
KimmyXYC/internal/db/db.go (2)
Connect(13-24)AutoMigrate(27-33)KimmyXYC/internal/provider/provider.go (1)
NewProviderFromEnv(31-37)KimmyXYC/internal/httpserver/router.go (1)
NewRouter(21-53)
KimmyXYC/internal/services/chat_service.go (2)
KimmyXYC/internal/provider/provider.go (2)
LLMProvider(24-27)ChatMessage(11-14)KimmyXYC/internal/models/models.go (2)
Conversation(22-33)Message(36-45)
KimmyXYC/internal/services/auth_service.go (2)
KimmyXYC/internal/models/models.go (1)
User(10-19)KimmyXYC/pkg/auth/token.go (1)
CreateToken(29-41)
KimmyXYC/internal/provider/provider.go (1)
KimmyXYC/internal/provider/openai.go (1)
NewOpenAIProviderFromEnv(29-43)
KimmyXYC/internal/db/db.go (1)
KimmyXYC/internal/models/models.go (3)
User(10-19)Conversation(22-33)Message(36-45)
KimmyXYC/web/js/api.js (2)
KimmyXYC/web/js/state.js (1)
getToken(4-6)KimmyXYC/pkg/middleware/auth.go (1)
AllowedModelsByRole(13-17)
🪛 dotenv-linter (3.3.0)
KimmyXYC/.env.example
[warning] 13-13: [UnorderedKey] The OPENAI_API_BASE key should go before the OPENAI_API_KEY key
(UnorderedKey)
🪛 markdownlint-cli2 (0.18.1)
KimmyXYC/docs/api.md
3-3: Bare URL used
(MD034, no-bare-urls)
7-7: Bare URL used
(MD034, no-bare-urls)
11-11: Bare URL used
(MD034, no-bare-urls)
18-18: Bare URL used
(MD034, no-bare-urls)
36-36: Bare URL used
(MD034, no-bare-urls)
48-48: Bare URL used
(MD034, no-bare-urls)
KimmyXYC/README.md
3-3: Bare URL used
(MD034, no-bare-urls)
7-7: Bare URL used
(MD034, no-bare-urls)
11-11: Bare URL used
(MD034, no-bare-urls)
18-18: Bare URL used
(MD034, no-bare-urls)
36-36: Bare URL used
(MD034, no-bare-urls)
48-48: Bare URL used
(MD034, no-bare-urls)
🪛 OSV Scanner (2.2.3)
KimmyXYC/go.mod
[HIGH] 1-1: github.com/golang-jwt/jwt/v5 5.2.1: Excessive memory allocation during header parsing in github.com/golang-jwt/jwt
(GO-2025-3553)
[HIGH] 1-1: github.com/golang-jwt/jwt/v5 5.2.1: jwt-go allows excessive memory allocation during header parsing
[CRITICAL] 1-1: golang.org/x/crypto 0.26.0: Misuse of connection.serverAuthenticate may cause authorization bypass in golang.org/x/crypto
(GO-2024-3321)
[CRITICAL] 1-1: golang.org/x/crypto 0.26.0: Potential denial of service in golang.org/x/crypto
(GO-2025-3487)
[CRITICAL] 1-1: golang.org/x/crypto 0.26.0: golang.org/x/crypto Vulnerable to Denial of Service (DoS) via Slow or Incomplete Key Exchange
[CRITICAL] 1-1: golang.org/x/crypto 0.26.0: Misuse of ServerConfig.PublicKeyCallback may cause authorization bypass in golang.org/x/crypto
🔇 Additional comments (10)
KimmyXYC/web/js/main.js (1)
21-47: LGTM!应用启动流程设计合理:
- 退出登录时清理token/user并重载页面
- 自动验证已存储的token并处理失效情况
- 错误处理得当,token失效时正确回退到认证页面
KimmyXYC/web/js/state.js (1)
1-32: LGTM!状态管理实现简洁且健壮:
- 安全的JSON解析并带有错误处理
- 条件存储避免存储空值
- 合理使用localStorage持久化token和用户信息
KimmyXYC/internal/db/db.go (1)
26-33: 验证生产环境迁移策略代码库中未检测到显式迁移脚本或工具配置,请确认:
- 该应用是否仅用于开发/演示环境?
- 若需生产部署,是否会采用 golang-migrate、GORM Migrator 或其他显式迁移方案?
KimmyXYC/internal/provider/provider.go (2)
10-21: 数据结构定义合理
ChatMessage和StreamChunk结构体设计清晰,字段定义恰当。如果需要 JSON 序列化,可考虑添加 json 标签,但当前实现已满足内部使用需求。
23-27: 接口设计符合 Go 最佳实践
LLMProvider接口采用基于 channel 的流式设计,支持上下文取消,这是处理流式 AI 响应的理想模式。KimmyXYC/web/js/api.js (5)
3-16: API 函数实现规范核心
api函数正确处理了令牌注入、错误解析和响应类型检测,实现质量良好。
18-32: 认证函数实现正确注册和登录函数简洁明了,正确使用了 JSON 格式的请求体。
34-52: 数据获取函数实现合理函数正确封装了 API 调用。
getMessages函数未验证convId参数,但由于后端会处理无效 ID,这是可接受的设计权衡。
54-109: 流式响应处理实现完整
chatStream函数正确实现了 SSE 解析逻辑,包括缓冲区管理和事件刷新。第 107 行的最终flushEvents()调用确保了缓冲区中的剩余数据得到处理。SSE 解析逻辑虽然复杂,但对于处理流式响应是必要的。当前实现已涵盖各种边界情况。
117-121: 明确空模型处理逻辑
roleAllowsModel对任何 falsy 值(如'')返回true,而在chat.js的updateModelHint中,modelSelect.value可能为'',此时前端不会提示限制,但后端会因空模型拒绝请求。请确认是否应将空字符串视为无效模型并在前端进行拦截。
KimmyXYC/pkg/auth/token.go
Outdated
| var defaultSecret = []byte("dev-secret-change-me") | ||
|
|
||
| func jwtSecret() []byte { | ||
| if s := os.Getenv("JWT_SECRET"); s != "" { | ||
| return []byte(s) | ||
| } | ||
| return defaultSecret | ||
| } | ||
|
|
||
| // Claims represents JWT claims for a user session. | ||
| type Claims struct { | ||
| UserID uint `json:"user_id"` | ||
| Email string `json:"email"` | ||
| Role string `json:"role"` | ||
| jwt.RegisteredClaims | ||
| } | ||
|
|
||
| // CreateToken issues a signed JWT for the given user. | ||
| func CreateToken(userID uint, email, role string, ttl time.Duration) (string, error) { | ||
| claims := Claims{ | ||
| UserID: userID, | ||
| Email: email, | ||
| Role: role, | ||
| RegisteredClaims: jwt.RegisteredClaims{ | ||
| ExpiresAt: jwt.NewNumericDate(time.Now().Add(ttl)), | ||
| IssuedAt: jwt.NewNumericDate(time.Now()), | ||
| }, | ||
| } | ||
| t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | ||
| return t.SignedString(jwtSecret()) | ||
| } | ||
|
|
||
| // ParseToken parses and validates a JWT, returning claims if valid. | ||
| func ParseToken(token string) (*Claims, error) { | ||
| tok, err := jwt.ParseWithClaims(token, &Claims{}, func(t *jwt.Token) (interface{}, error) { | ||
| return jwtSecret(), nil | ||
| }) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if !tok.Valid { | ||
| return nil, errors.New("invalid token") | ||
| } | ||
| claims, ok := tok.Claims.(*Claims) | ||
| if !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
禁止在生产环境使用硬编码默认 JWT 密钥
当前实现未配置 JWT_SECRET 时会退回到众所周知的常量 dev-secret-change-me。一旦部署端忘记设置环境变量,攻击者即可用该已知密钥伪造任意角色的令牌,直接拿到系统完全控制权,这是阻断级安全漏洞。请改为强制要求配置密钥,缺失时立即报错并阻止服务继续。可以参考以下修复思路:
-func jwtSecret() []byte {
- if s := os.Getenv("JWT_SECRET"); s != "" {
- return []byte(s)
- }
- return defaultSecret
-}
+func jwtSecret() ([]byte, error) {
+ s := os.Getenv("JWT_SECRET")
+ if s == "" {
+ return nil, errors.New("JWT_SECRET is not configured")
+ }
+ return []byte(s), nil
+}
@@
-func CreateToken(userID uint, email, role string, ttl time.Duration) (string, error) {
+func CreateToken(userID uint, email, role string, ttl time.Duration) (string, error) {
+ key, err := jwtSecret()
+ if err != nil {
+ return "", err
+ }
@@
- t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
- return t.SignedString(jwtSecret())
+ t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return t.SignedString(key)
}
@@
-func ParseToken(token string) (*Claims, error) {
- tok, err := jwt.ParseWithClaims(token, &Claims{}, func(t *jwt.Token) (interface{}, error) {
- return jwtSecret(), nil
+func ParseToken(token string) (*Claims, error) {
+ key, err := jwtSecret()
+ if err != nil {
+ return nil, err
+ }
+ tok, err := jwt.ParseWithClaims(token, &Claims{}, func(t *jwt.Token) (interface{}, error) {
+ return key, nil
})这样可以在服务启动路径上尽早发现配置缺失,并避免使用公开常量导致的令牌伪造风险。
Committable suggestion skipped: line range outside the PR's diff.
|
@coderabbitai generate docstrings |
✅ Actions performedDocstrings generation started. |
Docstrings generation was requested by @KimmyXYC. * #8 (comment) The following files were modified: * `KimmyXYC/cmd/server/main.go` * `KimmyXYC/internal/db/db.go` * `KimmyXYC/internal/httpserver/router.go` * `KimmyXYC/internal/provider/openai.go` * `KimmyXYC/internal/provider/provider.go` * `KimmyXYC/internal/services/auth_service.go` * `KimmyXYC/internal/services/chat_service.go` * `KimmyXYC/pkg/auth/token.go` * `KimmyXYC/pkg/middleware/auth.go` * `KimmyXYC/web/js/api.js` * `KimmyXYC/web/js/auth.js` * `KimmyXYC/web/js/chat.js` * `KimmyXYC/web/js/main.js` * `KimmyXYC/web/js/state.js`
|
Note Generated docstrings for this pull request at #9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
KimmyXYC/go.mod(1 hunks)KimmyXYC/internal/db/db.go(1 hunks)KimmyXYC/internal/provider/openai.go(1 hunks)KimmyXYC/internal/services/chat_service.go(1 hunks)KimmyXYC/pkg/auth/token.go(1 hunks)KimmyXYC/web/js/chat.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- KimmyXYC/internal/db/db.go
🧰 Additional context used
🧬 Code graph analysis (3)
KimmyXYC/internal/provider/openai.go (1)
KimmyXYC/internal/provider/provider.go (2)
ChatMessage(11-14)StreamChunk(17-21)
KimmyXYC/internal/services/chat_service.go (2)
KimmyXYC/internal/provider/provider.go (2)
LLMProvider(24-27)ChatMessage(11-14)KimmyXYC/internal/models/models.go (2)
Conversation(22-33)Message(36-45)
KimmyXYC/web/js/chat.js (2)
KimmyXYC/web/js/state.js (1)
getUser(16-20)KimmyXYC/web/js/api.js (6)
roleAllowsModel(117-121)listConversations(38-40)convId(73-73)getMessages(42-44)sendChat(46-52)chatStream(55-109)
🪛 OSV Scanner (2.2.3)
KimmyXYC/go.mod
[CRITICAL] 1-1: golang.org/x/crypto 0.27.0: Misuse of connection.serverAuthenticate may cause authorization bypass in golang.org/x/crypto
(GO-2024-3321)
[CRITICAL] 1-1: golang.org/x/crypto 0.27.0: Potential denial of service in golang.org/x/crypto
(GO-2025-3487)
[CRITICAL] 1-1: golang.org/x/crypto 0.27.0: golang.org/x/crypto Vulnerable to Denial of Service (DoS) via Slow or Incomplete Key Exchange
[CRITICAL] 1-1: golang.org/x/crypto 0.27.0: Misuse of ServerConfig.PublicKeyCallback may cause authorization bypass in golang.org/x/crypto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
KimmyXYC/go.mod(1 hunks)KimmyXYC/web/js/chat.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- KimmyXYC/web/js/chat.js
🧰 Additional context used
🪛 OSV Scanner (2.2.3)
KimmyXYC/go.mod
[HIGH] 1-1: golang.org/x/crypto 0.31.0: Potential denial of service in golang.org/x/crypto
(GO-2025-3487)
[HIGH] 1-1: golang.org/x/crypto 0.31.0: golang.org/x/crypto Vulnerable to Denial of Service (DoS) via Slow or Incomplete Key Exchange
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
KimmyXYC/go.mod (1)
1-1: 模块路径请与仓库路径保持一致当前
module AIBackend使用了裸名字,外部若想通过go get或作为依赖引入,会因为无法解析该模块路径而失败;本地 tooling(如 IDE 跳转、代码生成)也会遇到麻烦。建议改成仓库实际地址,便于后续复用与发布。-module AIBackend +module github.com/hduhelp/backend_2025_freshman_task
Summary by CodeRabbit