🎨 添加图库和客服插件(有问题-待修改)
This commit is contained in:
parent
5faff4afa0
commit
a5ae680f94
2
go.mod
2
go.mod
@ -101,11 +101,13 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -233,6 +233,8 @@ github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
@ -301,6 +303,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -1,6 +1,8 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
@ -104,6 +106,9 @@ func Routers() *gin.Engine {
|
||||
// 注册业务路由
|
||||
initBizRouter(PrivateGroup, PublicGroup)
|
||||
|
||||
PluginInit(PublicGroup, customerservice.CreateCustomerServicePlug())
|
||||
PluginInit(PrivateGroup, picturelibrary.CreatePictureLibraryPlug())
|
||||
|
||||
global.GVA_ROUTERS = Router.Routes()
|
||||
|
||||
global.GVA_LOG.Info("router register success")
|
||||
|
@ -1,12 +0,0 @@
|
||||
package system
|
||||
|
||||
import "git.echol.cn/loser/lckt/global"
|
||||
|
||||
type Class struct {
|
||||
global.GVA_MODEL
|
||||
Name string `json:"name" gorm:"comment:分类名"`
|
||||
}
|
||||
|
||||
func (c *Class) TableName() string {
|
||||
return "class"
|
||||
}
|
70
plugin/customerservice/README.md
Normal file
70
plugin/customerservice/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
[## GVA 客服聊天功能[前端用户聊天基于gva-shop的uniapp端]
|
||||
|
||||
### 手动安装方法
|
||||
|
||||
1.解压zip获得customerservice文件夹
|
||||
2.将 customerservice/web/plugin/customerservice 放置在web/plugin下
|
||||
3.将 customerservice/server/plugin/customerservice 放置在server/plugin下
|
||||
4.将 customerservice/uni 下的文件放到gva-shop商城插件下的uni里
|
||||
5.在gva-shop的uni下的pages.json里新增:
|
||||
{
|
||||
"path" : "pages/service/index",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "客服"
|
||||
}
|
||||
}
|
||||
#### 1. 前往GVA主程序下的initialize/router.go 在Routers 方法最末尾按照你需要的及安全模式添加本插件
|
||||
PluginInit(PublicGroup, customerservice.CreateCustomerServicePlug())
|
||||
到gva系统,角色管理,分配角色的api权限即可,插件会自动注册api,需要手动分配。
|
||||
注:会自动生成如下表:sys_service、sys_service_msg、sys_service_record、sys_service_reply、sys_service_script
|
||||
### 2. 配置说明
|
||||
|
||||
#### 2-1 后台主要功能
|
||||
|
||||
客服管理、客服话术、客服自动回复配置等
|
||||
|
||||
#### 2-2 使用说明
|
||||
|
||||
1、在前端vue部分路由需要手动配置:
|
||||
web/src/router/index.js下新增如下配置:
|
||||
{
|
||||
path: '/kefu/login',
|
||||
name: 'ServiceLogin',
|
||||
component: () => import('@/plugin/customerservice/view/login/index.vue'),
|
||||
meta:{
|
||||
client:true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/kefu/main',
|
||||
name: 'ServiceMain',
|
||||
component: () => import('@/plugin/customerservice/view/chat/index.vue'),
|
||||
meta:{
|
||||
client:true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/kefu/test',
|
||||
name: 'ServiceUserTest',
|
||||
component: () => import('@/plugin/customerservice/view/chat/test.vue'),
|
||||
meta:{
|
||||
client:true
|
||||
}
|
||||
},
|
||||
|
||||
2、后台使用方法:
|
||||
启动gva项目,安装后在客服列表添加客服,然后可以从客服列表的进入工作台进入客服聊天页,或者打开客服登录页
|
||||
http://localhost:8080/#/kefu/login进行登录
|
||||
3、此插件涉及的图片上传使用了插件管理中《图库》插件,可根据自己喜好进行替换
|
||||
4、后台客服websocket连接的地方在插件view/chat/index.vue,连接地址改成自己项目地址,
|
||||
客服:websocket.value = new WebSocket(`ws://localhost:8888/service/serve_ws?token=${token.value}`)
|
||||
|
||||
5、用户websocket连接的地方在uni/pages/service/index.vue下,连接地址改成自己项目地址,websocket.value = new WebSocket(`ws://localhost:8888/service/ws?token=${token.value}`)
|
||||
|
||||
6、项目没进行过啥大的测试,仅供参考学习
|
||||
|
||||
#### 2-3 参数说明
|
||||
|
||||
### 3. 方法API
|
||||
无
|
407
plugin/customerservice/api/api.go
Normal file
407
plugin/customerservice/api/api.go
Normal file
@ -0,0 +1,407 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
sysModel "git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service/ws"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CustomerServiceApi struct{}
|
||||
|
||||
func (cus *CustomerServiceApi) ServeWs(ctx *gin.Context) {
|
||||
ws.WsServe(ctx)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) ServeWsForKefu(ctx *gin.Context) {
|
||||
ws.ServeWsForKefu(ctx)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) HandleTransfer(c *gin.Context) {
|
||||
var transferReq struct {
|
||||
FromAgent string `json:"from_agent"`
|
||||
ToAgent string `json:"to_agent"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&transferReq); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新用户与客服的映射关系
|
||||
// 例如:userAgentMap[transferReq.UserID] = transferReq.ToAgent
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "success"})
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetKefuInfo(c *gin.Context) {
|
||||
userID, _ := c.Get("jwt_user_id")
|
||||
//uidStr := strconv.Itoa(int(userID))
|
||||
var serviceId int64
|
||||
var recordData sysModel.SysServiceRecord
|
||||
result := global.GVA_DB.Where("uid = ?", userID).Order("update_time DESC").Limit(1).Find(&recordData)
|
||||
if result.RowsAffected == 0 || result.Error != nil {
|
||||
//直接查询service表
|
||||
result2 := global.GVA_DB.Model(&sysModel.SysService{}).
|
||||
Select("id").
|
||||
Where("status = ?", 1).
|
||||
Order("add_time DESC").
|
||||
Limit(1).Scan(&serviceId)
|
||||
fmt.Println("sssssssssssss--->>>")
|
||||
fmt.Println(serviceId)
|
||||
if result2.Error != nil {
|
||||
response.FailWithMessage("获取客服信息失败-1", c)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
serviceId = recordData.ServiceId
|
||||
}
|
||||
var serviceData sysModel.SysService
|
||||
result3 := global.GVA_DB.Select("id,uid,online,avatar,nickname,add_time,status").
|
||||
Where("id = ?", serviceId).
|
||||
Where("status = ?", 1).
|
||||
Order("add_time DESC").
|
||||
First(&serviceData)
|
||||
if result3.Error != nil {
|
||||
response.FailWithMessage("获取客服信息失败-2", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(serviceData, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) SendMsg(c *gin.Context) {
|
||||
var msgJson ws.Message
|
||||
if jsErr := c.ShouldBindJSON(&msgJson); jsErr != nil {
|
||||
fmt.Println(jsErr)
|
||||
response.FailWithMessage("参数有误-1", c)
|
||||
return
|
||||
}
|
||||
fromIdStr := msgJson.Sender
|
||||
toIdStr := msgJson.Receiver
|
||||
content := msgJson.Content
|
||||
isKf := msgJson.IsKf
|
||||
msgTypeStr := msgJson.MsgType
|
||||
if content == "" || fromIdStr == "" || toIdStr == "" || msgTypeStr == "" {
|
||||
response.FailWithMessage("参数有误-2", c)
|
||||
return
|
||||
}
|
||||
toId, err_1 := strconv.ParseInt(toIdStr, 10, 64)
|
||||
fromId, err_2 := strconv.ParseInt(fromIdStr, 10, 64)
|
||||
msgType, err_3 := strconv.ParseInt(msgTypeStr, 10, 64)
|
||||
if err_1 != nil || err_2 != nil || err_3 != nil {
|
||||
response.FailWithMessage("参数有误", c)
|
||||
return
|
||||
}
|
||||
//限流
|
||||
if !tools.LimitFreqSingle("send_message:"+c.ClientIP(), 1, 2) {
|
||||
response.FailWithMessage("发送频率过快", c)
|
||||
return
|
||||
}
|
||||
var kfInfo sysModel.SysService
|
||||
//var userInfo sysModel.SysTestUser
|
||||
var userInfo user.User
|
||||
var err, err2 error
|
||||
if isKf == 1 {
|
||||
err = global.GVA_DB.Where("id = ?", fromId).First(&kfInfo).Error
|
||||
err2 = global.GVA_DB.Where("id = ?", toId).First(&userInfo).Error
|
||||
|
||||
} else if isKf == 0 {
|
||||
err = global.GVA_DB.Where("id = ?", toId).First(&kfInfo).Error
|
||||
err2 = global.GVA_DB.Where("id = ?", fromId).First(&userInfo).Error
|
||||
}
|
||||
if err != nil || err2 != nil {
|
||||
response.FailWithMessage("获取失败-1", c)
|
||||
return
|
||||
}
|
||||
|
||||
ser := service.ServiceGroupApp
|
||||
cErr := ser.CreateMsg(kfInfo, userInfo, msgType, content, strconv.FormatInt(isKf, 10))
|
||||
if cErr != nil {
|
||||
response.FailWithMessage("发送失败", c)
|
||||
return
|
||||
}
|
||||
message := ws.Message{
|
||||
Sender: fromIdStr,
|
||||
Receiver: toIdStr,
|
||||
Content: content,
|
||||
MsgType: msgTypeStr,
|
||||
Role: "kf",
|
||||
Timestamp: time.Now().Unix(),
|
||||
}
|
||||
var key string
|
||||
if isKf == 1 {
|
||||
//查找指定用户广播消息
|
||||
key = "user" + toIdStr
|
||||
message.AvatarUrl = kfInfo.Avatar
|
||||
message.Nickname = kfInfo.Nickname
|
||||
} else if isKf == 0 {
|
||||
//查找指定客服广播消息
|
||||
key = "kf" + toIdStr
|
||||
message.Role = "user"
|
||||
message.AvatarUrl = userInfo.Avatar
|
||||
message.Nickname = userInfo.NickName
|
||||
}
|
||||
conn, ok := ws.Manager.Clients[key]
|
||||
if conn != nil && ok {
|
||||
sendMsg := ws.TypeMsg{
|
||||
Type: "message",
|
||||
Data: message,
|
||||
}
|
||||
str, _ := json.Marshal(sendMsg)
|
||||
conn.Send <- str
|
||||
|
||||
if isKf == 0 {
|
||||
//客服给用户发送自动回复消息
|
||||
var autoReply sysModel.SysServiceReply
|
||||
autoContent := ""
|
||||
var autoMsgType int64
|
||||
aErr := global.GVA_DB.Where("is_complete = ? AND `status` = ? AND keyword = ?", 1, 1, content).First(&autoReply).Error
|
||||
fmt.Println(aErr)
|
||||
if aErr == nil {
|
||||
fmt.Println(autoReply)
|
||||
autoContent = autoReply.Content
|
||||
autoMsgType = autoReply.ReplyType
|
||||
} else {
|
||||
aErr = global.GVA_DB.Where("is_complete = ? AND `status` = ? AND keyword LIKE ?", 0, 1, "%"+content+"%").First(&autoReply).Error
|
||||
if aErr == nil {
|
||||
autoContent = autoReply.Content
|
||||
autoMsgType = autoReply.ReplyType
|
||||
}
|
||||
}
|
||||
if autoContent != "" {
|
||||
if autoMsgType == 2 {
|
||||
autoMsgType = 3 //图片
|
||||
}
|
||||
aErr = ser.CreateMsg(kfInfo, userInfo, autoMsgType, autoContent, "1")
|
||||
if aErr == nil {
|
||||
autoUidStr := strconv.FormatUint(uint64(userInfo.ID), 10)
|
||||
message.Sender = strconv.FormatInt(kfInfo.Id, 10)
|
||||
message.Receiver = autoUidStr
|
||||
message.MsgType = strconv.FormatInt(autoMsgType, 10)
|
||||
message.Content = autoContent
|
||||
message.IsKf = 1
|
||||
message.Role = "kf"
|
||||
message.AvatarUrl = kfInfo.Avatar
|
||||
message.Nickname = kfInfo.Nickname
|
||||
sendMsg.Data = message
|
||||
autoStr, _ := json.Marshal(sendMsg)
|
||||
kfConn, isOk := ws.Manager.Clients["user"+autoUidStr]
|
||||
if kfConn != nil && isOk {
|
||||
kfConn.Send <- autoStr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.OkWithDetailed(nil, "发送成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetMsgList(c *gin.Context) {
|
||||
|
||||
uid, ok := c.Get("jwt_user_id") //jwt里解出的
|
||||
jwtServiceId, ok2 := c.Get("service_id") //jwt里解出的
|
||||
if !ok2 {
|
||||
//gva-shop前端用户连接请求消息列表
|
||||
jwtServiceId = c.Query("kf_id")
|
||||
}
|
||||
if !ok {
|
||||
//后台客服连接请求消息列表
|
||||
uid = c.Query("uid")
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if pageSize > 20 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := pageSize * (page - 1)
|
||||
var total int64
|
||||
var list []sysModel.SysServiceMsg
|
||||
db := global.GVA_DB.Model(&sysModel.SysServiceMsg{}).Where("uid = ?", uid).Where("service_id = ?", jwtServiceId)
|
||||
db.Count(&total)
|
||||
err := db.Limit(pageSize).Offset(offset).Order("add_time desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
if len(list) > 0 {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].AddTime < list[j].AddTime
|
||||
})
|
||||
for k, v := range list {
|
||||
decoded, _ := base64.StdEncoding.DecodeString(v.Content)
|
||||
v.Content = string(decoded)
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetMsgUser(c *gin.Context) {
|
||||
kfId, _ := c.Get("service_id")
|
||||
var list []sysModel.SysServiceRecord
|
||||
err := global.GVA_DB.Where("service_id=?", kfId).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
if len(list) > 0 {
|
||||
//判断用户在线状况
|
||||
for k, v := range list {
|
||||
userKey := "user" + strconv.FormatInt(v.Uid, 10)
|
||||
isClent, ok := ws.Manager.Clients[userKey]
|
||||
if ok && isClent != nil {
|
||||
v.Online = 1
|
||||
} else {
|
||||
v.Online = 0
|
||||
}
|
||||
decoded, _ := base64.StdEncoding.DecodeString(v.Message)
|
||||
v.Message = string(decoded)
|
||||
//查找未读消息数
|
||||
var noCount int64
|
||||
global.GVA_DB.Model(&sysModel.SysServiceMsg{}).
|
||||
Where("is_view=?", 0).
|
||||
Where("is_kf=?", 0).
|
||||
Where("service_id=?", kfId).
|
||||
Where("uid=?", v.Uid).Count(&noCount)
|
||||
v.NoRead = noCount
|
||||
v.AddTimeStr = tools.FormatTimestamp(v.UpdateTime)
|
||||
if v.MessageType == 3 {
|
||||
v.Message = "[图片]"
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if list[i].Online != list[j].Online {
|
||||
return list[i].Online > list[j].Online
|
||||
}
|
||||
return list[i].AddTime > list[j].AddTime
|
||||
})
|
||||
}
|
||||
response.OkWithDetailed(list, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) SetMsgView(c *gin.Context) {
|
||||
kfId, _ := c.Get("service_id")
|
||||
uid := c.Query("uid")
|
||||
global.GVA_DB.Model(&sysModel.SysServiceMsg{}).
|
||||
Where(map[string]interface{}{"is_kf": 0, "service_id": kfId, "is_view": 0, "uid": uid}).
|
||||
Update("is_view", 1)
|
||||
response.Ok(c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) UploadFile(c *gin.Context) {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
extension := filepath.Ext(file.Filename)
|
||||
newUUID := uuid.New().String()
|
||||
hash := md5.Sum([]byte("gva-service" + newUUID))
|
||||
md5Pwd := hex.EncodeToString(hash[:])
|
||||
filename := md5Pwd + extension
|
||||
if err := c.SaveUploadedFile(file, "./uploads/file/"+filename); err != nil {
|
||||
response.FailWithMessage("上传失败-2", c)
|
||||
return
|
||||
|
||||
}
|
||||
//ser := service.ServiceGroupApp
|
||||
//url := ser.GetUrlHost(c)
|
||||
response.OkWithDetailed("uploads/file/"+filename, "获取成功", c)
|
||||
return
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetTestMsgList(c *gin.Context) {
|
||||
uid := c.Query("uid")
|
||||
serviceId := c.Query("service_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
if pageSize > 20 {
|
||||
pageSize = 20
|
||||
}
|
||||
offset := pageSize * (page - 1)
|
||||
var total int64
|
||||
var list []sysModel.SysServiceMsg
|
||||
global.GVA_DB.Model(&sysModel.SysServiceMsg{}).Where("uid=?", uid).Where("service_id=?", serviceId).Count(&total)
|
||||
err := global.GVA_DB.Where("uid=?", uid).Where("service_id=?", serviceId).Limit(pageSize).Offset(offset).Order("add_time desc").Find(&list).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
if len(list) > 0 {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].AddTime < list[j].AddTime
|
||||
})
|
||||
for k, v := range list {
|
||||
decoded, _ := base64.StdEncoding.DecodeString(v.Content)
|
||||
v.Content = string(decoded)
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetUserInfo(c *gin.Context) {
|
||||
//userID := utils.GetUserID(c)
|
||||
userID, ok := c.Get("jwt_user_id")
|
||||
if !ok {
|
||||
//后台客服连接请求
|
||||
userID = c.Query("uid")
|
||||
}
|
||||
var clientUser user.User
|
||||
result := global.GVA_DB.Omit("password").Where("id = ?", userID).Where("deleted_at IS NULL").First(&clientUser)
|
||||
if result.Error != nil {
|
||||
response.FailWithMessage("获取用户信息失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(clientUser, "获取成功", c)
|
||||
}
|
||||
|
||||
func (cus *CustomerServiceApi) GetServiceScript(c *gin.Context) {
|
||||
rType := c.Query("type")
|
||||
db := global.GVA_DB.Model(&sysModel.SysServiceScript{})
|
||||
if rType == "1" {
|
||||
serviceId, ok := c.Get("service_id")
|
||||
if serviceId != "" && ok {
|
||||
db = db.Where("service_id=?", serviceId)
|
||||
}
|
||||
} else {
|
||||
db = db.Where("service_id=?", 0)
|
||||
}
|
||||
var list []sysModel.SysServiceScript
|
||||
err := db.Order("add_time desc").Limit(20).Offset(0).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(list, "获取成功", c)
|
||||
}
|
8
plugin/customerservice/api/enter.go
Normal file
8
plugin/customerservice/api/enter.go
Normal file
@ -0,0 +1,8 @@
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
CustomerServiceApi
|
||||
AdminServiceApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
484
plugin/customerservice/api/service.go
Normal file
484
plugin/customerservice/api/service.go
Normal file
@ -0,0 +1,484 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AdminServiceApi struct{}
|
||||
|
||||
// GetServiceList
|
||||
// @Tags sysService
|
||||
// @Summary 客服列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页客服列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /service/get_service_list [post]
|
||||
func (ad *AdminServiceApi) GetServiceList(c *gin.Context) {
|
||||
var pageInfo model.PageInfo
|
||||
if err := c.ShouldBindQuery(&pageInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
limit := pageInfo.Limit
|
||||
offset := pageInfo.Limit * (pageInfo.Page - 1)
|
||||
db := global.GVA_DB.Model(&model.SysService{})
|
||||
var list []model.SysService
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
err := db.Omit("password").Order("add_time desc").Limit(limit).Offset(offset).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SaveService
|
||||
// @Tags sysService
|
||||
// @Summary 添加/更新客服
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request true ""
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} ""
|
||||
// @Router /service/save_service [post]
|
||||
func (ad *AdminServiceApi) SaveService(c *gin.Context) {
|
||||
var serviceData model.SysService
|
||||
if err := c.ShouldBindJSON(&serviceData); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
//校验数据
|
||||
ser := service.ServiceGroupApp
|
||||
if err := ser.ValidateServiceData(&serviceData); err != nil {
|
||||
response.FailWithMessage("操作失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
if serviceData.Password != "" {
|
||||
hash := md5.Sum([]byte("gva-service" + serviceData.Password))
|
||||
serviceData.Password = hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
if serviceData.Id == 0 {
|
||||
serviceData.AddTime = time.Now().Unix()
|
||||
if err := global.GVA_DB.Create(&serviceData).Error; err != nil {
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "添加成功"
|
||||
} else {
|
||||
if err := global.GVA_DB.Model(&model.SysService{}).Where("id = ?", serviceData.Id).Updates(serviceData).Error; err != nil {
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "更新成功"
|
||||
}
|
||||
response.OkWithMessage(msg, c)
|
||||
}
|
||||
|
||||
// DeleteService
|
||||
// @Tags sysService
|
||||
// @Summary 删除客服
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_service?id=xx [delete]
|
||||
func (ad *AdminServiceApi) DeleteService(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysService
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
//if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
//
|
||||
//}
|
||||
response.FailWithMessage("用户不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 删除用户
|
||||
if err := global.GVA_DB.Delete(&model.SysService{}, id).Error; err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FindService
|
||||
// @Tags sysService
|
||||
// @Summary 查找客服
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/find_service?id=xx [get]
|
||||
func (ad *AdminServiceApi) FindService(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysService
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("客服不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
ser.Password = ""
|
||||
response.OkWithDetailed(ser, "success", c)
|
||||
}
|
||||
|
||||
// AdminServiceLogin
|
||||
// @Tags sysService
|
||||
// @Summary 进入工作台
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_reply/:id [delete]
|
||||
func (ad *AdminServiceApi) AdminServiceLogin(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
var ser model.SysService
|
||||
if err := global.GVA_DB.First(&ser, idParam).Error; err != nil {
|
||||
response.FailWithMessage("客服不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
data := map[string]interface{}{}
|
||||
|
||||
expTime, token, err := tools.GenerateToken(ser.Id)
|
||||
if err != nil {
|
||||
response.FailWithMessage("登录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
data["token"] = token
|
||||
data["exp_time"] = expTime
|
||||
|
||||
response.OkWithDetailed(data, "success", c)
|
||||
}
|
||||
|
||||
// AccountServiceLogin
|
||||
// @Tags sysService
|
||||
// @Summary 账户密码登录
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/admin_login?id=xx [get]
|
||||
func (ad *AdminServiceApi) AccountServiceLogin(c *gin.Context) {
|
||||
var loginInfo model.LoginInfo
|
||||
if err := c.ShouldBindJSON(&loginInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if loginInfo.Account == "" || loginInfo.Password == "" {
|
||||
response.FailWithMessage("账户或密码为空", c)
|
||||
return
|
||||
}
|
||||
var serviceInfo model.SysService
|
||||
if err := global.GVA_DB.Limit(1).Where("account=?", loginInfo.Account).Find(&serviceInfo).Error; err != nil {
|
||||
response.FailWithMessage("客服不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
hash := md5.Sum([]byte("gva-service" + loginInfo.Password))
|
||||
md5Pwd := hex.EncodeToString(hash[:])
|
||||
if md5Pwd != serviceInfo.Password {
|
||||
response.FailWithMessage("密码不正确", c)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{}
|
||||
expTime, token, err := tools.GenerateToken(serviceInfo.Id)
|
||||
if err != nil {
|
||||
response.FailWithMessage("登录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
data["token"] = token
|
||||
data["exp_time"] = expTime
|
||||
response.OkWithDetailed(data, "success", c)
|
||||
}
|
||||
|
||||
// GetScriptList
|
||||
// @Tags sysService
|
||||
// @Summary 客服话术列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页客服列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /service/get_script_list [get]
|
||||
func (ad *AdminServiceApi) GetScriptList(c *gin.Context) {
|
||||
var pageInfo model.PageInfo
|
||||
if err := c.ShouldBindQuery(&pageInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
limit := pageInfo.Limit
|
||||
offset := pageInfo.Limit * (pageInfo.Page - 1)
|
||||
db := global.GVA_DB.Model(&model.SysServiceScript{})
|
||||
var list []model.SysServiceScript
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
err := db.Order("sort desc,add_time desc").Limit(limit).Offset(offset).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
for k, v := range list {
|
||||
t := time.Unix(v.AddTime, 0)
|
||||
v.AddTimeStr = t.Format("2006-01-02 15:04:05")
|
||||
list[k] = v
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SaveScript
|
||||
// @Tags sysService
|
||||
// @Summary 添加/更新客服话术
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request true ""
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} ""
|
||||
// @Router /service/save_script [post]
|
||||
func (ad *AdminServiceApi) SaveScript(c *gin.Context) {
|
||||
var scriptData model.SysServiceScript
|
||||
if err := c.ShouldBindJSON(&scriptData); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
//校验数据
|
||||
ser := service.ServiceGroupApp
|
||||
if err := ser.ValidateScriptData(&scriptData); err != nil {
|
||||
response.FailWithMessage("操作失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
if scriptData.Id == 0 {
|
||||
scriptData.AddTime = time.Now().Unix()
|
||||
if err := global.GVA_DB.Create(&scriptData).Error; err != nil {
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "添加成功"
|
||||
} else {
|
||||
if err := global.GVA_DB.Model(&model.SysServiceScript{}).Where("id = ?", scriptData.Id).Updates(scriptData).Error; err != nil {
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "更新成功"
|
||||
}
|
||||
response.OkWithMessage(msg, c)
|
||||
}
|
||||
|
||||
// DeleteScript
|
||||
// @Tags sysService
|
||||
// @Summary 删除客服话术
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_script?id=xxx [delete]
|
||||
func (ad *AdminServiceApi) DeleteScript(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceScript
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("话术不存在或已删除:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 删除
|
||||
if err := global.GVA_DB.Delete(&model.SysServiceScript{}, id).Error; err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FindScript
|
||||
// @Tags sysService
|
||||
// @Summary 查找话术
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/find_script?id=xx [get]
|
||||
func (ad *AdminServiceApi) FindScript(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceScript
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("话术不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(ser, "success", c)
|
||||
}
|
||||
|
||||
// AutoReplyList
|
||||
// @Tags sysService
|
||||
// @Summary 自动回复列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页客服列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /service/auto_reply_list [get]
|
||||
func (ad *AdminServiceApi) AutoReplyList(c *gin.Context) {
|
||||
var pageInfo model.AutoPageInfo
|
||||
if err := c.ShouldBindQuery(&pageInfo); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
limit := pageInfo.Limit
|
||||
offset := pageInfo.Limit * (pageInfo.Page - 1)
|
||||
db := global.GVA_DB.Model(&model.SysServiceReply{})
|
||||
var list []model.SysServiceReply
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
err := db.Order("add_time desc").Limit(limit).Offset(offset).Find(&list).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
for k, v := range list {
|
||||
t := time.Unix(v.AddTime, 0)
|
||||
v.AddTimeStr = t.Format("2006-01-02 15:04:05")
|
||||
list[k] = v
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SaveReply
|
||||
// @Tags sysService
|
||||
// @Summary 添加/更新自动回复
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request true ""
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} ""
|
||||
// @Router /service/save_reply [post]
|
||||
func (ad *AdminServiceApi) SaveReply(c *gin.Context) {
|
||||
var replyData model.SysServiceReply
|
||||
if err := c.ShouldBindJSON(&replyData); err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
//校验数据
|
||||
ser := service.ServiceGroupApp
|
||||
if err := ser.ValidateReplyData(&replyData); err != nil {
|
||||
response.FailWithMessage("操作失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var msg string
|
||||
if replyData.Id == 0 {
|
||||
replyData.AddTime = time.Now().Unix()
|
||||
if err := global.GVA_DB.Create(&replyData).Error; err != nil {
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "添加成功"
|
||||
} else {
|
||||
if err := global.GVA_DB.Model(&model.SysServiceReply{}).Where("id = ?", replyData.Id).Updates(replyData).Error; err != nil {
|
||||
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
msg = "更新成功"
|
||||
}
|
||||
response.OkWithMessage(msg, c)
|
||||
}
|
||||
|
||||
// DeleteReply
|
||||
// @Tags sysService
|
||||
// @Summary 删除自动回复
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/delete_reply?id=xx [delete]
|
||||
func (ad *AdminServiceApi) DeleteReply(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceReply
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("内容不存在或已删除:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
// 删除数据
|
||||
if err := global.GVA_DB.Delete(&model.SysServiceReply{}, id).Error; err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FindReply
|
||||
// @Tags sysService
|
||||
// @Summary 查找自动回复详情
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.id true "id"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "成功消息"
|
||||
// @Router /service/find_reply?id=xxx [get]
|
||||
func (ad *AdminServiceApi) FindReply(c *gin.Context) {
|
||||
idParam := c.Query("id")
|
||||
id, err := strconv.Atoi(idParam)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
var ser model.SysServiceReply
|
||||
if err := global.GVA_DB.First(&ser, id).Error; err != nil {
|
||||
response.FailWithMessage("自动回复内容不存在:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(ser, "success", c)
|
||||
}
|
1
plugin/customerservice/config/config.go
Normal file
1
plugin/customerservice/config/config.go
Normal file
@ -0,0 +1 @@
|
||||
package config
|
1
plugin/customerservice/global/global.go
Normal file
1
plugin/customerservice/global/global.go
Normal file
@ -0,0 +1 @@
|
||||
package global
|
214
plugin/customerservice/main.go
Normal file
214
plugin/customerservice/main.go
Normal file
@ -0,0 +1,214 @@
|
||||
package customerservice
|
||||
|
||||
import (
|
||||
gvaGlobal "git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/system"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/router"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/service/ws"
|
||||
"git.echol.cn/loser/lckt/plugin/plugin-tool/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type CustomerServicePlugin struct {
|
||||
}
|
||||
|
||||
func CreateCustomerServicePlug() *CustomerServicePlugin {
|
||||
go func() {
|
||||
err := gvaGlobal.GVA_DB.AutoMigrate(model.SysService{},
|
||||
model.SysServiceMsg{},
|
||||
model.SysServiceRecord{},
|
||||
model.SysServiceReply{},
|
||||
model.SysServiceScript{},
|
||||
model.SysTestUser{})
|
||||
if err != nil {
|
||||
gvaGlobal.GVA_LOG.Error("自动创建表失败", zap.Error(err))
|
||||
} else {
|
||||
gvaGlobal.GVA_LOG.Info("自动创建表成功")
|
||||
}
|
||||
}() // 此处可以把插件依赖的数据库结构体自动创建表 需要填写对应的结构体
|
||||
// 下方会自动注册菜单 第一个参数为菜单一级路由信息一般为定义好的组名 第二个参数为真实使用的web页面路由信息
|
||||
// 具体值请根据实际情况修改
|
||||
utils.RegisterMenus(
|
||||
system.SysBaseMenu{
|
||||
Path: "service",
|
||||
Name: "客服管理",
|
||||
Hidden: false,
|
||||
Component: "view/routerHolder.vue",
|
||||
Sort: 4,
|
||||
Meta: system.Meta{
|
||||
Title: "客服管理",
|
||||
Icon: "service",
|
||||
},
|
||||
},
|
||||
system.SysBaseMenu{
|
||||
Path: "index",
|
||||
Name: "客服列表",
|
||||
Hidden: false,
|
||||
Component: "plugin/customerservice/view/service/index.vue",
|
||||
Sort: 1,
|
||||
Meta: system.Meta{
|
||||
Title: "客服列表",
|
||||
Icon: "service",
|
||||
},
|
||||
},
|
||||
system.SysBaseMenu{
|
||||
Path: "script/list",
|
||||
Name: "客服话术",
|
||||
Hidden: false,
|
||||
Component: "plugin/customerservice/view/script/index.vue",
|
||||
Sort: 2,
|
||||
Meta: system.Meta{
|
||||
Title: "客服话术",
|
||||
Icon: "document",
|
||||
},
|
||||
},
|
||||
system.SysBaseMenu{
|
||||
Path: "reply/list",
|
||||
Name: "自动回复",
|
||||
Hidden: false,
|
||||
Component: "plugin/customerservice/view/reply/index.vue",
|
||||
Sort: 3,
|
||||
Meta: system.Meta{
|
||||
Title: "自动回复",
|
||||
Icon: "bell-filled",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// 下方会自动注册api 以下格式为示例格式,请按照实际情况修改
|
||||
utils.RegisterApis(
|
||||
//system.SysApi{
|
||||
// Path: "/service/ws",
|
||||
// Description: "用户连接接口",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/serve_ws",
|
||||
// Description: "客服连接接口",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/send_msg",
|
||||
// Description: "发送消息接口",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "POST",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/get_msg_list",
|
||||
// Description: "消息列表",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/get_msg_user",
|
||||
// Description: "客服聊天用户列表",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/get_kf_info",
|
||||
// Description: "当前客服详情",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
//system.SysApi{
|
||||
// Path: "/service/set_msg_view",
|
||||
// Description: "设置已读",
|
||||
// ApiGroup: "客服管理",
|
||||
// Method: "GET",
|
||||
//},
|
||||
system.SysApi{
|
||||
Path: "/service/get_service_list",
|
||||
Description: "后台客服列表",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/save_service",
|
||||
Description: "后台客服新增/更新",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/delete_service",
|
||||
Description: "删除客服",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/find_service",
|
||||
Description: "客服详情",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/get_script_list",
|
||||
Description: "客服话术列表",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/save_script",
|
||||
Description: "客服话术新增/更新",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/delete_script",
|
||||
Description: "删除客服话术",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/find_script",
|
||||
Description: "客服话术详情",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/auto_reply_list",
|
||||
Description: "自动回复列表",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/save_reply",
|
||||
Description: "自动回复新增/更新",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/delete_reply",
|
||||
Description: "删除自动回复",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/find_reply",
|
||||
Description: "自动回复详情",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/service/admin_login",
|
||||
Description: "进入客服工作台",
|
||||
ApiGroup: "客服管理",
|
||||
Method: "GET",
|
||||
},
|
||||
)
|
||||
go ws.Manager.Start()
|
||||
go ws.Manager.CheckClientActivity()
|
||||
return &CustomerServicePlugin{}
|
||||
}
|
||||
|
||||
func (*CustomerServicePlugin) Register(group *gin.RouterGroup) {
|
||||
router.RouterGroupApp.InitCustomerServiceRouter(group)
|
||||
}
|
||||
|
||||
func (*CustomerServicePlugin) RouterPath() string {
|
||||
return ""
|
||||
}
|
57
plugin/customerservice/middleware/jwt.go
Normal file
57
plugin/customerservice/middleware/jwt.go
Normal file
@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func JWTAuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 从请求头获取 token
|
||||
authHeader := c.GetHeader("chat-token")
|
||||
userHeader := c.GetHeader("x-token")
|
||||
if userHeader == "" && authHeader == "" {
|
||||
response.FailWithMessage("参数错误:"+"Authorization header is missing", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if authHeader != "" {
|
||||
// 按照格式 "Bearer <token>" 提取 token
|
||||
tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
|
||||
if tokenString == "" {
|
||||
response.FailWithMessage("参数错误:"+"Token is missing", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 验证 token
|
||||
claims, err := tools.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
response.FailWithMessage("Invalid token:"+err.Error(), c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息存储在上下文中,便于后续处理
|
||||
c.Set("service_id", claims.ServiceId)
|
||||
//c.Request.URL.Query().Add("service_id", strconv.FormatInt(claims.ServiceId, 10))
|
||||
c.Next() // 继续处理请求
|
||||
|
||||
} else {
|
||||
//为了方便客服后台和前端客服聊天界面公用方法,共用同一套jwt,前端的jwt的值取的还是gva-shop的x-token
|
||||
j := utils.NewJWT()
|
||||
claims, err := j.ParseToken(userHeader)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误:"+"Token is error", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set("jwt_user_id", claims.BaseClaims.ID)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
25
plugin/customerservice/model/model.go
Normal file
25
plugin/customerservice/model/model.go
Normal file
@ -0,0 +1,25 @@
|
||||
package model
|
||||
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Keyword string `json:"keyword" form:"keyword"`
|
||||
}
|
||||
|
||||
type MsgPageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
FromId int `json:"from_id" form:"from_id"`
|
||||
}
|
||||
|
||||
type AutoPageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Keyword string `json:"keyword" form:"keyword"`
|
||||
ReplyType int `json:"reply_type" form:"reply_type"`
|
||||
}
|
||||
|
||||
type LoginInfo struct {
|
||||
Account string `json:"account" form:"account"`
|
||||
Password string `json:"password" form:"password"`
|
||||
}
|
18
plugin/customerservice/model/sysService.go
Normal file
18
plugin/customerservice/model/sysService.go
Normal file
@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type SysService struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
MerchantId uint `json:"merchant_id" form:"merchant_id" gorm:"default:0;type:int;column:merchant_id;comment:商户id;"`
|
||||
Uid uint `json:"uid" form:"uid" gorm:"default:0;type:int;column:uid;comment:用户id;"`
|
||||
Online uint `json:"online" form:"online" gorm:"default:0;type:tinyint;column:online;comment:客服是否在线;"`
|
||||
Account string `json:"account" form:"account" gorm:"default:'';type:varchar(255);column:account;comment:账户;"`
|
||||
Password string `json:"password" form:"password" gorm:"default:'';type:varchar(255);column:password;comment:密码;"`
|
||||
Avatar string `json:"avatar" form:"avatar" gorm:"default:'';type:varchar(255);column:avatar;comment:头像;"`
|
||||
Nickname string `json:"nickname" form:"nickname" gorm:"default:'';type:varchar(255);column:nickname;comment:客服名称;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
Status *uint `json:"status" form:"status" gorm:"default:0;type:tinyint(1);column:status;comment:是否显示;"`
|
||||
}
|
||||
|
||||
func (SysService) TableName() string {
|
||||
return "sys_service"
|
||||
}
|
18
plugin/customerservice/model/sysServiceMsg.go
Normal file
18
plugin/customerservice/model/sysServiceMsg.go
Normal file
@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
type SysServiceMsg struct {
|
||||
Id uint `gorm:"primarykey" json:"id"` // 主键ID
|
||||
MerchantId uint `json:"merchant_id" form:"merchant_id" gorm:"default:0;type:int;column:merchant_id;comment:商户id;"`
|
||||
Content string `json:"content" form:"content" gorm:"type:text;column:content;comment:消息内容;"`
|
||||
ServiceId int64 `json:"service_id" form:"service_id" gorm:"default:0;type:int;column:service_id;comment:客服id;"`
|
||||
Uid int64 `json:"uid" form:"uid" gorm:"default:0;type:int;column:uid;comment:用户id;"`
|
||||
IsTourist uint `json:"is_tourist" form:"is_tourist" gorm:"default:0;type:tinyint;column:is_tourist;comment:是否游客;"`
|
||||
IsView uint `json:"is_view" form:"is_view" gorm:"default:0;type:tinyint;column:is_view;comment:是否已读;"`
|
||||
AddTime int `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
MsgType int64 `json:"msg_type" form:"msg_type" gorm:"default:1;type:tinyint;column:msg_type;comment:消息类型 1=文字 2=表情 3=图片 4=语音 5=视频 6=商品;"`
|
||||
IsKf int64 `json:"is_kf" form:"is_kf" gorm:"default:0;type:tinyint;column:is_kf;comment:是否客服消息;"`
|
||||
}
|
||||
|
||||
func (SysServiceMsg) TableName() string {
|
||||
return "sys_service_msg"
|
||||
}
|
21
plugin/customerservice/model/sysServiceRecord.go
Normal file
21
plugin/customerservice/model/sysServiceRecord.go
Normal file
@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
type SysServiceRecord struct {
|
||||
Id uint `json:"id" form:"id" gorm:"primarykey"`
|
||||
ServiceId int64 `json:"service_id" form:"service_id" gorm:"default:0;type:int;column:service_id;comment:客服id;"`
|
||||
Uid int64 `json:"uid" form:"uid" gorm:"default:0;type:int;column:uid;comment:用户id;"`
|
||||
Avatar string `json:"avatar" form:"avatar" gorm:"default:'';type:varchar(255);column:avatar;comment:用户头像;"`
|
||||
Nickname string `json:"nickname" form:"nickname" gorm:"default:'';type:varchar(255);column:nickname;comment:用户昵称;"`
|
||||
Online uint `json:"online" form:"online" gorm:"default:0;type:tinyint;column:online;comment:是否在线;"`
|
||||
IsTourist uint `json:"is_tourist" form:"is_tourist" gorm:"default:0;type:tinyint;column:is_tourist;comment:是否游客0:否;1:是;"`
|
||||
Message string `json:"message" form:"message" gorm:"type:text;column:message;comment:最新一条消息;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
UpdateTime int64 `json:"update_time" form:"update_time" gorm:"default:0;type:int;column:update_time;comment:更新时间;"`
|
||||
MessageType int64 `json:"message_type" form:"message_type" gorm:"default:0;type:tinyint(1);column:message_type;comment:消息类型:1=文字 2=表情 3=图片 4=语音 5=视频 6=商品;"`
|
||||
NoRead int64 `json:"no_read" gorm:"-"`
|
||||
AddTimeStr string `json:"add_time_str" gorm:"-"`
|
||||
}
|
||||
|
||||
func (SysServiceRecord) TableName() string {
|
||||
return "sys_service_record"
|
||||
}
|
16
plugin/customerservice/model/sysServiceReply.go
Normal file
16
plugin/customerservice/model/sysServiceReply.go
Normal file
@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
type SysServiceReply struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
ReplyType int64 `json:"reply_type" form:"reply_type" gorm:"default:1;type:int;column:reply_type;comment:回复类型1文本,2图片;"`
|
||||
IsComplete int64 `json:"is_complete" form:"is_complete" gorm:"default:0;type:int;column:is_complete;comment:是否完全匹配0否1是;"`
|
||||
Keyword string `json:"keyword" form:"keyword" gorm:"default:'';type:varchar(255);column:keyword;comment:关键字;"`
|
||||
Content string `json:"content" form:"content" gorm:"type:text;column:content;comment:回复内容;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
AddTimeStr string `json:"add_time_str" form:"add_time_str" gorm:"-"`
|
||||
Status int64 `json:"status" form:"status" gorm:"default:0;type:tinyint(1);column:status;comment:是否显示;"`
|
||||
}
|
||||
|
||||
func (SysServiceReply) TableName() string {
|
||||
return "sys_service_reply"
|
||||
}
|
15
plugin/customerservice/model/sysServiceScript.go
Normal file
15
plugin/customerservice/model/sysServiceScript.go
Normal file
@ -0,0 +1,15 @@
|
||||
package model
|
||||
|
||||
type SysServiceScript struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
ServiceId int64 `json:"service_id" form:"service_id" gorm:"default:0;type:int;column:service_id;comment:客服id为0说明是公共话术;"`
|
||||
Title string `json:"title" form:"title" gorm:"default:'';type:varchar(255);column:title;comment:话术标题;"`
|
||||
Content string `json:"content" form:"content" gorm:"type:text;column:content;comment:话术内容;"`
|
||||
AddTime int64 `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
AddTimeStr string `json:"add_time_str" form:"add_time_str" gorm:"-"`
|
||||
Sort int64 `json:"sort" form:"sort" gorm:"default:0;type:int;column:sort;comment:排序;"`
|
||||
}
|
||||
|
||||
func (SysServiceScript) TableName() string {
|
||||
return "sys_service_script"
|
||||
}
|
11
plugin/customerservice/model/sysTestUser.go
Normal file
11
plugin/customerservice/model/sysTestUser.go
Normal file
@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
type SysTestUser struct {
|
||||
Id int64 `json:"id" form:"id" gorm:"primarykey"`
|
||||
Avatar string `json:"avatar" form:"avatar" gorm:"default:'';type:varchar(255);column:avatar;comment:头像;"`
|
||||
Nickname string `json:"nickname" form:"nickname" gorm:"default:'';type:varchar(255);column:nickname;comment:昵称;"`
|
||||
}
|
||||
|
||||
func (SysTestUser) TableName() string {
|
||||
return "sys_test_user"
|
||||
}
|
7
plugin/customerservice/router/enter.go
Normal file
7
plugin/customerservice/router/enter.go
Normal file
@ -0,0 +1,7 @@
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
CustomerServiceRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
48
plugin/customerservice/router/router.go
Normal file
48
plugin/customerservice/router/router.go
Normal file
@ -0,0 +1,48 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/middleware"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/api"
|
||||
serMiddleware "git.echol.cn/loser/lckt/plugin/customerservice/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CustomerServiceRouter struct {
|
||||
}
|
||||
|
||||
func (s *CustomerServiceRouter) InitCustomerServiceRouter(Router *gin.RouterGroup) {
|
||||
wsRouter := Router.Group("")
|
||||
plugServiceRouter := Router.Group("").Use(serMiddleware.JWTAuthMiddleware()).Use(middleware.Cors())
|
||||
//plugRouter := Router.Group("").Use(middleware.JWTAuth())
|
||||
privateRouter := Router.Group("").Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
|
||||
plugAdminApi := api.ApiGroupApp.AdminServiceApi
|
||||
{
|
||||
privateRouter.GET("/service/get_service_list", plugAdminApi.GetServiceList)
|
||||
privateRouter.POST("/service/save_service", plugAdminApi.SaveService)
|
||||
privateRouter.DELETE("/service/delete_service", plugAdminApi.DeleteService)
|
||||
privateRouter.GET("/service/find_service", plugAdminApi.FindService)
|
||||
privateRouter.GET("/service/admin_login", plugAdminApi.AdminServiceLogin)
|
||||
privateRouter.GET("/service/get_script_list", plugAdminApi.GetScriptList)
|
||||
privateRouter.POST("/service/save_script", plugAdminApi.SaveScript)
|
||||
privateRouter.DELETE("/service/delete_script", plugAdminApi.DeleteScript)
|
||||
privateRouter.GET("/service/find_script", plugAdminApi.FindScript)
|
||||
privateRouter.GET("/service/auto_reply_list", plugAdminApi.AutoReplyList)
|
||||
privateRouter.POST("/service/save_reply", plugAdminApi.SaveReply)
|
||||
privateRouter.DELETE("/service/delete_reply", plugAdminApi.DeleteReply)
|
||||
privateRouter.GET("/service/find_reply", plugAdminApi.FindReply)
|
||||
}
|
||||
plugApi := api.ApiGroupApp.CustomerServiceApi
|
||||
{
|
||||
plugServiceRouter.POST("/service/send_msg", plugApi.SendMsg)
|
||||
plugServiceRouter.GET("/service/get_msg_list", plugApi.GetMsgList)
|
||||
plugServiceRouter.GET("/service/get_kf_info", plugApi.GetKefuInfo)
|
||||
plugServiceRouter.POST("/service/upload_file", plugApi.UploadFile)
|
||||
plugServiceRouter.GET("/service/get_user_info", plugApi.GetUserInfo)
|
||||
plugServiceRouter.GET("/service/get_msg_user", plugApi.GetMsgUser)
|
||||
plugServiceRouter.GET("/service/get_service_script", plugApi.GetServiceScript)
|
||||
plugServiceRouter.GET("/service/set_msg_view", plugApi.SetMsgView)
|
||||
}
|
||||
wsRouter.GET("/service/serve_ws", plugApi.ServeWsForKefu)
|
||||
wsRouter.GET("/service/ws", plugApi.ServeWs)
|
||||
wsRouter.POST("/service/account_login", plugAdminApi.AccountServiceLogin)
|
||||
}
|
7
plugin/customerservice/service/enter.go
Normal file
7
plugin/customerservice/service/enter.go
Normal file
@ -0,0 +1,7 @@
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
CustomerServiceService
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
130
plugin/customerservice/service/service.go
Normal file
130
plugin/customerservice/service/service.go
Normal file
@ -0,0 +1,130 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/user"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CustomerServiceService struct{}
|
||||
|
||||
func (e *CustomerServiceService) PlugService() (err error) {
|
||||
// 写你的业务逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) ValidateServiceData(sys *model.SysService) error {
|
||||
if sys.Uid == 0 {
|
||||
return errors.New("客服关联的用户id不能为空")
|
||||
} else {
|
||||
db := global.GVA_DB.Model(&model.SysService{})
|
||||
if sys.Id > 0 {
|
||||
db = db.Where("uid=?", sys.Uid).Where("id<>?", sys.Id)
|
||||
} else {
|
||||
db = db.Where("uid=?", sys.Uid)
|
||||
}
|
||||
var dCount int64
|
||||
db.Count(&dCount)
|
||||
if dCount > 0 {
|
||||
return errors.New("用户id已关联其他客服,请重新输入")
|
||||
}
|
||||
}
|
||||
db := global.GVA_DB.Model(&model.SysService{})
|
||||
if sys.Id == 0 {
|
||||
if sys.Password == "" {
|
||||
return errors.New("客服密码必须填写")
|
||||
}
|
||||
db = db.Where("account=?", sys.Account)
|
||||
} else {
|
||||
db = db.Where("account=?", sys.Account).Where("id<>?", sys.Id)
|
||||
var dCount int64
|
||||
db.Count(&dCount)
|
||||
if dCount > 0 {
|
||||
return errors.New("账户已存在,请重新输入")
|
||||
}
|
||||
}
|
||||
if sys.Account == "" {
|
||||
return errors.New("客服账户必须填写")
|
||||
}
|
||||
if sys.Nickname == "" {
|
||||
return errors.New("客服名称必须填写")
|
||||
}
|
||||
if sys.Avatar == "" {
|
||||
return errors.New("客服头像必须选择")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) ValidateScriptData(sys *model.SysServiceScript) error {
|
||||
if sys.Title == "" {
|
||||
return errors.New("话术标题必须填写")
|
||||
}
|
||||
if sys.Content == "" {
|
||||
return errors.New("话术内容必须填写")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) ValidateReplyData(sys *model.SysServiceReply) error {
|
||||
if sys.Keyword == "" {
|
||||
return errors.New("关键字必须填写")
|
||||
}
|
||||
if sys.Content == "" {
|
||||
return errors.New("回复内容必须填写")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) GetUrlHost(c *gin.Context) string {
|
||||
host := c.Request.Host
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
referer := c.Request.Referer()
|
||||
if referer != "" {
|
||||
return referer
|
||||
}
|
||||
return scheme + "://" + host + "/"
|
||||
}
|
||||
|
||||
func (e *CustomerServiceService) CreateMsg(kfInfo model.SysService, userInfo user.User, msgType int64, content string, isKf string) (err error) {
|
||||
msgRecord := &model.SysServiceRecord{
|
||||
ServiceId: kfInfo.Id,
|
||||
Uid: int64(userInfo.ID),
|
||||
Message: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
MessageType: msgType,
|
||||
UpdateTime: time.Now().Unix(),
|
||||
Avatar: userInfo.Avatar,
|
||||
Nickname: userInfo.NickName,
|
||||
Online: 1,
|
||||
}
|
||||
var record model.SysServiceRecord
|
||||
|
||||
eErr := global.GVA_DB.Where("service_id = ?", kfInfo.Id).Where("uid = ?", userInfo.ID).First(&record).Error
|
||||
if errors.Is(eErr, gorm.ErrRecordNotFound) {
|
||||
msgRecord.AddTime = time.Now().Unix()
|
||||
global.GVA_DB.Create(msgRecord)
|
||||
} else {
|
||||
global.GVA_DB.Model(&model.SysServiceRecord{}).Where("id = ?", record.Id).Updates(msgRecord)
|
||||
}
|
||||
|
||||
//插入消息记录
|
||||
msg := map[string]interface{}{
|
||||
"service_id": kfInfo.Id,
|
||||
"uid": userInfo.ID,
|
||||
"content": base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
"msg_type": msgType,
|
||||
"is_view": 0,
|
||||
"add_time": time.Now().Unix(),
|
||||
"is_kf": isKf,
|
||||
}
|
||||
err = global.GVA_DB.Model(&model.SysServiceMsg{}).Create(msg).Error
|
||||
return err
|
||||
}
|
271
plugin/customerservice/service/ws/ws.go
Normal file
271
plugin/customerservice/service/ws/ws.go
Normal file
@ -0,0 +1,271 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
sysModel "git.echol.cn/loser/lckt/plugin/customerservice/model"
|
||||
"git.echol.cn/loser/lckt/plugin/customerservice/tools"
|
||||
"git.echol.cn/loser/lckt/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Sender string `json:"sender"`
|
||||
Receiver string `json:"receiver"`
|
||||
Content string `json:"content"`
|
||||
MsgType string `json:"msg_type"` //对应msg表的msg_type
|
||||
Role string `json:"role"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Nickname string `json:"nickname"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
IsKf int64 `json:"is_kf"`
|
||||
}
|
||||
|
||||
type TypeMsg struct {
|
||||
Type string `json:"type"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
UserID string
|
||||
Role string
|
||||
Socket *websocket.Conn
|
||||
Send chan []byte
|
||||
LastPingTime time.Time
|
||||
}
|
||||
|
||||
type ClientManager struct {
|
||||
Clients map[string]*Client
|
||||
Broadcast chan TypeMsg
|
||||
Register chan *Client
|
||||
Unregister chan *Client
|
||||
}
|
||||
|
||||
var Manager = ClientManager{
|
||||
Clients: make(map[string]*Client),
|
||||
Broadcast: make(chan TypeMsg),
|
||||
Register: make(chan *Client),
|
||||
Unregister: make(chan *Client),
|
||||
}
|
||||
|
||||
// 定时检查连接的活动状态
|
||||
func (manager *ClientManager) CheckClientActivity() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
now := time.Now()
|
||||
|
||||
for ck, client := range manager.Clients {
|
||||
// 如果超过一定时间没有收到ping,则断开连接
|
||||
fmt.Println(ck)
|
||||
fmt.Println(now.Sub(client.LastPingTime))
|
||||
if now.Sub(client.LastPingTime) > 120*time.Second {
|
||||
client.Socket.Close()
|
||||
delete(manager.Clients, ck)
|
||||
//设置离线
|
||||
if client.Role == "user" {
|
||||
setUserOnline("offline", client.UserID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *ClientManager) Start() {
|
||||
for {
|
||||
select {
|
||||
case conn := <-manager.Register:
|
||||
key := conn.Role + conn.UserID
|
||||
if existingConn, ok := manager.Clients[key]; ok {
|
||||
existingConn.Socket.Close()
|
||||
delete(manager.Clients, key)
|
||||
}
|
||||
fmt.Println(key)
|
||||
manager.Clients[key] = conn
|
||||
case conn := <-manager.Unregister:
|
||||
key := conn.Role + conn.UserID
|
||||
if existingConn, ok := manager.Clients[key]; ok && existingConn == conn {
|
||||
delete(manager.Clients, key)
|
||||
}
|
||||
case message := <-manager.Broadcast:
|
||||
data := message.Data.(map[string]interface{})
|
||||
receiver := data["receiver"].(string)
|
||||
receiverKey := "user" + receiver
|
||||
if data["role"].(string) == "user" {
|
||||
receiverKey = "kf" + receiver
|
||||
}
|
||||
if client, ok := manager.Clients[receiverKey]; ok {
|
||||
str, _ := json.Marshal(message)
|
||||
client.Send <- str
|
||||
} else {
|
||||
fmt.Println(receiverKey + "链接不存在")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Read() {
|
||||
defer func() {
|
||||
Manager.Unregister <- c
|
||||
c.Socket.Close()
|
||||
}()
|
||||
c.Socket.SetReadLimit(512)
|
||||
|
||||
for {
|
||||
_, message, err := c.Socket.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
var msg TypeMsg
|
||||
if err := json.Unmarshal(message, &msg); err != nil {
|
||||
continue
|
||||
}
|
||||
switch msg.Type {
|
||||
case "ping":
|
||||
// 更新最后一次收到ping消息的时间
|
||||
c.LastPingTime = time.Now()
|
||||
|
||||
// 回复pong消息
|
||||
pongMsg := TypeMsg{
|
||||
Type: "pong",
|
||||
Data: time.Now().Unix(),
|
||||
}
|
||||
pongStr, _ := json.Marshal(pongMsg)
|
||||
c.Send <- pongStr
|
||||
|
||||
case "message":
|
||||
//发送消息走的后台接口去触发广播,改成前端发送消息走这里
|
||||
Manager.Broadcast <- msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Write() {
|
||||
defer func() {
|
||||
c.Socket.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.Send:
|
||||
c.Socket.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if !ok {
|
||||
c.Socket.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
if err := c.Socket.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var Upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func WsServe(ctx *gin.Context) {
|
||||
//token := ctx.Query("token")
|
||||
token := ctx.Query("token")
|
||||
j := utils.NewJWT()
|
||||
claims, err := j.ParseToken(token)
|
||||
if err != nil {
|
||||
if errors.Is(err, utils.TokenExpired) {
|
||||
http.NotFound(ctx.Writer, ctx.Request)
|
||||
return
|
||||
}
|
||||
}
|
||||
conn, err := Upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||
if err != nil {
|
||||
http.NotFound(ctx.Writer, ctx.Request)
|
||||
return
|
||||
}
|
||||
uidStr := strconv.Itoa(int(claims.BaseClaims.ID))
|
||||
client := &Client{
|
||||
UserID: uidStr,
|
||||
Role: "user",
|
||||
Socket: conn,
|
||||
Send: make(chan []byte),
|
||||
LastPingTime: time.Now(),
|
||||
}
|
||||
|
||||
Manager.Register <- client
|
||||
setUserOnline("online", uidStr)
|
||||
go client.Read()
|
||||
go client.Write()
|
||||
}
|
||||
|
||||
func ServeWsForKefu(ctx *gin.Context) {
|
||||
token := ctx.Query("token")
|
||||
claims, err := tools.ValidateToken(token)
|
||||
if err != nil {
|
||||
response.FailWithMessage("token已失效", ctx)
|
||||
return
|
||||
}
|
||||
kfId := claims.ServiceId
|
||||
db := global.GVA_DB.Model(&sysModel.SysService{})
|
||||
var info sysModel.SysService
|
||||
err = db.Find(&info).Error
|
||||
if err != nil {
|
||||
response.FailWithMessage("客服不存在", ctx)
|
||||
return
|
||||
}
|
||||
conn, err2 := Upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
|
||||
if err2 != nil {
|
||||
http.NotFound(ctx.Writer, ctx.Request)
|
||||
return
|
||||
}
|
||||
client := &Client{
|
||||
UserID: fmt.Sprintf("%v", kfId),
|
||||
Role: "kf",
|
||||
Socket: conn,
|
||||
Send: make(chan []byte),
|
||||
LastPingTime: time.Now(),
|
||||
}
|
||||
Manager.Register <- client
|
||||
|
||||
//设置客服在线
|
||||
global.GVA_DB.Model(&sysModel.SysService{}).Where("id = ?", kfId).Update("online", 1)
|
||||
go client.Read()
|
||||
go client.Write()
|
||||
}
|
||||
|
||||
func setUserOnline(cType string, Id string) {
|
||||
//给用户在record表里的客服广播此用户离线
|
||||
var list []sysModel.SysServiceRecord
|
||||
err := global.GVA_DB.Where("uid=?", Id).Find(&list).Error
|
||||
if err == nil && len(list) > 0 {
|
||||
for _, rec := range list {
|
||||
strSerId := strconv.FormatInt(rec.ServiceId, 10)
|
||||
roleKey := "kf" + strSerId
|
||||
fmt.Println(roleKey)
|
||||
serviceClient, ok := Manager.Clients[roleKey]
|
||||
if serviceClient != nil && ok {
|
||||
dataMsg := Message{
|
||||
MsgType: "1",
|
||||
Sender: Id,
|
||||
Receiver: strSerId,
|
||||
Role: "user",
|
||||
}
|
||||
sendMsg := TypeMsg{
|
||||
Type: cType,
|
||||
Data: dataMsg,
|
||||
}
|
||||
str, _ := json.Marshal(sendMsg)
|
||||
serviceClient.Send <- str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
plugin/customerservice/tools/jwt.go
Normal file
54
plugin/customerservice/tools/jwt.go
Normal file
@ -0,0 +1,54 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"time"
|
||||
)
|
||||
|
||||
var jwtKey = []byte("your-256-bit-secret")
|
||||
|
||||
// CustomClaims 结构体可以根据需要添加自定义的声明
|
||||
type CustomClaims struct {
|
||||
ServiceId int64 `json:"service_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func GenerateToken(serviceId int64) (int64, string, error) {
|
||||
// 设置JWT的声明
|
||||
claims := CustomClaims{
|
||||
ServiceId: serviceId,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{"GVA"}, // 受众
|
||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), // 签名生效时间
|
||||
Issuer: "gva",
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(72 * time.Hour)), // token 72小时后过期
|
||||
},
|
||||
}
|
||||
// 生成JWT token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenStr, err := token.SignedString(jwtKey)
|
||||
return time.Now().Add(72 * time.Hour).Unix(), tokenStr, err
|
||||
}
|
||||
|
||||
func ValidateToken(tokenString string) (*CustomClaims, error) {
|
||||
claims := &CustomClaims{}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
|
||||
// 验证签名方法
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
return jwtKey, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
77
plugin/customerservice/tools/limits.go
Normal file
77
plugin/customerservice/tools/limits.go
Normal file
@ -0,0 +1,77 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LimitQueeMap struct {
|
||||
sync.RWMutex
|
||||
LimitQueue map[string][]int64
|
||||
}
|
||||
|
||||
func (l *LimitQueeMap) readMap(key string) ([]int64, bool) {
|
||||
l.RLock()
|
||||
value, ok := l.LimitQueue[key]
|
||||
l.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (l *LimitQueeMap) writeMap(key string, value []int64) {
|
||||
l.Lock()
|
||||
l.LimitQueue[key] = value
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
var LimitQueue = &LimitQueeMap{
|
||||
LimitQueue: make(map[string][]int64),
|
||||
}
|
||||
var ok bool
|
||||
|
||||
func NewLimitQueue() {
|
||||
cleanLimitQueue()
|
||||
}
|
||||
func cleanLimitQueue() {
|
||||
go func() {
|
||||
for {
|
||||
log.Println("cleanLimitQueue start...")
|
||||
LimitQueue.LimitQueue = nil
|
||||
now := time.Now()
|
||||
// 计算下一个零点
|
||||
next := now.Add(time.Hour * 24)
|
||||
next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
|
||||
t := time.NewTimer(next.Sub(now))
|
||||
<-t.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//单机时间滑动窗口限流法
|
||||
func LimitFreqSingle(queueName string, count uint, timeWindow int64) bool {
|
||||
currTime := time.Now().Unix()
|
||||
if LimitQueue.LimitQueue == nil {
|
||||
LimitQueue.LimitQueue = make(map[string][]int64)
|
||||
}
|
||||
if _, ok = LimitQueue.readMap(queueName); !ok {
|
||||
LimitQueue.writeMap(queueName, make([]int64, 0))
|
||||
return true
|
||||
}
|
||||
q, _ := LimitQueue.readMap(queueName)
|
||||
//队列未满
|
||||
if uint(len(q)) < count {
|
||||
LimitQueue.writeMap(queueName, append(q, currTime))
|
||||
return true
|
||||
}
|
||||
//队列满了,取出最早访问的时间
|
||||
earlyTime := q[0]
|
||||
//说明最早期的时间还在时间窗口内,还没过期,所以不允许通过
|
||||
if currTime-earlyTime <= timeWindow {
|
||||
return false
|
||||
} else {
|
||||
//说明最早期的访问应该过期了,去掉最早期的
|
||||
q = q[1:]
|
||||
LimitQueue.writeMap(queueName, append(q, currTime))
|
||||
}
|
||||
return true
|
||||
}
|
20
plugin/customerservice/tools/timeformat.go
Normal file
20
plugin/customerservice/tools/timeformat.go
Normal file
@ -0,0 +1,20 @@
|
||||
package tools
|
||||
|
||||
import "time"
|
||||
|
||||
func FormatTimestamp(timestamp int64) string {
|
||||
t := time.Unix(timestamp, 0)
|
||||
now := time.Now()
|
||||
|
||||
// 格式化时间
|
||||
if t.Year() == now.Year() && t.YearDay() == now.YearDay() {
|
||||
// 当天,返回 24 小时制的时和分
|
||||
return t.Format("15:04")
|
||||
} else if t.Year() == now.Year() && t.YearDay() == now.YearDay()-1 {
|
||||
// 昨天,返回 "昨天"
|
||||
return "昨天"
|
||||
} else {
|
||||
// 其他时间,返回月和日
|
||||
return t.Format("01-02")
|
||||
}
|
||||
}
|
113
plugin/picturelibrary/README.md
Normal file
113
plugin/picturelibrary/README.md
Normal file
@ -0,0 +1,113 @@
|
||||
## GVA 图库功能
|
||||
|
||||
### 手动安装方法
|
||||
|
||||
1.解压zip获得picturelibrary文件夹
|
||||
2.将 picturelibrary/web/plugin/picturelibrary 放置在web/plugin下
|
||||
3.将 picturelibrary/server/plugin/picturelibrary 放置在server/plugin下
|
||||
|
||||
#### 执行如下注册方法
|
||||
|
||||
### 注册(手动自动都需要)
|
||||
|
||||
#### 1. 前往GVA主程序下的initialize/router.go 在Routers 方法最末尾按照你需要的及安全模式添加本插件
|
||||
PluginInit(PrivateGroup, picturelibrary.CreatePictureLibraryPlug())
|
||||
到gva系统,角色管理,分配角色的api权限即可,插件会自动注册api,需要手动分配。
|
||||
注:会生成一个表:sys_attachment_category,表exa_file_upload_and_downloads会新增一个cat_id字段
|
||||
### 2. 配置说明
|
||||
|
||||
#### 2-1 全局配置结构体说明
|
||||
|
||||
无配置
|
||||
|
||||
#### 2-2 使用说明
|
||||
|
||||
在你需要图片选择的前端页面引入图库组件,如下:
|
||||
<div class="selected-images">
|
||||
<div class="selected-image" v-for="image in selectedImages" :key="image">
|
||||
<el-image v-if="fileTypeList.includes(image.tag) === true" :src="image.url" style="width: 100%; height: 100%; object-fit: cover;margin-right: 10px;"></el-image>
|
||||
<video v-else controls style="width: 100%; height: 100%;">
|
||||
<source :src="image.url" />
|
||||
</video>
|
||||
<span class="remove-icon" @click="removeSelectedImage(image)"><el-icon><circle-close></circle-close></el-icon></span>
|
||||
</div>
|
||||
<el-icon v-if="isMultiple || selectedImages.length === 0" class="avatar-uploader-icon" @click="openImageLibrary"><Plus /></el-icon>
|
||||
</div>
|
||||
|
||||
图库弹窗:
|
||||
<el-dialog v-model="isDialogVisible" title="图片库" width="950px" destroy-on-close>
|
||||
<ImageLibrary @select="handleImageSelect" :multiple="isMultiple"/>
|
||||
</el-dialog>
|
||||
|
||||
js代码:
|
||||
import {CircleClose, Plus} from '@element-plus/icons-vue'
|
||||
import ImageLibrary from "@/plugin/picturelibrary/view/components/imageLibrary.vue";
|
||||
|
||||
const isDialogVisible = ref(false)
|
||||
const isMultiple = ref(false) // 设置是否允许多选
|
||||
const selectedImages = ref([])
|
||||
|
||||
const openImageLibrary = () => {
|
||||
isDialogVisible.value = true
|
||||
}
|
||||
const fileTypeList = ['png', 'jpg', 'jpeg', 'gif']
|
||||
const handleImageSelect = (images) => {
|
||||
if (isMultiple.value) {
|
||||
selectedImages.value = [...selectedImages.value, ...images]
|
||||
} else {
|
||||
selectedImages.value = Array.isArray(images) ? images : [images]
|
||||
}
|
||||
|
||||
// 此处是测试项目里上传头像的参数,根据实际情况进行修改
|
||||
formData.value.avatar = selectedImages.value[0]
|
||||
isDialogVisible.value = false
|
||||
}
|
||||
|
||||
const removeSelectedImage = (image) => {
|
||||
const index = selectedImages.value.indexOf(image)
|
||||
if (index !== -1) {
|
||||
selectedImages.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
style代码:
|
||||
.selected-images {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.selected-image {
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
margin-bottom:10px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.selected-image .remove-icon {
|
||||
position: absolute;
|
||||
top: 0; /* 微调位置 */
|
||||
right: 0; /* 微调位置 */
|
||||
color: black;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
border: 1px dashed #d4d9e1;
|
||||
}
|
||||
|
||||
#### 2-3 参数说明
|
||||
isMultiple 是控制能不能选择多张图的参数,false:只能选一张;true:可以选择多张
|
||||
### 3. 方法API
|
||||
无
|
||||
|
230
plugin/picturelibrary/api/api.go
Normal file
230
plugin/picturelibrary/api/api.go
Normal file
@ -0,0 +1,230 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/common/response"
|
||||
"git.echol.cn/loser/lckt/model/example"
|
||||
picModel "git.echol.cn/loser/lckt/plugin/picturelibrary/model"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type PictureLibraryApi struct{}
|
||||
|
||||
var picService = service.ServiceGroupApp
|
||||
|
||||
// @Tags PictureLibrary
|
||||
// @Summary 请手动填写接口功能
|
||||
// @Produce application/json
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"发送成功"}"
|
||||
// @Router /图片库/routerName [post]
|
||||
func (p *PictureLibraryApi) ApiName(c *gin.Context) {
|
||||
|
||||
if err := picService.PlugService(); err != nil {
|
||||
global.GVA_LOG.Error("失败!", zap.Error(err))
|
||||
response.FailWithMessage("失败", c)
|
||||
} else {
|
||||
|
||||
response.OkWithData("成功", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetFileList
|
||||
// @Tags sysAttachment
|
||||
// @Summary 分页文件列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /attachment/getFileList [post]
|
||||
func (p *PictureLibraryApi) GetFileList(c *gin.Context) {
|
||||
var pageInfo picModel.PageInfo
|
||||
_ = c.ShouldBindJSON(&pageInfo)
|
||||
list, total, err := picService.GetFileRecordInfoList(pageInfo, c)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.Limit,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetCategoryList
|
||||
// @Tags sysAttachment
|
||||
// @Summary 分类列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /attachment/getFileList [post]
|
||||
func (p *PictureLibraryApi) GetCategoryList(c *gin.Context) {
|
||||
db := global.GVA_DB.Model(&picModel.SysAttachmentCategory{})
|
||||
var fileLists []picModel.SysAttachmentCategory
|
||||
err := db.Find(&fileLists).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
data := picService.BuildTree(fileLists, 0)
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: data,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// AddCategory
|
||||
// @Tags sysAttachment
|
||||
// @Summary 添加分类
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /attachment/getFileList [post]
|
||||
func (p *PictureLibraryApi) AddCategory(c *gin.Context) {
|
||||
var input struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Pid uint `json:"pid"`
|
||||
Id uint `json:"id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
global.GVA_LOG.Error("参数错误!", zap.Error(err))
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否已存在相同名称的分类
|
||||
var existingCategory picModel.SysAttachmentCategory
|
||||
result := global.GVA_DB.Where("name = ? ", input.Name).First(&existingCategory)
|
||||
if result.Error == nil && existingCategory.Id != input.Id {
|
||||
response.FailWithMessage("分类名称已存在", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建新的分类
|
||||
newCategory := picModel.SysAttachmentCategory{Name: input.Name, Pid: input.Pid}
|
||||
var err error
|
||||
var title string
|
||||
if input.Id > 0 {
|
||||
err = global.GVA_DB.Where("id = ?", input.Id).Updates(newCategory).Error
|
||||
title = "更新失败"
|
||||
} else {
|
||||
err = global.GVA_DB.Create(&newCategory).Error
|
||||
title = "创建失败"
|
||||
}
|
||||
if err != nil {
|
||||
response.FailWithMessage(title, c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{}, "创建成功", c)
|
||||
}
|
||||
|
||||
// UploadHandler
|
||||
// @Tags sysAttachment
|
||||
// @Summary 多文件上传
|
||||
// @Security ApiKeyAuth
|
||||
// @accept multipart/form-data
|
||||
// @Produce application/json
|
||||
// @Param file formData [file] true "上传文件示例"
|
||||
// @Success 200 {object} response.Response{data=exampleRes.ExaFileResponse,msg=string} "上传文件示例,返回包括文件详情"
|
||||
// @Router /fileUploadAndDownload/upload [post]
|
||||
func (p *PictureLibraryApi) UploadHandler(c *gin.Context) {
|
||||
form, _ := c.MultipartForm()
|
||||
files := form.File["files"]
|
||||
categoryIDStr := c.PostForm("cat_id")
|
||||
categoryID, _ := strconv.ParseUint(categoryIDStr, 10, 32)
|
||||
noSave := c.DefaultQuery("noSave", "0")
|
||||
for _, file := range files {
|
||||
classId, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
|
||||
fileData, err := fileUploadAndDownloadService.UploadFile(file, noSave, classId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("上传失败!", zap.Error(err))
|
||||
response.FailWithMessage("上传失败", c)
|
||||
return
|
||||
}
|
||||
var attachment picModel.SysAttachment
|
||||
if err := global.GVA_DB.Where("`key` = ? ", fileData.Key).First(&attachment).Error; err != nil {
|
||||
response.FailWithMessage("上传失败", c)
|
||||
return
|
||||
}
|
||||
// 根据key更新数据
|
||||
attachment.CatId = uint(categoryID)
|
||||
if err := global.GVA_DB.Save(&attachment).Error; err != nil {
|
||||
response.FailWithMessage("上传文件失败", c)
|
||||
return
|
||||
}
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{}, "上传成功", c)
|
||||
}
|
||||
|
||||
// DeleteFile
|
||||
// @Tags sysAttachment
|
||||
// @Summary 删除文件
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除文件"
|
||||
// @Router /fileUploadAndDownload/deleteFile [post]
|
||||
func (p *PictureLibraryApi) DeleteFile(c *gin.Context) {
|
||||
var files []example.ExaFileUploadAndDownload
|
||||
err := c.ShouldBindJSON(&files)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := fileUploadAndDownloadService.DeleteFile(file); err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteCategory
|
||||
// @Tags sysAttachment
|
||||
// @Summary 删除分类
|
||||
// @Security ApiKeyAuth
|
||||
// @Produce application/json
|
||||
// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除文件"
|
||||
// @Router /fileUploadAndDownload/deleteFile [post]
|
||||
func (p *PictureLibraryApi) DeleteCategory(c *gin.Context) {
|
||||
var input struct {
|
||||
CatId uint `json:"cat_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
if input.CatId == 0 {
|
||||
response.FailWithMessage("参数错误-1", c)
|
||||
return
|
||||
}
|
||||
var childCount int64
|
||||
global.GVA_DB.Model(&picModel.SysAttachmentCategory{}).Where("pid = ?", input.CatId).Count(&childCount)
|
||||
|
||||
if childCount > 0 {
|
||||
response.FailWithMessage("请先删除子级", c)
|
||||
return
|
||||
}
|
||||
result := global.GVA_DB.Delete(&picModel.SysAttachmentCategory{}, input.CatId)
|
||||
if result.Error != nil {
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
11
plugin/picturelibrary/api/enter.go
Normal file
11
plugin/picturelibrary/api/enter.go
Normal file
@ -0,0 +1,11 @@
|
||||
package api
|
||||
|
||||
import "git.echol.cn/loser/lckt/service"
|
||||
|
||||
type ApiGroup struct {
|
||||
PictureLibraryApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
|
||||
var fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
|
1
plugin/picturelibrary/config/config.go
Normal file
1
plugin/picturelibrary/config/config.go
Normal file
@ -0,0 +1 @@
|
||||
package config
|
1
plugin/picturelibrary/global/global.go
Normal file
1
plugin/picturelibrary/global/global.go
Normal file
@ -0,0 +1 @@
|
||||
package global
|
77
plugin/picturelibrary/main.go
Normal file
77
plugin/picturelibrary/main.go
Normal file
@ -0,0 +1,77 @@
|
||||
package picturelibrary
|
||||
|
||||
import (
|
||||
gvaGlobal "git.echol.cn/loser/lckt/global"
|
||||
"git.echol.cn/loser/lckt/model/system"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/model"
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/router"
|
||||
"git.echol.cn/loser/lckt/plugin/plugin-tool/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PictureLibraryPlugin struct {
|
||||
}
|
||||
|
||||
func CreatePictureLibraryPlug() *PictureLibraryPlugin {
|
||||
|
||||
gvaGlobal.GVA_DB.AutoMigrate(model.SysAttachment{}, model.SysAttachmentCategory{}) // 此处可以把插件依赖的数据库结构体自动创建表 需要填写对应的结构体
|
||||
|
||||
// 下方会自动注册菜单 第一个参数为菜单一级路由信息一般为定义好的组名 第二个参数为真实使用的web页面路由信息
|
||||
//utils.RegisterMenus(
|
||||
// system.SysBaseMenu{
|
||||
// Path: "picturelibrary",
|
||||
// Name: "picturelibrary",
|
||||
// Hidden: false,
|
||||
// Component: "plugin/picturelibrary/view/index.vue",
|
||||
// Sort: 0,
|
||||
// Meta: system.Meta{
|
||||
// Title: "图片库",
|
||||
// Icon: "folder",
|
||||
// },
|
||||
// },
|
||||
//)
|
||||
|
||||
// 下方会自动注册api 以下格式为示例格式,请按照实际情况修改
|
||||
utils.RegisterApis(
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/list",
|
||||
Description: "图片列表",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/cat_list",
|
||||
Description: "图片分类列表",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/add_cat",
|
||||
Description: "添加分类",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/upload_handler",
|
||||
Description: "上传文件",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
system.SysApi{
|
||||
Path: "/pic/pic_library/delete_file",
|
||||
Description: "删除文件",
|
||||
ApiGroup: "图片库",
|
||||
Method: "POST",
|
||||
},
|
||||
)
|
||||
|
||||
return &PictureLibraryPlugin{}
|
||||
}
|
||||
|
||||
func (*PictureLibraryPlugin) Register(group *gin.RouterGroup) {
|
||||
router.RouterGroupApp.InitPictureLibraryRouter(group)
|
||||
}
|
||||
|
||||
func (*PictureLibraryPlugin) RouterPath() string {
|
||||
return "pic"
|
||||
}
|
8
plugin/picturelibrary/model/model.go
Normal file
8
plugin/picturelibrary/model/model.go
Normal file
@ -0,0 +1,8 @@
|
||||
package model
|
||||
|
||||
type PageInfo struct {
|
||||
Page int `json:"page" form:"page"`
|
||||
Limit int `json:"limit" form:"limit"`
|
||||
Cid int `json:"cid" form:"cid"` //分类id
|
||||
Keyword string `json:"keyword" form:"keyword"`
|
||||
}
|
14
plugin/picturelibrary/model/sysAttachment.go
Normal file
14
plugin/picturelibrary/model/sysAttachment.go
Normal file
@ -0,0 +1,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/model/example"
|
||||
)
|
||||
|
||||
type SysAttachment struct {
|
||||
example.ExaFileUploadAndDownload
|
||||
CatId uint `json:"cat_id" form:"cat_id" gorm:"default:0;type:int;column:cat_id;comment:分类id;"`
|
||||
}
|
||||
|
||||
func (SysAttachment) TableName() string {
|
||||
return "exa_file_upload_and_downloads"
|
||||
}
|
13
plugin/picturelibrary/model/sysAttachmentCatategory.go
Normal file
13
plugin/picturelibrary/model/sysAttachmentCatategory.go
Normal file
@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
type SysAttachmentCategory struct {
|
||||
Id uint `gorm:"primarykey" json:"id"` // 主键ID
|
||||
Name string `json:"name" form:"name" gorm:"default:'';type:varchar(255);column:name;comment:分类名称;"`
|
||||
Pid uint `json:"pid" form:"pid" gorm:"default:0;type:int;column:pid;comment:父节点ID;"`
|
||||
AddTime int `json:"add_time" form:"add_time" gorm:"default:0;type:int;column:add_time;comment:添加时间;"`
|
||||
Children []*SysAttachmentCategory `json:"children" gorm:"-"`
|
||||
}
|
||||
|
||||
func (SysAttachmentCategory) TableName() string {
|
||||
return "sys_attachment_category"
|
||||
}
|
7
plugin/picturelibrary/router/enter.go
Normal file
7
plugin/picturelibrary/router/enter.go
Normal file
@ -0,0 +1,7 @@
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
PictureLibraryRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
22
plugin/picturelibrary/router/router.go
Normal file
22
plugin/picturelibrary/router/router.go
Normal file
@ -0,0 +1,22 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/plugin/picturelibrary/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type PictureLibraryRouter struct {
|
||||
}
|
||||
|
||||
func (s *PictureLibraryRouter) InitPictureLibraryRouter(Router *gin.RouterGroup) {
|
||||
plugRouter := Router.Use()
|
||||
plugApi := api.ApiGroupApp.PictureLibraryApi
|
||||
{
|
||||
plugRouter.POST("/pic_library/list", plugApi.GetFileList)
|
||||
plugRouter.POST("/pic_library/cat_list", plugApi.GetCategoryList)
|
||||
plugRouter.POST("/pic_library/add_cat", plugApi.AddCategory)
|
||||
plugRouter.POST("/pic_library/upload_handler", plugApi.UploadHandler)
|
||||
plugRouter.POST("/pic_library/delete_file", plugApi.DeleteFile)
|
||||
plugRouter.POST("/pic_library/delete_cat", plugApi.DeleteCategory)
|
||||
}
|
||||
}
|
7
plugin/picturelibrary/service/enter.go
Normal file
7
plugin/picturelibrary/service/enter.go
Normal file
@ -0,0 +1,7 @@
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
PictureLibraryService
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
70
plugin/picturelibrary/service/service.go
Normal file
70
plugin/picturelibrary/service/service.go
Normal file
@ -0,0 +1,70 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"git.echol.cn/loser/lckt/global"
|
||||
picModel "git.echol.cn/loser/lckt/plugin/picturelibrary/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PictureLibraryService struct{}
|
||||
|
||||
func (e *PictureLibraryService) PlugService() (err error) {
|
||||
// 写你的业务逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PictureLibraryService) GetFileRecordInfoList(info picModel.PageInfo, c *gin.Context) (list interface{}, total int64, err error) {
|
||||
limit := info.Limit
|
||||
offset := info.Limit * (info.Page - 1)
|
||||
keyword := info.Keyword
|
||||
cid := info.Cid
|
||||
db := global.GVA_DB.Model(&picModel.SysAttachment{})
|
||||
var fileLists []picModel.SysAttachment
|
||||
if len(keyword) > 0 {
|
||||
db = db.Where("`name` LIKE ?", "%"+keyword+"%")
|
||||
}
|
||||
if cid > 0 {
|
||||
db = db.Where("cat_id = ?", cid)
|
||||
}
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&fileLists).Error
|
||||
urlHost := e.GetUrlHost(c)
|
||||
for k, v := range fileLists {
|
||||
if !strings.HasPrefix(v.Url, "http://") && !strings.HasPrefix(v.Url, "https://") {
|
||||
v.Url = urlHost + "api/" + v.Url
|
||||
fileLists[k] = v
|
||||
}
|
||||
}
|
||||
return fileLists, total, err
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
func (e *PictureLibraryService) BuildTree(categories []picModel.SysAttachmentCategory, parentID uint) []*picModel.SysAttachmentCategory {
|
||||
var tree []*picModel.SysAttachmentCategory
|
||||
for _, category := range categories {
|
||||
if category.Pid == parentID {
|
||||
children := e.BuildTree(categories, category.Id)
|
||||
category.Children = children
|
||||
tree = append(tree, &category)
|
||||
}
|
||||
}
|
||||
return tree
|
||||
}
|
||||
|
||||
func (e *PictureLibraryService) GetUrlHost(c *gin.Context) string {
|
||||
host := c.Request.Host
|
||||
scheme := "http"
|
||||
if c.Request.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
referer := c.Request.Referer()
|
||||
if referer != "" {
|
||||
return referer
|
||||
}
|
||||
return scheme + "://" + host + "/"
|
||||
}
|
27
test/rain_test.go
Normal file
27
test/rain_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRain(t *testing.T) {
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
verifyCode := fmt.Sprintf("%06v", rand.Int31n(1000000))
|
||||
fmt.Println(verifyCode)
|
||||
}
|
||||
|
||||
func TestPwd(t *testing.T) {
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("loser7659"), bcrypt.DefaultCost)
|
||||
fmt.Println(string(password))
|
||||
|
||||
err := bcrypt.CompareHashAndPassword(password, []byte("122456"))
|
||||
if err != nil {
|
||||
fmt.Println("密码错误")
|
||||
} else {
|
||||
fmt.Println("密码正确")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user