feat: add custom list
This commit is contained in:
parent
a203de46f7
commit
eeda43d30c
16
cmd/root.go
16
cmd/root.go
@ -1,7 +1,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"ai-css/common"
|
||||||
"ai-css/library/logger"
|
"ai-css/library/logger"
|
||||||
|
"ai-css/models"
|
||||||
|
"ai-css/ws"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@ -28,13 +31,16 @@ func args(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
func Execute() {
|
func Execute() {
|
||||||
logger.InitDefault()
|
logger.InitDefault()
|
||||||
|
common.LoadConfig()
|
||||||
|
rootCmd.AddCommand(serverCmd)
|
||||||
|
rootCmd.AddCommand(installCmd)
|
||||||
|
rootCmd.AddCommand(stopCmd)
|
||||||
|
|
||||||
|
models.Connect()
|
||||||
|
ws.StartUpdateVisitorStatusCron()
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(serverCmd)
|
|
||||||
rootCmd.AddCommand(installCmd)
|
|
||||||
rootCmd.AddCommand(stopCmd)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
PageSize uint = 10
|
PageSize uint = 10
|
||||||
VisitorPageSize uint = 8
|
VisitorPageSize uint = 8
|
||||||
@ -10,3 +16,21 @@ var (
|
|||||||
MysqlConf string = Dir + "mysql.json"
|
MysqlConf string = Dir + "mysql.json"
|
||||||
IsCompireTemplate bool = false //是否编译静态模板到二进制
|
IsCompireTemplate bool = false //是否编译静态模板到二进制
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ENV_DEV = "dev"
|
||||||
|
ENV_PROD = "prod"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
environment = os.Getenv("AICSS_ENV")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getConfigPath() string {
|
||||||
|
switch environment {
|
||||||
|
case ENV_DEV, ENV_PROD:
|
||||||
|
return path.Join(Dir, fmt.Sprintf("config_%s.yaml", environment))
|
||||||
|
default:
|
||||||
|
return path.Join(Dir, "config.yaml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,29 +1,59 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai-css/tools"
|
"fmt"
|
||||||
"encoding/json"
|
"sync"
|
||||||
"io/ioutil"
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mysql struct {
|
type Config struct {
|
||||||
Server string
|
Service Service `mapstructure:"service" json:"service"` // 服务配置
|
||||||
Port string
|
MysqlService Mysql `mapstructure:"mysql_service" json:"mysql_service"`
|
||||||
Database string
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMysqlConf() *Mysql {
|
type Service struct {
|
||||||
var mysql = &Mysql{}
|
Sites map[string]SiteConfig `mapstructure:"site" json:"site"`
|
||||||
isExist, _ := tools.IsFileExist(MysqlConf)
|
}
|
||||||
if !isExist {
|
|
||||||
return mysql
|
type SiteConfig struct {
|
||||||
}
|
UnauthURI string `mapstructure:"unauth_uri" json:"unauth_uri"`
|
||||||
info, err := ioutil.ReadFile(MysqlConf)
|
}
|
||||||
if err != nil {
|
|
||||||
return mysql
|
type Mysql struct {
|
||||||
}
|
Server string `mapstructure:"server" json:"server"`
|
||||||
err = json.Unmarshal(info, mysql)
|
Port string `mapstructure:"port" json:"port"`
|
||||||
return mysql
|
Database string `mapstructure:"database" json:"database"`
|
||||||
|
Username string `mapstructure:"username" json:"username"`
|
||||||
|
Password string `mapstructure:"password" json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfg Config
|
||||||
|
loadOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadConfig() Config {
|
||||||
|
loadOnce.Do(func() {
|
||||||
|
var configPath = getConfigPath()
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
viper.SetConfigFile(configPath)
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("read file failed err:%v,file:%s", err, configPath))
|
||||||
|
}
|
||||||
|
err = viper.Unmarshal(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("errpr viper.Unnarshal err:%v,file:%s", err, configPath))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMysqlConfig() Mysql {
|
||||||
|
return cfg.MysqlService
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetServiceConfig() Service {
|
||||||
|
return cfg.Service
|
||||||
}
|
}
|
||||||
|
|||||||
6
config/config.yaml
Normal file
6
config/config.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
mysql_service:
|
||||||
|
server: xpink-prod-mysql.cjkmm024cf76.ap-east-1.rds.amazonaws.com
|
||||||
|
port: 3306
|
||||||
|
database: aicss_db
|
||||||
|
username: admin
|
||||||
|
password: o()Vq$NuZdwoEe>VLalG]CBMp4LZ
|
||||||
6
config/config_prod.yaml
Normal file
6
config/config_prod.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
mysql_service:
|
||||||
|
server: xpink-prod-mysql.cjkmm024cf76.ap-east-1.rds.amazonaws.com
|
||||||
|
port: 3306
|
||||||
|
database: aicss_db
|
||||||
|
username: admin
|
||||||
|
password: o()Vq$NuZdwoEe>VLalG]CBMp4LZ
|
||||||
@ -5,10 +5,65 @@ import (
|
|||||||
"ai-css/tools"
|
"ai-css/tools"
|
||||||
"ai-css/ws"
|
"ai-css/ws"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func PostKefuStatus(c *gin.Context) {
|
||||||
|
v := c.GetString("kefu_name")
|
||||||
|
userInfo := models.FindUser(v)
|
||||||
|
if userInfo.Role != 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "权限不足",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuName := c.PostForm("username")
|
||||||
|
statusStr := c.PostForm("status")
|
||||||
|
status, _ := strconv.Atoi(statusStr)
|
||||||
|
|
||||||
|
models.UpdateUserStatus(kefuName, int32(status))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostAdminResetPass(c *gin.Context) {
|
||||||
|
v := c.GetString("kefu_name")
|
||||||
|
userInfo := models.FindUser(v)
|
||||||
|
if userInfo.Role != 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "权限不足",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kefuName := c.PostForm("username")
|
||||||
|
newPass := c.PostForm("password")
|
||||||
|
|
||||||
|
if kefuName == "" || newPass == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "Username and password are required",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
models.UpdateUserPass(kefuName, tools.Md5(newPass))
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func PostKefuAvator(c *gin.Context) {
|
func PostKefuAvator(c *gin.Context) {
|
||||||
|
|
||||||
avator := c.PostForm("avator")
|
avator := c.PostForm("avator")
|
||||||
@ -78,22 +133,46 @@ func PostKefuClient(c *gin.Context) {
|
|||||||
|
|
||||||
func GetIdleKefu(c *gin.Context) {
|
func GetIdleKefu(c *gin.Context) {
|
||||||
visitorId := c.Query("visitor_id")
|
visitorId := c.Query("visitor_id")
|
||||||
var kefuName string
|
var kefuName, oldKefuname string
|
||||||
|
var reset bool
|
||||||
|
var visitorDataId uint
|
||||||
if visitorId != "" {
|
if visitorId != "" {
|
||||||
visitor := models.FindVisitorByVistorId(visitorId)
|
visitor := models.FindVisitorByVistorId(visitorId)
|
||||||
if visitor.ToId != "" {
|
if visitor.ToId != "" {
|
||||||
kefuName = visitor.ToId
|
kefuName = visitor.ToId
|
||||||
|
visitorDataId = visitor.ID
|
||||||
|
}
|
||||||
|
if kefuName != "" {
|
||||||
|
userInfo := models.FindUser(kefuName)
|
||||||
|
if userInfo.ID == 0 || userInfo.Status == 0 || userInfo.IsOnline == 0 {
|
||||||
|
reset = true
|
||||||
|
oldKefuname = kefuName
|
||||||
|
kefuName = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if kefuName == "" {
|
if kefuName == "" {
|
||||||
user := models.FindIdleUser()
|
user := models.FindIdleUser()
|
||||||
kefuName = user.Name
|
kefuName = user.Name
|
||||||
|
if reset {
|
||||||
|
if visitorDataId > 0 {
|
||||||
|
models.UpdatesVisitor(visitorDataId, kefuName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if kefuName == "" {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "暂时没有在线客服",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "ok",
|
||||||
|
"result": kefuName,
|
||||||
|
"oldResult": oldKefuname,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
c.JSON(200, gin.H{
|
|
||||||
"code": 200,
|
|
||||||
"msg": "ok",
|
|
||||||
"result": kefuName,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
func GetKefuInfo(c *gin.Context) {
|
func GetKefuInfo(c *gin.Context) {
|
||||||
kefuName, _ := c.Get("kefu_name")
|
kefuName, _ := c.Get("kefu_name")
|
||||||
@ -124,7 +203,7 @@ func GetOtherKefuList(c *gin.Context) {
|
|||||||
id := idStr.(float64)
|
id := idStr.(float64)
|
||||||
result := make([]interface{}, 0)
|
result := make([]interface{}, 0)
|
||||||
ws.SendPingToKefuClient()
|
ws.SendPingToKefuClient()
|
||||||
kefus := models.FindUsers()
|
kefus := models.FindUsers("")
|
||||||
for _, kefu := range kefus {
|
for _, kefu := range kefus {
|
||||||
if uint(id) == kefu.ID {
|
if uint(id) == kefu.ID {
|
||||||
continue
|
continue
|
||||||
@ -180,6 +259,15 @@ func GetKefuInfoSetting(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
func PostKefuRegister(c *gin.Context) {
|
func PostKefuRegister(c *gin.Context) {
|
||||||
|
kefuName := c.GetString("kefu_name")
|
||||||
|
userInfo := models.FindUser(kefuName)
|
||||||
|
if userInfo.Role != 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "权限不足",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
name := c.PostForm("username")
|
name := c.PostForm("username")
|
||||||
password := c.PostForm("password")
|
password := c.PostForm("password")
|
||||||
nickname := c.PostForm("nickname")
|
nickname := c.PostForm("nickname")
|
||||||
@ -246,7 +334,16 @@ func PostKefuInfo(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
func GetKefuList(c *gin.Context) {
|
func GetKefuList(c *gin.Context) {
|
||||||
users := models.FindUsers()
|
kefuName := c.GetString("kefu_name")
|
||||||
|
userInfo := models.FindUser(kefuName)
|
||||||
|
if userInfo.Role != 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "权限不足",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
users := models.FindUsers(kefuName)
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"code": 200,
|
"code": 200,
|
||||||
"msg": "获取成功",
|
"msg": "获取成功",
|
||||||
|
|||||||
@ -3,8 +3,9 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"ai-css/models"
|
"ai-css/models"
|
||||||
"ai-css/tools"
|
"ai-css/tools"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Summary User Authentication API
|
// @Summary User Authentication API
|
||||||
@ -33,6 +34,14 @@ func LoginCheckPass(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if info.Status != 1 {
|
||||||
|
c.JSON(200, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"message": "账号已经停用",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare user session data
|
// Prepare user session data
|
||||||
userinfo := map[string]interface{}{
|
userinfo := map[string]interface{}{
|
||||||
"kefu_name": info.Name,
|
"kefu_name": info.Name,
|
||||||
|
|||||||
@ -16,6 +16,7 @@ func JwtPageMiddleware(c *gin.Context) {
|
|||||||
// c.Abort()
|
// c.Abort()
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
func JwtApiMiddleware(c *gin.Context) {
|
func JwtApiMiddleware(c *gin.Context) {
|
||||||
token := c.GetHeader("aicss-token")
|
token := c.GetHeader("aicss-token")
|
||||||
if token == "" {
|
if token == "" {
|
||||||
|
|||||||
31
middleware/login_direct/login.go
Normal file
31
middleware/login_direct/login.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package login_direct
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DomainAuthRedirectMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
host := c.Request.Host
|
||||||
|
|
||||||
|
// 只针对 admin 域名
|
||||||
|
if strings.HasPrefix(host, "admin.example.com") {
|
||||||
|
|
||||||
|
// 这里写你的认证逻辑
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
|
||||||
|
if token == "" {
|
||||||
|
// 未认证跳转
|
||||||
|
c.Redirect(http.StatusFound, "https://admin.example.com/login")
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,25 +18,21 @@ type Model struct {
|
|||||||
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
|
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func Connect() {
|
||||||
Connect()
|
mysql := common.GetMysqlConfig()
|
||||||
}
|
|
||||||
func Connect() error {
|
|
||||||
mysql := common.GetMysqlConf()
|
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", mysql.Username, mysql.Password, mysql.Server, mysql.Port, mysql.Database)
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", mysql.Username, mysql.Password, mysql.Server, mysql.Port, mysql.Database)
|
||||||
var err error
|
var err error
|
||||||
DB, err = gorm.Open("mysql", dsn)
|
DB, err = gorm.Open("mysql", dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
panic("数据库连接失败!")
|
panic("数据库连接失败!")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
DB.SingularTable(true)
|
DB.SingularTable(true)
|
||||||
DB.LogMode(true)
|
DB.LogMode(true)
|
||||||
DB.DB().SetMaxIdleConns(10)
|
DB.DB().SetMaxIdleConns(10)
|
||||||
DB.DB().SetMaxOpenConns(100)
|
DB.DB().SetMaxOpenConns(100)
|
||||||
DB.DB().SetConnMaxLifetime(59 * time.Second)
|
DB.DB().SetConnMaxLifetime(59 * time.Second)
|
||||||
return nil
|
DB.AutoMigrate(&User{})
|
||||||
}
|
}
|
||||||
func Execute(sql string) error {
|
func Execute(sql string) error {
|
||||||
return DB.Exec(sql).Error
|
return DB.Exec(sql).Error
|
||||||
|
|||||||
@ -16,6 +16,8 @@ type User struct {
|
|||||||
Role int32 `json:"role"`
|
Role int32 `json:"role"`
|
||||||
RoleName string `json:"role_name" sql:"-"`
|
RoleName string `json:"role_name" sql:"-"`
|
||||||
RoleId string `json:"role_id" sql:"-"`
|
RoleId string `json:"role_id" sql:"-"`
|
||||||
|
Status int32 `json:"status" gorm:"default:1"` // 1: active, 0: inactive
|
||||||
|
IsOnline int32 `json:"is_online" gorm:"default:0"` // 1: online, 0: offline
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateUser(name string, password string, avator string, nickname string) uint {
|
func CreateUser(name string, password string, avator string, nickname string) uint {
|
||||||
@ -29,6 +31,15 @@ func CreateUser(name string, password string, avator string, nickname string) ui
|
|||||||
DB.Create(user)
|
DB.Create(user)
|
||||||
return user.ID
|
return user.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateUserIsOnline(name string, isOnline int32) {
|
||||||
|
user := &User{
|
||||||
|
IsOnline: isOnline,
|
||||||
|
}
|
||||||
|
user.UpdatedAt = time.Now()
|
||||||
|
DB.Model(user).Where("name = ?", name).Update("IsOnline", isOnline)
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateUser(name string, password string, avator string, nickname string) {
|
func UpdateUser(name string, password string, avator string, nickname string) {
|
||||||
user := &User{
|
user := &User{
|
||||||
Avator: avator,
|
Avator: avator,
|
||||||
@ -47,6 +58,13 @@ func UpdateUserPass(name string, pass string) {
|
|||||||
user.UpdatedAt = time.Now()
|
user.UpdatedAt = time.Now()
|
||||||
DB.Model(user).Where("name = ?", name).Update("Password", pass)
|
DB.Model(user).Where("name = ?", name).Update("Password", pass)
|
||||||
}
|
}
|
||||||
|
func UpdateUserStatus(name string, status int32) {
|
||||||
|
user := &User{
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
user.UpdatedAt = time.Now()
|
||||||
|
DB.Model(user).Where("name = ?", name).Update("Status", status)
|
||||||
|
}
|
||||||
func UpdateUserAvator(name string, avator string) {
|
func UpdateUserAvator(name string, avator string) {
|
||||||
user := &User{
|
user := &User{
|
||||||
Avator: avator,
|
Avator: avator,
|
||||||
@ -70,7 +88,7 @@ func FindIdleUser() User {
|
|||||||
defer assignMutex.Unlock()
|
defer assignMutex.Unlock()
|
||||||
|
|
||||||
var users []User
|
var users []User
|
||||||
DB.Where("name != ?", "admin").Order("id desc").Find(&users)
|
DB.Where("name != ? and `status` = 1 and `is_online` = 1", "admin").Order("id desc").Find(&users)
|
||||||
|
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
return User{}
|
return User{}
|
||||||
@ -104,9 +122,13 @@ func FindUserById(id interface{}) User {
|
|||||||
func DeleteUserById(id string) {
|
func DeleteUserById(id string) {
|
||||||
DB.Where("id = ?", id).Delete(User{})
|
DB.Where("id = ?", id).Delete(User{})
|
||||||
}
|
}
|
||||||
func FindUsers() []User {
|
func FindUsers(withoutUsername string) []User {
|
||||||
var users []User
|
var users []User
|
||||||
DB.Select("user.*,role.name role_name").Joins("left join user_role on user.id=user_role.user_id").Joins("left join role on user_role.role_id=role.id").Order("user.id desc").Find(&users)
|
if withoutUsername == "" {
|
||||||
|
DB.Find(&users)
|
||||||
|
} else {
|
||||||
|
DB.Where("name != ?", withoutUsername).Find(&users)
|
||||||
|
}
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
func FindUserRole(query interface{}, id interface{}) User {
|
func FindUserRole(query interface{}, id interface{}) User {
|
||||||
|
|||||||
@ -39,6 +39,11 @@ func FindVisitorByVistorId(visitorId string) Visitor {
|
|||||||
DB.Where("visitor_id = ?", visitorId).First(&v)
|
DB.Where("visitor_id = ?", visitorId).First(&v)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdatesVisitor(id uint, toId string) {
|
||||||
|
DB.Model(&Visitor{}).Where("id = ?", id).Update("to_id", toId)
|
||||||
|
}
|
||||||
|
|
||||||
func FindVisitors(page uint, pagesize uint) []Visitor {
|
func FindVisitors(page uint, pagesize uint) []Visitor {
|
||||||
offset := (page - 1) * pagesize
|
offset := (page - 1) * pagesize
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
@ -86,21 +91,21 @@ func UpdateVisitorKefu(visitorId string, kefuId string) {
|
|||||||
DB.Model(&visitor).Where("visitor_id = ?", visitorId).Update("to_id", kefuId)
|
DB.Model(&visitor).Where("visitor_id = ?", visitorId).Update("to_id", kefuId)
|
||||||
}
|
}
|
||||||
|
|
||||||
//查询条数
|
// 查询条数
|
||||||
func CountVisitors() uint {
|
func CountVisitors() uint {
|
||||||
var count uint
|
var count uint
|
||||||
DB.Model(&Visitor{}).Count(&count)
|
DB.Model(&Visitor{}).Count(&count)
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
//查询条数
|
// 查询条数
|
||||||
func CountVisitorsByKefuId(kefuId string) uint {
|
func CountVisitorsByKefuId(kefuId string) uint {
|
||||||
var count uint
|
var count uint
|
||||||
DB.Model(&Visitor{}).Where("to_id=?", kefuId).Count(&count)
|
DB.Model(&Visitor{}).Where("to_id=?", kefuId).Count(&count)
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
//查询每天条数
|
// 查询每天条数
|
||||||
type EveryDayNum struct {
|
type EveryDayNum struct {
|
||||||
Day string `json:"day"`
|
Day string `json:"day"`
|
||||||
Num int64 `json:"num"`
|
Num int64 `json:"num"`
|
||||||
|
|||||||
Binary file not shown.
@ -25,7 +25,7 @@ func InitApiRouter(engine *gin.RouterGroup) {
|
|||||||
engine.POST("/check", controller.LoginCheckPass)
|
engine.POST("/check", controller.LoginCheckPass)
|
||||||
|
|
||||||
engine.GET("/userinfo", middleware.JwtApiMiddleware, controller.GetKefuInfoAll)
|
engine.GET("/userinfo", middleware.JwtApiMiddleware, controller.GetKefuInfoAll)
|
||||||
engine.POST("/register", middleware.Ipblack, controller.PostKefuRegister)
|
engine.POST("/register", middleware.JwtApiMiddleware, controller.PostKefuRegister)
|
||||||
engine.POST("/install", controller.PostInstall)
|
engine.POST("/install", controller.PostInstall)
|
||||||
//前后聊天
|
//前后聊天
|
||||||
engine.GET("/ws_kefu", middleware.JwtApiMiddleware, ws.NewKefuServer)
|
engine.GET("/ws_kefu", middleware.JwtApiMiddleware, ws.NewKefuServer)
|
||||||
@ -50,6 +50,8 @@ func InitApiRouter(engine *gin.RouterGroup) {
|
|||||||
engine.POST("/kefuinfo", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuInfo)
|
engine.POST("/kefuinfo", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuInfo)
|
||||||
engine.DELETE("/kefuinfo", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.DeleteKefuInfo)
|
engine.DELETE("/kefuinfo", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.DeleteKefuInfo)
|
||||||
engine.GET("/kefulist", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetKefuList)
|
engine.GET("/kefulist", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetKefuList)
|
||||||
|
engine.POST("/kefu_status", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuStatus)
|
||||||
|
engine.POST("/admin_reset_pass", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostAdminResetPass)
|
||||||
engine.GET("/other_kefulist", middleware.JwtApiMiddleware, controller.GetOtherKefuList)
|
engine.GET("/other_kefulist", middleware.JwtApiMiddleware, controller.GetOtherKefuList)
|
||||||
engine.GET("/trans_kefu", middleware.JwtApiMiddleware, controller.PostTransKefu)
|
engine.GET("/trans_kefu", middleware.JwtApiMiddleware, controller.PostTransKefu)
|
||||||
engine.POST("/modifypass", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuPass)
|
engine.POST("/modifypass", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuPass)
|
||||||
|
|||||||
@ -726,6 +726,16 @@
|
|||||||
success: function(data) {
|
success: function(data) {
|
||||||
if(data.code==200 && data.msg=="ok"){
|
if(data.code==200 && data.msg=="ok"){
|
||||||
KEFU_ID=data.result;
|
KEFU_ID=data.result;
|
||||||
|
var oldResult = data.oldResult;
|
||||||
|
if (oldResult && oldResult != KEFU_ID) {
|
||||||
|
var oldKey = "visitor_" + oldResult;
|
||||||
|
var newKey = "visitor_" + KEFU_ID;
|
||||||
|
var oldCache = localStorage.getItem(oldKey);
|
||||||
|
if(oldCache) {
|
||||||
|
localStorage.setItem(newKey, oldCache);
|
||||||
|
localStorage.removeItem(oldKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
KEFU_ID="default";
|
KEFU_ID="default";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -121,6 +121,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
showRegHtml: false,
|
showRegHtml: false,
|
||||||
|
kefuInfo:{},
|
||||||
|
kefuList: [],
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
validatePasswordMatch(rule, value, callback) {
|
validatePasswordMatch(rule, value, callback) {
|
||||||
@ -190,31 +192,45 @@
|
|||||||
"nickname": this.form.nickname,
|
"nickname": this.form.nickname,
|
||||||
};
|
};
|
||||||
|
|
||||||
$.post("/aicss/register", data, (response) => {
|
$.ajax({
|
||||||
if (response.code === 200) {
|
url: "/aicss/register",
|
||||||
|
type: "POST",
|
||||||
|
data: data,
|
||||||
|
headers: {
|
||||||
|
"aicss-token": localStorage.getItem("aicss-token"),
|
||||||
|
"X-Client-Type": "web",
|
||||||
|
"X-Custom-Domain": window.location.host
|
||||||
|
},
|
||||||
|
success: (response) => {
|
||||||
|
if (response.code === 200) {
|
||||||
|
this.$message({
|
||||||
|
message: 'Account created successfully!',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
this.showRegHtml = false;
|
||||||
|
} else {
|
||||||
|
this.$message({
|
||||||
|
message: response.msg || 'Registration failed',
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
this.$message({
|
this.$message({
|
||||||
message: 'Account created successfully!',
|
message: 'Connection error',
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
this.showRegHtml = false;
|
|
||||||
} else {
|
|
||||||
this.$message({
|
|
||||||
message: response.msg || 'Registration failed',
|
|
||||||
type: 'error'
|
type: 'error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).fail(() => {
|
|
||||||
this.$message({
|
|
||||||
message: 'Connection error',
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function() {
|
created: function() {
|
||||||
if (top.location != location) {
|
if (top.location != location) {
|
||||||
top.location.href = location.href;
|
top.location.href = location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.getKefuInfo();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -115,6 +115,43 @@
|
|||||||
</div>
|
</div>
|
||||||
>
|
>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane v-if="kefuInfo.role === 1" label="Customer List">
|
||||||
|
<div class="profile-form">
|
||||||
|
<div style="margin-bottom: 20px;">
|
||||||
|
<el-button type="primary" icon="el-icon-plus" @click="openCreateKefu">添加客服</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="kefuList" style="width: 100%" stripe>
|
||||||
|
<el-table-column label="头像" width="80">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-avatar :size="40" :src="scope.row.avator"></el-avatar>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="name" label="账号"></el-table-column>
|
||||||
|
<el-table-column prop="nickname" label="昵称"></el-table-column>
|
||||||
|
<el-table-column label="在线状态" width="100">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag v-if="scope.row.is_online === 1" type="success" size="mini">在线</el-tag>
|
||||||
|
<el-tag v-else type="info" size="mini">离线</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="100">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-switch
|
||||||
|
v-model="scope.row.status"
|
||||||
|
:active-value="1"
|
||||||
|
:inactive-value="0"
|
||||||
|
@change="handleStatusChange(scope.row)">
|
||||||
|
</el-switch>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="150">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button size="mini" type="warning" @click="openResetPass(scope.row)">重置密码</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
<el-tab-pane v-if="kefuInfo.role === 1" label="System Configuration">
|
<el-tab-pane v-if="kefuInfo.role === 1" label="System Configuration">
|
||||||
<div class="profile-form" style="margin-top: 20px">
|
<div class="profile-form" style="margin-top: 20px">
|
||||||
<el-table
|
<el-table
|
||||||
@ -174,6 +211,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
|
<el-dialog title="创建客服" :visible.sync="createKefuDialog" width="400px">
|
||||||
|
<el-form :model="createKefuForm">
|
||||||
|
<el-form-item label="账号">
|
||||||
|
<el-input v-model="createKefuForm.username"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="昵称">
|
||||||
|
<el-input v-model="createKefuForm.nickname"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码">
|
||||||
|
<el-input type="password" v-model="createKefuForm.password" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="createKefuDialog = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="submitCreateKefu">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="重置密码" :visible.sync="resetPassDialog" width="400px">
|
||||||
|
<el-form :model="resetPassForm">
|
||||||
|
<el-form-item label="新密码">
|
||||||
|
<el-input type="password" v-model="resetPassForm.password" show-password></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="resetPassDialog = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="submitResetPass">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -25,6 +25,18 @@
|
|||||||
role_name:"",
|
role_name:"",
|
||||||
role_id:"",
|
role_id:"",
|
||||||
},
|
},
|
||||||
|
kefuList: [],
|
||||||
|
resetPassDialog: false,
|
||||||
|
createKefuDialog: false,
|
||||||
|
resetPassForm: {
|
||||||
|
username: "",
|
||||||
|
password: ""
|
||||||
|
},
|
||||||
|
createKefuForm: {
|
||||||
|
username: "",
|
||||||
|
nickname: "",
|
||||||
|
password: ""
|
||||||
|
},
|
||||||
|
|
||||||
avatarUrl:"",
|
avatarUrl:"",
|
||||||
chatEndpoint: "",
|
chatEndpoint: "",
|
||||||
@ -303,10 +315,73 @@ A: 因二次结算导致,系统将按最终结果扣回或补发。`}
|
|||||||
if(data.code==200 && data.result!=null){
|
if(data.code==200 && data.result!=null){
|
||||||
_this.kefuInfo=data.result;
|
_this.kefuInfo=data.result;
|
||||||
_this.chatEndpoint=window.location.origin + '/aicss/livechat?kefu_id='+data.result.username;
|
_this.chatEndpoint=window.location.origin + '/aicss/livechat?kefu_id='+data.result.username;
|
||||||
|
if (_this.kefuInfo.role === 1) {
|
||||||
|
_this.getKefuList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// Get Kefu List
|
||||||
|
getKefuList() {
|
||||||
|
let _this = this;
|
||||||
|
this.sendAjax("/aicss/kefulist", "get", {}, function(result) {
|
||||||
|
_this.kefuList = result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Toggle Status
|
||||||
|
handleStatusChange(row) {
|
||||||
|
let _this = this;
|
||||||
|
this.sendAjax("/kefu_status", "post", {
|
||||||
|
username: row.name,
|
||||||
|
status: row.status
|
||||||
|
}, function(result) {
|
||||||
|
_this.$message({
|
||||||
|
message: "Status updated",
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Open Reset Password Dialog
|
||||||
|
openResetPass(row) {
|
||||||
|
this.resetPassForm.username = row.name;
|
||||||
|
this.resetPassForm.password = "";
|
||||||
|
this.resetPassDialog = true;
|
||||||
|
},
|
||||||
|
// Submit Reset Password
|
||||||
|
submitResetPass() {
|
||||||
|
let _this = this;
|
||||||
|
if (this.resetPassForm.password.length < 2) {
|
||||||
|
this.$message.error("Password too short");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sendAjax("/admin_reset_pass", "post", this.resetPassForm, function(result) {
|
||||||
|
_this.$message.success("Password reset successfully");
|
||||||
|
_this.resetPassDialog = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Open Create User Dialog
|
||||||
|
openCreateKefu() {
|
||||||
|
this.createKefuForm = {
|
||||||
|
username: "",
|
||||||
|
nickname: "",
|
||||||
|
password: ""
|
||||||
|
};
|
||||||
|
this.createKefuDialog = true;
|
||||||
|
},
|
||||||
|
// Submit Create User
|
||||||
|
submitCreateKefu() {
|
||||||
|
let _this = this;
|
||||||
|
if (!this.createKefuForm.username || !this.createKefuForm.password) {
|
||||||
|
this.$message.error("Username and password required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.sendAjax("/register", "post", this.createKefuForm, function(result) {
|
||||||
|
_this.$message.success("User created successfully");
|
||||||
|
_this.createKefuDialog = false;
|
||||||
|
_this.getKefuList();
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted:function(){
|
mounted:function(){
|
||||||
@ -325,4 +400,5 @@ A: 因二次结算导致,系统将按最终结果扣回或补发。`}
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -1,16 +1,13 @@
|
|||||||
package tmpl
|
package tmpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai-css/tools"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 登陆界面
|
// 登陆界面
|
||||||
func PageLogin(c *gin.Context) {
|
func PageLogin(c *gin.Context) {
|
||||||
if noExist, _ := tools.IsFileNotExist("./install.lock"); noExist {
|
|
||||||
c.Redirect(302, "/install")
|
|
||||||
}
|
|
||||||
c.HTML(http.StatusOK, "login.html", nil)
|
c.HTML(http.StatusOK, "login.html", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ func NewKefuServer(c *gin.Context) {
|
|||||||
kefu.Avator = kefuInfo.Avator
|
kefu.Avator = kefuInfo.Avator
|
||||||
kefu.Conn = conn
|
kefu.Conn = conn
|
||||||
AddKefuToList(&kefu)
|
AddKefuToList(&kefu)
|
||||||
|
models.UpdateUserIsOnline(kefuInfo.Name, 1)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
//接受消息
|
//接受消息
|
||||||
@ -43,6 +44,7 @@ func NewKefuServer(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("ws/user.go ", err)
|
log.Println("ws/user.go ", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
models.UpdateUserIsOnline(kefuInfo.Name, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +116,7 @@ func SendPingToKefuClient() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("定时发送ping给客服,失败", err.Error())
|
log.Println("定时发送ping给客服,失败", err.Error())
|
||||||
delete(KefuList, kefuId)
|
delete(KefuList, kefuId)
|
||||||
|
models.UpdateUserIsOnline(kefuId, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
ws/ws.go
3
ws/ws.go
@ -64,7 +64,7 @@ var message = make(chan *Message, 10)
|
|||||||
var upgrader = websocket.Upgrader{}
|
var upgrader = websocket.Upgrader{}
|
||||||
var Mux sync.RWMutex
|
var Mux sync.RWMutex
|
||||||
|
|
||||||
func init() {
|
func StartUpdateVisitorStatusCron() {
|
||||||
upgrader = websocket.Upgrader{
|
upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 1024,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 1024,
|
||||||
@ -75,6 +75,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
go UpdateVisitorStatusCron()
|
go UpdateVisitorStatusCron()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendServerJiang(title string, content string, domain string) string {
|
func SendServerJiang(title string, content string, domain string) string {
|
||||||
noticeServerJiang, err := strconv.ParseBool(models.FindConfig("NoticeServerJiang"))
|
noticeServerJiang, err := strconv.ParseBool(models.FindConfig("NoticeServerJiang"))
|
||||||
serverJiangAPI := models.FindConfig("ServerJiangAPI")
|
serverJiangAPI := models.FindConfig("ServerJiangAPI")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user