Skip to content
Merged
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
Binary file added AmberJc1/.gitignore
Binary file not shown.
103 changes: 95 additions & 8 deletions AmberJc1/controllers/chat.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package controllers

import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"

"houduan_from/config"
Expand All @@ -13,7 +18,7 @@ import (
"github.com/golang-jwt/jwt/v5"
)

// 用户注册
// ====================== 用户注册 ======================
func Register(c *gin.Context) {
var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
Expand All @@ -26,7 +31,7 @@ func Register(c *gin.Context) {
return
}

// 对密码做 MD5 加密
// 密码 MD5 加密
h := md5.New()
h.Write([]byte(user.Password))
user.Password = hex.EncodeToString(h.Sum(nil))
Expand All @@ -39,7 +44,7 @@ func Register(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "注册成功"})
}

// 用户登录
// ====================== 用户登录 ======================
func Login(c *gin.Context) {
var loginData struct {
Username string `json:"username"`
Expand All @@ -50,7 +55,7 @@ func Login(c *gin.Context) {
return
}

// 把输入的密码 MD5 加密
// 对密码做 MD5 加密再验证
h := md5.New()
h.Write([]byte(loginData.Password))
password := hex.EncodeToString(h.Sum(nil))
Expand All @@ -75,7 +80,7 @@ func Login(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"token": tokenString})
}

// JWT 鉴权中间件
// ====================== JWT 鉴权中间件 ======================
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
Expand Down Expand Up @@ -104,19 +109,101 @@ func AuthMiddleware() gin.HandlerFunc {
}
}

// 聊天接口
// ====================== 聊天接口(接入火山引擎) ======================
func Chat(c *gin.Context) {
var chatData struct {
Question string `json:"question"`
ImageURL string `json:"image_url"`
}
if err := c.ShouldBindJSON(&chatData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}

userID := c.GetInt("user_id")
answer := "这是AI的回答:" + chatData.Question

apiKey := os.Getenv("VOLC_API_KEY")
apiURL := os.Getenv("VOLC_API_URL")
model := os.Getenv("VOLC_MODEL_ID")

if apiKey == "" || apiURL == "" || model == "" {
c.JSON(http.StatusInternalServerError, gin.H{"error": "火山引擎配置缺失"})
return
}

// 构建消息内容
content := []map[string]interface{}{}
if chatData.ImageURL != "" {
content = append(content, map[string]interface{}{
"type": "image_url",
"image_url": map[string]string{
"url": chatData.ImageURL,
},
})
}
content = append(content, map[string]interface{}{
"type": "text",
"text": chatData.Question,
})

// 构建请求体
reqBody := map[string]interface{}{
"model": model,
"messages": []map[string]interface{}{
{
"role": "user",
"content": content,
},
},
}

bodyBytes, _ := json.Marshal(reqBody)

req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(bodyBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)

resp, err := http.DefaultClient.Do(req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "调用火山引擎失败", "detail": err.Error()})
return
}
defer resp.Body.Close()
Comment on lines +166 to +171
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 | 🟠 Major

为外部 HTTP 调用设置超时

http.DefaultClient 默认无超时,若火山接口网络抖动或响应挂起,请求会一直阻塞占用 Gin 处理线程,造成雪崩风险。建议自建 &http.Client{Timeout: ...} 或为请求设置 context.WithTimeout

-	resp, err := http.DefaultClient.Do(req)
+	client := &http.Client{Timeout: 10 * time.Second}
+	resp, err := client.Do(req)

按需调整超时并复用 client。 -->

🤖 Prompt for AI Agents
In AmberJc1/controllers/chat.go around lines 166 to 171, the code uses
http.DefaultClient.Do(req) which has no timeout; change it to use a client with
a timeout or perform the request with a context that has a deadline to avoid
blocking Gin handlers: create or reuse an &http.Client{Timeout: <reasonable
duration>} (or wrap the request with context.WithTimeout and pass
req.WithContext(ctx)), use that client to Do the request, handle the context
timeout error and still defer resp.Body.Close() only after a successful
response; ensure the client is reused (e.g., package-level variable) instead of
creating a new client per request if appropriate.


body, _ := io.ReadAll(resp.Body)

if resp.StatusCode != http.StatusOK {
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("火山接口错误: %s", resp.Status),
"detail": string(body),
})
return
}

// 解析响应
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "解析火山返回失败", "detail": err.Error()})
return
}

// 提取回答内容
answer := "AI暂无回复"
if choices, ok := result["choices"].([]interface{}); ok && len(choices) > 0 {
if choice, ok := choices[0].(map[string]interface{}); ok {
if message, ok := choice["message"].(map[string]interface{}); ok {
if contentArr, ok := message["content"].([]interface{}); ok && len(contentArr) > 0 {
if first, ok := contentArr[0].(map[string]interface{}); ok {
if text, ok := first["text"].(string); ok {
answer = text
}
}
}
}
}
}

// 保存聊天记录
chat := models.Chat{
UserID: uint(userID),
Question: chatData.Question,
Expand All @@ -130,7 +217,7 @@ func Chat(c *gin.Context) {
})
}

// 获取聊天记录
// ====================== 获取聊天记录 ======================
func GetChatHistory(c *gin.Context) {
userID := c.GetInt("user_id")
var chats []models.Chat
Expand Down
Empty file added AmberJc1/frontend/.env
Empty file.
12 changes: 12 additions & 0 deletions AmberJc1/frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chat Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading
Loading