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 != "" { 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服务器已正常关闭") } } }