Browse Source

crm ZOHO and Vtiger

dujunchen 4 months ago
parent
commit
cee8053a90
55 changed files with 1495 additions and 1487 deletions
  1. 2 8
      Makefile
  2. 0 95
      api/admin/adminModel/firewall.go
  3. 45 47
      api/admin/auth/account.go
  4. 3 4
      api/admin/router.go
  5. 2 2
      api/admin/vtiger/info.go
  6. 132 0
      api/admin/vtiger/push.go
  7. 267 17
      api/admin/zoho/info.go
  8. 42 0
      api/admin/zoho/model.go
  9. 446 0
      api/admin/zoho/push.go
  10. 11 3
      api/admin/zoho/router.go
  11. 4 4
      api/plugins/api/index.go
  12. 1 1
      api/response.go
  13. 1 1
      cmd/commands/service.go
  14. 4 4
      cmd/commands/web.go
  15. 2 2
      cmd/main.go
  16. BIN
      deployments/pms-api-arm_20241121bak
  17. BIN
      deployments/pms-api-arm
  18. BIN
      deployments/pbx-api-gin
  19. 3 15
      go.mod
  20. 45 189
      go.sum
  21. 3 3
      internal/app/ami/action/call.go
  22. 3 3
      internal/app/ami/action/channel.go
  23. 3 3
      internal/app/ami/action/database.go
  24. 2 2
      internal/app/ami/action/extension.go
  25. 2 2
      internal/app/ami/action/sip.go
  26. 2 2
      internal/app/ami/bridge.go
  27. 11 7
      internal/app/ami/index.go
  28. 1 1
      internal/app/ami/model/meetme.go
  29. 117 117
      internal/app/ami/vtiger.go
  30. 267 267
      internal/app/ami/zoho.go
  31. 6 8
      internal/app/http_server/index.go
  32. 2 1
      internal/app/http_server/pbx/pbx.go
  33. 0 7
      internal/app/http_server/socketio_client_tool/socketio_client_tool.go
  34. 42 18
      internal/app/index.go
  35. 1 1
      internal/app/mysql/extension.go
  36. 2 2
      internal/app/mysql/index.go
  37. 2 2
      internal/app/redis/extension.go
  38. 2 2
      internal/app/redis/index.go
  39. 1 1
      internal/app/socket_io/index.go
  40. 3 26
      internal/app/status/index.go
  41. 0 27
      pkg/commonService/check.go
  42. 0 15
      pkg/commonService/error.go
  43. 1 1
      pkg/configs/decode.go
  44. 1 1
      pkg/configs/push.go
  45. 7 7
      pkg/httpclient/index.go
  46. 0 64
      pkg/i18n/index.go
  47. 3 3
      pkg/utils/cmd.go
  48. 1 1
      pkg/utils/exit.go
  49. 0 63
      pkg/voicemail/parse.go
  50. 0 23
      pkg/weblog/index.go
  51. 0 12
      web/embed.go
  52. 0 179
      web/resources/en_US.json
  53. BIN
      web/resources/no_logo.png
  54. 0 179
      web/resources/zh_CN.json
  55. 0 45
      web/www/index.html

+ 2 - 8
Makefile

@@ -2,15 +2,9 @@ buildDateTime = $(shell date '+%Y-%m-%d %H:%M:%S')
 gitCommitCode = $(shell git rev-list --full-history --all --abbrev-commit --max-count 1)
 goVersion = $(shell go version)
 
-run3100: build
-	./deployments/pbx-api-gin --level 2 --reportCaller web -c ./configs/_config.3100.yaml
-
 run: build
 	#./deployments/pbx-api-gin --level 2  --reportCaller  web -c ./configs/config.yaml
 	./deployments/pbx-api-gin --level 5  --reportCaller  web -c ./configs/config.yaml
-# build: swagger
-# 	go build -tags "webuser pbx pprof socketio_client_tool swagger api" -o ./deployments/pbx-api-gin ./cmd/main.go
-# 	go build -tags "pbx" -o ./deployments/pbx-api-gin ./cmd/main.go
 build: 
 	go build -tags "pbx" -o ./deployments/pbx-api-gin ./cmd/main.go
 swagger:
@@ -23,10 +17,10 @@ swagger:
 
 release:
 	#cd web/www && find ./ui_pbx -type f  -name '*.js' | xargs gzip -k
-	GOOS=linux GOARCH=amd64 go build -tags "webuser pbx pprof api" -ldflags "-X 'main.buildDateTime=$(buildDateTime)' -X 'main.gitCommitCode=$(gitCommitCode)' -X 'main.goVersion=${goVersion}' -s -w" -o ./deployments/pms-api-arm ./cmd/main.go
+	GOOS=linux GOARCH=amd64 go build -tags "webuser pbx pprof api" -ldflags "-X 'main.buildDateTime=$(buildDateTime)' -X 'main.gitCommitCode=$(gitCommitCode)' -X 'main.goVersion=${goVersion}' -s -w" -o ./deployments/crm-api ./cmd/main.go
 release-arm:
 	#cd web/www && find ./ui_pbx -type f  -name '*.js' | xargs gzip -k
-	GOOS=linux GOARCH=arm go build -tags "webuser pbx pprof api" -ldflags "-X 'main.buildDateTime=$(buildDateTime)' -X 'main.gitCommitCode=$(gitCommitCode)' -X 'main.goVersion=${goVersion}' -s -w" -o ./deployments/pms-api-arm ./cmd/main.go
+	GOOS=linux GOARCH=arm go build -tags "webuser pbx pprof api" -ldflags "-X 'main.buildDateTime=$(buildDateTime)' -X 'main.gitCommitCode=$(gitCommitCode)' -X 'main.goVersion=${goVersion}' -s -w" -o ./deployments/crm-api-arm ./cmd/main.go
 
 upx:
 	upx -9 --lzma ./deployments/pbx-api-gin

+ 0 - 95
api/admin/adminModel/firewall.go

@@ -1,95 +0,0 @@
-package adminModel
-
-import "gopkg.in/guregu/null.v4"
-
-type FirewallGlobalInfo struct {
-	ID     int64  `xorm:"id pk autoincr" json:"id"`
-	Name   string `xorm:"name"`
-	Enable string `xorm:"enable"`
-}
-
-func (*FirewallGlobalInfo) TableName() string {
-	return "t_firewall_global"
-}
-
-type FirewallAutoDefenceRuleVO struct {
-	Name        string `xorm:"name" json:"name"`
-	Port        int64  `xorm:"port" json:"port"`
-	Protocol    string `xorm:"protocol" json:"protocol"`
-	PacketCount int64  `xorm:"packet_count" json:"packetCount"`
-	Interval    int64  `xorm:"time_interval" json:"interval"`
-}
-
-type FirewallAutoDefenceRule struct {
-	ID                        int64 `xorm:"id pk autoincr" json:"id"`
-	FirewallAutoDefenceRuleVO `xorm:"extends"`
-}
-
-func (*FirewallAutoDefenceRule) TableName() string {
-	return "t_firewall_auto_defence"
-}
-
-type FirewallCommonRule struct {
-	ID        int64       `xorm:"id pk autoincr" json:"id"`
-	Name      null.String `xorm:"name" json:"name"`
-	StartPort string      `xorm:"start_port" json:"startPort"`
-	EndPort   string      `xorm:"end_port" json:"endPort"`
-	Protocol  null.String `xorm:"protocol" json:"protocol"`
-	Ip        null.String `xorm:"ip" json:"ip"`
-	Netmask   null.String `xorm:"netmask" json:"netmask"`
-	Mac       null.String `xorm:"mac" json:"mac"`
-	Action    null.String `xorm:"rule_action" json:"action"`
-	Priority  int64       `xorm:"priority" json:"priority"`
-}
-
-func (*FirewallCommonRule) TableName() string {
-	return " t_firewall_common"
-}
-
-type GeoipRule struct {
-	ID          int64  `xorm:"id pk autoincr" json:"id"`
-	CountryName string `xorm:"country_name" json:"countryName"`
-}
-
-func (*GeoipRule) TableName() string {
-	return " t_firewall_geoip"
-}
-
-type GeoIPNames struct {
-	Names []GeoipRule `json:"names"`
-}
-
-type FailToBan struct {
-	ID       int64  `xorm:"id pk autoincr" json:"id"`
-	Name     string `xorm:"name" json:"name"`
-	Enable   bool   `xorm:"enable" json:"enable"`
-	MaxRetry int64  `xorm:"max_retry" json:"maxRetry"`
-	FindTime int64  `xorm:"find_time" json:"findTime"`
-	BanTime  int64  `xorm:"ban_time" json:"banTime"`
-}
-
-func (*FailToBan) TableName() string {
-	return " t_fail2ban_basic"
-}
-
-type FailToBanIgnored struct {
-	ID            int64  `xorm:"id pk autoincr" json:"id"`
-	Name          string `xorm:"name" json:"name"`
-	Enable        int    `xorm:"enable" json:"enable"`
-	Https         int    `xorm:"protocol_https" json:"https"`
-	Iax           int    `xorm:"protocol_iax" json:"iax"`
-	Sip           int    `xorm:"protocol_sip" json:"sip"`
-	Ssh           int    `xorm:"protocol_ssh" json:"ssh"`
-	Ip            string `xorm:"ip" json:"ip"`
-	Netmask       string `xorm:"netmask" json:"netmask"`
-	NetmaskLength int    `xorm:"netmask_length"`
-}
-
-func (*FailToBanIgnored) TableName() string {
-	return " t_fail2ban_ignored"
-}
-
-type UpdatePriority struct {
-	Action string `json:"action"`
-	ID     int64  `json:"id"`
-}

+ 45 - 47
api/admin/auth/account.go

@@ -1,12 +1,6 @@
 package auth
 
-import (
-	"pms-api-go/pkg/lfshook"
-
-	"gopkg.in/ini.v1"
-)
-
-var AuthAccounts = make(map[string]string)
+// var AuthAccounts = make(map[string]string)
 
 /* 20230419 pms 删除 =======================================================================================================
 // Login 登录认证
@@ -66,42 +60,44 @@ func Login(ctx *gin.Context) {
 	api.Success(ctx, fmt.Sprintf("Bearer %s", tokenString))
 }
 * ========================================================================================================================= */
-
-// AddAuth pms用户认证
-// @tags PBX-Auth
-// @Summary pms用户认证
-// @Description 从t_user表中抽出数据,添加到用户认证
-// @Security ApiKeyAuth
-
-func AddAuth() {
-	// 取表 t_user 中用户
-	/*var dbUser []commonModel.User
-	if err := mysql.DBOrmInstance.Find(&dbUser); err != nil {
-		lfshook.NewLogger().Error(err)
-	}
-	for _, item := range dbUser {
-		AuthAccounts[item.UserName] = item.PassWord
-	}
-	fmt.Printf("extenList=%s\n", AuthAccounts)*/
-
-	// 设计变更
-	// 读取pms配置文件中的用户密码
-	confPath := "/etc/asterisk/pms_api.conf"
-	cfg, err := ini.Load(confPath)
-	if err != nil {
-		lfshook.NewLogger().Error(err)
-		return
-	}
-	UserName := cfg.Section("general").Key("username").String()
-	PassWord := cfg.Section("general").Key("password").String()
-	if UserName == "" || PassWord == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set username or password")
-		return
-	}
-	AuthAccounts[UserName] = PassWord
-	// fmt.Printf("extenList=%s\n", AuthAccounts)
-}
-
+/* 20250102 删除 =======================================================================================================
+// // AddAuth pms用户认证
+// // @tags PBX-Auth
+// // @Summary pms用户认证
+// // @Description 从t_user表中抽出数据,添加到用户认证
+// // @Security ApiKeyAuth
+
+// func AddAuth() {
+// 	// 取表 t_user 中用户
+// 	/*var dbUser []commonModel.User
+// 	if err := mysql.DBOrmInstance.Find(&dbUser); err != nil {
+// 		lfshook.NewLogger().Error(err)
+// 	}
+// 	for _, item := range dbUser {
+// 		AuthAccounts[item.UserName] = item.PassWord
+// 	}
+// 	fmt.Printf("extenList=%s\n", AuthAccounts)*/
+
+// 	// 设计变更
+// 	// 读取pms配置文件中的用户密码
+// 	confPath := "/etc/asterisk/pms_api.conf"
+// 	cfg, err := ini.Load(confPath)
+// 	if err != nil {
+// 		lfshook.NewLogger().Error(err)
+// 		return
+// 	}
+// 	UserName := cfg.Section("general").Key("username").String()
+// 	PassWord := cfg.Section("general").Key("password").String()
+// 	if UserName == "" || PassWord == "" {
+// 		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set username or password")
+// 		return
+// 	}
+// 	AuthAccounts[UserName] = PassWord
+// 	// fmt.Printf("extenList=%s\n", AuthAccounts)
+// }
+// * ========================================================================================================================= */
+
+/* 20250102 删除 =======================================================================================================
 // VtigerAddAuth vtiger用户认证
 // @tags PBX-Auth
 // @Summary vtiger用户认证
@@ -111,7 +107,7 @@ func AddAuth() {
 func VtigerAddAuth() {
 	// 读取vtiger配置文件中的用户密码
 	// confPath := "/etc/asterisk/vtiger_api.conf"
-	confPath := "/etc/asterisk/pms_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
 	cfg, err := ini.Load(confPath)
 	if err != nil {
 		lfshook.NewLogger().Error(err)
@@ -122,7 +118,7 @@ func VtigerAddAuth() {
 	ApiKey := cfg.Section("general").Key("vtigerApiKey").String()
 	ApiKeyValue := cfg.Section("general").Key("vtigerApiKeyValue").String()
 	if (BasicAuthUser == "" || BasicAuthPWD == "") && (ApiKey == "" || ApiKeyValue == "") {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set vtigerBasicAuth or vtigerApiKey")
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set vtigerBasicAuth or vtigerApiKey")
 		return
 	}
 	AuthAccounts[BasicAuthUser] = BasicAuthPWD
@@ -139,7 +135,7 @@ func VtigerAddAuth() {
 func ZohoAddAuth() {
 	// 读取vtiger配置文件中的用户密码
 	// confPath := "/etc/asterisk/vtiger_api.conf"
-	confPath := "/etc/asterisk/pms_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
 	cfg, err := ini.Load(confPath)
 	if err != nil {
 		lfshook.NewLogger().Error(err)
@@ -148,13 +144,15 @@ func ZohoAddAuth() {
 	BasicAuthUser := cfg.Section("general").Key("zohoBasicAuthUser").String()
 	BasicAuthPWD := cfg.Section("general").Key("zohoBasicAuthPWD").String()
 	if BasicAuthUser == "" || BasicAuthPWD == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set zohoBasicAuthUser or zohoBasicAuthPWD")
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoBasicAuthUser or zohoBasicAuthPWD")
 		return
 	}
 	AuthAccounts[BasicAuthUser] = BasicAuthPWD
 	// fmt.Printf("AuthAccounts=%s\n", AuthAccounts)
 }
 
+// * ========================================================================================================================= */
+
 /* 20230419 pms 删除 =======================================================================================================
 // Logout 注销登录
 // @tags PBX-Auth

+ 3 - 4
api/admin/router.go

@@ -1,9 +1,8 @@
 package admin
 
 import (
-	"pms-api-go/api/admin/auth"
-	"pms-api-go/api/admin/vtiger"
-	"pms-api-go/api/admin/zoho"
+	"crm-api/api/admin/vtiger"
+	"crm-api/api/admin/zoho"
 
 	"github.com/gin-gonic/gin"
 )
@@ -15,7 +14,7 @@ func AddRouter(router *gin.Engine) {
 	//pbxGroup.GET("/the42/password", getPassword)
 
 	// pbxGroup.Use(middleware.JWTAuth(configs.ConfigGlobal.IdentityKey))
-	pbxGroup.Use(gin.BasicAuth(auth.AuthAccounts))
+	// pbxGroup.Use(gin.BasicAuth(auth.AuthAccounts)) // 20250103 crm 注释
 	// auth.AddRouter(pbxGroup) // 20230419 pms 注释
 
 	vtiger.AddRouter(pbxGroup)

+ 2 - 2
api/admin/vtiger/info.go

@@ -1,11 +1,11 @@
 package vtiger
 
 import (
+	"crm-api/api"
+	"crm-api/pkg/lfshook"
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"pms-api-go/api"
-	"pms-api-go/pkg/lfshook"
 
 	"github.com/gin-gonic/gin"
 	"gopkg.in/ini.v1"

+ 132 - 0
api/admin/vtiger/push.go

@@ -0,0 +1,132 @@
+package vtiger
+
+import (
+	"crm-api/pkg/configs"
+	"crm-api/pkg/httpclient"
+	"crm-api/pkg/lfshook"
+	"fmt"
+	"strings"
+
+	"gopkg.in/ini.v1"
+)
+
+func PhoneCalls(event map[string]string) {
+	fmt.Println("=========================================== ")
+	fmt.Println("event[] = ", event)
+	fmt.Println("event = ", event["Event"])
+	fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl)
+	fmt.Println("Channel = ", event["Channel"])
+
+	var callDest string
+	if event["Channel"] != "" {
+		callDest = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0]
+		fmt.Println("callDest = ", callDest)
+	}
+	// /* 20241128 vtiger crm 对应 ============================================================
+	// if configs.PushConfigValue.AMIPushUrl != "" {
+	// 	for _, eventName := range configs.PushConfigValue.AMIEvents {
+	// 		if eventName == event["Event"] {
+	// 			go httpclient.Post(event, configs.PushConfigValue.AMIPushUrl)
+	// 			break
+	// 		}
+	// 	}
+	// }
+
+	// var callId string
+	var callId = "10000007"                                                                                       // 测试用,每次测试 +1
+	recordingUrl := "https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3" // 测试用
+	event["Exten"] = "123456789"                                                                                  // 测试用
+	fmt.Println("Exten = ", event["Exten"])
+
+	// 读取vtiger配置文件
+	confPath := "/etc/asterisk/pms_api.conf"
+	cfg, err := ini.Load(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		return
+	}
+	VTIGER_URL := cfg.Section("general").Key("vtigerUrl").String()
+	if VTIGER_URL == "" {
+		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set vtigerUrl")
+		return
+	}
+
+	var getURL string
+	// 呼叫发起事件
+	if event["Event"] == "DialBegin" { // "DialBegin" "Newchannel"
+		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=147258369&to=12300001&event=call_initiated&call_id=12345678&direction=inbound
+		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&event=call_initiated&call_id=12345678  // 可以省去部分参数
+		// getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&event=call_initiated&call_id=%s", VTIGER_URL, event["Exten"], callId)
+		if callDest == "" {
+			getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&event=call_initiated&call_id=%s&direction=inbound", VTIGER_URL, event["Exten"], callId) // 20241212 省去参数 to
+		} else {
+			getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&to=%s&event=call_initiated&call_id=%s&direction=inbound", VTIGER_URL, event["Exten"], callDest, callId) // 20241212 不省参数
+		}
+		// fmt.Println("getURL = ", getURL)
+		// go httpclient.Get(getURL)
+		go httpclient.ApiKeyGet(getURL) // 后面要不要把用户密码给传过去
+		/*
+			resp := httpclient.ApiKeyGet(getURL)
+			// 读取请求后的响应
+			data, err := ioutil.ReadAll(resp.Body)
+			if err != nil {
+				fmt.Println("读取请求后的响应时发生错误:", err)
+				return
+			}
+			// 打印请求后的响应
+			fmt.Printf("data = %+v\n", string(data))
+		*/
+
+		// 呼叫已连接事件
+	} else if event["Event"] == "BridgeEnter" { // "Newstate" "BridgeEnter"
+		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&to=12300001&event=call_connected&call_id=12345678
+		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=12345678  // 可以省去部分参数
+		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=%s", VTIGER_URL, callId)
+		// getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?", VTIGER_URL)
+		if event["Exten"] != "" {
+			getURL = fmt.Sprintf("%sfrom=%s", getURL, event["Exten"])
+		}
+		if callDest != "" {
+			getURL = fmt.Sprintf("%s&to=%s", getURL, callDest)
+		}
+		getURL = fmt.Sprintf("%s&event=call_connected&call_id=%s", getURL, callId)
+		// fmt.Println("getURL = ", getURL)
+		go httpclient.ApiKeyGet(getURL)
+
+		// 通话录音事件
+	} else if event["Event"] == "Cdr" {
+		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_recording&recordingurl=https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3
+		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_recording&recordingurl=%s", VTIGER_URL, callId, recordingUrl)
+		// fmt.Println("getURL = ", getURL)
+		go httpclient.ApiKeyGet(getURL)
+
+		// 呼叫转移事件
+		// Command:SET VARIABLE FORWARD_TYPE "busy"   Command:SET VARIABLE FORWARD_DEST "136"
+		// Command:SET VARIABLE FORWARD_TYPE "always"  Command:SET VARIABLE FORWARD_DEST "136"
+		// Command:SET VARIABLE FORWARD_TYPE "noan_busy"  Command:SET VARIABLE FORWARD_DEST "136"
+		// Command:SET VARIABLE FORWARD_TYPE "noan_unav"   Command:SET VARIABLE FORWARD_DEST "136"
+		// Command:SET VARIABLE FORWARD_TYPE "unavailable"  Command:SET VARIABLE FORWARD_DEST "136"
+	} else if strings.Contains(event["Command"], "FORWARD_DEST") {
+		// transferredNumber := strings.Split(event["Command"], "\"")[1] // 正式用
+		transferredNumber := "147258369" // 测试用
+		// fmt.Println("Command = ", event["Command"])
+		// fmt.Println("transferredNumber = ", transferredNumber)
+
+		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_transfer&transferred_number=147258369
+		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_transfer&transferred_number=%s", VTIGER_URL, callId, transferredNumber)
+		// fmt.Println("getURL = ", getURL)
+		go httpclient.ApiKeyGet(getURL)
+
+		// 呼叫挂断事件
+	} else if event["Event"] == "HangupRequest" { // "HangupRequest" "Hangup"
+		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_hangup
+		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_hangup", VTIGER_URL, callId)
+		// fmt.Println("getURL = ", getURL)
+		go httpclient.ApiKeyGet(getURL)
+	}
+	fmt.Println("getURL = ", getURL)
+
+	// 按编号搜索API 暂时用的以下接口
+	// group.GET("/vtiger/lookup", contactsInfo) // 按编号搜索
+
+}

+ 267 - 17
api/admin/zoho/info.go

@@ -1,16 +1,178 @@
 package zoho
 
 import (
+	"crm-api/api"
+	"crm-api/pkg/lfshook"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"pms-api-go/api"
-	"pms-api-go/pkg/lfshook"
 
 	"github.com/gin-gonic/gin"
 	"gopkg.in/ini.v1"
 )
 
+// @tags PBX-zoho
+// @Summary 获取zoho数据中心列表
+// @Description 获取zoho数据中心列表
+// @Security oauth-serverinfo
+// @Accept  json
+// @Produce  json
+// @Router /api/zoho/oauth-serverinfo [get]
+func getOauthServerinfo(ctx *gin.Context) {
+	fmt.Printf("getOauthServerinfo ............\n")
+
+	// var ZohoOauthServerUrl = "https://accounts.zoho.com/oauth/serverinfo"
+	// data := httpRequest(ctx, "GET", ZohoOauthServerUrl)
+	// 获取配置文件信息
+	confPath := "/etc/asterisk/crm_api.conf"
+	cfg, err := ini.Load(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		return
+	}
+	ZohoOauthServerUrl := cfg.Section("general").Key("zohoAuthUrl").String()
+	if ZohoOauthServerUrl == "" {
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoAuthUrl")
+		return
+	}
+	getURL := fmt.Sprintf("%s/oauth/serverinfo", ZohoOauthServerUrl)
+	fmt.Printf("getURL = %s\n", getURL)
+	data := httpRequest(ctx, "GET", getURL)
+	/* =====================================================================================
+	// 创建HTTP客户端
+	client := &http.Client{}
+
+	// 创建请求
+	req, err := http.NewRequest("GET", ZohoOauthServerUrl, nil)
+	if err != nil {
+		fmt.Println("创建请求时发生错误:", err)
+		return
+	}
+
+	// 发送请求
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println("发送请求时发生错误:", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	// 读取请求后的响应
+	data, err := ioutil.ReadAll(resp.Body)
+	// if err != nil {
+	// 	fmt.Println("读取请求后的响应时发生错误:", err)
+	// 	return
+	// }
+	if err != nil {
+		// 读取数据错误
+		lfshook.NewLogger().Warn("ioutil ReadAll failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+	lfshook.NewLogger().Infof("data %+v", string(data))
+	// * ===================================================================================== */
+	infoData := OauthServerInfoResp{}
+	err = json.Unmarshal([]byte(data), &infoData)
+	if err != nil {
+		// 转换数据错误
+		lfshook.NewLogger().Warn("json unmarshal failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	// 打印请求后的响应
+	fmt.Printf("data = %+v\n", string(data))
+	// api.Success(ctx, string(data))
+	api.Success(ctx, infoData)
+
+}
+
+// @tags PBX-zoho
+// @Summary 获取device_code
+// @Description 获取device_code
+// @Security device_code
+// @Accept  json
+// @Produce  json
+// @Router /api/zoho/device-code [post]
+func getDeviceCode(ctx *gin.Context) {
+	fmt.Printf("getDeviceCode ............\n")
+
+	// 获取配置文件信息
+	confPath := "/etc/asterisk/crm_api.conf"
+	cfg, err := ini.Load(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		return
+	}
+	ZohoOauthUrl := cfg.Section("general").Key("zohoAuthUrl").String()
+	ZohoClientId := cfg.Section("general").Key("zohoClientId").String()
+	if ZohoOauthUrl == "" || ZohoClientId == "" {
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoAuthUrl or zohoClientId")
+		return
+	}
+
+	// 创建请求
+	// https://accounts.zoho.com/oauth/v3/device/code?scope=PhoneBridge.call.log,PhoneBridge.zohoone.search&client_id=1004.LWJCJZD5O9DB6SZLL5YJEWHT7LH0BV
+	// &grant_type=device_request&access_type=offline
+	getURL := fmt.Sprintf("%s/oauth/v3/device/code?scope=PhoneBridge.call.log,PhoneBridge.zohoone.search&client_id=%s&grant_type=device_request&access_type=offline", ZohoOauthUrl, ZohoClientId)
+	fmt.Printf("getURL = %s\n", getURL)
+
+	data := httpRequest(ctx, "POST", getURL)
+	/* =====================================================================================
+	req, err := http.NewRequest("POST", getURL, nil)
+	if err != nil {
+		fmt.Println("创建请求时发生错误:", err)
+		return
+	}
+
+	// 创建HTTP客户端
+	client := &http.Client{}
+
+	// req.Header.Set("Authorization", "Bearer "+accessToken)  // 获取token时不需要
+
+	// 发送请求
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println("发送请求时发生错误:", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	// 读取请求后的响应
+	data, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		// 读取数据错误
+		lfshook.NewLogger().Warn("ioutil ReadAll failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+	lfshook.NewLogger().Infof("data %+v", string(data))
+	// * ===================================================================================== */
+	userCodeData := UserCodeResp{}
+	err = json.Unmarshal([]byte(data), &userCodeData)
+	if err != nil {
+		// 转换数据错误
+		lfshook.NewLogger().Warn("json unmarshal failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	// 将获取的 device_code 写入文件 crm_api.conf
+	cfg.Section("general").Key("zohoCode").SetValue(userCodeData.DeviceCode)
+	err = cfg.SaveTo(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	// 打印请求后的响应
+	fmt.Printf("data = %+v\n", string(data))
+	// api.Success(ctx, string(data))
+	api.Success(ctx, userCodeData)
+}
+
 // @tags PBX-zoho
 // @Summary 获取token
 // @Description 获取token
@@ -22,8 +184,7 @@ func getToken(ctx *gin.Context) {
 	fmt.Printf("getToken ............\n")
 
 	// 获取配置文件信息
-	// confPath := "/etc/asterisk/vtiger_api.conf"
-	confPath := "/etc/asterisk/pms_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
 	cfg, err := ini.Load(confPath)
 	if err != nil {
 		lfshook.NewLogger().Error(err)
@@ -34,24 +195,26 @@ func getToken(ctx *gin.Context) {
 	ZohoClientId := cfg.Section("general").Key("zohoClientId").String()
 	ZohoClientSecret := cfg.Section("general").Key("zohoClientSecret").String()
 	if ZohoAuthUrl == "" || ZohoCode == "" || ZohoClientId == "" || ZohoClientSecret == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set zohoAuthUrl or zohoCode or zohoClientId or zohoClientSecret")
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoAuthUrl or zohoCode or zohoClientId or zohoClientSecret")
 		return
 	}
 
-	// 创建HTTP客户端
-	client := &http.Client{}
-
 	// 创建请求
 	// https://accounts.zoho.com/oauth/v3/device/token?code=1004.d4c145db9ec33e64f955290f0905ff1e.eebc39b33f228c3bee806d6f8200c50f
 	// &client_id=1004.LWJCJZD5O9DB6SZLL5YJEWHT7LH0BV&client_secret=fc3aef43dc58af8a49d3ed597710924200b03f74d0&grant_type=device_token
 	getURL := fmt.Sprintf("%s/oauth/v3/device/token?code=%s&client_id=%s&client_secret=%s&grant_type=device_token", ZohoAuthUrl, ZohoCode, ZohoClientId, ZohoClientSecret)
 	fmt.Printf("getURL = %s\n", getURL)
+	data := httpRequest(ctx, "POST", getURL)
+	/* =====================================================================================
 	req, err := http.NewRequest("POST", getURL, nil)
 	if err != nil {
 		fmt.Println("创建请求时发生错误:", err)
 		return
 	}
 
+	// 创建HTTP客户端
+	client := &http.Client{}
+
 	// req.Header.Set("Authorization", "Bearer "+accessToken)  // 获取token时不需要
 
 	// 发送请求
@@ -65,13 +228,37 @@ func getToken(ctx *gin.Context) {
 	// 读取请求后的响应
 	data, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
-		fmt.Println("读取请求后的响应时发生错误:", err)
+		// 读取数据错误
+		lfshook.NewLogger().Warn("ioutil ReadAll failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+	lfshook.NewLogger().Infof("data %+v", string(data))
+	// * ===================================================================================== */
+	tokenData := TokenResp{}
+	err = json.Unmarshal([]byte(data), &tokenData)
+	if err != nil {
+		// 转换数据错误
+		lfshook.NewLogger().Warn("json unmarshal failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	// 将获取的 refresh_token access_token 写入文件 crm_api.conf
+	cfg.Section("general").Key("zohoRefreshToken").SetValue(tokenData.RefreshToken)
+	cfg.Section("general").Key("zohoAccessToken").SetValue(tokenData.AccessToken)
+	err = cfg.SaveTo(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
 		return
 	}
 
 	// 打印请求后的响应
 	fmt.Printf("data = %+v\n", string(data))
-	api.Success(ctx, string(data))
+	// api.Success(ctx, string(data))
+	api.Success(ctx, tokenData)
+
 }
 
 // @tags PBX-zoho
@@ -86,7 +273,7 @@ func refreshToken(ctx *gin.Context) {
 
 	// 获取配置文件信息
 	// confPath := "/etc/asterisk/vtiger_api.conf"
-	confPath := "/etc/asterisk/pms_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
 	cfg, err := ini.Load(confPath)
 	if err != nil {
 		lfshook.NewLogger().Error(err)
@@ -97,24 +284,26 @@ func refreshToken(ctx *gin.Context) {
 	ZohoClientId := cfg.Section("general").Key("zohoClientId").String()
 	ZohoClientSecret := cfg.Section("general").Key("zohoClientSecret").String()
 	if ZohoAuthUrl == "" || ZohoRefreshToken == "" || ZohoClientId == "" || ZohoClientSecret == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set zohoAuthUrl or zohoRefreshToken or zohoClientId or zohoClientSecret")
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoAuthUrl or zohoRefreshToken or zohoClientId or zohoClientSecret")
 		return
 	}
 
-	// 创建HTTP客户端
-	client := &http.Client{}
-
 	// 创建请求
 	// https://accounts.zoho.com/oauth/v2/token?refresh_token=1004.86c8c0e3db7bfe9133598825bef28eb9.17a82a3bf3e675c504f478c1b0b5c456
 	// &client_id=1004.LWJCJZD5O9DB6SZLL5YJEWHT7LH0BV&client_secret=fc3aef43dc58af8a49d3ed597710924200b03f74d0&grant_type=refresh_token
 	getURL := fmt.Sprintf("%s/oauth/v2/token?refresh_token=%s&client_id=%s&client_secret=%s&grant_type=refresh_token", ZohoAuthUrl, ZohoRefreshToken, ZohoClientId, ZohoClientSecret)
 	fmt.Printf("getURL = %s\n", getURL)
+	data := httpRequest(ctx, "POST", getURL)
+	/* =====================================================================================
 	req, err := http.NewRequest("POST", getURL, nil)
 	if err != nil {
 		fmt.Println("创建请求时发生错误:", err)
 		return
 	}
 
+	// 创建HTTP客户端
+	client := &http.Client{}
+
 	// req.Header.Set("Authorization", "Bearer "+accessToken) // 刷新token时不需要
 
 	// 发送请求
@@ -128,11 +317,72 @@ func refreshToken(ctx *gin.Context) {
 	// 读取请求后的响应
 	data, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
-		fmt.Println("读取请求后的响应时发生错误:", err)
+		// 读取数据错误
+		lfshook.NewLogger().Warn("ioutil ReadAll failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+	lfshook.NewLogger().Infof("data %+v", string(data))
+	// * ===================================================================================== */
+	refreshTokenData := RefreshTokenResp{}
+	err = json.Unmarshal([]byte(data), &refreshTokenData)
+	if err != nil {
+		// 转换数据错误
+		lfshook.NewLogger().Warn("json unmarshal failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
 		return
 	}
 
+	// 将获取的 access_token 写入文件 crm_api.conf
+	cfg.Section("general").Key("zohoAccessToken").SetValue(refreshTokenData.AccessToken)
+	err = cfg.SaveTo(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	// 打印请求后的响应
+	fmt.Printf("data = %+v\n", string(data))
+	// api.Success(ctx, string(data))
+	api.Success(ctx, refreshTokenData)
+
+}
+
+// HTTP请求
+func httpRequest(ctx *gin.Context, method string, url string) []byte {
+	fmt.Printf("httpRequest ............\n")
+
+	// 创建HTTP客户端
+	client := &http.Client{}
+
+	// 创建请求
+	// req, err := http.NewRequest("POST", getURL, nil)
+	req, err := http.NewRequest(method, url, nil)
+	if err != nil {
+		fmt.Println("创建请求时发生错误:", err)
+		return nil
+	}
+
+	// 发送请求
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println("发送请求时发生错误:", err)
+		return nil
+	}
+	defer resp.Body.Close()
+
+	// 读取请求后的响应
+	data, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		// 读取数据错误
+		lfshook.NewLogger().Warn("ioutil ReadAll failed :", err.Error())
+		api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return nil
+	}
+	lfshook.NewLogger().Infof("data %+v", string(data))
+
 	// 打印请求后的响应
 	fmt.Printf("data = %+v\n", string(data))
-	api.Success(ctx, string(data))
+	return data
 }

+ 42 - 0
api/admin/zoho/model.go

@@ -0,0 +1,42 @@
+package zoho
+
+// const PROXY_URL = "https://proxy.pbx.zycoo.com"
+
+type OauthServerInfoResp struct {
+	Result    string   `json:"result"`
+	Locations Location `json:"locations"`
+}
+type Location struct {
+	Eu string `json:"eu"`
+	Au string `json:"au"`
+	In string `json:"in"`
+	Jp string `json:"jp"`
+	Uk string `json:"uk"`
+	Us string `json:"us"`
+	Ca string `json:"ca"`
+	Sa string `json:"Sa"`
+}
+
+type UserCodeResp struct {
+	UserCode                string `json:"user_code"`
+	DeviceCode              string `json:"device_code"`
+	Interval                int    `json:"interval"`
+	VerificationUriComplete string `json:"verification_uri_complete"`
+	ExpiresIn               int    `json:"expires_in"`
+	VerificationUrl         string `json:"verification_url"`
+}
+
+type TokenResp struct {
+	AccessToken  string `json:"access_token"`
+	RefreshToken string `json:"refresh_token"`
+	ApiDomain    string `json:"api_domain"`
+	TokenType    string `json:"token_type"`
+	ExpiresIn    int    `json:"expires_in"`
+}
+
+type RefreshTokenResp struct {
+	AccessToken string `json:"access_token"`
+	ApiDomain   string `json:"api_domain"`
+	TokenType   string `json:"token_type"`
+	ExpiresIn   int    `json:"expires_in"`
+}

+ 446 - 0
api/admin/zoho/push.go

@@ -0,0 +1,446 @@
+package zoho
+
+import (
+	"crm-api/pkg/configs"
+	"crm-api/pkg/httpclient"
+	"crm-api/pkg/lfshook"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"time"
+
+	"gopkg.in/ini.v1"
+)
+
+var TimestampList = make(map[string]string)
+var StartTimeList = make(map[string]time.Time) // 事件中 Timestamp 转 StartTime 有问题,重新记录时间
+// var CallTypeList = make(map[string]string)     // 临时记录 incoming 和 outgoing
+var DialStatusList = make(map[string]string) // 临时记录 DialStatus:ANSWER NOANSWER
+
+func CallNotify(event map[string]string) {
+	fmt.Println("=========================================== ")
+	fmt.Println("event[] = ", event)
+	fmt.Println("event = ", event["Event"])
+	fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl)
+	fmt.Println("Channel = ", event["Channel"])
+
+	/* ===================不这样取callDest,删除 ====================
+	var callDest string
+	if event["Channel"] != "" {
+		callDest = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0]
+		fmt.Println("callDest = ", callDest)
+	}
+	// * ========================================================= */
+
+	// var callId string
+	var callId = "10000026" // 测试用,每次测试 +1
+	// event["Exten"] = "123456789" // 测试用
+	// fmt.Println("Exten = ", event["Exten"])
+	var zohoUser = "872297346" // 测试用 872297346 873447071  // 后面看是否从数据库中取 t_zoho_user
+
+	// 读取vtiger配置文件
+	confPath := "/etc/asterisk/crm_api.conf"
+	cfg, err := ini.Load(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		return
+	}
+	ZOHO_URL := cfg.Section("general").Key("zohoUrl").String()
+	if ZOHO_URL == "" {
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoUrl")
+		return
+	}
+
+	// var getURL string
+	fmt.Println("Context = ", event["Context"])
+	// 呼叫发起事件
+	// if event["Event"] == "DialBegin" && event["Context"] == "macro-stdexten" {
+	// if event["Event"] == "DialBegin" && strings.Compare(event["Context"], "macro-stdexten") == 0 {
+	if event["Event"] == "DialBegin" {
+
+		/* ===================先放这里测试用 ============================================================
+		// 记录开始时间
+		TimestampList[event["Uniqueid"]] = event["Timestamp"]
+		// StartTimeList[event["Uniqueid"]] = time.Now().Format("2006-01-02 15:04:09")
+		StartTimeList[event["Uniqueid"]] = time.Now()
+		/* ===================先放这里测试用 ============================================================
+		fmt.Printf("Uniqueid=%s\n", event["Uniqueid"])
+		fmt.Printf("Timestamp=%s\n", event["Timestamp"])
+
+		TimestampTmp, err := strconv.Atoi(event["Timestamp"]) // 不要
+		if err != nil {
+			fmt.Printf("转换出错: %v\n", err)
+			return
+		}
+		fmt.Printf("TimestampAtoi=%d\n", TimestampTmp)
+
+		TimestampTmp002, err := strconv.ParseFloat(event["Timestamp"], 64) // OK
+		if err != nil {
+			fmt.Printf("转换出错: %v\n", err)
+			return
+		}
+		fmt.Printf("TimestampParseFloat=%f\n", TimestampTmp002)
+
+		// * ===================================================================================== */
+		// Outgoing Call - Ringing
+		if event["Context"] == "macro-trunkdial-failover" {
+			StartTimeList[event["Linkedid"]] = time.Now()
+			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=ringing&id=10031&from=123456789&to=12300000001
+			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"])
+			if zohoUser != "" {
+				getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+			}
+			fmt.Println("getURL = ", getURL)
+			go httpclient.ZohoGet(getURL)
+
+			// Incoming Call - Ringing
+			// if event["Context"] == "macro-stdexten" {
+		} else if event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm" { // 开启语音留言:macro-stdexten  关闭语音留言:macro-stdexten-withoutvm
+			StartTimeList[event["Linkedid"]] = time.Now()
+			// if strings.Compare(event["Context"], "macro-stdexten") == 0 {  // OK
+			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ringing&id=10033&from=12300000001&to=123456789
+			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["Exten"], callDest) // 取得from to 可能不准
+			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"])
+			if zohoUser != "" {
+				getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+			}
+			fmt.Println("getURL = ", getURL)
+			go httpclient.ZohoGet(getURL)
+
+		}
+
+		// 呼叫结束事件
+	} else if event["Event"] == "DialEnd" {
+		// 记录呼叫状态
+		DialStatusList[event["Linkedid"]] = event["DialStatus"]
+
+		// Outgoing Call - Unattended
+		if event["Context"] == "macro-trunkdial-failover" && event["DialStatus"] != "ANSWER" {
+			startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09")
+			fmt.Println("startTime: ", startTime)
+			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=busy&id=10025&from=123456789&to=12300000001&start_time=2024-12-04 15:09:10
+			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=busy&id=%s&from=%s&to=%s&start_time=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"], startTime)
+			if zohoUser != "" {
+				getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+			}
+			fmt.Println("getURL = ", getURL)
+			go httpclient.ZohoGet(getURL)
+
+			// Incoming Call - Missed
+			// if event["Context"] == "macro-stdexten" && event["DialStatus"] != "ANSWER" {
+		} else if (event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm") && event["DialStatus"] != "ANSWER" { // 开启语音留言:macro-stdexten  关闭语音留言:macro-stdexten-withoutvm
+			startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09")
+			fmt.Println("startTime: ", startTime)
+			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=missed&id=10005&from=12300000001&to=123456789&start_time=2024-12-04 15:09:10
+			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=missed&id=%s&from=%s&to=%s&start_time=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"], startTime)
+			if zohoUser != "" {
+				getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+			}
+			fmt.Println("getURL = ", getURL)
+			go httpclient.ZohoGet(getURL)
+
+		}
+
+		// 呼叫已连接事件
+	} else if event["Event"] == "BridgeEnter" {
+		// Outgoing Call - Answered
+		if event["Context"] == "macro-trunkdial-failover" && event["Priority"] == "1" { // 注意这里选择 Priority:1 的,便于确认 CallerIDNum,ConnectedLineNum
+			// 记录开始时间
+			// TimestampList[event["Uniqueid"]] = event["Timestamp"] // error 需要把 Uniqueid 改为 Linkedid
+			// StartTimeList[event["Uniqueid"]] = time.Now()         // error 需要把 Uniqueid 改为 Linkedid
+			TimestampList[event["Linkedid"]] = event["Timestamp"]
+			StartTimeList[event["Linkedid"]] = time.Now()
+
+			// https: //www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=answered&id=10003&from=123456789&to=12300000001
+			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"])
+			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"])
+			if zohoUser != "" {
+				getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+			}
+			fmt.Println("getURL = ", getURL)
+			go httpclient.ZohoGet(getURL)
+
+			// Incoming Call - Answered
+			// if event["Context"] == "macro-stdexten" {
+		} else if event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm" { // 开启语音留言:macro-stdexten  关闭语音留言:macro-stdexten-withoutvm
+			// 记录开始时间
+			// TimestampList[event["Uniqueid"]] = event["Timestamp"] // Uniqueid 有时通话也不合适  NG  // 都改为 Linkedid
+			// StartTimeList[event["Uniqueid"]] = time.Now()         // Uniqueid 有时通话也不合适  NG  // 都改为 Linkedid
+			TimestampList[event["Linkedid"]] = event["Timestamp"] // 呼入时设置 Uniqueid 不是 Linkedid
+			StartTimeList[event["Linkedid"]] = time.Now()         // 呼入时设置 Uniqueid 不是 Linkedid
+
+			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=answered&id=10023&from=12300000001&to=123456789
+			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"])
+			if zohoUser != "" {
+				getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+			}
+			fmt.Println("getURL = ", getURL)
+			go httpclient.ZohoGet(getURL)
+
+		}
+
+		// 呼叫挂断事件
+	} else if event["Event"] == "HangupRequest" {
+		/* ===================先放这里测试用 ============================================================
+		// 记录结束时间
+		// TimestampList[event["Uniqueid"]] = event["Timestamp"]
+		// startTimeStamp := int64(TimestampList[event["Uniqueid"]])
+
+		fmt.Println("startTimeStamp Str: ", TimestampList[event["Uniqueid"]])
+		// keyTmp := event["Uniqueid"]
+		// fmt.Println("keyTmp: ", keyTmp)
+		// fmt.Println("startTimeStamp Str: ", TimestampList[keyTmp])
+
+		startTimeStamp, err := strconv.ParseFloat(TimestampList[event["Uniqueid"]], 64) // OK
+		if err != nil {
+			fmt.Printf("startTimeStamp转换出错: %v\n", err)
+			return
+		}
+		fmt.Printf("startTimeStamp=%f\n", startTimeStamp)
+
+		endTimeStamp, err := strconv.ParseFloat(event["Timestamp"], 64) // OK
+		if err != nil {
+			fmt.Printf("endTimeStamp转换出错: %v\n", err)
+			return
+		}
+		fmt.Printf("endTimeStamp=%f\n", endTimeStamp)
+
+		durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second
+		fmt.Println("durationTime=", durationTime)
+		// 删除记录时间
+		// delete(TimestampList, event["Uniqueid"])
+		// * ===================================================================================== */
+		// 判断呼叫状态,为 ANSWER 时才推送,否则见以上推送 Outgoing Call - Unattended 或 Incoming Call - Missed
+		if DialStatusList[event["Linkedid"]] == event["DialStatus"] {
+
+			// Outgoing Call - Ended
+			if event["Context"] == "macro-trunkdial-failover" {
+				// 获取时间
+				// durationTime := getDuration(event["Uniqueid"], event["Timestamp"])
+				durationTime := getDuration(event["Linkedid"], event["Timestamp"])
+				// /* ===================测试用 ============================================================
+				// startTime, _ := time.Parse("2006-01-02 15:04:05", TimestampList[event["Uniqueid"]]) // error   1734328213.774410  =>  0001-01-01 00:00:00 +0000 UTC
+				// startTime := StartTimeList[event["Uniqueid"]]
+				// startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+				startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+				fmt.Println("startTime: ", startTime)
+				// durationTime002 := getDuration002(startTime)
+				// durationTime002 := time.Now().Sub(StartTimeList[event["Uniqueid"]]) // ok    durationTime002:  7.78408849s   // should use time.Since instead of time.Now().Sub (S1012)
+				// durationTime002 := time.Since(StartTimeList[event["Uniqueid"]]) // ok    durationTime002:  11.611233458s
+				// fmt.Println("durationTime002: ", durationTime002)
+				// * ===================================================================================== */
+
+				// 删除记录时间
+				delete(StartTimeList, event["Linkedid"])
+
+				// https://www.zohoapis.com/phonebridge/v3/callnotify?type=dailed&state=ended&id=10030&from=123456789&to=12300000001&start_time=2024-12-04 11:32:15&duration=325
+				// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime)
+				getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"], startTime, durationTime)
+				if zohoUser != "" {
+					getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+				}
+				fmt.Println("getURL = ", getURL)
+				go httpclient.ZohoGet(getURL)
+				// Incoming Call - Ended
+				// if event["Context"] == "macro-stdexten" { // Context:DialPlan1
+			} else if event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm" { // 开启语音留言:macro-stdexten  关闭语音留言:macro-stdexten-withoutvm
+				// if event["Context"] != "macro-trunkdial-failover" {
+
+				// 还需优化判断,开启语音留言时,主叫挂断才是 macro-stdexten , 被叫挂断是 DialPlan1
+				//              关闭语音留言时,主叫挂断才是 macro-stdexten-withoutvm , 被叫挂断是 DialPlan1
+				// 如果 Outgoing Call 只会是 macro-trunkdial-failover 的话,那么和以下判断换一下,else 不判断,都为 Incoming Call 处理
+
+				// 获取时间
+				// durationTime := getDuration(event["Uniqueid"], event["Timestamp"])
+				// startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+				durationTime := getDuration(event["Linkedid"], event["Timestamp"])
+				startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+				fmt.Println("startTime: ", startTime)
+
+				// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ended&id=100&from=12300000001&to=123456789&start_time=2024-12-04 10:32:10&duration=223
+				getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime)
+				// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDName"], startTime, durationTime)
+				if zohoUser != "" {
+					getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser)
+				}
+				fmt.Println("getURL = ", getURL)
+				go httpclient.ZohoGet(getURL)
+
+			}
+		}
+	}
+	// fmt.Println("getURL = ", getURL)
+
+	// 测试验证下上面有没有删除,以防TimestampList越来越大
+	for k, v := range TimestampList {
+		fmt.Printf("===Linkedid:%s   Timestamp:%s===\n", k, v)
+	}
+
+}
+
+// 使用事件中event["Timestamp"]的值计算时间间隔
+// func getDuration(uniqueid string, timeStamp string) int {
+func getDuration(linkedid string, timeStamp string) int { // 都改为 Linkedid
+
+	fmt.Println("startTimeStamp Str: ", TimestampList[linkedid])
+	if TimestampList[linkedid] == "" || timeStamp == "" {
+		fmt.Printf("linkedid 或 timeStamp 为空\n")
+		return 9999
+	}
+
+	startTimeStamp, err := strconv.ParseFloat(TimestampList[linkedid], 64)
+	if err != nil {
+		fmt.Printf("startTimeStamp转换出错: %v\n", err)
+		return 9999
+	}
+	fmt.Printf("startTimeStamp=%f\n", startTimeStamp)
+
+	endTimeStamp, err := strconv.ParseFloat(timeStamp, 64)
+	if err != nil {
+		fmt.Printf("endTimeStamp转换出错: %v\n", err)
+		return 9999
+	}
+	fmt.Printf("endTimeStamp=%f\n", endTimeStamp)
+
+	durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second
+	// fmt.Println("durationTime=", durationTime)                   // durationTime= 35s
+	// fmt.Printf("durationTime=%d\n", durationTime)                // durationTime=35000000000
+	// fmt.Printf("durationTime=%d\n", durationTime.Seconds())      // durationTime=%!d(float64=35)
+	// fmt.Printf("durationTime=%f\n", durationTime.Seconds())      // durationTime=35.000000
+	fmt.Printf("durationTime=%d\n", int(durationTime.Seconds())) // durationTime=35
+
+	// durationTime001 := time.Duration(endTimeStamp - startTimeStamp)
+	// fmt.Println("durationTime001=", durationTime001) // durationTime001= 35ns
+
+	// durationTime003 := time.Duration(endTimeStamp - startTimeStamp).Seconds()
+	// fmt.Println("durationTime003=", durationTime003) // durationTime003= 3.5e-08
+
+	// 删除记录时间
+	delete(TimestampList, linkedid)
+
+	// return durationTime
+	return int(durationTime.Seconds())
+}
+
+// // 程序中取时间计算间隔多少秒
+// func getDuration002(startTime string) time.Duration {
+// 	// durationTime := endTime.Sub(startTime)
+// 	startTime002, err := time.Parse("2006-01-02 15:04:05", startTime)
+// 	if err != nil {
+// 		fmt.Println("Error parsing time:", err)
+// 		return 0
+// 	}
+// 	durationTime := time.Now().Sub(startTime002)
+// 	return durationTime
+
+// }
+
+// @tags PBX-zoho
+// @Summary 刷新token
+// @Description 刷新token
+// @Security ZohoToken
+// @Accept  json
+// @Produce  json
+// @Router /api/zoho/refresh-token [post]
+func RefreshToken() {
+	fmt.Printf("RefreshToken ............\n")
+
+	// 获取配置文件信息
+	// confPath := "/etc/asterisk/vtiger_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
+	cfg, err := ini.Load(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		return
+	}
+	ZohoAuthUrl := cfg.Section("general").Key("zohoAuthUrl").String()
+	ZohoRefreshToken := cfg.Section("general").Key("zohoRefreshToken").String()
+	ZohoClientId := cfg.Section("general").Key("zohoClientId").String()
+	ZohoClientSecret := cfg.Section("general").Key("zohoClientSecret").String()
+	if ZohoAuthUrl == "" || ZohoRefreshToken == "" || ZohoClientId == "" || ZohoClientSecret == "" {
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoAuthUrl or zohoRefreshToken or zohoClientId or zohoClientSecret")
+		return
+	}
+
+	// 创建请求
+	// https://accounts.zoho.com/oauth/v2/token?refresh_token=1004.86c8c0e3db7bfe9133598825bef28eb9.17a82a3bf3e675c504f478c1b0b5c456
+	// &client_id=1004.LWJCJZD5O9DB6SZLL5YJEWHT7LH0BV&client_secret=fc3aef43dc58af8a49d3ed597710924200b03f74d0&grant_type=refresh_token
+	getURL := fmt.Sprintf("%s/oauth/v2/token?refresh_token=%s&client_id=%s&client_secret=%s&grant_type=refresh_token", ZohoAuthUrl, ZohoRefreshToken, ZohoClientId, ZohoClientSecret)
+	fmt.Printf("getURL = %s\n", getURL)
+	// data := httpRequest(ctx, "POST", getURL)
+	// /* =====================================================================================
+	req, err := http.NewRequest("POST", getURL, nil)
+	if err != nil {
+		fmt.Println("创建请求时发生错误:", err)
+		return
+	}
+
+	// 创建HTTP客户端
+	client := &http.Client{}
+
+	// req.Header.Set("Authorization", "Bearer "+accessToken) // 刷新token时不需要
+
+	// 发送请求
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println("发送请求时发生错误:", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	// 读取请求后的响应
+	data, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		// 读取数据错误
+		lfshook.NewLogger().Warn("ioutil ReadAll failed :", err.Error())
+		// api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+	lfshook.NewLogger().Infof("data %+v", string(data))
+	// * ===================================================================================== */
+	refreshTokenData := RefreshTokenResp{}
+	err = json.Unmarshal([]byte(data), &refreshTokenData)
+	if err != nil {
+		// 转换数据错误
+		lfshook.NewLogger().Warn("json unmarshal failed :", err.Error())
+		// api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	// 将获取的 access_token 写入文件 crm_api.conf
+	cfg.Section("general").Key("zohoAccessToken").SetValue(refreshTokenData.AccessToken)
+	err = cfg.SaveTo(confPath)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		// api.Error(ctx, http.StatusInternalServerError, err.Error())
+		return
+	}
+
+	// 打印请求后的响应
+	fmt.Printf("data = %+v\n", string(data))
+	// api.Success(ctx, string(data))
+	// api.Success(ctx, refreshTokenData)
+	// return
+
+}
+
+// 每隔55分钟刷新token
+func RefreshTokenTicker() {
+	fmt.Printf("RefreshTokenTicker ............\n")
+	ticker := time.NewTicker(55 * time.Minute)
+	defer ticker.Stop()
+	done := make(chan bool)
+	// go func() { // 注释掉OK
+	for {
+		select {
+		case <-done:
+			return
+		case t := <-ticker.C:
+			fmt.Println("Tick at", t)
+			RefreshToken()
+		}
+	}
+	// }()
+}

+ 11 - 3
api/admin/zoho/router.go

@@ -4,8 +4,16 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-func AddRouter(group *gin.RouterGroup) {
-	group.POST("/zoho/gettoken", getToken)          // 获取token
-	group.POST("/zoho/refresh-token", refreshToken) // 刷新token
+// func AddRouter(group *gin.RouterGroup) {
+func AddRouter(router *gin.RouterGroup) {
+	// handle := router.Group("/crm")
+	// handle.GET("/zoho/oauth-serverinfo", getOauthServerinfo) // 获取zoho数据中心列表
+	// handle.POST("/zoho/device-code", getDeviceCode)          // 获取device_code
+	// handle.POST("/zoho/gettoken", getToken)                  // 获取token
+	// handle.POST("/zoho/refresh-token", refreshToken)         // 刷新token
+
+	// group.POST("/zoho/add-zohouser", addZohouser)   // 添加zoho用户
+	// group.POST("/zoho/enable-dial", enableDial)  // enable click to dial
+	// group.DELETE("/zoho/disable-dial", disableDial)  // disable click to dial
 
 }

+ 4 - 4
api/plugins/api/index.go

@@ -1,13 +1,13 @@
 package api
 
 import (
+	"crm-api/api"
+	"crm-api/internal/app/ami/action"
+	"crm-api/internal/app/mysql"
+	"crm-api/pkg/lfshook"
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"pms-api-go/api"
-	"pms-api-go/internal/app/ami/action"
-	"pms-api-go/internal/app/mysql"
-	"pms-api-go/pkg/lfshook"
 	"strings"
 
 	"github.com/gin-gonic/gin"

+ 1 - 1
api/response.go

@@ -1,9 +1,9 @@
 package api
 
 import (
+	"crm-api/pkg/lfshook"
 	"fmt"
 	"net/http"
-	"pms-api-go/pkg/lfshook"
 	"runtime"
 
 	"github.com/gin-gonic/gin"

+ 1 - 1
cmd/commands/service.go

@@ -1,8 +1,8 @@
 package commands
 
 import (
+	"crm-api/pkg/lfshook"
 	"fmt"
-	"pms-api-go/pkg/lfshook"
 
 	"github.com/kardianos/service"
 	"github.com/urfave/cli"

+ 4 - 4
cmd/commands/web.go

@@ -1,12 +1,12 @@
 package commands
 
 import (
+	"crm-api/internal/app"
+	"crm-api/pkg/configs"
+	"crm-api/pkg/lfshook"
+	"crm-api/pkg/utils"
 	"fmt"
 	"io/ioutil"
-	"pms-api-go/internal/app"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/lfshook"
-	"pms-api-go/pkg/utils"
 
 	// _ "pms-api-go/api/admin/swagger"  // 20230419 pms 注释
 

+ 2 - 2
cmd/main.go

@@ -1,10 +1,10 @@
 package main
 
 import (
+	"crm-api/cmd/commands"
+	"crm-api/pkg/utils"
 	"fmt"
 	"os"
-	"pms-api-go/cmd/commands"
-	"pms-api-go/pkg/utils"
 	"runtime"
 	"time"
 

BIN
deployments/pms-api-arm_20241121bak


BIN
deployments/pms-api-arm


BIN
deployments/pbx-api-gin


+ 3 - 15
go.mod

@@ -1,35 +1,23 @@
-module pms-api-go
+module crm-api
 
 go 1.16
 
 require (
-	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/BurntSushi/toml v1.4.0 // indirect
 	github.com/gin-contrib/gzip v0.0.5
-	github.com/gin-contrib/pprof v1.3.0
 	github.com/gin-gonic/gin v1.9.0
-	github.com/go-ldap/ldap/v3 v3.4.4
-	github.com/go-ping/ping v1.1.0
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-sql-driver/mysql v1.6.0
 	github.com/gofrs/uuid v4.0.0+incompatible
-	github.com/google/go-cmp v0.5.7 // indirect
 	github.com/googollee/go-socket.io v1.4.5
 	github.com/kardianos/service v1.2.0
 	github.com/mitchellh/mapstructure v1.4.1
-	github.com/nicksnyder/go-i18n/v2 v2.1.2
-	github.com/shirou/gopsutil/v3 v3.21.7
 	github.com/sirupsen/logrus v1.9.3
 	github.com/smartystreets/goconvey v1.6.4 // indirect
-	github.com/spf13/cast v1.5.0
-	github.com/swaggo/gin-swagger v1.4.3
-	github.com/swaggo/swag v1.8.2
 	github.com/tqcenglish/amigo-go v1.1.15
 	github.com/ugorji/go v1.2.7 // indirect
-	github.com/unrolled/secure v1.13.0
 	github.com/urfave/cli v1.20.0
-	github.com/xuri/excelize/v2 v2.4.1
-	golang.org/x/text v0.7.0
-	gopkg.in/guregu/null.v4 v4.0.0
+	golang.org/x/text v0.19.0 // indirect
 	gopkg.in/ini.v1 v1.61.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/yaml.v2 v2.4.0

+ 45 - 189
go.sum

@@ -1,27 +1,12 @@
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
 gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
-github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ=
-github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
-github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
-github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
-github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
+github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
+github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
 github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
-github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
-github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
-github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
-github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
 github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
 github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
 github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
@@ -30,85 +15,41 @@ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583j
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
-github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/gzip v0.0.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0=
 github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk=
-github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
-github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
-github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/gin-gonic/gin v1.8.0 h1:4WFH5yycBMA3za5Hnl425yd9ymdw1XPm4666oab+hv4=
-github.com/gin-gonic/gin v1.8.0/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
 github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
 github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
-github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
-github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
-github.com/go-ldap/ldap/v3 v3.4.3 h1:JCKUtJPIcyOuG7ctGabLKMgIlKnGumD/iGjuWeEruDI=
-github.com/go-ldap/ldap/v3 v3.4.3/go.mod h1:7LdHfVt6iIOESVEe3Bs4Jp2sHEKgDeduAhgM1/f9qmo=
-github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs=
-github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI=
-github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
-github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
-github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
-github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
-github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
-github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
-github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
-github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
-github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
-github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
 github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
 github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
-github.com/go-redis/redis/v8 v8.8.2 h1:O/NcHqobw7SEptA0yA6up6spZVFtwE06SXM8rgLtsP8=
-github.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=
 github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
 github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
-github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
 github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
@@ -132,12 +73,10 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
-github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googollee/go-socket.io v1.4.5 h1:9uq4RvfxTH7wvfvnPS9ff2NJ9xuBvAwI1ujcN9e6LhA=
 github.com/googollee/go-socket.io v1.4.5/go.mod h1:oxHyzWprXBI9NRq9MtScgQsb5x4f72GZ92ZSJLLFh6w=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -146,8 +85,6 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -169,13 +106,7 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
-github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
 github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
@@ -187,91 +118,50 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
-github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
-github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
-github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
-github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
-github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
+github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
-github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
-github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
-github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
-github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
-github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
-github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
-github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
 github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
 github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI=
-github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
-github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o=
-github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/shirou/gopsutil/v3 v3.21.7 h1:PnTqQamUjwEDSgn+nBGu0qSDV/CfvyiR/gwTH3i7HTU=
-github.com/shirou/gopsutil/v3 v3.21.7/go.mod h1:RGl11Y7XMTQPmHh8F0ayC6haKNBgH4PXMJuTAcMOlz4=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
-github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
-github.com/swaggo/gin-swagger v1.4.3 h1:mHJz+yzJne0udgYnC5qlDf4e7KuxUbVNX2dhD/cw2rU=
-github.com/swaggo/gin-swagger v1.4.3/go.mod h1:hBg6tGeKJsUu/P79BH+WGUR8nq2LuGE0O160+s4iefo=
-github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
-github.com/swaggo/swag v1.8.2 h1:D4aBiVS2a65zhyk3WFqOUz7Rz0sOaUcgeErcid5uGL4=
-github.com/swaggo/swag v1.8.2/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
 github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
-github.com/tklauser/go-sysconf v0.3.7 h1:HT7h4+536gjqeq1ZIJPgOl1rg1XFatQGVZWp7Py53eg=
-github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4=
-github.com/tklauser/numcpus v0.2.3 h1:nQ0QYpiritP6ViFhrKYsiv6VVxOpum2Gks5GhnJbS/8=
-github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E=
-github.com/tqcenglish/amigo-go v1.1.12 h1:Lti9DcuLjASy7MOocGdFsI2unoK4XMEnJPJbue4O6fA=
-github.com/tqcenglish/amigo-go v1.1.12/go.mod h1:4EgGuajmQawlqIPtDtRPETIj/0eNkzr5kq2P6moMJAU=
 github.com/tqcenglish/amigo-go v1.1.15 h1:lVVkEXZQSCR8EAX5tgySVTTa9ot9rSX2+RnKaviME6Q=
 github.com/tqcenglish/amigo-go v1.1.15/go.mod h1:R4D2J0kumfy0aJU5n4262rgXgqsRgeSr2NgDD4yWoFQ=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -280,57 +170,32 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
 github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
 github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
 github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
-github.com/unrolled/secure v1.0.9 h1:BWRuEb1vDrBFFDdbCnKkof3gZ35I/bnHGyt0LB0TNyQ=
-github.com/unrolled/secure v1.0.9/go.mod h1:fO+mEan+FLB0CdEnHf6Q4ZZVNqG+5fuLFnP8p0BXDPI=
-github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk=
-github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
 github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
-github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
-github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
-github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o=
-github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
-github.com/xuri/excelize/v2 v2.4.1 h1:veeeFLAJwsNEBPBlDepzPIYS1eLyBVcXNZUW79exZ1E=
-github.com/xuri/excelize/v2 v2.4.1/go.mod h1:rSu0C3papjzxQA3sdK8cU544TebhrPUoTOaGPIh0Q1A=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
-go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng=
-go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
-go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg=
-go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=
-go.opentelemetry.io/otel/oteltest v0.19.0 h1:YVfA0ByROYqTwOxqHVZYZExzEpfZor+MU1rU+ip2v9Q=
-go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=
-go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc=
-go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
-golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
-golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
-golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -340,34 +205,31 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
-golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
-golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -376,51 +238,54 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
-golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -430,19 +295,14 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/guregu/null.v4 v4.0.0 h1:1Wm3S1WEA2I26Kq+6vcW+w0gcDo44YKYD7YIEJNHDjg=
-gopkg.in/guregu/null.v4 v4.0.0/go.mod h1:YoQhUrADuG3i9WqesrCmpNRwm1ypAgSHYqoOcTu/JrI=
 gopkg.in/ini.v1 v1.61.0 h1:LBCdW4FmFYL4s/vDZD1RQYX7oAR6IjujCYgMdbHBR10=
 gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
@@ -451,16 +311,12 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 3 - 3
internal/app/ami/action/call.go

@@ -1,10 +1,10 @@
 package action
 
 import (
+	"crm-api/internal/app/ami"
+	"crm-api/pkg/lfshook"
+	"crm-api/pkg/utils"
 	"fmt"
-	"pms-api-go/internal/app/ami"
-	"pms-api-go/pkg/lfshook"
-	"pms-api-go/pkg/utils"
 )
 
 // Hangup 挂断指定分机或通道

+ 3 - 3
internal/app/ami/action/channel.go

@@ -1,10 +1,10 @@
 package action
 
 import (
+	"crm-api/internal/app/ami"
+	"crm-api/pkg/lfshook"
+	"crm-api/pkg/utils"
 	"errors"
-	"pms-api-go/internal/app/ami"
-	"pms-api-go/pkg/lfshook"
-	"pms-api-go/pkg/utils"
 	"strconv"
 	"strings"
 )

+ 3 - 3
internal/app/ami/action/database.go

@@ -1,11 +1,11 @@
 package action
 
 import (
+	"crm-api/internal/app/ami"
+	"crm-api/internal/app/redis"
+	"crm-api/pkg/utils"
 	"errors"
 	"fmt"
-	"pms-api-go/internal/app/ami"
-	"pms-api-go/internal/app/redis"
-	"pms-api-go/pkg/utils"
 )
 
 func DBPut(family, key, value string) (err error) {

+ 2 - 2
internal/app/ami/action/extension.go

@@ -1,9 +1,9 @@
 package action
 
 import (
+	"crm-api/internal/app/ami"
+	"crm-api/internal/app/ami/model"
 	"errors"
-	"pms-api-go/internal/app/ami"
-	"pms-api-go/internal/app/ami/model"
 	"strings"
 )
 

+ 2 - 2
internal/app/ami/action/sip.go

@@ -1,9 +1,9 @@
 package action
 
 import (
+	"crm-api/internal/app/ami"
+	"crm-api/internal/app/ami/model"
 	"encoding/json"
-	"pms-api-go/internal/app/ami"
-	"pms-api-go/internal/app/ami/model"
 )
 
 func PJSIPShowEndpoints() (points []*model.EndpointList, err error) {

+ 2 - 2
internal/app/ami/bridge.go

@@ -1,9 +1,9 @@
 package ami
 
 import (
+	socketio "crm-api/internal/app/socket_io"
+	"crm-api/pkg/lfshook"
 	"fmt"
-	socketio "pms-api-go/internal/app/socket_io"
-	"pms-api-go/pkg/lfshook"
 	"sync"
 
 	"github.com/mitchellh/mapstructure"

+ 11 - 7
internal/app/ami/index.go

@@ -1,13 +1,15 @@
 package ami
 
 import (
-	"pms-api-go/internal/app/ami/model"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/lfshook"
+	"crm-api/api/admin/vtiger"
+	"crm-api/api/admin/zoho"
+	"crm-api/internal/app/ami/model"
+	"crm-api/pkg/configs"
+	"crm-api/pkg/lfshook"
 	"strings"
 
-	"pms-api-go/internal/app/redis"
-	socketio "pms-api-go/internal/app/socket_io"
+	"crm-api/internal/app/redis"
+	socketio "crm-api/internal/app/socket_io"
 
 	"github.com/mitchellh/mapstructure"
 	"github.com/sirupsen/logrus"
@@ -45,9 +47,11 @@ func StartAMI(connectOKCallBack func(), handleEvents []func(event map[string]str
 
 		// /* 20241128 vtiger crm 对应 ============================================================
 		if apiType == "CRM_vtiger" {
-			phoneCalls(event)
+			// phoneCalls(event)
+			vtiger.PhoneCalls(event)
 		} else if apiType == "CRM_zoho" {
-			callNotify(event)
+			// callNotify(event)
+			zoho.CallNotify(event) // 将函数名称 callNotify 改为 CallNotify 即可
 		}
 		// * ===================================================================================== */
 		/* 20241128 vtiger crm 对应 ============================================================

+ 1 - 1
internal/app/ami/model/meetme.go

@@ -1,8 +1,8 @@
 package model
 
 import (
+	"crm-api/pkg/utils"
 	"encoding/json"
-	"pms-api-go/pkg/utils"
 )
 
 type MeetMeListRooms struct {

+ 117 - 117
internal/app/ami/vtiger.go

@@ -1,132 +1,132 @@
 package ami
 
-import (
-	"fmt"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/httpclient"
-	"pms-api-go/pkg/lfshook"
-	"strings"
+// import (
+// 	"fmt"
+// 	"pms-api-go/pkg/configs"
+// 	"pms-api-go/pkg/httpclient"
+// 	"pms-api-go/pkg/lfshook"
+// 	"strings"
 
-	"gopkg.in/ini.v1"
-)
+// 	"gopkg.in/ini.v1"
+// )
 
-func phoneCalls(event map[string]string) {
-	fmt.Println("=========================================== ")
-	fmt.Println("event[] = ", event)
-	fmt.Println("event = ", event["Event"])
-	fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl)
-	fmt.Println("Channel = ", event["Channel"])
+// func phoneCalls(event map[string]string) {
+// 	fmt.Println("=========================================== ")
+// 	fmt.Println("event[] = ", event)
+// 	fmt.Println("event = ", event["Event"])
+// 	fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl)
+// 	fmt.Println("Channel = ", event["Channel"])
 
-	var callDest string
-	if event["Channel"] != "" {
-		callDest = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0]
-		fmt.Println("callDest = ", callDest)
-	}
-	// /* 20241128 vtiger crm 对应 ============================================================
-	// if configs.PushConfigValue.AMIPushUrl != "" {
-	// 	for _, eventName := range configs.PushConfigValue.AMIEvents {
-	// 		if eventName == event["Event"] {
-	// 			go httpclient.Post(event, configs.PushConfigValue.AMIPushUrl)
-	// 			break
-	// 		}
-	// 	}
-	// }
+// 	var callDest string
+// 	if event["Channel"] != "" {
+// 		callDest = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0]
+// 		fmt.Println("callDest = ", callDest)
+// 	}
+// 	// /* 20241128 vtiger crm 对应 ============================================================
+// 	// if configs.PushConfigValue.AMIPushUrl != "" {
+// 	// 	for _, eventName := range configs.PushConfigValue.AMIEvents {
+// 	// 		if eventName == event["Event"] {
+// 	// 			go httpclient.Post(event, configs.PushConfigValue.AMIPushUrl)
+// 	// 			break
+// 	// 		}
+// 	// 	}
+// 	// }
 
-	// var callId string
-	var callId = "10000007"                                                                                       // 测试用,每次测试 +1
-	recordingUrl := "https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3" // 测试用
-	event["Exten"] = "123456789"                                                                                  // 测试用
-	fmt.Println("Exten = ", event["Exten"])
+// 	// var callId string
+// 	var callId = "10000007"                                                                                       // 测试用,每次测试 +1
+// 	recordingUrl := "https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3" // 测试用
+// 	event["Exten"] = "123456789"                                                                                  // 测试用
+// 	fmt.Println("Exten = ", event["Exten"])
 
-	// 读取vtiger配置文件
-	confPath := "/etc/asterisk/pms_api.conf"
-	cfg, err := ini.Load(confPath)
-	if err != nil {
-		lfshook.NewLogger().Error(err)
-		return
-	}
-	VTIGER_URL := cfg.Section("general").Key("vtigerUrl").String()
-	if VTIGER_URL == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set vtigerUrl")
-		return
-	}
+// 	// 读取vtiger配置文件
+// 	confPath := "/etc/asterisk/pms_api.conf"
+// 	cfg, err := ini.Load(confPath)
+// 	if err != nil {
+// 		lfshook.NewLogger().Error(err)
+// 		return
+// 	}
+// 	VTIGER_URL := cfg.Section("general").Key("vtigerUrl").String()
+// 	if VTIGER_URL == "" {
+// 		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set vtigerUrl")
+// 		return
+// 	}
 
-	var getURL string
-	// 呼叫发起事件
-	if event["Event"] == "DialBegin" { // "DialBegin" "Newchannel"
-		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=147258369&to=12300001&event=call_initiated&call_id=12345678&direction=inbound
-		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&event=call_initiated&call_id=12345678  // 可以省去部分参数
-		// getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&event=call_initiated&call_id=%s", VTIGER_URL, event["Exten"], callId)
-		if callDest == "" {
-			getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&event=call_initiated&call_id=%s&direction=inbound", VTIGER_URL, event["Exten"], callId) // 20241212 省去参数 to
-		} else {
-			getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&to=%s&event=call_initiated&call_id=%s&direction=inbound", VTIGER_URL, event["Exten"], callDest, callId) // 20241212 不省参数
-		}
-		// fmt.Println("getURL = ", getURL)
-		// go httpclient.Get(getURL)
-		go httpclient.ApiKeyGet(getURL) // 后面要不要把用户密码给传过去
-		/*
-			resp := httpclient.ApiKeyGet(getURL)
-			// 读取请求后的响应
-			data, err := ioutil.ReadAll(resp.Body)
-			if err != nil {
-				fmt.Println("读取请求后的响应时发生错误:", err)
-				return
-			}
-			// 打印请求后的响应
-			fmt.Printf("data = %+v\n", string(data))
-		*/
+// 	var getURL string
+// 	// 呼叫发起事件
+// 	if event["Event"] == "DialBegin" { // "DialBegin" "Newchannel"
+// 		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=147258369&to=12300001&event=call_initiated&call_id=12345678&direction=inbound
+// 		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&event=call_initiated&call_id=12345678  // 可以省去部分参数
+// 		// getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&event=call_initiated&call_id=%s", VTIGER_URL, event["Exten"], callId)
+// 		if callDest == "" {
+// 			getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&event=call_initiated&call_id=%s&direction=inbound", VTIGER_URL, event["Exten"], callId) // 20241212 省去参数 to
+// 		} else {
+// 			getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&to=%s&event=call_initiated&call_id=%s&direction=inbound", VTIGER_URL, event["Exten"], callDest, callId) // 20241212 不省参数
+// 		}
+// 		// fmt.Println("getURL = ", getURL)
+// 		// go httpclient.Get(getURL)
+// 		go httpclient.ApiKeyGet(getURL) // 后面要不要把用户密码给传过去
+// 		/*
+// 			resp := httpclient.ApiKeyGet(getURL)
+// 			// 读取请求后的响应
+// 			data, err := ioutil.ReadAll(resp.Body)
+// 			if err != nil {
+// 				fmt.Println("读取请求后的响应时发生错误:", err)
+// 				return
+// 			}
+// 			// 打印请求后的响应
+// 			fmt.Printf("data = %+v\n", string(data))
+// 		*/
 
-		// 呼叫已连接事件
-	} else if event["Event"] == "BridgeEnter" { // "Newstate" "BridgeEnter"
-		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&to=12300001&event=call_connected&call_id=12345678
-		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=12345678  // 可以省去部分参数
-		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=%s", VTIGER_URL, callId)
-		// getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?", VTIGER_URL)
-		if event["Exten"] != "" {
-			getURL = fmt.Sprintf("%sfrom=%s", getURL, event["Exten"])
-		}
-		if callDest != "" {
-			getURL = fmt.Sprintf("%s&to=%s", getURL, callDest)
-		}
-		getURL = fmt.Sprintf("%s&event=call_connected&call_id=%s", getURL, callId)
-		// fmt.Println("getURL = ", getURL)
-		go httpclient.ApiKeyGet(getURL)
+// 		// 呼叫已连接事件
+// 	} else if event["Event"] == "BridgeEnter" { // "Newstate" "BridgeEnter"
+// 		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&to=12300001&event=call_connected&call_id=12345678
+// 		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=12345678  // 可以省去部分参数
+// 		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=%s", VTIGER_URL, callId)
+// 		// getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?", VTIGER_URL)
+// 		if event["Exten"] != "" {
+// 			getURL = fmt.Sprintf("%sfrom=%s", getURL, event["Exten"])
+// 		}
+// 		if callDest != "" {
+// 			getURL = fmt.Sprintf("%s&to=%s", getURL, callDest)
+// 		}
+// 		getURL = fmt.Sprintf("%s&event=call_connected&call_id=%s", getURL, callId)
+// 		// fmt.Println("getURL = ", getURL)
+// 		go httpclient.ApiKeyGet(getURL)
 
-		// 通话录音事件
-	} else if event["Event"] == "Cdr" {
-		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_recording&recordingurl=https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3
-		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_recording&recordingurl=%s", VTIGER_URL, callId, recordingUrl)
-		// fmt.Println("getURL = ", getURL)
-		go httpclient.ApiKeyGet(getURL)
+// 		// 通话录音事件
+// 	} else if event["Event"] == "Cdr" {
+// 		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_recording&recordingurl=https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3
+// 		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_recording&recordingurl=%s", VTIGER_URL, callId, recordingUrl)
+// 		// fmt.Println("getURL = ", getURL)
+// 		go httpclient.ApiKeyGet(getURL)
 
-		// 呼叫转移事件
-		// Command:SET VARIABLE FORWARD_TYPE "busy"   Command:SET VARIABLE FORWARD_DEST "136"
-		// Command:SET VARIABLE FORWARD_TYPE "always"  Command:SET VARIABLE FORWARD_DEST "136"
-		// Command:SET VARIABLE FORWARD_TYPE "noan_busy"  Command:SET VARIABLE FORWARD_DEST "136"
-		// Command:SET VARIABLE FORWARD_TYPE "noan_unav"   Command:SET VARIABLE FORWARD_DEST "136"
-		// Command:SET VARIABLE FORWARD_TYPE "unavailable"  Command:SET VARIABLE FORWARD_DEST "136"
-	} else if strings.Contains(event["Command"], "FORWARD_DEST") {
-		// transferredNumber := strings.Split(event["Command"], "\"")[1] // 正式用
-		transferredNumber := "147258369" // 测试用
-		// fmt.Println("Command = ", event["Command"])
-		// fmt.Println("transferredNumber = ", transferredNumber)
+// 		// 呼叫转移事件
+// 		// Command:SET VARIABLE FORWARD_TYPE "busy"   Command:SET VARIABLE FORWARD_DEST "136"
+// 		// Command:SET VARIABLE FORWARD_TYPE "always"  Command:SET VARIABLE FORWARD_DEST "136"
+// 		// Command:SET VARIABLE FORWARD_TYPE "noan_busy"  Command:SET VARIABLE FORWARD_DEST "136"
+// 		// Command:SET VARIABLE FORWARD_TYPE "noan_unav"   Command:SET VARIABLE FORWARD_DEST "136"
+// 		// Command:SET VARIABLE FORWARD_TYPE "unavailable"  Command:SET VARIABLE FORWARD_DEST "136"
+// 	} else if strings.Contains(event["Command"], "FORWARD_DEST") {
+// 		// transferredNumber := strings.Split(event["Command"], "\"")[1] // 正式用
+// 		transferredNumber := "147258369" // 测试用
+// 		// fmt.Println("Command = ", event["Command"])
+// 		// fmt.Println("transferredNumber = ", transferredNumber)
 
-		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_transfer&transferred_number=147258369
-		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_transfer&transferred_number=%s", VTIGER_URL, callId, transferredNumber)
-		// fmt.Println("getURL = ", getURL)
-		go httpclient.ApiKeyGet(getURL)
+// 		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_transfer&transferred_number=147258369
+// 		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_transfer&transferred_number=%s", VTIGER_URL, callId, transferredNumber)
+// 		// fmt.Println("getURL = ", getURL)
+// 		go httpclient.ApiKeyGet(getURL)
 
-		// 呼叫挂断事件
-	} else if event["Event"] == "HangupRequest" { // "HangupRequest" "Hangup"
-		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_hangup
-		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_hangup", VTIGER_URL, callId)
-		// fmt.Println("getURL = ", getURL)
-		go httpclient.ApiKeyGet(getURL)
-	}
-	fmt.Println("getURL = ", getURL)
+// 		// 呼叫挂断事件
+// 	} else if event["Event"] == "HangupRequest" { // "HangupRequest" "Hangup"
+// 		// https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_hangup
+// 		getURL = fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_hangup", VTIGER_URL, callId)
+// 		// fmt.Println("getURL = ", getURL)
+// 		go httpclient.ApiKeyGet(getURL)
+// 	}
+// 	fmt.Println("getURL = ", getURL)
 
-	// 按编号搜索API 暂时用的以下接口
-	// group.GET("/vtiger/lookup", contactsInfo) // 按编号搜索
+// 	// 按编号搜索API 暂时用的以下接口
+// 	// group.GET("/vtiger/lookup", contactsInfo) // 按编号搜索
 
-}
+// }

+ 267 - 267
internal/app/ami/zoho.go

@@ -1,273 +1,273 @@
 package ami
 
-import (
-	"fmt"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/httpclient"
-	"pms-api-go/pkg/lfshook"
-	"strconv"
-	"strings"
-	"time"
-
-	"gopkg.in/ini.v1"
-)
-
-var TimestampList = make(map[string]string)
-var StartTimeList = make(map[string]time.Time) // 事件中 Timestamp 转 StartTime 有问题,重新记录时间
-var CallTypeList = make(map[string]string)     // 临时记录 incoming 和 outgoing ,为incoming时: HangupRequest 中如果CallerIDNum为被叫号码,则未接听
-// var DialStatusList = make(map[string]string)     // 临时记录 DialStatus:ANSWER NOANSWER
-
-func callNotify(event map[string]string) {
-	fmt.Println("=========================================== ")
-	fmt.Println("event[] = ", event)
-	fmt.Println("event = ", event["Event"])
-	fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl)
-	fmt.Println("Channel = ", event["Channel"])
-
-	var callDest string
-	if event["Channel"] != "" {
-		callDest = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0]
-		fmt.Println("callDest = ", callDest)
-	}
-
-	// var callId string
-	var callId = "10000021" // 测试用,每次测试 +1
-	// event["Exten"] = "123456789" // 测试用
-	// fmt.Println("Exten = ", event["Exten"])
-
-	// 读取vtiger配置文件
-	confPath := "/etc/asterisk/pms_api.conf"
-	cfg, err := ini.Load(confPath)
-	if err != nil {
-		lfshook.NewLogger().Error(err)
-		return
-	}
-	ZOHO_URL := cfg.Section("general").Key("zohoUrl").String()
-	if ZOHO_URL == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set zohoUrl")
-		return
-	}
-
-	// var getURL string
-	fmt.Println("Context = ", event["Context"])
-	// 呼叫发起事件
-	// if event["Event"] == "DialBegin" && event["Context"] == "macro-stdexten" {
-	// if event["Event"] == "DialBegin" && strings.Compare(event["Context"], "macro-stdexten") == 0 {
-	if event["Event"] == "DialBegin" {
-
-		/* ===================先放这里测试用 ============================================================
-		// 记录开始时间
-		TimestampList[event["Uniqueid"]] = event["Timestamp"]
-		// StartTimeList[event["Uniqueid"]] = time.Now().Format("2006-01-02 15:04:09")
-		StartTimeList[event["Uniqueid"]] = time.Now()
-		/* ===================先放这里测试用 ============================================================
-		fmt.Printf("Uniqueid=%s\n", event["Uniqueid"])
-		fmt.Printf("Timestamp=%s\n", event["Timestamp"])
-
-		TimestampTmp, err := strconv.Atoi(event["Timestamp"]) // 不要
-		if err != nil {
-			fmt.Printf("转换出错: %v\n", err)
-			return
-		}
-		fmt.Printf("TimestampAtoi=%d\n", TimestampTmp)
-
-		TimestampTmp002, err := strconv.ParseFloat(event["Timestamp"], 64) // OK
-		if err != nil {
-			fmt.Printf("转换出错: %v\n", err)
-			return
-		}
-		fmt.Printf("TimestampParseFloat=%f\n", TimestampTmp002)
-
-		// * ===================================================================================== */
-
-		// Incoming Call - Ringing
-		if event["Context"] == "macro-stdexten" {
-			// if strings.Compare(event["Context"], "macro-stdexten") == 0 {  // OK
-			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ringing&id=10033&from=12300000001&to=123456789
-			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["Exten"], callDest) // 取得from to 可能不准
-			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["DestConnectedLineNum"], event["DestCallerIDNum"])
-
-			fmt.Println("getURL = ", getURL)
-			go httpclient.ZohoGet(getURL)
-
-			// Outgoing Call - Ringing
-		} else if event["Context"] == "macro-trunkdial-failover" {
-			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=ringing&id=10031&from=123456789&to=12300000001
-			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["DestConnectedLineNum"], event["DestCallerIDNum"])
-			fmt.Println("getURL = ", getURL)
-			go httpclient.ZohoGet(getURL)
-		}
-
-		// 呼叫已连接事件
-	} else if event["Event"] == "BridgeEnter" {
-		// Incoming Call - Answered
-		if event["Context"] == "macro-stdexten" {
-			// 记录开始时间
-			// TimestampList[event["Uniqueid"]] = event["Timestamp"] // Uniqueid 有时通话也不合适  NG  // 都改为 Linkedid
-			// StartTimeList[event["Uniqueid"]] = time.Now()         // Uniqueid 有时通话也不合适  NG  // 都改为 Linkedid
-			TimestampList[event["Linkedid"]] = event["Timestamp"] // 呼入时设置 Uniqueid 不是 Linkedid
-			StartTimeList[event["Linkedid"]] = time.Now()         // 呼入时设置 Uniqueid 不是 Linkedid
-
-			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=answered&id=10023&from=12300000001&to=123456789
-			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"])
-			fmt.Println("getURL = ", getURL)
-			go httpclient.ZohoGet(getURL)
-
-			// Outgoing Call - Answered
-		} else if event["Context"] == "macro-trunkdial-failover" && event["Priority"] == "1" { // 注意这里选择 Priority:1 的,便于确认 CallerIDNum,ConnectedLineNum
-			// 记录开始时间
-			// TimestampList[event["Uniqueid"]] = event["Timestamp"] // error 需要把 Uniqueid 改为 Linkedid
-			// StartTimeList[event["Uniqueid"]] = time.Now()         // error 需要把 Uniqueid 改为 Linkedid
-			TimestampList[event["Linkedid"]] = event["Timestamp"]
-			StartTimeList[event["Linkedid"]] = time.Now()
-
-			// https: //www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=answered&id=10003&from=123456789&to=12300000001
-			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"])
-			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"])
-			fmt.Println("getURL = ", getURL)
-			go httpclient.ZohoGet(getURL)
-		}
-
-		// 呼叫挂断事件
-	} else if event["Event"] == "HangupRequest" {
-		/* ===================先放这里测试用 ============================================================
-		// 记录结束时间
-		// TimestampList[event["Uniqueid"]] = event["Timestamp"]
-		// startTimeStamp := int64(TimestampList[event["Uniqueid"]])
-
-		fmt.Println("startTimeStamp Str: ", TimestampList[event["Uniqueid"]])
-		// keyTmp := event["Uniqueid"]
-		// fmt.Println("keyTmp: ", keyTmp)
-		// fmt.Println("startTimeStamp Str: ", TimestampList[keyTmp])
-
-		startTimeStamp, err := strconv.ParseFloat(TimestampList[event["Uniqueid"]], 64) // OK
-		if err != nil {
-			fmt.Printf("startTimeStamp转换出错: %v\n", err)
-			return
-		}
-		fmt.Printf("startTimeStamp=%f\n", startTimeStamp)
-
-		endTimeStamp, err := strconv.ParseFloat(event["Timestamp"], 64) // OK
-		if err != nil {
-			fmt.Printf("endTimeStamp转换出错: %v\n", err)
-			return
-		}
-		fmt.Printf("endTimeStamp=%f\n", endTimeStamp)
-
-		durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second
-		fmt.Println("durationTime=", durationTime)
-		// 删除记录时间
-		// delete(TimestampList, event["Uniqueid"])
-		// * ===================================================================================== */
-		// Incoming Call - Ended
-		// if event["Context"] == "macro-stdexten" {  // Context:DialPlan1
-		if event["Context"] != "macro-trunkdial-failover" {
-			// 获取时间
-			// durationTime := getDuration(event["Uniqueid"], event["Timestamp"])
-			// startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
-			durationTime := getDuration(event["Linkedid"], event["Timestamp"])
-			startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
-			fmt.Println("startTime: ", startTime)
-
-			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ended&id=100&from=12300000001&to=123456789&start_time=2024-12-04 10:32:10&duration=223
-			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime)
-			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDName"], startTime, durationTime)
-
-			fmt.Println("getURL = ", getURL)
-			go httpclient.ZohoGet(getURL)
-
-			// Outgoing Call - Ended
-		} else if event["Context"] == "macro-trunkdial-failover" {
-			// 获取时间
-			// durationTime := getDuration(event["Uniqueid"], event["Timestamp"])
-			durationTime := getDuration(event["Linkedid"], event["Timestamp"])
-			// /* ===================测试用 ============================================================
-			// startTime, _ := time.Parse("2006-01-02 15:04:05", TimestampList[event["Uniqueid"]]) // error   1734328213.774410  =>  0001-01-01 00:00:00 +0000 UTC
-			// startTime := StartTimeList[event["Uniqueid"]]
-			// startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
-			startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
-			fmt.Println("startTime: ", startTime)
-			// durationTime002 := getDuration002(startTime)
-			// durationTime002 := time.Now().Sub(StartTimeList[event["Uniqueid"]]) // ok    durationTime002:  7.78408849s   // should use time.Since instead of time.Now().Sub (S1012)
-			// durationTime002 := time.Since(StartTimeList[event["Uniqueid"]]) // ok    durationTime002:  11.611233458s
-			// fmt.Println("durationTime002: ", durationTime002)
-			// * ===================================================================================== */
-
-			// 删除记录时间
-			delete(StartTimeList, event["Linkedid"])
-
-			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=dailed&state=ended&id=10030&from=123456789&to=12300000001&start_time=2024-12-04 11:32:15&duration=325
-			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime)
-			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"], startTime, durationTime)
-			fmt.Println("getURL = ", getURL)
-			go httpclient.ZohoGet(getURL)
-		}
-	}
-	// fmt.Println("getURL = ", getURL)
-
-	// 测试验证下上面有没有删除,以防TimestampList越来越大
-	for k, v := range TimestampList {
-		fmt.Printf("===Linkedid:%s   Timestamp:%s===\n", k, v)
-	}
-
-	// 按编号搜索API 暂时用的以下接口
-	// group.GET("/vtiger/lookup", contactsInfo) // 按编号搜索
-
-}
-
-// 使用事件中event["Timestamp"]的值计算时间间隔
-// func getDuration(uniqueid string, timeStamp string) int {
-func getDuration(linkedid string, timeStamp string) int { // 都改为 Linkedid
-
-	fmt.Println("startTimeStamp Str: ", TimestampList[linkedid])
-	if TimestampList[linkedid] == "" || timeStamp == "" {
-		fmt.Printf("linkedid 或 timeStamp 为空\n")
-		return 9999
-	}
-
-	startTimeStamp, err := strconv.ParseFloat(TimestampList[linkedid], 64)
-	if err != nil {
-		fmt.Printf("startTimeStamp转换出错: %v\n", err)
-		return 9999
-	}
-	fmt.Printf("startTimeStamp=%f\n", startTimeStamp)
-
-	endTimeStamp, err := strconv.ParseFloat(timeStamp, 64)
-	if err != nil {
-		fmt.Printf("endTimeStamp转换出错: %v\n", err)
-		return 9999
-	}
-	fmt.Printf("endTimeStamp=%f\n", endTimeStamp)
-
-	durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second
-	// fmt.Println("durationTime=", durationTime)                   // durationTime= 35s
-	// fmt.Printf("durationTime=%d\n", durationTime)                // durationTime=35000000000
-	// fmt.Printf("durationTime=%d\n", durationTime.Seconds())      // durationTime=%!d(float64=35)
-	// fmt.Printf("durationTime=%f\n", durationTime.Seconds())      // durationTime=35.000000
-	fmt.Printf("durationTime=%d\n", int(durationTime.Seconds())) // durationTime=35
-
-	// durationTime001 := time.Duration(endTimeStamp - startTimeStamp)
-	// fmt.Println("durationTime001=", durationTime001) // durationTime001= 35ns
-
-	// durationTime003 := time.Duration(endTimeStamp - startTimeStamp).Seconds()
-	// fmt.Println("durationTime003=", durationTime003) // durationTime003= 3.5e-08
-
-	// 删除记录时间
-	delete(TimestampList, linkedid)
-
-	// return durationTime
-	return int(durationTime.Seconds())
-}
-
-// // 程序中取时间计算间隔多少秒
-// func getDuration002(startTime string) time.Duration {
-// 	// durationTime := endTime.Sub(startTime)
-// 	startTime002, err := time.Parse("2006-01-02 15:04:05", startTime)
+// import (
+// 	"fmt"
+// 	"pms-api-go/pkg/configs"
+// 	"pms-api-go/pkg/httpclient"
+// 	"pms-api-go/pkg/lfshook"
+// 	"strconv"
+// 	"strings"
+// 	"time"
+
+// 	"gopkg.in/ini.v1"
+// )
+
+// var TimestampList = make(map[string]string)
+// var StartTimeList = make(map[string]time.Time) // 事件中 Timestamp 转 StartTime 有问题,重新记录时间
+// var CallTypeList = make(map[string]string)     // 临时记录 incoming 和 outgoing ,为incoming时: HangupRequest 中如果CallerIDNum为被叫号码,则未接听
+// // var DialStatusList = make(map[string]string)     // 临时记录 DialStatus:ANSWER NOANSWER
+
+// func callNotify(event map[string]string) {
+// 	fmt.Println("=========================================== ")
+// 	fmt.Println("event[] = ", event)
+// 	fmt.Println("event = ", event["Event"])
+// 	fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl)
+// 	fmt.Println("Channel = ", event["Channel"])
+
+// 	var callDest string
+// 	if event["Channel"] != "" {
+// 		callDest = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0]
+// 		fmt.Println("callDest = ", callDest)
+// 	}
+
+// 	// var callId string
+// 	var callId = "10000021" // 测试用,每次测试 +1
+// 	// event["Exten"] = "123456789" // 测试用
+// 	// fmt.Println("Exten = ", event["Exten"])
+
+// 	// 读取vtiger配置文件
+// 	confPath := "/etc/asterisk/pms_api.conf"
+// 	cfg, err := ini.Load(confPath)
 // 	if err != nil {
-// 		fmt.Println("Error parsing time:", err)
-// 		return 0
+// 		lfshook.NewLogger().Error(err)
+// 		return
+// 	}
+// 	ZOHO_URL := cfg.Section("general").Key("zohoUrl").String()
+// 	if ZOHO_URL == "" {
+// 		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set zohoUrl")
+// 		return
 // 	}
-// 	durationTime := time.Now().Sub(startTime002)
-// 	return durationTime
+
+// 	// var getURL string
+// 	fmt.Println("Context = ", event["Context"])
+// 	// 呼叫发起事件
+// 	// if event["Event"] == "DialBegin" && event["Context"] == "macro-stdexten" {
+// 	// if event["Event"] == "DialBegin" && strings.Compare(event["Context"], "macro-stdexten") == 0 {
+// 	if event["Event"] == "DialBegin" {
+
+// 		/* ===================先放这里测试用 ============================================================
+// 		// 记录开始时间
+// 		TimestampList[event["Uniqueid"]] = event["Timestamp"]
+// 		// StartTimeList[event["Uniqueid"]] = time.Now().Format("2006-01-02 15:04:09")
+// 		StartTimeList[event["Uniqueid"]] = time.Now()
+// 		/* ===================先放这里测试用 ============================================================
+// 		fmt.Printf("Uniqueid=%s\n", event["Uniqueid"])
+// 		fmt.Printf("Timestamp=%s\n", event["Timestamp"])
+
+// 		TimestampTmp, err := strconv.Atoi(event["Timestamp"]) // 不要
+// 		if err != nil {
+// 			fmt.Printf("转换出错: %v\n", err)
+// 			return
+// 		}
+// 		fmt.Printf("TimestampAtoi=%d\n", TimestampTmp)
+
+// 		TimestampTmp002, err := strconv.ParseFloat(event["Timestamp"], 64) // OK
+// 		if err != nil {
+// 			fmt.Printf("转换出错: %v\n", err)
+// 			return
+// 		}
+// 		fmt.Printf("TimestampParseFloat=%f\n", TimestampTmp002)
+
+// 		// * ===================================================================================== */
+
+// 		// Incoming Call - Ringing
+// 		if event["Context"] == "macro-stdexten" {
+// 			// if strings.Compare(event["Context"], "macro-stdexten") == 0 {  // OK
+// 			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ringing&id=10033&from=12300000001&to=123456789
+// 			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["Exten"], callDest) // 取得from to 可能不准
+// 			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["DestConnectedLineNum"], event["DestCallerIDNum"])
+
+// 			fmt.Println("getURL = ", getURL)
+// 			go httpclient.ZohoGet(getURL)
+
+// 			// Outgoing Call - Ringing
+// 		} else if event["Context"] == "macro-trunkdial-failover" {
+// 			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=ringing&id=10031&from=123456789&to=12300000001
+// 			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["DestConnectedLineNum"], event["DestCallerIDNum"])
+// 			fmt.Println("getURL = ", getURL)
+// 			go httpclient.ZohoGet(getURL)
+// 		}
+
+// 		// 呼叫已连接事件
+// 	} else if event["Event"] == "BridgeEnter" {
+// 		// Incoming Call - Answered
+// 		if event["Context"] == "macro-stdexten" {
+// 			// 记录开始时间
+// 			// TimestampList[event["Uniqueid"]] = event["Timestamp"] // Uniqueid 有时通话也不合适  NG  // 都改为 Linkedid
+// 			// StartTimeList[event["Uniqueid"]] = time.Now()         // Uniqueid 有时通话也不合适  NG  // 都改为 Linkedid
+// 			TimestampList[event["Linkedid"]] = event["Timestamp"] // 呼入时设置 Uniqueid 不是 Linkedid
+// 			StartTimeList[event["Linkedid"]] = time.Now()         // 呼入时设置 Uniqueid 不是 Linkedid
+
+// 			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=answered&id=10023&from=12300000001&to=123456789
+// 			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"])
+// 			fmt.Println("getURL = ", getURL)
+// 			go httpclient.ZohoGet(getURL)
+
+// 			// Outgoing Call - Answered
+// 		} else if event["Context"] == "macro-trunkdial-failover" && event["Priority"] == "1" { // 注意这里选择 Priority:1 的,便于确认 CallerIDNum,ConnectedLineNum
+// 			// 记录开始时间
+// 			// TimestampList[event["Uniqueid"]] = event["Timestamp"] // error 需要把 Uniqueid 改为 Linkedid
+// 			// StartTimeList[event["Uniqueid"]] = time.Now()         // error 需要把 Uniqueid 改为 Linkedid
+// 			TimestampList[event["Linkedid"]] = event["Timestamp"]
+// 			StartTimeList[event["Linkedid"]] = time.Now()
+
+// 			// https: //www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=answered&id=10003&from=123456789&to=12300000001
+// 			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"])
+// 			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"])
+// 			fmt.Println("getURL = ", getURL)
+// 			go httpclient.ZohoGet(getURL)
+// 		}
+
+// 		// 呼叫挂断事件
+// 	} else if event["Event"] == "HangupRequest" {
+// 		/* ===================先放这里测试用 ============================================================
+// 		// 记录结束时间
+// 		// TimestampList[event["Uniqueid"]] = event["Timestamp"]
+// 		// startTimeStamp := int64(TimestampList[event["Uniqueid"]])
+
+// 		fmt.Println("startTimeStamp Str: ", TimestampList[event["Uniqueid"]])
+// 		// keyTmp := event["Uniqueid"]
+// 		// fmt.Println("keyTmp: ", keyTmp)
+// 		// fmt.Println("startTimeStamp Str: ", TimestampList[keyTmp])
+
+// 		startTimeStamp, err := strconv.ParseFloat(TimestampList[event["Uniqueid"]], 64) // OK
+// 		if err != nil {
+// 			fmt.Printf("startTimeStamp转换出错: %v\n", err)
+// 			return
+// 		}
+// 		fmt.Printf("startTimeStamp=%f\n", startTimeStamp)
+
+// 		endTimeStamp, err := strconv.ParseFloat(event["Timestamp"], 64) // OK
+// 		if err != nil {
+// 			fmt.Printf("endTimeStamp转换出错: %v\n", err)
+// 			return
+// 		}
+// 		fmt.Printf("endTimeStamp=%f\n", endTimeStamp)
+
+// 		durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second
+// 		fmt.Println("durationTime=", durationTime)
+// 		// 删除记录时间
+// 		// delete(TimestampList, event["Uniqueid"])
+// 		// * ===================================================================================== */
+// 		// Incoming Call - Ended
+// 		// if event["Context"] == "macro-stdexten" {  // Context:DialPlan1
+// 		if event["Context"] != "macro-trunkdial-failover" {
+// 			// 获取时间
+// 			// durationTime := getDuration(event["Uniqueid"], event["Timestamp"])
+// 			// startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+// 			durationTime := getDuration(event["Linkedid"], event["Timestamp"])
+// 			startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+// 			fmt.Println("startTime: ", startTime)
+
+// 			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ended&id=100&from=12300000001&to=123456789&start_time=2024-12-04 10:32:10&duration=223
+// 			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime)
+// 			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDName"], startTime, durationTime)
+
+// 			fmt.Println("getURL = ", getURL)
+// 			go httpclient.ZohoGet(getURL)
+
+// 			// Outgoing Call - Ended
+// 		} else if event["Context"] == "macro-trunkdial-failover" {
+// 			// 获取时间
+// 			// durationTime := getDuration(event["Uniqueid"], event["Timestamp"])
+// 			durationTime := getDuration(event["Linkedid"], event["Timestamp"])
+// 			// /* ===================测试用 ============================================================
+// 			// startTime, _ := time.Parse("2006-01-02 15:04:05", TimestampList[event["Uniqueid"]]) // error   1734328213.774410  =>  0001-01-01 00:00:00 +0000 UTC
+// 			// startTime := StartTimeList[event["Uniqueid"]]
+// 			// startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+// 			startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time
+// 			fmt.Println("startTime: ", startTime)
+// 			// durationTime002 := getDuration002(startTime)
+// 			// durationTime002 := time.Now().Sub(StartTimeList[event["Uniqueid"]]) // ok    durationTime002:  7.78408849s   // should use time.Since instead of time.Now().Sub (S1012)
+// 			// durationTime002 := time.Since(StartTimeList[event["Uniqueid"]]) // ok    durationTime002:  11.611233458s
+// 			// fmt.Println("durationTime002: ", durationTime002)
+// 			// * ===================================================================================== */
+
+// 			// 删除记录时间
+// 			delete(StartTimeList, event["Linkedid"])
+
+// 			// https://www.zohoapis.com/phonebridge/v3/callnotify?type=dailed&state=ended&id=10030&from=123456789&to=12300000001&start_time=2024-12-04 11:32:15&duration=325
+// 			getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime)
+// 			// getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"], startTime, durationTime)
+// 			fmt.Println("getURL = ", getURL)
+// 			go httpclient.ZohoGet(getURL)
+// 		}
+// 	}
+// 	// fmt.Println("getURL = ", getURL)
+
+// 	// 测试验证下上面有没有删除,以防TimestampList越来越大
+// 	for k, v := range TimestampList {
+// 		fmt.Printf("===Linkedid:%s   Timestamp:%s===\n", k, v)
+// 	}
+
+// 	// 按编号搜索API 暂时用的以下接口
+// 	// group.GET("/vtiger/lookup", contactsInfo) // 按编号搜索
 
 // }
+
+// // 使用事件中event["Timestamp"]的值计算时间间隔
+// // func getDuration(uniqueid string, timeStamp string) int {
+// func getDuration(linkedid string, timeStamp string) int { // 都改为 Linkedid
+
+// 	fmt.Println("startTimeStamp Str: ", TimestampList[linkedid])
+// 	if TimestampList[linkedid] == "" || timeStamp == "" {
+// 		fmt.Printf("linkedid 或 timeStamp 为空\n")
+// 		return 9999
+// 	}
+
+// 	startTimeStamp, err := strconv.ParseFloat(TimestampList[linkedid], 64)
+// 	if err != nil {
+// 		fmt.Printf("startTimeStamp转换出错: %v\n", err)
+// 		return 9999
+// 	}
+// 	fmt.Printf("startTimeStamp=%f\n", startTimeStamp)
+
+// 	endTimeStamp, err := strconv.ParseFloat(timeStamp, 64)
+// 	if err != nil {
+// 		fmt.Printf("endTimeStamp转换出错: %v\n", err)
+// 		return 9999
+// 	}
+// 	fmt.Printf("endTimeStamp=%f\n", endTimeStamp)
+
+// 	durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second
+// 	// fmt.Println("durationTime=", durationTime)                   // durationTime= 35s
+// 	// fmt.Printf("durationTime=%d\n", durationTime)                // durationTime=35000000000
+// 	// fmt.Printf("durationTime=%d\n", durationTime.Seconds())      // durationTime=%!d(float64=35)
+// 	// fmt.Printf("durationTime=%f\n", durationTime.Seconds())      // durationTime=35.000000
+// 	fmt.Printf("durationTime=%d\n", int(durationTime.Seconds())) // durationTime=35
+
+// 	// durationTime001 := time.Duration(endTimeStamp - startTimeStamp)
+// 	// fmt.Println("durationTime001=", durationTime001) // durationTime001= 35ns
+
+// 	// durationTime003 := time.Duration(endTimeStamp - startTimeStamp).Seconds()
+// 	// fmt.Println("durationTime003=", durationTime003) // durationTime003= 3.5e-08
+
+// 	// 删除记录时间
+// 	delete(TimestampList, linkedid)
+
+// 	// return durationTime
+// 	return int(durationTime.Seconds())
+// }
+
+// // // 程序中取时间计算间隔多少秒
+// // func getDuration002(startTime string) time.Duration {
+// // 	// durationTime := endTime.Sub(startTime)
+// // 	startTime002, err := time.Parse("2006-01-02 15:04:05", startTime)
+// // 	if err != nil {
+// // 		fmt.Println("Error parsing time:", err)
+// // 		return 0
+// // 	}
+// // 	durationTime := time.Now().Sub(startTime002)
+// // 	return durationTime
+
+// // }

+ 6 - 8
internal/app/http_server/index.go

@@ -2,12 +2,10 @@ package http_server
 
 import (
 	"fmt"
-	"pms-api-go/api/admin/auth"
 
-	// "pms-api-go/internal/app/http_server/bill" // 20230411 pms 删除
-	"pms-api-go/internal/app/http_server/pbx"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/lfshook"
+	"crm-api/internal/app/http_server/pbx"
+	"crm-api/pkg/configs"
+	"crm-api/pkg/lfshook"
 	"syscall"
 
 	"github.com/gin-contrib/gzip"
@@ -19,13 +17,13 @@ func StartRoute() {
 	router := gin.New()
 
 	// pms 对应 =======================
-	auth.AddAuth()
+	// auth.AddAuth()  // pms和crm分开
 
 	// vtiger 对应 =======================
-	auth.VtigerAddAuth()
+	// auth.VtigerAddAuth())  // 20250102 删除
 
 	// zoho 对应 =======================
-	auth.ZohoAddAuth()
+	// auth.ZohoAddAuth() // 20250102 删除
 
 	// 配置中间件
 	// 使用 logrus 自定义 logger, 可以统一写入日志文件

+ 2 - 1
internal/app/http_server/pbx/pbx.go

@@ -4,8 +4,9 @@
 package pbx
 
 import (
+	"crm-api/api/admin"
+
 	"github.com/gin-gonic/gin"
-	"pms-api-go/api/admin"
 )
 
 func Enable(router *gin.Engine) {

+ 0 - 7
internal/app/http_server/socketio_client_tool/socketio_client_tool.go

@@ -3,13 +3,6 @@
 
 package socketio_client_tool
 
-import (
-	"github.com/gin-gonic/gin"
-	"io/fs"
-	"net/http"
-	"pms-api-go/web"
-)
-
 /* 20230419 pms 删除 =======================================================================================================
 
 func Enable(router *gin.Engine) {

+ 42 - 18
internal/app/index.go

@@ -1,17 +1,18 @@
 package app
 
 import (
+	"crm-api/api/admin/zoho"
+	"crm-api/internal/app/ami"
+	httpServer "crm-api/internal/app/http_server"
+	"crm-api/internal/app/mysql"
+	"crm-api/internal/app/redis"
+
+	socketio "crm-api/internal/app/socket_io"
+	"crm-api/internal/app/status"
 	"fmt"
-	"pms-api-go/internal/app/ami"
-	httpServer "pms-api-go/internal/app/http_server"
-	"pms-api-go/internal/app/mysql"
-	"pms-api-go/internal/app/redis"
-	socketio "pms-api-go/internal/app/socket_io"
-	"pms-api-go/internal/app/status"
 	"sync"
 
-	"pms-api-go/pkg/i18n"
-	"pms-api-go/pkg/lfshook"
+	"crm-api/pkg/lfshook"
 
 	"gopkg.in/ini.v1"
 )
@@ -23,7 +24,7 @@ func StartApp() {
 	mysql.CreateDBInstance()
 	redis.CreateRedisInstance()
 	socketio.StartSocketIO()
-	i18n.InitBundle()
+	// i18n.InitBundle()
 	// initCronTask()
 
 	go httpServer.StartRoute()
@@ -45,16 +46,16 @@ func StartApp() {
 	// go plugin.StartUP() // 20230411 pms 删除
 
 	// /* 20241128 vtiger crm 对应 =======================================================================================================
-	PmsConfPath := "/etc/asterisk/pms_api.conf"
-	pmsCfg, pmsErr := ini.Load(PmsConfPath)
-	if pmsErr != nil {
-		lfshook.NewLogger().Error(pmsErr)
+	CrmConfPath := "/etc/asterisk/crm_api.conf"
+	crmCfg, crmErr := ini.Load(CrmConfPath)
+	if crmErr != nil {
+		lfshook.NewLogger().Error(crmErr)
 		return
 	}
-	PmsEnabled := pmsCfg.Section("general").Key("enable").String()
-	ApiType := pmsCfg.Section("general").Key("apitype").String()
-	if PmsEnabled == "" || ApiType == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set enabled or apitype")
+	CrmEnabled := crmCfg.Section("general").Key("enable").String()
+	ApiType := crmCfg.Section("general").Key("apiType").String()
+	if CrmEnabled == "" || ApiType == "" {
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set enabled or apiType")
 		return
 	}
 
@@ -75,7 +76,30 @@ func StartApp() {
 	if VtigerEnabled == "yes" && PmsEnabled == "no" { // pms基本是数据库操作,但是有个叫醒,会产生事件,所以当 pms 开启时,不能开启 vtiger
 	// ************************************************************************** */
 	fmt.Println("ApiType = ", ApiType)
-	if ApiType == "CRM_vtiger" || ApiType == "CRM_zoho" {
+	// if ApiType == "CRM_vtiger" || ApiType == "CRM_zoho" {  // 把 crm 和 pms 分开了
+	if CrmEnabled == "yes" {
+		if ApiType == "CRM_zoho" {
+			fmt.Println("Ticker ...")
+			// 每隔55分钟刷新token
+			/* ************************************************************************** 定时器放在这里没有用
+			ticker := time.NewTicker(1 * time.Minute)
+			defer ticker.Stop()
+			done := make(chan bool)
+			go func() {
+				for {
+					select {
+					case <-done:
+						return
+					case t := <-ticker.C:
+						fmt.Println("Tick at", t)
+						zoho.RefreshToken()
+					}
+				}
+			}()
+			// ************************************************************************** */
+			go zoho.RefreshTokenTicker()
+		}
+		fmt.Println("ami.StartAMI ...")
 		go ami.StartAMI(func() {
 			lfshook.NewLogger().Info("ami callback function")
 			// 首次连接才进行初始化

+ 1 - 1
internal/app/mysql/extension.go

@@ -1,7 +1,7 @@
 package mysql
 
 import (
-	"pms-api-go/pkg/lfshook"
+	"crm-api/pkg/lfshook"
 )
 
 /* *****************************************************************************************

+ 2 - 2
internal/app/mysql/index.go

@@ -1,10 +1,10 @@
 package mysql
 
 import (
+	"crm-api/pkg/configs"
+	"crm-api/pkg/lfshook"
 	"fmt"
 	"os"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/lfshook"
 	"syscall"
 	"time"
 

+ 2 - 2
internal/app/redis/extension.go

@@ -2,10 +2,10 @@ package redis
 
 import (
 	"context"
+	"crm-api/internal/app/ami/model"
+	"crm-api/pkg/lfshook"
 	"encoding/json"
 	"fmt"
-	"pms-api-go/internal/app/ami/model"
-	"pms-api-go/pkg/lfshook"
 	"strings"
 )
 

+ 2 - 2
internal/app/redis/index.go

@@ -2,9 +2,9 @@ package redis
 
 import (
 	"context"
+	"crm-api/pkg/configs"
+	"crm-api/pkg/lfshook"
 	"fmt"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/lfshook"
 	"syscall"
 
 	"github.com/go-redis/redis/v8"

+ 1 - 1
internal/app/socket_io/index.go

@@ -1,7 +1,7 @@
 package socket_io
 
 import (
-	"pms-api-go/pkg/lfshook"
+	"crm-api/pkg/lfshook"
 
 	socketio "github.com/googollee/go-socket.io"
 )

+ 3 - 26
internal/app/status/index.go

@@ -1,23 +1,14 @@
 package status
 
 import (
-	"pms-api-go/api/admin/adminModel"
-	"pms-api-go/internal/app/ami/action"
-	"pms-api-go/internal/app/mysql"
-	"pms-api-go/internal/app/redis"
-	socketio "pms-api-go/internal/app/socket_io"
-	"pms-api-go/pkg/lfshook"
+	"crm-api/internal/app/ami/action"
+	"crm-api/internal/app/redis"
+	"crm-api/pkg/lfshook"
 	"strings"
-	"time"
 )
 
 // InitAsterisk 初始化状态
 func InitAsterisk() {
-	var extensions []adminModel.GeoipRule
-	if err := mysql.DBOrmInstance.Find(&extensions); err != nil {
-		lfshook.NewLogger().Errorf("db find failure %+v", err)
-		return
-	}
 
 	// 清空存在的状态
 	redis.ExtensionClean()
@@ -72,17 +63,3 @@ func initExtensionStatus() {
 		redis.ExtensionSetStatus(event)
 	}
 }
-
-func initScheduleChannel() {
-	ticker := time.NewTicker(1500 * time.Millisecond)
-
-	for {
-		<-ticker.C
-		data, err := action.CoreShowChannels()
-		if err != nil {
-			lfshook.NewLogger().Errorf("initScheduleChannel %+v", err)
-			continue
-		}
-		socketio.SocketIOServer.BroadcastToNamespace("", "CustomCoreShowChannel", data)
-	}
-}

+ 0 - 27
pkg/commonService/check.go

@@ -1,27 +0,0 @@
-package commonService
-
-import (
-	"net/http"
-	"pms-api-go/api"
-	"pms-api-go/internal/app/mysql"
-
-	"github.com/gin-gonic/gin"
-)
-
-// 检查是否存在一条记录
-func CheckHasOne(bean interface{}, ctx *gin.Context, id int64) {
-	session := mysql.DBOrmInstance.NewSession()
-	if id != 0 {
-		session = session.Where("id != ?", id)
-	}
-	has, err := session.Get(bean)
-	if err != nil {
-		api.Error(ctx, http.StatusInternalServerError, err.Error())
-		return
-	}
-	if has {
-		api.Success(ctx, false)
-		return
-	}
-	api.Success(ctx, true)
-}

+ 0 - 15
pkg/commonService/error.go

@@ -1,15 +0,0 @@
-package commonService
-
-import "pms-api-go/pkg/lfshook"
-
-//HasError 统一处理错误
-func HasError(err error, msg ...string) {
-	if err == nil {
-		return
-	}
-	if len(msg) == 0 {
-		lfshook.NewLogger().Errorf("has error %+v", err)
-	} else {
-		lfshook.NewLogger().Errorf("has error %+v, msg %+v", err, msg)
-	}
-}

+ 1 - 1
pkg/configs/decode.go

@@ -1,9 +1,9 @@
 package configs
 
 import (
+	"crm-api/pkg/lfshook"
 	"io/ioutil"
 	"os"
-	"pms-api-go/pkg/lfshook"
 
 	"github.com/gofrs/uuid"
 	log "github.com/sirupsen/logrus"

+ 1 - 1
pkg/configs/push.go

@@ -1,9 +1,9 @@
 package configs
 
 import (
+	"crm-api/pkg/lfshook"
 	"encoding/json"
 	"io/ioutil"
-	"pms-api-go/pkg/lfshook"
 )
 
 type PushConfig struct {

+ 7 - 7
pkg/httpclient/index.go

@@ -2,11 +2,11 @@ package httpclient
 
 import (
 	"bytes"
+	"crm-api/pkg/lfshook"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
-	"pms-api-go/pkg/lfshook"
 	"time"
 
 	"gopkg.in/ini.v1"
@@ -53,7 +53,7 @@ func BasicAuthGet(url string) *http.Response {
 
 	// 读取vtiger配置文件中的用户密码
 	// confPath := "/etc/asterisk/vtiger_api.conf"
-	confPath := "/etc/asterisk/pms_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
 	cfg, err := ini.Load(confPath)
 	if err != nil {
 		lfshook.NewLogger().Error(err)
@@ -62,7 +62,7 @@ func BasicAuthGet(url string) *http.Response {
 	BasicAuthUser := cfg.Section("general").Key("vtigerBasicAuthUser").String()
 	BasicAuthPWD := cfg.Section("general").Key("vtigerBasicAuthPWD").String()
 	if BasicAuthUser == "" || BasicAuthPWD == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set BasicAuthUser or BasicAuthPWD")
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set BasicAuthUser or BasicAuthPWD")
 		return nil
 	}
 	// 认证
@@ -98,7 +98,7 @@ func ApiKeyGet(url string) *http.Response {
 
 	// 读取vtiger配置文件中的用户密码
 	// confPath := "/etc/asterisk/vtiger_api.conf"
-	confPath := "/etc/asterisk/pms_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
 	cfg, err := ini.Load(confPath)
 	if err != nil {
 		lfshook.NewLogger().Error(err)
@@ -107,7 +107,7 @@ func ApiKeyGet(url string) *http.Response {
 	ApiKey := cfg.Section("general").Key("vtigerApiKey").String()
 	ApiKeyValue := cfg.Section("general").Key("vtigerApiKeyValue").String()
 	if ApiKey == "" || ApiKeyValue == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set ApiKey or ApiKeyValue")
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set ApiKey or ApiKeyValue")
 		return nil
 	}
 	// 认证
@@ -151,7 +151,7 @@ func ZohoGet(url string) *http.Response {
 
 	// 读取vtiger配置文件中的用户密码
 	// confPath := "/etc/asterisk/vtiger_api.conf"
-	confPath := "/etc/asterisk/pms_api.conf"
+	confPath := "/etc/asterisk/crm_api.conf"
 	cfg, err := ini.Load(confPath)
 	if err != nil {
 		lfshook.NewLogger().Error(err)
@@ -159,7 +159,7 @@ func ZohoGet(url string) *http.Response {
 	}
 	AccessToken := cfg.Section("general").Key("zohoAccessToken").String()
 	if AccessToken == "" {
-		lfshook.NewLogger().Error("/etc/asterisk/pms_api.conf not set zohoAccessToken")
+		lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoAccessToken")
 		return nil
 	}
 	// 认证

+ 0 - 64
pkg/i18n/index.go

@@ -1,64 +0,0 @@
-package i18n
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/fs"
-	"pms-api-go/pkg/configs"
-	"pms-api-go/pkg/utils"
-	"pms-api-go/web"
-
-	"github.com/nicksnyder/go-i18n/v2/i18n"
-	"github.com/sirupsen/logrus"
-	"golang.org/x/text/language"
-)
-
-var i18nBundle *i18n.Bundle
-
-func InitBundle() {
-	i18nBundle = i18n.NewBundle(language.English)
-	i18nBundle.RegisterUnmarshalFunc("json", json.Unmarshal)
-	files := []string{"zh_CN", "en_US"}
-	for _, file := range files {
-		filePath := fmt.Sprintf("%s/%s.json", configs.ConfigGlobal.I18nPath, file)
-		if utils.PathExists(filePath) {
-			_, err := i18nBundle.LoadMessageFile(filePath)
-			if err != nil {
-				logrus.Errorf("load i18n file %+v", err)
-			}
-			continue
-		}
-
-		buf, err := fs.ReadFile(web.ResourcesFiles, fmt.Sprintf("resources/%s.json", file))
-		if err != nil {
-			if err != nil {
-				logrus.Errorf("load i18n file %+v", err)
-			}
-			continue
-		}
-		i18nBundle.ParseMessageFileBytes(buf, fmt.Sprintf("resources/%s.json", file))
-	}
-}
-
-func Message(lang string, id string) string {
-	localizer := i18n.NewLocalizer(i18nBundle, lang)
-	result, err := localizer.Localize(&i18n.LocalizeConfig{
-		DefaultMessage: &i18n.Message{
-			ID: id,
-		},
-	})
-	if err != nil {
-		// lfshook.NewLogger().Warn(err)
-		return id
-	}
-	return result
-}
-
-// func (b *i18n.Bundle) LoadMessageFileFS(fsys fs.FS, path string) (*i18n.MessageFile, error) {
-// 	buf, err := fs.ReadFile(fsys, path)
-// 	if err != nil {
-// 		return nil, err
-// 	}
-
-// 	return b.ParseMessageFileBytes(buf, path)
-// }

+ 3 - 3
pkg/utils/cmd.go

@@ -3,13 +3,13 @@ package utils
 import (
 	"bytes"
 	"context"
+	"crm-api/pkg/lfshook"
 	"os/exec"
-	"pms-api-go/pkg/lfshook"
 	"strings"
 	"time"
 )
 
-//ExecCmdAsync 执行指定命令
+// ExecCmdAsync 执行指定命令
 func ExecCmdAsync(cmdName string, arg ...string) (stdOut, errOut string, err error) {
 	lfshook.NewLogger().Infof("ExecCmdAsync cmd %s %s", cmdName, arg)
 	cmd := exec.Command(cmdName, arg...)
@@ -30,7 +30,7 @@ func ExecCmdAsync(cmdName string, arg ...string) (stdOut, errOut string, err err
 	return outStr, errStr, err
 }
 
-//ExecCmd 执行指定命令
+// ExecCmd 执行指定命令
 func ExecCmd(cmdName string, arg ...string) (stdOut, errOut string, err error) {
 	///etc/scripts/sysinfo.sh 会不断刷新
 	if cmdName != "/etc/scripts/sysinfo.sh" {

+ 1 - 1
pkg/utils/exit.go

@@ -1,9 +1,9 @@
 package utils
 
 import (
+	"crm-api/pkg/lfshook"
 	"os"
 	"os/signal"
-	"pms-api-go/pkg/lfshook"
 	"syscall"
 )
 

+ 0 - 63
pkg/voicemail/parse.go

@@ -1,63 +0,0 @@
-package voicemail
-
-import (
-	"encoding/json"
-	"pms-api-go/pkg/lfshook"
-	"strings"
-
-	"gopkg.in/ini.v1"
-)
-
-// {"vmBox": "100", "newMsgs": "9"}
-type VocieMailNew struct {
-	VMBox   string `json:"vmBox"`
-	NewMsgs string `json:"newMsgs"`
-}
-
-func ParseVoiceMailNew(in string) map[string]string {
-	data := strings.Split(in, "\n")
-	result := make(map[string]string)
-	for _, item := range data {
-		member := &VocieMailNew{}
-		json.Unmarshal([]byte(item), member)
-		result[member.VMBox] = member.NewMsgs
-	}
-	return result
-}
-
-type VoiceMail struct {
-	ID        string `json:"id"`
-	Extension string `json:"extension"`
-	MsgPath   string `json:"msgPath"`
-	MsgID     string `json:"msgID"`
-	Date      string `json:"date"`
-	Duration  string `json:"duration"`
-	CallerID  string `json:"callerID"`
-	Type      string `json:"type"`
-	Origdate  string `json:"origdate"`
-}
-
-func ParseVoiceMail(in string) []*VoiceMail {
-	data := strings.Split(in, "\n")
-	result := make([]*VoiceMail, len(data))
-	for index, item := range data {
-		vmInfo := strings.Split(item, ",")
-		member := &VoiceMail{
-			ID:        vmInfo[0],
-			Extension: vmInfo[1],
-			MsgPath:   vmInfo[5],
-			MsgID:     vmInfo[4],
-			Type:      vmInfo[3],
-		}
-		iniInfo, err := ini.Load(vmInfo[2])
-		if err != nil {
-			lfshook.NewLogger().Error(err)
-		} else {
-			member.Duration = iniInfo.Section("message").Key("duration").Value()
-			member.Origdate = iniInfo.Section("message").Key("origdate").Value()
-			member.CallerID = iniInfo.Section("message").Key("callerid").Value()
-		}
-		result[index] = member
-	}
-	return result
-}

+ 0 - 23
pkg/weblog/index.go

@@ -1,23 +0,0 @@
-package weblog
-
-import (
-	"fmt"
-	"os"
-	"time"
-
-	"github.com/sirupsen/logrus"
-)
-
-var fail2banLog = "/var/log/invalid_web_visit.log"
-
-func AuthError(ip, msg string) {
-	f, err := os.OpenFile(fail2banLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
-	if err != nil {
-		logrus.Error("auth open error", err)
-	}
-	defer f.Close()
-	context := fmt.Sprintf("%s pbx api auth error from '%s', msg: %s\n", time.Now(), ip, msg)
-	if _, err := f.Write([]byte(context)); err != nil {
-		logrus.Error("auth write error", err)
-	}
-}

+ 0 - 12
web/embed.go

@@ -1,12 +0,0 @@
-package web
-
-import "embed"
-
-//go:embed www/index.html
-var IndexFile string
-
-////go:embed www/socketio-client-tool/*
-var SocketIOClientTool embed.FS
-
-//go:embed resources/*
-var ResourcesFiles embed.FS

+ 0 - 179
web/resources/en_US.json

@@ -1,179 +0,0 @@
-{
-	"weblog": {
-		"weblog_file_name": "User operation logs.xlsx",
-		"weblog_file_worksheet_name": "Operation logs",
-		"weblog_title_time": "Time",
-		"weblog_title_username": "Username",
-		"weblog_title_role": "User",
-		"weblog_title_ip": "IP Address",
-		"weblog_title_operation": "Operation",
-		"weblog_role_admin": "Manager User",
-		"weblog_role_operator": "Operator User",
-		"weblog_role_extension": "Extension User",
-		"weblog_role_conference": "Conference Manager",
-		"weblog_role_bill": "Billing Manager",
-		"weblog_role_api": "API"
-	},
-	"pincode": {
-		"pincode_file_name": "Extensions PIN code list.xlsx",
-		"pincode_worksheet_name": "Extensions PIN code list",
-		"pincode_name": "Username",
-		"pincode_extension": "Extension",
-		"pincode_code": "PIN Code"
-	},
-	"exten_digital": {
-		"exten_digital_file_name": "Digital Extension.xlsx",
-		"exten_digital_worksheet_name": "Extension",
-		"exten_digital_prop_extension_number": "Extension Number",
-		"exten_digital_prop_enable": "Enable",
-		"exten_digital_prop_username": "Name",
-		"exten_digital_prop_password": "Password",
-		"exten_digital_prop_coocall_password": "CooCall Password",
-		"exten_digital_prop_pin_code": "Quick Register Code",
-		"exten_digital_prop_email": "Email",
-		"exten_digital_prop_outbound_cid_1": "Outbound CID 1",
-		"exten_digital_prop_outbound_cid_2": "Outbound CID 2",
-		"exten_digital_prop_mobile_number": "Mobile Number",
-		"exten_digital_prop_dial_plan_id": "Dial Permission(id,default is 1)",
-		"exten_digital_prop_language": "Language",
-		"exten_digital_prop_music_on_hold_id": "Music On Hold(id)",
-		"exten_digital_prop_voicemail_enable": "Voicemail",
-		"exten_digital_prop_voicemail_password": "Voicemail Password",
-		"exten_digital_prop_remote_extension": "Remote Extension",
-		"exten_digital_prop_regist_count": "Simultaneous Register Count(default is 1)",
-		"exten_digital_prop_video_call_enable": "Video Call Enable",
-		"exten_digital_prop_video_call_codec": "Video Codec",
-		"exten_digital_prop_web_rtc": "Web Portal",
-		"exten_digital_prop_web_login": "Web Login",
-		"exten_digital_prop_app": "App",
-		"exten_digital_prop_dnd": "DND",
-		"exten_digital_prop_recording": "Call Recording",
-		"exten_digital_prop_spy": "Call Spy",
-		"exten_digital_prop_regist_expiration": "Register Expiration",
-		"exten_digital_prop_transport": "Transport Protocol",
-		"exten_digital_prop_dtmf": "DTMF Mode",
-		"exten_digital_prop_srtp": "SRTP",
-		"exten_digital_prop_qualify": "Qualify(sec.)",
-		"exten_digital_prop_qualify_timeout": "Qualify Timeout(sec.)",
-		"exten_digital_prop_nat": "NAT Support",
-		"exten_digital_prop_iax": "IAX2 Extension",
-		"exten_digital_prop_audio_codec": "Audio Codec(s)",
-		"exten_digital_prop_permit": "Permit IP",
-		"exten_digital_prop_send_pai": "Send PAI",
-		"exten_digital_prop_send_rpid": "Send RPID",
-		"exten_digital_prop_rtp_timeout": "RTP Timeout",
-		"exten_digital_prop_group_number": "Pickup Group(Default 1)",
-		"exten_digital_prop_inband_progress": "Inband Progress",
-		"exten_digital_prop_white_list_tag_id": "White List Tag(id, default 0)"
-	},
-	"did": {
-		"did_template_file_name": "DID template file.xlsx",
-		"did_file_name": "DID list file.xlsx",
-		"did_file_worksheet_name": "DID",
-		"did_title_callDestNum": "DID Number",
-		"did_title_destType": "Destination Type",
-		"did_title_dest": "Destination",
-		"did_title_destTrunkType": "Destination Trunk Type",
-		"did_title_destTrunkAddNumber": "Destination Trunk Add Number",
-		"did_title_ringTone": "Ring Tone",
-		"did_error_title": "Type Error",
-		"did_error_message": "You must select TYPE from the select list!"
-	},
-	"phonebook": {
-		"phonebook_template_file_name": "Contact template file.xlsx",
-		"phonebook_contact_file_name": "Contact list file.xlsx",
-		"phonebook_contact_file_worksheet_name": "Contacts",
-		"phonebook_title_name": "Name",
-		"phonebook_title_phone": "Phone Number",
-		"phonebook_title_email": "E-Mail",
-		"phonebook_title_company": "Company Name",
-		"phonebook_title_dial_code": "Speed Dial Number"
-	},
-	"blacklist": {
-		"blacklist_template_file_name": "Blacklist Template File.xlsx",
-		"blacklist_file_name": "Blacklist File.xlsx",
-		"blacklist_file_worksheet_name": "Blacklist",
-		"blacklist_title_number": "Phone Number",
-		"blacklist_title_createUser": "Create User",
-		"blacklist_title_createTime": "Create Time"
-	},
-	"trunk": {
-		"trunk_sip_file_name": "Sip Trunk.xlsx",
-		"trunk_sip_worksheet_name": "Sip",
-		"trunk_sip_prop_name": "Trunk Name",
-		"trunk_sip_prop_enable": "Enable",
-		"trunk_sip_prop_trunk_type": "Trunk Type",
-		"trunk_sip_prop_auth": "Authentication",
-		"trunk_sip_prop_server": "Host",
-		"trunk_sip_prop_port": "Port",
-		"trunk_sip_prop_auth_user": "AuthUser",
-		"trunk_sip_prop_username": "User Name",
-		"trunk_sip_prop_password": "Password",
-		"trunk_sip_prop_contact": "Contact",
-		"trunk_sip_prop_regist_expiration": "Register Expiration",
-		"trunk_sip_prop_retry_interval": "Retry Interval",
-		"trunk_sip_prop_max_retry": "Max Retry",
-		"trunk_sip_prop_identify_by": "Identify By",
-		"trunk_sip_prop_max_expiration": "Max Expiration",
-		"trunk_sip_prop_fax_detect": "Fax Detect",
-		"trunk_sip_prop_qualify": "Qualify",
-		"trunk_sip_prop_srtp": "SRTP",
-		"trunk_sip_prop_nat": "NAT Surpport",
-		"trunk_sip_prop_client_uri": "Client URI",
-		"trunk_sip_prop_transport": "Transport Protocol",
-		"trunk_sip_prop_server_uri": "Server URI",
-		"trunk_sip_prop_language": "Prompts Language",
-		"trunk_sip_prop_aor_contact": "AOR Contact",
-		"trunk_sip_prop_call_max": "Simultaneous Calls",
-		"trunk_sip_prop_recording": "Call Recording",
-		"trunk_sip_prop_cid_preferred": "Preferred Outbound CID",
-		"trunk_sip_prop_from_user": "From User",
-		"trunk_sip_prop_outbound_cid": "Outbound CID",
-		"trunk_sip_prop_dtmf": "DTMF Mode",
-		"trunk_sip_prop_dial_plan_id": "Dial Permission(id,default is 0)",
-		"trunk_sip_prop_from_domain": "From Domain",
-		"trunk_sip_prop_send_pai": "Send PAI",
-		"trunk_sip_prop_send_rpid": "Send RPID",
-		"trunk_sip_prop_rtp_timeout": "RTP Timeout",
-		"trunk_sip_prop_video_codec": "Video Codec",
-		"trunk_sip_prop_audio_codec": "Audio Codec(s)",
-		"trunk_sip_prop_out_proxy": "Proxy",
-		"trunk_sip_prop_out_proxy_port": "Port"
-	},
-	"bill": {
-		"bill_rate_file_name": "Bill Rate.xlsx",
-		"bill_rate_file_worksheet_name": "Rate list",
-		"bill_rate_prop_enable": "Activate",
-		"bill_rate_prop_remark": "Remark",
-		"bill_rate_prop_prefix": "Prefix",
-		"bill_rate_prop_number_length": "Number Length",
-		"bill_rate_prop_init_cost": "Initial Cost",
-		"bill_rate_prop_init_time": "Initial Time(s)",
-		"bill_rate_prop_rate": "Rate",
-		"bill_rate_prop_bill_unit": "Billing Unit(s)",
-		"bill_rate_prop_bill_time_start": "Start Time",
-		"bill_rate_prop_bill_time_end": "End Time",
-		"bill_rate_prop_extension_list": "Extensions",
-		"bill_charge_log":"Charge Log.xlsx",
-		"bill_charge_time":"Time",
-		"bill_charge_extension":"Extension",
-		"bill_charge_pre_balance":"Pre Balance",
-		"bill_charge_charge_amount":"Charge Amount",
-		"bill_charge_post_balance":"Post Balance",
-		"bill_call_log":"Call Log.xlsx",
-		"bill_call_time":"Start Time",
-		"bill_call_src":"Src",
-		"bill_call_dst":"Dst",
-		"bill_call_duration":"Duration",
-		"bill_call_trunk":"Trunk",
-		"bill_call_cast":"Cast",
-		"bill_statistics_file_name": "Bill Call Statistics.xlsx",
-		"bill_statistics_file_worksheet_name": "Call log statistics",
-		"bill_statistics_title_search_time": "Statistics By",
-		"bill_statistics_title_extension": "Extension",
-		"bill_statistics_title_call": "Calls",
-		"bill_statistics_title_time": "Total Call Time",
-		"bill_statistics_title_time_avg": "Average Call Time",
-		"bill_statistics_title_total_cost": "Total Cost"
-	}
-}

BIN
web/resources/no_logo.png


+ 0 - 179
web/resources/zh_CN.json

@@ -1,179 +0,0 @@
-{
-	"weblog": {
-		"weblog_file_name": "操作日志.xlsx",
-		"weblog_file_worksheet_name": "操作日志",
-		"weblog_title_time": "时间",
-		"weblog_title_username": "用户名",
-		"weblog_title_role": "用户",
-		"weblog_title_ip": "IP地址",
-		"weblog_title_operation": "操作",
-		"weblog_role_admin": "管理员",
-		"weblog_role_operator": "接线员",
-		"weblog_role_extension": "分机用户",
-		"weblog_role_conference": "会议管理员",
-		"weblog_role_bill": "计费用户",
-		"weblog_role_api": "API"
-	},
-	"pincode": {
-		"pincode_file_name": "分机PIN码.xlsx",
-		"pincode_worksheet_name": "分机PIN码",
-		"pincode_name": "用户名",
-		"pincode_extension": "分机号",
-		"pincode_code": "PIN码"
-	},
-	"exten_digital": {
-		"exten_digital_file_name": "数字分机.xlsx",
-		"exten_digital_worksheet_name": "分机",
-		"exten_digital_prop_extension_number": "分机号",
-		"exten_digital_prop_enable": "启用",
-		"exten_digital_prop_username": "名称",
-		"exten_digital_prop_password": "密码",
-		"exten_digital_prop_coocall_password": "CooCall密码",
-		"exten_digital_prop_pin_code": "快速注册码",
-		"exten_digital_prop_email": "邮箱",
-		"exten_digital_prop_outbound_cid_1": "外呼显示号码1",
-		"exten_digital_prop_outbound_cid_2": "外呼显示号码2",
-		"exten_digital_prop_mobile_number": "手机号码",
-		"exten_digital_prop_dial_plan_id": "拨号权限(编号,默认1)",
-		"exten_digital_prop_language": "语言",
-		"exten_digital_prop_music_on_hold_id": "通话保持音乐(编号)",
-		"exten_digital_prop_voicemail_enable": "语音留言开启",
-		"exten_digital_prop_voicemail_password": "语音信箱密码",
-		"exten_digital_prop_remote_extension": "远程分机",
-		"exten_digital_prop_regist_count": "同时注册数量",
-		"exten_digital_prop_video_call_enable": "视频通话开启",
-		"exten_digital_prop_video_call_codec": "视频编码",
-		"exten_digital_prop_web_rtc": "网页分机",
-		"exten_digital_prop_web_login": "网页登录",
-		"exten_digital_prop_app": "App注册",
-		"exten_digital_prop_dnd": "免打扰",
-		"exten_digital_prop_recording": "通话录音",
-		"exten_digital_prop_spy": "通话监听",
-		"exten_digital_prop_regist_expiration": "注册超时",
-		"exten_digital_prop_transport": "传输协议",
-		"exten_digital_prop_dtmf": "DTMF模式",
-		"exten_digital_prop_srtp": "SRTP",
-		"exten_digital_prop_qualify": "在线检测间隔(秒)",
-		"exten_digital_prop_qualify_timeout": "在线检测超时(秒)",
-		"exten_digital_prop_nat": "NAT支持",
-		"exten_digital_prop_iax": "IAX2分机",
-		"exten_digital_prop_audio_codec": "语音编码",
-		"exten_digital_prop_permit": "受信地址",
-		"exten_digital_prop_send_pai": "发送PAI",
-		"exten_digital_prop_send_rpid": "发送RPID",
-		"exten_digital_prop_rtp_timeout": "RTP超时",
-		"exten_digital_prop_group_number": "代接组(默认 1)",
-		"exten_digital_prop_inband_progress": "Inband Progress",
-		"exten_digital_prop_white_list_tag_id": "白名单标签(编号, 默认0)"
-	},
-	"did": {
-		"did_template_file_name": "DID模板文件.xlsx",
-		"did_file_name": "DID列表文件.xlsx",
-		"did_file_worksheet_name": "DID",
-		"did_title_callDestNum": "DID号码",
-		"did_title_destType": "目的地类型",
-		"did_title_dest": "目的地",
-		"did_title_destTrunkType": "目的地中继类型",
-		"did_title_destTrunkAddNumber": "目的地中继送号",
-		"did_title_ringTone": "铃声",
-		"did_error_title": "类型错误",
-		"did_error_message": "只能从下拉列表中选择值!"
-	},
-	"phonebook": {
-		"phonebook_template_file_name": "联系人模板文件.xlsx",
-		"phonebook_contact_file_name": "联系人列表.xlsx",
-		"phonebook_contact_file_worksheet_name": "联系人",
-		"phonebook_title_name": "姓名",
-		"phonebook_title_phone": "电话号码",
-		"phonebook_title_email": "邮箱",
-		"phonebook_title_company": "公司",
-		"phonebook_title_dial_code": "拨号代码"
-	},
-	"blacklist": {
-		"blacklist_template_file_name": "黑名单模板文件.xlsx",
-		"blacklist_file_name": "黑名单列表.xlsx",
-		"blacklist_file_worksheet_name": "黑名单",
-		"blacklist_title_number": "电话号码",
-		"blacklist_title_createUser": "创建者",
-		"blacklist_title_createTime": "创建时间"
-	},
-	"trunk": {
-		"trunk_sip_file_name": "sip中继.xlsx",
-		"trunk_sip_worksheet_name": "sip",
-		"trunk_sip_prop_name": "中继名",
-		"trunk_sip_prop_enable": "启用",
-		"trunk_sip_prop_trunk_type": "中继类型",
-		"trunk_sip_prop_auth": "认证",
-		"trunk_sip_prop_server": "主机",
-		"trunk_sip_prop_port": "端口",
-		"trunk_sip_prop_auth_user": "认证用户",
-		"trunk_sip_prop_username": "用户名",
-		"trunk_sip_prop_password": "密码",
-		"trunk_sip_prop_contact": "联系人头域",
-		"trunk_sip_prop_regist_expiration": "注册超时",
-		"trunk_sip_prop_retry_interval": "重试间隔",
-		"trunk_sip_prop_max_retry": "最大重试次数",
-		"trunk_sip_prop_identify_by": "认证方式",
-		"trunk_sip_prop_max_expiration": "最大超时时间",
-		"trunk_sip_prop_fax_detect": "传真检测",
-		"trunk_sip_prop_qualify": "保活",
-		"trunk_sip_prop_srtp": "SRTP",
-		"trunk_sip_prop_nat": "NAT支持",
-		"trunk_sip_prop_client_uri": "客户端URI",
-		"trunk_sip_prop_transport": "传输协议",
-		"trunk_sip_prop_server_uri": "服务端URI",
-		"trunk_sip_prop_language": "提示音语言",
-		"trunk_sip_prop_aor_contact": "AOR Contact",
-		"trunk_sip_prop_call_max": "最高并发",
-		"trunk_sip_prop_recording": "通话录音",
-		"trunk_sip_prop_cid_preferred": "优先使用(外呼显示号码)",
-		"trunk_sip_prop_from_user": "来自用户",
-		"trunk_sip_prop_outbound_cid": "外呼显示号码",
-		"trunk_sip_prop_dtmf": "DTMF模式",
-		"trunk_sip_prop_dial_plan_id": "拨号权限(编号,默认是0)",
-		"trunk_sip_prop_from_domain": "From头域",
-		"trunk_sip_prop_send_pai": "发送PAI",
-		"trunk_sip_prop_send_rpid": "发送RPID",
-		"trunk_sip_prop_rtp_timeout": "RTP超时",
-		"trunk_sip_prop_video_codec": "视频编码",
-		"trunk_sip_prop_audio_codec": "语音编码",
-		"trunk_sip_prop_out_proxy": "Proxy",
-		"trunk_sip_prop_out_proxy_port": "Port"
-	},
-	"bill": {
-		"bill_rate_file_name": "计费费率.xlsx",
-		"bill_rate_file_worksheet_name": "费率列表",
-		"bill_rate_prop_enable": "启用",
-		"bill_rate_prop_remark": "备注",
-		"bill_rate_prop_prefix": "前缀",
-		"bill_rate_prop_number_length": "号码长度",
-		"bill_rate_prop_init_cost": "初始费用",
-		"bill_rate_prop_init_time": "初始时间",
-		"bill_rate_prop_rate": "费率",
-		"bill_rate_prop_bill_unit": "计费周期(秒)",
-		"bill_rate_prop_bill_time_start": "开始时间",
-		"bill_rate_prop_bill_time_end": "结束时间",
-		"bill_rate_prop_extension_list": "分机",
-		"bill_charge_log":"充值记录.xlsx",
-		"bill_charge_time":"充值时间",
-		"bill_charge_extension":"分机",
-		"bill_charge_pre_balance":"充值前余额",
-		"bill_charge_charge_amount":"充值金额",
-		"bill_charge_post_balance":"充值后余额",
-		"bill_call_log":"通话记录.xlsx",
-		"bill_call_time":"开始时间",
-		"bill_call_src":"主叫",
-		"bill_call_dst":"被叫",
-		"bill_call_duration":"时长",
-		"bill_call_trunk":"中继名",
-		"bill_call_cast":"话费",
-		"bill_statistics_file_name": "计费通话统计.xlsx",
-		"bill_statistics_file_worksheet_name": "通话记录统计",
-		"bill_statistics_title_search_time": "统计方式",
-		"bill_statistics_title_extension": "分机",
-		"bill_statistics_title_call": "所有通话",
-		"bill_statistics_title_time": "总通话时长",
-		"bill_statistics_title_time_avg": "平均通话时长",
-		"bill_statistics_title_total_cost": "总话费"
-	}
-}

+ 0 - 45
web/www/index.html

@@ -1,45 +0,0 @@
-<!DOCTYPE html>
-<html>
-<meta charset="utf-8">
-
-<head>
-    <title>help</title>
-</head>
-
-<body>
-<div style="display: flex;
-    flex-direction: column;
-    align-items: center;">
-    <h3>swagger</h3>
-    <p style="display: flex;
-        flex-direction: column;">
-        <a href="/pbx/swagger/index.html" target="_blank">PBX 电话系统 API</a>
-        <a href="/ginapi/swagger/index.html" target="_blank">Panel API</a>
-        <a href="/bill/swagger/index.html" target="_blank">Bill API</a>
-        <a href="/webuser/swagger/index.html" target="_blank">WebUser API</a>
-    </p>
-</div>
-
-<div style="display: flex;
-    flex-direction: column;
-    align-items: center;">
-    <h3>debug</h3>
-    <p style="display: flex;
-        flex-direction: column;">
-        <a href="/debug/pprof/" target="_blank">debug pprof</a>
-    </p>
-</div>
-
-<div style="display: flex;
-    flex-direction: column;
-    align-items: center;">
-    <h3>socket.io</h3>
-    <p style="display: flex;
-        flex-direction: column;">
-        <a href="/socketio-client-tool" target="_blank">SocketIO</a>
-    </p>
-</div>
-
-<body>
-
-</html>