Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ZaeXT/configs/config.yaml
.DS_Store
ZaeXT/frontend/.codex
239 changes: 239 additions & 0 deletions ZaeXT/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@


## ✨ 核心功能

- **用户账户系统**:
- **用户注册与登录**(密码使用 bcrypt 加密)。
- 基于 **JWT (JSON Web Token)** 的无状态 API 认证。
- **对话体验**:
- 支持与大语言模型(**火山引擎方舟大模型**)进行**流式对话 (SSE)**。
- **上下文记忆**,支持流畅的多轮对话。
- **用户记忆功能**,允许用户保存个人信息,让 AI 提供更具个性化的回答。
- **模型权限管理**:
- **多等级模型访问**:可配置不同用户等级(如 `free`, `premium`)可使用的 AI 模型。
- **继承式权限**:高级用户自动获得所有低级用户的模型使用权限。
- **对话管理**:
- **AI 自动生成标题**:在新对话开始时,后台会自动调用 AI 为对话生成一个简洁的摘要标题。
- **多级对话分类**:用户可以创建树状结构的分类来组织对话。
- **AI 自动分类**:一键调用 AI,智能地将当前对话归入最合适的分类。
- **数据管理**:
- **对话回收站**:删除的对话会先进入回收站,可恢复或永久删除。
- **自动清理机制**:后台定时任务(Cron Job)会自动永久删除回收站中的过期对话(默认30天)。
- **架构**:
- **分层架构** (Handler, Service, Repository)。
- 支持**安全停机**,确保服务在关停时数据的一致性和安全性。

## 🛠️ 技术栈

- **语言**: Go
- **Web 框架**: Gin
- **数据库 ORM**: GORM
- **配置管理**: Viper
- **API 认证**: JWT for Go
- **定时任务**: robfig/cron
- **密码加密**: golang.org/x/crypto/bcrypt

## 🚀 快速开始

### 1. 先决条件

- Go (版本 1.18 或更高)
- MySQL 或 PostgreSQL 数据库
- 一个有效的火山引擎方舟大模型 API Key

### 2. 配置

1. 将项目克隆到本地:
```bash
git clone https://github.com/ZaeXT/backend_2025_freshman_task.git
cd backend_2025_freshman_task/ZaeXT
```
2. 复制配置文件模板:
```bash
cp configs/config.yaml.example configs/config.yaml
```
3. 编辑 `configs/config.yaml` 文件,填入您的配置信息:
- **`database`**: 数据库连接信息。
- **`jwt.secret`**: 设置一个长且随机的 JWT 密钥。
- **`volcengine.api_key`**: 填入火山引擎 API Key。
- **`volcengine.available_models`**: 根据火山引擎平台开通的模型,配置模型 ID、名称和访问等级。

### 3. 安装依赖

```bash
go mod tidy
```

### 4. 运行服务

```bash
go run cmd/server/main.go
```
服务将在 `config.yaml` 中配置的端口上启动(默认为 `8080`)。

## 📁 项目结构

本项目采用清晰、可扩展的分层架构。主要目录结构和职责如下:

```
.
├── cmd/server/ # 程序主入口
│ └── main.go # 负责初始化所有组件并启动HTTP服务
├── configs/ # 配置文件
│ ├── config.yaml # 项目的核心配置文件
│ └── config.yaml.example # 配置模板
├── internal/ # 项目内部代码 (核心逻辑)
│ ├── adapter/ # 适配器层 (与外部服务交互)
│ │ └── volcengine/ # 封装火山引擎API的客户端
│ ├── handler/ # HTTP处理层 (控制器)
│ │ ├── middleware/ # 中间件 (如: JWT认证)
│ │ ├── request/ # 定义请求体的JSON结构
│ │ ├── response/ # 定义响应体的JSON结构
│ │ └── *.go # 具体的业务模块Handler
│ ├── model/ # 数据模型层 (GORM模型)
│ ├── pkg/ # 内部共享工具包
│ │ ├── e/ # 错误码定义
│ │ ├── hash/ # 密码加密
│ │ └── jwt/ # JWT生成与解析
│ ├── repository/ # 数据仓库层 (数据库操作)
│ │ └── db/ # 数据库初始化
│ ├── service/ # 业务逻辑层 (核心业务处理)
│ └── tasks/ # 定时任务
├── test-scripts.py # 功能测试脚本
├── go.mod # Go模块依赖
└── README.md # 项目说明
```

---

### 代码分层逻辑

- **请求流**: 一个HTTP请求会从 `handler` 进入,`handler` 调用 `service` 来处理业务逻辑,`service` 调用 `repository` 来操作数据库,`repository` 使用 `model` 来映射数据表。
- **外部服务调用**: 当 `service` 需要调用外部AI服务时,它会通过 `adapter` 层,`adapter` 负责处理所有与第三方API的通信细节。
- **关注点分离**: 每一层都只关心自己的职责,例如 `repository` 只管数据库的增删改查,而不知道这些操作是因何而起;`service` 只管业务规则,而不知道数据具体是如何存储的。
- **可测试性**: 这种清晰的分层使得每一层都可以被独立测试,极大地提高了代码质量和可维护性。


---


## 📖 API 文档

所有需要认证的 API 都需要在请求头中包含 `Authorization` 字段,格式为 `Bearer [your_jwt_token]`。

---

### 认证 (Auth)

- `POST /api/v1/register`
- **功能**: 注册一个新用户。
- **请求体**: `{"username": "your_username", "password": "your_password"}`
- **成功响应**: `200 OK`
- `POST /api/v1/login`
- **功能**: 用户登录。
- **请求体**: `{"username": "your_username", "password": "your_password"}`
- **成功响应**: `200 OK`, `{"data": {"token": "..."}}`

---

### 用户 (User)

- `GET /api/v1/profile`
- **功能**: 获取当前登录用户的个人信息。
- **成功响应**: `200 OK`, `{"data": {"id": 1, "username": "...", "tier": "free", ...}}`
- `PUT /api/v1/profile/memory`
- **功能**: 更新用户的记忆信息。
- **请求体**: `{"memory_info": "我是...,我喜欢..."}`
- **成功响应**: `200 OK`

---

### AI 模型 (Models)

- `GET /api/v1/models`
- **功能**: 获取当前用户可用的所有 AI 模型列表。
- **成功响应**: `200 OK`, `{"data": [{"id": "...", "name": "..."}, ...]}`

---

### 对话 (Conversations)

- `POST /api/v1/conversations`
- **功能**: 创建一个新的对话。
- **请求体 (可选)**: `{"is_temporary": false, "category_id": 123}`
- **成功响应**: `200 OK`, `{"data": {"id": 1, "title": "New Chat", ...}}`
- `GET /api/v1/conversations`
- **功能**: 获取当前用户的所有对话列表。
- **成功响应**: `200 OK`, `{"data": [...]}`
- `POST /api/v1/conversations/:id/messages`
- **功能**: 在指定对话中发送消息并获取**流式响应**。
- **请求体**: `{"message": "你好", "model_id": "your_chosen_model_id"}`
- **成功响应**: `200 OK` (SSE stream)
- `PUT /api/v1/conversations/:id/title`
- **功能**: 手动更新对话标题。
- **请求体**: `{"title": "我的新标题"}`
- **成功响应**: `200 OK`
- `POST /api/v1/conversations/:id/auto-classify`
- **功能**: 请求 AI 自动为该对话进行分类。
- **成功响应**: `200 OK`
- `DELETE /api/v1/conversations/:id`
- **功能**: 将对话移入回收站(软删除)。
- **成功响应**: `200 OK`

---

### 分类 (Categories)

- `POST /api/v1/categories`
- **功能**: 创建一个新的分类。
- **请求体**: `{"name": "我的分类", "parent_id": 456}` (`parent_id` 可选)
- **成功响应**: `200 OK`, `{"data": {"id": 1, "name": "...", ...}}`
- `GET /api/v1/categories`
- **功能**: 获取用户的所有分类(树状结构)。
- **成功响应**: `200 OK`, `{"data": [...]}`
- `PUT /api/v1/categories/:id`
- **功能**: 更新一个分类。
- **请求体**: `{"name": "新名字", "parent_id": 789}`
- **成功响应**: `200 OK`
- `DELETE /api/v1/categories/:id`
- **功能**: 删除一个分类及其所有子分类(级联删除)。
- **成功响应**: `200 OK`

---

### 回收站 (Recycle Bin)

- `GET /api/v1/recycle-bin`
- **功能**: 查看回收站中的所有对话。
- **成功响应**: `200 OK`, `{"data": [...]}`
- `POST /api/v1/recycle-bin/restore/:id`
- **功能**: 从回收站恢复一个对话。
- **成功响应**: `200 OK`
- `DELETE /api/v1/recycle-bin/permanent/:id`
- **功能**: 永久删除一个对话。
- **成功响应**: `200 OK`

## 🧪 测试

项目内置了多套 Python 测试脚本,用于端到端地验证所有功能和安全性。

1. **安装测试依赖**:
```bash
pip install requests sseclient-py
```
2. **运行功能测试**: (测试所有成功路径)
```bash
python test_backend.py
```
3. **运行失败与安全测试**: (测试错误处理和权限隔离)
```bash
python test_failures.py
```
4. **运行模型权限继承测试**: (测试不同等级模型调用限制与权限继承)
```bash
python test_premissions.py
```
5. **运行分类递归删除测试**: (测试删除父级分类时自动删除子分类)
```bash
python test_cascade_delete.py
```
75 changes: 75 additions & 0 deletions ZaeXT/cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"ai-qa-backend/internal/adapter/volcengine"
"ai-qa-backend/internal/configs"
"ai-qa-backend/internal/handler"
"ai-qa-backend/internal/repository"
"ai-qa-backend/internal/repository/db"
"ai-qa-backend/internal/service"
"ai-qa-backend/internal/tasks"
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

func main() {
if err := configs.Init(); err != nil {
log.Fatalf("Failed to initialize configuration: %v", err)
}
gormDB, err := db.Init()
if err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}

repos := repository.NewRepository(gormDB)

aiAdapter := volcengine.NewVolcengineAdapter()

services := service.NewService(repos, aiAdapter)

cronScheduler := tasks.StartCronJobs(services)

router := handler.SetupRouter(services)

addr := fmt.Sprintf(":%d", configs.Conf.Server.Port)
srv := &http.Server{
Addr: addr,
Handler: router,
}

go func() {
log.Printf("Server listening on %s", addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cronCtx := cronScheduler.Stop()
<-cronCtx.Done()
log.Println("Cron scheduler stopped.")

if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}

sqlDB, err := gormDB.DB()
if err == nil {
sqlDB.Close()
log.Println("Database connection closed.")
}
Comment on lines +68 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

检查数据库关闭错误

sqlDB.Close() 的返回值应该被检查。关闭连接时可能会失败,忽略错误会导致资源泄漏问题难以诊断。

应用此 diff 来检查关闭错误:

 	sqlDB, err := gormDB.DB()
 	if err == nil {
-		sqlDB.Close()
+		if err := sqlDB.Close(); err != nil {
+			log.Printf("Error closing database connection: %v", err)
+		}
 		log.Println("Database connection closed.")
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sqlDB, err := gormDB.DB()
if err == nil {
sqlDB.Close()
log.Println("Database connection closed.")
}
sqlDB, err := gormDB.DB()
if err == nil {
if err := sqlDB.Close(); err != nil {
log.Printf("Error closing database connection: %v", err)
}
log.Println("Database connection closed.")
}
🤖 Prompt for AI Agents
In ZaeXT/cmd/server/main.go around lines 68 to 72, the call to sqlDB.Close()
ignores its returned error; update the code to capture the error returned by
Close(), check if it is non-nil, and log or handle it (e.g., log.Printf("error
closing DB: %v", err)) so connection close failures are not silently ignored;
retain the existing check that gormDB.DB() succeeded before calling Close().


log.Println("Server exited successfully.")
}
40 changes: 40 additions & 0 deletions ZaeXT/configs/config.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
server:
port: 8080 # 应用监听的端口
mode: "debug" # 运行模式: debug, release, test

database:
host: "127.0.0.1"
port: 3306
user: "root"
password: "YOUR_DATABASE_PASSWORD"
dbname: "aichat_system"
max_open_conns: 100 # 最大打开连接数
max_idle_conns: 10 # 最大空闲连接数

jwt:
secret: "REPLACE_WITH_A_LONG_RANDOM_STRING_IN_PRODUCTION"
expiration: "72h" # Token 有效期,支持单位: s, m, h

volcengine:
api_key: "***-***-***-***-***"
base_url: "https://ark.cn-beijing.volces.com/api/v3"

available_models:
- id: "ep-xxx-xxx"
name: "豆包 1.6 lite" # 用于前端展示的名称
tier: "free"

- id: "ep-xxx-xxx"
name: "豆包 1.6 flash"
tier: "premium" # 最低 'premium' 等级用户才可使用

- id: "ep-xxx-xxx"
name: "豆包 1.6"
tier: "pro"

log:
level: "info" # 日志级别: debug, info, warn, error
format: "text" # 日志格式: text, json

recycle_bin:
retention_days: 30 # 回收站对话保留天数
1 change: 1 addition & 0 deletions ZaeXT/frontend/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_API_BASE=http://localhost:8080/api/v1
1 change: 1 addition & 0 deletions ZaeXT/frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_API_BASE=http://localhost:8080/api/v1
6 changes: 6 additions & 0 deletions ZaeXT/frontend/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
dist
coverage
*.config.cjs
*.config.js
public
Loading