Initial qiwei secondary development handoff

This commit is contained in:
2026-06-23 21:11:20 +08:00
commit 858cb68f4f
207 changed files with 52782 additions and 0 deletions

856
helper/http_server.go Normal file
View File

@@ -0,0 +1,856 @@
package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"qiweimanager/config"
)
var (
httpServer *http.Server
httpServerMutex sync.Mutex
httpPort = 10001 // REST API服务端口默认值
)
// getHTTPPort 从配置中获取HTTP端口
func getHTTPPort() int {
// 获取全局配置
appConfig := config.GetGlobalConfig()
if appConfig != nil && appConfig.CallbackConfig.HTTPPort != "" {
// 尝试将字符串端口转换为整数
port, err := strconv.Atoi(appConfig.CallbackConfig.HTTPPort)
if err == nil && port > 0 && port <= 65535 {
return port
}
}
// 使用默认值
return 10001
}
// startHTTPServer 启动HTTP服务器提供REST API接口
func startHTTPServer() error {
// 从配置获取端口号
httpPort = getHTTPPort()
// 创建路由器
router := http.NewServeMux()
// 注册Web管理页面
router.HandleFunc("/", handleDashboardPage)
router.HandleFunc("/dashboard", handleDashboardPage)
router.HandleFunc("/api/dashboard/state", handleDashboardState)
router.HandleFunc("/api/dashboard/messages", handleDashboardMessages)
router.HandleFunc("/api/debug/clients", handleDebugClients)
router.HandleFunc("/api/debug/clients/", handleDebugClientIdentify)
router.HandleFunc("/api/wxwork/new-instance", handleWxWorkNewInstance)
registerAutoReplyRoutes(router)
registerAfterSalesRoutes(router)
registerKingdeeMonitorRoutes(router)
// 注册SendWxWorkData接口
router.HandleFunc("/api/send-wxwork-data", handleSendWxWorkDataHTTP)
// 注册第三方请求接口
router.HandleFunc("/api/third-party-request", handleThirdPartyRequest)
// 注册健康检查接口
router.HandleFunc("/api/health", handleHealthCheck)
// 创建HTTP服务器
httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", httpPort),
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
// 保存到全局变量
httpServerMutex.Lock()
defer httpServerMutex.Unlock()
globalLogger.Info("[辅助程序] HTTP REST服务器启动在端口 %d", httpPort)
// 检查端口是否被占用
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", httpPort))
if err != nil {
globalLogger.Error("[错误] 检查端口可用性失败: %v", err)
return fmt.Errorf("端口 %d 已被占用或无法绑定", httpPort)
}
// 关闭临时监听器
listener.Close()
// 启动HTTP服务器非阻塞
go func() {
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
globalLogger.Error("[错误] HTTP服务器运行失败: %v", err)
// 创建错误日志文件
errFile, err := os.OpenFile("http_server_error.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
fmt.Fprintf(errFile, "[%s] HTTP服务器运行失败: %v\n", timestamp, err)
errFile.Close()
}
}
}()
// 等待服务器启动
time.Sleep(500 * time.Millisecond)
// 执行简单的健康检查以确认服务器已启动
globalLogger.Info("[辅助程序] 开始执行健康检查...")
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://localhost:%d/api/health", httpPort), nil)
if err != nil {
globalLogger.Error("[错误] 创建健康检查请求失败: %v", err)
// 创建错误日志文件
errFile, logErr := os.OpenFile("http_server_health_check_error.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if logErr == nil {
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
fmt.Fprintf(errFile, "[%s] 创建健康检查请求失败: %v\n", timestamp, err)
errFile.Close()
}
return fmt.Errorf("无法创建健康检查请求: %v", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
globalLogger.Error("[错误] HTTP服务器启动后健康检查失败: %v", err)
// 创建错误日志文件
errFile, logErr := os.OpenFile("http_server_health_check_error.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if logErr == nil {
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
fmt.Fprintf(errFile, "[%s] 健康检查请求失败: %v\n", timestamp, err)
errFile.Close()
}
return fmt.Errorf("服务器启动失败,健康检查不通过: %v", err)
}
globalLogger.Info("[辅助程序] 健康检查响应状态码: %s", resp.Status)
resp.Body.Close()
globalLogger.Info("[辅助程序] HTTP REST服务器启动成功健康检查通过")
// 创建成功日志文件
/*successFile, logErr := os.OpenFile("http_server_start_success.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if logErr == nil {
timestamp := time.Now().Format("2006-01-02 15:04:05.000")
fmt.Fprintf(successFile, "[%s] HTTP服务器启动成功健康检查通过\n", timestamp)
successFile.Close()
}*/
return nil
}
// handleSendWxWorkDataHTTP 处理发送企业微信数据的HTTP请求
func handleSendWxWorkDataHTTP(w http.ResponseWriter, r *http.Request) {
// 只允许POST请求
if r.Method != "POST" {
http.Error(w, "只支持POST请求", http.StatusMethodNotAllowed)
return
}
// 设置CORS头
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Content-Type", "application/json")
// 读取并记录HTTP请求内容
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
globalLogger.Error("读取HTTP请求内容失败: %v", err)
} else {
// 将请求体内容重新设置回r.Body因为ReadAll会消费掉它
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
globalLogger.Info("HTTP请求内容: %s", string(bodyBytes))
}
// 解析请求体
var requestBody map[string]interface{}
err = json.NewDecoder(r.Body).Decode(&requestBody)
if err != nil {
globalLogger.Error("[错误] 解析HTTP请求体失败: %v", err)
sendJSONResponse(w, http.StatusBadRequest, map[string]interface{}{
"success": false,
"error": "请求体格式错误",
})
return
}
// 从请求体中获取clientId和data
//clientIdValue, clientIdExists := requestBody["clientId"]
dataValue, dataExists := requestBody["data"]
if !dataExists {
globalLogger.Error("[错误] 缺少data参数")
sendJSONResponse(w, http.StatusBadRequest, map[string]interface{}{
"success": false,
"error": "缺少data参数",
})
return
}
// 处理dataValue为字符串或JSON对象的情况
var jsonData string
// 检查是否为字符串类型
if strValue, ok := dataValue.(string); ok {
jsonData = strValue
} else if objValue, ok := dataValue.(map[string]interface{}); ok {
// 如果是JSON对象将其转换为JSON字符串
jsonBytes, err := json.Marshal(objValue)
if err != nil {
globalLogger.Error("[错误] 转换JSON对象失败: %v", err)
sendJSONResponse(w, http.StatusBadRequest, map[string]interface{}{
"success": false,
"error": "转换JSON对象失败",
})
return
}
jsonData = string(jsonBytes)
} else {
globalLogger.Error("[错误] data参数类型错误: %T", dataValue)
sendJSONResponse(w, http.StatusBadRequest, map[string]interface{}{
"success": false,
"error": "data参数类型错误支持字符串和JSON对象",
})
return
}
// 处理clientId
clientId := uint32(0) // 默认值
// 从globalClientMap获取对应的客户端ID
if clientIdValue, exists := requestBody["clientId"]; exists {
// 尝试转换clientId为uint32
clientIdStr, ok := clientIdValue.(string)
if ok {
if parsed, parseErr := strconv.ParseUint(strings.TrimSpace(clientIdStr), 10, 32); parseErr == nil {
clientId = uint32(parsed)
globalLogger.Info("[辅助程序] 从请求体获取字符串数字clientId: %d", clientId)
} else {
clientIdMutex.Lock()
for cId, userId := range globalClientMap {
if userId == clientIdStr {
clientId = cId
globalLogger.Info("[辅助程序] 从globalClientMap获取clientId: %d -> %s", clientId, userId)
break
}
}
clientIdMutex.Unlock()
}
} else if clientIdNum, ok := clientIdValue.(float64); ok {
// 如果是数字类型
clientId = uint32(clientIdNum)
globalLogger.Info("[辅助程序] 从请求体获取数字clientId: %d", clientId)
}
} else {
// 如果没有提供clientId使用第一个活跃的客户端
clientId = getFirstAvailableClientID()
if clientId == 0 {
clientId = 1
globalLogger.Info("[辅助程序] 使用默认clientId: %d", clientId)
} else {
globalLogger.Info("[辅助程序] 使用第一个可用客户端: %d", clientId)
}
}
// 记录请求日志
globalLogger.Info("[辅助程序] 收到HTTP SendWxWorkData请求客户端ID: %d, 数据: %s", clientId, jsonData)
recordDashboardMessage(int32(clientId), "outgoing", jsonData, nil, "api-request")
// 检查是否需要等待回调响应
// 解析jsonData获取type字段
var requestData map[string]interface{}
needCallback := true
err = json.Unmarshal([]byte(jsonData), &requestData)
if err == nil {
if requestType, ok := requestData["type"]; ok {
// 对于特定类型的请求,我们需要等待回调响应
// 例如请求类型为11035的企微账号信息请求
if typeInt, ok := requestType.(float64); ok && (typeInt == 10000 || typeInt == 10002 || typeInt == 11170) {
needCallback = false
}
}
}
if needCallback {
// 创建响应通道
responseChan := make(chan ClientResponseData, 1)
defer close(responseChan)
// 对于回调请求如果clientId为0使用默认值以保持一致
if clientId == 0 {
clientId = 1
globalLogger.Info("[辅助程序] 回调请求使用默认clientId: %d", clientId)
}
// 设置响应通道
iClientId := int32(clientId)
SetResponseChannel(iClientId, responseChan)
globalLogger.Info("[辅助程序] 已设置响应通道, clientId: %d", iClientId)
// 延迟移除响应通道,确保回调完成后再移除
defer RemoveResponseChannel(iClientId)
// 调用现有的处理逻辑传入clientId
params := map[string]interface{}{
"data": jsonData,
"clientId": clientId,
}
_, err := handleSendWxWorkData(params)
if err != nil {
globalLogger.Error("[错误] 处理SendWxWorkData请求失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": err.Error(),
})
return
}
// 等待响应或超时
timeout := time.After(10 * time.Second)
globalLogger.Info("[辅助程序] 开始等待回调响应数据, clientId: %d, 超时时间: 10秒", clientId)
select {
case responseData := <-responseChan:
// 收到回调数据,将其作为响应返回
globalLogger.Info("[辅助程序] 收到回调响应数据, clientId: %d, data: %v", responseData.ClientId, responseData.Data)
sendJSONResponse(w, http.StatusOK, map[string]interface{}{
"success": true,
"data": responseData.Data,
})
globalLogger.Info("[辅助程序] 已发送HTTP响应, clientId: %d", clientId)
case <-timeout:
// 超时处理
globalLogger.Error("[错误] 等待回调响应超时")
sendJSONResponse(w, http.StatusRequestTimeout, map[string]interface{}{
"success": false,
"error": "等待回调响应超时",
})
}
} else {
// 不需要等待回调响应的情况,直接调用处理逻辑并返回结果
params := map[string]interface{}{
"data": jsonData,
}
result, err := handleSendWxWorkData(params)
if err != nil {
globalLogger.Error("[错误] 处理SendWxWorkData请求失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": err.Error(),
})
return
}
// 发送响应
sendJSONResponse(w, http.StatusOK, result)
}
}
// handleHealthCheck 处理健康检查请求
func handleHealthCheck(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
sendJSONResponse(w, http.StatusOK, map[string]interface{}{
"status": "ok",
"time": time.Now().Format("2006-01-02 15:04:05"),
})
}
// sendJSONResponse 发送JSON格式的响应
func sendJSONResponse(w http.ResponseWriter, statusCode int, data interface{}) {
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(data)
}
// handleThirdPartyRequest 处理第三方HTTP请求通过requestdata文件夹中的JSON文件进行数据转换
func handleThirdPartyRequest(w http.ResponseWriter, r *http.Request) {
// 只允许POST请求
if r.Method != "POST" {
http.Error(w, "只支持POST请求", http.StatusMethodNotAllowed)
return
}
// 设置CORS头
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Content-Type", "application/json")
// 解析请求体
var requestBody map[string]interface{}
err := json.NewDecoder(r.Body).Decode(&requestBody)
if err != nil {
globalLogger.Error("[错误] 解析第三方HTTP请求体失败: %v", err)
sendJSONResponse(w, http.StatusBadRequest, map[string]interface{}{
"success": false,
"error": "请求体格式错误",
})
return
}
// 获取请求类型
requestType, typeExists := requestBody["type"].(string)
if !typeExists {
globalLogger.Error("[错误] 第三方请求缺少type参数")
sendJSONResponse(w, http.StatusBadRequest, map[string]interface{}{
"success": false,
"error": "缺少type参数",
})
return
}
// 记录请求日志
globalLogger.Info("[辅助程序] 收到第三方HTTP请求类型: %s", requestType)
// 把获取的完整requestBody发送到回调URL
requestBodyBytes, err := json.Marshal(requestBody)
if err != nil {
globalLogger.Error("[错误] 序列化requestBody失败: %v", err)
} else {
// 获取全局配置
appConfig := config.GetGlobalConfig()
if appConfig != nil && appConfig.CallbackConfig.EnableCallback && appConfig.CallbackConfig.CallbackURL != "" {
resp, err := http.Post(appConfig.CallbackConfig.CallbackURL, "application/json", bytes.NewBuffer(requestBodyBytes))
if err != nil {
globalLogger.Error("[错误] 发送requestBody到回调接口失败: %v", err)
} else {
defer resp.Body.Close()
globalLogger.Info("[辅助程序] requestBody已成功发送到回调接口数据: %v", requestBody)
globalLogger.Info("[辅助程序] requestBody已成功发送到回调接口响应状态码: %d", resp.StatusCode)
}
} else {
globalLogger.Info("[辅助程序] 回调功能未启用或回调URL为空不发送第三方请求数据")
}
}
// 获取请求参数
params, _ := requestBody["params"].(map[string]interface{})
// 构建JSON文件路径
// 优先使用可执行文件同级目录下的requestdata文件夹
exePath, exeErr := os.Executable()
var jsonFilePath string
if exeErr != nil {
globalLogger.Error("[错误] 获取程序路径失败: %v", exeErr)
// 默认使用当前工作目录
jsonFilePath = filepath.Join("requestdata", requestType+".json")
} else {
exeDir := filepath.Dir(exePath)
jsonFilePath = filepath.Join(exeDir, "requestdata", requestType+".json")
globalLogger.Debug("[调试] 尝试从可执行文件同级目录查找模板文件: %s", jsonFilePath)
}
// 如果文件不存在,尝试其他路径或默认文件
if _, err := os.Stat(jsonFilePath); os.IsNotExist(err) {
globalLogger.Error("[错误] 未找到请求模板文件: %s", jsonFilePath)
sendJSONResponse(w, http.StatusNotFound, map[string]interface{}{
"success": false,
"error": "未找到对应的请求模板文件",
})
return
}
// 读取JSON文件内容
fileContent, err := os.ReadFile(jsonFilePath)
if err != nil {
globalLogger.Error("[错误] 读取JSON文件失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": "读取请求模板文件失败",
})
return
}
// 解析JSON文件处理多JSON对象的情况
var templateData []map[string]interface{}
jsonStr := string(fileContent)
// 修复改进解析逻辑正确处理多个顶层JSON对象
// 首先尝试解析整个文件作为单个JSON对象或数组
var singleData map[string]interface{}
err = json.Unmarshal([]byte(jsonStr), &singleData)
if err == nil {
templateData = append(templateData, singleData)
} else {
// 尝试解析为数组
var arrayData []map[string]interface{}
err = json.Unmarshal([]byte(jsonStr), &arrayData)
if err == nil {
templateData = arrayData
} else {
// 尝试处理多个独立的JSON对象如示例文件中的格式
// 改进的分割方法,更稳健地处理换行和空白字符
scanner := bufio.NewScanner(strings.NewReader(jsonStr))
var currentObject strings.Builder
depth := 0
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue // 跳过空行
}
// 计算当前行的花括号深度变化
for _, char := range line {
if char == '{' {
depth++
} else if char == '}' {
depth--
}
}
// 追加当前行到当前对象
if currentObject.Len() > 0 {
currentObject.WriteString("\n")
}
currentObject.WriteString(line)
// 当深度回到0时表示一个完整的JSON对象已处理完毕
if depth == 0 && currentObject.Len() > 0 {
var obj map[string]interface{}
if err := json.Unmarshal([]byte(currentObject.String()), &obj); err == nil {
templateData = append(templateData, obj)
} else {
globalLogger.Debug("[调试] 解析部分JSON失败: %v\n内容: %s", err, currentObject.String())
}
// 重置当前对象
currentObject.Reset()
}
}
// 如果扫描过程中有错误
if err := scanner.Err(); err != nil {
globalLogger.Error("[错误] 扫描JSON文件内容失败: %v", err)
}
}
}
// 如果仍然没有解析到数据,报告错误
if len(templateData) == 0 {
globalLogger.Error("[错误] 解析JSON文件内容失败: 文件格式不正确或内容为空")
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": "解析请求模板文件内容失败",
})
return
}
// 检查是否有两个JSON对象请求模板和转换模板
if len(templateData) < 2 {
// 如果只有一个对象,假设它是最终的请求数据
finalData := templateData[0]
// 处理参数替换
finalData = replaceParams(finalData, params)
// 转换为JSON字符串
jsonData, err := json.Marshal(finalData)
if err != nil {
globalLogger.Error("[错误] 转换最终请求数据失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": "转换请求数据失败",
})
return
}
// 调用handleSendWxWorkData处理请求
handleParams := map[string]interface{}{
"data": string(jsonData),
}
result, err := handleSendWxWorkData(handleParams)
if err != nil {
globalLogger.Error("[错误] 处理请求失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": err.Error(),
})
return
}
// 发送响应
sendJSONResponse(w, http.StatusOK, result)
} else {
// 使用第二个对象作为转换后的请求数据
finalData := templateData[1]
// 处理参数替换
finalData = replaceParams(finalData, params)
// 转换为JSON字符串
jsonData, err := json.Marshal(finalData)
if err != nil {
globalLogger.Error("[错误] 转换最终请求数据失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": "转换请求数据失败",
})
return
}
// 检查是否需要回调响应 //第三方请求只返回简单结果,原监听结果数据通过回调返回,又因好友信息要返回结果需要改回去了
needCallback := true
var requestTypeData map[string]interface{}
err = json.Unmarshal(jsonData, &requestTypeData)
if err == nil {
if reqType, ok := requestTypeData["type"]; ok {
if typeInt, ok := reqType.(float64); ok {
// typeInt等于10003时返回机器人列表
if typeInt == 10003 {
// 从client_status.json获取status为1的用户数据
activeUsers := getActiveUsersFromClientStatus()
response := map[string]interface{}{
"code": 200,
"description": "获取机器人列表",
"time": time.Now().UnixNano() / 1e6,
"data": activeUsers,
}
sendJSONResponse(w, http.StatusOK, response)
return
}
}
}
}
if needCallback {
// 创建响应通道
responseChan := make(chan ClientResponseData, 1)
defer close(responseChan)
// 从第三方请求参数中获取clientId
clientId := GetClientIdFromRequestParams(params)
globalLogger.Info("[辅助程序] 第三方请求使用从参数获取的clientId: %d", clientId)
if clientId == 0 {
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": "没有此企微id客户端",
})
return
}
// 设置响应通道
iClientId := int32(clientId)
SetResponseChannel(iClientId, responseChan)
globalLogger.Info("[辅助程序] 已设置第三方请求响应通道, clientId: %d", iClientId)
// 延迟移除响应通道
defer RemoveResponseChannel(iClientId)
// 调用处理逻辑
handleParams := map[string]interface{}{
"data": string(jsonData),
"clientId": clientId,
}
_, err := handleSendWxWorkData(handleParams)
if err != nil {
globalLogger.Error("[错误] 处理第三方请求失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": err.Error(),
})
return
}
// 等待响应或超时
timeout := time.After(10 * time.Second)
globalLogger.Info("[辅助程序] 开始等待第三方请求回调响应数据, clientId: %d, 超时时间: 10秒", clientId)
select {
case responseData := <-responseChan:
// 收到回调数据,将其作为响应返回
globalLogger.Info("[辅助程序] 收到第三方请求回调响应数据, clientId: %d, data: %v", responseData.ClientId, responseData.Data)
sendJSONResponse(w, http.StatusOK, map[string]interface{}{
"success": true,
"data": responseData.Data,
})
case <-timeout:
// 超时处理
globalLogger.Error("[错误] 等待第三方请求回调响应超时")
sendJSONResponse(w, http.StatusRequestTimeout, map[string]interface{}{
"success": false,
"error": "等待回调响应超时",
})
}
} else {
// 不需要等待回调响应的情况
// 从第三方请求参数中获取clientId
clientId := GetClientIdFromRequestParams(params)
globalLogger.Info("[辅助程序] 第三方请求(不需要回调)使用从参数获取的clientId: %d", clientId)
handleParams := map[string]interface{}{
"data": string(jsonData),
"clientId": clientId,
}
result, err := handleSendWxWorkData(handleParams)
if err != nil {
globalLogger.Error("[错误] 处理第三方请求失败: %v", err)
sendJSONResponse(w, http.StatusInternalServerError, map[string]interface{}{
"success": false,
"error": err.Error(),
})
return
}
// 发送响应
sendJSONResponse(w, http.StatusOK, result)
}
}
}
// replaceParams 替换JSON数据中的参数占位符
func replaceParams(data map[string]interface{}, params map[string]interface{}) map[string]interface{} {
// 递归替换占位符
for key, value := range data {
switch v := value.(type) {
case string:
// 检查是否是占位符 {{params.xxx}}
if strings.HasPrefix(v, "{{params.") && strings.HasSuffix(v, "}}") {
// 提取参数名
paramName := v[9 : len(v)-2]
// 查找对应的参数值
if paramValue, exists := params[paramName]; exists {
data[key] = paramValue
}
}
case map[string]interface{}:
// 递归处理嵌套对象
data[key] = replaceParams(v, params)
case []interface{}:
// 处理数组
for i, item := range v {
if m, ok := item.(map[string]interface{}); ok {
v[i] = replaceParams(m, params)
}
}
}
}
return data
}
// getActiveUsersFromClientStatus 从client_status.json获取status为1的用户数据
func getActiveUsersFromClientStatus() []map[string]interface{} {
// 获取程序路径
exePath, err := os.Executable()
if err != nil {
globalLogger.Error("[辅助程序] 获取程序路径失败: %v", err)
return []map[string]interface{}{}
}
exeDir := filepath.Dir(exePath)
clientStatusFile := filepath.Join(exeDir, "config", "client_status.json")
// 读取client_status.json文件
var clientStatus map[string]interface{}
if data, err := os.ReadFile(clientStatusFile); err == nil {
if err := json.Unmarshal(data, &clientStatus); err != nil {
globalLogger.Error("[辅助程序] 解析client_status文件失败: %v", err)
clientStatus = make(map[string]interface{})
}
} else {
globalLogger.Error("[辅助程序] 读取client_status文件失败: %v", err)
clientStatus = make(map[string]interface{})
}
currentUsers := getIdentifiedUserIDSet()
if len(currentUsers) == 0 {
globalLogger.Info("[辅助程序] 当前没有已识别账号dashboard不展示历史client_status账号")
}
// 筛选status为1且当前已识别的用户
activeUsers := make([]map[string]interface{}, 0)
for userID, userData := range clientStatus {
if !currentUsers[userID] {
continue
}
if userMap, ok := userData.(map[string]interface{}); ok {
// 检查status是否为1
if status, exists := userMap["status"]; exists {
if statusFloat, ok := status.(float64); ok && statusFloat == 1 {
// 确保包含所有必要字段
userInfo := make(map[string]interface{})
// 定义需要包含的字段
fields := []string{
"account", "acctid", "avatar", "corp_id", "corp_name",
"corp_short_name", "email", "job_name", "mobile", "nickname",
"position", "sex", "status", "user_id", "username",
}
// 复制存在的字段
for _, field := range fields {
if value, exists := userMap[field]; exists {
userInfo[field] = value
} else {
// 为缺失的字段提供默认值
switch field {
case "sex", "status":
userInfo[field] = 1
case "user_id":
userInfo[field] = userID
default:
userInfo[field] = ""
}
}
}
activeUsers = append(activeUsers, userInfo)
}
}
}
}
activeUsers = appendRuntimeOnlyAccounts(activeUsers)
globalLogger.Info("[辅助程序] 从client_status.json获取到 %d 个status为1的用户", len(activeUsers))
return activeUsers
}
func appendRuntimeOnlyAccounts(accounts []map[string]interface{}) []map[string]interface{} {
seen := make(map[string]bool)
for _, account := range accounts {
userID := strings.TrimSpace(fmt.Sprint(account["user_id"]))
if userID != "" && userID != "<nil>" {
seen[userID] = true
}
}
for _, account := range getRuntimeAccountRows() {
userID := strings.TrimSpace(fmt.Sprint(account["user_id"]))
if userID == "" || seen[userID] {
continue
}
accounts = append(accounts, account)
seen[userID] = true
}
return accounts
}
// shutdownHTTPServer 关闭HTTP服务器
func shutdownHTTPServer() {
httpServerMutex.Lock()
server := httpServer
httpServer = nil
httpServerMutex.Unlock()
if server != nil {
globalLogger.Info("[辅助程序] 关闭HTTP服务器...")
// 创建一个超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 关闭服务器
if err := server.Shutdown(ctx); err != nil {
globalLogger.Error("[错误] HTTP服务器关闭失败: %v", err)
// 强制关闭
if err := server.Close(); err != nil {
globalLogger.Error("[错误] HTTP服务器强制关闭失败: %v", err)
}
} else {
globalLogger.Info("[辅助程序] HTTP服务器已正常关闭")
}
}
}