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.
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.
禁止在生产环境使用硬编码默认 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.
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.
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.
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