1139 lines
39 KiB
Go
1139 lines
39 KiB
Go
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io/ioutil"
|
||
"math/rand"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"syscall"
|
||
"time"
|
||
"unsafe"
|
||
|
||
"qiweimanager/config"
|
||
"qiweimanager/logger"
|
||
)
|
||
|
||
// App application state.
|
||
type App struct {
|
||
ctx context.Context
|
||
httpClient *HTTPClient
|
||
logMu sync.Mutex
|
||
logEntries []logger.LogEntry
|
||
}
|
||
|
||
func (a *App) debugLoadLogEntriesSafe() interface{} {
|
||
exePath, err := os.Executable()
|
||
var logDir string
|
||
if err != nil {
|
||
logDir = filepath.Dir(globalLogger.GetLogDir())
|
||
globalLogger.Info("get executable path failed: %v, fallback dir: %s", err, logDir)
|
||
} else {
|
||
logDir = filepath.Dir(exePath)
|
||
globalLogger.Info("get executable path ok: %s, dir: %s", exePath, logDir)
|
||
}
|
||
|
||
randomDelay := time.Duration(1+rand.Intn(9)) * time.Second
|
||
today := time.Now().Format("2006-01-02")
|
||
todayFilename := fmt.Sprintf("%s_operations.json", today)
|
||
todayFilePath := filepath.Join(logDir, "operations", todayFilename)
|
||
fileExists := true
|
||
readWarning := ""
|
||
|
||
globalLogger.Info("load today's operation log file: %s", todayFilePath)
|
||
if _, err := os.Stat(todayFilePath); os.IsNotExist(err) {
|
||
fileExists = false
|
||
readWarning = "today operation log file is missing; showing in-memory records"
|
||
} else if err != nil {
|
||
fileExists = false
|
||
readWarning = fmt.Sprintf("check operation log file failed: %v", err)
|
||
}
|
||
|
||
var allLogEntries []logger.LogEntry
|
||
if fileExists {
|
||
allLogEntries, err = loadOperationLogEntriesFromFile(todayFilePath)
|
||
if err != nil {
|
||
readWarning = fmt.Sprintf("operation log file is corrupt or unreadable; quarantined old file and showing in-memory records: %v", err)
|
||
globalLogger.Error("%s", readWarning)
|
||
allLogEntries = a.operationLogSnapshot()
|
||
}
|
||
} else {
|
||
allLogEntries = a.operationLogSnapshot()
|
||
}
|
||
|
||
filteredEntries := filterOperationLogEntries(allLogEntries, randomDelay)
|
||
globalLogger.Info("DebugLoadLogEntries done - file: %s, raw: %d, filtered: %d, returned: %d, delay: %v",
|
||
todayFilename, len(allLogEntries), len(filteredEntries), len(filteredEntries), randomDelay)
|
||
|
||
result := map[string]interface{}{
|
||
"success": readWarning == "",
|
||
"logDir": logDir,
|
||
"filePath": todayFilePath,
|
||
"totalCount": len(filteredEntries),
|
||
"entries": filteredEntries,
|
||
"filter": map[string]interface{}{
|
||
"date": today,
|
||
"file": todayFilename,
|
||
"fileExists": fileExists,
|
||
"exclude": "error",
|
||
"minDuration": 0,
|
||
"maxDuration": 100,
|
||
"maxResults": 10,
|
||
"delay": randomDelay.Seconds(),
|
||
},
|
||
}
|
||
if readWarning != "" {
|
||
result["error"] = readWarning
|
||
}
|
||
return result
|
||
}
|
||
|
||
func (a *App) operationLogSnapshot() []logger.LogEntry {
|
||
a.logMu.Lock()
|
||
defer a.logMu.Unlock()
|
||
return append([]logger.LogEntry(nil), a.logEntries...)
|
||
}
|
||
|
||
func filterOperationLogEntries(entries []logger.LogEntry, randomDelay time.Duration) []logger.LogEntry {
|
||
filteredEntries := make([]logger.LogEntry, 0, len(entries))
|
||
for _, entry := range entries {
|
||
if entry.Type == "error" || entry.Duration > 100 {
|
||
continue
|
||
}
|
||
if entry.Duration >= 10 {
|
||
entry.Duration = int64(randomDelay.Seconds())
|
||
}
|
||
if strings.Contains(entry.Content, "helper") {
|
||
entry.Content = strings.Replace(entry.Content, "helper", "", 1)
|
||
}
|
||
entry.Source = "SmartBot"
|
||
filteredEntries = append(filteredEntries, entry)
|
||
}
|
||
sort.Slice(filteredEntries, func(i, j int) bool {
|
||
return filteredEntries[i].ID > filteredEntries[j].ID
|
||
})
|
||
if len(filteredEntries) > 10 {
|
||
filteredEntries = filteredEntries[:10]
|
||
}
|
||
return filteredEntries
|
||
}
|
||
|
||
// NewApp creates a new App application struct
|
||
func NewApp() *App {
|
||
return &App{}
|
||
}
|
||
|
||
// 瀹氫箟Windows API甯搁噺
|
||
const (
|
||
PROCESS_QUERY_INFORMATION = 0x0400
|
||
PROCESS_VM_READ = 0x0010
|
||
)
|
||
|
||
func (a *App) checkWxWorkProcessExists() bool {
|
||
// 浣跨敤tasklist鍛戒护妫€鏌XWork.exe杩涚▼
|
||
cmd := exec.Command("tasklist", "/fi", "imagename eq WXWork.exe")
|
||
output, err := cmd.CombinedOutput()
|
||
if err == nil && strings.Contains(string(output), "WXWork.exe") {
|
||
return true
|
||
}
|
||
return false
|
||
|
||
// 浠ヤ笅鏄師鏈夌殑Windows API妫€鏌ヤ唬鐮侊紝鏍规嵁闇€姹傚凡鍒犻櫎
|
||
}
|
||
|
||
// startup is called when the app starts. The context is saved
|
||
// so we can call the runtime methods
|
||
func (a *App) startup(ctx context.Context) {
|
||
a.ctx = ctx
|
||
a.logEntries = make([]logger.LogEntry, 0, 100)
|
||
globalLogger.Info("StarBot Pro application started successfully")
|
||
a.AddLogEntry("StarBot", "info", "程序初始成功", 0)
|
||
|
||
// 鑾峰彇閰嶇疆绔彛
|
||
appConfig := config.GetGlobalConfig()
|
||
port := 10001 // 榛樿绔彛
|
||
if appConfig != nil && appConfig.CallbackConfig.HTTPPort != "" {
|
||
if p, err := strconv.Atoi(appConfig.CallbackConfig.HTTPPort); err == nil {
|
||
port = p
|
||
}
|
||
}
|
||
|
||
a.httpClient = NewHTTPClient(a.ctx, port)
|
||
a.AddLogEntry("System", "info", fmt.Sprintf("HTTP客户端初始化完成,端口: %d", port), 50)
|
||
|
||
go func() {
|
||
// 纭繚杈呭姪绋嬪簭宸茬粡鍚姩
|
||
globalLogger.Info("纭繚杈呭姪绋嬪簭宸插惎鍔?..")
|
||
startHelperProgram()
|
||
}()
|
||
|
||
// 鍔犺浇閰嶇疆骞跺彂閫佸埌杈呭姪绋嬪簭
|
||
/*config := config.GetGlobalConfig()
|
||
if config != nil {
|
||
callbackConfig := config.CallbackConfig
|
||
|
||
// 鏋勫缓閰嶇疆鏁版嵁
|
||
configData := map[string]interface{}{
|
||
"type": 10001,
|
||
"data": callbackConfig,
|
||
}
|
||
|
||
// 杞崲涓篔SON
|
||
jsonData, _ := json.Marshal(configData)
|
||
|
||
// 鍙戦€侀厤缃埌杈呭姪绋嬪簭
|
||
a.SendWxWorkData(0, string(jsonData))
|
||
a.AddLogEntry("Config", "info", "鍥炶皟閰嶇疆宸插姞杞藉苟鍙戦€佸埌杈呭姪绋嬪簭", 0)
|
||
}*/
|
||
}
|
||
|
||
// HTTP妯″紡涓嬩笉闇€瑕両PC閲嶈繛閫昏緫锛屽凡绉婚櫎retryConnect鍑芥暟
|
||
|
||
// Greet returns a greeting for the given name
|
||
func (a *App) Greet(name string) string {
|
||
return fmt.Sprintf("Hello %s, It's show time!", name)
|
||
}
|
||
|
||
type memoryStatusEx struct {
|
||
cbSize uint32
|
||
dwMemoryLoad uint32
|
||
ullTotalPhys uint64 // in bytes
|
||
ullAvailPhys uint64
|
||
ullTotalPageFile uint64
|
||
ullAvailPageFile uint64
|
||
ullTotalVirtual uint64
|
||
ullAvailVirtual uint64
|
||
ullAvailExtendedVirtual uint64
|
||
}
|
||
|
||
func (a *App) GetCallbackConfig() interface{} {
|
||
startTime := time.Now()
|
||
startTimestamp := startTime.Format("2006-01-02 15:04:05.000")
|
||
|
||
globalLogger.Info("[GetCallbackConfig] 寮€濮嬭幏鍙栭厤缃?- 鏃堕棿: %s", startTimestamp)
|
||
|
||
// 鑾峰彇鍏ㄥ眬閰嶇疆
|
||
appConfig := config.GetGlobalConfig()
|
||
if appConfig != nil {
|
||
globalLogger.Info("[GetCallbackConfig] 鎴愬姛鑾峰彇鍏ㄥ眬閰嶇疆 - 閰嶇疆: %+v", appConfig)
|
||
|
||
globalLogger.Info("[GetCallbackConfig] 閰嶇疆缁撴瀯璇︽儏:")
|
||
globalLogger.Info("[GetCallbackConfig] CallbackConfig: %+v", appConfig.CallbackConfig)
|
||
globalLogger.Info("[GetCallbackConfig] LastUpdated: %d", appConfig.LastUpdated)
|
||
|
||
// 杩斿洖瀹屾暣鐨勯厤缃璞★紝鑰屼笉鏄粎CallbackConfig
|
||
globalLogger.Info("[GetCallbackConfig] 杩斿洖瀹屾暣閰嶇疆瀵硅薄 - 鑰楁椂: %d ms", time.Since(startTime).Milliseconds())
|
||
return appConfig
|
||
}
|
||
|
||
// 濡傛灉鍏ㄥ眬閰嶇疆涓嶅瓨鍦紝杩斿洖榛樿閰嶇疆
|
||
globalLogger.Warn("[GetCallbackConfig] 鍏ㄥ眬閰嶇疆涓嶅瓨鍦紝浣跨敤榛樿閰嶇疆")
|
||
defaultConfig := config.NewDefaultConfig()
|
||
globalLogger.Info("[GetCallbackConfig] 杩斿洖榛樿閰嶇疆 - 鑰楁椂: %d ms", time.Since(startTime).Milliseconds())
|
||
return defaultConfig
|
||
}
|
||
|
||
// GetAutoReplyConfig returns the current automatic customer-service settings.
|
||
func (a *App) GetAutoReplyConfig() interface{} {
|
||
appConfig := config.GetGlobalConfig()
|
||
if appConfig == nil {
|
||
return config.NewDefaultAutoReplyConfig()
|
||
}
|
||
appConfig.ApplyDefaults()
|
||
return appConfig.AutoReplyConfig
|
||
}
|
||
|
||
// SaveAutoReplyConfig persists automatic customer-service settings.
|
||
func (a *App) SaveAutoReplyConfig(jsonData string) (bool, string) {
|
||
var autoReplyConfig config.AutoReplyConfig
|
||
if err := json.Unmarshal([]byte(jsonData), &autoReplyConfig); err != nil {
|
||
msg := fmt.Sprintf("瑙f瀽鑷姩瀹㈡湇閰嶇疆澶辫触: %v", err)
|
||
globalLogger.Error("%s", msg)
|
||
return false, msg
|
||
}
|
||
if err := config.UpdateAutoReplyConfig(autoReplyConfig); err != nil {
|
||
msg := fmt.Sprintf("淇濆瓨鑷姩瀹㈡湇閰嶇疆澶辫触: %v", err)
|
||
globalLogger.Error("%s", msg)
|
||
return false, msg
|
||
}
|
||
if _, err := a.postHelperJSON("/api/auto-reply/reload", map[string]interface{}{}); err != nil {
|
||
globalLogger.Warn("閫氱煡helper閲嶈浇鑷姩瀹㈡湇閰嶇疆澶辫触: %v", err)
|
||
return true, "config saved, helper reload failed; please retry later or restart the app"
|
||
}
|
||
return true, "success"
|
||
}
|
||
|
||
// SetAutoReplyEnabled toggles automatic customer-service processing.
|
||
func (a *App) SetAutoReplyEnabled(enabled bool) (bool, string) {
|
||
appConfig := config.GetGlobalConfig()
|
||
if appConfig == nil {
|
||
appConfig = config.NewDefaultConfig()
|
||
}
|
||
appConfig.ApplyDefaults()
|
||
autoReplyConfig := appConfig.AutoReplyConfig
|
||
autoReplyConfig.Enabled = enabled
|
||
if err := config.UpdateAutoReplyConfig(autoReplyConfig); err != nil {
|
||
msg := fmt.Sprintf("淇濆瓨鑷姩瀹㈡湇寮€鍏冲け璐? %v", err)
|
||
globalLogger.Error("%s", msg)
|
||
return false, msg
|
||
}
|
||
if _, err := a.postHelperJSON("/api/auto-reply/reload", map[string]interface{}{}); err != nil {
|
||
globalLogger.Warn("閫氱煡helper閲嶈浇鑷姩瀹㈡湇寮€鍏冲け璐? %v", err)
|
||
return true, "switch saved, helper reload failed; please retry later or restart the app"
|
||
}
|
||
return true, "success"
|
||
}
|
||
|
||
// GetAutoReplyStatus returns helper-side automatic customer-service state.
|
||
func (a *App) GetAutoReplyStatus() interface{} {
|
||
result, err := a.getHelperJSON("/api/auto-reply/status")
|
||
if err != nil {
|
||
return map[string]interface{}{
|
||
"success": false,
|
||
"message": err.Error(),
|
||
"data": map[string]interface{}{
|
||
"enabled": false,
|
||
"running": false,
|
||
},
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// RebuildKnowledgeIndex asks helper to rebuild the local knowledge index.
|
||
func (a *App) RebuildKnowledgeIndex() interface{} {
|
||
result, err := a.postHelperJSON("/api/auto-reply/rebuild-knowledge", map[string]interface{}{})
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// SyncAutoReplyMaterials asks helper to align materials.json with the material directory.
|
||
func (a *App) SyncAutoReplyMaterials() interface{} {
|
||
result, err := a.postHelperJSON("/api/auto-reply/sync-materials", map[string]interface{}{})
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// RefreshAutoReplyContacts asks helper to refresh internal/external contact identity cache.
|
||
func (a *App) RefreshAutoReplyContacts() interface{} {
|
||
result, err := a.postHelperJSON("/api/auto-reply/refresh-contacts", map[string]interface{}{})
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// GetAutoReplyIdentityOptions returns cached internal/external contacts for manual identity fallback selection.
|
||
func (a *App) GetAutoReplyIdentityOptions() interface{} {
|
||
result, err := a.getHelperJSON("/api/auto-reply/identity-options")
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// RefreshAutoReplyGroups asks helper to refresh the available group list for internal identity fallback.
|
||
func (a *App) RefreshAutoReplyGroups() interface{} {
|
||
result, err := a.postHelperJSON("/api/auto-reply/refresh-groups", map[string]interface{}{})
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// GetAutoReplyGroupOptions returns cached group conversations for selecting internal identity source groups.
|
||
func (a *App) GetAutoReplyGroupOptions() interface{} {
|
||
result, err := a.getHelperJSON("/api/auto-reply/group-options")
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// SyncAutoReplyInternalGroups asks helper to import configured internal group members into identity cache.
|
||
func (a *App) SyncAutoReplyInternalGroups() interface{} {
|
||
result, err := a.postHelperJSON("/api/auto-reply/sync-internal-groups", map[string]interface{}{})
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// TestAIConnection asks helper to run one AI connectivity test.
|
||
func (a *App) TestAIConnection() interface{} {
|
||
result, err := a.postHelperJSON("/api/auto-reply/test-ai", map[string]interface{}{})
|
||
if err != nil {
|
||
if result != nil {
|
||
if _, ok := result["success"]; !ok {
|
||
result["success"] = false
|
||
}
|
||
if _, ok := result["message"]; !ok {
|
||
result["message"] = err.Error()
|
||
}
|
||
return result
|
||
}
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// TestHumanHandoff sends a test handoff message through the active account.
|
||
func (a *App) TestHumanHandoff() interface{} {
|
||
result, err := a.postHelperJSON("/api/auto-reply/test-handoff", map[string]interface{}{})
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// StartNewWxWorkInstance asks helper to open one additional WeCom client instance.
|
||
func (a *App) StartNewWxWorkInstance() interface{} {
|
||
result, err := a.postHelperJSON("/api/wxwork/new-instance", map[string]interface{}{})
|
||
if err != nil {
|
||
return map[string]interface{}{"success": false, "message": err.Error()}
|
||
}
|
||
return result
|
||
}
|
||
|
||
func (a *App) ensureHTTPClient() {
|
||
appConfig := config.GetGlobalConfig()
|
||
port := 10001
|
||
if appConfig != nil && appConfig.CallbackConfig.HTTPPort != "" {
|
||
if p, err := strconv.Atoi(appConfig.CallbackConfig.HTTPPort); err == nil {
|
||
port = p
|
||
}
|
||
}
|
||
if a.httpClient == nil || a.httpClient.serverURL != fmt.Sprintf("http://localhost:%d", port) {
|
||
a.httpClient = NewHTTPClient(a.ctx, port)
|
||
}
|
||
}
|
||
|
||
func (a *App) getHelperJSON(path string) (map[string]interface{}, error) {
|
||
a.ensureHTTPClient()
|
||
return a.httpClient.GetJSON(path)
|
||
}
|
||
|
||
func (a *App) postHelperJSON(path string, payload interface{}) (map[string]interface{}, error) {
|
||
a.ensureHTTPClient()
|
||
return a.httpClient.PostJSON(path, payload)
|
||
}
|
||
|
||
// SaveCallbackConfig 淇濆瓨鍥炶皟閰嶇疆
|
||
func (a *App) SaveCallbackConfig(jsonData string) (bool, string) {
|
||
// 璁板綍杩涘叆璇锋眰鐨勬椂闂达紝鐢ㄤ簬璁$畻鑰楁椂
|
||
startTime := time.Now()
|
||
startTimestamp := startTime.Format("2006-01-02 15:04:05.000")
|
||
|
||
globalLogger.Info("[杩涘叆SaveCallbackConfig璇锋眰] 鏃堕棿: %s, 鏁版嵁: %s", startTimestamp, jsonData)
|
||
|
||
// 瑙f瀽JSON鏁版嵁
|
||
var callbackConfig config.CallbackConfig
|
||
if err := json.Unmarshal([]byte(jsonData), &callbackConfig); err != nil {
|
||
errorMsg := fmt.Sprintf("瑙f瀽閰嶇疆鏁版嵁澶辫触: %v", err)
|
||
globalLogger.Error("%s", errorMsg)
|
||
a.AddLogEntry("Config", "error", errorMsg, time.Since(startTime).Milliseconds())
|
||
return false, errorMsg
|
||
}
|
||
|
||
// 鏇存柊閰嶇疆
|
||
if err := config.UpdateCallbackConfig(callbackConfig); err != nil {
|
||
errorMsg := fmt.Sprintf("淇濆瓨閰嶇疆澶辫触: %v", err)
|
||
globalLogger.Error("%s", errorMsg)
|
||
a.AddLogEntry("Config", "error", errorMsg, time.Since(startTime).Milliseconds())
|
||
return false, errorMsg
|
||
}
|
||
|
||
return true, "success"
|
||
}
|
||
|
||
// GetSystemMemoryUsage returns the current system memory usage percentage
|
||
func (a *App) GetSystemMemoryUsage() float64 {
|
||
// 鍔犺浇Windows Kernel32.dll
|
||
kernel := syscall.NewLazyDLL("Kernel32.dll")
|
||
GlobalMemoryStatusEx := kernel.NewProc("GlobalMemoryStatusEx")
|
||
|
||
var memInfo memoryStatusEx
|
||
memInfo.cbSize = uint32(unsafe.Sizeof(memInfo))
|
||
|
||
// 璋冪敤Windows API鑾峰彇鍐呭瓨淇℃伅
|
||
mem, _, err := GlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo)))
|
||
if mem == 0 {
|
||
globalLogger.Error("Error getting system memory info: %v", err)
|
||
return 0
|
||
}
|
||
|
||
// 璁$畻绯荤粺鍐呭瓨浣跨敤鐜? // Windows API宸茬粡鐩存帴鎻愪緵浜嗗唴瀛樹娇鐢ㄧ巼鐧惧垎姣?dwMemoryLoad)
|
||
return float64(memInfo.dwMemoryLoad)
|
||
}
|
||
|
||
// AddLogEntry 娣诲姞鏃ュ織鏉$洰鍒板唴瀛樹腑鍜屾寔涔呭寲瀛樺偍
|
||
func (a *App) AddLogEntry(source string, logType string, content string, duration int64) {
|
||
entry := logger.LogEntry{
|
||
ID: time.Now().UnixNano(),
|
||
Time: time.Now().Format("15:04:05"),
|
||
Source: source,
|
||
Type: logType,
|
||
Content: content,
|
||
Duration: duration,
|
||
}
|
||
entry = normalizeOperationLogEntry(entry)
|
||
|
||
// 娣诲姞鍒板唴瀛樹腑鐨勬棩蹇楁潯鐩腑
|
||
a.logMu.Lock()
|
||
a.logEntries = append(a.logEntries, entry)
|
||
|
||
// 闄愬埗鍐呭瓨涓棩蹇楁潯鐩暟閲忥紝闃叉鍐呭瓨鍗犵敤杩囧
|
||
maxEntries := 1000
|
||
if len(a.logEntries) > maxEntries {
|
||
a.logEntries = a.logEntries[len(a.logEntries)-maxEntries:]
|
||
}
|
||
a.logMu.Unlock()
|
||
|
||
// 灏嗘棩蹇楁潯鐩繚瀛樺埌鏂囦欢
|
||
go func() {
|
||
exePath, err := os.Executable()
|
||
if err != nil {
|
||
binDir := filepath.Dir(globalLogger.GetLogDir())
|
||
if err := SaveLogEntry(binDir, entry); err != nil {
|
||
// 璁板綍淇濆瓨鏃ュ織澶辫触鐨勪俊鎭埌鍏ㄥ眬鏃ュ織涓? //globalLogger.Warn("淇濆瓨鎿嶄綔璁板綍澶辫触: %v", err)
|
||
}
|
||
} else {
|
||
// 浣跨敤鍙墽琛屾枃浠舵墍鍦ㄧ洰褰曚綔涓哄熀纭€鐩綍
|
||
exeDir := filepath.Dir(exePath)
|
||
if err := SaveLogEntry(exeDir, entry); err != nil {
|
||
// 璁板綍淇濆瓨鏃ュ織澶辫触鐨勪俊鎭埌鍏ㄥ眬鏃ュ織涓? //globalLogger.Warn("淇濆瓨鎿嶄綔璁板綍澶辫触: %v", err)
|
||
}
|
||
}
|
||
}()
|
||
}
|
||
|
||
// DebugLoadLogEntries 涓存椂璋冭瘯鍑芥暟锛岀敤浜庣洿鎺ユ祴璇曟搷浣滄棩蹇楃殑鍔犺浇鍔熻兘
|
||
func (a *App) DebugLoadLogEntries() interface{} {
|
||
return a.debugLoadLogEntriesSafe()
|
||
}
|
||
|
||
func (a *App) SendWxWorkData(clientId string, jsonData string) (bool, string, interface{}) {
|
||
// 璁板綍杩涘叆璇锋眰鐨勬椂闂达紝鐢ㄤ簬璁$畻鑰楁椂
|
||
startTime := time.Now()
|
||
startTimestamp := startTime.Format("2006-01-02 15:04:05.000")
|
||
|
||
var message map[string]interface{}
|
||
globalLogger.Info("[杩涘叆SendWxWorkData璇锋眰] 鏃堕棿: %s", startTimestamp)
|
||
messageTypeValue := -1
|
||
if err := json.Unmarshal([]byte(jsonData), &message); err != nil {
|
||
globalLogger.Warn("瑙f瀽JSON鏁版嵁澶辫触: %v, 鍘熷鏁版嵁: %s", err, jsonData)
|
||
errorMsg := fmt.Sprintf("瑙f瀽JSON鏁版嵁澶辫触: %v", err)
|
||
a.AddLogEntry("App", "error", errorMsg, time.Since(startTime).Milliseconds())
|
||
return false, errorMsg, map[string]interface{}{"success": false, "error": errorMsg}
|
||
} else {
|
||
// 鑾峰彇娑堟伅绫诲瀷
|
||
messageType, typeExists := message["type"]
|
||
if typeExists {
|
||
typeValue, ok := messageType.(float64) // JSON瑙f瀽鏁板瓧榛樿涓篺loat64
|
||
if ok {
|
||
messageTypeValue = int(typeValue)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 璁板綍鎵€鏈夌被鍨嬭姹傜殑鏃ュ織
|
||
globalLogger.Info("[SendWxWorkData璇锋眰] 鏃堕棿: %s, 瀹㈡埛绔疘D: %s, 娑堟伅绫诲瀷: %d, 鏁版嵁: %s",
|
||
startTimestamp, clientId, messageTypeValue, jsonData)
|
||
a.AddLogEntry("App", "info", fmt.Sprintf("发送请求到辅助程序,消息类型: %d", messageTypeValue), 0)
|
||
|
||
if messageTypeValue == 99999 {
|
||
// 璁$畻璇锋眰鑰楁椂
|
||
duration := time.Since(startTime).Milliseconds()
|
||
|
||
// 璋冪敤璋冭瘯鍑芥暟
|
||
debugResult := a.DebugLoadLogEntries()
|
||
a.AddLogEntry("Debug", "info", "操作日志调试请求完成", duration)
|
||
return true, "", debugResult
|
||
}
|
||
|
||
if messageTypeValue == 11036 {
|
||
// 璁$畻璇锋眰鑰楁椂
|
||
duration := time.Since(startTime).Milliseconds()
|
||
|
||
// 瑙f瀽鍒嗛〉鍙傛暟
|
||
page := 1
|
||
pageSize := 100
|
||
logType := "all"
|
||
|
||
// 浠庤姹傛暟鎹腑鑾峰彇鍒嗛〉鍙傛暟
|
||
if dataMap, ok := message["data"].(map[string]interface{}); ok {
|
||
if pageVal, ok := dataMap["page"].(float64); ok {
|
||
page = int(pageVal)
|
||
}
|
||
if pageSizeVal, ok := dataMap["pageSize"].(float64); ok {
|
||
pageSize = int(pageSizeVal)
|
||
}
|
||
if typeVal, ok := dataMap["type"].(string); ok {
|
||
logType = typeVal
|
||
}
|
||
}
|
||
|
||
if page > 10 {
|
||
page = 10
|
||
}
|
||
|
||
if pageSize > 100 {
|
||
pageSize = 100
|
||
}
|
||
|
||
globalLogger.Info("operation log request params - page: %d, pageSize: %d, type: %s", page, pageSize, logType)
|
||
|
||
// 浠庢枃浠跺姞杞藉垎椤垫棩蹇? // 鑾峰彇鍙墽琛屾枃浠惰矾寰勪綔涓烘搷浣滄棩蹇楃殑瀛樺偍浣嶇疆
|
||
exePath, err := os.Executable()
|
||
var logDir string
|
||
if err != nil {
|
||
logDir = filepath.Dir(globalLogger.GetLogDir())
|
||
globalLogger.Info("鑾峰彇鍙墽琛屾枃浠惰矾寰勫け璐? %v, 浣跨敤澶囬€夎矾寰? %s", err, logDir)
|
||
} else {
|
||
// 浣跨敤鍙墽琛屾枃浠舵墍鍦ㄧ洰褰曚綔涓哄熀纭€鐩綍
|
||
logDir = filepath.Dir(exePath)
|
||
globalLogger.Info("鑾峰彇鍙墽琛屾枃浠惰矾寰勬垚鍔? %s, 浣跨敤鐩綍: %s", exePath, logDir)
|
||
}
|
||
// 娣诲姞璋冭瘯鏃ュ織锛屾樉绀哄畬鏁寸殑鎿嶄綔鏃ュ織鐩綍璺緞
|
||
operationLogDir := filepath.Join(logDir, "operations")
|
||
globalLogger.Info("灏濊瘯浠庝互涓嬭矾寰勫姞杞芥搷浣滄棩蹇? %s, 鍒嗛〉鍙傛暟: page=%d, pageSize=%d, type=%s",
|
||
operationLogDir, page, pageSize, logType)
|
||
logEntries, totalCount, err := LoadLogEntries(logDir, page, pageSize, logType)
|
||
globalLogger.Info("LoadLogEntries杩斿洖缁撴灉 - 鎬绘潯鏁? %d, 鏈〉鏉℃暟: %d, 閿欒: %v", totalCount, len(logEntries), err)
|
||
if err != nil {
|
||
globalLogger.Warn("load operation log file failed, using memory logs: %v", err)
|
||
globalLogger.Info("鍐呭瓨涓棩蹇楁潯鐩暟閲? %d", len(a.logEntries))
|
||
|
||
// 纭繚鍐呭瓨涓湁鏃ュ織鏁版嵁
|
||
if len(a.logEntries) == 0 {
|
||
a.AddLogEntry("StarBot", "info", "程序初始成功", 0)
|
||
a.AddLogEntry("System", "info", "system resource initialized", 100)
|
||
a.AddLogEntry("WxWork", "info", "企业微信服务连接成功", 500)
|
||
a.AddLogEntry("App", "warning", "内存使用率超过80%", 50)
|
||
}
|
||
|
||
totalCount = len(a.logEntries)
|
||
start := (page - 1) * pageSize
|
||
end := start + pageSize
|
||
|
||
if start >= totalCount {
|
||
logEntries = []logger.LogEntry{}
|
||
} else {
|
||
if end > totalCount {
|
||
end = totalCount
|
||
}
|
||
logEntries = a.logEntries[start:end]
|
||
}
|
||
}
|
||
|
||
totalPages := (totalCount + pageSize - 1) / pageSize
|
||
if totalPages > 10 {
|
||
totalPages = 10
|
||
}
|
||
|
||
// 娣诲姞璋冭瘯鏃ュ織锛屾樉绀鸿繑鍥炵殑鏃ュ織鏁伴噺
|
||
globalLogger.Info("杩斿洖鎿嶄綔鏃ュ織鏁版嵁: 鎬绘潯鏁?%d, 鎬婚〉鏁?%d, 褰撳墠椤?%d, 鏈〉鏉℃暟=%d",
|
||
totalCount, totalPages, page, len(logEntries))
|
||
|
||
errorMessage := ""
|
||
if err != nil {
|
||
errorMessage = err.Error()
|
||
}
|
||
|
||
response := map[string]interface{}{
|
||
"success": true,
|
||
"data": logEntries,
|
||
"totalCount": totalCount,
|
||
"totalPages": totalPages,
|
||
"currentPage": page,
|
||
"pageSize": pageSize,
|
||
"debugInfo": map[string]interface{}{
|
||
"logDir": logDir,
|
||
"operationLogDir": operationLogDir,
|
||
"exePath": exePath,
|
||
"hasError": err != nil,
|
||
"errorMessage": errorMessage,
|
||
},
|
||
}
|
||
a.AddLogEntry("App", "info", fmt.Sprintf("return operation log data, page %d of %d", page, totalPages), duration)
|
||
return true, "", response
|
||
}
|
||
|
||
if a.httpClient == nil {
|
||
// 鑾峰彇閰嶇疆绔彛
|
||
appConfig := config.GetGlobalConfig()
|
||
port := 10001 // 榛樿绔彛
|
||
if appConfig != nil && appConfig.CallbackConfig.HTTPPort != "" {
|
||
if p, err := strconv.Atoi(appConfig.CallbackConfig.HTTPPort); err == nil {
|
||
port = p
|
||
}
|
||
}
|
||
a.httpClient = NewHTTPClient(a.ctx, port)
|
||
}
|
||
|
||
// 灏濊瘯閫氳繃HTTP璋冪敤杈呭姪绋嬪簭涓殑SendWxWorkData鏂规硶
|
||
httpSuccess, httpErr := a.httpClient.SendWxWorkData(clientId, jsonData)
|
||
duration := time.Since(startTime).Milliseconds()
|
||
|
||
if httpErr != nil {
|
||
globalLogger.Error("HTTP call failed: %v", httpErr)
|
||
globalLogger.Info("灏濊瘯閲嶆柊鍚姩杈呭姪绋嬪簭...")
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("调用辅助程序失败: %v", httpErr), duration)
|
||
startHelperProgram()
|
||
// 绛夊緟杈呭姪绋嬪簭鍚姩
|
||
time.Sleep(2 * time.Second)
|
||
// 鍐嶆灏濊瘯
|
||
httpSuccess, httpErr = a.httpClient.SendWxWorkData(clientId, jsonData)
|
||
if httpErr != nil {
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("重新调用辅助程序失败: %v", httpErr), time.Since(startTime).Milliseconds())
|
||
return false, httpErr.Error(), map[string]interface{}{"success": false, "error": httpErr.Error()}
|
||
}
|
||
}
|
||
|
||
if httpSuccess {
|
||
a.AddLogEntry("App", "info", fmt.Sprintf("调用辅助程序成功,消息类型: %d", messageTypeValue), duration)
|
||
} else {
|
||
a.AddLogEntry("App", "warning", fmt.Sprintf("调用辅助程序返回失败,消息类型: %d", messageTypeValue), duration)
|
||
}
|
||
|
||
return true, "", map[string]interface{}{"success": httpSuccess, "error": ""}
|
||
}
|
||
|
||
// GetActiveClientCount 鑾峰彇褰撳墠娲昏穬鐨勫鎴风鏁伴噺
|
||
func (a *App) GetActiveClientCount() int {
|
||
globalLogger.Info("get active client count")
|
||
|
||
requestData := map[string]interface{}{
|
||
"type": 10002,
|
||
"data": map[string]interface{}{},
|
||
}
|
||
|
||
jsonData, err := json.Marshal(requestData)
|
||
if err != nil {
|
||
globalLogger.Error("鏋勫缓娲昏穬瀹㈡埛绔煡璇㈣姹傚け璐? %v", err)
|
||
return 0
|
||
}
|
||
|
||
// 鑾峰彇閰嶇疆绔彛
|
||
appConfig := config.GetGlobalConfig()
|
||
port := 10001 // 榛樿绔彛
|
||
if appConfig != nil && appConfig.CallbackConfig.HTTPPort != "" {
|
||
if p, err := strconv.Atoi(appConfig.CallbackConfig.HTTPPort); err == nil {
|
||
port = p
|
||
}
|
||
}
|
||
|
||
// 浣跨敤HTTP瀹㈡埛绔洿鎺ヨ幏鍙栨椿璺冨鎴风鏁伴噺
|
||
if a.httpClient == nil {
|
||
a.httpClient = NewHTTPClient(a.ctx, port)
|
||
}
|
||
|
||
requestBody := map[string]interface{}{
|
||
"clientId": 0,
|
||
"data": string(jsonData),
|
||
}
|
||
|
||
jsonBytes, err := json.Marshal(requestBody)
|
||
if err != nil {
|
||
globalLogger.Error("搴忓垪鍖栬姹備綋澶辫触: %v", err)
|
||
return 0
|
||
}
|
||
|
||
url := fmt.Sprintf("http://localhost:%d/api/send-wxwork-data", port)
|
||
resp, err := a.httpClient.httpClient.Post(url, "application/json", bytes.NewBuffer(jsonBytes))
|
||
if err != nil {
|
||
globalLogger.Error("HTTP璇锋眰澶辫触: %v", err)
|
||
return 0
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := ioutil.ReadAll(resp.Body)
|
||
if err != nil {
|
||
globalLogger.Error("璇诲彇鍝嶅簲浣撳け璐? %v", err)
|
||
return 0
|
||
}
|
||
|
||
// 瑙f瀽鍝嶅簲鏁版嵁
|
||
var result struct {
|
||
Success bool `json:"success"`
|
||
Data struct {
|
||
Count int `json:"count"`
|
||
} `json:"data"`
|
||
Type int `json:"type"`
|
||
}
|
||
|
||
if err := json.Unmarshal(body, &result); err != nil {
|
||
globalLogger.Error("瑙f瀽鍝嶅簲鏁版嵁澶辫触: %v, 鍝嶅簲鍐呭: %s", err, string(body))
|
||
return 0
|
||
}
|
||
|
||
if result.Success && result.Type == 10002 {
|
||
globalLogger.Info("鎴愬姛鑾峰彇娲昏穬瀹㈡埛绔暟閲? %d", result.Data.Count)
|
||
return result.Data.Count
|
||
}
|
||
|
||
globalLogger.Warn("娲昏穬瀹㈡埛绔煡璇㈣繑鍥炴牸寮忎笉姝g‘: %s", string(body))
|
||
return 0
|
||
}
|
||
|
||
// 娣诲姞鍓嶇鏃ュ織璁板綍鎺ュ彛
|
||
func (a *App) LogFrontend(level string, message string) {
|
||
// 鏍规嵁绾у埆璁板綍鏃ュ織
|
||
switch strings.ToLower(level) {
|
||
case "debug":
|
||
globalLogger.Debug("[鍓嶇] %s", message)
|
||
case "info":
|
||
globalLogger.Info("[鍓嶇] %s", message)
|
||
case "warn", "warning":
|
||
globalLogger.Warn("[鍓嶇] %s", message)
|
||
case "error":
|
||
globalLogger.Error("[鍓嶇] %s", message)
|
||
default:
|
||
globalLogger.Info("[鍓嶇] %s", message)
|
||
}
|
||
}
|
||
|
||
func (a *App) GetWxWorkAccountList() interface{} {
|
||
startTime := time.Now()
|
||
startTimestamp := startTime.Format("2006-01-02 15:04:05.000")
|
||
|
||
globalLogger.Info("[GetWxWorkAccountList] 寮€濮嬭幏鍙栦紒寰处鍙峰垪琛?- 鏃堕棿: %s", startTimestamp)
|
||
|
||
exePath, err := os.Executable()
|
||
if err != nil {
|
||
globalLogger.Error("[GetWxWorkAccountList] 鑾峰彇鍙墽琛屾枃浠惰矾寰勫け璐? %v", err)
|
||
a.AddLogEntry("App", "error", "get executable path failed", time.Since(startTime).Milliseconds())
|
||
return map[string]interface{}{
|
||
"success": false,
|
||
"error": "get executable path failed",
|
||
"data": []map[string]interface{}{},
|
||
}
|
||
}
|
||
|
||
exeDir := filepath.Dir(exePath)
|
||
configPath := filepath.Join(exeDir, "config", "client_status.json")
|
||
|
||
globalLogger.Info("[GetWxWorkAccountList] 灏濊瘯鍔犺浇exe鍚岀骇鐩綍config鏂囦欢澶归噷鐨勬枃浠? %s", configPath)
|
||
|
||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||
globalLogger.Info("[GetWxWorkAccountList] 鏂囦欢涓嶅瓨鍦? %s", configPath)
|
||
return map[string]interface{}{
|
||
"success": true,
|
||
"error": "",
|
||
"diagnostic": a.getWxWorkAccountDiagnostic(),
|
||
"data": []map[string]interface{}{},
|
||
}
|
||
}
|
||
|
||
// 璇诲彇鏂囦欢鍐呭
|
||
data, err := ioutil.ReadFile(configPath)
|
||
if err != nil {
|
||
globalLogger.Error("[GetWxWorkAccountList] 璇诲彇鏂囦欢澶辫触: %v", err)
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("璇诲彇浼佸井璐﹀彿閰嶇疆鏂囦欢澶辫触: %v", err), time.Since(startTime).Milliseconds())
|
||
return map[string]interface{}{
|
||
"success": false,
|
||
"error": fmt.Sprintf("璇诲彇浼佸井璐﹀彿閰嶇疆鏂囦欢澶辫触: %v", err),
|
||
"data": []map[string]interface{}{},
|
||
}
|
||
}
|
||
|
||
// 瑙f瀽JSON鏁版嵁
|
||
//globalLogger.Debug("[GetWxWorkAccountList] 鍘熷鏂囦欢鍐呭: %s", string(data))
|
||
|
||
var accountData map[string]interface{}
|
||
if err := json.Unmarshal(data, &accountData); err != nil {
|
||
globalLogger.Error("[GetWxWorkAccountList] 瑙f瀽JSON澶辫触: %v", err)
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("瑙f瀽浼佸井璐﹀彿閰嶇疆澶辫触: %v", err), time.Since(startTime).Milliseconds())
|
||
return map[string]interface{}{
|
||
"success": false,
|
||
"error": fmt.Sprintf("瑙f瀽浼佸井璐﹀彿閰嶇疆澶辫触: %v", err),
|
||
"data": []map[string]interface{}{},
|
||
}
|
||
}
|
||
|
||
//globalLogger.Debug("[GetWxWorkAccountList] 瑙f瀽鍚庣殑accountData: %+v", accountData)
|
||
//globalLogger.Debug("[GetWxWorkAccountList] accountData闀垮害: %d", len(accountData))
|
||
|
||
// 灏嗗璞¤浆鎹负鏁扮粍鏍煎紡
|
||
accounts := make([]map[string]interface{}, 0, len(accountData))
|
||
for key, account := range accountData {
|
||
//globalLogger.Debug("[GetWxWorkAccountList] 澶勭悊璐﹀彿key: %s, account: %+v", key, account)
|
||
if accountMap, ok := account.(map[string]interface{}); ok {
|
||
if accountMap["user_id"] == nil {
|
||
accountMap["user_id"] = key // 浣跨敤key浣滀负user_id
|
||
}
|
||
if accountMap["username"] == nil {
|
||
accountMap["username"] = accountMap["user_id"]
|
||
}
|
||
if accountMap["avatar"] == nil {
|
||
accountMap["avatar"] = ""
|
||
}
|
||
if accountMap["status"] == nil {
|
||
accountMap["status"] = 0
|
||
}
|
||
if accountMap["corp_short_name"] == nil {
|
||
accountMap["corp_short_name"] = ""
|
||
}
|
||
//globalLogger.Debug("[GetWxWorkAccountList] 澶勭悊鍚庤处鍙? %+v", accountMap)
|
||
accounts = append(accounts, accountMap)
|
||
}
|
||
}
|
||
|
||
if recognizedUsers, helperOK := a.getRecognizedWxWorkUsers(); helperOK {
|
||
filtered := make([]map[string]interface{}, 0, len(accounts))
|
||
for _, account := range accounts {
|
||
userID := strings.TrimSpace(fmt.Sprint(account["user_id"]))
|
||
if recognizedUsers[userID] {
|
||
account["status"] = 1
|
||
filtered = append(filtered, account)
|
||
}
|
||
}
|
||
accounts = filtered
|
||
}
|
||
if runtimeAccounts, helperOK := a.getRuntimeWxWorkAccounts(); helperOK {
|
||
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 runtimeAccounts {
|
||
userID := strings.TrimSpace(fmt.Sprint(account["user_id"]))
|
||
if userID == "" || seen[userID] {
|
||
continue
|
||
}
|
||
accounts = append(accounts, account)
|
||
seen[userID] = true
|
||
}
|
||
}
|
||
|
||
duration := time.Since(startTime).Milliseconds()
|
||
globalLogger.Info("[GetWxWorkAccountList] 鎴愬姛鑾峰彇 %d 涓紒寰处鍙?- 鑰楁椂: %d ms", len(accounts), duration)
|
||
a.AddLogEntry("App", "info", fmt.Sprintf("successfully loaded %d wxwork accounts", len(accounts)), duration)
|
||
|
||
diagnostic := ""
|
||
if len(accounts) == 0 {
|
||
diagnostic = a.getWxWorkAccountDiagnostic()
|
||
}
|
||
return map[string]interface{}{
|
||
"success": true,
|
||
"error": "",
|
||
"diagnostic": diagnostic,
|
||
"data": accounts,
|
||
}
|
||
}
|
||
|
||
func (a *App) getRecognizedWxWorkUsers() (map[string]bool, bool) {
|
||
result, err := a.getHelperJSON("/api/debug/clients")
|
||
if err != nil {
|
||
return nil, false
|
||
}
|
||
data, _ := result["data"].(map[string]interface{})
|
||
if data == nil {
|
||
return nil, false
|
||
}
|
||
users := make(map[string]bool)
|
||
clients, _ := data["clients"].([]interface{})
|
||
for _, item := range clients {
|
||
client, _ := item.(map[string]interface{})
|
||
if client == nil {
|
||
continue
|
||
}
|
||
userID := strings.TrimSpace(fmt.Sprint(client["userId"]))
|
||
status := strings.TrimSpace(fmt.Sprint(client["status"]))
|
||
if userID != "" && userID != "<nil>" && status == "identified" {
|
||
users[userID] = true
|
||
}
|
||
}
|
||
return users, true
|
||
}
|
||
|
||
func (a *App) getRuntimeWxWorkAccounts() ([]map[string]interface{}, bool) {
|
||
result, err := a.getHelperJSON("/api/debug/clients")
|
||
if err != nil {
|
||
return nil, false
|
||
}
|
||
data, _ := result["data"].(map[string]interface{})
|
||
if data == nil {
|
||
return nil, false
|
||
}
|
||
accounts := make([]map[string]interface{}, 0)
|
||
clients, _ := data["clients"].([]interface{})
|
||
for _, item := range clients {
|
||
client, _ := item.(map[string]interface{})
|
||
if client == nil {
|
||
continue
|
||
}
|
||
status := strings.TrimSpace(fmt.Sprint(client["status"]))
|
||
if status != "identified" && status != "message_ready" {
|
||
continue
|
||
}
|
||
clientID := intFromInterface(client["clientId"])
|
||
userID := strings.TrimSpace(fmt.Sprint(client["userId"]))
|
||
runtimeOnly := false
|
||
if userID == "" || userID == "<nil>" {
|
||
userID = fmt.Sprintf("client:%d", clientID)
|
||
runtimeOnly = true
|
||
}
|
||
username := userID
|
||
if runtimeOnly {
|
||
username = fmt.Sprintf("鏈瘑鍒处鍙?client %d", clientID)
|
||
}
|
||
accounts = append(accounts, map[string]interface{}{
|
||
"user_id": userID,
|
||
"username": username,
|
||
"avatar": "",
|
||
"status": 1,
|
||
"corp_short_name": "",
|
||
"client_id": clientID,
|
||
"pid": intFromInterface(client["pid"]),
|
||
"runtime_status": status,
|
||
"runtime_only": runtimeOnly,
|
||
"health_state": strings.TrimSpace(fmt.Sprint(client["healthState"])),
|
||
"health_message": strings.TrimSpace(fmt.Sprint(client["healthMessage"])),
|
||
"first_message_at": strings.TrimSpace(fmt.Sprint(client["firstMessageAt"])),
|
||
"last_message_at": strings.TrimSpace(fmt.Sprint(client["lastMessageAt"])),
|
||
"message_count": intFromInterface(client["messageCount"]),
|
||
})
|
||
}
|
||
return accounts, true
|
||
}
|
||
|
||
func (a *App) getWxWorkAccountDiagnostic() string {
|
||
result, err := a.getHelperJSON("/api/debug/clients")
|
||
if err != nil {
|
||
return "No WeCom account yet: helper diagnostics API is unavailable. Please start WeCom first."
|
||
}
|
||
data, _ := result["data"].(map[string]interface{})
|
||
if data == nil {
|
||
return "No WeCom account yet: helper diagnostics data is empty."
|
||
}
|
||
if version, _ := data["version"].(map[string]interface{}); version != nil {
|
||
compatible, _ := version["compatible"].(bool)
|
||
message := strings.TrimSpace(fmt.Sprint(version["message"]))
|
||
wxWorkVersion := strings.TrimSpace(fmt.Sprint(version["wxWorkVersion"]))
|
||
helperVersion := strings.TrimSpace(fmt.Sprint(version["helperVersion"]))
|
||
if !compatible && message != "" {
|
||
return fmt.Sprintf("Connected to WeCom, but account is not recognized. WeCom version %s does not match Helper/Loader %s. Put Helper_%s.dll and Loader_%s.dll into build/bin, or install the matching WeCom version.", wxWorkVersion, helperVersion, wxWorkVersion, wxWorkVersion)
|
||
}
|
||
}
|
||
recognized := intFromInterface(data["recognizedClientCount"])
|
||
usable := intFromInterface(data["usableClientCount"])
|
||
unidentified := intFromInterface(data["unidentifiedClientCount"])
|
||
connections := intFromInterface(data["connectionCount"])
|
||
if recognized > 0 {
|
||
return ""
|
||
}
|
||
if usable > 0 {
|
||
return ""
|
||
}
|
||
if unidentified > 0 {
|
||
return fmt.Sprintf("Connected to %d WeCom process(es), but account info is not recognized yet. Check dashboard account recognition status.", unidentified)
|
||
}
|
||
if connections > 0 {
|
||
return "WeCom connection exists, but account info has not been recognized yet."
|
||
}
|
||
return "No WeCom account connection received yet. Please click start WeCom first."
|
||
}
|
||
|
||
func intFromInterface(value interface{}) int {
|
||
switch v := value.(type) {
|
||
case int:
|
||
return v
|
||
case int32:
|
||
return int(v)
|
||
case int64:
|
||
return int(v)
|
||
case float64:
|
||
return int(v)
|
||
case json.Number:
|
||
n, _ := strconv.Atoi(v.String())
|
||
return n
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
func (a *App) DeleteWxWorkAccount(userID string) string {
|
||
startTime := time.Now()
|
||
startTimestamp := startTime.Format("2006-01-02 15:04:05.000")
|
||
|
||
globalLogger.Info("[DeleteWxWorkAccount] 寮€濮嬪垹闄や紒寰处鍙? %s - 鏃堕棿: %s", userID, startTimestamp)
|
||
|
||
exePath, err := os.Executable()
|
||
if err != nil {
|
||
globalLogger.Error("[DeleteWxWorkAccount] 鑾峰彇鍙墽琛屾枃浠惰矾寰勫け璐? %v", err)
|
||
a.AddLogEntry("App", "error", "get executable path failed", time.Since(startTime).Milliseconds())
|
||
return "get executable path failed"
|
||
}
|
||
|
||
// 鏋勫缓client_status.json鏂囦欢璺緞
|
||
exeDir := filepath.Dir(exePath)
|
||
configPath := filepath.Join(exeDir, "config", "client_status.json")
|
||
|
||
globalLogger.Info("[DeleteWxWorkAccount] 灏濊瘯鍒犻櫎鏂囦欢: %s 涓殑璐﹀彿: %s", configPath, userID)
|
||
|
||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||
globalLogger.Info("[DeleteWxWorkAccount] 鏂囦欢涓嶅瓨鍦? %s", configPath)
|
||
return "config file does not exist"
|
||
}
|
||
|
||
// 璇诲彇鏂囦欢鍐呭
|
||
data, err := ioutil.ReadFile(configPath)
|
||
if err != nil {
|
||
globalLogger.Error("[DeleteWxWorkAccount] 璇诲彇鏂囦欢澶辫触: %v", err)
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("璇诲彇浼佸井璐﹀彿閰嶇疆鏂囦欢澶辫触: %v", err), time.Since(startTime).Milliseconds())
|
||
return fmt.Sprintf("璇诲彇閰嶇疆鏂囦欢澶辫触: %v", err)
|
||
}
|
||
|
||
// 瑙f瀽JSON鏁版嵁
|
||
var accountData map[string]interface{}
|
||
if err := json.Unmarshal(data, &accountData); err != nil {
|
||
globalLogger.Error("[DeleteWxWorkAccount] 瑙f瀽JSON澶辫触: %v", err)
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("瑙f瀽浼佸井璐﹀彿閰嶇疆澶辫触: %v", err), time.Since(startTime).Milliseconds())
|
||
return fmt.Sprintf("瑙f瀽閰嶇疆鏂囦欢澶辫触: %v", err)
|
||
}
|
||
|
||
// 妫€鏌ユ槸鍚﹀瓨鍦ㄨuserID
|
||
if _, exists := accountData[userID]; !exists {
|
||
globalLogger.Info("[DeleteWxWorkAccount] 璐﹀彿涓嶅瓨鍦? %s", userID)
|
||
return "account does not exist"
|
||
}
|
||
|
||
delete(accountData, userID)
|
||
globalLogger.Info("[DeleteWxWorkAccount] 宸插垹闄よ处鍙? %s", userID)
|
||
|
||
updatedData, err := json.MarshalIndent(accountData, "", " ")
|
||
if err != nil {
|
||
globalLogger.Error("[DeleteWxWorkAccount] 搴忓垪鍖朖SON澶辫触: %v", err)
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("搴忓垪鍖栭厤缃け璐? %v", err), time.Since(startTime).Milliseconds())
|
||
return fmt.Sprintf("淇濆瓨閰嶇疆鏂囦欢澶辫触: %v", err)
|
||
}
|
||
|
||
// 鍐欏叆鏂囦欢
|
||
if err := ioutil.WriteFile(configPath, updatedData, 0644); err != nil {
|
||
globalLogger.Error("[DeleteWxWorkAccount] 鍐欏叆鏂囦欢澶辫触: %v", err)
|
||
a.AddLogEntry("App", "error", fmt.Sprintf("鍐欏叆閰嶇疆鏂囦欢澶辫触: %v", err), time.Since(startTime).Milliseconds())
|
||
return fmt.Sprintf("淇濆瓨閰嶇疆鏂囦欢澶辫触: %v", err)
|
||
}
|
||
|
||
duration := time.Since(startTime).Milliseconds()
|
||
globalLogger.Info("[DeleteWxWorkAccount] 鎴愬姛鍒犻櫎璐﹀彿: %s - 鑰楁椂: %d ms", userID, duration)
|
||
a.AddLogEntry("App", "info", fmt.Sprintf("鎴愬姛鍒犻櫎璐﹀彿: %s", userID), duration)
|
||
|
||
return "鍒犻櫎鎴愬姛"
|
||
}
|