优化: 前后端字段对齐、AI分析JSON解析修复、AutoMigrate

- 修复登录参数 username→account
- 修复前后端字段名不匹配 (ticketno/contactname/createtime等)
- 修复AI分析GLM返回markdown包裹和priority类型问题
- 添加AutoMigrate自动建表
- 统一API路由为 /api/auth/ 前缀
- 添加config.example.yaml,.gitignore排除config.yaml
This commit is contained in:
2026-05-13 18:53:53 +08:00
parent 4793b1a533
commit c5c2a64a48
17 changed files with 3034 additions and 13 deletions

98
backend/README.md Normal file
View File

@@ -0,0 +1,98 @@
# Ticket Workbench Backend
Go Gin 工单管理系统后端服务。
## 环境要求
- Go 1.24+
- MySQL 5.7+
## 配置文件
编辑 `config.yaml`:
```yaml
server:
port: 8090
db:
host: 39.99.243.191
port: 3306
user: root
password: Lake@2019
dbname: ticket_dev
glm:
api_key: 7f83dc939a60488b8cf48a2ee1c8150e.NY3aOR0qlVS8m37a
base_url: https://open.bigmodel.cn/api/paas/v4
model: glm-4-flash
```
## 启动方式
```bash
# 安装依赖
go mod tidy
# 运行
go run .
# 编译
go build -o ticket-workbench.exe .
# 运行编译后的程序
./ticket-workbench.exe
```
## API 接口
### 认证
- `POST /api/login` - 登录
- `POST /api/logout` - 登出
- `GET /api/user/info` - 当前用户信息
### 工单
- `GET /api/tickets` - 工单列表
- `POST /api/tickets` - 创建工单
- `GET /api/tickets/:id` - 工单详情
- `PUT /api/tickets/:id` - 更新工单
- `PUT /api/tickets/:id/status` - 更新状态
### AI 分析
- `POST /api/tickets/:id/analyze` - 触发 AI 分析
- `GET /api/tickets/:id/analysis` - 获取分析结果
- `PUT /api/tickets/:id/analysis` - 确认/修改分析结果
### 备注
- `GET /api/tickets/:id/notes` - 获取备注列表
- `POST /api/tickets/:id/notes` - 添加备注
### 日志
- `GET /api/tickets/:id/logs` - 获取操作日志
## 认证方式
所有 API除登录/登出外)需要通过 Authorization header 或 jsessionid cookie 携带 token。
```
Authorization: {token}
```
## 项目结构
```
backend/
├── main.go # 入口文件
├── config.yaml # 配置文件
├── go.mod / go.sum # 依赖管理
└── internal/
├── config/ # 配置读取
├── model/ # GORM 模型
├── dto/ # 请求/响应 DTO
├── handler/ # Gin handler
├── service/ # 业务逻辑
└── middleware/ # 中间件
```

View File

@@ -0,0 +1,12 @@
server:
port: 8091
db:
host: 127.0.0.1
port: 3306
user: root
password: your_password
dbname: ticket_dev
glm:
api_key: your_api_key
base_url: https://open.bigmodel.cn/api/paas/v4
model: glm-4-flash

View File

@@ -30,9 +30,9 @@ type UpdateStatusRequest struct {
}
type TicketListQuery struct {
Status int16 `form:"status"`
Status *int16 `form:"status"`
Category string `form:"category"`
Priority int16 `form:"priority"`
Priority *int16 `form:"priority"`
Keyword string `form:"keyword"`
Page int `form:"page"`
PageSize int `form:"pageSize"`

View File

@@ -25,6 +25,7 @@ func Auth(db *gorm.DB) gin.HandlerFunc {
c.Set("userid", user.Userid)
c.Set("username", user.Username)
c.Set("account", user.Account)
c.Set("role", user.Role)
c.Set("team", user.Team)
c.Next()

View File

@@ -4,13 +4,17 @@ import (
"crypto/md5"
"encoding/hex"
"fmt"
"sync"
"github.com/casehub/ticket-workbench/internal/model"
"github.com/google/uuid"
"gorm.io/gorm"
)
var sessions = make(map[string]*model.TicketUser)
var (
sessions = make(map[string]*model.TicketUser)
sessionMu sync.RWMutex
)
func MD5Hash(text string) string {
hash := md5.Sum([]byte(text))
@@ -32,15 +36,21 @@ func Login(db *gorm.DB, account, password string) (*model.TicketUser, string, er
}
sessionID := uuid.New().String()
sessionMu.Lock()
sessions[sessionID] = &user
sessionMu.Unlock()
return &user, sessionID, nil
}
func Logout(sessionID string) {
sessionMu.Lock()
delete(sessions, sessionID)
sessionMu.Unlock()
}
func GetUserBySession(sessionID string) *model.TicketUser {
sessionMu.RLock()
defer sessionMu.RUnlock()
return sessions[sessionID]
}

View File

@@ -14,20 +14,20 @@ type TicketListResult struct {
Rows []model.TicketInfo `json:"rows"`
}
func ListTickets(db *gorm.DB, status int16, category string, priority int16, keyword string, page, pageSize int) (*TicketListResult, error) {
func ListTickets(db *gorm.DB, status *int16, category string, priority *int16, keyword string, page, pageSize int) (*TicketListResult, error) {
var total int64
var tickets []model.TicketInfo
query := db.Model(&model.TicketInfo{})
if status >= 0 {
query = query.Where("status = ?", status)
if status != nil {
query = query.Where("status = ?", *status)
}
if category != "" {
query = query.Where("category = ?", category)
}
if priority >= 0 {
query = query.Where("priority = ?", priority)
if priority != nil {
query = query.Where("priority = ?", *priority)
}
if keyword != "" {
query = query.Where("title LIKE ? OR content LIKE ?", "%"+keyword+"%", "%"+keyword+"%")

View File

@@ -8,6 +8,7 @@ import (
"github.com/casehub/ticket-workbench/internal/config"
"github.com/casehub/ticket-workbench/internal/handler"
"github.com/casehub/ticket-workbench/internal/middleware"
"github.com/casehub/ticket-workbench/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
@@ -42,6 +43,16 @@ func main() {
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
if err := db.AutoMigrate(
&model.TicketUser{},
&model.TicketInfo{},
&model.TicketAiAnalysis{},
&model.TicketNote{},
&model.TicketOperationLog{},
); err != nil {
log.Fatalf("Failed to auto migrate: %v", err)
}
gin.SetMode(gin.ReleaseMode)
r := gin.Default()