26a61cb57c
## 🐛 Bug 修复 - 修复前端对话无响应:消除 ChatContainer 中的双重 WebSocket 连接,优化 sendMessage 失败提示 - 修复 Memory-Service 数据库迁移失败:ai-core 和 memory-service 均添加 ALTER TABLE ADD COLUMN IF NOT EXISTS 模式演化 - 修复语音/STT 不可用:添加 MediaRecorder API 降级方案,修复 whisper-cli 输出文件名错误 - 修复仪表盘数据库按钮失效:补充按钮 ID 属性,重写 controlDB() 控制逻辑 ## 🎨 UI 修复 - 修正用户消息头像位置:从 flex-row-reverse 改为 justify-end - 移除空聊天列表的 emoji 占位图标 ## ✨ 新功能 - devtools 新增 STT 处理日志面板(环形缓冲区 + WebSocket 广播 + 可视化表格) - 新增 ADMIN_NICKNAME 环境变量,支持自定义管理员昵称 ## 🔧 改进 - 注册流程增加昵称必填字段(前后端同步) ## 🏗️ 架构重构 - 重构自主思考逻辑:从定时器轮询改为事件驱动(对话后触发 + 静默检测),优化提示词使其更自然人性化 - 实现主-子会话架构:新增 4 种子会话类型(general/memory/iot/knowledge),意图分析 → 并行分发 → 结果合成流程 ## 📄 新增文档 - docs/architecture/main-session-sub-session-design.md — 子会话架构设计文档
198 lines
4.7 KiB
Go
198 lines
4.7 KiB
Go
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"time"
|
||
|
||
"github.com/golang-jwt/jwt/v5"
|
||
)
|
||
|
||
// Config 应用配置
|
||
type Config struct {
|
||
Env string
|
||
Port string
|
||
|
||
// 数据库
|
||
PostgresHost string
|
||
PostgresPort string
|
||
PostgresUser string
|
||
PostgresPass string
|
||
PostgresDB string
|
||
|
||
// Redis
|
||
RedisHost string
|
||
RedisPort string
|
||
RedisPass string
|
||
|
||
// JWT
|
||
JWTSecret string
|
||
JWTExpiryHours time.Duration
|
||
|
||
// 管理员账户 (开发阶段使用)
|
||
AdminUsername string
|
||
AdminPassword string
|
||
AdminNickname string // 昔涟对用户的基本称呼
|
||
|
||
// 注册开关
|
||
RegistrationEnabled bool
|
||
|
||
// AI-Core 服务
|
||
AICoreURL string
|
||
|
||
// Memory 服务
|
||
MemoryServiceURL string
|
||
|
||
// IoT 调试服务
|
||
IoTDebugServiceURL string
|
||
|
||
// Voice 语音识别服务
|
||
VoiceServiceURL string
|
||
|
||
// Tool-Engine 工具引擎服务
|
||
ToolEngineURL string
|
||
|
||
// LLM (透传给AI-Core,Gateway可能也需要)
|
||
LLMAPIURL string
|
||
LLMAPIKey string
|
||
LLMModel string
|
||
|
||
// WebSocket
|
||
WSMaxConnections int
|
||
|
||
// 会话闲置超时 (分钟) — 超过此时间后会话标记为 idle 但不删除
|
||
SessionIdleTimeoutMin int
|
||
|
||
// Webhook (第三方平台接入)
|
||
WebhookAPIKey string
|
||
|
||
// Internal Service Token (内部服务间认证)
|
||
InternalServiceToken string
|
||
|
||
// 每日简报时间 (HH:MM 格式)
|
||
BriefingTime string
|
||
}
|
||
|
||
// Load 从环境变量加载配置
|
||
func Load() *Config {
|
||
return &Config{
|
||
Env: getEnv("ENV", "development"),
|
||
Port: getEnv("GATEWAY_PORT", "8080"),
|
||
|
||
PostgresHost: getEnv("POSTGRES_HOST", "localhost"),
|
||
PostgresPort: getEnv("POSTGRES_PORT", "5432"),
|
||
PostgresUser: getEnv("POSTGRES_USER", "cyrene"),
|
||
PostgresPass: getEnv("POSTGRES_PASSWORD", "change_me"),
|
||
PostgresDB: getEnv("POSTGRES_DB", "cyrene_ai"),
|
||
|
||
RedisHost: getEnv("REDIS_HOST", "localhost"),
|
||
RedisPort: getEnv("REDIS_PORT", "6379"),
|
||
RedisPass: getEnv("REDIS_PASSWORD", ""),
|
||
|
||
JWTSecret: getEnv("JWT_SECRET", "change-me-in-production"),
|
||
JWTExpiryHours: time.Duration(getEnvInt("JWT_EXPIRY_HOURS", 720)) * time.Hour,
|
||
|
||
// 管理员账户 (开发阶段使用)
|
||
AdminUsername: getEnv("ADMIN_USERNAME", "admin"),
|
||
AdminPassword: getEnv("ADMIN_PASSWORD", "cyrene-dev-admin"),
|
||
AdminNickname: getEnv("ADMIN_NICKNAME", "管理员"),
|
||
|
||
// 注册开关 (开发阶段默认关闭)
|
||
RegistrationEnabled: getEnvBool("REGISTRATION_ENABLED", false),
|
||
|
||
AICoreURL: getEnv("AI_CORE_URL", "http://localhost:8081"),
|
||
|
||
MemoryServiceURL: getEnv("MEMORY_SERVICE_URL", "http://localhost:8091"),
|
||
|
||
IoTDebugServiceURL: getEnv("IOT_DEBUG_SERVICE_URL", "http://localhost:8083"),
|
||
|
||
VoiceServiceURL: getEnv("VOICE_SERVICE_URL", "http://localhost:8093"),
|
||
|
||
ToolEngineURL: getEnv("TOOL_ENGINE_URL", "http://localhost:8092"),
|
||
|
||
LLMAPIURL: getEnv("LLM_API_URL", "https://api.openai.com/v1"),
|
||
LLMAPIKey: getEnv("LLM_API_KEY", ""),
|
||
LLMModel: getEnv("LLM_MODEL", "gpt-4o"),
|
||
|
||
WSMaxConnections: getEnvInt("WS_MAX_CONNECTIONS", 1000),
|
||
SessionIdleTimeoutMin: getEnvInt("SESSION_IDLE_TIMEOUT_MIN", 30),
|
||
|
||
WebhookAPIKey: getEnv("WEBHOOK_API_KEY", ""),
|
||
InternalServiceToken: getEnv("INTERNAL_SERVICE_TOKEN", "cyrene-internal-token-change-me"),
|
||
|
||
BriefingTime: getEnv("BRIEFING_TIME", "08:00"),
|
||
}
|
||
}
|
||
|
||
// DatabaseURL 构建 PostgreSQL 连接字符串
|
||
func (c *Config) DatabaseURL() string {
|
||
return fmt.Sprintf(
|
||
"postgres://%s:%s@%s:%s/%s?sslmode=disable",
|
||
c.PostgresUser, c.PostgresPass,
|
||
c.PostgresHost, c.PostgresPort,
|
||
c.PostgresDB,
|
||
)
|
||
}
|
||
|
||
// GenerateToken 生成JWT token
|
||
func (c *Config) GenerateToken(userID string) (string, error) {
|
||
claims := jwt.MapClaims{
|
||
"user_id": userID,
|
||
"exp": time.Now().Add(c.JWTExpiryHours).Unix(),
|
||
"iat": time.Now().Unix(),
|
||
}
|
||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
return token.SignedString([]byte(c.JWTSecret))
|
||
}
|
||
|
||
// ValidateToken 验证JWT token
|
||
func (c *Config) ValidateToken(tokenString string) (string, error) {
|
||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||
return nil, jwt.ErrSignatureInvalid
|
||
}
|
||
return []byte(c.JWTSecret), nil
|
||
})
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
claims, ok := token.Claims.(jwt.MapClaims)
|
||
if !ok || !token.Valid {
|
||
return "", jwt.ErrSignatureInvalid
|
||
}
|
||
|
||
userID, _ := claims["user_id"].(string)
|
||
return userID, nil
|
||
}
|
||
|
||
func getEnv(key, fallback string) string {
|
||
if v := os.Getenv(key); v != "" {
|
||
return v
|
||
}
|
||
return fallback
|
||
}
|
||
|
||
func getEnvInt(key string, fallback int) int {
|
||
v := os.Getenv(key)
|
||
if v == "" {
|
||
return fallback
|
||
}
|
||
var result int
|
||
for _, c := range v {
|
||
if c < '0' || c > '9' {
|
||
return fallback
|
||
}
|
||
result = result*10 + int(c-'0')
|
||
}
|
||
return result
|
||
}
|
||
|
||
func getEnvBool(key string, fallback bool) bool {
|
||
v := os.Getenv(key)
|
||
if v == "" {
|
||
return fallback
|
||
}
|
||
return v == "true" || v == "1" || v == "yes"
|
||
}
|