init project
8
Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM golang:alpine
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct
|
||||
VOLUME ["/app/config"]
|
||||
RUN go build go-fly.go
|
||||
EXPOSE 8081
|
||||
CMD ["/app/go-fly","server"]
|
||||
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
71
cmd/install.go
Normal file
@ -0,0 +1,71 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Initialize database and import data", // More precise description
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
install()
|
||||
},
|
||||
}
|
||||
|
||||
func install() {
|
||||
// Check if already installed
|
||||
if ok, _ := tools.IsFileNotExist("./install.lock"); !ok {
|
||||
log.Println("Please remove ./install.lock file to reinstall")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Verify required files exist
|
||||
sqlFile := "import.sql"
|
||||
dataExists, _ := tools.IsFileExist(sqlFile)
|
||||
if !dataExists {
|
||||
log.Println("Configuration file config/mysql.json or database import file import.sql not found")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Execute SQL statements
|
||||
sqls, err := os.ReadFile(sqlFile)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read SQL file %s: %v\n", sqlFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
sqlArr := strings.Split(string(sqls), ";")
|
||||
for _, sql := range sqlArr {
|
||||
sql = strings.TrimSpace(sql)
|
||||
if sql == "" {
|
||||
continue
|
||||
}
|
||||
err := models.Execute(sql)
|
||||
if err != nil {
|
||||
log.Printf("SQL execution failed: %s\nError: %v\n", sql, err)
|
||||
log.Println("Database initialization failed - please check SQL statements")
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Printf("Executed successfully: %s\n", sql)
|
||||
}
|
||||
|
||||
// Create installation lock file
|
||||
installFile, err := os.OpenFile("./install.lock", os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Printf("Failed to create lock file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer installFile.Close()
|
||||
|
||||
_, err = installFile.WriteString("gofly live chat installation complete")
|
||||
if err != nil {
|
||||
log.Printf("Failed to write lock file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Println("Database initialization completed successfully")
|
||||
}
|
||||
37
cmd/root.go
Normal file
@ -0,0 +1,37 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "go-fly",
|
||||
Short: "go-fly",
|
||||
Long: `简洁快速的GO语言WEB在线客服 https://gofly.sopans.com`,
|
||||
Args: args,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func args(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
|
||||
return errors.New("至少需要一个参数!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
func init() {
|
||||
rootCmd.AddCommand(serverCmd)
|
||||
rootCmd.AddCommand(installCmd)
|
||||
rootCmd.AddCommand(stopCmd)
|
||||
}
|
||||
77
cmd/server.go
Normal file
@ -0,0 +1,77 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zh-five/xdaemon"
|
||||
"goflylivechat/middleware"
|
||||
"goflylivechat/router"
|
||||
"goflylivechat/tools"
|
||||
"goflylivechat/ws"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
port string
|
||||
daemon bool
|
||||
)
|
||||
var serverCmd = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "启动http服务",
|
||||
Example: "gofly server -p 8082",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
run()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
serverCmd.PersistentFlags().StringVarP(&port, "port", "p", "8081", "监听端口号")
|
||||
serverCmd.PersistentFlags().BoolVarP(&daemon, "daemon", "d", false, "是否为守护进程模式")
|
||||
}
|
||||
func run() {
|
||||
if daemon == true {
|
||||
logFilePath := ""
|
||||
if dir, err := os.Getwd(); err == nil {
|
||||
logFilePath = dir + "/logs/"
|
||||
}
|
||||
_, err := os.Stat(logFilePath)
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(logFilePath, 0777); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}
|
||||
d := xdaemon.NewDaemon(logFilePath + "go-fly.log")
|
||||
d.MaxCount = 10
|
||||
d.Run()
|
||||
}
|
||||
|
||||
baseServer := "0.0.0.0:" + port
|
||||
log.Println("start server...\r\ngo:http://" + baseServer)
|
||||
tools.Logger().Println("start server...\r\ngo:http://" + baseServer)
|
||||
|
||||
engine := gin.Default()
|
||||
engine.LoadHTMLGlob("static/templates/*")
|
||||
engine.Static("/assets", "./static")
|
||||
engine.Static("/static", "./static")
|
||||
engine.Use(tools.Session("gofly"))
|
||||
engine.Use(middleware.CrossSite)
|
||||
//性能监控
|
||||
//pprof.Register(engine)
|
||||
|
||||
//记录日志
|
||||
engine.Use(middleware.NewMidLogger())
|
||||
router.InitViewRouter(engine)
|
||||
router.InitApiRouter(engine)
|
||||
//记录pid
|
||||
os.WriteFile("gofly.sock", []byte(fmt.Sprintf("%d,%d", os.Getppid(), os.Getpid())), 0666)
|
||||
//限流类
|
||||
tools.NewLimitQueue()
|
||||
//清理
|
||||
ws.CleanVisitorExpire()
|
||||
//后端websocket
|
||||
go ws.WsServerBackend()
|
||||
|
||||
engine.Run(baseServer)
|
||||
}
|
||||
30
cmd/stop.go
Normal file
@ -0,0 +1,30 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "停止http服务",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pids, err := ioutil.ReadFile("gofly.sock")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pidSlice := strings.Split(string(pids), ",")
|
||||
var command *exec.Cmd
|
||||
for _, pid := range pidSlice {
|
||||
if runtime.GOOS == "windows" {
|
||||
command = exec.Command("taskkill.exe", "/f", "/pid", pid)
|
||||
} else {
|
||||
command = exec.Command("kill", pid)
|
||||
}
|
||||
command.Start()
|
||||
}
|
||||
},
|
||||
}
|
||||
12
common/common.go
Normal file
@ -0,0 +1,12 @@
|
||||
package common
|
||||
|
||||
var (
|
||||
PageSize uint = 10
|
||||
VisitorPageSize uint = 8
|
||||
Version string = "0.3.9"
|
||||
VisitorExpire float64 = 600
|
||||
Upload string = "static/upload/"
|
||||
Dir string = "config/"
|
||||
MysqlConf string = Dir + "mysql.json"
|
||||
IsCompireTemplate bool = false //是否编译静态模板到二进制
|
||||
)
|
||||
29
common/config.go
Normal file
@ -0,0 +1,29 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"goflylivechat/tools"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type Mysql struct {
|
||||
Server string
|
||||
Port string
|
||||
Database string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
func GetMysqlConf() *Mysql {
|
||||
var mysql = &Mysql{}
|
||||
isExist, _ := tools.IsFileExist(MysqlConf)
|
||||
if !isExist {
|
||||
return mysql
|
||||
}
|
||||
info, err := ioutil.ReadFile(MysqlConf)
|
||||
if err != nil {
|
||||
return mysql
|
||||
}
|
||||
err = json.Unmarshal(info, mysql)
|
||||
return mysql
|
||||
}
|
||||
BIN
config/city.free.ipdb
Normal file
7
config/mysql.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"Server":"localhost",
|
||||
"Port":"3306",
|
||||
"Database":"goflychat",
|
||||
"Username":"goflychat",
|
||||
"Password":"goflychat"
|
||||
}
|
||||
52
controller/about.go
Normal file
@ -0,0 +1,52 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
)
|
||||
|
||||
func GetAbout(c *gin.Context) {
|
||||
page := c.Query("page")
|
||||
if page == "" {
|
||||
page = "index"
|
||||
}
|
||||
about := models.FindAboutByPage(page)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": about,
|
||||
})
|
||||
}
|
||||
func GetAbouts(c *gin.Context) {
|
||||
about := models.FindAbouts()
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": about,
|
||||
})
|
||||
}
|
||||
func PostAbout(c *gin.Context) {
|
||||
title_cn := c.PostForm("title_cn")
|
||||
title_en := c.PostForm("title_en")
|
||||
keywords_cn := c.PostForm("keywords_cn")
|
||||
keywords_en := c.PostForm("keywords_en")
|
||||
desc_cn := c.PostForm("desc_cn")
|
||||
desc_en := c.PostForm("desc_en")
|
||||
css_js := c.PostForm("css_js")
|
||||
html_cn := c.PostForm("html_cn")
|
||||
html_en := c.PostForm("html_en")
|
||||
if title_cn == "" || title_en == "" || html_cn == "" || html_en == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.UpdateAbout("index", title_cn, title_en, keywords_cn, keywords_en, desc_cn, desc_en, css_js, html_cn, html_en)
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": "",
|
||||
})
|
||||
}
|
||||
44
controller/captcha.go
Normal file
@ -0,0 +1,44 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/dchest/captcha"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetCaptcha(c *gin.Context) {
|
||||
l := captcha.DefaultLen
|
||||
w, h := 107, 36
|
||||
captchaId := captcha.NewLen(l)
|
||||
session := sessions.Default(c)
|
||||
session.Set("captcha", captchaId)
|
||||
_ = session.Save()
|
||||
_ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h)
|
||||
}
|
||||
|
||||
func Serve(w http.ResponseWriter, r *http.Request, id, ext, lang string, download bool, width, height int) error {
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
w.Header().Set("Expires", "0")
|
||||
|
||||
var content bytes.Buffer
|
||||
switch ext {
|
||||
case ".png":
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_ = captcha.WriteImage(&content, id, width, height)
|
||||
case ".wav":
|
||||
w.Header().Set("Content-Type", "audio/x-wav")
|
||||
_ = captcha.WriteAudio(&content, id, lang)
|
||||
default:
|
||||
return captcha.ErrNotFound
|
||||
}
|
||||
|
||||
if download {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes()))
|
||||
return nil
|
||||
}
|
||||
36
controller/chart.go
Normal file
@ -0,0 +1,36 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetChartStatistic(c *gin.Context) {
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
|
||||
dayNumMap := make(map[string]string)
|
||||
result := models.CountVisitorsEveryDay(kefuName.(string))
|
||||
for _, item := range result {
|
||||
dayNumMap[item.Day] = tools.Int2Str(item.Num)
|
||||
}
|
||||
|
||||
nowTime := time.Now()
|
||||
list := make([]map[string]string, 0)
|
||||
for i := 0; i > -46; i-- {
|
||||
getTime := nowTime.AddDate(0, 0, i) //年,月,日 获取一天前的时间
|
||||
resTime := getTime.Format("06-01-02") //获取的时间的格式
|
||||
tmp := make(map[string]string)
|
||||
tmp["day"] = resTime
|
||||
tmp["num"] = dayNumMap[resTime]
|
||||
list = append(list, tmp)
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": list,
|
||||
})
|
||||
|
||||
}
|
||||
14
controller/index.go
Normal file
@ -0,0 +1,14 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
)
|
||||
|
||||
func Index(c *gin.Context) {
|
||||
jump := models.FindConfig("JumpLang")
|
||||
if jump != "cn" {
|
||||
jump = "en"
|
||||
}
|
||||
c.Redirect(302, "/index_"+jump)
|
||||
}
|
||||
66
controller/ip.go
Normal file
@ -0,0 +1,66 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/common"
|
||||
"goflylivechat/models"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func PostIpblack(c *gin.Context) {
|
||||
ip := c.PostForm("ip")
|
||||
if ip == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "请输入IP!",
|
||||
})
|
||||
return
|
||||
}
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
models.CreateIpblack(ip, kefuId.(string))
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "添加黑名单成功!",
|
||||
})
|
||||
}
|
||||
func DelIpblack(c *gin.Context) {
|
||||
ip := c.Query("ip")
|
||||
if ip == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "请输入IP!",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.DeleteIpblackByIp(ip)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "删除黑名单成功!",
|
||||
})
|
||||
}
|
||||
func GetIpblacks(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.Query("page"))
|
||||
if page == 0 {
|
||||
page = 1
|
||||
}
|
||||
count := models.CountIps(nil, nil)
|
||||
list := models.FindIps(nil, nil, uint(page), common.VisitorPageSize)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": gin.H{
|
||||
"list": list,
|
||||
"count": count,
|
||||
"pagesize": common.PageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
func GetIpblacksByKefuId(c *gin.Context) {
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
list := models.FindIpsByKefuId(kefuId.(string))
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": list,
|
||||
})
|
||||
}
|
||||
243
controller/kefu.go
Normal file
@ -0,0 +1,243 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"goflylivechat/ws"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func PostKefuAvator(c *gin.Context) {
|
||||
|
||||
avator := c.PostForm("avator")
|
||||
if avator == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "不能为空",
|
||||
"result": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
models.UpdateUserAvator(kefuName.(string), avator)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": "",
|
||||
})
|
||||
}
|
||||
func PostKefuPass(c *gin.Context) {
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
newPass := c.PostForm("new_pass")
|
||||
confirmNewPass := c.PostForm("confirm_new_pass")
|
||||
old_pass := c.PostForm("old_pass")
|
||||
if newPass != confirmNewPass {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "密码不一致",
|
||||
"result": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
user := models.FindUser(kefuName.(string))
|
||||
if user.Password != tools.Md5(old_pass) {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "旧密码不正确",
|
||||
"result": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.UpdateUserPass(kefuName.(string), tools.Md5(newPass))
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": "",
|
||||
})
|
||||
}
|
||||
func PostKefuClient(c *gin.Context) {
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
clientId := c.PostForm("client_id")
|
||||
|
||||
if clientId == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "client_id不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.CreateUserClient(kefuName.(string), clientId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": "",
|
||||
})
|
||||
}
|
||||
func GetKefuInfo(c *gin.Context) {
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
user := models.FindUser(kefuName.(string))
|
||||
info := make(map[string]interface{})
|
||||
info["avator"] = user.Avator
|
||||
info["username"] = user.Name
|
||||
info["nickname"] = user.Nickname
|
||||
info["uid"] = user.ID
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": info,
|
||||
})
|
||||
}
|
||||
func GetKefuInfoAll(c *gin.Context) {
|
||||
id, _ := c.Get("kefu_id")
|
||||
userinfo := models.FindUserRole("user.avator,user.name,user.id, role.name role_name", id)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "验证成功",
|
||||
"result": userinfo,
|
||||
})
|
||||
}
|
||||
func GetOtherKefuList(c *gin.Context) {
|
||||
idStr, _ := c.Get("kefu_id")
|
||||
id := idStr.(float64)
|
||||
result := make([]interface{}, 0)
|
||||
ws.SendPingToKefuClient()
|
||||
kefus := models.FindUsers()
|
||||
for _, kefu := range kefus {
|
||||
if uint(id) == kefu.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
item := make(map[string]interface{})
|
||||
item["name"] = kefu.Name
|
||||
item["nickname"] = kefu.Nickname
|
||||
item["avator"] = kefu.Avator
|
||||
item["status"] = "offline"
|
||||
kefu, ok := ws.KefuList[kefu.Name]
|
||||
if ok && kefu != nil {
|
||||
item["status"] = "online"
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": result,
|
||||
})
|
||||
}
|
||||
func PostTransKefu(c *gin.Context) {
|
||||
kefuId := c.Query("kefu_id")
|
||||
visitorId := c.Query("visitor_id")
|
||||
curKefuId, _ := c.Get("kefu_name")
|
||||
user := models.FindUser(kefuId)
|
||||
visitor := models.FindVisitorByVistorId(visitorId)
|
||||
if user.Name == "" || visitor.Name == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "访客或客服不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.UpdateVisitorKefu(visitorId, kefuId)
|
||||
ws.UpdateVisitorUser(visitorId, kefuId)
|
||||
go ws.VisitorOnline(kefuId, visitor)
|
||||
go ws.VisitorOffline(curKefuId.(string), visitor.VisitorId, visitor.Name)
|
||||
go ws.VisitorNotice(visitor.VisitorId, "客服转接到"+user.Nickname)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "转移成功",
|
||||
})
|
||||
}
|
||||
func GetKefuInfoSetting(c *gin.Context) {
|
||||
kefuId := c.Query("kefu_id")
|
||||
user := models.FindUserById(kefuId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": user,
|
||||
})
|
||||
}
|
||||
func PostKefuRegister(c *gin.Context) {
|
||||
name := c.PostForm("username")
|
||||
password := c.PostForm("password")
|
||||
nickname := c.PostForm("nickname")
|
||||
avatar := "/static/images/4.jpg"
|
||||
|
||||
if name == "" || password == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 400,
|
||||
"msg": "All fields are required",
|
||||
"result": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
existingUser := models.FindUser(name)
|
||||
if existingUser.Name != "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 409,
|
||||
"msg": "Username already exists",
|
||||
"result": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
userID := models.CreateUser(name, tools.Md5(password), avatar, nickname)
|
||||
if userID == 0 {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"code": 500,
|
||||
"msg": "Registration Failed",
|
||||
"result": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": 200,
|
||||
"msg": "Registration successful",
|
||||
"result": gin.H{
|
||||
"user_id": userID,
|
||||
},
|
||||
})
|
||||
}
|
||||
func PostKefuInfo(c *gin.Context) {
|
||||
name, _ := c.Get("kefu_name")
|
||||
password := c.PostForm("password")
|
||||
avator := c.PostForm("avator")
|
||||
nickname := c.PostForm("nickname")
|
||||
if password != "" {
|
||||
password = tools.Md5(password)
|
||||
}
|
||||
if name == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "客服账号不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.UpdateUser(name.(string), password, avator, nickname)
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": "",
|
||||
})
|
||||
}
|
||||
func GetKefuList(c *gin.Context) {
|
||||
users := models.FindUsers()
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "获取成功",
|
||||
"result": users,
|
||||
})
|
||||
}
|
||||
func DeleteKefuInfo(c *gin.Context) {
|
||||
kefuId := c.Query("id")
|
||||
models.DeleteUserById(kefuId)
|
||||
models.DeleteRoleByUserId(kefuId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "删除成功",
|
||||
"result": "",
|
||||
})
|
||||
}
|
||||
62
controller/login.go
Normal file
@ -0,0 +1,62 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @Summary User Authentication API
|
||||
// @Description Validates user credentials and returns access token
|
||||
// @Tags Authentication
|
||||
// @Produce json
|
||||
// @Accept multipart/form-data
|
||||
// @Param username formData string true "Registered username"
|
||||
// @Param password formData string true "Account password"
|
||||
// @Param type formData string true "Auth type (e.g., 'admin' or 'user')"
|
||||
// @Success 200 {object} Response
|
||||
// @Failure 401 {object} Response
|
||||
// @Failure 500 {object} Response
|
||||
// @Router /check [post]
|
||||
func LoginCheckPass(c *gin.Context) {
|
||||
password := c.PostForm("password")
|
||||
username := c.PostForm("username")
|
||||
info := models.FindUser(username)
|
||||
|
||||
// Authentication failed case
|
||||
if info.Name == "" || info.Password != tools.Md5(password) {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 401,
|
||||
"message": "Incorrect username or password", // User-friendly message
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare user session data
|
||||
userinfo := map[string]interface{}{
|
||||
"kefu_name": info.Name,
|
||||
"kefu_id": info.ID,
|
||||
"create_time": time.Now().Unix(),
|
||||
}
|
||||
|
||||
// Token generation
|
||||
token, err := tools.MakeToken(userinfo)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 500,
|
||||
"message": "Login temporarily unavailable",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Successful response
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"message": "Login successful",
|
||||
"result": gin.H{
|
||||
"token": token,
|
||||
"created_at": userinfo["create_time"],
|
||||
},
|
||||
})
|
||||
}
|
||||
113
controller/main.go
Normal file
@ -0,0 +1,113 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jinzhu/gorm"
|
||||
"goflylivechat/common"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"goflylivechat/ws"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PostInstall(c *gin.Context) {
|
||||
notExist, _ := tools.IsFileNotExist("./install.lock")
|
||||
if !notExist {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "系统已经安装过了",
|
||||
})
|
||||
return
|
||||
}
|
||||
server := c.PostForm("server")
|
||||
port := c.PostForm("port")
|
||||
database := c.PostForm("database")
|
||||
username := c.PostForm("username")
|
||||
password := c.PostForm("password")
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, server, port, database)
|
||||
_, err := gorm.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
tools.Logger().Println(err)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "数据库连接失败:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
isExist, _ := tools.IsFileExist(common.Dir)
|
||||
if !isExist {
|
||||
os.Mkdir(common.Dir, os.ModePerm)
|
||||
}
|
||||
fileConfig := common.MysqlConf
|
||||
file, _ := os.OpenFile(fileConfig, os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||
|
||||
format := `{
|
||||
"Server":"%s",
|
||||
"Port":"%s",
|
||||
"Database":"%s",
|
||||
"Username":"%s",
|
||||
"Password":"%s"
|
||||
}
|
||||
`
|
||||
data := fmt.Sprintf(format, server, port, database, username, password)
|
||||
file.WriteString(data)
|
||||
models.Connect()
|
||||
installFile, _ := os.OpenFile("./install.lock", os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||
installFile.WriteString("gofly live chat")
|
||||
ok, err := install()
|
||||
if !ok {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "安装成功",
|
||||
})
|
||||
}
|
||||
func install() (bool, error) {
|
||||
sqlFile := common.Dir + "go-fly.sql"
|
||||
isExit, _ := tools.IsFileExist(common.MysqlConf)
|
||||
dataExit, _ := tools.IsFileExist(sqlFile)
|
||||
if !isExit || !dataExit {
|
||||
return false, errors.New("config/mysql.json 数据库配置文件或者数据库文件go-fly.sql不存在")
|
||||
}
|
||||
sqls, _ := ioutil.ReadFile(sqlFile)
|
||||
sqlArr := strings.Split(string(sqls), "|")
|
||||
for _, sql := range sqlArr {
|
||||
if sql == "" {
|
||||
continue
|
||||
}
|
||||
err := models.Execute(sql)
|
||||
if err == nil {
|
||||
log.Println(sql, "\t success!")
|
||||
} else {
|
||||
log.Println(sql, err, "\t failed!")
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func GetStatistics(c *gin.Context) {
|
||||
visitors := models.CountVisitors()
|
||||
message := models.CountMessage(nil, nil)
|
||||
session := len(ws.ClientList)
|
||||
kefuNum := 0
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": gin.H{
|
||||
"visitors": visitors,
|
||||
"message": message,
|
||||
"session": session + kefuNum,
|
||||
},
|
||||
})
|
||||
}
|
||||
353
controller/message.go
Normal file
@ -0,0 +1,353 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"goflylivechat/common"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"goflylivechat/ws"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SendMessageV2(c *gin.Context) {
|
||||
fromId := c.PostForm("from_id")
|
||||
toId := c.PostForm("to_id")
|
||||
content := c.PostForm("content")
|
||||
cType := c.PostForm("type")
|
||||
if content == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "内容不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
//限流
|
||||
if !tools.LimitFreqSingle("sendmessage:"+c.ClientIP(), 1, 2) {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": c.ClientIP() + "发送频率过快",
|
||||
})
|
||||
return
|
||||
}
|
||||
var kefuInfo models.User
|
||||
var vistorInfo models.Visitor
|
||||
if cType == "kefu" {
|
||||
kefuInfo = models.FindUser(fromId)
|
||||
vistorInfo = models.FindVisitorByVistorId(toId)
|
||||
} else if cType == "visitor" {
|
||||
vistorInfo = models.FindVisitorByVistorId(fromId)
|
||||
kefuInfo = models.FindUser(toId)
|
||||
}
|
||||
|
||||
if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, content, cType)
|
||||
//var msg TypeMessage
|
||||
if cType == "kefu" {
|
||||
guest, ok := ws.ClientList[vistorInfo.VisitorId]
|
||||
|
||||
if guest != nil && ok {
|
||||
ws.VisitorMessage(vistorInfo.VisitorId, content, kefuInfo)
|
||||
}
|
||||
ws.KefuMessage(vistorInfo.VisitorId, content, kefuInfo)
|
||||
//msg = TypeMessage{
|
||||
// Type: "message",
|
||||
// Data: ws.ClientMessage{
|
||||
// Name: kefuInfo.Nickname,
|
||||
// Avator: kefuInfo.Avator,
|
||||
// Id: vistorInfo.VisitorId,
|
||||
// Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||
// ToId: vistorInfo.VisitorId,
|
||||
// Content: content,
|
||||
// IsKefu: "yes",
|
||||
// },
|
||||
//}
|
||||
//str2, _ := json.Marshal(msg)
|
||||
//ws.OneKefuMessage(kefuInfo.Name, str2)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
if cType == "visitor" {
|
||||
guest, ok := ws.ClientList[vistorInfo.VisitorId]
|
||||
if ok && guest != nil {
|
||||
guest.UpdateTime = time.Now()
|
||||
}
|
||||
//kefuConns, ok := ws.KefuList[kefuInfo.Name]
|
||||
//if kefuConns == nil || !ok {
|
||||
// c.JSON(200, gin.H{
|
||||
// "code": 200,
|
||||
// "msg": "ok",
|
||||
// })
|
||||
// return
|
||||
//}
|
||||
msg := ws.TypeMessage{
|
||||
Type: "message",
|
||||
Data: ws.ClientMessage{
|
||||
Avator: vistorInfo.Avator,
|
||||
Id: vistorInfo.VisitorId,
|
||||
Name: vistorInfo.Name,
|
||||
ToId: kefuInfo.Name,
|
||||
Content: content,
|
||||
Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||
IsKefu: "no",
|
||||
},
|
||||
}
|
||||
str, _ := json.Marshal(msg)
|
||||
ws.OneKefuMessage(kefuInfo.Name, str)
|
||||
//ws.KefuMessage(vistorInfo.VisitorId, content, kefuInfo)
|
||||
kefu, ok := ws.KefuList[kefuInfo.Name]
|
||||
if !ok || kefu == nil {
|
||||
go SendNoticeEmail(content+"|"+vistorInfo.Name, content)
|
||||
}
|
||||
go ws.VisitorAutoReply(vistorInfo, kefuInfo, content)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func SendKefuMessage(c *gin.Context) {
|
||||
fromId, _ := c.Get("kefu_name")
|
||||
toId := c.PostForm("to_id")
|
||||
content := c.PostForm("content")
|
||||
cType := c.PostForm("type")
|
||||
if content == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "内容不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
//限流
|
||||
if !tools.LimitFreqSingle("sendmessage:"+c.ClientIP(), 1, 2) {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": c.ClientIP() + "发送频率过快",
|
||||
})
|
||||
return
|
||||
}
|
||||
var kefuInfo models.User
|
||||
var vistorInfo models.Visitor
|
||||
kefuInfo = models.FindUser(fromId.(string))
|
||||
vistorInfo = models.FindVisitorByVistorId(toId)
|
||||
|
||||
if kefuInfo.ID == 0 || vistorInfo.ID == 0 {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
models.CreateMessage(kefuInfo.Name, vistorInfo.VisitorId, content, cType)
|
||||
//var msg TypeMessage
|
||||
|
||||
guest, ok := ws.ClientList[vistorInfo.VisitorId]
|
||||
|
||||
if guest != nil && ok {
|
||||
ws.VisitorMessage(vistorInfo.VisitorId, content, kefuInfo)
|
||||
}
|
||||
ws.KefuMessage(vistorInfo.VisitorId, content, kefuInfo)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
|
||||
}
|
||||
func SendVisitorNotice(c *gin.Context) {
|
||||
notice := c.Query("msg")
|
||||
if notice == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "msg不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
msg := ws.TypeMessage{
|
||||
Type: "notice",
|
||||
Data: notice,
|
||||
}
|
||||
str, _ := json.Marshal(msg)
|
||||
for _, visitor := range ws.ClientList {
|
||||
visitor.Conn.WriteMessage(websocket.TextMessage, str)
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
func SendCloseMessageV2(c *gin.Context) {
|
||||
visitorId := c.Query("visitor_id")
|
||||
if visitorId == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "visitor_id不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
oldUser, ok := ws.ClientList[visitorId]
|
||||
if oldUser != nil || ok {
|
||||
msg := ws.TypeMessage{
|
||||
Type: "force_close",
|
||||
Data: visitorId,
|
||||
}
|
||||
str, _ := json.Marshal(msg)
|
||||
err := oldUser.Conn.WriteMessage(websocket.TextMessage, str)
|
||||
oldUser.Conn.Close()
|
||||
delete(ws.ClientList, visitorId)
|
||||
tools.Logger().Println("close_message", oldUser, err)
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
func UploadImg(c *gin.Context) {
|
||||
f, err := c.FormFile("imgfile")
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "上传失败!",
|
||||
})
|
||||
return
|
||||
} else {
|
||||
|
||||
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||
if fileExt != ".png" && fileExt != ".jpg" && fileExt != ".gif" && fileExt != ".jpeg" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "上传失败!只允许png,jpg,gif,jpeg文件",
|
||||
})
|
||||
return
|
||||
}
|
||||
isMainUploadExist, _ := tools.IsFileExist(common.Upload)
|
||||
if !isMainUploadExist {
|
||||
os.Mkdir(common.Upload, os.ModePerm)
|
||||
}
|
||||
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||
isExist, _ := tools.IsFileExist(fildDir)
|
||||
if !isExist {
|
||||
os.Mkdir(fildDir, os.ModePerm)
|
||||
}
|
||||
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||
c.SaveUploadedFile(f, filepath)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "上传成功!",
|
||||
"result": gin.H{
|
||||
"path": filepath,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
func UploadFile(c *gin.Context) {
|
||||
f, err := c.FormFile("realfile")
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "上传失败!",
|
||||
})
|
||||
return
|
||||
} else {
|
||||
|
||||
fileExt := strings.ToLower(path.Ext(f.Filename))
|
||||
if f.Size >= 90*1024*1024 {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "上传失败!不允许超过90M",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
fileName := tools.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
|
||||
fildDir := fmt.Sprintf("%s%d%s/", common.Upload, time.Now().Year(), time.Now().Month().String())
|
||||
isExist, _ := tools.IsFileExist(fildDir)
|
||||
if !isExist {
|
||||
os.Mkdir(fildDir, os.ModePerm)
|
||||
}
|
||||
filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
|
||||
c.SaveUploadedFile(f, filepath)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "上传成功!",
|
||||
"result": gin.H{
|
||||
"path": filepath,
|
||||
"ext": fileExt,
|
||||
"size": f.Size,
|
||||
"name": f.Filename,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
func GetMessagesV2(c *gin.Context) {
|
||||
visitorId := c.Query("visitor_id")
|
||||
messages := models.FindMessageByVisitorId(visitorId)
|
||||
//result := make([]map[string]interface{}, 0)
|
||||
chatMessages := make([]ChatMessage, 0)
|
||||
var visitor models.Visitor
|
||||
var kefu models.User
|
||||
for _, message := range messages {
|
||||
//item := make(map[string]interface{})
|
||||
if visitor.Name == "" || kefu.Name == "" {
|
||||
kefu = models.FindUser(message.KefuId)
|
||||
visitor = models.FindVisitorByVistorId(message.VisitorId)
|
||||
}
|
||||
var chatMessage ChatMessage
|
||||
chatMessage.Time = message.CreatedAt.Format("2006-01-02 15:04:05")
|
||||
chatMessage.Content = message.Content
|
||||
chatMessage.MesType = message.MesType
|
||||
if message.MesType == "kefu" {
|
||||
chatMessage.Name = kefu.Nickname
|
||||
chatMessage.Avator = kefu.Avator
|
||||
} else {
|
||||
chatMessage.Name = visitor.Name
|
||||
chatMessage.Avator = visitor.Avator
|
||||
}
|
||||
chatMessages = append(chatMessages, chatMessage)
|
||||
}
|
||||
models.ReadMessageByVisitorId(visitorId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": chatMessages,
|
||||
})
|
||||
}
|
||||
func GetMessagespages(c *gin.Context) {
|
||||
visitorId := c.Query("visitor_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pagesize", "10"))
|
||||
if pageSize > 20 {
|
||||
pageSize = 20
|
||||
}
|
||||
count := models.CountMessage("visitor_id = ?", visitorId)
|
||||
list := models.FindMessageByPage(uint(page), uint(pageSize), "message.visitor_id = ?", visitorId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": gin.H{
|
||||
"count": count,
|
||||
"page": page,
|
||||
"list": list,
|
||||
"pagesize": pageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
32
controller/notice.go
Normal file
@ -0,0 +1,32 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
)
|
||||
|
||||
func GetNotice(c *gin.Context) {
|
||||
kefuId := c.Query("kefu_id")
|
||||
user := models.FindUser(kefuId)
|
||||
if user.ID == 0 {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "user not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
welcomeMessage := models.FindConfigByUserId(user.Name, "WelcomeMessage")
|
||||
offlineMessage := models.FindConfigByUserId(user.Name, "OfflineMessage")
|
||||
allNotice := models.FindConfigByUserId(user.Name, "AllNotice")
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": gin.H{
|
||||
"welcome": welcomeMessage.ConfValue,
|
||||
"offline": offlineMessage.ConfValue,
|
||||
"avatar": user.Avator,
|
||||
"nickname": user.Nickname,
|
||||
"allNotice": allNotice.ConfValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
123
controller/reply.go
Normal file
@ -0,0 +1,123 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
"log"
|
||||
)
|
||||
|
||||
type ReplyForm struct {
|
||||
GroupName string `form:"group_name" binding:"required"`
|
||||
}
|
||||
type ReplyContentForm struct {
|
||||
GroupId string `form:"group_id" binding:"required"`
|
||||
Content string `form:"content" binding:"required"`
|
||||
ItemName string `form:"item_name" binding:"required"`
|
||||
}
|
||||
|
||||
func GetReplys(c *gin.Context) {
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
log.Println(kefuId)
|
||||
res := models.FindReplyByUserId(kefuId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": res,
|
||||
})
|
||||
}
|
||||
func GetAutoReplys(c *gin.Context) {
|
||||
kefu_id := c.Query("kefu_id")
|
||||
res := models.FindReplyTitleByUserId(kefu_id)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": res,
|
||||
})
|
||||
}
|
||||
func PostReply(c *gin.Context) {
|
||||
var replyForm ReplyForm
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
err := c.Bind(&replyForm)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "error:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
models.CreateReplyGroup(replyForm.GroupName, kefuId.(string))
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
func PostReplyContent(c *gin.Context) {
|
||||
var replyContentForm ReplyContentForm
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
err := c.Bind(&replyContentForm)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"code": 200,
|
||||
"msg": "error:" + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
models.CreateReplyContent(replyContentForm.GroupId, kefuId.(string), replyContentForm.Content, replyContentForm.ItemName)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
func PostReplyContentSave(c *gin.Context) {
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
replyId := c.PostForm("reply_id")
|
||||
replyTitle := c.PostForm("reply_title")
|
||||
replyContent := c.PostForm("reply_content")
|
||||
if replyId == "" || replyTitle == "" || replyContent == "" {
|
||||
c.JSON(400, gin.H{
|
||||
"code": 200,
|
||||
"msg": "参数错误!",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.UpdateReplyContent(replyId, kefuId.(string), replyTitle, replyContent)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
func DelReplyContent(c *gin.Context) {
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
id := c.Query("id")
|
||||
models.DeleteReplyContent(id, kefuId.(string))
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
func DelReplyGroup(c *gin.Context) {
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
id := c.Query("id")
|
||||
models.DeleteReplyGroup(id, kefuId.(string))
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
func PostReplySearch(c *gin.Context) {
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
search := c.PostForm("search")
|
||||
if search == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
res := models.FindReplyBySearcch(kefuId, search)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": res,
|
||||
})
|
||||
}
|
||||
33
controller/response.go
Normal file
@ -0,0 +1,33 @@
|
||||
package controller
|
||||
|
||||
var (
|
||||
Port string
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
result interface{} `json:"result"`
|
||||
}
|
||||
type ChatMessage struct {
|
||||
Time string `json:"time"`
|
||||
Content string `json:"content"`
|
||||
MesType string `json:"mes_type"`
|
||||
Name string `json:"name"`
|
||||
Avator string `json:"avator"`
|
||||
}
|
||||
type VisitorOnline struct {
|
||||
Uid string `json:"uid"`
|
||||
Username string `json:"username"`
|
||||
Avator string `json:"avator"`
|
||||
LastMessage string `json:"last_message"`
|
||||
}
|
||||
type GetuiResponse struct {
|
||||
Code float64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
type VisitorExtra struct {
|
||||
VisitorName string `json:"visitorName"`
|
||||
VisitorAvatar string `json:"visitorAvatar"`
|
||||
}
|
||||
33
controller/role.go
Normal file
@ -0,0 +1,33 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
)
|
||||
|
||||
func GetRoleList(c *gin.Context) {
|
||||
roles := models.FindRoles()
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "获取成功",
|
||||
"result": roles,
|
||||
})
|
||||
}
|
||||
func PostRole(c *gin.Context) {
|
||||
roleId := c.PostForm("id")
|
||||
method := c.PostForm("method")
|
||||
name := c.PostForm("name")
|
||||
path := c.PostForm("path")
|
||||
if roleId == "" || method == "" || name == "" || path == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "参数不能为空",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.SaveRole(roleId, name, method, path)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "修改成功",
|
||||
})
|
||||
}
|
||||
44
controller/setting.go
Normal file
@ -0,0 +1,44 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
)
|
||||
|
||||
func GetConfigs(c *gin.Context) {
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
configs := models.FindConfigsByUserId(kefuName)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": configs,
|
||||
})
|
||||
}
|
||||
func GetConfig(c *gin.Context) {
|
||||
key := c.Query("key")
|
||||
config := models.FindConfig(key)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": config,
|
||||
})
|
||||
}
|
||||
func PostConfig(c *gin.Context) {
|
||||
key := c.PostForm("key")
|
||||
value := c.PostForm("value")
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
if key == "" || value == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
models.UpdateConfig(kefuName, key, value)
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": "",
|
||||
})
|
||||
}
|
||||
155
controller/shout.go
Normal file
@ -0,0 +1,155 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"goflylivechat/ws"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SendServerJiang(title string, content string, domain string) string {
|
||||
noticeServerJiang, err := strconv.ParseBool(models.FindConfig("NoticeServerJiang"))
|
||||
serverJiangAPI := models.FindConfig("ServerJiangAPI")
|
||||
if err != nil || !noticeServerJiang || serverJiangAPI == "" {
|
||||
log.Println("do not notice serverjiang:", serverJiangAPI, noticeServerJiang)
|
||||
return ""
|
||||
}
|
||||
sendStr := fmt.Sprintf("%s%s", title, content)
|
||||
desp := title + ":" + content + "[登录](http://" + domain + "/main)"
|
||||
url := serverJiangAPI + "?text=" + sendStr + "&desp=" + desp
|
||||
//log.Println(url)
|
||||
res := tools.Get(url)
|
||||
return res
|
||||
}
|
||||
func SendVisitorLoginNotice(kefuName, visitorName, avator, content, visitorId string) {
|
||||
if !tools.LimitFreqSingle("sendnotice:"+visitorId, 1, 120) {
|
||||
log.Println("SendVisitorLoginNotice limit")
|
||||
return
|
||||
}
|
||||
userInfo := make(map[string]string)
|
||||
userInfo["username"] = visitorName
|
||||
userInfo["avator"] = avator
|
||||
userInfo["content"] = content
|
||||
msg := ws.TypeMessage{
|
||||
Type: "notice",
|
||||
Data: userInfo,
|
||||
}
|
||||
str, _ := json.Marshal(msg)
|
||||
ws.OneKefuMessage(kefuName, str)
|
||||
}
|
||||
func SendNoticeEmail(username, msg string) {
|
||||
smtp := models.FindConfig("NoticeEmailSmtp")
|
||||
email := models.FindConfig("NoticeEmailAddress")
|
||||
password := models.FindConfig("NoticeEmailPassword")
|
||||
if smtp == "" || email == "" || password == "" {
|
||||
return
|
||||
}
|
||||
err := tools.SendSmtp(smtp, email, password, []string{email}, "[通知]"+username, msg)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
func SendAppGetuiPush(kefu string, title, content string) {
|
||||
token := models.FindConfig("GetuiToken")
|
||||
if token == "" {
|
||||
token = getGetuiToken()
|
||||
if token == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
format := `
|
||||
{
|
||||
"request_id":"%s",
|
||||
"settings":{
|
||||
"ttl":3600000
|
||||
},
|
||||
"audience":{
|
||||
"cid":[
|
||||
"%s"
|
||||
]
|
||||
},
|
||||
"push_message":{
|
||||
"notification":{
|
||||
"title":"%s",
|
||||
"body":"%s",
|
||||
"click_type":"url",
|
||||
"url":"https//:xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
clients := models.FindClients(kefu)
|
||||
if len(clients) == 0 {
|
||||
return
|
||||
}
|
||||
//clientIds := make([]string, 0)
|
||||
for _, client := range clients {
|
||||
//clientIds = append(clientIds, client.Client_id)
|
||||
req := fmt.Sprintf(format, tools.Md5(tools.Uuid()), client.Client_id, title, content)
|
||||
num := sendPushApi(token, req)
|
||||
if num == 10001 {
|
||||
token = getGetuiToken()
|
||||
sendPushApi(token, req)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func sendPushApi(token string, req string) int {
|
||||
appid := models.FindConfig("GetuiAppID")
|
||||
if appid == "" {
|
||||
return 0
|
||||
}
|
||||
url := "https://restapi.getui.com/v2/" + appid + "/push/single/cid"
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/json;charset=utf-8"
|
||||
headers["token"] = token
|
||||
res, err := tools.PostHeader(url, []byte(req), headers)
|
||||
tools.Logger().Infoln(url, req, err, res)
|
||||
|
||||
if err == nil && res != "" {
|
||||
var pushRes GetuiResponse
|
||||
json.Unmarshal([]byte(res), &pushRes)
|
||||
if pushRes.Code == 10001 {
|
||||
return 10001
|
||||
}
|
||||
}
|
||||
return 200
|
||||
}
|
||||
func getGetuiToken() string {
|
||||
appid := models.FindConfig("GetuiAppID")
|
||||
appkey := models.FindConfig("GetuiAppKey")
|
||||
//appsecret := models.FindConfig("GetuiAppSecret")
|
||||
appmastersecret := models.FindConfig("GetuiMasterSecret")
|
||||
if appid == "" {
|
||||
return ""
|
||||
}
|
||||
type req struct {
|
||||
Sign string `json:"sign"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Appkey string `json:"appkey"`
|
||||
}
|
||||
timestamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
|
||||
reqJson := req{
|
||||
Sign: tools.Sha256(appkey + timestamp + appmastersecret),
|
||||
Timestamp: timestamp,
|
||||
Appkey: appkey,
|
||||
}
|
||||
reqStr, _ := json.Marshal(reqJson)
|
||||
url := "https://restapi.getui.com/v2/" + appid + "/auth"
|
||||
res, err := tools.Post(url, "application/json;charset=utf-8", reqStr)
|
||||
log.Println(url, string(reqStr), err, res)
|
||||
if err == nil && res != "" {
|
||||
var pushRes GetuiResponse
|
||||
json.Unmarshal([]byte(res), &pushRes)
|
||||
if pushRes.Code == 0 {
|
||||
token := pushRes.Data["token"].(string)
|
||||
//models.UpdateConfig("GetuiToken", token)
|
||||
return token
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
58
controller/tcp.go
Normal file
@ -0,0 +1,58 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
var clientTcpList = make(map[string]net.Conn)
|
||||
|
||||
func NewTcpServer(tcpBaseServer string) {
|
||||
listener, err := net.Listen("tcp", tcpBaseServer)
|
||||
if err != nil {
|
||||
log.Println("Error listening", err.Error())
|
||||
return //终止程序
|
||||
}
|
||||
// 监听并接受来自客户端的连接
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Println("Error accepting", err.Error())
|
||||
return // 终止程序
|
||||
}
|
||||
var remoteIpAddress = conn.RemoteAddr()
|
||||
clientTcpList[remoteIpAddress.String()] = conn
|
||||
log.Println(remoteIpAddress, clientTcpList)
|
||||
//clientTcpList=append(clientTcpList,conn)
|
||||
}
|
||||
}
|
||||
func PushServerTcp(str []byte) {
|
||||
for ip, conn := range clientTcpList {
|
||||
line := append(str, []byte("\r\n")...)
|
||||
_, err := conn.Write(line)
|
||||
log.Println(ip, err)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
delete(clientTcpList, ip)
|
||||
//clientTcpList=append(clientTcpList[:index],clientTcpList[index+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
func DeleteOnlineTcp(c *gin.Context) {
|
||||
ip := c.Query("ip")
|
||||
for ipkey, conn := range clientTcpList {
|
||||
if ip == ipkey {
|
||||
conn.Close()
|
||||
delete(clientTcpList, ip)
|
||||
}
|
||||
if ip == "all" {
|
||||
conn.Close()
|
||||
delete(clientTcpList, ipkey)
|
||||
}
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
})
|
||||
}
|
||||
310
controller/visitor.go
Normal file
@ -0,0 +1,310 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/common"
|
||||
"goflylivechat/models"
|
||||
"goflylivechat/tools"
|
||||
"goflylivechat/ws"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// func PostVisitor(c *gin.Context) {
|
||||
// name := c.PostForm("name")
|
||||
// avator := c.PostForm("avator")
|
||||
// toId := c.PostForm("to_id")
|
||||
// id := c.PostForm("id")
|
||||
// refer := c.PostForm("refer")
|
||||
// city := c.PostForm("city")
|
||||
// client_ip := c.PostForm("client_ip")
|
||||
// if name == "" || avator == "" || toId == "" || id == "" || refer == "" || city == "" || client_ip == "" {
|
||||
// c.JSON(200, gin.H{
|
||||
// "code": 400,
|
||||
// "msg": "error",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// kefuInfo := models.FindUser(toId)
|
||||
// if kefuInfo.ID == 0 {
|
||||
// c.JSON(200, gin.H{
|
||||
// "code": 400,
|
||||
// "msg": "用户不存在",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// models.CreateVisitor(name, avator, c.ClientIP(), toId, id, refer, city, client_ip)
|
||||
//
|
||||
// userInfo := make(map[string]string)
|
||||
// userInfo["uid"] = id
|
||||
// userInfo["username"] = name
|
||||
// userInfo["avator"] = avator
|
||||
// msg := TypeMessage{
|
||||
// Type: "userOnline",
|
||||
// Data: userInfo,
|
||||
// }
|
||||
// str, _ := json.Marshal(msg)
|
||||
// kefuConns := kefuList[toId]
|
||||
// if kefuConns != nil {
|
||||
// for k, kefuConn := range kefuConns {
|
||||
// log.Println(k, "xxxxxxxx")
|
||||
// kefuConn.WriteMessage(websocket.TextMessage, str)
|
||||
// }
|
||||
// }
|
||||
// c.JSON(200, gin.H{
|
||||
// "code": 200,
|
||||
// "msg": "ok",
|
||||
// })
|
||||
// }
|
||||
func PostVisitorLogin(c *gin.Context) {
|
||||
ipcity := tools.ParseIp(c.ClientIP())
|
||||
avator := ""
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
if tools.IsMobile(userAgent) {
|
||||
avator = "/static/images/1.png"
|
||||
} else {
|
||||
avator = "/static/images/2.png"
|
||||
}
|
||||
|
||||
toId := c.PostForm("to_id")
|
||||
id := c.PostForm("visitor_id")
|
||||
|
||||
if id == "" {
|
||||
id = tools.Uuid()
|
||||
}
|
||||
refer := c.PostForm("refer")
|
||||
var (
|
||||
city string
|
||||
name string
|
||||
)
|
||||
|
||||
if ipcity != nil {
|
||||
city = ipcity.CountryName + ipcity.RegionName + ipcity.CityName
|
||||
name = ipcity.CountryName + ipcity.RegionName + ipcity.CityName
|
||||
if ipcity.CityName == "本机地址" || ipcity.RegionName == "本机地址" || ipcity.CountryName == "本机地址" {
|
||||
city = "local address"
|
||||
}
|
||||
} else {
|
||||
city = "Unrecognized Region"
|
||||
name = "visitor"
|
||||
}
|
||||
|
||||
if name == "本机地址本机地址" {
|
||||
name = "local visitor"
|
||||
}
|
||||
client_ip := c.ClientIP()
|
||||
extra := c.PostForm("extra")
|
||||
extraJson := tools.Base64Decode(extra)
|
||||
if extraJson != "" {
|
||||
var extraObj VisitorExtra
|
||||
err := json.Unmarshal([]byte(extraJson), &extraObj)
|
||||
if err == nil {
|
||||
if extraObj.VisitorName != "" {
|
||||
name = extraObj.VisitorName
|
||||
}
|
||||
if extraObj.VisitorAvatar != "" {
|
||||
avator = extraObj.VisitorAvatar
|
||||
}
|
||||
}
|
||||
}
|
||||
//log.Println(name,avator,c.ClientIP(),toId,id,refer,city,client_ip)
|
||||
if name == "" || avator == "" || toId == "" || id == "" || refer == "" || city == "" || client_ip == "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "error",
|
||||
})
|
||||
return
|
||||
}
|
||||
kefuInfo := models.FindUser(toId)
|
||||
if kefuInfo.ID == 0 {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "客服不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
visitor := models.FindVisitorByVistorId(id)
|
||||
if visitor.Name != "" {
|
||||
avator = visitor.Avator
|
||||
//更新状态上线
|
||||
models.UpdateVisitor(name, visitor.Avator, id, 1, c.ClientIP(), c.ClientIP(), refer, extra)
|
||||
} else {
|
||||
models.CreateVisitor(name, avator, c.ClientIP(), toId, id, refer, city, client_ip, extra)
|
||||
}
|
||||
visitor.Name = name
|
||||
visitor.Avator = avator
|
||||
visitor.ToId = toId
|
||||
visitor.ClientIp = c.ClientIP()
|
||||
visitor.VisitorId = id
|
||||
|
||||
//各种通知
|
||||
go SendNoticeEmail(visitor.Name, " incoming!")
|
||||
//go SendAppGetuiPush(kefuInfo.Name, visitor.Name, visitor.Name+" incoming!")
|
||||
go SendVisitorLoginNotice(kefuInfo.Name, visitor.Name, visitor.Avator, visitor.Name+" incoming!", visitor.VisitorId)
|
||||
go ws.VisitorOnline(kefuInfo.Name, visitor)
|
||||
//go SendServerJiang(visitor.Name, "来了", c.Request.Host)
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": visitor,
|
||||
})
|
||||
}
|
||||
func GetVisitor(c *gin.Context) {
|
||||
visitorId := c.Query("visitorId")
|
||||
vistor := models.FindVisitorByVistorId(visitorId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": vistor,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 获取访客列表接口
|
||||
// @Produce json
|
||||
// @Accept multipart/form-data
|
||||
// @Param page query string true "分页"
|
||||
// @Param token header string true "认证token"
|
||||
// @Success 200 {object} controller.Response
|
||||
// @Failure 200 {object} controller.Response
|
||||
// @Router /visitors [get]
|
||||
func GetVisitors(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.Query("page"))
|
||||
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
||||
if pagesize == 0 {
|
||||
pagesize = int(common.VisitorPageSize)
|
||||
}
|
||||
kefuId, _ := c.Get("kefu_name")
|
||||
vistors := models.FindVisitorsByKefuId(uint(page), uint(pagesize), kefuId.(string))
|
||||
count := models.CountVisitorsByKefuId(kefuId.(string))
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": gin.H{
|
||||
"list": vistors,
|
||||
"count": count,
|
||||
"pagesize": common.PageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 获取访客聊天信息接口
|
||||
// @Produce json
|
||||
// @Accept multipart/form-data
|
||||
// @Param visitorId query string true "访客ID"
|
||||
// @Param token header string true "认证token"
|
||||
// @Success 200 {object} controller.Response
|
||||
// @Failure 200 {object} controller.Response
|
||||
// @Router /messages [get]
|
||||
func GetVisitorMessage(c *gin.Context) {
|
||||
visitorId := c.Query("visitorId")
|
||||
|
||||
query := "message.visitor_id= ?"
|
||||
messages := models.FindMessageByWhere(query, visitorId)
|
||||
result := make([]map[string]interface{}, 0)
|
||||
for _, message := range messages {
|
||||
item := make(map[string]interface{})
|
||||
|
||||
item["time"] = message.CreatedAt.Format("2006-01-02 15:04:05")
|
||||
item["content"] = message.Content
|
||||
item["mes_type"] = message.MesType
|
||||
item["visitor_name"] = message.VisitorName
|
||||
item["visitor_avator"] = message.VisitorAvator
|
||||
item["kefu_name"] = message.KefuName
|
||||
item["kefu_avator"] = message.KefuAvator
|
||||
result = append(result, item)
|
||||
|
||||
}
|
||||
go models.ReadMessageByVisitorId(visitorId)
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": result,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 获取在线访客列表接口
|
||||
// @Produce json
|
||||
// @Success 200 {object} controller.Response
|
||||
// @Failure 200 {object} controller.Response
|
||||
// @Router /visitors_online [get]
|
||||
func GetVisitorOnlines(c *gin.Context) {
|
||||
users := make([]map[string]string, 0)
|
||||
visitorIds := make([]string, 0)
|
||||
for uid, visitor := range ws.ClientList {
|
||||
userInfo := make(map[string]string)
|
||||
userInfo["uid"] = uid
|
||||
userInfo["name"] = visitor.Name
|
||||
userInfo["avator"] = visitor.Avator
|
||||
users = append(users, userInfo)
|
||||
visitorIds = append(visitorIds, visitor.Id)
|
||||
}
|
||||
|
||||
//查询最新消息
|
||||
messages := models.FindLastMessage(visitorIds)
|
||||
temp := make(map[string]string, 0)
|
||||
for _, mes := range messages {
|
||||
temp[mes.VisitorId] = mes.Content
|
||||
}
|
||||
for _, user := range users {
|
||||
user["last_message"] = temp[user["uid"]]
|
||||
}
|
||||
|
||||
tcps := make([]string, 0)
|
||||
for ip, _ := range clientTcpList {
|
||||
tcps = append(tcps, ip)
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": gin.H{
|
||||
"ws": users,
|
||||
"tcp": tcps,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary 获取客服的在线访客列表接口
|
||||
// @Produce json
|
||||
// @Success 200 {object} controller.Response
|
||||
// @Failure 200 {object} controller.Response
|
||||
// @Router /visitors_kefu_online [get]
|
||||
func GetKefusVisitorOnlines(c *gin.Context) {
|
||||
kefuName, _ := c.Get("kefu_name")
|
||||
users := make([]*VisitorOnline, 0)
|
||||
visitorIds := make([]string, 0)
|
||||
for uid, visitor := range ws.ClientList {
|
||||
if visitor.To_id != kefuName {
|
||||
continue
|
||||
}
|
||||
userInfo := new(VisitorOnline)
|
||||
userInfo.Uid = uid
|
||||
userInfo.Username = visitor.Name
|
||||
userInfo.Avator = visitor.Avator
|
||||
users = append(users, userInfo)
|
||||
visitorIds = append(visitorIds, visitor.Id)
|
||||
}
|
||||
|
||||
//查询最新消息
|
||||
messages := models.FindLastMessage(visitorIds)
|
||||
temp := make(map[string]string, 0)
|
||||
for _, mes := range messages {
|
||||
temp[mes.VisitorId] = mes.Content
|
||||
}
|
||||
for _, user := range users {
|
||||
user.LastMessage = temp[user.Uid]
|
||||
if user.LastMessage == "" {
|
||||
user.LastMessage = "new visitor"
|
||||
}
|
||||
}
|
||||
|
||||
tcps := make([]string, 0)
|
||||
for ip, _ := range clientTcpList {
|
||||
tcps = append(tcps, ip)
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"result": users,
|
||||
})
|
||||
}
|
||||
35
controller/weixin.go
Normal file
@ -0,0 +1,35 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
"log"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func GetCheckWeixinSign(c *gin.Context) {
|
||||
token := models.FindConfig("WeixinToken")
|
||||
signature := c.Query("signature")
|
||||
timestamp := c.Query("timestamp")
|
||||
nonce := c.Query("nonce")
|
||||
echostr := c.Query("echostr")
|
||||
//将token、timestamp、nonce三个参数进行字典序排序
|
||||
var tempArray = []string{token, timestamp, nonce}
|
||||
sort.Strings(tempArray)
|
||||
//将三个参数字符串拼接成一个字符串进行sha1加密
|
||||
var sha1String string = ""
|
||||
for _, v := range tempArray {
|
||||
sha1String += v
|
||||
}
|
||||
h := sha1.New()
|
||||
h.Write([]byte(sha1String))
|
||||
sha1String = hex.EncodeToString(h.Sum([]byte("")))
|
||||
//获得加密后的字符串可与signature对比
|
||||
if sha1String == signature {
|
||||
c.Writer.Write([]byte(echostr))
|
||||
} else {
|
||||
log.Println("微信API验证失败")
|
||||
}
|
||||
}
|
||||
23
go.mod
Normal file
@ -0,0 +1,23 @@
|
||||
module goflylivechat
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||
github.com/emersion/go-smtp v0.13.0
|
||||
github.com/gin-contrib/sessions v0.0.3
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gobuffalo/packr/v2 v2.5.1
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/ipipdotnet/ipdb-go v1.3.0
|
||||
github.com/jinzhu/gorm v1.9.14
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/zh-five/xdaemon v0.1.1
|
||||
)
|
||||
188
go.sum
Normal file
@ -0,0 +1,188 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.13.0 h1:aC3Kc21TdfvXnuJXCQXuhnDXUldhc12qME/S7Y3Y94g=
|
||||
github.com/emersion/go-smtp v0.13.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gin-contrib/sessions v0.0.3 h1:PoBXki+44XdJdlgDqDrY5nDVe3Wk7wDV/UCOuLP6fBI=
|
||||
github.com/gin-contrib/sessions v0.0.3/go.mod h1:8C/J6cad3Il1mWYYgtw0w+hqasmpvy25mPkXdOgeB9I=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
|
||||
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/ipipdotnet/ipdb-go v1.3.0 h1:FfkSkAI1do3bZ7F35ueGuF7Phur64jmikQ1C4IPl/gc=
|
||||
github.com/ipipdotnet/ipdb-go v1.3.0/go.mod h1:yZ+8puwe3R37a/3qRftXo40nZVQbxYDLqls9o5foexs=
|
||||
github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM=
|
||||
github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/zh-five/xdaemon v0.1.1 h1:W5VyJ+5ROjjcb9vNcF/SgWPwTzIRYIsW2yZBAomqMW8=
|
||||
github.com/zh-five/xdaemon v0.1.1/go.mod h1:i3cluMVOPp/UcX2KDU2qzRv25f8u4y14tHzBPQhD8lI=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
9
gofly.go
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"goflylivechat/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
112
import.sql
Normal file
@ -0,0 +1,112 @@
|
||||
DROP TABLE IF EXISTS `user`;
|
||||
CREATE TABLE `user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL DEFAULT '',
|
||||
`password` varchar(50) NOT NULL DEFAULT '',
|
||||
`nickname` varchar(50) NOT NULL DEFAULT '',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
`avator` varchar(100) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `idx_name` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
TRUNCATE TABLE `user`;
|
||||
INSERT INTO `user` (`id`, `name`, `password`, `nickname`, `created_at`, `updated_at`, `deleted_at`, `avator`) VALUE
|
||||
(1, 'agent', 'b33aed8f3134996703dc39f9a7c95783', 'Open Source LiveChat Support', '2020-06-27 19:32:41', '2020-07-04 09:32:20', NULL, '/static/images/4.jpg');
|
||||
|
||||
DROP TABLE IF EXISTS `visitor`;
|
||||
CREATE TABLE `visitor` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(50) NOT NULL DEFAULT '',
|
||||
`avator` varchar(500) NOT NULL DEFAULT '',
|
||||
`source_ip` varchar(50) NOT NULL DEFAULT '',
|
||||
`to_id` varchar(50) NOT NULL DEFAULT '',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
`visitor_id` varchar(100) NOT NULL DEFAULT '',
|
||||
`status` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`refer` varchar(500) NOT NULL DEFAULT '',
|
||||
`city` varchar(100) NOT NULL DEFAULT '',
|
||||
`client_ip` varchar(100) NOT NULL DEFAULT '',
|
||||
`extra` varchar(2048) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `visitor_id` (`visitor_id`),
|
||||
KEY `to_id` (`to_id`),
|
||||
KEY `idx_update` (`updated_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `message`;
|
||||
CREATE TABLE `message` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`kefu_id` varchar(100) NOT NULL DEFAULT '',
|
||||
`visitor_id` varchar(100) NOT NULL DEFAULT '',
|
||||
`content` varchar(2048) NOT NULL DEFAULT '',
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||
`mes_type` enum('kefu','visitor') NOT NULL DEFAULT 'visitor',
|
||||
`status` enum('read','unread') NOT NULL DEFAULT 'unread',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `kefu_id` (`kefu_id`),
|
||||
KEY `visitor_id` (`visitor_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
DROP TABLE IF EXISTS `ipblack`;
|
||||
CREATE TABLE `ipblack` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`ip` varchar(100) NOT NULL DEFAULT '',
|
||||
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`kefu_id` varchar(100) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `ip` (`ip`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `config`;
|
||||
CREATE TABLE `config` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`conf_name` varchar(255) NOT NULL DEFAULT '',
|
||||
`conf_key` varchar(255) NOT NULL DEFAULT '',
|
||||
`conf_value` varchar(255) NOT NULL DEFAULT '',
|
||||
`user_id` varchar(500) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `conf_key` (`conf_key`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`, `user_id`) VALUES
|
||||
(NULL, 'Announcement', 'AllNotice', 'Open source customer support system at your service','agent');
|
||||
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`, `user_id`) VALUES
|
||||
(NULL, 'Offline Message', 'OfflineMessage', 'I am currently offline and will reply to you later!','agent');
|
||||
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`, `user_id`) VALUES
|
||||
(NULL, 'Welcome Message', 'WelcomeMessage', 'How may I help you?','agent');
|
||||
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`, `user_id`) VALUES
|
||||
(NULL, 'Email Address (SMTP)', 'NoticeEmailSmtp', '','agent');
|
||||
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`, `user_id`) VALUES
|
||||
(NULL, 'Email Account', 'NoticeEmailAddress', '','agent');
|
||||
INSERT INTO `config` (`id`, `conf_name`, `conf_key`, `conf_value`, `user_id`) VALUES
|
||||
(NULL, 'Email Password (SMTP)', 'NoticeEmailPassword', '','agent');
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `reply_group`;
|
||||
CREATE TABLE `reply_group` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`group_name` varchar(50) NOT NULL DEFAULT '',
|
||||
`user_id` varchar(50) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
INSERT INTO `reply_group` (`id`, `group_name`, `user_id`) VALUES
|
||||
(NULL, 'Frequently Asked Questions', 'agent');
|
||||
|
||||
DROP TABLE IF EXISTS `reply_item`;
|
||||
CREATE TABLE `reply_item` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`content` varchar(1024) NOT NULL DEFAULT '',
|
||||
`group_id` int(11) NOT NULL DEFAULT '0',
|
||||
`user_id` varchar(50) NOT NULL DEFAULT '',
|
||||
`item_name` varchar(50) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `group_id` (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
16
middleware/cross.go
Normal file
@ -0,0 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func CrossSite(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
//服务器支持的所有跨域请求的方法
|
||||
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
|
||||
//允许跨域设置可以返回其他子段,可以自定义字段
|
||||
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session")
|
||||
// 允许浏览器(客户端)可以解析的头部 (重要)
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
|
||||
//允许客户端传递校验信息比如 cookie (重要)
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
c.Next()
|
||||
}
|
||||
12
middleware/domain_limit.go
Normal file
@ -0,0 +1,12 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
/**
|
||||
域名中间件
|
||||
*/
|
||||
func DomainLimitMiddleware(c *gin.Context) {
|
||||
|
||||
}
|
||||
19
middleware/ipblack.go
Normal file
@ -0,0 +1,19 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/models"
|
||||
)
|
||||
|
||||
func Ipblack(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
ipblack := models.FindIp(ip)
|
||||
if ipblack.IP != "" {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "IP已被加入黑名单",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
45
middleware/jwt.go
Normal file
@ -0,0 +1,45 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/tools"
|
||||
"time"
|
||||
)
|
||||
|
||||
func JwtPageMiddleware(c *gin.Context) {
|
||||
//暂时不处理
|
||||
//token := c.Query("token")
|
||||
//userinfo := tools.ParseToken(token)
|
||||
//if userinfo == nil {
|
||||
// c.Redirect(302,"/login")
|
||||
// c.Abort()
|
||||
//}
|
||||
}
|
||||
func JwtApiMiddleware(c *gin.Context) {
|
||||
token := c.GetHeader("token")
|
||||
if token == "" {
|
||||
token = c.Query("token")
|
||||
}
|
||||
userinfo := tools.ParseToken(token)
|
||||
if userinfo == nil || userinfo["kefu_name"] == nil || userinfo["create_time"] == nil {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 400,
|
||||
"msg": "验证失败",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
createTime := int64(userinfo["create_time"].(float64))
|
||||
var expire int64 = 24 * 60 * 60
|
||||
nowTime := time.Now().Unix()
|
||||
if (nowTime - createTime) >= expire {
|
||||
c.JSON(200, gin.H{
|
||||
"code": 401,
|
||||
"msg": "token失效",
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
c.Set("kefu_id", userinfo["kefu_id"])
|
||||
c.Set("kefu_name", userinfo["kefu_name"])
|
||||
|
||||
}
|
||||
45
middleware/logger.go
Normal file
@ -0,0 +1,45 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/tools"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewMidLogger() gin.HandlerFunc {
|
||||
logger := tools.Logger()
|
||||
return func(c *gin.Context) {
|
||||
// 开始时间
|
||||
startTime := time.Now()
|
||||
|
||||
// 处理请求
|
||||
c.Next()
|
||||
|
||||
// 结束时间
|
||||
endTime := time.Now()
|
||||
|
||||
// 执行时间
|
||||
latencyTime := endTime.Sub(startTime)
|
||||
|
||||
// 请求方式
|
||||
reqMethod := c.Request.Method
|
||||
|
||||
// 请求路由
|
||||
reqUri := c.Request.RequestURI
|
||||
|
||||
// 状态码
|
||||
statusCode := c.Writer.Status()
|
||||
|
||||
// 请求IP
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
//日志格式
|
||||
logger.Infof("| %3d | %13v | %15s | %s | %s |",
|
||||
statusCode,
|
||||
latencyTime,
|
||||
clientIP,
|
||||
reqMethod,
|
||||
reqUri,
|
||||
)
|
||||
}
|
||||
}
|
||||
64
middleware/rbac.go
Normal file
@ -0,0 +1,64 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func RbacAuth(c *gin.Context) {
|
||||
return
|
||||
//roleId, _ := c.Get("role_id")
|
||||
//role := models.FindRole(roleId)
|
||||
//var flag bool
|
||||
//rPaths := strings.Split(c.Request.RequestURI, "?")
|
||||
//uriParam := fmt.Sprintf("%s:%s", c.Request.Method, rPaths[0])
|
||||
//if role.Method != "*" || role.Path != "*" {
|
||||
// paths := strings.Split(role.Path, ",")
|
||||
// for _, p := range paths {
|
||||
// if uriParam == p {
|
||||
// flag = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if !flag {
|
||||
// c.JSON(200, gin.H{
|
||||
// "code": 403,
|
||||
// "msg": "没有权限:" + uriParam,
|
||||
// })
|
||||
// c.Abort()
|
||||
// return
|
||||
// }
|
||||
//methods := strings.Split(role.Method, ",")
|
||||
//for _, m := range methods {
|
||||
// if c.Request.Method == m {
|
||||
// methodFlag = true
|
||||
// break
|
||||
// }
|
||||
//}
|
||||
//if !methodFlag {
|
||||
// c.JSON(200, gin.H{
|
||||
// "code": 403,
|
||||
// "msg": "没有权限:" + c.Request.Method + "," + rPaths[0],
|
||||
// })
|
||||
// c.Abort()
|
||||
// return
|
||||
//}
|
||||
//}
|
||||
//var flag bool
|
||||
//if role.Path != "*" {
|
||||
// paths := strings.Split(role.Path, ",")
|
||||
// for _, p := range paths {
|
||||
// if rPaths[0] == p {
|
||||
// flag = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if !flag {
|
||||
// c.JSON(200, gin.H{
|
||||
// "code": 403,
|
||||
// "msg": "没有权限:" + rPaths[0],
|
||||
// })
|
||||
// c.Abort()
|
||||
// return
|
||||
// }
|
||||
//}
|
||||
}
|
||||
53
models/abouts.go
Normal file
@ -0,0 +1,53 @@
|
||||
package models
|
||||
|
||||
type About struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
TitleCn string `json:"title_cn"`
|
||||
TitleEn string `json:"title_en"`
|
||||
KeywordsCn string `json:"keywords_cn"`
|
||||
KeywordsEn string `json:"keywords_en"`
|
||||
DescCn string `json:"desc_cn"`
|
||||
DescEn string `json:"desc_en"`
|
||||
CssJs string `json:"css_js"`
|
||||
HtmlCn string `json:"html_cn"`
|
||||
HtmlEn string `json:"html_en"`
|
||||
Page string `json:"page"`
|
||||
}
|
||||
|
||||
func FindAbouts() []About {
|
||||
var a []About
|
||||
DB.Select("id,title_cn,page").Find(&a)
|
||||
return a
|
||||
}
|
||||
|
||||
func FindAboutByPage(page interface{}) About {
|
||||
var a About
|
||||
DB.Where("page = ?", page).First(&a)
|
||||
return a
|
||||
}
|
||||
func FindAboutByPageLanguage(page interface{}, lang string) About {
|
||||
var a About
|
||||
if lang == "" {
|
||||
lang = "cn"
|
||||
}
|
||||
if lang == "en" {
|
||||
DB.Select("css_js,title_en,keywords_en,desc_en,html_en").Where("page = ?", page).First(&a)
|
||||
} else {
|
||||
DB.Select("css_js,title_cn,keywords_cn,desc_cn,html_cn").Where("page = ?", page).First(&a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
func UpdateAbout(page string, title_cn string, title_en string, keywords_cn string, keywords_en string, desc_cn string, desc_en string, css_js string, html_cn string, html_en string) {
|
||||
c := &About{
|
||||
TitleCn: title_cn,
|
||||
TitleEn: title_en,
|
||||
KeywordsCn: keywords_cn,
|
||||
KeywordsEn: keywords_en,
|
||||
DescCn: desc_cn,
|
||||
DescEn: desc_en,
|
||||
CssJs: css_js,
|
||||
HtmlCn: html_cn,
|
||||
HtmlEn: html_en,
|
||||
}
|
||||
DB.Model(c).Where("page = ?", page).Update(c)
|
||||
}
|
||||
53
models/configs.go
Normal file
@ -0,0 +1,53 @@
|
||||
package models
|
||||
|
||||
var CustomConfigs []Config
|
||||
|
||||
type Config struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
ConfName string `json:"conf_name"`
|
||||
ConfKey string `json:"conf_key"`
|
||||
ConfValue string `json:"conf_value"`
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
func UpdateConfig(userid interface{}, key string, value string) {
|
||||
config := FindConfigByUserId(userid, key)
|
||||
if config.ID != 0 {
|
||||
config.ConfValue = value
|
||||
DB.Where("user_id = ? and conf_key = ?", userid, key).Update(config)
|
||||
} else {
|
||||
newConfig := &Config{
|
||||
ID: 0,
|
||||
ConfName: "",
|
||||
ConfKey: key,
|
||||
ConfValue: value,
|
||||
UserId: userid.(string),
|
||||
}
|
||||
DB.Create(newConfig)
|
||||
}
|
||||
|
||||
}
|
||||
func FindConfigs() []Config {
|
||||
var config []Config
|
||||
DB.Find(&config)
|
||||
return config
|
||||
}
|
||||
func FindConfigsByUserId(userid interface{}) []Config {
|
||||
var config []Config
|
||||
DB.Where("user_id = ?", userid).Find(&config)
|
||||
return config
|
||||
}
|
||||
|
||||
func FindConfig(key string) string {
|
||||
for _, config := range CustomConfigs {
|
||||
if key == config.ConfKey {
|
||||
return config.ConfValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func FindConfigByUserId(userId interface{}, key string) Config {
|
||||
var config Config
|
||||
DB.Where("user_id = ? and conf_key = ?", userId, key).Find(&config)
|
||||
return config
|
||||
}
|
||||
57
models/ipblacks.go
Normal file
@ -0,0 +1,57 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Ipblack struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
IP string `json:"ip"`
|
||||
KefuId string `json:"kefu_id"`
|
||||
CreateAt time.Time `json:"create_at"`
|
||||
}
|
||||
|
||||
func CreateIpblack(ip string, kefuId string) uint {
|
||||
black := &Ipblack{
|
||||
IP: ip,
|
||||
KefuId: kefuId,
|
||||
CreateAt: time.Now(),
|
||||
}
|
||||
DB.Create(black)
|
||||
return black.ID
|
||||
}
|
||||
func DeleteIpblackByIp(ip string) {
|
||||
DB.Where("ip = ?", ip).Delete(Ipblack{})
|
||||
}
|
||||
func FindIp(ip string) Ipblack {
|
||||
var ipblack Ipblack
|
||||
DB.Where("ip = ?", ip).First(&ipblack)
|
||||
return ipblack
|
||||
}
|
||||
func FindIpsByKefuId(id string) []Ipblack {
|
||||
var ipblack []Ipblack
|
||||
DB.Where("kefu_id = ?", id).Find(&ipblack)
|
||||
return ipblack
|
||||
}
|
||||
func FindIps(query interface{}, args []interface{}, page uint, pagesize uint) []Ipblack {
|
||||
offset := (page - 1) * pagesize
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
var ipblacks []Ipblack
|
||||
if query != nil {
|
||||
DB.Where(query, args...).Offset(offset).Limit(pagesize).Find(&ipblacks)
|
||||
} else {
|
||||
DB.Offset(offset).Limit(pagesize).Find(&ipblacks)
|
||||
}
|
||||
return ipblacks
|
||||
}
|
||||
|
||||
//查询条数
|
||||
func CountIps(query interface{}, args []interface{}) uint {
|
||||
var count uint
|
||||
if query != nil {
|
||||
DB.Model(&Visitor{}).Where(query, args...).Count(&count)
|
||||
} else {
|
||||
DB.Model(&Visitor{}).Count(&count)
|
||||
}
|
||||
return count
|
||||
}
|
||||
120
models/messages.go
Normal file
@ -0,0 +1,120 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Model
|
||||
KefuId string `json:"kefu_id"`
|
||||
VisitorId string `json:"visitor_id"`
|
||||
Content string `json:"content"`
|
||||
MesType string `json:"mes_type"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
type MessageKefu struct {
|
||||
Model
|
||||
KefuId string `json:"kefu_id"`
|
||||
VisitorId string `json:"visitor_id"`
|
||||
Content string `json:"content"`
|
||||
MesType string `json:"mes_type"`
|
||||
Status string `json:"status"`
|
||||
VisitorName string `json:"visitor_name"`
|
||||
VisitorAvator string `json:"visitor_avator"`
|
||||
KefuName string `json:"kefu_name"`
|
||||
KefuAvator string `json:"kefu_avator"`
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
|
||||
func CreateMessage(kefu_id string, visitor_id string, content string, mes_type string) {
|
||||
DB.Exec("set names utf8mb4")
|
||||
v := &Message{
|
||||
KefuId: kefu_id,
|
||||
VisitorId: visitor_id,
|
||||
Content: content,
|
||||
MesType: mes_type,
|
||||
Status: "unread",
|
||||
}
|
||||
v.UpdatedAt = time.Now()
|
||||
DB.Create(v)
|
||||
}
|
||||
func FindMessageByVisitorId(visitor_id string) []Message {
|
||||
var messages []Message
|
||||
DB.Where("visitor_id=?", visitor_id).Order("id asc").Find(&messages)
|
||||
return messages
|
||||
}
|
||||
|
||||
//修改消息状态
|
||||
func ReadMessageByVisitorId(visitor_id string) {
|
||||
message := &Message{
|
||||
Status: "read",
|
||||
}
|
||||
DB.Model(&message).Where("visitor_id=?", visitor_id).Update(message)
|
||||
}
|
||||
|
||||
//获取未读数
|
||||
func FindUnreadMessageNumByVisitorId(visitor_id string) uint {
|
||||
var count uint
|
||||
DB.Where("visitor_id=? and status=?", visitor_id, "unread").Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
//查询最后一条消息
|
||||
func FindLastMessage(visitorIds []string) []Message {
|
||||
var messages []Message
|
||||
if len(visitorIds) <= 0 {
|
||||
return messages
|
||||
}
|
||||
var ids []Message
|
||||
DB.Select("MAX(id) id").Where(" visitor_id in (? )", visitorIds).Group("visitor_id").Find(&ids)
|
||||
if len(ids) <= 0 {
|
||||
return messages
|
||||
}
|
||||
var idStr = make([]string, 0, 0)
|
||||
for _, mes := range ids {
|
||||
idStr = append(idStr, fmt.Sprintf("%d", mes.ID))
|
||||
}
|
||||
DB.Select("visitor_id,id,content").Where(" id in (? )", idStr).Find(&messages)
|
||||
//subQuery := DB.
|
||||
// Table("message").
|
||||
// Where(" visitor_id in (? )", visitorIds).
|
||||
// Order("id desc").
|
||||
// Limit(1024).
|
||||
// SubQuery()
|
||||
//DB.Raw("SELECT ANY_VALUE(visitor_id) visitor_id,ANY_VALUE(id) id,ANY_VALUE(content) content FROM ? message_alia GROUP BY visitor_id", subQuery).Scan(&messages)
|
||||
//DB.Select("ANY_VALUE(visitor_id) visitor_id,MAX(ANY_VALUE(id)) id,ANY_VALUE(content) content").Group("visitor_id").Find(&messages)
|
||||
return messages
|
||||
}
|
||||
|
||||
//查询最后一条消息
|
||||
func FindLastMessageByVisitorId(visitorId string) Message {
|
||||
var m Message
|
||||
DB.Select("content").Where("visitor_id=?", visitorId).Order("id desc").First(&m)
|
||||
return m
|
||||
}
|
||||
func FindMessageByWhere(query interface{}, args ...interface{}) []MessageKefu {
|
||||
var messages []MessageKefu
|
||||
DB.Table("message").Where(query, args...).Select("message.*,visitor.avator visitor_avator,visitor.name visitor_name,user.avator kefu_avator,user.nickname kefu_name").Joins("left join user on message.kefu_id=user.name").Joins("left join visitor on visitor.visitor_id=message.visitor_id").Order("message.id asc").Find(&messages)
|
||||
return messages
|
||||
}
|
||||
|
||||
//查询条数
|
||||
func CountMessage(query interface{}, args ...interface{}) uint {
|
||||
var count uint
|
||||
DB.Model(&Message{}).Where(query, args...).Count(&count)
|
||||
return count
|
||||
}
|
||||
//分页查询
|
||||
func FindMessageByPage(page uint, pagesize uint, query interface{}, args ...interface{}) []*MessageKefu {
|
||||
offset := (page - 1) * pagesize
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
var messages []*MessageKefu
|
||||
DB.Table("message").Select("message.*,visitor.avator visitor_avator,visitor.name visitor_name,user.avator kefu_avator,user.nickname kefu_name").Offset(offset).Joins("left join user on message.kefu_id=user.name").Joins("left join visitor on visitor.visitor_id=message.visitor_id").Where(query, args...).Limit(pagesize).Order("message.id desc").Find(&messages)
|
||||
for _, mes := range messages {
|
||||
mes.CreateTime = mes.CreatedAt.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
return messages
|
||||
}
|
||||
45
models/models.go
Normal file
@ -0,0 +1,45 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jinzhu/gorm"
|
||||
"goflylivechat/common"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
type Model struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt *time.Time `sql:"index" json:"deleted_at"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
Connect()
|
||||
}
|
||||
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)
|
||||
var err error
|
||||
DB, err = gorm.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
panic("数据库连接失败!")
|
||||
return err
|
||||
}
|
||||
DB.SingularTable(true)
|
||||
DB.LogMode(true)
|
||||
DB.DB().SetMaxIdleConns(10)
|
||||
DB.DB().SetMaxOpenConns(100)
|
||||
DB.DB().SetConnMaxLifetime(59 * time.Second)
|
||||
return nil
|
||||
}
|
||||
func Execute(sql string) error {
|
||||
return DB.Exec(sql).Error
|
||||
}
|
||||
func CloseDB() {
|
||||
defer DB.Close()
|
||||
}
|
||||
104
models/replys.go
Normal file
@ -0,0 +1,104 @@
|
||||
package models
|
||||
|
||||
type ReplyItem struct {
|
||||
Id string `json:"item_id"`
|
||||
Content string `json:"item_content"`
|
||||
GroupId string `json:"group_id"`
|
||||
ItemName string `json:"item_name"`
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
type ReplyGroup struct {
|
||||
Id string `json:"group_id"`
|
||||
GroupName string `json:"group_name"`
|
||||
UserId string `json:"user_id"`
|
||||
Items []*ReplyItem `json:"items";"`
|
||||
}
|
||||
|
||||
func FindReplyItemByUserIdTitle(userId interface{}, title string) ReplyItem {
|
||||
var reply ReplyItem
|
||||
DB.Where("user_id = ? and item_name = ?", userId, title).Find(&reply)
|
||||
return reply
|
||||
}
|
||||
func FindReplyByUserId(userId interface{}) []*ReplyGroup {
|
||||
var replyGroups []*ReplyGroup
|
||||
//DB.Raw("select a.*,b.* from reply_group a left join reply_item b on a.id=b.group_id where a.user_id=? ", userId).Scan(&replyGroups)
|
||||
var replyItems []*ReplyItem
|
||||
DB.Where("user_id = ?", userId).Find(&replyGroups)
|
||||
DB.Where("user_id = ?", userId).Find(&replyItems)
|
||||
temp := make(map[string]*ReplyGroup)
|
||||
for _, replyGroup := range replyGroups {
|
||||
replyGroup.Items = make([]*ReplyItem, 0)
|
||||
temp[replyGroup.Id] = replyGroup
|
||||
}
|
||||
for _, replyItem := range replyItems {
|
||||
temp[replyItem.GroupId].Items = append(temp[replyItem.GroupId].Items, replyItem)
|
||||
}
|
||||
return replyGroups
|
||||
}
|
||||
func FindReplyTitleByUserId(userId interface{}) []*ReplyGroup {
|
||||
var replyGroups []*ReplyGroup
|
||||
//DB.Raw("select a.*,b.* from reply_group a left join reply_item b on a.id=b.group_id where a.user_id=? ", userId).Scan(&replyGroups)
|
||||
var replyItems []*ReplyItem
|
||||
DB.Where("user_id = ?", userId).Find(&replyGroups)
|
||||
DB.Select("item_name,group_id").Where("user_id = ?", userId).Find(&replyItems)
|
||||
temp := make(map[string]*ReplyGroup)
|
||||
for _, replyGroup := range replyGroups {
|
||||
replyGroup.Items = make([]*ReplyItem, 0)
|
||||
temp[replyGroup.Id] = replyGroup
|
||||
}
|
||||
for _, replyItem := range replyItems {
|
||||
temp[replyItem.GroupId].Items = append(temp[replyItem.GroupId].Items, replyItem)
|
||||
}
|
||||
return replyGroups
|
||||
}
|
||||
func CreateReplyGroup(groupName string, userId string) {
|
||||
g := &ReplyGroup{
|
||||
GroupName: groupName,
|
||||
UserId: userId,
|
||||
}
|
||||
DB.Create(g)
|
||||
}
|
||||
func CreateReplyContent(groupId string, userId string, content, itemName string) {
|
||||
g := &ReplyItem{
|
||||
GroupId: groupId,
|
||||
UserId: userId,
|
||||
Content: content,
|
||||
ItemName: itemName,
|
||||
}
|
||||
DB.Create(g)
|
||||
}
|
||||
func UpdateReplyContent(id, userId, title, content string) {
|
||||
r := &ReplyItem{
|
||||
ItemName: title,
|
||||
Content: content,
|
||||
}
|
||||
DB.Model(&ReplyItem{}).Where("user_id = ? and id = ?", userId, id).Update(r)
|
||||
}
|
||||
func DeleteReplyContent(id string, userId string) {
|
||||
DB.Where("user_id = ? and id = ?", userId, id).Delete(ReplyItem{})
|
||||
}
|
||||
func DeleteReplyGroup(id string, userId string) {
|
||||
DB.Where("user_id = ? and id = ?", userId, id).Delete(ReplyGroup{})
|
||||
DB.Where("user_id = ? and group_id = ?", userId, id).Delete(ReplyItem{})
|
||||
}
|
||||
func FindReplyBySearcch(userId interface{}, search string) []*ReplyGroup {
|
||||
var replyGroups []*ReplyGroup
|
||||
var replyItems []*ReplyItem
|
||||
DB.Where("user_id = ?", userId).Find(&replyGroups)
|
||||
DB.Where("user_id = ? and content like ?", userId, "%"+search+"%").Find(&replyItems)
|
||||
temp := make(map[string]*ReplyGroup)
|
||||
for _, replyGroup := range replyGroups {
|
||||
replyGroup.Items = make([]*ReplyItem, 0)
|
||||
temp[replyGroup.Id] = replyGroup
|
||||
}
|
||||
for _, replyItem := range replyItems {
|
||||
temp[replyItem.GroupId].Items = append(temp[replyItem.GroupId].Items, replyItem)
|
||||
}
|
||||
var newReplyGroups []*ReplyGroup = make([]*ReplyGroup, 0)
|
||||
for _, replyGroup := range replyGroups {
|
||||
if len(replyGroup.Items) != 0 {
|
||||
newReplyGroups = append(newReplyGroups, replyGroup)
|
||||
}
|
||||
}
|
||||
return newReplyGroups
|
||||
}
|
||||
27
models/roles.go
Normal file
@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
type Role struct {
|
||||
Id string `json:"role_id"`
|
||||
Name string `json:"role_name"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func FindRoles() []Role {
|
||||
var roles []Role
|
||||
DB.Order("id desc").Find(&roles)
|
||||
return roles
|
||||
}
|
||||
func FindRole(id interface{}) Role {
|
||||
var role Role
|
||||
DB.Where("id = ?", id).First(&role)
|
||||
return role
|
||||
}
|
||||
func SaveRole(id string, name string, method string, path string) {
|
||||
role := &Role{
|
||||
Method: method,
|
||||
Name: name,
|
||||
Path: path,
|
||||
}
|
||||
DB.Model(role).Where("id=?", id).Update(role)
|
||||
}
|
||||
25
models/user_client.go
Normal file
@ -0,0 +1,25 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type User_client struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
Kefu string `json:"kefu"`
|
||||
Client_id string `json:"client_id"`
|
||||
Created_at string `json:"created_at"`
|
||||
}
|
||||
|
||||
func CreateUserClient(kefu, clientId string) uint {
|
||||
u := &User_client{
|
||||
Kefu: kefu,
|
||||
Client_id: clientId,
|
||||
Created_at: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
DB.Create(u)
|
||||
return u.ID
|
||||
}
|
||||
func FindClients(kefu string) []User_client {
|
||||
var arr []User_client
|
||||
DB.Where("kefu = ?", kefu).Find(&arr)
|
||||
return arr
|
||||
}
|
||||
27
models/user_roles.go
Normal file
@ -0,0 +1,27 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type User_role struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
UserId string `json:"user_id"`
|
||||
RoleId uint `json:"role_id"`
|
||||
}
|
||||
|
||||
func FindRoleByUserId(userId interface{}) User_role {
|
||||
var uRole User_role
|
||||
DB.Where("user_id = ?", userId).First(&uRole)
|
||||
return uRole
|
||||
}
|
||||
func CreateUserRole(userId uint, roleId uint) {
|
||||
uRole := &User_role{
|
||||
UserId: strconv.Itoa(int(userId)),
|
||||
RoleId: roleId,
|
||||
}
|
||||
DB.Create(uRole)
|
||||
}
|
||||
func DeleteRoleByUserId(userId interface{}) {
|
||||
DB.Where("user_id = ?", userId).Delete(User_role{})
|
||||
}
|
||||
76
models/users.go
Normal file
@ -0,0 +1,76 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Model
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
Avator string `json:"avator"`
|
||||
RoleName string `json:"role_name" sql:"-"`
|
||||
RoleId string `json:"role_id" sql:"-"`
|
||||
}
|
||||
|
||||
func CreateUser(name string, password string, avator string, nickname string) uint {
|
||||
user := &User{
|
||||
Name: name,
|
||||
Password: password,
|
||||
Avator: avator,
|
||||
Nickname: nickname,
|
||||
}
|
||||
user.UpdatedAt = time.Now()
|
||||
DB.Create(user)
|
||||
return user.ID
|
||||
}
|
||||
func UpdateUser(name string, password string, avator string, nickname string) {
|
||||
user := &User{
|
||||
Avator: avator,
|
||||
Nickname: nickname,
|
||||
}
|
||||
user.UpdatedAt = time.Now()
|
||||
if password != "" {
|
||||
user.Password = password
|
||||
}
|
||||
DB.Model(&User{}).Where("name = ?", name).Update(user)
|
||||
}
|
||||
func UpdateUserPass(name string, pass string) {
|
||||
user := &User{
|
||||
Password: pass,
|
||||
}
|
||||
user.UpdatedAt = time.Now()
|
||||
DB.Model(user).Where("name = ?", name).Update("Password", pass)
|
||||
}
|
||||
func UpdateUserAvator(name string, avator string) {
|
||||
user := &User{
|
||||
Avator: avator,
|
||||
}
|
||||
user.UpdatedAt = time.Now()
|
||||
DB.Model(user).Where("name = ?", name).Update("Avator", avator)
|
||||
}
|
||||
func FindUser(username string) User {
|
||||
var user User
|
||||
DB.Where("name = ?", username).First(&user)
|
||||
return user
|
||||
}
|
||||
func FindUserById(id interface{}) User {
|
||||
var user User
|
||||
DB.Select("user.*,role.name role_name,role.id role_id").Joins("join user_role on user.id=user_role.user_id").Joins("join role on user_role.role_id=role.id").Where("user.id = ?", id).First(&user)
|
||||
return user
|
||||
}
|
||||
func DeleteUserById(id string) {
|
||||
DB.Where("id = ?", id).Delete(User{})
|
||||
}
|
||||
func FindUsers() []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)
|
||||
return users
|
||||
}
|
||||
func FindUserRole(query interface{}, id interface{}) User {
|
||||
var user User
|
||||
DB.Select(query).Where("user.id = ?", id).Joins("join user_role on user.id=user_role.user_id").Joins("join role on user_role.role_id=role.id").First(&user)
|
||||
return user
|
||||
}
|
||||
115
models/visitors.go
Normal file
@ -0,0 +1,115 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Visitor struct {
|
||||
Model
|
||||
Name string `json:"name"`
|
||||
Avator string `json:"avator"`
|
||||
SourceIp string `json:"source_ip"`
|
||||
ToId string `json:"to_id"`
|
||||
VisitorId string `json:"visitor_id"`
|
||||
Status uint `json:"status"`
|
||||
Refer string `json:"refer"`
|
||||
City string `json:"city"`
|
||||
ClientIp string `json:"client_ip"`
|
||||
Extra string `json:"extra"`
|
||||
}
|
||||
|
||||
func CreateVisitor(name, avator, sourceIp, toId, visitorId, refer, city, clientIp, extra string) {
|
||||
v := &Visitor{
|
||||
Name: name,
|
||||
Avator: avator,
|
||||
SourceIp: sourceIp,
|
||||
ToId: toId,
|
||||
VisitorId: visitorId,
|
||||
Status: 1,
|
||||
Refer: refer,
|
||||
City: city,
|
||||
ClientIp: clientIp,
|
||||
Extra: extra,
|
||||
}
|
||||
v.UpdatedAt = time.Now()
|
||||
DB.Create(v)
|
||||
}
|
||||
func FindVisitorByVistorId(visitorId string) Visitor {
|
||||
var v Visitor
|
||||
DB.Where("visitor_id = ?", visitorId).First(&v)
|
||||
return v
|
||||
}
|
||||
func FindVisitors(page uint, pagesize uint) []Visitor {
|
||||
offset := (page - 1) * pagesize
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
var visitors []Visitor
|
||||
DB.Offset(offset).Limit(pagesize).Order("status desc, updated_at desc").Find(&visitors)
|
||||
return visitors
|
||||
}
|
||||
func FindVisitorsByKefuId(page uint, pagesize uint, kefuId string) []Visitor {
|
||||
offset := (page - 1) * pagesize
|
||||
if offset <= 0 {
|
||||
offset = 0
|
||||
}
|
||||
var visitors []Visitor
|
||||
//sql := fmt.Sprintf("select * from visitor where id>=(select id from visitor where to_id='%s' order by updated_at desc limit %d,1) and to_id='%s' order by updated_at desc limit %d ", kefuId, offset, kefuId, pagesize)
|
||||
//DB.Raw(sql).Scan(&visitors)
|
||||
DB.Where("to_id=?", kefuId).Offset(offset).Limit(pagesize).Order("updated_at desc").Find(&visitors)
|
||||
return visitors
|
||||
}
|
||||
func FindVisitorsOnline() []Visitor {
|
||||
var visitors []Visitor
|
||||
DB.Where("status = ?", 1).Find(&visitors)
|
||||
return visitors
|
||||
}
|
||||
func UpdateVisitorStatus(visitorId string, status uint) {
|
||||
visitor := Visitor{}
|
||||
DB.Model(&visitor).Where("visitor_id = ?", visitorId).Update("status", status)
|
||||
}
|
||||
func UpdateVisitor(name, avator, visitorId string, status uint, clientIp string, sourceIp string, refer, extra string) {
|
||||
visitor := &Visitor{
|
||||
Status: status,
|
||||
ClientIp: clientIp,
|
||||
SourceIp: sourceIp,
|
||||
Refer: refer,
|
||||
Extra: extra,
|
||||
Name: name,
|
||||
Avator: avator,
|
||||
}
|
||||
visitor.UpdatedAt = time.Now()
|
||||
DB.Model(visitor).Where("visitor_id = ?", visitorId).Update(visitor)
|
||||
}
|
||||
func UpdateVisitorKefu(visitorId string, kefuId string) {
|
||||
visitor := Visitor{}
|
||||
DB.Model(&visitor).Where("visitor_id = ?", visitorId).Update("to_id", kefuId)
|
||||
}
|
||||
|
||||
//查询条数
|
||||
func CountVisitors() uint {
|
||||
var count uint
|
||||
DB.Model(&Visitor{}).Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
//查询条数
|
||||
func CountVisitorsByKefuId(kefuId string) uint {
|
||||
var count uint
|
||||
DB.Model(&Visitor{}).Where("to_id=?", kefuId).Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
//查询每天条数
|
||||
type EveryDayNum struct {
|
||||
Day string `json:"day"`
|
||||
Num int64 `json:"num"`
|
||||
}
|
||||
|
||||
func CountVisitorsEveryDay(toId string) []EveryDayNum {
|
||||
var results []EveryDayNum
|
||||
DB.Raw("select DATE_FORMAT(created_at,'%y-%m-%d') as day ,"+
|
||||
"count(*) as num from visitor where to_id=? group by day order by day desc limit 30",
|
||||
toId).Scan(&results)
|
||||
return results
|
||||
}
|
||||
54
models/welcomes.go
Normal file
@ -0,0 +1,54 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Welcome struct {
|
||||
ID uint `gorm:"primary_key" json:"id"`
|
||||
UserId string `json:"user_id"`
|
||||
Keyword string `json:"keyword"`
|
||||
Content string `json:"content"`
|
||||
IsDefault uint `json:"is_default"`
|
||||
Ctime time.Time `json:"ctime"`
|
||||
}
|
||||
|
||||
func CreateWelcome(userId string, content string) uint {
|
||||
if userId == "" || content == "" {
|
||||
return 0
|
||||
}
|
||||
w := &Welcome{
|
||||
UserId: userId,
|
||||
Content: content,
|
||||
Ctime: time.Now(),
|
||||
Keyword: "welcome",
|
||||
}
|
||||
DB.Create(w)
|
||||
return w.ID
|
||||
}
|
||||
func UpdateWelcome(userId string, id string, content string) uint {
|
||||
if userId == "" || content == "" {
|
||||
return 0
|
||||
}
|
||||
w := &Welcome{
|
||||
Content: content,
|
||||
}
|
||||
DB.Model(w).Where("user_id = ? and id = ?", userId, id).Update(w)
|
||||
return w.ID
|
||||
}
|
||||
func FindWelcomeByUserIdKey(userId interface{}, keyword interface{}) Welcome {
|
||||
var w Welcome
|
||||
DB.Where("user_id = ? and keyword=?", userId, keyword).First(&w)
|
||||
return w
|
||||
}
|
||||
func FindWelcomesByUserId(userId interface{}) []Welcome {
|
||||
var w []Welcome
|
||||
DB.Where("user_id = ?", userId).Find(&w)
|
||||
return w
|
||||
}
|
||||
func FindWelcomesByKeyword(userId interface{}, keyword interface{}) []Welcome {
|
||||
var w []Welcome
|
||||
DB.Where("user_id = ? and keyword=?", userId, keyword).Find(&w)
|
||||
return w
|
||||
}
|
||||
func DeleteWelcome(userId interface{}, id string) {
|
||||
DB.Where("user_id = ? and id = ?", userId, id).Delete(Welcome{})
|
||||
}
|
||||
96
router/api.go
Normal file
@ -0,0 +1,96 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/controller"
|
||||
"goflylivechat/middleware"
|
||||
"goflylivechat/ws"
|
||||
)
|
||||
|
||||
func InitApiRouter(engine *gin.Engine) {
|
||||
//路由分组
|
||||
v2 := engine.Group("/2")
|
||||
{
|
||||
//获取消息
|
||||
v2.GET("/messages", controller.GetMessagesV2)
|
||||
//发送单条信息
|
||||
v2.POST("/message", middleware.Ipblack, controller.SendMessageV2)
|
||||
//关闭连接
|
||||
v2.GET("/message_close", controller.SendCloseMessageV2)
|
||||
//分页查询消息
|
||||
v2.GET("/messagesPages", controller.GetMessagespages)
|
||||
}
|
||||
engine.GET("/captcha", controller.GetCaptcha)
|
||||
engine.POST("/check", controller.LoginCheckPass)
|
||||
|
||||
engine.GET("/userinfo", middleware.JwtApiMiddleware, controller.GetKefuInfoAll)
|
||||
engine.POST("/register", middleware.Ipblack, controller.PostKefuRegister)
|
||||
engine.POST("/install", controller.PostInstall)
|
||||
//前后聊天
|
||||
engine.GET("/ws_kefu", middleware.JwtApiMiddleware, ws.NewKefuServer)
|
||||
engine.GET("/ws_visitor", middleware.Ipblack, ws.NewVisitorServer)
|
||||
|
||||
engine.GET("/messages", controller.GetVisitorMessage)
|
||||
engine.GET("/message_notice", controller.SendVisitorNotice)
|
||||
//上传文件
|
||||
engine.POST("/uploadimg", middleware.Ipblack, controller.UploadImg)
|
||||
//上传文件
|
||||
engine.POST("/uploadfile", middleware.Ipblack, controller.UploadFile)
|
||||
//获取未读消息数
|
||||
engine.GET("/message_status", controller.GetVisitorMessage)
|
||||
//设置消息已读
|
||||
engine.POST("/message_status", controller.GetVisitorMessage)
|
||||
|
||||
//获取客服信息
|
||||
engine.POST("/kefuinfo_client", middleware.JwtApiMiddleware, controller.PostKefuClient)
|
||||
engine.GET("/kefuinfo", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetKefuInfo)
|
||||
engine.GET("/kefuinfo_setting", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetKefuInfoSetting)
|
||||
engine.POST("/kefuinfo", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuInfo)
|
||||
engine.DELETE("/kefuinfo", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.DeleteKefuInfo)
|
||||
engine.GET("/kefulist", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetKefuList)
|
||||
engine.GET("/other_kefulist", middleware.JwtApiMiddleware, controller.GetOtherKefuList)
|
||||
engine.GET("/trans_kefu", middleware.JwtApiMiddleware, controller.PostTransKefu)
|
||||
engine.POST("/modifypass", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuPass)
|
||||
engine.POST("/modifyavator", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostKefuAvator)
|
||||
//角色列表
|
||||
engine.GET("/roles", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetRoleList)
|
||||
engine.POST("/role", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostRole)
|
||||
|
||||
engine.GET("/visitors_online", controller.GetVisitorOnlines)
|
||||
engine.GET("/visitors_kefu_online", middleware.JwtApiMiddleware, controller.GetKefusVisitorOnlines)
|
||||
engine.GET("/clear_online_tcp", controller.DeleteOnlineTcp)
|
||||
engine.POST("/visitor_login", middleware.Ipblack, controller.PostVisitorLogin)
|
||||
//engine.POST("/visitor", controller.PostVisitor)
|
||||
engine.GET("/visitor", middleware.JwtApiMiddleware, controller.GetVisitor)
|
||||
engine.GET("/visitors", middleware.JwtApiMiddleware, controller.GetVisitors)
|
||||
engine.GET("/statistics", middleware.JwtApiMiddleware, controller.GetStatistics)
|
||||
//前台接口
|
||||
engine.GET("/about", controller.GetAbout)
|
||||
engine.POST("/about", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostAbout)
|
||||
engine.GET("/aboutpages", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetAbouts)
|
||||
engine.GET("/notice", controller.GetNotice)
|
||||
engine.POST("/ipblack", middleware.JwtApiMiddleware, middleware.Ipblack, controller.PostIpblack)
|
||||
engine.DELETE("/ipblack", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.DelIpblack)
|
||||
engine.GET("/ipblacks_all", middleware.JwtApiMiddleware, controller.GetIpblacks)
|
||||
engine.GET("/ipblacks", middleware.JwtApiMiddleware, controller.GetIpblacksByKefuId)
|
||||
engine.GET("/configs", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.GetConfigs)
|
||||
engine.POST("/config", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostConfig)
|
||||
engine.GET("/config", controller.GetConfig)
|
||||
engine.GET("/autoreply", controller.GetAutoReplys)
|
||||
engine.GET("/replys", middleware.JwtApiMiddleware, controller.GetReplys)
|
||||
engine.POST("/reply", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostReply)
|
||||
engine.POST("/reply_content", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.PostReplyContent)
|
||||
engine.POST("/reply_content_save", middleware.JwtApiMiddleware, controller.PostReplyContentSave)
|
||||
engine.DELETE("/reply_content", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.DelReplyContent)
|
||||
engine.DELETE("/reply", middleware.JwtApiMiddleware, middleware.RbacAuth, controller.DelReplyGroup)
|
||||
engine.POST("/reply_search", middleware.JwtApiMiddleware, controller.PostReplySearch)
|
||||
//客服路由分组
|
||||
kefuGroup := engine.Group("/kefu")
|
||||
kefuGroup.Use(middleware.JwtApiMiddleware)
|
||||
{
|
||||
kefuGroup.GET("/chartStatistics", controller.GetChartStatistic)
|
||||
kefuGroup.POST("/message", controller.SendKefuMessage)
|
||||
}
|
||||
//微信接口
|
||||
engine.GET("/micro_program", middleware.JwtApiMiddleware, controller.GetCheckWeixinSign)
|
||||
}
|
||||
32
router/view.go
Normal file
@ -0,0 +1,32 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"goflylivechat/middleware"
|
||||
"goflylivechat/tmpl"
|
||||
)
|
||||
|
||||
func InitViewRouter(engine *gin.Engine) {
|
||||
engine.GET("/", tmpl.PageIndex)
|
||||
|
||||
engine.GET("/login", tmpl.PageLogin)
|
||||
engine.GET("/pannel", tmpl.PagePannel)
|
||||
engine.GET("/chatIndex", tmpl.PageChat)
|
||||
engine.GET("/livechat", tmpl.PageChat)
|
||||
engine.GET("/main", middleware.JwtPageMiddleware, tmpl.PageMain)
|
||||
engine.GET("/chat_main", middleware.JwtPageMiddleware, middleware.DomainLimitMiddleware, tmpl.PageChatMain)
|
||||
engine.GET("/setting", middleware.DomainLimitMiddleware, tmpl.PageSetting)
|
||||
engine.GET("/setting_statistics", tmpl.PageSettingStatis)
|
||||
engine.GET("/setting_indexpage", tmpl.PageSettingIndexPage)
|
||||
engine.GET("/setting_indexpages", tmpl.PageSettingIndexPages)
|
||||
engine.GET("/setting_mysql", tmpl.PageSettingMysql)
|
||||
engine.GET("/setting_welcome", tmpl.PageSettingWelcome)
|
||||
engine.GET("/setting_deploy", tmpl.PageSettingDeploy)
|
||||
engine.GET("/setting_kefu_list", tmpl.PageKefuList)
|
||||
engine.GET("/setting_avator", tmpl.PageAvator)
|
||||
engine.GET("/setting_modifypass", tmpl.PageModifypass)
|
||||
engine.GET("/setting_ipblack", tmpl.PageIpblack)
|
||||
engine.GET("/setting_config", tmpl.PageConfig)
|
||||
engine.GET("/mail_list", tmpl.PageMailList)
|
||||
engine.GET("/roles_list", tmpl.PageRoleList)
|
||||
}
|
||||
BIN
static/.DS_Store
vendored
Normal file
BIN
static/cdn/.DS_Store
vendored
Normal file
1
static/cdn/element-ui/2.15.1/index.js
Normal file
1
static/cdn/element-ui/2.15.1/theme-chalk/index.min.css
vendored
Normal file
1
static/cdn/element-ui/2.15.7/index.js
Normal file
1
static/cdn/element-ui/2.15.7/theme-chalk/index.min.css
vendored
Normal file
2
static/cdn/jquery/3.6.0/jquery.min.js
vendored
Normal file
28
static/cdn/jquery/jquery.qrcode.min.js
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
(function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;d<a.length&&0==a[d];)d++;this.num=Array(a.length-d+c);for(var b=0;b<a.length-d;b++)this.num[b]=a[b+d]}function p(a,c){this.totalCount=a;this.dataCount=c}function t(){this.buffer=[];this.length=0}u.prototype={getLength:function(){return this.data.length},
|
||||
write:function(a){for(var c=0;c<this.data.length;c++)a.put(this.data.charCodeAt(c),8)}};o.prototype={addData:function(a){this.dataList.push(new u(a));this.dataCache=null},isDark:function(a,c){if(0>a||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e<c.length;e++)b+=c[e].dataCount;
|
||||
for(e=0;e<this.dataList.length;e++)c=this.dataList[e],d.put(c.mode,4),d.put(c.getLength(),j.getLengthInBits(c.mode,a)),c.write(d);if(d.getLengthInBits()<=8*b)break}this.typeNumber=a}this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17;this.modules=Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=Array(this.moduleCount);for(var b=0;b<this.moduleCount;b++)this.modules[d][b]=null}this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-
|
||||
7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(a,c);7<=this.typeNumber&&this.setupTypeNumber(a);null==this.dataCache&&(this.dataCache=o.createData(this.typeNumber,this.errorCorrectLevel,this.dataList));this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,c){for(var d=-1;7>=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
|
||||
0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c<this.modules.length;c++)for(var d=1*c,b=0;b<this.modules[c].length;b++){var e=1*b;this.modules[c][b]&&(a.beginFill(0,100),a.moveTo(e,d),a.lineTo(e+1,d),a.lineTo(e+1,d+1),a.lineTo(e,d+1),a.endFill())}return a},
|
||||
setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(a=8;a<this.moduleCount-8;a++)null==this.modules[6][a]&&(this.modules[6][a]=0==a%2)},setupPositionAdjustPattern:function(){for(var a=j.getPatternPosition(this.typeNumber),c=0;c<a.length;c++)for(var d=0;d<a.length;d++){var b=a[c],e=a[d];if(null==this.modules[b][e])for(var f=-2;2>=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
|
||||
j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
|
||||
b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0<i;i-=2)for(6==i&&i--;;){for(var g=0;2>g;g++)if(null==this.modules[b][i-g]){var n=!1;f<a.length&&(n=1==(a[f]>>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
|
||||
c),b=new t,e=0;e<d.length;e++){var f=d[e];b.put(f.mode,4);b.put(f.getLength(),j.getLengthInBits(f.mode,a));f.write(b)}for(e=a=0;e<c.length;e++)a+=c[e].dataCount;if(b.getLengthInBits()>8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
|
||||
0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g<c.length;g++){var n=c[g].dataCount,h=c[g].totalCount-n,b=Math.max(b,n),e=Math.max(e,h);f[g]=Array(n);for(var k=0;k<f[g].length;k++)f[g][k]=255&a.buffer[k+d];d+=n;k=j.getErrorCorrectPolynomial(h);n=(new q(f[g],k.getLength()-1)).mod(k);i[g]=Array(k.getLength()-1);for(k=0;k<i[g].length;k++)h=k+n.getLength()-i[g].length,i[g][k]=0<=h?n.get(h):0}for(k=g=0;k<c.length;k++)g+=c[k].totalCount;d=Array(g);for(k=n=0;k<b;k++)for(g=0;g<c.length;g++)k<f[g].length&&
|
||||
(d[n++]=f[g][k]);for(k=0;k<e;k++)for(g=0;g<c.length;g++)k<i[g].length&&(d[n++]=i[g][k]);return d};s=4;for(var j={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,
|
||||
78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var c=a<<10;0<=j.getBCHDigit(c)-j.getBCHDigit(j.G15);)c^=j.G15<<j.getBCHDigit(c)-j.getBCHDigit(j.G15);return(a<<10|c)^j.G15_MASK},getBCHTypeNumber:function(a){for(var c=a<<12;0<=j.getBCHDigit(c)-
|
||||
j.getBCHDigit(j.G18);)c^=j.G18<<j.getBCHDigit(c)-j.getBCHDigit(j.G18);return a<<12|c},getBCHDigit:function(a){for(var c=0;0!=a;)c++,a>>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
|
||||
a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;d<a;d++)c=c.multiply(new q([1,l.gexp(d)],0));return c},getLengthInBits:function(a,c){if(1<=c&&10>c)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
|
||||
a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b<c;b++)for(var e=0;e<c;e++){for(var f=0,i=a.isDark(b,e),g=-1;1>=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5<f&&(d+=3+f-5)}for(b=0;b<c-1;b++)for(e=0;e<c-1;e++)if(f=0,a.isDark(b,e)&&f++,a.isDark(b+1,e)&&f++,a.isDark(b,e+1)&&f++,a.isDark(b+1,e+1)&&f++,0==f||4==f)d+=3;for(b=0;b<c;b++)for(e=0;e<c-6;e++)a.isDark(b,e)&&!a.isDark(b,e+1)&&a.isDark(b,e+
|
||||
2)&&a.isDark(b,e+3)&&a.isDark(b,e+4)&&!a.isDark(b,e+5)&&a.isDark(b,e+6)&&(d+=40);for(e=0;e<c;e++)for(b=0;b<c-6;b++)a.isDark(b,e)&&!a.isDark(b+1,e)&&a.isDark(b+2,e)&&a.isDark(b+3,e)&&a.isDark(b+4,e)&&!a.isDark(b+5,e)&&a.isDark(b+6,e)&&(d+=40);for(e=f=0;e<c;e++)for(b=0;b<c;b++)a.isDark(b,e)&&f++;a=Math.abs(100*f/c/c-50)/5;return d+10*a}},l={glog:function(a){if(1>a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
|
||||
LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<<m;for(m=8;256>m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d<this.getLength();d++)for(var b=0;b<a.getLength();b++)c[d+b]^=l.gexp(l.glog(this.get(d))+l.glog(a.get(b)));return new q(c,0)},mod:function(a){if(0>
|
||||
this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b<this.getLength();b++)d[b]=this.get(b);for(b=0;b<a.getLength();b++)d[b]^=l.gexp(l.glog(a.get(b))+c);return(new q(d,0)).mod(a)}};p.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],
|
||||
[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,
|
||||
116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,
|
||||
43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,
|
||||
3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,
|
||||
55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,
|
||||
45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];p.getRSBlocks=function(a,c){var d=p.getRsBlockTable(a,c);if(void 0==d)throw Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+c);for(var b=d.length/3,e=[],f=0;f<b;f++)for(var h=d[3*f+0],g=d[3*f+1],j=d[3*f+2],l=0;l<h;l++)e.push(new p(g,j));return e};p.getRsBlockTable=function(a,c){switch(c){case 1:return p.RS_BLOCK_TABLE[4*(a-1)+0];case 0:return p.RS_BLOCK_TABLE[4*(a-1)+1];case 3:return p.RS_BLOCK_TABLE[4*
|
||||
(a-1)+2];case 2:return p.RS_BLOCK_TABLE[4*(a-1)+3]}};t.prototype={get:function(a){return 1==(this.buffer[Math.floor(a/8)]>>>7-a%8&1)},put:function(a,c){for(var d=0;d<c;d++)this.putBit(1==(a>>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
|
||||
correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f<a.getModuleCount();f++)for(var i=0;i<a.getModuleCount();i++){d.fillStyle=a.isDark(f,i)?h.foreground:h.background;var g=Math.ceil((i+1)*b)-Math.floor(i*b),
|
||||
j=Math.ceil((f+1)*b)-Math.floor(f*b);d.fillRect(Math.round(i*b),Math.round(f*e),g,j)}}else{a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();c=r("<table></table>").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e<a.getModuleCount();e++){f=r("<tr></tr>").css("height",b+"px").appendTo(c);for(i=0;i<a.getModuleCount();i++)r("<td></td>").css("width",
|
||||
d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery);
|
||||
6
static/cdn/vue/2.6.11/vue.min.js
vendored
Normal file
BIN
static/css/.DS_Store
vendored
Normal file
7
static/css/bootstrap.min.css
vendored
Normal file
586
static/css/common.css
Normal file
@ -0,0 +1,586 @@
|
||||
*{padding:0;margin:0}
|
||||
.floatRight{float: right;}
|
||||
.clear{clear: both;}
|
||||
.visitorBody{
|
||||
background-color: #4c4c4c;
|
||||
}
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: inherit;
|
||||
}
|
||||
.el-menu.el-menu--horizontal{
|
||||
border-bottom: none;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.el-menu--horizontal>.el-menu-item.is-active{
|
||||
border-bottom: 3px solid #409EFF;
|
||||
}
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.chatBg .el-tabs__header{margin: 0;}
|
||||
|
||||
|
||||
.visitorFaceBtn{
|
||||
float: left;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.visitorFaceBox{
|
||||
position: absolute;
|
||||
bottom: 85px;
|
||||
}
|
||||
.visitorIconBtns{
|
||||
margin-right: 8px;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
.visitorIconBtns:hover{
|
||||
color:#484848;
|
||||
}
|
||||
|
||||
.kefuFaceBox{
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.faceBox{
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
z-index: 99999999;
|
||||
padding: 2px;
|
||||
}
|
||||
.faceBoxList{
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.faceBoxList li{
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
border: 1px solid #e8e8e8;
|
||||
width: 28px;
|
||||
overflow: hidden;
|
||||
margin: -1px 0 0 -1px;
|
||||
padding: 4px 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@-webkit-keyframes bounce-up {
|
||||
25% {-webkit-transform: translateY(10px);}
|
||||
50%, 100% {-webkit-transform: translateY(0);}
|
||||
75% {-webkit-transform: translateY(-10px);}
|
||||
}
|
||||
|
||||
@keyframes bounce-up {
|
||||
25% {transform: translateY(10px);}
|
||||
50%, 100% {transform: translateY(0);}
|
||||
75% {transform: translateY(-10px);}
|
||||
}
|
||||
.animate-bounce-up{ -webkit-animation: bounce-up 1.4s linear infinite;animation: bounce-up 1.4s linear infinite;}
|
||||
.mainLogo{
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
.mainVersion{
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-container{
|
||||
height: 100%;
|
||||
}
|
||||
.el-aside {
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
.textDark {color: #343a40;}
|
||||
.bgInfo {background-color: #17a2b8}
|
||||
.bgSuccess {background-color: #28a745}
|
||||
.bgDanger {background-color: #dc3545}
|
||||
.bgInfo {background-color: #17a2b8}
|
||||
.smallBox {
|
||||
border-radius: .25rem;
|
||||
box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2);
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
}
|
||||
.settingMain h2{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.settingMain h3{
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.bigPic{
|
||||
background: #ccc;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
display: none;
|
||||
text-align: center;
|
||||
}
|
||||
/*客服聊天主板*/
|
||||
.chatBg{background: #fff;border: solid 1px #e6e6e6;overflow: hidden;}
|
||||
.chatLeft{height:100%;overflow:auto;}
|
||||
.chatLeft .el-tabs__nav,.chatRight .el-tabs__nav {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.chatRight{
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.onlineUsers {
|
||||
padding: 10px 4px;
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
border-bottom: solid 1px #f1f1f1;
|
||||
border-left: 4px solid #fff;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
.onlineUsers a{
|
||||
color: #333;
|
||||
}
|
||||
.onlineUsers:hover, .onlineUsers.cur {
|
||||
background-color: rgb(238 247 255);
|
||||
border-left: 4px solid #4299e2;
|
||||
}
|
||||
.imgGray {-webkit-filter: grayscale(100%);-ms-filter: grayscale(100%);filter: grayscale(100%);filter: gray;color:#888;}
|
||||
.hasLastMsg{line-height: normal;}
|
||||
.lastNewMsg{font-size: 12px;color: #7f7f7f;margin-top: 4px;overflow: hidden;height: 16px;}
|
||||
/*客服页*/
|
||||
.chatKfPageApp{
|
||||
max-width: 800px;
|
||||
margin:0 auto;
|
||||
}
|
||||
.chatCenter {
|
||||
background: #fff;
|
||||
max-width: 840px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 2px 2px 6px rgba(0,0,0,.3);
|
||||
border-top: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
.chatContext{
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
background: rgb(245,245,245);
|
||||
}
|
||||
.chatBox{
|
||||
/*overflow-y: auto;*/
|
||||
overflow-x: hidden;
|
||||
background: rgb(245,245,245);
|
||||
/*margin-bottom: 80px;*/
|
||||
}
|
||||
.chatVisitorPage{
|
||||
height: calc(100% - 86px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.chatVisitorPage .chatBox{
|
||||
padding: 5px 4px;
|
||||
}
|
||||
.chatBox .el-col{margin:10px 0;}
|
||||
.chatUser{
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
color: #999;
|
||||
text-align: left;
|
||||
margin: -5px 0px 3px 0px;
|
||||
}
|
||||
.chatMainPage{
|
||||
margin-top: 1px;
|
||||
}
|
||||
.chatContent {
|
||||
background-color: rgb(255,255,255);
|
||||
color: #000;
|
||||
border: 1px solid rgb(237,237,237);
|
||||
min-height: 20px;
|
||||
padding: 8px 15px;
|
||||
word-break: break-all;
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
line-height: 21px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.chatContent2 {
|
||||
border-radius: 8px 8px 8px 0px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.chatBoxMe{
|
||||
margin-top: 10px;
|
||||
}
|
||||
.chatBoxMe .chatContent2 {
|
||||
border-radius: 8px 8px 0px 8px;
|
||||
background-color: #cde0ff;
|
||||
color: #000;
|
||||
}
|
||||
a{color: #07a9fe;text-decoration: none;}
|
||||
|
||||
|
||||
.chatBoxMe .chatContent{float: right;}
|
||||
.chatBoxMe .chatContent:after{border-left-color: rgba(152,225,101,1);border-right:none;left:auto;right: -5px;}
|
||||
.chatBoxMe .chatContent:before{border-left-color: rgb(152,225,101);border-right:none;right: -5px;left: auto;}
|
||||
.chatBoxMe .el-col-3{float: right;text-align: right;}
|
||||
.chatBoxMe .chatUser{text-align: right}
|
||||
.btnArea{width: 10%;float: right;}
|
||||
|
||||
|
||||
.chatTitle{height: 30px;line-height: 30px;color: #1989fa}
|
||||
.chatBoxSend{
|
||||
position: relative;
|
||||
padding-top: 5px;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
z-index: 99;
|
||||
border-top: 1px solid #e4e4e4;
|
||||
}
|
||||
.chatBoxSendBtn{float: right;margin: 12px 4px 0 0;}
|
||||
.footContact{text-align: center;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.footContact a{font-size: 12px;color: #999;text-decoration: none;}
|
||||
.chatTime{text-align: center;color: #bbb;margin: 12px 0;font-size: 12px;}
|
||||
.chatTime span{display: inline-block;padding: 2px 5px;background: rgb(218,218,218);color: #fff;}
|
||||
.chatTimeHide{display: none;}
|
||||
.visitorInfo .el-menu-item{
|
||||
font-size: 12px;
|
||||
}
|
||||
.chatRightTitle{
|
||||
color: #303133;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding:0 10px;
|
||||
font-size: 14px;
|
||||
border-bottom:1px solid #E4E7ED;
|
||||
}
|
||||
.chatRightTitle a{
|
||||
float: right;
|
||||
text-decoration: none;
|
||||
color: #519eff;
|
||||
}
|
||||
.replyBox{
|
||||
font-size: 12px;
|
||||
min-height: 300px;
|
||||
background: #fff;
|
||||
}
|
||||
.replyItem:hover{
|
||||
background-color: #f0f9eb;
|
||||
color: #67C23A;
|
||||
}
|
||||
.replyContent{
|
||||
padding: 0 10px;
|
||||
}
|
||||
.replySearch{
|
||||
margin: 5px 7px;
|
||||
width: 96% !important;
|
||||
}
|
||||
.iconBtnsBox{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #7e7e7e;
|
||||
height: 35px;
|
||||
}
|
||||
.iconBtnsBox .iconBtn{
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.kefuMainBg{background: #f5f5f5;border-right: solid 1px #e6e6e6;boder-top:none;}
|
||||
.kefuFuncBtns{background:#fff;margin: 2px 0px;color: #7f7f7f;border-bottom: 1px solid #e6e6e6;font-size: 12px;padding: 5px 0px;}
|
||||
|
||||
.kefuFuncBox{
|
||||
position: absolute;
|
||||
height: 135px;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
.kefuFuncBox .faceBox{
|
||||
position: absolute;
|
||||
bottom:100px;
|
||||
}
|
||||
.kefuFolderBtn{vertical-align: middle;}
|
||||
.visitorReply{
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.visitorReplyTitle{
|
||||
|
||||
}
|
||||
.visitorReplyContent{
|
||||
color:#007aff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.kefuSendBtn{
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
z-index: 2;
|
||||
}
|
||||
.clear{clear:both;}
|
||||
|
||||
|
||||
.chatEntTitle {
|
||||
display: none;
|
||||
width: 100%;
|
||||
z-index: 9;
|
||||
margin: 0 auto;
|
||||
height: 56px;
|
||||
overflow: hidden;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
background-color: rgb(11 113 236);
|
||||
background-image: url(../images/visitor_title_bg.png);
|
||||
background-size: auto 72px;
|
||||
background-position: center 0;
|
||||
align-items: center;
|
||||
}
|
||||
.chatEntTitle span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.chatEntTitle .el-badge__content.is-fixed.is-dot {
|
||||
right: 17px;
|
||||
bottom: -5px;
|
||||
top: unset;
|
||||
}
|
||||
.chatEntBox{
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.chatArticle{
|
||||
display: none;
|
||||
}
|
||||
.visitorIconBox{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color:#9c9c9c;
|
||||
}
|
||||
.visitorIconBox .iconBtn{
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.productCard{
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
border-radius: 2px;
|
||||
width: auto;
|
||||
max-width: 730px;
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.productCard img{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
.productCard .productCardPrice{
|
||||
color: #ff7736;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.productCard .productCardTitle{
|
||||
color: #333;
|
||||
}
|
||||
@media screen and (min-width: 900px) {
|
||||
.chatCenter {
|
||||
max-height: 650px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,.15);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.chatVisitorPage {
|
||||
height: calc(100% - 156px);
|
||||
}
|
||||
.chatEntTitle{display: flex;}
|
||||
.chatEntTitleLogo {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.visitorBody {
|
||||
display: flex;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
.chatEntBox {
|
||||
width: calc(100% - 265px);
|
||||
float: left;
|
||||
border-right: 1px solid #e6e6e6;
|
||||
}
|
||||
.chatArticle {
|
||||
display: block;
|
||||
width: 260px;
|
||||
float: right;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.hotQuestionTitle {
|
||||
padding: 10px 10px 10px 10px;
|
||||
border-bottom: 1px solid rgba(0,0,0,.09);
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
}
|
||||
.hotQuestionTitle .fire {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
color: rgb(250, 84, 28);
|
||||
}
|
||||
}
|
||||
.visitorEditorArea{
|
||||
}
|
||||
.visitorEditorArea textarea {
|
||||
padding: 7px 0 7px 8px;
|
||||
font-size:16px;
|
||||
line-height: 21px;
|
||||
border: none;
|
||||
}
|
||||
.visitorEditorBtn{
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 2px;
|
||||
}
|
||||
.mainLeftMenu {
|
||||
width: 70px;
|
||||
float: left;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: #0a2f5a;
|
||||
position: relative;
|
||||
}
|
||||
.menuLeftItem {
|
||||
height: 55px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
padding-top: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.menuLeftItem i, .menuLeftItem .el-badge {
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.menuLeftItem span {
|
||||
font-size: 12px;
|
||||
}
|
||||
.menuLeftItemLogout {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
}
|
||||
.mainRight {
|
||||
width: calc(100% - 70px);
|
||||
height: 100%;
|
||||
float: left;
|
||||
}
|
||||
.mainIframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.menuLeftItem:active, .menuLeftItem.active {
|
||||
background: #2b5a96;
|
||||
}
|
||||
.chatNotice {
|
||||
text-align: center;
|
||||
margin: 12px 0px;
|
||||
}
|
||||
.chatNoticeContent {
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
color: rgba(0,0,0,.45);
|
||||
margin: 0 24px;
|
||||
max-width: calc(100% - 48px);
|
||||
background-color: #fff;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
.chatRow {
|
||||
display: flex;
|
||||
}
|
||||
.chatRowAvator {
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.chatBoxMe .chatRow {
|
||||
float: right;
|
||||
}
|
||||
.chatBoxMe .chatRowAvator {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.allNotice{
|
||||
font-size: 12px;
|
||||
margin: 10px 0px;
|
||||
line-height: 23px;
|
||||
color: #666;
|
||||
}
|
||||
.chatArea .el-textarea__inner{
|
||||
border: none;
|
||||
}
|
||||
.tongji{
|
||||
display: flex;
|
||||
}
|
||||
.tongji .tongjiItem{
|
||||
background: #fff;
|
||||
margin: 10px;
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
.tongji .tongjiHeader{
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
color:#17233d;
|
||||
}
|
||||
.tongji .tongjiBody{
|
||||
font-size: 30px;
|
||||
margin: 0px 10px;
|
||||
padding: 10px 0px;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
color: #515a6e;
|
||||
}
|
||||
.tongji .tongjiFooter{
|
||||
font-size: 14px;
|
||||
margin: 10px 10px;
|
||||
color: #515a6e;
|
||||
}
|
||||
.tongji .tongjiFooter span{
|
||||
color: red;
|
||||
padding: 0 3px;
|
||||
}
|
||||
/* 定义滚动条的宽度、高度和背景色 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 10px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 定义滚动条滑块的样式 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #c5c5c5;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 定义滚动条滑块在 hover 状态下的样式 */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #999;
|
||||
}
|
||||
37
static/css/front.css
Normal file
@ -0,0 +1,37 @@
|
||||
::-webkit-scrollbar
|
||||
{
|
||||
width: 5px;
|
||||
height: 110px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
/*定义滚动条轨道 内阴影+圆角*/
|
||||
::-webkit-scrollbar-track
|
||||
{
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||
border-radius: 10px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
/*定义滑块 内阴影+圆角*/
|
||||
::-webkit-scrollbar-thumb
|
||||
{
|
||||
border-radius: 10px;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
|
||||
background-color: #bdbdbd;
|
||||
}
|
||||
/*滑块效果*/
|
||||
::-webkit-scrollbar-thumb:hover
|
||||
{
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
|
||||
background: rgba(0,0,0,0.4);
|
||||
}
|
||||
/*IE滚动条颜色*/
|
||||
html {
|
||||
scrollbar-face-color:#bfbfbf;/*滚动条颜色*/
|
||||
scrollbar-highlight-color:#000;
|
||||
scrollbar-3dlight-color:#000;
|
||||
scrollbar-darkshadow-color:#000;
|
||||
scrollbar-Shadow-color:#adadad;/*滑块边色*/
|
||||
scrollbar-arrow-color:rgba(0,0,0,0.4);/*箭头颜色*/
|
||||
scrollbar-track-color:#eeeeee;/*背景颜色*/
|
||||
}
|
||||
229
static/css/gofly-front.css
Normal file
@ -0,0 +1,229 @@
|
||||
.launchButtonBox{
|
||||
position: fixed!important;
|
||||
bottom: 2px;
|
||||
right: 20px;
|
||||
left: auto;
|
||||
z-index: 999999;
|
||||
}
|
||||
.launchButtonNotice{
|
||||
width: 270px;
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
clear: both;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 3px 15px 0 rgba(0,0,0,.25)!important;
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
right: 0;
|
||||
z-index: 999999;
|
||||
color: #222;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
.launchButtonNotice:after{
|
||||
content: "";
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 6px solid transparent;
|
||||
border-top-color: rgba(255,255,255,1);
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
.launchButtonNotice a{
|
||||
color: #07a9fe;!important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.launchIcon{
|
||||
background: #ff305f;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 0px;
|
||||
color: #fff;
|
||||
display: none;
|
||||
}
|
||||
.launchButton{
|
||||
height: 42px;
|
||||
width: auto;
|
||||
z-index: 10000000000000!important;
|
||||
border-radius: 2px;
|
||||
border: 0!important;
|
||||
background: rgb(18, 122, 202);
|
||||
box-shadow: rgba(0, 0, 0, 0.06) 0px 1px 6px, rgba(0, 0, 0, 0.16) 0px 2px 32px;
|
||||
box-sizing: border-box!important;
|
||||
padding: 0 25px;
|
||||
cursor: pointer!important;
|
||||
outline: 0!important;
|
||||
display: inline-block;
|
||||
margin: 0!important;
|
||||
-webkit-font-smoothing: antialiased!important;
|
||||
-webkit-tap-highlight-color: transparent!important;
|
||||
color: #ffffff;
|
||||
position: relative;
|
||||
}
|
||||
.launchButton:hover {
|
||||
box-shadow: 0 3px 20px 0 rgba(0,0,0,.5)!important;
|
||||
}
|
||||
.launchButton svg{
|
||||
width: 28px;
|
||||
height: 48px;
|
||||
}
|
||||
.launchButtonText {
|
||||
color: #fff;
|
||||
display: inline-block!important;
|
||||
font-family: -apple-system,BlinkMacSystemFont,segoe ui,Roboto,Oxygen,Ubuntu,Cantarell,fira sans,droid sans,helvetica neue,sans-serif!important;
|
||||
font-size: 1em;
|
||||
line-height: 42px;
|
||||
font-weight: 700!important;
|
||||
overflow: hidden!important;
|
||||
text-overflow: ellipsis!important;
|
||||
vertical-align: top!important;
|
||||
white-space: nowrap;
|
||||
transition: .6s ease-in-out!important;
|
||||
}
|
||||
.launchButtonNotice .flyAvatar{
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
border:1px solid #cccccc;
|
||||
float: left;
|
||||
}
|
||||
.flyAvatar{
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
border:2px solid #fff;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.layui-layer-title .flyAvatar{
|
||||
margin-top: 5px;
|
||||
}
|
||||
.launchButtonNotice .flyUsername{
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.launchButtonNotice .flyUser{
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
line-height: 32px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.launchButtonNotice .flyClose{
|
||||
float: right;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#layui-layer19911116{
|
||||
display: none;
|
||||
}
|
||||
.launchPointer{
|
||||
background: #3cc51f;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 42px;
|
||||
border:2px solid #fff;
|
||||
}
|
||||
.launchPointer.offline{
|
||||
background: #ce3c39;
|
||||
}
|
||||
.folderBtn {
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
font-size: 1px;
|
||||
}
|
||||
.folderBtn:before {
|
||||
content: '';
|
||||
float: left;
|
||||
background-color: #9da0a0;
|
||||
width: 15px;
|
||||
height: 3px;
|
||||
margin-left: 2px;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
box-shadow: 2px 2px 0 0 #9da0a0;
|
||||
}
|
||||
.folderBtn:after {
|
||||
content: '';
|
||||
float: left;
|
||||
clear: left;
|
||||
background-color: #d4d6d6;
|
||||
width: 33px;
|
||||
height: 22px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
@-webkit-keyframes bounce-up {
|
||||
25% {-webkit-transform: translateY(4px);}
|
||||
50%, 100% {-webkit-transform: translateY(0);}
|
||||
75% {-webkit-transform: translateY(-4px);}
|
||||
}
|
||||
|
||||
@keyframes bounce-up {
|
||||
25% {transform: translateY(4px);}
|
||||
50%, 100% {transform: translateY(0);}
|
||||
75% {transform: translateY(-4px);}
|
||||
}
|
||||
.animateUpDown{
|
||||
-webkit-animation: bounce-up 0.5s linear infinite;
|
||||
animation: bounce-up 0.5s linear infinite;
|
||||
}
|
||||
@media screen and (max-width: 500px) {
|
||||
.launchButtonBox{
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
right: 0px;
|
||||
}
|
||||
.launchButton{
|
||||
width: 43px;
|
||||
height: auto;
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
bottom: 140px;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
.launchButtonNotice{
|
||||
width: 90%;
|
||||
bottom: 270px;
|
||||
right: 2%;
|
||||
text-align: left;
|
||||
}
|
||||
.launchButtonText{
|
||||
width: 40px;
|
||||
font-size: 8px;
|
||||
line-height: 21px;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
max-height: 145px;
|
||||
}
|
||||
.launchButtonText .flyAvatar{
|
||||
display: block;
|
||||
margin-right: 0px;
|
||||
float: none;
|
||||
}
|
||||
.launchButtonNotice:after{
|
||||
right: 4%;
|
||||
}
|
||||
}
|
||||
51
static/css/icon/iconfont.css
Normal file
@ -0,0 +1,51 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3864484 */
|
||||
src: url('iconfont.woff2?t=1678338039263') format('woff2'),
|
||||
url('iconfont.woff?t=1678338039263') format('woff'),
|
||||
url('iconfont.ttf?t=1678338039263') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-more:before {
|
||||
content: "\e867";
|
||||
}
|
||||
|
||||
.icon-zengjiatianjiajiahao:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.icon-jianshaominimize3:before {
|
||||
content: "\e68b";
|
||||
}
|
||||
|
||||
.icon-folder-fill:before {
|
||||
content: "\e7c4";
|
||||
}
|
||||
|
||||
.icon-jietu:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.icon-duoyuyan:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.icon-jiahao:before {
|
||||
content: "\eaf3";
|
||||
}
|
||||
|
||||
.icon-xiaolian:before {
|
||||
content: "\ec80";
|
||||
}
|
||||
|
||||
.icon-fasong:before {
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
1
static/css/icon/iconfont.js
Normal file
72
static/css/icon/iconfont.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"id": "3864484",
|
||||
"name": "我的客服",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "18991683",
|
||||
"name": "more",
|
||||
"font_class": "more",
|
||||
"unicode": "e867",
|
||||
"unicode_decimal": 59495
|
||||
},
|
||||
{
|
||||
"icon_id": "8817897",
|
||||
"name": "增加添加加号",
|
||||
"font_class": "zengjiatianjiajiahao",
|
||||
"unicode": "e62a",
|
||||
"unicode_decimal": 58922
|
||||
},
|
||||
{
|
||||
"icon_id": "608294",
|
||||
"name": "减少",
|
||||
"font_class": "jianshaominimize3",
|
||||
"unicode": "e68b",
|
||||
"unicode_decimal": 59019
|
||||
},
|
||||
{
|
||||
"icon_id": "6151130",
|
||||
"name": "folder-fill",
|
||||
"font_class": "folder-fill",
|
||||
"unicode": "e7c4",
|
||||
"unicode_decimal": 59332
|
||||
},
|
||||
{
|
||||
"icon_id": "13397103",
|
||||
"name": "截图",
|
||||
"font_class": "jietu",
|
||||
"unicode": "e611",
|
||||
"unicode_decimal": 58897
|
||||
},
|
||||
{
|
||||
"icon_id": "10598085",
|
||||
"name": "language,多语言",
|
||||
"font_class": "duoyuyan",
|
||||
"unicode": "e606",
|
||||
"unicode_decimal": 58886
|
||||
},
|
||||
{
|
||||
"icon_id": "5387527",
|
||||
"name": "加号",
|
||||
"font_class": "jiahao",
|
||||
"unicode": "eaf3",
|
||||
"unicode_decimal": 60147
|
||||
},
|
||||
{
|
||||
"icon_id": "6337465",
|
||||
"name": "笑脸",
|
||||
"font_class": "xiaolian",
|
||||
"unicode": "ec80",
|
||||
"unicode_decimal": 60544
|
||||
},
|
||||
{
|
||||
"icon_id": "1418205",
|
||||
"name": "发送",
|
||||
"font_class": "fasong",
|
||||
"unicode": "e604",
|
||||
"unicode_decimal": 58884
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
static/css/icon/iconfont.ttf
Normal file
BIN
static/css/icon/iconfont.woff
Normal file
BIN
static/css/icon/iconfont.woff2
Normal file
1
static/css/icono.min.css
vendored
Normal file
1
static/css/index.css
Normal file
@ -0,0 +1 @@
|
||||
.footer{background: #30313a;color: #acacac;padding: 30px 0;min-width: 1190px;}
|
||||
743
static/css/kefu-front.css
Normal file
@ -0,0 +1,743 @@
|
||||
.launchButtonBox{
|
||||
position: fixed!important;
|
||||
bottom: 25px;
|
||||
right: 30px;
|
||||
left: auto;
|
||||
z-index: 999999;
|
||||
}
|
||||
.launchButtonNotice{
|
||||
width: 270px;
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
clear: both;
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
right: 0;
|
||||
z-index: 999999;
|
||||
color: #545454;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
.launchNoticeContent{
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #f7f7f9;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
||||
padding: 10px;
|
||||
}
|
||||
.flexBox{
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.launchButtonNotice a{
|
||||
color: #07a9fe;!important;
|
||||
text-decoration: none;
|
||||
}
|
||||
.launchIcon{
|
||||
background: #ff305f;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: 14px;
|
||||
color: #fff;
|
||||
display: none;
|
||||
}
|
||||
.launchButton{
|
||||
border-radius: 30px;
|
||||
z-index: 10000000000000!important;
|
||||
background: #2d8cf0;
|
||||
box-shadow: rgb(0 0 0 / 6%) 0px 1px 6px, rgb(0 0 0 / 16%) 0px 2px 32px;
|
||||
box-sizing: border-box!important;
|
||||
padding: 15px 20px;
|
||||
cursor: pointer!important;
|
||||
outline: 0!important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0!important;
|
||||
-webkit-font-smoothing: antialiased!important;
|
||||
-webkit-tap-highlight-color: transparent!important;
|
||||
color: #ffffff;
|
||||
position: relative;
|
||||
}
|
||||
.launchButton:hover {
|
||||
box-shadow: 0 8px 32px rgb(0 0 0 / 40%) !important;
|
||||
}
|
||||
.launchButton svg{
|
||||
width: 28px;
|
||||
height: 48px;
|
||||
}
|
||||
.launchButtonText {
|
||||
display: inline-block;
|
||||
/*font-size: 14px;*/
|
||||
letter-spacing:1px;
|
||||
text-overflow: ellipsis!important;
|
||||
vertical-align: top!important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.launchButtonText img{
|
||||
width: 26px;
|
||||
display: inline-block;
|
||||
}
|
||||
.launchButtonNotice .flyAvatar{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
.flyAvatar{
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
border:2px solid #fff;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.layui-layer-title .flyAvatar{
|
||||
margin-top: 5px;
|
||||
}
|
||||
.launchButtonNotice .flyUsername{
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.launchButtonNotice .flyUser{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
margin-right: 4px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.launchButtonNotice .flyClose {
|
||||
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
color: #464646;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
right: 0px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #f7f7f9;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
||||
}
|
||||
#layui-layer19911116{
|
||||
/*display: none;*/
|
||||
box-shadow: 0 5px 40px rgb(0 0 0 / 16%);
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
animation: up 0.5s ease-in-out both
|
||||
}
|
||||
#layui-layer-iframe19911116{
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
.launchPointer{
|
||||
background: #3cc51f;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 42px;
|
||||
border:2px solid #fff;
|
||||
}
|
||||
.launchPointer.offline{
|
||||
background: #ce3c39;
|
||||
}
|
||||
.folderBtn {
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
font-size: 1px;
|
||||
}
|
||||
.folderBtn:before {
|
||||
content: '';
|
||||
float: left;
|
||||
background-color: #9da0a0;
|
||||
width: 15px;
|
||||
height: 3px;
|
||||
margin-left: 2px;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
box-shadow: 2px 2px 0 0 #9da0a0;
|
||||
}
|
||||
.folderBtn:after {
|
||||
content: '';
|
||||
float: left;
|
||||
clear: left;
|
||||
background-color: #d4d6d6;
|
||||
width: 33px;
|
||||
height: 22px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
@-webkit-keyframes bounce-up {
|
||||
25% {-webkit-transform: translateY(4px);}
|
||||
50%, 100% {-webkit-transform: translateY(0);}
|
||||
75% {-webkit-transform: translateY(-4px);}
|
||||
}
|
||||
|
||||
@keyframes bounce-up {
|
||||
25% {transform: translateY(4px);}
|
||||
50%, 100% {transform: translateY(0);}
|
||||
75% {transform: translateY(-4px);}
|
||||
}
|
||||
.animateUpDown{
|
||||
-webkit-animation: bounce-up 0.5s linear infinite;
|
||||
animation: bounce-up 0.5s linear infinite;
|
||||
}
|
||||
@keyframes up {
|
||||
0% {
|
||||
-webkit-transform: translateY(20px);
|
||||
transform: translateY(20px);
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: translateY(-20px);
|
||||
transform: translateY(-20px);
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 500px) {
|
||||
.launchButtonBox{
|
||||
width: 100%;
|
||||
right: 0px;
|
||||
}
|
||||
.launchButton{
|
||||
width: 43px;
|
||||
height: auto;
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
bottom: 140px;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
.launchButtonNotice{
|
||||
bottom: 120px;
|
||||
right: 42px;
|
||||
}
|
||||
.launchButtonText{
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
text-align: center;
|
||||
writing-mode: vertical-lr;
|
||||
text-orientation: mixed;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.launchButtonText img{margin-bottom: 5px;}
|
||||
.launchButtonText .flyAvatar{
|
||||
display: block;
|
||||
margin-right: 0px;
|
||||
float: none;
|
||||
}
|
||||
.launchButtonNotice:after{
|
||||
right: 4%;
|
||||
}
|
||||
}
|
||||
/*看板娘*/
|
||||
.waifu-toggle {
|
||||
background-color: #fa0;
|
||||
border-radius: 5px;
|
||||
bottom: 66px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
left: 0;
|
||||
margin-left: -100px;
|
||||
padding: 5px 2px 5px 5px;
|
||||
position: fixed;
|
||||
transition: margin-left 1s;
|
||||
width: 60px;
|
||||
writing-mode: vertical-rl;
|
||||
}
|
||||
|
||||
.waifu-toggle.waifu-toggle-active {
|
||||
margin-left: -50px;
|
||||
}
|
||||
|
||||
.waifu-toggle.waifu-toggle-active:hover {
|
||||
margin-left: -30px;
|
||||
}
|
||||
|
||||
.waifu {
|
||||
bottom: -1000px;
|
||||
left: 0;
|
||||
line-height: 0;
|
||||
margin-bottom: -10px;
|
||||
position: fixed;
|
||||
transform: translateY(3px);
|
||||
transition: transform .3s ease-in-out, bottom 3s ease-in-out;
|
||||
z-index: 1;
|
||||
}
|
||||
.waifu:hover {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.waifu-tips {
|
||||
top:-50px;
|
||||
animation: shake 50s ease-in-out 5s infinite;
|
||||
background-color: rgba(236, 217, 188, .5);
|
||||
border: 1px solid rgba(224, 186, 140, .62);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 3px 15px 2px rgba(191, 158, 118, .2);
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
margin: 0px 20px;
|
||||
min-height: 70px;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
padding: 5px 10px;
|
||||
position: absolute;
|
||||
text-overflow: ellipsis;
|
||||
transition: opacity 1s;
|
||||
width: 250px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.waifu-tips.waifu-tips-active {
|
||||
opacity: 1;
|
||||
transition: opacity .2s;
|
||||
background-color: rgba(236, 217, 188, 0.8);
|
||||
}
|
||||
|
||||
.waifu-tips span {
|
||||
color: #0099cc;
|
||||
}
|
||||
|
||||
.waifu #live2d {
|
||||
cursor: grab;
|
||||
height: 300px;
|
||||
position: relative;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.waifu #live2d:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.waifu-tool {
|
||||
color: #aaa;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 70px;
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
.waifu:hover #waifu-tool {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.waifu-tool span {
|
||||
color: #7b8c9d;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
transition: color .3s;
|
||||
}
|
||||
|
||||
.waifu-tool span:hover {
|
||||
color: #0684bd; /* #34495e */
|
||||
}
|
||||
.waifu-input{
|
||||
background-color: rgba(236, 217, 188, .9);
|
||||
border: 1px solid rgba(224, 186, 140, .8);
|
||||
border-radius: 12px;
|
||||
box-shadow:0 3px 15px 2px rgba(191, 158, 118, .4);
|
||||
left: 20px;
|
||||
bottom: 30px;
|
||||
width: 250px;
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
z-index: 2;
|
||||
}
|
||||
@keyframes shake {
|
||||
2% {
|
||||
transform: translate(.5px, -1.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
4% {
|
||||
transform: translate(.5px, 1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
6% {
|
||||
transform: translate(1.5px, 1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
8% {
|
||||
transform: translate(2.5px, 1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: translate(.5px, 2.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
12% {
|
||||
transform: translate(1.5px, 1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
14% {
|
||||
transform: translate(.5px, .5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
16% {
|
||||
transform: translate(-1.5px, -.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
18% {
|
||||
transform: translate(.5px, .5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: translate(2.5px, 2.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
22% {
|
||||
transform: translate(.5px, -1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
24% {
|
||||
transform: translate(-1.5px, 1.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
26% {
|
||||
transform: translate(1.5px, .5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
28% {
|
||||
transform: translate(-.5px, -.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
30% {
|
||||
transform: translate(1.5px, -.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
32% {
|
||||
transform: translate(2.5px, -1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
34% {
|
||||
transform: translate(2.5px, 2.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
36% {
|
||||
transform: translate(.5px, -1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
38% {
|
||||
transform: translate(2.5px, -.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translate(-.5px, 2.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
42% {
|
||||
transform: translate(-1.5px, 2.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
44% {
|
||||
transform: translate(-1.5px, 1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
46% {
|
||||
transform: translate(1.5px, -.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
48% {
|
||||
transform: translate(2.5px, -.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translate(-1.5px, 1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
52% {
|
||||
transform: translate(-.5px, 1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
54% {
|
||||
transform: translate(-1.5px, 1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
56% {
|
||||
transform: translate(.5px, 2.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
58% {
|
||||
transform: translate(2.5px, 2.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translate(2.5px, -1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
62% {
|
||||
transform: translate(-1.5px, .5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
64% {
|
||||
transform: translate(-1.5px, 1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
66% {
|
||||
transform: translate(.5px, 2.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
68% {
|
||||
transform: translate(2.5px, -1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translate(2.5px, 2.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
72% {
|
||||
transform: translate(-.5px, -1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
74% {
|
||||
transform: translate(-1.5px, 2.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
76% {
|
||||
transform: translate(-1.5px, 2.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
78% {
|
||||
transform: translate(-1.5px, 2.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translate(-1.5px, .5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
82% {
|
||||
transform: translate(-1.5px, .5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
84% {
|
||||
transform: translate(-.5px, .5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
86% {
|
||||
transform: translate(2.5px, 1.5px) rotate(.5deg);
|
||||
}
|
||||
|
||||
88% {
|
||||
transform: translate(-1.5px, .5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translate(-1.5px, -.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
92% {
|
||||
transform: translate(-1.5px, -1.5px) rotate(1.5deg);
|
||||
}
|
||||
|
||||
94% {
|
||||
transform: translate(.5px, .5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
96% {
|
||||
transform: translate(2.5px, -.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
98% {
|
||||
transform: translate(-1.5px, -1.5px) rotate(-.5deg);
|
||||
}
|
||||
|
||||
0%, 100% {
|
||||
transform: translate(0, 0) rotate(0);
|
||||
}
|
||||
}
|
||||
.flySimpleIconBox{
|
||||
position: fixed;
|
||||
bottom: 150px;
|
||||
right: 10px;
|
||||
z-index: 999999;
|
||||
}
|
||||
.flySimpleIcon{
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
text-align: center;
|
||||
box-shadow: rgba(0, 0, 0, 0.16) 0px 5px 14px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.flySimpleIcon .flySimpleDefaultImg{
|
||||
display: inline-block;
|
||||
margin-top: 12px;
|
||||
width: 35px;
|
||||
}
|
||||
.flySimpleIcon .flySimpleUserImg{
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.flySimpleIconTip{
|
||||
display: none;
|
||||
width: 250px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
top:5px;
|
||||
right: 70px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 3px 15px 0 rgba(0,0,0,.25)!important;
|
||||
padding: 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.flySimpleIconBox .flyClose{
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 2px;
|
||||
}
|
||||
.chatImagePic{
|
||||
max-width: 100%;
|
||||
}
|
||||
.lineBox{
|
||||
position: fixed!important;
|
||||
bottom: 30%;
|
||||
right: 0px;
|
||||
left: auto;
|
||||
z-index: 999999;
|
||||
}
|
||||
.lineBox .lineItem{
|
||||
cursor: pointer;
|
||||
width: 50px;
|
||||
height: 55px;
|
||||
background: #2d8cf0;
|
||||
margin-bottom: 1px;
|
||||
color: #fff;
|
||||
line-height: 55px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
.lineItem .layui-icon{
|
||||
font-size: 26px;
|
||||
}
|
||||
.lineItem:hover{
|
||||
opacity: 0.8;
|
||||
}
|
||||
.lineTop{
|
||||
margin-top: 4px;
|
||||
}
|
||||
.lineTip{
|
||||
border-radius: 2px;
|
||||
box-shadow: 1px 1px 3px rgba(0,0,0,.2);
|
||||
position: absolute;
|
||||
top:0px;
|
||||
right: 59px;
|
||||
color: #000;
|
||||
padding: 0 10px;
|
||||
background: #fff;
|
||||
display: none;
|
||||
}
|
||||
.lineTip:before, .lineTip:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 10px solid transparent;
|
||||
border-left-color: rgba(255,255,255,1);
|
||||
right: -16px;
|
||||
top: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lineTip:after{
|
||||
right: -18px;
|
||||
border-left-color: rgb(237,237,237);
|
||||
z-index: 0;
|
||||
}
|
||||
.lineWechat{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
padding: 0px;
|
||||
}
|
||||
.kfLayer .layui-layer-title{
|
||||
background-color: #3369FF;
|
||||
background-image: linear-gradient(to right, #0d6efd, #2aadeb);
|
||||
height: 60px;
|
||||
border: 0px;
|
||||
color: #fff;
|
||||
padding: 0px;
|
||||
line-height: normal;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
.kfLayer .kfBar{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
}
|
||||
.kfLayer .kfBarLogo{
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.kfLayer .kfBarText{
|
||||
float: left;
|
||||
margin:0px 0px 0px 10px;
|
||||
max-width: 220px;
|
||||
}
|
||||
.kfLayer .kfBarBtn{
|
||||
margin-right: 40px;
|
||||
margin-left: auto;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.kfLayer .kfBarBtn .cursor{
|
||||
cursor: pointer;
|
||||
}
|
||||
.kfLayer .kfDesc{
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.kfLayer .layui-layer-setwin .layui-layer-close1{
|
||||
background: url("../images/zoom_out.png") no-repeat 0;
|
||||
width: 22px;
|
||||
}
|
||||
.kfBarAvator{
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
margin: 0 0px 0 15px;
|
||||
}
|
||||
.kfBarStatus{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
--bg-opacity: 1;
|
||||
background-color: #00c41d;
|
||||
background-color: rgba(0,196,29,var(--bg-opacity));
|
||||
border:1px solid #fff;
|
||||
display: inline-block;
|
||||
}
|
||||
.kfBarAvator i.offline{
|
||||
background: #f56c6c;
|
||||
}
|
||||
1
static/css/layui/css/layui.css
Normal file
1
static/css/layui/css/modules/code.css
Normal file
@ -0,0 +1 @@
|
||||
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #eee;border-left-width:6px;background-color:#FAFAFA;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:40px;line-height:40px;border-bottom:1px solid #eee}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 10px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view .layui-code-ol li:first-child{padding-top:10px}.layui-code-view .layui-code-ol li:last-child{padding-bottom:10px}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}.layui-code-demo .layui-code{visibility:visible!important;margin:-15px;border-top:none;border-right:none;border-bottom:none}.layui-code-demo .layui-tab-content{padding:15px;border-top:none}
|
||||
1
static/css/layui/css/modules/laydate/default/laydate.css
Normal file
BIN
static/css/layui/css/modules/layer/default/icon-ext.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
static/css/layui/css/modules/layer/default/icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
1
static/css/layui/css/modules/layer/default/layer.css
Normal file
BIN
static/css/layui/css/modules/layer/default/loading-0.gif
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
static/css/layui/css/modules/layer/default/loading-1.gif
Normal file
|
After Width: | Height: | Size: 701 B |
BIN
static/css/layui/css/modules/layer/default/loading-2.gif
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
static/css/layui/font/iconfont.eot
Normal file
554
static/css/layui/font/iconfont.svg
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
static/css/layui/font/iconfont.ttf
Normal file
BIN
static/css/layui/font/iconfont.woff
Normal file
BIN
static/css/layui/font/iconfont.woff2
Normal file
5
static/css/layui/layui.js
Normal file
188
static/css/style.css
Normal file
@ -0,0 +1,188 @@
|
||||
*{
|
||||
margin: 0;padding: 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #3973ac;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
pre{
|
||||
background:#f9f9f9;
|
||||
color: #d73a49;
|
||||
line-height: 21px;
|
||||
border: 1px solid #e8e8e8;
|
||||
padding: 5px;
|
||||
}
|
||||
.header{
|
||||
background-color: #fff;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
.container{
|
||||
width: 1140px;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header .logo{
|
||||
margin: 0;
|
||||
float: left;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.header a{
|
||||
color: #828282;
|
||||
font-weight: bold;
|
||||
font-family: "Microsoft JhengHei";
|
||||
text-decoration: none;
|
||||
}
|
||||
.header .logo a{
|
||||
font-size: 30px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.header .logo img{
|
||||
width: 120px;
|
||||
}
|
||||
.header .navBtn{
|
||||
float: right;
|
||||
margin:30px 0 30px 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
.banner{
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background-color: #3385ff;
|
||||
padding: 20px 10px;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.banner h1 {
|
||||
font-size: 34px;
|
||||
margin: 20px 0px;
|
||||
line-height: 45px;
|
||||
font-weight: 500;
|
||||
font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;
|
||||
}
|
||||
.banner p{
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.banner .downloadBtn{margin-top: 20px;}
|
||||
.downloadBtn{display:inline-block;text-decoration:none;color:#fff;border:1px solid #fff;padding:10px 15px;border-radius:5px;}
|
||||
.downloadBtn:hover{background:#fff;color:#20b2bb;text-decoration: none;}
|
||||
.jumbotron{
|
||||
width: 1000px;
|
||||
margin: 10px auto;
|
||||
box-shadow: 2px 2px 15px rgba(0,0,0,.3);
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.copyright{
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.links{
|
||||
margin-top: 20px;
|
||||
}
|
||||
.links a{
|
||||
color: #20b2bb;
|
||||
line-height: 23px;
|
||||
margin: 5px;
|
||||
}
|
||||
.main{
|
||||
background: #ededed;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mainIntro{
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 5px 0 rgba(0,0,0,.05);
|
||||
margin: 20px auto;
|
||||
}
|
||||
.product{
|
||||
padding: 10px;
|
||||
}
|
||||
.product h3{
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
line-height: 36px;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
.product h4{
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
border-left: 2px #3385ff solid;
|
||||
background: #e6e6e6;
|
||||
margin: 10px 0px;
|
||||
}
|
||||
.productContent{
|
||||
color: #444;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.productContent ol{
|
||||
margin: 0px 15px;
|
||||
}
|
||||
.productContent a{
|
||||
color: #3973ac;
|
||||
text-decoration: none;
|
||||
}
|
||||
.productContent a:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
.productPost{
|
||||
overflow: hidden;
|
||||
}
|
||||
.productPost li:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
.productPost li {
|
||||
float: left;
|
||||
margin-right: 2%;
|
||||
width: 48%;
|
||||
line-height: 36px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
border-top: 1px dashed #e6e6e6;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.container{
|
||||
width: 100%;
|
||||
}
|
||||
.header .logo{
|
||||
float: none;
|
||||
text-align: center;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
.header .navBtn{
|
||||
margin:5px 0 10px 10px;
|
||||
float: none;
|
||||
}
|
||||
.banner p{
|
||||
text-align: left;
|
||||
}
|
||||
.jumbotron{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.clear{clear:both}
|
||||
.links{
|
||||
margin-top: 20px;
|
||||
}
|
||||
.links a{
|
||||
color: #20b2bb;
|
||||
line-height: 23px;
|
||||
margin: 5px;
|
||||
}
|
||||
62
static/demos/websocket.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
// 设置重连时间间隔(单位:毫秒)
|
||||
const RECONNECT_INTERVAL = 1000;
|
||||
|
||||
// 设置最大重连次数
|
||||
const MAX_RECONNECT_TIMES = 3;
|
||||
|
||||
let reconnectTimes = 0;
|
||||
let ws;
|
||||
|
||||
// 尝试连接 WebSocket
|
||||
function connect() {
|
||||
ws = new WebSocket('wss://gofly.v1kf.com/ws_visitor?visitor_id=5|a780d122-daa3-4315-a413-f93b29b026d0&to_id=taoshihan');
|
||||
|
||||
ws.onopen = function () {
|
||||
console.log('WebSocket 连接已打开');
|
||||
reconnectTimes = 0;
|
||||
};
|
||||
|
||||
ws.onclose = function () {
|
||||
console.log('WebSocket 连接已关闭');
|
||||
// 尝试重连
|
||||
reconnect();
|
||||
};
|
||||
ws.onmessage = function (event) {
|
||||
console.log(`收到服务器的消息:${event.data}`);
|
||||
|
||||
// // 解析消息
|
||||
// const message = JSON.parse(event.data);
|
||||
// if (message.type === 'message') {
|
||||
// console.log(`收到消息:${message.data}`);
|
||||
// }
|
||||
};
|
||||
}
|
||||
|
||||
// 尝试重连
|
||||
function reconnect() {
|
||||
if (reconnectTimes >= MAX_RECONNECT_TIMES) {
|
||||
console.log('重连失败');
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectTimes++;
|
||||
console.log(`正在尝试重连(第 ${reconnectTimes} 次)`);
|
||||
|
||||
setTimeout(function () {
|
||||
connect();
|
||||
}, RECONNECT_INTERVAL);
|
||||
}
|
||||
|
||||
connect();
|
||||
</script>
|
||||
</html>
|
||||
BIN
static/images/.DS_Store
vendored
Normal file
BIN
static/images/0.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
static/images/1.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
static/images/1.png
Normal file
|
After Width: | Height: | Size: 18 KiB |