Files
qiweimanager-master/helper/http_server.go

857 lines
28 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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服务器已正常关闭")
}
}
}