├── .gitignore ├── Dockerfile ├── LICENSE ├── cmd ├── install.go ├── root.go ├── server.go └── stop.go ├── common ├── common.go └── config.go ├── config ├── city.free.ipdb ├── go-fly.sql └── mysql.json ├── controller ├── about.go ├── auth.go ├── captcha.go ├── chart.go ├── index.go ├── ip.go ├── kefu.go ├── login.go ├── main.go ├── message.go ├── notice.go ├── reply.go ├── response.go ├── role.go ├── setting.go ├── shout.go ├── tcp.go ├── visitor.go └── weixin.go ├── go-fly.go ├── go.mod ├── go.sum ├── import.sql ├── middleware ├── cross.go ├── domain_limit.go ├── ipblack.go ├── jwt.go ├── logger.go └── rbac.go ├── models ├── abouts.go ├── configs.go ├── ipblacks.go ├── messages.go ├── models.go ├── replys.go ├── roles.go ├── user_client.go ├── user_roles.go ├── users.go ├── visitors.go └── welcomes.go ├── readme.md ├── readme_en.md ├── router ├── api.go └── view.go ├── start.bat ├── static ├── cdn │ ├── element-ui │ │ ├── 2.15.1 │ │ │ ├── index.js │ │ │ └── theme-chalk │ │ │ │ ├── fonts │ │ │ │ └── element-icons.woff │ │ │ │ └── index.min.css │ │ └── 2.15.7 │ │ │ ├── index.js │ │ │ └── theme-chalk │ │ │ ├── fonts │ │ │ └── element-icons.woff │ │ │ └── index.min.css │ ├── jquery │ │ ├── 3.6.0 │ │ │ └── jquery.min.js │ │ └── jquery.qrcode.min.js │ └── vue │ │ └── 2.6.11 │ │ └── vue.min.js ├── css │ ├── bootstrap.min.css │ ├── common.css │ ├── front.css │ ├── gofly-front.css │ ├── icon │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── icono.min.css │ ├── index.css │ ├── kefu-front.css │ ├── layui │ │ ├── css │ │ │ ├── layui.css │ │ │ └── modules │ │ │ │ ├── code.css │ │ │ │ ├── laydate │ │ │ │ └── default │ │ │ │ │ └── laydate.css │ │ │ │ └── layer │ │ │ │ └── default │ │ │ │ ├── icon-ext.png │ │ │ │ ├── icon.png │ │ │ │ ├── layer.css │ │ │ │ ├── loading-0.gif │ │ │ │ ├── loading-1.gif │ │ │ │ └── loading-2.gif │ │ ├── font │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ ├── iconfont.woff │ │ │ └── iconfont.woff2 │ │ └── layui.js │ └── style.css ├── demos │ └── websocket.html ├── images │ ├── 0.jpg │ ├── 1.jpg │ ├── 1.png │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 2.jpg │ ├── 2.png │ ├── 3.jpg │ ├── 3.png │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ ├── 9.jpg │ ├── admin.jpg │ ├── admin.png │ ├── alert.mp3 │ ├── alert2.ogg │ ├── attachent.png │ ├── avator.jpg │ ├── computer.png │ ├── ext │ │ ├── 7z.png │ │ ├── BAT.png │ │ ├── BMP.png │ │ ├── DOC.png │ │ ├── DOCX.png │ │ ├── JPEG.png │ │ ├── JPG.png │ │ ├── MP3.png │ │ ├── MP4.png │ │ ├── MPGE.png │ │ ├── PDF.png │ │ ├── PNG.png │ │ ├── PPT.png │ │ ├── RAR.png │ │ ├── SVG.png │ │ ├── TAR.png │ │ ├── TXT.png │ │ ├── XLSX.png │ │ ├── ZIP.png │ │ ├── default.png │ │ └── voice.png │ ├── face │ │ ├── 0.gif │ │ ├── 1.gif │ │ ├── 10.gif │ │ ├── 11.gif │ │ ├── 12.gif │ │ ├── 13.gif │ │ ├── 14.gif │ │ ├── 15.gif │ │ ├── 16.gif │ │ ├── 17.gif │ │ ├── 18.gif │ │ ├── 19.gif │ │ ├── 2.gif │ │ ├── 20.gif │ │ ├── 21.gif │ │ ├── 22.gif │ │ ├── 23.gif │ │ ├── 24.gif │ │ ├── 25.gif │ │ ├── 26.gif │ │ ├── 27.gif │ │ ├── 28.gif │ │ ├── 29.gif │ │ ├── 3.gif │ │ ├── 30.gif │ │ ├── 31.gif │ │ ├── 32.gif │ │ ├── 33.gif │ │ ├── 34.gif │ │ ├── 35.gif │ │ ├── 36.gif │ │ ├── 37.gif │ │ ├── 38.gif │ │ ├── 39.gif │ │ ├── 4.gif │ │ ├── 40.gif │ │ ├── 41.gif │ │ ├── 42.gif │ │ ├── 43.gif │ │ ├── 44.gif │ │ ├── 45.gif │ │ ├── 46.gif │ │ ├── 47.gif │ │ ├── 48.gif │ │ ├── 49.gif │ │ ├── 5.gif │ │ ├── 50.gif │ │ ├── 51.gif │ │ ├── 52.gif │ │ ├── 53.gif │ │ ├── 54.gif │ │ ├── 55.gif │ │ ├── 56.gif │ │ ├── 57.gif │ │ ├── 58.gif │ │ ├── 59.gif │ │ ├── 6.gif │ │ ├── 60.gif │ │ ├── 61.gif │ │ ├── 62.gif │ │ ├── 63.gif │ │ ├── 64.gif │ │ ├── 65.gif │ │ ├── 66.gif │ │ ├── 67.gif │ │ ├── 68.gif │ │ ├── 69.gif │ │ ├── 7.gif │ │ ├── 70.gif │ │ ├── 71.gif │ │ ├── 8.gif │ │ └── 9.gif │ ├── fire.svg │ ├── image-text.png │ ├── intro1.jpg │ ├── intro2.jpg │ ├── intro3.png │ ├── inviteColorBack1.png │ ├── logo.png │ ├── newintro1.jpg │ ├── newintro2.jpg │ ├── newintro3.jpg │ ├── phone.png │ ├── sent.ogg │ ├── smile.png │ ├── visitor_title_bg.png │ ├── wechatLogo.png │ └── zoom_out.png ├── js │ ├── chat-lang.js │ ├── chat-main.js │ ├── chat-page.js │ ├── echarts.min.js │ ├── functions.js │ ├── gofly-front.js │ ├── index.js │ ├── kefu-front.js │ ├── layer │ │ ├── layer.js │ │ ├── mobile │ │ │ ├── layer.js │ │ │ └── need │ │ │ │ └── layer.css │ │ └── theme │ │ │ └── default │ │ │ ├── icon-ext.png │ │ │ ├── icon.png │ │ │ ├── layer.css │ │ │ ├── loading-0.gif │ │ │ ├── loading-1.gif │ │ │ └── loading-2.gif │ └── reconnecting-websocket.min.js ├── static.go └── templates │ ├── bind.html │ ├── chat_main.html │ ├── chat_page.html │ ├── chat_video.html │ ├── detail.html │ ├── header.html │ ├── index.html │ ├── index_demo.html │ ├── install.html │ ├── list.html │ ├── login.html │ ├── mail_detail.html │ ├── mail_left.html │ ├── main.html │ ├── nav.html │ ├── pannel.html │ ├── setting.html │ ├── setting_avator.html │ ├── setting_bottom.html │ ├── setting_config.html │ ├── setting_deploy.html │ ├── setting_indexpages.html │ ├── setting_ipblack.html │ ├── setting_kefu_list.html │ ├── setting_left.html │ ├── setting_modifypass.html │ ├── setting_mysql.html │ ├── setting_pageindex.html │ ├── setting_role_list.html │ ├── setting_statistics.html │ ├── setting_welcome.html │ └── write.html ├── stop.bat ├── stop.sh ├── tmpl ├── chat.go ├── common.go ├── detail.go ├── folder.go ├── login.go ├── mail.go └── setting.go ├── tools ├── binsearch.go ├── binsearch_test.go ├── cookie.go ├── file.go ├── hash.go ├── http.go ├── http_tool.go ├── import_sql.go ├── import_sql_test.go ├── ip.go ├── ip_test.go ├── jwt.go ├── limits.go ├── logger.go ├── mytest.go ├── mytest_test.go ├── paniclog_linux.go ├── paniclog_windows.go ├── reverse_test.go ├── rpc_test.go ├── session.go ├── singlelist.go ├── smtp.go ├── smtp_test.go ├── snowflake.go ├── sorts.go ├── sorts_test.go ├── stringutil.go ├── stringutil_test.go ├── test.go ├── test_test.go ├── types.go └── uuid.go └── ws ├── user.go ├── visitor.go └── ws.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | imaptool.exe -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | WORKDIR /app 3 | COPY . /app 4 | RUN go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct 5 | VOLUME ["/app/config"] 6 | RUN go build go-fly.go 7 | EXPOSE 8081 8 | CMD ["/app/go-fly","server"] -------------------------------------------------------------------------------- /cmd/install.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "github.com/taoshihan1991/imaptool/common" 6 | "github.com/taoshihan1991/imaptool/models" 7 | "github.com/taoshihan1991/imaptool/tools" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | var installCmd = &cobra.Command{ 15 | Use: "install", 16 | Short: "安装导入数据", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | install() 19 | }, 20 | } 21 | 22 | func install() { 23 | if ok, _ := tools.IsFileNotExist("./install.lock"); !ok { 24 | log.Println("请先删除./install.lock") 25 | os.Exit(1) 26 | } 27 | sqlFile := "import.sql" 28 | isExit, _ := tools.IsFileExist(common.MysqlConf) 29 | dataExit, _ := tools.IsFileExist(sqlFile) 30 | if !isExit || !dataExit { 31 | log.Println("config/mysql.json 数据库配置文件或者数据库文件go-fly.sql不存在") 32 | os.Exit(1) 33 | } 34 | sqls, _ := ioutil.ReadFile(sqlFile) 35 | sqlArr := strings.Split(string(sqls), ";") 36 | for _, sql := range sqlArr { 37 | sql = strings.TrimSpace(sql) 38 | if sql == "" { 39 | continue 40 | } 41 | err := models.Execute(sql) 42 | if err == nil { 43 | log.Println(sql, "\t success!") 44 | } else { 45 | log.Println(sql, err, "\t failed!", "数据库导入失败") 46 | os.Exit(1) 47 | } 48 | } 49 | installFile, _ := os.OpenFile("./install.lock", os.O_RDWR|os.O_CREATE, os.ModePerm) 50 | installFile.WriteString("gofly live chat") 51 | } 52 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/spf13/cobra" 7 | "os" 8 | ) 9 | 10 | var rootCmd = &cobra.Command{ 11 | Use: "go-fly", 12 | Short: "go-fly", 13 | Long: `简洁快速的GO语言WEB在线客服 https://gofly.sopans.com`, 14 | Args: args, 15 | Run: func(cmd *cobra.Command, args []string) { 16 | 17 | }, 18 | } 19 | 20 | func args(cmd *cobra.Command, args []string) error { 21 | if len(args) < 1 { 22 | 23 | return errors.New("至少需要一个参数!") 24 | } 25 | return nil 26 | } 27 | func Execute() { 28 | if err := rootCmd.Execute(); err != nil { 29 | fmt.Println(err) 30 | os.Exit(1) 31 | } 32 | } 33 | func init() { 34 | rootCmd.AddCommand(serverCmd) 35 | rootCmd.AddCommand(installCmd) 36 | rootCmd.AddCommand(stopCmd) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/server.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-contrib/pprof" 6 | "github.com/gin-gonic/gin" 7 | "github.com/spf13/cobra" 8 | "github.com/taoshihan1991/imaptool/common" 9 | "github.com/taoshihan1991/imaptool/middleware" 10 | "github.com/taoshihan1991/imaptool/router" 11 | "github.com/taoshihan1991/imaptool/static" 12 | "github.com/taoshihan1991/imaptool/tools" 13 | "github.com/taoshihan1991/imaptool/ws" 14 | "github.com/zh-five/xdaemon" 15 | "html/template" 16 | "io/ioutil" 17 | "log" 18 | "net/http" 19 | "os" 20 | ) 21 | 22 | var ( 23 | port string 24 | daemon bool 25 | ) 26 | var serverCmd = &cobra.Command{ 27 | Use: "server", 28 | Short: "启动http服务", 29 | Example: "go-fly server -c config/", 30 | Run: func(cmd *cobra.Command, args []string) { 31 | run() 32 | }, 33 | } 34 | 35 | func init() { 36 | serverCmd.PersistentFlags().StringVarP(&port, "port", "p", "8081", "监听端口号") 37 | serverCmd.PersistentFlags().BoolVarP(&daemon, "daemon", "d", false, "是否为守护进程模式") 38 | } 39 | func run() { 40 | if daemon == true { 41 | logFilePath := "" 42 | if dir, err := os.Getwd(); err == nil { 43 | logFilePath = dir + "/logs/" 44 | } 45 | _, err := os.Stat(logFilePath) 46 | if os.IsNotExist(err) { 47 | if err := os.MkdirAll(logFilePath, 0777); err != nil { 48 | log.Println(err.Error()) 49 | } 50 | } 51 | d := xdaemon.NewDaemon(logFilePath + "go-fly.log") 52 | d.MaxCount = 10 53 | d.Run() 54 | } 55 | 56 | baseServer := "0.0.0.0:" + port 57 | log.Println("start server...\r\ngo:http://" + baseServer) 58 | tools.Logger().Println("start server...\r\ngo:http://" + baseServer) 59 | 60 | engine := gin.Default() 61 | if common.IsCompireTemplate { 62 | templ := template.Must(template.New("").ParseFS(static.TemplatesEmbed, "templates/*.html")) 63 | engine.SetHTMLTemplate(templ) 64 | engine.StaticFS("/assets", http.FS(static.JsEmbed)) 65 | } else { 66 | engine.LoadHTMLGlob("static/templates/*") 67 | engine.Static("/assets", "./static") 68 | } 69 | 70 | engine.Static("/static", "./static") 71 | engine.Use(tools.Session("gofly")) 72 | engine.Use(middleware.CrossSite) 73 | //性能监控 74 | pprof.Register(engine) 75 | 76 | //记录日志 77 | engine.Use(middleware.NewMidLogger()) 78 | router.InitViewRouter(engine) 79 | router.InitApiRouter(engine) 80 | //记录pid 81 | ioutil.WriteFile("gofly.sock", []byte(fmt.Sprintf("%d,%d", os.Getppid(), os.Getpid())), 0666) 82 | //限流类 83 | tools.NewLimitQueue() 84 | //清理 85 | ws.CleanVisitorExpire() 86 | //后端websocket 87 | go ws.WsServerBackend() 88 | 89 | engine.Run(baseServer) 90 | } 91 | -------------------------------------------------------------------------------- /cmd/stop.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "io/ioutil" 6 | "os/exec" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | var stopCmd = &cobra.Command{ 12 | Use: "stop", 13 | Short: "停止http服务", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | pids, err := ioutil.ReadFile("gofly.sock") 16 | if err != nil { 17 | return 18 | } 19 | pidSlice := strings.Split(string(pids), ",") 20 | var command *exec.Cmd 21 | for _, pid := range pidSlice { 22 | if runtime.GOOS == "windows" { 23 | command = exec.Command("taskkill.exe", "/f", "/pid", pid) 24 | } else { 25 | command = exec.Command("kill", pid) 26 | } 27 | command.Start() 28 | } 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | var ( 4 | PageSize uint = 10 5 | VisitorPageSize uint = 8 6 | Version string = "0.3.9" 7 | VisitorExpire float64 = 600 8 | Upload string = "static/upload/" 9 | Dir string = "config/" 10 | MysqlConf string = Dir + "mysql.json" 11 | IsCompireTemplate bool = false //是否编译静态模板到二进制 12 | ) 13 | -------------------------------------------------------------------------------- /common/config.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/taoshihan1991/imaptool/tools" 6 | "io/ioutil" 7 | ) 8 | 9 | type Mysql struct { 10 | Server string 11 | Port string 12 | Database string 13 | Username string 14 | Password string 15 | } 16 | 17 | func GetMysqlConf() *Mysql { 18 | var mysql = &Mysql{} 19 | isExist, _ := tools.IsFileExist(MysqlConf) 20 | if !isExist { 21 | return mysql 22 | } 23 | info, err := ioutil.ReadFile(MysqlConf) 24 | if err != nil { 25 | return mysql 26 | } 27 | err = json.Unmarshal(info, mysql) 28 | return mysql 29 | } 30 | -------------------------------------------------------------------------------- /config/city.free.ipdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/config/city.free.ipdb -------------------------------------------------------------------------------- /config/mysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "Server":"localhost", 3 | "Port":"3306", 4 | "Database":"go-fly", 5 | "Username":"go-fly", 6 | "Password":"go-fly" 7 | } 8 | -------------------------------------------------------------------------------- /controller/about.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | ) 7 | 8 | func GetAbout(c *gin.Context) { 9 | page := c.Query("page") 10 | if page == "" { 11 | page = "index" 12 | } 13 | about := models.FindAboutByPage(page) 14 | c.JSON(200, gin.H{ 15 | "code": 200, 16 | "msg": "ok", 17 | "result": about, 18 | }) 19 | } 20 | func GetAbouts(c *gin.Context) { 21 | about := models.FindAbouts() 22 | c.JSON(200, gin.H{ 23 | "code": 200, 24 | "msg": "ok", 25 | "result": about, 26 | }) 27 | } 28 | func PostAbout(c *gin.Context) { 29 | title_cn := c.PostForm("title_cn") 30 | title_en := c.PostForm("title_en") 31 | keywords_cn := c.PostForm("keywords_cn") 32 | keywords_en := c.PostForm("keywords_en") 33 | desc_cn := c.PostForm("desc_cn") 34 | desc_en := c.PostForm("desc_en") 35 | css_js := c.PostForm("css_js") 36 | html_cn := c.PostForm("html_cn") 37 | html_en := c.PostForm("html_en") 38 | if title_cn == "" || title_en == "" || html_cn == "" || html_en == "" { 39 | c.JSON(200, gin.H{ 40 | "code": 400, 41 | "msg": "error", 42 | }) 43 | return 44 | } 45 | models.UpdateAbout("index", title_cn, title_en, keywords_cn, keywords_en, desc_cn, desc_en, css_js, html_cn, html_en) 46 | 47 | c.JSON(200, gin.H{ 48 | "code": 200, 49 | "msg": "ok", 50 | "result": "", 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /controller/auth.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/taoshihan1991/imaptool/models" 5 | "github.com/taoshihan1991/imaptool/tools" 6 | ) 7 | 8 | func CheckKefuPass(username string, password string) (models.User, models.User_role, bool) { 9 | info := models.FindUser(username) 10 | var uRole models.User_role 11 | if info.Name == "" || info.Password != tools.Md5(password) { 12 | return info, uRole, false 13 | } 14 | uRole = models.FindRoleByUserId(info.ID) 15 | 16 | return info, uRole, true 17 | } 18 | -------------------------------------------------------------------------------- /controller/captcha.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "bytes" 5 | "github.com/dchest/captcha" 6 | "github.com/gin-contrib/sessions" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | func GetCaptcha(c *gin.Context) { 13 | l := captcha.DefaultLen 14 | w, h := 107, 36 15 | captchaId := captcha.NewLen(l) 16 | session := sessions.Default(c) 17 | session.Set("captcha", captchaId) 18 | _ = session.Save() 19 | _ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h) 20 | } 21 | 22 | func Serve(w http.ResponseWriter, r *http.Request, id, ext, lang string, download bool, width, height int) error { 23 | w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") 24 | w.Header().Set("Pragma", "no-cache") 25 | w.Header().Set("Expires", "0") 26 | 27 | var content bytes.Buffer 28 | switch ext { 29 | case ".png": 30 | w.Header().Set("Content-Type", "image/png") 31 | _ = captcha.WriteImage(&content, id, width, height) 32 | case ".wav": 33 | w.Header().Set("Content-Type", "audio/x-wav") 34 | _ = captcha.WriteAudio(&content, id, lang) 35 | default: 36 | return captcha.ErrNotFound 37 | } 38 | 39 | if download { 40 | w.Header().Set("Content-Type", "application/octet-stream") 41 | } 42 | http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes())) 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /controller/chart.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | "github.com/taoshihan1991/imaptool/tools" 7 | "time" 8 | ) 9 | 10 | func GetChartStatistic(c *gin.Context) { 11 | kefuName, _ := c.Get("kefu_name") 12 | 13 | dayNumMap := make(map[string]string) 14 | result := models.CountVisitorsEveryDay(kefuName.(string)) 15 | for _, item := range result { 16 | dayNumMap[item.Day] = tools.Int2Str(item.Num) 17 | } 18 | 19 | nowTime := time.Now() 20 | list := make([]map[string]string, 0) 21 | for i := 0; i > -46; i-- { 22 | getTime := nowTime.AddDate(0, 0, i) //年,月,日 获取一天前的时间 23 | resTime := getTime.Format("06-01-02") //获取的时间的格式 24 | tmp := make(map[string]string) 25 | tmp["day"] = resTime 26 | tmp["num"] = dayNumMap[resTime] 27 | list = append(list, tmp) 28 | } 29 | 30 | c.JSON(200, gin.H{ 31 | "code": 200, 32 | "msg": "ok", 33 | "result": list, 34 | }) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /controller/index.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | ) 7 | 8 | func Index(c *gin.Context) { 9 | jump := models.FindConfig("JumpLang") 10 | if jump != "cn" { 11 | jump = "en" 12 | } 13 | c.Redirect(302, "/index_"+jump) 14 | } 15 | -------------------------------------------------------------------------------- /controller/ip.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/common" 6 | "github.com/taoshihan1991/imaptool/models" 7 | "strconv" 8 | ) 9 | 10 | func PostIpblack(c *gin.Context) { 11 | ip := c.PostForm("ip") 12 | if ip == "" { 13 | c.JSON(200, gin.H{ 14 | "code": 400, 15 | "msg": "请输入IP!", 16 | }) 17 | return 18 | } 19 | kefuId, _ := c.Get("kefu_name") 20 | models.CreateIpblack(ip, kefuId.(string)) 21 | c.JSON(200, gin.H{ 22 | "code": 200, 23 | "msg": "添加黑名单成功!", 24 | }) 25 | } 26 | func DelIpblack(c *gin.Context) { 27 | ip := c.Query("ip") 28 | if ip == "" { 29 | c.JSON(200, gin.H{ 30 | "code": 400, 31 | "msg": "请输入IP!", 32 | }) 33 | return 34 | } 35 | models.DeleteIpblackByIp(ip) 36 | c.JSON(200, gin.H{ 37 | "code": 200, 38 | "msg": "删除黑名单成功!", 39 | }) 40 | } 41 | func GetIpblacks(c *gin.Context) { 42 | page, _ := strconv.Atoi(c.Query("page")) 43 | if page == 0 { 44 | page = 1 45 | } 46 | count := models.CountIps(nil, nil) 47 | list := models.FindIps(nil, nil, uint(page), common.VisitorPageSize) 48 | c.JSON(200, gin.H{ 49 | "code": 200, 50 | "msg": "ok", 51 | "result": gin.H{ 52 | "list": list, 53 | "count": count, 54 | "pagesize": common.PageSize, 55 | }, 56 | }) 57 | } 58 | func GetIpblacksByKefuId(c *gin.Context) { 59 | kefuId, _ := c.Get("kefu_name") 60 | list := models.FindIpsByKefuId(kefuId.(string)) 61 | c.JSON(200, gin.H{ 62 | "code": 200, 63 | "msg": "ok", 64 | "result": list, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /controller/login.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/tools" 6 | "log" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | // @Summary 登陆验证接口 12 | // @Produce json 13 | // @Accept multipart/form-data 14 | // @Param username formData string true "用户名" 15 | // @Param password formData string true "密码" 16 | // @Param type formData string true "类型" 17 | // @Success 200 {object} controller.Response 18 | // @Failure 200 {object} controller.Response 19 | // @Router /check [post] 20 | //验证接口 21 | func LoginCheckPass(c *gin.Context) { 22 | password := c.PostForm("password") 23 | username := c.PostForm("username") 24 | 25 | info, uRole, ok := CheckKefuPass(username, password) 26 | userinfo := make(map[string]interface{}) 27 | if !ok { 28 | c.JSON(200, gin.H{ 29 | "code": 400, 30 | "msg": "验证失败", 31 | }) 32 | return 33 | } 34 | 35 | userinfo["name"] = info.Name 36 | userinfo["kefu_id"] = info.ID 37 | userinfo["type"] = "kefu" 38 | if uRole.RoleId != 0 { 39 | userinfo["role_id"] = uRole.RoleId 40 | } else { 41 | userinfo["role_id"] = 2 42 | } 43 | userinfo["create_time"] = time.Now().Unix() 44 | 45 | token, _ := tools.MakeToken(userinfo) 46 | userinfo["ref_token"] = true 47 | refToken, _ := tools.MakeToken(userinfo) 48 | c.JSON(200, gin.H{ 49 | "code": 200, 50 | "msg": "验证成功,正在跳转", 51 | "result": gin.H{ 52 | "token": token, 53 | "ref_token": refToken, 54 | "create_time": userinfo["create_time"], 55 | }, 56 | }) 57 | } 58 | //远程请求 59 | func PostBindOfficial(c *gin.Context) { 60 | api := "https://gofly.v1kf.com/2/officialBindIp" 61 | 62 | phone := c.PostForm("phone") 63 | password := c.PostForm("password") 64 | host := c.Request.Host 65 | data := url.Values{} 66 | data.Set("phone", phone) 67 | data.Set("password", password) 68 | data.Set("host", host) 69 | res, err := tools.PostForm(api, data) 70 | if err != nil { 71 | log.Println("绑定官网账户发送认证连接错误") 72 | } 73 | c.Writer.Write([]byte(res)) 74 | } -------------------------------------------------------------------------------- /controller/main.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "github.com/jinzhu/gorm" 8 | "github.com/taoshihan1991/imaptool/common" 9 | "github.com/taoshihan1991/imaptool/models" 10 | "github.com/taoshihan1991/imaptool/tools" 11 | "github.com/taoshihan1991/imaptool/ws" 12 | "io/ioutil" 13 | "log" 14 | "os" 15 | "strings" 16 | ) 17 | 18 | func PostInstall(c *gin.Context) { 19 | notExist, _ := tools.IsFileNotExist("./install.lock") 20 | if !notExist { 21 | c.JSON(200, gin.H{ 22 | "code": 400, 23 | "msg": "系统已经安装过了", 24 | }) 25 | return 26 | } 27 | server := c.PostForm("server") 28 | port := c.PostForm("port") 29 | database := c.PostForm("database") 30 | username := c.PostForm("username") 31 | password := c.PostForm("password") 32 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, server, port, database) 33 | _, err := gorm.Open("mysql", dsn) 34 | if err != nil { 35 | log.Println(err) 36 | tools.Logger().Println(err) 37 | c.JSON(200, gin.H{ 38 | "code": 400, 39 | "msg": "数据库连接失败:" + err.Error(), 40 | }) 41 | return 42 | } 43 | isExist, _ := tools.IsFileExist(common.Dir) 44 | if !isExist { 45 | os.Mkdir(common.Dir, os.ModePerm) 46 | } 47 | fileConfig := common.MysqlConf 48 | file, _ := os.OpenFile(fileConfig, os.O_RDWR|os.O_CREATE, os.ModePerm) 49 | 50 | format := `{ 51 | "Server":"%s", 52 | "Port":"%s", 53 | "Database":"%s", 54 | "Username":"%s", 55 | "Password":"%s" 56 | } 57 | ` 58 | data := fmt.Sprintf(format, server, port, database, username, password) 59 | file.WriteString(data) 60 | models.Connect() 61 | installFile, _ := os.OpenFile("./install.lock", os.O_RDWR|os.O_CREATE, os.ModePerm) 62 | installFile.WriteString("gofly live chat") 63 | ok, err := install() 64 | if !ok { 65 | c.JSON(200, gin.H{ 66 | "code": 200, 67 | "msg": err.Error(), 68 | }) 69 | return 70 | } 71 | c.JSON(200, gin.H{ 72 | "code": 200, 73 | "msg": "安装成功", 74 | }) 75 | } 76 | func install() (bool, error) { 77 | sqlFile := common.Dir + "go-fly.sql" 78 | isExit, _ := tools.IsFileExist(common.MysqlConf) 79 | dataExit, _ := tools.IsFileExist(sqlFile) 80 | if !isExit || !dataExit { 81 | return false, errors.New("config/mysql.json 数据库配置文件或者数据库文件go-fly.sql不存在") 82 | } 83 | sqls, _ := ioutil.ReadFile(sqlFile) 84 | sqlArr := strings.Split(string(sqls), "|") 85 | for _, sql := range sqlArr { 86 | if sql == "" { 87 | continue 88 | } 89 | err := models.Execute(sql) 90 | if err == nil { 91 | log.Println(sql, "\t success!") 92 | } else { 93 | log.Println(sql, err, "\t failed!") 94 | } 95 | } 96 | return true, nil 97 | } 98 | func MainCheckAuth(c *gin.Context) { 99 | id, _ := c.Get("kefu_id") 100 | userinfo := models.FindUserRole("user.avator,user.name,user.id, role.name role_name", id) 101 | c.JSON(200, gin.H{ 102 | "code": 200, 103 | "msg": "验证成功", 104 | "result": gin.H{ 105 | "avator": userinfo.Avator, 106 | "name": userinfo.Name, 107 | "role_name": userinfo.RoleName, 108 | }, 109 | }) 110 | } 111 | func GetStatistics(c *gin.Context) { 112 | visitors := models.CountVisitors() 113 | message := models.CountMessage(nil,nil) 114 | session := len(ws.ClientList) 115 | kefuNum := 0 116 | c.JSON(200, gin.H{ 117 | "code": 200, 118 | "msg": "ok", 119 | "result": gin.H{ 120 | "visitors": visitors, 121 | "message": message, 122 | "session": session + kefuNum, 123 | }, 124 | }) 125 | } 126 | -------------------------------------------------------------------------------- /controller/notice.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | ) 7 | 8 | func GetNotice(c *gin.Context) { 9 | kefuId := c.Query("kefu_id") 10 | user := models.FindUser(kefuId) 11 | if user.ID==0{ 12 | c.JSON(200, gin.H{ 13 | "code": 400, 14 | "msg": "user not found", 15 | }) 16 | return 17 | } 18 | welcomeMessage:=models.FindConfig("WelcomeMessage") 19 | offlineMessage:=models.FindConfig("OfflineMessage") 20 | allNotice:=models.FindConfig("AllNotice") 21 | c.JSON(200, gin.H{ 22 | "code": 200, 23 | "msg": "ok", 24 | "result": gin.H{ 25 | "welcome":welcomeMessage, 26 | "offline":offlineMessage, 27 | "avatar":user.Avator, 28 | "nickname":user.Nickname, 29 | "allNotice":allNotice, 30 | }, 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /controller/reply.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | "log" 7 | ) 8 | 9 | type ReplyForm struct { 10 | GroupName string `form:"group_name" binding:"required"` 11 | } 12 | type ReplyContentForm struct { 13 | GroupId string `form:"group_id" binding:"required"` 14 | Content string `form:"content" binding:"required"` 15 | ItemName string `form:"item_name" binding:"required"` 16 | } 17 | 18 | func GetReplys(c *gin.Context) { 19 | kefuId, _ := c.Get("kefu_name") 20 | log.Println(kefuId) 21 | res := models.FindReplyByUserId(kefuId) 22 | c.JSON(200, gin.H{ 23 | "code": 200, 24 | "msg": "ok", 25 | "result": res, 26 | }) 27 | } 28 | func GetAutoReplys(c *gin.Context) { 29 | kefu_id := c.Query("kefu_id") 30 | res := models.FindReplyTitleByUserId(kefu_id) 31 | c.JSON(200, gin.H{ 32 | "code": 200, 33 | "msg": "ok", 34 | "result": res, 35 | }) 36 | } 37 | func PostReply(c *gin.Context) { 38 | var replyForm ReplyForm 39 | kefuId, _ := c.Get("kefu_name") 40 | err := c.Bind(&replyForm) 41 | if err != nil { 42 | c.JSON(200, gin.H{ 43 | "code": 400, 44 | "msg": "error:" + err.Error(), 45 | }) 46 | return 47 | } 48 | models.CreateReplyGroup(replyForm.GroupName, kefuId.(string)) 49 | c.JSON(200, gin.H{ 50 | "code": 200, 51 | "msg": "ok", 52 | }) 53 | } 54 | func PostReplyContent(c *gin.Context) { 55 | var replyContentForm ReplyContentForm 56 | kefuId, _ := c.Get("kefu_name") 57 | err := c.Bind(&replyContentForm) 58 | if err != nil { 59 | c.JSON(400, gin.H{ 60 | "code": 200, 61 | "msg": "error:" + err.Error(), 62 | }) 63 | return 64 | } 65 | models.CreateReplyContent(replyContentForm.GroupId, kefuId.(string), replyContentForm.Content, replyContentForm.ItemName) 66 | c.JSON(200, gin.H{ 67 | "code": 200, 68 | "msg": "ok", 69 | }) 70 | } 71 | func PostReplyContentSave(c *gin.Context) { 72 | kefuId, _ := c.Get("kefu_name") 73 | replyId := c.PostForm("reply_id") 74 | replyTitle := c.PostForm("reply_title") 75 | replyContent := c.PostForm("reply_content") 76 | if replyId == "" || replyTitle == "" || replyContent == "" { 77 | c.JSON(400, gin.H{ 78 | "code": 200, 79 | "msg": "参数错误!", 80 | }) 81 | return 82 | } 83 | models.UpdateReplyContent(replyId, kefuId.(string), replyTitle, replyContent) 84 | c.JSON(200, gin.H{ 85 | "code": 200, 86 | "msg": "ok", 87 | }) 88 | } 89 | func DelReplyContent(c *gin.Context) { 90 | kefuId, _ := c.Get("kefu_name") 91 | id := c.Query("id") 92 | models.DeleteReplyContent(id, kefuId.(string)) 93 | c.JSON(200, gin.H{ 94 | "code": 200, 95 | "msg": "ok", 96 | }) 97 | } 98 | func DelReplyGroup(c *gin.Context) { 99 | kefuId, _ := c.Get("kefu_name") 100 | id := c.Query("id") 101 | models.DeleteReplyGroup(id, kefuId.(string)) 102 | c.JSON(200, gin.H{ 103 | "code": 200, 104 | "msg": "ok", 105 | }) 106 | } 107 | func PostReplySearch(c *gin.Context) { 108 | kefuId, _ := c.Get("kefu_name") 109 | search := c.PostForm("search") 110 | if search == "" { 111 | c.JSON(200, gin.H{ 112 | "code": 400, 113 | "msg": "参数错误", 114 | }) 115 | return 116 | } 117 | res := models.FindReplyBySearcch(kefuId, search) 118 | c.JSON(200, gin.H{ 119 | "code": 200, 120 | "msg": "ok", 121 | "result": res, 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /controller/response.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | var ( 4 | Port string 5 | ) 6 | 7 | type Response struct { 8 | Code int `json:"code"` 9 | Msg string `json:"msg"` 10 | result interface{} `json:"result"` 11 | } 12 | type ChatMessage struct { 13 | Time string `json:"time"` 14 | Content string `json:"content"` 15 | MesType string `json:"mes_type"` 16 | Name string `json:"name"` 17 | Avator string `json:"avator"` 18 | } 19 | type VisitorOnline struct { 20 | Uid string `json:"uid"` 21 | Username string `json:"username"` 22 | Avator string `json:"avator"` 23 | LastMessage string `json:"last_message"` 24 | } 25 | type GetuiResponse struct { 26 | Code float64 `json:"code"` 27 | Msg string `json:"msg"` 28 | Data map[string]interface{} `json:"data"` 29 | } 30 | type VisitorExtra struct { 31 | VisitorName string `json:"visitorName"` 32 | VisitorAvatar string `json:"visitorAvatar"` 33 | } 34 | -------------------------------------------------------------------------------- /controller/role.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | ) 7 | 8 | func GetRoleList(c *gin.Context) { 9 | roles := models.FindRoles() 10 | c.JSON(200, gin.H{ 11 | "code": 200, 12 | "msg": "获取成功", 13 | "result": roles, 14 | }) 15 | } 16 | func PostRole(c *gin.Context) { 17 | roleId := c.PostForm("id") 18 | method := c.PostForm("method") 19 | name := c.PostForm("name") 20 | path := c.PostForm("path") 21 | if roleId == "" || method == "" || name == "" || path == "" { 22 | c.JSON(200, gin.H{ 23 | "code": 400, 24 | "msg": "参数不能为空", 25 | }) 26 | return 27 | } 28 | models.SaveRole(roleId, name, method, path) 29 | c.JSON(200, gin.H{ 30 | "code": 200, 31 | "msg": "修改成功", 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /controller/setting.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | ) 7 | 8 | func GetConfigs(c *gin.Context) { 9 | configs := models.FindConfigs() 10 | c.JSON(200, gin.H{ 11 | "code": 200, 12 | "msg": "ok", 13 | "result": configs, 14 | }) 15 | } 16 | func GetConfig(c *gin.Context) { 17 | key := c.Query("key") 18 | config := models.FindConfig(key) 19 | c.JSON(200, gin.H{ 20 | "code": 200, 21 | "msg": "ok", 22 | "result": config, 23 | }) 24 | } 25 | func PostConfig(c *gin.Context) { 26 | key := c.PostForm("key") 27 | value := c.PostForm("value") 28 | if key == "" || value == "" { 29 | c.JSON(200, gin.H{ 30 | "code": 400, 31 | "msg": "error", 32 | }) 33 | return 34 | } 35 | models.UpdateConfig(key, value) 36 | 37 | c.JSON(200, gin.H{ 38 | "code": 200, 39 | "msg": "ok", 40 | "result": "", 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /controller/tcp.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "log" 6 | "net" 7 | ) 8 | 9 | var clientTcpList = make(map[string]net.Conn) 10 | 11 | func NewTcpServer(tcpBaseServer string) { 12 | listener, err := net.Listen("tcp", tcpBaseServer) 13 | if err != nil { 14 | log.Println("Error listening", err.Error()) 15 | return //终止程序 16 | } 17 | // 监听并接受来自客户端的连接 18 | for { 19 | conn, err := listener.Accept() 20 | if err != nil { 21 | log.Println("Error accepting", err.Error()) 22 | return // 终止程序 23 | } 24 | var remoteIpAddress = conn.RemoteAddr() 25 | clientTcpList[remoteIpAddress.String()] = conn 26 | log.Println(remoteIpAddress, clientTcpList) 27 | //clientTcpList=append(clientTcpList,conn) 28 | } 29 | } 30 | func PushServerTcp(str []byte) { 31 | for ip, conn := range clientTcpList { 32 | line := append(str, []byte("\r\n")...) 33 | _, err := conn.Write(line) 34 | log.Println(ip, err) 35 | if err != nil { 36 | conn.Close() 37 | delete(clientTcpList, ip) 38 | //clientTcpList=append(clientTcpList[:index],clientTcpList[index+1:]...) 39 | } 40 | } 41 | } 42 | func DeleteOnlineTcp(c *gin.Context) { 43 | ip := c.Query("ip") 44 | for ipkey, conn := range clientTcpList { 45 | if ip == ipkey { 46 | conn.Close() 47 | delete(clientTcpList, ip) 48 | } 49 | if ip == "all" { 50 | conn.Close() 51 | delete(clientTcpList, ipkey) 52 | } 53 | } 54 | c.JSON(200, gin.H{ 55 | "code": 200, 56 | "msg": "ok", 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /controller/weixin.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | "github.com/gin-gonic/gin" 7 | "github.com/taoshihan1991/imaptool/models" 8 | "log" 9 | "sort" 10 | ) 11 | 12 | func GetCheckWeixinSign(c *gin.Context) { 13 | token := models.FindConfig("WeixinToken") 14 | signature := c.Query("signature") 15 | timestamp := c.Query("timestamp") 16 | nonce := c.Query("nonce") 17 | echostr := c.Query("echostr") 18 | //将token、timestamp、nonce三个参数进行字典序排序 19 | var tempArray = []string{token, timestamp, nonce} 20 | sort.Strings(tempArray) 21 | //将三个参数字符串拼接成一个字符串进行sha1加密 22 | var sha1String string = "" 23 | for _, v := range tempArray { 24 | sha1String += v 25 | } 26 | h := sha1.New() 27 | h.Write([]byte(sha1String)) 28 | sha1String = hex.EncodeToString(h.Sum([]byte(""))) 29 | //获得加密后的字符串可与signature对比 30 | if sha1String == signature { 31 | c.Writer.Write([]byte(echostr)) 32 | } else { 33 | log.Println("微信API验证失败") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /go-fly.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/taoshihan1991/imaptool/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/taoshihan1991/imaptool 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f 7 | github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 8 | github.com/emersion/go-smtp v0.13.0 9 | github.com/gin-contrib/pprof v1.3.0 10 | github.com/gin-contrib/sessions v0.0.3 11 | github.com/gin-gonic/gin v1.7.7 12 | github.com/go-sql-driver/mysql v1.5.0 13 | github.com/gobuffalo/packr/v2 v2.5.1 14 | github.com/golang-jwt/jwt v3.2.2+incompatible 15 | github.com/gorilla/websocket v1.4.2 16 | github.com/ipipdotnet/ipdb-go v1.3.0 17 | github.com/jinzhu/gorm v1.9.14 18 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 19 | github.com/modern-go/reflect2 v1.0.1 // indirect 20 | github.com/satori/go.uuid v1.2.0 21 | github.com/sirupsen/logrus v1.4.2 22 | github.com/spf13/cobra v0.0.5 23 | github.com/zh-five/xdaemon v0.1.1 24 | ) 25 | -------------------------------------------------------------------------------- /middleware/cross.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func CrossSite(c *gin.Context) { 6 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 7 | //服务器支持的所有跨域请求的方法 8 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") 9 | //允许跨域设置可以返回其他子段,可以自定义字段 10 | c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session") 11 | // 允许浏览器(客户端)可以解析的头部 (重要) 12 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers") 13 | //允许客户端传递校验信息比如 cookie (重要) 14 | c.Header("Access-Control-Allow-Credentials", "true") 15 | c.Next() 16 | } 17 | -------------------------------------------------------------------------------- /middleware/domain_limit.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | /** 8 | 域名中间件 9 | */ 10 | func DomainLimitMiddleware(c *gin.Context) { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /middleware/ipblack.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | ) 7 | 8 | func Ipblack(c *gin.Context) { 9 | ip := c.ClientIP() 10 | ipblack := models.FindIp(ip) 11 | if ipblack.IP != "" { 12 | c.JSON(200, gin.H{ 13 | "code": 400, 14 | "msg": "IP已被加入黑名单", 15 | }) 16 | c.Abort() 17 | return 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /middleware/jwt.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/tools" 6 | "time" 7 | ) 8 | 9 | func JwtPageMiddleware(c *gin.Context) { 10 | //暂时不处理 11 | //token := c.Query("token") 12 | //userinfo := tools.ParseToken(token) 13 | //if userinfo == nil { 14 | // c.Redirect(302,"/login") 15 | // c.Abort() 16 | //} 17 | } 18 | func JwtApiMiddleware(c *gin.Context) { 19 | token := c.GetHeader("token") 20 | if token == "" { 21 | token = c.Query("token") 22 | } 23 | userinfo := tools.ParseToken(token) 24 | if userinfo == nil || userinfo["name"] == nil || userinfo["create_time"] == nil { 25 | c.JSON(200, gin.H{ 26 | "code": 400, 27 | "msg": "验证失败", 28 | }) 29 | c.Abort() 30 | return 31 | } 32 | createTime := int64(userinfo["create_time"].(float64)) 33 | var expire int64 = 24 * 60 * 60 34 | nowTime := time.Now().Unix() 35 | if (nowTime - createTime) >= expire { 36 | c.JSON(200, gin.H{ 37 | "code": 401, 38 | "msg": "token失效", 39 | }) 40 | c.Abort() 41 | } 42 | c.Set("user", userinfo["name"]) 43 | //log.Println(userinfo) 44 | //if userinfo["type"]=="kefu"{ 45 | c.Set("kefu_id", userinfo["kefu_id"]) 46 | c.Set("kefu_name", userinfo["name"]) 47 | c.Set("role_id", userinfo["role_id"]) 48 | //} 49 | } 50 | -------------------------------------------------------------------------------- /middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/tools" 6 | "time" 7 | ) 8 | 9 | func NewMidLogger() gin.HandlerFunc { 10 | logger := tools.Logger() 11 | return func(c *gin.Context) { 12 | // 开始时间 13 | startTime := time.Now() 14 | 15 | // 处理请求 16 | c.Next() 17 | 18 | // 结束时间 19 | endTime := time.Now() 20 | 21 | // 执行时间 22 | latencyTime := endTime.Sub(startTime) 23 | 24 | // 请求方式 25 | reqMethod := c.Request.Method 26 | 27 | // 请求路由 28 | reqUri := c.Request.RequestURI 29 | 30 | // 状态码 31 | statusCode := c.Writer.Status() 32 | 33 | // 请求IP 34 | clientIP := c.ClientIP() 35 | 36 | //日志格式 37 | logger.Infof("| %3d | %13v | %15s | %s | %s |", 38 | statusCode, 39 | latencyTime, 40 | clientIP, 41 | reqMethod, 42 | reqUri, 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /middleware/rbac.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "github.com/taoshihan1991/imaptool/models" 7 | "strings" 8 | ) 9 | 10 | func RbacAuth(c *gin.Context) { 11 | roleId, _ := c.Get("role_id") 12 | role := models.FindRole(roleId) 13 | var flag bool 14 | rPaths := strings.Split(c.Request.RequestURI, "?") 15 | uriParam := fmt.Sprintf("%s:%s", c.Request.Method, rPaths[0]) 16 | if role.Method != "*" || role.Path != "*" { 17 | paths := strings.Split(role.Path, ",") 18 | for _, p := range paths { 19 | if uriParam == p { 20 | flag = true 21 | break 22 | } 23 | } 24 | if !flag { 25 | c.JSON(200, gin.H{ 26 | "code": 403, 27 | "msg": "没有权限:" + uriParam, 28 | }) 29 | c.Abort() 30 | return 31 | } 32 | //methods := strings.Split(role.Method, ",") 33 | //for _, m := range methods { 34 | // if c.Request.Method == m { 35 | // methodFlag = true 36 | // break 37 | // } 38 | //} 39 | //if !methodFlag { 40 | // c.JSON(200, gin.H{ 41 | // "code": 403, 42 | // "msg": "没有权限:" + c.Request.Method + "," + rPaths[0], 43 | // }) 44 | // c.Abort() 45 | // return 46 | //} 47 | } 48 | //var flag bool 49 | //if role.Path != "*" { 50 | // paths := strings.Split(role.Path, ",") 51 | // for _, p := range paths { 52 | // if rPaths[0] == p { 53 | // flag = true 54 | // break 55 | // } 56 | // } 57 | // if !flag { 58 | // c.JSON(200, gin.H{ 59 | // "code": 403, 60 | // "msg": "没有权限:" + rPaths[0], 61 | // }) 62 | // c.Abort() 63 | // return 64 | // } 65 | //} 66 | } 67 | -------------------------------------------------------------------------------- /models/abouts.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type About struct { 4 | ID uint `gorm:"primary_key" json:"id"` 5 | TitleCn string `json:"title_cn"` 6 | TitleEn string `json:"title_en"` 7 | KeywordsCn string `json:"keywords_cn"` 8 | KeywordsEn string `json:"keywords_en"` 9 | DescCn string `json:"desc_cn"` 10 | DescEn string `json:"desc_en"` 11 | CssJs string `json:"css_js"` 12 | HtmlCn string `json:"html_cn"` 13 | HtmlEn string `json:"html_en"` 14 | Page string `json:"page"` 15 | } 16 | 17 | func FindAbouts() []About { 18 | var a []About 19 | DB.Select("id,title_cn,page").Find(&a) 20 | return a 21 | } 22 | 23 | func FindAboutByPage(page interface{}) About { 24 | var a About 25 | DB.Where("page = ?", page).First(&a) 26 | return a 27 | } 28 | func FindAboutByPageLanguage(page interface{}, lang string) About { 29 | var a About 30 | if lang == "" { 31 | lang = "cn" 32 | } 33 | if lang == "en" { 34 | DB.Select("css_js,title_en,keywords_en,desc_en,html_en").Where("page = ?", page).First(&a) 35 | } else { 36 | DB.Select("css_js,title_cn,keywords_cn,desc_cn,html_cn").Where("page = ?", page).First(&a) 37 | } 38 | return a 39 | } 40 | 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) { 41 | c := &About{ 42 | TitleCn: title_cn, 43 | TitleEn: title_en, 44 | KeywordsCn: keywords_cn, 45 | KeywordsEn: keywords_en, 46 | DescCn: desc_cn, 47 | DescEn: desc_en, 48 | CssJs: css_js, 49 | HtmlCn: html_cn, 50 | HtmlEn: html_en, 51 | } 52 | DB.Model(c).Where("page = ?", page).Update(c) 53 | InitConfig() 54 | } 55 | -------------------------------------------------------------------------------- /models/configs.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | var CustomConfigs []Config 4 | 5 | type Config struct { 6 | ID uint `gorm:"primary_key" json:"id"` 7 | ConfName string `json:"conf_name"` 8 | ConfKey string `json:"conf_key"` 9 | ConfValue string `json:"conf_value"` 10 | } 11 | 12 | func UpdateConfig(key string, value string) { 13 | c := &Config{ 14 | ConfValue: value, 15 | } 16 | DB.Model(c).Where("conf_key = ?", key).Update(c) 17 | InitConfig() 18 | } 19 | func FindConfigs() []Config { 20 | var config []Config 21 | DB.Find(&config) 22 | return config 23 | } 24 | func InitConfig() { 25 | CustomConfigs = FindConfigs() 26 | } 27 | func FindConfig(key string) string { 28 | for _, config := range CustomConfigs { 29 | if key == config.ConfKey { 30 | return config.ConfValue 31 | } 32 | } 33 | return "" 34 | } 35 | -------------------------------------------------------------------------------- /models/ipblacks.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Ipblack struct { 6 | ID uint `gorm:"primary_key" json:"id"` 7 | IP string `json:"ip"` 8 | KefuId string `json:"kefu_id"` 9 | CreateAt time.Time `json:"create_at"` 10 | } 11 | 12 | func CreateIpblack(ip string, kefuId string) uint { 13 | black := &Ipblack{ 14 | IP: ip, 15 | KefuId: kefuId, 16 | CreateAt: time.Now(), 17 | } 18 | DB.Create(black) 19 | return black.ID 20 | } 21 | func DeleteIpblackByIp(ip string) { 22 | DB.Where("ip = ?", ip).Delete(Ipblack{}) 23 | } 24 | func FindIp(ip string) Ipblack { 25 | var ipblack Ipblack 26 | DB.Where("ip = ?", ip).First(&ipblack) 27 | return ipblack 28 | } 29 | func FindIpsByKefuId(id string) []Ipblack { 30 | var ipblack []Ipblack 31 | DB.Where("kefu_id = ?", id).Find(&ipblack) 32 | return ipblack 33 | } 34 | func FindIps(query interface{}, args []interface{}, page uint, pagesize uint) []Ipblack { 35 | offset := (page - 1) * pagesize 36 | if offset < 0 { 37 | offset = 0 38 | } 39 | var ipblacks []Ipblack 40 | if query != nil { 41 | DB.Where(query, args...).Offset(offset).Limit(pagesize).Find(&ipblacks) 42 | } else { 43 | DB.Offset(offset).Limit(pagesize).Find(&ipblacks) 44 | } 45 | return ipblacks 46 | } 47 | 48 | //查询条数 49 | func CountIps(query interface{}, args []interface{}) uint { 50 | var count uint 51 | if query != nil { 52 | DB.Model(&Visitor{}).Where(query, args...).Count(&count) 53 | } else { 54 | DB.Model(&Visitor{}).Count(&count) 55 | } 56 | return count 57 | } 58 | -------------------------------------------------------------------------------- /models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jinzhu/gorm" 6 | "github.com/taoshihan1991/imaptool/common" 7 | "log" 8 | "time" 9 | ) 10 | 11 | var DB *gorm.DB 12 | 13 | type Model struct { 14 | ID uint `gorm:"primary_key" json:"id"` 15 | CreatedAt time.Time `json:"created_at"` 16 | UpdatedAt time.Time `json:"updated_at"` 17 | DeletedAt *time.Time `sql:"index" json:"deleted_at"` 18 | } 19 | 20 | func init() { 21 | Connect() 22 | } 23 | func Connect() error { 24 | mysql := common.GetMysqlConf() 25 | 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) 26 | var err error 27 | DB, err = gorm.Open("mysql", dsn) 28 | if err != nil { 29 | log.Println(err) 30 | panic("数据库连接失败!") 31 | return err 32 | } 33 | DB.SingularTable(true) 34 | DB.LogMode(true) 35 | DB.DB().SetMaxIdleConns(10) 36 | DB.DB().SetMaxOpenConns(100) 37 | DB.DB().SetConnMaxLifetime(59 * time.Second) 38 | InitConfig() 39 | return nil 40 | } 41 | func Execute(sql string) error { 42 | return DB.Exec(sql).Error 43 | } 44 | func CloseDB() { 45 | defer DB.Close() 46 | } 47 | -------------------------------------------------------------------------------- /models/replys.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ReplyItem struct { 4 | Id string `json:"item_id"` 5 | Content string `json:"item_content"` 6 | GroupId string `json:"group_id"` 7 | ItemName string `json:"item_name"` 8 | UserId string `json:"user_id"` 9 | } 10 | type ReplyGroup struct { 11 | Id string `json:"group_id"` 12 | GroupName string `json:"group_name"` 13 | UserId string `json:"user_id"` 14 | Items []*ReplyItem `json:"items";"` 15 | } 16 | 17 | func FindReplyItemByUserIdTitle(userId interface{}, title string) ReplyItem { 18 | var reply ReplyItem 19 | DB.Where("user_id = ? and item_name = ?", userId, title).Find(&reply) 20 | return reply 21 | } 22 | func FindReplyByUserId(userId interface{}) []*ReplyGroup { 23 | var replyGroups []*ReplyGroup 24 | //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) 25 | var replyItems []*ReplyItem 26 | DB.Where("user_id = ?", userId).Find(&replyGroups) 27 | DB.Where("user_id = ?", userId).Find(&replyItems) 28 | temp := make(map[string]*ReplyGroup) 29 | for _, replyGroup := range replyGroups { 30 | replyGroup.Items = make([]*ReplyItem, 0) 31 | temp[replyGroup.Id] = replyGroup 32 | } 33 | for _, replyItem := range replyItems { 34 | temp[replyItem.GroupId].Items = append(temp[replyItem.GroupId].Items, replyItem) 35 | } 36 | return replyGroups 37 | } 38 | func FindReplyTitleByUserId(userId interface{}) []*ReplyGroup { 39 | var replyGroups []*ReplyGroup 40 | //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) 41 | var replyItems []*ReplyItem 42 | DB.Where("user_id = ?", userId).Find(&replyGroups) 43 | DB.Select("item_name,group_id").Where("user_id = ?", userId).Find(&replyItems) 44 | temp := make(map[string]*ReplyGroup) 45 | for _, replyGroup := range replyGroups { 46 | replyGroup.Items = make([]*ReplyItem, 0) 47 | temp[replyGroup.Id] = replyGroup 48 | } 49 | for _, replyItem := range replyItems { 50 | temp[replyItem.GroupId].Items = append(temp[replyItem.GroupId].Items, replyItem) 51 | } 52 | return replyGroups 53 | } 54 | func CreateReplyGroup(groupName string, userId string) { 55 | g := &ReplyGroup{ 56 | GroupName: groupName, 57 | UserId: userId, 58 | } 59 | DB.Create(g) 60 | } 61 | func CreateReplyContent(groupId string, userId string, content, itemName string) { 62 | g := &ReplyItem{ 63 | GroupId: groupId, 64 | UserId: userId, 65 | Content: content, 66 | ItemName: itemName, 67 | } 68 | DB.Create(g) 69 | } 70 | func UpdateReplyContent(id, userId, title, content string) { 71 | r := &ReplyItem{ 72 | ItemName: title, 73 | Content: content, 74 | } 75 | DB.Model(&ReplyItem{}).Where("user_id = ? and id = ?", userId, id).Update(r) 76 | } 77 | func DeleteReplyContent(id string, userId string) { 78 | DB.Where("user_id = ? and id = ?", userId, id).Delete(ReplyItem{}) 79 | } 80 | func DeleteReplyGroup(id string, userId string) { 81 | DB.Where("user_id = ? and id = ?", userId, id).Delete(ReplyGroup{}) 82 | DB.Where("user_id = ? and group_id = ?", userId, id).Delete(ReplyItem{}) 83 | } 84 | func FindReplyBySearcch(userId interface{}, search string) []*ReplyGroup { 85 | var replyGroups []*ReplyGroup 86 | var replyItems []*ReplyItem 87 | DB.Where("user_id = ?", userId).Find(&replyGroups) 88 | DB.Where("user_id = ? and content like ?", userId, "%"+search+"%").Find(&replyItems) 89 | temp := make(map[string]*ReplyGroup) 90 | for _, replyGroup := range replyGroups { 91 | replyGroup.Items = make([]*ReplyItem, 0) 92 | temp[replyGroup.Id] = replyGroup 93 | } 94 | for _, replyItem := range replyItems { 95 | temp[replyItem.GroupId].Items = append(temp[replyItem.GroupId].Items, replyItem) 96 | } 97 | var newReplyGroups []*ReplyGroup = make([]*ReplyGroup, 0) 98 | for _, replyGroup := range replyGroups { 99 | if len(replyGroup.Items) != 0 { 100 | newReplyGroups = append(newReplyGroups, replyGroup) 101 | } 102 | } 103 | return newReplyGroups 104 | } 105 | -------------------------------------------------------------------------------- /models/roles.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Role struct { 4 | Id string `json:"role_id"` 5 | Name string `json:"role_name"` 6 | Method string `json:"method"` 7 | Path string `json:"path"` 8 | } 9 | 10 | func FindRoles() []Role { 11 | var roles []Role 12 | DB.Order("id desc").Find(&roles) 13 | return roles 14 | } 15 | func FindRole(id interface{}) Role { 16 | var role Role 17 | DB.Where("id = ?", id).First(&role) 18 | return role 19 | } 20 | func SaveRole(id string, name string, method string, path string) { 21 | role := &Role{ 22 | Method: method, 23 | Name: name, 24 | Path: path, 25 | } 26 | DB.Model(role).Where("id=?", id).Update(role) 27 | } 28 | -------------------------------------------------------------------------------- /models/user_client.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type User_client struct { 6 | ID uint `gorm:"primary_key" json:"id"` 7 | Kefu string `json:"kefu"` 8 | Client_id string `json:"client_id"` 9 | Created_at string `json:"created_at"` 10 | } 11 | 12 | func CreateUserClient(kefu, clientId string) uint { 13 | u := &User_client{ 14 | Kefu: kefu, 15 | Client_id: clientId, 16 | Created_at: time.Now().Format("2006-01-02 15:04:05"), 17 | } 18 | DB.Create(u) 19 | return u.ID 20 | } 21 | func FindClients(kefu string) []User_client { 22 | var arr []User_client 23 | DB.Where("kefu = ?", kefu).Find(&arr) 24 | return arr 25 | } 26 | -------------------------------------------------------------------------------- /models/user_roles.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type User_role struct { 8 | ID uint `gorm:"primary_key" json:"id"` 9 | UserId string `json:"user_id"` 10 | RoleId uint `json:"role_id"` 11 | } 12 | 13 | func FindRoleByUserId(userId interface{}) User_role { 14 | var uRole User_role 15 | DB.Where("user_id = ?", userId).First(&uRole) 16 | return uRole 17 | } 18 | func CreateUserRole(userId uint, roleId uint) { 19 | uRole := &User_role{ 20 | UserId: strconv.Itoa(int(userId)), 21 | RoleId: roleId, 22 | } 23 | DB.Create(uRole) 24 | } 25 | func DeleteRoleByUserId(userId interface{}) { 26 | DB.Where("user_id = ?", userId).Delete(User_role{}) 27 | } 28 | -------------------------------------------------------------------------------- /models/users.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | _ "github.com/jinzhu/gorm/dialects/mysql" 5 | "time" 6 | ) 7 | 8 | type User struct { 9 | Model 10 | Name string `json:"name"` 11 | Password string `json:"password"` 12 | Nickname string `json:"nickname"` 13 | Avator string `json:"avator"` 14 | RoleName string `json:"role_name" sql:"-"` 15 | RoleId string `json:"role_id" sql:"-"` 16 | } 17 | 18 | func CreateUser(name string, password string, avator string, nickname string) uint { 19 | user := &User{ 20 | Name: name, 21 | Password: password, 22 | Avator: avator, 23 | Nickname: nickname, 24 | } 25 | user.UpdatedAt = time.Now() 26 | DB.Create(user) 27 | return user.ID 28 | } 29 | func UpdateUser(id string, name string, password string, avator string, nickname string) { 30 | user := &User{ 31 | Name: name, 32 | Avator: avator, 33 | Nickname: nickname, 34 | } 35 | user.UpdatedAt = time.Now() 36 | if password != "" { 37 | user.Password = password 38 | } 39 | DB.Model(&User{}).Where("id = ?", id).Update(user) 40 | } 41 | func UpdateUserPass(name string, pass string) { 42 | user := &User{ 43 | Password: pass, 44 | } 45 | user.UpdatedAt = time.Now() 46 | DB.Model(user).Where("name = ?", name).Update("Password", pass) 47 | } 48 | func UpdateUserAvator(name string, avator string) { 49 | user := &User{ 50 | Avator: avator, 51 | } 52 | user.UpdatedAt = time.Now() 53 | DB.Model(user).Where("name = ?", name).Update("Avator", avator) 54 | } 55 | func FindUser(username string) User { 56 | var user User 57 | DB.Where("name = ?", username).First(&user) 58 | return user 59 | } 60 | func FindUserById(id interface{}) User { 61 | var user User 62 | 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) 63 | return user 64 | } 65 | func DeleteUserById(id string) { 66 | DB.Where("id = ?", id).Delete(User{}) 67 | } 68 | func FindUsers() []User { 69 | var users []User 70 | 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) 71 | return users 72 | } 73 | func FindUserRole(query interface{}, id interface{}) User { 74 | var user User 75 | 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) 76 | return user 77 | } 78 | -------------------------------------------------------------------------------- /models/visitors.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Visitor struct { 8 | Model 9 | Name string `json:"name"` 10 | Avator string `json:"avator"` 11 | SourceIp string `json:"source_ip"` 12 | ToId string `json:"to_id"` 13 | VisitorId string `json:"visitor_id"` 14 | Status uint `json:"status"` 15 | Refer string `json:"refer"` 16 | City string `json:"city"` 17 | ClientIp string `json:"client_ip"` 18 | Extra string `json:"extra"` 19 | } 20 | 21 | func CreateVisitor(name, avator, sourceIp, toId, visitorId, refer, city, clientIp, extra string) { 22 | v := &Visitor{ 23 | Name: name, 24 | Avator: avator, 25 | SourceIp: sourceIp, 26 | ToId: toId, 27 | VisitorId: visitorId, 28 | Status: 1, 29 | Refer: refer, 30 | City: city, 31 | ClientIp: clientIp, 32 | Extra: extra, 33 | } 34 | v.UpdatedAt = time.Now() 35 | DB.Create(v) 36 | } 37 | func FindVisitorByVistorId(visitorId string) Visitor { 38 | var v Visitor 39 | DB.Where("visitor_id = ?", visitorId).First(&v) 40 | return v 41 | } 42 | func FindVisitors(page uint, pagesize uint) []Visitor { 43 | offset := (page - 1) * pagesize 44 | if offset < 0 { 45 | offset = 0 46 | } 47 | var visitors []Visitor 48 | DB.Offset(offset).Limit(pagesize).Order("status desc, updated_at desc").Find(&visitors) 49 | return visitors 50 | } 51 | func FindVisitorsByKefuId(page uint, pagesize uint, kefuId string) []Visitor { 52 | offset := (page - 1) * pagesize 53 | if offset <= 0 { 54 | offset = 0 55 | } 56 | var visitors []Visitor 57 | //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) 58 | //DB.Raw(sql).Scan(&visitors) 59 | DB.Where("to_id=?", kefuId).Offset(offset).Limit(pagesize).Order("updated_at desc").Find(&visitors) 60 | return visitors 61 | } 62 | func FindVisitorsOnline() []Visitor { 63 | var visitors []Visitor 64 | DB.Where("status = ?", 1).Find(&visitors) 65 | return visitors 66 | } 67 | func UpdateVisitorStatus(visitorId string, status uint) { 68 | visitor := Visitor{} 69 | DB.Model(&visitor).Where("visitor_id = ?", visitorId).Update("status", status) 70 | } 71 | func UpdateVisitor(name, avator, visitorId string, status uint, clientIp string, sourceIp string, refer, extra string) { 72 | visitor := &Visitor{ 73 | Status: status, 74 | ClientIp: clientIp, 75 | SourceIp: sourceIp, 76 | Refer: refer, 77 | Extra: extra, 78 | Name: name, 79 | Avator: avator, 80 | } 81 | visitor.UpdatedAt = time.Now() 82 | DB.Model(visitor).Where("visitor_id = ?", visitorId).Update(visitor) 83 | } 84 | func UpdateVisitorKefu(visitorId string, kefuId string) { 85 | visitor := Visitor{} 86 | DB.Model(&visitor).Where("visitor_id = ?", visitorId).Update("to_id", kefuId) 87 | } 88 | 89 | //查询条数 90 | func CountVisitors() uint { 91 | var count uint 92 | DB.Model(&Visitor{}).Count(&count) 93 | return count 94 | } 95 | 96 | //查询条数 97 | func CountVisitorsByKefuId(kefuId string) uint { 98 | var count uint 99 | DB.Model(&Visitor{}).Where("to_id=?", kefuId).Count(&count) 100 | return count 101 | } 102 | 103 | //查询每天条数 104 | type EveryDayNum struct { 105 | Day string `json:"day"` 106 | Num int64 `json:"num"` 107 | } 108 | 109 | func CountVisitorsEveryDay(toId string) []EveryDayNum { 110 | var results []EveryDayNum 111 | DB.Raw("select DATE_FORMAT(created_at,'%y-%m-%d') as day ,"+ 112 | "count(*) as num from visitor where to_id=? group by day order by day desc limit 30", 113 | toId).Scan(&results) 114 | return results 115 | } 116 | -------------------------------------------------------------------------------- /models/welcomes.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Welcome struct { 6 | ID uint `gorm:"primary_key" json:"id"` 7 | UserId string `json:"user_id"` 8 | Keyword string `json:"keyword"` 9 | Content string `json:"content"` 10 | IsDefault uint `json:"is_default"` 11 | Ctime time.Time `json:"ctime"` 12 | } 13 | 14 | func CreateWelcome(userId string, content string) uint { 15 | if userId == "" || content == "" { 16 | return 0 17 | } 18 | w := &Welcome{ 19 | UserId: userId, 20 | Content: content, 21 | Ctime: time.Now(), 22 | Keyword: "welcome", 23 | } 24 | DB.Create(w) 25 | return w.ID 26 | } 27 | func UpdateWelcome(userId string, id string, content string) uint { 28 | if userId == "" || content == "" { 29 | return 0 30 | } 31 | w := &Welcome{ 32 | Content: content, 33 | } 34 | DB.Model(w).Where("user_id = ? and id = ?", userId, id).Update(w) 35 | return w.ID 36 | } 37 | func FindWelcomeByUserIdKey(userId interface{}, keyword interface{}) Welcome { 38 | var w Welcome 39 | DB.Where("user_id = ? and keyword=?", userId, keyword).First(&w) 40 | return w 41 | } 42 | func FindWelcomesByUserId(userId interface{}) []Welcome { 43 | var w []Welcome 44 | DB.Where("user_id = ?", userId).Find(&w) 45 | return w 46 | } 47 | func FindWelcomesByKeyword(userId interface{}, keyword interface{}) []Welcome { 48 | var w []Welcome 49 | DB.Where("user_id = ? and keyword=?", userId, keyword).Find(&w) 50 | return w 51 | } 52 | func DeleteWelcome(userId interface{}, id string) { 53 | DB.Where("user_id = ? and id = ?", userId, id).Delete(Welcome{}) 54 | } 55 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### 郑重提示 2 | 禁止将本项目用于含病毒、木马、色情、赌博、诈骗、违禁用品、假冒产品、虚假信息、数字货币、金融等违法违规业务 3 | 4 | 当前项目仅供个人学习测试,禁止一切线上商用行为,禁止一切违法使用!!! 5 | 6 | 7 | 8 | 9 | ### 项目简介 10 | 11 | Golang语言开源客服系统,主要使用了gin + jwt-go + websocket + go.uuid + gorm + cobra + VueJS + ElementUI + MySQL等技术 12 | 13 | 14 | ### 安装使用 15 | 16 | 17 | * 先安装和运行mysql数据库 ,版本>=5.5 ,创建数据库 18 | 19 | ``` 20 | create database gofly charset utf8mb4; 21 | ``` 22 | 23 | * 配置数据库链接信息,在config目录mysql.json中 24 | ```php 25 | { 26 | "Server":"127.0.0.1", 27 | "Port":"3306", 28 | "Database":"gofly", 29 | "Username":"go-fly", 30 | "Password":"go-fly" 31 | } 32 | ``` 33 | * 安装配置Golang运行环境,请参照下面的命令去执行 34 | ```php 35 | wget https://studygolang.com/dl/golang/go1.20.2.linux-amd64.tar.gz 36 | tar -C /usr/local -xvf go1.20.2.linux-amd64.tar.gz 37 | mv go1.20.2.linux-amd64.tar.gz /tmp 38 | echo "PATH=\$PATH:/usr/local/go/bin" >> /etc/profile 39 | echo "PATH=\$PATH:/usr/local/go/bin" >> ~/.bashrc 40 | source /etc/profile 41 | go version 42 | go env -w GO111MODULE=on 43 | go env -w GOPROXY=https://goproxy.cn,direct 44 | ``` 45 | * 下载代码 46 | 47 | 在任意目录 git clone https://github.com/taoshihan1991/go-fly.git 48 | 49 | 进入go-fly 目录 50 | 51 | * 导入数据库 go run go-fly.go install 52 | 53 | * 源码运行 go run go-fly.go server 54 | 55 | * 源码打包 go build -o kefu 会生成kefu可以执行文件 56 | 57 | * 二进制文件运行 58 | 59 | linux: ./kefu server [可选 -p 8082 -d] 60 | 61 | windows: kefu.exe server [可选 -p 8082 -d] 62 | 63 | * 关闭程序 64 | 65 | killall kefu 66 | 67 | 68 | 程序正常运行后,监听端口8081,可以直接ip+端口8081访问 69 | 70 | 也可以配置域名访问,反向代理到8081端口,就能隐藏端口号 71 | ### 客服对接 72 | 聊天链接 73 | 74 | http://127.0.0.1:8081/chatIndex?kefu_id=kefu2 75 | 76 | 弹窗使用 77 | 78 | ``` 79 | (function(a, b, c, d) { 80 | let h = b.getElementsByTagName('head')[0];let s = b.createElement('script'); 81 | s.type = 'text/javascript';s.src = c+"/static/js/kefu-front.js";s.onload = s.onreadystatechange = function () { 82 | if (!this.readyState || this.readyState === "loaded" || this.readyState === "complete") d(c); 83 | };h.appendChild(s); 84 | })(window, document,"http://127.0.0.1:8081",function(u){ 85 | KEFU.init({ 86 | KEFU_URL:u, 87 | KEFU_KEFU_ID: "kefu2", 88 | }) 89 | }); 90 | 91 | ``` 92 | ### 版权声明 93 | 94 | 当前项目是完整功能代码 , 但是仍然仅支持个人演示测试 , 不包含线上使用 ,禁止一切商用行为。 95 | 使用本软件时,请遵守当地法律法规,任何违法用途一切后果请自行承担. -------------------------------------------------------------------------------- /router/view.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/middleware" 6 | "github.com/taoshihan1991/imaptool/tmpl" 7 | ) 8 | 9 | func InitViewRouter(engine *gin.Engine) { 10 | engine.GET("/", tmpl.PageIndex) 11 | 12 | engine.GET("/login", tmpl.PageLogin) 13 | engine.GET("/pannel", tmpl.PagePannel) 14 | engine.GET("/chatIndex", tmpl.PageChat) 15 | engine.GET("/main", middleware.JwtPageMiddleware, tmpl.PageMain) 16 | engine.GET("/chat_main", middleware.JwtPageMiddleware, middleware.DomainLimitMiddleware, tmpl.PageChatMain) 17 | engine.GET("/setting", middleware.DomainLimitMiddleware, tmpl.PageSetting) 18 | engine.GET("/setting_statistics", tmpl.PageSettingStatis) 19 | engine.GET("/setting_indexpage", tmpl.PageSettingIndexPage) 20 | engine.GET("/setting_indexpages", tmpl.PageSettingIndexPages) 21 | engine.GET("/setting_mysql", tmpl.PageSettingMysql) 22 | engine.GET("/setting_welcome", tmpl.PageSettingWelcome) 23 | engine.GET("/setting_deploy", tmpl.PageSettingDeploy) 24 | engine.GET("/setting_kefu_list", tmpl.PageKefuList) 25 | engine.GET("/setting_avator", tmpl.PageAvator) 26 | engine.GET("/setting_modifypass", tmpl.PageModifypass) 27 | engine.GET("/setting_ipblack", tmpl.PageIpblack) 28 | engine.GET("/setting_config", tmpl.PageConfig) 29 | engine.GET("/mail_list", tmpl.PageMailList) 30 | engine.GET("/roles_list", tmpl.PageRoleList) 31 | } 32 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | go-fly.exe server 2 | pause -------------------------------------------------------------------------------- /static/cdn/element-ui/2.15.1/theme-chalk/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/cdn/element-ui/2.15.1/theme-chalk/fonts/element-icons.woff -------------------------------------------------------------------------------- /static/cdn/element-ui/2.15.7/theme-chalk/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/cdn/element-ui/2.15.7/theme-chalk/fonts/element-icons.woff -------------------------------------------------------------------------------- /static/css/front.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar 2 | { 3 | width: 5px; 4 | height: 110px; 5 | background-color: #F5F5F5; 6 | } 7 | /*定义滚动条轨道 内阴影+圆角*/ 8 | ::-webkit-scrollbar-track 9 | { 10 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 11 | border-radius: 10px; 12 | background-color: #F5F5F5; 13 | } 14 | /*定义滑块 内阴影+圆角*/ 15 | ::-webkit-scrollbar-thumb 16 | { 17 | border-radius: 10px; 18 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); 19 | background-color: #bdbdbd; 20 | } 21 | /*滑块效果*/ 22 | ::-webkit-scrollbar-thumb:hover 23 | { 24 | border-radius: 5px; 25 | -webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2); 26 | background: rgba(0,0,0,0.4); 27 | } 28 | /*IE滚动条颜色*/ 29 | html { 30 | scrollbar-face-color:#bfbfbf;/*滚动条颜色*/ 31 | scrollbar-highlight-color:#000; 32 | scrollbar-3dlight-color:#000; 33 | scrollbar-darkshadow-color:#000; 34 | scrollbar-Shadow-color:#adadad;/*滑块边色*/ 35 | scrollbar-arrow-color:rgba(0,0,0,0.4);/*箭头颜色*/ 36 | scrollbar-track-color:#eeeeee;/*背景颜色*/ 37 | } -------------------------------------------------------------------------------- /static/css/icon/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 3864484 */ 3 | src: url('iconfont.woff2?t=1678338039263') format('woff2'), 4 | url('iconfont.woff?t=1678338039263') format('woff'), 5 | url('iconfont.ttf?t=1678338039263') format('truetype'); 6 | } 7 | 8 | .iconfont { 9 | font-family: "iconfont" !important; 10 | font-size: 16px; 11 | font-style: normal; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | .icon-more:before { 17 | content: "\e867"; 18 | } 19 | 20 | .icon-zengjiatianjiajiahao:before { 21 | content: "\e62a"; 22 | } 23 | 24 | .icon-jianshaominimize3:before { 25 | content: "\e68b"; 26 | } 27 | 28 | .icon-folder-fill:before { 29 | content: "\e7c4"; 30 | } 31 | 32 | .icon-jietu:before { 33 | content: "\e611"; 34 | } 35 | 36 | .icon-duoyuyan:before { 37 | content: "\e606"; 38 | } 39 | 40 | .icon-jiahao:before { 41 | content: "\eaf3"; 42 | } 43 | 44 | .icon-xiaolian:before { 45 | content: "\ec80"; 46 | } 47 | 48 | .icon-fasong:before { 49 | content: "\e604"; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /static/css/icon/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3864484", 3 | "name": "我的客服", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "18991683", 10 | "name": "more", 11 | "font_class": "more", 12 | "unicode": "e867", 13 | "unicode_decimal": 59495 14 | }, 15 | { 16 | "icon_id": "8817897", 17 | "name": "增加添加加号", 18 | "font_class": "zengjiatianjiajiahao", 19 | "unicode": "e62a", 20 | "unicode_decimal": 58922 21 | }, 22 | { 23 | "icon_id": "608294", 24 | "name": "减少", 25 | "font_class": "jianshaominimize3", 26 | "unicode": "e68b", 27 | "unicode_decimal": 59019 28 | }, 29 | { 30 | "icon_id": "6151130", 31 | "name": "folder-fill", 32 | "font_class": "folder-fill", 33 | "unicode": "e7c4", 34 | "unicode_decimal": 59332 35 | }, 36 | { 37 | "icon_id": "13397103", 38 | "name": "截图", 39 | "font_class": "jietu", 40 | "unicode": "e611", 41 | "unicode_decimal": 58897 42 | }, 43 | { 44 | "icon_id": "10598085", 45 | "name": "language,多语言", 46 | "font_class": "duoyuyan", 47 | "unicode": "e606", 48 | "unicode_decimal": 58886 49 | }, 50 | { 51 | "icon_id": "5387527", 52 | "name": "加号", 53 | "font_class": "jiahao", 54 | "unicode": "eaf3", 55 | "unicode_decimal": 60147 56 | }, 57 | { 58 | "icon_id": "6337465", 59 | "name": "笑脸", 60 | "font_class": "xiaolian", 61 | "unicode": "ec80", 62 | "unicode_decimal": 60544 63 | }, 64 | { 65 | "icon_id": "1418205", 66 | "name": "发送", 67 | "font_class": "fasong", 68 | "unicode": "e604", 69 | "unicode_decimal": 58884 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /static/css/icon/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/icon/iconfont.ttf -------------------------------------------------------------------------------- /static/css/icon/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/icon/iconfont.woff -------------------------------------------------------------------------------- /static/css/icon/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/icon/iconfont.woff2 -------------------------------------------------------------------------------- /static/css/index.css: -------------------------------------------------------------------------------- 1 | .footer{background: #30313a;color: #acacac;padding: 30px 0;min-width: 1190px;} -------------------------------------------------------------------------------- /static/css/layui/css/modules/code.css: -------------------------------------------------------------------------------- 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} -------------------------------------------------------------------------------- /static/css/layui/css/modules/layer/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/css/modules/layer/default/icon-ext.png -------------------------------------------------------------------------------- /static/css/layui/css/modules/layer/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/css/modules/layer/default/icon.png -------------------------------------------------------------------------------- /static/css/layui/css/modules/layer/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/css/modules/layer/default/loading-0.gif -------------------------------------------------------------------------------- /static/css/layui/css/modules/layer/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/css/modules/layer/default/loading-1.gif -------------------------------------------------------------------------------- /static/css/layui/css/modules/layer/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/css/modules/layer/default/loading-2.gif -------------------------------------------------------------------------------- /static/css/layui/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/font/iconfont.eot -------------------------------------------------------------------------------- /static/css/layui/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/font/iconfont.ttf -------------------------------------------------------------------------------- /static/css/layui/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/font/iconfont.woff -------------------------------------------------------------------------------- /static/css/layui/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/css/layui/font/iconfont.woff2 -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0;padding: 0; 3 | } 4 | a { 5 | text-decoration: none; 6 | color: #3973ac; 7 | } 8 | a:hover { 9 | text-decoration: underline; 10 | } 11 | pre{ 12 | background:#f9f9f9; 13 | color: #d73a49; 14 | line-height: 21px; 15 | border: 1px solid #e8e8e8; 16 | padding: 5px; 17 | } 18 | .header{ 19 | background-color: #fff; 20 | color: #fff; 21 | width: 100%; 22 | z-index: 100; 23 | } 24 | .container{ 25 | width: 1140px; 26 | padding: 0; 27 | margin: 0 auto; 28 | } 29 | .header .logo{ 30 | margin: 0; 31 | float: left; 32 | font-size: 32px; 33 | font-weight: bold; 34 | margin-top: 10px; 35 | } 36 | .header a{ 37 | color: #828282; 38 | font-weight: bold; 39 | font-family: "Microsoft JhengHei"; 40 | text-decoration: none; 41 | } 42 | .header .logo a{ 43 | font-size: 30px; 44 | text-decoration: none; 45 | } 46 | .header .logo img{ 47 | width: 120px; 48 | } 49 | .header .navBtn{ 50 | float: right; 51 | margin:30px 0 30px 20px; 52 | display: inline-block; 53 | } 54 | .banner{ 55 | text-align: center; 56 | color: #fff; 57 | background-color: #3385ff; 58 | padding: 20px 10px; 59 | font-size: 18px; 60 | line-height: 28px; 61 | } 62 | .banner h1 { 63 | font-size: 34px; 64 | margin: 20px 0px; 65 | line-height: 45px; 66 | font-weight: 500; 67 | font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif; 68 | } 69 | .banner p{ 70 | max-width: 1200px; 71 | margin: 0 auto; 72 | } 73 | .banner .downloadBtn{margin-top: 20px;} 74 | .downloadBtn{display:inline-block;text-decoration:none;color:#fff;border:1px solid #fff;padding:10px 15px;border-radius:5px;} 75 | .downloadBtn:hover{background:#fff;color:#20b2bb;text-decoration: none;} 76 | .jumbotron{ 77 | width: 1000px; 78 | margin: 10px auto; 79 | box-shadow: 2px 2px 15px rgba(0,0,0,.3); 80 | display: block; 81 | } 82 | 83 | 84 | .copyright{ 85 | font-size: 14px; 86 | color: #333; 87 | text-align: center; 88 | padding: 10px; 89 | } 90 | .links{ 91 | margin-top: 20px; 92 | } 93 | .links a{ 94 | color: #20b2bb; 95 | line-height: 23px; 96 | margin: 5px; 97 | } 98 | .main{ 99 | background: #ededed; 100 | overflow: hidden; 101 | } 102 | .mainIntro{ 103 | background: #fff; 104 | box-shadow: 0 1px 5px 0 rgba(0,0,0,.05); 105 | margin: 20px auto; 106 | } 107 | .product{ 108 | padding: 10px; 109 | } 110 | .product h3{ 111 | color: #333; 112 | font-size: 16px; 113 | line-height: 36px; 114 | border-bottom: 1px solid #e6e6e6; 115 | } 116 | .product h4{ 117 | padding: 10px; 118 | font-size: 14px; 119 | font-weight: normal; 120 | border-left: 2px #3385ff solid; 121 | background: #e6e6e6; 122 | margin: 10px 0px; 123 | } 124 | .productContent{ 125 | color: #444; 126 | font-size: 14px; 127 | line-height: 30px; 128 | margin-top: 10px; 129 | } 130 | .productContent ol{ 131 | margin: 0px 15px; 132 | } 133 | .productContent a{ 134 | color: #3973ac; 135 | text-decoration: none; 136 | } 137 | .productContent a:hover{ 138 | text-decoration: underline; 139 | } 140 | .productPost{ 141 | overflow: hidden; 142 | } 143 | .productPost li:first-child { 144 | border-top: 0; 145 | } 146 | .productPost li { 147 | float: left; 148 | margin-right: 2%; 149 | width: 48%; 150 | line-height: 36px; 151 | text-overflow: ellipsis; 152 | white-space: nowrap; 153 | overflow: hidden; 154 | border-top: 1px dashed #e6e6e6; 155 | } 156 | 157 | @media screen and (max-width: 800px) { 158 | .container{ 159 | width: 100%; 160 | } 161 | .header .logo{ 162 | float: none; 163 | text-align: center; 164 | margin: 20px 0px; 165 | } 166 | .header .navBtn{ 167 | margin:5px 0 10px 10px; 168 | float: none; 169 | } 170 | .banner p{ 171 | text-align: left; 172 | } 173 | .jumbotron{ 174 | width: 100%; 175 | height: auto; 176 | } 177 | 178 | 179 | } 180 | .clear{clear:both} 181 | .links{ 182 | margin-top: 20px; 183 | } 184 | .links a{ 185 | color: #20b2bb; 186 | line-height: 23px; 187 | margin: 5px; 188 | } -------------------------------------------------------------------------------- /static/demos/websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 62 | -------------------------------------------------------------------------------- /static/images/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/0.jpg -------------------------------------------------------------------------------- /static/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/1.jpg -------------------------------------------------------------------------------- /static/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/1.png -------------------------------------------------------------------------------- /static/images/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/10.jpg -------------------------------------------------------------------------------- /static/images/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/11.jpg -------------------------------------------------------------------------------- /static/images/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/12.jpg -------------------------------------------------------------------------------- /static/images/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/13.jpg -------------------------------------------------------------------------------- /static/images/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/14.jpg -------------------------------------------------------------------------------- /static/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/2.jpg -------------------------------------------------------------------------------- /static/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/2.png -------------------------------------------------------------------------------- /static/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/3.jpg -------------------------------------------------------------------------------- /static/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/3.png -------------------------------------------------------------------------------- /static/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/4.jpg -------------------------------------------------------------------------------- /static/images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/5.jpg -------------------------------------------------------------------------------- /static/images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/6.jpg -------------------------------------------------------------------------------- /static/images/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/7.jpg -------------------------------------------------------------------------------- /static/images/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/8.jpg -------------------------------------------------------------------------------- /static/images/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/9.jpg -------------------------------------------------------------------------------- /static/images/admin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/admin.jpg -------------------------------------------------------------------------------- /static/images/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/admin.png -------------------------------------------------------------------------------- /static/images/alert.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/alert.mp3 -------------------------------------------------------------------------------- /static/images/alert2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/alert2.ogg -------------------------------------------------------------------------------- /static/images/attachent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/attachent.png -------------------------------------------------------------------------------- /static/images/avator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/avator.jpg -------------------------------------------------------------------------------- /static/images/computer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/computer.png -------------------------------------------------------------------------------- /static/images/ext/7z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/7z.png -------------------------------------------------------------------------------- /static/images/ext/BAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/BAT.png -------------------------------------------------------------------------------- /static/images/ext/BMP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/BMP.png -------------------------------------------------------------------------------- /static/images/ext/DOC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/DOC.png -------------------------------------------------------------------------------- /static/images/ext/DOCX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/DOCX.png -------------------------------------------------------------------------------- /static/images/ext/JPEG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/JPEG.png -------------------------------------------------------------------------------- /static/images/ext/JPG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/JPG.png -------------------------------------------------------------------------------- /static/images/ext/MP3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/MP3.png -------------------------------------------------------------------------------- /static/images/ext/MP4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/MP4.png -------------------------------------------------------------------------------- /static/images/ext/MPGE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/MPGE.png -------------------------------------------------------------------------------- /static/images/ext/PDF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/PDF.png -------------------------------------------------------------------------------- /static/images/ext/PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/PNG.png -------------------------------------------------------------------------------- /static/images/ext/PPT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/PPT.png -------------------------------------------------------------------------------- /static/images/ext/RAR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/RAR.png -------------------------------------------------------------------------------- /static/images/ext/SVG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/SVG.png -------------------------------------------------------------------------------- /static/images/ext/TAR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/TAR.png -------------------------------------------------------------------------------- /static/images/ext/TXT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/TXT.png -------------------------------------------------------------------------------- /static/images/ext/XLSX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/XLSX.png -------------------------------------------------------------------------------- /static/images/ext/ZIP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/ZIP.png -------------------------------------------------------------------------------- /static/images/ext/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/default.png -------------------------------------------------------------------------------- /static/images/ext/voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/ext/voice.png -------------------------------------------------------------------------------- /static/images/face/0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/0.gif -------------------------------------------------------------------------------- /static/images/face/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/1.gif -------------------------------------------------------------------------------- /static/images/face/10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/10.gif -------------------------------------------------------------------------------- /static/images/face/11.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/11.gif -------------------------------------------------------------------------------- /static/images/face/12.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/12.gif -------------------------------------------------------------------------------- /static/images/face/13.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/13.gif -------------------------------------------------------------------------------- /static/images/face/14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/14.gif -------------------------------------------------------------------------------- /static/images/face/15.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/15.gif -------------------------------------------------------------------------------- /static/images/face/16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/16.gif -------------------------------------------------------------------------------- /static/images/face/17.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/17.gif -------------------------------------------------------------------------------- /static/images/face/18.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/18.gif -------------------------------------------------------------------------------- /static/images/face/19.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/19.gif -------------------------------------------------------------------------------- /static/images/face/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/2.gif -------------------------------------------------------------------------------- /static/images/face/20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/20.gif -------------------------------------------------------------------------------- /static/images/face/21.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/21.gif -------------------------------------------------------------------------------- /static/images/face/22.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/22.gif -------------------------------------------------------------------------------- /static/images/face/23.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/23.gif -------------------------------------------------------------------------------- /static/images/face/24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/24.gif -------------------------------------------------------------------------------- /static/images/face/25.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/25.gif -------------------------------------------------------------------------------- /static/images/face/26.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/26.gif -------------------------------------------------------------------------------- /static/images/face/27.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/27.gif -------------------------------------------------------------------------------- /static/images/face/28.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/28.gif -------------------------------------------------------------------------------- /static/images/face/29.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/29.gif -------------------------------------------------------------------------------- /static/images/face/3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/3.gif -------------------------------------------------------------------------------- /static/images/face/30.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/30.gif -------------------------------------------------------------------------------- /static/images/face/31.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/31.gif -------------------------------------------------------------------------------- /static/images/face/32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/32.gif -------------------------------------------------------------------------------- /static/images/face/33.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/33.gif -------------------------------------------------------------------------------- /static/images/face/34.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/34.gif -------------------------------------------------------------------------------- /static/images/face/35.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/35.gif -------------------------------------------------------------------------------- /static/images/face/36.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/36.gif -------------------------------------------------------------------------------- /static/images/face/37.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/37.gif -------------------------------------------------------------------------------- /static/images/face/38.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/38.gif -------------------------------------------------------------------------------- /static/images/face/39.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/39.gif -------------------------------------------------------------------------------- /static/images/face/4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/4.gif -------------------------------------------------------------------------------- /static/images/face/40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/40.gif -------------------------------------------------------------------------------- /static/images/face/41.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/41.gif -------------------------------------------------------------------------------- /static/images/face/42.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/42.gif -------------------------------------------------------------------------------- /static/images/face/43.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/43.gif -------------------------------------------------------------------------------- /static/images/face/44.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/44.gif -------------------------------------------------------------------------------- /static/images/face/45.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/45.gif -------------------------------------------------------------------------------- /static/images/face/46.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/46.gif -------------------------------------------------------------------------------- /static/images/face/47.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/47.gif -------------------------------------------------------------------------------- /static/images/face/48.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/48.gif -------------------------------------------------------------------------------- /static/images/face/49.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/49.gif -------------------------------------------------------------------------------- /static/images/face/5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/5.gif -------------------------------------------------------------------------------- /static/images/face/50.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/50.gif -------------------------------------------------------------------------------- /static/images/face/51.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/51.gif -------------------------------------------------------------------------------- /static/images/face/52.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/52.gif -------------------------------------------------------------------------------- /static/images/face/53.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/53.gif -------------------------------------------------------------------------------- /static/images/face/54.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/54.gif -------------------------------------------------------------------------------- /static/images/face/55.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/55.gif -------------------------------------------------------------------------------- /static/images/face/56.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/56.gif -------------------------------------------------------------------------------- /static/images/face/57.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/57.gif -------------------------------------------------------------------------------- /static/images/face/58.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/58.gif -------------------------------------------------------------------------------- /static/images/face/59.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/59.gif -------------------------------------------------------------------------------- /static/images/face/6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/6.gif -------------------------------------------------------------------------------- /static/images/face/60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/60.gif -------------------------------------------------------------------------------- /static/images/face/61.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/61.gif -------------------------------------------------------------------------------- /static/images/face/62.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/62.gif -------------------------------------------------------------------------------- /static/images/face/63.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/63.gif -------------------------------------------------------------------------------- /static/images/face/64.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/64.gif -------------------------------------------------------------------------------- /static/images/face/65.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/65.gif -------------------------------------------------------------------------------- /static/images/face/66.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/66.gif -------------------------------------------------------------------------------- /static/images/face/67.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/67.gif -------------------------------------------------------------------------------- /static/images/face/68.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/68.gif -------------------------------------------------------------------------------- /static/images/face/69.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/69.gif -------------------------------------------------------------------------------- /static/images/face/7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/7.gif -------------------------------------------------------------------------------- /static/images/face/70.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/70.gif -------------------------------------------------------------------------------- /static/images/face/71.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/71.gif -------------------------------------------------------------------------------- /static/images/face/8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/8.gif -------------------------------------------------------------------------------- /static/images/face/9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/face/9.gif -------------------------------------------------------------------------------- /static/images/fire.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/images/image-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/image-text.png -------------------------------------------------------------------------------- /static/images/intro1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/intro1.jpg -------------------------------------------------------------------------------- /static/images/intro2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/intro2.jpg -------------------------------------------------------------------------------- /static/images/intro3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/intro3.png -------------------------------------------------------------------------------- /static/images/inviteColorBack1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/inviteColorBack1.png -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/logo.png -------------------------------------------------------------------------------- /static/images/newintro1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/newintro1.jpg -------------------------------------------------------------------------------- /static/images/newintro2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/newintro2.jpg -------------------------------------------------------------------------------- /static/images/newintro3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/newintro3.jpg -------------------------------------------------------------------------------- /static/images/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/phone.png -------------------------------------------------------------------------------- /static/images/sent.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/sent.ogg -------------------------------------------------------------------------------- /static/images/smile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/smile.png -------------------------------------------------------------------------------- /static/images/visitor_title_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/visitor_title_bg.png -------------------------------------------------------------------------------- /static/images/wechatLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/wechatLogo.png -------------------------------------------------------------------------------- /static/images/zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taoshihan1991/go-fly/9988222db083df397604281124311c87c2ff9967/static/images/zoom_out.png -------------------------------------------------------------------------------- /static/js/chat-lang.js: -------------------------------------------------------------------------------- 1 | var GOFLY_LANG={ 2 | "cn":{ 3 | "sent":"发送", 4 | "connecting":"正在连接...", 5 | "connectok":"连接成功!", 6 | "chating":"正在与您沟通!", 7 | "historymes":"—— 以上是历史消息 ——", 8 | "moremessage":" 点击加载更多记录", 9 | "copyright":"", 10 | "textarea":"请输入内容", 11 | "closemes":"系统自动关闭连接!点击会重连", 12 | "forceclosemes":"客服关闭连接!请重新打开页面", 13 | "autoclosemes":"长时间未回应关闭连接!请刷新页面", 14 | "mesBtn":"全部消息记录", 15 | }, 16 | "en":{ 17 | "sent":"Send", 18 | "connecting":"connecting...", 19 | "connectok":"connection succeeded!", 20 | "chating":"chating with you!", 21 | "historymes":"—— Today ——", 22 | "moremessage":" click buuton show more messages", 23 | "copyright":"We run on GOFLY0.4.1", 24 | "textarea":"Enter your message", 25 | "closemes":"The system automatically closes the connection!", 26 | "forceclosemes":"Admin closes the connection! please reload", 27 | "autoclosemes":"session closed!please reload", 28 | "mesBtn":"all messages", 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /static/js/layer/mobile/layer.js: -------------------------------------------------------------------------------- 1 | /*! layer mobile-v2.0.0 Web 通用弹出层组件 MIT License */ 2 | ;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'

'+(e?n.title[0]:n.title)+"

":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e=''+n.btn[0]+"",2===t&&(e=''+n.btn[1]+""+e),'
'+e+"
"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='

'+(n.content||"")+"

"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
':"")+'
"+l+'
'+n.content+"
"+c+"
",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;og.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a}); 2 | -------------------------------------------------------------------------------- /static/static.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import "embed" 4 | 5 | //go:embed templates/* 6 | var TemplatesEmbed embed.FS 7 | 8 | //go:embed js/* 9 | var JsEmbed embed.FS 10 | -------------------------------------------------------------------------------- /static/templates/chat_video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 视频聊天 4 | 5 | 6 | 7 | 8 | 9 |

本地视频

10 | 11 |
12 | 自己ID(自动获取) 13 | 对方ID(请手动输入) 14 | 15 |
16 |

远程视频

17 | 18 | 19 | 20 | 21 | 86 | 87 | -------------------------------------------------------------------------------- /static/templates/detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{.Title}} 8 | 9 | 10 | {{.CssJs}} 11 | 12 | 13 | {{.Content}} 14 | 15 | -------------------------------------------------------------------------------- /static/templates/header.html: -------------------------------------------------------------------------------- 1 | {{define "header"}} 2 | 3 | 4 | 5 | 6 | 7 | GO语言开源客服系统-GOFLY 8 | 9 | 10 | 11 | 12 | 13 | 14 | 41 | 42 | 43 | 44 | {{end}} -------------------------------------------------------------------------------- /static/templates/index_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 开源免费客服系统-极简强大Go语言开发客服单页营销系统 - GOFLY 7 | 10 | 11 |

:)

HELLO GOFLY LIVE CHAT !

Administrator English 中文

12 | 13 | -------------------------------------------------------------------------------- /static/templates/list.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 42 | 43 |
44 | 45 | 100 | 101 | -------------------------------------------------------------------------------- /static/templates/mail_detail.html: -------------------------------------------------------------------------------- 1 | {{.Header}} 2 |
3 | 53 |
54 | 55 | 108 | 109 | -------------------------------------------------------------------------------- /static/templates/mail_left.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 写信 6 | 7 | 8 | 9 | <{v}> 10 | 11 | -------------------------------------------------------------------------------- /static/templates/nav.html: -------------------------------------------------------------------------------- 1 | {{define "nav"}} 2 |
3 |
4 | 5 | 6 | 7 |
8 | 12 | 13 | 19 | 20 | 21 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 38 | 39 |
40 | 41 | {{end}} 42 | -------------------------------------------------------------------------------- /static/templates/pannel.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 54 | 55 |
56 | 57 | 58 | 61 | {{template "setting_bottom" .}} 62 | -------------------------------------------------------------------------------- /static/templates/setting.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 10 | 11 |
12 | 13 | {{template "setting_bottom" .}} 14 | -------------------------------------------------------------------------------- /static/templates/setting_avator.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 | 27 |
28 | 61 | 62 |
63 | 64 | {{template "setting_bottom" .}} 65 | -------------------------------------------------------------------------------- /static/templates/setting_config.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 | 7 |
8 | 40 | 41 |
42 | 43 | {{template "setting_bottom" .}} 44 | -------------------------------------------------------------------------------- /static/templates/setting_deploy.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 24 | 25 |
26 | 27 | {{template "setting_bottom" .}} 28 | -------------------------------------------------------------------------------- /static/templates/setting_indexpages.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 30 | 31 |
32 | 33 | {{template "setting_bottom" .}} 34 | -------------------------------------------------------------------------------- /static/templates/setting_ipblack.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 31 | 32 |
33 | 34 | {{template "setting_bottom" .}} 35 | -------------------------------------------------------------------------------- /static/templates/setting_kefu_list.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 83 | 84 |
85 | 86 | {{template "setting_bottom" .}} 87 | -------------------------------------------------------------------------------- /static/templates/setting_left.html: -------------------------------------------------------------------------------- 1 | {{define "setting_left"}} 2 | 3 | 6 | 7 | 11 | 12 | 统计信息 13 | 修改密码 14 | 修改资料 15 | 16 | 17 | 18 | 22 | 23 | 配置参数 24 | IP黑名单 25 | 网页部署 26 | 27 | 28 | 29 | 30 | {{end}} -------------------------------------------------------------------------------- /static/templates/setting_modifypass.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 26 | 27 |
28 | 29 | {{template "setting_bottom" .}} -------------------------------------------------------------------------------- /static/templates/setting_mysql.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 32 | 33 |
34 | 35 | {{template "setting_bottom" .}} 36 | -------------------------------------------------------------------------------- /static/templates/setting_pageindex.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 44 | 45 |
46 | 47 | {{template "setting_bottom" .}} 48 | -------------------------------------------------------------------------------- /static/templates/setting_role_list.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 57 | 58 |
59 | 60 | {{template "setting_bottom" .}} 61 | -------------------------------------------------------------------------------- /static/templates/setting_statistics.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 30 |
31 | 32 | {{template "setting_bottom" .}} 33 | 34 | 35 | -------------------------------------------------------------------------------- /static/templates/setting_welcome.html: -------------------------------------------------------------------------------- 1 | {{template "header" }} 2 |
3 | 50 | 51 |
52 | 53 | {{template "setting_bottom" .}} 54 | -------------------------------------------------------------------------------- /stop.bat: -------------------------------------------------------------------------------- 1 | taskkill -f -t -im go-fly.exe -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | ps -ef|grep "go-fly" 2 | kill -9 $(pidof 'go-fly') -------------------------------------------------------------------------------- /tmpl/chat.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | //咨询界面 9 | func PageChat(c *gin.Context) { 10 | kefuId := c.Query("kefu_id") 11 | refer := c.Query("refer") 12 | if refer == "" { 13 | refer = c.Request.Referer() 14 | } 15 | if refer == "" { 16 | refer = "直接访问" 17 | } 18 | c.HTML(http.StatusOK, "chat_page.html", gin.H{ 19 | "KEFU_ID": kefuId, 20 | "Refer": refer, 21 | }) 22 | } 23 | func PageKfChat(c *gin.Context) { 24 | kefuId := c.Query("kefu_id") 25 | visitorId := c.Query("visitor_id") 26 | token := c.Query("token") 27 | c.HTML(http.StatusOK, "chat_kf_page.html", gin.H{ 28 | "KefuId": kefuId, 29 | "VisitorId": visitorId, 30 | "Token": token, 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /tmpl/common.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/tools" 6 | "html/template" 7 | "net/http" 8 | ) 9 | 10 | type CommonHtml struct { 11 | Header template.HTML 12 | Nav template.HTML 13 | Left template.HTML 14 | Bottom template.HTML 15 | Rw http.ResponseWriter 16 | } 17 | 18 | func NewRender(rw http.ResponseWriter) *CommonHtml { 19 | obj := new(CommonHtml) 20 | obj.Rw = rw 21 | header := tools.FileGetContent("html/header.html") 22 | nav := tools.FileGetContent("html/nav.html") 23 | obj.Header = template.HTML(header) 24 | obj.Nav = template.HTML(nav) 25 | return obj 26 | } 27 | func (obj *CommonHtml) SetLeft(file string) { 28 | leftStr := tools.FileGetContent("html/" + file + ".html") 29 | obj.Left = template.HTML(leftStr) 30 | } 31 | func (obj *CommonHtml) SetBottom(file string) { 32 | str := tools.FileGetContent("html/" + file + ".html") 33 | obj.Bottom = template.HTML(str) 34 | } 35 | func (obj *CommonHtml) Display(file string, data interface{}) { 36 | if data == nil { 37 | data = obj 38 | } 39 | main := tools.FileGetContent("html/" + file + ".html") 40 | t, _ := template.New(file).Parse(main) 41 | t.Execute(obj.Rw, data) 42 | } 43 | 44 | //首页 45 | func PageIndex(c *gin.Context) { 46 | if c.Request.RequestURI == "/favicon.ico" { 47 | return 48 | } 49 | if noExist, _ := tools.IsFileNotExist("./install.lock"); noExist { 50 | c.Redirect(302, "/install") 51 | } 52 | c.HTML(http.StatusOK, "index.html", gin.H{}) 53 | } 54 | 55 | //登陆界面 56 | func PageMain(c *gin.Context) { 57 | nav := tools.FileGetContent("html/nav.html") 58 | c.HTML(http.StatusOK, "main.html", gin.H{ 59 | "Nav": template.HTML(nav), 60 | }) 61 | } 62 | 63 | //客服界面 64 | func PageChatMain(c *gin.Context) { 65 | c.HTML(http.StatusOK, "chat_main.html", nil) 66 | } 67 | 68 | //安装界面 69 | func PageInstall(c *gin.Context) { 70 | if noExist, _ := tools.IsFileNotExist("./install.lock"); !noExist { 71 | c.Redirect(302, "/login") 72 | } 73 | c.HTML(http.StatusOK, "install.html", nil) 74 | } 75 | 76 | //面板界面 77 | func PagePannel(c *gin.Context) { 78 | c.HTML(http.StatusOK, "pannel.html", nil) 79 | } 80 | -------------------------------------------------------------------------------- /tmpl/detail.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/models" 6 | "html" 7 | "html/template" 8 | "net/http" 9 | ) 10 | 11 | func PageDetail(c *gin.Context) { 12 | if c.Request.RequestURI == "/favicon.ico" { 13 | return 14 | } 15 | page := c.Param("page") 16 | lang, _ := c.Get("lang") 17 | about := models.FindAboutByPageLanguage(page, lang.(string)) 18 | cssJs := html.UnescapeString(about.CssJs) 19 | title := about.TitleCn 20 | keywords := about.KeywordsCn 21 | desc := html.UnescapeString(about.DescCn) 22 | content := html.UnescapeString(about.HtmlCn) 23 | if lang == "en" { 24 | title = about.TitleEn 25 | keywords = about.KeywordsEn 26 | desc = html.UnescapeString(about.DescEn) 27 | content = html.UnescapeString(about.HtmlEn) 28 | } 29 | c.HTML(http.StatusOK, "detail.html", gin.H{ 30 | "Lang": lang, 31 | "Title": title, 32 | "Keywords": keywords, 33 | "Desc": desc, 34 | "Content": template.HTML(content), 35 | "CssJs": template.HTML(cssJs), 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /tmpl/folder.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import "net/http" 4 | 5 | type FolderHtml struct { 6 | *CommonHtml 7 | CurrentPage int 8 | Fid string 9 | } 10 | 11 | func NewFolderHtml(w http.ResponseWriter) *FolderHtml { 12 | obj := new(FolderHtml) 13 | parent := NewRender(w) 14 | obj.CommonHtml = parent 15 | return obj 16 | } 17 | -------------------------------------------------------------------------------- /tmpl/login.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/taoshihan1991/imaptool/tools" 6 | "net/http" 7 | ) 8 | 9 | //登陆界面 10 | func PageLogin(c *gin.Context) { 11 | if noExist, _ := tools.IsFileNotExist("./install.lock"); noExist { 12 | c.Redirect(302, "/install") 13 | } 14 | c.HTML(http.StatusOK, "login.html", nil) 15 | } 16 | //绑定界面 17 | func PageBind(c *gin.Context) { 18 | c.HTML(http.StatusOK, "bind.html", gin.H{}) 19 | } -------------------------------------------------------------------------------- /tmpl/mail.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | //邮箱列表界面 9 | func PageMailList(c *gin.Context) { 10 | return 11 | c.HTML(http.StatusOK, "list.html", gin.H{}) 12 | } 13 | -------------------------------------------------------------------------------- /tmpl/setting.go: -------------------------------------------------------------------------------- 1 | package tmpl 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | ) 7 | 8 | //设置界面 9 | func PageSetting(c *gin.Context) { 10 | c.HTML(http.StatusOK, "setting.html", gin.H{ 11 | "tab_index": "1-1", 12 | "action": "setting", 13 | }) 14 | } 15 | 16 | //设置欢迎 17 | func PageSettingWelcome(c *gin.Context) { 18 | c.HTML(http.StatusOK, "setting_welcome.html", gin.H{ 19 | "tab_index": "1-2", 20 | "action": "setting_welcome", 21 | }) 22 | } 23 | 24 | //统计 25 | func PageSettingStatis(c *gin.Context) { 26 | c.HTML(http.StatusOK, "setting_statistics.html", gin.H{ 27 | "tab_index": "1-3", 28 | "action": "setting_statistics", 29 | }) 30 | } 31 | 32 | //设置mysql 33 | func PageSettingMysql(c *gin.Context) { 34 | c.HTML(http.StatusOK, "setting_mysql.html", gin.H{ 35 | "tab_index": "2-4", 36 | "action": "setting_mysql", 37 | }) 38 | } 39 | 40 | //设置部署 41 | func PageSettingDeploy(c *gin.Context) { 42 | c.HTML(http.StatusOK, "setting_deploy.html", gin.H{ 43 | "tab_index": "2-5", 44 | "action": "setting_deploy", 45 | }) 46 | } 47 | 48 | func PageKefuList(c *gin.Context) { 49 | c.HTML(http.StatusOK, "setting_kefu_list.html", gin.H{ 50 | "tab_index": "3-2", 51 | "action": "setting_kefu_list", 52 | }) 53 | } 54 | func PageAvator(c *gin.Context) { 55 | c.HTML(http.StatusOK, "setting_avator.html", gin.H{ 56 | "tab_index": "3-2", 57 | "action": "setting_avator", 58 | }) 59 | } 60 | func PageModifypass(c *gin.Context) { 61 | c.HTML(http.StatusOK, "setting_modifypass.html", gin.H{ 62 | "tab_index": "3-2", 63 | "action": "setting_modifypass", 64 | }) 65 | } 66 | 67 | //角色列表 68 | func PageRoleList(c *gin.Context) { 69 | c.HTML(http.StatusOK, "setting_role_list.html", gin.H{ 70 | "tab_index": "3-1", 71 | "action": "roles_list", 72 | }) 73 | } 74 | 75 | //角色列表 76 | func PageIpblack(c *gin.Context) { 77 | c.HTML(http.StatusOK, "setting_ipblack.html", gin.H{ 78 | "tab_index": "4-5", 79 | "action": "setting_ipblack", 80 | }) 81 | } 82 | 83 | //配置项列表 84 | func PageConfig(c *gin.Context) { 85 | c.HTML(http.StatusOK, "setting_config.html", gin.H{ 86 | "tab_index": "4-6", 87 | "action": "setting_config", 88 | }) 89 | } 90 | 91 | //配置项编辑首页 92 | func PageSettingIndexPage(c *gin.Context) { 93 | c.HTML(http.StatusOK, "setting_pageindex.html", gin.H{ 94 | "tab_index": "4-7", 95 | "action": "setting_pageindex", 96 | }) 97 | } 98 | 99 | //配置项编辑首页 100 | func PageSettingIndexPages(c *gin.Context) { 101 | c.HTML(http.StatusOK, "setting_indexpages.html", gin.H{ 102 | "tab_index": "4-7", 103 | "action": "setting_indexpages", 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /tools/binsearch.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | func BinarySearch(nums []int, target int) int { 4 | left := 0 5 | right := len(nums) - 1 //注意 6 | 7 | for left <= right { //注意 8 | mid := left + (right-left)/2 9 | if nums[mid] == target { 10 | return mid 11 | } else if nums[mid] < target { 12 | left = mid + 1 //注意 13 | } else if nums[mid] > target { 14 | right = mid - 1 //注意 15 | } 16 | } 17 | return -1 18 | } 19 | func LeftBound(nums []int, target int) int { 20 | if len(nums) == 0 { 21 | return -1 22 | } 23 | left := 0 24 | right := len(nums) //注意 25 | 26 | for left < right { //注意 27 | mid := left + (right-left)/2 28 | if nums[mid] == target { 29 | right = mid 30 | } else if nums[mid] < target { 31 | left = mid + 1 32 | } else if nums[mid] > target { 33 | right = mid //注意 34 | } 35 | } 36 | if left == len(nums) || nums[left] != target { 37 | return -1 38 | } 39 | return left 40 | } 41 | func LeftBound2(nums []int, target int) int { 42 | left := 0 43 | right := len(nums) - 1 //注意 44 | 45 | for left <= right { //注意 46 | mid := left + (right-left)/2 47 | if nums[mid] == target { 48 | //收缩右侧边界 49 | right = mid - 1 50 | } else if nums[mid] < target { 51 | //搜索区间变为 [mid+1, right] 52 | left = mid + 1 //注意 53 | } else if nums[mid] > target { 54 | //搜索区间变为 [left, mid-1] 55 | right = mid - 1 56 | } 57 | } 58 | if left >= len(nums) || nums[left] != target { 59 | return -1 60 | } 61 | return left 62 | } 63 | func RightBound(nums []int, target int) int { 64 | left := 0 65 | right := len(nums) - 1 //注意 66 | 67 | for left <= right { //注意 68 | mid := left + (right-left)/2 69 | if nums[mid] == target { 70 | //收缩左侧边界 71 | left = mid + 1 72 | } else if nums[mid] < target { 73 | //搜索区间变为 [mid+1, right] 74 | left = mid + 1 //注意 75 | } else if nums[mid] > target { 76 | //搜索区间变为 [left, mid-1] 77 | right = mid - 1 78 | } 79 | } 80 | if right < 0 || nums[right] != target { 81 | return -1 82 | } 83 | return right 84 | } 85 | -------------------------------------------------------------------------------- /tools/binsearch_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "testing" 4 | 5 | func TestBinarySearch(t *testing.T) { 6 | myTest := struct { 7 | Arg1 []int 8 | Arg2 int 9 | Want int 10 | }{ 11 | []int{1, 4, 7, 9, 10}, 12 | 9, 13 | 3, 14 | } 15 | res := BinarySearch(myTest.Arg1, myTest.Arg2) 16 | if res != myTest.Want { 17 | t.Errorf("BinarySearch(%d,%d) == %d, want %d", myTest.Arg1, myTest.Arg2, res, myTest.Want) 18 | } 19 | } 20 | func TestLeftBound(t *testing.T) { 21 | myTest := struct { 22 | Arg1 []int 23 | Arg2 int 24 | Want int 25 | }{ 26 | []int{1, 4, 4, 4, 7, 9, 10}, 27 | 4, 28 | 1, 29 | } 30 | res := LeftBound(myTest.Arg1, myTest.Arg2) 31 | if res != myTest.Want { 32 | t.Errorf("LeftBound(%d,%d) == %d, want %d", myTest.Arg1, myTest.Arg2, res, myTest.Want) 33 | } 34 | } 35 | func TestRightBound(t *testing.T) { 36 | myTest := struct { 37 | Arg1 []int 38 | Arg2 int 39 | Want int 40 | }{ 41 | []int{1, 4, 4, 4, 7, 9, 10}, 42 | 4, 43 | 3, 44 | } 45 | res := RightBound(myTest.Arg1, myTest.Arg2) 46 | if res != myTest.Want { 47 | t.Errorf("RightBound(%d,%d) == %d, want %d", myTest.Arg1, myTest.Arg2, res, myTest.Want) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tools/cookie.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | func SetCookie(name string, value string, w *http.ResponseWriter) { 9 | cookie := http.Cookie{ 10 | Name: name, 11 | Value: value, 12 | } 13 | http.SetCookie(*w, &cookie) 14 | } 15 | func GetCookie(r *http.Request, name string) string { 16 | cookies := r.Cookies() 17 | for _, cookie := range cookies { 18 | if cookie.Name == name { 19 | return cookie.Value 20 | } 21 | } 22 | return "" 23 | } 24 | func GetMailServerFromCookie(r *http.Request) *MailServer { 25 | auth := GetCookie(r, "auth") 26 | if !strings.Contains(auth, "|") { 27 | return nil 28 | } 29 | authStrings := strings.Split(auth, "|") 30 | mailServer := &MailServer{ 31 | Server: authStrings[0], 32 | Email: authStrings[1], 33 | Password: authStrings[2], 34 | } 35 | return mailServer 36 | } 37 | -------------------------------------------------------------------------------- /tools/file.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "os" 4 | 5 | //判断文件文件夹是否存在(字节0也算不存在) 6 | func IsFileExist(path string) (bool, error) { 7 | fileInfo, err := os.Stat(path) 8 | 9 | if os.IsNotExist(err) { 10 | return false, nil 11 | } 12 | //我这里判断了如果是0也算不存在 13 | if fileInfo.Size() == 0 { 14 | return false, nil 15 | } 16 | if err == nil { 17 | return true, nil 18 | } 19 | return false, err 20 | } 21 | 22 | //判断文件文件夹不存在 23 | func IsFileNotExist(path string) (bool, error) { 24 | _, err := os.Stat(path) 25 | if os.IsNotExist(err) { 26 | return true, nil 27 | } 28 | return false, err 29 | } 30 | -------------------------------------------------------------------------------- /tools/hash.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "encoding/hex" 8 | "strings" 9 | ) 10 | 11 | //md5加密 12 | func Md5(src string) string { 13 | m := md5.New() 14 | m.Write([]byte(src)) 15 | res := hex.EncodeToString(m.Sum(nil)) 16 | return res 17 | } 18 | 19 | //Sha256加密 20 | func Sha256(src string) string { 21 | m := sha256.New() 22 | m.Write([]byte(src)) 23 | res := hex.EncodeToString(m.Sum(nil)) 24 | return res 25 | } 26 | func Base64Decode(str string) string { 27 | reader := strings.NewReader(str) 28 | decoder := base64.NewDecoder(base64.RawStdEncoding, reader) 29 | // 以流式解码 30 | buf := make([]byte, 1024) 31 | // 保存解码后的数据 32 | dst := "" 33 | for { 34 | n, err := decoder.Read(buf) 35 | dst += string(buf[:n]) 36 | if n == 0 || err != nil { 37 | break 38 | } 39 | } 40 | return dst 41 | } 42 | -------------------------------------------------------------------------------- /tools/http.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/url" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | func Get(url string) string { 12 | res, err := http.Get(url) 13 | if err != nil { 14 | return "" 15 | } 16 | robots, err := ioutil.ReadAll(res.Body) 17 | res.Body.Close() 18 | if err != nil { 19 | return "" 20 | } 21 | return string(robots) 22 | } 23 | 24 | //Post("http://xxxx","application/json;charset=utf-8",[]byte("{'aaa':'bbb'}")) 25 | func Post(url string, contentType string, body []byte) (string, error) { 26 | res, err := http.Post(url, contentType, strings.NewReader(string(body))) 27 | if err != nil { 28 | return "", err 29 | } 30 | defer res.Body.Close() 31 | content, err := ioutil.ReadAll(res.Body) 32 | if err != nil { 33 | return "", err 34 | } 35 | return string(content), nil 36 | } 37 | func PostHeader(url string, msg []byte, headers map[string]string) (string, error) { 38 | client := &http.Client{} 39 | 40 | req, err := http.NewRequest("POST", url, strings.NewReader(string(msg))) 41 | if err != nil { 42 | return "", err 43 | } 44 | for key, header := range headers { 45 | req.Header.Set(key, header) 46 | } 47 | resp, err := client.Do(req) 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | return "", err 52 | } 53 | return string(body), nil 54 | } 55 | func IsMobile(userAgent string) bool { 56 | mobileRe, _ := regexp.Compile("(?i:Mobile|iPod|iPhone|Android|Opera Mini|BlackBerry|webOS|UCWEB|Blazer|PSP)") 57 | str := mobileRe.FindString(userAgent) 58 | if str != "" { 59 | return true 60 | } 61 | return false 62 | } 63 | //发送http post请求数据为form 64 | func PostForm(url string, data url.Values) (string, error) { 65 | resp, err := http.PostForm(url, data) 66 | if err != nil { 67 | return "", err 68 | } 69 | defer resp.Body.Close() 70 | content, err := ioutil.ReadAll(resp.Body) 71 | if err != nil { 72 | return "", err 73 | } 74 | return string(content), nil 75 | } -------------------------------------------------------------------------------- /tools/import_sql.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/jinzhu/gorm" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | type ImportSqlTool struct { 15 | SqlPath string 16 | Username, Password, Server, Port, Database string 17 | } 18 | 19 | func (this *ImportSqlTool) ImportSql() error { 20 | _, err := os.Stat(this.SqlPath) 21 | if os.IsNotExist(err) { 22 | log.Println("数据库SQL文件不存在:", err) 23 | return err 24 | } 25 | 26 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", this.Username, this.Password, this.Server, this.Port, this.Database) 27 | db, err := gorm.Open("mysql", dsn) 28 | if err != nil { 29 | log.Println("数据库连接失败:", err) 30 | //panic("数据库连接失败!") 31 | return err 32 | } 33 | db.SingularTable(true) 34 | db.LogMode(true) 35 | db.DB().SetMaxIdleConns(10) 36 | db.DB().SetMaxOpenConns(100) 37 | db.DB().SetConnMaxLifetime(59 * time.Second) 38 | 39 | sqls, _ := ioutil.ReadFile(this.SqlPath) 40 | sqlArr := strings.Split(string(sqls), ";") 41 | for _, sql := range sqlArr { 42 | sql = strings.TrimSpace(sql) 43 | if sql == "" { 44 | continue 45 | } 46 | err := db.Exec(sql).Error 47 | if err != nil { 48 | log.Println("数据库导入失败:" + err.Error()) 49 | return err 50 | } else { 51 | log.Println(sql, "\t success!") 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tools/import_sql_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "testing" 4 | 5 | func TestImportSql(t *testing.T) { 6 | tool:=&ImportSqlTool{ 7 | SqlPath: "../import.sql", 8 | Username: "go-fly", 9 | Password: "go-fly", 10 | Server: "127.0.0.1", 11 | Port: "3306", 12 | Database: "go-fly", 13 | } 14 | tool.ImportSql() 15 | } -------------------------------------------------------------------------------- /tools/ip.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "errors" 5 | "github.com/ipipdotnet/ipdb-go" 6 | "net" 7 | ) 8 | 9 | func ParseIp(myip string) *ipdb.CityInfo { 10 | db, err := ipdb.NewCity("./config/city.free.ipdb") 11 | if err != nil { 12 | return nil 13 | } 14 | db.Reload("./config/city.free.ipdb") 15 | c, err := db.FindInfo(myip, "CN") 16 | if err != nil { 17 | return nil 18 | } 19 | return c 20 | } 21 | func GetServerIP() (net.IP, error) { 22 | ifaces, err := net.Interfaces() 23 | if err != nil { 24 | return nil, err 25 | } 26 | for _, iface := range ifaces { 27 | if iface.Flags&net.FlagUp == 0 { 28 | continue // interface down 29 | } 30 | if iface.Flags&net.FlagLoopback != 0 { 31 | continue // loopback interface 32 | } 33 | addrs, err := iface.Addrs() 34 | if err != nil { 35 | return nil, err 36 | } 37 | for _, addr := range addrs { 38 | ip := getIpFromAddr(addr) 39 | if ip == nil { 40 | continue 41 | } 42 | return ip, nil 43 | } 44 | } 45 | return nil, errors.New("connected to the network?") 46 | } 47 | func getIpFromAddr(addr net.Addr) net.IP { 48 | var ip net.IP 49 | switch v := addr.(type) { 50 | case *net.IPNet: 51 | ip = v.IP 52 | case *net.IPAddr: 53 | ip = v.IP 54 | } 55 | if ip == nil || ip.IsLoopback() { 56 | return nil 57 | } 58 | ip = ip.To4() 59 | if ip == nil { 60 | return nil // not an ipv4 address 61 | } 62 | 63 | return ip 64 | } 65 | 66 | //获取出站IP地址 67 | func GetOutboundIP() (net.IP, error) { 68 | conn, err := net.Dial("udp", "8.8.8.8:80") 69 | if err != nil { 70 | return nil, err 71 | } 72 | defer conn.Close() 73 | 74 | localAddr := conn.LocalAddr().(*net.UDPAddr) 75 | 76 | return localAddr.IP, nil 77 | } 78 | -------------------------------------------------------------------------------- /tools/ip_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestGetOutboundIP(t *testing.T) { 9 | ip, err := GetOutboundIP() 10 | log.Println(ip, err) 11 | } 12 | -------------------------------------------------------------------------------- /tools/jwt.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt" 5 | ) 6 | 7 | const SECRET = "taoshihan" 8 | 9 | func MakeToken(obj map[string]interface{}) (string, error) { 10 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(obj)) 11 | tokenString, err := token.SignedString([]byte(SECRET)) 12 | return tokenString, err 13 | } 14 | func ParseToken(tokenStr string) map[string]interface{} { 15 | token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (i interface{}, e error) { 16 | return []byte(SECRET), nil 17 | }) 18 | if err != nil { 19 | return nil 20 | } 21 | finToken := token.Claims.(jwt.MapClaims) 22 | return finToken 23 | } 24 | -------------------------------------------------------------------------------- /tools/limits.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type LimitQueeMap struct { 10 | sync.RWMutex 11 | LimitQueue map[string][]int64 12 | } 13 | 14 | func (l *LimitQueeMap) readMap(key string) ([]int64, bool) { 15 | l.RLock() 16 | value, ok := l.LimitQueue[key] 17 | l.RUnlock() 18 | return value, ok 19 | } 20 | 21 | func (l *LimitQueeMap) writeMap(key string, value []int64) { 22 | l.Lock() 23 | l.LimitQueue[key] = value 24 | l.Unlock() 25 | } 26 | 27 | var LimitQueue = &LimitQueeMap{ 28 | LimitQueue: make(map[string][]int64), 29 | } 30 | var ok bool 31 | 32 | func NewLimitQueue() { 33 | cleanLimitQueue() 34 | } 35 | func cleanLimitQueue() { 36 | go func() { 37 | for { 38 | log.Println("cleanLimitQueue start...") 39 | LimitQueue.LimitQueue = nil 40 | now := time.Now() 41 | // 计算下一个零点 42 | next := now.Add(time.Hour * 24) 43 | next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location()) 44 | t := time.NewTimer(next.Sub(now)) 45 | <-t.C 46 | } 47 | }() 48 | } 49 | 50 | //单机时间滑动窗口限流法 51 | func LimitFreqSingle(queueName string, count uint, timeWindow int64) bool { 52 | currTime := time.Now().Unix() 53 | if LimitQueue.LimitQueue == nil { 54 | LimitQueue.LimitQueue = make(map[string][]int64) 55 | } 56 | if _, ok = LimitQueue.readMap(queueName); !ok { 57 | LimitQueue.writeMap(queueName, make([]int64, 0)) 58 | return true 59 | } 60 | q, _ := LimitQueue.readMap(queueName) 61 | //队列未满 62 | if uint(len(q)) < count { 63 | LimitQueue.writeMap(queueName, append(q, currTime)) 64 | return true 65 | } 66 | //队列满了,取出最早访问的时间 67 | earlyTime := q[0] 68 | //说明最早期的时间还在时间窗口内,还没过期,所以不允许通过 69 | if currTime-earlyTime <= timeWindow { 70 | return false 71 | } else { 72 | //说明最早期的访问应该过期了,去掉最早期的 73 | q = q[1:] 74 | LimitQueue.writeMap(queueName, append(q, currTime)) 75 | } 76 | return true 77 | } 78 | -------------------------------------------------------------------------------- /tools/logger.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "log" 6 | "os" 7 | "path" 8 | "time" 9 | ) 10 | 11 | var logrusObj *logrus.Logger 12 | 13 | func Logger() *logrus.Logger { 14 | if logrusObj != nil { 15 | src, _ := setOutputFile() 16 | //设置输出 17 | logrusObj.Out = src 18 | return logrusObj 19 | } 20 | 21 | //实例化 22 | logger := logrus.New() 23 | src, _ := setOutputFile() 24 | //设置输出 25 | logger.Out = src 26 | //设置日志级别 27 | logger.SetLevel(logrus.DebugLevel) 28 | //设置日志格式 29 | logger.SetFormatter(&logrus.TextFormatter{ 30 | TimestampFormat: "2006-01-02 15:04:05", 31 | }) 32 | logrusObj = logger 33 | return logger 34 | } 35 | func setOutputFile() (*os.File, error) { 36 | now := time.Now() 37 | logFilePath := "" 38 | if dir, err := os.Getwd(); err == nil { 39 | logFilePath = dir + "/logs/" 40 | } 41 | _, err := os.Stat(logFilePath) 42 | if os.IsNotExist(err) { 43 | if err := os.MkdirAll(logFilePath, 0777); err != nil { 44 | log.Println(err.Error()) 45 | return nil, err 46 | } 47 | } 48 | logFileName := now.Format("2006-01-02") + ".log" 49 | //日志文件 50 | fileName := path.Join(logFilePath, logFileName) 51 | if _, err := os.Stat(fileName); err != nil { 52 | if _, err := os.Create(fileName); err != nil { 53 | log.Println(err.Error()) 54 | return nil, err 55 | } 56 | } 57 | //写入文件 58 | src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) 59 | if err != nil { 60 | log.Println(err) 61 | return nil, err 62 | } 63 | return src, nil 64 | } 65 | 66 | //func LoggerToFile() gin.HandlerFunc { 67 | // logger := Logger() 68 | // return func(c *gin.Context) { 69 | // // 开始时间 70 | // startTime := time.Now() 71 | // 72 | // // 处理请求 73 | // c.Next() 74 | // 75 | // // 结束时间 76 | // endTime := time.Now() 77 | // 78 | // // 执行时间 79 | // latencyTime := endTime.Sub(startTime) 80 | // 81 | // // 请求方式 82 | // reqMethod := c.Request.Method 83 | // 84 | // // 请求路由 85 | // reqUri := c.Request.RequestURI 86 | // 87 | // // 状态码 88 | // statusCode := c.Writer.Status() 89 | // 90 | // // 请求IP 91 | // clientIP := c.ClientIP() 92 | // 93 | // //日志格式 94 | // logger.Infof("| %3d | %13v | %15s | %s | %s |", 95 | // statusCode, 96 | // latencyTime, 97 | // clientIP, 98 | // reqMethod, 99 | // reqUri, 100 | // ) 101 | // } 102 | //} 103 | -------------------------------------------------------------------------------- /tools/mytest.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "fmt" 4 | 5 | func MyTest() { 6 | type MConn struct { 7 | Name string 8 | } 9 | var conn *MConn 10 | var conn2 MConn 11 | conn3 := new(MConn) 12 | conn4 := &MConn{} 13 | fmt.Printf("%v,%v,%v,%v \r\n", conn, conn2, conn3, conn4) 14 | 15 | var mMap map[string][]*MConn 16 | m1, _ := mMap["name"] 17 | //if ok { 18 | // m1.Name = "qqq" 19 | //} 20 | fmt.Printf("ssss%T", m1) 21 | } 22 | func MyStruct() { 23 | type s2 struct { 24 | name string 25 | } 26 | aa := s2{ 27 | name: "aa", 28 | } 29 | bb := s2{ 30 | name: "aa", 31 | } 32 | fmt.Printf("%v\n", aa == bb) 33 | 34 | type s1 struct { 35 | one map[string]string 36 | two []string 37 | three string 38 | } 39 | 40 | a := &s1{ 41 | one: map[string]string{"aaa": "bbb"}, 42 | two: []string{"aaa", "bbb"}, 43 | three: "aaaa", 44 | } 45 | b := &s1{ 46 | one: map[string]string{"aaa": "bbb"}, 47 | two: []string{"aaa", "bbb"}, 48 | three: "aaaa", 49 | } 50 | c := a 51 | fmt.Printf("%v;%v", a == b, a == c) 52 | } 53 | -------------------------------------------------------------------------------- /tools/mytest_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestMyTest(t *testing.T) { 9 | MyTest() 10 | } 11 | func TestMyStruct(t *testing.T) { 12 | MyStruct() 13 | } 14 | 15 | type SMap struct { 16 | sync.RWMutex 17 | Map map[int]int 18 | } 19 | 20 | func (l *SMap) readMap(key int) (int, bool) { 21 | l.RLock() 22 | value, ok := l.Map[key] 23 | l.RUnlock() 24 | return value, ok 25 | } 26 | 27 | func (l *SMap) writeMap(key int, value int) { 28 | l.Lock() 29 | l.Map[key] = value 30 | l.Unlock() 31 | } 32 | 33 | var mMap *SMap 34 | 35 | func TestMyMap(t *testing.T) { 36 | mMap = &SMap{ 37 | Map: make(map[int]int), 38 | } 39 | 40 | for i := 0; i < 10000; i++ { 41 | go func() { 42 | mMap.writeMap(i, i) 43 | }() 44 | go readMap(i) 45 | } 46 | } 47 | func readMap(i int) (int, bool) { 48 | return mMap.readMap(i) 49 | } 50 | -------------------------------------------------------------------------------- /tools/paniclog_linux.go: -------------------------------------------------------------------------------- 1 | // Log the panic under unix to the log file 2 | 3 | //+build linux 4 | 5 | package tools 6 | 7 | import ( 8 | "log" 9 | "os" 10 | "syscall" 11 | ) 12 | 13 | // redirectStderr to the file passed in 14 | func RedirectStderr(f *os.File) { 15 | err := syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd())) 16 | if err != nil { 17 | log.Printf("Failed to redirect stderr to file: %v", err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tools/paniclog_windows.go: -------------------------------------------------------------------------------- 1 | // Log the panic under windows to the log file 2 | // 3 | // Code from minix, via 4 | // 5 | // http://play.golang.org/p/kLtct7lSUg 6 | 7 | //+build windows 8 | 9 | package tools 10 | 11 | import ( 12 | "log" 13 | "os" 14 | "syscall" 15 | ) 16 | 17 | var ( 18 | kernel32 = syscall.MustLoadDLL("kernel32.dll") 19 | procSetStdHandle = kernel32.MustFindProc("SetStdHandle") 20 | ) 21 | 22 | func setStdHandle(stdhandle int32, handle syscall.Handle) error { 23 | r0, _, e1 := syscall.Syscall(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0) 24 | if r0 == 0 { 25 | if e1 != 0 { 26 | return error(e1) 27 | } 28 | return syscall.EINVAL 29 | } 30 | return nil 31 | } 32 | 33 | // redirectStderr to the file passed in 34 | func RedirectStderr(f *os.File) { 35 | err := setStdHandle(syscall.STD_ERROR_HANDLE, syscall.Handle(f.Fd())) 36 | if err != nil { 37 | log.Printf("Failed to redirect stderr to file: %v", err) 38 | } 39 | // SetStdHandle does not affect prior references to stderr 40 | os.Stderr = f 41 | } 42 | -------------------------------------------------------------------------------- /tools/reverse_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "testing" 4 | 5 | func TestReverse(t *testing.T) { 6 | cases := []struct { 7 | in, want string 8 | }{ 9 | {"Hello, world", "dlrow ,olleH"}, 10 | {"Hello, 世界", "界世 ,olleH"}, 11 | {"", ""}, 12 | } 13 | for _, c := range cases { 14 | got := Reverse(c.in) 15 | if got != c.want { 16 | t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tools/rpc_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "go-fly-muti/frpc" 5 | "testing" 6 | ) 7 | 8 | func TestClientRpc(t *testing.T) { 9 | frpc.ClientRpc() 10 | } 11 | func TestServerRpc(t *testing.T) { 12 | frpc.NewRpcServer("127.0.0.1:8082") 13 | } 14 | -------------------------------------------------------------------------------- /tools/session.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "github.com/gin-contrib/sessions" 5 | "github.com/gin-contrib/sessions/cookie" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // 中间件,处理session 10 | func Session(keyPairs string) gin.HandlerFunc { 11 | store := SessionConfig() 12 | return sessions.Sessions(keyPairs, store) 13 | } 14 | func SessionConfig() sessions.Store { 15 | sessionMaxAge := 3600 16 | sessionSecret := "gofly" 17 | var store sessions.Store 18 | store = cookie.NewStore([]byte(sessionSecret)) 19 | store.Options(sessions.Options{ 20 | MaxAge: sessionMaxAge, //seconds 21 | Path: "/", 22 | }) 23 | return store 24 | } 25 | -------------------------------------------------------------------------------- /tools/singlelist.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | // 单链表节点的结构 4 | type ListNode struct { 5 | val int 6 | next *ListNode 7 | } 8 | 9 | func NewListNode(x int) *ListNode { 10 | return &ListNode{ 11 | val: x, 12 | } 13 | } 14 | func ReverseList(head *ListNode) *ListNode { 15 | if head.next == nil { 16 | return head 17 | } 18 | last := ReverseList(head.next) 19 | head.next.next = head 20 | head.next = nil 21 | return last 22 | } 23 | 24 | var successor *ListNode // 后驱节点 25 | // 将链表的前 n 个节点反转(n <= 链表长度) 26 | func ReverseListN(head *ListNode, n int) *ListNode { 27 | if n == 1 { 28 | // 记录第 n + 1 个节点 29 | successor = head.next 30 | return head 31 | } 32 | // 以 head.next 为起点,需要反转前 n - 1 个节点 33 | last := ReverseListN(head.next, n-1) 34 | head.next.next = head 35 | // 让反转之后的 head 节点和后面的节点连起来 36 | head.next = successor 37 | return last 38 | } 39 | 40 | func ReverseBetween(head *ListNode, m int, n int) *ListNode { 41 | if m == 1 { 42 | return ReverseListN(head, m) 43 | } 44 | // 前进到反转的起点触发 base case 45 | head.next = ReverseBetween(head.next, m-1, n-1) 46 | return head 47 | } 48 | 49 | /** 反转区间 [a, b) 的元素,注意是左闭右开 */ 50 | func ReverseSingleList(a *ListNode, b *ListNode) *ListNode { 51 | var ( 52 | pre, cur, nxt *ListNode 53 | ) 54 | pre = nil 55 | cur = a 56 | nxt = a 57 | //终止的条件改一下就行了 58 | for cur != b { 59 | nxt = cur.next 60 | // 逐个结点反转 61 | cur.next = pre 62 | // 更新指针位置 63 | pre = cur 64 | cur = nxt 65 | } 66 | // 返回反转后的头结点 67 | return pre 68 | } 69 | func ReverseKGroup(head *ListNode, k int) *ListNode { 70 | if head == nil { 71 | return nil 72 | } 73 | // 区间 [a, b) 包含 k 个待反转元素 74 | var ( 75 | a, b *ListNode 76 | ) 77 | b = head 78 | a = head 79 | for i := 0; i < k; i++ { 80 | // 不足 k 个,不需要反转,base case 81 | if b == nil { 82 | return head 83 | } 84 | b = b.next 85 | } 86 | // 反转前 k 个元素 87 | newHead := ReverseSingleList(a, b) 88 | // 递归反转后续链表并连接起来 89 | a.next = ReverseKGroup(b, k) 90 | return newHead 91 | } 92 | -------------------------------------------------------------------------------- /tools/smtp.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "encoding/base64" 5 | "github.com/emersion/go-sasl" 6 | "github.com/emersion/go-smtp" 7 | "strings" 8 | ) 9 | 10 | func SendSmtp(server string, from string, password string, to []string, subject string, body string) error { 11 | auth := sasl.NewPlainClient("", from, password) 12 | subjectBase := base64.StdEncoding.EncodeToString([]byte(subject)) 13 | msg := strings.NewReader( 14 | "From: " + from + "\r\n" + 15 | "To: " + strings.Join(to, ",") + "\r\n" + 16 | "Subject: =?UTF-8?B?" + subjectBase + "?=\r\n" + 17 | "Content-Type: text/html; charset=UTF-8" + 18 | "\r\n\r\n" + 19 | body + "\r\n") 20 | err := smtp.SendMail(server, auth, from, to, msg) 21 | if err != nil { 22 | return err 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /tools/smtp_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "testing" 4 | 5 | func TestSendSmtp(t *testing.T) { 6 | body := "hello" 7 | SendSmtp("smtp.sina.cn:25", "taoshihan1@sina.com", "382e8a5e11cfae8c", []string{"taoshihan1@sina.com"}, "123456", body) 8 | } 9 | -------------------------------------------------------------------------------- /tools/snowflake.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | /* 10 | * Snowflake 11 | * 12 | * 1 42 52 64 13 | * +-----------------------------------------------+------------+---------------+ 14 | * | timestamp(ms) | workerid | sequence | 15 | * +-----------------------------------------------+------------+---------------+ 16 | * | 0000000000 0000000000 0000000000 0000000000 0 | 0000000000 | 0000000000 00 | 17 | * +-----------------------------------------------+------------+---------------+ 18 | * 19 | * 1. 41位时间截(毫秒级),注意这是时间截的差值(当前时间截 - 开始时间截)。可以使用约70年: (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69 20 | * 2. 10位数据机器位,可以部署在1024个节点 21 | * 3. 12位序列,毫秒内的计数,同一机器,同一时间截并发4096个序号 22 | */ 23 | 24 | const ( 25 | twepoch = int64(1483228800000) //开始时间截 (2017-01-01) 26 | workeridBits = uint(10) //机器id所占的位数 27 | sequenceBits = uint(12) //序列所占的位数 28 | workeridMax = int64(-1 ^ (-1 << workeridBits)) //支持的最大机器id数量 29 | sequenceMask = int64(-1 ^ (-1 << sequenceBits)) // 30 | workeridShift = sequenceBits //机器id左移位数 31 | timestampShift = sequenceBits + workeridBits //时间戳左移位数 32 | ) 33 | 34 | // A Snowflake struct holds the basic information needed for a snowflake generator worker 35 | type Snowflake struct { 36 | sync.Mutex 37 | timestamp int64 38 | workerid int64 39 | sequence int64 40 | } 41 | 42 | // NewNode returns a new snowflake worker that can be used to generate snowflake IDs 43 | func NewSnowflake(workerid int64) (*Snowflake, error) { 44 | 45 | if workerid < 0 || workerid > workeridMax { 46 | return nil, errors.New("workerid must be between 0 and 1023") 47 | } 48 | 49 | return &Snowflake{ 50 | timestamp: 0, 51 | workerid: workerid, 52 | sequence: 0, 53 | }, nil 54 | } 55 | 56 | // Generate creates and returns a unique snowflake ID 57 | func (s *Snowflake) Generate() int64 { 58 | 59 | s.Lock() 60 | 61 | now := time.Now().UnixNano() / 1000000 62 | 63 | if s.timestamp == now { 64 | s.sequence = (s.sequence + 1) & sequenceMask 65 | 66 | if s.sequence == 0 { 67 | for now <= s.timestamp { 68 | now = time.Now().UnixNano() / 1000000 69 | } 70 | } 71 | } else { 72 | s.sequence = 0 73 | } 74 | 75 | s.timestamp = now 76 | 77 | r := int64((now-twepoch)<= right { 39 | return 40 | } 41 | privot := partition(arr, left, right) 42 | QuickSort(arr, left, privot-1) 43 | QuickSort(arr, privot+1, right) 44 | } 45 | 46 | //快速排序2 47 | //找到一个基准,左边是所有比它小的,右边是比它大的,分别递归左右 48 | func QuickSort2(arr *[]int, left int, right int) { 49 | if left >= right { 50 | return 51 | } 52 | privot := (*arr)[left] 53 | i := left 54 | j := right 55 | for i < j { 56 | for i < j && (*arr)[j] > privot { 57 | j-- 58 | } 59 | for i < j && (*arr)[i] <= privot { 60 | i++ 61 | } 62 | temp := (*arr)[i] 63 | (*arr)[i] = (*arr)[j] 64 | (*arr)[j] = temp 65 | } 66 | (*arr)[left] = (*arr)[i] 67 | (*arr)[i] = privot 68 | 69 | QuickSort(arr, left, i-1) 70 | QuickSort(arr, i+1, right) 71 | } 72 | 73 | //冒泡排序 74 | //比较相邻元素,较大的往右移 75 | func BubbleSort(arr *[]int) { 76 | flag := true 77 | lastSwapIndex := 0 78 | for i := 0; i < len(*arr)-1; i++ { 79 | sortBorder := len(*arr) - 1 - i 80 | for j := 0; j < sortBorder; j++ { 81 | if (*arr)[j] > (*arr)[j+1] { 82 | temp := (*arr)[j] 83 | (*arr)[j] = (*arr)[j+1] 84 | (*arr)[j+1] = temp 85 | flag = false 86 | lastSwapIndex = j 87 | } 88 | } 89 | sortBorder = lastSwapIndex 90 | if flag { 91 | break 92 | } 93 | } 94 | } 95 | 96 | //插入排序 97 | //将未排序部分插入到已排序部分的适当位置 98 | func InsertionSort(arr *[]int) { 99 | for i := 1; i < len(*arr); i++ { 100 | curKey := (*arr)[i] 101 | j := i - 1 102 | for curKey < (*arr)[j] { 103 | (*arr)[j+1] = (*arr)[j] 104 | j-- 105 | if j < 0 { 106 | break 107 | } 108 | } 109 | (*arr)[j+1] = curKey 110 | } 111 | } 112 | 113 | //选择排序 114 | //选择一个最小值,再寻找比它还小的进行交换 115 | func SelectionSort(arr *[]int) { 116 | for i := 0; i < len(*arr); i++ { 117 | minIndex := i 118 | for j := i + 1; j < len(*arr); j++ { 119 | if (*arr)[j] < (*arr)[minIndex] { 120 | minIndex = j 121 | } 122 | } 123 | temp := (*arr)[i] 124 | (*arr)[i] = (*arr)[minIndex] 125 | (*arr)[minIndex] = temp 126 | } 127 | } 128 | 129 | //归并排序 130 | //合久必分,分久必合,利用临时数组合并两个有序数组 131 | func MergeSort(arr *[]int, left int, right int) { 132 | if left >= right { 133 | return 134 | } 135 | 136 | mid := (left + right) / 2 137 | MergeSort(arr, left, mid) 138 | MergeSort(arr, mid+1, right) 139 | 140 | i := left 141 | j := mid + 1 142 | p := 0 143 | temp := make([]int, right-left+1) 144 | for i <= mid && j <= right { 145 | if (*arr)[i] <= (*arr)[j] { 146 | temp[p] = (*arr)[i] 147 | i++ 148 | } else { 149 | temp[p] = (*arr)[j] 150 | j++ 151 | } 152 | p++ 153 | } 154 | 155 | for i <= mid { 156 | temp[p] = (*arr)[i] 157 | i++ 158 | p++ 159 | } 160 | for j <= right { 161 | temp[p] = (*arr)[j] 162 | j++ 163 | p++ 164 | } 165 | for i = 0; i < len(temp); i++ { 166 | (*arr)[left+i] = temp[i] 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /tools/sorts_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestQuickSort(t *testing.T) { 8 | arr := []int{6, 8, 3, 9, 4, 5, 4, 7} 9 | t.Log(arr) 10 | QuickSort(&arr, 0, len(arr)-1) 11 | t.Log(arr) 12 | } 13 | func TestQuickSort2(t *testing.T) { 14 | arr := []int{6, 8, 3, 9, 4, 5, 4, 7} 15 | t.Log(arr) 16 | QuickSort2(&arr, 0, len(arr)-1) 17 | t.Log(arr) 18 | } 19 | func TestBubbleSort(t *testing.T) { 20 | arr := []int{6, 8, 3, 9, 4, 5, 4, 7} 21 | t.Log(arr) 22 | BubbleSort(&arr) 23 | t.Log(arr) 24 | } 25 | func TestInsertionSort(t *testing.T) { 26 | arr := []int{6, 8, 3, 9, 4, 5, 4, 7} 27 | t.Log(arr) 28 | InsertionSort(&arr) 29 | t.Log(arr) 30 | } 31 | func TestSelectionSort(t *testing.T) { 32 | arr := []int{6, 8, 3, 9, 4, 5, 4, 7} 33 | t.Log(arr) 34 | SelectionSort(&arr) 35 | t.Log(arr) 36 | } 37 | func TestMergeSort(t *testing.T) { 38 | arr := []int{6, 8, 3, 9, 4, 5, 4, 7} 39 | t.Log(arr) 40 | MergeSort(&arr, 0, len(arr)-1) 41 | t.Log(arr) 42 | } 43 | func TestNilChannel(t *testing.T) { 44 | NilChannel() 45 | } 46 | -------------------------------------------------------------------------------- /tools/stringutil.go: -------------------------------------------------------------------------------- 1 | // stringutil 包含有用于处理字符串的工具函数。 2 | package tools 3 | 4 | import ( 5 | "fmt" 6 | "github.com/gobuffalo/packr/v2" 7 | "net/http" 8 | ) 9 | 10 | //获取URL的GET参数 11 | func GetUrlArg(r *http.Request, name string) string { 12 | var arg string 13 | values := r.URL.Query() 14 | arg = values.Get(name) 15 | return arg 16 | } 17 | 18 | // Reverse 将其实参字符串以符文为单位左右反转。 19 | func Reverse(s string) string { 20 | r := []rune(s) 21 | for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { 22 | r[i], r[j] = r[j], r[i] 23 | } 24 | return string(r) 25 | } 26 | 27 | // Reverse2 将其实参字符串以符文为单位左右反转。 28 | func Reverse2(s string) string { 29 | r := []rune(s) 30 | left := 0 31 | right := len(r) - 1 32 | for left < right { 33 | r[left], r[right] = r[right], r[left] 34 | left++ 35 | right-- 36 | } 37 | return string(r) 38 | } 39 | 40 | //获取文件内容,可以打包到二进制 41 | func FileGetContent(file string) string { 42 | str := "" 43 | box := packr.New("tmpl", "../static") 44 | content, err := box.FindString(file) 45 | if err != nil { 46 | return str 47 | } 48 | return content 49 | } 50 | 51 | func ShowStringByte(str string) { 52 | s := []byte(str) 53 | for i, c := range s { 54 | fmt.Println(i, c) 55 | } 56 | } 57 | func NilChannel() { 58 | var ch chan int 59 | ch <- 1 60 | } 61 | func Int2Str(i interface{}) string { 62 | return fmt.Sprintf("%v", i) 63 | } 64 | -------------------------------------------------------------------------------- /tools/stringutil_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "testing" 4 | 5 | func TestShowString(t *testing.T) { 6 | ShowStringByte("hello,世界") 7 | } 8 | func TestReverse2(t *testing.T) { 9 | cases := []struct { 10 | in, want string 11 | }{ 12 | {"Hello, world", "dlrow ,olleH"}, 13 | {"Hello, 世界", "界世 ,olleH"}, 14 | {"", ""}, 15 | } 16 | for _, c := range cases { 17 | got := Reverse2(c.in) 18 | if got != c.want { 19 | t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tools/test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | func MyPointer() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /tools/test_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | -------------------------------------------------------------------------------- /tools/types.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import "html/template" 4 | 5 | type MailServer struct { 6 | Server, Email, Password string 7 | } 8 | type ViewHtml struct { 9 | Header template.HTML 10 | Nav template.HTML 11 | } 12 | type IndexData struct { 13 | ViewHtml 14 | Folders map[string]int 15 | Mails interface{} 16 | MailPagelist []*MailItem 17 | CurrentPage int 18 | Fid string 19 | NextPage, PrePage string 20 | NumPages template.HTML 21 | } 22 | type ViewData struct { 23 | Folders map[string]int 24 | HtmlBody template.HTML 25 | MailItem 26 | } 27 | type MailItem struct { 28 | Subject string 29 | Fid string 30 | Id uint32 31 | From string 32 | To string 33 | Body string 34 | Date string 35 | } 36 | type MailPageList struct { 37 | MailItems []*MailItem 38 | } 39 | type JsonResult struct { 40 | Code int `json:"code"` 41 | Msg string `json:"msg"` 42 | } 43 | type JsonListResult struct { 44 | JsonResult 45 | Result interface{} `json:"result"` 46 | } 47 | type SmtpBody struct { 48 | Smtp string 49 | From string 50 | To []string 51 | Password string 52 | Subject string 53 | Body string 54 | } 55 | -------------------------------------------------------------------------------- /tools/uuid.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "github.com/satori/go.uuid" 5 | ) 6 | 7 | func Uuid() string { 8 | u2 := uuid.NewV4() 9 | return u2.String() 10 | } 11 | -------------------------------------------------------------------------------- /ws/user.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/gin-gonic/gin" 6 | "github.com/gorilla/websocket" 7 | "github.com/taoshihan1991/imaptool/models" 8 | "github.com/taoshihan1991/imaptool/tools" 9 | "log" 10 | "time" 11 | ) 12 | 13 | func NewKefuServer(c *gin.Context) { 14 | kefuId, _ := c.Get("kefu_id") 15 | kefuInfo := models.FindUserById(kefuId) 16 | if kefuInfo.ID == 0 { 17 | c.JSON(200, gin.H{ 18 | "code": 400, 19 | "msg": "用户不存在", 20 | }) 21 | return 22 | } 23 | 24 | //go kefuServerBackend() 25 | conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) 26 | if err != nil { 27 | log.Print("upgrade:", err) 28 | return 29 | } 30 | //获取GET参数,创建WS 31 | var kefu User 32 | kefu.Id = kefuInfo.Name 33 | kefu.Name = kefuInfo.Nickname 34 | kefu.Avator = kefuInfo.Avator 35 | kefu.Role_id = kefuInfo.RoleId 36 | kefu.Conn = conn 37 | AddKefuToList(&kefu) 38 | 39 | for { 40 | //接受消息 41 | var receive []byte 42 | messageType, receive, err := conn.ReadMessage() 43 | if err != nil { 44 | log.Println("ws/user.go ", err) 45 | conn.Close() 46 | return 47 | } 48 | 49 | message <- &Message{ 50 | conn: conn, 51 | content: receive, 52 | context: c, 53 | messageType: messageType, 54 | } 55 | } 56 | } 57 | func AddKefuToList(kefu *User) { 58 | oldUser, ok := KefuList[kefu.Id] 59 | if oldUser != nil || ok { 60 | msg := TypeMessage{ 61 | Type: "close", 62 | Data: kefu.Id, 63 | } 64 | str, _ := json.Marshal(msg) 65 | if err := oldUser.Conn.WriteMessage(websocket.TextMessage, str); err != nil { 66 | oldUser.Conn.Close() 67 | } 68 | } 69 | KefuList[kefu.Id] = kefu 70 | } 71 | 72 | //给指定客服发消息 73 | func OneKefuMessage(toId string, str []byte) { 74 | kefu, ok := KefuList[toId] 75 | if ok{ 76 | log.Println("OneKefuMessage lock") 77 | kefu.Mux.Lock() 78 | defer kefu.Mux.Unlock() 79 | log.Println("OneKefuMessage unlock") 80 | error := kefu.Conn.WriteMessage(websocket.TextMessage, str) 81 | tools.Logger().Println("send_kefu_message", error, string(str)) 82 | } 83 | } 84 | func KefuMessage(visitorId, content string, kefuInfo models.User) { 85 | msg := TypeMessage{ 86 | Type: "message", 87 | Data: ClientMessage{ 88 | Name: kefuInfo.Nickname, 89 | Avator: kefuInfo.Avator, 90 | Id: visitorId, 91 | Time: time.Now().Format("2006-01-02 15:04:05"), 92 | ToId: visitorId, 93 | Content: content, 94 | IsKefu: "yes", 95 | }, 96 | } 97 | str, _ := json.Marshal(msg) 98 | OneKefuMessage(kefuInfo.Name, str) 99 | } 100 | 101 | //给客服客户端发送消息判断客户端是否在线 102 | func SendPingToKefuClient() { 103 | msg := TypeMessage{ 104 | Type: "many pong", 105 | } 106 | str, _ := json.Marshal(msg) 107 | for kefuId, kefu := range KefuList { 108 | if kefu == nil { 109 | continue 110 | } 111 | kefu.Mux.Lock() 112 | defer kefu.Mux.Unlock() 113 | err := kefu.Conn.WriteMessage(websocket.TextMessage, str) 114 | if err != nil { 115 | log.Println("定时发送ping给客服,失败",err.Error()) 116 | delete(KefuList, kefuId) 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /ws/ws.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/gin-gonic/gin" 7 | "github.com/gorilla/websocket" 8 | "github.com/taoshihan1991/imaptool/models" 9 | "github.com/taoshihan1991/imaptool/tools" 10 | "log" 11 | "net/http" 12 | "strconv" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | type User struct { 18 | Conn *websocket.Conn 19 | Name string 20 | Id string 21 | Avator string 22 | To_id string 23 | Role_id string 24 | Mux sync.Mutex 25 | UpdateTime time.Time 26 | } 27 | type Message struct { 28 | conn *websocket.Conn 29 | context *gin.Context 30 | content []byte 31 | messageType int 32 | Mux sync.Mutex 33 | } 34 | type TypeMessage struct { 35 | Type interface{} `json:"type"` 36 | Data interface{} `json:"data"` 37 | } 38 | type ClientMessage struct { 39 | Name string `json:"name"` 40 | Avator string `json:"avator"` 41 | Id string `json:"id"` 42 | VisitorId string `json:"visitor_id"` 43 | Group string `json:"group"` 44 | Time string `json:"time"` 45 | ToId string `json:"to_id"` 46 | Content string `json:"content"` 47 | City string `json:"city"` 48 | ClientIp string `json:"client_ip"` 49 | Refer string `json:"refer"` 50 | IsKefu string `json:"is_kefu"` 51 | } 52 | 53 | var ClientList = make(map[string]*User) 54 | var KefuList = make(map[string]*User) 55 | var message = make(chan *Message, 10) 56 | var upgrader = websocket.Upgrader{} 57 | var Mux sync.RWMutex 58 | 59 | func init() { 60 | upgrader = websocket.Upgrader{ 61 | ReadBufferSize: 1024, 62 | WriteBufferSize: 1024, 63 | // 解决跨域问题 64 | CheckOrigin: func(r *http.Request) bool { 65 | return true 66 | }, 67 | } 68 | go UpdateVisitorStatusCron() 69 | } 70 | func SendServerJiang(title string, content string, domain string) string { 71 | noticeServerJiang, err := strconv.ParseBool(models.FindConfig("NoticeServerJiang")) 72 | serverJiangAPI := models.FindConfig("ServerJiangAPI") 73 | if err != nil || !noticeServerJiang || serverJiangAPI == "" { 74 | log.Println("do not notice serverjiang:", serverJiangAPI, noticeServerJiang) 75 | return "" 76 | } 77 | sendStr := fmt.Sprintf("%s%s", title, content) 78 | desp := title + ":" + content + "[登录](http://" + domain + "/main)" 79 | url := serverJiangAPI + "?text=" + sendStr + "&desp=" + desp 80 | //log.Println(url) 81 | res := tools.Get(url) 82 | return res 83 | } 84 | func SendFlyServerJiang(title string, content string, domain string) string { 85 | return "" 86 | } 87 | 88 | //定时给更新数据库状态 89 | func UpdateVisitorStatusCron() { 90 | for { 91 | visitors := models.FindVisitorsOnline() 92 | for _, visitor := range visitors { 93 | if visitor.VisitorId == "" { 94 | continue 95 | } 96 | _, ok := ClientList[visitor.VisitorId] 97 | if !ok { 98 | models.UpdateVisitorStatus(visitor.VisitorId, 0) 99 | } 100 | } 101 | SendPingToKefuClient() 102 | time.Sleep(60 * time.Second) 103 | } 104 | } 105 | 106 | //后端广播发送消息 107 | func WsServerBackend() { 108 | for { 109 | message := <-message 110 | var typeMsg TypeMessage 111 | json.Unmarshal(message.content, &typeMsg) 112 | conn := message.conn 113 | if typeMsg.Type == nil || typeMsg.Data == nil { 114 | continue 115 | } 116 | msgType := typeMsg.Type.(string) 117 | log.Println("客户端:", string(message.content)) 118 | 119 | switch msgType { 120 | //心跳 121 | case "ping": 122 | msg := TypeMessage{ 123 | Type: "pong", 124 | } 125 | str, _ := json.Marshal(msg) 126 | message.Mux.Lock() 127 | defer message.Mux.Unlock() 128 | conn.WriteMessage(websocket.TextMessage, str) 129 | case "inputing": 130 | data := typeMsg.Data.(map[string]interface{}) 131 | from := data["from"].(string) 132 | to := data["to"].(string) 133 | //限流 134 | if tools.LimitFreqSingle("inputing:"+from, 1, 2) { 135 | OneKefuMessage(to, message.content) 136 | } 137 | } 138 | 139 | } 140 | } 141 | func UpdateVisitorUser(visitorId string, toId string) { 142 | guest, ok := ClientList[visitorId] 143 | if ok { 144 | guest.To_id = toId 145 | } 146 | } 147 | --------------------------------------------------------------------------------