Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions KimmyXYC/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# PostgreSQL connection string
DATABASE_URL=postgres://postgres:postgres@localhost:5432/aibackend?sslmode=disable

# JWT secret for signing tokens
JWT_SECRET=change-me

# HTTP listen address
ADDR=:8080

# OpenAI provider configuration (optional; if OPENAI_API_KEY is set, OpenAI provider is used)
OPENAI_API_KEY=
# Custom API base, e.g. https://api.openai.com or your proxy endpoint
OPENAI_API_BASE=

# (Optional) provider keys
# VOLC_API_KEY=your-volcengine-key
99 changes: 99 additions & 0 deletions KimmyXYC/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
AIBackend - 简易AI问答后端 (Golang + PostgreSQL)

简介
- 使用 Gin + GORM + PostgreSQL 实现的简易问答后端,支持:
- 用户注册/登录(JWT)
- 会话与消息存储(PostgreSQL)
- 基于角色的模型访问控制(free/pro/admin)
- 上下文对话与流式输出(SSE)
- 可插拔的模型提供方接口(默认 Mock,便于本地演示)

快速开始
1) 准备 Postgres 并创建数据库,例如:aibackend

2) 配置环境变量(可创建 .env 文件)
- DATABASE_URL=postgres://<user>:<pass>@localhost:5432/aibackend?sslmode=disable
- JWT_SECRET=change-me
- ADDR=:8080

3) 运行
- go run ./cmd/server

4) 健康检查
- GET http://localhost:8080/health -> {"status":"ok"}

API 文档
- 见 docs/api.md,包含注册、登录、会话、聊天(支持 SSE 流式)等接口说明与 curl 示例。

模型提供方
- 默认使用 MockProvider(无需外部 Key,本地直接演示)。
- OpenAI 兼容:当设置 OPENAI_API_KEY 时,自动切换为 OpenAI Chat Completions 协议(支持流式)。
- 环境变量:
- OPENAI_API_KEY=你的密钥
- OPENAI_API_BASE=自定义 Endpoint(可选,默认 https://api.openai.com)
- Chat 请求将发送到 {OPENAI_API_BASE}/v1/chat/completions(stream=true),并解析 SSE 的 data: 行,提取 choices[0].delta.content。
- 若需接入火山引擎(Volcengine)或其他厂商:
1. 在 internal/provider 中实现 LLMProvider 接口。
2. 在 NewProviderFromEnv 中根据环境变量选择对应 Provider。
3. 在 ChatService 中无需改动,保持调用接口不变。

项目结构
- cmd/server/main.go 程序入口
- internal/db 数据库连接与迁移
- internal/models GORM 模型(User/Conversation/Message)
- internal/provider 模型提供方接口与 Mock 实现
- internal/services Auth 与 Chat 业务逻辑
- internal/httpserver Gin 路由与 HTTP 处理器
- pkg/auth JWT 生成与解析
- pkg/middleware Gin 中间件(鉴权与模型权限)
- docs/api.md API 文档

角色与模型权限(示例)
- free: [mock-mini]
- pro: [mock-mini, mock-pro]
- admin: [mock-mini, mock-pro, mock-admin]

注意
- 初次运行会自动迁移数据库表结构。
- 流式输出采用 SSE(text/event-stream)。
- 若未设置 DATABASE_URL,程序会尝试使用本地默认串,但数据库必须实际可连接。



前端(Web)
- 本项目内置了一个简单的 Web 前端,覆盖了后端的全部核心功能:
- 注册/登录(JWT 持久化在 localStorage)
- 查看会话列表、查看会话消息
- 发起聊天(支持非流式与流式输出)
- 模型选择(会提示当前角色可用的模型范围,越权将被后端拦截)
- 代码位置:web/
- index.html 页面骨架
- css/styles.css 样式
- js/state.js 本地状态与 Token 管理
- js/api.js 封装所有后端 API 调用(含流式解析)
- js/auth.js 登录与注册表单逻辑
- js/chat.js 会话/消息渲染与发送消息(含流式处理)
- js/main.js 应用入口与视图切换

如何使用前端
1) 启动后端:
- go run ./cmd/server
2) 打开浏览器访问:
- http://localhost:8080
3) 在首页进行注册或登录:
- 你可以选择角色(free/pro/admin),不同角色允许的模型不同:
- free: [mock-mini]
- pro: [mock-mini, mock-pro]
- admin:[mock-mini, mock-pro, mock-admin]
4) 进入应用后:
- 左侧为会话列表,可点击切换;
- 右侧为消息区与输入框;
- 顶部可切换模型、开启/关闭“流式输出”;
- 点击“+ 新建对话”开始新的会话;
- 输入问题后按“发送”即可,开启“流式输出”时可看到回复逐步出现。

注意事项
- 前端与后端同域部署(由 Gin 静态服务提供),无需额外配置 CORS;
- 流式回复采用 fetch ReadableStream 解析服务端的 text/event-stream(后端使用 POST 返回 SSE 风格数据,无法直接用 EventSource,因此用 fetch 流解析);
- 若模型越权,后端会返回 403,前端会在输入区下方给出提示;
- Mock 模型会回显你的输入,便于本地验证体验。
44 changes: 44 additions & 0 deletions KimmyXYC/cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"log"
"os"

"github.com/joho/godotenv"

"AIBackend/internal/db"
"AIBackend/internal/httpserver"
"AIBackend/internal/provider"
)

func main() {
// Load .env if present (dev convenience)
_ = godotenv.Load()

// Initialize DB
pgURL := os.Getenv("DATABASE_URL")
if pgURL == "" {
log.Println("WARNING: DATABASE_URL is not set. The server may fail to start when DB is required.")
}
gormDB, err := db.Connect(pgURL)
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
if err := db.AutoMigrate(gormDB); err != nil {
log.Fatalf("failed to migrate database: %v", err)
}

// Initialize LLM provider (Mock by default)
llm := provider.NewProviderFromEnv()

// Start HTTP server
r := httpserver.NewRouter(gormDB, llm)
addr := os.Getenv("ADDR")
if addr == "" {
addr = ":8080"
}
log.Printf("Server listening on %s", addr)
if err := r.Run(addr); err != nil {
log.Fatalf("server error: %v", err)
}
}
49 changes: 49 additions & 0 deletions KimmyXYC/docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
API Documentation

Base URL: http://localhost:8080

Auth
- POST /api/auth/register
Request JSON: { "email": "[email protected]", "password": "pass123", "role": "free|pro|admin" }
Response: { "user": {id, email, role, created_at}, "token": "JWT" }

- POST /api/auth/login
Request JSON: { "email": "[email protected]", "password": "pass123" }
Response: { "user": {...}, "token": "JWT" }

Use Authorization: Bearer <token> for all protected endpoints below.

User
- GET /api/me
Response: { "user_id": 1, "user_email": "[email protected]", "user_role": "free" }

Conversations
- GET /api/conversations
Response: { "conversations": [ {id, title, model, created_at, updated_at}, ... ] }

- GET /api/conversations/:id/messages
Response: { "messages": [ {id, role, content, created_at}, ... ] }

Chat
- POST /api/chat
Request JSON: { "conversation_id": 0, "model": "mock-mini", "message": "hello", "stream": false }
Response (non-stream): { "conversation_id": 1, "reply": "..." }

Streaming: set stream=true or ?stream=1 and use text/event-stream (SSE).
Example curl:
curl -N -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"model":"mock-mini","message":"你好","stream":true}' \
http://localhost:8080/api/chat

Models and Permissions
- Roles and allowed models:
- free: [mock-mini]
- pro: [mock-mini, mock-pro]
- admin: [mock-mini, mock-pro, mock-admin]

Notes
- Default provider is Mock (no external key). If OPENAI_API_KEY is set, the backend switches to OpenAI-compatible Chat Completions API.
- Env:
- OPENAI_API_KEY=your-key
- OPENAI_API_BASE=custom endpoint (optional; default https://api.openai.com)
- To integrate another provider (e.g., 火山引擎/Volcengine), implement provider.LLMProvider and update provider.NewProviderFromEnv().
49 changes: 49 additions & 0 deletions KimmyXYC/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module AIBackend

go 1.23

require (
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.27.0
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.11
)

require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
32 changes: 32 additions & 0 deletions KimmyXYC/internal/db/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package db

import (
"fmt"

"gorm.io/driver/postgres"
"gorm.io/gorm"

"AIBackend/internal/models"
)

// Connect opens a PostgreSQL connection using DATABASE_URL.
func Connect(databaseURL string) (*gorm.DB, error) {
if databaseURL == "" {
return nil, fmt.Errorf("DATABASE_URL is required")
}
dsn := databaseURL
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("connect postgres: %w", err)
}
return db, nil
}

// AutoMigrate applies database schema for all models.
func AutoMigrate(db *gorm.DB) error {
return db.AutoMigrate(
&models.User{},
&models.Conversation{},
&models.Message{},
)
}
Loading