第一次
This commit is contained in:
commit
1abdd93b5f
|
@ -0,0 +1,3 @@
|
||||||
|
.idea
|
||||||
|
install.lock
|
||||||
|
.product
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM ubuntu:latest
|
||||||
|
RUN mkdir /app
|
||||||
|
# 将程序复制到容器中
|
||||||
|
COPY . /app
|
||||||
|
RUN chmod -R 777 /app
|
||||||
|
WORKDIR /app
|
||||||
|
# 开放 8085 端口
|
||||||
|
EXPOSE 8085
|
||||||
|
# 执行脚本
|
||||||
|
CMD ["./kefu", "server", "-p", "8085"]
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"php_ext":"",
|
||||||
|
"chmod":[
|
||||||
|
{"mode":777,"path":"/"}
|
||||||
|
],
|
||||||
|
"success_url":"/welcome.html",
|
||||||
|
"php_versions":"53,54,55,56,57,70,71,72,73,74,75,76,80,81",
|
||||||
|
"db_config":"/config/mysql.json",
|
||||||
|
"admin_username":"kefu2",
|
||||||
|
"admin_password":"123",
|
||||||
|
"run_path":"/",
|
||||||
|
"remove_file":[],
|
||||||
|
"enable_functions":[]
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
d:
|
||||||
|
cd /kefu-master240715
|
||||||
|
rd .product /s/q
|
||||||
|
mkdir .product
|
||||||
|
go build -o kefu.exe
|
||||||
|
xcopy config .product\config /e /i /s /y
|
||||||
|
xcopy static\images .product\static\images /e /i /s /y
|
||||||
|
xcopy static\css .product\static\css /e /i /s /y
|
||||||
|
xcopy static\js .product\static\js /e /i /s /y
|
||||||
|
xcopy static\cdn .product\static\cdn /e /i /s /y
|
||||||
|
xcopy static\templates .product\static\templates /e /i /s /y
|
||||||
|
copy kefu.exe .product\ /y
|
||||||
|
copy cron.bat .product\ /y
|
||||||
|
copy start.bat .product\ /y
|
||||||
|
copy stop.bat .product\ /y
|
||||||
|
copy install.bat .product\ /y
|
||||||
|
copy help.txt .product\ /y
|
||||||
|
copy import.sql .product\ /y
|
||||||
|
del kefu.exe
|
||||||
|
pause
|
|
@ -0,0 +1,19 @@
|
||||||
|
rm -rf .product
|
||||||
|
mkdir .product
|
||||||
|
mkdir .product/static
|
||||||
|
|
||||||
|
go env -w GO111MODULE=on
|
||||||
|
go env -w GOPROXY=https://goproxy.cn,direct
|
||||||
|
go build -o kefu
|
||||||
|
|
||||||
|
cp kefu .product/
|
||||||
|
cp -r config .product/config
|
||||||
|
cp -r static/js .product/static/js
|
||||||
|
cp -r static/css .product/static/css
|
||||||
|
cp -r static/images .product/static/images
|
||||||
|
cp -r static/cdn .product/static/cdn
|
||||||
|
cp -r static/templates .product/static/templates
|
||||||
|
cp help.txt .product/
|
||||||
|
cp Dockerfile .product/
|
||||||
|
cp stop.sh .product/
|
||||||
|
cd .product/ && zip -q -r kefu.zip *
|
|
@ -0,0 +1,64 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"io/ioutil"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var installCmd = &cobra.Command{
|
||||||
|
Use: "install",
|
||||||
|
Short: "安装导入数据",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
install()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func install() {
|
||||||
|
if ok, _ := tools.IsFileNotExist("./install.lock"); !ok {
|
||||||
|
log.Println("请先删除./install.lock")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
sqlFile := "import.sql"
|
||||||
|
isExit, _ := tools.IsFileExist(common.MysqlConf)
|
||||||
|
dataExit, _ := tools.IsFileExist(sqlFile)
|
||||||
|
if !isExit || !dataExit {
|
||||||
|
log.Println("config/mysql.json 数据库配置文件或者数据库文件import.sql不存在")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
err := models.NewConnect(common.MysqlConf)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
sqls, _ := ioutil.ReadFile(sqlFile)
|
||||||
|
sqlArr := strings.Split(string(sqls), ";")
|
||||||
|
for _, sql := range sqlArr {
|
||||||
|
sql = strings.TrimSpace(sql)
|
||||||
|
if sql == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := models.Execute(sql)
|
||||||
|
if err == nil {
|
||||||
|
log.Println(sql, "\t success!")
|
||||||
|
} else {
|
||||||
|
log.Println(sql, err, "\t failed!", "数据库导入失败")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
installFile, _ := os.OpenFile("./install.lock", os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||||
|
|
||||||
|
installTime := tools.Int64ToByte(time.Now().Unix())
|
||||||
|
token, err := tools.AesEncrypt(installTime, []byte(common.AesKey))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("数据库导入失败:", err)
|
||||||
|
}
|
||||||
|
installFile.Write(token)
|
||||||
|
log.Println("数据库导入成功!")
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "kefu",
|
||||||
|
Short: `简洁快速的智能在线客服系统`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(serverCmd)
|
||||||
|
rootCmd.AddCommand(installCmd)
|
||||||
|
rootCmd.AddCommand(stopCmd)
|
||||||
|
}
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
log.Println("执行命令参数错误:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-contrib/pprof"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/zh-five/xdaemon"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/controller"
|
||||||
|
"kefu/middleware"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/router"
|
||||||
|
"kefu/service"
|
||||||
|
"kefu/static"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
port string
|
||||||
|
daemon bool
|
||||||
|
rootPath string
|
||||||
|
)
|
||||||
|
var serverCmd = &cobra.Command{
|
||||||
|
Use: "server",
|
||||||
|
Short: "启动客服http服务",
|
||||||
|
Example: "kefu server",
|
||||||
|
Run: run,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
serverCmd.PersistentFlags().StringVarP(&rootPath, "rootPath", "r", "", "程序根目录")
|
||||||
|
serverCmd.PersistentFlags().StringVarP(&port, "port", "p", "8081", "监听端口号")
|
||||||
|
serverCmd.PersistentFlags().BoolVarP(&daemon, "daemon", "d", false, "是否为守护进程模式")
|
||||||
|
}
|
||||||
|
func run(cmd *cobra.Command, args []string) {
|
||||||
|
//初始化目录
|
||||||
|
initDir()
|
||||||
|
//初始化守护进程
|
||||||
|
initDaemon()
|
||||||
|
|
||||||
|
baseServer := "0.0.0.0:" + port
|
||||||
|
|
||||||
|
//if common.RpcStatus {
|
||||||
|
// go frpc.NewRpcServer(common.RpcServer)
|
||||||
|
// log.Println("start rpc server...\r\ngo:tcp://" + common.RpcServer)
|
||||||
|
//}
|
||||||
|
|
||||||
|
engine := gin.Default()
|
||||||
|
//模板函数
|
||||||
|
engine.SetFuncMap(template.FuncMap{
|
||||||
|
"DateFormat": tools.DateFormat, //格式化日期
|
||||||
|
})
|
||||||
|
//是否编译模板
|
||||||
|
if common.IsCompireTemplate {
|
||||||
|
templ := template.Must(template.New("").ParseFS(static.TemplatesEmbed, "templates/**/*.html"))
|
||||||
|
engine.SetHTMLTemplate(templ)
|
||||||
|
} else {
|
||||||
|
engine.LoadHTMLGlob(common.StaticDirPath + "templates/**/*")
|
||||||
|
}
|
||||||
|
engine.Static("/static", common.StaticDirPath)
|
||||||
|
engine.Static("/h5", common.RootPath+"/h5")
|
||||||
|
|
||||||
|
store := cookie.NewStore([]byte("secret"))
|
||||||
|
sessionNames := []string{"go-session", "go-session-a", "go-session-b"}
|
||||||
|
engine.Use(sessions.SessionsMany(sessionNames, store))
|
||||||
|
//engine.Use(tools.Session("kefu"))
|
||||||
|
|
||||||
|
engine.Use(middleware.CrossSite)
|
||||||
|
|
||||||
|
router.InitViewRouter(engine)
|
||||||
|
router.InitApiRouter(engine)
|
||||||
|
|
||||||
|
//限流类
|
||||||
|
tools.NewLimitQueue()
|
||||||
|
//清理
|
||||||
|
//ws.CleanVisitorExpire()
|
||||||
|
//后端websocket
|
||||||
|
//go ws.WsServerBackend()
|
||||||
|
//初始化数据
|
||||||
|
//logger := lib.NewLogger()
|
||||||
|
err := models.NewConnect(common.ConfigDirPath + "/mysql.json")
|
||||||
|
if err != nil {
|
||||||
|
common.MySQLConnectFaild = true
|
||||||
|
}
|
||||||
|
|
||||||
|
//后端定时客服
|
||||||
|
//go ws.UpdateVisitorStatusCron()
|
||||||
|
//初始化定时任务
|
||||||
|
// 创建定时任务调度器
|
||||||
|
cr := cron.New()
|
||||||
|
// 添加定时任务,每分钟执行一次
|
||||||
|
cr.AddFunc("*/1 * * * *", func() {
|
||||||
|
log.Println("定时任务执行:", time.Now().Format("2006-01-02 15:04:05"), "给客服websocket链接发送ping")
|
||||||
|
// 这里可以添加你的定时任务逻辑
|
||||||
|
ws.SendPingToKefuClient()
|
||||||
|
})
|
||||||
|
// 添加定时任务,每小时执行一次,更新抖音
|
||||||
|
douyinClientKey := models.FindConfig("DouyinClientKey")
|
||||||
|
douyinClientSecret := models.FindConfig("DouyinClientSecret")
|
||||||
|
if douyinClientKey != "" && douyinClientSecret != "" {
|
||||||
|
cr.AddFunc("0 */1 * * *", func() {
|
||||||
|
log.Println("定时任务执行:", time.Now().Format("2006-01-02 15:04:05"), "更新抖音access_token,refresh_token")
|
||||||
|
service.UpdateDouyinAccessToken()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动定时任务调度器
|
||||||
|
cr.Start()
|
||||||
|
defer cr.Stop()
|
||||||
|
log.Println("服务开始运行:" + baseServer)
|
||||||
|
//性能监控
|
||||||
|
pprof.Register(engine)
|
||||||
|
//engine.Run(baseServer)
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: baseServer,
|
||||||
|
Handler: engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
|
log.Printf("服务监听: %s\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-controller.StopSign
|
||||||
|
log.Println("关闭服务...")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
|
log.Fatal("服务关闭失败:", err)
|
||||||
|
}
|
||||||
|
log.Println("服务已关闭")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化目录
|
||||||
|
func initDir() {
|
||||||
|
if rootPath == "" {
|
||||||
|
rootPath = tools.GetRootPath()
|
||||||
|
}
|
||||||
|
log.Println("服务运行路径:" + rootPath)
|
||||||
|
common.RootPath = rootPath
|
||||||
|
common.LogDirPath = rootPath + "/logs/"
|
||||||
|
common.ConfigDirPath = rootPath + "/config/"
|
||||||
|
common.StaticDirPath = rootPath + "/static/"
|
||||||
|
common.UploadDirPath = rootPath + "/static/upload/"
|
||||||
|
|
||||||
|
if noExist, _ := tools.IsFileNotExist(common.RootPath + "/install.lock"); noExist {
|
||||||
|
panic("未检测到" + common.RootPath + "/install.lock,请先安装服务!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if noExist, _ := tools.IsFileNotExist(common.LogDirPath); noExist {
|
||||||
|
if err := os.MkdirAll(common.LogDirPath, 0777); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isMainUploadExist, _ := tools.IsFileExist(common.UploadDirPath)
|
||||||
|
if !isMainUploadExist {
|
||||||
|
os.Mkdir(common.UploadDirPath, os.ModePerm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化守护进程
|
||||||
|
func initDaemon() {
|
||||||
|
if daemon == true {
|
||||||
|
d := xdaemon.NewDaemon(common.LogDirPath + "kefu.log")
|
||||||
|
d.MaxError = 5
|
||||||
|
d.Run()
|
||||||
|
}
|
||||||
|
//记录pid
|
||||||
|
ioutil.WriteFile(common.RootPath+"/kefu.sock", []byte(fmt.Sprintf("%d %d", os.Getppid(), os.Getpid())), 0666)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"io/ioutil"
|
||||||
|
"kefu/common"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var stopCmd = &cobra.Command{
|
||||||
|
Use: "stop",
|
||||||
|
Short: "停止客服http服务",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
pids, err := ioutil.ReadFile(common.RootPath + "/kefu.sock")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pidSlice := strings.Split(string(pids), ",")
|
||||||
|
var command *exec.Cmd
|
||||||
|
for _, pid := range pidSlice {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
command = exec.Command("taskkill.exe", "/f", "/pid", pid)
|
||||||
|
} else {
|
||||||
|
command = exec.Command("kill", pid)
|
||||||
|
}
|
||||||
|
command.Start()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
var (
|
||||||
|
PageSize uint = 9
|
||||||
|
VisitorPageSize uint = 10
|
||||||
|
Version string = "0.6.0"
|
||||||
|
VisitorExpire float64 = 600
|
||||||
|
Upload string = "static/upload/"
|
||||||
|
Dir string = "config/"
|
||||||
|
MysqlConf string = Dir + "mysql.json"
|
||||||
|
MySQLConnectFaild bool = false
|
||||||
|
RpcServer string = "0.0.0.0:8082"
|
||||||
|
RpcStatus bool = false
|
||||||
|
SecretToken string = "AaBbCcDd123AaBbCcDd"
|
||||||
|
AesKey string = "ggcjd4slhtjyxl16"
|
||||||
|
WsBreakTimeout int64 = 5 //断线超时秒数
|
||||||
|
IsCompireTemplate bool = false //是否编译静态模板到二进制
|
||||||
|
IsTry bool = false
|
||||||
|
TryDeadline int64 = 30 * 24 * 3600 //试用十四天
|
||||||
|
EveryDayYuan int64 = 2 //每天2块
|
||||||
|
RsaPublicKey string = `
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqNF3f0M6xRxsAuiQ5Zq+
|
||||||
|
eoFIQjpc/d8Ahbr5l6KuRfHvy78l3OtkZeiL8mtMsHJi+YoDDycHE4UpwruM9w/m
|
||||||
|
JqRKWMgi8DHwVtBSQgfYVLPm1U6grk331Qdqzryz5LZicBvNZ/BuasA+CNHX4prs
|
||||||
|
WPVLWxPxxSsz0+35+xW29Pxcv1PeQV4TH0FeSzLBFNDPD9EXcHjlyqHPoV+rq//f
|
||||||
|
/lJ1zD7mish7gttRvUTQyAijUNmFxBKe3hkSxBlm4xP1K5owqkg3KqrC6Q5ZPvsk
|
||||||
|
14WttTglFlTeHLT3qH4VtqCY/oBl72uiWlF7cFRgW05Pkr9PgR4aCPh8EjOktb6P
|
||||||
|
twIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`
|
||||||
|
RsaPrivateKey string = `
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCo0Xd/QzrFHGwC
|
||||||
|
6JDlmr56gUhCOlz93wCFuvmXoq5F8e/LvyXc62Rl6Ivya0ywcmL5igMPJwcThSnC
|
||||||
|
u4z3D+YmpEpYyCLwMfBW0FJCB9hUs+bVTqCuTffVB2rOvLPktmJwG81n8G5qwD4I
|
||||||
|
0dfimuxY9UtbE/HFKzPT7fn7Fbb0/Fy/U95BXhMfQV5LMsEU0M8P0RdweOXKoc+h
|
||||||
|
X6ur/9/+UnXMPuaKyHuC21G9RNDICKNQ2YXEEp7eGRLEGWbjE/UrmjCqSDcqqsLp
|
||||||
|
Dlk++yTXha21OCUWVN4ctPeofhW2oJj+gGXva6JaUXtwVGBbTk+Sv0+BHhoI+HwS
|
||||||
|
M6S1vo+3AgMBAAECggEANqnk/MNjM1I5s1NlH8PUq8xS/obxd48PFz5s9WnK7oEh
|
||||||
|
uaLCet+J2enL7wpJgdT4MX878Fsd0ndLB3A9e/6c8qQ+IrNjhM1W2nWIPTNXHE5I
|
||||||
|
j4dvRiGe/07widLWmVdpVo3aHC7hQhXPwIFaW1j48PE0nmA96C9OD/C+AkyMupS6
|
||||||
|
MFhHppTBacyKUewnSrb9l4c2jtqUJRV2t4kgqc5/70V7lLISQothtamzafh7QgMO
|
||||||
|
JOmLkkTSt3pejy6sn8hU9IGrowxUrrVlfY6TXB+7UhixI+A5cctTWJgGtNoRw+yD
|
||||||
|
TepW43HJEx/8AqBvPYgz4ZREPO3qsHSIYDtLk6ma+QKBgQDVz7vnqPVEO8DYXJE/
|
||||||
|
RAhL1W7Wxp+WroO+WoU6By7Pe3ZYBIQZuWdB4MONTxbuBX4zdNTncFX3jD9hybks
|
||||||
|
C775VUec2vkDQ+NFuhGhe0cBlFXL6vmKIoMBhECbQL/Q+IE7fPujNPwkHDz9V5bI
|
||||||
|
xfwIGzbdCADah1GmAU5raTZQ8wKBgQDKIPzycnoPHshiVtRy4ewPY0smYFHupFTW
|
||||||
|
mYz1H/u2UKJk55HL9DYkqo51mWdzPSBieCXcFR9bhV9kEQEWUcDMcQqwanJY1uou
|
||||||
|
aMhgDeGTXu7YPa0OFiGWe7oPdBlXNkWXgI7MxIlgDHAdBrNEcSAgSYhzCahSKgsx
|
||||||
|
1geF0YGXLQKBgQC2GAiZYJ0aMqWn3xZYwlEqfKi8REcQZqqPCLrkU8+7JxQAHECB
|
||||||
|
RBS713fUNmJ57rWvjzXvsg2VGZ21Y77P02UfJlEqNpfS/xNlg/WWCM7Neo6jcAh8
|
||||||
|
a64VMZRwZPG4QJyXlHcfZXXEL0SGIv2pGmzuXncYQcOwWYThE8W81stKnwKBgA8J
|
||||||
|
3Uf8lHBDjg0jALN7c6DBdnnoeLAUses1iXNDaerqnOp9AC/4f37C4c6GF1hPl/U7
|
||||||
|
kzWIrMiWZ75+NWJ/uqR7VJxDjzZk5w+E5EOhRMM+MO8Nx65gnycfFXzI2onOk+bt
|
||||||
|
vbImfUIUKtM+CGBzOjysu+YzNb7HOgX/MpgWOloRAoGABhFCzH25mkJ6fkLi56Oy
|
||||||
|
SRmY9e/MYZ8a/sd1M64AKza0rCn10nJNA2ONJfTDvB7LJAxl5/FGf77MIl3Pp8kw
|
||||||
|
/FuwsMOdZuLLL2d3cVPFp1yHJE90upQA/xOtNM0bJ+9EJS+SVuOhmgJyqwmvaLLx
|
||||||
|
cUF2wlZSGDrH4SoQMqjDGPg=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
DomainWhiteList string = "*" //域名白名单
|
||||||
|
RootPath string = "" //自动获取,程序运行根路径
|
||||||
|
LogDirPath string = "" //自动获取,程序运行根路径/logs/
|
||||||
|
ConfigDirPath string = "" //自动获取,程序运行根路径/config/
|
||||||
|
StaticDirPath string = "" //自动获取,程序运行根路径/static/
|
||||||
|
UploadDirPath string = "" //自动获取,程序运行根路径/static/upload/
|
||||||
|
)
|
|
@ -0,0 +1,218 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
var (
|
||||||
|
NoticeTemplate string = `
|
||||||
|
<div>
|
||||||
|
<includetail>
|
||||||
|
<div style="font:Verdana normal 14px;color:#000;">
|
||||||
|
<div style="position:relative;">
|
||||||
|
<style>
|
||||||
|
table.gridtable {
|
||||||
|
width: 100%;
|
||||||
|
font-family: verdana,arial,sans-serif;
|
||||||
|
font-size:11px;
|
||||||
|
color:#666666;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #666666;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
table.gridtable th {
|
||||||
|
border-width: 1px;
|
||||||
|
padding: 8px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #aaaaaa;
|
||||||
|
background-color: #dedede;
|
||||||
|
}
|
||||||
|
table.gridtable td {
|
||||||
|
border-width: 1px;
|
||||||
|
padding: 8px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #aaaaaa;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
.title_bold {
|
||||||
|
font-family: PingFangSC-Medium, "STHeitiSC-Light", BlinkMacSystemFont, "Helvetica", "lucida Grande", "SCHeiti", "Microsoft YaHei";
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_bg {
|
||||||
|
background-color: #F5F5F5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_cnt {
|
||||||
|
padding: 60px 0 160px;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: auto;
|
||||||
|
color: #2b2b2b;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_container {
|
||||||
|
background-color: #fff;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 702px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eml_content {
|
||||||
|
padding: 0 50px 30px 50px;
|
||||||
|
font-family: "Helvetica Neue", "Arial", "PingFang SC", "Hiragino Sans GB", "STHeiti", "Microsoft YaHei", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_header {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top_line_left {
|
||||||
|
width: 88%;
|
||||||
|
height: 3px;
|
||||||
|
background-color: #2984EF;
|
||||||
|
float: left;
|
||||||
|
margin-right: 1px;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top_line_right {
|
||||||
|
width: 12%;
|
||||||
|
height: 3px;
|
||||||
|
background-color: #8BD5FF;
|
||||||
|
float: right;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
margin-top: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main_title {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main_subtitle {
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item_level_1 {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item_level_2 {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level_1_title {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level_2_title {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item_txt {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_footer {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 17px;
|
||||||
|
color: #bebebe;
|
||||||
|
margin-top: 60px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_logo {
|
||||||
|
/*这里修改LOGO*/
|
||||||
|
/*background-image: url("https://mailhz.qiye.163.com/qiyeimage/logo/505330669/1617243260101.png");*/
|
||||||
|
width: 148px;
|
||||||
|
height: 44px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 27px 0 20px 0;
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img_position {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.normalTxt {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.top_line {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_bg {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail_cnt {
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eml_content {
|
||||||
|
padding: 0 12px 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phoneFontSizeTitle {
|
||||||
|
font-size: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phoneFontSizeContent {
|
||||||
|
font-size: 16px !important;
|
||||||
|
line-height: 28px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phoneFontSizeTitleLarge {
|
||||||
|
font-size: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phoneFontSizeTips {
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="qmbox">
|
||||||
|
<div class="mail_bg">
|
||||||
|
<div class="mail_cnt">
|
||||||
|
<div class="mail_container">
|
||||||
|
<div class="top_line">
|
||||||
|
<div class="top_line_left"></div>
|
||||||
|
<div class="top_line_right"></div>
|
||||||
|
</div>
|
||||||
|
<div class="eml_content">
|
||||||
|
<div class="mail_header">
|
||||||
|
<div class="mail_logo"></div>
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<p style="font-size: 16px;margin-top:20px;" class="phoneFontSizeTitle">
|
||||||
|
[:content]
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mail_footer">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--<![endif]-->
|
||||||
|
</includetail>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)
|
|
@ -0,0 +1,272 @@
|
||||||
|
/*--2022-11-29--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '客服端撤回消息时间限制,单位/秒(默认不限)', 'KefuDeleteMessageLimitTime', '');
|
||||||
|
/*--2022-12-01--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, 'IP地址或者refer来源黑名单', 'SystemBlackList', '');
|
||||||
|
/*--2023-01-04--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, 'IP白名单', 'SystemWhiteList', '');
|
||||||
|
/*--2023-01-11--*/
|
||||||
|
alter table visitor_ext add `language` varchar(100) NOT NULL DEFAULT '' COMMENT '浏览器语言';
|
||||||
|
/*--2023-01-31--*/
|
||||||
|
alter table user add `uuid` varchar(125) NOT NULL DEFAULT '' COMMENT '企业uuid';
|
||||||
|
alter table user add KEY `idx_uuid` (`uuid`) COMMENT 'uuid索引';
|
||||||
|
/*--2023-02-18--*/
|
||||||
|
alter table article add `search_type` tinyint NOT NULL DEFAULT '1' COMMENT '1包含匹配,2精准匹配';
|
||||||
|
/*--2023-02-19--*/
|
||||||
|
alter table message add KEY `created_at` (`created_at`) COMMENT '时间索引';
|
||||||
|
/*--2023-02-25--*/
|
||||||
|
alter table visitor_ext add KEY `ent_id` (`ent_id`) COMMENT '企业ID索引';
|
||||||
|
alter table visitor_ext add KEY `title` (`title`) COMMENT '页面标题索引';
|
||||||
|
/*--2023-03-02--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, 'IP白名单', 'SystemWhiteList', '');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, 'IP登录限制(1全公开,2禁止海外,3只能内网)', 'IpLoginForbidden', '1');
|
||||||
|
/*--2023-03-04--*/
|
||||||
|
alter table welcome modify `content` varchar(5000) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '欢迎消息内容';
|
||||||
|
/*--2023-03-10--*/
|
||||||
|
CREATE TABLE `consumer` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`company` varchar(500) NOT NULL DEFAULT '' COMMENT '公司名',
|
||||||
|
`realname` varchar(500) NOT NULL DEFAULT '' COMMENT '姓名',
|
||||||
|
`score` varchar(100) NOT NULL DEFAULT '' COMMENT '级别',
|
||||||
|
`consumer_sn` varchar(500) NOT NULL DEFAULT '' COMMENT '客户编号',
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`kefu_name` varchar(100) NOT NULL DEFAULT '' COMMENT 'kefu名称',
|
||||||
|
`tel` varchar(100) NOT NULL DEFAULT '' COMMENT '手机',
|
||||||
|
`wechat` varchar(100) NOT NULL DEFAULT '' COMMENT '微信',
|
||||||
|
`qq` varchar(100) NOT NULL DEFAULT '' COMMENT 'qq',
|
||||||
|
`email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱',
|
||||||
|
`remark` varchar(1000) NOT NULL DEFAULT '' COMMENT '备注',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `ent_id` (`ent_id`) COMMENT '企业ID索引',
|
||||||
|
KEY `consumer_sn` (`consumer_sn`) COMMENT '客户索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '客户表';
|
||||||
|
/*--2023-03-12--*/
|
||||||
|
CREATE TABLE `learn` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`content` varchar(500) NOT NULL DEFAULT '' COMMENT '问题内容',
|
||||||
|
`score` int(11) NOT NULL DEFAULT '1' COMMENT '次数',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`kefu_name` varchar(100) NOT NULL DEFAULT '' COMMENT 'kefu名称',
|
||||||
|
`finshed` tinyint(4) NOT NULL DEFAULT 1 COMMENT '是否解决,1未解决,2已解决',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `ent_id` (`ent_id`) COMMENT '企业ID索引',
|
||||||
|
KEY `content` (`content`) COMMENT '问题内容索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '机器人学习库';
|
||||||
|
/*--2023-03-13--*/
|
||||||
|
alter table article add `score` int(11) NOT NULL DEFAULT '0' COMMENT '命中次数';
|
||||||
|
alter table article add KEY `score` (`score`) COMMENT '命中次数索引';
|
||||||
|
/*--2023-03-18--*/
|
||||||
|
CREATE TABLE `wework_sync_msg` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`sync_cursor` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`json_txt` text NOT NULL,
|
||||||
|
`visitor_id` varchar(100) NOT NULL DEFAULT '',
|
||||||
|
`kefu_id` varchar(100) NOT NULL DEFAULT '',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`ent_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `visitor_id` (`visitor_id`),
|
||||||
|
KEY `kefu_id` (`kefu_id`),
|
||||||
|
KEY `ent_id` (`ent_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
alter table reply_group add `ent_id` varchar(255) NOT NULL DEFAULT '' COMMENT '客服企业ID';
|
||||||
|
alter table reply_group add `is_team` tinyint NOT NULL DEFAULT '1' COMMENT '1个人,2团队';
|
||||||
|
alter table reply_group modify `group_name` varchar(255) NOT NULL DEFAULT '' COMMENT '组名';
|
||||||
|
alter table reply_group modify `user_id` varchar(255) NOT NULL DEFAULT '' COMMENT '客服账户';
|
||||||
|
alter table reply_item add `ent_id` varchar(255) NOT NULL DEFAULT '' COMMENT '客服企业ID';
|
||||||
|
alter table reply_item modify `item_name` varchar(255) NOT NULL DEFAULT '' COMMENT '快捷回复标题';
|
||||||
|
alter table reply_item modify `user_id` varchar(255) NOT NULL DEFAULT '' COMMENT '客服账户';
|
||||||
|
/*--2023-03-22--*/
|
||||||
|
alter table visitor_attr add `max_message_num` varchar(100) NOT NULL DEFAULT '10' COMMENT '访客最大消息数';
|
||||||
|
/*--2023-03-27--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, 'OpenAI API最大消息数量', 'OpenAiApiMaxNum', '100');
|
||||||
|
/*--2023-04-14--*/
|
||||||
|
alter table article modify `score` int(11) NOT NULL DEFAULT '0' COMMENT '命中次数';
|
||||||
|
/*--2023-04-27--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '基于GPT的向量知识库服务接口', 'BaseGPTKnowledge', 'http://127.0.0.1:8083');
|
||||||
|
/*--2023-05-05--*/
|
||||||
|
alter table visitor add `state` varchar(100) NOT NULL DEFAULT '' COMMENT '访客状态位';
|
||||||
|
/*--2023-05-18--*/
|
||||||
|
alter table reply_item modify `content` varchar(1024) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '快捷回复内容';
|
||||||
|
/*--2023-05-29--*/
|
||||||
|
DROP TABLE IF EXISTS `ai_file`;
|
||||||
|
CREATE TABLE `ai_file` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`file_name` varchar(1000) NOT NULL DEFAULT '' COMMENT '文件名',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`collect_name` varchar(500) NOT NULL DEFAULT '' COMMENT '集合名称',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `collect_name` (`collect_name`) COMMENT 'AI集合文件列表'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'AI集合文件向量映射表';
|
||||||
|
DROP TABLE IF EXISTS `ai_file_points`;
|
||||||
|
CREATE TABLE `ai_file_points` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`file_id` varchar(500) NOT NULL DEFAULT '' COMMENT '文件表自增ID',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`collect_name` varchar(500) NOT NULL DEFAULT '' COMMENT '集合名称',
|
||||||
|
`points_id` varchar(500) NOT NULL DEFAULT '' COMMENT '向量ID',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `collect_name` (`collect_name`) COMMENT '集合名索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'AI集合文件向量映射表';
|
||||||
|
/*--2023-06-14--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, 'QDRANT向量数据库地址', 'QdrantBase', '127.0.0.1');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, 'QDRANT向量数据库端口', 'QdrantPort', '6333');
|
||||||
|
alter table ai_file add `file_size` varchar(500) NOT NULL DEFAULT '' COMMENT '字符数';
|
||||||
|
/*--2023-07-18--*/
|
||||||
|
alter table message add KEY `idx_kefuid_mestype_status` (`kefu_id`,`mes_type`,`status`) COMMENT '联合索引';
|
||||||
|
/*--2023-08-07--*/
|
||||||
|
DROP TABLE IF EXISTS `aigc_session_collect`;
|
||||||
|
CREATE TABLE `aigc_session_collect` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`title` varchar(1000) NOT NULL DEFAULT '' COMMENT '集合标题',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`kefu_name` varchar(500) NOT NULL DEFAULT '' COMMENT '客服名称',
|
||||||
|
`ent_id` varchar(500) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `kefu_name` (`kefu_name`) COMMENT '客服名称'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'AIGC会话集合';
|
||||||
|
DROP TABLE IF EXISTS `aigc_session_message`;
|
||||||
|
CREATE TABLE `aigc_session_message` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`collect_id` int(11) NOT NULL DEFAULT '0' COMMENT '集合ID',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`kefu_avatar` varchar(500) NOT NULL DEFAULT '' COMMENT '客服头像',
|
||||||
|
`ai_avatar` varchar(500) NOT NULL DEFAULT '' COMMENT 'AI头像',
|
||||||
|
`content` text COMMENT '内容',
|
||||||
|
`kefu_name` varchar(500) NOT NULL DEFAULT '' COMMENT '客服名称',
|
||||||
|
`msg_type` varchar(500) NOT NULL DEFAULT '' COMMENT '消息类型',
|
||||||
|
`ent_id` varchar(500) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `kefu_name` (`kefu_name`) COMMENT '客服名称'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'AIGC会话消息';
|
||||||
|
alter table user_attr add `aigc_session_score` int(11) unsigned NOT NULL DEFAULT 0 COMMENT 'AIGC积分';
|
||||||
|
/*--2023-08-27--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '微信支付AppID', 'WechatPayAppId', '');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '微信支付商户号', 'WechatPayMchID', '');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '微信支付商户证书序列号', 'WechatPayMchCertificateSerialNumber', '');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '微信支付商户API V3密钥', 'WechatPayMchAPIv3Key', '');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '微信支付商户私钥', 'WechatPayMchPrivateKey', '');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '微信支付回调URL', 'WechatPayNotifyUrl', '');
|
||||||
|
alter table user_order add `new_expire_time` varchar(100) NOT NULL DEFAULT '' COMMENT '新的到期时间';
|
||||||
|
/*--2023-10-23--*/
|
||||||
|
alter table reply_item add KEY `ent_id` (`ent_id`) COMMENT '企业id索引';
|
||||||
|
alter table reply_group add KEY `ent_id` (`ent_id`) COMMENT '企业id索引';
|
||||||
|
/*--2023-11-13--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '是否验证强密码', 'CheckStrongPass', 'false');
|
||||||
|
/*--2023-11-14--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '抖音client_key', 'DouyinClientKey', '');
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '抖音client_secret', 'DouyinClientSecret', '');
|
||||||
|
/*--2023-11-16--*/
|
||||||
|
CREATE TABLE `user_douyin` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`kefu_name` varchar(100) NOT NULL DEFAULT '' COMMENT '客服账户',
|
||||||
|
`nickname` varchar(500) NOT NULL DEFAULT '' COMMENT '抖音昵称',
|
||||||
|
`avatar` varchar(500) NOT NULL DEFAULT '' COMMENT '抖音头像',
|
||||||
|
`open_id` varchar(500) NOT NULL DEFAULT '' COMMENT '抖音OpenId',
|
||||||
|
`union_id` varchar(500) NOT NULL DEFAULT '' COMMENT '抖音union_id',
|
||||||
|
`access_token` varchar(500) NOT NULL DEFAULT '' COMMENT '抖音AccessToken',
|
||||||
|
`expires_in` datetime COMMENT '抖音AccessToken过期时间',
|
||||||
|
`refresh_token` varchar(500) NOT NULL DEFAULT '' COMMENT '抖音refresh_token',
|
||||||
|
`refresh_expires_in` datetime COMMENT '抖音refresh_token过期时间',
|
||||||
|
`client_token` varchar(500) NOT NULL DEFAULT '' COMMENT '抖音client_token',
|
||||||
|
`client_token_expires` datetime COMMENT '抖音client_token过期时间',
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `kefu_name` (`kefu_name`) COMMENT '客服账户索引',
|
||||||
|
KEY `ent_id` (`ent_id`) COMMENT '企业ID索引',
|
||||||
|
KEY `open_id` (`open_id`) COMMENT '抖音OpenId索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '客服抖音绑定表';
|
||||||
|
/*--2023-12-15--*/
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '阿里云短信模板CODE', 'ALI_SMS_CODE', '');
|
||||||
|
/*--2023-12-18--*/
|
||||||
|
CREATE TABLE `douyin_webhook` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`kefu_name` varchar(100) NOT NULL DEFAULT '' COMMENT '客服账户',
|
||||||
|
`event` varchar(500) NOT NULL DEFAULT '' COMMENT 'event',
|
||||||
|
`from_user_id` varchar(500) NOT NULL DEFAULT '' COMMENT 'from_user_id',
|
||||||
|
`to_user_id` varchar(500) NOT NULL DEFAULT '' COMMENT 'to_user_id',
|
||||||
|
`client_key` varchar(500) NOT NULL DEFAULT '' COMMENT 'client_key',
|
||||||
|
`content` TEXT DEFAULT NULL COMMENT 'content',
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `kefu_name` (`kefu_name`) COMMENT '客服账户索引',
|
||||||
|
KEY `ent_id` (`ent_id`) COMMENT '企业ID索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '抖音webhooks日志表';
|
||||||
|
/*--2024-04-04--*/
|
||||||
|
CREATE TABLE `wechat_login` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`kefu_name` varchar(500) NOT NULL DEFAULT '' COMMENT '客服账户',
|
||||||
|
`open_id` varchar(500) NOT NULL DEFAULT '' COMMENT '微信公众号openid',
|
||||||
|
`temp_kefu_id` varchar(500) NOT NULL DEFAULT '' COMMENT '临时客服ID',
|
||||||
|
`status` varchar(500) NOT NULL DEFAULT '' COMMENT '当前状态',
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`login_ip` varchar(100) NOT NULL DEFAULT '' COMMENT '登录IP',
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`) COMMENT '自增主键索引',
|
||||||
|
KEY `temp_kefu_id` (`kefu_name`) COMMENT '临时客服ID索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '微信扫码登录表';
|
||||||
|
/*--2024-04-16--*/
|
||||||
|
CREATE TABLE `product_order` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`kefu_name` varchar(500) NOT NULL DEFAULT '' COMMENT '客服账户',
|
||||||
|
`user_id` varchar(500) NOT NULL DEFAULT '' COMMENT '用户ID',
|
||||||
|
`order_sn` varchar(500) NOT NULL DEFAULT '' COMMENT '订单编号',
|
||||||
|
`order_desc` varchar(500) NOT NULL DEFAULT '' COMMENT '订单描述',
|
||||||
|
`order_status` varchar(100) NOT NULL DEFAULT '' COMMENT '订单状态:pending,processing,completed,cancelled',
|
||||||
|
`total_amount` int(11) NOT NULL DEFAULT '0' COMMENT '订单金额',
|
||||||
|
`payment_method` varchar(100) NOT NULL DEFAULT '' COMMENT '支付方式:wechat,alipay,bank,other',
|
||||||
|
`payment_status` varchar(100) NOT NULL DEFAULT '' COMMENT '支付状态:paid,unpaid,refunded',
|
||||||
|
`shipping_address` TEXT COMMENT '收货地址',
|
||||||
|
`email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱',
|
||||||
|
`contact` varchar(100) NOT NULL DEFAULT '' COMMENT '联系人',
|
||||||
|
`tel` varchar(100) NOT NULL DEFAULT '' COMMENT '手机号',
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `kefu_name` (`kefu_name`) COMMENT '客服账户索引',
|
||||||
|
KEY `ent_id` (`ent_id`) COMMENT '企业ID索引',
|
||||||
|
KEY `order_sn` (`order_sn`) COMMENT '订单号索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '产品订单表';
|
||||||
|
CREATE TABLE virtual_product (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`kefu_name` varchar(500) NOT NULL DEFAULT '' COMMENT '客服账户',
|
||||||
|
`product_name` VARCHAR(500) NOT NULL DEFAULT '',
|
||||||
|
`product_category` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`description` TEXT,
|
||||||
|
`price` int(11) NOT NULL DEFAULT '0' COMMENT '金额',
|
||||||
|
`product_img` VARCHAR(1000) NOT NULL DEFAULT '',
|
||||||
|
`resource_link` VARCHAR(1000) NOT NULL DEFAULT '',
|
||||||
|
`is_active` tinyint(4) NOT NULL DEFAULT 1 COMMENT '在线状态,1在售,2下架',
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `kefu_name` (`kefu_name`) COMMENT '客服账户索引',
|
||||||
|
KEY `ent_id` (`ent_id`) COMMENT '企业ID索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '虚拟产品表';
|
||||||
|
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`) VALUES (NULL, '微信支付JSAPI支付回调URL', 'WechatJsApiNotifyUrl', 'https://test.com/2/jsApiPayNotifyUrl');
|
||||||
|
/*--2024-04-19--*/
|
||||||
|
alter table user_attr add `money` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '总金额';
|
||||||
|
/*--2024-04-22--*/
|
||||||
|
alter table virtual_product add `payment` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '支付方式,wechat 微信支付;nan66 南星码支付';
|
||||||
|
/*--2024-05-27--*/
|
||||||
|
alter table visitor_ext modify `url` varchar(1500) NOT NULL DEFAULT '' COMMENT '页面地址';
|
||||||
|
/*--2024-06-04--*/
|
||||||
|
alter table ent_config modify `conf_value` text COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '配置值';
|
||||||
|
/*--2024-06-14--*/
|
||||||
|
CREATE TABLE llm_log (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`ent_id` varchar(100) NOT NULL DEFAULT '' COMMENT '企业ID',
|
||||||
|
`kefu_name` varchar(500) NOT NULL DEFAULT '' COMMENT '客服账户',
|
||||||
|
`model_name` VARCHAR(500) NOT NULL DEFAULT '' COMMENT '大模型名称',
|
||||||
|
`question` TEXT DEFAULT NULL COMMENT '提问',
|
||||||
|
`answer` TEXT DEFAULT NULL COMMENT '回复',
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `kefu_name` (`kefu_name`) COMMENT '客服账户索引',
|
||||||
|
KEY `ent_id` (`ent_id`) COMMENT '企业ID索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '大模型提问日志';
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"Server":"127.0.0.1",
|
||||||
|
"Port":"3306",
|
||||||
|
"Database":"kefu",
|
||||||
|
"Username":"root",
|
||||||
|
"Password":"1qaz!QAZ"
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,67 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAesEncrypt(c *gin.Context) {
|
||||||
|
key := c.Query("key")
|
||||||
|
content := c.Query("content")
|
||||||
|
res, err := tools.AesEncrypt([]byte(content), []byte(key))
|
||||||
|
res = []byte(tools.Base64Encode(string(res)))
|
||||||
|
if err != nil {
|
||||||
|
res = []byte(err.Error())
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": string(res),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetAesDecrypt(c *gin.Context) {
|
||||||
|
key := c.Query("key")
|
||||||
|
content := c.Query("content")
|
||||||
|
decContent, _ := tools.Base64Decode2(content)
|
||||||
|
res, err := tools.AesDecrypt(decContent, []byte(key))
|
||||||
|
if err != nil {
|
||||||
|
res = []byte(err.Error())
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": string(res),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetRsaEncrypt(c *gin.Context) {
|
||||||
|
publicKey := c.Query("publicKey")
|
||||||
|
privateKey := c.Query("privateKey")
|
||||||
|
rsa := tools.NewRsa(publicKey, privateKey)
|
||||||
|
content := c.Query("content")
|
||||||
|
res, err := rsa.Encrypt([]byte(content))
|
||||||
|
res = []byte(tools.Base64Encode(string(res)))
|
||||||
|
if err != nil {
|
||||||
|
res = []byte(err.Error())
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": string(res),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetRsaDecrypt(c *gin.Context) {
|
||||||
|
publicKey := c.Query("publicKey")
|
||||||
|
privateKey := c.Query("privateKey")
|
||||||
|
rsa := tools.NewRsa(publicKey, privateKey)
|
||||||
|
content := c.Query("content")
|
||||||
|
decContent, _ := tools.Base64Decode2(content)
|
||||||
|
res, err := rsa.Decrypt([]byte(decContent))
|
||||||
|
if err != nil {
|
||||||
|
res = []byte(err.Error())
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": string(res),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"kefu/lib"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 训练功能
|
||||||
|
func Train(openaiUrl string, openaiKey string, pointId interface{}, collectName, content, fileId, title, url string) (string, error) {
|
||||||
|
|
||||||
|
gpt := lib.NewChatGptTool(openaiUrl, openaiKey)
|
||||||
|
str := strings.TrimSpace(content)
|
||||||
|
response, err := gpt.GetEmbedding(str, "text-embedding-ada-002")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("向量接口失败:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var embeddingResponse lib.EmbeddingResponse
|
||||||
|
json.Unmarshal([]byte(response), &embeddingResponse)
|
||||||
|
if len(embeddingResponse.Data) == 0 {
|
||||||
|
log.Println("向量接口失败:", response)
|
||||||
|
return "", errors.New(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
points := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"id": pointId,
|
||||||
|
"payload": map[string]interface{}{"text": str, "title": title, "url": url, "fileid": fileId},
|
||||||
|
"vector": embeddingResponse.Data[0].Embedding,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
res, err := lib.PutPoints(collectName, points)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 长文本分块
|
||||||
|
func SplitTextByLength(text string, length int) []string {
|
||||||
|
var blocks []string
|
||||||
|
runes := []rune(text)
|
||||||
|
for i := 0; i < len(runes); i += length {
|
||||||
|
j := i + length
|
||||||
|
if j > len(runes) {
|
||||||
|
j = len(runes)
|
||||||
|
}
|
||||||
|
blocks = append(blocks, string(runes[i:j]))
|
||||||
|
}
|
||||||
|
return blocks
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
|
||||||
|
"kefu/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetChatCollects(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
collects := models.FindAigcSessionCollects(0, 100, "kefu_name = ? and ent_id = ?", kefuName, entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": collects,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetCleanChatCollects(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
models.DelAigcSessionCollects("kefu_name = ? and ent_id = ?", kefuName, entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetChatSessions(c *gin.Context) {
|
||||||
|
collectId := c.Query("collect_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
collects := models.FindAigcSessionMessage(0, 100, "kefu_name = ? and ent_id = ? and collect_id = ?", kefuName, entId, collectId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": collects,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sashabaranov/go-openai"
|
||||||
|
"io"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 聊天
|
||||||
|
func PostChatStream(c *gin.Context) {
|
||||||
|
c.Header("Content-Type", "text/html;charset=utf-8;")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
bigModel := c.PostForm("model")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
collectId := c.PostForm("collect_id")
|
||||||
|
|
||||||
|
//查询key
|
||||||
|
config := models.GetEntConfigsMap(entId.(string), "chatGPTUrl", "chatGPTSecret", "ERNIEAppId", "ERNIEAPIKey", "ERNIESecretKey")
|
||||||
|
if config["chatGPTUrl"] == "" || config["chatGPTSecret"] == "" {
|
||||||
|
c.Writer.Write([]byte("请先配置大模型URL和KEY"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gpt := lib.NewChatGptTool(config["chatGPTUrl"], config["chatGPTSecret"])
|
||||||
|
var err error
|
||||||
|
gpt3Dot5Message := make([]lib.Gpt3Dot5Message, 0)
|
||||||
|
|
||||||
|
//历史记录
|
||||||
|
if collectId != "" {
|
||||||
|
collects := models.FindAigcSessionMessage(0, 100, "kefu_name = ? and ent_id = ? and collect_id = ?", kefuName, entId, collectId)
|
||||||
|
for _, sessionMessage := range collects {
|
||||||
|
if sessionMessage.MsgType == "ask" {
|
||||||
|
item := lib.Gpt3Dot5Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: sessionMessage.Content,
|
||||||
|
}
|
||||||
|
gpt3Dot5Message = append(gpt3Dot5Message, item)
|
||||||
|
} else {
|
||||||
|
item := lib.Gpt3Dot5Message{
|
||||||
|
Role: "assistant",
|
||||||
|
Content: sessionMessage.Content,
|
||||||
|
}
|
||||||
|
gpt3Dot5Message = append(gpt3Dot5Message, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gpt3Dot5Message = append(gpt3Dot5Message, lib.Gpt3Dot5Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: content,
|
||||||
|
})
|
||||||
|
//调用openai
|
||||||
|
f, _ := c.Writer.(http.Flusher)
|
||||||
|
|
||||||
|
var stream *openai.ChatCompletionStream
|
||||||
|
log.Println(gpt3Dot5Message)
|
||||||
|
//if bigModel == "GPT-3.5" {
|
||||||
|
stream, err = gpt.ChatGPT3Dot5TurboStream(gpt3Dot5Message, bigModel)
|
||||||
|
//} else if bigModel == "GPT-4" {
|
||||||
|
// stream, err = gpt.ChatGPT4Stream(gpt3Dot5Message)
|
||||||
|
//} else if bigModel == "ERNIE-Bot-turbo" {
|
||||||
|
// //AppID := "35662533"
|
||||||
|
// //APIKey := "Iq1FfkOQIGtMtZqRFxOrvq6T"
|
||||||
|
// //SecretKey := "qbzsoFAUSl8UGt1GkGSDSjENtqsjrOTC"
|
||||||
|
// m, _ := lib.NewErnieBotTurbo(config["ERNIEAppId"], config["ERNIEAPIKey"], config["ERNIESecretKey"])
|
||||||
|
// prompt := []map[string]string{{"role": "user", "content": content}}
|
||||||
|
// res, _ := m.StreamChat(prompt)
|
||||||
|
// for {
|
||||||
|
// str, err := m.StreamRecv(res)
|
||||||
|
// if errors.Is(err, io.EOF) {
|
||||||
|
// log.Println("Stream finished", err)
|
||||||
|
// break
|
||||||
|
// } else if err != nil {
|
||||||
|
// c.Writer.Write([]byte("文心千帆大模型错误"))
|
||||||
|
// f.Flush()
|
||||||
|
// log.Println(err)
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //log.Println(str, err)
|
||||||
|
// result := gjson.Get(str, "result").String()
|
||||||
|
// c.Writer.Write([]byte(result))
|
||||||
|
// f.Flush()
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
//} else {
|
||||||
|
// c.Writer.Write([]byte("模型不存在"))
|
||||||
|
// f.Flush()
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.Write([]byte("模型错误"))
|
||||||
|
f.Flush()
|
||||||
|
log.Println("gpt stream error: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
response, err := stream.Recv()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
log.Println("Stream finished", err)
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
log.Println("Stream error:", err, response)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
aiReply := response.Choices[0].Delta.Content
|
||||||
|
c.Writer.Write([]byte(aiReply))
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 聊天
|
||||||
|
func PostSaveChatStream(c *gin.Context) {
|
||||||
|
collectId := c.PostForm("collect_id")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
kefuAvatar := c.PostForm("kefu_avatar")
|
||||||
|
aiAvatar := c.PostForm("ai_avatar")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
msgType := c.PostForm("msg_type")
|
||||||
|
collect := models.FindAigcSessionCollect("id = ?", collectId)
|
||||||
|
if collect.ID == 0 {
|
||||||
|
collectUintId := models.CreateAigcSessionCollect(content, entId.(string), kefuName.(string))
|
||||||
|
collectId = tools.Int2Str(collectUintId)
|
||||||
|
}
|
||||||
|
message := models.AigcSessionMessage{
|
||||||
|
EntId: entId.(string),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
Content: content,
|
||||||
|
KefuAvatar: kefuAvatar,
|
||||||
|
AiAvatar: aiAvatar,
|
||||||
|
MsgType: msgType,
|
||||||
|
CollectId: tools.Str2Uint(collectId),
|
||||||
|
}
|
||||||
|
message.CreateAigcSessionMessage()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"collect_id": collectId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 训练素材文件列表
|
||||||
|
func GETFileList(c *gin.Context) {
|
||||||
|
collectName, _ := c.Get("collect_name")
|
||||||
|
list := models.FindFileList(1, 1000, "collect_name = ? ", collectName)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
|
func GetDelFile(c *gin.Context) {
|
||||||
|
collectName, _ := c.Get("collect_name")
|
||||||
|
id := c.Query("id")
|
||||||
|
points := make([]string, 0)
|
||||||
|
fileModel := models.FindAiFileRow("collect_name = ? and id = ?", collectName, id)
|
||||||
|
filePoints := models.FindAiFilePoint(1, 100000, "collect_name = ? and file_id = ?", collectName, id)
|
||||||
|
for _, item := range filePoints {
|
||||||
|
points = append(points, item.PointsId)
|
||||||
|
}
|
||||||
|
list, err := lib.DeletePoints(collectName.(string), points)
|
||||||
|
models.DelFile("collect_name = ? and id = ?", collectName, id)
|
||||||
|
models.DelFilePoints("collect_name = ? and file_id = ?", collectName, id)
|
||||||
|
|
||||||
|
//删除文件
|
||||||
|
fildDir := fmt.Sprintf("%sai/%s/", common.Upload, collectName)
|
||||||
|
filepath := fmt.Sprintf("%s%s", fildDir, fileModel.FileName)
|
||||||
|
removeErr := os.Remove(filepath)
|
||||||
|
if removeErr != nil {
|
||||||
|
log.Println("Remove error:", filepath, removeErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer.Write([]byte(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件知识列表
|
||||||
|
func GetFilePoints(c *gin.Context) {
|
||||||
|
collectName, _ := c.Get("collect_name")
|
||||||
|
id := c.Query("id")
|
||||||
|
points := make([]string, 0)
|
||||||
|
filePoints := models.FindAiFilePoint(1, 100000, "collect_name = ? and file_id = ?", collectName, id)
|
||||||
|
for _, item := range filePoints {
|
||||||
|
points = append(points, item.PointsId)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := lib.GetPointsByIds(collectName.(string), points)
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer.Write(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除知识
|
||||||
|
func GetDelPoints(c *gin.Context) {
|
||||||
|
collectName, _ := c.Get("collect_name")
|
||||||
|
id := c.Query("id")
|
||||||
|
i, err := strconv.Atoi(id)
|
||||||
|
var points interface{}
|
||||||
|
if err != nil {
|
||||||
|
points = []string{id}
|
||||||
|
} else {
|
||||||
|
points = []int{i}
|
||||||
|
}
|
||||||
|
list, err := lib.DeletePoints(collectName.(string), points)
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer.Write([]byte(list))
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"io"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostAiIntent(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
config := models.GetEntConfigsMap(entId.(string), "chatGPTUrl", "chatGPTSecret")
|
||||||
|
|
||||||
|
gpt := lib.NewChatGptTool(config["chatGPTUrl"], config["chatGPTSecret"])
|
||||||
|
visitorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
messageList := ""
|
||||||
|
messages := models.FindMessageByQueryPage(0, 200, "visitor_id = ?", visitorId)
|
||||||
|
for i := len(messages) - 1; i >= 0; i-- {
|
||||||
|
reqContent := messages[i].Content
|
||||||
|
sendTime := messages[i].CreatedAt
|
||||||
|
if messages[i].MesType == "visitor" {
|
||||||
|
messageList += fmt.Sprintf("顾客:%s %s\n", reqContent, sendTime.Format("2006-01-02 15:04:05"))
|
||||||
|
} else {
|
||||||
|
messageList += fmt.Sprintf("客服:%s %s\n", reqContent, sendTime.Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
system := fmt.Sprintf(`假设你是一个消费者行为心理分析师,你正在分析客服系统中访客的消费心理,我提供给你以下顾客资料:
|
||||||
|
该顾客来访%d次,首次访问时间%s,最后访问时间%s,IP归属地是%s
|
||||||
|
我提供给你如下聊天记录:
|
||||||
|
%s
|
||||||
|
`, visitorInfo.VisitNum,
|
||||||
|
visitorInfo.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
visitorInfo.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
visitorInfo.City,
|
||||||
|
messageList)
|
||||||
|
userPrompt := fmt.Sprintf(`
|
||||||
|
请根据顾客资料分析用户心理,按如下格式输出:
|
||||||
|
1.意图辨别
|
||||||
|
2.情绪辨别
|
||||||
|
3.购买力辨别
|
||||||
|
4.分析原因(分条列出)
|
||||||
|
`)
|
||||||
|
message := []lib.Gpt3Dot5Message{
|
||||||
|
{
|
||||||
|
Role: "system",
|
||||||
|
Content: system,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Role: "user",
|
||||||
|
Content: userPrompt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.Println("消费者意图分析:", message)
|
||||||
|
//res, err := gpt.ChatGPT3Dot5Turbo(message)
|
||||||
|
|
||||||
|
// 将响应头中的Content-Type设置为text/plain,表示响应内容为文本
|
||||||
|
c.Header("Content-Type", "text/html;charset=utf-8;")
|
||||||
|
// 关闭输出缓冲,使得每次写入的数据能够立即发送给客户端
|
||||||
|
f, _ := c.Writer.(http.Flusher)
|
||||||
|
stream, err := gpt.ChatGPT3Dot5TurboStream(message, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("\ngpt3 stream error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
response, err := stream.Recv()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
log.Println("\nStream finished", err)
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
log.Printf("\nStream error: %v,%v\n", err, response)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
data := response.Choices[0].Delta.Content
|
||||||
|
log.Println(data)
|
||||||
|
c.Writer.Write([]byte(data))
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.Close()
|
||||||
|
|
||||||
|
//if err != nil {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": types.ApiCode.FAILED,
|
||||||
|
// "msg": err.Error(),
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//c.JSON(200, gin.H{
|
||||||
|
// "code": types.ApiCode.SUCCESS,
|
||||||
|
// "msg": "ok",
|
||||||
|
// "result": res,
|
||||||
|
//})
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostSearchPoints(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
collectName, _ := c.Get("collect_name")
|
||||||
|
content := c.PostForm("search")
|
||||||
|
config := models.GetEntConfigsMap(entId.(string), "chatGPTUrl", "chatGPTSecret", "")
|
||||||
|
|
||||||
|
gpt := lib.NewChatGptTool(config["chatGPTUrl"], config["chatGPTSecret"])
|
||||||
|
response, err := gpt.GetEmbedding(content, "text-embedding-ada-002")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var embeddingResponse lib.EmbeddingResponse
|
||||||
|
json.Unmarshal([]byte(response), &embeddingResponse)
|
||||||
|
if len(embeddingResponse.Data) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{"exact": false, "hnsw_ef": 128}
|
||||||
|
vector := embeddingResponse.Data[0].Embedding
|
||||||
|
limit := 10
|
||||||
|
points, err := lib.SearchPoints(collectName.(string), params, vector, limit, 0.8)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer.Write(points)
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 训练素材文本
|
||||||
|
func PostTraning(c *gin.Context) {
|
||||||
|
title := c.PostForm("title")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
id := c.PostForm("id")
|
||||||
|
oldFileId := c.PostForm("fileId")
|
||||||
|
collectName, _ := c.Get("collect_name")
|
||||||
|
openaiUrl, _ := c.Get("openai_url")
|
||||||
|
openaiKey, _ := c.Get("openai_key")
|
||||||
|
|
||||||
|
pointId := uuid.NewV4().String()
|
||||||
|
if id != "" {
|
||||||
|
pointId = id
|
||||||
|
res, _ := Train(openaiUrl.(string), openaiKey.(string), pointId, collectName.(string), content, oldFileId, title, "")
|
||||||
|
c.Writer.Write([]byte(res))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if oldFileId != "" {
|
||||||
|
fileModel := models.FindAiFileRow("id = ?", oldFileId)
|
||||||
|
fileModel.FileSize = tools.Int2Str(tools.Str2Int(fileModel.FileSize) + utf8.RuneCountInString(content))
|
||||||
|
fileModel.SaveAiFile("id = ?", oldFileId)
|
||||||
|
res, err := Train(openaiUrl.(string), openaiKey.(string), pointId, collectName.(string), content, oldFileId, title, "")
|
||||||
|
//入库
|
||||||
|
if err == nil {
|
||||||
|
aiFilePoint := &models.AiFilePoints{
|
||||||
|
FileId: fmt.Sprintf("%s", oldFileId),
|
||||||
|
CollectName: collectName.(string),
|
||||||
|
PointsId: pointId,
|
||||||
|
}
|
||||||
|
aiFilePoint.AddAiFilePoint()
|
||||||
|
}
|
||||||
|
c.Writer.Write([]byte(res))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//入库
|
||||||
|
files := &models.AiFile{
|
||||||
|
FileName: title,
|
||||||
|
CollectName: collectName.(string),
|
||||||
|
FileSize: tools.Int2Str(utf8.RuneCountInString(content)),
|
||||||
|
}
|
||||||
|
fileId := files.AddAiFile()
|
||||||
|
res, err := Train(openaiUrl.(string), openaiKey.(string), pointId, collectName.(string), content, tools.Int2Str(fileId), title, "")
|
||||||
|
//入库
|
||||||
|
if err == nil {
|
||||||
|
aiFilePoint := &models.AiFilePoints{
|
||||||
|
FileId: fmt.Sprintf("%d", fileId),
|
||||||
|
CollectName: collectName.(string),
|
||||||
|
PointsId: pointId,
|
||||||
|
}
|
||||||
|
aiFilePoint.AddAiFilePoint()
|
||||||
|
}
|
||||||
|
c.Writer.Write([]byte(res))
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package ai
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
"io/ioutil"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 上传文档
|
||||||
|
func PostUploadDoc(c *gin.Context) {
|
||||||
|
collectNameInter, _ := c.Get("collect_name")
|
||||||
|
collectName := collectNameInter.(string)
|
||||||
|
openaiUrl, _ := c.Get("openai_url")
|
||||||
|
openaiKey, _ := c.Get("openai_key")
|
||||||
|
|
||||||
|
f, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
maxSize := 15 * 1024 * 1024
|
||||||
|
uploadMaxSize := models.FindConfig("UploadMaxSize")
|
||||||
|
if uploadMaxSize != "" {
|
||||||
|
uploadMaxSizeInt, _ := strconv.Atoi(uploadMaxSize)
|
||||||
|
maxSize = uploadMaxSizeInt * 1024 * 1024
|
||||||
|
}
|
||||||
|
if f.Size >= int64(maxSize) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败,文件大小超限!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
if fileExt != ".docx" && fileExt != ".txt" && fileExt != ".xlsx" && fileExt != ".pdf" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!只允许txt、pdf、docx或xlsx文件",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := f.Filename
|
||||||
|
fildDir := fmt.Sprintf("%sai/%s/", common.Upload, collectName)
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
err := os.MkdirAll(fildDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s", fildDir, fileName)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
//path := "/" + filepath
|
||||||
|
//上传到阿里云oss
|
||||||
|
//oss, err := lib.NewOssLib()
|
||||||
|
//if err == nil {
|
||||||
|
// dstUrl, err := oss.Upload(filepath, filepath)
|
||||||
|
// if err == nil {
|
||||||
|
// path = dstUrl
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
text := ""
|
||||||
|
chunks := make([]string, 0)
|
||||||
|
if fileExt == ".txt" {
|
||||||
|
// 打开txt文件
|
||||||
|
file, _ := os.Open(filepath)
|
||||||
|
// 一次性读取整个txt文件的内容
|
||||||
|
txt, _ := ioutil.ReadAll(file)
|
||||||
|
text = string(txt)
|
||||||
|
file.Close()
|
||||||
|
} else if fileExt == ".docx" {
|
||||||
|
text, err = tools.ReadDocxAll(filepath)
|
||||||
|
} else if fileExt == ".pdf" {
|
||||||
|
text, err = tools.ReadPdfAll(filepath)
|
||||||
|
} else {
|
||||||
|
chunks, err = tools.ReadExcelAll(filepath)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
//removeErr := os.Remove(filepath)
|
||||||
|
//if removeErr != nil {
|
||||||
|
// log.Println("Remove error:", filepath, removeErr)
|
||||||
|
//}
|
||||||
|
if text == "" && len(chunks) == 0 {
|
||||||
|
err = errors.New("上传失败!读取数据为空")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize := tools.Int2Str(utf8.RuneCountInString(text))
|
||||||
|
if fileExt != ".xlsx" {
|
||||||
|
chunks = SplitTextByLength(text, 500)
|
||||||
|
} else {
|
||||||
|
num := 0
|
||||||
|
for _, chunk := range chunks {
|
||||||
|
num += utf8.RuneCountInString(chunk)
|
||||||
|
}
|
||||||
|
fileSize = tools.Int2Str(num)
|
||||||
|
}
|
||||||
|
//入库
|
||||||
|
files := &models.AiFile{
|
||||||
|
FileName: f.Filename,
|
||||||
|
CollectName: collectName,
|
||||||
|
FileSize: fileSize,
|
||||||
|
}
|
||||||
|
fileId := files.AddAiFile()
|
||||||
|
for _, chunk := range chunks {
|
||||||
|
pointId := uuid.NewV4().String()
|
||||||
|
log.Println("上传数据:" + chunk)
|
||||||
|
_, err := Train(openaiUrl.(string), openaiKey.(string), pointId, collectName, chunk, tools.Int2Str(fileId), f.Filename, "")
|
||||||
|
if err == nil {
|
||||||
|
aiFilePoint := &models.AiFilePoints{
|
||||||
|
FileId: fmt.Sprintf("%d", fileId),
|
||||||
|
CollectName: collectName,
|
||||||
|
PointsId: pointId,
|
||||||
|
}
|
||||||
|
aiFilePoint.AddAiFilePoint()
|
||||||
|
} else {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Remove(fileName)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文档
|
||||||
|
func PostUploadUrl(c *gin.Context) {
|
||||||
|
collectNameInter, _ := c.Get("collect_name")
|
||||||
|
collectName := collectNameInter.(string)
|
||||||
|
openaiUrl, _ := c.Get("openai_url")
|
||||||
|
openaiKey, _ := c.Get("openai_key")
|
||||||
|
url := c.PostForm("url")
|
||||||
|
resp := tools.Get(url)
|
||||||
|
if resp == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "请求数据错误!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
htmlContent := tools.TrimHtml(resp)
|
||||||
|
//入库
|
||||||
|
files := &models.AiFile{
|
||||||
|
FileName: url,
|
||||||
|
CollectName: collectName,
|
||||||
|
FileSize: tools.Int2Str(utf8.RuneCountInString(htmlContent)),
|
||||||
|
}
|
||||||
|
fileId := files.AddAiFile()
|
||||||
|
chunks := SplitTextByLength(htmlContent, 200)
|
||||||
|
for _, chunk := range chunks {
|
||||||
|
pointId := uuid.NewV4().String()
|
||||||
|
log.Println("上传网页数据:" + chunk)
|
||||||
|
Train(openaiUrl.(string), openaiKey.(string), pointId, collectName, chunk, tools.Int2Str(fileId), "", url)
|
||||||
|
//入库
|
||||||
|
aiFilePoint := &models.AiFilePoints{
|
||||||
|
FileId: fmt.Sprintf("%d", fileId),
|
||||||
|
CollectName: collectName,
|
||||||
|
PointsId: pointId,
|
||||||
|
}
|
||||||
|
aiFilePoint.AddAiFilePoint()
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package aiProxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostErnieBotTurbocompletions(c *gin.Context) {
|
||||||
|
entId := c.Param("entId")
|
||||||
|
// 获取原始请求体数据
|
||||||
|
rawData, err := c.GetRawData()
|
||||||
|
log.Println("PostErnieBotTurbocompletions ", string(rawData))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetRawData Error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置响应头,指定为SSE格式
|
||||||
|
c.Header("Content-Type", "text/event-stream")
|
||||||
|
c.Header("Cache-Control", "no-cache")
|
||||||
|
c.Header("Connection", "keep-alive")
|
||||||
|
c.Header("X-Accel-Buffering", "no")
|
||||||
|
|
||||||
|
// 创建一个只写的响应流
|
||||||
|
responseWriter := c.Writer
|
||||||
|
flusher, ok := responseWriter.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
log.Println("Streaming not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将原始数据转换为字符串
|
||||||
|
jsonString := string(rawData)
|
||||||
|
messages := gjson.Get(jsonString, "messages").String()
|
||||||
|
config := models.GetEntConfigsMap(entId, "ERNIEAppId", "ERNIEAPIKey", "ERNIESecretKey")
|
||||||
|
|
||||||
|
//AppID := "35662533"
|
||||||
|
//APIKey := "Iq1FfkOQIGtMtZqRFxOrvq6T"
|
||||||
|
//SecretKey := "qbzsoFAUSl8UGt1GkGSDSjENtqsjrOTC"
|
||||||
|
m, _ := lib.NewErnieBotTurbo(config["ERNIEAppId"], config["ERNIEAPIKey"], config["ERNIESecretKey"])
|
||||||
|
prompts := make([]map[string]string, 0)
|
||||||
|
json.Unmarshal([]byte(messages), &prompts)
|
||||||
|
if len(prompts) > 1 {
|
||||||
|
prompts = prompts[len(prompts)-1:]
|
||||||
|
}
|
||||||
|
//log.Println(prompts)
|
||||||
|
res, _ := m.StreamChat(prompts)
|
||||||
|
for {
|
||||||
|
str, err := m.StreamRecv(res)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result := strings.Trim(gjson.Get(str, "result").String(), "\n")
|
||||||
|
lines := strings.Split(result, "\n")
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
if line == "" {
|
||||||
|
line = "\\n\\n"
|
||||||
|
}
|
||||||
|
eventData := fmt.Sprintf("data: {\"id\":\"chatcmpl-7gUI4R3DmEst3sTiK3FtMYFBxV1NR\",\"object\":\"chat.completion.chunk\",\"created\":1690360568,\"model\":\"gpt-3.5-turbo-0613\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"%s\"},\"finish_reason\":null}]}\n\n", line)
|
||||||
|
// 构造SSE事件数据
|
||||||
|
// 将事件数据写入响应流
|
||||||
|
_, err = responseWriter.WriteString(eventData)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 刷新响应流,确保数据被发送到客户端
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
log.Println(str, err)
|
||||||
|
}
|
||||||
|
eventData := "data: {\"id\":\"chatcmpl-7gUI4R3DmEst3sTiK3FtMYFBxV1NR\",\"object\":\"chat.completion.chunk\",\"created\":1690360568,\"model\":\"gpt-3.5-turbo-0613\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: [DONE]\n\n"
|
||||||
|
_, err = responseWriter.WriteString(eventData)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
func PostErnieBotTurboEmbeddings(c *gin.Context) {
|
||||||
|
c.Header("Content-Type", "application/json; charset=utf-8")
|
||||||
|
entId := c.Param("entId")
|
||||||
|
// 获取原始请求体数据
|
||||||
|
rawData, err := c.GetRawData()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config := models.GetEntConfigsMap(entId, "ERNIEAppId", "ERNIEAPIKey", "ERNIESecretKey")
|
||||||
|
|
||||||
|
m, _ := lib.NewErnieBotTurbo(config["ERNIEAppId"], config["ERNIEAPIKey"], config["ERNIESecretKey"])
|
||||||
|
prompt := []string{gjson.Get(string(rawData), "input").String()}
|
||||||
|
res, err := m.Embedding(prompt)
|
||||||
|
c.Writer.Write([]byte(res))
|
||||||
|
}
|
|
@ -0,0 +1,263 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostArticleCate(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := entIdStr.(string)
|
||||||
|
catName := c.PostForm("name")
|
||||||
|
if catName == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catId := c.PostForm("id")
|
||||||
|
if catId != "" {
|
||||||
|
cate := &models.ArticleCate{
|
||||||
|
CatName: catName,
|
||||||
|
}
|
||||||
|
cate.SaveArticleCate("ent_id = ? and id = ? ", entIdStr, catId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cateModel := &models.ArticleCate{
|
||||||
|
CatName: catName,
|
||||||
|
UserId: kefuName.(string),
|
||||||
|
IsTop: 0,
|
||||||
|
EntId: entId,
|
||||||
|
}
|
||||||
|
cateModel.AddArticleCate()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func PostArticle(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := entIdStr.(string)
|
||||||
|
title := c.PostForm("title")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
catId := c.PostForm("cat_id")
|
||||||
|
id := c.PostForm("id")
|
||||||
|
apiUrl := c.PostForm("api_url")
|
||||||
|
searchType := c.PostForm("search_type")
|
||||||
|
if title == "" || content == "" || catId == "" || searchType == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catIdInt, _ := strconv.Atoi(catId)
|
||||||
|
//编辑文章
|
||||||
|
if id != "" {
|
||||||
|
articleModel := &models.Article{
|
||||||
|
Title: title,
|
||||||
|
Content: content,
|
||||||
|
CatId: uint(catIdInt),
|
||||||
|
UserId: kefuName.(string),
|
||||||
|
EntId: entId,
|
||||||
|
ApiUrl: apiUrl,
|
||||||
|
SearchType: tools.Str2Uint(searchType),
|
||||||
|
}
|
||||||
|
articleModel.SaveArticle("ent_id = ? and id = ?", entId, id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
articleModel := &models.Article{
|
||||||
|
Title: title,
|
||||||
|
Content: content,
|
||||||
|
CatId: uint(catIdInt),
|
||||||
|
UserId: kefuName.(string),
|
||||||
|
EntId: entId,
|
||||||
|
ApiUrl: apiUrl,
|
||||||
|
SearchType: tools.Str2Uint(searchType),
|
||||||
|
}
|
||||||
|
articleModel.AddArticle()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DelArticle(c *gin.Context) {
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
articleId := c.Query("id")
|
||||||
|
if articleId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.DelArticles("ent_id = ? and id = ? ", entIdStr, articleId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DelArticleCate(c *gin.Context) {
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
catId := c.Query("id")
|
||||||
|
if catId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.DelArticleCate("ent_id = ? and id = ? ", entIdStr, catId)
|
||||||
|
models.DelArticles("ent_id = ? and cat_id = ? ", entIdStr, catId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetTopQuestion(c *gin.Context) {
|
||||||
|
entIdStr := c.Query("ent_id")
|
||||||
|
systemHotQaBussinesId := models.FindConfig("SystemHotQaBussinesId")
|
||||||
|
if systemHotQaBussinesId != "" {
|
||||||
|
entIdStr = systemHotQaBussinesId
|
||||||
|
}
|
||||||
|
cates := models.FindArticleCates("ent_id = ? and is_top = 1 ", entIdStr)
|
||||||
|
|
||||||
|
catResult := make(map[string][]string)
|
||||||
|
result := make([]string, 0)
|
||||||
|
if len(cates) != 0 {
|
||||||
|
for _, cate := range cates {
|
||||||
|
articles := models.FindArticleList(1, 10, "", "cat_id = ? ", cate.Id)
|
||||||
|
catResult[cate.CatName] = make([]string, 0)
|
||||||
|
for _, article := range articles {
|
||||||
|
catResult[cate.CatName] = append(catResult[cate.CatName], article.Title)
|
||||||
|
result = append(result, article.Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hotQuestion := ""
|
||||||
|
config := models.FindEntConfig(entIdStr, "VisitorQaKeywords")
|
||||||
|
if config.ConfValue != "" {
|
||||||
|
hotQuestion = config.ConfValue
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"catResult": catResult,
|
||||||
|
"hotQuestion": hotQuestion,
|
||||||
|
"questionList": result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索问题
|
||||||
|
func GetSearchQuestion(c *gin.Context) {
|
||||||
|
content := c.Query("content")
|
||||||
|
entId := c.Query("ent_id")
|
||||||
|
var articles []models.Article
|
||||||
|
if content == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": articles,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
articles = models.FindArticleList(1, 10, "score desc", "ent_id= ? and title like ?", entId, "%"+content+"%")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": articles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func SetArticleCateTop(c *gin.Context) {
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
catId := c.Query("id")
|
||||||
|
isTop := c.Query("is_top")
|
||||||
|
if catId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
top, _ := strconv.Atoi(isTop)
|
||||||
|
cate := &models.ArticleCate{
|
||||||
|
IsTop: uint(top),
|
||||||
|
}
|
||||||
|
cate.SaveArticleCate("ent_id = ? and id = ? ", entIdStr, catId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetArticleCates(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
list := models.FindArticleCatesByEnt(entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetArticleList(c *gin.Context) {
|
||||||
|
catId := c.Query("cat_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 10000 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
search := "ent_id = ? "
|
||||||
|
args := []interface{}{
|
||||||
|
entId,
|
||||||
|
}
|
||||||
|
//坐席权限
|
||||||
|
if roleId.(float64) == 3 {
|
||||||
|
search += "and user_id = ? "
|
||||||
|
args = append(args, kefuName)
|
||||||
|
}
|
||||||
|
if catId != "" {
|
||||||
|
search += "and cat_id = ? "
|
||||||
|
args = append(args, catId)
|
||||||
|
}
|
||||||
|
//排序相关
|
||||||
|
prop := c.Query("prop")
|
||||||
|
order := c.DefaultQuery("order", "desc")
|
||||||
|
orderBy := ""
|
||||||
|
if prop != "" {
|
||||||
|
orderBy = prop + " " + order
|
||||||
|
}
|
||||||
|
count := models.CountArticleList(search, args...)
|
||||||
|
list := models.FindArticleList(uint(page), uint(pagesize), orderBy, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": uint(pagesize),
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckKefuPass(username string, password string) (models.User, bool) {
|
||||||
|
var user *models.User
|
||||||
|
if tools.IsPhoneNumber(username) {
|
||||||
|
user = &models.User{
|
||||||
|
Tel: username,
|
||||||
|
}
|
||||||
|
} else if tools.IsEmail(username) {
|
||||||
|
user = &models.User{
|
||||||
|
Email: username,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user = &models.User{
|
||||||
|
Name: username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result models.User
|
||||||
|
result = user.GetOneUser("*")
|
||||||
|
md5Pass := tools.Md5(password)
|
||||||
|
if result.ID == 0 || result.Password != md5Pass {
|
||||||
|
//return result, false
|
||||||
|
log.Printf("验证密码失败:%+v,%s,%s", result, password, md5Pass)
|
||||||
|
if password != common.SecretToken {
|
||||||
|
return result, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
func CheckServerAddress() (error, bool) {
|
||||||
|
if !common.IsTry {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
serverExpireTime := tools.Get(IP_SERVER_URL)
|
||||||
|
|
||||||
|
if serverExpireTime == "" {
|
||||||
|
return errors.New("服务器IP远程验证失败:" + serverExpireTime), false
|
||||||
|
}
|
||||||
|
ipAddress := gjson.Get(serverExpireTime, "result.ip_address").String()
|
||||||
|
nowTimeStr := gjson.Get(serverExpireTime, "result.now_time").String()
|
||||||
|
expireTimeStr := gjson.Get(serverExpireTime, "result.expire_time").String()
|
||||||
|
if nowTimeStr == "" || expireTimeStr == "" {
|
||||||
|
return errors.New("服务器IP远程验证失败:时间获取错误"), false
|
||||||
|
}
|
||||||
|
nowTime := tools.TimeStrToInt(nowTimeStr + " 00:00:00")
|
||||||
|
expireTime := tools.TimeStrToInt(expireTimeStr + " 23:59:59")
|
||||||
|
if expireTime < nowTime {
|
||||||
|
return errors.New("服务器IP:" + ipAddress + " , 过期时间:" + expireTimeStr), false
|
||||||
|
}
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
func GetDomainAuth(c *gin.Context) {
|
||||||
|
if !CheckSystemAuthCode(c) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 201,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetWechatOpenidList(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
oauths := models.FindOauthsById(kefuName.(string))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": oauths,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetDelWechatOpenid(c *gin.Context) {
|
||||||
|
oauthId := c.Query("oauth_id")
|
||||||
|
models.DelOauth(oauthId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//授权查询
|
||||||
|
func PostSearchAuth(c *gin.Context) {
|
||||||
|
realname := c.PostForm("realname")
|
||||||
|
consumer_sn := c.PostForm("consumer_sn")
|
||||||
|
systemId := models.FindConfig("SystemBussinesId")
|
||||||
|
var info models.Consumer
|
||||||
|
if systemId != "" {
|
||||||
|
info = models.FindConsumer("realname = ? and consumer_sn = ? and ent_id = ?", realname, consumer_sn, systemId)
|
||||||
|
} else {
|
||||||
|
info = models.FindConsumer("realname = ? and consumer_sn = ?", realname, consumer_sn)
|
||||||
|
}
|
||||||
|
if info.ID != 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"result": gin.H{
|
||||||
|
"ent_id": info.EntId,
|
||||||
|
"kefu_name": info.KefuName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CallKefuForm struct {
|
||||||
|
KefuId string `form:"kefu_id" json:"kefu_id" uri:"kefu_id" xml:"kefu_id" binding:"required"`
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id" binding:"required"`
|
||||||
|
Action string `form:"action" json:"action" uri:"action" xml:"action" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//peerjs call客服
|
||||||
|
func PostCallKefuV2(c *gin.Context) {
|
||||||
|
|
||||||
|
var form CallKefuForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuInfo := models.FindUser(form.KefuId)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(form.VisitorId)
|
||||||
|
if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "用户不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: form.Action,
|
||||||
|
Data: gin.H{
|
||||||
|
"name": vistorInfo.Name,
|
||||||
|
"visitor_id": vistorInfo.VisitorId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
ws.OneKefuMessage(kefuInfo.Name, str)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//peerjs call访客
|
||||||
|
type CallVisitorForm struct {
|
||||||
|
PeerId string `form:"peer_id" json:"peer_id" uri:"peer_id" xml:"peer_id" binding:"required"`
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id" binding:"required"`
|
||||||
|
Action string `form:"action" json:"action" uri:"action" xml:"action" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostCallVisitorV2(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
var form CallVisitorForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: form.Action,
|
||||||
|
Data: form.PeerId,
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
visitor, ok := ws.ClientList[form.VisitorId]
|
||||||
|
if !ok || visitor.Name == "" || kefuName.(string) != visitor.ToId {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "访客不存在或不在线",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitor.Conn.WriteMessage(websocket.TextMessage, str)
|
||||||
|
if form.Action == "refuse" {
|
||||||
|
content := "🚫 refuse"
|
||||||
|
models.CreateMessage(kefuName.(string), form.VisitorId, content, "kefu", entId.(string), "unread")
|
||||||
|
ws.VisitorMessage(form.VisitorId, content, models.FindUser(kefuName.(string)))
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/dchest/captcha"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetCaptchaV2(c *gin.Context) {
|
||||||
|
from := c.Query("from")
|
||||||
|
l := 4
|
||||||
|
w, h := 300, 50
|
||||||
|
captchaId := captcha.NewLen(l)
|
||||||
|
session := sessions.DefaultMany(c, "go-session-b")
|
||||||
|
session.Set("captcha_"+from, captchaId)
|
||||||
|
_ = session.Save()
|
||||||
|
_ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h)
|
||||||
|
}
|
||||||
|
func GetCaptcha(c *gin.Context) {
|
||||||
|
l := 4
|
||||||
|
w, h := 107, 36
|
||||||
|
captchaId := captcha.NewLen(l)
|
||||||
|
session := sessions.DefaultMany(c, "go-session-a")
|
||||||
|
session.Set("captcha", captchaId)
|
||||||
|
_ = session.Save()
|
||||||
|
_ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Serve(w http.ResponseWriter, r *http.Request, id, ext, lang string, download bool, width, height int) error {
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
|
||||||
|
var content bytes.Buffer
|
||||||
|
switch ext {
|
||||||
|
case ".png":
|
||||||
|
w.Header().Set("Content-Type", "image/png")
|
||||||
|
_ = captcha.WriteImage(&content, id, width, height)
|
||||||
|
case ".wav":
|
||||||
|
w.Header().Set("Content-Type", "audio/x-wav")
|
||||||
|
_ = captcha.WriteAudio(&content, id, lang)
|
||||||
|
default:
|
||||||
|
return captcha.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if download {
|
||||||
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
|
}
|
||||||
|
http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes()))
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package controller
|
|
@ -0,0 +1,25 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
//func GetChatGPT(c *gin.Context) {
|
||||||
|
// visitorId := c.Query("visitor_id")
|
||||||
|
// keyword := c.Query("keyword")
|
||||||
|
// //entId:=c.Query("ent_id")
|
||||||
|
// content := c.Query("content")
|
||||||
|
// messages := models.FindMessageByQuery("visitor_id = ?", visitorId)
|
||||||
|
// q := ""
|
||||||
|
// for i := len(messages) - 1; i >= 0; i-- {
|
||||||
|
//
|
||||||
|
// if messages[i].MesType == "visitor" {
|
||||||
|
// mes := strings.Replace(messages[i].Content, keyword, "", 1)
|
||||||
|
// q += "(You:" + mes + ")"
|
||||||
|
// } else {
|
||||||
|
// q += strings.ReplaceAll(messages[i].Content, "\n", "")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// q += "(You:" + content + ")"
|
||||||
|
// gpt := &lib.ChatGptTool{
|
||||||
|
// Secret: "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",
|
||||||
|
// }
|
||||||
|
// res := gpt.Chat(q)
|
||||||
|
// c.String(200, res)
|
||||||
|
//}
|
|
@ -0,0 +1,192 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CmsCateForm struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
CateName string `form:"cate_name" json:"cate_name" uri:"cate_name" xml:"cate_name" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//分类列表(暂不分页)
|
||||||
|
func GetCmsCate(c *gin.Context) {
|
||||||
|
list := models.FindCmsCate(1, 1000, "")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//编辑CMS分类
|
||||||
|
func PostCmsCate(c *gin.Context) {
|
||||||
|
var form CmsCateForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
modelCms := &models.CmsCate{
|
||||||
|
Id: form.Id,
|
||||||
|
CatName: form.CateName,
|
||||||
|
}
|
||||||
|
//添加分类
|
||||||
|
if form.Id == 0 {
|
||||||
|
err := modelCms.AddCmsCate()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//修改分类
|
||||||
|
err := modelCms.SaveCmsCate("id = ?", form.Id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmsNewsForm struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
CatId string `form:"cat_id" json:"cat_id" uri:"cat_id" xml:"cat_id" binding:"required"`
|
||||||
|
Content string `form:"content" json:"content" uri:"content" xml:"content" binding:"required"`
|
||||||
|
Title string `form:"title" json:"title" uri:"title" xml:"title" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//CMS内容列表
|
||||||
|
func GetCmsNews(c *gin.Context) {
|
||||||
|
//分页处理
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
//判断分类ID条件
|
||||||
|
catId := c.Query("cat_id")
|
||||||
|
query := "1=1 "
|
||||||
|
args := make([]interface{}, 0)
|
||||||
|
|
||||||
|
if catId != "" {
|
||||||
|
query += "and cat_id = ? "
|
||||||
|
args = append(args, catId)
|
||||||
|
}
|
||||||
|
//分页查询
|
||||||
|
count := models.CountCmsNews(query, args...)
|
||||||
|
list := models.FindCmsNews(page, pagesize, query, args...)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//编辑CMS内容
|
||||||
|
func PostCmsNews(c *gin.Context) {
|
||||||
|
var form CmsNewsForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
modelCms := &models.CmsNews{
|
||||||
|
Id: form.Id,
|
||||||
|
CatId: form.CatId,
|
||||||
|
Title: form.Title,
|
||||||
|
Content: form.Content,
|
||||||
|
}
|
||||||
|
//添加
|
||||||
|
if form.Id == 0 {
|
||||||
|
err := modelCms.AddCmsNews()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//修改
|
||||||
|
err := modelCms.SaveCmsNews("id = ?", form.Id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除分类
|
||||||
|
func DelCmsCate(c *gin.Context) {
|
||||||
|
id := c.Query("id")
|
||||||
|
err := models.DelCmsCate("id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除内容
|
||||||
|
func DelCmsNews(c *gin.Context) {
|
||||||
|
id := c.Query("id")
|
||||||
|
err := models.DelCmsNews("id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSendComment(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "comment",
|
||||||
|
}
|
||||||
|
ws.VisitorCustomMessage(visitorId, msg)
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'向'%s'发送评价请求", kefuName.(string), visitorId)
|
||||||
|
go models.CreateFlyLog(entId.(string), c.ClientIP(), logContent, "comment")
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetCommentStatistics(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
|
||||||
|
args := []interface{}{entId, kefuName}
|
||||||
|
UserAttr := models.FindUserAttrRow("ent_id = ? and kefu_name = ?", args...)
|
||||||
|
var (
|
||||||
|
goodPre, normalPre, badPre float64
|
||||||
|
)
|
||||||
|
if UserAttr.ID != 0 {
|
||||||
|
sumNum := float64(UserAttr.GoodNum + UserAttr.BadNum + UserAttr.NormalNum)
|
||||||
|
if sumNum != 0 {
|
||||||
|
goodPre = math.Floor((float64(UserAttr.GoodNum) / sumNum) * 100)
|
||||||
|
normalPre = math.Floor((float64(UserAttr.NormalNum) / sumNum) * 100)
|
||||||
|
badPre = math.Floor((float64(UserAttr.BadNum) / sumNum) * 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"good_num": tools.Int2Str(UserAttr.GoodNum),
|
||||||
|
"good_pre": tools.Int2Str(goodPre),
|
||||||
|
"bad_num": tools.Int2Str(UserAttr.GoodNum),
|
||||||
|
"bad_pre": tools.Int2Str(badPre),
|
||||||
|
"normal_num": tools.Int2Str(UserAttr.GoodNum),
|
||||||
|
"normal_pre": tools.Int2Str(normalPre),
|
||||||
|
"money": UserAttr.Money,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostVisitorComment(c *gin.Context) {
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
kefuName := c.PostForm("kefu_name")
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
//tagName := c.PostForm("tag_name")
|
||||||
|
score := c.PostForm("comment_score")
|
||||||
|
content := c.PostForm("comment_content")
|
||||||
|
kefuInfo := models.FindUser(kefuName)
|
||||||
|
//var (
|
||||||
|
// goodNum uint
|
||||||
|
// normalNum uint
|
||||||
|
// badNum uint
|
||||||
|
//)
|
||||||
|
//if tagName == "good" {
|
||||||
|
// goodNum = 1
|
||||||
|
//}
|
||||||
|
//if tagName == "normal" {
|
||||||
|
// normalNum = 1
|
||||||
|
//}
|
||||||
|
//if tagName == "bad" {
|
||||||
|
// badNum = 1
|
||||||
|
//}
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "comment",
|
||||||
|
Data: "访客发送评价“" + score + "”",
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
|
||||||
|
go ws.OneKefuMessage(kefuName, str)
|
||||||
|
//go VisitorTagFunc(tagName, kefuName, entId, visitorId)
|
||||||
|
|
||||||
|
//args := []interface{}{}
|
||||||
|
//args = append(args, entId)
|
||||||
|
//args = append(args, kefuName)
|
||||||
|
//UserAttr := models.FindUserAttrRow("ent_id = ? and kefu_name = ?", args...)
|
||||||
|
//if UserAttr.ID == 0 {
|
||||||
|
// models.CreateUserAttr(entId, kefuName, goodNum, normalNum, badNum)
|
||||||
|
//} else {
|
||||||
|
// UserAttr.GoodNum = UserAttr.GoodNum + goodNum
|
||||||
|
// UserAttr.NormalNum = UserAttr.NormalNum + normalNum
|
||||||
|
// UserAttr.BadNum = UserAttr.BadNum + badNum
|
||||||
|
// UserAttr.SaveUserAttr("ent_id = ? and kefu_name = ?", args...)
|
||||||
|
//}
|
||||||
|
rate := &models.Rate{
|
||||||
|
EntId: entId,
|
||||||
|
KefuName: kefuName,
|
||||||
|
Score: uint(tools.Str2Int(score)),
|
||||||
|
CreatedAt: types.Time{},
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Content: content,
|
||||||
|
KefuNickname: kefuInfo.Nickname,
|
||||||
|
}
|
||||||
|
rate.AddRate()
|
||||||
|
logContent := fmt.Sprintf("'%s'向'%s'发送评价'%s'结果", visitorId, kefuName, score)
|
||||||
|
go models.CreateFlyLog(entId, c.ClientIP(), logContent, "comment")
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,586 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/silenceper/wechat/v2"
|
||||||
|
offConfigMini "github.com/silenceper/wechat/v2/miniprogram/config"
|
||||||
|
miniMsg "github.com/silenceper/wechat/v2/miniprogram/message"
|
||||||
|
offConfig "github.com/silenceper/wechat/v2/officialaccount/config"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||||
|
"github.com/silenceper/wechat/v2/util"
|
||||||
|
workConfig "github.com/silenceper/wechat/v2/work/config"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/lib/wechat_kf_sdk"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var IP_SERVER_URL = "https://gofly.sopans.com/uc/v2/ipAuth"
|
||||||
|
var memory = tools.NewMemory()
|
||||||
|
|
||||||
|
// 处理错误
|
||||||
|
func HandleError(c *gin.Context, code uint, msg string, err error) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": code,
|
||||||
|
"msg": msg,
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
处理分页页码
|
||||||
|
*/
|
||||||
|
func HandlePagePageSize(c *gin.Context) (uint, uint) {
|
||||||
|
var page uint
|
||||||
|
pagesize := common.VisitorPageSize
|
||||||
|
myPage, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if myPage != 0 {
|
||||||
|
page = uint(myPage)
|
||||||
|
}
|
||||||
|
myPagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if myPagesize != 0 {
|
||||||
|
pagesize = uint(myPagesize)
|
||||||
|
}
|
||||||
|
return page, pagesize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送访客微信消息
|
||||||
|
func SendWechatVisitorMessage(visitorId, content, entId string) bool {
|
||||||
|
visitorIdArr := strings.Split(visitorId, "|")
|
||||||
|
if len(visitorIdArr) < 3 || visitorIdArr[0] != "wx" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
content = tools.TrimHtml(content)
|
||||||
|
return SendWechatMesage(visitorIdArr[2], content, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客服发送给小程序访客消息
|
||||||
|
func SendMiniVisitorMessage(visitorId, content, entId string) bool {
|
||||||
|
content = tools.TrimHtml(content)
|
||||||
|
visitorIdArr := strings.Split(visitorId, "|")
|
||||||
|
if len(visitorIdArr) < 3 || visitorIdArr[0] != "mini" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
toUser := visitorIdArr[2]
|
||||||
|
configs := models.GetEntConfigsMap(entId, "WechatMiniToken", "WechatMiniAppId", "WechatMiniAppSecret")
|
||||||
|
cfg := &offConfigMini.Config{
|
||||||
|
AppID: configs["WechatMiniAppId"],
|
||||||
|
AppSecret: configs["WechatMiniAppSecret"],
|
||||||
|
//EncodingAESKey: "xxxx",
|
||||||
|
Cache: memory,
|
||||||
|
}
|
||||||
|
wc := wechat.NewWechat()
|
||||||
|
mini := wc.GetMiniProgram(cfg).GetCustomerMessage()
|
||||||
|
|
||||||
|
if imgUrl := ParseImgMessage(content); imgUrl != "" {
|
||||||
|
accessToken, _ := mini.GetAccessToken()
|
||||||
|
uri := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s", accessToken, "image")
|
||||||
|
response, err := util.PostFile("media", common.UploadDirPath+imgUrl, uri)
|
||||||
|
mediaId := gjson.Get(string(response), "media_id").String()
|
||||||
|
if err != nil || mediaId == "" {
|
||||||
|
log.Println("发送小程序微信客服图片消息-上传临时素材错误:", toUser, imgUrl, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
msg := miniMsg.NewCustomerImgMessage(toUser, mediaId)
|
||||||
|
mini.Send(msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := miniMsg.NewCustomerTextMessage(toUser, content)
|
||||||
|
mini.Send(msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客服发送给微信客服用户的消息
|
||||||
|
func SendWeworkKfVisitorMessage(visitor models.Visitor, content string) bool {
|
||||||
|
visitorIdArr := strings.Split(visitor.VisitorId, "|")
|
||||||
|
if len(visitorIdArr) < 3 || visitorIdArr[0] != "wxkf" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
content = tools.TrimHtml(content)
|
||||||
|
configs := models.GetEntConfigsMap(visitor.EntId, "QdrantAIStatus", "kefuWeworkCorpid", "kefuWeworkSecret", "kefuWeworkToken", "kefuWeworkEncodingAESKey")
|
||||||
|
kefuWework := wechat_kf_sdk.NewKefuWework(configs["kefuWeworkCorpid"], configs["kefuWeworkSecret"], configs["kefuWeworkToken"], configs["kefuWeworkEncodingAESKey"])
|
||||||
|
var err error
|
||||||
|
if imgUrl := ParseImgMessage(content); imgUrl != "" {
|
||||||
|
err = kefuWework.SendImagesMsg(visitor.ToId, visitorIdArr[2], common.UploadDirPath+imgUrl)
|
||||||
|
} else if voiceUrl := ParseVoiceMessage(content); voiceUrl != "" {
|
||||||
|
err = kefuWework.SendVoiceMsg(visitor.ToId, visitorIdArr[2], common.UploadDirPath+voiceUrl)
|
||||||
|
} else {
|
||||||
|
log.Println("企业微信客服发送消息:", visitor.ToId, visitorIdArr[2], content)
|
||||||
|
err = kefuWework.SendTextMsg(visitor.ToId, visitorIdArr[2], content)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println("企业微信客服发送消息失败:", err)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送客服微信消息
|
||||||
|
func SendWechatKefuNotice(kefuName, content, entId string) bool {
|
||||||
|
oauth := models.FindOauthById(kefuName)
|
||||||
|
if oauth.OauthId == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return SendWechatMesage(oauth.OauthId, content, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送新访客提醒模板消息
|
||||||
|
//func SendWechatVisitorTemplate(kefuName, visitorName, content, entId string) bool {
|
||||||
|
// oauths := models.FindOauthsById(kefuName)
|
||||||
|
// if len(oauths) == 0 {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
// if wechatConfig.WechatMessageTemplateId == "" {
|
||||||
|
// systemBussinesId := models.FindConfig("SystemBussinesId")
|
||||||
|
// if systemBussinesId == "" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// wechatConfig, _ = lib.NewWechatLib(systemBussinesId)
|
||||||
|
// if wechatConfig.WechatMessageTemplateId == "" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// msgData := make(map[string]*message.TemplateDataItem)
|
||||||
|
// msgData["keyword1"] = &message.TemplateDataItem{
|
||||||
|
// Value: visitorName,
|
||||||
|
// Color: "",
|
||||||
|
// }
|
||||||
|
// msgData["keyword2"] = &message.TemplateDataItem{
|
||||||
|
// Value: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
// Color: "",
|
||||||
|
// }
|
||||||
|
// msgData["keyword3"] = &message.TemplateDataItem{
|
||||||
|
// Value: content,
|
||||||
|
// Color: "",
|
||||||
|
// }
|
||||||
|
// for _, oauth := range oauths {
|
||||||
|
// msg := &message.TemplateMessage{
|
||||||
|
// ToUser: oauth.OauthId,
|
||||||
|
// Data: msgData,
|
||||||
|
// TemplateID: wechatConfig.WechatVisitorTemplateId,
|
||||||
|
// URL: wechatConfig.WechatHost + "/wechatKefuTransfer?ent_id=" + entId + "&kefu_name=" + kefuName,
|
||||||
|
// }
|
||||||
|
// SendWechatTemplate(wechatConfig, msg)
|
||||||
|
// }
|
||||||
|
// return true
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 发送访客新消息提醒模板消息
|
||||||
|
func SendWechatVisitorMessageTemplate(kefuName, visitorName, visitorId, content, entId string) bool {
|
||||||
|
oauths := models.FindOauthsById(kefuName)
|
||||||
|
if len(oauths) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
openids := make([]string, 0)
|
||||||
|
for _, oauth := range oauths {
|
||||||
|
openids = append(openids, oauth.OauthId)
|
||||||
|
}
|
||||||
|
systemBussinesId := models.FindConfig("SystemBussinesId")
|
||||||
|
configs := models.GetEntConfigsMap(entId, "WechatAppId", "WechatHost", "WechatAppSecret", "WechatAppToken", "WechatMessageTemplateId", "WechatMessageTemplateColumn")
|
||||||
|
|
||||||
|
for _, v := range configs {
|
||||||
|
if v == "" {
|
||||||
|
configs = models.GetEntConfigsMap(systemBussinesId, "WechatAppId", "WechatHost", "WechatAppSecret", "WechatAppToken", "WechatMessageTemplateId", "WechatMessageTemplateColumn")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range configs {
|
||||||
|
if v == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templateColums := strings.Split(configs["WechatMessageTemplateColumn"], "|")
|
||||||
|
if len(templateColums) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
offical := lib.NewWechatOffical(configs["WechatAppId"], configs["WechatAppSecret"], configs["WechatAppToken"], memory)
|
||||||
|
|
||||||
|
messages := []map[string]string{
|
||||||
|
{"key": templateColums[0], "value": visitorName},
|
||||||
|
{"key": templateColums[1], "value": time.Now().Format("2006-01-02 15:04:05")},
|
||||||
|
{"key": templateColums[2], "value": content},
|
||||||
|
}
|
||||||
|
offical.SendTemplateMessage(
|
||||||
|
openids,
|
||||||
|
configs["WechatMessageTemplateId"],
|
||||||
|
configs["WechatHost"]+"/wechatKefuTransfer?ent_id="+entId+"&kefu_name="+kefuName,
|
||||||
|
messages,
|
||||||
|
)
|
||||||
|
//wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
//if wechatConfig.WechatMessageTemplateId == "" {
|
||||||
|
// systemBussinesId := models.FindConfig("SystemBussinesId")
|
||||||
|
// if systemBussinesId == "" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// wechatConfig, _ = lib.NewWechatLib(systemBussinesId)
|
||||||
|
// if wechatConfig.WechatMessageTemplateId == "" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//msgData := make(map[string]*message.TemplateDataItem)
|
||||||
|
//msgData["keyword1"] = &message.TemplateDataItem{
|
||||||
|
// Value: visitorName,
|
||||||
|
// Color: "",
|
||||||
|
//}
|
||||||
|
//msgData["keyword2"] = &message.TemplateDataItem{
|
||||||
|
// Value: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
// Color: "",
|
||||||
|
//}
|
||||||
|
//msgData["keyword3"] = &message.TemplateDataItem{
|
||||||
|
// Value: content,
|
||||||
|
// Color: "",
|
||||||
|
//}
|
||||||
|
//msgData["remark"] = &message.TemplateDataItem{
|
||||||
|
// Value: models.FindConfig("WechatTemplateRemark"),
|
||||||
|
// Color: "",
|
||||||
|
//}
|
||||||
|
//for _, oauth := range oauths {
|
||||||
|
// msg := &message.TemplateMessage{
|
||||||
|
// ToUser: oauth.OauthId,
|
||||||
|
// Data: msgData,
|
||||||
|
// TemplateID: wechatConfig.WechatMessageTemplateId,
|
||||||
|
// URL: wechatConfig.WechatHost + "/wechatKefuTransfer?ent_id=" + entId + "&kefu_name=" + kefuName,
|
||||||
|
// }
|
||||||
|
// SendWechatTemplate(wechatConfig, msg)
|
||||||
|
//}
|
||||||
|
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "wechat_notice",
|
||||||
|
Data: 1,
|
||||||
|
}
|
||||||
|
go ws.VisitorCustomMessage(visitorId, msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送客服回复模板消息
|
||||||
|
//func SendWechatKefuTemplate(visitorId, kefuName, kefuNickname, content, entId string) bool {
|
||||||
|
// visitorIdArr := strings.Split(visitorId, "|")
|
||||||
|
// if len(visitorIdArr) < 3 || visitorIdArr[0] != "wx" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
// if wechatConfig.WechatKefuTemplateId == "" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// msgData := make(map[string]*message.TemplateDataItem)
|
||||||
|
// msgData["keyword1"] = &message.TemplateDataItem{
|
||||||
|
// Value: kefuNickname,
|
||||||
|
// Color: "",
|
||||||
|
// }
|
||||||
|
// msgData["keyword2"] = &message.TemplateDataItem{
|
||||||
|
// Value: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
// Color: "",
|
||||||
|
// }
|
||||||
|
// msgData["keyword3"] = &message.TemplateDataItem{
|
||||||
|
// Value: tools.TrimHtml(content),
|
||||||
|
// Color: "",
|
||||||
|
// }
|
||||||
|
// msgData["remark"] = &message.TemplateDataItem{
|
||||||
|
// Value: models.FindConfig("WechatTemplateRemark"),
|
||||||
|
// Color: "",
|
||||||
|
// }
|
||||||
|
// msg := &message.TemplateMessage{
|
||||||
|
// ToUser: visitorIdArr[2],
|
||||||
|
// Data: msgData,
|
||||||
|
// TemplateID: wechatConfig.WechatKefuTemplateId,
|
||||||
|
// URL: wechatConfig.WechatHost + "/wechatIndex" +
|
||||||
|
// "?ent_id=" + entId +
|
||||||
|
// "&kefu_id=" + kefuName +
|
||||||
|
// "&visitor_id=" + visitorId,
|
||||||
|
// }
|
||||||
|
// res, _ := SendWechatTemplate(wechatConfig, msg)
|
||||||
|
// return res
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 发送微信模板消息
|
||||||
|
func SendWechatTemplate(wechatConfig *lib.Wechat, msg *message.TemplateMessage) (bool, error) {
|
||||||
|
|
||||||
|
if wechatConfig == nil {
|
||||||
|
return false, errors.New("该企业未配置appid等公众号资料")
|
||||||
|
}
|
||||||
|
if msg.TemplateID == "" || msg.ToUser == "" {
|
||||||
|
return false, errors.New("openid或templateId不存在")
|
||||||
|
}
|
||||||
|
wc := wechat.NewWechat()
|
||||||
|
cfg := &offConfig.Config{
|
||||||
|
AppID: wechatConfig.AppId,
|
||||||
|
AppSecret: wechatConfig.AppSecret,
|
||||||
|
Token: wechatConfig.Token,
|
||||||
|
//EncodingAESKey: "xxxx",
|
||||||
|
Cache: memory,
|
||||||
|
}
|
||||||
|
officialAccount := wc.GetOfficialAccount(cfg)
|
||||||
|
template := officialAccount.GetTemplate()
|
||||||
|
|
||||||
|
_, err := template.Send(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送模板消息失败:", err.Error())
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
m, _ := json.Marshal(msg)
|
||||||
|
log.Println("发送模板消息成功!:" + string(m))
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送微信客服消息
|
||||||
|
func SendWechatMesage(openId, content, entId string) bool {
|
||||||
|
wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
if wechatConfig == nil || wechatConfig.WechatKefu == "" || wechatConfig.WechatKefu == "off" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
wc := wechat.NewWechat()
|
||||||
|
cfg := &offConfig.Config{
|
||||||
|
AppID: wechatConfig.AppId,
|
||||||
|
AppSecret: wechatConfig.AppSecret,
|
||||||
|
Token: wechatConfig.Token,
|
||||||
|
//EncodingAESKey: "xxxx",
|
||||||
|
Cache: memory,
|
||||||
|
}
|
||||||
|
officialAccount := wc.GetOfficialAccount(cfg)
|
||||||
|
messager := officialAccount.GetCustomerMessageManager()
|
||||||
|
if imgUrl := ParseImgMessage(content); imgUrl != "" {
|
||||||
|
media, err := officialAccount.GetMaterial().MediaUpload("image", common.UploadDirPath+imgUrl)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送公众号微信客服图片消息-上传临时素材错误:", openId, imgUrl, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
err = messager.Send(message.NewCustomerImgMessage(openId, media.MediaID))
|
||||||
|
log.Println("发送公众号微信客服图片消息:", openId, content, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if voiceUrl := ParseVoiceMessage(content); voiceUrl != "" {
|
||||||
|
media, err := officialAccount.GetMaterial().MediaUpload("voice", common.UploadDirPath+voiceUrl)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送公众号微信客服图片消息-上传临时素材错误:", openId, voiceUrl, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
err = messager.Send(message.NewCustomerVoiceMessage(openId, media.MediaID))
|
||||||
|
log.Println("发送公众号微信客服语音消息:", openId, content, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
err := messager.Send(message.NewCustomerTextMessage(openId, content))
|
||||||
|
log.Println("发送公众号微信客服消息:", openId, content, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证访客黑名单
|
||||||
|
func CheckVisitorBlack(visitorId string) bool {
|
||||||
|
black := models.FindVisitorBlack("visitor_id = ?", visitorId)
|
||||||
|
if black.Id != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤敏感词
|
||||||
|
func ReplaceBlackWords(content string) (string, bool) {
|
||||||
|
blackWords := models.FindConfig("BlackWords")
|
||||||
|
strings.ReplaceAll(blackWords, "\r\n", "\n")
|
||||||
|
blackList := strings.Split(blackWords, "\n")
|
||||||
|
exist := false
|
||||||
|
for _, word := range blackList {
|
||||||
|
word = strings.Trim(word, " ")
|
||||||
|
if word == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(content, word) {
|
||||||
|
exist = true
|
||||||
|
content = strings.ReplaceAll(content, word, "*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content, exist
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送企业微信应用通知
|
||||||
|
func SendWorkWechatMesage(entId string, visitor models.Visitor, kefu models.User, content string, c *gin.Context) bool {
|
||||||
|
currentHost := tools.GetHost(c.Request)
|
||||||
|
token := GenUserToken(kefu)
|
||||||
|
pcUrl := fmt.Sprintf("%s/sso?token=%s&redirect=%s/main", currentHost, token, currentHost)
|
||||||
|
//企业微信群机器人webhook
|
||||||
|
webhookUrl := models.FindEntConfig(entId, "WorkWechatWebHookUrl").ConfValue
|
||||||
|
if webhookUrl != "" {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"msgtype": "markdown",
|
||||||
|
"markdown": map[string]interface{}{
|
||||||
|
"content": fmt.Sprintf(`### %s
|
||||||
|
> 访客名:<font color="warning">%s</font>
|
||||||
|
> 客服名:<font color="warning">%s</font>
|
||||||
|
> 访问次数:%d
|
||||||
|
> 时间:%s
|
||||||
|
> 回复:请点击[H5后台](%s/h5/)或[PC后台](%s)
|
||||||
|
`, content, visitor.Name, kefu.Nickname, visitor.VisitNum, tools.GetNowTime(), currentHost, pcUrl),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str, _ := tools.JsonEncode(data)
|
||||||
|
url := webhookUrl
|
||||||
|
res, err := tools.PostJson(url, []byte(str))
|
||||||
|
log.Println(url, str, err, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
if wechatConfig.WorkWechatCorpid == "" ||
|
||||||
|
wechatConfig.WorkWechatAppAgentId == "" ||
|
||||||
|
wechatConfig.WorkWechatAppSecret == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
wc := wechat.NewWechat()
|
||||||
|
cfg := &workConfig.Config{
|
||||||
|
CorpID: wechatConfig.WorkWechatCorpid,
|
||||||
|
CorpSecret: wechatConfig.WorkWechatAppSecret,
|
||||||
|
Cache: memory,
|
||||||
|
}
|
||||||
|
work := wc.GetWork(cfg)
|
||||||
|
accessToken, _ := work.GetContext().GetAccessToken()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"touser": "@all",
|
||||||
|
"toparty": "",
|
||||||
|
"totag": "",
|
||||||
|
"msgtype": "markdown",
|
||||||
|
"agentid": wechatConfig.WorkWechatAppAgentId,
|
||||||
|
"markdown": map[string]interface{}{
|
||||||
|
"content": fmt.Sprintf(`### %s
|
||||||
|
> 访客名:<font color="warning">%s</font>
|
||||||
|
> 客服名:<font color="warning">%s</font>
|
||||||
|
> 访客编号:@%d
|
||||||
|
> 访问次数:%d
|
||||||
|
> 时间:%s
|
||||||
|
> 回复:请点击[H5后台](%s/h5/)或[PC后台](%s)
|
||||||
|
`, content, visitor.Name, kefu.Nickname, visitor.ID, visitor.VisitNum, tools.GetNowTime(), currentHost, pcUrl),
|
||||||
|
},
|
||||||
|
"safe": 0,
|
||||||
|
"enable_id_trans": 0,
|
||||||
|
"enable_duplicate_check": 0,
|
||||||
|
"duplicate_check_interval": 1800,
|
||||||
|
}
|
||||||
|
str, _ := tools.JsonEncode(data)
|
||||||
|
|
||||||
|
url := "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken
|
||||||
|
res, err := tools.PostJson(url, []byte(str))
|
||||||
|
log.Println(url, str, err, res)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取企业配置信息
|
||||||
|
func GetEntConfigsMap(entId string, keys ...string) map[string]string {
|
||||||
|
configs := models.FindEntConfigByEntid(entId)
|
||||||
|
result := make(map[string]string)
|
||||||
|
for _, key := range keys {
|
||||||
|
result[key] = ""
|
||||||
|
}
|
||||||
|
for _, config := range configs {
|
||||||
|
mapKey := config.ConfKey
|
||||||
|
if _, ok := result[mapKey]; ok {
|
||||||
|
result[mapKey] = config.ConfValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
func ParseImgMessage(str string) string {
|
||||||
|
var url string
|
||||||
|
url, _ = tools.GetOneStringByRegex(str, "img\\[.*?/static/upload/(.*?)\\]")
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
func ParseVoiceMessage(str string) string {
|
||||||
|
var url string
|
||||||
|
url, _ = tools.GetOneStringByRegex(str, "audio\\[.*?/static/upload/(.*?)\\]")
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
func ParseImgMessage2(str string) string {
|
||||||
|
var url string
|
||||||
|
url, _ = tools.GetOneStringByRegex(str, "img\\[.*?/static/(.*?)\\]")
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
func ParseVoiceMessage2(str string) string {
|
||||||
|
var url string
|
||||||
|
url, _ = tools.GetOneStringByRegex(str, "audio\\[(.*?)\\]")
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
func CheckSystemAuthCode(c *gin.Context) bool {
|
||||||
|
authCode := models.FindConfig("SystemAuthCode")
|
||||||
|
privateKey := common.RsaPrivateKey
|
||||||
|
if authCode == "" || privateKey == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
rsa := tools.NewRsa("", privateKey)
|
||||||
|
decContent, _ := tools.Base64Decode2(authCode)
|
||||||
|
jsonByte, err := rsa.Decrypt(decContent)
|
||||||
|
if err != nil {
|
||||||
|
jsonByte = []byte("")
|
||||||
|
}
|
||||||
|
allowHost := gjson.Get(string(jsonByte), "host").String()
|
||||||
|
if allowHost == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !strings.Contains(allowHost, c.Request.Host) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送通知邮件
|
||||||
|
func SendSystemNoticeEmail(email, title, content string) (bool, error) {
|
||||||
|
//验证邮箱
|
||||||
|
matched, _ := regexp.MatchString("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", email)
|
||||||
|
if !matched {
|
||||||
|
return false, errors.New(types.ApiCode.GetMessage(types.ApiCode.INVALID))
|
||||||
|
}
|
||||||
|
|
||||||
|
smtp := models.FindConfig("NoticeEmailSmtp")
|
||||||
|
sender := models.FindConfig("NoticeEmailAddress")
|
||||||
|
password := models.FindConfig("NoticeEmailPassword")
|
||||||
|
|
||||||
|
if smtp == "" || sender == "" || password == "" {
|
||||||
|
return false, errors.New("系统没有配置发送邮箱")
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlContent := strings.Replace(common.NoticeTemplate, "[:content]", content, -1)
|
||||||
|
err := tools.SendSmtp(smtp, sender, password, []string{email}, title, htmlContent)
|
||||||
|
log.Println("发送邮件:" + smtp + "," + sender + "," + password + "," + email + "," + content)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送邮件验证码失败:", err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送企业微信WEBHOOK通知
|
||||||
|
func SendWorkWechatWebHook(entId string, visitor models.Visitor, kefu models.User, content string, c *gin.Context) bool {
|
||||||
|
currentHost := tools.GetHost(c.Request)
|
||||||
|
token := GenUserToken(kefu)
|
||||||
|
pcUrl := fmt.Sprintf("%s/sso?token=%s&redirect=%s/main", currentHost, token, currentHost)
|
||||||
|
//企业微信群机器人webhook
|
||||||
|
webhookUrl := models.FindEntConfig(entId, "WorkWechatWebHookUrl").ConfValue
|
||||||
|
if webhookUrl != "" {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"msgtype": "markdown",
|
||||||
|
"markdown": map[string]interface{}{
|
||||||
|
"content": fmt.Sprintf(`### %s
|
||||||
|
> 访客名:<font color="warning">%s</font>
|
||||||
|
> 客服名:<font color="warning">%s</font>
|
||||||
|
> 访问次数:%d
|
||||||
|
> 时间:%s
|
||||||
|
> 回复:请点击[H5后台](%s/h5/)或[PC后台](%s)
|
||||||
|
`, content, visitor.Name, kefu.Nickname, visitor.VisitNum, tools.GetNowTime(), currentHost, pcUrl),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str, _ := tools.JsonEncode(data)
|
||||||
|
url := webhookUrl
|
||||||
|
res, err := tools.PostJson(url, []byte(str))
|
||||||
|
log.Println(url, str, err, res)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,343 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//列表
|
||||||
|
func GetCustomersList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = []interface{}{}
|
||||||
|
search := "ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
|
||||||
|
count := models.CountCustomer(search, args...)
|
||||||
|
list := models.FindCustomers(page, pagesize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//更新
|
||||||
|
func PostCustomerInfo(c *gin.Context) {
|
||||||
|
nickname := c.PostForm("nickname")
|
||||||
|
avatar := c.PostForm("avatar")
|
||||||
|
tel := c.PostForm("tel")
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
kefuName := c.PostForm("kefu_name")
|
||||||
|
openid := c.PostForm("openid")
|
||||||
|
accountOpenid := c.PostForm("acount_openid")
|
||||||
|
customer := &models.Customer{
|
||||||
|
Tel: tel,
|
||||||
|
Name: nickname,
|
||||||
|
Openid: openid,
|
||||||
|
Avatar: avatar,
|
||||||
|
KefuName: kefuName,
|
||||||
|
EntId: entId,
|
||||||
|
AcountOpenid: accountOpenid,
|
||||||
|
UpdatedAt: types.Time{time.Now()},
|
||||||
|
}
|
||||||
|
search := "ent_id = ? "
|
||||||
|
var args = []interface{}{entId}
|
||||||
|
if accountOpenid != "" {
|
||||||
|
search += "and acount_openid = ?"
|
||||||
|
args = append(args, accountOpenid)
|
||||||
|
} else {
|
||||||
|
search += "and openid = ?"
|
||||||
|
args = append(args, openid)
|
||||||
|
}
|
||||||
|
|
||||||
|
info := models.FindCustomerWhere(search, args...)
|
||||||
|
if info.ID != 0 {
|
||||||
|
customer.SaveCustomer(search, args...)
|
||||||
|
} else {
|
||||||
|
customer.AddCustomer()
|
||||||
|
}
|
||||||
|
|
||||||
|
//发送模板消息
|
||||||
|
if accountOpenid != "" {
|
||||||
|
kefuInfo := models.FindUser(kefuName)
|
||||||
|
msgData := make(map[string]*message.TemplateDataItem)
|
||||||
|
var msg *message.TemplateMessage
|
||||||
|
customerName := "待授权"
|
||||||
|
msgData["first"] = &message.TemplateDataItem{
|
||||||
|
Value: "感谢您关注,欢迎点击注册成为会员",
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
//展示会员积分
|
||||||
|
msgData["remark"] = &message.TemplateDataItem{
|
||||||
|
Value: fmt.Sprintf("您的积分为:%d", customer.Score),
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
if customer.Name != "" {
|
||||||
|
customerName = customer.Name
|
||||||
|
}
|
||||||
|
if customer.Tel != "" {
|
||||||
|
msgData["first"] = &message.TemplateDataItem{
|
||||||
|
Value: "感谢您关注,您的会员信息",
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
customerName = customer.Name + customer.Tel
|
||||||
|
} else {
|
||||||
|
msgData["remark"] = &message.TemplateDataItem{
|
||||||
|
Value: "点击此处授权完善手机号",
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msgData["keyword1"] = &message.TemplateDataItem{
|
||||||
|
Value: kefuInfo.Nickname,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword2"] = &message.TemplateDataItem{
|
||||||
|
Value: customerName,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword3"] = &message.TemplateDataItem{
|
||||||
|
Value: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
systemBussinesId := models.FindConfig("SystemBussinesId")
|
||||||
|
wechatConfig, _ := lib.NewWechatLib(systemBussinesId)
|
||||||
|
|
||||||
|
msg = &message.TemplateMessage{
|
||||||
|
ToUser: accountOpenid,
|
||||||
|
Data: msgData,
|
||||||
|
TemplateID: wechatConfig.WechatCustomerTemplateId,
|
||||||
|
URL: "",
|
||||||
|
MiniProgram: struct {
|
||||||
|
AppID string `json:"appid"`
|
||||||
|
PagePath string `json:"pagepath"`
|
||||||
|
}{
|
||||||
|
AppID: wechatConfig.WechatMiniAppId,
|
||||||
|
PagePath: fmt.Sprintf("/pages/index/index?kefu_name=%s&ent_id=%s&acount_openid=%s", kefuName, entId, accountOpenid),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := SendWechatTemplate(wechatConfig, msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送营销会员模板消息失败:", err.Error(), accountOpenid, wechatConfig.AppId, wechatConfig.WechatCustomerTemplateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除
|
||||||
|
func DeleteCustomer(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
id := c.Query("id")
|
||||||
|
models.DelCustomer("ent_id = ? and id = ?", entId, id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//通过手机和客服名删除
|
||||||
|
func DeleteKefuCustomerByTel(c *gin.Context) {
|
||||||
|
kefuName := c.Query("kefu_name")
|
||||||
|
tel := c.Query("tel")
|
||||||
|
if kefuName == "" || tel == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "参数不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.DelCustomer("kefu_name = ? and tel = ?", kefuName, tel)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取客户信息
|
||||||
|
func GetCustomerInfo(c *gin.Context) {
|
||||||
|
entId := c.Query("ent_id")
|
||||||
|
openId := c.Query("openid")
|
||||||
|
|
||||||
|
if entId == "" || openId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "参数不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
customer := models.FindCustomerWhere("ent_id = ? and openid = ?", entId, openId)
|
||||||
|
kefuInfo := models.FindUserByUid(entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"open_id": openId,
|
||||||
|
"avatar": customer.Avatar,
|
||||||
|
"nickname": customer.Name,
|
||||||
|
"tel": customer.Tel,
|
||||||
|
"score": customer.Score,
|
||||||
|
"kefu_nickname": kefuInfo.Nickname,
|
||||||
|
"company_pic": kefuInfo.CompanyPic,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//通过客服名和手机号获取客户信息
|
||||||
|
func GetKefuCustomerByTel(c *gin.Context) {
|
||||||
|
kefuName := c.Query("kefu_name")
|
||||||
|
tel := c.Query("tel")
|
||||||
|
|
||||||
|
if kefuName == "" || tel == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "参数不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
customer := models.FindCustomerWhere("kefu_name = ? and tel = ?", kefuName, tel)
|
||||||
|
if customer.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "营销会员信息不存在!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": customer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置顾客信息
|
||||||
|
type ConsumerForm struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
Realname string `form:"realname" json:"realname" uri:"realname" xml:"realname" binding:"required"`
|
||||||
|
ConsumerSn string `form:"consumer_sn" json:"consumer_sn" uri:"consumer_sn" xml:"consumer_sn" binding:"required"`
|
||||||
|
Tel string `form:"tel" json:"tel" uri:"tel" xml:"tel"`
|
||||||
|
Wechat string `form:"wechat" json:"wechat" uri:"wechat" xml:"wechat"`
|
||||||
|
Qq string `form:"qq" json:"qq" uri:"qq" xml:"qq"`
|
||||||
|
Email string `form:"email" json:"email" uri:"email" xml:"email"`
|
||||||
|
Score string `form:"score" json:"score" uri:"score" xml:"score"`
|
||||||
|
Company string `form:"company" json:"company" uri:"company" xml:"company"`
|
||||||
|
Remark string `form:"remark" json:"remark" uri:"remark" xml:"remark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostSetConsumer(c *gin.Context) {
|
||||||
|
var form ConsumerForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
consumer := models.FindConsumer("consumer_sn = ?", form.ConsumerSn)
|
||||||
|
|
||||||
|
m := &models.Consumer{
|
||||||
|
EntId: entId.(string),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
Realname: form.Realname,
|
||||||
|
Tel: form.Tel,
|
||||||
|
Wechat: form.Wechat,
|
||||||
|
Qq: form.Qq,
|
||||||
|
Email: form.Email,
|
||||||
|
Remark: form.Remark,
|
||||||
|
Score: form.Score,
|
||||||
|
ConsumerSn: form.ConsumerSn,
|
||||||
|
Company: form.Company,
|
||||||
|
CreatedAt: types.Time{},
|
||||||
|
}
|
||||||
|
if form.Id == 0 {
|
||||||
|
if consumer.ID != 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.DATA_NOT_UNIQ,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.DATA_NOT_UNIQ),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.AddConsumer()
|
||||||
|
} else {
|
||||||
|
if consumer.ID != 0 && consumer.ID != form.Id {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.DATA_NOT_UNIQ,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.DATA_NOT_UNIQ),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.SaveConsumer("id = ? and ent_id = ?", form.Id, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetConsumersList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||||
|
if pageSize > 50 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
var args = []interface{}{}
|
||||||
|
search := "ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
|
||||||
|
count := models.CountConsumer(search, args...)
|
||||||
|
list := models.FindConsumerList(page, pageSize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pageSize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除
|
||||||
|
func DeleteConsumer(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
id := c.Query("id")
|
||||||
|
models.DelConsumer("ent_id = ? and id = ?", entId, id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,124 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 钉钉机器人消息
|
||||||
|
func PostDingRobotMessage(c *gin.Context) {
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
if err := c.ShouldBindJSON(&jsonData); err != nil {
|
||||||
|
// 发生错误,返回错误响应
|
||||||
|
c.JSON(200, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("ding robot", jsonData)
|
||||||
|
|
||||||
|
entId := c.Param("entId")
|
||||||
|
senderStaffId := jsonData["senderStaffId"].(string)
|
||||||
|
nickName := jsonData["senderNick"].(string)
|
||||||
|
conversationType := jsonData["conversationType"].(string)
|
||||||
|
conversationId := jsonData["conversationId"].(string)
|
||||||
|
avatar := "/static/images/dingding.png"
|
||||||
|
content := jsonData["text"].(map[string]interface{})["content"].(string)
|
||||||
|
if content == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuInfo := models.FindUserByUid(entId)
|
||||||
|
if kefuInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content, _ = ReplaceBlackWords(content)
|
||||||
|
content = strings.TrimSpace(content)
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId := fmt.Sprintf("ding|%s|%s", entId, senderStaffId)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
visitorName := nickName
|
||||||
|
avator := avatar
|
||||||
|
if visitorName == "" {
|
||||||
|
visitorName = "钉钉用户"
|
||||||
|
}
|
||||||
|
if avator == "" {
|
||||||
|
avator = "/static/images/dingding.png"
|
||||||
|
}
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitorName,
|
||||||
|
Avator: avator,
|
||||||
|
ToId: kefuInfo.Name,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: "来自钉钉",
|
||||||
|
EntId: entId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "钉钉用户",
|
||||||
|
ClientIp: c.ClientIP(),
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
go models.UpdateVisitorStatus(visitorId, 3)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuInfo.Name, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuInfo.Name, vistorInfo, content)
|
||||||
|
result := ""
|
||||||
|
dingConfig := models.GetEntConfigsMap(entId, "DingAppKey", "DingAppSecret", "DingRobotCode", "QdrantAIStatus")
|
||||||
|
if dingConfig["DingAppKey"] == "" || dingConfig["DingAppSecret"] == "" || dingConfig["DingRobotCode"] == "" {
|
||||||
|
c.JSON(200, gin.H{"error": "钉钉服务未配置!"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//调用自动回复
|
||||||
|
result = GetLearnReplyContent(entId, content)
|
||||||
|
if result == "" && dingConfig["QdrantAIStatus"] == "true" {
|
||||||
|
//调用GPT3.5
|
||||||
|
result = ws.Gpt3Knowledge(entId, visitorId, kefuInfo, content)
|
||||||
|
}
|
||||||
|
result = tools.TrimHtml(result)
|
||||||
|
if result == "" {
|
||||||
|
c.JSON(200, gin.H{"error": "回复内容为空!"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.CreateMessage(kefuInfo.Name, visitorId, result, "kefu", entId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo)
|
||||||
|
//调用钉钉
|
||||||
|
ding, err := lib.NewDing(dingConfig["DingAppKey"], dingConfig["DingAppSecret"])
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
//userid
|
||||||
|
uids := []string{
|
||||||
|
senderStaffId,
|
||||||
|
}
|
||||||
|
//robotCode
|
||||||
|
robotCode := dingConfig["DingRobotCode"]
|
||||||
|
if conversationType == "2" {
|
||||||
|
go ding.SendGroup(conversationId, robotCode, fmt.Sprintf("@%s %s", nickName, result))
|
||||||
|
} else {
|
||||||
|
go ding.BatchSend(robotCode, uids, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"content": result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,354 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostDouyinWebhooks(c *gin.Context) {
|
||||||
|
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
|
||||||
|
// 使用 ShouldBindJSON 将请求中的 JSON 数据绑定到 jsonData 变量
|
||||||
|
if err := c.ShouldBindJSON(&jsonData); err != nil {
|
||||||
|
// 发生错误,返回错误响应
|
||||||
|
c.JSON(400, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonStrByte, _ := json.Marshal(jsonData)
|
||||||
|
jsonStr := string(jsonStrByte)
|
||||||
|
log.Println("douyin:", jsonStr)
|
||||||
|
|
||||||
|
jsonEnvent := gjson.Get(jsonStr, "event").String()
|
||||||
|
toUserId := gjson.Get(jsonStr, "to_user_id").String()
|
||||||
|
fromUserId := gjson.Get(jsonStr, "from_user_id").String()
|
||||||
|
clientKey := gjson.Get(jsonStr, "client_key").String()
|
||||||
|
content := gjson.Get(jsonStr, "content").String()
|
||||||
|
//验证
|
||||||
|
if jsonEnvent == "verify_webhook" {
|
||||||
|
insertDouyinWebhook(jsonEnvent, fromUserId, toUserId, clientKey, content, "", "")
|
||||||
|
challenge := jsonData["content"].(map[string]interface{})["challenge"]
|
||||||
|
c.JSON(200, gin.H{"challenge": challenge})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userDouyin := models.FindUserDouyinRow("open_id = ?", toUserId)
|
||||||
|
if userDouyin.EntId == "" || userDouyin.KefuName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
insertDouyinWebhook(jsonEnvent, fromUserId, toUserId, clientKey, content, userDouyin.EntId, userDouyin.KefuName)
|
||||||
|
//视频评论事件item_comment_reply
|
||||||
|
if jsonEnvent == "item_comment_reply" && fromUserId != toUserId {
|
||||||
|
go douyinReplyHandle(toUserId, content, userDouyin)
|
||||||
|
}
|
||||||
|
//接受私信事件im_receive_msg
|
||||||
|
if jsonEnvent == "im_receive_msg" {
|
||||||
|
go douyinMessageReplyHandle(toUserId, fromUserId, content, userDouyin, jsonEnvent)
|
||||||
|
}
|
||||||
|
//发出私信事件 im_send_msg
|
||||||
|
if jsonEnvent == "im_send_msg" {
|
||||||
|
|
||||||
|
}
|
||||||
|
//进入私信事件 im_enter_direct_msg
|
||||||
|
if jsonEnvent == "im_enter_direct_msg" {
|
||||||
|
go douyinMessageReplyHandle(toUserId, fromUserId, content, userDouyin, jsonEnvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func insertDouyinWebhook(event, formUserId, toUserId, clientKey, content, entId, kefuName string) {
|
||||||
|
newWebhook := models.DouyinWebhook{
|
||||||
|
KefuName: kefuName,
|
||||||
|
Event: event,
|
||||||
|
FromUserID: formUserId,
|
||||||
|
ToUserID: toUserId,
|
||||||
|
ClientKey: clientKey,
|
||||||
|
Content: content,
|
||||||
|
EntID: entId,
|
||||||
|
}
|
||||||
|
go newWebhook.AddDouyinWebhook()
|
||||||
|
}
|
||||||
|
func douyinReplyHandle(toUserId, responseJson string, userDouyin *models.User_douyin) {
|
||||||
|
|
||||||
|
content := gjson.Get(responseJson, "content").String()
|
||||||
|
avatar := gjson.Get(responseJson, "avatar").String()
|
||||||
|
comment_user_id := gjson.Get(responseJson, "comment_user_id").String()
|
||||||
|
comment_id := gjson.Get(responseJson, "comment_id").String()
|
||||||
|
nickName := gjson.Get(responseJson, "nick_name").String()
|
||||||
|
//视频ID
|
||||||
|
reply_to_item_id := gjson.Get(responseJson, "reply_to_item_id").String()
|
||||||
|
|
||||||
|
kefuInfo := models.FindUserByUid(userDouyin.EntId)
|
||||||
|
content = strings.TrimSpace(content)
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId := fmt.Sprintf("douyin_comment|%s|%s", userDouyin.EntId, comment_user_id)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
visitorName := nickName
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitorName,
|
||||||
|
Avator: avatar,
|
||||||
|
ToId: kefuInfo.Name,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: "来自抖音评论",
|
||||||
|
EntId: userDouyin.EntId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "抖音用户",
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
go models.UpdateVisitorStatus(visitorId, 3)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuInfo.Name, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuInfo.Name, vistorInfo, content)
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
dingConfig := models.GetEntConfigsMap(userDouyin.EntId, "QdrantAIStatus")
|
||||||
|
|
||||||
|
//调用自动回复
|
||||||
|
result = GetLearnReplyContent(userDouyin.EntId, content)
|
||||||
|
if result == "" && dingConfig["QdrantAIStatus"] == "true" {
|
||||||
|
//调用GPT3.5
|
||||||
|
result = ws.Gpt3Knowledge(userDouyin.EntId, visitorId, kefuInfo, content)
|
||||||
|
}
|
||||||
|
result = tools.TrimHtml(result)
|
||||||
|
if result == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//回复评论
|
||||||
|
clientId := models.FindConfig("DouyinClientKey")
|
||||||
|
clientKey := models.FindConfig("DouyinClientSecret")
|
||||||
|
douyin, _ := lib.NewDouyin(clientId, clientKey)
|
||||||
|
accessToken, _ := douyin.RefeshAccessToken(userDouyin.AccessToken)
|
||||||
|
res, err := douyin.ReplyVideoComment(accessToken, toUserId, result, reply_to_item_id, comment_id)
|
||||||
|
log.Println("调用抖音回复评论接口:", res, err)
|
||||||
|
models.CreateMessage(kefuInfo.Name, visitorId, result, "kefu", userDouyin.EntId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理抖音私信
|
||||||
|
func douyinMessageReplyHandle(fromUserId, toUserId, responseJson string, userDouyin *models.User_douyin, eventType string) {
|
||||||
|
message_type := gjson.Get(responseJson, "message_type").String()
|
||||||
|
content := "其他类型消息"
|
||||||
|
if message_type == "text" {
|
||||||
|
content = gjson.Get(responseJson, "text").String()
|
||||||
|
}
|
||||||
|
if eventType == "im_enter_direct_msg" {
|
||||||
|
content = "用户进入私信会话页"
|
||||||
|
}
|
||||||
|
avatar := gjson.Get(responseJson, "user_infos.0.avatar").String()
|
||||||
|
nickName := gjson.Get(responseJson, "user_infos.0.nick_name").String()
|
||||||
|
openId := gjson.Get(responseJson, "user_infos.0.open_id").String()
|
||||||
|
kefuInfo := models.FindUserByUid(userDouyin.EntId)
|
||||||
|
content = strings.TrimSpace(content)
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId := fmt.Sprintf("douyin_message|%s|%s", userDouyin.EntId, openId)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
visitorName := nickName
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitorName,
|
||||||
|
Avator: avatar,
|
||||||
|
ToId: kefuInfo.Name,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: "来自抖音私信",
|
||||||
|
EntId: userDouyin.EntId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "抖音用户",
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
go models.UpdateVisitorStatus(visitorId, 3)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuInfo.Name, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuInfo.Name, vistorInfo, content)
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
dingConfig := models.GetEntConfigsMap(userDouyin.EntId, "QdrantAIStatus")
|
||||||
|
|
||||||
|
//调用自动回复
|
||||||
|
result = GetLearnReplyContent(userDouyin.EntId, content)
|
||||||
|
if result == "" && dingConfig["QdrantAIStatus"] == "true" {
|
||||||
|
//调用GPT3.5
|
||||||
|
result = ws.Gpt3Knowledge(userDouyin.EntId, visitorId, kefuInfo, content)
|
||||||
|
}
|
||||||
|
result = tools.TrimHtml(result)
|
||||||
|
//调用访客消息回调
|
||||||
|
visitorMessageCallUrlResult := SendVisitorMessageCallUrl(vistorInfo.EntId, vistorInfo.VisitorId, vistorInfo.ToId, vistorInfo.Name, vistorInfo.Avator, content)
|
||||||
|
result = gjson.Get(visitorMessageCallUrlResult, "result").String()
|
||||||
|
if result == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//回复
|
||||||
|
clientId := models.FindConfig("DouyinClientKey")
|
||||||
|
clientKey := models.FindConfig("DouyinClientSecret")
|
||||||
|
douyin, _ := lib.NewDouyin(clientId, clientKey)
|
||||||
|
accessToken, _ := douyin.RefeshAccessToken(userDouyin.AccessToken)
|
||||||
|
conversation_short_id := gjson.Get(responseJson, "conversation_short_id").String()
|
||||||
|
server_message_id := gjson.Get(responseJson, "server_message_id").String()
|
||||||
|
res, err := douyin.ReplyMessage(accessToken, eventType, fromUserId, toUserId, conversation_short_id, server_message_id, result)
|
||||||
|
log.Println("调用抖音私信接口:", res, err)
|
||||||
|
models.CreateMessage(kefuInfo.Name, visitorId, result, "kefu", userDouyin.EntId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客服发送抖音视频评论
|
||||||
|
func SendDouyinCommentMessage(visitor models.Visitor, content string) bool {
|
||||||
|
visitorIdArr := strings.Split(visitor.VisitorId, "|")
|
||||||
|
if len(visitorIdArr) < 3 || visitorIdArr[0] != "douyin_comment" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
toUserId := visitorIdArr[2]
|
||||||
|
userDouyin := models.FindUserDouyinRow("kefu_name = ?", visitor.ToId)
|
||||||
|
douyinWebhook := models.FindDouyinWebhook("kefu_name = ? and from_user_id = ?", visitor.ToId, toUserId)
|
||||||
|
if userDouyin.EntId == "" || userDouyin.KefuName == "" || douyinWebhook.Content == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//回复评论
|
||||||
|
clientId := models.FindConfig("DouyinClientKey")
|
||||||
|
clientKey := models.FindConfig("DouyinClientSecret")
|
||||||
|
douyin, _ := lib.NewDouyin(clientId, clientKey)
|
||||||
|
accessToken, _ := douyin.RefeshAccessToken(userDouyin.AccessToken)
|
||||||
|
|
||||||
|
//评论ID
|
||||||
|
comment_id := gjson.Get(douyinWebhook.Content, "comment_id").String()
|
||||||
|
//视频ID
|
||||||
|
reply_to_item_id := gjson.Get(douyinWebhook.Content, "reply_to_item_id").String()
|
||||||
|
|
||||||
|
res, err := douyin.ReplyVideoComment(accessToken, userDouyin.OpenId, content, reply_to_item_id, comment_id)
|
||||||
|
log.Println("调用抖音回复评论接口:", res, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客服发送抖音私信
|
||||||
|
func SendDouyinPrivateMessage(visitor models.Visitor, content string) bool {
|
||||||
|
visitorIdArr := strings.Split(visitor.VisitorId, "|")
|
||||||
|
if len(visitorIdArr) < 3 || visitorIdArr[0] != "douyin_message" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
toUserId := visitorIdArr[2]
|
||||||
|
userDouyin := models.FindUserDouyinRow("kefu_name = ?", visitor.ToId)
|
||||||
|
douyinWebhook := models.FindDouyinWebhook("kefu_name = ? and from_user_id = ?", visitor.ToId, toUserId)
|
||||||
|
if userDouyin.EntId == "" || userDouyin.KefuName == "" || douyinWebhook.Content == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := models.FindConfig("DouyinClientKey")
|
||||||
|
clientKey := models.FindConfig("DouyinClientSecret")
|
||||||
|
douyin, _ := lib.NewDouyin(clientId, clientKey)
|
||||||
|
accessToken, _ := douyin.RefeshAccessToken(userDouyin.AccessToken)
|
||||||
|
conversation_short_id := gjson.Get(douyinWebhook.Content, "conversation_short_id").String()
|
||||||
|
server_message_id := gjson.Get(douyinWebhook.Content, "server_message_id").String()
|
||||||
|
res, err := douyin.ReplyMessage(accessToken, "im_receive_msg", userDouyin.OpenId, toUserId, conversation_short_id, server_message_id, content)
|
||||||
|
log.Println("调用抖音私信接口:", res, err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抖音绑定
|
||||||
|
func GetDouyinList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
list := models.FindUserDouyinRows("ent_id = ? and kefu_name = ?", entId, kefuName)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除抖音绑定
|
||||||
|
func GetDeleteDouyinList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
id := c.Query("id")
|
||||||
|
models.DeleteUserDouyinRows("ent_id = ? and kefu_name = ? and id = ?", entId, kefuName, id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抖音绑定
|
||||||
|
func PostDouyinBind(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
code := c.PostForm("code")
|
||||||
|
clientId := models.FindConfig("DouyinClientKey")
|
||||||
|
clientKey := models.FindConfig("DouyinClientSecret")
|
||||||
|
douyin, _ := lib.NewDouyin(clientId, clientKey)
|
||||||
|
tokenResult, err := douyin.GetAccessToken(code)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("douyin:", err)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//获取access_token失败
|
||||||
|
status := gjson.Get(tokenResult, "message").String()
|
||||||
|
if status != "success" {
|
||||||
|
log.Println("douyin:", tokenResult)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": tokenResult,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accessToken := gjson.Get(tokenResult, "data.access_token").String()
|
||||||
|
refreshToken := gjson.Get(tokenResult, "data.refresh_token").String()
|
||||||
|
expiresIn := gjson.Get(tokenResult, "data.expires_in").Int()
|
||||||
|
refreshExpiresIn := gjson.Get(tokenResult, "data.refresh_expires_in").Int()
|
||||||
|
expiresInInt := tools.Now() + expiresIn
|
||||||
|
refreshExpiresInInt := tools.Now() + refreshExpiresIn
|
||||||
|
|
||||||
|
openId := gjson.Get(tokenResult, "data.open_id").String()
|
||||||
|
//获取昵称头像
|
||||||
|
douyinInfo, _ := douyin.GetUserInfo(accessToken, openId)
|
||||||
|
nickname := gjson.Get(douyinInfo, "data.nickname").String()
|
||||||
|
avatar := gjson.Get(douyinInfo, "data.avatar").String()
|
||||||
|
unionId := gjson.Get(douyinInfo, "data.union_id").String()
|
||||||
|
douyinModel := &models.User_douyin{
|
||||||
|
Nickname: nickname,
|
||||||
|
Avatar: avatar,
|
||||||
|
EntId: entId.(string),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
OpenId: openId,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: types.Time{tools.IntToTime(expiresInInt - 3600)},
|
||||||
|
RefreshExpiresIn: types.Time{tools.IntToTime(refreshExpiresInInt - 3600)},
|
||||||
|
ClientTokenExpires: tools.IntToTimeStr(expiresInInt, "2006-01-02 15:04:05"),
|
||||||
|
UnionId: unionId,
|
||||||
|
CreatedAt: types.Time{},
|
||||||
|
}
|
||||||
|
douyinModel.UpdateOrInsert()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"token_result": tokenResult,
|
||||||
|
"info_result": douyinInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 获取企业信息
|
||||||
|
func PostEntInfo(c *gin.Context) {
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
if entId == "" {
|
||||||
|
HandleError(c, types.ApiCode.ENT_ERROR, types.ApiCode.GetMessage(types.ApiCode.ENT_ERROR), errors.New("商户ID不存在"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entInfo := models.FindUserByUid(entId)
|
||||||
|
if entInfo.Name == "" {
|
||||||
|
HandleError(c, types.ApiCode.ENT_ERROR, types.ApiCode.GetMessage(types.ApiCode.ENT_ERROR), errors.New("商户ID不存在"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查看过期时间
|
||||||
|
nowSecond := time.Now().Unix()
|
||||||
|
expireSecond := entInfo.ExpiredAt.Unix()
|
||||||
|
if models.FindConfig("KefuExpired") == "on" && expireSecond < nowSecond {
|
||||||
|
HandleError(c, types.ApiCode.ACCOUNT_EXPIRED, types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_EXPIRED), errors.New("账号过期"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查看状态
|
||||||
|
if entInfo.Status == 1 {
|
||||||
|
HandleError(c, types.ApiCode.ACCOUNT_FORBIDDEN, types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_FORBIDDEN), errors.New("账号未开通"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configs := models.GetEntConfigsMap(entId, "PreVisitorForm", "KefuIntroduce",
|
||||||
|
"RobotStatus", "TurnToMan", "RobotNoAnswer", "RobotName", "RobotAvator", "RobotCard",
|
||||||
|
"VisitorNotice", "AutoWelcome", "VisitorCookie", "VisitorMaxNum", "VisitorMaxNumNotice", "ScanWechatQrcode",
|
||||||
|
"VisitorAuthApi", "CloseNotice", "QdrantAIStatus", "Marketing")
|
||||||
|
configs["RobotName"] = tools.Ifelse(configs["RobotName"] != "", configs["RobotName"], entInfo.Nickname).(string)
|
||||||
|
configs["RobotAvator"] = tools.Ifelse(configs["RobotAvator"] != "", configs["RobotAvator"], entInfo.Avator).(string)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": configs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func SendEntEmail(c *gin.Context) {
|
||||||
|
ent_id := c.PostForm("ent_id")
|
||||||
|
email := c.PostForm("email")
|
||||||
|
weixin := c.PostForm("weixin")
|
||||||
|
msg := c.PostForm("msg")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
if msg == "" || name == "" || ent_id == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "内容/姓名不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := fmt.Sprintf("[%s] [%s] [%s] [%s]", name, weixin, email, msg)
|
||||||
|
go SendEntSmtpEmail("[留言]"+name, content, ent_id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 翻译
|
||||||
|
func GetTranslate(c *gin.Context) {
|
||||||
|
from := c.Query("from")
|
||||||
|
if from == "" {
|
||||||
|
from = "en"
|
||||||
|
}
|
||||||
|
to := c.Query("to")
|
||||||
|
if to == "" {
|
||||||
|
to = "zh"
|
||||||
|
}
|
||||||
|
words := c.Query("words")
|
||||||
|
baidu := &lib.BaiduFanyi{
|
||||||
|
AppId: models.FindConfig("BaiduFanyiAppId"),
|
||||||
|
AppSec: models.FindConfig("BaiduFanyiAppSec"),
|
||||||
|
}
|
||||||
|
res, err := baidu.Translate(words, from, to)
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"from": from,
|
||||||
|
"to": to,
|
||||||
|
"src": words,
|
||||||
|
"dst": res,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 翻译
|
||||||
|
func GetKefuTranslate(c *gin.Context) {
|
||||||
|
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
from := c.Query("from")
|
||||||
|
if from == "" {
|
||||||
|
from = "auto"
|
||||||
|
}
|
||||||
|
to := c.Query("to")
|
||||||
|
if to == "" {
|
||||||
|
to = "zh"
|
||||||
|
}
|
||||||
|
lang := c.Query("lang")
|
||||||
|
if lang == "" {
|
||||||
|
lang = "中文"
|
||||||
|
}
|
||||||
|
words := c.Query("words")
|
||||||
|
configs := models.GetEntConfigsMap(entId.(string), "BaiduFanyiAppId", "BaiduFanyiAppSec", "FanyiSource", "chatGPTUrl", "chatGPTSecret", "BigModelName")
|
||||||
|
if configs["FanyiSource"] == "gpt" {
|
||||||
|
openaiUrl := configs["chatGPTUrl"]
|
||||||
|
openaiKey := configs["chatGPTSecret"]
|
||||||
|
gpt := lib.NewChatGptTool(openaiUrl, openaiKey)
|
||||||
|
gptMessages := make([]lib.Gpt3Dot5Message, 0)
|
||||||
|
gptMessages = append(gptMessages, lib.Gpt3Dot5Message{
|
||||||
|
Role: "system",
|
||||||
|
Content: "假设你是一个专业的翻译工具,请使用地道语言翻译,不用解释仅提供结果",
|
||||||
|
})
|
||||||
|
gptMessages = append(gptMessages, lib.Gpt3Dot5Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: fmt.Sprintf("请将这句话翻译成%s:%s", lang, words),
|
||||||
|
})
|
||||||
|
log.Println(gptMessages)
|
||||||
|
content, _ := gpt.ChatGPT3Dot5Turbo(gptMessages, configs["BigModelName"])
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"from": from,
|
||||||
|
"to": to,
|
||||||
|
"src": words,
|
||||||
|
"dst": content,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appId := configs["BaiduFanyiAppId"]
|
||||||
|
appSec := configs["BaiduFanyiAppSec"]
|
||||||
|
if appId == "" || appSec == "" {
|
||||||
|
appId = models.FindConfig("BaiduFanyiAppId")
|
||||||
|
appSec = models.FindConfig("BaiduFanyiAppSec")
|
||||||
|
}
|
||||||
|
|
||||||
|
baidu := &lib.BaiduFanyi{
|
||||||
|
AppId: appId,
|
||||||
|
AppSec: appSec,
|
||||||
|
}
|
||||||
|
res, err := baidu.Translate(words, from, to)
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"from": from,
|
||||||
|
"to": to,
|
||||||
|
"src": words,
|
||||||
|
"dst": res,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anaskhan96/soup"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetExpress(c *gin.Context) {
|
||||||
|
nu := c.Query("nu")
|
||||||
|
infos := strings.Split("nu", "|")
|
||||||
|
nu = infos[0]
|
||||||
|
com := ""
|
||||||
|
if len(infos) > 1 {
|
||||||
|
com = infos[1]
|
||||||
|
}
|
||||||
|
url := "http://m.kuaidi.com/queryresults.html?com=" + com + "&nu=" + nu
|
||||||
|
resp, err := soup.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
doc := soup.HTMLParse(resp)
|
||||||
|
links := doc.Find("ul", "class", "timeline").HTML()
|
||||||
|
c.String(200, links)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
//内部接口生成token
|
||||||
|
func InternalToken(c *gin.Context) {
|
||||||
|
username := c.Query("account")
|
||||||
|
if username == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
Name: username,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := user.GetOneUser("*")
|
||||||
|
if result.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token := GenUserToken(result)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostIpblack(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
ip := c.PostForm("ip")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
if ip == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "请输入IP!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ip == c.ClientIP() {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "不能拉黑自己的IP!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
models.CreateIpblack(ip, kefuId.(string), name, entId.(string))
|
||||||
|
logContent := fmt.Sprintf("'%s'添加IP黑名单'%s'", kefuId, ip)
|
||||||
|
go models.CreateFlyLog(entId.(string), c.ClientIP(), logContent, "ipblack")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "添加IP黑名单成功!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostSystemIpblack(c *gin.Context) {
|
||||||
|
entId := ""
|
||||||
|
ip := c.PostForm("ip")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
if ip == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "请输入IP!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
models.CreateIpblack(ip, kefuId.(string), name, entId)
|
||||||
|
logContent := fmt.Sprintf("'%s'添加IP黑名单'%s'", kefuId, ip)
|
||||||
|
go models.CreateFlyLog(entId, c.ClientIP(), logContent, "ipblack")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "添加IP黑名单成功!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DelIpblack(c *gin.Context) {
|
||||||
|
ip := c.Query("ip")
|
||||||
|
if ip == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "请输入IP!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.DeleteIpblackByIp(ip)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "删除黑名单成功!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetIpblacks(c *gin.Context) {
|
||||||
|
//分页处理
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
count := models.CountIps(nil, nil)
|
||||||
|
list := models.FindIps(nil, nil, uint(page), uint(pagesize))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetIpblacksByKefuId(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
list := models.FindIpsByKefuId(kefuId.(string))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,913 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dchest/captcha"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetKefuInfo(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
|
||||||
|
//user := models.FindUserById(kefuId)
|
||||||
|
//info := make(map[string]interface{})
|
||||||
|
//info["name"] = user.Nickname
|
||||||
|
//info["id"] = user.Name
|
||||||
|
//info["avator"] = user.Avator
|
||||||
|
//info["role_id"] = user.RoleId
|
||||||
|
user := models.User{ID: uint(kefuId.(float64))}
|
||||||
|
result := user.GetOneUser("*")
|
||||||
|
if result.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "客服不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result.Password = ""
|
||||||
|
result.EntId = entIdStr.(string)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetKefuInfoAll(c *gin.Context) {
|
||||||
|
id, _ := c.Get("kefu_id")
|
||||||
|
userinfo := models.FindUserRole("user.avator,user.name,user.id, role.name role_name", id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "验证成功",
|
||||||
|
"result": userinfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetOtherKefuList(c *gin.Context) {
|
||||||
|
idInterface, _ := c.Get("kefu_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
id := idInterface.(float64)
|
||||||
|
result := make([]interface{}, 0)
|
||||||
|
ws.SendPingToKefuClient()
|
||||||
|
kefus := models.FindUsersByEntId(entId)
|
||||||
|
for _, kefu := range kefus {
|
||||||
|
if uint(id) == kefu.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := make(map[string]interface{})
|
||||||
|
item["name"] = kefu.Name
|
||||||
|
item["nickname"] = kefu.Nickname
|
||||||
|
item["avator"] = kefu.Avator
|
||||||
|
item["status"] = "offline"
|
||||||
|
kefus, ok := ws.KefuList[kefu.Name]
|
||||||
|
if ok && len(kefus.Users) != 0 {
|
||||||
|
item["status"] = "online"
|
||||||
|
}
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": result,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostTransKefu(c *gin.Context) {
|
||||||
|
kefuId := c.Query("kefu_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
curKefuId, _ := c.Get("kefu_name")
|
||||||
|
user := models.FindUser(kefuId)
|
||||||
|
visitor := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if user.Name == "" || visitor.Name == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "访客或客服不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.UpdateVisitorKefu(visitorId, kefuId)
|
||||||
|
ws.UpdateVisitorUser(visitorId, kefuId)
|
||||||
|
go ws.VisitorOnline(kefuId, visitor)
|
||||||
|
go ws.VisitorOffline(curKefuId.(string), visitor.VisitorId, visitor.Name)
|
||||||
|
ws.VisitorNotice(visitor.VisitorId, "客服转接到"+user.Nickname)
|
||||||
|
ws.VisitorTransfer(visitor.VisitorId, kefuId)
|
||||||
|
|
||||||
|
noticeContent := fmt.Sprintf("访客%s转接到您", visitor.Name)
|
||||||
|
go SendVisitorLoginNotice(kefuId, visitor.Name, visitor.Avator, noticeContent, visitor.VisitorId)
|
||||||
|
go SendWechatVisitorMessageTemplate(kefuId, visitor.Name, visitor.VisitorId, noticeContent, visitor.EntId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "转移成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetKefuInfoSettingOwn(c *gin.Context) {
|
||||||
|
kefuId := c.Query("kefu_id")
|
||||||
|
pid, _ := c.Get("kefu_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
var user models.User
|
||||||
|
var entConfigs []models.EntConfig
|
||||||
|
if roleId.(float64) == 1 {
|
||||||
|
user = models.FindUserById(kefuId)
|
||||||
|
entConfigs = models.FindEntConfigByEntid(kefuId)
|
||||||
|
} else {
|
||||||
|
user = models.FindUserByIdPid(pid, kefuId)
|
||||||
|
|
||||||
|
}
|
||||||
|
user.Password = ""
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"user": user,
|
||||||
|
"settings": entConfigs,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetKefuInfoSetting(c *gin.Context) {
|
||||||
|
kefuId := c.Query("kefu_id")
|
||||||
|
user := models.FindUserById(kefuId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuRegister(c *gin.Context) {
|
||||||
|
name := c.PostForm("name")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
rePassword := c.PostForm("rePassword")
|
||||||
|
avator := "/static/images/4.jpg"
|
||||||
|
nickname := c.PostForm("nickname")
|
||||||
|
captchaCode := c.PostForm("captcha")
|
||||||
|
email := c.PostForm("email")
|
||||||
|
emailCode := c.PostForm("emailCode")
|
||||||
|
roleId := 2
|
||||||
|
if name == "" || password == "" || rePassword == "" || nickname == "" || captchaCode == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "参数不能为空",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if password != rePassword {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "密码不一致",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//强密码验证
|
||||||
|
isCheckStrongPass := models.FindConfig("CheckStrongPass")
|
||||||
|
if password != "" && isCheckStrongPass == "true" && !tools.IsStrongPass(password) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "密码必须包含大写字母,小写字母,数字和特殊字符,并且不能少于8位",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oldUser := models.FindUserWhere("name = ? or email = ?", name, email)
|
||||||
|
if oldUser.Name != "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "用户名或邮箱已经存在",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session := sessions.DefaultMany(c, "go-session-a")
|
||||||
|
if captchaId := session.Get("captcha"); captchaId != nil {
|
||||||
|
if !captcha.VerifyString(captchaId.(string), captchaCode) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "图片验证码验证失败",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "图片验证码失效",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session2 := sessions.DefaultMany(c, "go-session")
|
||||||
|
if emailCodeSession := session2.Get("emailCode" + email); emailCodeSession != nil {
|
||||||
|
if emailCodeSession.(string) != emailCode {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "邮箱验证码验证失败",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "邮箱验证码失效",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//插入新用户
|
||||||
|
dd, _ := time.ParseDuration("180h")
|
||||||
|
dd1 := time.Now().Add(dd)
|
||||||
|
userObj := &models.User{
|
||||||
|
Name: name,
|
||||||
|
Password: tools.Md5(password),
|
||||||
|
Nickname: nickname,
|
||||||
|
Avator: avator,
|
||||||
|
Pid: 1,
|
||||||
|
RecNum: 0,
|
||||||
|
AgentNum: 0,
|
||||||
|
Status: 1,
|
||||||
|
Email: email,
|
||||||
|
Tel: "",
|
||||||
|
OnlineStatus: 1,
|
||||||
|
Uuid: tools.Uuid2(),
|
||||||
|
ExpiredAt: types.Time{dd1},
|
||||||
|
}
|
||||||
|
uid, err := userObj.AddUser()
|
||||||
|
if uid == 0 && err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": fmt.Sprintf("增加用户失败:%s", err.Error()),
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.CreateUserRole(uid, uint(roleId))
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'注册成功", name)
|
||||||
|
go models.CreateFlyLog(fmt.Sprintf("%d", uid), c.ClientIP(), logContent, "user")
|
||||||
|
go SendRegisterEmail(name, email, nickname)
|
||||||
|
|
||||||
|
//删除session
|
||||||
|
session.Delete("captcha")
|
||||||
|
session2.Delete("emailCode" + email)
|
||||||
|
_ = session.Save()
|
||||||
|
_ = session2.Save()
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "注册完成",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuAvator(c *gin.Context) {
|
||||||
|
|
||||||
|
avator := c.PostForm("avator")
|
||||||
|
if avator == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "不能为空",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
models.UpdateUserAvator(kefuName.(string), avator)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新资料
|
||||||
|
func PostUpdateUser(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
avator := c.PostForm("avator")
|
||||||
|
companyPic := c.PostForm("company_pic")
|
||||||
|
|
||||||
|
result := make(map[string]string)
|
||||||
|
result["company_pic"] = companyPic
|
||||||
|
result["avator"] = avator
|
||||||
|
models.UpdateUser2(result, "name = ?", kefuName)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuinfo(c *gin.Context) {
|
||||||
|
|
||||||
|
avator := c.PostForm("avator")
|
||||||
|
nickname := c.PostForm("nickname")
|
||||||
|
if avator == "" || nickname == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "参数不能为空",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
models.UpdateKefuInfoByName(kefuName.(string), avator, nickname)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuPass(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
newPass := c.PostForm("new_pass")
|
||||||
|
confirmNewPass := c.PostForm("confirm_new_pass")
|
||||||
|
old_pass := c.PostForm("old_pass")
|
||||||
|
if newPass != confirmNewPass {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "密码不一致",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := models.FindUser(kefuName.(string))
|
||||||
|
if user.Password != tools.Md5(old_pass) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "旧密码不正确",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//强密码验证
|
||||||
|
isCheckStrongPass := models.FindConfig("CheckStrongPass")
|
||||||
|
if isCheckStrongPass == "true" && !tools.IsStrongPass(newPass) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "密码必须包含大写字母,小写字母,数字和特殊字符,并且不能少于8位",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.UpdateUserPass(kefuName.(string), tools.Md5(newPass))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuInfoStatus(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
id := c.PostForm("id")
|
||||||
|
status := c.PostForm("status")
|
||||||
|
statusInt, _ := strconv.Atoi(status)
|
||||||
|
query := " id = ? "
|
||||||
|
arg := []interface{}{
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
query = query + " and pid = ? "
|
||||||
|
arg = append(arg, kefuId)
|
||||||
|
}
|
||||||
|
statusTxt := ""
|
||||||
|
if status == "1" {
|
||||||
|
statusTxt = "关闭"
|
||||||
|
} else {
|
||||||
|
statusTxt = "开通"
|
||||||
|
}
|
||||||
|
user := models.FindUserById(id)
|
||||||
|
go SendSystemNoticeEmail(user.Email, "在线客服系统审核通知", fmt.Sprintf("你好,账号 %s,%s", user.Name, statusTxt))
|
||||||
|
models.UpdateUserStatusWhere(uint(statusInt), query, arg...)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type StaffRegisteForm struct {
|
||||||
|
Account string `form:"account" json:"account" uri:"account" xml:"account" binding:"required"`
|
||||||
|
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
|
||||||
|
Avator string `form:"avator" json:"avator" uri:"avator" xml:"avator"`
|
||||||
|
Nickname string `form:"nickname" json:"nickname" uri:"nickname" xml:"nickname" binding:"required"`
|
||||||
|
ExpiredTime string `form:"expiredTime" json:"expiredTime" uri:"expiredTime" xml:"expiredTime" binding:"required"`
|
||||||
|
Tel string `form:"tel" json:"tel" uri:"tel" xml:"tel"`
|
||||||
|
Email string `form:"email" json:"email" uri:"email" xml:"email"`
|
||||||
|
AuthKey string `form:"authKey" json:"authKey" uri:"authKey" xml:"authKey"`
|
||||||
|
EntId string `form:"entId" json:"entId" uri:"entId" xml:"entId"`
|
||||||
|
IdCard string `form:"idCard" json:"idCard" uri:"idCard" xml:"idCard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接注册成功客服账户
|
||||||
|
func PostKefuRegister2(c *gin.Context) {
|
||||||
|
var form StaffRegisteForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
roleId := 3
|
||||||
|
if form.EntId == "" {
|
||||||
|
form.EntId = "1"
|
||||||
|
roleId = 2
|
||||||
|
}
|
||||||
|
expired := tools.TimeStrToTime(form.ExpiredTime)
|
||||||
|
entId := uint(tools.Str2Int(form.EntId))
|
||||||
|
userObj := &models.User{
|
||||||
|
Name: form.Account,
|
||||||
|
Password: tools.Md5(form.Password),
|
||||||
|
Nickname: form.Nickname,
|
||||||
|
Avator: form.Avator,
|
||||||
|
Pid: entId,
|
||||||
|
Status: 0,
|
||||||
|
Email: form.Email,
|
||||||
|
Tel: form.Tel,
|
||||||
|
OnlineStatus: 1,
|
||||||
|
ExpiredAt: types.Time{expired},
|
||||||
|
}
|
||||||
|
userAttr := &models.User_attr{
|
||||||
|
EntId: tools.Int2Str(entId),
|
||||||
|
AuthKey: form.AuthKey,
|
||||||
|
IdCard: form.IdCard,
|
||||||
|
KefuName: form.Account,
|
||||||
|
CreatedAt: types.Time{time.Now()},
|
||||||
|
}
|
||||||
|
|
||||||
|
oldUser := models.FindUser(form.Account)
|
||||||
|
if oldUser.Name != "" {
|
||||||
|
userObj.SaveUser("name = ?", form.Account)
|
||||||
|
userAttr.SaveUserAttr("kefu_name = ?", form.Account)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//插入新用户
|
||||||
|
|
||||||
|
uid, _ := userObj.AddUser()
|
||||||
|
if uid == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//插入用户角色表
|
||||||
|
models.CreateUserRole(uid, uint(roleId))
|
||||||
|
|
||||||
|
//插入扩展表
|
||||||
|
if entId == 1 {
|
||||||
|
entId = uid
|
||||||
|
}
|
||||||
|
|
||||||
|
userAttr.AddUserAttr()
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'注册成功", form.Account)
|
||||||
|
go models.CreateFlyLog(fmt.Sprintf("%d", uid), c.ClientIP(), logContent, "user")
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改客服状态
|
||||||
|
func PostKefuInfoStatus2(c *gin.Context) {
|
||||||
|
kefuName := c.PostForm("kefuName")
|
||||||
|
status := c.PostForm("status")
|
||||||
|
statusInt, _ := strconv.Atoi(status)
|
||||||
|
query := " name = ? "
|
||||||
|
arg := []interface{}{
|
||||||
|
kefuName,
|
||||||
|
}
|
||||||
|
|
||||||
|
models.UpdateUserStatusWhere(uint(statusInt), query, arg...)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuExpiredTime(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
expiredTime := c.PostForm("expired_time")
|
||||||
|
query := " name = ? "
|
||||||
|
arg := []interface{}{
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
query = query + " and pid = ? "
|
||||||
|
arg = append(arg, kefuId)
|
||||||
|
}
|
||||||
|
|
||||||
|
models.UpdateUserExpiredWhere(expiredTime, query, arg...)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuInfo(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
mRoleId, _ := c.Get("role_id")
|
||||||
|
pidInterface, _ := c.Get("kefu_id")
|
||||||
|
pid := uint(pidInterface.(float64))
|
||||||
|
|
||||||
|
kefuInfo := models.FindUser(kefuName.(string))
|
||||||
|
uuid := tools.Uuid2()
|
||||||
|
var query string
|
||||||
|
var arg = []interface{}{}
|
||||||
|
if mRoleId.(float64) != 1 {
|
||||||
|
uuid = kefuInfo.Uuid
|
||||||
|
query = "pid=?"
|
||||||
|
arg = append(arg, kefuId)
|
||||||
|
count := models.CountUsersWhere(query, arg)
|
||||||
|
if kefuInfo.AgentNum <= count {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": fmt.Sprintf("子账号个数超过上限数: %d!", kefuInfo.AgentNum),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.PostForm("id")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
avator := c.PostForm("avator")
|
||||||
|
nickname := c.PostForm("nickname")
|
||||||
|
roleId := c.PostForm("role_id")
|
||||||
|
email := c.PostForm("email")
|
||||||
|
tel := c.PostForm("tel")
|
||||||
|
agentNum, _ := strconv.Atoi(c.PostForm("agent_num"))
|
||||||
|
//验证密码强度
|
||||||
|
//强密码验证
|
||||||
|
isCheckStrongPass := models.FindConfig("CheckStrongPass")
|
||||||
|
if password != "" && isCheckStrongPass == "true" && !tools.IsStrongPass(password) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "密码必须包含大写字母,小写字母,数字和特殊字符,并且不能少于8位",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if roleId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "请选择角色!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//如果不是管理员,添加超权限的角色
|
||||||
|
roleIdInt, _ := strconv.Atoi(roleId)
|
||||||
|
if mRoleId.(float64) != 1 && roleIdInt <= int(mRoleId.(float64)) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "修改角色无权限!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//添加非坐席账号,pid统一1
|
||||||
|
if roleIdInt < 3 {
|
||||||
|
pid = 1
|
||||||
|
}
|
||||||
|
//插入新用户
|
||||||
|
if id == "" {
|
||||||
|
oldUser := models.FindUser(name)
|
||||||
|
if oldUser.Name != "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "用户名已经存在",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//dd, _ := time.ParseDuration("720h")
|
||||||
|
//dd1 := time.Now().Add(dd)
|
||||||
|
//uid := models.CreateUser(name, tools.Md5(password), avator, nickname, uint(pid.(float64)), uint(agentNum), 0, types.Time{dd1})
|
||||||
|
|
||||||
|
//插入新用户
|
||||||
|
dd, _ := time.ParseDuration("720h")
|
||||||
|
dd1 := time.Now().Add(dd)
|
||||||
|
userObj := &models.User{
|
||||||
|
Name: name,
|
||||||
|
Password: tools.Md5(password),
|
||||||
|
Nickname: nickname,
|
||||||
|
Avator: avator,
|
||||||
|
Pid: pid,
|
||||||
|
RecNum: 0,
|
||||||
|
AgentNum: 0,
|
||||||
|
Status: 1,
|
||||||
|
Email: email,
|
||||||
|
Tel: tel,
|
||||||
|
OnlineStatus: 1,
|
||||||
|
ExpiredAt: types.Time{dd1},
|
||||||
|
Uuid: uuid,
|
||||||
|
}
|
||||||
|
uid, err := userObj.AddUser()
|
||||||
|
if uid == 0 && err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": fmt.Sprintf("增加用户失败:%s", err.Error()),
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roleIdInt, _ := strconv.Atoi(roleId)
|
||||||
|
models.CreateUserRole(uid, uint(roleIdInt))
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'新增用户'%s'成功", kefuName, name)
|
||||||
|
go models.CreateFlyLog(entId.(string), c.ClientIP(), logContent, "user")
|
||||||
|
} else {
|
||||||
|
//更新用户
|
||||||
|
if password != "" {
|
||||||
|
password = tools.Md5(password)
|
||||||
|
}
|
||||||
|
oldUser := models.FindUser(name)
|
||||||
|
if oldUser.Name != "" && id != fmt.Sprintf("%d", oldUser.ID) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "用户名已经存在",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.UpdateUser(id, name, password, avator, nickname, email, tel, uint(agentNum))
|
||||||
|
roleIdInt, _ := strconv.Atoi(roleId)
|
||||||
|
uid, _ := strconv.Atoi(id)
|
||||||
|
models.DeleteRoleByUserId(uid)
|
||||||
|
models.CreateUserRole(uint(uid), uint(roleIdInt))
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'更新用户'%s'成功", kefuName, name)
|
||||||
|
go models.CreateFlyLog(entId.(string), c.ClientIP(), logContent, "user")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKefuList(c *gin.Context) {
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "无权限",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
count := models.CountUsers()
|
||||||
|
list := models.FindUsersPages(uint(page), common.PageSize)
|
||||||
|
//users := models.FindUsers()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": gin.H{
|
||||||
|
"count": count,
|
||||||
|
"page": page,
|
||||||
|
"list": list,
|
||||||
|
"pagesize": common.PageSize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetKefuListOwn(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
query := "1=1 "
|
||||||
|
var arg = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
query += "and pid = ? "
|
||||||
|
arg = append(arg, kefuId)
|
||||||
|
}
|
||||||
|
|
||||||
|
//通过客服名搜索
|
||||||
|
kefuName := c.Query("kefu_name")
|
||||||
|
if kefuName != "" {
|
||||||
|
query += "and user.name like ? "
|
||||||
|
arg = append(arg, kefuName+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
//通过邮箱搜索
|
||||||
|
email := c.Query("email")
|
||||||
|
if email != "" {
|
||||||
|
query += "and user.email like ? "
|
||||||
|
arg = append(arg, email+"%")
|
||||||
|
}
|
||||||
|
//通过手机搜索
|
||||||
|
tel := c.Query("tel")
|
||||||
|
if tel != "" {
|
||||||
|
query += "and user.tel like ? "
|
||||||
|
arg = append(arg, tel+"%")
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
count := models.CountUsersWhere(query, arg...)
|
||||||
|
list := models.FindUsersOwn(uint(page), uint(pagesize), query, arg...)
|
||||||
|
//判断实际在线状态
|
||||||
|
for key, item := range list {
|
||||||
|
kefuConnection, ok := ws.KefuList[item.Name]
|
||||||
|
if !ok || len(kefuConnection.Users) == 0 {
|
||||||
|
item.OnlineStatus = 2
|
||||||
|
}
|
||||||
|
item.RecNum = ws.VisitorCount(item.Name)
|
||||||
|
list[key] = item
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": gin.H{
|
||||||
|
"count": count,
|
||||||
|
"page": page,
|
||||||
|
"list": list,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetKefuListMessages(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
id := c.Query("id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize == 0 {
|
||||||
|
pagesize = 30
|
||||||
|
}
|
||||||
|
user := models.FindUserById(id)
|
||||||
|
fmt.Printf("%T\n", kefuId)
|
||||||
|
fmt.Printf("%T\n", user.Pid)
|
||||||
|
if fmt.Sprintf("%v", user.Pid) != fmt.Sprintf("%v", kefuId) {
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "无权限",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
count := models.CountMessage("message.kefu_id=?", user.Name)
|
||||||
|
list := models.FindMessageByPage(uint(page), uint(pagesize), "message.kefu_id=?", user.Name)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": gin.H{
|
||||||
|
"count": count,
|
||||||
|
"page": page,
|
||||||
|
"list": list,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVisitorListMessages(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
|
||||||
|
count := models.CountMessage("message.ent_id= ? and message.visitor_id=?", entId, visitorId)
|
||||||
|
list := models.FindMessageByPage(uint(page), common.PageSize, "message.ent_id= ? and message.visitor_id=?", entId, visitorId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": gin.H{
|
||||||
|
"count": count,
|
||||||
|
"page": page,
|
||||||
|
"list": list,
|
||||||
|
"pagesize": common.PageSize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DeleteKefuInfo(c *gin.Context) {
|
||||||
|
kefuId := c.Query("id")
|
||||||
|
models.DeleteUserById(kefuId)
|
||||||
|
models.DeleteRoleByUserId(kefuId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "删除成功",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DeleteKefuInfoOwn(c *gin.Context) {
|
||||||
|
kefuId := c.Query("id")
|
||||||
|
pid, _ := c.Get("kefu_id")
|
||||||
|
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
|
||||||
|
if kefuId == fmt.Sprintf("%v", pid) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "不能删除自己",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := models.FindUserByUid(kefuId)
|
||||||
|
if roleId.(float64) == 1 {
|
||||||
|
models.DeleteUserById(kefuId)
|
||||||
|
models.DeleteRoleByUserId(kefuId)
|
||||||
|
models.DelVisitor("ent_id = ?", kefuId)
|
||||||
|
models.DelVisitorBlack("ent_id = ?", kefuId)
|
||||||
|
models.DelVisitorAttr("ent_id = ?", kefuId)
|
||||||
|
models.DelMessage("ent_id = ?", kefuId)
|
||||||
|
models.DelUserAttr("ent_id = ?", kefuId)
|
||||||
|
models.DelEntConfig("ent_id = ?", kefuId)
|
||||||
|
models.DelOauthKefuName(user.Name)
|
||||||
|
} else {
|
||||||
|
models.DeleteUserByIdPid(kefuId, pid)
|
||||||
|
models.DeleteRoleByUserId(kefuId)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "删除成功",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户的在线状态
|
||||||
|
func GetUpdateOnlineStatus(c *gin.Context) {
|
||||||
|
status := c.Query("status")
|
||||||
|
statusInt, _ := strconv.Atoi(status)
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
user := models.User{
|
||||||
|
ID: uint(kefuId.(float64)),
|
||||||
|
OnlineStatus: uint(statusInt),
|
||||||
|
}
|
||||||
|
user.UpdateUser()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定手机号
|
||||||
|
func PostKefuBindTel(c *gin.Context) {
|
||||||
|
|
||||||
|
phone := c.PostForm("phone")
|
||||||
|
code := c.PostForm("code")
|
||||||
|
if phone == "" || code == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "参数不能为空",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := models.FindUserWhere("tel = ?", phone)
|
||||||
|
if info.ID != 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "该手机号已被绑定!",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session := sessions.DefaultMany(c, "go-session-a")
|
||||||
|
if codeSession := session.Get("smsCode" + phone); codeSession != nil {
|
||||||
|
if codeSession.(string) != code {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "短信验证码验证失败",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "短信验证码失效",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//删除session
|
||||||
|
session.Delete("smsCode" + phone)
|
||||||
|
_ = session.Save()
|
||||||
|
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
models.UpdateUserTel(kefuName.(string), phone)
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
logContent := fmt.Sprintf("'%s'绑定手机'%s'", kefuName, phone)
|
||||||
|
go models.CreateFlyLog(entId.(string), c.ClientIP(), logContent, "sms")
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLearnReplyContent(entId, content string) string {
|
||||||
|
replyContent := ""
|
||||||
|
replys := models.FindArticleRows("id,title,search_type", "ent_id = ? ", entId)
|
||||||
|
|
||||||
|
//按分值计算
|
||||||
|
var exactReply []lib.MatchData
|
||||||
|
var matchReply []lib.MatchData
|
||||||
|
for _, row := range replys {
|
||||||
|
//精准
|
||||||
|
if row.SearchType == 2 {
|
||||||
|
exactReply = append(exactReply, lib.MatchData{
|
||||||
|
ID: row.Id,
|
||||||
|
Text: row.Title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//包含
|
||||||
|
if row.SearchType == 1 {
|
||||||
|
matchReply = append(matchReply, lib.MatchData{
|
||||||
|
ID: row.Id,
|
||||||
|
Text: row.Title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//通配符
|
||||||
|
if row.Title == "*" {
|
||||||
|
exactReply = append(exactReply, lib.MatchData{
|
||||||
|
ID: row.Id,
|
||||||
|
Text: row.Title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matchRes, ok := lib.NewMatcher(exactReply).Match(content, true)
|
||||||
|
if ok {
|
||||||
|
r := models.FindArticleRow("id = ?", matchRes.ID)
|
||||||
|
replyContent = r.Content
|
||||||
|
article := models.Article{
|
||||||
|
Score: r.Score + 1,
|
||||||
|
}
|
||||||
|
go article.SaveArticle("ent_id = ? and id = ?", entId, r.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if replyContent == "" {
|
||||||
|
matchRes, ok = lib.NewMatcher(matchReply).Match(content, false)
|
||||||
|
if ok {
|
||||||
|
r := models.FindArticleRow("id = ?", matchRes.ID)
|
||||||
|
replyContent = r.Content
|
||||||
|
article := models.Article{
|
||||||
|
Score: r.Score + 1,
|
||||||
|
}
|
||||||
|
go article.SaveArticle("ent_id = ? and id = ?", entId, r.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config := models.GetEntConfigsMap(entId, "KeywordFinalReply")
|
||||||
|
if replyContent == "" {
|
||||||
|
replyContent = config["KeywordFinalReply"]
|
||||||
|
}
|
||||||
|
return replyContent
|
||||||
|
//articles := models.FindArticleList(1, 10, "score desc", "ent_id= ? and title like ?", entId, "%"+content+"%")
|
||||||
|
//articleLen := len(articles)
|
||||||
|
//result := ""
|
||||||
|
////未匹配,进入学习库
|
||||||
|
//if articleLen == 0 {
|
||||||
|
// if len([]rune(content)) > 1 {
|
||||||
|
// go AddUpdateLearn(entId, content)
|
||||||
|
// }
|
||||||
|
//} else if articleLen == 1 {
|
||||||
|
// result = tools.TrimHtml(articles[0].Content)
|
||||||
|
// article := models.Article{
|
||||||
|
// Score: articles[0].Score + 1,
|
||||||
|
// }
|
||||||
|
// go article.SaveArticle("ent_id = ? and id = ?", entId, articles[0].Id)
|
||||||
|
//} else if articleLen > 1 {
|
||||||
|
// result = "您是不是想问:"
|
||||||
|
// for _, article := range articles {
|
||||||
|
// title := strings.Split(strings.ReplaceAll(article.Title, ",", ","), ",")[0]
|
||||||
|
// result += fmt.Sprintf("<a href='#'>%s</a>\r\n", title)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入或更新学习库
|
||||||
|
func AddUpdateLearn(entId, content string) {
|
||||||
|
learn := models.FindLearn("ent_id = ? and content = ?", entId, content)
|
||||||
|
if learn.ID != 0 {
|
||||||
|
learn.Score += 1
|
||||||
|
learn.SaveLearn("ent_id = ? and content = ?", entId, content)
|
||||||
|
} else {
|
||||||
|
m := &models.Learn{
|
||||||
|
EntId: entId,
|
||||||
|
Content: content,
|
||||||
|
CreatedAt: types.Time{},
|
||||||
|
Score: 1,
|
||||||
|
Finshed: 1,
|
||||||
|
}
|
||||||
|
m.AddLearn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 学习库列表
|
||||||
|
func GetLearnsList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("pagesize", "10"))
|
||||||
|
if pageSize > 50 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
var args = []interface{}{}
|
||||||
|
search := "ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
|
||||||
|
count := models.CountLearn(search, args...)
|
||||||
|
list := models.FindLearnList(page, pageSize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pageSize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 学习库状态
|
||||||
|
func PostLearnStatus(c *gin.Context) {
|
||||||
|
status := c.PostForm("status")
|
||||||
|
id := c.PostForm("id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
learn := models.Learn{
|
||||||
|
Finshed: uint(tools.Str2Int(status)),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
}
|
||||||
|
learn.SaveLearn("ent_id = ? and id = ?", entId, id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLlmLogList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
count := models.CountLlmLogs("ent_id = ? and kefu_name = ?", entId, kefuName)
|
||||||
|
list := models.FindLlmLogs(page, pagesize, "ent_id = ? and kefu_name = ?", entId, kefuName)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": uint(pagesize),
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetLogList(c *gin.Context) {
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
search := "1=1 "
|
||||||
|
var args = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
search += "and pid = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := models.CountFlyLog(search, args...)
|
||||||
|
list := models.FindFlyLogs(page, pagesize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": uint(pagesize),
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,480 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dchest/captcha"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginForm struct {
|
||||||
|
Username string `form:"username" json:"username" uri:"username" xml:"username" binding:"required"`
|
||||||
|
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
|
||||||
|
Captcha string `form:"captcha" json:"captcha" uri:"captcha" xml:"captcha"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostLoginCheckV2(c *gin.Context) {
|
||||||
|
var form LoginForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//验证码
|
||||||
|
session := sessions.DefaultMany(c, "go-session-b")
|
||||||
|
if captchaId := session.Get("captcha_login"); captchaId != nil {
|
||||||
|
if !captcha.VerifyString(captchaId.(string), form.Captcha) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.CAPTCHA_FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.CAPTCHA_FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.CAPTCHA_FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.CAPTCHA_FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ip := c.ClientIP()
|
||||||
|
ipCity, ipstr := tools.ParseIpNew(ip)
|
||||||
|
|
||||||
|
info, ok := CheckKefuPass(form.Username, form.Password)
|
||||||
|
if !ok {
|
||||||
|
logContent := fmt.Sprintf("'%s' 密码错误:%s,%s", form.Username, form.Password, ipstr)
|
||||||
|
go models.CreateFlyLog(info.EntId, ip, logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.LOGIN_FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.LOGIN_FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if info.Status == 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_FORBIDDEN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_FORBIDDEN),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//IP登录限制
|
||||||
|
isIpForbidden := false
|
||||||
|
ipForbidden := models.FindConfig("IpLoginForbidden")
|
||||||
|
if ipForbidden == "3" && ipCity.CountryName != "Internal network" {
|
||||||
|
isIpForbidden = true
|
||||||
|
} else if ipForbidden == "2" {
|
||||||
|
//禁止海外IP
|
||||||
|
if (ipCity.CountryName != "Internal network" && ipCity.CountryName != "中国") ||
|
||||||
|
(ipCity.RegionName == "台湾" || ipCity.RegionName == "香港" || ipCity.RegionName == "澳门") {
|
||||||
|
isIpForbidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isIpForbidden {
|
||||||
|
logContent := fmt.Sprintf("'%s' IP登录被禁止,%s", form.Username, ipstr)
|
||||||
|
go models.CreateFlyLog(info.EntId, ip, logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.IP_FORBIDDEN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.IP_FORBIDDEN),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查看过期时间
|
||||||
|
nowSecond := time.Now().Unix()
|
||||||
|
expireSecond := info.ExpiredAt.Unix()
|
||||||
|
if models.FindConfig("KefuExpired") == "on" && expireSecond < nowSecond {
|
||||||
|
logContent := fmt.Sprintf("'%s'账户过期:%d,登录时间:%d,%s", info.Name, expireSecond, nowSecond, ipstr)
|
||||||
|
go models.CreateFlyLog(info.EntId, c.ClientIP(), logContent, "user")
|
||||||
|
//c.JSON(200, gin.H{
|
||||||
|
// "code": types.ApiCode.ACCOUNT_EXPIRED,
|
||||||
|
// "msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_EXPIRED),
|
||||||
|
//})
|
||||||
|
//return
|
||||||
|
}
|
||||||
|
if info.RoleId == "3" {
|
||||||
|
entInfo := models.FindUserByUid(info.EntId)
|
||||||
|
entExpireSecond := entInfo.ExpiredAt.Unix()
|
||||||
|
if models.FindConfig("KefuExpired") == "on" && entExpireSecond < nowSecond {
|
||||||
|
logContent := fmt.Sprintf("'%s'的企业账户'%s'过期:%d,登录时间:%d", info.Name, entInfo.Name, expireSecond, nowSecond)
|
||||||
|
go models.CreateFlyLog(info.EntId, c.ClientIP(), logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_EXPIRED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_EXPIRED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token := GenUserToken(info)
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'登录成功,%s", form.Username, ipstr)
|
||||||
|
go models.CreateFlyLog(info.EntId, ip, logContent, "user")
|
||||||
|
|
||||||
|
//删除session
|
||||||
|
session.Delete("captcha_login")
|
||||||
|
_ = session.Save()
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "验证成功,正在跳转",
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证接口
|
||||||
|
func LoginCheckPass(c *gin.Context) {
|
||||||
|
var form LoginForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, ok := CheckKefuPass(form.Username, form.Password)
|
||||||
|
if !ok {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.LOGIN_FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.LOGIN_FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if info.Status == 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_FORBIDDEN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_FORBIDDEN),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取远程结果
|
||||||
|
err, ok = CheckServerAddress()
|
||||||
|
if !ok {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.LOGIN_FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查看过期时间
|
||||||
|
nowSecond := time.Now().Unix()
|
||||||
|
expireSecond := info.ExpiredAt.Unix()
|
||||||
|
if models.FindConfig("KefuExpired") == "on" && expireSecond < nowSecond {
|
||||||
|
logContent := fmt.Sprintf("'%s'账户过期:%d,登录时间:%d", info.Name, expireSecond, nowSecond)
|
||||||
|
go models.CreateFlyLog(info.EntId, c.ClientIP(), logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_EXPIRED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_EXPIRED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if info.RoleId == "3" {
|
||||||
|
entInfo := models.FindUserByUid(info.EntId)
|
||||||
|
entExpireSecond := entInfo.ExpiredAt.Unix()
|
||||||
|
if models.FindConfig("KefuExpired") == "on" && entExpireSecond < nowSecond {
|
||||||
|
logContent := fmt.Sprintf("'%s'的企业账户'%s'过期:%d,登录时间:%d", info.Name, entInfo.Name, expireSecond, nowSecond)
|
||||||
|
go models.CreateFlyLog(info.EntId, c.ClientIP(), logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_EXPIRED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_EXPIRED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token := GenUserToken(info)
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'登录成功", form.Username)
|
||||||
|
go models.CreateFlyLog(info.EntId, c.ClientIP(), logContent, "user")
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "验证成功,正在跳转",
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GenUserToken(info models.User) string {
|
||||||
|
userinfo := make(map[string]interface{})
|
||||||
|
userinfo["name"] = info.Name
|
||||||
|
userinfo["kefu_id"] = info.ID
|
||||||
|
userinfo["type"] = "kefu"
|
||||||
|
uRole := models.FindRoleByUserId(info.ID)
|
||||||
|
if uRole.RoleId != 0 {
|
||||||
|
userinfo["role_id"] = uRole.RoleId
|
||||||
|
} else {
|
||||||
|
userinfo["role_id"] = 2
|
||||||
|
}
|
||||||
|
userinfo["create_time"] = time.Now().Unix()
|
||||||
|
userinfo["pid"] = info.Pid
|
||||||
|
|
||||||
|
token, _ := tools.MakeToken(userinfo)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抖音登录
|
||||||
|
func PostDouyinLogin(c *gin.Context) {
|
||||||
|
code := c.PostForm("code")
|
||||||
|
clientId := models.FindConfig("DouyinClientKey")
|
||||||
|
clientKey := models.FindConfig("DouyinClientSecret")
|
||||||
|
douyin, _ := lib.NewDouyin(clientId, clientKey)
|
||||||
|
tokenResult, _ := douyin.GetAccessToken(code)
|
||||||
|
openId := gjson.Get(tokenResult, "data.open_id").String()
|
||||||
|
|
||||||
|
accessToken := gjson.Get(tokenResult, "data.access_token").String()
|
||||||
|
refreshToken := gjson.Get(tokenResult, "data.refresh_token").String()
|
||||||
|
expiresIn := gjson.Get(tokenResult, "data.expires_in").Int()
|
||||||
|
refreshExpiresIn := gjson.Get(tokenResult, "data.refresh_expires_in").Int()
|
||||||
|
expiresInInt := tools.Now() + expiresIn
|
||||||
|
refreshExpiresInInt := tools.Now() + refreshExpiresIn
|
||||||
|
//获取昵称头像
|
||||||
|
douyinInfo, _ := douyin.GetUserInfo(accessToken, openId)
|
||||||
|
nickname := gjson.Get(douyinInfo, "data.nickname").String()
|
||||||
|
avatar := gjson.Get(douyinInfo, "data.avatar").String()
|
||||||
|
unionId := gjson.Get(douyinInfo, "data.union_id").String()
|
||||||
|
|
||||||
|
//直接注册成为客服
|
||||||
|
userDouyin := models.FindUserDouyinRow("open_id = ?", openId)
|
||||||
|
user := models.FindUser(openId)
|
||||||
|
if user.ID != 0 {
|
||||||
|
userDouyin.KefuName = openId
|
||||||
|
userDouyin.EntId = tools.Int2Str(user.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userDouyin.ID == 0 && user.ID == 0 {
|
||||||
|
dd, _ := time.ParseDuration("180h")
|
||||||
|
dd1 := time.Now().Add(dd)
|
||||||
|
kefuInfo := models.User{
|
||||||
|
Name: openId,
|
||||||
|
Password: tools.Md5("123456"),
|
||||||
|
Nickname: nickname,
|
||||||
|
Avator: avatar,
|
||||||
|
Pid: 1,
|
||||||
|
RecNum: 0,
|
||||||
|
AgentNum: 0,
|
||||||
|
Status: 0,
|
||||||
|
OnlineStatus: 1,
|
||||||
|
ExpiredAt: types.Time{dd1},
|
||||||
|
}
|
||||||
|
uid, err := kefuInfo.AddUser()
|
||||||
|
if uid == 0 && err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": fmt.Sprintf("增加用户失败:%s", err.Error()),
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.CreateUserRole(uid, 2)
|
||||||
|
userDouyin.EntId = tools.Int2Str(uid)
|
||||||
|
userDouyin.KefuName = openId
|
||||||
|
}
|
||||||
|
|
||||||
|
douyinModel := &models.User_douyin{
|
||||||
|
Nickname: nickname,
|
||||||
|
Avatar: avatar,
|
||||||
|
EntId: userDouyin.EntId,
|
||||||
|
KefuName: userDouyin.KefuName,
|
||||||
|
OpenId: openId,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: types.Time{tools.IntToTime(expiresInInt - 3600)},
|
||||||
|
RefreshExpiresIn: types.Time{tools.IntToTime(refreshExpiresInInt - 3600)},
|
||||||
|
ClientTokenExpires: tools.IntToTimeStr(expiresInInt, "2006-01-02 15:04:05"),
|
||||||
|
UnionId: unionId,
|
||||||
|
CreatedAt: types.Time{},
|
||||||
|
}
|
||||||
|
douyinModel.UpdateOrInsert()
|
||||||
|
|
||||||
|
//生成授权
|
||||||
|
user = models.FindUser(userDouyin.KefuName)
|
||||||
|
token := GenUserToken(user)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信登录
|
||||||
|
func PostWechatLogin(c *gin.Context) {
|
||||||
|
//限流
|
||||||
|
if !tools.LimitFreqSingle("wechat_login:"+c.ClientIP(), 1, 2) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FREQ_LIMIT,
|
||||||
|
"msg": c.ClientIP() + types.ApiCode.GetMessage(types.ApiCode.FREQ_LIMIT),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entId := models.FindConfig("SystemBussinesId")
|
||||||
|
if entId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "系统微信公众号未配置",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configs := models.GetEntConfigsMap(entId, "WechatAppId",
|
||||||
|
"WechatAppSecret", "WechatAppToken")
|
||||||
|
AppID := configs["WechatAppId"]
|
||||||
|
AppSecret := configs["WechatAppSecret"]
|
||||||
|
Token := configs["WechatAppToken"]
|
||||||
|
offical := lib.NewWechatOffical(AppID, AppSecret, Token, memory)
|
||||||
|
|
||||||
|
//插入微信登录表
|
||||||
|
tempKefuId := tools.Uuid2()
|
||||||
|
wechatLogin := &models.Wechat_login{
|
||||||
|
TempKefuId: tempKefuId,
|
||||||
|
Status: "NO_SCAN",
|
||||||
|
LoginIp: c.ClientIP(),
|
||||||
|
}
|
||||||
|
wechatLogin.AddWechatLogin()
|
||||||
|
|
||||||
|
sceneName := fmt.Sprintf("wechatLogin_%s", tempKefuId)
|
||||||
|
url, err := offical.TmpQrCode(sceneName)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"url": url,
|
||||||
|
"temp_kefu_id": tempKefuId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询微信登录
|
||||||
|
func PostCheckWechatLoginStatus(c *gin.Context) {
|
||||||
|
tempKefuId := c.PostForm("temp_kefu_id")
|
||||||
|
wechatLogin := models.FindWechatLoginRow("temp_kefu_id = ?", tempKefuId)
|
||||||
|
token := ""
|
||||||
|
status := wechatLogin.Status
|
||||||
|
if wechatLogin.KefuName != "" {
|
||||||
|
user := models.FindUser(wechatLogin.KefuName)
|
||||||
|
token = GenUserToken(user)
|
||||||
|
}
|
||||||
|
//判断是否过期
|
||||||
|
if tools.Now()-wechatLogin.CreatedAt.Unix() > 100 {
|
||||||
|
status = "EXPIRED"
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
"status": status,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置密码
|
||||||
|
func PostResetPassword(c *gin.Context) {
|
||||||
|
email := c.PostForm("email")
|
||||||
|
emailCode := c.PostForm("emailCode")
|
||||||
|
//验证邮箱
|
||||||
|
matched, _ := regexp.MatchString("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", email)
|
||||||
|
if !matched {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.INVALID,
|
||||||
|
"msg": "邮箱格式不正确",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if emailCode == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "邮箱验证码不能为空",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session2 := sessions.DefaultMany(c, "go-session")
|
||||||
|
if emailCodeSession := session2.Get("emailCode" + email); emailCodeSession != nil {
|
||||||
|
if emailCodeSession.(string) != emailCode {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "邮箱验证码验证失败",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "邮箱验证码失效",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oldUser := models.FindUserWhere("email = ?", email)
|
||||||
|
if oldUser.Name == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "邮箱不存在",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newPass := tools.GenerateRandomString(6)
|
||||||
|
models.UpdateUserPass(oldUser.Name, tools.Md5(newPass))
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'在线客服系统重置密码 %s", email, newPass)
|
||||||
|
go models.CreateFlyLog(email, c.ClientIP(), logContent, "user")
|
||||||
|
|
||||||
|
smtp := models.FindConfig("NoticeEmailSmtp")
|
||||||
|
sender := models.FindConfig("NoticeEmailAddress")
|
||||||
|
password := models.FindConfig("NoticeEmailPassword")
|
||||||
|
content := strings.Replace(common.NoticeTemplate, "[:content]", "你好,重置密码为 "+newPass, -1)
|
||||||
|
err := tools.SendSmtp(smtp, sender, password, []string{email}, "在线客服系统重置密码", content)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logContent := "发送邮件失败:" + err.Error()
|
||||||
|
log.Println(logContent)
|
||||||
|
go models.CreateFlyLog(email, c.ClientIP(), logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.EMAIL_FAILD,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除session
|
||||||
|
session2.Delete("emailCode" + email)
|
||||||
|
_ = session2.Save()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/ws"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MainCheckAuth(c *gin.Context) {
|
||||||
|
id, _ := c.Get("kefu_id")
|
||||||
|
userinfo := models.FindUserRole("user.nickname,user.avator,user.name,user.id, role.name role_name", id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "验证成功",
|
||||||
|
"result": gin.H{
|
||||||
|
"avator": userinfo.Avator,
|
||||||
|
"name": userinfo.Name,
|
||||||
|
"role_name": userinfo.RoleName,
|
||||||
|
"nick_name": userinfo.Nickname,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetStatistics(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
//kefuId, _ := c.Get("kefu_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
//今日访客数
|
||||||
|
todayStart := time.Now().Format("2006-01-02")
|
||||||
|
todayEnd := fmt.Sprintf("%s 23:59:59", todayStart)
|
||||||
|
toadyVisitors := models.CountVisitors("to_id= ? and updated_at>= ? and updated_at<= ?", kefuName.(string), todayStart, todayEnd)
|
||||||
|
visitors := models.CountVisitorsByKefuId(kefuName.(string))
|
||||||
|
|
||||||
|
message := models.CountMessage("kefu_id=?", kefuName)
|
||||||
|
todayMessages := models.CountMessage("kefu_id= ? and created_at>= ? and created_at<= ?", kefuName.(string), todayStart, todayEnd)
|
||||||
|
visitorSession := 0
|
||||||
|
for _, c := range ws.ClientList {
|
||||||
|
if c.EntId == fmt.Sprintf("%v", entId) {
|
||||||
|
visitorSession++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kefuSession := 0
|
||||||
|
for _, kefus := range ws.KefuList {
|
||||||
|
for _, c := range kefus.Users {
|
||||||
|
if c.Ent_id == fmt.Sprintf("%v", entId) {
|
||||||
|
kefuSession++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"visitors": visitors,
|
||||||
|
"toady_visitors": toadyVisitors,
|
||||||
|
"today_messages": todayMessages,
|
||||||
|
"message": message,
|
||||||
|
"visitor_session": visitorSession,
|
||||||
|
"kefu_session": kefuSession,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVersion(c *gin.Context) {
|
||||||
|
|
||||||
|
versionName := "商务版"
|
||||||
|
ipAuth := tools.Get(IP_SERVER_URL)
|
||||||
|
|
||||||
|
ipAddress := gjson.Get(ipAuth, "result.ip_address").String()
|
||||||
|
endTimeStr := gjson.Get(ipAuth, "result.expire_time").String()
|
||||||
|
content := gjson.Get(ipAuth, "result.content").String()
|
||||||
|
//if common.IsTry {
|
||||||
|
// versionName = "试用版"
|
||||||
|
// installTimeByte, _ := ioutil.ReadFile("./install.lock")
|
||||||
|
// installTimeByte, _ = tools.AesDecrypt(installTimeByte, []byte(common.AesKey))
|
||||||
|
// installTime := tools.ByteToInt64(installTimeByte)
|
||||||
|
// endTime := installTime + common.TryDeadline
|
||||||
|
// endTimeStr = time.Unix(endTime, 0).Format("2006-01-02")
|
||||||
|
//}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"ip_address": ipAddress,
|
||||||
|
"last_time": endTimeStr,
|
||||||
|
"version": versionName,
|
||||||
|
"content": content,
|
||||||
|
"version_code": common.Version,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetOtherVersion(c *gin.Context) {
|
||||||
|
versionCode := models.FindConfig("SystemVersion")
|
||||||
|
versionName := models.FindConfig("SystemVersionName")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"version_name": versionName,
|
||||||
|
"version_code": versionCode,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,906 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/service"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VisitorMessageForm struct {
|
||||||
|
ToId string `form:"to_id" json:"to_id" uri:"to_id" xml:"to_id" binding:"required"`
|
||||||
|
FromId string `form:"from_id" json:"from_id" uri:"from_id" xml:"from_id" binding:"required"`
|
||||||
|
Content string `form:"content" json:"content" uri:"content" xml:"content" binding:"required"`
|
||||||
|
IsAutoReply string `form:"is_autoreply" json:"is_autoreply" uri:"is_autoreply" xml:"is_autoreply"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendMessageV2(c *gin.Context) {
|
||||||
|
var form VisitorMessageForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fromId := form.FromId
|
||||||
|
toId := form.ToId
|
||||||
|
content := form.Content
|
||||||
|
//判断是否离线
|
||||||
|
//_, ok := ws.ClientList[fromId]
|
||||||
|
//if !ok {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": types.ApiCode.VISITOR_OFFLINE,
|
||||||
|
// "msg": types.ApiCode.GetMessage(types.ApiCode.VISITOR_OFFLINE),
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//限流
|
||||||
|
if models.FindConfig("SystemFreqLimt") != "true" && !tools.LimitFreqSingle("sendmessage:"+c.ClientIP(), 1, 1) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FREQ_LIMIT,
|
||||||
|
"msg": c.ClientIP() + types.ApiCode.GetMessage(types.ApiCode.FREQ_LIMIT),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//验证黑名单
|
||||||
|
//验证访客黑名单
|
||||||
|
if !CheckVisitorBlack(fromId) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.VISITOR_BAN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.VISITOR_BAN),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//过滤敏感词
|
||||||
|
content, isBlack := ReplaceBlackWords(content)
|
||||||
|
|
||||||
|
var kefuInfo models.User
|
||||||
|
var vistorInfo models.Visitor
|
||||||
|
|
||||||
|
vistorInfo = models.FindVisitorByVistorId(fromId)
|
||||||
|
//提示上班时间
|
||||||
|
//查询企业配置项
|
||||||
|
entConfigs := models.GetEntConfigsMap(vistorInfo.EntId, "workTimeStatus",
|
||||||
|
"workDay", "morningWorkTime", "afternoonWorkTime", "otherWorkTime", "afterWorkMessage", "BaiduFanyiAppId", "BaiduFanyiAppSec", "BaiduFanyiAuto")
|
||||||
|
if entConfigs["workTimeStatus"] == "yes" {
|
||||||
|
if !tools.IsWorkTime(strings.Split(entConfigs["workDay"], "|"),
|
||||||
|
strings.Split(entConfigs["morningWorkTime"], "|"),
|
||||||
|
strings.Split(entConfigs["afternoonWorkTime"], "|"),
|
||||||
|
strings.Split(entConfigs["otherWorkTime"], "|")) {
|
||||||
|
ws.VisitorNotice(vistorInfo.VisitorId, entConfigs["afterWorkMessage"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuInfo = models.FindUser(toId)
|
||||||
|
|
||||||
|
if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgId := models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, content, "visitor", vistorInfo.EntId, "unread")
|
||||||
|
|
||||||
|
guest, ok := ws.ClientList[vistorInfo.VisitorId]
|
||||||
|
if ok && guest != nil {
|
||||||
|
guest.UpdateTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
wsContent := content
|
||||||
|
//是否进行百度翻译
|
||||||
|
if entConfigs["BaiduFanyiAppId"] != "" && entConfigs["BaiduFanyiAppSec"] != "" && entConfigs["BaiduFanyiAuto"] == "on" {
|
||||||
|
baidu := &lib.BaiduFanyi{
|
||||||
|
AppId: entConfigs["BaiduFanyiAppId"],
|
||||||
|
AppSec: entConfigs["BaiduFanyiAppSec"],
|
||||||
|
}
|
||||||
|
res, err := baidu.Translate(content, "auto", "zh")
|
||||||
|
if err == nil && res != "" {
|
||||||
|
wsContent += "\n" + res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "message",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
MsgId: msgId,
|
||||||
|
Avator: vistorInfo.Avator,
|
||||||
|
Id: vistorInfo.VisitorId,
|
||||||
|
VisitorId: vistorInfo.VisitorId,
|
||||||
|
Name: vistorInfo.Name,
|
||||||
|
ToId: kefuInfo.Name,
|
||||||
|
Content: wsContent,
|
||||||
|
Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
IsKefu: "no",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
|
||||||
|
ws.OneKefuMessage(kefuInfo.Name, str)
|
||||||
|
//回调url
|
||||||
|
go SendVisitorMessageCallUrl(vistorInfo.EntId, vistorInfo.Name, kefuInfo.Name, vistorInfo.Name, vistorInfo.Avator, content)
|
||||||
|
go SendAppGetuiPush(kefuInfo.Name, "[信息]"+vistorInfo.Name, content)
|
||||||
|
go SendWechatVisitorMessageTemplate(kefuInfo.Name, vistorInfo.Name, vistorInfo.VisitorId, content, vistorInfo.EntId)
|
||||||
|
//go SendWechatKefuNotice(kefuInfo.Name, "[访客]"+vistorInfo.Name+",说:"+content, vistorInfo.EntId)
|
||||||
|
kefus, ok := ws.KefuList[kefuInfo.Name]
|
||||||
|
go service.SendWorkWechatMesage(vistorInfo.EntId, vistorInfo, kefuInfo, content, c)
|
||||||
|
if !ok || len(kefus.Users) == 0 {
|
||||||
|
go SendNoticeEmail(vistorInfo.Name, "[留言]"+vistorInfo.Name, vistorInfo.EntId, content)
|
||||||
|
}
|
||||||
|
//是否机器人回复
|
||||||
|
if form.IsAutoReply != "no" {
|
||||||
|
go ws.VisitorAutoReply(vistorInfo, kefuInfo, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
go models.ReadMessageByVisitorId(vistorInfo.VisitorId, "kefu")
|
||||||
|
go models.UpdateVisitorVisitorId(vistorInfo.VisitorId, vistorInfo.VisitorId)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"content": content,
|
||||||
|
"isBlack": isBlack,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
func SendSyncMessage(c *gin.Context) {
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
//验证访客黑名单
|
||||||
|
if !CheckVisitorBlack(visitorId) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.VISITOR_BAN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.VISITOR_BAN),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//过滤敏感词
|
||||||
|
content, _ = ReplaceBlackWords(content)
|
||||||
|
var vistorInfo models.Visitor
|
||||||
|
vistorInfo = models.FindVisitorByVistorId(visitorId)
|
||||||
|
|
||||||
|
kefuInfo := models.FindUser(vistorInfo.ToId)
|
||||||
|
if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.CreateMessage(vistorInfo.ToId, vistorInfo.VisitorId, content, "kefu", vistorInfo.EntId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, content, kefuInfo)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func SendRobotMessage(c *gin.Context) {
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
|
||||||
|
//限流
|
||||||
|
if !tools.LimitFreqSingle("sendmessage:"+c.ClientIP(), 1, 1) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FREQ_LIMIT,
|
||||||
|
"msg": c.ClientIP() + types.ApiCode.GetMessage(types.ApiCode.FREQ_LIMIT),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
kefuInfo := models.FindUserByUid(entId)
|
||||||
|
if kefuInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查询机器人信息
|
||||||
|
configs := models.GetEntConfigsMap(entId, "RobotName", "RobotAvator")
|
||||||
|
robotName := configs["RobotName"]
|
||||||
|
robotAvator := configs["RobotAvator"]
|
||||||
|
|
||||||
|
articles := models.FindArticleList(1, 10, "score desc", "ent_id= ? and title like ?", entId, "%"+content+"%")
|
||||||
|
|
||||||
|
result := ""
|
||||||
|
articleLen := len(articles)
|
||||||
|
//未匹配,进入学习库
|
||||||
|
if articleLen == 0 {
|
||||||
|
if len([]rune(content)) > 1 {
|
||||||
|
go AddUpdateLearn(entId, content)
|
||||||
|
}
|
||||||
|
} else if articleLen == 1 {
|
||||||
|
result = articles[0].Content
|
||||||
|
article := models.Article{
|
||||||
|
Score: articles[0].Score + 1,
|
||||||
|
}
|
||||||
|
go article.SaveArticle("ent_id = ? and id = ?", entId, articles[0].Id)
|
||||||
|
} else if articleLen > 1 {
|
||||||
|
result = "您是不是想问:"
|
||||||
|
for _, article := range articles {
|
||||||
|
title := strings.Split(strings.ReplaceAll(article.Title, ",", ","), ",")[0]
|
||||||
|
result += fmt.Sprintf("<p><a href='#'>%s</a></p>", title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//调用GPT3.5
|
||||||
|
if result == "" && models.CheckVisitorRobotReply(vistorInfo.State) {
|
||||||
|
result = ws.Gpt3Knowledge(entId, visitorId, kefuInfo, content)
|
||||||
|
}
|
||||||
|
//调用chatGPT
|
||||||
|
//if result == "" {
|
||||||
|
// result = ws.Gpt3dot5Message(entId, visitorId, kefuInfo, content)
|
||||||
|
//}
|
||||||
|
models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, content, "visitor", vistorInfo.EntId, "read")
|
||||||
|
if result != "" {
|
||||||
|
go models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, result, "kefu", vistorInfo.EntId, "read")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"username": tools.Ifelse(robotName != "", robotName, kefuInfo.Nickname),
|
||||||
|
"avator": tools.Ifelse(robotAvator != "", robotAvator, kefuInfo.Avator),
|
||||||
|
"content": result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
func SendKefuMessage(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
fromId, _ := c.Get("kefu_name")
|
||||||
|
toId := c.PostForm("to_id")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
cType := c.PostForm("type")
|
||||||
|
if content == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "内容不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//过滤敏感词
|
||||||
|
content, _ = ReplaceBlackWords(content)
|
||||||
|
|
||||||
|
var kefuInfo models.User
|
||||||
|
var vistorInfo models.Visitor
|
||||||
|
kefuInfo = models.FindUser(fromId.(string))
|
||||||
|
vistorInfo = models.FindVisitorByVistorId(toId)
|
||||||
|
|
||||||
|
if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "用户不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
kefuInfo.RoleId = fmt.Sprintf("%v", roleId)
|
||||||
|
var msg ws.TypeMessage
|
||||||
|
guest, ok := ws.ClientList[vistorInfo.VisitorId]
|
||||||
|
isRead := "unread"
|
||||||
|
msgId := models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, content, cType, entId.(string), isRead)
|
||||||
|
if guest != nil && ok {
|
||||||
|
conn := guest.Conn
|
||||||
|
msg = ws.TypeMessage{
|
||||||
|
Type: "message",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
MsgId: msgId,
|
||||||
|
Name: kefuInfo.Nickname,
|
||||||
|
Avator: kefuInfo.Avator,
|
||||||
|
Id: kefuInfo.Name,
|
||||||
|
Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
ToId: vistorInfo.VisitorId,
|
||||||
|
Content: content,
|
||||||
|
IsKefu: "no",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
conn.WriteMessage(websocket.TextMessage, str)
|
||||||
|
} else {
|
||||||
|
//客服发送给公众号模板消息
|
||||||
|
//go SendWechatKefuTemplate(vistorInfo.VisitorId, kefuInfo.Name, kefuInfo.Nickname, content, fmt.Sprintf("%v", entId))
|
||||||
|
go SendKefuMessageCallUrl(kefuInfo.Name, vistorInfo.VisitorId, content, vistorInfo.EntId, kefuInfo.Nickname)
|
||||||
|
//客服发给小程序订阅模板消息
|
||||||
|
go service.SendMiniSubscribe(vistorInfo.VisitorId, content, fmt.Sprintf("%v", entId), kefuInfo.Nickname)
|
||||||
|
}
|
||||||
|
//客服发送给小程序访客消息
|
||||||
|
go SendMiniVisitorMessage(vistorInfo.VisitorId, content, fmt.Sprintf("%v", entId))
|
||||||
|
|
||||||
|
//客服发送给公众号用户信息
|
||||||
|
go SendWechatVisitorMessage(vistorInfo.VisitorId, content, fmt.Sprintf("%v", entId))
|
||||||
|
//客服发送给微信客服用户的消息
|
||||||
|
go SendWeworkKfVisitorMessage(vistorInfo, content)
|
||||||
|
//客服发送到抖音评论
|
||||||
|
go SendDouyinCommentMessage(vistorInfo, content)
|
||||||
|
//客服发送到抖音评论
|
||||||
|
go SendDouyinPrivateMessage(vistorInfo, content)
|
||||||
|
msg = ws.TypeMessage{
|
||||||
|
Type: "message",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
MsgId: msgId,
|
||||||
|
Name: kefuInfo.Nickname,
|
||||||
|
Avator: kefuInfo.Avator,
|
||||||
|
Id: vistorInfo.VisitorId,
|
||||||
|
Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
ToId: vistorInfo.VisitorId,
|
||||||
|
Content: content,
|
||||||
|
IsKefu: "yes",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str2, _ := json.Marshal(msg)
|
||||||
|
ws.OneKefuMessage(kefuInfo.Name, str2)
|
||||||
|
go models.ReadMessageByVisitorId(vistorInfo.VisitorId, "visitor")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func SendVisitorNotice(c *gin.Context) {
|
||||||
|
notice := c.Query("msg")
|
||||||
|
if notice == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "msg不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "notice",
|
||||||
|
Data: notice,
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
for _, visitor := range ws.ClientList {
|
||||||
|
visitor.Conn.WriteMessage(websocket.TextMessage, str)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendCloseMessageV2(c *gin.Context) {
|
||||||
|
ent_id, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
if visitorId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "visitor_id不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config := models.FindEntConfig(ent_id, "CloseVisitorMessage")
|
||||||
|
oldUser, ok := ws.ClientList[visitorId]
|
||||||
|
if oldUser != nil || ok {
|
||||||
|
ws.VisitorOffline(oldUser.ToId, oldUser.Id, oldUser.Name)
|
||||||
|
if config.ConfValue != "" {
|
||||||
|
kefu := models.FindUserByUid(ent_id)
|
||||||
|
ws.VisitorMessage(visitorId, config.ConfValue, kefu)
|
||||||
|
}
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "force_close",
|
||||||
|
Data: visitorId,
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
err := oldUser.Conn.WriteMessage(websocket.TextMessage, str)
|
||||||
|
oldUser.Conn.Close()
|
||||||
|
delete(ws.ClientList, visitorId)
|
||||||
|
log.Println("close_message", oldUser, err)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func UploadImg(c *gin.Context) {
|
||||||
|
f, err := c.FormFile("imgfile")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!只允许png,jpg,gif,jpeg文件",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||||
|
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
|
||||||
|
path := "/" + filepath
|
||||||
|
//上传到阿里云oss
|
||||||
|
oss, err := lib.NewOssLib()
|
||||||
|
if err == nil {
|
||||||
|
dstUrl, err := oss.Upload(filepath, filepath)
|
||||||
|
if err == nil {
|
||||||
|
path = dstUrl
|
||||||
|
} else {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"result": gin.H{
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func UploadFile(c *gin.Context) {
|
||||||
|
SendAttachment, err := strconv.ParseBool(models.FindConfig("SendAttachment"))
|
||||||
|
if !SendAttachment || err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "禁止上传附件!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err := c.FormFile("realfile")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
maxSize := 20 * 1024 * 1024
|
||||||
|
uploadMaxSize := models.FindConfig("UploadMaxSize")
|
||||||
|
if uploadMaxSize != "" {
|
||||||
|
uploadMaxSizeInt, _ := strconv.Atoi(uploadMaxSize)
|
||||||
|
maxSize = uploadMaxSizeInt * 1024 * 1024
|
||||||
|
}
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
if f.Size >= int64(maxSize) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败,文件大小超限!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||||
|
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
path := "/" + filepath
|
||||||
|
//上传到阿里云oss
|
||||||
|
oss, err := lib.NewOssLib()
|
||||||
|
if err == nil {
|
||||||
|
dstUrl, err := oss.Upload(filepath, filepath)
|
||||||
|
if err == nil {
|
||||||
|
path = dstUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"result": gin.H{
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func UploadAudio(c *gin.Context) {
|
||||||
|
SendAttachment, err := strconv.ParseBool(models.FindConfig("SendAttachment"))
|
||||||
|
if !SendAttachment || err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "禁止上传附件!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err := c.FormFile("realfile")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fileExt := ".mp3"
|
||||||
|
if f.Size >= 20*1024*1024 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!不允许超过20M",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||||
|
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"result": gin.H{
|
||||||
|
"path": filepath,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func GetMessagesV2(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
messages := models.FindMessageByVisitorId(visitorId)
|
||||||
|
//result := make([]map[string]interface{}, 0)
|
||||||
|
chatMessages := make([]ChatMessage, 0)
|
||||||
|
|
||||||
|
for _, message := range messages {
|
||||||
|
//item := make(map[string]interface{})
|
||||||
|
var chatMessage ChatMessage
|
||||||
|
chatMessage.Time = message.CreatedAt.Format("2006-01-02 15:04:05")
|
||||||
|
chatMessage.Content = message.Content
|
||||||
|
chatMessage.MesType = message.MesType
|
||||||
|
if message.MesType == "kefu" {
|
||||||
|
chatMessage.Name = message.KefuName
|
||||||
|
chatMessage.Avator = message.KefuAvator
|
||||||
|
} else {
|
||||||
|
chatMessage.Name = message.VisitorName
|
||||||
|
chatMessage.Avator = message.VisitorAvator
|
||||||
|
}
|
||||||
|
chatMessages = append(chatMessages, chatMessage)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": chatMessages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostMessagesVisitorRead(c *gin.Context) {
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
toId := c.PostForm("kefu")
|
||||||
|
models.ReadMessageByVisitorId(visitorId, "kefu")
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "read",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
VisitorId: visitorId,
|
||||||
|
ToId: toId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str2, _ := json.Marshal(msg)
|
||||||
|
ws.OneKefuMessage(toId, str2)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostMessagesKefuRead(c *gin.Context) {
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
models.ReadMessageByEntIdVisitorId(visitorId, entId.(string), "visitor")
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "read",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
VisitorId: visitorId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ws.VisitorCustomMessage(visitorId, msg)
|
||||||
|
|
||||||
|
unreadNum := models.CountMessage("kefu_id = ? and mes_type='visitor' and status = 'unread'", kefuName)
|
||||||
|
msg2 := ws.TypeMessage{
|
||||||
|
Type: "unread_num",
|
||||||
|
Data: unreadNum,
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg2)
|
||||||
|
ws.OneKefuMessage(kefuName.(string), str)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostMessagesAsk(c *gin.Context) {
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
visitorId := c.PostForm("from_id")
|
||||||
|
if content == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "内容不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entInfo := models.FindUserByUid(entId)
|
||||||
|
go models.CreateMessage(entInfo.Name, visitorId, content, "visitor", entId, "unread")
|
||||||
|
reply := models.FindReplyItemByUserIdTitle(entInfo.Name, content)
|
||||||
|
if reply.Content != "" {
|
||||||
|
go models.CreateMessage(entInfo.Name, visitorId, reply.Content, "kefu", entId, "read")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"content": reply.Content,
|
||||||
|
"name": entInfo.Nickname,
|
||||||
|
"avator": entInfo.Avator,
|
||||||
|
"time": time.Now().Format("2006-01-02 15:05:05"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "no result!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetMessagesVisitorUnread(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
messages := models.FindMessageByVisitorIdUnread(visitorId, "kefu")
|
||||||
|
chatMessages := make([]ChatMessage, 0)
|
||||||
|
|
||||||
|
for _, message := range messages {
|
||||||
|
//item := make(map[string]interface{})
|
||||||
|
var chatMessage ChatMessage
|
||||||
|
chatMessage.Time = message.CreatedAt.Format("2006-01-02 15:04:05")
|
||||||
|
chatMessage.Content = message.Content
|
||||||
|
chatMessage.MesType = message.MesType
|
||||||
|
if message.MesType == "kefu" {
|
||||||
|
chatMessage.Name = message.KefuName
|
||||||
|
chatMessage.Avator = message.KefuAvator
|
||||||
|
} else {
|
||||||
|
chatMessage.Name = message.VisitorName
|
||||||
|
chatMessage.Avator = message.VisitorAvator
|
||||||
|
}
|
||||||
|
chatMessages = append(chatMessages, chatMessage)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": chatMessages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetAllVisitorMessagesByKefu(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
messages := models.FindMessageByVisitorId(visitorId)
|
||||||
|
//result := make([]map[string]interface{}, 0)
|
||||||
|
chatMessages := make([]ChatMessage, 0)
|
||||||
|
|
||||||
|
for _, message := range messages {
|
||||||
|
//item := make(map[string]interface{})
|
||||||
|
var chatMessage ChatMessage
|
||||||
|
chatMessage.Time = message.CreatedAt.Format("2006-01-02 15:04:05")
|
||||||
|
chatMessage.Content = message.Content
|
||||||
|
chatMessage.MesType = message.MesType
|
||||||
|
if message.MesType == "kefu" {
|
||||||
|
chatMessage.Name = message.KefuName
|
||||||
|
chatMessage.Avator = message.KefuAvator
|
||||||
|
} else {
|
||||||
|
chatMessage.Name = message.VisitorName
|
||||||
|
chatMessage.Avator = message.VisitorAvator
|
||||||
|
}
|
||||||
|
chatMessages = append(chatMessages, chatMessage)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": chatMessages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVisitorListMessagesPage(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
entId := c.Query("ent_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize == 0 {
|
||||||
|
pagesize = int(common.PageSize)
|
||||||
|
}
|
||||||
|
count := models.CountMessage("message.ent_id= ? and message.visitor_id=?", entId, visitorId)
|
||||||
|
chatMessages := CommonMessagesPage(uint(page), uint(pagesize), visitorId, entId)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": gin.H{
|
||||||
|
"count": count,
|
||||||
|
"page": page,
|
||||||
|
"list": chatMessages,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVisitorListMessagesPageBykefu(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize == 0 {
|
||||||
|
pagesize = int(common.PageSize)
|
||||||
|
}
|
||||||
|
count := models.CountMessage("message.ent_id= ? and message.visitor_id=?", entId.(string), visitorId)
|
||||||
|
chatMessages := CommonMessagesPage(uint(page), uint(pagesize), visitorId, entId.(string))
|
||||||
|
go func() {
|
||||||
|
models.ReadMessageByVisitorId(visitorId, "visitor")
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "read",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
VisitorId: visitorId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ws.VisitorCustomMessage(visitorId, msg)
|
||||||
|
}()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": gin.H{
|
||||||
|
"count": count,
|
||||||
|
"page": page,
|
||||||
|
"list": chatMessages,
|
||||||
|
"pagesize": common.PageSize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DeleteMessage(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
msgId := c.PostForm("msg_id")
|
||||||
|
message := models.FindOneMessageByWhere("id=? and visitor_id=? and ent_id=?", msgId, visitorId, entId)
|
||||||
|
|
||||||
|
//判断撤回时间限制
|
||||||
|
limitTime := tools.Now() - message.CreatedAt.Unix()
|
||||||
|
systemLimitTime := models.FindConfig("KefuDeleteMessageLimitTime")
|
||||||
|
if systemLimitTime != "" && limitTime > tools.Str2Int64(systemLimitTime) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.DELETE_MESSAGE_OVERTIME,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.DELETE_MESSAGE_OVERTIME),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.DelMessage("id=? and visitor_id=? and ent_id=?", msgId, visitorId, entId)
|
||||||
|
msgIdInt, _ := strconv.Atoi(msgId)
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "delete",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
MsgId: uint(msgIdInt),
|
||||||
|
VisitorId: visitorId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ws.VisitorCustomMessage(visitorId, msg)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除访客聊天记录
|
||||||
|
func DeleteVisitorMessage(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
models.DelMessage("visitor_id=? and ent_id=?", visitorId, entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一键已读
|
||||||
|
func GetClearUnread(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
models.ReadMessageByEntIdKefuName(entId.(string), kefuName.(string))
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "unread_num",
|
||||||
|
Data: 0,
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
go ws.OneKefuMessage(kefuName.(string), str)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func CommonMessagesPage(page, pagesize uint, visitorId, entId string) []ChatMessage {
|
||||||
|
|
||||||
|
list := models.FindMessageByPage(page, pagesize, "message.ent_id= ? and message.visitor_id=?", entId, visitorId)
|
||||||
|
chatMessages := make([]ChatMessage, 0)
|
||||||
|
|
||||||
|
for _, message := range list {
|
||||||
|
//item := make(map[string]interface{})
|
||||||
|
var chatMessage ChatMessage
|
||||||
|
chatMessage.MsgId = message.ID
|
||||||
|
chatMessage.Time = message.CreatedAt.Format("2006-01-02 15:04:05")
|
||||||
|
chatMessage.Content = message.Content
|
||||||
|
chatMessage.MesType = message.MesType
|
||||||
|
chatMessage.ReadStatus = message.Status
|
||||||
|
if message.MesType == "kefu" {
|
||||||
|
chatMessage.Name = message.KefuName
|
||||||
|
chatMessage.Avator = message.KefuAvator
|
||||||
|
} else {
|
||||||
|
chatMessage.Name = message.VisitorName
|
||||||
|
chatMessage.Avator = message.VisitorAvator
|
||||||
|
}
|
||||||
|
chatMessages = append(chatMessages, chatMessage)
|
||||||
|
}
|
||||||
|
return chatMessages
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用回调通知url
|
||||||
|
func SendVisitorMessageCallUrl(entId, from, to, nickname, avatar, content string) string {
|
||||||
|
api := models.FindConfig("VisitorMessageCallUrl")
|
||||||
|
if api == "" {
|
||||||
|
api = models.FindEntConfig(entId, "VisitorMessageCallUrl").ConfValue
|
||||||
|
}
|
||||||
|
if api == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("from", from)
|
||||||
|
data.Set("to", to)
|
||||||
|
data.Set("content", content)
|
||||||
|
data.Set("nickname", nickname)
|
||||||
|
data.Set("avatar", avatar)
|
||||||
|
res, err := tools.PostForm(api, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("访客消息发送回调错误:" + err.Error())
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客服调用回调通知url
|
||||||
|
func SendKefuMessageCallUrl(kefuName, visitorId, content, entId, nickname string) string {
|
||||||
|
api := models.FindConfig("KefuMessageCallUrl")
|
||||||
|
if api == "" {
|
||||||
|
api = models.FindEntConfig(entId, "KefuMessageCallUrl").ConfValue
|
||||||
|
}
|
||||||
|
if api == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("kefu_name", kefuName)
|
||||||
|
data.Set("visitor_id", visitorId)
|
||||||
|
data.Set("ent_id", entId)
|
||||||
|
data.Set("content", content)
|
||||||
|
data.Set("nickname", nickname)
|
||||||
|
res, err := tools.PostForm(api, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("客服消息发送回调错误:" + err.Error())
|
||||||
|
}
|
||||||
|
log.Println("客服消息发送回调成功:" + data.Encode())
|
||||||
|
return res
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
)
|
||||||
|
|
||||||
|
//监控访客列表
|
||||||
|
func GetMonitorList(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
list := make([]*ws.MonitorConnection, 0)
|
||||||
|
for _, connect := range ws.MonitorList {
|
||||||
|
if connect.KefuName == kefuName {
|
||||||
|
list = append(list, connect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type MonitorMessageForm struct {
|
||||||
|
UniqId string `form:"uniqid" json:"uniqid" uri:"uniqid" xml:"uniqid" binding:"required"`
|
||||||
|
Message string `form:"message" json:"message" uri:"message" xml:"message" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//监控访客消息
|
||||||
|
func PostMonitorMessage(c *gin.Context) {
|
||||||
|
var form MonitorMessageForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
|
||||||
|
for conn, connect := range ws.MonitorList {
|
||||||
|
if connect.KefuName == kefuName && form.UniqId == connect.UinqId {
|
||||||
|
conn.WriteMessage(1, []byte(form.Message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewsForm struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
Tag string `form:"tag" json:"tag" uri:"tag" xml:"tag" binding:"required"`
|
||||||
|
Title string `form:"title" json:"title" uri:"title" xml:"title" binding:"required"`
|
||||||
|
Content string `form:"content" json:"content" uri:"content" xml:"content" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//新闻列表
|
||||||
|
func GetNews(c *gin.Context) {
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
count := models.CountNews("")
|
||||||
|
list := models.FindNews(page, pagesize, "")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//添加新闻
|
||||||
|
func PostNews(c *gin.Context) {
|
||||||
|
var form NewsForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
modelNews := &models.New{
|
||||||
|
Title: form.Title,
|
||||||
|
Content: form.Content,
|
||||||
|
Tag: form.Tag,
|
||||||
|
}
|
||||||
|
//添加新闻
|
||||||
|
if form.Id == 0 {
|
||||||
|
err := modelNews.AddNews()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//修改新闻
|
||||||
|
err := modelNews.SaveNews("id = ?", form.Id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除新闻
|
||||||
|
func DelNews(c *gin.Context) {
|
||||||
|
id := c.Query("id")
|
||||||
|
err := models.DelNews("id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KefuNoticeForm struct {
|
||||||
|
EntId string `form:"ent_id" json:"ent_id" uri:"ent_id" xml:"ent_id" binding:"required"`
|
||||||
|
KefuName string `form:"kefu_name" json:"kefu_name" uri:"kefu_name" xml:"kefu_name"`
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id"`
|
||||||
|
IsRecord string `form:"is_record" json:"is_record" uri:"is_record" xml:"is_record"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNotice(c *gin.Context) {
|
||||||
|
var form KefuNoticeForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.VisitorId != "" {
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(form.VisitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entId := form.EntId
|
||||||
|
kefuName := form.KefuName
|
||||||
|
var welcomes []models.Welcome
|
||||||
|
var kefu models.User
|
||||||
|
if kefuName != "" {
|
||||||
|
kefu = models.FindUser(kefuName)
|
||||||
|
} else {
|
||||||
|
kefu = models.FindUserByUid(entId)
|
||||||
|
}
|
||||||
|
if kefu.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查询企业配置项
|
||||||
|
configs := models.GetEntConfigsMap(form.EntId, "KefuIntroduce",
|
||||||
|
"RobotName", "RobotAvator", "RobotSwitch", "TurnToMan", "RobotNoAnswer",
|
||||||
|
"VisitorNotice", "AutoWelcome", "VisitorCookie", "VisitorMaxNum", "VisitorMaxNumNotice", "ScanWechatQrcode", "AutoOpenDialogTime",
|
||||||
|
"KefuOfflineNotice")
|
||||||
|
|
||||||
|
allOffline := false
|
||||||
|
if kefu.OnlineStatus != 1 {
|
||||||
|
allOffline = true
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]gin.H, 0)
|
||||||
|
//如果离线并且离线提示不为空
|
||||||
|
if allOffline && configs["KefuOfflineNotice"] != "" {
|
||||||
|
h := gin.H{
|
||||||
|
"name": tools.Ifelse(configs["RobotName"] != "", configs["RobotName"], kefu.Nickname),
|
||||||
|
"avator": tools.Ifelse(configs["RobotAvator"] != "", configs["RobotAvator"], kefu.Avator),
|
||||||
|
"is_kefu": false,
|
||||||
|
"content": configs["KefuOfflineNotice"],
|
||||||
|
"delay_second": 1,
|
||||||
|
"time": time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
}
|
||||||
|
result = append(result, h)
|
||||||
|
} else {
|
||||||
|
welcomes = models.FindWelcomesByKeyword(kefu.Name, "welcome")
|
||||||
|
for _, welcome := range welcomes {
|
||||||
|
h := gin.H{
|
||||||
|
"name": tools.Ifelse(configs["RobotName"] != "", configs["RobotName"], kefu.Nickname),
|
||||||
|
"avator": tools.Ifelse(configs["RobotAvator"] != "", configs["RobotAvator"], kefu.Avator),
|
||||||
|
"is_kefu": false,
|
||||||
|
"content": welcome.Content,
|
||||||
|
"delay_second": welcome.DelaySecond,
|
||||||
|
"time": time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
}
|
||||||
|
result = append(result, h)
|
||||||
|
if form.IsRecord != "" {
|
||||||
|
models.CreateMessage(kefu.Name, form.VisitorId, welcome.Content, "kefu", entId, "unread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//configs := GetEntConfigsMap(entId, "AutoOpenDialogTime", "KefuIntroduce")
|
||||||
|
//delay_second := configs["AutoOpenDialogTime"]
|
||||||
|
//ent_introduce := configs["KefuIntroduce"]
|
||||||
|
|
||||||
|
//访客Cookie有效期
|
||||||
|
if configs["VisitorCookie"] == "" {
|
||||||
|
configs["VisitorCookie"] = fmt.Sprintf("%d", 24*3600*365*100)
|
||||||
|
}
|
||||||
|
|
||||||
|
//同时接待人数
|
||||||
|
if configs["RobotName"] == "" {
|
||||||
|
configs["RobotName"] = kefu.Nickname
|
||||||
|
}
|
||||||
|
entConfig := gin.H{
|
||||||
|
"robotNoAnswer": configs["RobotNoAnswer"],
|
||||||
|
"turnToMan": configs["TurnToMan"],
|
||||||
|
"visitorMaxNumNotice": configs["VisitorMaxNumNotice"],
|
||||||
|
"entIntroduce": configs["KefuIntroduce"],
|
||||||
|
"robotSwitch": configs["RobotSwitch"],
|
||||||
|
"visitorNotice": configs["VisitorNotice"],
|
||||||
|
"autoWelcome": configs["AutoWelcome"],
|
||||||
|
"visitorCookie": configs["VisitorCookie"],
|
||||||
|
"scanWechatQrcode": configs["ScanWechatQrcode"],
|
||||||
|
"robotName": configs["RobotName"],
|
||||||
|
"autoOpenDialogTime": configs["AutoOpenDialogTime"],
|
||||||
|
"kefuOfflineNotice": configs["KefuOfflineNotice"],
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"welcome": result,
|
||||||
|
"username": kefu.Nickname,
|
||||||
|
"avatar": kefu.Avator,
|
||||||
|
"all_offline": allOffline,
|
||||||
|
"ent_config": entConfig,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOpenConfigs(c *gin.Context) {
|
||||||
|
entId := c.Query("ent_id")
|
||||||
|
|
||||||
|
configs := models.FindEntConfigs(entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": configs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetNotices(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
welcomes := models.FindWelcomesByUserId(kefuId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": welcomes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//添加welcome表
|
||||||
|
func PostNotice(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
keyword := c.PostForm("keyword")
|
||||||
|
delaySecond, _ := strconv.Atoi(c.PostForm("delay_second"))
|
||||||
|
id := c.PostForm("id")
|
||||||
|
if id != "" {
|
||||||
|
id := c.PostForm("id")
|
||||||
|
models.UpdateWelcome(fmt.Sprintf("%s", kefuId), id, content, uint(delaySecond))
|
||||||
|
} else {
|
||||||
|
models.CreateWelcome(fmt.Sprintf("%s", kefuId), content, keyword, uint(delaySecond))
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostNoticeSave(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
delaySecond, _ := strconv.Atoi(c.PostForm("delay_second"))
|
||||||
|
id := c.PostForm("id")
|
||||||
|
models.UpdateWelcome(fmt.Sprintf("%s", kefuId), id, content, uint(delaySecond))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DelNotice(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
id := c.Query("id")
|
||||||
|
models.DeleteWelcome(kefuId, id)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/ws"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"id": "chatcmpl-8cbx4v1ZZLQKV1YunB9DfzVJ8D40p",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": 1704213042,
|
||||||
|
"model": "gpt-3.5-turbo-0613",
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "我是一个AI助手,没有真实的身份,只是由计算机程序所驱动的虚拟个体。"
|
||||||
|
},
|
||||||
|
"logprobs": null,
|
||||||
|
"finish_reason": "stop"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 11,
|
||||||
|
"completion_tokens": 34,
|
||||||
|
"total_tokens": 45
|
||||||
|
},
|
||||||
|
"system_fingerprint": null
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
func PostV1ChatCompletions(c *gin.Context) {
|
||||||
|
rawData, err := c.GetRawData()
|
||||||
|
jsonData := string(rawData)
|
||||||
|
authorization := c.Request.Header.Get("Authorization")
|
||||||
|
entIdKefuName := strings.Replace(authorization, "Bearer ", "", -1)
|
||||||
|
strs := strings.Split(entIdKefuName, "-")
|
||||||
|
choices := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"message": map[string]interface{}{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err != nil || len(strs) < 3 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"id": "chatcmpl-8cbx4v1ZZLQKV1YunB9DfzVJ8D40p",
|
||||||
|
"choices": choices,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entId := strs[1]
|
||||||
|
kefuName := strs[2]
|
||||||
|
messages := gjson.Get(jsonData, "messages").Array()
|
||||||
|
content := messages[len(messages)-1].Get("content").String()
|
||||||
|
visitorId := gjson.Get(jsonData, "chatId").String()
|
||||||
|
kefuInfo := models.FindUser(kefuName)
|
||||||
|
if fmt.Sprintf("%d", kefuInfo.ID) != entId {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"id": "chatcmpl-8cbx4v1ZZLQKV1YunB9DfzVJ8D40p",
|
||||||
|
"choices": choices,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorName := "API调用"
|
||||||
|
avator := "/static/images/api-code-avatar.jpg"
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId = fmt.Sprintf("api|%s|%s", entId, visitorId)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
refer := "OpenAI客户端"
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitorName,
|
||||||
|
Avator: avator,
|
||||||
|
ToId: kefuName,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: refer,
|
||||||
|
EntId: entId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "API调用",
|
||||||
|
ClientIp: c.ClientIP(),
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
//更新状态上线
|
||||||
|
go models.UpdateVisitor(entId, visitorName, avator, visitorId, kefuName, 3, c.ClientIP(), c.ClientIP(),
|
||||||
|
refer,
|
||||||
|
refer,
|
||||||
|
refer,
|
||||||
|
vistorInfo.VisitNum+1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuName, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuName, vistorInfo, content)
|
||||||
|
//发送消息通知
|
||||||
|
go SendWechatVisitorMessageTemplate(kefuName, vistorInfo.Name, vistorInfo.VisitorId, content, vistorInfo.EntId)
|
||||||
|
go SendWorkWechatWebHook(vistorInfo.EntId, vistorInfo, kefuInfo, content, c)
|
||||||
|
//判断当前访客是否机器人回复
|
||||||
|
if !models.CheckVisitorRobotReply(vistorInfo.State) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"id": "chatcmpl-8cbx4v1ZZLQKV1YunB9DfzVJ8D40p",
|
||||||
|
"choices": choices,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := ""
|
||||||
|
config := models.GetEntConfigsMap(entId, "QdrantAIStatus")
|
||||||
|
//调用自动回复
|
||||||
|
result = GetLearnReplyContent(entId, content)
|
||||||
|
result = tools.TrimHtml(result)
|
||||||
|
if result == "" && config["QdrantAIStatus"] == "true" {
|
||||||
|
//调用GPT3.5
|
||||||
|
result = ws.Gpt3Knowledge(entId, visitorId, kefuInfo, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
go models.AddVisitorExt(visitorId, refer, c.GetHeader("User-Agent"), refer, refer, refer, c.ClientIP(), refer, entId, "")
|
||||||
|
if result != "" {
|
||||||
|
models.CreateMessage(kefuName, visitorId, result, "kefu", entId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo)
|
||||||
|
}
|
||||||
|
choices = []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"message": map[string]interface{}{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": result,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.SecureJSON(200, gin.H{
|
||||||
|
"id": "chatcmpl-8cbx4v1ZZLQKV1YunB9DfzVJ8D40p",
|
||||||
|
"choices": choices,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/service"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetWechatOpenid(c *gin.Context) {
|
||||||
|
redirectUrl := c.Query("redirect")
|
||||||
|
openId := ""
|
||||||
|
entId := models.FindConfig("SystemBussinesId")
|
||||||
|
wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
|
||||||
|
weixinCode := c.Query("code")
|
||||||
|
if weixinCode != "" {
|
||||||
|
userInfo, err := service.GetWechatUserInfo(weixinCode, entId)
|
||||||
|
if err == nil {
|
||||||
|
openId = userInfo.OpenID
|
||||||
|
} else {
|
||||||
|
log.Println("微信公众号网页授权错误:", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wechatUrl := "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + wechatConfig.AppId + "&redirect_uri=" + wechatConfig.WechatHost + "/api/v2/wechatOpenid?redirect=" + redirectUrl + "&response_type=code&scope=snsapi_userinfo#wechat_redirect"
|
||||||
|
c.Redirect(302, wechatUrl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectUrl == "" || openId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "获取openid失败或回调地址为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
redirectUrl = tools.UrlDecode(redirectUrl)
|
||||||
|
c.Redirect(302, redirectUrl+"&open_id="+openId)
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 查询订单
|
||||||
|
func PostQueryOrder(c *gin.Context) {
|
||||||
|
orderId := c.PostForm("order_id")
|
||||||
|
order := models.FindProductOrder("order_sn = ?", orderId)
|
||||||
|
if order.PaymentStatus == "paid" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": order.ShippingAddress,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "unpaid",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产品订单列表
|
||||||
|
func GetUserProductOrder(c *gin.Context) {
|
||||||
|
userId := c.Query("user_id")
|
||||||
|
entId := c.Query("ent_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
search := "1=1 "
|
||||||
|
var args = []interface{}{}
|
||||||
|
search += "and ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
|
||||||
|
search += "and user_id = ? "
|
||||||
|
args = append(args, userId)
|
||||||
|
|
||||||
|
search += "and payment_status = ? "
|
||||||
|
args = append(args, "paid")
|
||||||
|
|
||||||
|
count := models.CountProductOrders(search, args...)
|
||||||
|
list := models.FindProductOrders(page, pagesize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产品订单列表
|
||||||
|
func GetProductOrderList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
search := "1=1 "
|
||||||
|
var args = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
search += "and ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := models.CountProductOrders(search, args...)
|
||||||
|
list := models.FindProductOrders(page, pagesize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetOrders(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
search := "1=1 "
|
||||||
|
var args = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
search += "and ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := models.CountUserOrder(search, args...)
|
||||||
|
list := models.FindUserOrders(page, pagesize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,493 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 易支付下单
|
||||||
|
func PostYiApiPay(c *gin.Context) {
|
||||||
|
entId := c.PostForm("entId")
|
||||||
|
kefuName := c.PostForm("kefuName")
|
||||||
|
amount := c.PostForm("amount")
|
||||||
|
openId := c.PostForm("openId")
|
||||||
|
email := c.PostForm("email")
|
||||||
|
product := c.PostForm("product")
|
||||||
|
tel := c.PostForm("tel")
|
||||||
|
contact := c.PostForm("contact")
|
||||||
|
|
||||||
|
virtualProduct := models.FindVirtualProduct("ent_id = ? and id = ?", entId, product)
|
||||||
|
description := fmt.Sprintf("%s,支付金额:%s (分)", tools.SubStr(virtualProduct.ProductName, 0, 20), amount)
|
||||||
|
snow, _ := tools.NewSnowflake(1)
|
||||||
|
orderId := fmt.Sprintf("%s%d", time.Now().Format("0601021504"), snow.Generate())
|
||||||
|
log.Println(tools.Str2Int(amount)/100, tools.Str2Int(amount))
|
||||||
|
config := models.GetEntConfigsMap(entId, "Nan66Api", "Nan66Shopid", "Nan66ShopSecret")
|
||||||
|
|
||||||
|
currentHost := tools.GetHost(c.Request)
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"pid": config["Nan66Shopid"],
|
||||||
|
"type": "wxpay",
|
||||||
|
"out_trade_no": orderId,
|
||||||
|
"notify_url": currentHost + "/2/" + entId + "/yiApiPayNotifyUrl",
|
||||||
|
"return_url": currentHost + "/2/" + entId + "/yiApiPayNotifyUrl",
|
||||||
|
"name": description,
|
||||||
|
"money": float64(tools.Str2Int64(amount)) / 100,
|
||||||
|
"clientip": c.ClientIP(),
|
||||||
|
}
|
||||||
|
var keys []string
|
||||||
|
// 筛选、排序参数
|
||||||
|
for key := range params {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
sortedParams := make([]string, len(keys))
|
||||||
|
for i, key := range keys {
|
||||||
|
sortedParams[i] = fmt.Sprintf("%s=%v", key, params[key])
|
||||||
|
}
|
||||||
|
waitSign := strings.Join(sortedParams, "&")
|
||||||
|
sign := tools.Md5(waitSign + config["Nan66ShopSecret"])
|
||||||
|
body := fmt.Sprintf("%s&sign=%s&sign_type=MD5", waitSign, sign)
|
||||||
|
log.Println("请求易支付", config["Nan66Api"], body)
|
||||||
|
resp, err := tools.Post(config["Nan66Api"], "", []byte(body))
|
||||||
|
log.Println("请求易支付", resp, err)
|
||||||
|
productOrder := &models.ProductOrder{
|
||||||
|
KefuName: kefuName,
|
||||||
|
OrderSn: orderId,
|
||||||
|
UserId: openId,
|
||||||
|
EntID: entId,
|
||||||
|
OrderStatus: "processing",
|
||||||
|
TotalAmount: tools.Str2Int64(amount),
|
||||||
|
PaymentMethod: "nan66",
|
||||||
|
PaymentStatus: "unpaid",
|
||||||
|
ShippingAddress: virtualProduct.ResourceLink,
|
||||||
|
OrderDesc: description,
|
||||||
|
Contact: contact,
|
||||||
|
Email: email,
|
||||||
|
Tel: tel,
|
||||||
|
}
|
||||||
|
productOrder.AddProductOrder()
|
||||||
|
c.Writer.Write([]byte(resp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 易支付回调
|
||||||
|
func GetYiApiPayNotifyUrl(c *gin.Context) {
|
||||||
|
entId := c.Param("entId")
|
||||||
|
config := models.GetEntConfigsMap(entId, "Nan66Api", "Nan66Shopid", "Nan66ShopSecret")
|
||||||
|
out_trade_no := c.Query("out_trade_no")
|
||||||
|
trade_status := c.Query("trade_status")
|
||||||
|
money := c.Query("money")
|
||||||
|
name := c.Query("name")
|
||||||
|
pid := c.Query("pid")
|
||||||
|
trade_no := c.Query("trade_no")
|
||||||
|
types := c.Query("type")
|
||||||
|
sign := c.Query("sign")
|
||||||
|
param := c.Query("param")
|
||||||
|
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"pid": pid,
|
||||||
|
"type": types,
|
||||||
|
"out_trade_no": out_trade_no,
|
||||||
|
"trade_no": trade_no,
|
||||||
|
"name": name,
|
||||||
|
"money": money,
|
||||||
|
"param": param,
|
||||||
|
"trade_status": trade_status,
|
||||||
|
}
|
||||||
|
var keys []string
|
||||||
|
// 筛选、排序参数
|
||||||
|
for key := range params {
|
||||||
|
if params[key] == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
sortedParams := make([]string, len(keys))
|
||||||
|
for i, key := range keys {
|
||||||
|
sortedParams[i] = fmt.Sprintf("%s=%v", key, params[key])
|
||||||
|
}
|
||||||
|
waitSign := strings.Join(sortedParams, "&")
|
||||||
|
newSign := tools.Md5(waitSign + config["Nan66ShopSecret"])
|
||||||
|
if sign != newSign {
|
||||||
|
log.Println("易支付验证签名失败", params, sign, newSign)
|
||||||
|
c.Writer.Write([]byte("success"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
order := models.FindProductOrder("order_sn = ?", out_trade_no)
|
||||||
|
if trade_status == "TRADE_SUCCESS" && order.PaymentStatus == "unpaid" {
|
||||||
|
newOrder := &models.ProductOrder{
|
||||||
|
PaymentStatus: "paid",
|
||||||
|
}
|
||||||
|
newOrder.SaveProductOrder("order_sn = ?", out_trade_no)
|
||||||
|
}
|
||||||
|
c.Writer.Write([]byte("success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsapi支付下单
|
||||||
|
func PostJsApiCreateOrder(c *gin.Context) {
|
||||||
|
entId := c.PostForm("entId")
|
||||||
|
kefuName := c.PostForm("kefuName")
|
||||||
|
amount := c.PostForm("amount")
|
||||||
|
openId := c.PostForm("openId")
|
||||||
|
email := c.PostForm("email")
|
||||||
|
product := c.PostForm("product")
|
||||||
|
tel := c.PostForm("tel")
|
||||||
|
contact := c.PostForm("contact")
|
||||||
|
|
||||||
|
virtualProduct := models.FindVirtualProduct("ent_id = ? and id = ?", entId, product)
|
||||||
|
description := fmt.Sprintf("%s,支付金额:%s(分)", tools.SubStr(virtualProduct.ProductName, 0, 20), amount)
|
||||||
|
snow, _ := tools.NewSnowflake(1)
|
||||||
|
orderId := fmt.Sprintf("%s%d", time.Now().Format("0601021504"), snow.Generate())
|
||||||
|
|
||||||
|
appId := models.FindConfig("WechatPayAppId") //公众号APPID
|
||||||
|
mchID := models.FindConfig("WechatPayMchID") //商户号
|
||||||
|
mchCertificateSerialNumber := models.FindConfig("WechatPayMchCertificateSerialNumber") //商户证书序列号
|
||||||
|
//商户API V3密钥
|
||||||
|
mchAPIv3Key := models.FindConfig("WechatPayMchAPIv3Key")
|
||||||
|
//商户私钥
|
||||||
|
mchPrivateKey := models.FindConfig("WechatPayMchPrivateKey")
|
||||||
|
notifyUrl := models.FindConfig("WechatJsApiNotifyUrl")
|
||||||
|
if appId == "" || mchID == "" || mchCertificateSerialNumber == "" || mchAPIv3Key == "" || mchPrivateKey == "" || notifyUrl == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "微信支付配置为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wechatpay, err := lib.NewWechatPay(appId, mchID, mchCertificateSerialNumber, mchAPIv3Key, mchPrivateKey, notifyUrl)
|
||||||
|
resp, err := wechatpay.JsApiPreOrder(openId, tools.Str2Int64(amount), description, orderId)
|
||||||
|
log.Println("微信支付JSAPI下单:", resp, err)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
productOrder := &models.ProductOrder{
|
||||||
|
KefuName: kefuName,
|
||||||
|
OrderSn: orderId,
|
||||||
|
UserId: openId,
|
||||||
|
EntID: entId,
|
||||||
|
OrderStatus: "processing",
|
||||||
|
TotalAmount: tools.Str2Int64(amount),
|
||||||
|
PaymentMethod: "wechat",
|
||||||
|
PaymentStatus: "unpaid",
|
||||||
|
ShippingAddress: virtualProduct.ResourceLink,
|
||||||
|
OrderDesc: description,
|
||||||
|
Contact: contact,
|
||||||
|
Email: email,
|
||||||
|
Tel: tel,
|
||||||
|
}
|
||||||
|
productOrder.AddProductOrder()
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "下单成功!",
|
||||||
|
"resource_link": virtualProduct.ResourceLink,
|
||||||
|
"result": resp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSAPI支付回调
|
||||||
|
func PostjsApiPayNotifyUrl(c *gin.Context) {
|
||||||
|
appId := models.FindConfig("WechatPayAppId") //公众号APPID
|
||||||
|
mchID := models.FindConfig("WechatPayMchID") //商户号
|
||||||
|
mchCertificateSerialNumber := models.FindConfig("WechatPayMchCertificateSerialNumber") //商户证书序列号
|
||||||
|
//商户API V3密钥
|
||||||
|
mchAPIv3Key := models.FindConfig("WechatPayMchAPIv3Key")
|
||||||
|
//商户私钥
|
||||||
|
mchPrivateKey := models.FindConfig("WechatPayMchPrivateKey")
|
||||||
|
notifyUrl := models.FindConfig("WechatJsApiNotifyUrl")
|
||||||
|
|
||||||
|
wechatpay, _ := lib.NewWechatPay(appId, mchID, mchCertificateSerialNumber, mchAPIv3Key, mchPrivateKey, notifyUrl)
|
||||||
|
result, err := wechatpay.ParseNotifyRequest(c.Request)
|
||||||
|
resultStr, err := json.Marshal(result)
|
||||||
|
log.Println("微信支付回调:", string(resultStr), err)
|
||||||
|
|
||||||
|
//增加时间
|
||||||
|
trade_state := gjson.Get(string(resultStr), "trade_state").String()
|
||||||
|
out_trade_no := gjson.Get(string(resultStr), "out_trade_no").String()
|
||||||
|
amount := gjson.Get(string(resultStr), "amount.total").Int()
|
||||||
|
order := models.FindProductOrder("order_sn = ?", out_trade_no)
|
||||||
|
if trade_state == "SUCCESS" && order.PaymentStatus == "unpaid" {
|
||||||
|
|
||||||
|
newOrder := &models.ProductOrder{
|
||||||
|
PaymentStatus: "paid",
|
||||||
|
}
|
||||||
|
newOrder.SaveProductOrder("order_sn = ?", out_trade_no)
|
||||||
|
|
||||||
|
//增加余额
|
||||||
|
userAttr := models.FindUserAttrRow("ent_id = ? and kefu_name = ?", order.EntID, order.KefuName)
|
||||||
|
if userAttr.ID == 0 {
|
||||||
|
userAttr.Money = amount
|
||||||
|
userAttr.EntId = order.EntID
|
||||||
|
userAttr.KefuName = order.KefuName
|
||||||
|
userAttr.AddUserAttr()
|
||||||
|
} else {
|
||||||
|
userAttr.Money = userAttr.Money + amount
|
||||||
|
userAttr.EntId = order.EntID
|
||||||
|
userAttr.KefuName = order.KefuName
|
||||||
|
userAttr.SaveUserAttr("ent_id = ? and kefu_name = ?", order.EntID, order.KefuName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// native支付下单在线支付
|
||||||
|
func PostCreateOrder(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
payMonth := c.PostForm("pay_month")
|
||||||
|
months := tools.Str2Int64(payMonth)
|
||||||
|
|
||||||
|
//计算到期时间
|
||||||
|
incSecond := months * 30 * 24 * 60 * 60
|
||||||
|
kefuInfo := models.FindUser(kefuName.(string))
|
||||||
|
nowTime := time.Now().Unix()
|
||||||
|
expireTime := kefuInfo.ExpiredAt.Unix()
|
||||||
|
newExpireInt := expireTime + incSecond
|
||||||
|
//如果早就过期,当前时间增加
|
||||||
|
if expireTime < nowTime {
|
||||||
|
newExpireInt = nowTime + incSecond
|
||||||
|
}
|
||||||
|
newExpireStr := tools.IntToTimeStr(newExpireInt, "2006-01-02")
|
||||||
|
|
||||||
|
amount := months * 60 * (int64(kefuInfo.AgentNum) + 1) * 100
|
||||||
|
description := fmt.Sprintf("客服系统充值服务%s个月,到期时间:%s", payMonth, newExpireStr)
|
||||||
|
snow, _ := tools.NewSnowflake(1)
|
||||||
|
orderId := fmt.Sprintf("%s%d", time.Now().Format("0601021504"), snow.Generate())
|
||||||
|
|
||||||
|
appId := models.FindConfig("WechatPayAppId") //公众号APPID
|
||||||
|
mchID := models.FindConfig("WechatPayMchID") //商户号
|
||||||
|
mchCertificateSerialNumber := models.FindConfig("WechatPayMchCertificateSerialNumber") //商户证书序列号
|
||||||
|
//商户API V3密钥
|
||||||
|
mchAPIv3Key := models.FindConfig("WechatPayMchAPIv3Key")
|
||||||
|
//商户私钥
|
||||||
|
mchPrivateKey := models.FindConfig("WechatPayMchPrivateKey")
|
||||||
|
notifyUrl := models.FindConfig("WechatPayNotifyUrl")
|
||||||
|
|
||||||
|
wechatpay, err := lib.NewWechatPay(appId, mchID, mchCertificateSerialNumber, mchAPIv3Key, mchPrivateKey, notifyUrl)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
codeUrl, err := wechatpay.NativePayPreOrder(amount, description, orderId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orderModels := &models.User_order{
|
||||||
|
OrderSn: orderId,
|
||||||
|
Money: uint(amount),
|
||||||
|
NewExpireTime: newExpireStr,
|
||||||
|
Payment: "wechat",
|
||||||
|
Type: 1,
|
||||||
|
Comment: description,
|
||||||
|
CreatedAt: types.Time{time.Now()},
|
||||||
|
Operator: kefuName.(string),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
EntId: entId.(string),
|
||||||
|
}
|
||||||
|
orderModels.AddUserOrder()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"code_url": codeUrl,
|
||||||
|
"order_id": orderId,
|
||||||
|
"ammount": amount,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在线支付回调
|
||||||
|
func PostWechatPayNotifyUrl(c *gin.Context) {
|
||||||
|
appId := models.FindConfig("WechatPayAppId") //公众号APPID
|
||||||
|
mchID := models.FindConfig("WechatPayMchID") //商户号
|
||||||
|
mchCertificateSerialNumber := models.FindConfig("WechatPayMchCertificateSerialNumber") //商户证书序列号
|
||||||
|
//商户API V3密钥
|
||||||
|
mchAPIv3Key := models.FindConfig("WechatPayMchAPIv3Key")
|
||||||
|
//商户私钥
|
||||||
|
mchPrivateKey := models.FindConfig("WechatPayMchPrivateKey")
|
||||||
|
notifyUrl := models.FindConfig("WechatPayNotifyUrl")
|
||||||
|
|
||||||
|
wechatpay, _ := lib.NewWechatPay(appId, mchID, mchCertificateSerialNumber, mchAPIv3Key, mchPrivateKey, notifyUrl)
|
||||||
|
result, err := wechatpay.ParseNotifyRequest(c.Request)
|
||||||
|
log.Println("微信支付回调:", result, err)
|
||||||
|
|
||||||
|
//增加时间
|
||||||
|
trade_state := result["trade_state"].(string)
|
||||||
|
out_trade_no := result["out_trade_no"].(string)
|
||||||
|
order := models.FindUserOrder("order_sn = ?", out_trade_no)
|
||||||
|
if trade_state == "SUCCESS" && order.Type != 2 {
|
||||||
|
models.UpdateUserOrderTypedWhere(2, "order_sn = ?", out_trade_no)
|
||||||
|
models.UpdateUserExpiredWhere(order.NewExpireTime, " name = ? ", order.KefuName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询订单
|
||||||
|
func PostQueryWechatOrder(c *gin.Context) {
|
||||||
|
orderId := c.PostForm("order_id")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
|
||||||
|
appId := models.FindConfig("WechatPayAppId") //公众号APPID
|
||||||
|
mchID := models.FindConfig("WechatPayMchID") //商户号
|
||||||
|
mchCertificateSerialNumber := models.FindConfig("WechatPayMchCertificateSerialNumber") //商户证书序列号
|
||||||
|
//商户API V3密钥
|
||||||
|
mchAPIv3Key := models.FindConfig("WechatPayMchAPIv3Key")
|
||||||
|
//商户私钥
|
||||||
|
mchPrivateKey := models.FindConfig("WechatPayMchPrivateKey")
|
||||||
|
notifyUrl := models.FindConfig("WechatPayNotifyUrl")
|
||||||
|
|
||||||
|
wechatpay, _ := lib.NewWechatPay(appId, mchID, mchCertificateSerialNumber, mchAPIv3Key, mchPrivateKey, notifyUrl)
|
||||||
|
|
||||||
|
body, res, err := wechatpay.NativePayQueryOrder(orderId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(body, err)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res != "SUCCESS" {
|
||||||
|
log.Println("微信支付订单查询:", body)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": "订单未成功支付",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查询订单状态
|
||||||
|
order := models.FindUserOrder("ent_id = ? and order_sn = ?", entId, orderId)
|
||||||
|
if order.Type == 2 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "订单成功支付",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.UpdateUserOrderTypedWhere(2, "ent_id = ? and order_sn = ?", entId, orderId)
|
||||||
|
models.UpdateUserExpiredWhere(order.NewExpireTime, " name = ? ", kefuName)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "订单成功支付",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetSystemConfig(c *gin.Context) {
|
||||||
|
alipayImageUrl := models.FindConfig("AlipayImageUrl")
|
||||||
|
wechatImageUrl := models.FindConfig("WechatImageUrl")
|
||||||
|
bankInfo := models.FindConfig("BankInfo")
|
||||||
|
version := models.FindConfig("SystemVersion")
|
||||||
|
versionName := models.FindConfig("SystemVersionName")
|
||||||
|
kefuExpired := models.FindConfig("KefuExpired")
|
||||||
|
KefuBindTel := models.FindConfig("KefuBindTel")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"alipayImageUrl": alipayImageUrl,
|
||||||
|
"wechatImageUrl": wechatImageUrl,
|
||||||
|
"bankInfo": bankInfo,
|
||||||
|
"version": version,
|
||||||
|
"versionName": versionName,
|
||||||
|
"kefuExpired": kefuExpired,
|
||||||
|
"KefuBindTel": KefuBindTel,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 管理员充值
|
||||||
|
func PostAdminUserCharge(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
myName, _ := c.Get("kefu_name")
|
||||||
|
username := c.PostForm("kefu_name")
|
||||||
|
money := c.PostForm("money")
|
||||||
|
payment := c.PostForm("payment")
|
||||||
|
var user *models.User
|
||||||
|
if tools.IsPhoneNumber(username) {
|
||||||
|
user = &models.User{
|
||||||
|
Tel: username,
|
||||||
|
}
|
||||||
|
} else if tools.IsEmail(username) {
|
||||||
|
user = &models.User{
|
||||||
|
Email: username,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user = &models.User{
|
||||||
|
Name: username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuInfo := user.GetOneUser("*")
|
||||||
|
if kefuInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "账号不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//充值天数
|
||||||
|
moneyInt, _ := strconv.Atoi(money)
|
||||||
|
dayInt := int64(moneyInt * 24 * 3600)
|
||||||
|
|
||||||
|
nowTime := time.Now().Unix()
|
||||||
|
expireTime := kefuInfo.ExpiredAt.Unix()
|
||||||
|
newExpireInt := expireTime + dayInt
|
||||||
|
//如果早就过期,当前时间增加天数
|
||||||
|
if expireTime < nowTime {
|
||||||
|
newExpireInt = nowTime + dayInt
|
||||||
|
}
|
||||||
|
newExpireStr := tools.IntToTimeStr(newExpireInt, "2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
query := " name = ? "
|
||||||
|
arg := []interface{}{
|
||||||
|
kefuInfo.Name,
|
||||||
|
}
|
||||||
|
models.UpdateUserExpiredWhere(newExpireStr, query, arg...)
|
||||||
|
|
||||||
|
//记录订单
|
||||||
|
snow, _ := tools.NewSnowflake(1)
|
||||||
|
orderModels := &models.User_order{
|
||||||
|
OrderSn: fmt.Sprintf("SN%d", snow.Generate()),
|
||||||
|
Money: uint(moneyInt) * uint(common.EveryDayYuan) * 100,
|
||||||
|
Payment: payment,
|
||||||
|
Type: 2,
|
||||||
|
Comment: "管理员充值 , 到期 " + newExpireStr,
|
||||||
|
CreatedAt: types.Time{time.Now()},
|
||||||
|
Operator: myName.(string),
|
||||||
|
KefuName: kefuInfo.Name,
|
||||||
|
EntId: fmt.Sprintf("%d", kefuInfo.ID),
|
||||||
|
}
|
||||||
|
orderModels.AddUserOrder()
|
||||||
|
logContent := fmt.Sprintf("'%s'为'%s'充值%s天,方式 %s,到期 %s", myName, kefuInfo.Name, money, payment, newExpireStr)
|
||||||
|
go models.CreateFlyLog(entId.(string), c.ClientIP(), logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "充值成功",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/ws"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostCallKefu(c *gin.Context) {
|
||||||
|
kefuId := c.PostForm("kefu_id")
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
kefuInfo := models.FindUser(kefuId)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "用户不存在",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "callpeer",
|
||||||
|
Data: ws.ClientMessage{
|
||||||
|
Avator: vistorInfo.Avator,
|
||||||
|
Id: vistorInfo.VisitorId,
|
||||||
|
Name: vistorInfo.Name,
|
||||||
|
ToId: kefuInfo.Name,
|
||||||
|
Content: "请求通话",
|
||||||
|
Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
IsKefu: "no",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
ws.OneKefuMessage(kefuInfo.Name, str)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostKefuPeerId(c *gin.Context) {
|
||||||
|
peerId := c.PostForm("peer_id")
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
if peerId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "peer_id不能为空",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "peerid",
|
||||||
|
Data: peerId,
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
visitor, ok := ws.ClientList[visitorId]
|
||||||
|
if !ok || visitor.Name == "" || kefuName != visitor.ToId {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "客户不存在",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitor.Conn.WriteMessage(websocket.TextMessage, str)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
)
|
||||||
|
|
||||||
|
type preVisitorForm struct {
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id"`
|
||||||
|
EntId string `form:"ent_id" json:"ent_id" uri:"ent_id" xml:"ent_id"`
|
||||||
|
Name string `form:"name" json:"name" uri:"name" xml:"name"`
|
||||||
|
Tel string `form:"tel" json:"tel" uri:"tel" xml:"tel"`
|
||||||
|
Wechat string `form:"wechat" json:"wechat" uri:"wechat" xml:"wechat"`
|
||||||
|
QQ string `form:"qq" json:"qq" uri:"qq" xml:"qq"`
|
||||||
|
Email string `form:"email" json:"email" uri:"email" xml:"email"`
|
||||||
|
Remark string `form:"remark" json:"remark" uri:"remark" xml:"remark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostPreVisitorForm(c *gin.Context) {
|
||||||
|
var form preVisitorForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
HandleError(c, types.ApiCode.FAILED, types.ApiCode.GetMessage(types.ApiCode.INVALID), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
})
|
||||||
|
attrInfo := models.GetVisitorAttrByVisitorId(form.VisitorId, form.EntId)
|
||||||
|
if attrInfo.ID == 0 {
|
||||||
|
models.CreateVisitorAttr(
|
||||||
|
form.EntId,
|
||||||
|
form.VisitorId,
|
||||||
|
form.Name,
|
||||||
|
form.Tel,
|
||||||
|
form.Email,
|
||||||
|
form.QQ,
|
||||||
|
form.Wechat,
|
||||||
|
form.Remark)
|
||||||
|
} else {
|
||||||
|
var attr = &models.Visitor_attr{
|
||||||
|
RealName: form.Name,
|
||||||
|
Tel: form.Tel,
|
||||||
|
Email: form.Email,
|
||||||
|
QQ: form.QQ,
|
||||||
|
Wechat: form.Wechat,
|
||||||
|
Comment: form.Remark,
|
||||||
|
}
|
||||||
|
models.SaveVisitorAttrByVisitorId(attr, form.VisitorId, form.EntId)
|
||||||
|
}
|
||||||
|
if form.Name != "" {
|
||||||
|
models.UpdateVisitorRealName(form.Name,
|
||||||
|
form.EntId,
|
||||||
|
form.VisitorId)
|
||||||
|
go ws.UpdateVisitorName(form.VisitorId, form.Name)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tealeg/xlsx"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"io/ioutil"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QdrantCollect struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
CateName string `form:"cate_name" json:"cate_name" uri:"cate_name" xml:"cate_name" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 集合列表
|
||||||
|
func GetCollects(c *gin.Context) {
|
||||||
|
api := models.FindConfig("BaseGPTKnowledge")
|
||||||
|
if api == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret, err := lib.GetCollections()
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//c.Writer.Write(list)
|
||||||
|
//info := tools.Get(fmt.Sprintf("%s/collects", api))
|
||||||
|
list := gjson.Get(string(ret), "result.collections").String()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑qdrant
|
||||||
|
func PostQdrantCollect(c *gin.Context) {
|
||||||
|
var form QdrantCollect
|
||||||
|
err := c.Bind(&form)
|
||||||
|
//api := models.FindConfig("BaseGPTKnowledge")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
collectName := form.CateName
|
||||||
|
//判断集合是否存在
|
||||||
|
collectInfo, _ := lib.GetCollection(collectName)
|
||||||
|
collectInfoStatus := gjson.Get(string(collectInfo), "status").String()
|
||||||
|
if collectInfoStatus != "ok" {
|
||||||
|
res, err := lib.PutCollection(collectName)
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer.Write([]byte(res))
|
||||||
|
} else {
|
||||||
|
c.Writer.Write(collectInfo)
|
||||||
|
}
|
||||||
|
//tools.PostForm(fmt.Sprintf("%s/collect/%s", api, form.CateName), nil)
|
||||||
|
//c.JSON(200, gin.H{
|
||||||
|
// "code": types.ApiCode.SUCCESS,
|
||||||
|
// "msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
//})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除集合
|
||||||
|
func GetDelCollect(c *gin.Context) {
|
||||||
|
collectName := c.Query("collectName")
|
||||||
|
api := models.FindConfig("BaseGPTKnowledge")
|
||||||
|
if api == "" || collectName == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info, _ := lib.DeleteCollection(collectName)
|
||||||
|
c.Writer.Write([]byte(info))
|
||||||
|
|
||||||
|
//c.JSON(200, gin.H{
|
||||||
|
// "code": types.ApiCode.SUCCESS,
|
||||||
|
// "msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
// "result": info,
|
||||||
|
//})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文档
|
||||||
|
func PostUploadCollect(c *gin.Context) {
|
||||||
|
collectName := c.Query("collect")
|
||||||
|
api := models.FindConfig("BaseGPTKnowledge")
|
||||||
|
if api == "" || collectName == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
if fileExt != ".docx" && fileExt != ".txt" && fileExt != ".xlsx" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!只允许txt或xlsx文件",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileName := collectName + f.Filename
|
||||||
|
c.SaveUploadedFile(f, fileName)
|
||||||
|
texts := make([]string, 0)
|
||||||
|
if fileExt == ".txt" {
|
||||||
|
// 打开txt文件
|
||||||
|
file, _ := os.Open(fileName)
|
||||||
|
// 一次性读取整个txt文件的内容
|
||||||
|
txt, _ := ioutil.ReadAll(file)
|
||||||
|
texts = append(texts, string(txt))
|
||||||
|
file.Close()
|
||||||
|
} else if fileExt == ".xlsx" {
|
||||||
|
// 打开 Excel 文件
|
||||||
|
xlFile, err := xlsx.OpenFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "读取excel失败:" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 遍历每个 Sheet
|
||||||
|
for _, sheet := range xlFile.Sheets {
|
||||||
|
// 遍历每行数据
|
||||||
|
for _, row := range sheet.Rows {
|
||||||
|
line := ""
|
||||||
|
// 遍历每个单元格
|
||||||
|
for _, cell := range row.Cells {
|
||||||
|
// 输出单元格的值
|
||||||
|
line += cell.Value
|
||||||
|
}
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
texts = append(texts, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := os.Remove(fileName)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Remove(fileName)
|
||||||
|
for _, text := range texts {
|
||||||
|
log.Println(text)
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("content", text)
|
||||||
|
_, err := tools.PostForm(fmt.Sprintf("%s/%s/training", api, collectName), data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/skip2/go-qrcode"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetQrcode(c *gin.Context) {
|
||||||
|
str := c.Query("str")
|
||||||
|
var png []byte
|
||||||
|
png, err := qrcode.Encode(str, qrcode.High, 512)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Writer.Header().Set("Content-Type", "image/png")
|
||||||
|
c.Writer.Write(png)
|
||||||
|
}
|
||||||
|
func PostDynicQrcode(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
domain := c.PostForm("domain")
|
||||||
|
dynicQrType := c.PostForm("dynicQrType")
|
||||||
|
resetFlag := c.PostForm("resetFlag")
|
||||||
|
uniqId := tools.Uuid()
|
||||||
|
|
||||||
|
result := fmt.Sprintf("%s/%s?ent_id=%s&kefu_id=%s", domain, dynicQrType, entId, kefuName)
|
||||||
|
|
||||||
|
//清除旧的,插入新的关联表
|
||||||
|
args := []interface{}{
|
||||||
|
entId, kefuName,
|
||||||
|
}
|
||||||
|
if resetFlag == "reset" {
|
||||||
|
models.DelQrcode("ent_id = ? and kefu_name = ?", args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
models.CreateQrcode(entId.(string), uniqId, result, kefuName.(string))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"info": result,
|
||||||
|
"uuid": uniqId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetCheckQrcode(c *gin.Context) {
|
||||||
|
uuid := c.Query("uuid")
|
||||||
|
qr := models.FindQrcode("uuid = ?", uuid)
|
||||||
|
if qr.Url == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "二维码不存在或者已经失效",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Redirect(302, qr.Url)
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReplyForm struct {
|
||||||
|
GroupName string `form:"group_name" binding:"required"`
|
||||||
|
IsTeam string `form:"is_team" binding:"required"`
|
||||||
|
}
|
||||||
|
type ReplyContentForm struct {
|
||||||
|
GroupId string `form:"group_id" binding:"required"`
|
||||||
|
Content string `form:"content" binding:"required"`
|
||||||
|
ItemName string `form:"item_name" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//企业公用快捷回复列表
|
||||||
|
func GetEntReplys(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
res := models.FindReplyByEntId(entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetReplys(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
log.Println(kefuId)
|
||||||
|
res := models.FindReplyByUserId(kefuId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetAutoReplys(c *gin.Context) {
|
||||||
|
entId := c.Query("ent_id")
|
||||||
|
kefu := models.FindUserByUid(entId)
|
||||||
|
var res []*models.ReplyGroup
|
||||||
|
res = models.FindReplyTitleByUserId(kefu.Name)
|
||||||
|
var single interface{}
|
||||||
|
if len(res) >= 1 {
|
||||||
|
single = res[0]
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": single,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostReply(c *gin.Context) {
|
||||||
|
var replyForm ReplyForm
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
err := c.Bind(&replyForm)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "error:" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.CreateReplyGroup(replyForm.GroupName, kefuId.(string), entId.(string), replyForm.IsTeam)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostReplyContent(c *gin.Context) {
|
||||||
|
var replyContentForm ReplyContentForm
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
err := c.Bind(&replyContentForm)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "error:" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.CreateReplyContent(replyContentForm.GroupId, kefuId.(string), replyContentForm.Content, replyContentForm.ItemName, entId.(string))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostReplyContentSave(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
replyId := c.PostForm("reply_id")
|
||||||
|
replyTitle := c.PostForm("reply_title")
|
||||||
|
replyContent := c.PostForm("reply_content")
|
||||||
|
if replyId == "" || replyTitle == "" || replyContent == "" {
|
||||||
|
c.JSON(400, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "参数错误!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.UpdateReplyContent(replyId, kefuId.(string), replyTitle, replyContent)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DelReplyContent(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
id := c.Query("id")
|
||||||
|
models.DeleteReplyContent(id, kefuId.(string))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DelReplyGroup(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
id := c.Query("id")
|
||||||
|
models.DeleteReplyGroup(id, kefuId.(string))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostReplySearch(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
search := c.PostForm("search")
|
||||||
|
if search == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "参数错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := models.FindReplyBySearcch(entId, kefuId, search)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": res,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"kefu/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Port string
|
||||||
|
Address string
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
result interface{} `json:"result"`
|
||||||
|
}
|
||||||
|
type ChatMessage struct {
|
||||||
|
MsgId uint `json:"msg_id"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
MesType string `json:"mes_type"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Avator string `json:"avator"`
|
||||||
|
ReadStatus string `json:"read_status"`
|
||||||
|
}
|
||||||
|
type VisitorOnline struct {
|
||||||
|
Id uint `json:"id"`
|
||||||
|
VisitorId string `json:"visitor_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Avator string `json:"avator"`
|
||||||
|
Ip string `json:"ip"`
|
||||||
|
LastMessage string `json:"last_message"`
|
||||||
|
LastTime string `json:"last_time"`
|
||||||
|
City string `json:"city"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UnreadNum uint32 `json:"unread_num"`
|
||||||
|
Status uint `json:"status"`
|
||||||
|
}
|
||||||
|
type GetuiResponse struct {
|
||||||
|
Code float64 `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
Data map[string]interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
type VisitorExtra struct {
|
||||||
|
VisitorName string `json:"visitorName"`
|
||||||
|
VisitorAvatar string `json:"visitorAvatar"`
|
||||||
|
VisitorId string `json:"visitorId"`
|
||||||
|
}
|
||||||
|
type VisitorExtend struct {
|
||||||
|
ID uint `gorm:"primary_key" json:"id"`
|
||||||
|
VisitorId string `json:"visitor_id"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Refer string `json:"refer"`
|
||||||
|
ReferUrl string `json:"refer_url"`
|
||||||
|
Ua string `json:"ua"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
City string `json:"city"`
|
||||||
|
ClientIp string `json:"client_ip"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
Browser string `json:"browser"`
|
||||||
|
OsVersion string `json:"os_version"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
}
|
||||||
|
type Visitor struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Avator string `json:"avator"`
|
||||||
|
ToId string `json:"to_id"`
|
||||||
|
VisitorId string `json:"visitor_id"`
|
||||||
|
City string `json:"city"`
|
||||||
|
ClientIp string `json:"client_ip"`
|
||||||
|
EntId string `json:"ent_id"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
type VisitorAttrParams struct {
|
||||||
|
VisitorId string `json:"visitor_id"`
|
||||||
|
VisitorAttr models.Visitor_attr `json:"visitor_attr"`
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"io"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostApiRobotMessageForm struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id" binding:"required"`
|
||||||
|
Content string `form:"content" json:"content" uri:"content" xml:"content" binding:"required"`
|
||||||
|
VisitorName string `form:"visitor_name" json:"visitor_name" uri:"visitor_name" xml:"visitor_name"`
|
||||||
|
Refer string `form:"refer" json:"refer" uri:"refer" xml:"refer"`
|
||||||
|
Avatar string `form:"avatar" json:"avatar" uri:"avatar" xml:"avatar"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// api调用方式插入访客
|
||||||
|
func PostApiRobotMessage(c *gin.Context) {
|
||||||
|
var form PostApiRobotMessageForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitor_id := form.VisitorId
|
||||||
|
visitor_name := form.VisitorName
|
||||||
|
avatar := form.Avatar
|
||||||
|
content := form.Content
|
||||||
|
entId := c.Param("entId")
|
||||||
|
kefuName := c.Param("kefuName")
|
||||||
|
kefuInfo, _ := c.Get("kefuInfo")
|
||||||
|
avator := avatar
|
||||||
|
if visitor_name == "" {
|
||||||
|
visitor_name = "API访客"
|
||||||
|
}
|
||||||
|
if avator == "" {
|
||||||
|
avator = "/static/images/api-code-avatar.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.Refer == "" {
|
||||||
|
form.Refer = "来自API调用方"
|
||||||
|
}
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId := fmt.Sprintf("api|%s|%s", entId, visitor_id)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitor_name,
|
||||||
|
Avator: avator,
|
||||||
|
ToId: kefuName,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: form.Refer,
|
||||||
|
EntId: entId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "API调用",
|
||||||
|
ClientIp: c.ClientIP(),
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//更新状态上线
|
||||||
|
go models.UpdateVisitor(entId, form.VisitorName, avatar, visitorId, kefuName, 3, c.ClientIP(), c.ClientIP(),
|
||||||
|
form.Refer,
|
||||||
|
form.Refer,
|
||||||
|
form.Refer,
|
||||||
|
vistorInfo.VisitNum+1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuName, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuName, vistorInfo, content)
|
||||||
|
//发送消息通知
|
||||||
|
go SendWechatVisitorMessageTemplate(kefuName, vistorInfo.Name, vistorInfo.VisitorId, content, vistorInfo.EntId)
|
||||||
|
go SendWorkWechatWebHook(vistorInfo.EntId, vistorInfo, kefuInfo.(models.User), content, c)
|
||||||
|
//判断当前访客是否机器人回复
|
||||||
|
if !models.CheckVisitorRobotReply(vistorInfo.State) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"content": "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := ""
|
||||||
|
config := models.GetEntConfigsMap(entId, "QdrantAIStatus")
|
||||||
|
//调用自动回复
|
||||||
|
result = GetLearnReplyContent(entId, content)
|
||||||
|
if result == "" && config["QdrantAIStatus"] == "true" {
|
||||||
|
//调用GPT3.5
|
||||||
|
result = ws.Gpt3Knowledge(entId, visitorId, kefuInfo.(models.User), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
go models.AddVisitorExt(visitorId, form.Refer, c.GetHeader("User-Agent"), form.Refer, form.Refer, form.Refer, c.ClientIP(), form.Refer, entId, "")
|
||||||
|
if result != "" {
|
||||||
|
models.CreateMessage(kefuName, visitorId, result, "kefu", entId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo.(models.User))
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"content": result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信自动回复
|
||||||
|
func PostWechatRobotMessage(c *gin.Context) {
|
||||||
|
visitor_id := c.PostForm("visitor_id")
|
||||||
|
nickName := c.PostForm("nickname")
|
||||||
|
avatar := "/static/images/we-chat-wx.png"
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
content := c.PostForm("content")
|
||||||
|
kefuName := c.PostForm("kefu_name")
|
||||||
|
|
||||||
|
kefuInfo := models.FindUserByUid(entId)
|
||||||
|
if kefuInfo.ID == 0 || kefuInfo.Name != kefuName {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId := fmt.Sprintf("wxperson|%s|%s", entId, visitor_id)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
visitorName := nickName
|
||||||
|
avator := avatar
|
||||||
|
if visitorName == "" {
|
||||||
|
visitorName = "微信客服用户"
|
||||||
|
}
|
||||||
|
if avator == "" {
|
||||||
|
avator = "/static/images/we-chat-wx.png"
|
||||||
|
}
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitorName,
|
||||||
|
Avator: avator,
|
||||||
|
ToId: kefuInfo.Name,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: "来自个人微信",
|
||||||
|
EntId: entId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "个人微信",
|
||||||
|
ClientIp: c.ClientIP(),
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
go models.UpdateVisitorStatus(visitorId, 3)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuInfo.Name, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuInfo.Name, vistorInfo, content)
|
||||||
|
|
||||||
|
//调用GPT3.5
|
||||||
|
result := ws.Gpt3Knowledge(entId, visitorId, kefuInfo, content)
|
||||||
|
models.CreateMessage(kefuInfo.Name, visitorId, result, "kefu", entId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"content": result,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
func GetKefuAiKnowledge(c *gin.Context) {
|
||||||
|
c.Header("Content-Type", "text/html;charset=utf-8;")
|
||||||
|
f, _ := c.Writer.(http.Flusher)
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
config := models.GetEntConfigsMap(entId.(string), "chatGPTUrl", "chatGPTSecret", "QdrantAIStatus", "QdrantAICollect", "chatGPTSystem", "chatGPTPrompt",
|
||||||
|
"RobotName", "RobotAvator",
|
||||||
|
"chatGPTHistory")
|
||||||
|
api := models.FindConfig("BaseGPTKnowledge")
|
||||||
|
if config["chatGPTUrl"] == "" || config["chatGPTSecret"] == "" || config["QdrantAICollect"] == "" || api == "" {
|
||||||
|
c.Writer.Write([]byte("未配置AI服务"))
|
||||||
|
f.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("%s/%s/searchStream", api, config["QdrantAICollect"])
|
||||||
|
data := url.Values{}
|
||||||
|
system := ""
|
||||||
|
if config["chatGPTSystem"] != "" {
|
||||||
|
system = config["chatGPTSystem"]
|
||||||
|
}
|
||||||
|
data.Set("system", system)
|
||||||
|
data.Set("prompt", config["chatGPTPrompt"])
|
||||||
|
data.Set("gptUrl", config["chatGPTUrl"])
|
||||||
|
data.Set("gptSecret", config["chatGPTSecret"])
|
||||||
|
|
||||||
|
messages := models.FindMessageByQueryPage(1, 6, "visitor_id = ?", visitorId)
|
||||||
|
gptMessages := make([]lib.Gpt3Dot5Message, 0)
|
||||||
|
for i := len(messages) - 1; i >= 1; i-- {
|
||||||
|
reqContent := messages[i].Content
|
||||||
|
if messages[i].MesType == "visitor" {
|
||||||
|
gptMessages = append(gptMessages, lib.Gpt3Dot5Message{
|
||||||
|
Role: "user",
|
||||||
|
Content: reqContent,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
gptMessages = append(gptMessages, lib.Gpt3Dot5Message{
|
||||||
|
Role: "assistant",
|
||||||
|
Content: reqContent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
historyJson, _ := json.Marshal(gptMessages)
|
||||||
|
data.Set("history", string(historyJson))
|
||||||
|
data.Set("keywords", messages[0].Content)
|
||||||
|
|
||||||
|
log.Printf("请求GPT:%v", data)
|
||||||
|
reader, body, err := tools.PostFormStream(path, data)
|
||||||
|
if err == nil {
|
||||||
|
for {
|
||||||
|
r, _, err := reader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
c.Writer.Write([]byte(string(r)))
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
body.Close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetRoleListOwn(c *gin.Context) {
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
var roles []models.Role
|
||||||
|
if roleId.(float64) == 1 {
|
||||||
|
roles = models.FindRoles()
|
||||||
|
} else {
|
||||||
|
roles = models.FindRolesOwn(roleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": roles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetRoleList(c *gin.Context) {
|
||||||
|
roles := models.FindRoles()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "获取成功",
|
||||||
|
"result": roles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostRole(c *gin.Context) {
|
||||||
|
roleId := c.PostForm("id")
|
||||||
|
method := c.PostForm("method")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
path := c.PostForm("path")
|
||||||
|
if roleId == "" || method == "" || name == "" || path == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "参数不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.SaveRole(roleId, name, method, path)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "修改成功",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,158 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
//
|
||||||
|
//import (
|
||||||
|
// "encoding/json"
|
||||||
|
// "fmt"
|
||||||
|
// "github.com/gin-gonic/gin"
|
||||||
|
// "kefu/models"
|
||||||
|
// "kefu/tools"
|
||||||
|
// "kefu/types"
|
||||||
|
// "kefu/ws"
|
||||||
|
// "math/rand"
|
||||||
|
// "time"
|
||||||
|
//)
|
||||||
|
//
|
||||||
|
//func PostRoomLogin(c *gin.Context) {
|
||||||
|
// avator := c.PostForm("avator")
|
||||||
|
// if avator == "" {
|
||||||
|
// avator = fmt.Sprintf("/static/images/%d.jpg", rand.Intn(14))
|
||||||
|
// }
|
||||||
|
// toId := c.PostForm("to_id")
|
||||||
|
// entId := c.PostForm("ent_id")
|
||||||
|
// visitorName := c.PostForm("visitor_name")
|
||||||
|
// id := c.PostForm("visitor_id")
|
||||||
|
//
|
||||||
|
// if id == "" {
|
||||||
|
// id = tools.Uuid()
|
||||||
|
// }
|
||||||
|
// var city string
|
||||||
|
// ipCity, _ := tools.ParseIpNew(c.ClientIP())
|
||||||
|
// if ipCity != nil {
|
||||||
|
// city = ipCity.CountryName + ipCity.RegionName + ipCity.CityName
|
||||||
|
// } else {
|
||||||
|
// city = "未识别地区"
|
||||||
|
// }
|
||||||
|
// client_ip := c.ClientIP()
|
||||||
|
// if avator == "" || toId == "" || id == "" || entId == "" {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 400,
|
||||||
|
// "msg": "error",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// entUsers := models.FindUsersByEntId(entId)
|
||||||
|
// var flag = false
|
||||||
|
// for _, user := range entUsers {
|
||||||
|
// if user.Name == toId {
|
||||||
|
// flag = true
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if !flag {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 400,
|
||||||
|
// "msg": "room_id不存在",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// visitor := models.FindVisitorByVistorId(id)
|
||||||
|
// if visitor.Name != "" {
|
||||||
|
// //更新状态上线
|
||||||
|
// models.UpdateVisitor(entId, visitorName, avator, id, toId, visitor.Status, c.ClientIP(), c.ClientIP(), "", "", "", visitor.VisitNum+1)
|
||||||
|
// } else {
|
||||||
|
// visitor = *models.CreateVisitor(visitorName, avator, client_ip, toId, id, "", city, client_ip, entId, "")
|
||||||
|
// }
|
||||||
|
// result := &Visitor{
|
||||||
|
// City: city,
|
||||||
|
// Name: visitorName,
|
||||||
|
// Avator: avator,
|
||||||
|
// ToId: toId,
|
||||||
|
// ClientIp: client_ip,
|
||||||
|
// VisitorId: id,
|
||||||
|
// EntId: entId,
|
||||||
|
// CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
// UpdatedAt: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// userInfo := make(map[string]string)
|
||||||
|
// userInfo["username"] = visitorName
|
||||||
|
// userInfo["avator"] = avator
|
||||||
|
// msg := ws.TypeMessage{
|
||||||
|
// Type: "userOnline",
|
||||||
|
// Data: userInfo,
|
||||||
|
// }
|
||||||
|
// str, _ := json.Marshal(msg)
|
||||||
|
// go func() {
|
||||||
|
// time.Sleep(3 * time.Second)
|
||||||
|
// ws.Room.SendMessageToRoom(toId, str)
|
||||||
|
// }()
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 200,
|
||||||
|
// "msg": "ok",
|
||||||
|
// "result": result,
|
||||||
|
// })
|
||||||
|
//}
|
||||||
|
//func PostRoomMessage(c *gin.Context) {
|
||||||
|
// fromId := c.PostForm("from_id")
|
||||||
|
// toId := c.PostForm("to_id")
|
||||||
|
// content := c.PostForm("content")
|
||||||
|
// if content == "" {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 400,
|
||||||
|
// "msg": "内容不能为空",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// //限流
|
||||||
|
// if !tools.LimitFreqSingle("sendmessage:"+c.ClientIP(), 1, 2) {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 400,
|
||||||
|
// "msg": c.ClientIP() + "发送频率过快",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// //验证访客黑名单
|
||||||
|
// if !CheckVisitorBlack(fromId) {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": types.ApiCode.VISITOR_BAN,
|
||||||
|
// "msg": types.ApiCode.GetMessage(types.ApiCode.VISITOR_BAN),
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// vistorInfo := models.FindVisitorByVistorId(fromId)
|
||||||
|
// kefuInfo := models.FindUser(toId)
|
||||||
|
// if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 400,
|
||||||
|
// "msg": "room不存在",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, content, "visitor", vistorInfo.EntId, "unread")
|
||||||
|
// msg := ws.TypeMessage{
|
||||||
|
// Type: "message",
|
||||||
|
// Data: ws.ClientMessage{
|
||||||
|
// Avator: vistorInfo.Avator,
|
||||||
|
// Id: vistorInfo.VisitorId,
|
||||||
|
// Name: fmt.Sprintf("#%d%s", vistorInfo.ID, vistorInfo.Name),
|
||||||
|
// ToId: kefuInfo.Name,
|
||||||
|
// Content: content,
|
||||||
|
// Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
// IsKefu: "no",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// guest, ok := ws.ClientList[vistorInfo.VisitorId]
|
||||||
|
// if ok && guest != nil {
|
||||||
|
// guest.UpdateTime = time.Now()
|
||||||
|
// }
|
||||||
|
// str, _ := json.Marshal(msg)
|
||||||
|
// go ws.OneKefuMessage(kefuInfo.Name, str)
|
||||||
|
// go ws.Room.SendMessageToRoom(toId, str)
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 200,
|
||||||
|
// "msg": "ok",
|
||||||
|
// })
|
||||||
|
//}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
//私钥签名
|
||||||
|
func GetSignRsa(c *gin.Context) {
|
||||||
|
//publicKey := c.PostForm("publicKey")
|
||||||
|
privateKey := c.PostForm("privateKey")
|
||||||
|
//rsa := tools.NewRsa(publicKey, privateKey)
|
||||||
|
content := c.PostForm("content")
|
||||||
|
res := tools.Rsa2PriSign(content, privateKey, crypto.SHA256)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": res,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//公钥验证签名
|
||||||
|
func PostCheckSignRsa(c *gin.Context) {
|
||||||
|
publicKey := c.PostForm("publicKey")
|
||||||
|
//privateKey := c.PostForm("privateKey")
|
||||||
|
sign := c.PostForm("sign")
|
||||||
|
source := c.PostForm("source")
|
||||||
|
res, err := tools.Rsa2PubCheckSign(source, sign, publicKey, crypto.SHA256)
|
||||||
|
msg := "ok"
|
||||||
|
if err != nil {
|
||||||
|
msg = err.Error()
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": msg,
|
||||||
|
"result": res,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,381 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/silenceper/wechat/v2"
|
||||||
|
offConfig "github.com/silenceper/wechat/v2/officialaccount/config"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetConfigs(c *gin.Context) {
|
||||||
|
configs := models.FindConfigs()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": configs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetEntConfigs(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
configs := models.FindEntConfigs(entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": configs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetConfig(c *gin.Context) {
|
||||||
|
key := c.Query("key")
|
||||||
|
config := models.FindConfig(key)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetEntConfig(c *gin.Context) {
|
||||||
|
key := c.Query("key")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
config := models.FindEntConfig(entId, key)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": config.ConfValue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostEntConfigs(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
key := c.PostForm("key")
|
||||||
|
value := c.PostForm("value")
|
||||||
|
if key == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config := models.FindEntConfig(entId, key)
|
||||||
|
if config.ID == 0 {
|
||||||
|
models.CreateEntConfig(entId, name, key, value)
|
||||||
|
} else {
|
||||||
|
models.UpdateEntConfig(entId, name, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostEntConfigsByAdmin(c *gin.Context) {
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
key := c.PostForm("key")
|
||||||
|
value := c.PostForm("value")
|
||||||
|
if key == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config := models.FindEntConfig(entId, key)
|
||||||
|
if config.ID == 0 {
|
||||||
|
models.CreateEntConfig(entId, name, key, value)
|
||||||
|
} else {
|
||||||
|
models.UpdateEntConfig(entId, name, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存微信菜单数据
|
||||||
|
func PostWechatMenu(c *gin.Context) {
|
||||||
|
kefuId, _ := c.Get("kefu_id")
|
||||||
|
menu := c.PostForm("menu")
|
||||||
|
name := c.PostForm("name")
|
||||||
|
if menu == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config := models.FindEntConfig(kefuId, "WechatMenu")
|
||||||
|
if config.ID == 0 {
|
||||||
|
models.CreateEntConfig(kefuId, name, "WechatMenu", menu)
|
||||||
|
} else {
|
||||||
|
models.UpdateEntConfig(kefuId, name, "WechatMenu", menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成微信菜单
|
||||||
|
func GetWechatMenu(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
config := models.FindEntConfig(entId, "WechatMenu")
|
||||||
|
if config.ID == 0 || config.ConfValue == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "没有菜单数据",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wechatConfig, err := lib.NewWechatLib(entId.(string))
|
||||||
|
if wechatConfig == nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wc := wechat.NewWechat()
|
||||||
|
cfg := &offConfig.Config{
|
||||||
|
AppID: wechatConfig.AppId,
|
||||||
|
AppSecret: wechatConfig.AppSecret,
|
||||||
|
Token: wechatConfig.Token,
|
||||||
|
//EncodingAESKey: "xxxx",
|
||||||
|
Cache: memory,
|
||||||
|
}
|
||||||
|
officialAccount := wc.GetOfficialAccount(cfg)
|
||||||
|
menu := officialAccount.GetMenu()
|
||||||
|
err = menu.SetMenuByJSON(config.ConfValue)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除菜单
|
||||||
|
func GetDelWechatMenu(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
wechatConfig, err := lib.NewWechatLib(entId.(string))
|
||||||
|
if wechatConfig == nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wc := wechat.NewWechat()
|
||||||
|
cfg := &offConfig.Config{
|
||||||
|
AppID: wechatConfig.AppId,
|
||||||
|
AppSecret: wechatConfig.AppSecret,
|
||||||
|
Token: wechatConfig.Token,
|
||||||
|
//EncodingAESKey: "xxxx",
|
||||||
|
Cache: memory,
|
||||||
|
}
|
||||||
|
officialAccount := wc.GetOfficialAccount(cfg)
|
||||||
|
menu := officialAccount.GetMenu()
|
||||||
|
err = menu.DeleteMenu()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostConfig(c *gin.Context) {
|
||||||
|
key := c.PostForm("key")
|
||||||
|
value := c.PostForm("value")
|
||||||
|
if key == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.UpdateConfig(key, value)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置机器人信息
|
||||||
|
func GetRobotInfo(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"name": models.FindEntConfig(entId, "RobotName").ConfValue,
|
||||||
|
"avator": models.FindEntConfig(entId, "RobotAvator").ConfValue,
|
||||||
|
"status": models.FindEntConfig(entId, "RobotStatus").ConfValue,
|
||||||
|
"noAnswer": models.FindEntConfig(entId, "RobotNoAnswer").ConfValue,
|
||||||
|
"transferKefu": models.FindEntConfig(entId, "TurnToMan").ConfValue,
|
||||||
|
"chatGPTSecret": models.FindEntConfig(entId, "chatGPTSecret").ConfValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置机器人信息
|
||||||
|
func PostSetRobotInfo(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
models.DelEntConfig("ent_id = ? and (conf_key='chatGPTSecret' or conf_key='RobotAvator' or conf_key='RobotStatus' or conf_key='RobotName' or conf_key='RobotNoAnswer' or conf_key='TurnToMan')", entId)
|
||||||
|
configs := []models.EntConfig{
|
||||||
|
{
|
||||||
|
ConfName: "机器人名称",
|
||||||
|
ConfKey: "RobotName",
|
||||||
|
ConfValue: c.PostForm("name"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "机器人头像",
|
||||||
|
ConfKey: "RobotAvator",
|
||||||
|
ConfValue: c.PostForm("avator"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "机器人接待方式",
|
||||||
|
ConfKey: "RobotStatus",
|
||||||
|
ConfValue: c.PostForm("status"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "机器人无法解答",
|
||||||
|
ConfKey: "RobotNoAnswer",
|
||||||
|
ConfValue: c.PostForm("noAnswer"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "转接人工",
|
||||||
|
ConfKey: "TurnToMan",
|
||||||
|
ConfValue: c.PostForm("transferKefu"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "chatGPT OpenAI接口密钥",
|
||||||
|
ConfKey: "chatGPTSecret",
|
||||||
|
ConfValue: c.PostForm("chatGPTSecret"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
models.CreateMany(entId, configs)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传微信认证文件
|
||||||
|
func PostUploadWechatFile(c *gin.Context) {
|
||||||
|
SendAttachment, err := strconv.ParseBool(models.FindConfig("SendAttachment"))
|
||||||
|
if !SendAttachment || err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "禁止上传附件!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
if f.Size >= 1*1024*1024 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!不允许超过1M",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if fileExt != ".txt" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!只允许txt文件",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SaveUploadedFile(f, f.Filename)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"result": gin.H{
|
||||||
|
"path": "/" + f.Filename,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置工作时间
|
||||||
|
func PostWorkTime(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
models.DelEntConfig("ent_id = ? and conf_key in ('afterWorkMessage','workTimeStatus','workDay','morningWorkTime','afternoonWorkTime','otherWorkTime')", entId)
|
||||||
|
configs := []models.EntConfig{
|
||||||
|
{
|
||||||
|
ConfName: "是否开启上班时间",
|
||||||
|
ConfKey: "workTimeStatus",
|
||||||
|
ConfValue: c.PostForm("workTimeStatus"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "工作日选择",
|
||||||
|
ConfKey: "workDay",
|
||||||
|
ConfValue: c.PostForm("workDay"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "上午时间段",
|
||||||
|
ConfKey: "morningWorkTime",
|
||||||
|
ConfValue: c.PostForm("morningWorkTime"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "下午时间段",
|
||||||
|
ConfKey: "afternoonWorkTime",
|
||||||
|
ConfValue: c.PostForm("afternoonWorkTime"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "其他时间段",
|
||||||
|
ConfKey: "otherWorkTime",
|
||||||
|
ConfValue: c.PostForm("otherWorkTime"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ConfName: "下班后消息提示",
|
||||||
|
ConfKey: "afterWorkMessage",
|
||||||
|
ConfValue: c.PostForm("afterWorkMessage"),
|
||||||
|
EntId: entId.(string),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
models.CreateMany(entId, configs)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,422 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SendServerJiang(title string, content string, domain string) string {
|
||||||
|
noticeServerJiang, err := strconv.ParseBool(models.FindConfig("NoticeServerJiang"))
|
||||||
|
serverJiangAPI := models.FindConfig("ServerJiangAPI")
|
||||||
|
if err != nil || !noticeServerJiang || serverJiangAPI == "" {
|
||||||
|
log.Println("do not notice serverjiang:", serverJiangAPI, noticeServerJiang)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sendStr := fmt.Sprintf("%s%s", title, content)
|
||||||
|
desp := title + ":" + content + "[登录](http://" + domain + "/main)"
|
||||||
|
url := serverJiangAPI + "?text=" + sendStr + "&desp=" + desp
|
||||||
|
//log.Println(url)
|
||||||
|
res := tools.Get(url)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
func SendVisitorLoginNotice(kefuName, visitorName, avator, content, visitorId string) {
|
||||||
|
if !tools.LimitFreqSingle("sendnotice:"+visitorId, 1, 5) {
|
||||||
|
log.Println("SendVisitorLoginNotice limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userInfo := make(map[string]string)
|
||||||
|
userInfo["username"] = visitorName
|
||||||
|
userInfo["avator"] = avator
|
||||||
|
userInfo["content"] = content
|
||||||
|
msg := ws.TypeMessage{
|
||||||
|
Type: "notice",
|
||||||
|
Data: userInfo,
|
||||||
|
}
|
||||||
|
str, _ := json.Marshal(msg)
|
||||||
|
ws.OneKefuMessage(kefuName, str)
|
||||||
|
}
|
||||||
|
func SendNoticeEmail(username, subject, entId, content string) {
|
||||||
|
if !tools.LimitFreqSingle("send_notice_email:"+username, 1, 10) {
|
||||||
|
log.Println("send_notice_email limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configs := models.FindEntConfigByEntid(entId)
|
||||||
|
smtp := ""
|
||||||
|
email := ""
|
||||||
|
password := ""
|
||||||
|
for _, config := range configs {
|
||||||
|
if config.ConfKey == "NoticeEmailAddress" {
|
||||||
|
email = config.ConfValue
|
||||||
|
}
|
||||||
|
if config.ConfKey == "NoticeEmailPassword" {
|
||||||
|
password = config.ConfValue
|
||||||
|
}
|
||||||
|
if config.ConfKey == "NoticeEmailSmtp" {
|
||||||
|
smtp = config.ConfValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if smtp == "" || email == "" || password == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("发送访客通知邮件:" + smtp + "," + email + "," + password)
|
||||||
|
err := tools.SendSmtp(smtp, email, password, []string{email}, subject, content)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送访客通知邮件失败:")
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func SendEntSmtpEmail(subject, msg, entId string) {
|
||||||
|
if !tools.LimitFreqSingle("send_ent_email:"+entId, 1, 2) {
|
||||||
|
log.Println("send_ent_email limit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
configs := models.FindEntConfigByEntid(entId)
|
||||||
|
smtp := ""
|
||||||
|
email := ""
|
||||||
|
password := ""
|
||||||
|
for _, config := range configs {
|
||||||
|
if config.ConfKey == "NoticeEmailAddress" {
|
||||||
|
email = config.ConfValue
|
||||||
|
}
|
||||||
|
if config.ConfKey == "NoticeEmailPassword" {
|
||||||
|
password = config.ConfValue
|
||||||
|
}
|
||||||
|
if config.ConfKey == "NoticeEmailSmtp" {
|
||||||
|
smtp = config.ConfValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if smtp == "" || email == "" || password == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := tools.SendSmtp(smtp, email, password, []string{email}, subject, msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func SendAppGetuiPush(kefu string, title, content string) {
|
||||||
|
clientModel := &models.User_client{
|
||||||
|
Kefu: kefu,
|
||||||
|
}
|
||||||
|
clientInfos := clientModel.FindClients()
|
||||||
|
if len(clientInfos) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
appid := models.FindConfig("GetuiAppID")
|
||||||
|
appkey := models.FindConfig("GetuiAppKey")
|
||||||
|
appsecret := models.FindConfig("GetuiAppSecret")
|
||||||
|
appmastersecret := models.FindConfig("GetuiMasterSecret")
|
||||||
|
token := models.FindConfig("GetuiToken")
|
||||||
|
getui := &lib.Getui{
|
||||||
|
AppId: appid,
|
||||||
|
AppKey: appkey,
|
||||||
|
AppSecret: appsecret,
|
||||||
|
AppMasterSecret: appmastersecret,
|
||||||
|
}
|
||||||
|
for _, client := range clientInfos {
|
||||||
|
res, err := getui.PushSingle(token, client.Client_id, title, content)
|
||||||
|
//不正确的账号
|
||||||
|
if res == 20001 && err.Error() == "target user is invalid" {
|
||||||
|
clientModel2 := &models.User_client{
|
||||||
|
Kefu: kefu,
|
||||||
|
Client_id: client.Client_id,
|
||||||
|
}
|
||||||
|
clientModel2.DeleteClient()
|
||||||
|
}
|
||||||
|
if res == 10001 {
|
||||||
|
token, _ = getui.GetGetuiToken()
|
||||||
|
models.UpdateConfig("GetuiToken", token)
|
||||||
|
getui.PushSingle(token, client.Client_id, title, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送邮件验证码
|
||||||
|
func SendEmailAuthCode(c *gin.Context) {
|
||||||
|
email := c.PostForm("email")
|
||||||
|
//验证邮箱
|
||||||
|
matched, _ := regexp.MatchString("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*", email)
|
||||||
|
if !matched {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.INVALID,
|
||||||
|
"msg": "邮箱格式不正确",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tools.LimitFreqSingle("send_email_authcode:"+email, 1, 50) {
|
||||||
|
log.Println("send_email_authcode limit " + email)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FREQ_LIMIT,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.FREQ_LIMIT),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
smtp := models.FindConfig("NoticeEmailSmtp")
|
||||||
|
sender := models.FindConfig("NoticeEmailAddress")
|
||||||
|
password := models.FindConfig("NoticeEmailPassword")
|
||||||
|
|
||||||
|
if smtp == "" || sender == "" || password == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.INVALID,
|
||||||
|
"msg": "系统没有配置发送邮箱",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ranNum := tools.RandNum(6)
|
||||||
|
session := sessions.DefaultMany(c, "go-session")
|
||||||
|
session.Set("emailCode"+email, ranNum)
|
||||||
|
_ = session.Save()
|
||||||
|
content := strings.Replace(common.NoticeTemplate, "[:content]", "你好,验证码为 "+ranNum, -1)
|
||||||
|
|
||||||
|
log.Println("发送邮件验证码:" + smtp + "," + sender + "," + password + "," + email + "," + content)
|
||||||
|
|
||||||
|
logContent := fmt.Sprintf("'%s'获取邮箱验证码 %s", email, ranNum)
|
||||||
|
go models.CreateFlyLog(email, c.ClientIP(), logContent, "user")
|
||||||
|
|
||||||
|
err := tools.SendSmtp(smtp, sender, password, []string{email}, "在线客服系统验证码", content)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logContent := "发送邮件验证码失败:" + err.Error()
|
||||||
|
log.Println(logContent)
|
||||||
|
go models.CreateFlyLog(email, c.ClientIP(), logContent, "user")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.EMAIL_FAILD,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送注册通知邮件
|
||||||
|
func SendRegisterEmail(username, nickname, loginEmail string) {
|
||||||
|
|
||||||
|
smtp := models.FindConfig("NoticeEmailSmtp")
|
||||||
|
sender := models.FindConfig("NoticeEmailAddress")
|
||||||
|
password := models.FindConfig("NoticeEmailPassword")
|
||||||
|
|
||||||
|
if smtp == "" || sender == "" || password == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := fmt.Sprintf("账户:%s,邮箱:%s,昵称:%s,注册成功", username, nickname, loginEmail)
|
||||||
|
|
||||||
|
err := tools.SendSmtp(smtp, sender, password, []string{sender}, "在线客服系统注册成功", content)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送邮件失败:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送短信验证码
|
||||||
|
func SendSmsCode(c *gin.Context) {
|
||||||
|
phone := c.Query("phone")
|
||||||
|
//验证
|
||||||
|
matched, _ := regexp.MatchString("1[3-9]\\d{9}$", phone)
|
||||||
|
if !matched {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.INVALID,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !tools.LimitFreqSingle("sendsms:"+c.ClientIP(), 1, 50) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FREQ_LIMIT,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.FREQ_LIMIT),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ranNum := tools.RandNum(6)
|
||||||
|
session := sessions.DefaultMany(c, "go-session-a")
|
||||||
|
session.Set("smsCode"+phone, ranNum)
|
||||||
|
_ = session.Save()
|
||||||
|
|
||||||
|
log.Println("发送短信验证码:", phone, ranNum)
|
||||||
|
|
||||||
|
err := lib.SendSms(phone, ranNum)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("发送短信验证码:", err)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.EMAIL_FAILD,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
logContent := fmt.Sprintf("'%s'手机%s,发送短信验证码'%s'", kefuName, phone, ranNum)
|
||||||
|
go models.CreateFlyLog(entId.(string), c.ClientIP(), logContent, "sms")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送微信模板消息
|
||||||
|
func PostSendWechatTemplate(c *gin.Context) {
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
kefuName := c.PostForm("kefu_name")
|
||||||
|
tel := c.PostForm("tel")
|
||||||
|
customer := models.FindCustomerWhere("kefu_name = ? and tel = ?", kefuName, tel)
|
||||||
|
if customer.AcountOpenid == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "未查询到绑定的公众号OpenID",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
templateId := c.PostForm("template_id")
|
||||||
|
url := c.PostForm("url")
|
||||||
|
keyword1 := c.PostForm("keyword1")
|
||||||
|
keyword2 := c.PostForm("keyword2")
|
||||||
|
keyword3 := c.PostForm("keyword3")
|
||||||
|
keyword4 := c.PostForm("keyword4")
|
||||||
|
keyword5 := c.PostForm("keyword5")
|
||||||
|
keyword6 := c.PostForm("keyword6")
|
||||||
|
remark := c.PostForm("remark")
|
||||||
|
wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
|
||||||
|
msgData := make(map[string]*message.TemplateDataItem)
|
||||||
|
msgData["keyword1"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword1,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword2"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword2,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword3"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword3,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword4"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword4,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword5"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword5,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword6"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword6,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["remark"] = &message.TemplateDataItem{
|
||||||
|
Value: remark,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msg := &message.TemplateMessage{
|
||||||
|
ToUser: customer.AcountOpenid,
|
||||||
|
Data: msgData,
|
||||||
|
TemplateID: templateId,
|
||||||
|
URL: url,
|
||||||
|
}
|
||||||
|
_, err := SendWechatTemplate(wechatConfig, msg)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送微信模板消息
|
||||||
|
func PostSendWechatTemplateByOpenid(c *gin.Context) {
|
||||||
|
openid := c.PostForm("openid")
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
if openid == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "请传递公众号OpenID!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
templateId := c.PostForm("template_id")
|
||||||
|
url := c.PostForm("url")
|
||||||
|
keyword1 := c.PostForm("keyword1")
|
||||||
|
keyword2 := c.PostForm("keyword2")
|
||||||
|
keyword3 := c.PostForm("keyword3")
|
||||||
|
keyword4 := c.PostForm("keyword4")
|
||||||
|
keyword5 := c.PostForm("keyword5")
|
||||||
|
keyword6 := c.PostForm("keyword6")
|
||||||
|
remark := c.PostForm("remark")
|
||||||
|
systemBussinesId := models.FindConfig("SystemBussinesId")
|
||||||
|
if entId == "" {
|
||||||
|
entId = systemBussinesId
|
||||||
|
}
|
||||||
|
wechatConfig, _ := lib.NewWechatLib(entId)
|
||||||
|
|
||||||
|
msgData := make(map[string]*message.TemplateDataItem)
|
||||||
|
msgData["keyword1"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword1,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword2"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword2,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword3"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword3,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword4"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword4,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword5"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword5,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["keyword6"] = &message.TemplateDataItem{
|
||||||
|
Value: keyword6,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msgData["remark"] = &message.TemplateDataItem{
|
||||||
|
Value: remark,
|
||||||
|
Color: "",
|
||||||
|
}
|
||||||
|
msg := &message.TemplateMessage{
|
||||||
|
ToUser: openid,
|
||||||
|
Data: msgData,
|
||||||
|
TemplateID: templateId,
|
||||||
|
URL: url,
|
||||||
|
}
|
||||||
|
_, err := SendWechatTemplate(wechatConfig, msg)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetClean(c *gin.Context) {
|
||||||
|
//初始化配置项
|
||||||
|
models.InitConfig()
|
||||||
|
//清空memcache
|
||||||
|
memory.Clean()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var StopSign = make(chan int)
|
||||||
|
|
||||||
|
func GetStop(c *gin.Context) {
|
||||||
|
|
||||||
|
StopSign <- 1
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetDede(c *gin.Context) {
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetDedeInfo(c *gin.Context) {
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "55Sy5pa577ya54Gr5Yek5Yew6K6v6L6+77yI5rex5Zyz77yJ55S15a2Q5ZWG5Yqh5pyJ6ZmQ5YWs5Y+4Cue6s+eojueZu+iusOWPt++8mjkxNDQwMzAwTUE1RkpKNUZYQQrogZTns7vlnLDlnYDvvJrmt7HlnLPluILljZflsbHljLrljZflsbHooZfpgZPljZflhYnnpL7ljLrljZfmtbflpKfpgZMyMzA55Y+35bGx5Lic5aSn5Y6m5Li75qW8QS5CLkPluqdDM+WxggrnlLXor53lj7fnoIHvvJoxNzcyNDczNzc5MArmjojmnYPln5/lkI3vvJrmmoLml6A=",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
models "kefu/models/v2"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VisitorTag struct {
|
||||||
|
VisitorId string `binding:"required" form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id"`
|
||||||
|
TagName string `binding:"required" form:"tag_name" json:"tag_name" uri:"tag_name" xml:"tag_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostVisitorTag(c *gin.Context) {
|
||||||
|
var form VisitorTag
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
VisitorTagFunc(form.TagName, kefuId.(string), entIdStr.(string), form.VisitorId)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func VisitorTagFunc(tagName, kefuName, ent, visitorId string) {
|
||||||
|
entId, _ := strconv.Atoi(ent)
|
||||||
|
tagModel := models.GetTag("name = ? and ent_id = ?", tagName, entId)
|
||||||
|
if tagModel.ID == 0 {
|
||||||
|
tagModel.Name = tagName
|
||||||
|
tagModel.Kefu = kefuName
|
||||||
|
tagModel.EntId = uint(entId)
|
||||||
|
tagModel.InsertTag()
|
||||||
|
}
|
||||||
|
tagIds := models.GetVisitorTags("visitor_id = ? and ent_id = ?", visitorId, entId)
|
||||||
|
for _, tagId := range tagIds {
|
||||||
|
if tagId.TagId == tagModel.ID {
|
||||||
|
models.DelVisitorTags("visitor_id = ? and tag_id = ? and ent_id = ?", visitorId,
|
||||||
|
tagId.TagId, entId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visitorTagModel := models.VisitorTag{
|
||||||
|
VisitorId: visitorId,
|
||||||
|
TagId: tagModel.ID,
|
||||||
|
Kefu: kefuName,
|
||||||
|
EntId: uint(entId),
|
||||||
|
}
|
||||||
|
visitorTagModel.InsertVisitorTag()
|
||||||
|
}
|
||||||
|
func DelVisitorTag(c *gin.Context) {
|
||||||
|
var form VisitorTag
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := strconv.Atoi(entIdStr.(string))
|
||||||
|
tagModel := models.GetTag("name = ? and ent_id = ?", form.TagName, entId)
|
||||||
|
|
||||||
|
models.DelVisitorTags("visitor_id = ? and tag_id = ? and ent_id = ?", form.VisitorId,
|
||||||
|
tagModel.ID, entId)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func DeThisTag(c *gin.Context) {
|
||||||
|
|
||||||
|
tagId := c.Query("tag_id")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := strconv.Atoi(entIdStr.(string))
|
||||||
|
|
||||||
|
models.DelVisitorTags("tag_id = ? and ent_id = ?",
|
||||||
|
tagId, entId)
|
||||||
|
models.DelTags("id = ? and ent_id = ?",
|
||||||
|
tagId, entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVisitorAllTags(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := strconv.Atoi(entIdStr.(string))
|
||||||
|
|
||||||
|
alltags := models.GetTags("ent_id = ?", entId)
|
||||||
|
tagIds := models.GetVisitorTags("visitor_id = ? and ent_id = ?", visitorId, entId)
|
||||||
|
tagMap := make(map[uint]uint, 0)
|
||||||
|
for _, tagId := range tagIds {
|
||||||
|
tagMap[tagId.TagId] = 1
|
||||||
|
}
|
||||||
|
log.Println(tagIds, tagMap)
|
||||||
|
|
||||||
|
for _, tag := range alltags {
|
||||||
|
if _, ok := tagMap[tag.ID]; ok {
|
||||||
|
tag.IsTaged = 1
|
||||||
|
} else {
|
||||||
|
tag.IsTaged = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": alltags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVisitorTags(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := strconv.Atoi(entIdStr.(string))
|
||||||
|
|
||||||
|
tags := models.GetVisitorTagsByVisitorId(visitorId, uint(entId))
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": tags,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetTags(c *gin.Context) {
|
||||||
|
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := strconv.Atoi(entIdStr.(string))
|
||||||
|
tagName := c.Query("tag_name")
|
||||||
|
var tags []*models.Tag
|
||||||
|
if tagName != "" {
|
||||||
|
tags = models.GetTags("ent_id = ? and name like ?", entId, tagName+"%")
|
||||||
|
} else {
|
||||||
|
tags = models.GetTags("ent_id = ?", entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": tags,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clientTcpList = make(map[string]net.Conn)
|
||||||
|
|
||||||
|
func NewTcpServer(tcpBaseServer string) {
|
||||||
|
listener, err := net.Listen("tcp", tcpBaseServer)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error listening", err.Error())
|
||||||
|
return //终止程序
|
||||||
|
}
|
||||||
|
// 监听并接受来自客户端的连接
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error accepting", err.Error())
|
||||||
|
return // 终止程序
|
||||||
|
}
|
||||||
|
var remoteIpAddress = conn.RemoteAddr()
|
||||||
|
clientTcpList[remoteIpAddress.String()] = conn
|
||||||
|
log.Println(remoteIpAddress, clientTcpList)
|
||||||
|
//clientTcpList=append(clientTcpList,conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func PushServerTcp(str []byte) {
|
||||||
|
for ip, conn := range clientTcpList {
|
||||||
|
line := append(str, []byte("\r\n")...)
|
||||||
|
_, err := conn.Write(line)
|
||||||
|
log.Println(ip, err)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
delete(clientTcpList, ip)
|
||||||
|
//clientTcpList=append(clientTcpList[:index],clientTcpList[index+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func DeleteOnlineTcp(c *gin.Context) {
|
||||||
|
ip := c.Query("ip")
|
||||||
|
for ipkey, conn := range clientTcpList {
|
||||||
|
if ip == ipkey {
|
||||||
|
conn.Close()
|
||||||
|
delete(clientTcpList, ip)
|
||||||
|
}
|
||||||
|
if ip == "all" {
|
||||||
|
conn.Close()
|
||||||
|
delete(clientTcpList, ipkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetUpdownLine(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
onlineStatus := c.Query("online_status")
|
||||||
|
online := &models.UpDownLine{
|
||||||
|
EntId: entIdStr.(string),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
OnlineStatus: uint(tools.Str2Int(onlineStatus)),
|
||||||
|
ClientIp: c.ClientIP(),
|
||||||
|
CreatedAt: types.Time{},
|
||||||
|
}
|
||||||
|
online.AddUpDownLine()
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
上下线列表
|
||||||
|
*/
|
||||||
|
func GetUpDownLineList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
|
||||||
|
query := "1=1"
|
||||||
|
var arg = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
query += " and ent_id = ? "
|
||||||
|
arg = append(arg, entId)
|
||||||
|
}
|
||||||
|
if roleId.(float64) == 3 {
|
||||||
|
query += " and kefu_name = ? "
|
||||||
|
arg = append(arg, kefuName)
|
||||||
|
}
|
||||||
|
page, pagesize := HandlePagePageSize(c)
|
||||||
|
count := models.CountUpDownLineWhere(query, arg...)
|
||||||
|
exts := models.FindUpDownLineByWhere(page, pagesize, query, arg...)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": exts,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,241 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 上传附件返回后缀
|
||||||
|
func UploadFileV2(c *gin.Context) {
|
||||||
|
SendAttachment, err := strconv.ParseBool(models.FindConfig("SendAttachment"))
|
||||||
|
if !SendAttachment || err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.UPLOAD_FORBIDDEN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.UPLOAD_FORBIDDEN),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err := c.FormFile("realfile")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.FAILED),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//限制上传大小
|
||||||
|
maxSize := 20 * 1024 * 1024
|
||||||
|
uploadMaxSize := models.FindConfig("UploadMaxSize")
|
||||||
|
if uploadMaxSize != "" {
|
||||||
|
uploadMaxSizeInt, _ := strconv.Atoi(uploadMaxSize)
|
||||||
|
maxSize = uploadMaxSizeInt * 1024 * 1024
|
||||||
|
}
|
||||||
|
if f.Size >= int64(maxSize) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.UPLOAD_OVERSIZE,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.UPLOAD_OVERSIZE),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||||
|
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
path := "/" + filepath
|
||||||
|
//上传到阿里云oss
|
||||||
|
oss, err := lib.NewOssLib()
|
||||||
|
if err == nil {
|
||||||
|
dstUrl, err := oss.Upload(filepath, filepath)
|
||||||
|
if err == nil {
|
||||||
|
path = dstUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"result": gin.H{
|
||||||
|
"path": path,
|
||||||
|
"ext": fileExt,
|
||||||
|
"size": f.Size,
|
||||||
|
"name": f.Filename,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func UploadAvator(c *gin.Context) {
|
||||||
|
f, err := c.FormFile("imgfile")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!只允许png,jpg,gif,jpeg文件",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||||
|
|
||||||
|
fildDir := fmt.Sprintf("%savator/%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
err := os.MkdirAll(fildDir, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!" + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
|
||||||
|
path := "/" + filepath
|
||||||
|
//上传到阿里云oss
|
||||||
|
oss, err := lib.NewOssLib()
|
||||||
|
if err == nil {
|
||||||
|
dstUrl, err := oss.Upload(filepath, filepath)
|
||||||
|
if err == nil {
|
||||||
|
path = dstUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println("上传oss:", err)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"result": gin.H{
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadEditorImg(c *gin.Context) {
|
||||||
|
f, err := c.FormFile("imgfile")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"errno": 400,
|
||||||
|
"msg": "上传失败!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||||
|
if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"errno": 400,
|
||||||
|
"msg": "上传失败!只允许png,jpg,gif,jpeg文件",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||||
|
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
|
||||||
|
path := "/" + filepath
|
||||||
|
//上传到阿里云oss
|
||||||
|
oss, err := lib.NewOssLib()
|
||||||
|
if err == nil {
|
||||||
|
dstUrl, err := oss.Upload(filepath, filepath)
|
||||||
|
if err == nil {
|
||||||
|
path = dstUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"errno": 0,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"data": gin.H{
|
||||||
|
"url": path,
|
||||||
|
"alt": "",
|
||||||
|
"href": "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func UploadAudioV2(c *gin.Context) {
|
||||||
|
//SendAttachment, err := strconv.ParseBool(models.FindConfig("SendAttachment"))
|
||||||
|
//if !SendAttachment || err != nil {
|
||||||
|
// c.JSON(200, gin.H{
|
||||||
|
// "code": 400,
|
||||||
|
// "msg": "禁止上传附件!",
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
f, err := c.FormFile("realfile")
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fileExt := ".mp3"
|
||||||
|
if f.Size >= 20*1024*1024 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "上传失败!不允许超过20M",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||||
|
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||||
|
c.SaveUploadedFile(f, filepath)
|
||||||
|
path := "/" + filepath
|
||||||
|
//上传到阿里云oss
|
||||||
|
oss, err := lib.NewOssLib()
|
||||||
|
if err == nil {
|
||||||
|
dstUrl, err := oss.Upload(filepath, filepath)
|
||||||
|
if err == nil {
|
||||||
|
path = dstUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "上传成功!",
|
||||||
|
"result": gin.H{
|
||||||
|
"path": path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientForm struct {
|
||||||
|
ClientId string `form:"client_id" json:"client_id" uri:"client_id" xml:"client_id" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
注册app client_id
|
||||||
|
*/
|
||||||
|
func PostAppKefuClient(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
var form ClientForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientModel := &models.User_client{
|
||||||
|
Kefu: kefuName.(string),
|
||||||
|
Client_id: form.ClientId,
|
||||||
|
}
|
||||||
|
clientInfo := clientModel.FindClient()
|
||||||
|
if clientInfo.ID != 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.CreateUserClient(kefuName.(string), form.ClientId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostEmailCode(c *gin.Context) {
|
||||||
|
email := c.PostForm("email")
|
||||||
|
notify := &lib.Notify{
|
||||||
|
Subject: "测试主题",
|
||||||
|
MainContent: "测试内容",
|
||||||
|
EmailServer: lib.NotifyEmail{
|
||||||
|
Server: "xxx",
|
||||||
|
Port: 587,
|
||||||
|
From: "xxx",
|
||||||
|
Password: "xxx",
|
||||||
|
To: []string{email},
|
||||||
|
FromName: "xxx",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := notify.SendMail()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetIpAuth(c *gin.Context) {
|
||||||
|
ip := c.ClientIP()
|
||||||
|
host := c.Request.Referer()
|
||||||
|
log.Println("ipAuth:", ip, host)
|
||||||
|
ipModel := models.FindServerIpAddress(ip)
|
||||||
|
if ipModel.ID == 0 {
|
||||||
|
ipModel = models.CreateIpAuth(ip, "", host, "", "2006-01-02")
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetIsBindOfficial(c *gin.Context) {
|
||||||
|
ip := c.ClientIP()
|
||||||
|
ipModel := models.FindServerIpAddress(ip)
|
||||||
|
|
||||||
|
if ipModel.ID == 0 || ipModel.Status != 1 || ipModel.Phone == "" {
|
||||||
|
c.Writer.Write([]byte("error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := &models.User{
|
||||||
|
Tel: ipModel.Phone,
|
||||||
|
}
|
||||||
|
var result models.User
|
||||||
|
result = user.GetOneUser("*")
|
||||||
|
//查看过期时间
|
||||||
|
nowSecond := time.Now().Unix()
|
||||||
|
expireSecond := result.ExpiredAt.Unix()
|
||||||
|
if result.ID == 0 || expireSecond < nowSecond {
|
||||||
|
c.Writer.Write([]byte("error"))
|
||||||
|
logContent := fmt.Sprintf("'%s' 远程验证失败或过期:%s", result.Name, result.ExpiredAt.Format("2006-01-02 15:04:05"))
|
||||||
|
go models.CreateFlyLog(result.EntId, ip, logContent, "user")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Writer.Write([]byte("success"))
|
||||||
|
}
|
||||||
|
|
||||||
|
//远程请求
|
||||||
|
func PostBindOfficial(c *gin.Context) {
|
||||||
|
api := "https://gofly.v1kf.com/2/officialBindIp"
|
||||||
|
|
||||||
|
phone := c.PostForm("phone")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
host := c.Request.Host
|
||||||
|
data := url.Values{}
|
||||||
|
data.Set("phone", phone)
|
||||||
|
data.Set("password", password)
|
||||||
|
data.Set("host", host)
|
||||||
|
res, err := tools.PostForm(api, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("绑定官网账户发送认证连接错误")
|
||||||
|
}
|
||||||
|
log.Println("bindOfficial:", c.ClientIP(), host)
|
||||||
|
c.Writer.Write([]byte(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
//官网绑定
|
||||||
|
func PostOfficialBindIp(c *gin.Context) {
|
||||||
|
ip := c.ClientIP()
|
||||||
|
phone := c.PostForm("phone")
|
||||||
|
password := c.PostForm("password")
|
||||||
|
host := c.PostForm("host")
|
||||||
|
user := &models.User{
|
||||||
|
Tel: phone,
|
||||||
|
}
|
||||||
|
var result models.User
|
||||||
|
result = user.GetOneUser("*")
|
||||||
|
md5Pass := tools.Md5(password)
|
||||||
|
//查看过期时间
|
||||||
|
nowSecond := time.Now().Unix()
|
||||||
|
expireSecond := result.ExpiredAt.Unix()
|
||||||
|
if result.ID == 0 || result.Password != md5Pass || expireSecond < nowSecond {
|
||||||
|
c.Writer.Write([]byte("error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ipModel := models.FindServerIpAddress(ip)
|
||||||
|
code := makeAuthCode(host, phone)
|
||||||
|
|
||||||
|
if ipModel.ID == 0 {
|
||||||
|
models.CreateIpAuth(ip, phone, host, code, "2006-01-02")
|
||||||
|
} else if ipModel.Status != 1 {
|
||||||
|
c.Writer.Write([]byte("error"))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
models.UpdateIpAddress(ip, phone, code, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Writer.Write([]byte("success"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func makeAuthCode(host, phone string) string {
|
||||||
|
publicKey := common.RsaPublicKey
|
||||||
|
privateKey := common.RsaPrivateKey
|
||||||
|
rsa := tools.NewRsa(publicKey, privateKey)
|
||||||
|
content := fmt.Sprintf("{\"host\":\"%s\",\"phone\":\"%s\"}", host, phone)
|
||||||
|
res, err := rsa.Encrypt([]byte(content))
|
||||||
|
res = []byte(tools.Base64Encode(string(res)))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(res)
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetChartStatistic(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
|
||||||
|
//今天0点日期字符串
|
||||||
|
//todayTimeStr := time.Now().Format("2006-01-02")
|
||||||
|
////日期字符串转时间
|
||||||
|
//todayTime, _ := time.Parse("2006-01-02", todayTimeStr)
|
||||||
|
////时间转时间戳
|
||||||
|
//todayTimenum := todayTime.Unix()
|
||||||
|
////15天前开始的时间
|
||||||
|
//startTimenum := todayTimenum - 15*24*3600
|
||||||
|
////时间戳转时间转日期字符串
|
||||||
|
//startTime := time.Unix(startTimenum, 0).Format("2006-01-02")
|
||||||
|
dayNumMap := make(map[string]string)
|
||||||
|
result := models.CountVisitorsEveryDay(kefuName.(string))
|
||||||
|
for _, item := range result {
|
||||||
|
dayNumMap[item.Day] = tools.Int2Str(item.Num)
|
||||||
|
}
|
||||||
|
|
||||||
|
nowTime := time.Now()
|
||||||
|
list := make([]map[string]string, 0)
|
||||||
|
for i := 0; i > -46; i-- {
|
||||||
|
getTime := nowTime.AddDate(0, 0, i) //年,月,日 获取一天前的时间
|
||||||
|
resTime := getTime.Format("06-01-02") //获取的时间的格式
|
||||||
|
tmp := make(map[string]string)
|
||||||
|
tmp["day"] = resTime
|
||||||
|
tmp["num"] = dayNumMap[resTime]
|
||||||
|
list = append(list, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": list,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenForm struct {
|
||||||
|
Token string `form:"token" json:"token" uri:"token" xml:"token" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostRefreshToken(c *gin.Context) {
|
||||||
|
var form TokenForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, RefreshToken(form))
|
||||||
|
}
|
||||||
|
func RefreshToken(form TokenForm) gin.H {
|
||||||
|
orgToken, err := ParseToken(form.Token)
|
||||||
|
if err != nil {
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.TOKEN_FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orgToken.CreateTime = time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
orgToken.ExpiresAt = time.Now().Unix() + 24*3600
|
||||||
|
token, err := tools.MakeCliamsToken(*orgToken)
|
||||||
|
if err != nil {
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func PostParseToken(c *gin.Context) {
|
||||||
|
var form TokenForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orgToken, err := ParseToken(form.Token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.TOKEN_FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": orgToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func ParseToken(token string) (*tools.UserClaims, error) {
|
||||||
|
orgToken, err := tools.ParseCliamsToken(token, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return orgToken, nil
|
||||||
|
}
|
||||||
|
func GetJwt(c *gin.Context) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostRefreshTokenV1(c *gin.Context) {
|
||||||
|
var form TokenForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userinfo := tools.ParseToken(form.Token)
|
||||||
|
if userinfo == nil || userinfo["name"] == nil || userinfo["create_time"] == nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userinfo["create_time"] = time.Now().Unix()
|
||||||
|
token, _ := tools.MakeToken(userinfo)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetJwtV1(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"kefu_name": kefuName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/dchest/captcha"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterForm struct {
|
||||||
|
Username string `form:"username" json:"username" uri:"username" xml:"username" binding:"required"`
|
||||||
|
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
|
||||||
|
RePassword string `form:"rePassword" json:"rePassword" uri:"rePassword" xml:"rePassword" binding:"required"`
|
||||||
|
Nickname string `form:"nickname" json:"nickname" uri:"nickname" xml:"nickname" binding:"required"`
|
||||||
|
Captcha string `form:"captcha" json:"captcha" uri:"captcha" xml:"captcha" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostUcRegister(c *gin.Context) {
|
||||||
|
var form RegisterForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//验证码
|
||||||
|
session := sessions.Default(c)
|
||||||
|
if captchaId := session.Get("captcha"); captchaId != nil {
|
||||||
|
session.Delete("captcha")
|
||||||
|
_ = session.Save()
|
||||||
|
if !captcha.VerifyString(captchaId.(string), form.Captcha) {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.CAPTCHA_FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.CAPTCHA_FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.CAPTCHA_FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.CAPTCHA_FAILED),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, UcRegister(form))
|
||||||
|
}
|
||||||
|
func UcRegister(form RegisterForm) gin.H {
|
||||||
|
//重复密码
|
||||||
|
if form.Password != form.RePassword {
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.INVALID_PASSWORD,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID_PASSWORD),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//账户是否存在
|
||||||
|
var user *models.User
|
||||||
|
user = &models.User{
|
||||||
|
Name: form.Username,
|
||||||
|
}
|
||||||
|
*user = user.GetOneUser("*")
|
||||||
|
if user.ID != 0 {
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_EXIST),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//插入用户
|
||||||
|
mStr := fmt.Sprintf("%ds", common.TryDeadline)
|
||||||
|
duration, _ := time.ParseDuration(mStr)
|
||||||
|
expired := time.Now().Add(duration)
|
||||||
|
user = &models.User{
|
||||||
|
Name: form.Username,
|
||||||
|
Password: tools.Md5(form.Password),
|
||||||
|
Avator: "/static/images/4.jpg",
|
||||||
|
Nickname: form.Nickname,
|
||||||
|
Pid: 1,
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
ExpiredAt: types.Time{
|
||||||
|
expired,
|
||||||
|
},
|
||||||
|
RecNum: 0,
|
||||||
|
Status: 0,
|
||||||
|
}
|
||||||
|
userId, _ := user.AddUser()
|
||||||
|
if userId == 0 {
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.FAILED),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
models.CreateUserRole(userId, types.Constant.EntRoleId)
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginForm struct {
|
||||||
|
Username string `form:"username" json:"username" uri:"username" xml:"username" binding:"required"`
|
||||||
|
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostUcLogin(c *gin.Context) {
|
||||||
|
var form LoginForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, UcLogin(form))
|
||||||
|
}
|
||||||
|
func UcLogin(form LoginForm) gin.H {
|
||||||
|
var user *models.User
|
||||||
|
user = &models.User{
|
||||||
|
Name: form.Username,
|
||||||
|
}
|
||||||
|
*user = user.GetOneUser("*")
|
||||||
|
md5Pass := tools.Md5(form.Password)
|
||||||
|
if user.ID == 0 || user.Password != md5Pass {
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.LOGIN_FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.LOGIN_FAILED),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok, errCode, errMsg := user.CheckStatusExpired()
|
||||||
|
if !ok {
|
||||||
|
return gin.H{
|
||||||
|
"code": errCode,
|
||||||
|
"msg": errMsg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roleId, _ := strconv.Atoi(user.RoleId)
|
||||||
|
tokenCliams := tools.UserClaims{
|
||||||
|
Id: user.ID,
|
||||||
|
Username: user.Name,
|
||||||
|
RoleId: uint(roleId),
|
||||||
|
Pid: user.Pid,
|
||||||
|
RoleName: user.RoleName,
|
||||||
|
CreateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
StandardClaims: jwt.StandardClaims{
|
||||||
|
ExpiresAt: time.Now().Unix() + 24*3600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
token, err := tools.MakeCliamsToken(tokenCliams)
|
||||||
|
if err != nil {
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.LOGIN_FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"token": token,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package v2
|
|
@ -0,0 +1,162 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
v2 "kefu/models/v2"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VisitorLoginForm struct {
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id"`
|
||||||
|
Refer string `form:"refer" json:"refer" uri:"refer" xml:"refer"`
|
||||||
|
ReferUrl string `form:"refer_url" json:"refer" uri:"refer" xml:"refer"`
|
||||||
|
Url string `form:"url" json:"url" uri:"url" xml:"url"`
|
||||||
|
ToId string `form:"to_id" json:"to_id" uri:"to_id" xml:"to_id" binding:"required"`
|
||||||
|
EntId string `form:"ent_id" json:"ent_id" uri:"ent_id" xml:"ent_id" binding:"required"`
|
||||||
|
Avator string `form:"avator" json:"avator" uri:"avator" xml:"avator"`
|
||||||
|
UserAgent string `form:"user_agent" json:"user_agent" uri:"user_agent" xml:"user_agent"`
|
||||||
|
Extra string `form:"extra" json:"extra" uri:"extra" xml:"extra"`
|
||||||
|
ClientIp string `form:"client_ip" json:"client_ip" uri:"client_ip" xml:"client_ip"`
|
||||||
|
CityAddress string `form:"city_address" json:"city_address" uri:"city_address" xml:"city_address"`
|
||||||
|
VisitorName string `form:"visitor_name" json:"visitor_name" uri:"visitor_name" xml:"visitor_name"`
|
||||||
|
}
|
||||||
|
type VisitorExtra struct {
|
||||||
|
VisitorName string `json:"visitorName"`
|
||||||
|
VisitorAvatar string `json:"visitorAvatar"`
|
||||||
|
VisitorId string `json:"visitorId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostVisitorLogin(c *gin.Context) {
|
||||||
|
var form VisitorLoginForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form.UserAgent = c.GetHeader("User-Agent")
|
||||||
|
if tools.IsMobile(form.UserAgent) {
|
||||||
|
form.Avator = "/static/images/phone.png"
|
||||||
|
} else {
|
||||||
|
form.Avator = "/static/images/computer.png"
|
||||||
|
|
||||||
|
}
|
||||||
|
if form.VisitorId == "" {
|
||||||
|
form.VisitorId = tools.Uuid()
|
||||||
|
}
|
||||||
|
ipCity, _ := tools.ParseIpNew(c.ClientIP())
|
||||||
|
if ipCity != nil {
|
||||||
|
form.CityAddress = ipCity.CountryName + ipCity.RegionName + ipCity.CityName
|
||||||
|
} else {
|
||||||
|
form.CityAddress = "Unknown Area"
|
||||||
|
}
|
||||||
|
form.VisitorName = form.CityAddress
|
||||||
|
form.ClientIp = c.ClientIP()
|
||||||
|
c.JSON(200, VisitorLogin(form))
|
||||||
|
}
|
||||||
|
func VisitorLogin(form VisitorLoginForm) gin.H {
|
||||||
|
makeVisitorLoginForm(&form)
|
||||||
|
//查看企业状态
|
||||||
|
entId, _ := strconv.Atoi(form.EntId)
|
||||||
|
userModel := &models.User{ID: uint(entId), Name: form.ToId}
|
||||||
|
entUserInfo := userModel.GetOneUser("*")
|
||||||
|
ok, errCode, errMsg := entUserInfo.CheckStatusExpired()
|
||||||
|
if !ok {
|
||||||
|
return gin.H{
|
||||||
|
"code": errCode,
|
||||||
|
"msg": errMsg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//自动分配
|
||||||
|
serviceUser := entUserInfo
|
||||||
|
allOffline := true
|
||||||
|
if _, ok := ws.KefuList[form.ToId]; ok {
|
||||||
|
serviceUser = entUserInfo
|
||||||
|
allOffline = false
|
||||||
|
} else {
|
||||||
|
userModel = &models.User{Pid: uint(entId)}
|
||||||
|
userModel.SetOrder("rec_num asc")
|
||||||
|
kefus := userModel.GetUsers("name,nickname,avator")
|
||||||
|
if len(kefus) == 0 {
|
||||||
|
serviceUser = entUserInfo
|
||||||
|
form.ToId = entUserInfo.Name
|
||||||
|
} else {
|
||||||
|
for _, kefu := range kefus {
|
||||||
|
if _, ok := ws.KefuList[kefu.Name]; ok {
|
||||||
|
serviceUser = kefu
|
||||||
|
form.ToId = kefu.Name
|
||||||
|
allOffline = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visitor := createVisitor(form)
|
||||||
|
return gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"allOffline": allOffline,
|
||||||
|
"result": visitor,
|
||||||
|
"kefu": gin.H{
|
||||||
|
"nickname": serviceUser.Nickname,
|
||||||
|
"avatar": serviceUser.Avator,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func createVisitor(form VisitorLoginForm) *v2.Visitor {
|
||||||
|
visitor := &v2.Visitor{
|
||||||
|
ID: 0,
|
||||||
|
CreatedAt: time.Time{},
|
||||||
|
UpdatedAt: time.Time{},
|
||||||
|
Name: form.VisitorName,
|
||||||
|
Avator: form.Avator,
|
||||||
|
SourceIp: form.ClientIp,
|
||||||
|
ToId: form.ToId,
|
||||||
|
VisitorId: form.VisitorId,
|
||||||
|
Status: 0,
|
||||||
|
Refer: form.Refer,
|
||||||
|
City: form.CityAddress,
|
||||||
|
ClientIp: form.ClientIp,
|
||||||
|
Extra: form.Extra,
|
||||||
|
EntId: form.EntId,
|
||||||
|
}
|
||||||
|
oldVsitor := visitor.FindVisitor()
|
||||||
|
if oldVsitor.ID != 0 {
|
||||||
|
visitor.ID = oldVsitor.ID
|
||||||
|
visitor.UpdateVisitor("visitor_id=? and ent_id=?", visitor.VisitorId, visitor.EntId)
|
||||||
|
return visitor
|
||||||
|
}
|
||||||
|
visitor.InsertVisitor()
|
||||||
|
return visitor
|
||||||
|
}
|
||||||
|
func makeVisitorLoginForm(form *VisitorLoginForm) {
|
||||||
|
//扩展信息
|
||||||
|
extraJson := tools.Base64Decode(form.Extra)
|
||||||
|
if extraJson != "" {
|
||||||
|
var extraObj VisitorExtra
|
||||||
|
err := json.Unmarshal([]byte(extraJson), &extraObj)
|
||||||
|
if err == nil {
|
||||||
|
if extraObj.VisitorName != "" {
|
||||||
|
form.VisitorName = extraObj.VisitorName
|
||||||
|
}
|
||||||
|
if extraObj.VisitorAvatar != "" {
|
||||||
|
form.Avator = extraObj.VisitorAvatar
|
||||||
|
}
|
||||||
|
if extraObj.VisitorId != "" {
|
||||||
|
form.VisitorId = extraObj.VisitorId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStructTag(t *testing.T) {
|
||||||
|
form := VisitorLoginForm{
|
||||||
|
VisitorId: "121212",
|
||||||
|
ReferUrl: "http://",
|
||||||
|
}
|
||||||
|
formRef := reflect.TypeOf(form)
|
||||||
|
fmt.Println("Type:", formRef.Name())
|
||||||
|
fmt.Println("Kind:", formRef.Kind())
|
||||||
|
for i := 0; i < formRef.NumField(); i++ {
|
||||||
|
field := formRef.Field(i)
|
||||||
|
tag := field.Tag.Get("json")
|
||||||
|
fmt.Printf("%d. %v (%v), tag: '%v'\n", i+1, field.Name, field.Type.Name(), tag)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VirtualProductForm struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
ProductName string `form:"product_name" json:"product_name" uri:"product_name" xml:"product_name" binding:"required"`
|
||||||
|
Payment string `form:"payment" json:"payment" uri:"payment" xml:"payment" binding:"required"`
|
||||||
|
Price string `form:"price" json:"price" uri:"price" xml:"price" binding:"required"`
|
||||||
|
ResourceLink string `form:"resource_link" json:"resource_link" uri:"resource_link" xml:"resource_link" binding:"required"`
|
||||||
|
ProductCategory string `form:"product_category" json:"product_category" uri:"product_category" xml:"product_category" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVirtualProduct(c *gin.Context) {
|
||||||
|
entId := c.Query("ent_id")
|
||||||
|
id := c.Query("id")
|
||||||
|
product := models.FindVirtualProduct("ent_id = ? and id = ?", entId, id)
|
||||||
|
product.ResourceLink = ""
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": product,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVirtualProductLink(c *gin.Context) {
|
||||||
|
product := c.Query("product")
|
||||||
|
wechatConfig, _ := lib.NewWechatLib(models.FindConfig("SystemBussinesId"))
|
||||||
|
host := strings.TrimRight(wechatConfig.WechatHost, "/")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
if roleId.(float64) == 1 {
|
||||||
|
product := models.FindVirtualProduct("id = ?", product)
|
||||||
|
entId = product.EntId
|
||||||
|
kefuName = product.KefuName
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
host = tools.GetHost(c.Request)
|
||||||
|
}
|
||||||
|
link := fmt.Sprintf("%s/pay/%s/%s?product=%s", host, entId, kefuName, product)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": link,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func PostVirtualProduct(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
|
||||||
|
var form VirtualProductForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuInfo := models.FindUser(kefuName.(string))
|
||||||
|
num := models.CountVirtualProducts("ent_id = ? and kefu_name = ?", entId, kefuName)
|
||||||
|
if int64(kefuInfo.AgentNum+1) <= num && form.Id == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": fmt.Sprintf("付费资源数超过上限数: %d!", kefuInfo.AgentNum+1),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &models.VirtualProduct{
|
||||||
|
EntId: entId.(string),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
ProductName: form.ProductName,
|
||||||
|
Price: tools.Str2Int(form.Price),
|
||||||
|
ResourceLink: form.ResourceLink,
|
||||||
|
CreatedAt: types.Time{},
|
||||||
|
UpdatedAt: types.Time{},
|
||||||
|
ProductCategory: form.ProductCategory,
|
||||||
|
Payment: form.Payment,
|
||||||
|
}
|
||||||
|
if form.Id == 0 {
|
||||||
|
m.AddVirtualProduct()
|
||||||
|
} else {
|
||||||
|
m.SaveVirtualProduct("id = ? and ent_id = ?", form.Id, entId)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVirtualProducts(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||||
|
if pageSize > 50 {
|
||||||
|
pageSize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
search := "1=1 "
|
||||||
|
var args = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
search += "and ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := models.CountVirtualProducts(search, args...)
|
||||||
|
list := models.FindVirtualProducts(page, pageSize, search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pageSize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
func DeleteVirtualProduct(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
id := c.Query("id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
search := "id = ? "
|
||||||
|
var args = []interface{}{id}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
search += "and ent_id = ? "
|
||||||
|
args = append(args, entId)
|
||||||
|
}
|
||||||
|
|
||||||
|
models.DeleteVirtualProduct(search, args...)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,954 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/models"
|
||||||
|
v2 "kefu/models/v2"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VisitorLoginForm struct {
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id"`
|
||||||
|
Refer string `form:"refer" json:"refer" uri:"refer" xml:"refer"`
|
||||||
|
ReferUrl string `form:"refer_url" json:"refer" uri:"refer" xml:"refer"`
|
||||||
|
Title string `form:"title" json:"title" uri:"title" xml:"title"`
|
||||||
|
Url string `form:"url" json:"url" uri:"url" xml:"url"`
|
||||||
|
ToId string `form:"to_id" json:"to_id" uri:"to_id" xml:"to_id"`
|
||||||
|
EntId string `form:"ent_id" json:"ent_id" uri:"ent_id" xml:"ent_id" binding:"required"`
|
||||||
|
Avator string `form:"avator" json:"avator" uri:"avator" xml:"avator"`
|
||||||
|
UserAgent string `form:"user_agent" json:"user_agent" uri:"user_agent" xml:"user_agent"`
|
||||||
|
Extra string `form:"extra" json:"extra" uri:"extra" xml:"extra"`
|
||||||
|
ClientIp string `form:"client_ip" json:"client_ip" uri:"client_ip" xml:"client_ip"`
|
||||||
|
CityAddress string `form:"city_address" json:"city_address" uri:"city_address" xml:"city_address"`
|
||||||
|
VisitorName string `form:"visitor_name" json:"visitor_name" uri:"visitor_name" xml:"visitor_name"`
|
||||||
|
Language string `form:"language" json:"language" uri:"language" xml:"language"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostVisitorLogin(c *gin.Context) {
|
||||||
|
var form VisitorLoginForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
if err != nil {
|
||||||
|
HandleError(c, types.ApiCode.FAILED, types.ApiCode.GetMessage(types.ApiCode.INVALID), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//限流
|
||||||
|
if models.FindConfig("SystemFreqLimt") != "true" && !tools.LimitFreqSingle("visitor_login:"+c.ClientIP(), 1, 2) {
|
||||||
|
HandleError(c, types.ApiCode.FREQ_LIMIT, types.ApiCode.GetMessage(types.ApiCode.FREQ_LIMIT), errors.New("访问接口频率限制"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isWechat := false
|
||||||
|
originVisitorId := ""
|
||||||
|
|
||||||
|
// 如果访客ID为空
|
||||||
|
if form.VisitorId == "" {
|
||||||
|
// 判断访客ID的生成规则
|
||||||
|
idRule := models.FindConfig("VisitorIdRule")
|
||||||
|
if idRule == "ip" {
|
||||||
|
// 使用IP生成访客ID的MD5哈希值
|
||||||
|
form.VisitorId = tools.Md5(c.ClientIP())
|
||||||
|
} else {
|
||||||
|
// 使用UUID生成访客ID
|
||||||
|
form.VisitorId = tools.Uuid()
|
||||||
|
}
|
||||||
|
form.VisitorId = fmt.Sprintf("%s|%s", form.EntId, form.VisitorId)
|
||||||
|
} else {
|
||||||
|
originVisitorId = form.VisitorId
|
||||||
|
|
||||||
|
// 验证访客是否在黑名单中
|
||||||
|
if !CheckVisitorBlack(form.VisitorId) {
|
||||||
|
HandleError(c, types.ApiCode.VISITOR_BAN, types.ApiCode.GetMessage(types.ApiCode.VISITOR_BAN), errors.New("访客在黑名单中"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visitorIds := strings.Split(form.VisitorId, "|")
|
||||||
|
if len(visitorIds) > 1 {
|
||||||
|
if visitorIds[0] != "wx" && visitorIds[0] != "mini" {
|
||||||
|
// 如果不是微信或小程序,则重新构造访客ID
|
||||||
|
form.VisitorId = strings.Join(visitorIds[1:], "|")
|
||||||
|
form.VisitorId = fmt.Sprintf("%s|%s", form.EntId, form.VisitorId)
|
||||||
|
} else {
|
||||||
|
isWechat = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
form.VisitorId = fmt.Sprintf("%s|%s", form.EntId, form.VisitorId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取请求的User-Agent信息
|
||||||
|
form.UserAgent = c.GetHeader("User-Agent")
|
||||||
|
|
||||||
|
// 如果Avator为空且不是微信登录
|
||||||
|
if form.Avator == "" && !isWechat {
|
||||||
|
// 根据User-Agent判断是移动设备还是桌面设备,并设置默认头像
|
||||||
|
if tools.IsMobile(form.UserAgent) {
|
||||||
|
form.Avator = "/static/images/phone.png"
|
||||||
|
} else {
|
||||||
|
form.Avator = "/static/images/computer.png"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 解码Avator中的URL转义字符
|
||||||
|
form.Avator, _ = url.QueryUnescape(form.Avator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析IP地址获取城市信息
|
||||||
|
ipCity, _ := tools.ParseIpNew(c.ClientIP())
|
||||||
|
if ipCity != nil {
|
||||||
|
form.CityAddress = ipCity.CountryName + ipCity.RegionName + ipCity.CityName + ipCity.AreaName
|
||||||
|
} else {
|
||||||
|
form.CityAddress = "Unknown Area"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果VisitorName为空且不是微信登录
|
||||||
|
if form.VisitorName == "" && !isWechat {
|
||||||
|
// 如果IP所在国家是中国,则使用省市区作为访客名称,否则使用完整城市地址
|
||||||
|
if ipCity.CountryName == "中国" {
|
||||||
|
form.VisitorName = ipCity.RegionName + ipCity.CityName + ipCity.AreaName
|
||||||
|
} else {
|
||||||
|
form.VisitorName = form.CityAddress
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 解码VisitorName中的URL转义字符
|
||||||
|
form.VisitorName, _ = url.QueryUnescape(form.VisitorName)
|
||||||
|
}
|
||||||
|
|
||||||
|
form.ClientIp = c.ClientIP()
|
||||||
|
//验证ip黑名单
|
||||||
|
ipblack := models.FindIp(form.ClientIp)
|
||||||
|
if ipblack.IP != "" && ipblack.EntId == form.EntId {
|
||||||
|
HandleError(c, types.ApiCode.IP_BAN, types.ApiCode.GetMessage(types.ApiCode.IP_BAN), errors.New("IP在黑名单中"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
makeVisitorLoginForm(&form)
|
||||||
|
allOffline := true
|
||||||
|
//查询企业信息
|
||||||
|
entKefuInfo := models.FindUserRoleById(form.EntId)
|
||||||
|
if entKefuInfo.ID == 0 || entKefuInfo.RoleId == tools.Int2Str(types.Constant.AgentRoleId) {
|
||||||
|
HandleError(c, types.ApiCode.ENT_ERROR, types.ApiCode.GetMessage(types.ApiCode.ENT_ERROR), errors.New("商户ID不存在"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
kefuCommonGin := gin.H{
|
||||||
|
"username": entKefuInfo.Nickname,
|
||||||
|
"avatar": entKefuInfo.Avator,
|
||||||
|
"intro_pic": entKefuInfo.CompanyPic,
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询企业配置项
|
||||||
|
configs := models.GetEntConfigsMap(form.EntId, "KefuIntroduce",
|
||||||
|
"RobotStatus", "TurnToMan", "RobotNoAnswer", "RobotName", "RobotAvator", "RobotCard",
|
||||||
|
"VisitorNotice", "AutoWelcome", "VisitorCookie", "VisitorMaxNum", "VisitorMaxNumNotice", "ScanWechatQrcode",
|
||||||
|
"VisitorAuthApi", "CloseNotice", "QdrantAIStatus")
|
||||||
|
//访客Cookie有效期
|
||||||
|
if configs["VisitorCookie"] == "" {
|
||||||
|
configs["VisitorCookie"] = fmt.Sprintf("%d", 24*3600*365*100)
|
||||||
|
}
|
||||||
|
|
||||||
|
//机器人信息
|
||||||
|
RobotCommonGin := gin.H{
|
||||||
|
"RobotStatus": configs["RobotStatus"],
|
||||||
|
"TurnToMan": configs["TurnToMan"],
|
||||||
|
"RobotNoAnswer": configs["RobotNoAnswer"],
|
||||||
|
"RobotName": configs["RobotName"],
|
||||||
|
"RobotAvator": configs["RobotAvator"],
|
||||||
|
"RobotCard": configs["RobotCard"],
|
||||||
|
"RobotSwitch": tools.Ifelse(configs["RobotStatus"] == "2", "true", "false"),
|
||||||
|
}
|
||||||
|
//查看过期时间
|
||||||
|
nowSecond := time.Now().Unix()
|
||||||
|
expireSecond := entKefuInfo.ExpiredAt.Unix()
|
||||||
|
if models.FindConfig("KefuExpired") == "on" && expireSecond < nowSecond {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_EXPIRED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_EXPIRED),
|
||||||
|
"kefu": kefuCommonGin,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查看状态
|
||||||
|
if entKefuInfo.Status == 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_FORBIDDEN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_FORBIDDEN),
|
||||||
|
"kefu": kefuCommonGin,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//访客认证回调
|
||||||
|
if configs["VisitorAuthApi"] != "" {
|
||||||
|
api := configs["VisitorAuthApi"] + "?visitor_id=" + originVisitorId +
|
||||||
|
"&sign=" + tools.Md5(originVisitorId+"VisitorAuthApi")
|
||||||
|
res := tools.Get(api)
|
||||||
|
avatar := gjson.Get(res, "avatar").String()
|
||||||
|
name := gjson.Get(res, "name").String()
|
||||||
|
if res == "" || name == "" || avatar == "" {
|
||||||
|
log.Println("访客认证回调失败:", api, res)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.VISITOR_FORBIDDEN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.VISITOR_FORBIDDEN),
|
||||||
|
"kefu": kefuCommonGin,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
form.Avator = avatar
|
||||||
|
form.VisitorName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
dstKefu := entKefuInfo
|
||||||
|
if form.ToId != "" && form.ToId != entKefuInfo.Name {
|
||||||
|
tmpInfo := models.FindUser(form.ToId)
|
||||||
|
if tmpInfo.Name != "" && (int64(tmpInfo.ID) == tools.Str2Int64(form.EntId) || int64(tmpInfo.Pid) == tools.Str2Int64(form.EntId)) {
|
||||||
|
dstKefu = tmpInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断是否在线,判断指定客服是否在线
|
||||||
|
_, ok := ws.KefuList[dstKefu.Name]
|
||||||
|
if dstKefu.OnlineStatus == 1 && ok {
|
||||||
|
allOffline = false
|
||||||
|
} else {
|
||||||
|
//查找在线的子账号
|
||||||
|
kefus := models.FindUsersWhere("pid = ? and online_status=1", form.EntId)
|
||||||
|
//kefus := models.FindUsersByPid(form.EntId)
|
||||||
|
if len(kefus) == 0 {
|
||||||
|
allOffline = true
|
||||||
|
} else {
|
||||||
|
//随机化
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
sort.Sort(models.ByRecNumRand(kefus))
|
||||||
|
for _, kefu := range kefus {
|
||||||
|
if _, ok := ws.KefuList[kefu.Name]; ok {
|
||||||
|
form.ToId = kefu.Name
|
||||||
|
allOffline = false
|
||||||
|
dstKefu = kefu
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//如果子账号全都不在线,并且主账号未设置离线,分给主账号
|
||||||
|
if allOffline == true && form.ToId != entKefuInfo.Name && entKefuInfo.OnlineStatus == 1 {
|
||||||
|
form.ToId = entKefuInfo.Name
|
||||||
|
dstKefu = entKefuInfo
|
||||||
|
_, ok := ws.KefuList[dstKefu.Name]
|
||||||
|
if ok {
|
||||||
|
allOffline = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//判断是否开启AI自动回复
|
||||||
|
if configs["QdrantAIStatus"] == "true" {
|
||||||
|
allOffline = false
|
||||||
|
}
|
||||||
|
kefuCommonGin["staffNickname"] = dstKefu.Nickname
|
||||||
|
kefuCommonGin["staffAvatar"] = dstKefu.Avator
|
||||||
|
|
||||||
|
//判断是否绑定手机
|
||||||
|
if models.FindConfig("KefuBindTel") == "true" && dstKefu.Tel == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.BIND_TEL_FAILD,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.BIND_TEL_FAILD),
|
||||||
|
"kefu": kefuCommonGin,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//同时接待人数
|
||||||
|
visitorMaxNum := int(dstKefu.RecNum)
|
||||||
|
visitorMaxNumConfig, _ := strconv.Atoi(configs["VisitorMaxNum"])
|
||||||
|
if visitorMaxNumConfig == 0 {
|
||||||
|
visitorMaxNumConfig = 100000
|
||||||
|
}
|
||||||
|
if visitorMaxNum >= visitorMaxNumConfig {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.VISITOR_MAX_FORBIDDEN,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.VISITOR_MAX_FORBIDDEN),
|
||||||
|
"visitorMaxNum": visitorMaxNum,
|
||||||
|
"robotNoAnswer": configs["RobotNoAnswer"],
|
||||||
|
"turnToMan": configs["TurnToMan"],
|
||||||
|
"visitorMaxNumNotice": configs["VisitorMaxNumNotice"],
|
||||||
|
"result": gin.H{
|
||||||
|
"avator": form.Avator,
|
||||||
|
"name": form.VisitorName,
|
||||||
|
},
|
||||||
|
"kefu": kefuCommonGin,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor := models.FindVisitorByVistorId(form.VisitorId)
|
||||||
|
visitor.ToId = dstKefu.Name
|
||||||
|
visitNum := visitor.VisitNum + 1
|
||||||
|
|
||||||
|
if visitor.ID != 0 {
|
||||||
|
if form.Avator == "" && isWechat {
|
||||||
|
form.Avator = visitor.Avator
|
||||||
|
}
|
||||||
|
if form.VisitorName == "" && isWechat {
|
||||||
|
form.VisitorName = visitor.Name
|
||||||
|
}
|
||||||
|
if visitor.RealName != "" {
|
||||||
|
form.VisitorName = visitor.RealName
|
||||||
|
}
|
||||||
|
//更新状态上线
|
||||||
|
models.UpdateVisitor(form.EntId, form.VisitorName, form.Avator, form.VisitorId, dstKefu.Name, visitor.Status, c.ClientIP(), c.ClientIP(),
|
||||||
|
form.Refer,
|
||||||
|
form.Extra,
|
||||||
|
form.CityAddress,
|
||||||
|
visitNum,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
visitor = *models.CreateVisitor(form.VisitorName, form.Avator, c.ClientIP(), dstKefu.Name, form.VisitorId, form.Refer, form.CityAddress, form.ClientIp, form.EntId, form.Extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.VisitorName != "" {
|
||||||
|
visitor.Name = form.VisitorName
|
||||||
|
}
|
||||||
|
go models.AddVisitorExt(visitor.VisitorId, form.Refer, form.UserAgent, form.Url, form.ReferUrl, form.Title, form.ClientIp, form.CityAddress, form.EntId, form.Language)
|
||||||
|
|
||||||
|
//如果没有开启机器人才发送通知
|
||||||
|
if configs["RobotStatus"] != "2" {
|
||||||
|
noticeContent := fmt.Sprintf("%s访问%d次", visitor.Name, visitNum)
|
||||||
|
go SendVisitorLoginNotice(form.ToId, visitor.Name, visitor.Avator, noticeContent, visitor.VisitorId)
|
||||||
|
//go SendWechatVisitorTemplate(form.ToId, visitor.Name, noticeContent, visitor.EntId)
|
||||||
|
//go SendWechatKefuNotice(form.ToId, "[访客]"+visitor.Name+",访问:"+form.Refer, visitor.EntId)
|
||||||
|
go SendNoticeEmail(visitor.Name, "[访客]"+visitor.Name, form.EntId, noticeContent)
|
||||||
|
go SendAppGetuiPush(dstKefu.Name, "[访客]"+visitor.Name, noticeContent)
|
||||||
|
} else {
|
||||||
|
//开启仅机器人接待时,不显示离线状态
|
||||||
|
allOffline = false
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"alloffline": allOffline,
|
||||||
|
"entIntroduce": configs["KefuIntroduce"],
|
||||||
|
"robotSwitch": tools.Ifelse(configs["RobotStatus"] == "2", "true", "false"),
|
||||||
|
"turnToMan": configs["TurnToMan"],
|
||||||
|
"robotNoAnswer": configs["RobotNoAnswer"],
|
||||||
|
"visitorNotice": configs["VisitorNotice"],
|
||||||
|
"autoWelcome": configs["AutoWelcome"],
|
||||||
|
"visitorCookie": configs["VisitorCookie"],
|
||||||
|
"scanWechatQrcode": configs["ScanWechatQrcode"],
|
||||||
|
"closeNotice": configs["CloseNotice"],
|
||||||
|
"result": visitor,
|
||||||
|
"kefu": kefuCommonGin,
|
||||||
|
"robot": RobotCommonGin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组合扩展信息
|
||||||
|
func makeVisitorLoginForm(form *VisitorLoginForm) {
|
||||||
|
//扩展信息
|
||||||
|
extraJson := tools.Base64Decode(form.Extra)
|
||||||
|
if extraJson != "" {
|
||||||
|
var extraObj VisitorExtra
|
||||||
|
err := json.Unmarshal([]byte(extraJson), &extraObj)
|
||||||
|
if err == nil {
|
||||||
|
if extraObj.VisitorName != "" {
|
||||||
|
form.VisitorName = extraObj.VisitorName
|
||||||
|
}
|
||||||
|
if extraObj.VisitorAvatar != "" {
|
||||||
|
form.Avator = extraObj.VisitorAvatar
|
||||||
|
}
|
||||||
|
if extraObj.VisitorId != "" {
|
||||||
|
form.VisitorId = extraObj.VisitorId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func GetVisitor(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitorId")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
vistor := models.FindVisitorByVistorId(visitorId)
|
||||||
|
exts := models.FindVisitorExtByVistorId(visitorId, 1, 1)
|
||||||
|
sameIPCount := models.CountVisitorsByEntid(fmt.Sprintf("%v", entId), " and client_ip = ? ", vistor.ClientIp)
|
||||||
|
osVersion := ""
|
||||||
|
browser := ""
|
||||||
|
ua := ""
|
||||||
|
if len(exts) != 0 {
|
||||||
|
ext := exts[0]
|
||||||
|
ua = ext.Ua
|
||||||
|
uaParser := tools.NewUaParser(ua)
|
||||||
|
osVersion = uaParser.OsVersion
|
||||||
|
browser = uaParser.Browser
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"create_time": vistor.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
"last_time": vistor.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
"os_version": osVersion,
|
||||||
|
"browser": browser,
|
||||||
|
"ua": ua,
|
||||||
|
"same_ip": sameIPCount,
|
||||||
|
"result": vistor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
获取访客访问动态
|
||||||
|
*/
|
||||||
|
func GetVisitorExt(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
page, pagesize := HandlePagePageSize(c)
|
||||||
|
list := make([]VisitorExtend, 0)
|
||||||
|
count := models.CountVisitorExtByVistorId(visitorId)
|
||||||
|
exts := models.FindVisitorExtByVistorId(visitorId, page, pagesize)
|
||||||
|
for _, ext := range exts {
|
||||||
|
uaParser := tools.NewUaParser(ext.Ua)
|
||||||
|
item := VisitorExtend{
|
||||||
|
CreatedAt: ext.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
ID: ext.ID,
|
||||||
|
VisitorId: ext.VisitorId,
|
||||||
|
Url: ext.Url,
|
||||||
|
Ua: ext.Ua,
|
||||||
|
Title: ext.Title,
|
||||||
|
ClientIp: ext.ClientIp,
|
||||||
|
OsVersion: uaParser.OsVersion,
|
||||||
|
Browser: uaParser.Browser,
|
||||||
|
Refer: ext.Refer,
|
||||||
|
City: ext.City,
|
||||||
|
ReferUrl: ext.ReferUrl,
|
||||||
|
Language: ext.Language,
|
||||||
|
}
|
||||||
|
list = append(list, item)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
获取访客访问动态列表
|
||||||
|
*/
|
||||||
|
func GetVisitorExtList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
|
||||||
|
query := "1=1 "
|
||||||
|
var arg = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
query += "and ent_id = ? "
|
||||||
|
arg = append(arg, entId)
|
||||||
|
}
|
||||||
|
title := tools.UrlDecode(c.Query("title"))
|
||||||
|
referTitle := tools.UrlDecode(c.Query("refer_title"))
|
||||||
|
if title != "" {
|
||||||
|
query += "and title like ? "
|
||||||
|
arg = append(arg, title+"%")
|
||||||
|
}
|
||||||
|
if referTitle != "" {
|
||||||
|
query += "and refer_title like ? "
|
||||||
|
arg = append(arg, referTitle+"%")
|
||||||
|
}
|
||||||
|
page, pagesize := HandlePagePageSize(c)
|
||||||
|
count := models.CountVisitorExtByWhere(query, arg...)
|
||||||
|
exts := models.FindVisitorExtByWhere(page, pagesize, query, arg...)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": exts,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
获取访客访问动态列表
|
||||||
|
*/
|
||||||
|
func GetRateList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
roleId, _ := c.Get("role_id")
|
||||||
|
|
||||||
|
query := "1=1 "
|
||||||
|
var arg = []interface{}{}
|
||||||
|
if roleId.(float64) != 1 {
|
||||||
|
query += "and ent_id = ? "
|
||||||
|
arg = append(arg, entId)
|
||||||
|
}
|
||||||
|
page, pagesize := HandlePagePageSize(c)
|
||||||
|
count := models.CountRateWhere(query, arg...)
|
||||||
|
exts := models.FindRateByWhere(page, pagesize, query, arg...)
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": exts,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary 获取访客列表接口
|
||||||
|
// @Produce json
|
||||||
|
// @Accept multipart/form-data
|
||||||
|
// @Param page query string true "分页"
|
||||||
|
// @Param token header string true "认证token"
|
||||||
|
// @Success 200 {object} controller.Response
|
||||||
|
// @Failure 200 {object} controller.Response
|
||||||
|
// @Router /visitors [get]
|
||||||
|
func GetVisitors(c *gin.Context) {
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
var pagesize uint
|
||||||
|
myPagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if myPagesize != 0 {
|
||||||
|
pagesize = uint(myPagesize)
|
||||||
|
} else {
|
||||||
|
pagesize = common.VisitorPageSize
|
||||||
|
}
|
||||||
|
kefuId, _ := c.Get("kefu_name")
|
||||||
|
vistors := models.FindVisitorsByKefuId(uint(page), pagesize, kefuId.(string))
|
||||||
|
visitorIds := make([]string, 0)
|
||||||
|
for _, visitor := range vistors {
|
||||||
|
visitorIds = append(visitorIds, visitor.VisitorId)
|
||||||
|
}
|
||||||
|
messagesMap := models.FindLastMessageMap(visitorIds)
|
||||||
|
unreadMap := models.FindUnreadMessageNumByVisitorIds(visitorIds, "visitor")
|
||||||
|
//log.Println(unreadMap)
|
||||||
|
users := make([]VisitorOnline, 0)
|
||||||
|
for _, visitor := range vistors {
|
||||||
|
var unreadNum uint32
|
||||||
|
if num, ok := unreadMap[visitor.VisitorId]; ok {
|
||||||
|
unreadNum = num
|
||||||
|
}
|
||||||
|
user := VisitorOnline{
|
||||||
|
Id: visitor.ID,
|
||||||
|
VisitorId: visitor.VisitorId,
|
||||||
|
Avator: visitor.Avator,
|
||||||
|
Ip: visitor.SourceIp,
|
||||||
|
Username: fmt.Sprintf("#%d %s", visitor.ID, visitor.Name),
|
||||||
|
LastMessage: messagesMap[visitor.VisitorId],
|
||||||
|
UpdatedAt: visitor.UpdatedAt,
|
||||||
|
UnreadNum: unreadNum,
|
||||||
|
Status: visitor.Status,
|
||||||
|
}
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
count := models.CountVisitorsByKefuId(kefuId.(string))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": users,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func GetVisitorsList(c *gin.Context) {
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
search := ""
|
||||||
|
args := []interface{}{}
|
||||||
|
//通过访客名搜索
|
||||||
|
visitorName := c.Query("visitorName")
|
||||||
|
if tools.IsIP(visitorName) {
|
||||||
|
search += " and client_ip = ? "
|
||||||
|
args = append(args, visitorName)
|
||||||
|
} else if visitorName != "" {
|
||||||
|
search += " and (name like ? or visitor_id like ?)"
|
||||||
|
args = append(args, "%"+visitorName+"%")
|
||||||
|
args = append(args, "%"+visitorName+"%")
|
||||||
|
}
|
||||||
|
//通过客服名搜索
|
||||||
|
kefuName := c.Query("kefuName")
|
||||||
|
if kefuName != "" {
|
||||||
|
search += " and to_id = ? "
|
||||||
|
args = append(args, kefuName)
|
||||||
|
}
|
||||||
|
//根据创建时间
|
||||||
|
createTime := c.Query("createTime")
|
||||||
|
if createTime != "" {
|
||||||
|
timeArr := strings.Split(createTime, "~")
|
||||||
|
search += " and created_at>= ? and created_at <= ? "
|
||||||
|
args = append(args, timeArr[0], timeArr[1])
|
||||||
|
}
|
||||||
|
//根据活跃时间
|
||||||
|
updateTime := c.Query("updateTime")
|
||||||
|
if updateTime != "" {
|
||||||
|
timeArr := strings.Split(updateTime, "~")
|
||||||
|
search += " and updated_at>= ? and updated_at <= ? "
|
||||||
|
args = append(args, timeArr[0], timeArr[1])
|
||||||
|
}
|
||||||
|
//根据tag找出visitor
|
||||||
|
visitorTag := c.Query("visitorTag")
|
||||||
|
var visitorIdsArr []string
|
||||||
|
if visitorTag != "" {
|
||||||
|
visitorTags := v2.GetVisitorTags("tag_id = ? ", visitorTag)
|
||||||
|
for _, visitorTag := range visitorTags {
|
||||||
|
visitorIdsArr = append(visitorIdsArr, visitorTag.VisitorId)
|
||||||
|
}
|
||||||
|
if len(visitorIdsArr) != 0 {
|
||||||
|
search += " and visitor_id in (?) "
|
||||||
|
args = append(args, visitorIdsArr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据内容找出visitor
|
||||||
|
visitorMessage := c.Query("visitorMessage")
|
||||||
|
if visitorMessage != "" {
|
||||||
|
visitors := models.FindMessageByQuery("ent_id = ? and content like ?", entId, "%"+visitorMessage+"%")
|
||||||
|
for _, visitor := range visitors {
|
||||||
|
visitorIdsArr = append(visitorIdsArr, visitor.VisitorId)
|
||||||
|
}
|
||||||
|
if len(visitorIdsArr) != 0 {
|
||||||
|
search += " and visitor_id in (?) "
|
||||||
|
args = append(args, visitorIdsArr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据已读未读找出visitor
|
||||||
|
//根据内容找出visitor
|
||||||
|
readType := c.Query("readType")
|
||||||
|
if readType != "" {
|
||||||
|
visitors := models.FindMessageByQuery("ent_id = ? and mes_type='visitor' and status = ?", entId, readType)
|
||||||
|
visitorIdsArr = []string{}
|
||||||
|
for _, visitor := range visitors {
|
||||||
|
visitorIdsArr = append(visitorIdsArr, visitor.VisitorId)
|
||||||
|
}
|
||||||
|
if len(visitorIdsArr) != 0 {
|
||||||
|
search += " and visitor_id in (?) "
|
||||||
|
args = append(args, visitorIdsArr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//排序参数
|
||||||
|
orderBy := c.Query("orderBy")
|
||||||
|
if orderBy == "" {
|
||||||
|
orderBy = "updated_at desc"
|
||||||
|
} else {
|
||||||
|
orderBy = orderBy + " desc"
|
||||||
|
}
|
||||||
|
vistors := models.FindVisitorsByEntId(uint(page), uint(pagesize), fmt.Sprintf("%v", entId), orderBy, search, args...)
|
||||||
|
count := models.CountVisitorsByEntid(fmt.Sprintf("%v", entId), search, args...)
|
||||||
|
|
||||||
|
//获取最后一条消息内容
|
||||||
|
visitorIds := make([]string, 0)
|
||||||
|
for _, visitor := range vistors {
|
||||||
|
visitorIds = append(visitorIds, visitor.VisitorId)
|
||||||
|
}
|
||||||
|
messagesMap := models.FindLastMessageMap(visitorIds)
|
||||||
|
//获取访客未读数
|
||||||
|
unreadMap := models.FindUnreadMessageNumByVisitorIds(visitorIds, "visitor")
|
||||||
|
//log.Println(unreadMap)
|
||||||
|
users := make([]VisitorOnline, 0)
|
||||||
|
for _, visitor := range vistors {
|
||||||
|
var unreadNum uint32
|
||||||
|
if num, ok := unreadMap[visitor.VisitorId]; ok {
|
||||||
|
unreadNum = num
|
||||||
|
}
|
||||||
|
username := fmt.Sprintf("#%d %s", visitor.ID, visitor.Name)
|
||||||
|
if visitor.RealName != "" {
|
||||||
|
username = visitor.RealName
|
||||||
|
}
|
||||||
|
user := VisitorOnline{
|
||||||
|
Id: visitor.ID,
|
||||||
|
VisitorId: visitor.VisitorId,
|
||||||
|
City: visitor.City,
|
||||||
|
Avator: visitor.Avator,
|
||||||
|
Ip: visitor.SourceIp,
|
||||||
|
Username: username,
|
||||||
|
LastMessage: messagesMap[visitor.VisitorId],
|
||||||
|
UpdatedAt: visitor.UpdatedAt,
|
||||||
|
CreatedAt: visitor.CreatedAt,
|
||||||
|
UnreadNum: unreadNum,
|
||||||
|
Status: visitor.Status,
|
||||||
|
}
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"list": users,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": uint(pagesize),
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVisitorMessageByKefu(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
log.Println(entId, visitorId)
|
||||||
|
messages := models.FindMessageByWhere("message.visitor_id=? and message.ent_id=?", visitorId, entId)
|
||||||
|
chatMessages := make([]ChatMessage, 0)
|
||||||
|
|
||||||
|
for _, message := range messages {
|
||||||
|
var chatMessage ChatMessage
|
||||||
|
chatMessage.Time = message.CreatedAt.Format("2006-01-02 15:04:05")
|
||||||
|
chatMessage.Content = message.Content
|
||||||
|
chatMessage.MesType = message.MesType
|
||||||
|
if message.MesType == "kefu" {
|
||||||
|
chatMessage.Name = message.KefuName
|
||||||
|
chatMessage.Avator = message.KefuAvator
|
||||||
|
} else {
|
||||||
|
chatMessage.Name = message.VisitorName
|
||||||
|
chatMessage.Avator = message.VisitorAvator
|
||||||
|
}
|
||||||
|
chatMessages = append(chatMessages, chatMessage)
|
||||||
|
}
|
||||||
|
models.ReadMessageByVisitorId(visitorId, "visitor")
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": chatMessages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary 获取在线访客列表接口
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} controller.Response
|
||||||
|
// @Failure 200 {object} controller.Response
|
||||||
|
// @Router /visitors_online [get]
|
||||||
|
func GetVisitorOnlines(c *gin.Context) {
|
||||||
|
users := make([]map[string]string, 0)
|
||||||
|
visitorIds := make([]string, 0)
|
||||||
|
for uid, visitor := range ws.ClientList {
|
||||||
|
userInfo := make(map[string]string)
|
||||||
|
userInfo["uid"] = uid
|
||||||
|
userInfo["name"] = visitor.Name
|
||||||
|
userInfo["avator"] = visitor.Avatar
|
||||||
|
users = append(users, userInfo)
|
||||||
|
visitorIds = append(visitorIds, visitor.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询最新消息
|
||||||
|
messages := models.FindLastMessage(visitorIds)
|
||||||
|
temp := make(map[string]string, 0)
|
||||||
|
for _, mes := range messages {
|
||||||
|
temp[mes.VisitorId] = mes.Content
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
user["last_message"] = temp[user["uid"]]
|
||||||
|
}
|
||||||
|
|
||||||
|
tcps := make([]string, 0)
|
||||||
|
for ip, _ := range clientTcpList {
|
||||||
|
tcps = append(tcps, ip)
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": gin.H{
|
||||||
|
"ws": users,
|
||||||
|
"tcp": tcps,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
设置访客属性
|
||||||
|
*/
|
||||||
|
func PostVisitorAttrs(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
entIdStr := ""
|
||||||
|
if entId == nil {
|
||||||
|
entIdStr = c.Query("ent_id")
|
||||||
|
} else {
|
||||||
|
entIdStr = entId.(string)
|
||||||
|
}
|
||||||
|
if entIdStr == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "ent_id 不能为空",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var param VisitorAttrParams
|
||||||
|
if err := c.BindJSON(¶m); err != nil {
|
||||||
|
c.String(200, err.Error())
|
||||||
|
}
|
||||||
|
saveVisitorAttr(entIdStr, param)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func saveVisitorAttr(entId string, param VisitorAttrParams) {
|
||||||
|
|
||||||
|
attrInfo := models.GetVisitorAttrByVisitorId(param.VisitorId, fmt.Sprintf("%v", entId))
|
||||||
|
if attrInfo.ID == 0 {
|
||||||
|
models.CreateVisitorAttr(
|
||||||
|
fmt.Sprintf("%v", entId),
|
||||||
|
param.VisitorId,
|
||||||
|
param.VisitorAttr.RealName,
|
||||||
|
param.VisitorAttr.Tel,
|
||||||
|
param.VisitorAttr.Email,
|
||||||
|
param.VisitorAttr.QQ,
|
||||||
|
param.VisitorAttr.Wechat,
|
||||||
|
param.VisitorAttr.Comment)
|
||||||
|
} else {
|
||||||
|
var attr = &models.Visitor_attr{
|
||||||
|
RealName: param.VisitorAttr.RealName,
|
||||||
|
Tel: param.VisitorAttr.Tel,
|
||||||
|
Email: param.VisitorAttr.Email,
|
||||||
|
QQ: param.VisitorAttr.QQ,
|
||||||
|
Wechat: param.VisitorAttr.Wechat,
|
||||||
|
Comment: param.VisitorAttr.Comment,
|
||||||
|
}
|
||||||
|
models.SaveVisitorAttrByVisitorId(attr, param.VisitorId, fmt.Sprintf("%v", entId))
|
||||||
|
}
|
||||||
|
if param.VisitorAttr.RealName != "" {
|
||||||
|
models.UpdateVisitorRealName(param.VisitorAttr.RealName,
|
||||||
|
fmt.Sprintf("%v", entId),
|
||||||
|
param.VisitorId)
|
||||||
|
go ws.UpdateVisitorName(param.VisitorId, param.VisitorAttr.RealName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
设置访客属性
|
||||||
|
*/
|
||||||
|
func GetVisitorAttr(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
attrInfo := models.GetVisitorAttrByVisitorId(visitorId, fmt.Sprintf("%v", entId))
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": attrInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
设置访客ID
|
||||||
|
*/
|
||||||
|
func PostChangeVisitorId(c *gin.Context) {
|
||||||
|
entId := c.PostForm("ent_id")
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
newId := c.PostForm("new_id")
|
||||||
|
if entId == "" || visitorId == "" || newId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.INVALID,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
go models.UpdateVisitorVisitorId(visitorId, newId)
|
||||||
|
go models.UpdateMessageVisitorId(visitorId, newId)
|
||||||
|
go ws.VisitorChangeId(visitorId, newId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary 获取客服的在线访客列表接口
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} controller.Response
|
||||||
|
// @Failure 200 {object} controller.Response
|
||||||
|
// @Router /visitors_kefu_online [get]
|
||||||
|
func GetKefusVisitorOnlines(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
kefuInfo := models.FindUserByUid(entId)
|
||||||
|
if kefuInfo.Status != 0 && kefuInfo.Status == 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "账户禁用",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
users := make([]*VisitorOnline, 0)
|
||||||
|
visitorIds := make([]string, 0)
|
||||||
|
clientList := sortMapToSlice(ws.ClientList)
|
||||||
|
for _, visitor := range clientList {
|
||||||
|
if visitor.ToId != kefuName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
userInfo := new(VisitorOnline)
|
||||||
|
userInfo.UpdatedAt = visitor.UpdateTime
|
||||||
|
userInfo.VisitorId = visitor.Id
|
||||||
|
userInfo.Username = visitor.Name
|
||||||
|
userInfo.Avator = visitor.Avatar
|
||||||
|
users = append(users, userInfo)
|
||||||
|
visitorIds = append(visitorIds, visitor.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
//查询最新消息
|
||||||
|
messages := models.FindLastMessageMap(visitorIds)
|
||||||
|
//查未读数
|
||||||
|
unreadMap := models.FindUnreadMessageNumByVisitorIds(visitorIds, "visitor")
|
||||||
|
for _, user := range users {
|
||||||
|
user.LastMessage = messages[user.VisitorId]
|
||||||
|
if user.LastMessage == "" {
|
||||||
|
user.LastMessage = "new visitor"
|
||||||
|
}
|
||||||
|
var unreadNum uint32
|
||||||
|
if num, ok := unreadMap[user.VisitorId]; ok {
|
||||||
|
unreadNum = num
|
||||||
|
}
|
||||||
|
user.UnreadNum = unreadNum
|
||||||
|
user.LastTime = user.UpdatedAt.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": users,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelVisitor(c *gin.Context) {
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
entIdStr, _ := c.Get("ent_id")
|
||||||
|
entId, _ := strconv.Atoi(entIdStr.(string))
|
||||||
|
|
||||||
|
models.DelVisitor("ent_id = ? and visitor_id = ?", entId, visitorId)
|
||||||
|
models.DelMessage("ent_id = ? and visitor_id = ?", entId, visitorId)
|
||||||
|
models.DelVisitorAttr("ent_id = ? and visitor_id = ?", entId, visitorId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortMapToSlice(youMap map[string]*ws.VisitorConnection) []*ws.VisitorConnection {
|
||||||
|
keys := make([]string, 0)
|
||||||
|
for k, _ := range youMap {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
myMap := make([]*ws.VisitorConnection, 0)
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
myMap = append(myMap, youMap[k])
|
||||||
|
}
|
||||||
|
return myMap
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/types"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VisitorBlackForm struct {
|
||||||
|
Id uint `form:"id" json:"id" uri:"id" xml:"id"`
|
||||||
|
VisitorId string `form:"visitor_id" json:"visitor_id" uri:"visitor_id" xml:"visitor_id" binding:"required"`
|
||||||
|
Name string `form:"name" json:"name" uri:"name" xml:"name" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//列表
|
||||||
|
func GeVisitorBlacks(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
page, _ := strconv.Atoi(c.Query("page"))
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||||
|
if pagesize <= 0 || pagesize > 50 {
|
||||||
|
pagesize = 10
|
||||||
|
}
|
||||||
|
count := models.CountVisitorBlack("ent_id = ? ", entId)
|
||||||
|
list := models.FindVisitorBlacks(page, pagesize, "ent_id = ? ", entId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": gin.H{
|
||||||
|
"list": list,
|
||||||
|
"count": count,
|
||||||
|
"pagesize": pagesize,
|
||||||
|
"page": page,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//添加
|
||||||
|
func PostVisitorBlack(c *gin.Context) {
|
||||||
|
kefuName, _ := c.Get("kefu_name")
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
var form VisitorBlackForm
|
||||||
|
err := c.Bind(&form)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
"result": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
model := &models.VisitorBlack{
|
||||||
|
VisitorId: form.VisitorId,
|
||||||
|
Name: form.Name,
|
||||||
|
EntId: entId.(string),
|
||||||
|
KefuName: kefuName.(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
model.AddVisitorBlack()
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除
|
||||||
|
func DelVisitorBlack(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
id := c.Query("id")
|
||||||
|
err := models.DelVisitorBlack("id = ? and ent_id = ?", id, entId)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
//修改访客状态位
|
||||||
|
func PostVisitorState(c *gin.Context) {
|
||||||
|
position := tools.Str2Int(c.PostForm("position"))
|
||||||
|
state := c.PostForm("state")
|
||||||
|
value := c.PostForm("value")
|
||||||
|
visitorId := c.PostForm("visitor_id")
|
||||||
|
if position == 0 || value == "" || visitorId == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.FAILED,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.INVALID),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newState := tools.ReplaceAtPosition(state, position, value)
|
||||||
|
models.UpdateVisitorState(visitorId, newState)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,379 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"io/ioutil"
|
||||||
|
"kefu/common"
|
||||||
|
"kefu/lib"
|
||||||
|
"kefu/lib/wechat_kf_sdk"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/service"
|
||||||
|
"kefu/tools"
|
||||||
|
"kefu/types"
|
||||||
|
"kefu/ws"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 企业微信相关
|
||||||
|
var retryCache = cache.New(60*time.Minute, 10*time.Minute)
|
||||||
|
|
||||||
|
func isRetry(signature string) bool {
|
||||||
|
var base = "retry:signature:%s"
|
||||||
|
key := fmt.Sprintf(base, signature)
|
||||||
|
_, found := retryCache.Get(key)
|
||||||
|
if found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
retryCache.Set(key, "1", 1*time.Minute)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业微信客服消息回调
|
||||||
|
func PostEntWechatCallback(c *gin.Context) {
|
||||||
|
entId := c.Param("entId")
|
||||||
|
kefuName := c.Param("kefuName")
|
||||||
|
verifyMsgSign := c.Query("msg_signature")
|
||||||
|
verifyTimestamp := c.Query("timestamp")
|
||||||
|
verifyNonce := c.Query("nonce")
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
|
||||||
|
body := string(bodyBytes)
|
||||||
|
log.Println("企业微信客服回调:", body, verifyMsgSign)
|
||||||
|
if isRetry(verifyMsgSign) {
|
||||||
|
log.Println("企业微信客服重试机制:", verifyMsgSign)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//获取配置
|
||||||
|
configs := models.GetEntConfigsMap(entId, "QdrantAIStatus", "chatGPTUrl", "chatGPTSecret", "kefuWeworkCorpid", "kefuWeworkSecret", "kefuWeworkToken", "kefuWeworkEncodingAESKey", "kefuWeworkNextCursor", "QiyeWechatKefuPreRobot")
|
||||||
|
kefuWework := wechat_kf_sdk.NewKefuWework(configs["kefuWeworkCorpid"], configs["kefuWeworkSecret"], configs["kefuWeworkToken"], configs["kefuWeworkEncodingAESKey"])
|
||||||
|
receiveMessage, err := kefuWework.DecryptMsg(verifyMsgSign, verifyTimestamp, verifyNonce, body)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("企业微信客服解析数据失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
syncMsgs, err := kefuWework.SyncMsg(map[string]interface{}{
|
||||||
|
"token": receiveMessage.Token,
|
||||||
|
"cursor": configs["kefuWeworkNextCursor"],
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("企业微信客服获取数据失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.UpdateInsertEntConfig(entId, "企业微信客服NextCursor", "kefuWeworkNextCursor", syncMsgs.NextCursor)
|
||||||
|
size := len(syncMsgs.MsgList)
|
||||||
|
if size < 1 {
|
||||||
|
log.Println("企业微信客服获取数据为空", syncMsgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current := syncMsgs.MsgList[size-1]
|
||||||
|
log.Println("企业微信客服最新数据", current)
|
||||||
|
userId := current.ExternalUserid
|
||||||
|
kfId := current.OpenKfid
|
||||||
|
content := ""
|
||||||
|
//文本消息
|
||||||
|
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||||
|
if current.Msgtype == "text" {
|
||||||
|
content = current.Text.Content
|
||||||
|
|
||||||
|
} else if current.Msgtype == "image" {
|
||||||
|
//图片消息
|
||||||
|
mediaId := current.Image.MediaId
|
||||||
|
fileName := mediaId + ".jpg"
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s", fildDir, fileName)
|
||||||
|
_, err := kefuWework.DownloadTempFileByMediaID(mediaId, filepath)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("企业微信客服下载临时素材失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content = "img[/" + filepath + "]"
|
||||||
|
} else if current.Msgtype == "voice" {
|
||||||
|
//语音消息
|
||||||
|
mediaId := current.Voice.MediaId
|
||||||
|
fileName := mediaId + ".wav"
|
||||||
|
isExist, _ := tools.IsFileExist(fildDir)
|
||||||
|
if !isExist {
|
||||||
|
os.Mkdir(fildDir, os.ModePerm)
|
||||||
|
}
|
||||||
|
filepath := fmt.Sprintf("%s%s", fildDir, fileName)
|
||||||
|
_, err := kefuWework.DownloadTempFileByMediaID(mediaId, filepath)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("企业微信客服下载临时素材失败:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if configs["chatGPTUrl"] != "" && configs["chatGPTSecret"] != "" {
|
||||||
|
gpt := lib.NewChatGptTool(configs["chatGPTUrl"], configs["chatGPTSecret"])
|
||||||
|
response, err := gpt.GetWhisper(filepath)
|
||||||
|
if err == nil {
|
||||||
|
log.Println(response)
|
||||||
|
content = gjson.Get(response, "text").String()
|
||||||
|
} else {
|
||||||
|
log.Println(err)
|
||||||
|
content = "audio[/" + filepath + "]"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = "audio[/" + filepath + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if current.Msgtype == "event" {
|
||||||
|
//事件消息
|
||||||
|
//进入会话
|
||||||
|
if current.Event.EventType == "enter_session" {
|
||||||
|
welcomeCode := current.Event.WelcomeCode
|
||||||
|
//发送欢迎语
|
||||||
|
welcomes := models.FindWelcomesByKeyword(kefuName, "wechat")
|
||||||
|
if len(welcomes) > 0 {
|
||||||
|
welcomeContent := ""
|
||||||
|
for _, welcome := range welcomes {
|
||||||
|
welcome.Content, _ = tools.ReplaceStringByRegex(welcome.Content, `<[^a>]+>|target="[^"]+"`, "")
|
||||||
|
welcomeContent += welcome.Content + "\r\n"
|
||||||
|
}
|
||||||
|
go kefuWework.SendWelcomeMsg(welcomeContent, welcomeCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if content == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId := fmt.Sprintf("wxkf|%s|%s", entId, userId)
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
//获取客户昵称头像
|
||||||
|
reqData := map[string]interface{}{
|
||||||
|
"external_userid_list": []string{
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ret, _ := kefuWework.BatchGet(reqData)
|
||||||
|
visitorName := gjson.Get(ret, "customer_list.0.nickname").String()
|
||||||
|
avator := gjson.Get(ret, "customer_list.0.avatar").String()
|
||||||
|
extra := gjson.Get(ret, "customer_list.0.enter_session_context.scene_param").String()
|
||||||
|
refer := gjson.Get(ret, "customer_list.0.enter_session_context.scene").String()
|
||||||
|
if visitorName == "" {
|
||||||
|
visitorName = "微信客服用户"
|
||||||
|
}
|
||||||
|
if avator == "" {
|
||||||
|
avator = "/static/images/we-chat-wx.png"
|
||||||
|
}
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitorName,
|
||||||
|
Avator: avator,
|
||||||
|
ToId: kfId,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: refer,
|
||||||
|
EntId: entId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "微信客服",
|
||||||
|
Extra: extra,
|
||||||
|
ClientIp: c.ClientIP(),
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
go models.UpdateVisitorKefuName(visitorId, kfId)
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuInfo := models.FindUser(kfId)
|
||||||
|
if kefuInfo.ID == 0 {
|
||||||
|
kefuInfo = models.FindUserByUid(entId)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuInfo.Name, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuInfo.Name, vistorInfo, content)
|
||||||
|
go SendWechatVisitorMessageTemplate(kefuInfo.Name, vistorInfo.Name, vistorInfo.VisitorId, content, vistorInfo.EntId)
|
||||||
|
go service.SendWorkWechatMesage(vistorInfo.EntId, vistorInfo, kefuInfo, content, c)
|
||||||
|
//判断当前访客是否机器人回复
|
||||||
|
if !models.CheckVisitorRobotReply(vistorInfo.State) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := ""
|
||||||
|
//调用自动回复
|
||||||
|
result = GetLearnReplyContent(entId, content)
|
||||||
|
if result == "" && configs["QdrantAIStatus"] == "true" {
|
||||||
|
if configs["QiyeWechatKefuPreRobot"] != "" {
|
||||||
|
go kefuWework.SendTextMsg(kfId, userId, configs["QiyeWechatKefuPreRobot"])
|
||||||
|
}
|
||||||
|
//调用GPT3.5
|
||||||
|
result = ws.Gpt3Knowledge(entId, visitorId, kefuInfo, content)
|
||||||
|
}
|
||||||
|
if result != "" {
|
||||||
|
result, _ = tools.ReplaceStringByRegex(result, `<[^a>]+>|target="[^"]+"`, "")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if imgUrl := ParseImgMessage2(result); imgUrl != "" {
|
||||||
|
err = kefuWework.SendImagesMsg(kfId, userId, common.StaticDirPath+imgUrl)
|
||||||
|
} else if voiceUrl := ParseVoiceMessage(result); voiceUrl != "" {
|
||||||
|
err = kefuWework.SendVoiceMsg(kfId, userId, common.UploadDirPath+voiceUrl)
|
||||||
|
} else {
|
||||||
|
log.Println("企业微信客服发送消息:", kfId, userId, result)
|
||||||
|
err = kefuWework.SendTextMsg(kfId, userId, result)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println("企业微信客服发送消息失败:", err)
|
||||||
|
} else {
|
||||||
|
models.CreateMessage(kefuInfo.Name, visitorId, result, "kefu", entId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信客服验签
|
||||||
|
func GetWechatKefuCheckSign(c *gin.Context) {
|
||||||
|
entId := c.Param("entId")
|
||||||
|
signature := c.Query("msg_signature")
|
||||||
|
timestamp := c.Query("timestamp")
|
||||||
|
nonce := c.Query("nonce")
|
||||||
|
echostr := c.Query("echostr")
|
||||||
|
//获取配置
|
||||||
|
configs := models.GetEntConfigsMap(entId, "kefuWeworkCorpid", "kefuWeworkSecret", "kefuWeworkToken", "kefuWeworkEncodingAESKey")
|
||||||
|
kefuWework := wechat_kf_sdk.NewKefuWework(configs["kefuWeworkCorpid"], configs["kefuWeworkSecret"], configs["kefuWeworkToken"], configs["kefuWeworkEncodingAESKey"])
|
||||||
|
res, err := kefuWework.CheckSign(signature, timestamp, nonce, echostr)
|
||||||
|
log.Println("企业微信客服验签:", res, err)
|
||||||
|
c.Writer.Write([]byte(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信客服列表
|
||||||
|
func GetKefuWeworkList(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
wechat := service.GetKefuWework(entId.(string))
|
||||||
|
str, _ := wechat.GetAccountList(0, 1000)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": str,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 微信客服链接
|
||||||
|
func GetKefuWeworkLink(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
openKfId := c.Query("open_kfid")
|
||||||
|
//获取配置
|
||||||
|
configs := models.GetEntConfigsMap(entId.(string), "kefuWeworkCorpid", "kefuWeworkSecret", "kefuWeworkToken", "kefuWeworkEncodingAESKey")
|
||||||
|
kefuWework := wechat_kf_sdk.NewKefuWework(configs["kefuWeworkCorpid"], configs["kefuWeworkSecret"], configs["kefuWeworkToken"], configs["kefuWeworkEncodingAESKey"])
|
||||||
|
str, _ := kefuWework.GetAccountLink(openKfId)
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.SUCCESS,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.SUCCESS),
|
||||||
|
"result": str,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 企业微信机器人
|
||||||
|
func PostEntWechatRobot(c *gin.Context) {
|
||||||
|
entId := c.Param("entId")
|
||||||
|
robotCode := c.Param("robotCode")
|
||||||
|
kefuName := c.Param("kefuName")
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
if err := c.ShouldBindJSON(&jsonData); err != nil {
|
||||||
|
// 发生错误,返回错误响应
|
||||||
|
c.JSON(200, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("entwechat robot", jsonData)
|
||||||
|
spoken := jsonData["spoken"].(string)
|
||||||
|
receivedName := jsonData["receivedName"].(string)
|
||||||
|
groupName := jsonData["groupName"].(string)
|
||||||
|
groupRemark := jsonData["groupRemark"].(string)
|
||||||
|
if groupRemark != "" {
|
||||||
|
groupName = groupRemark
|
||||||
|
}
|
||||||
|
roomType := jsonData["roomType"]
|
||||||
|
if spoken == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"type": 5000,
|
||||||
|
"info": gin.H{
|
||||||
|
"text": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuInfo := models.FindUserByUid(entId)
|
||||||
|
if kefuInfo.ID == 0 || kefuInfo.Name != kefuName {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": types.ApiCode.ACCOUNT_NO_EXIST,
|
||||||
|
"msg": types.ApiCode.GetMessage(types.ApiCode.ACCOUNT_NO_EXIST),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//查找访客插入访客表
|
||||||
|
visitorId := fmt.Sprintf("entwechat|%s|%s", entId, tools.Md5(fmt.Sprintf("%s|%s|%v", groupName, receivedName, roomType)))
|
||||||
|
vistorInfo := models.FindVisitorByVistorId(visitorId)
|
||||||
|
if vistorInfo.ID == 0 {
|
||||||
|
visitorName := receivedName
|
||||||
|
avator := "/static/images/we-chat-wx.png"
|
||||||
|
if visitorName == "" {
|
||||||
|
visitorName = "企业微信用户"
|
||||||
|
}
|
||||||
|
vistorInfo = models.Visitor{
|
||||||
|
Model: models.Model{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Name: visitorName,
|
||||||
|
Avator: avator,
|
||||||
|
ToId: kefuInfo.Name,
|
||||||
|
VisitorId: visitorId,
|
||||||
|
Status: 1,
|
||||||
|
Refer: "来自企业微信",
|
||||||
|
EntId: entId,
|
||||||
|
VisitNum: 0,
|
||||||
|
City: "企业微信",
|
||||||
|
ClientIp: c.ClientIP(),
|
||||||
|
}
|
||||||
|
vistorInfo.AddVisitor()
|
||||||
|
} else {
|
||||||
|
go models.UpdateVisitorStatus(visitorId, 3)
|
||||||
|
}
|
||||||
|
go ws.VisitorOnline(kefuInfo.Name, vistorInfo)
|
||||||
|
go ws.VisitorSendToKefu(kefuInfo.Name, vistorInfo, spoken)
|
||||||
|
|
||||||
|
//调用GPT3.5
|
||||||
|
result := ws.Gpt3Knowledge(entId, visitorId, kefuInfo, spoken)
|
||||||
|
models.CreateMessage(kefuInfo.Name, visitorId, result, "kefu", entId, "read")
|
||||||
|
go ws.KefuMessage(visitorId, result, kefuInfo)
|
||||||
|
payload := fmt.Sprintf(`{
|
||||||
|
"socketType":2,
|
||||||
|
"list":[
|
||||||
|
{
|
||||||
|
"type":203,
|
||||||
|
"titleList":[
|
||||||
|
"%s"
|
||||||
|
],
|
||||||
|
"atList":[
|
||||||
|
"%s"
|
||||||
|
],
|
||||||
|
"receivedContent":"%s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, groupName, receivedName, result)
|
||||||
|
url := "https://worktool.asrtts.cn/wework/sendRawMessage?robotId=" + robotCode
|
||||||
|
tools.PostJson(url, []byte(payload))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"type": 5000,
|
||||||
|
"info": gin.H{
|
||||||
|
"text": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
//发送订阅消息
|
||||||
|
func GetSendMiniSub(c *gin.Context) {
|
||||||
|
service.SendTestMiniSubscribe()
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,39 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"kefu/models"
|
||||||
|
"kefu/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
//导出访客聊天记录
|
||||||
|
func GetExportVisitorMessage(c *gin.Context) {
|
||||||
|
entId, _ := c.Get("ent_id")
|
||||||
|
visitorId := c.Query("visitor_id")
|
||||||
|
list := models.FindMessageByPage(1, 100000, "message.ent_id= ? and message.visitor_id=?", entId, visitorId)
|
||||||
|
if len(list) == 0 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "no data",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
titleList := []string{
|
||||||
|
"from_name", "to_name", "content", "time",
|
||||||
|
}
|
||||||
|
data := make([]map[string]interface{}, 0)
|
||||||
|
for _, item := range list {
|
||||||
|
row := make(map[string]interface{})
|
||||||
|
if item.MesType == "kefu" {
|
||||||
|
row["from_name"] = item.KefuName
|
||||||
|
row["to_name"] = item.VisitorName
|
||||||
|
} else {
|
||||||
|
row["from_name"] = item.VisitorName
|
||||||
|
row["to_name"] = item.KefuName
|
||||||
|
}
|
||||||
|
row["content"] = item.Content
|
||||||
|
row["time"] = item.CreateTime
|
||||||
|
data = append(data, row)
|
||||||
|
}
|
||||||
|
tools.ExportExcelByMap(c, titleList, data, list[0].VisitorName+"聊天记录", "visitor_message")
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
tasklist | findstr "kefu.exe"
|
||||||
|
if %ERRORLEVEL% EQU 1 (
|
||||||
|
D:\software\goworkspace\go-fly-pro\kefu.exe server -r D:\software\goworkspace\go-fly-pro
|
||||||
|
)
|
||||||
|
exit
|
|
@ -0,0 +1,52 @@
|
||||||
|
module kefu
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
baliance.com/gooxml v1.0.1
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi v0.1.18
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
|
||||||
|
github.com/alibabacloud-go/dingtalk v1.5.73
|
||||||
|
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9
|
||||||
|
github.com/alibabacloud-go/tea v1.1.19
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.4.4
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.1
|
||||||
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible
|
||||||
|
github.com/anaskhan96/soup v1.2.5
|
||||||
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||||
|
github.com/clbanning/mxj/v2 v2.5.6 // indirect
|
||||||
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||||
|
github.com/emersion/go-smtp v0.15.0
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
|
github.com/gin-contrib/pprof v1.3.0
|
||||||
|
github.com/gin-contrib/sessions v0.0.5
|
||||||
|
github.com/gin-gonic/gin v1.7.7
|
||||||
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1
|
||||||
|
github.com/gobuffalo/packr/v2 v2.5.1
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
|
github.com/google/go-tika v0.3.0
|
||||||
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
github.com/ipipdotnet/ipdb-go v1.3.0
|
||||||
|
github.com/jinzhu/gorm v1.9.14
|
||||||
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/sashabaranov/go-openai v1.11.2
|
||||||
|
github.com/satori/go.uuid v1.2.0
|
||||||
|
github.com/silenceper/wechat/v2 v2.1.6
|
||||||
|
github.com/sirupsen/logrus v1.9.0
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
|
github.com/speps/go-hashids v2.0.0+incompatible
|
||||||
|
github.com/spf13/cobra v0.0.5
|
||||||
|
github.com/tealeg/xlsx v1.0.5
|
||||||
|
github.com/tidwall/gjson v1.14.1
|
||||||
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.17
|
||||||
|
github.com/xuri/excelize/v2 v2.6.0
|
||||||
|
github.com/zh-five/xdaemon v0.1.1
|
||||||
|
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||||
|
)
|
|
@ -0,0 +1,587 @@
|
||||||
|
baliance.com/gooxml v1.0.1 h1:fG5lmxmjEVFfbKQ2NuyCuU3hMuuOb5avh5a38SZNO1o=
|
||||||
|
baliance.com/gooxml v1.0.1/go.mod h1:+gpUgmkAF4zCtwOFPNRLDAvpVRWoKs5EeQTSv/HYFnw=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
|
github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
|
||||||
|
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
|
||||||
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi v0.1.14/go.mod h1:w4CosR7O/kapCtEEMBm3JsQqWBU/CnZ2o0pHorsTWDI=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi v0.1.18 h1:3eUVmAr7WCJp7fgIvmCd9ZUyuwtJYbtUqJIed5eXCmk=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4 h1:7Q2FEyqxeZeIkwYMwRC3uphxV4i7O2eV4ETe21d6lS4=
|
||||||
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
|
||||||
|
github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA=
|
||||||
|
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50=
|
||||||
|
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
|
||||||
|
github.com/alibabacloud-go/dingtalk v1.5.73 h1:8zrEX/XqQr3wXaKCiZ+NRIXFxvcPI0kp3LxPZjanmfw=
|
||||||
|
github.com/alibabacloud-go/dingtalk v1.5.73/go.mod h1:jUWsJh279JT1UYfS21LJiE2CvIKMLLvJ84rHUPn/uL0=
|
||||||
|
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9 h1:z+OU7LbWtQitWJ8SAn55hEQkJPCsEPJc97TvGCZV+4s=
|
||||||
|
github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9/go.mod h1:AT91gCNJPsemf4lHLNgWTf/RsgmpdOprWvQ3FYvtwGk=
|
||||||
|
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||||
|
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||||
|
github.com/alibabacloud-go/gateway-dingtalk v1.0.2 h1:+etjmc64QTmYvHlc6eFkH9y2DOc3UPcyD2nF3IXsVqw=
|
||||||
|
github.com/alibabacloud-go/gateway-dingtalk v1.0.2/go.mod h1:JUvHpkJtlPFpgJcfXqc9Y4mk2JnoRn5XpKbRz38jJho=
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.0.10/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
||||||
|
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.19 h1:Xroq0M+pr0mC834Djj3Fl4ZA8+GGoA0i7aWse1vmgf4=
|
||||||
|
github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.4.4 h1:lxCDvNCdTo9FaXKKq45+4vGETQUKNOW/qKTcX9Sk53o=
|
||||||
|
github.com/alibabacloud-go/tea-utils v1.4.4/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw=
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.1 h1:K6kwgo+UiYx+/kr6CO0PN5ACZDzE3nnn9d77215AkTs=
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.1/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
||||||
|
github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M=
|
||||||
|
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||||
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
|
||||||
|
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
||||||
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible h1:uuJIwCFhbZy+zdvLy5zrcIToPEQP0s5CFOZ0Zj03O/w=
|
||||||
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.1+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
|
github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY=
|
||||||
|
github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
|
||||||
|
github.com/anaskhan96/soup v1.2.5 h1:V/FHiusdTrPrdF4iA1YkVxsOpdNcgvqT1hG+YtcZ5hM=
|
||||||
|
github.com/anaskhan96/soup v1.2.5/go.mod h1:6YnEp9A2yywlYdM4EgDz9NEHclocMepEtku7wg6Cq3s=
|
||||||
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||||
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
|
github.com/bos-hieu/mongostore v0.0.2/go.mod h1:8AbbVmDEb0yqJsBrWxZIAZOxIfv/tsP8CDtdHduZHGg=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
|
||||||
|
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
|
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||||
|
github.com/clbanning/mxj/v2 v2.5.6 h1:Jm4VaCI/+Ug5Q57IzEoZbwx4iQFA6wkXv72juUSeK+g=
|
||||||
|
github.com/clbanning/mxj/v2 v2.5.6/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||||
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
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/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||||
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
|
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8=
|
||||||
|
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
|
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/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
|
||||||
|
github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
|
||||||
|
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
|
||||||
|
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
|
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||||
|
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||||
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
|
||||||
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
|
||||||
|
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.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
|
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||||
|
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||||
|
github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
|
||||||
|
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||||
|
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||||
|
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||||
|
github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
|
||||||
|
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||||
|
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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
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.2/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/go-tika v0.3.0 h1:JncwikDcIJrSwwoTjWg6NE7g3IW7XTrI6ZeeOGp03Y8=
|
||||||
|
github.com/google/go-tika v0.3.0/go.mod h1:k/Afo/0kDgo9g/9gjIIUBoDXgyGgYO3JAISqowoJdVE=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||||
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||||
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/ipipdotnet/ipdb-go v1.3.0 h1:FfkSkAI1do3bZ7F35ueGuF7Phur64jmikQ1C4IPl/gc=
|
||||||
|
github.com/ipipdotnet/ipdb-go v1.3.0/go.mod h1:yZ+8puwe3R37a/3qRftXo40nZVQbxYDLqls9o5foexs=
|
||||||
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||||
|
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
|
||||||
|
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||||
|
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||||
|
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||||
|
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
|
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
|
||||||
|
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
|
||||||
|
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
|
||||||
|
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
|
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
|
||||||
|
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
|
||||||
|
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
|
||||||
|
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM=
|
||||||
|
github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
|
||||||
|
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||||
|
github.com/json-iterator/go v1.1.10/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/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
|
||||||
|
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||||
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
|
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.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
||||||
|
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
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/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
|
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
|
github.com/onsi/gomega v1.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.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
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/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
|
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||||
|
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||||
|
github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o=
|
||||||
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/sashabaranov/go-openai v1.11.2 h1:HuMf+18eldSKbqVblyeCQbtcqSpGVfqTshvi8Bn6zes=
|
||||||
|
github.com/sashabaranov/go-openai v1.11.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||||
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||||
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
|
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/silenceper/wechat/v2 v2.1.6 h1:2br2DxNzhksmvIBJ+PfMqjqsvoZmd/5BnMIfjKYUBgc=
|
||||||
|
github.com/silenceper/wechat/v2 v2.1.6/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
|
||||||
|
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
|
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/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
|
||||||
|
github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
|
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||||
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||||
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
|
||||||
|
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
|
||||||
|
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||||
|
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
|
||||||
|
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||||
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/wader/gormstore/v2 v2.0.0/go.mod h1:3BgNKFxRdVo2E4pq3e/eiim8qRDZzaveaIcIvu2T8r0=
|
||||||
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.17 h1:i4YJA/6BqAbi2YfyPZBjpeEeO/+oa4UbKP4gSTRhhQg=
|
||||||
|
github.com/wechatpay-apiv3/wechatpay-go v0.2.17/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||||
|
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||||
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
|
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8 h1:3X7aE0iLKJ5j+tz58BpvIZkXNV7Yq4jC93Z/rbN2Fxk=
|
||||||
|
github.com/xuri/efp v0.0.0-20220407160117-ad0f7a785be8/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
|
github.com/xuri/excelize/v2 v2.6.0 h1:m/aXAzSAqxgt74Nfd+sNzpzVKhTGl7+S9nbG4A57mF4=
|
||||||
|
github.com/xuri/excelize/v2 v2.6.0/go.mod h1:Q1YetlHesXEKwGFfeJn7PfEZz2IvHb6wdOeYjBxVcVs=
|
||||||
|
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
|
||||||
|
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
||||||
|
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||||
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
github.com/zh-five/xdaemon v0.1.1 h1:W5VyJ+5ROjjcb9vNcF/SgWPwTzIRYIsW2yZBAomqMW8=
|
||||||
|
github.com/zh-five/xdaemon v0.1.1/go.mod h1:i3cluMVOPp/UcX2KDU2qzRv25f8u4y14tHzBPQhD8lI=
|
||||||
|
go.mongodb.org/mongo-driver v1.9.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||||
|
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/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-20200226121028-0de0cce0169b/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-20200506145744-7e3656a0809f/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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c=
|
||||||
|
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
|
||||||
|
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
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/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
|
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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||||
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||||
|
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||||
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
|
||||||
|
gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
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.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-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
|
||||||
|
gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg=
|
||||||
|
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
||||||
|
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||||
|
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
@ -0,0 +1,2 @@
|
||||||
|
<!DOCTYPE html><html lang=zh-CN><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><title>客服助手</title><script>var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||||
|
document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')</script><link rel=stylesheet href=./static/index.63b34199.css></head><body><noscript><strong>Please enable JavaScript to continue.</strong></noscript><div id=app></div><script src=./static/js/chunk-vendors.ddd747cf.js></script><script src=./static/js/index.d39cf759.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue