857 lines
28 KiB
Go
857 lines
28 KiB
Go
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服务器已正常关闭")
|
||
}
|
||
}
|
||
}
|