dujunchen пре 3 дана
родитељ
комит
db5ef9771d
68 измењених фајлова са 5545 додато и 0 уклоњено
  1. 28 0
      Makefile
  2. 46 0
      README.md
  3. 1 0
      api/index.go
  4. 16 0
      api/model/callforward.go
  5. 6 0
      api/model/conference.go
  6. 11 0
      api/model/database.go
  7. 16 0
      api/model/extension.go
  8. 10 0
      api/model/jwt.go
  9. 36 0
      api/model/meetme.go
  10. 34 0
      api/model/page.go
  11. 16 0
      api/model/park.go
  12. 28 0
      api/model/request.go
  13. 29 0
      api/model/response.go
  14. 232 0
      api/panel/asterisk/call.go
  15. 39 0
      api/panel/asterisk/channel.go
  16. 154 0
      api/panel/asterisk/conference.go
  17. 272 0
      api/panel/asterisk/meetme.go
  18. 109 0
      api/panel/asterisk/park.go
  19. 94 0
      cmd/commands/service.go
  20. 85 0
      cmd/main.go
  21. 45 0
      configs/config.demo.yaml
  22. 35 0
      configs/config.yaml
  23. BIN
      deployments/pbx-panel
  24. BIN
      deployments/pbx-panel-32
  25. 58 0
      go.mod
  26. 250 0
      go.sum
  27. 409 0
      info.log
  28. 230 0
      internal/app/ami/action/call.go
  29. 94 0
      internal/app/ami/action/channel.go
  30. 131 0
      internal/app/ami/action/conference.go
  31. 37 0
      internal/app/ami/action/extension.go
  32. 150 0
      internal/app/ami/action/meetme.go
  33. 82 0
      internal/app/ami/action/park.go
  34. 32 0
      internal/app/ami/action/playback.go
  35. 134 0
      internal/app/ami/action/queue.go
  36. 99 0
      internal/app/ami/bridge.go
  37. 239 0
      internal/app/ami/index.go
  38. 24 0
      internal/app/ami/model/cdr.go
  39. 40 0
      internal/app/ami/model/dial.go
  40. 84 0
      internal/app/ami/model/event.go
  41. 80 0
      internal/app/ami/model/meetme.go
  42. 210 0
      internal/app/ami/model/park.go
  43. 90 0
      internal/app/ami/model/queue.go
  44. 131 0
      internal/app/ami/model/sip.go
  45. 15 0
      internal/app/ami/model/userevent.go
  46. 22 0
      internal/app/index.go
  47. 52 0
      internal/app/mysql/index.go
  48. 245 0
      internal/app/stc/index.go
  49. 145 0
      internal/app/stc/operation/broadcast.go
  50. 89 0
      internal/pkg/configs/decode.go
  51. 218 0
      pkg/lfshook/log.go
  52. 57 0
      pkg/systeminfo/file.go
  53. 90 0
      pkg/systeminfo/index.go
  54. 96 0
      pkg/systeminfo/time.go
  55. 69 0
      pkg/utils/cmd.go
  56. 17 0
      pkg/utils/common.go
  57. 49 0
      pkg/utils/cron.go
  58. 22 0
      pkg/utils/exit.go
  59. 42 0
      pkg/utils/file.go
  60. 24 0
      pkg/utils/ini.go
  61. 18 0
      pkg/utils/time.go
  62. 63 0
      pkg/utils/trans.go
  63. 130 0
      pkg/utils/utils.go
  64. 20 0
      pkg/utils/uuid.go
  65. 31 0
      test/config.yaml
  66. 35 0
      test/info_test.go
  67. 39 0
      test/ini.go
  68. 11 0
      test/my.ini

+ 28 - 0
Makefile

@@ -0,0 +1,28 @@
+buildDateTime = $(shell date '+%Y-%m-%d %H:%M:%S')
+gitCommitCode = $(shell git rev-parse --short HEAD)
+goVersion = $(shell go version)
+
+run: build
+	./deployments/pbx-panel --level 5 web -c ./configs/config.yaml
+build: swagger
+	go build -tags "swagger apidoc socketio_client_tool manager panel" -o ./deployments/pbx-panel ./cmd/main.go
+release: 
+	GOOS=linux GOARCH=amd64 go build -tags "panel" -ldflags "-X 'main.buildDateTime=$(buildDateTime)' -X 'main.gitCommitCode=$(gitCommitCode)' -X 'main.goVersion=${goVersion}' -s -w" -o ./deployments/pbx-panel ./cmd/main.go
+release-32: 
+	GOOS=linux GOARCH=386 go build -tags "panel" -ldflags "-X 'main.buildDateTime=$(buildDateTime)' -X 'main.gitCommitCode=$(gitCommitCode)' -X 'main.goVersion=${goVersion}' -s -w" -o ./deployments/pbx-panel-32 ./cmd/main.go
+	
+swagger:
+	swag init -g ../internal/app/http_server/swagger/swagger.go --dir ./api --exclude ./api/admin --output ./web/swagger
+
+release-panel-arm: 
+	GOOS=linux GOARCH=arm go build -tags "panel" -ldflags "-X 'main.buildDateTime=$(buildDateTime)' -X 'main.gitCommitCode=$(gitCommitCode)' -X 'main.goVersion=${goVersion}' -s -w" -o ./deployments/pbx-panel-arm ./cmd/main.go
+	
+apidoc:
+	apidoc -i api -f ".go" -o web/www/apidoc
+
+upx:
+	upx -9 --lzma ./deployments/pbx-panel
+
+scp:
+	scp deployments/pbx-panel root@192.168.18.252:/usr/local/pbx-webserver/deployments
+

+ 46 - 0
README.md

@@ -0,0 +1,46 @@
+# PBX-Panel-Server
+Panel API
+
+## 项目依赖
+
+* mysql: 数据库, 分机账号, 用户信息
+* redis: 记录分机状态等信息
+* sox: 对上传文件转码
+* asterisk:  通过 AMI 连接进行交互
+
+## 编译
+
+下载 [go >= 1.16](https://golang.org/dl/), [安装](https://golang.org/doc/install). 设置环境变量
+
+``` shell
+export PATH=$PATH:/usr/local/go/bin
+```
+
+```shell
+1. go env -w GO111MODULE=on
+2. go env -w GOPROXY=https://goproxy.io
+3. go get -u github.com/swaggo/swag/cmd/swag
+4. cp configs/config.demo.yaml configs/config.yaml
+4. make run
+```
+
+参考 Makefile
+
+## 页面
+
+[swaggger](http://localhost:8080/swagger/index.html)
+[vue home](http://localhost:8080/home/)
+[apidoc](http://localhost:8080/apidoc/)
+[socketio-client-tool](http://localhost:8080/socketio-client-tool)
+
+## 安装
+
+[apidoc](https://apidocjs.com/)
+[sox](http://sox.sourceforge.net/)
+[upx](https://github.com/upx/upx)
+[socketio 测试](http://amritb.github.io/socketio-client-tool/v1)
+
+## ChangeLog
+
+1. panel 与 pbx-v4 仓库拆分
+2. 不需要将前端 manager 包含到项目中

+ 1 - 0
api/index.go

@@ -0,0 +1 @@
+package api

+ 16 - 0
api/model/callforward.go

@@ -0,0 +1,16 @@
+package model
+
+// CallForward 呼叫转移
+type CallForward struct {
+	ID        int64  `xorm:"id pk autoincr" json:"id"`
+	Extension string `xorm:"exten" json:"extension"`
+	FwType    string `xorm:"fw_type" json:"fw_type"`
+	FwDest    string `xorm:"fw_dest" json:"fw_dest"`
+	Timeout   int    `xorm:"timeout" json:"timeout"`
+	Timerule  int    `xorm:"timerule" json:"timerule"`
+	Enable    bool   `xorm:"enable" json:"enable"`
+}
+
+func (*CallForward) TableName() string {
+	return "t_call_forward"
+}

+ 6 - 0
api/model/conference.go

@@ -0,0 +1,6 @@
+package model
+
+type ConferenceInfo struct {
+	ConfNum string `json:"confnum"  binding:"required"`
+	Channel string `json:"channel"  binding:"required"`
+}

+ 11 - 0
api/model/database.go

@@ -0,0 +1,11 @@
+package model
+
+type DBCommonVO struct {
+	Family string `json:"family"  binding:"required"`
+	Key    string `json:"key"  binding:"required"`
+}
+
+type DBActionVO struct {
+	DBCommonVO
+	Value string `json:"value" binding:"required"`
+}

+ 16 - 0
api/model/extension.go

@@ -0,0 +1,16 @@
+package model
+
+// Extension describes a user
+type Extension struct {
+	Extension string `xorm:"exten" json:"exten"`
+	DevType      string `xorm:"devtype" json:"devtype"`
+	Priority  string `xorm:"priority" json:"priority"`
+	Special   int    `xorm:"special" json:"special"`
+	Status    string `xorm:"status" json:"status"`
+	PaType    string `xorm:"patype" json:"patype"`
+	Remark    string `xorm:"remark" json:"remark"`
+}
+
+func (*Extension) TableName() string {
+	return "t_extension"
+}

+ 10 - 0
api/model/jwt.go

@@ -0,0 +1,10 @@
+package model
+
+import "github.com/dgrijalva/jwt-go"
+
+type JWTCustomClaims struct {
+	UserName string `json:"UserName"`
+	ID       int64  `json:"ID"`
+	Role     string `json:"Role"`
+	jwt.StandardClaims
+}

+ 36 - 0
api/model/meetme.go

@@ -0,0 +1,36 @@
+package model
+
+// MeetMe describes a user
+type MeetMe struct {
+	Conference string `xorm:"confno" json:"confno"`
+	Pin        string `xorm:"pin" json:"pin"`
+	AdminPin   string `xorm:"adminpin" json:"adminPin"`
+}
+
+func (*MeetMe) TableName() string {
+	return "t_conference"
+}
+
+type MeetMeRoom struct {
+	MeetMe
+
+	Activity string ` json:"activity"`
+	Creation string ` json:"creation"`
+	Event    string ` json:"event"`
+	Locked   string ` json:"locked"`
+	Marked   string ` json:"marked"`
+	Parties  string ` json:"parties"`
+}
+
+type MeetMeCommonVO struct {
+	Meetme string `json:"meetMe" binding:"required"`
+}
+
+type MeetMeVO struct {
+	MeetMeCommonVO
+	UserNum []string `json:"userNumbers" binding:"required"`
+}
+type MeetMeInviteVO struct {
+	MeetMeCommonVO
+	Extensions []string `json:"extensions" binding:"required"`
+}

+ 34 - 0
api/model/page.go

@@ -0,0 +1,34 @@
+package model
+
+import (
+	"fmt"
+)
+
+type PageGroup struct {
+	ID         int64  `xorm:"id pk autoincr" json:"id"`
+	Name       string `xorm:"name" json:"name"`
+	PageNumber string `xorm:"paging_number" json:"pageNumber"`
+	Mode       string `xorm:"mode" json:"mode"`
+	Timeout    string `xorm:"timeout" json:"timeout"`
+	VoiceFile  string `xorm:"voice_file" json:"voiceFile"`
+	AutoAnswer string `xorm:"auto_answer" json:"autoAnswer"`
+
+	Members []*PageGroupMember `xorm:"-"`
+}
+
+func (*PageGroup) TableName() string {
+	return "t_paging_group"
+}
+
+type PageGroupMember struct {
+	ID        int64  `xorm:"id pk autoincr" json:"id"`
+	Extension string `xorm:"exten"`
+	GroupID   string `xorm:"group_id"`
+}
+
+func (*PageGroupMember) TableName() string {
+	return "t_paging_group_member"
+}
+func (group PageGroupMember) String() string {
+	return fmt.Sprintf("Extension: %+v GroupID: %+v", group.Extension, group.GroupID)
+}

+ 16 - 0
api/model/park.go

@@ -0,0 +1,16 @@
+package model
+
+type ParkCommonVO struct {
+	Parkinglot string `json:"parkinglot"`
+}
+
+type ParkInfoVO struct {
+	Extension string `json:"extension"`
+	ParkCommonVO
+}
+
+type ParkBackVO struct {
+	Extension    string `json:"extension"`
+	CallerID     string `json:"callerID"`     // callerID 显示,空值使用 Extension
+	CallerNumber string `json:"callerNumber"` // 主叫, 若未传入通过登录账号查询关联分机
+}

+ 28 - 0
api/model/request.go

@@ -0,0 +1,28 @@
+package model
+
+type SrcDstInfo struct {
+	Src string `json:"src"  binding:"required"`
+	Dst string `json:"dst"  binding:"required"`
+}
+
+type ChannelDstInfo struct {
+	Channel string `json:"channel"  binding:"required"`
+	Dst     string `json:"dst"  binding:"required"`
+}
+
+type ExtensionDstInfo struct {
+	Extension string `json:"extension"  binding:"required"`
+	Dst       string `json:"dst"  binding:"required"`
+}
+
+type PageVO struct {
+	Extensions []string `json:"extensions"  binding:"required"`
+	Src        string   `json:"src"  binding:"required"`
+	Duplex     bool     `json:"duplex"`
+}
+
+type DialVO struct {
+	Extension    string `json:"extension"  binding:"required"` // 被叫
+	CallerID     string `json:"callerID"`                      // callerID 显示,空值使用 Extension
+	CallerNumber string `json:"callerNumber"`                  // 主叫, 若未传入通过登录账号查询关联分机
+}

+ 29 - 0
api/model/response.go

@@ -0,0 +1,29 @@
+package model
+
+type APIError struct {
+	ErrorCode    int    `json:"errorCode"`
+	ErrorMessage string `json:"message"`
+}
+
+type APIOK struct {
+	Code    int         `json:"code"`
+	Message string      `json:"message"`
+	Data    interface{} `json:"data"`
+}
+
+type APIRET struct {
+	Ret bool `json:"result"`
+}
+
+type APICOUNT struct {
+	Ret int64 `json:"result"`
+}
+type CoreShowChannelResVO struct {
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Duration          string `json:"duration"`
+	DurationSecond    int    `json:"durationSecond"`
+}

+ 232 - 0
api/panel/asterisk/call.go

@@ -0,0 +1,232 @@
+package asterisk
+
+import (
+	"fmt"
+	"net/http"
+	"pbx-api-gin/api/model"
+	"pbx-api-gin/internal/app/ami/action"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+)
+
+// @tags Asterisk-Call
+// @Summary 挂断
+// @Description 挂断分机或通道数组
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  []string  true "需要挂断的分机或通道数组"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/hangup [post]
+func Hangup(extens string) {
+	//var extensions []string
+	extensions := strings.Split(extens, ";")
+	for _, extension := range extensions {
+		action.Hangup(extension)
+	}
+}
+
+// @tags Asterisk-Call
+// @Summary 拨打电话
+// @Description 拨打指定的分机
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.DialVO  true "需要拨打的分机信息"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/dial [post]
+func Dial(ctx *gin.Context) {
+	var info model.DialVO
+	if err := ctx.ShouldBindJSON(&info); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	// 根据参数确定主叫, 再考虑用户登录的分机信息
+	myExtension := info.CallerNumber
+	if myExtension == "" {
+		ID, _ := ctx.Get("ID")
+		myExtension = "===================="
+		if myExtension == "" {
+			ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: fmt.Sprintf("not found user extension by ID %d", ID)})
+			return
+		}
+	}
+
+	dialplan := "default"
+	if info.CallerID == "" {
+		info.CallerID = info.Extension
+	}
+
+	// 喊话 主动挂断被叫
+	action.Hangup(info.Extension)
+
+	action.Dial(myExtension, info.Extension, dialplan, info.CallerID, "================", "Local")
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: info})
+}
+
+// @tags Asterisk-Call
+// @Summary 强拆()
+// @Description 将当前正在进行的通话保持,src 与 正在通话的 dst建立通话。
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.SrcDstInfo  true "src, dst"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/clear [post]
+func Clear(ctx *gin.Context) {
+	var input model.SrcDstInfo
+	if err := ctx.ShouldBindJSON(&input); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.Redirect(input.Src, input.Dst, "default")
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: input})
+}
+
+// @tags Asterisk-Call
+// @Summary 密语(悄悄话)
+// @Description 将 src 插入到 dst 的通话中,与 dst 密语
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.ExtensionDstInfo  true "src, dst"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/whisper [post]
+func extenWhisper(ctx *gin.Context) {
+	var input model.ExtensionDstInfo
+
+	if err := ctx.ShouldBindJSON(&input); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	action.ChanSpy(input.Extension, input.Dst, true, false)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: input})
+}
+
+// @tags Asterisk-Call
+// @Summary 强插(三方通话)
+// @Description  将 src 插入 dst 的通话中
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.ExtensionDstInfo  true "src, dst"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/bargein [post]
+func extenBargeIn(ctx *gin.Context) {
+	var input model.ExtensionDstInfo
+
+	if err := ctx.ShouldBindJSON(&input); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	action.ChanSpy(input.Extension, input.Dst, false, true)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: input})
+}
+
+// @tags Asterisk-Call
+// @Summary 监听
+// @Description 将当前正在进行的通话终止,并与分机建立通话。
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.ExtensionDstInfo  true "src 为已进行的通话分机, dst 为未通话的分机"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/spy [post]
+func extenSpy(ctx *gin.Context) {
+	var input model.ExtensionDstInfo
+
+	if err := ctx.ShouldBindJSON(&input); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	action.ChanSpy(input.Extension, input.Dst, false, false)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: input})
+}
+
+// @tags Asterisk-Call
+// @Summary 转接
+// @Description 转接 src 到 dst
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.SrcDstInfo  true "src, dst"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/transfer [post]
+func transfer(ctx *gin.Context) {
+	var input model.SrcDstInfo
+
+	if err := ctx.ShouldBindJSON(&input); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	myExtension := ""
+	if myExtension == "" {
+		ID, _ := ctx.Get("ID")
+		myExtension = "============================="
+		if myExtension == "" {
+			ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: fmt.Sprintf("not found user extension by ID %d", ID)})
+			return
+		}
+	}
+
+	//action.BlindTransfer(input.Src, input.Dst, mysql.GetDialPlanByExtension(myExtension))
+	action.Redirect(input.Src, input.Dst, "default")
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: input})
+}
+
+// @tags Asterisk-Call
+// @Summary 转移
+// @Description 通话已进行, 将 Src 的通话转移到 Dst, 挂断后 Dst 与 Src 继续通话
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.ChannelDstInfo  true "src, dst"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/atxfer [post]
+func atxfer(ctx *gin.Context) {
+	var input model.ChannelDstInfo
+
+	if err := ctx.ShouldBindJSON(&input); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	//ID, _ := ctx.Get("ID")
+	dialplan := "default"
+	// admin 用户通过主叫获取
+
+	action.Atxfer(input.Channel, input.Dst, dialplan)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: input})
+}
+
+// @tags Asterisk-Call
+// @Summary 广播
+// @Description 广播
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.PageVO  true "广播参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/call/page [post]
+func page(ctx *gin.Context) {
+	var input model.PageVO
+
+	if err := ctx.ShouldBindJSON(&input); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	// 主动挂断被叫
+	for _, exten := range input.Extensions {
+		action.Hangup(exten)
+	}
+
+	action.Page(input.Src, input.Extensions, input.Duplex)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: input})
+}

+ 39 - 0
api/panel/asterisk/channel.go

@@ -0,0 +1,39 @@
+package asterisk
+
+import (
+	"net/http"
+	"pbx-api-gin/api/model"
+	"pbx-api-gin/internal/app/ami"
+	"pbx-api-gin/internal/app/ami/action"
+
+	"github.com/gin-gonic/gin"
+)
+
+// @tags Asterisk-Channel
+// @Summary 当前通话通道
+// @Description 当前通话通道
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/channel/show-channels [get]
+func showChannels(ctx *gin.Context) {
+	data, err := action.CoreShowChannels()
+	if err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+// @tags Asterisk-Channel
+// @Summary 当前 Bridge 通道
+// @Description 当前 Bridge 通道
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/channel/show-bridge-channels [get]
+func showBridgeChannels(ctx *gin.Context) {
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: ami.GetBridgeMapValue()})
+}

+ 154 - 0
api/panel/asterisk/conference.go

@@ -0,0 +1,154 @@
+package asterisk
+
+import (
+	"net/http"
+	"pbx-api-gin/api/model"
+	"pbx-api-gin/internal/app/ami/action"
+
+	"github.com/gin-gonic/gin"
+)
+
+/*
+// @tags Asterisk-Conference
+// @Summary 查看所有会议室
+// @Description 会议室列表
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/conference/listroom [get]
+*/
+func listRoom(ctx *gin.Context) {
+	res, err := action.ListRoom(make(map[string]string))
+	if err != nil {
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: res})
+}
+
+/*
+// @tags Asterisk-Conference
+// @Summary 查看指定会议室
+// @Description 会议室信息
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.ConferenceInfo  true "信息"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/conference/list [post]
+*/
+func list(ctx *gin.Context) {
+	var data model.ConferenceInfo
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.List(data.ConfNum)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+/*
+// @tags Asterisk-Conference
+// @Summary 踢人
+// @Description 移除指定通道
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.ConferenceInfo  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/conference/kick [post]
+*/
+func kick(ctx *gin.Context) {
+	var data model.ConferenceInfo
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.Kick(data.ConfNum, data.Channel)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+/*
+// @tags Asterisk-Conference
+// @Summary 禁言
+// @Description 禁言
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.ConferenceInfo  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/conference/mute [post]
+*/
+func mute(ctx *gin.Context) {
+	var data model.ConferenceInfo
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.Mute(data.ConfNum, data.Channel)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+/*
+// @tags Asterisk-Conference
+// @Summary 取消禁言
+// @Description 取消禁言
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.ConferenceInfo  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/conference/unmute [post]
+*/
+func unMute(ctx *gin.Context) {
+	var data model.ConferenceInfo
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.UnMute(data.ConfNum, data.Channel)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+/*
+// @tags Asterisk-Conference
+// @Summary 开启锁定
+// @Description 开启锁定
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.ConferenceInfo  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/conference/lock [post]
+*/
+func lock(ctx *gin.Context) {
+	var data model.ConferenceInfo
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.Lock(data.ConfNum)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+/*
+// @tags Asterisk-Conference
+// @Summary 关闭锁定
+// @Description 关闭锁定
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.ConferenceInfo  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/conference/unlock [post]
+*/
+func unLock(ctx *gin.Context) {
+	var data model.ConferenceInfo
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.UnLock(data.ConfNum)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}

+ 272 - 0
api/panel/asterisk/meetme.go

@@ -0,0 +1,272 @@
+package asterisk
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"pbx-api-gin/api/model"
+	"pbx-api-gin/internal/app/ami/action"
+	amiModel "pbx-api-gin/internal/app/ami/model"
+	"pbx-api-gin/internal/app/mysql"
+	"pbx-api-gin/pkg/lfshook"
+	"strings"
+
+	"github.com/gin-gonic/gin"
+	"github.com/mitchellh/mapstructure"
+)
+
+// @tags Asterisk-MeetMe
+// @Summary 查看所有会议室
+// @Description 会议室列表
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/list [get]
+func listMeetMe(ctx *gin.Context) {
+	amiRooms, _ := action.ListRoomMeetMe(make(map[string]string))
+
+	var meets []model.MeetMe
+	if err := mysql.DBOrmInstance.Find(&meets); err != nil {
+		lfshook.NewLogger().Errorf("db index error %+v", err)
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: "db error"})
+		return
+	}
+
+	numRoomMap := make(map[string]*amiModel.MeetMeListRooms)
+	for _, room := range amiRooms {
+		numRoomMap[room.Conference] = room
+	}
+
+	rooms := make([]*model.MeetMeRoom, 0)
+	for _, meet := range meets {
+		room := &model.MeetMeRoom{
+			MeetMe: meet,
+		}
+		if data, has := numRoomMap[meet.Conference]; has {
+			mapstructure.Decode(data, room)
+		}
+		rooms = append(rooms, room)
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: rooms})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 查看指定会议室
+// @Description 会议室信息
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param conference query string true "会议室号"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/room [get]
+func room(ctx *gin.Context) {
+	conference := ctx.Query("conference")
+	if conference == "" {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: errors.New("conference is empty").Error()})
+		return
+	}
+	items, err := action.ListMeetMe(conference)
+	if err != nil {
+		lfshook.NewLogger().Warn(err)
+	}
+
+	// 查询 room 信息
+	var roomInfo *amiModel.MeetMeListRooms
+	amiRooms, _ := action.ListRoomMeetMe(make(map[string]string))
+	for _, room := range amiRooms {
+		if room.Conference == conference {
+			roomInfo = room
+			break
+		}
+	}
+
+	meet := model.MeetMe{Conference: conference}
+	if _, err := mysql.DBOrmInstance.Get(&meet); err != nil {
+		lfshook.NewLogger().Errorf("db get error %+v", err)
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: "db error"})
+		return
+	}
+	room := &model.MeetMeRoom{
+		MeetMe: meet,
+	}
+	mapstructure.Decode(roomInfo, room)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: gin.H{"items": items, "room": room}})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 邀请会议
+// @Description 邀请会议
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.MeetMeInviteVO  true "加入会议参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/invite [post]
+func inviteMeetMe(ctx *gin.Context) {
+	var data model.MeetMeInviteVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	// 根据参数确定主叫, 再考虑用户登录的分机信息
+	myExtension := ""
+	if myExtension == "" {
+		ID, _ := ctx.Get("ID")
+		myExtension = "=================="
+		if myExtension == "" {
+			ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: fmt.Sprintf("not found user extension by ID %d", ID)})
+			return
+		}
+	}
+	dialplan := "default"
+
+	action.IviteMeetMe(data.Meetme, strings.Join(data.Extensions, ","), dialplan, myExtension)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 踢出会议
+// @Description 踢出会议
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.MeetMeVO  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/kick [post]
+func kickMeetMe(ctx *gin.Context) {
+	var data model.MeetMeVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	err := action.KickMeetMe(data.Meetme, strings.Join(data.UserNum, ","))
+	if err != nil {
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 禁言
+// @Description 禁言
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.MeetMeVO  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/mute [post]
+func muteMeetMe(ctx *gin.Context) {
+	var data model.MeetMeVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	var hasError bool
+	for _, number := range data.UserNum {
+		_, err := action.MuteMeetMe(data.Meetme, number)
+		if err != nil {
+			lfshook.NewLogger().Error(err)
+			hasError = true
+		}
+	}
+
+	if hasError {
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: "check error.log"})
+		return
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: ""})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 取消禁言
+// @Description 取消禁言
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.MeetMeVO  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/unmute [post]
+func unMuteMeetMe(ctx *gin.Context) {
+	var data model.MeetMeVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+
+	var hasError bool
+	for _, number := range data.UserNum {
+		_, err := action.UnMuteMeetMe(data.Meetme, number)
+		if err != nil {
+			lfshook.NewLogger().Error(err)
+			hasError = true
+		}
+	}
+
+	if hasError {
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: "check error.log"})
+		return
+	}
+
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: "mute ok"})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 开启锁定
+// @Description 开启锁定
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.MeetMeCommonVO  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/lock [post]
+func lockMeetMe(ctx *gin.Context) {
+	var data model.MeetMeCommonVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.LockMeetMe(data.Meetme)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 关闭锁定
+// @Description 关闭锁定
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.MeetMeCommonVO  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/unlock [post]
+func unLockMeetMe(ctx *gin.Context) {
+	var data model.MeetMeCommonVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.UnLockMeetMe(data.Meetme)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+// @tags Asterisk-MeetMe
+// @Summary 关闭会议
+// @Description 关闭会议
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param  data body  model.MeetMeCommonVO  true "参数"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/meetme/end [post]
+func endMeetMe(ctx *gin.Context) {
+	var data model.MeetMeCommonVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	action.EndMeetMe(data.Meetme)
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}

+ 109 - 0
api/panel/asterisk/park.go

@@ -0,0 +1,109 @@
+package asterisk
+
+import (
+	"net/http"
+	"pbx-api-gin/api/model"
+	"pbx-api-gin/internal/app/ami/action"
+	"pbx-api-gin/pkg/lfshook"
+
+	"github.com/gin-gonic/gin"
+)
+
+// @tags Asterisk-Park
+// @Summary  停泊分机
+// @Description 停泊分机
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.ParkInfoVO  true "更新信息"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/park/park [post]
+func park(ctx *gin.Context) {
+	var data model.ParkInfoVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	lfshook.NewLogger().Infof("park %+v", data)
+	if err := action.Park(data.Extension, data.Parkinglot); err != nil {
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}
+
+// @tags Asterisk-Park
+// @Summary  停泊接回
+// @Description 停泊接回
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.ParkBackVO  true "更新信息"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/park/back [post]
+func parkBack(ctx *gin.Context) {
+	var info model.ParkBackVO
+	if err := ctx.ShouldBindJSON(&info); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	lfshook.NewLogger().Infof("park back %+v", info)
+
+	// 根据参数确定主叫, 再考虑用户登录的分机信息
+	myExtension := info.CallerNumber
+	if myExtension == "" {
+		//ID, _ := ctx.Get("ID")
+		myExtension = "=============="
+		if myExtension == "" {
+			ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: "not found extension"})
+			return
+		}
+	}
+
+	dialplan := "default"
+	if info.CallerID == "" {
+		info.CallerID = info.Extension
+	}
+	action.Dial(myExtension, info.Extension, dialplan, info.CallerID, "=================", "Local")
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: info})
+}
+
+// @tags Asterisk-Park
+// @Summary  获取停泊信息
+// @Description 获取停泊信息
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Param   data body  model.ParkCommonVO  true "更新信息"
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/park/calls [post]
+func parkedCalls(ctx *gin.Context) {
+	var data model.ParkCommonVO
+	if err := ctx.ShouldBindJSON(&data); err != nil {
+		ctx.JSON(http.StatusBadRequest, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	res, err := action.ParkedCalls(data.Parkinglot)
+	if err != nil {
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: res})
+}
+
+// @tags Asterisk-Park
+// @Summary 获取停泊池信息
+// @Description 获取停泊池信息
+// @Security ApiKeyAuth
+// @Accept  json
+// @Produce  json
+// @Success 200 {object} model.APIOK "请求成功"
+// @Router /ginapi/plugin-asterisk/park/lots [get]
+func parkinglots(ctx *gin.Context) {
+	data, err := action.Parkinglots()
+	if err != nil {
+		ctx.JSON(http.StatusInternalServerError, model.APIError{ErrorMessage: err.Error()})
+		return
+	}
+	ctx.JSON(http.StatusOK, model.APIOK{Message: "ok", Data: data})
+}

+ 94 - 0
cmd/commands/service.go

@@ -0,0 +1,94 @@
+package commands
+
+import (
+	"fmt"
+
+	"github.com/kardianos/service"
+	"github.com/sirupsen/logrus"
+	"github.com/urfave/cli"
+)
+
+var logger service.Logger
+
+type program struct {
+	ctx *cli.Context
+}
+
+func (p *program) Start(s service.Service) error {
+	//go p.run()
+	return nil
+}
+
+func (p *program) Stop(s service.Service) error {
+	// Stop should not block. Return with a few seconds.
+	return nil
+}
+
+// CmdService cli 命令
+var CmdService = cli.Command{
+	Name:        "service",
+	Usage:       "./pbx-api-gin service",
+	Description: "PBX API install/uninstall",
+	Action:      runService,
+	Flags: []cli.Flag{
+		cli.StringFlag{Name: "action, a", Usage: "install/uninstall/start/stop"},
+		cli.StringFlag{Name: "config, c", Usage: "配置文件路径"},
+		cli.Int64Flag{Name: "port, p", Usage: "默认绑定端口 8080", Value: 8080},
+		cli.StringFlag{Name: "host", Usage: "默认绑定地址 0.0.0.0", Value: "0.0.0.0"},
+	},
+}
+
+func runService(ctx *cli.Context) {
+	svcConfig := &service.Config{
+		Name:        "PBX-API",
+		DisplayName: "PBX-API",
+		Description: "PBX-API as service.",
+		Arguments:   []string{"--config", ctx.String("config")},
+	}
+
+	prg := &program{
+		ctx: ctx,
+	}
+	s, err := service.New(prg, svcConfig)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+	logger, err = s.Logger(nil)
+	if err != nil {
+		logrus.Fatal(err)
+	}
+
+	switch ctx.String("action") {
+	case "install":
+		if !ctx.IsSet("config") {
+			fmt.Print("install must set config path, please by  -config set")
+			return
+		}
+		err = s.Install()
+		if err != nil {
+			fmt.Println("install err", err)
+		} else {
+			fmt.Println("install success")
+		}
+		return
+	case "uninstall":
+		err = s.Uninstall()
+		if err != nil {
+			fmt.Println("Uninstall err", err)
+		} else {
+			fmt.Println("Uninstall success")
+		}
+		return
+	case "start":
+		s.Start()
+		return
+	case "stop":
+		s.Stop()
+		return
+	}
+
+	err = s.Run()
+	if err != nil {
+		logger.Error(err)
+	}
+}

+ 85 - 0
cmd/main.go

@@ -0,0 +1,85 @@
+package main
+
+import (
+	"pbx-api-gin/internal/app"
+	"pbx-api-gin/internal/pkg/configs"
+	"pbx-api-gin/pkg/lfshook"
+	"pbx-api-gin/pkg/utils"
+
+	"github.com/gin-gonic/gin"
+	"github.com/sirupsen/logrus"
+	"gopkg.in/natefinch/lumberjack.v2"
+)
+
+var (
+	gitCommitCode string
+	buildDateTime string
+	goVersion     string
+)
+
+func main() {
+	initVersion()
+
+	// 解析配置文件
+	configs.ConfigPath = "./configs/config.yaml"
+	configs.DecodeConfig()
+
+	configs.ConfigGlobal.LogLevel = logrus.InfoLevel
+
+	gin.SetMode(gin.ReleaseMode)
+
+	//gin.SetMode(gin.DebugMode)
+
+	lfshook.NewLogger().Logger.SetReportCaller(true)
+
+	lfshook.NewLogger().Logger.SetFormatter(&logrus.TextFormatter{
+		ForceQuote:      false,
+		FullTimestamp:   true,
+		TimestampFormat: "2006-01-02 15:04:05",
+	})
+
+	if configs.ConfigGlobal.LogLevel < logrus.DebugLevel {
+
+		pathMap := lfshook.LoggerMap{
+			logrus.InfoLevel: &lumberjack.Logger{
+				Filename:   configs.ConfigGlobal.LogInfoPath,
+				MaxSize:    10, // maxSize M
+				MaxBackups: 5,  // keep 5 file
+				MaxAge:     7,  //  7 day
+			},
+
+			logrus.ErrorLevel: &lumberjack.Logger{
+				Filename:   configs.ConfigGlobal.LogErrorPath,
+				MaxSize:    10, // maxSize M
+				MaxBackups: 5,  // keep 5 file
+				MaxAge:     7,  //  7 day
+			},
+		}
+
+		lfshook.NewLogger().Logger.Hooks.Add(lfshook.NewHook(
+			pathMap,
+			&logrus.TextFormatter{
+				DisableColors:   true,
+				ForceQuote:      true,
+				TimestampFormat: "2006-01-02 15:04:05",
+			},
+		))
+	}
+
+	app.StartApp()
+	utils.Exit()
+	//return nil
+}
+
+func initVersion() {
+	if gitCommitCode != "" {
+		//构建信息
+		lfshook.NewLogger().Printf("git commit code: %s", gitCommitCode)
+		lfshook.NewLogger().Printf("build date: %s", buildDateTime)
+		lfshook.NewLogger().Printf("go version: %s", goVersion)
+
+		utils.VersionInstance.BuildDate = buildDateTime
+		utils.VersionInstance.GoVersion = goVersion
+		utils.VersionInstance.GitCommitCode = gitCommitCode
+	}
+}

+ 45 - 0
configs/config.demo.yaml

@@ -0,0 +1,45 @@
+identityKey: 'zycoozycoo' # jwt secret
+
+# AMI
+asteriskAMIHost: '127.0.0.1'
+asteriskAMIPort: '5038'
+asteriskAMIUser: 'admin'
+asteriskAMISecret: 'admin'
+
+# AGI
+asteriskAGIPort: '18080'
+
+asteriskBroadcastTimeout: 30
+asteriskBroadcastName: Broadcast
+asteriskBroadcastID: Broadcast
+
+# Trigger
+asteriskTriggerPath: '/etc/asterisk/extensions_trigger.conf'
+# Paging
+asteriskPagingPath : '/etc/asterisk/extensions_paging.conf'
+
+# 数据库配置
+mysqlDBHost: '127.0.0.1'
+mysqlDBUser: 'coovox_admin'
+mysqlDBName: 'ippbx_db'
+mysqlDBSecret: 'ZycooCoovoxDba42'
+
+# Redis
+redisDBHost: 'localhost'
+redisDBPort: '6379'
+redisDBSecret: ''
+
+# log
+logErrorPath: './error.log'
+logInfoPath: './info.log'
+
+# music upload
+storagePath: '/tmp'
+
+webhost: 0.0.0.0
+webport: 8080
+
+#cors
+allowOrigin: http://localhost:8080
+#push
+allEventPushUrl:

+ 35 - 0
configs/config.yaml

@@ -0,0 +1,35 @@
+identityKey: 'zycoozycoo' # jwt secret
+
+# AMI
+asteriskAMIHost: '127.0.0.1'
+asteriskAMIPort: '5038'
+asteriskAMIUser: 'admin'
+asteriskAMISecret: 'admin'
+
+# AGI
+asteriskAGIPort: '18080'
+
+asteriskBroadcastTimeout: 30
+asteriskBroadcastName: Broadcast
+asteriskBroadcastID: Broadcast
+
+# Trigger
+asteriskTriggerPath: '/etc/asterisk/extensions_trigger.conf'
+# Paging
+asteriskPagingPath : '/etc/asterisk/extensions_paging.conf'
+
+# 数据库配置
+mysqlDBHost: '127.0.0.1'
+mysqlDBUser: 'ippbx'
+mysqlDBName: 'ippbx_db'
+mysqlDBSecret: '123456'
+
+# Redis
+redisDBHost: 'localhost'
+redisDBPort: '6379'
+redisDBSecret: ''
+
+# log
+logErrorPath: './error.log'
+logInfoPath: './info.log'
+

BIN
deployments/pbx-panel


BIN
deployments/pbx-panel-32


+ 58 - 0
go.mod

@@ -0,0 +1,58 @@
+module pbx-api-gin
+
+require (
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/gin-gonic/gin v1.7.1
+	github.com/go-sql-driver/mysql v1.6.0
+	github.com/kardianos/service v1.2.0
+	github.com/mitchellh/mapstructure v1.4.1
+	github.com/shirou/gopsutil/v3 v3.21.7
+	github.com/sirupsen/logrus v1.9.3
+	github.com/swaggo/swag v1.16.2
+	github.com/tqcenglish/amigo-go v1.1.14
+	github.com/urfave/cli v1.20.0
+	gopkg.in/guregu/null.v4 v4.0.0
+	gopkg.in/ini.v1 v1.61.0
+	gopkg.in/natefinch/lumberjack.v2 v2.0.0
+	gopkg.in/yaml.v2 v2.4.0
+	xorm.io/xorm v1.0.7
+)
+
+require (
+	github.com/BurntSushi/toml v1.3.2 // indirect
+	github.com/KyleBanks/depth v1.2.1 // indirect
+	github.com/StackExchange/wmi v1.2.1 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-ole/go-ole v1.2.5 // indirect
+	github.com/go-openapi/jsonpointer v0.20.0 // indirect
+	github.com/go-openapi/jsonreference v0.20.2 // indirect
+	github.com/go-openapi/spec v0.20.9 // indirect
+	github.com/go-openapi/swag v0.22.4 // indirect
+	github.com/go-playground/locales v0.13.0 // indirect
+	github.com/go-playground/universal-translator v0.17.0 // indirect
+	github.com/go-playground/validator/v10 v10.4.1 // indirect
+	github.com/golang/protobuf v1.4.2 // indirect
+	github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
+	github.com/google/go-cmp v0.5.5 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
+	github.com/json-iterator/go v1.1.9 // indirect
+	github.com/leodido/go-urn v1.2.0 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mattn/go-isatty v0.0.12 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.1 // indirect
+	github.com/onsi/ginkgo v1.15.0 // indirect
+	github.com/onsi/gomega v1.10.5 // indirect
+	github.com/rogpeppe/go-internal v1.12.0 // indirect
+	github.com/smartystreets/goconvey v1.6.4 // indirect
+	github.com/syndtr/goleveldb v1.0.0 // indirect
+	github.com/ugorji/go/codec v1.1.13 // indirect
+	golang.org/x/crypto v0.13.0 // indirect
+	golang.org/x/sys v0.12.0 // indirect
+	golang.org/x/tools v0.13.0 // indirect
+	google.golang.org/protobuf v1.23.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	xorm.io/builder v0.3.7 // indirect
+)
+
+go 1.20

+ 250 - 0
go.sum

@@ -0,0 +1,250 @@
+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/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
+github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
+github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+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/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+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/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/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.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8=
+github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
+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/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
+github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
+github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
+github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
+github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
+github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+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/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
+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/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+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 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g=
+github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+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/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
+github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
+github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+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/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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+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/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/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
+github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
+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/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4=
+github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E=
+github.com/tqcenglish/amigo-go v1.1.14 h1:XIioEPj0+b4+EY+Cr0z8R33IwqnwWUS6w1FL9HUYTtE=
+github.com/tqcenglish/amigo-go v1.1.14/go.mod h1:R4D2J0kumfy0aJU5n4262rgXgqsRgeSr2NgDD4yWoFQ=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4=
+github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
+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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+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.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+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=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+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.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
+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/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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+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.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+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=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+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/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=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+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.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
+xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
+xorm.io/xorm v1.0.7 h1:26yBTDVI+CfQpVz2Y88fISh+aiJXIPP4eNoTJlwzsC4=
+xorm.io/xorm v1.0.7/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=

+ 409 - 0
info.log

@@ -0,0 +1,409 @@
+time="2025-09-09 20:36:27" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:11" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="=========222=========" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:20" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="====111==============" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:28" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:43" appname="pbx-panel"
+time="2025-09-09 20:36:27" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-09 20:36:31" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:20" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="=========222=========" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:29" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="====111==============" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:37" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-09 20:49:49" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-09 21:24:25" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="=========222=========" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:83" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="====111==============" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:91" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-09 21:46:20" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-09 21:53:39" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-09 21:53:43" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 21:53:43" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 21:53:43" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:54:27" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="====111==============" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:83" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-09 21:54:52" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-09 21:54:55" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-09 21:54:57" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 21:54:57" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:54:57" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-09 21:55:44" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 21:55:44" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:55:50" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 21:55:50" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 21:55:50" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="==================" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:49" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="====111==============" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:84" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-09 21:56:42" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-09 22:40:26" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-09 22:40:46" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 22:44:02" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-09 22:44:02" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-09 22:44:02" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-09 22:44:02" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-09 22:44:02" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-09 22:44:02" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-09 22:44:02" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 00:34:58" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 00:35:27" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 00:40:01" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 00:40:01" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 00:40:01" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 00:40:01" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 00:40:01" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 00:40:01" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 00:40:01" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 00:53:00" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 00:53:01" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 00:53:01" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 00:53:01" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:05:50" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:05:50" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:05:50" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:05:50" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:05:50" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:05:50" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:05:50" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:14:47" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:14:50" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:23:53" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:23:53" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:23:53" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:23:53" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:23:53" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:23:53" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:23:53" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:24:07" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:28:25" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:25" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:28:25" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:25" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:28:25" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:28:25" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:28:25" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:28:27" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:28:29" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:29" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:29" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:28:29" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:28:29" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:28:29" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:28:29" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:28:31" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:28:35" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:35" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:28:35" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:35" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:28:35" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:28:35" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:28:35" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:28:37" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:28:51" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:51" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:28:51" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:28:51" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:28:51" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:28:51" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:28:51" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:28:59" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:29:06" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:29:06" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:29:06" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:29:06" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:29:06" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:29:06" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:29:06" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:29:09" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:30:34" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:30:34" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:30:34" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:30:34" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:30:34" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:30:34" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:30:34" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:30:47" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="Connect to: %+v192.168.17.14:6090" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:63" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:32:58" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:33:01" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="Connect to: %+v192.168.17.14:6090" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:63" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:34:06" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:34:18" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:35:52" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:35:52" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:35:52" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:35:52" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:35:52" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:35:52" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:35:52" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:37:10" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:37:31" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:37:31" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:37:31" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:37:31" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:37:31" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:37:31" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:37:31" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:37:38" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:38:45" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:38:45" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:38:45" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:38:45" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:38:45" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:38:45" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:38:45" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:39:41" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:39:47" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:39:47" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:39:47" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:39:47" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:39:47" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:39:47" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:39:47" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:40:31" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:40:53" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:41:06" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:41:06" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:41:06" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:41:06" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:41:06" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:41:06" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:41:06" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:41:12" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:42:47" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:42:47" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:42:47" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:42:47" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:42:47" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:42:47" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:42:47" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:44:32" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:44:36" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:44:36" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:44:36" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:44:36" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:44:36" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:44:36" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:44:36" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:44:44" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:45:02" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:45:02" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:45:02" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:45:02" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:45:02" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:45:02" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:45:02" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:45:12" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:45:34" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:45:45" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:48:29" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:48:29" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:48:29" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:48:29" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:48:29" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:48:29" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:48:29" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:48:44" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:48:45" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:49:37" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:49:37" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:49:37" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:49:37" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:49:37" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:49:37" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:49:37" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:49:57" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:50:52" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:50:52" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:50:52" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:50:52" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:50:52" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:50:52" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:50:52" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:51:08" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:52:14" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:52:14" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:52:14" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:52:14" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:52:14" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:52:14" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:52:14" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:52:24" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:53:31" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:53:31" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:53:31" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:53:31" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:53:31" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:53:31" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:53:31" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:53:33" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="=================conn===================" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:71" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="=================conn===================:&{{0xc0003a4000}}" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:79" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="=================conn===================err:<nil>" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:80" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:56:26" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:56:34" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:57:07" level="info" msg="=================conn===================" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:71" appname="pbx-panel"
+time="2025-09-10 01:57:07" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:57:07" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:57:07" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:57:07" level="info" msg="=================conn===================:<nil>" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:79" appname="pbx-panel"
+time="2025-09-10 01:57:07" level="info" msg="=================conn===================err:dial tcp 0.0.0.0:10201->192.168.17.14:6090: bind: address already in use" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:80" appname="pbx-panel"
+time="2025-09-10 01:57:21" level="info" msg="=================conn===================" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:71" appname="pbx-panel"
+time="2025-09-10 01:57:21" level="info" msg="=================conn===================:<nil>" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:79" appname="pbx-panel"
+time="2025-09-10 01:57:21" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:57:21" level="info" msg="=================conn===================err:dial tcp 0.0.0.0:10201->192.168.17.14:6090: bind: address already in use" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:80" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="=================conn===================" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:71" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="=================conn===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:79" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="=================conn===================err:<nil>" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:80" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="=================conn1111===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:58" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="=================conn3333===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.heartbeat" file="/root/git-project/cicc.bak/internal/app/stc/index.go:105" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:57:47" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 01:59:05" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="=================conn===================" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:71" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="=================conn===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:79" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="=================conn===================err:<nil>" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:80" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="=================conn1111===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:58" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="=================conn===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.sendMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:144" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="=================conn===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.readMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:127" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="=================conn3333===================:&{{0xc000344000}}" func="pbx-api-gin/internal/app/stc.heartbeat" file="/root/git-project/cicc.bak/internal/app/stc/index.go:105" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 01:59:08" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 02:00:05" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="=================conn1111===================:&{{0xc0003a6000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:58" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="=================conn===================:&{{0xc0003a6000}}" func="pbx-api-gin/internal/app/stc.sendMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:144" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="=================conn===================:&{{0xc0003a6000}}" func="pbx-api-gin/internal/app/stc.readMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:127" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="=================conn3333===================:&{{0xc0003a6000}}" func="pbx-api-gin/internal/app/stc.heartbeat" file="/root/git-project/cicc.bak/internal/app/stc/index.go:105" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 02:00:22" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 02:01:39" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="=================conn===================:&{{0xc0000a8000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:60" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="=================conn===================err:<nil>" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:61" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="=================conn1111===================:&{{0xc0000a8000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:81" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="=================conn===================:&{{0xc0000a8000}}" func="pbx-api-gin/internal/app/stc.sendMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:167" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="=================conn3333===================:&{{0xc0000a8000}}" func="pbx-api-gin/internal/app/stc.heartbeat" file="/root/git-project/cicc.bak/internal/app/stc/index.go:128" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="=================conn===================:&{{0xc0000a8000}}" func="pbx-api-gin/internal/app/stc.readMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:150" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 02:01:41" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 02:02:25" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 02:02:27" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 02:02:27" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 02:02:27" level="info" msg="=================conn===================:<nil>" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:60" appname="pbx-panel"
+time="2025-09-10 02:02:27" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 02:02:27" level="info" msg="=================conn===================err:dial tcp 0.0.0.0:10201->192.168.17.14:6090: bind: address already in use" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:61" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="=================conn===================:&{{0xc0000a0000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:60" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="=================conn===================err:<nil>" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:61" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="=================conn3333===================:&{{0xc0000a0000}}" func="pbx-api-gin/internal/app/stc.heartbeat" file="/root/git-project/cicc.bak/internal/app/stc/index.go:128" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="=================conn1111===================:&{{0xc0000a0000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:81" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="=================conn===================:&{{0xc0000a0000}}" func="pbx-api-gin/internal/app/stc.sendMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:167" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 02:04:44" level="info" msg="=================conn===================:&{{0xc0000a0000}}" func="pbx-api-gin/internal/app/stc.readMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:150" appname="pbx-panel"
+time="2025-09-10 02:04:49" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="=================conn===================" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:92" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="=================conn===================:&{{0xc0000c0000}}" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:100" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="=================conn===================err:<nil>" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:101" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="=================conn1111===================:&{{0xc0000c0000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:79" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="=================conn===================:&{{0xc0000c0000}}" func="pbx-api-gin/internal/app/stc.sendMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:163" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="=================conn===================:&{{0xc0000c0000}}" func="pbx-api-gin/internal/app/stc.readMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:146" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="=================conn3333===================:&{{0xc0000c0000}}" func="pbx-api-gin/internal/app/stc.heartbeat" file="/root/git-project/cicc.bak/internal/app/stc/index.go:124" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 02:08:24" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 02:09:32" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="=================conn===================" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:92" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="=================conn===================:&{{0xc0003a4000}}" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:100" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="=================conn===================err:<nil>" func="pbx-api-gin/internal/app/stc.connectServer" file="/root/git-project/cicc.bak/internal/app/stc/index.go:101" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="=================conn1111===================:&{{0xc0003a4000}}" func="pbx-api-gin/internal/app/stc.ConnectStc" file="/root/git-project/cicc.bak/internal/app/stc/index.go:79" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="=================conn3333===================:&{{0xc0003a4000}}" func="pbx-api-gin/internal/app/stc.heartbeat" file="/root/git-project/cicc.bak/internal/app/stc/index.go:124" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="=================conn===================:&{{0xc0003a4000}}" func="pbx-api-gin/internal/app/stc.sendMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:163" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="=================conn===================:&{{0xc0003a4000}}" func="pbx-api-gin/internal/app/stc.readMsg" file="/root/git-project/cicc.bak/internal/app/stc/index.go:146" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 02:09:33" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 02:10:59" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"
+time="2025-09-10 02:11:02" level="info" msg="Start AMI" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:19" appname="pbx-panel"
+time="2025-09-10 02:11:02" level="info" msg="ami setting: &{Username:admin Password:admin Host:127.0.0.1 Port:5038 DialTimeout:0s ReconnectInterval:0s Keepalive:false LogLevel:error Report:false}" func="pbx-api-gin/internal/app/ami.StartAMI" file="/root/git-project/cicc.bak/internal/app/ami/index.go:26" appname="pbx-panel"
+time="2025-09-10 02:11:02" level="info" msg="Server Start Awaiting Signal" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:19" appname="pbx-panel"
+time="2025-09-10 02:11:02" level="info" msg="ami connect: Asterisk Call Manager/7.0.1" func="github.com/tqcenglish/amigo-go.(*amiAdapter).initializeSocket" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/ami.go:103" appname="pbx-panel"
+time="2025-09-10 02:11:02" level="info" msg="ami login action: map[Action:Login Secret:admin Username:admin]" func="github.com/tqcenglish/amigo-go.(*amiAdapter).login" file="/root/go/pkg/mod/github.com/tqcenglish/amigo-go@v1.1.14/action.go:38" appname="pbx-panel"
+time="2025-09-10 02:11:02" level="info" msg="ami connect on Connect OK" func="pbx-api-gin/internal/app/ami.StartAMI.func2" file="/root/git-project/cicc.bak/internal/app/ami/index.go:41" appname="pbx-panel"
+time="2025-09-10 02:11:02" level="info" msg="ami callback" func="pbx-api-gin/internal/app.StartApp.func1" file="/root/git-project/cicc.bak/internal/app/index.go:22" appname="pbx-panel"
+time="2025-09-10 04:30:48" level="info" msg="Exiting" func="pbx-api-gin/pkg/utils.Exit" file="/root/git-project/cicc.bak/pkg/utils/exit.go:21" appname="pbx-panel"

+ 230 - 0
internal/app/ami/action/call.go

@@ -0,0 +1,230 @@
+package action
+
+import (
+	"fmt"
+	"pbx-api-gin/internal/app/ami"
+	"pbx-api-gin/pkg/lfshook"
+	"pbx-api-gin/pkg/utils"
+	"strings"
+	"time"
+)
+
+// Hangup 挂断指定分机或通道
+func Hangup(channel string) {
+	lfshook.NewLogger().Infof("hangup extensions/channel %s", channel)
+	if !utils.IsChannel(channel) {
+		channel = fmt.Sprintf(`/^(PJ)?SIP\/%s-.*$/`, channel)
+	}
+
+	action := map[string]string{
+		"Action":  "hangup",
+		"Channel": channel,
+	}
+	lfshook.NewLogger().Infof("hangup action %+v", action)
+	if _, _, err := ami.AminInstance.Send(action); err != nil {
+		lfshook.NewLogger().Errorf("Hangup %+v", err)
+	}
+}
+
+// Dial 拨打号码
+func Dial(src, dst, dialrule, callerID, callerName string, callType string) {
+	chanel := fmt.Sprintf("%s/%s", callType, src)
+
+	action := map[string]string{
+		"Action":   "Originate",
+		"Channel":  chanel,
+		"Exten":    dst,
+		"Context":  dialrule,
+		"CallerID": fmt.Sprintf("%s<%s>", callerName, callerID),
+		"Priority": "1",
+		"async":    "true",
+	}
+	lfshook.NewLogger().Infof("dial action %+v", action)
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		lfshook.NewLogger().Errorf("%+v", err)
+	}
+	lfshook.NewLogger().Info(res)
+}
+
+// ChanSpy
+func ChanSpy(src, dst string, whisper, bargein bool) {
+	lfshook.NewLogger().Infof("chan spy src:%s dst:%s", src, dst)
+
+	channel := fmt.Sprintf("%s/%s", utils.DialPrefix, dst)
+	data := fmt.Sprintf("%s/%s,q,E", utils.DialPrefix, src)
+
+	if whisper {
+		data = fmt.Sprintf("%s,w", data)
+	}
+
+	if bargein {
+		data = fmt.Sprintf("%s,B", data)
+	}
+
+	action := map[string]string{
+		"Action":      "Originate",
+		"Channel":     channel, // 不存在的通话
+		"Application": "ChanSpy",
+		"Data":        data, // 存在的通话
+		"CallerID":    dst,
+		"Async":       "true",
+	}
+	lfshook.NewLogger().Infof("ChanSpy action %+v", action)
+	_, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		lfshook.NewLogger().Errorf("%+v", err)
+	}
+}
+
+// Page
+func Page(src string, extensions []string, duplex bool) {
+	channel := fmt.Sprintf("%s/%s", utils.DialPrefix, src)
+	data := make([]string, 0)
+	for _, exten := range extensions {
+		data = append(data, fmt.Sprintf("%s/%s", utils.DialPrefix, exten))
+	}
+	appData := strings.Join(data, "&")
+	if duplex {
+		appData = fmt.Sprintf("%s,db(header-handler^addheader^1)", appData)
+	} else {
+		appData = fmt.Sprintf("%s,b(header-handler^addheader^1)", appData)
+	}
+	//timeout
+	appData = fmt.Sprintf("%s,30", appData)
+	action := map[string]string{
+		"Action":      "Originate",
+		"Channel":     channel,
+		"Application": "page",
+		"Data":        appData,
+		"CallerID":    src,
+		"CallerSrc":   src,
+		"Variable":    "PJSIP_HEADER(add,answer-after)=0",
+		"async":       "true",
+	}
+	lfshook.NewLogger().Infof("page action %+v", action)
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		lfshook.NewLogger().Errorf("%+v", err)
+	}
+	lfshook.NewLogger().Infof("%+v", res)
+}
+
+// Play 转 AGI 播放
+func Play(name, UUID string, extensions []string, hangupAll bool, autoanswer string) {
+	if hangupAll {
+		lfshook.NewLogger().Infof("hangup all before play to %+v", extensions)
+		for _, extension := range extensions {
+			Hangup(extension)
+		}
+		time.Sleep(3 * time.Second)
+	}
+
+	channel := "Local/broadcast@broadcast"
+	data := make([]string, 0)
+	for _, exten := range extensions {
+		data = append(data, fmt.Sprintf("%s/%s", utils.DialPrefix, exten))
+	}
+	appData := strings.Join(data, "&")
+	//timeout
+	/*if autoanswer == "yes" {
+		appData = fmt.Sprintf("%s,%s,%d", appData, "b(header-handler^addheader^1)", configs.ConfigGlobal.AsteriskBroadcastTimeout)
+	} else {
+		appData = fmt.Sprintf("%s,,%d", appData, configs.ConfigGlobal.AsteriskBroadcastTimeout)
+	}*/
+	variable := fmt.Sprintf("task_UUID=%s", UUID)
+	action := map[string]string{
+		"Action":      "Originate",
+		"Channel":     channel,
+		"Application": "page",
+		"Data":        appData,
+		"Variable":    variable,
+		//"CallerID":    fmt.Sprintf("%s<%s>", configs.ConfigGlobal.AsteriskBroadcastName, configs.ConfigGlobal.AsteriskBroadcastID),
+		"CallerID": fmt.Sprintf("%s<%s>", name, name),
+		"CallSrc":  name,
+		"async":    "true",
+	}
+	lfshook.NewLogger().Infof("play action %+v", action)
+	_, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		lfshook.NewLogger().Errorf("%+v", err)
+	}
+}
+
+// Redirect 转接
+func Redirect(channel, dst, dialrule string) (err error) {
+	callerID := "redirect"
+	lfshook.NewLogger().Infof("redirect src %s to dst %s", channel, dst)
+	if !utils.IsChannel(channel) {
+		callerID = channel
+		if channel, err = GetChannelByExten(channel); err != nil {
+			return err
+		}
+	}
+
+	action := map[string]string{
+		"Action":   "Redirect",
+		"Channel":  channel,
+		"Exten":    dst,
+		"Context":  dialrule,
+		"CallerID": callerID,
+		"Priority": "1",
+		"async":    "true",
+	}
+	lfshook.NewLogger().Infof("redirect %+v", action)
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+	}
+	lfshook.NewLogger().Info(res)
+	return err
+}
+
+// Redirect 转接
+func BlindTransfer(channel, dst, dialrule string) (err error) {
+
+	lfshook.NewLogger().Infof("BlindTransfer src %s to dst %s", channel, dst)
+	if !utils.IsChannel(channel) {
+		if channel, err = GetExtenChan(channel); err != nil {
+			return err
+		}
+	}
+	action := map[string]string{
+		"Action":  "BlindTransfer",
+		"Channel": channel,
+		"Exten":   dst,
+		"Context": dialrule,
+	}
+	lfshook.NewLogger().Infof("BlindTransfer %+v", action)
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+	}
+	lfshook.NewLogger().Info(res)
+	return err
+}
+
+// Atxfer
+func Atxfer(channel, dst, dialrule string) (err error) {
+	// 获取通道
+	if !utils.IsChannel(channel) {
+		if channel, err = GetChannelByExten(channel); err != nil {
+			return err
+		}
+	}
+
+	action := map[string]string{
+		"Action":  "Atxfer",
+		"Channel": channel,
+		"Exten":   dst,
+		"Context": dialrule,
+	}
+	lfshook.NewLogger().Infof("atxfer action %+v", action)
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		lfshook.NewLogger().Errorf("%+v", err)
+		return err
+	}
+	lfshook.NewLogger().Debugf("atxfer res %+v", res)
+	return err
+}

+ 94 - 0
internal/app/ami/action/channel.go

@@ -0,0 +1,94 @@
+package action
+
+import (
+	"errors"
+	"pbx-api-gin/api/model"
+	"pbx-api-gin/internal/app/ami"
+	"pbx-api-gin/pkg/lfshook"
+	"pbx-api-gin/pkg/utils"
+)
+
+// CoreShowChannels 获取通话通道
+func CoreShowChannels() (result []model.CoreShowChannelResVO, err error) {
+	// 通过 src 查询对应通道
+	_, events, err := ami.AminInstance.Send(map[string]string{
+		"Action": "CoreShowChannels",
+	})
+	if err != nil {
+		lfshook.NewLogger().Errorf("core show channels error %+v", err)
+		return nil, err
+	}
+
+	lfshook.NewLogger().Tracef("events %+v", events)
+	result = make([]model.CoreShowChannelResVO, 0)
+	for _, event := range events {
+		if event.Data["Event"] == "CoreShowChannel" {
+			channel := model.CoreShowChannelResVO{
+				CallerIDName:      event.Data["CallerIDName"],
+				CallerIDNum:       event.Data["CallerIDNum"],
+				ConnectedLineName: event.Data["ConnectedLineName"],
+				ConnectedLineNum:  event.Data["ConnectedLineNum"],
+				Channel:           event.Data["Channel"],
+				Duration:          event.Data["Duration"],
+				DurationSecond:    utils.TimeStringToSecond(event.Data["Duration"]),
+			}
+			result = append(result, channel)
+		}
+	}
+	lfshook.NewLogger().Tracef("channels %+v", result)
+	return result, nil
+}
+
+// GetChannelByExten  通过 exten 查询对应通道
+func GetChannelByExten(exten string) (channel string, err error) {
+	lfshook.NewLogger().Infof("GetChannelByExten %s", exten)
+	_, events, err := ami.AminInstance.Send(map[string]string{
+		"Action": "CoreShowChannels",
+	})
+	if err != nil {
+		lfshook.NewLogger().Errorf("core show channels error %+v", err)
+		return "", err
+	}
+
+	for _, event := range events {
+		lfshook.NewLogger().Infof("CoreShowChannels event Data %+v", event.Data)
+		if event.Data["Event"] == "CoreShowChannel" && event.Data["ConnectedLineNum"] == exten && event.Data["CallerIDNum"] != exten {
+			channel = event.Data["Channel"]
+			lfshook.NewLogger().Infof("GetChannelByExten get channel %s", channel)
+			break
+		}
+	}
+	if channel == "" {
+		lfshook.NewLogger().Errorf("not found channel %s", exten)
+		return "", errors.New("not found channel")
+	}
+
+	return channel, nil
+}
+
+// GetBridgedChan  通过 exten 查询对应通道
+func GetExtenChan(exten string) (channel string, err error) {
+	lfshook.NewLogger().Infof("GetExtenChan %s", exten)
+	_, events, err := ami.AminInstance.Send(map[string]string{
+		"Action": "CoreShowChannels",
+	})
+	if err != nil {
+		lfshook.NewLogger().Errorf("core show channels error %+v", err)
+		return "", err
+	}
+
+	for _, event := range events {
+		lfshook.NewLogger().Infof("CoreShowChannels event Data %+v", event.Data)
+		if event.Data["Event"] == "CoreShowChannel" && event.Data["ConnectedLineNum"] != exten && event.Data["CallerIDNum"] == exten {
+			channel = event.Data["Channel"]
+			lfshook.NewLogger().Infof("GetChannelByExten get channel %s", channel)
+			break
+		}
+	}
+	if channel == "" {
+		lfshook.NewLogger().Errorf("not found channel %s", exten)
+		return "", errors.New("not found channel")
+	}
+
+	return channel, nil
+}

+ 131 - 0
internal/app/ami/action/conference.go

@@ -0,0 +1,131 @@
+package action
+
+import (
+	"errors"
+	"pbx-api-gin/internal/app/ami"
+)
+
+func ListRoom(options map[string]string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action": "ConfbridgeListRooms",
+	}
+
+	for key, value := range options {
+		action[key] = value
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+func List(confnum string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action":     "ConfbridgeList",
+		"Conference": confnum,
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+func Kick(confnum, channel string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action":     "ConfbridgeKick",
+		"Conference": confnum,
+		"Channel":    channel,
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+func Mute(confnum, channel string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action":     "ConfbridgeMute",
+		"Conference": confnum,
+		"Channel":    channel,
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+func UnMute(confnum, channel string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action":     "ConfbridgeUnmute",
+		"Conference": confnum,
+		"Channel":    channel,
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+func Lock(confnum string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action":     "ConfbridgeLock",
+		"Conference": confnum,
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+func UnLock(confnum string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action":     "ConfbridgeUnlock",
+		"Conference": confnum,
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}

+ 37 - 0
internal/app/ami/action/extension.go

@@ -0,0 +1,37 @@
+package action
+
+import (
+	"errors"
+	"pbx-api-gin/internal/app/ami"
+	"pbx-api-gin/internal/app/ami/model"
+)
+
+func ExtensionStateList() (result []*model.ExtensionStatus, err error) {
+	action := map[string]string{
+		"Action": "ExtensionStateList",
+	}
+
+	res, events, err := ami.AminInstance.Send(action)
+
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	for _, event := range events {
+		if event.Data["Event"] == "ExtensionStatus" {
+			point := &model.ExtensionStatus{
+				Event:      event.Data["Event"],
+				Exten:      event.Data["Exten"],
+				Context:    event.Data["Context"],
+				Hint:       event.Data["Hint"],
+				Status:     event.Data["StatusText"],
+				StatusText: event.Data["StatusText"],
+			}
+			result = append(result, point)
+		}
+	}
+	return result, nil
+}

+ 150 - 0
internal/app/ami/action/meetme.go

@@ -0,0 +1,150 @@
+package action
+
+import (
+	"errors"
+	"pbx-api-gin/internal/app/ami"
+	"pbx-api-gin/internal/app/ami/model"
+	"pbx-api-gin/pkg/lfshook"
+	"pbx-api-gin/pkg/utils"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+func ListRoomMeetMe(options map[string]string) (points []*model.MeetMeListRooms, err error) {
+	action := map[string]string{
+		"Action": "MeetmeListRooms",
+	}
+
+	for key, value := range options {
+		action[key] = value
+	}
+
+	res, events, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	for _, event := range events {
+		if event.Data["Event"] == "MeetmeListRooms" {
+			point := &model.MeetMeListRooms{}
+			mapstructure.Decode(event.Data, point)
+			points = append(points, point)
+		}
+	}
+	return points, nil
+}
+
+func ListMeetMe(confnum string) (points []*model.MeetmeList, err error) {
+	points = make([]*model.MeetmeList, 0)
+	action := map[string]string{
+		"Action": "MeetmeList",
+	}
+	if confnum != "" {
+		action["Conference"] = confnum
+	}
+
+	res, events, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return points, err
+	}
+	if res["Response"] != "Success" {
+		return points, errors.New(res["Message"])
+	}
+
+	lfshook.NewLogger().Tracef("ListMeetMe %+v", events)
+	for _, event := range events {
+		if event.Data["Event"] == "MeetmeList" {
+			point := &model.MeetmeList{}
+			mapstructure.Decode(event.Data, point)
+			points = append(points, point)
+		}
+
+	}
+	return points, nil
+}
+
+// 踢出成员:/etc/scripts/conference.sh kick 会议室号码 numbers
+func KickMeetMe(confnum, numbers string) (err error) {
+	lfshook.NewLogger().Infof("kick %s %s", confnum, numbers)
+	_, _, err = utils.ExecCmd("/etc/scripts/conference.sh", "kick", confnum, numbers)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func MuteMeetMe(meetme, usernum string) (res map[string]string, err error) {
+	lfshook.NewLogger().Infof("mute %s %s", meetme, usernum)
+	action := map[string]string{
+		"Action":  "MeetmeMute",
+		"Meetme":  meetme,
+		"Usernum": usernum,
+	}
+	lfshook.NewLogger().Infof("meetme action %+v", action)
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+func UnMuteMeetMe(meetme, usernum string) (res map[string]string, err error) {
+	action := map[string]string{
+		"Action":  "MeetmeUnmute",
+		"Meetme":  meetme,
+		"Usernum": usernum,
+	}
+
+	res, _, err = ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	return res, nil
+}
+
+// 锁定会议室:/etc/scripts/conference.sh lock  会议室号码
+func LockMeetMe(confnum string) (err error) {
+	_, _, err = utils.ExecCmd("/etc/scripts/conference.sh", "lock", confnum)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 解除锁定会议室:/etc/scripts/conference.sh unlock  会议室号码
+func UnLockMeetMe(confnum string) (err error) {
+	_, _, err = utils.ExecCmd("/etc/scripts/conference.sh", "unlock", confnum)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 结束会议:/etc/scripts/conference.sh endconf  会议室号码
+func EndMeetMe(number string) (err error) {
+	_, _, err = utils.ExecCmd("/etc/scripts/conference.sh", "endconf", number)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 邀请会议:/etc/scripts/conference.sh invite 会议室号码
+func IviteMeetMe(conference, number string, dialplan string, myExtension string) (err error) {
+	_, _, err = utils.ExecCmd("/etc/scripts/conference.sh", "invite", conference, number, dialplan, myExtension)
+	if err != nil {
+		return err
+	}
+	return nil
+}

+ 82 - 0
internal/app/ami/action/park.go

@@ -0,0 +1,82 @@
+package action
+
+import (
+	"errors"
+	"pbx-api-gin/internal/app/ami"
+	amiModel "pbx-api-gin/internal/app/ami/model"
+	"pbx-api-gin/pkg/lfshook"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+func Park(extension, lot string) (err error) {
+	channel, err := GetChannelByExten(extension)
+	if err != nil {
+		return err
+	}
+	action := map[string]string{
+		"Action":  "Park",
+		"Channel": channel,
+	}
+	if lot != "" {
+		action["Parkinglot"] = lot
+	}
+
+	res, events, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return err
+	}
+	lfshook.NewLogger().Infof("Park %+v %+v", res, events)
+	if res["Response"] != "Success" {
+		return errors.New(res["Message"])
+	}
+	return nil
+}
+
+func ParkedCalls(lot string) (result []*amiModel.ParkedCall, err error) {
+	action := map[string]string{
+		"Action":     "ParkedCalls",
+		"Parkinglot": lot,
+	}
+	res, events, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return result, errors.New(res["Message"])
+	}
+
+	result = make([]*amiModel.ParkedCall, 0)
+	for _, event := range events {
+		if event.Data["Event"] == "ParkedCall" {
+			call := &amiModel.ParkedCall{}
+			mapstructure.Decode(event.Data, call)
+			result = append(result, call)
+		}
+	}
+
+	return result, nil
+}
+
+func Parkinglots() (result []*amiModel.Parkinglot, err error) {
+	action := map[string]string{
+		"Action": "Parkinglots",
+	}
+	res, events, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return nil, err
+	}
+	if res["Response"] != "Success" {
+		return nil, errors.New(res["Message"])
+	}
+
+	result = make([]*amiModel.Parkinglot, 0)
+	for _, event := range events {
+		if event.Data["Event"] == "Parkinglot" {
+			lot := &amiModel.Parkinglot{}
+			mapstructure.Decode(event.Data, lot)
+			result = append(result, lot)
+		}
+	}
+	return result, nil
+}

+ 32 - 0
internal/app/ami/action/playback.go

@@ -0,0 +1,32 @@
+package action
+
+import (
+	"errors"
+	"fmt"
+	"pbx-api-gin/internal/app/ami"
+	"strings"
+)
+
+func Playback(filename string, count int, exten string, timelen int, delay int) (err error) {
+
+	Para := fmt.Sprintf("count=%d,timelen=%d,filename=%s,delay=%d", count, timelen, strings.Replace(filename, ".wav", "", -1), delay)
+	Chan := fmt.Sprintf("Local/%s", exten)
+
+	action := map[string]string{
+		"Action":   "Originate",
+		"Channel":  Chan,
+		"Exten":    exten,
+		"Context":  "broadcast-playfile",
+		"Priority": "1",
+		"Variable": Para,
+	}
+
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return err
+	}
+	if res["Response"] != "Success" {
+		return errors.New(res["Message"])
+	}
+	return nil
+}

+ 134 - 0
internal/app/ami/action/queue.go

@@ -0,0 +1,134 @@
+package action
+
+import (
+	"errors"
+	"pbx-api-gin/internal/app/ami"
+	"pbx-api-gin/internal/app/ami/model"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+// QueueStatus 队列信息
+func QueueStatus(queue, member string) (queueParams *model.QueueParams, err error) {
+	action := map[string]string{
+		"Action": "QueueStatus",
+	}
+	if queue != "" {
+		action["Queue"] = queue
+	}
+	if member != "" {
+		action["Member"] = member
+	}
+	_, events, _ := ami.AminInstance.Send(action)
+	for _, event := range events {
+		if event.Data["Event"] == "QueueParams" {
+			queueParams = &model.QueueParams{Members: make([]*model.QueueMember, 0), Entrys: make([]*model.QueueEntry, 0)}
+			mapstructure.Decode(event.Data, queueParams)
+		}
+		if event.Data["Event"] == "QueueMember" {
+			member := &model.QueueMember{}
+			mapstructure.Decode(event.Data, member)
+			queueParams.Members = append(queueParams.Members, member)
+		}
+
+		if event.Data["Event"] == "QueueEntry" {
+			entry := &model.QueueEntry{}
+			mapstructure.Decode(event.Data, entry)
+			queueParams.Entrys = append(queueParams.Entrys, entry)
+		}
+	}
+	return queueParams, nil
+}
+
+// QueueAllStatus 队列信息
+func QueueAllStatus(queue, member string) (queuesMap map[string]*model.QueueParams, err error) {
+	queuesMap = make(map[string]*model.QueueParams)
+	action := map[string]string{
+		"Action": "QueueStatus",
+	}
+	if queue != "" {
+		action["Queue"] = queue
+	}
+	if member != "" {
+		action["Member"] = member
+	}
+	_, events, _ := ami.AminInstance.Send(action)
+	for _, event := range events {
+		if event.Data["Event"] == "QueueParams" {
+			queueParams := &model.QueueParams{Members: make([]*model.QueueMember, 0), Entrys: make([]*model.QueueEntry, 0)}
+			// data, _ := json.Marshal(event.Data)
+			// json.Unmarshal(data, queueParams)
+			mapstructure.Decode(event.Data, queueParams)
+			queuesMap[queueParams.Queue] = queueParams
+		}
+		if event.Data["Event"] == "QueueMember" {
+			member := &model.QueueMember{}
+			mapstructure.Decode(event.Data, member)
+			if queue, ok := queuesMap[member.Queue]; ok {
+				queue.Members = append(queue.Members, member)
+			}
+		}
+
+		if event.Data["Event"] == "QueueEntry" {
+			entry := &model.QueueEntry{}
+			mapstructure.Decode(event.Data, entry)
+			if queue, ok := queuesMap[entry.Queue]; ok {
+				queue.Entrys = append(queue.Entrys, entry)
+			}
+		}
+	}
+	return queuesMap, nil
+}
+
+// QueueAdd 添加到队列
+func QueueAdd(queue, interface_value, pause string) error {
+	action := map[string]string{
+		"Action":    "QueueAdd",
+		"Queue":     queue,
+		"Interface": interface_value,
+		"Paused":    pause,
+	}
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return err
+	}
+	if res["Response"] != "Success" {
+		return errors.New(res["Message"])
+	}
+	return nil
+}
+
+// QueueRemove 从队列移除
+func QueueRemove(queue, interface_value string) error {
+	action := map[string]string{
+		"Action":    "QueueRemove",
+		"Queue":     queue,
+		"Interface": interface_value,
+	}
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return err
+	}
+	if res["Response"] != "Success" {
+		return errors.New(res["Message"])
+	}
+	return nil
+}
+
+// QueuePasue 暂停控制
+func QueuePasue(queue, interface_value, pause string) error {
+	action := map[string]string{
+		"Action":    "QueuePause",
+		"Queue":     queue,
+		"Interface": interface_value,
+		"Paused":    pause,
+	}
+	res, _, err := ami.AminInstance.Send(action)
+	if err != nil {
+		return err
+	}
+	if res["Response"] != "Success" {
+		return errors.New(res["Message"])
+	}
+	return nil
+}

+ 99 - 0
internal/app/ami/bridge.go

@@ -0,0 +1,99 @@
+package ami
+
+import (
+	"fmt"
+
+	"pbx-api-gin/pkg/lfshook"
+	"sync"
+
+	"github.com/mitchellh/mapstructure"
+)
+
+var bridgeMap sync.Map
+
+const bridgeEventName = "CustomBridgeEvent"
+
+type BridgeEvent struct {
+	Event string `json:"event"`
+	// Timestamp         string `json:"timestamp"`
+	BridgeUniqueid          string `json:"-"`
+	Channel                 string `json:"channel"`
+	ChannelState            string `json:"channelState"`
+	ChannelStateDesc        string `json:"channelStateDesc"`
+	CallerIDNum             string `json:"callerIDNumber"`
+	CallerIDName            string `json:"callerIDName"`
+	CallerConnectedLineNum  string `json:"callerConnectedLineNumber"`
+	CallerConnectedLineName string `json:"callerConnectedLineName"`
+	BridgeType              string `json:"bridgeType"`
+	BridgeNumChannels       string `json:"bridgeNumberChannels"`
+	Uniqueid                string `json:"uniqueid"`
+}
+
+func (event BridgeEvent) String() string {
+	return fmt.Sprintf("channel: %s, uniqueid: %s\n", event.Channel, event.Uniqueid)
+}
+
+func handleAMIBridge(event map[string]string) {
+	if event["BridgeType"] != "basic" {
+		return
+	}
+
+	if event["CallerIDName"] == "conference" {
+		return
+	}
+
+	switch event["Event"] {
+	case "BridgeCreate":
+		bridgeMap.Store(event["BridgeUniqueid"], []*BridgeEvent{})
+	case "BridgeEnter":
+		status := &BridgeEvent{}
+		mapstructure.Decode(event, status)
+		if events, ok := bridgeMap.Load(status.BridgeUniqueid); ok {
+			bridgeMap.Store(status.BridgeUniqueid, append(events.([]*BridgeEvent), status))
+		} else {
+			lfshook.NewLogger().Warnf("BridgeEnter add failure id: %s on load", status.BridgeUniqueid)
+		}
+		//socketio.SocketIOServer.BroadcastToNamespace("", bridgeEventName, GetBridgeMapValue())
+	case "BridgeLeave":
+		status := &BridgeEvent{}
+		mapstructure.Decode(event, status)
+		if events, ok := bridgeMap.Load(status.BridgeUniqueid); ok {
+			events := events.([]*BridgeEvent)
+			for index, event := range events {
+				if event.Channel == status.Channel {
+					if index == len(events)-1 {
+						bridgeMap.Store(status.BridgeUniqueid, events[:index])
+					} else {
+						bridgeMap.Store(status.BridgeUniqueid, append(events[:index], events[index+1]))
+					}
+					break
+				}
+			}
+		} else {
+			lfshook.NewLogger().Warnf("BridgeLeave remove failure id: %s on load", status.BridgeUniqueid)
+		}
+		//socketio.SocketIOServer.BroadcastToNamespace("", bridgeEventName, GetBridgeMapValue())
+	case "BridgeDestroy":
+		bridgeMap.Delete(event["BridgeUniqueid"])
+		//socketio.SocketIOServer.BroadcastToNamespace("", bridgeEventName, GetBridgeMapValue())
+	}
+
+}
+
+func GetBridgeMapValue() (result map[string][]*BridgeEvent) {
+	result = make(map[string][]*BridgeEvent)
+	bridgeMap.Range(func(key, value interface{}) bool {
+		events := value.([]*BridgeEvent)
+		result[key.(string)] = events
+		return true
+	})
+	return
+}
+
+// ClearBridge Asterisk 重启,内部通话清空, AMI 重连成功也需求清空 Bridge
+func ClearBridge() {
+	bridgeMap.Range(func(key, value interface{}) bool {
+		bridgeMap.Delete(key)
+		return true
+	})
+}

+ 239 - 0
internal/app/ami/index.go

@@ -0,0 +1,239 @@
+package ami
+
+import (
+	"pbx-api-gin/internal/app/ami/model"
+	"pbx-api-gin/internal/app/mysql"
+	"pbx-api-gin/internal/pkg/configs"
+
+	"pbx-api-gin/pkg/lfshook"
+	"strings"
+
+	"github.com/mitchellh/mapstructure"
+	"github.com/sirupsen/logrus"
+	"github.com/tqcenglish/amigo-go"
+	"github.com/tqcenglish/amigo-go/pkg"
+)
+
+var AminInstance *amigo.Amigo
+
+func StartAMI(connectOKCallBack func(), handleEvents []func(event map[string]string)) {
+	lfshook.NewLogger().Info("Start AMI")
+	settings := &amigo.Settings{
+		Host:     configs.ConfigGlobal.AsteriskAMIHost,
+		Port:     configs.ConfigGlobal.AsteriskAMIPort,
+		Username: configs.ConfigGlobal.AsteriskAMIUser,
+		Password: configs.ConfigGlobal.AsteriskAMISecret,
+		LogLevel: logrus.ErrorLevel}
+	lfshook.NewLogger().Infof("ami setting: %+v", settings)
+	AminInstance = amigo.New(settings, lfshook.NewLogger())
+	AminInstance.EventOn(func(payload ...interface{}) {
+		// lfshook.NewLogger().Infof("ami event on %+v", payload[0])
+		event := payload[0].(map[string]string)
+		handleAMIBridge(event)
+		handleAMI(event)
+		//handleSocketIO(event)
+
+		for _, handle := range handleEvents {
+			go handle(event)
+		}
+
+	})
+	AminInstance.ConnectOn(func(payload ...interface{}) {
+		lfshook.NewLogger().Infof("ami connect on %+v", payload[0])
+		if payload[0] == pkg.Connect_OK {
+			ClearBridge()
+			connectOKCallBack()
+		} else {
+			lfshook.NewLogger().Errorf("ami connect failure %+v", payload)
+		}
+	})
+	AminInstance.Connect()
+}
+
+func handleAMI(event map[string]string) {
+	switch event["Event"] {
+	case "ContactStatus": /*
+			endpointName := event["EndpointName"]
+			contactStatus := event["ContactStatus"]
+
+			// 中继变化也会触发此事件, 需要忽略
+			switch contactStatus {
+			case "Reachable":
+				//add contact
+				uri := event["URI"]
+				data := strings.Split(uri, "@")
+				if len(data) < 2 {
+					lfshook.NewLogger().Debugf("split URI by @ error URI: %s event:%+v", uri, event)
+					return
+				}
+				first := data[1]
+				redis.ExtensionSet(endpointName, event["URI"], strings.Split(first, ":")[0], event["RoundtripUsec"])*/
+	case "Removed":
+		//remove contact
+		//redis.ExtensionDel(endpointName)
+
+	case "ExtensionStatus":
+
+		status := &model.Extension{
+			Extension: event["Exten"],
+			Status:    event["StatusText"],
+		}
+		_, err := mysql.DBOrmInstance.Where("exten = ?", status.Extension).Cols("status").Update(status)
+		if err != nil {
+			lfshook.NewLogger().Infof("update extension status err : %+v", err.Error())
+		}
+
+	}
+}
+
+func handleSocketIO(event map[string]string) {
+	var data interface{}
+	switch event["Event"] {
+	case "ExtensionStatus":
+		lfshook.NewLogger().Tracef("ExtensionStatus %+v", event)
+		status := &model.ExtensionStatus{}
+		mapstructure.Decode(event, status)
+		data = status
+	case "QueueMemberStatus":
+		lfshook.NewLogger().Tracef("QueueMemberStatus %+v", event)
+		status := &model.QueueMember{}
+		mapstructure.Decode(event, status)
+		data = status
+	case "QueueCallerJoin":
+		status := &model.QueueCallerJoin{}
+		lfshook.NewLogger().Infof("status %+v", event)
+		mapstructure.Decode(event, status)
+		data = status
+	case "QueueCallerLeave":
+		status := &model.QueueCallerLeave{}
+		mapstructure.Decode(event, status)
+		data = status
+	case "QueueCallerAbandon":
+		status := &model.QueueCallerAbandon{}
+		mapstructure.Decode(event, status)
+		data = status
+	case "ParkedCall":
+		status := &model.ParkedCall{}
+		mapstructure.Decode(event, status)
+		data = status
+	case "ParkedCallTimeOut":
+		status := &model.ParkedCallTimeOut{}
+		mapstructure.Decode(event, status)
+		data = status
+	case "ParkedCallGiveUp":
+		status := &model.ParkedCallGiveUp{}
+		mapstructure.Decode(event, status)
+		data = status
+	case "UnParkedCall":
+		status := &model.UnParkedCall{}
+		mapstructure.Decode(event, status)
+		data = status
+
+	case "MeetmeEnd":
+		status := &model.MeetmeEnd{}
+		mapstructure.Decode(event, status)
+		data = status
+
+	case "MeetmeJoin":
+		fallthrough
+	case "MeetmeLeave":
+		fallthrough
+	case "MeetmeMute":
+		fallthrough
+	case "MeetmeTalking":
+		fallthrough
+	case "MeetmeTalkRequest":
+		status := &model.Meetme{}
+		mapstructure.Decode(event, status)
+		data = status
+
+	case "PresenceStateChange":
+		status := &model.PresenceStateChange{}
+		mapstructure.Decode(event, status)
+		if status.Presentity != "" {
+			data := strings.Split(status.Presentity, ":")
+			if len(data) == 2 {
+				status.Extension = data[1]
+			}
+		}
+
+		if status.Status == "dnd" {
+			status.DndStatus = "yes"
+		} else {
+			status.DndStatus = ""
+		}
+		data = status
+
+	case "DialBegin":
+		fallthrough
+	case "DialEnd":
+		fallthrough
+	case "DialState":
+		status := &model.Dial{}
+		mapstructure.Decode(event, status)
+		data = status
+
+	case "Newstate":
+		status := &model.Newstate{}
+		mapstructure.Decode(event, status)
+		data = status
+
+	case "Hangup":
+		status := &model.Hangup{}
+		mapstructure.Decode(event, status)
+		data = status
+
+	case "UserEvent":
+		switch event["UserEvent"] {
+		case "SetDND":
+			status := &model.SetDND{}
+			mapstructure.Decode(event, status)
+			data = status
+			event["Event"] = event["UserEvent"]
+		case "WakeUpStatus":
+			status := &model.WakeUpStatus{}
+			mapstructure.Decode(event, status)
+			data = status
+			event["Event"] = event["UserEvent"]
+		default:
+			data = event
+		}
+	case "SuccessfulAuth":
+		// 获取上线 IP 地址
+		status := &model.SuccessfulAuth{}
+		mapstructure.Decode(event, status)
+		status.GetAddress()
+		data = status
+
+	case "MessageWaiting":
+		status := &model.MessageWaiting{}
+		mapstructure.Decode(event, status)
+		status.GetExtension()
+		data = status
+		if status.Extension == "" {
+			lfshook.NewLogger().Warnf("MessageWaiting error %+v", event)
+			return
+		}
+	default:
+		// if !strings.Contains(event["Event"], "RTP") &&
+		// 	!strings.Contains(event["Event"], "RTCP") &&
+		// 	!strings.Contains(event["Event"], "VarSet") &&
+		// 	!strings.Contains(event["Event"], "Bridge") &&
+		// 	!strings.Contains(event["Event"], "New") &&
+		// 	!strings.Contains(event["Event"], "SuccessfulAuth") &&
+		// 	!strings.Contains(event["Event"], "ChallengeSent") {
+		// 	lfshook.NewLogger().Info(event)
+		// }
+	}
+
+	if data != nil {
+		//socketio.SocketIOServer.BroadcastToNamespace("", event["Event"], data)
+	}
+}
+
+func Connected() bool {
+	if AminInstance != nil {
+		return AminInstance.Connected()
+	}
+	return false
+}

+ 24 - 0
internal/app/ami/model/cdr.go

@@ -0,0 +1,24 @@
+package model
+
+//Cdr 通话记录
+type Cdr struct {
+	AnswerTime         string `xorm:"AnswerTime"`
+	BillableSeconds    string `xorm:"BillableSeconds"`
+	CallerID           string `xorm:"CallerID"`
+	Channel            string `xorm:"Channel"`
+	Destination        string `xorm:"Destination"`
+	DestinationChannel string `xorm:"DestinationChannel"`
+	DestinationContext string `xorm:"DestinationContext"`
+	Disposition        string `xorm:"Disposition"`
+	Duration           string `xorm:"Duration"`
+	EndTime            string `xorm:"EndTime"`
+	Event              string `xorm:"Event"`
+	LastApplication    string `xorm:"LastApplication"`
+	LastData           string `xorm:"LastData"`
+	Privilege          string `xorm:"Privilege"`
+	RecordFile         string `xorm:"RecordFile"`
+	Source             string `xorm:"Source"`
+	StartTime          string `xorm:"StartTime"`
+	Timestamp          string `xorm:"Timestamp"`
+	UniqueID           string `xorm:"UniqueID"`
+}

+ 40 - 0
internal/app/ami/model/dial.go

@@ -0,0 +1,40 @@
+package model
+
+type Dial struct {
+	Event                 string `json:"event"`
+	Channel               string `json:"channel"`
+	ChannelState          string `json:"channelState"`
+	ChannelStateDesc      string `json:"channelStateDesc"`
+	CallerIDNum           string `json:"callerIDNum"`
+	CallerIDName          string `json:"callerIDName"`
+	ConnectedLineNum      string `json:"connectedLineNum"`
+	ConnectedLineName     string `json:"connectedLineName"`
+	AccountCode           string `json:"accountCode"`
+	Context               string `json:"context"`
+	Exten                 string `json:"exten"`
+	Priority              string `json:"priority"`
+	Uniqueid              string `json:"uniqueid"`
+	Linkedid              string `json:"linkedid"`
+	DestChannel           string `json:"destChannel"`
+	DestChannelState      string `json:"destChannelState"`
+	DestChannelStateDesc  string `json:"destChannelStateDesc"`
+	DestCallerIDNum       string `json:"destCallerIDNum"`
+	DestCallerIDName      string `json:"destCallerIDName"`
+	DestConnectedLineNum  string `json:"destConnectedLineNum"`
+	DestConnectedLineName string `json:"destConnectedLineName"`
+	DestLanguage          string `json:"destLanguage"`
+	DestAccountCode       string `json:"destAccountCode"`
+	DestContext           string `json:"destContext"`
+	DestExten             string `json:"destExten"`
+	DestPriority          string `json:"destPriority"`
+	DestUniqueid          string `json:"destUniqueid"`
+	DestLinkedid          string `json:"destLinkedid"`
+	DialString            string `json:"dialString"`
+	Forward               string `json:"forward"`
+}
+
+type DialBeign Dial
+
+type DialEnd Dial
+
+type DialState Dial

+ 84 - 0
internal/app/ami/model/event.go

@@ -0,0 +1,84 @@
+package model
+
+import "strings"
+
+type PresenceStateChange struct {
+	Event      string `json:"event"`
+	Status     string `json:"status"`
+	Message    string `json:"message"`
+	Presentity string `json:"presentity"`
+	Extension  string `json:"extension"`
+	DndStatus  string `json:"dndStatus"`
+}
+
+type Hangup struct {
+	AccountCode       string `json:"accountCode"`
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Cause             string `json:"cause"`
+	Causetxt          string `json:"causetxt"`
+	Channel           string `json:"channel"`
+	ChannelState      string `json:"channelState"`
+	ChannelStateDesc  string `json:"channelStateDesc"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Context           string `json:"context"`
+	Event             string `json:"event"`
+	Exten             string `json:"exten"`
+	Linkedid          string `json:"linkedid"`
+	Priority          string `json:"priority"`
+	Privilege         string `json:"privilege"`
+	Timestamp         string `json:"timestamp"`
+	Uniqueid          string `json:"uniqueid"`
+}
+
+type Newstate struct {
+	AccountCode       string `json:"accountCode"`
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	ChannelState      string `json:"channelState"`
+	ChannelStateDesc  string `json:"channelStateDesc"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Context           string `json:"context"`
+	Event             string `json:"event"`
+	Exten             string `json:"exten"`
+	Language          string `json:"language"`
+	Linkedid          string `json:"linkedid"`
+	Priority          string `json:"priority"`
+	Privilege         string `json:"privilege"`
+	Timestamp         string `json:"timestamp"`
+	Uniqueid          string `json:"uniqueid"`
+}
+
+type SuccessfulAuth struct {
+	Event         string `json:"event"`
+	AccountID     string `json:"accountID"`
+	RemoteAddress string `json:"remoteAddress"`
+	Address       string `json:"address"`
+}
+
+func (event *SuccessfulAuth) GetAddress() {
+	data := strings.Split(event.RemoteAddress, "/")
+	if len(data) == 4 {
+		event.Address = data[2]
+	} else {
+		event.Address = event.RemoteAddress
+	}
+}
+
+type MessageWaiting struct {
+	Event     string `json:"event"`
+	Extension string `json:"extension"`
+	New       string `json:"new"`
+	Old       string `json:"old"`
+	Mailbox   string `json:"mainbox"`
+}
+
+func (event *MessageWaiting) GetExtension() {
+	data := strings.Split(event.Mailbox, "@")
+	if len(data) == 2 {
+		event.Extension = data[0]
+	}
+}

+ 80 - 0
internal/app/ami/model/meetme.go

@@ -0,0 +1,80 @@
+package model
+
+import (
+	"encoding/json"
+	"pbx-api-gin/pkg/utils"
+)
+
+type MeetMeListRooms struct {
+	Activity   string `json:"activity"`
+	Conference string `json:"conference"`
+	Creation   string `json:"creation"`
+	Event      string `json:"event"`
+	Locked     string `json:"locked"`
+	Marked     string `json:"marked"`
+	Parties    string `json:"parties"`
+}
+
+type MeetmeList struct {
+	Admin             string `json:"admin"`
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	Conference        string `json:"conference"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Event             string `json:"event"`
+	MarkedUser        string `json:"markedUser"`
+	Muted             string `json:"muted"`
+	Role              string `json:"role"`
+	Talking           string `json:"talking"`
+	UserNumber        string `json:"user"`
+}
+
+func (data *MeetmeList) MarshalJSON() ([]byte, error) {
+	type Alias MeetmeList
+
+	muted := utils.YesToOn(data.Muted)
+	talking := utils.YesToOn(data.Talking)
+
+	return json.Marshal(&struct {
+		Talking string `json:"talking"`
+		Muted   string `json:"muted"`
+		*Alias
+	}{
+		Muted:   muted,
+		Talking: talking,
+		Alias:   (*Alias)(data),
+	})
+}
+
+type MeetmeEnd struct {
+	Event     string `json:"event"`
+	Meetme    string `json:"meetme"`
+	Timestamp string `json:"timestamp"`
+}
+
+type Meetme struct {
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	ChannelState      string `json:"channelState"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Duration          string `json:"duration"`
+	Context           string `json:"context"`
+	Event             string `json:"event"`
+	Exten             string `json:"exten"`
+	Meetme            string `json:"meetme"`
+	Priority          string `json:"priority"`
+	Privilege         string `json:"privilege"`
+	Timestamp         string `json:"timestamp"`
+	User              string `json:"user"`
+	Status            string `json:"status"`
+}
+
+type MeetmeJoin Meetme
+type MeetmeLeave Meetme
+type MeetmeMute Meetme
+type MeetmeTalkRequest Meetme
+type MeetmeTalking Meetme

+ 210 - 0
internal/app/ami/model/park.go

@@ -0,0 +1,210 @@
+package model
+
+type Parkinglot struct {
+	Name       string `json:"name"`
+	StartSpace string `json:"startSpace"`
+	StopSpace  string `json:"stopSpace"`
+	Timeout    string `json:"timeout"`
+}
+
+// Event:ParkedCall
+// ParkeeAccountCode:
+// ParkeeCallerIDName:106
+// ParkeeCallerIDNum:106
+// ParkeeChannel:PJSIP/106-00000171
+// ParkeeChannelState:6
+// ParkeeChannelStateDesc:Up
+// ParkeeConnectedLineName:120
+// ParkeeConnectedLineNum:120
+// ParkeeContext:macro-stdexten
+// ParkeeExten:s
+// ParkeeLanguage:en
+// ParkeeLinkedid:1621821838.695
+// ParkeePriority:26
+// ParkeeUniqueid:1621821838.695
+// ParkerDialString:PJSIP/106
+// ParkingDuration:113
+// ParkingSpace:41
+// ParkingTimeout:487
+// Parkinglot:default
+
+type ParkedCall struct {
+	Event                   string `json:"event"`
+	ParkeeCallerIDName      string `json:"parkeeCallerIDName"`
+	ParkeeCallerIDNum       string `json:"parkeeCallerIDNumber"`
+	ParkeeChannel           string `json:"parkeeChannel"`
+	ParkeeChannelState      string `json:"parkeeChannelState"`
+	ParkeeChannelStateDesc  string `json:"parkeeChannelStateDesc"`
+	ParkeeConnectedLineName string `json:"parkeeConnectedLineName"`
+	ParkeeConnectedLineNum  string `json:"parkeeConnectedLineNumber"`
+	ParkeeContext           string `json:"parkeeContext"`
+	ParkeeExten             string `json:"parkeeExten"`
+	ParkeeLanguage          string `json:"parkeeLanguage"`
+	ParkeeLinkedid          string `json:"parkeeLinkedid"`
+	ParkeePriority          string `json:"parkeePriority"`
+	ParkeeUniqueid          string `json:"parkeeUniqueid"`
+	ParkerDialString        string `json:"parkerDialString"`
+	ParkingDuration         string `json:"parkingDuration"`
+	ParkingSpace            string `json:"parkingSpace"`
+	ParkingTimeout          string `json:"parkingTimeout"`
+	Parkinglot              string `json:"parkinglot"`
+}
+
+// Event:UnParkedCall
+// ParkeeAccountCode: ParkeeCallerIDName:106
+// ParkeeCallerIDNum:106
+// ParkeeChannel:PJSIP/106-000001a0
+// ParkeeChannelState:6
+// ParkeeChannelStateDesc:Up
+// ParkeeConnectedLineName:120
+// ParkeeConnectedLineNum:120
+// ParkeeContext:macro-stdexten
+// ParkeeExten:s
+// ParkeeLanguage:en
+// ParkeeLinkedid:1621835298.828
+// ParkeePriority:26
+// ParkeeUniqueid:1621835298.828
+// ParkerDialString:PJSIP/106
+// ParkingDuration:12
+// ParkingSpace:41
+// ParkingTimeout:588
+// Parkinglot:default
+// Privilege:call,all
+// RetrieverAccountCode:
+// RetrieverCallerIDName:119
+// RetrieverCallerIDNum:119
+// RetrieverChannel:PJSIP/119-000001a2
+// RetrieverChannelState:6
+// RetrieverChannelStateDesc:Up
+// RetrieverConnectedLineName:<unknown>
+// RetrieverConnectedLineNum:<unknown>
+// RetrieverContext:DialPlan1
+// RetrieverExten:41
+// RetrieverLanguage:en
+// RetrieverLinkedid:1621835340.830
+// RetrieverPriority:1
+// RetrieverUniqueid:1621835340.830
+// Timestamp:1621835340.214037
+
+type UnParkedCall struct {
+	Event                   string `json:"event"`
+	ParkeeCallerIDName      string `json:"parkeeCallerIDName"`
+	ParkeeCallerIDNum       string `json:"parkeeCallerIDNumber"`
+	ParkeeChannel           string `json:"parkeeChannel"`
+	ParkeeChannelState      string `json:"parkeeChannelState"`
+	ParkeeChannelStateDesc  string `json:"parkeeChannelStateDesc"`
+	ParkeeConnectedLineName string `json:"parkeeConnectedLineName"`
+	ParkeeConnectedLineNum  string `json:"parkeeConnectedLineNumber"`
+	ParkeeContext           string `json:"parkeeContext"`
+	ParkeeExten             string `json:"parkeeExten"`
+	ParkeeLanguage          string `json:"parkeeLanguage"`
+	ParkeeLinkedid          string `json:"parkeeLinkedid"`
+	ParkeePriority          string `json:"parkeePriority"`
+	ParkeeUniqueid          string `json:"parkeeUniqueid"`
+	ParkerDialString        string `json:"parkerDialString"`
+	ParkingDuration         string `json:"parkingDuration"`
+	ParkingSpace            string `json:"parkingSpace"`
+	ParkingTimeout          string `json:"parkingTimeout"`
+	Parkinglot              string `json:"parkinglot"`
+}
+
+// Event: ParkedCallTimeOut
+// ParkeeAccountCode: <value>
+// ParkeeCallerIDName: <value>
+// ParkeeCallerIDNum: <value>
+// ParkeeChannel: <value>
+// ParkeeChannelState: <value>
+// ParkeeChannelStateDesc: <value>
+// ParkeeConnectedLineName: <value>
+// ParkeeConnectedLineNum: <value>
+// ParkeeContext: <value>
+// ParkeeExten: <value>
+// ParkeeLanguage: <value>
+// ParkeeLinkedid: <value>
+// ParkeePriority: <value>
+// ParkeeUniqueid: <value>
+// ParkerAccountCode: <value>
+// ParkerCallerIDName: <value>
+// ParkerCallerIDNum: <value>
+// ParkerChannel: <value>
+// ParkerChannelState: <value>
+// ParkerChannelStateDesc: <value>
+// ParkerConnectedLineName: <value>
+// ParkerConnectedLineNum: <value>
+// ParkerContext: <value>
+// ParkerDialString: <value>
+// ParkerExten: <value>
+// ParkerLanguage: <value>
+// ParkerLinkedid: <value>
+// ParkerPriority: <value>
+// ParkerUniqueid: <value>
+// ParkingDuration: <value>
+// ParkingSpace: <value>
+// ParkingTimeout: <value>
+// Parkinglot: <value>
+type ParkedCallTimeOut struct {
+	Event                   string `json:"event"`
+	ParkeeCallerIDName      string `json:"parkeeCallerIDName"`
+	ParkeeCallerIDNum       string `json:"parkeeCallerIDNumber"`
+	ParkeeChannel           string `json:"parkeeChannel"`
+	ParkeeChannelState      string `json:"parkeeChannelState"`
+	ParkeeChannelStateDesc  string `json:"parkeeChannelStateDesc"`
+	ParkeeConnectedLineName string `json:"parkeeConnectedLineName"`
+	ParkeeConnectedLineNum  string `json:"parkeeConnectedLineNumber"`
+	ParkeeContext           string `json:"parkeeContext"`
+	ParkeeExten             string `json:"parkeeExten"`
+	ParkeeLanguage          string `json:"parkeeLanguage"`
+	ParkeeLinkedid          string `json:"parkeeLinkedid"`
+	ParkeePriority          string `json:"parkeePriority"`
+	ParkeeUniqueid          string `json:"parkeeUniqueid"`
+	ParkerDialString        string `json:"parkerDialString"`
+	ParkingDuration         string `json:"parkingDuration"`
+	ParkingSpace            string `json:"parkingSpace"`
+	ParkingTimeout          string `json:"parkingTimeout"`
+	Parkinglot              string `json:"parkinglot"`
+}
+
+// Event: ParkedCallGiveUp
+// ParkeeAccountCode:
+// ParkeeCallerIDName: 106
+// ParkeeCallerIDNum: 106
+// ParkeeChannel: PJSIP/106-00000191
+// ParkeeChannelState: 6
+// ParkeeChannelStateDesc: Up
+// ParkeeConnectedLineName: 120
+// ParkeeConnectedLineNum: 120
+// ParkeeContext: macro-stdexten
+// ParkeeExten: s
+// ParkeeLanguage: en
+// ParkeeLinkedid: 1621827325.781
+// ParkeePriority: 26
+// ParkeeUniqueid: 1621827325.781
+// ParkerDialString: PJSIP/106
+// ParkingDuration: 48
+// ParkingSpace: 41
+// ParkingTimeout: 552
+// Parkinglot: default
+// Privilege: call,all
+// Timestamp: 1621827421.460917
+
+type ParkedCallGiveUp struct {
+	Event                   string `json:"event"`
+	ParkeeCallerIDName      string `json:"parkeeCallerIDName"`
+	ParkeeCallerIDNum       string `json:"parkeeCallerIDNumber"`
+	ParkeeChannel           string `json:"parkeeChannel"`
+	ParkeeChannelState      string `json:"parkeeChannelState"`
+	ParkeeChannelStateDesc  string `json:"parkeeChannelStateDesc"`
+	ParkeeConnectedLineName string `json:"parkeeConnectedLineName"`
+	ParkeeConnectedLineNum  string `json:"parkeeConnectedLineNumber"`
+	ParkeeContext           string `json:"parkeeContext"`
+	ParkeeExten             string `json:"parkeeExten"`
+	ParkeeLanguage          string `json:"parkeeLanguage"`
+	ParkeeLinkedid          string `json:"parkeeLinkedid"`
+	ParkeePriority          string `json:"parkeePriority"`
+	ParkeeUniqueid          string `json:"parkeeUniqueid"`
+	ParkerDialString        string `json:"parkerDialString"`
+	ParkingDuration         string `json:"parkingDuration"`
+	ParkingSpace            string `json:"parkingSpace"`
+	ParkingTimeout          string `json:"parkingTimeout"`
+	Parkinglot              string `json:"parkinglot"`
+}

+ 90 - 0
internal/app/ami/model/queue.go

@@ -0,0 +1,90 @@
+package model
+
+//QueueParams 队列参数
+type QueueParams struct {
+	Queue            string         `json:"queue"`
+	Max              string         `json:"max"`
+	Strategy         string         `json:"strategy"`
+	Calls            string         `json:"calls"`
+	Holdtime         string         `json:"holdtime"`
+	TalkTime         string         `json:"talkTime"`
+	Completed        string         `json:"completed"`
+	Abandoned        string         `json:"abandoned"`
+	ServiceLevel     string         `json:"serviceLevel"`
+	ServicelevelPerf string         `json:"servicelevelPerf"`
+	Weight           string         `json:"weight"`
+	Members          []*QueueMember `json:"members"`
+	Entrys           []*QueueEntry  `json:"entrys"`
+}
+
+//QueueMember 队列成员
+type QueueMember struct {
+	Queue          string `json:"queue"`
+	Name           string `json:"name"`
+	Location       string `json:"location"`
+	StateInterface string `json:"stateInterface"`
+	Membership     string `json:"membership"`
+	Penalty        string `json:"penalty"`
+	CallsTaken     string `json:"callsTaken"`
+	LastCall       string `json:"lastCall"`
+	InCall         string `json:"inCall"`
+	Status         string `json:"status"`
+	Paused         string `json:"paused"`
+	PausedReason   string `json:"pausedReason"`
+}
+
+type QueueEntry struct {
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Event             string `json:"event"`
+	Position          string `json:"position"`
+	Priority          string `json:"priority"`
+	Queue             string `json:"queue"`
+	Uniqueid          string `json:"uniqueid"`
+	Wait              string `json:"wait"`
+}
+
+type QueueCallerJoin struct {
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Event             string `json:"event"`
+	Position          string `json:"position"`
+	Priority          string `json:"priority"`
+	Queue             string `json:"queue"`
+	Uniqueid          string `json:"uniqueid"`
+	Count             string `json:"count"`
+}
+
+type QueueCallerAbandon struct {
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Event             string `json:"event"`
+	Position          string `json:"position"`
+	Priority          string `json:"priority"`
+	Queue             string `json:"queue"`
+	Uniqueid          string `json:"uniqueid"`
+	HoldTime          string `json:"holdTime"`
+}
+
+type QueueCallerLeave struct {
+	CallerIDName      string `json:"callerIDName"`
+	CallerIDNum       string `json:"callerIDNumber"`
+	Channel           string `json:"channel"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNumber"`
+	Event             string `json:"event"`
+	Position          string `json:"position"`
+	Priority          string `json:"priority"`
+	Queue             string `json:"queue"`
+	Uniqueid          string `json:"uniqueid"`
+	Count             string `json:"count"`
+}

+ 131 - 0
internal/app/ami/model/sip.go

@@ -0,0 +1,131 @@
+package model
+
+import "encoding/json"
+
+// Event: EndpointList
+// ObjectType: endpoint
+// ObjectName: www
+// Transport: UDP
+// Aor: www
+// Auths: www
+// OutboundAuths: www
+// Contacts:
+// DeviceState: Unavailable
+// ActiveChannels:
+
+// Event: EndpointList
+// ObjectType: endpoint
+// ObjectName: 899
+// Transport:
+// Aor: 899
+// Auths: 899
+// OutboundAuths:
+// Contacts:
+// DeviceState: Unavailable
+// ActiveChannels:
+type Extension struct {
+	Extension string `xorm:"exten" json:"exten"`
+	Type      string `xorm:"type" json:"type"`
+	Priority  string `xorm:"priority" json:"priority"`
+	Special   string `xorm:"special" json:"special"`
+	Status    string `xorm:"status" json:"status"`
+	Remark    string `xorm:"remark" json:"remark"`
+}
+
+func (*Extension) TableName() string {
+	return "t_extension"
+}
+
+type EndpointList struct {
+	Event          string `json:"Event"`
+	ObjectType     string `json:"ObjectType"`
+	ObjectName     string `json:"ObjectName"`
+	Transport      string `json:"Transport"`
+	Aor            string `json:"Aor"`
+	Auths          string `json:"Auths"`
+	OutboundAuths  string `json:"OutboundAuths"`
+	Contacts       string `json:"Contacts"`
+	DeviceState    string `json:"DeviceState"`
+	ActiveChannels string `json:"ActiveChannels"`
+}
+
+// Event: PeerEntry
+// Channeltype: SIP
+// ObjectName: 875
+// ChanObjectType: peer
+// IPaddress: -none-
+// IPport: 0
+// Dynamic: yes
+// AutoForcerport: no
+// Forcerport: yes
+// AutoComedia: no
+// Comedia: yes
+// VideoSupport: no
+// TextSupport: no
+// ACL: no
+// Status: UNKNOWN
+// RealtimeDevice: no
+// Description:
+// Accountcode:
+type PeerEntry struct {
+	Event          string `json:"Event"`
+	Channeltype    string `json:"Channeltype"`
+	ObjectName     string `json:"ObjectName"`
+	ChanObjectType string `json:"ChanObjectType"`
+	IPaddress      string `json:"IPaddress"`
+	IPport         string `json:"IPport"`
+	Dynamic        string `json:"Dynamic"`
+	AutoForcerport string `json:"AutoForcerport"`
+	Forcerport     string `json:"Forcerport"`
+	AutoComedia    string `json:"AutoComedia"`
+	Status         string `json:"Status"`
+	RealtimeDevice string `json:"RealtimeDevice"`
+}
+
+// Event: ContactList
+// ObjectType: contact
+// ObjectName: 120;@199f77999ab9abe1ec7fbe48e0b51053
+// ViaAddr: 192.168.17.183
+// QualifyTimeout: 30.000000
+// CallId: DBnHD24ea-7MTKkYj9ewfafR0KAmWv4z
+// RegServer:
+// PruneOnBoot: no
+// Path:
+// Endpoint: 120
+// ViaPort: 60442
+// AuthenticateQualify: no
+// Uri: sip:120@192.168.17.183:60442;ob
+// QualifyFrequency: 300
+// UserAgent: Telephone 1.2.6
+// ExpirationTime: 1620818122
+// OutboundProxy:
+// Status: Reachable
+// RoundtripUsec: 85040
+type ContactList struct {
+	Event         string `json:"Event"`
+	ObjectName    string `json:"ObjectName"`
+	ViaAddr       string `json:"ViaAddr"`
+	Uri           string `json:"Uri"`
+	RoundtripUsec string `json:"RoundtripUsec"`
+}
+
+func (list ContactList) String() string {
+	data, _ := json.Marshal(list)
+	return string(data)
+}
+
+// 分机状态
+// Event: ExtensionStatus
+// Exten: 118
+// Context: channelhints_exten
+// Hint: PJSIP/118,CustomPresence:118
+// Status: 4
+// StatusText: Unavailable
+type ExtensionStatus struct {
+	Event      string `json:"event"`
+	Exten      string `json:"extension"`
+	Context    string `json:"context"`
+	Hint       string `json:"hint"`
+	Status     string `json:"status"`
+	StatusText string `json:"statusText"`
+}

+ 15 - 0
internal/app/ami/model/userevent.go

@@ -0,0 +1,15 @@
+package model
+
+type SetDND struct {
+	Context   string `json:"context"`
+	Exten     string `json:"exten"`
+	UserEvent string `json:"event"`
+	DNDStatus string `json:"dndStatus"`
+}
+
+type WakeUpStatus struct {
+	Status            string `json:"status"`
+	ResTime           string `json:"resTime"`
+	ConnectedLineName string `json:"connectedLineName"`
+	ConnectedLineNum  string `json:"connectedLineNum"`
+}

+ 22 - 0
internal/app/index.go

@@ -0,0 +1,22 @@
+package app
+
+import (
+	"pbx-api-gin/internal/app/ami"
+	"pbx-api-gin/internal/app/mysql"
+	"pbx-api-gin/internal/app/stc"
+
+	"pbx-api-gin/pkg/lfshook"
+)
+
+func StartApp() {
+	mysql.CreateDBInstance()
+	go ami.StartAMI(func() {
+		lfshook.NewLogger().Info("ami callback")
+		// 首次连接才进行初始化
+	}, []func(event map[string]string){})
+	//go agi.StartAGI()
+
+	//go stc.ConnectStc("127.0.0.1", "10100") //connect mc1
+	go stc.ConnectStc("192.168.17.14", 6090) //connect mc2
+
+}

+ 52 - 0
internal/app/mysql/index.go

@@ -0,0 +1,52 @@
+package mysql
+
+import (
+	"fmt"
+	"os"
+	"pbx-api-gin/internal/pkg/configs"
+	"pbx-api-gin/pkg/lfshook"
+	"syscall"
+
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/sirupsen/logrus"
+	"xorm.io/xorm"
+	"xorm.io/xorm/log"
+)
+
+var DBOrmInstance *xorm.Engine
+
+func CreateDBInstance() {
+	var err error
+	// DBOrmInstance, err = xorm.NewEngine("sqlite3", "playcall.db")
+	url := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8",
+		configs.ConfigGlobal.MysqlDBUser,
+		// "coovox_admin",
+		configs.ConfigGlobal.MysqlDBSecret,
+		// "ZycooCoovoxDba42",
+		configs.ConfigGlobal.MysqlDBHost,
+		configs.ConfigGlobal.MysqlDBName,
+	)
+	lfshook.NewLogger().Infof("mysql url %s", url)
+	DBOrmInstance, err = xorm.NewEngine("mysql", url)
+	if err != nil {
+		lfshook.NewLogger().Panic(err)
+		return
+	}
+	err = DBOrmInstance.Ping()
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		syscall.Kill(syscall.Getpid(), syscall.SIGINT)
+		return
+	}
+	//DBOrmInstance.ShowSQL(true)
+	if configs.ConfigGlobal.LogLevel >= logrus.DebugLevel {
+		DBOrmInstance.ShowSQL(true)
+	} else {
+		info, err := os.Open(configs.ConfigGlobal.LogInfoPath)
+		if err != nil {
+			println(err.Error())
+			return
+		}
+		DBOrmInstance.SetLogger(log.NewSimpleLogger(info))
+	}
+}

+ 245 - 0
internal/app/stc/index.go

@@ -0,0 +1,245 @@
+package stc
+
+import (
+	"bufio"
+	"bytes"
+	"encoding/binary"
+	"fmt"
+	"log"
+	"net"
+	"os"
+	stc "pbx-api-gin/internal/app/stc/operation"
+	"syscall"
+	"time"
+)
+
+// Protocol 定义协议数据结构
+type Protocol struct {
+	StartBytes    [3]byte // 协议开始符
+	SourceID      uint8   // 源设备号
+	DestinationID uint8   // 目的设备号
+	MessageID     uint8   // 消息号
+	DataLength    uint16  // 数据长度
+	Data          []byte  // 数据
+	Checksum      uint8   // 异或校验码
+	EndByte       uint8   // 协议结束符
+}
+
+// NewProtocol 创建一个新的 Protocol 实例
+func NewProtocol() *Protocol {
+	return &Protocol{
+		StartBytes: [3]byte{0x7F, 0x8E, 0x9D},
+		EndByte:    0xFE,
+	}
+
+}
+
+func ConnectStc(ServerAddr string, Port int) {
+
+	//=================================================
+	// connect server
+	conn := connectServer(ServerAddr, Port)
+	defer conn.Close()
+
+	//read msg
+	go func(conn net.Conn) {
+		readMsg(conn)
+	}(conn)
+
+	// send msg
+	sendMsg(conn)
+}
+
+func connectServer(ServerAddr string, Port int) net.Conn {
+	// connect server
+	conn, err := net.DialTCP("tcp", &net.TCPAddr{
+		IP:   net.ParseIP("0.0.0.0"),
+		Port: 0,
+	}, &net.TCPAddr{
+		IP:   net.ParseIP(ServerAddr),
+		Port: Port,
+	})
+	if err != nil {
+		fmt.Println("Error conn server:", err)
+		log.Fatal(err)
+	}
+
+	fileDesc, _ := conn.File()
+	fd := fileDesc.Fd()
+
+	syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
+	return conn
+}
+
+func Sendheartbeat(conn net.Conn) {
+	var count uint8
+	//init heartbeat data
+	protocol := NewProtocol()
+	protocol.SourceID = 0x02
+	protocol.DestinationID = 0x01
+	protocol.MessageID = 0x21
+	protocol.DataLength = 0x04
+
+	ticker := time.NewTicker(2 * time.Second)
+	defer ticker.Stop()
+	for range ticker.C {
+
+		count = count + 1
+		protocol.Data = []byte{count, 0x00, 0x00, 0x00}
+		encoded, errEn := protocol.Encode()
+		if errEn != nil {
+			fmt.Println("Encode error:", errEn)
+			return
+		}
+
+		_, err := conn.Write(encoded)
+		if err != nil {
+			fmt.Println("send heartbeat err:", err)
+			return
+		}
+	}
+}
+
+func readMsg(conn net.Conn) {
+
+	for {
+		buffer := make([]byte, 1024)
+		n, err := conn.Read(buffer)
+		if err != nil {
+			fmt.Println("Error reading from server:", err)
+			return
+		}
+		fmt.Println("Received from server:", string(buffer[:n]))
+
+		switch buffer[5] {
+		case 0x01: //heartbeat
+
+		case 0x02: //STN
+			stc.StationAnn([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+
+		case 0x03: //ACTIVE
+			stc.Active([1]byte{buffer[8]})
+
+		case 0x05: //SPC
+			stc.SpecialAnn([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+
+		case 0x06: //EMG
+			stc.EmgMsg([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+		case 0x07: //STOP
+			stc.AnnStop([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+		case 0x08: //DCS
+			stc.DcsAnn([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+		case 0x09: //SELF CHECK
+			stc.SelfCheck([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+		case 0x0a: //
+			stc.AlarmHandle([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+		case 0x0b: //
+			stc.AlarmResetAll([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+		case 0x0c: //
+			stc.RecordStorageConf([4]byte{buffer[8], buffer[9], buffer[10], buffer[11]})
+		}
+	}
+}
+
+func sendMsg(conn net.Conn) {
+
+	//heartbeat
+	go func(conn net.Conn) {
+		Sendheartbeat(conn)
+	}(conn)
+
+	scanner := bufio.NewScanner(os.Stdin)
+	for scanner.Scan() {
+		text := scanner.Text()
+		_, err := conn.Write([]byte(text + "\n"))
+		if err != nil {
+			fmt.Println("send msg err:", err)
+			return
+		}
+	}
+}
+
+// Encode 将 Protocol 结构体编码为字节切片
+func (p *Protocol) Encode() ([]byte, error) {
+	// 初始化字节缓冲区
+	var buf bytes.Buffer // 写入协议开始符
+	buf.Write(p.StartBytes[:])
+
+	// 写入源设备号、目的设备号和消息号
+	binary.Write(&buf, binary.BigEndian, p.SourceID)
+	binary.Write(&buf, binary.BigEndian, p.DestinationID)
+	binary.Write(&buf, binary.BigEndian, p.MessageID)
+
+	// 写入数据长度
+	binary.Write(&buf, binary.BigEndian, p.DataLength)
+
+	// 写入数据
+	buf.Write(p.Data[:])
+
+	// 计算校验码
+	checksum := p.CalculateChecksum()
+	p.Checksum = checksum // 写入校验码
+	binary.Write(&buf, binary.BigEndian, p.Checksum)
+
+	// 写入协议结束符
+	binary.Write(&buf, binary.BigEndian, p.EndByte)
+
+	return buf.Bytes(), nil
+}
+
+// CalculateChecksum 计算校验码
+func (p *Protocol) CalculateChecksum() uint8 {
+	// 初始化校验码
+	checksum := uint8(0)
+
+	// 跳过协议开始符
+	data := append([]byte{byte(p.SourceID), byte(p.DestinationID), byte(p.MessageID)},
+		append([]byte{byte(p.DataLength >> 8), byte(p.DataLength)}, p.Data...)...)
+
+	// 计算校验码
+	for _, b := range data {
+		checksum ^= b
+	}
+
+	return checksum
+}
+
+// Decode 解码字节切片为 Protocol 结构体
+func Decode(data []byte) (*Protocol, error) {
+	if len(data) < 10 { // 最小长度:3(StartBytes) +1(SourceID) +1(DestinationID) +1(MessageID) +2(DataLength) +1(Checksum) +1(EndByte)
+		return nil, fmt.Errorf("data too short")
+	}
+
+	p := NewProtocol()
+
+	// 读取协议开始符
+	copy(p.StartBytes[:], data[:3])
+
+	// 读取源设备号、目的设备号和消息号
+	p.SourceID = data[3]
+	p.DestinationID = data[4]
+	p.MessageID = data[5]
+
+	// 读取数据长度
+	p.DataLength = binary.BigEndian.Uint16(data[6:8])
+
+	// 读取数据
+	dataLength := int(p.DataLength)
+	if len(data) < 10+dataLength {
+		return nil, fmt.Errorf("data length mismatch")
+	}
+	p.Data = data[8 : 8+dataLength]
+
+	// 读取校验码
+	p.Checksum = data[8+dataLength]
+
+	// 读取协议结束符
+	p.EndByte = data[9+dataLength]
+
+	// 验证校验码
+	if p.Checksum != p.CalculateChecksum() {
+		return nil, fmt.Errorf("checksum mismatch")
+	}
+
+	return p, nil
+}

+ 145 - 0
internal/app/stc/operation/broadcast.go

@@ -0,0 +1,145 @@
+package stc
+
+import (
+	"bytes"
+	"pbx-api-gin/api/model"
+	"pbx-api-gin/api/panel/asterisk"
+	"pbx-api-gin/internal/app/ami/action"
+	"pbx-api-gin/internal/app/mysql"
+	"pbx-api-gin/pkg/lfshook"
+)
+
+func StationAnn(data [4]byte) (err error) {
+	specialVoice := int(data[0])
+	Exten := "2411"
+	delay := data[1]
+	cycleCount := data[2]
+	timeLen := data[3]
+
+	filename := "welcome"
+
+	//update special voice
+	_, er := mysql.DBOrmInstance.Where("exten = ?", Exten).Update(&model.Extension{Special: specialVoice})
+	if err != nil {
+		lfshook.NewLogger().Logger.Infof("update special voice to exten err : %+v", er.Error())
+		return er
+	}
+
+	action.Playback(filename, int(cycleCount), Exten, int(timeLen), int(delay))
+	return nil
+}
+
+func Active(data [1]byte) {
+
+	Num := int(data[0])
+
+	switch Num { // 设置全局的激活信号,并通过协议(待定)通知终端注册到对应的激活主机上
+	case 0:
+	case 1:
+	case 8:
+	case 9:
+	}
+
+}
+
+func SpecialAnn(data [4]byte) {
+	//specialVoice := int(data[0])
+	Exten := "2411"
+	delay := data[0]
+	cycleCount := data[1]
+	timeLen := data[2]
+
+	filename := "welcome"
+
+	if int(cycleCount) == 255 {
+		action.Playback(filename, 9999999, Exten, int(timeLen), int(delay))
+	} else {
+		action.Playback(filename, int(cycleCount), Exten, int(timeLen), int(delay))
+	}
+
+}
+
+func EmgMsg(data [4]byte) {
+	//specialVoice := int(data[0])
+	Exten := "2411"
+	delay := data[0]
+	cycleCount := data[1]
+	timeLen := data[2]
+
+	filename := "welcome"
+
+	if int(cycleCount) == 255 {
+		action.Playback(filename, 9999999, Exten, int(timeLen), int(delay))
+	} else {
+		action.Playback(filename, int(cycleCount), Exten, int(timeLen), int(delay))
+	}
+}
+
+func AnnStop(data [4]byte) {
+	if data[0] == 0x03 {
+		asterisk.Hangup("2412")
+	} else if data[0] == 0x04 {
+		asterisk.Hangup("2412")
+	} else if data[0] == 0x07 {
+		asterisk.Hangup("2412")
+	} else if data[0] == 0x08 {
+		asterisk.Hangup("2412")
+	} else if data[0] == 0x09 {
+		asterisk.Hangup("2412")
+	}
+}
+
+func DcsAnn(data [4]byte) {
+	Exten := "2411"
+	delay := data[0]
+	cycleCount := data[1]
+	timeLen := data[2]
+
+	filename := "welcome"
+
+	if cycleCount == 255 {
+		action.Playback(filename, 9999999, Exten, int(timeLen), int(delay))
+	} else {
+		action.Playback(filename, int(cycleCount), Exten, int(timeLen), int(delay))
+	}
+}
+
+func SelfCheck(data [4]byte) {
+	check := data[0]
+	delay := data[1]
+	cycleCount := data[2]
+	timeLen := data[3]
+	filename := "welcome"
+	Exten := ""
+
+	if check == 0x01 {
+		action.Playback(filename, int(cycleCount), Exten, int(timeLen), int(delay))
+	} else if check == 0x02 {
+		asterisk.Hangup(Exten)
+	}
+}
+
+func AlarmHandle(data [4]byte) {
+	handler := data[0]
+	//len := data[1]
+	//src := data[2]
+
+	if handler == 0x01 { //answer
+
+	} else if handler == 0x02 { //hold
+
+	} else if handler == 0x03 { //hangup
+
+	}
+}
+
+func AlarmResetAll(data [4]byte) {
+
+	if bytes.Equal(data[:], make([]byte, len(data))) {
+		//reset all the alarm
+	}
+}
+
+func RecordStorageConf(data [4]byte) {
+
+}

+ 89 - 0
internal/pkg/configs/decode.go

@@ -0,0 +1,89 @@
+package configs
+
+import (
+	"io/ioutil"
+	"os"
+	"pbx-api-gin/pkg/lfshook"
+
+	log "github.com/sirupsen/logrus"
+
+	"gopkg.in/yaml.v2"
+)
+
+// Config https://godoc.org/gopkg.in/yaml.v2
+// Config 存储配置
+type Config struct {
+	IdentityKey string `yaml:"identityKey"`
+
+	AsteriskAMIHost   string `yaml:"asteriskAMIHost"`   // Host
+	AsteriskAMIPort   string `yaml:"asteriskAMIPort"`   // Port
+	AsteriskAMIUser   string `yaml:"asteriskAMIUser"`   // User
+	AsteriskAMISecret string `yaml:"asteriskAMISecret"` // Secret
+
+	AsteriskAGIPort string `yaml:"asteriskAGIPort"` // AGIPort
+
+	//AsteriskTriggerPath      string `yaml:"asteriskTriggerPath"`      // 广播触发规则
+	//AsteriskPagingPath       string `yaml:"asteriskPagingPath"`       // 广播规则
+	//AsteriskBroadcastTimeout int64  `yaml:"asteriskBroadcastTimeout"` // 广播响铃时长
+	//AsteriskBroadcastName    string `yaml:"asteriskBroadcastName"`    // 广播时名称
+	//AsteriskBroadcastID      string `yaml:"asteriskBroadcastID"`      // 广播时ID
+
+	MysqlDBHost   string `yaml:"mysqlDBHost"`   // Host
+	MysqlDBUser   string `yaml:"mysqlDBUser"`   // User
+	MysqlDBSecret string `yaml:"mysqlDBSecret"` // Secret
+	MysqlDBName   string `yaml:"mysqlDBName"`   // Name
+
+	//RedisDBHost   string `yaml:"redisDBHost"`   // Host
+	//RedisDBPort   string `yaml:"redisDBPort"`   // User
+	//RedisDBSecret string `yaml:"redisDBSecret"` // Secret
+
+	LogInfoPath  string `yaml:"logInfoPath"`  //logInfoPath
+	LogErrorPath string `yaml:"logErrorPath"` //logErrorPath
+
+	//StoragePath string `yaml:"storagePath"` // 存储目录
+
+	//AllEventPushUrl string `yaml:"allEventPushUrl"`
+
+	//WebHost string `yaml:"webhost"` //Host
+	//WebPort int64  `yaml:"webport"` //port
+
+	AllowOrigin string `yaml:"allowOrigin"` // allowOrigin
+
+	LogLevel log.Level //logLevel
+}
+
+// ConfigPath 配置文件路径
+var ConfigPath = "./config.yaml"
+
+// ConfigGlobal 全局配置变量
+var ConfigGlobal *Config
+
+// DecodeConfig 解析配置
+func DecodeConfig() {
+	_, err := os.Stat(ConfigPath)
+	if err != nil {
+		lfshook.NewLogger().Errorf("config file not exist %+v", err)
+		return
+	}
+
+	fileByte, err := ioutil.ReadFile(ConfigPath)
+	if err != nil {
+		lfshook.NewLogger().Errorf("read config file %+v", err)
+	}
+
+	ConfigGlobal = &Config{}
+	err = yaml.Unmarshal(fileByte, ConfigGlobal)
+	if err != nil {
+		lfshook.NewLogger().Errorf("Unmarshal config file %+v", err)
+	}
+}
+
+// EncodeConfig 保存配置
+func EncodeConfig() error {
+	out, _ := yaml.Marshal(ConfigGlobal)
+	err := ioutil.WriteFile(ConfigPath, out, 0777)
+	if err != nil {
+		lfshook.NewLogger().Errorf("save config file %+v", err)
+	}
+	return err
+}

+ 218 - 0
pkg/lfshook/log.go

@@ -0,0 +1,218 @@
+// Package lfshook is hook for sirupsen/logrus that used for writing the logs to local files.
+package lfshook
+
+import (
+	"fmt"
+	"io"
+	"reflect"
+	"sync"
+
+	"github.com/sirupsen/logrus"
+	"gopkg.in/natefinch/lumberjack.v2"
+)
+
+// We are logging to file, strip colors to make the output more readable.
+var defaultFormatter = &logrus.TextFormatter{DisableColors: true}
+
+// PathMap is map for mapping a log level to a file's path.
+// Multiple levels may share a file, but multiple files may not be used for one level.
+// type PathMap map[logrus.Level]string
+
+// 一个 level 对应 一个 logger
+type LoggerMap map[logrus.Level]*lumberjack.Logger
+
+// WriterMap is map for mapping a log level to an io.Writer.
+// Multiple levels may share a writer, but multiple writers may not be used for one level.
+type WriterMap map[logrus.Level]io.Writer
+
+type RotateFileConfig struct {
+	Filename   string
+	MaxSize    int
+	MaxBackups int
+	MaxAge     int
+	Level      logrus.Level
+	Formatter  logrus.Formatter
+}
+
+// LfsHook is a hook to handle writing to local log files.
+type LfsHook struct {
+	loggers   LoggerMap
+	writers   WriterMap
+	levels    []logrus.Level
+	lock      *sync.Mutex
+	formatter logrus.Formatter
+
+	Config RotateFileConfig
+
+	defaultPath      string
+	defaultWriter    io.Writer
+	hasDefaultPath   bool
+	hasDefaultWriter bool
+}
+
+// NewHook returns new LFS hook.
+// Output can be a string, io.Writer, WriterMap or PathMap.
+// If using io.Writer or WriterMap, user is responsible for closing the used io.Writer.
+func NewHook(output interface{}, formatter logrus.Formatter) *LfsHook {
+	hook := &LfsHook{
+		lock: new(sync.Mutex),
+	}
+
+	hook.SetFormatter(formatter)
+
+	switch output.(type) {
+	case string:
+		hook.SetDefaultPath(output.(string))
+		break
+	case io.Writer:
+		hook.SetDefaultWriter(output.(io.Writer))
+		break
+	case LoggerMap:
+		hook.loggers = output.(LoggerMap)
+		for level := range output.(LoggerMap) {
+			hook.levels = append(hook.levels, level)
+		}
+		break
+	case WriterMap:
+		hook.writers = output.(WriterMap)
+		for level := range output.(WriterMap) {
+			hook.levels = append(hook.levels, level)
+		}
+		break
+	default:
+		panic(fmt.Sprintf("unsupported level map type: %v", reflect.TypeOf(output)))
+	}
+
+	return hook
+}
+
+// SetFormatter sets the format that will be used by hook.
+// If using text formatter, this method will disable color output to make the log file more readable.
+func (hook *LfsHook) SetFormatter(formatter logrus.Formatter) {
+	hook.lock.Lock()
+	defer hook.lock.Unlock()
+	if formatter == nil {
+		formatter = defaultFormatter
+	} else {
+		switch formatter.(type) {
+		case *logrus.TextFormatter:
+			textFormatter := formatter.(*logrus.TextFormatter)
+			textFormatter.DisableColors = true
+		}
+	}
+
+	hook.formatter = formatter
+}
+
+// SetDefaultPath sets default path for levels that don't have any defined output path.
+func (hook *LfsHook) SetDefaultPath(defaultPath string) {
+	hook.lock.Lock()
+	defer hook.lock.Unlock()
+	hook.defaultPath = defaultPath
+	hook.hasDefaultPath = true
+}
+
+// SetDefaultWriter sets default writer for levels that don't have any defined writer.
+func (hook *LfsHook) SetDefaultWriter(defaultWriter io.Writer) {
+	hook.lock.Lock()
+	defer hook.lock.Unlock()
+	hook.defaultWriter = defaultWriter
+	hook.hasDefaultWriter = true
+}
+
+// Fire writes the log file to defined path or using the defined writer.
+// User who run this function needs write permissions to the file or directory if the file does not yet exist.
+func (hook *LfsHook) Fire(entry *logrus.Entry) error {
+	hook.lock.Lock()
+	defer hook.lock.Unlock()
+	if hook.writers != nil || hook.hasDefaultWriter {
+		return hook.ioWrite(entry)
+	} else if hook.loggers != nil || hook.hasDefaultPath {
+		return hook.fileWrite(entry)
+	}
+
+	return nil
+}
+
+// Write a log line to an io.Writer.
+func (hook *LfsHook) ioWrite(entry *logrus.Entry) error {
+	var (
+		writer io.Writer
+		msg    []byte
+		err    error
+		ok     bool
+	)
+
+	if writer, ok = hook.writers[entry.Level]; !ok {
+		if hook.hasDefaultWriter {
+			writer = hook.defaultWriter
+		} else {
+			return nil
+		}
+	}
+
+	// use our formatter instead of entry.String()
+	msg, err = hook.formatter.Format(entry)
+
+	if err != nil {
+		log.Println("failed to generate string for entry:", err)
+		return err
+	}
+	_, err = writer.Write(msg)
+	return err
+}
+
+// Write a log line directly to a file.
+func (hook *LfsHook) fileWrite(entry *logrus.Entry) error {
+	var (
+		logger *lumberjack.Logger
+		msg    []byte
+		err    error
+		ok     bool
+	)
+
+	if logger, ok = hook.loggers[entry.Level]; !ok {
+		if hook.hasDefaultPath {
+			logger = &lumberjack.Logger{
+				Filename:   hook.defaultPath,
+				MaxSize:    10, // maxSize M
+				MaxBackups: 5,  // keep 5 file
+				MaxAge:     7,  //  7 day
+			}
+		} else {
+			return nil
+		}
+	}
+
+	// use our formatter instead of entry.String()
+	msg, err = hook.formatter.Format(entry)
+
+	if err != nil {
+		log.Println("failed to generate string for entry:", err)
+		return err
+	}
+
+	defer logger.Close()
+	logger.Write(msg)
+	return nil
+}
+
+// Levels returns configured log levels.
+func (hook *LfsHook) Levels() []logrus.Level {
+	return logrus.AllLevels
+}
+
+var log *logrus.Entry
+var once sync.Once
+
+//NewLogger 初始化 logger
+func NewLogger() *logrus.Entry {
+	once.Do(func() {
+		log = logrus.New().WithFields(
+			logrus.Fields{
+				"appname": "pbx-panel",
+			},
+		)
+	})
+	return log
+}

+ 57 - 0
pkg/systeminfo/file.go

@@ -0,0 +1,57 @@
+// Copyright 2017 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package systeminfo
+
+import (
+	"fmt"
+	"math"
+	"net/http"
+	"strings"
+)
+
+// IsTextFile returns true if file content format is plain text or empty.
+func IsTextFile(data []byte) bool {
+	if len(data) == 0 {
+		return true
+	}
+	return strings.Contains(http.DetectContentType(data), "text/")
+}
+
+func IsImageFile(data []byte) bool {
+	return strings.Contains(http.DetectContentType(data), "image/")
+}
+
+func IsPDFFile(data []byte) bool {
+	return strings.Contains(http.DetectContentType(data), "application/pdf")
+}
+
+func IsVideoFile(data []byte) bool {
+	return strings.Contains(http.DetectContentType(data), "video/")
+}
+
+func logn(n, b float64) float64 {
+	return math.Log(n) / math.Log(b)
+}
+
+func humanateBytes(s uint64, base float64, sizes []string) string {
+	if s < 10 {
+		return fmt.Sprintf("%d B", s)
+	}
+	e := math.Floor(logn(float64(s), base))
+	suffix := sizes[int(e)]
+	val := float64(s) / math.Pow(base, math.Floor(e))
+	f := "%.0f"
+	if val < 10 {
+		f = "%.1f"
+	}
+
+	return fmt.Sprintf(f+" %s", val, suffix)
+}
+
+// FileSize calculates the file size and generate user-friendly string.
+func FileSize(s int64) string {
+	sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
+	return humanateBytes(uint64(s), 1024, sizes)
+}

+ 90 - 0
pkg/systeminfo/index.go

@@ -0,0 +1,90 @@
+package systeminfo
+
+import (
+	"fmt"
+	"runtime"
+	"time"
+)
+
+var initTime = time.Now()
+
+type SystemInfo struct {
+	Uptime       string
+	NumGoroutine int
+
+	// General statistics.
+	MemAllocated string // bytes allocated and still in use
+	MemTotal     string // bytes allocated (even if freed)
+	MemSys       string // bytes obtained from system (sum of XxxSys below)
+	Lookups      uint64 // number of pointer lookups
+	MemMallocs   uint64 // number of mallocs
+	MemFrees     uint64 // number of frees
+
+	// Main allocation heap statistics.
+	HeapAlloc    string // bytes allocated and still in use
+	HeapSys      string // bytes obtained from system
+	HeapIdle     string // bytes in idle spans
+	HeapInuse    string // bytes in non-idle span
+	HeapReleased string // bytes released to the OS
+	HeapObjects  uint64 // total number of allocated objects
+
+	// Low-level fixed-size structure allocator statistics.
+	//	Inuse is bytes used now.
+	//	Sys is bytes obtained from system.
+	StackInuse  string // bootstrap stacks
+	StackSys    string
+	MSpanInuse  string // mspan structures
+	MSpanSys    string
+	MCacheInuse string // mcache structures
+	MCacheSys   string
+	BuckHashSys string // profiling bucket hash table
+	GCSys       string // GC metadata
+	OtherSys    string // other system allocations
+
+	// Garbage collector statistics.
+	NextGC       string // next run in HeapAlloc time (bytes)
+	LastGC       string // last run in absolute time (ns)
+	PauseTotalNs string
+	PauseNs      string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
+	NumGC        uint32
+}
+
+func GetSystemInfo() (sysStatus *SystemInfo) {
+	sysStatus = &SystemInfo{}
+	sysStatus.Uptime = TimeSincePro(initTime)
+
+	m := new(runtime.MemStats)
+	runtime.ReadMemStats(m)
+	sysStatus.NumGoroutine = runtime.NumGoroutine()
+
+	sysStatus.MemAllocated = FileSize(int64(m.Alloc))
+	sysStatus.MemTotal = FileSize(int64(m.TotalAlloc))
+	sysStatus.MemSys = FileSize(int64(m.Sys))
+	sysStatus.Lookups = m.Lookups
+	sysStatus.MemMallocs = m.Mallocs
+	sysStatus.MemFrees = m.Frees
+
+	sysStatus.HeapAlloc = FileSize(int64(m.HeapAlloc))
+	sysStatus.HeapSys = FileSize(int64(m.HeapSys))
+	sysStatus.HeapIdle = FileSize(int64(m.HeapIdle))
+	sysStatus.HeapInuse = FileSize(int64(m.HeapInuse))
+	sysStatus.HeapReleased = FileSize(int64(m.HeapReleased))
+	sysStatus.HeapObjects = m.HeapObjects
+
+	sysStatus.StackInuse = FileSize(int64(m.StackInuse))
+	sysStatus.StackSys = FileSize(int64(m.StackSys))
+	sysStatus.MSpanInuse = FileSize(int64(m.MSpanInuse))
+	sysStatus.MSpanSys = FileSize(int64(m.MSpanSys))
+	sysStatus.MCacheInuse = FileSize(int64(m.MCacheInuse))
+	sysStatus.MCacheSys = FileSize(int64(m.MCacheSys))
+	sysStatus.BuckHashSys = FileSize(int64(m.BuckHashSys))
+	sysStatus.GCSys = FileSize(int64(m.GCSys))
+	sysStatus.OtherSys = FileSize(int64(m.OtherSys))
+
+	sysStatus.NextGC = FileSize(int64(m.NextGC))
+	sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000)
+	sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
+	sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
+	sysStatus.NumGC = m.NumGC
+	return
+}

+ 96 - 0
pkg/systeminfo/time.go

@@ -0,0 +1,96 @@
+package systeminfo
+
+import (
+	"fmt"
+	"strings"
+	"time"
+)
+
+// Seconds-based time units
+const (
+	Minute = 60
+	Hour   = 60 * Minute
+	Day    = 24 * Hour
+	Week   = 7 * Day
+	Month  = 30 * Day
+	Year   = 12 * Month
+)
+
+func computeTimeDiff(diff int64) (int64, string) {
+	diffStr := ""
+	switch {
+	case diff <= 0:
+		diff = 0
+		diffStr = "now"
+	case diff < 2:
+		diff = 0
+		diffStr = "1 second"
+	case diff < 1*Minute:
+		diffStr = fmt.Sprintf("%d seconds", diff)
+		diff = 0
+
+	case diff < 2*Minute:
+		diff -= 1 * Minute
+		diffStr = "1 minute"
+	case diff < 1*Hour:
+		diffStr = fmt.Sprintf("%d minutes", diff/Minute)
+		diff -= diff / Minute * Minute
+
+	case diff < 2*Hour:
+		diff -= 1 * Hour
+		diffStr = "1 hour"
+	case diff < 1*Day:
+		diffStr = fmt.Sprintf("%d hours", diff/Hour)
+		diff -= diff / Hour * Hour
+
+	case diff < 2*Day:
+		diff -= 1 * Day
+		diffStr = "1 day"
+	case diff < 1*Week:
+		diffStr = fmt.Sprintf("%d days", diff/Day)
+		diff -= diff / Day * Day
+
+	case diff < 2*Week:
+		diff -= 1 * Week
+		diffStr = "1 week"
+	case diff < 1*Month:
+		diffStr = fmt.Sprintf("%d weeks", diff/Week)
+		diff -= diff / Week * Week
+
+	case diff < 2*Month:
+		diff -= 1 * Month
+		diffStr = "1 month"
+	case diff < 1*Year:
+		diffStr = fmt.Sprintf("%d months", diff/Month)
+		diff -= diff / Month * Month
+
+	case diff < 2*Year:
+		diff -= 1 * Year
+		diffStr = "1 year"
+	default:
+		diffStr = fmt.Sprintf("%d years", diff/Year)
+		diff = 0
+	}
+	return diff, diffStr
+}
+
+// TimeSincePro calculates the time interval and generate full user-friendly string.
+func TimeSincePro(then time.Time) string {
+	now := time.Now()
+	diff := now.Unix() - then.Unix()
+
+	if then.After(now) {
+		return "future"
+	}
+
+	var timeStr, diffStr string
+	for {
+		if diff == 0 {
+			break
+		}
+
+		diff, diffStr = computeTimeDiff(diff)
+		timeStr += ", " + diffStr
+	}
+	return strings.TrimPrefix(timeStr, ", ")
+}

+ 69 - 0
pkg/utils/cmd.go

@@ -0,0 +1,69 @@
+package utils
+
+import (
+	"bytes"
+	"context"
+	"os/exec"
+	"pbx-api-gin/pkg/lfshook"
+	"time"
+)
+
+//ExecCmdAsync 执行指定命令
+func ExecCmdAsync(cmdName string, arg ...string) (stdOut, errOut string, err error) {
+	cmd := exec.Command(cmdName, arg...)
+	var stdout, stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	err = cmd.Run()
+	if err != nil {
+		lfshook.NewLogger().Errorf("cmd.Run(%s) failed with %s\n", cmdName, err)
+	}
+	outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
+	if len(outStr) > 0 {
+		lfshook.NewLogger().Debugf("cmd.Run(%s) %s", cmdName, outStr)
+	}
+	if len(errStr) > 0 {
+		lfshook.NewLogger().Errorf("cmd.Run(%s)%s", cmdName, errStr)
+	}
+	return outStr, errStr, err
+}
+
+//ExecCmd 执行指定命令
+func ExecCmd(cmdName string, arg ...string) (stdOut, errOut string, err error) {
+	cmd := exec.Command(cmdName, arg...)
+	var stdout, stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	err = cmd.Start()
+	if err != nil {
+		lfshook.NewLogger().Error(err)
+		return "", "", err
+	}
+
+	go func() {
+		err = cmd.Wait()
+		if err != nil {
+			lfshook.NewLogger().Errorf("cmd.Wait(%s) %s\n", cmdName, err)
+		} else {
+			lfshook.NewLogger().Info("cmd.Wait(%s)\n", cmdName)
+		}
+		cancel()
+	}()
+
+	select {
+	case <-ctx.Done():
+	}
+
+	outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
+	if len(outStr) > 0 {
+		lfshook.NewLogger().Debugf("cmd.Start(%s) %s", cmdName, outStr)
+	}
+	if len(errStr) > 0 {
+		lfshook.NewLogger().Errorf("cmd.Start(%s)%s", cmdName, errStr)
+	}
+	return outStr, errStr, err
+}

+ 17 - 0
pkg/utils/common.go

@@ -0,0 +1,17 @@
+package utils
+
+import "fmt"
+
+var DialPrefix = "PJSIP"
+
+type Version struct {
+	GitCommitCode string `json:"gitCommitcode"`
+	GoVersion     string `json:"goVersion"`
+	BuildDate     string `json:"buildDate"`
+}
+
+func (version *Version) String() string {
+	return fmt.Sprintf("git commit code: %s\nbuild date: %s\ngo version: %s", version.GitCommitCode, version.BuildDate, version.GoVersion)
+}
+
+var VersionInstance = &Version{}

+ 49 - 0
pkg/utils/cron.go

@@ -0,0 +1,49 @@
+package utils
+
+import (
+	"fmt"
+	"pbx-api-gin/pkg/lfshook"
+	"strings"
+)
+
+func GenCronRule(mode, timeStr string, weeks []string) string {
+	switch mode {
+	case "once":
+		return genOnceCronRule(timeStr)
+	case "loop":
+		return genWeekCronRule(timeStr, weeks)
+	}
+	return ""
+}
+
+//genOnceCronRule
+func genOnceCronRule(timeStr string) string {
+	hourMinute := strings.Split(timeStr, ":")
+	if len(hourMinute) != 2 {
+		lfshook.NewLogger().Errorf("error timeStr %s", timeStr)
+		return ""
+	}
+	return fmt.Sprintf("%s %s * * *", hourMinute[1], hourMinute[0])
+}
+
+//GenOnceCronRule
+func genWeekCronRule(timeStr string, weeks []string) string {
+	hourMinute := strings.Split(timeStr, ":")
+	if len(weeks) == 0 {
+		return fmt.Sprintf("%s %s * * *", hourMinute[1], hourMinute[0])
+	}
+	return fmt.Sprintf("%s %s * * %s", hourMinute[1], hourMinute[0], strings.Join(weeks, ","))
+}
+
+//FkZero 格式化数据,去掉多余的0
+func FkZero(times string) (fmttime string) {
+	if string(times[0]) != "0" {
+		return times
+	} else if strings.Split(times, "0")[1] == "" {
+		fkzero := "0"
+		return fkzero
+	} else {
+		fkzero := strings.Split(times, "0")[1]
+		return fkzero
+	}
+}

+ 22 - 0
pkg/utils/exit.go

@@ -0,0 +1,22 @@
+package utils
+
+import (
+	"os"
+	"os/signal"
+	"pbx-api-gin/pkg/lfshook"
+	"syscall"
+)
+
+func Exit() {
+	sigs := make(chan os.Signal, 1)
+	done := make(chan bool, 1)
+	// syscall.SIGSTOP windows not support
+	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
+	go func() {
+		<-sigs
+		done <- true
+	}()
+	lfshook.NewLogger().Info("Server Start Awaiting Signal")
+	<-done
+	lfshook.NewLogger().Info("Exiting")
+}

+ 42 - 0
pkg/utils/file.go

@@ -0,0 +1,42 @@
+package utils
+
+import (
+	"os"
+
+	"github.com/sirupsen/logrus"
+	"gopkg.in/ini.v1"
+)
+
+// 缓存 timeZone 信息
+var timeZoneCache string
+
+// FileExists checks if a file exists and is not a directory before we
+// try using it to prevent further errors.
+func FileExists(filename string) bool {
+	info, err := os.Stat(filename)
+	if os.IsNotExist(err) {
+		return false
+	}
+	return !info.IsDir()
+}
+
+// pbx 不能正确读取时区, 读取自定义文件  /etc/asterisk/ntp.conf
+func GetLocationName() string {
+	if timeZoneCache != "" {
+		return timeZoneCache
+	}
+	filePath := "/etc/asterisk/ntp.conf"
+	_, err := os.Stat(filePath)
+	if err != nil {
+		logrus.Error(err)
+		return ""
+	}
+	iniFile, err := ini.Load(filePath)
+	if err != nil {
+		logrus.Error(err)
+		return ""
+	}
+	timeZoneCache = iniFile.Section("ntp").Key("TZNAME").Value()
+	logrus.Infof("use location %s", timeZoneCache)
+	return timeZoneCache
+}

+ 24 - 0
pkg/utils/ini.go

@@ -0,0 +1,24 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+
+	"gopkg.in/ini.v1"
+)
+
+var conf *ini.File
+
+//Conf 配置转 ini
+func Conf(path string) *ini.File {
+	if conf != nil {
+		return conf
+	}
+	var err error
+	conf, err = ini.Load(path)
+	if err != nil {
+		fmt.Printf("Fail to read file: %v", err)
+		os.Exit(1)
+	}
+	return conf
+}

+ 18 - 0
pkg/utils/time.go

@@ -0,0 +1,18 @@
+package utils
+
+import (
+	"strconv"
+	"strings"
+)
+
+//TimeStringToSecond 00:01:01 => 61s
+func TimeStringToSecond(input string) int {
+	times := strings.Split(input, ":")
+	timeHour := times[0]
+	timeMin := times[1]
+	timeSecond := times[2]
+	timeHourtInt, _ := strconv.Atoi(timeHour)
+	timeMinInt, _ := strconv.Atoi(timeMin)
+	timeSecondInt, _ := strconv.Atoi(timeSecond)
+	return timeHourtInt*3600 + timeMinInt*60 + timeSecondInt
+}

+ 63 - 0
pkg/utils/trans.go

@@ -0,0 +1,63 @@
+package utils
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+func ReflectTrans(obj interface{}) {
+	v := reflect.ValueOf(obj).Elem()
+	t := v.Type()
+
+	data := make(map[string]string)
+
+	for k := 0; k < t.NumField(); k++ {
+		if t.Field(k).Type.Kind() == reflect.Bool {
+			key := fmt.Sprintf("%sStr", t.Field(k).Name)
+			value := "no"
+			if v.Field(k).Interface().(bool) {
+				value = "yes"
+			}
+			data[key] = value
+		}
+	}
+
+	for k := 0; k < t.NumField(); k++ {
+		if value, ok := data[t.Field(k).Name]; ok {
+			v.Field(k).SetString(value)
+			if v.Field(k).CanSet() {
+				v.Field(k).SetString(value)
+			}
+		}
+	}
+}
+
+func MapTrans(obj interface{}) {
+	v := reflect.ValueOf(obj).Elem()
+	t := v.Type()
+
+	data := make(map[string]bool)
+
+	for k := 0; k < t.NumField(); k++ {
+		if v.Field(k).Interface() == "yes" || v.Field(k).Interface() == "no" {
+			key := strings.TrimSuffix(t.Field(k).Name, "Str")
+			var value bool
+			if v.Field(k).Interface().(string) == "yes" {
+				value = true
+			} else {
+				value = false
+			}
+			data[key] = value
+		}
+	}
+
+	for k := 0; k < t.NumField(); k++ {
+		if value, ok := data[t.Field(k).Name]; ok {
+			v.Field(k).SetBool(value)
+			if v.Field(k).CanSet() {
+				v.Field(k).SetBool(value)
+			}
+		}
+	}
+}

+ 130 - 0
pkg/utils/utils.go

@@ -0,0 +1,130 @@
+package utils
+
+import (
+	"flag"
+	"fmt"
+	"math/rand"
+	"os"
+	"strings"
+	"time"
+)
+
+// IsFlagPassed 判断是否设置了指定 flag
+func IsFlagPassed(name string) bool {
+	found := false
+	flag.Visit(func(f *flag.Flag) {
+		if f.Name == name {
+			found = true
+		}
+	})
+	return found
+}
+
+// NumberOrStringToBool 字符串 1, yes 返回 true 否则 false
+func NumberOrStringToBool(input string) bool {
+	if input == "1" {
+		return true
+	}
+	if input == "yes" {
+		return true
+	}
+	return false
+}
+
+// NumberToBool 大于0 返回 true 否则 false
+func NumberToBool(input int64) bool {
+	if input > 0 {
+		return true
+	} else {
+		return false
+	}
+}
+
+// Bool to int true 返回 1 否则 0
+func BoolToInt(input bool) int64 {
+	var output int64
+	if input {
+		output = 1
+	} else {
+		output = 0
+	}
+	return output
+}
+
+// BoolToNumber true 返回 "1" false 返回  "0"
+func BoolToNumber(input bool) string {
+	if input {
+		return "1"
+	}
+	return "0"
+}
+
+// BoolToString true 返回 "yes" false 返回  "no"
+func BoolToString(input bool) string {
+	if input {
+		return "yes"
+	}
+	return "no"
+}
+
+// YesToOn  Yes 或 yes 返回 "on" 否则返回 "off"
+// 会议 Muted 的值可能是 By admin
+func YesToOn(input string) string {
+	if input == "Yes" || input == "yes" || strings.Contains(input, "By") {
+		return "on"
+	}
+	return "off"
+}
+
+// GetRandStr 生成随机密码
+func GetRandStr(baseStr string, length int) string {
+	r := rand.New(rand.NewSource(time.Now().UnixNano() + rand.Int63()))
+	bytes := make([]byte, length)
+	l := len(baseStr)
+	for i := 0; i < length; i++ {
+		bytes[i] = baseStr[r.Intn(l)]
+	}
+	return string(bytes)
+}
+
+// SqlLike sql like 头尾添加 %%
+func SqlLike(input string) string {
+	return fmt.Sprintf("%%%s%%", input)
+}
+
+// IsChannel 判断输入是否是 Channel
+func IsChannel(input string) bool {
+	if strings.Contains(input, "SIP") {
+		return true
+	}
+
+	if strings.Contains(input, "Local") {
+		return true
+	}
+
+	if strings.Contains(input, "DAHDI") {
+		return true
+	}
+	return false
+}
+
+// 判断文件是否存在
+func PathExists(path string) bool {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true
+	}
+	if os.IsNotExist(err) {
+		return false
+	}
+	return false
+}
+
+func IndexOf(sliceID []string, value string) int {
+	for i, v := range sliceID {
+		if v == value {
+			return i
+		}
+	}
+	return -1
+}

+ 20 - 0
pkg/utils/uuid.go

@@ -0,0 +1,20 @@
+package utils
+
+import (
+	"crypto/rand"
+	"fmt"
+)
+
+//NewV4 Returns a new uuid v4
+func NewV4() string {
+	u := [16]byte{}
+	_, err := rand.Read(u[:16])
+	if err != nil {
+		panic(err)
+	}
+
+	u[8] = (u[8] | 0x80) & 0xBf
+	u[6] = (u[6] | 0x40) & 0x4f
+
+	return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
+}

+ 31 - 0
test/config.yaml

@@ -0,0 +1,31 @@
+identityKey: 'zycoozycoo' # jwt secret
+
+ttsPath: './msc/res/tts' #tts 路径
+googleJsonKeyFile: '/google.json' #google key file
+savePath: '/tmp' #存储路径
+
+# AMI
+asteriskAMIHost: '127.0.0.1'
+asteriskAMIPort: '5038'
+asteriskAMIUser: 'admin'
+asteriskAMISecret: 'admin'
+
+# AGI
+asteriskAGIPort: '18080'
+
+# 数据库配置
+mysqlDBHost: '127.0.0.1'
+mysqlDBUser: 'coovox_admin'
+mysqlDBSecret: 'ZycooCoovoxDba42'
+
+# Redis
+redisDBHost: 'localhost'
+redisDBPort: '6379'
+redisDBSecret: ''
+
+# log
+logErrorPath: './error.log'
+logInfoPath: './info.log'
+
+# music upload
+storagePath: '/tmp'

+ 35 - 0
test/info_test.go

@@ -0,0 +1,35 @@
+package test
+
+import (
+	"pbx-api-gin/api/admin/extensioncommon"
+	"pbx-api-gin/api/admin/rdepartment"
+	"pbx-api-gin/internal/app/mysql"
+	"testing"
+
+	"github.com/gin-gonic/gin"
+)
+
+func TestServer(t *testing.T) {
+	// t.Run("测试AdminExtensionRangeInfo函数获取部门号码取值范围", testinfo)
+	t.Run("测试AdminExtensionRangeInfo函数获取部门号码取值范围", testdepartment)
+}
+
+//testinfo 测试获取部门号码
+func testinfo(t *testing.T) {
+
+	mysql.CreateDBInstance()
+	r := gin.Default()
+	r.POST("/admin/extension-common/extension-range-info", extensioncommon.AdminExtensionRangeInfo)
+	r.Run(":51873")
+
+}
+
+//testdepartment 测试获取部门数据和部门成员信息
+func testdepartment(t *testing.T) {
+	mysql.CreateDBInstance()
+	r := gin.Default()
+	// r.GET("/r-department/list-department-info", rdepartment.AdminListDepartmentInfo)
+	// r.POST("/r-department/add-department", rdepartment.AdminAddDepartment)
+	r.POST("/r-department/update-department", rdepartment.AdminUpdateDepartment)
+	r.Run(":51873")
+}

+ 39 - 0
test/ini.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"gopkg.in/ini.v1"
+)
+
+func main() {
+	ini.PrettyFormat = false
+	cfg, err := ini.LoadSources(ini.LoadOptions{
+		SpaceBeforeInlineComment: false,
+		IgnoreInlineComment:      true,
+	}, "my.ini")
+	if err != nil {
+		fmt.Printf("Fail to read file: %v", err)
+		os.Exit(1)
+	}
+	// Classic read of values, default section can be represented as empty string
+	cfg.Section("").Comment = "#include extensions_trigger.conf"
+	fmt.Println("App Mode:", cfg.Section("").Key("app_mode").String())
+	fmt.Println("Data Path:", cfg.Section("paths").Key("data").String())
+
+	// Let's do some candidate value limitation
+	fmt.Println("Server Protocol:",
+		cfg.Section("server").Key("protocol").In("http", []string{"http", "https"}))
+	// Value read that is not in candidates will be discarded and fall back to given default value
+	fmt.Println("Email Protocol:",
+		cfg.Section("server").Key("protocol").In("smtp", []string{"imap", "smtp"}))
+
+	// Try out auto-type conversion
+	fmt.Printf("Port Number: (%[1]T) %[1]d\n", cfg.Section("server").Key("http_port").MustInt(9999))
+	fmt.Printf("Enforce Domain: (%[1]T) %[1]v\n", cfg.Section("server").Key("enforce_domain").MustBool(false))
+
+	// Now, make some changes and save it
+	cfg.Section("").Key("app_mode").SetValue("production")
+	cfg.SaveTo("my.ini")
+}

+ 11 - 0
test/my.ini

@@ -0,0 +1,11 @@
+# include extensions_trigger.conf
+app_mode=production
+
+[paths]
+data=
+
+[server]
+protocol=
+http_port=9999
+enforce_domain=false
+