Version: 0.4.4.dev.260307
feat: 🚀 增强会话管理与缓存机制 * 会话 ID 空值兜底,若 `conversation_id` 为空时自动生成 UUID * 在响应头写入 `X-Conversation-ID`,供前端使用,保持同一会话状态 perf: ⚡ 会话状态缓存优化 * 当缓存未命中但 DB 已确认/创建会话后,调用 `SetConversationStatus` 回写 Redis * 缓存写回失败时记录日志,不中断聊天主流程,确保业务流畅性 fix: 🐛 修复历史消息顺序问题与编译错误 * 修复历史消息顺序问题,保证返回的 N 条历史消息按时间正序喂给模型 * 通过反转 `created_at desc` 查询结果的切片,确保模型输入顺序正确 * 修复 `fmt.Errorf` 参数不匹配问题,修正编译错误 * 整理 `agent-cache.go` 为标准 UTF-8 编码,避免 Go 编译报错 `invalid UTF-8 encoding` feat: 🛠️ 独立构建 MCP 服务器 * 使用 `Codex` 构建独立于后端的 MCP 服务器,简化与 Codex 的协作 * 通过该服务器方便 Codex 直接测试和查看 Redis 与 MySQL 中的数据
This commit is contained in:
163
infra/smartflow-mcp-server/internal/config/config.go
Normal file
163
infra/smartflow-mcp-server/internal/config/config.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServerName string
|
||||
ServerVersion string
|
||||
ProtocolVersion string
|
||||
DefaultCaller string
|
||||
ToolTimeout time.Duration
|
||||
RateLimitRPS float64
|
||||
RateLimitBurst float64
|
||||
MaxResultRows int
|
||||
AuditLogPath string
|
||||
EnforceWhitelist bool
|
||||
RedisScanMaxKeys int
|
||||
RedisScanMaxCount int
|
||||
RedisValueMaxItems int
|
||||
RedisMaxStringBytes int
|
||||
|
||||
MySQL MySQLConfig
|
||||
Redis RedisConfig
|
||||
}
|
||||
|
||||
type MySQLConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
Params string
|
||||
AllowedDatabases []string
|
||||
AllowedTables []string
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Addr string
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
func LoadFromEnv() (Config, error) {
|
||||
cfg := Config{
|
||||
ServerName: getEnv("MCP_SERVER_NAME", "smartflow-mcp-server"),
|
||||
ServerVersion: getEnv("MCP_SERVER_VERSION", "0.1.0"),
|
||||
ProtocolVersion: getEnv("MCP_PROTOCOL_VERSION", "2024-11-05"),
|
||||
DefaultCaller: getEnv("MCP_DEFAULT_CALLER", "unknown"),
|
||||
ToolTimeout: getEnvDurationMS("MCP_TOOL_TIMEOUT_MS", 5000),
|
||||
RateLimitRPS: getEnvFloat("MCP_RATE_LIMIT_RPS", 5),
|
||||
RateLimitBurst: getEnvFloat("MCP_RATE_LIMIT_BURST", 10),
|
||||
MaxResultRows: getEnvInt("MCP_MAX_RESULT_ROWS", 500),
|
||||
AuditLogPath: getEnv("MCP_AUDIT_LOG_PATH", "logs/audit.log"),
|
||||
EnforceWhitelist: getEnvBool("MCP_ENFORCE_WHITELIST", false),
|
||||
RedisScanMaxKeys: getEnvInt("MCP_REDIS_SCAN_MAX_KEYS", 200),
|
||||
RedisScanMaxCount: getEnvInt("MCP_REDIS_SCAN_MAX_COUNT", 200),
|
||||
RedisValueMaxItems: getEnvInt("MCP_REDIS_VALUE_MAX_ITEMS", 100),
|
||||
RedisMaxStringBytes: getEnvInt("MCP_REDIS_MAX_STRING_BYTES", 4096),
|
||||
MySQL: MySQLConfig{
|
||||
Host: getEnv("MYSQL_HOST", "127.0.0.1"),
|
||||
Port: getEnvInt("MYSQL_PORT", 3306),
|
||||
User: getEnv("MYSQL_USER", ""),
|
||||
Password: getEnv("MYSQL_PASSWORD", ""),
|
||||
Database: getEnv("MYSQL_DATABASE", ""),
|
||||
Params: getEnv("MYSQL_PARAMS", "charset=utf8mb4&parseTime=true&loc=Local"),
|
||||
AllowedDatabases: splitCommaList(getEnv("MYSQL_ALLOWED_DATABASES", "")),
|
||||
AllowedTables: splitCommaList(getEnv("MYSQL_ALLOWED_TABLES", "")),
|
||||
},
|
||||
Redis: RedisConfig{
|
||||
Addr: getEnv("REDIS_ADDR", "127.0.0.1:6379"),
|
||||
Password: getEnv("REDIS_PASSWORD", ""),
|
||||
DB: getEnvInt("REDIS_DB", 0),
|
||||
},
|
||||
}
|
||||
|
||||
if cfg.MySQL.User == "" || cfg.MySQL.Database == "" {
|
||||
return Config{}, fmt.Errorf("MYSQL_USER and MYSQL_DATABASE are required")
|
||||
}
|
||||
if cfg.Redis.Addr == "" {
|
||||
return Config{}, fmt.Errorf("REDIS_ADDR is required")
|
||||
}
|
||||
if cfg.MaxResultRows <= 0 {
|
||||
return Config{}, fmt.Errorf("MCP_MAX_RESULT_ROWS must be > 0")
|
||||
}
|
||||
if cfg.RedisScanMaxKeys <= 0 {
|
||||
return Config{}, fmt.Errorf("MCP_REDIS_SCAN_MAX_KEYS must be > 0")
|
||||
}
|
||||
if cfg.RedisScanMaxCount <= 0 {
|
||||
return Config{}, fmt.Errorf("MCP_REDIS_SCAN_MAX_COUNT must be > 0")
|
||||
}
|
||||
if cfg.ToolTimeout <= 0 {
|
||||
return Config{}, fmt.Errorf("MCP_TOOL_TIMEOUT_MS must be > 0")
|
||||
}
|
||||
if cfg.RateLimitRPS <= 0 || cfg.RateLimitBurst <= 0 {
|
||||
return Config{}, fmt.Errorf("MCP_RATE_LIMIT_RPS and MCP_RATE_LIMIT_BURST must be > 0")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func getEnv(key string, defaultValue string) string {
|
||||
if v, ok := os.LookupEnv(key); ok {
|
||||
return strings.TrimSpace(v)
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvInt(key string, defaultValue int) int {
|
||||
v := getEnv(key, "")
|
||||
if v == "" {
|
||||
return defaultValue
|
||||
}
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func getEnvFloat(key string, defaultValue float64) float64 {
|
||||
v := getEnv(key, "")
|
||||
if v == "" {
|
||||
return defaultValue
|
||||
}
|
||||
n, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func getEnvBool(key string, defaultValue bool) bool {
|
||||
v := strings.ToLower(getEnv(key, ""))
|
||||
if v == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return v == "1" || v == "true" || v == "yes" || v == "on"
|
||||
}
|
||||
|
||||
func getEnvDurationMS(key string, defaultValueMs int) time.Duration {
|
||||
ms := getEnvInt(key, defaultValueMs)
|
||||
return time.Duration(ms) * time.Millisecond
|
||||
}
|
||||
|
||||
func splitCommaList(raw string) []string {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(raw, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
trimmed := strings.TrimSpace(p)
|
||||
if trimmed != "" {
|
||||
out = append(out, strings.ToLower(trimmed))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user