Skip to content

Commit 3e0dc0c

Browse files
authored
Merge pull request #8 from KimmyXYC/main
Add: KimmyXYC LLM Backend
2 parents 2858ded + 17506aa commit 3e0dc0c

File tree

21 files changed

+1655
-0
lines changed

21 files changed

+1655
-0
lines changed

KimmyXYC/.env.example

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# PostgreSQL connection string
2+
DATABASE_URL=postgres://postgres:postgres@localhost:5432/aibackend?sslmode=disable
3+
4+
# JWT secret for signing tokens
5+
JWT_SECRET=change-me
6+
7+
# HTTP listen address
8+
ADDR=:8080
9+
10+
# OpenAI provider configuration (optional; if OPENAI_API_KEY is set, OpenAI provider is used)
11+
OPENAI_API_KEY=
12+
# Custom API base, e.g. https://api.openai.com or your proxy endpoint
13+
OPENAI_API_BASE=
14+
15+
# (Optional) provider keys
16+
# VOLC_API_KEY=your-volcengine-key

KimmyXYC/README.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
AIBackend - 简易AI问答后端 (Golang + PostgreSQL)
2+
3+
简介
4+
- 使用 Gin + GORM + PostgreSQL 实现的简易问答后端,支持:
5+
- 用户注册/登录(JWT)
6+
- 会话与消息存储(PostgreSQL)
7+
- 基于角色的模型访问控制(free/pro/admin)
8+
- 上下文对话与流式输出(SSE)
9+
- 可插拔的模型提供方接口(默认 Mock,便于本地演示)
10+
11+
快速开始
12+
1) 准备 Postgres 并创建数据库,例如:aibackend
13+
14+
2) 配置环境变量(可创建 .env 文件)
15+
- DATABASE_URL=postgres://<user>:<pass>@localhost:5432/aibackend?sslmode=disable
16+
- JWT_SECRET=change-me
17+
- ADDR=:8080
18+
19+
3) 运行
20+
- go run ./cmd/server
21+
22+
4) 健康检查
23+
- GET http://localhost:8080/health -> {"status":"ok"}
24+
25+
API 文档
26+
- 见 docs/api.md,包含注册、登录、会话、聊天(支持 SSE 流式)等接口说明与 curl 示例。
27+
28+
模型提供方
29+
- 默认使用 MockProvider(无需外部 Key,本地直接演示)。
30+
- OpenAI 兼容:当设置 OPENAI_API_KEY 时,自动切换为 OpenAI Chat Completions 协议(支持流式)。
31+
- 环境变量:
32+
- OPENAI_API_KEY=你的密钥
33+
- OPENAI_API_BASE=自定义 Endpoint(可选,默认 https://api.openai.com)
34+
- Chat 请求将发送到 {OPENAI_API_BASE}/v1/chat/completions(stream=true),并解析 SSE 的 data: 行,提取 choices[0].delta.content。
35+
- 若需接入火山引擎(Volcengine)或其他厂商:
36+
1. 在 internal/provider 中实现 LLMProvider 接口。
37+
2. 在 NewProviderFromEnv 中根据环境变量选择对应 Provider。
38+
3. 在 ChatService 中无需改动,保持调用接口不变。
39+
40+
项目结构
41+
- cmd/server/main.go 程序入口
42+
- internal/db 数据库连接与迁移
43+
- internal/models GORM 模型(User/Conversation/Message)
44+
- internal/provider 模型提供方接口与 Mock 实现
45+
- internal/services Auth 与 Chat 业务逻辑
46+
- internal/httpserver Gin 路由与 HTTP 处理器
47+
- pkg/auth JWT 生成与解析
48+
- pkg/middleware Gin 中间件(鉴权与模型权限)
49+
- docs/api.md API 文档
50+
51+
角色与模型权限(示例)
52+
- free: [mock-mini]
53+
- pro: [mock-mini, mock-pro]
54+
- admin: [mock-mini, mock-pro, mock-admin]
55+
56+
注意
57+
- 初次运行会自动迁移数据库表结构。
58+
- 流式输出采用 SSE(text/event-stream)。
59+
- 若未设置 DATABASE_URL,程序会尝试使用本地默认串,但数据库必须实际可连接。
60+
61+
62+
63+
前端(Web)
64+
- 本项目内置了一个简单的 Web 前端,覆盖了后端的全部核心功能:
65+
- 注册/登录(JWT 持久化在 localStorage)
66+
- 查看会话列表、查看会话消息
67+
- 发起聊天(支持非流式与流式输出)
68+
- 模型选择(会提示当前角色可用的模型范围,越权将被后端拦截)
69+
- 代码位置:web/
70+
- index.html 页面骨架
71+
- css/styles.css 样式
72+
- js/state.js 本地状态与 Token 管理
73+
- js/api.js 封装所有后端 API 调用(含流式解析)
74+
- js/auth.js 登录与注册表单逻辑
75+
- js/chat.js 会话/消息渲染与发送消息(含流式处理)
76+
- js/main.js 应用入口与视图切换
77+
78+
如何使用前端
79+
1) 启动后端:
80+
- go run ./cmd/server
81+
2) 打开浏览器访问:
82+
- http://localhost:8080
83+
3) 在首页进行注册或登录:
84+
- 你可以选择角色(free/pro/admin),不同角色允许的模型不同:
85+
- free: [mock-mini]
86+
- pro: [mock-mini, mock-pro]
87+
- admin:[mock-mini, mock-pro, mock-admin]
88+
4) 进入应用后:
89+
- 左侧为会话列表,可点击切换;
90+
- 右侧为消息区与输入框;
91+
- 顶部可切换模型、开启/关闭“流式输出”;
92+
- 点击“+ 新建对话”开始新的会话;
93+
- 输入问题后按“发送”即可,开启“流式输出”时可看到回复逐步出现。
94+
95+
注意事项
96+
- 前端与后端同域部署(由 Gin 静态服务提供),无需额外配置 CORS;
97+
- 流式回复采用 fetch ReadableStream 解析服务端的 text/event-stream(后端使用 POST 返回 SSE 风格数据,无法直接用 EventSource,因此用 fetch 流解析);
98+
- 若模型越权,后端会返回 403,前端会在输入区下方给出提示;
99+
- Mock 模型会回显你的输入,便于本地验证体验。

KimmyXYC/cmd/server/main.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"os"
6+
7+
"github.com/joho/godotenv"
8+
9+
"AIBackend/internal/db"
10+
"AIBackend/internal/httpserver"
11+
"AIBackend/internal/provider"
12+
)
13+
14+
func main() {
15+
// Load .env if present (dev convenience)
16+
_ = godotenv.Load()
17+
18+
// Initialize DB
19+
pgURL := os.Getenv("DATABASE_URL")
20+
if pgURL == "" {
21+
log.Println("WARNING: DATABASE_URL is not set. The server may fail to start when DB is required.")
22+
}
23+
gormDB, err := db.Connect(pgURL)
24+
if err != nil {
25+
log.Fatalf("failed to connect database: %v", err)
26+
}
27+
if err := db.AutoMigrate(gormDB); err != nil {
28+
log.Fatalf("failed to migrate database: %v", err)
29+
}
30+
31+
// Initialize LLM provider (Mock by default)
32+
llm := provider.NewProviderFromEnv()
33+
34+
// Start HTTP server
35+
r := httpserver.NewRouter(gormDB, llm)
36+
addr := os.Getenv("ADDR")
37+
if addr == "" {
38+
addr = ":8080"
39+
}
40+
log.Printf("Server listening on %s", addr)
41+
if err := r.Run(addr); err != nil {
42+
log.Fatalf("server error: %v", err)
43+
}
44+
}

KimmyXYC/docs/api.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
API Documentation
2+
3+
Base URL: http://localhost:8080
4+
5+
Auth
6+
- POST /api/auth/register
7+
Request JSON: { "email": "[email protected]", "password": "pass123", "role": "free|pro|admin" }
8+
Response: { "user": {id, email, role, created_at}, "token": "JWT" }
9+
10+
- POST /api/auth/login
11+
Request JSON: { "email": "[email protected]", "password": "pass123" }
12+
Response: { "user": {...}, "token": "JWT" }
13+
14+
Use Authorization: Bearer <token> for all protected endpoints below.
15+
16+
User
17+
- GET /api/me
18+
Response: { "user_id": 1, "user_email": "[email protected]", "user_role": "free" }
19+
20+
Conversations
21+
- GET /api/conversations
22+
Response: { "conversations": [ {id, title, model, created_at, updated_at}, ... ] }
23+
24+
- GET /api/conversations/:id/messages
25+
Response: { "messages": [ {id, role, content, created_at}, ... ] }
26+
27+
Chat
28+
- POST /api/chat
29+
Request JSON: { "conversation_id": 0, "model": "mock-mini", "message": "hello", "stream": false }
30+
Response (non-stream): { "conversation_id": 1, "reply": "..." }
31+
32+
Streaming: set stream=true or ?stream=1 and use text/event-stream (SSE).
33+
Example curl:
34+
curl -N -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
35+
-d '{"model":"mock-mini","message":"你好","stream":true}' \
36+
http://localhost:8080/api/chat
37+
38+
Models and Permissions
39+
- Roles and allowed models:
40+
- free: [mock-mini]
41+
- pro: [mock-mini, mock-pro]
42+
- admin: [mock-mini, mock-pro, mock-admin]
43+
44+
Notes
45+
- Default provider is Mock (no external key). If OPENAI_API_KEY is set, the backend switches to OpenAI-compatible Chat Completions API.
46+
- Env:
47+
- OPENAI_API_KEY=your-key
48+
- OPENAI_API_BASE=custom endpoint (optional; default https://api.openai.com)
49+
- To integrate another provider (e.g., 火山引擎/Volcengine), implement provider.LLMProvider and update provider.NewProviderFromEnv().

KimmyXYC/go.mod

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module AIBackend
2+
3+
go 1.23
4+
5+
require (
6+
github.com/gin-gonic/gin v1.10.0
7+
github.com/golang-jwt/jwt/v5 v5.2.2
8+
github.com/joho/godotenv v1.5.1
9+
golang.org/x/crypto v0.42.0
10+
gorm.io/driver/postgres v1.5.9
11+
gorm.io/gorm v1.25.11
12+
)
13+
14+
require (
15+
github.com/bytedance/sonic v1.11.6 // indirect
16+
github.com/bytedance/sonic/loader v0.1.1 // indirect
17+
github.com/cloudwego/base64x v0.1.4 // indirect
18+
github.com/cloudwego/iasm v0.2.0 // indirect
19+
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
20+
github.com/gin-contrib/sse v0.1.0 // indirect
21+
github.com/go-playground/locales v0.14.1 // indirect
22+
github.com/go-playground/universal-translator v0.18.1 // indirect
23+
github.com/go-playground/validator/v10 v10.20.0 // indirect
24+
github.com/goccy/go-json v0.10.2 // indirect
25+
github.com/jackc/pgpassfile v1.0.0 // indirect
26+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
27+
github.com/jackc/pgx/v5 v5.5.5 // indirect
28+
github.com/jackc/puddle/v2 v2.2.1 // indirect
29+
github.com/jinzhu/inflection v1.0.0 // indirect
30+
github.com/jinzhu/now v1.1.5 // indirect
31+
github.com/json-iterator/go v1.1.12 // indirect
32+
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
33+
github.com/kr/text v0.2.0 // indirect
34+
github.com/leodido/go-urn v1.4.0 // indirect
35+
github.com/mattn/go-isatty v0.0.20 // indirect
36+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
37+
github.com/modern-go/reflect2 v1.0.2 // indirect
38+
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
39+
github.com/rogpeppe/go-internal v1.14.1 // indirect
40+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
41+
github.com/ugorji/go/codec v1.2.12 // indirect
42+
golang.org/x/arch v0.8.0 // indirect
43+
golang.org/x/net v0.25.0 // indirect
44+
golang.org/x/sync v0.8.0 // indirect
45+
golang.org/x/sys v0.26.0 // indirect
46+
golang.org/x/text v0.17.0 // indirect
47+
google.golang.org/protobuf v1.34.1 // indirect
48+
gopkg.in/yaml.v3 v3.0.1 // indirect
49+
)

KimmyXYC/internal/db/db.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package db
2+
3+
import (
4+
"fmt"
5+
6+
"gorm.io/driver/postgres"
7+
"gorm.io/gorm"
8+
9+
"AIBackend/internal/models"
10+
)
11+
12+
// Connect opens a PostgreSQL connection using DATABASE_URL.
13+
func Connect(databaseURL string) (*gorm.DB, error) {
14+
if databaseURL == "" {
15+
return nil, fmt.Errorf("DATABASE_URL is required")
16+
}
17+
dsn := databaseURL
18+
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
19+
if err != nil {
20+
return nil, fmt.Errorf("connect postgres: %w", err)
21+
}
22+
return db, nil
23+
}
24+
25+
// AutoMigrate applies database schema for all models.
26+
func AutoMigrate(db *gorm.DB) error {
27+
return db.AutoMigrate(
28+
&models.User{},
29+
&models.Conversation{},
30+
&models.Message{},
31+
)
32+
}

0 commit comments

Comments
 (0)