From 44c7487099d911cd83c0b9d037e4c6001b30860d Mon Sep 17 00:00:00 2001 From: 3343780376 <3343780376@qq.com> Date: Sun, 20 Nov 2022 22:11:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- api/api.go | 8 ++ api/cron/cron.go | 2 +- api/dependencies/dependencies.go | 50 ++++++++ api/logs/log.go | 32 ++++++ api/midware.go | 113 ++++++++++++++++++ api/open.go | 27 +++++ api/open/auth.go | 40 +++++++ api/open/open.go | 96 ++++++++++++++++ api/scripts/scripts.go | 2 +- api/subscription/subscription.go | 165 +++++++++++++++++++++++++++ api/system/system.go | 6 +- api/user/config_sample.sh | 163 ++++++++++++++++++++++++++ api/user/user.go | 12 +- controller/router.go | 6 +- go.mod | 4 + go.sum | 11 ++ main.go | 31 +++++ models/Apps.go | 61 ++++++++-- models/Crontabs.go | 20 ++++ models/Dependences.go | 63 ++++++++-- models/Subscriptions.go | 97 ++++++++++++---- models/models.go | 4 +- service/config/config.go | 20 ++++ service/config/config_test.go | 7 ++ service/cron/cron.go | 4 +- service/cron/manager.go | 115 ++++++++++--------- service/dependencies/dependencies.go | 39 +++++++ service/open/open.go | 48 ++++++++ service/scripts/scripts.go | 75 +++++++----- service/subscription/manager.go | 163 ++++++++++++++++++++++++++ service/subscription/subscription.go | 82 +++++++++++++ utils/array.go | 10 ++ utils/file.go | 50 ++++++++ utils/jwt.go | 86 +++----------- utils/run.go | 53 +++++++++ 36 files changed, 1557 insertions(+), 216 deletions(-) create mode 100644 api/dependencies/dependencies.go create mode 100644 api/logs/log.go create mode 100644 api/midware.go create mode 100644 api/open.go create mode 100644 api/open/auth.go create mode 100644 api/open/open.go create mode 100644 api/subscription/subscription.go create mode 100644 api/user/config_sample.sh create mode 100644 service/config/config.go create mode 100644 service/config/config_test.go create mode 100644 service/dependencies/dependencies.go create mode 100644 service/open/open.go create mode 100644 service/subscription/manager.go create mode 100644 service/subscription/subscription.go create mode 100644 utils/array.go create mode 100644 utils/file.go create mode 100644 utils/run.go diff --git a/README.md b/README.md index 3ede43d..044722f 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,12 @@ - [x] 环境变量的增删改查 - [x] 配置文件的编辑 - [x] 脚本文件的增删改查 -- [ ] 定时任务的视图筛选功能 -- [ ] 订阅功能的实现 +- [x] 应用授权登录的实现 +- [x] 订阅功能的实现 +- [x] 日志功能的实现 +- [x] 依赖功能的实现 - [ ] 推送功能的实现 -- [ ] 应用授权登录的实现 +- [ ] 定时任务的视图筛选功能 - [ ] 热更新的实现 diff --git a/api/api.go b/api/api.go index e803911..2836fe3 100644 --- a/api/api.go +++ b/api/api.go @@ -4,8 +4,12 @@ import ( "github.com/gin-gonic/gin" "github.com/huoxue1/qinglong-go/api/config" "github.com/huoxue1/qinglong-go/api/cron" + "github.com/huoxue1/qinglong-go/api/dependencies" "github.com/huoxue1/qinglong-go/api/env" + "github.com/huoxue1/qinglong-go/api/logs" + "github.com/huoxue1/qinglong-go/api/open" "github.com/huoxue1/qinglong-go/api/scripts" + "github.com/huoxue1/qinglong-go/api/subscription" "github.com/huoxue1/qinglong-go/api/system" "github.com/huoxue1/qinglong-go/api/user" ) @@ -17,4 +21,8 @@ func Api(group *gin.RouterGroup) { env.Api(group.Group("/envs")) config.Api(group.Group("/configs")) scripts.Api(group.Group("/scripts")) + open.Api(group.Group("/apps")) + subscription.Api(group.Group("/subscriptions")) + logs.APi(group.Group("/logs")) + dependencies.Api(group.Group("/dependencies")) } diff --git a/api/cron/cron.go b/api/cron/cron.go index ad8f3ff..714dcbe 100644 --- a/api/cron/cron.go +++ b/api/cron/cron.go @@ -40,7 +40,7 @@ func get() gin.HandlerFunc { } ctx.JSON(200, res.Ok(gin.H{ "data": crons, - "total": len(crons), + "total": models.Count(ctx.Query("searchValue")), })) } } diff --git a/api/dependencies/dependencies.go b/api/dependencies/dependencies.go new file mode 100644 index 0000000..50539aa --- /dev/null +++ b/api/dependencies/dependencies.go @@ -0,0 +1,50 @@ +package dependencies + +import ( + "github.com/gin-gonic/gin" + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/service/dependencies" + "github.com/huoxue1/qinglong-go/utils/res" + "strconv" +) + +func Api(group *gin.RouterGroup) { + group.POST("", post()) + group.GET("", get()) + group.GET("/:id", getDep()) +} + +func get() gin.HandlerFunc { + return func(ctx *gin.Context) { + dependences, err := models.QueryDependences(ctx.Query("searchValue")) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + ctx.JSON(200, res.Ok(dependences)) + } +} + +func post() gin.HandlerFunc { + return func(ctx *gin.Context) { + var deps []*models.Dependences + err := ctx.ShouldBindJSON(&deps) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + for _, dep := range deps { + dependencies.AddDep(dep) + } + + ctx.JSON(200, res.Ok(deps)) + } +} + +func getDep() gin.HandlerFunc { + return func(ctx *gin.Context) { + id, _ := strconv.Atoi(ctx.Param("id")) + dep, _ := models.GetDependences(id) + ctx.JSON(200, res.Ok(dep)) + } +} diff --git a/api/logs/log.go b/api/logs/log.go new file mode 100644 index 0000000..556a5a2 --- /dev/null +++ b/api/logs/log.go @@ -0,0 +1,32 @@ +package logs + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/huoxue1/qinglong-go/service/scripts" + "github.com/huoxue1/qinglong-go/utils/res" + "os" + "path" +) + +func APi(group *gin.RouterGroup) { + group.GET("", get()) + group.GET("/:name", getFile()) +} + +func get() gin.HandlerFunc { + + return func(ctx *gin.Context) { + files := scripts.GetFiles(path.Join("data", "log"), "") + ctx.JSON(200, res.Ok(files)) + } +} + +func getFile() gin.HandlerFunc { + return func(ctx *gin.Context) { + fileName := ctx.Param("name") + path := ctx.Query("path") + data, _ := os.ReadFile(fmt.Sprintf("data/log/%s/%s", path, fileName)) + ctx.JSON(200, res.Ok(string(data))) + } +} diff --git a/api/midware.go b/api/midware.go new file mode 100644 index 0000000..7cbc728 --- /dev/null +++ b/api/midware.go @@ -0,0 +1,113 @@ +package api + +import ( + "encoding/json" + "errors" + "github.com/gin-gonic/gin" + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/utils" + "github.com/huoxue1/qinglong-go/utils/res" + "os" + "path" + "strconv" + "strings" + "time" +) + +var ( + unExcludedPath = []string{ + "/api/system", + "/api/user/login", + "/api/user/init", + } +) + +func OpenJwt() gin.HandlerFunc { + return func(ctx *gin.Context) { + if strings.HasPrefix(ctx.Request.URL.Path, "/open/auth/token") { + ctx.Next() + return + } else { + tokenHeader := ctx.GetHeader("Authorization") + if tokenHeader == "" { + ctx.JSON(401, res.Err(401, errors.New("no authorization token was found"))) + ctx.Abort() + return + } + authToken := strings.Split(tokenHeader, " ")[1] + claims, _ := utils.ParseToken(authToken) + if claims.ExpiresAt < time.Now().Unix() { + ctx.JSON(401, res.Err(401, errors.New("the authorization token is expired"))) + ctx.Abort() + return + } + userId, _ := strconv.Atoi(claims.UserID) + app, err := models.GetApp(userId) + if err != nil { + ctx.JSON(401, res.Err(401, errors.New("the authorization token is invalid"))) + ctx.Abort() + return + } + for _, scope := range app.Scopes { + if strings.HasPrefix(ctx.Request.URL.Path, "/open/"+scope) { + ctx.Next() + } + } + ctx.Abort() + } + } +} + +func Jwt() gin.HandlerFunc { + return func(ctx *gin.Context) { + for _, s := range unExcludedPath { + if strings.HasPrefix(ctx.Request.URL.Path, s) { + ctx.Next() + return + } + } + + data, err := os.ReadFile(path.Join("data", "config", "auth.json")) + if err != nil { + ctx.Abort() + return + } + auth := new(models.AuthFile) + _ = json.Unmarshal(data, auth) + tokenHeader := ctx.GetHeader("Authorization") + if tokenHeader == "" { + ctx.JSON(401, res.Err(401, errors.New("no authorization token was found"))) + ctx.Abort() + return + } + authToken := strings.Split(tokenHeader, " ")[1] + + mobile := utils.IsMobile(ctx.GetHeader("User-Agent")) + claims, _ := utils.ParseToken(authToken) + if claims.ExpiresAt < time.Now().Unix() { + ctx.JSON(401, res.Err(401, errors.New("the authorization token is expired"))) + ctx.Abort() + return + } + if mobile { + if authToken != auth.Tokens.Mobile && authToken != auth.Token { + ctx.JSON(401, res.Err(401, errors.New("the authorization token is error"))) + ctx.Abort() + return + } else { + ctx.Next() + return + } + } else { + if authToken != auth.Tokens.Desktop && authToken != auth.Token { + ctx.JSON(401, res.Err(401, errors.New("the authorization token is error"))) + ctx.Abort() + return + } else { + ctx.Next() + return + } + } + + } +} diff --git a/api/open.go b/api/open.go new file mode 100644 index 0000000..ab73d14 --- /dev/null +++ b/api/open.go @@ -0,0 +1,27 @@ +package api + +import ( + "github.com/gin-gonic/gin" + "github.com/huoxue1/qinglong-go/api/config" + "github.com/huoxue1/qinglong-go/api/cron" + "github.com/huoxue1/qinglong-go/api/dependencies" + "github.com/huoxue1/qinglong-go/api/env" + "github.com/huoxue1/qinglong-go/api/logs" + "github.com/huoxue1/qinglong-go/api/open" + "github.com/huoxue1/qinglong-go/api/scripts" + "github.com/huoxue1/qinglong-go/api/subscription" + "github.com/huoxue1/qinglong-go/api/system" +) + +func Open(group *gin.RouterGroup) { + group.GET("/auth/token", open.Auth()) + system.Api(group.Group("/system")) + cron.Api(group.Group("/crons")) + env.Api(group.Group("/envs")) + config.Api(group.Group("/configs")) + scripts.Api(group.Group("/scripts")) + open.Api(group.Group("/apps")) + subscription.Api(group.Group("/subscriptions")) + logs.APi(group.Group("/logs")) + dependencies.Api(group.Group("/dependencies")) +} diff --git a/api/open/auth.go b/api/open/auth.go new file mode 100644 index 0000000..3dcb927 --- /dev/null +++ b/api/open/auth.go @@ -0,0 +1,40 @@ +package open + +import ( + "errors" + "github.com/gin-gonic/gin" + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/utils" + "github.com/huoxue1/qinglong-go/utils/res" + "strconv" + "time" +) + +func Auth() gin.HandlerFunc { + return func(ctx *gin.Context) { + clientId := ctx.Query("client_id") + clientSecret := ctx.Query("client_secret") + app, err := models.GetAppById(clientId) + if err != nil { + ctx.JSON(401, res.Err(401, err)) + return + } + if app.ClientSecret != clientSecret { + ctx.JSON(401, res.Err(401, errors.New("the auth fail"))) + return + } + token, err := utils.GenerateToken(strconv.Itoa(app.Id), 720) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + app.Tokens = append(app.Tokens, token) + models.UpdateApp(app) + ctx.JSON(200, res.Ok(map[string]any{ + "token": token, + "token_type": "Bearer", + "expiration": time.Now().Add(time.Hour * 720).Unix(), + })) + + } +} diff --git a/api/open/open.go b/api/open/open.go new file mode 100644 index 0000000..e45ed75 --- /dev/null +++ b/api/open/open.go @@ -0,0 +1,96 @@ +package open + +import ( + "github.com/gin-gonic/gin" + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/service/open" + "github.com/huoxue1/qinglong-go/utils/res" + "strconv" +) + +func Api(group *gin.RouterGroup) { + group.GET("", get()) + group.POST("", post()) + group.PUT("", put()) + group.DELETE("", del()) + group.PUT("/:id/reset-secret", reset()) +} + +func get() gin.HandlerFunc { + return func(ctx *gin.Context) { + apps, err := models.QueryApp() + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(apps)) + } +} + +func del() gin.HandlerFunc { + return func(ctx *gin.Context) { + var ids []int + err := ctx.ShouldBindJSON(&ids) + if err != nil { + ctx.JSON(503, res.Err(502, err)) + return + } + err = open.DeleteApp(ids) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(true)) + } +} + +func put() gin.HandlerFunc { + return func(ctx *gin.Context) { + m := new(models.Apps) + err := ctx.ShouldBindJSON(m) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + err = open.UpdateApp(m) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(m)) + } +} + +func post() gin.HandlerFunc { + return func(ctx *gin.Context) { + m := new(models.Apps) + err := ctx.ShouldBindJSON(m) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + id, err := open.AddApp(m) + if err != nil { + return + } + app, _ := models.GetApp(id) + ctx.JSON(200, res.Ok(app)) + } +} + +func reset() gin.HandlerFunc { + return func(ctx *gin.Context) { + id, _ := strconv.Atoi(ctx.Param("id")) + app, err := models.GetApp(id) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + err = open.ResetApp(app) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(app)) + } +} diff --git a/api/scripts/scripts.go b/api/scripts/scripts.go index c620066..733ebfd 100644 --- a/api/scripts/scripts.go +++ b/api/scripts/scripts.go @@ -19,7 +19,7 @@ func Api(group *gin.RouterGroup) { func get() gin.HandlerFunc { return func(ctx *gin.Context) { - files := scripts.GetFiles() + files := scripts.GetFiles(path2.Join("data", "scripts"), "") ctx.JSON(200, res.Ok(files)) } } diff --git a/api/subscription/subscription.go b/api/subscription/subscription.go new file mode 100644 index 0000000..35a941f --- /dev/null +++ b/api/subscription/subscription.go @@ -0,0 +1,165 @@ +package subscription + +import ( + "github.com/gin-gonic/gin" + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/service/subscription" + "github.com/huoxue1/qinglong-go/utils/res" + "os" + "strconv" +) + +func Api(group *gin.RouterGroup) { + group.GET("", get()) + group.POST("", post()) + group.PUT("", put()) + group.PUT("/disable", disable()) + group.PUT("/enable", enable()) + group.DELETE("", del()) + group.PUT("/run", run()) + group.GET("/:id/log", log1()) + group.PUT("/stop", stop()) +} + +func run() gin.HandlerFunc { + return func(ctx *gin.Context) { + var ids []int + err := ctx.ShouldBindJSON(&ids) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + err = subscription.RunSubscription(ids) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(true)) + } +} + +func stop() gin.HandlerFunc { + return func(ctx *gin.Context) { + var ids []int + err := ctx.ShouldBindJSON(&ids) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + err = subscription.StopSubscription(ids) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(true)) + } +} + +func get() gin.HandlerFunc { + return func(ctx *gin.Context) { + + subs, err := models.QuerySubscription(ctx.Query("searchValue")) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(subs)) + + } + +} + +func post() gin.HandlerFunc { + + return func(ctx *gin.Context) { + sub := new(models.Subscriptions) + err := ctx.ShouldBindJSON(sub) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + id, err := subscription.AddSubscription(sub) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + sub.Id = id + ctx.JSON(200, res.Ok(sub)) + } +} + +func enable() gin.HandlerFunc { + return func(ctx *gin.Context) { + var ids []int + err := ctx.ShouldBindJSON(&ids) + if err != nil { + ctx.JSON(503, res.Err(502, err)) + return + } + err = subscription.EnableSubscription(ids) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(true)) + } +} +func disable() gin.HandlerFunc { + return func(ctx *gin.Context) { + var ids []int + err := ctx.ShouldBindJSON(&ids) + if err != nil { + ctx.JSON(502, res.Err(502, err)) + return + } + err = subscription.DisableSubscription(ids) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(true)) + } +} + +func put() gin.HandlerFunc { + return func(ctx *gin.Context) { + s := new(models.Subscriptions) + err := ctx.ShouldBindJSON(s) + if err != nil { + ctx.JSON(503, res.Err(502, err)) + return + } + err = subscription.UpdateSubscription(s) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(s)) + } +} + +func del() gin.HandlerFunc { + return func(ctx *gin.Context) { + var ids []int + err := ctx.ShouldBindJSON(&ids) + if err != nil { + ctx.JSON(503, res.Err(502, err)) + return + } + err = subscription.DeleteSubscription(ids) + if err != nil { + ctx.JSON(503, res.Err(503, err)) + return + } + ctx.JSON(200, res.Ok(true)) + } +} + +func log1() gin.HandlerFunc { + return func(ctx *gin.Context) { + id, _ := strconv.Atoi(ctx.Param("id")) + s, _ := models.GetSubscription(id) + data, _ := os.ReadFile(s.LogPath) + ctx.JSON(200, res.Ok(string(data))) + } +} diff --git a/api/system/system.go b/api/system/system.go index 0db0698..557f20a 100644 --- a/api/system/system.go +++ b/api/system/system.go @@ -9,15 +9,15 @@ import ( ) func Api(group *gin.RouterGroup) { - group.GET("/", get()) + group.GET("", get()) } func get() gin.HandlerFunc { return func(ctx *gin.Context) { _, err := os.Stat(path.Join("data", "config", "auth.json")) - exist := os.IsExist(err) + exist := os.IsNotExist(err) ctx.JSON(200, res.Ok(system.System{ - IsInitialized: exist, + IsInitialized: !exist, Version: "2.0.14", LastCommitTime: "", LastCommitId: "", diff --git a/api/user/config_sample.sh b/api/user/config_sample.sh new file mode 100644 index 0000000..7b87362 --- /dev/null +++ b/api/user/config_sample.sh @@ -0,0 +1,163 @@ +## Version: v2.8.0 +## Date: 2021-06-20 +## Update Content: 可持续发展纲要\n1. session管理破坏性修改\n2. 配置管理可编辑config下文件\n3. 自定义脚本改为查看脚本\n4. 移除互助相关 + +## 上面版本号中,如果第2位数字有变化,那么代表增加了新的参数,如果只有第3位数字有变化,仅代表更新了注释,没有增加新的参数,可更新可不更新 + +## 在运行 ql repo 命令时,是否自动删除失效的脚本与定时任务 +AutoDelCron="true" + +## 在运行 ql repo 命令时,是否自动增加新的本地定时任务 +AutoAddCron="true" + +## 拉取脚本时默认的定时规则,当匹配不到定时规则时使用,例如: 0 9 * * * +DefaultCronRule="" + +## ql repo命令拉取脚本时需要拉取的文件后缀,直接写文件后缀名即可 +RepoFileExtensions="js py" + +## 代理地址,支持HTTP/SOCK5,例如 http://127.0.0.1:7890 +ProxyUrl="" + +## 资源告警阙值,默认CPU 80%、内存80%、磁盘90% +CpuWarn=80 +MemoryWarn=80 +DiskWarn=90 + +## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天 +CommandTimeoutTime="1h" + +## 设置批量执行任务时的并发数,默认同时执行5个任务 +MaxConcurrentNum="5" + +## 在运行 task 命令时,随机延迟启动任务的最大延迟时间 +## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空 +RandomDelay="300" + +## 需要随机延迟运行任务的文件后缀,直接写后缀名即可,多个后缀用空格分开,例如: js py ts +## 默认仅给javascript任务加随机延迟,其它任务按定时规则准点运行。全部任务随机延迟赋值为空 +RandomDelayFileExtensions="js" + +## 每小时的第几分钟准点运行任务,当在这些时间运行任务时将忽略 RandomDelay 配置,不会被随机延迟 +## 默认是第0分钟和第30分钟,例如21:00或21:30分的任务将会准点运行。不需要准点运行赋值为空 +RandomDelayIgnoredMinutes="0 30" + +## 如果你自己会写shell脚本,并且希望在每次运行 ql update 命令时,额外运行你的 shell 脚本,请赋值为 "true",默认为true +EnableExtraShell="true" + +## 是否自动启动bot,默认不启动,设置为true时自动启动,目前需要自行克隆bot仓库所需代码,存到ql/repo目录下,文件夹命名为dockerbot +AutoStartBot="" + +## 是否使用第三方bot,默认不使用,使用时填入仓库地址,存到ql/repo目录下,文件夹命名为diybot +BotRepoUrl="" + +## 安装python依赖时指定pip源 +PipMirror="https://pypi.doubanio.com/simple/" + +## 安装node依赖时指定npm源 +NpmMirror="https://registry.npmmirror.com" + +## 通知环境变量 +## 1. Server酱 +## https://sct.ftqq.com +## 下方填写 SCHKEY 值或 SendKey 值 +export PUSH_KEY="" + +## 2. BARK +## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123 +export BARK_PUSH="" +## 下方填写推送图标设置,自定义推送图标(需iOS15或以上) +export BARK_ICON="https://qn.whyour.cn/logo.png" +## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声 +export BARK_SOUND="" +## 下方填写推送消息分组,默认为"QingLong" +export BARK_GROUP="QingLong" + +## 3. Telegram +## 下方填写自己申请@BotFather的Token,如10xxx4:AAFcqxxxxgER5uw +export TG_BOT_TOKEN="" +## 下方填写 @getuseridbot 中获取到的纯数字ID +export TG_USER_ID="" +## Telegram 代理IP(选填) +## 下方填写代理IP地址,代理类型为 http,比如您代理是 http://127.0.0.1:1080,则填写 "127.0.0.1" +## 如需使用,请自行解除下一行的注释 +export TG_PROXY_HOST="" +## Telegram 代理端口(选填) +## 下方填写代理端口号,代理类型为 http,比如您代理是 http://127.0.0.1:1080,则填写 "1080" +## 如需使用,请自行解除下一行的注释 +export TG_PROXY_PORT="" +## Telegram 代理的认证参数(选填) +export TG_PROXY_AUTH="" +## Telegram api自建反向代理地址(选填) +## 教程:https://www.hostloc.com/thread-805441-1-1.html +## 如反向代理地址 http://aaa.bbb.ccc 则填写 aaa.bbb.ccc +## 如需使用,请赋值代理地址链接,并自行解除下一行的注释 +export TG_API_HOST="" + +## 4. 钉钉 +## 官方文档:https://developers.dingtalk.com/document/app/custom-robot-access +## 下方填写token后面的内容,只需 https://oapi.dingtalk.com/robot/send?access_token=XXX 等于=符号后面的XXX即可 +export DD_BOT_TOKEN="" +export DD_BOT_SECRET="" + +## 5. 企业微信机器人 +## 官方说明文档:https://work.weixin.qq.com/api/doc/90000/90136/91770 +## 下方填写密钥,企业微信推送 webhook 后面的 key +export QYWX_KEY="" + +## 6. 企业微信应用 +## 参考文档:http://note.youdao.com/s/HMiudGkb +## 下方填写素材库图片id(corpid,corpsecret,touser,agentid),素材库图片填0为图文消息, 填1为纯文本消息 +export QYWX_AM="" + +## 7. iGot聚合 +## 参考文档:https://wahao.github.io/Bark-MP-helper +## 下方填写iGot的推送key,支持多方式推送,确保消息可达 +export IGOT_PUSH_KEY="" + +## 8. Push Plus +## 官方网站:http://www.pushplus.plus +## 下方填写您的Token,微信扫码登录后一对一推送或一对多推送下面的token,只填 PUSH_PLUS_TOKEN 默认为一对一推送 +export PUSH_PLUS_TOKEN="" +## 一对一多推送(选填) +## 下方填写您的一对多推送的 "群组编码" ,(一对多推送下面->您的群组(如无则新建)->群组编码) +## 1. 需订阅者扫描二维码 2、如果您是创建群组所属人,也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送 +export PUSH_PLUS_USER="" + +## 9. go-cqhttp +## gobot_url 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg +## gobot_token 填写在go-cqhttp文件设置的访问密钥 +## gobot_qq 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群 +## go-cqhttp相关API https://docs.go-cqhttp.org/api +export GOBOT_URL="" +export GOBOT_TOKEN="" +export GOBOT_QQ="" + +## 10. gotify +## gotify_url 填写gotify地址,如https://push.example.de:8080 +## gotify_token 填写gotify的消息应用token +## gotify_priority 填写推送消息优先级,默认为0 +export GOTIFY_URL="" +export GOTIFY_TOKEN="" +export GOTIFY_PRIORITY=0 + +## 11. PushDeer +## deer_key 填写PushDeer的key +export DEER_KEY="" + +## 12. Chat +## chat_url 填写synology chat地址,http://IP:PORT/webapi/***token= +## chat_token 填写后面的token +export CHAT_URL="" +export CHAT_TOKEN="" + +## 13. aibotk +## 官方说明文档:http://wechat.aibotk.com/oapi/oapi?from=ql +## aibotk_key (必填)填写智能微秘书个人中心的apikey +export AIBOTK_KEY="" +## aibotk_type (必填)填写发送的目标 room 或 contact, 填其他的不生效 +export AIBOTK_TYPE="" +## aibotk_name (必填)填写群名或用户昵称,和上面的type类型要对应 +export AIBOTK_NAME="" + +## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可 \ No newline at end of file diff --git a/api/user/user.go b/api/user/user.go index 918de08..97597fc 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -1,6 +1,7 @@ package user import ( + _ "embed" "encoding/json" "github.com/gin-gonic/gin" "github.com/huoxue1/qinglong-go/models" @@ -12,6 +13,9 @@ import ( "time" ) +//go:embed config_sample.sh +var sample []byte + func Api(group *gin.RouterGroup) { group.GET("/", get()) group.PUT("/init", appInit()) @@ -37,6 +41,12 @@ func appInit() gin.HandlerFunc { ctx.JSON(400, res.Err(400, err)) return } + _ = os.MkdirAll(path.Join("data", "config"), 0666) + _ = os.MkdirAll(path.Join("data", "log"), 0666) + _ = os.MkdirAll(path.Join("data", "repo"), 0666) + _ = os.MkdirAll(path.Join("data", "scripts"), 0666) + _ = os.WriteFile(path.Join("data", "config", "config.sh"), sample, 0666) + _ = os.WriteFile(path.Join("data", "config", "config_sample.sh"), sample, 0666) type Req struct { UserName string `json:"username"` Password string `json:"password"` @@ -78,7 +88,7 @@ func login() gin.HandlerFunc { auth := new(models.AuthFile) _ = json.Unmarshal(data, auth) if auth.Username == r.UserName && auth.Password == r.Password { - token, err := utils.GenerateToken(r.UserName) + token, err := utils.GenerateToken(r.UserName, 48) if err != nil { ctx.JSON(503, res.Err(503, err)) return diff --git a/controller/router.go b/controller/router.go index da8a7f6..d213078 100644 --- a/controller/router.go +++ b/controller/router.go @@ -4,12 +4,13 @@ import ( "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "github.com/huoxue1/qinglong-go/api" - "github.com/huoxue1/qinglong-go/utils" "net/http" ) func Router() *gin.Engine { engine := gin.New() + engine.Use(gin.Logger()) + engine.Use(gin.Recovery()) engine.Use(static.Serve("/", static.LocalFile("static/dist/", false))) engine.NoRoute(func(ctx *gin.Context) { if ctx.Request.Method == http.MethodGet { @@ -18,7 +19,8 @@ func Router() *gin.Engine { } ctx.Next() }) - api.Api(engine.Group("/api", utils.Jwt())) + api.Api(engine.Group("/api", api.Jwt())) + api.Open(engine.Group("/open", api.OpenJwt())) return engine } diff --git a/go.mod b/go.mod index 03d3972..7401aaa 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,12 @@ module github.com/huoxue1/qinglong-go go 1.18 require ( + github.com/Lyrics-you/sail-logrus-formatter v1.3.1 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-contrib/static v0.0.1 github.com/gin-gonic/gin v1.8.1 github.com/google/uuid v1.3.0 + github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/robfig/cron/v3 v3.0.1 github.com/sirupsen/logrus v1.9.0 modernc.org/sqlite v1.19.4 @@ -25,12 +27,14 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/lestrrat-go/strftime v1.0.6 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.13.0 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/stretchr/testify v1.8.1 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7b78159..488da7a 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0p gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Lyrics-you/sail-logrus-formatter v1.3.1 h1:y/9QraPbDwfccHa4QFZ9g2zNiPoSoQnE5MYizWLiYwY= +github.com/Lyrics-you/sail-logrus-formatter v1.3.1/go.mod h1:e9FX8+3MxwQGGkK+8ne3kRpu0gaBc9QTE7jtP+zP070= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -221,6 +223,7 @@ github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -249,6 +252,12 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= +github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= +github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= +github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -334,6 +343,8 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/main.go b/main.go index 4081235..3be5008 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,40 @@ package main import ( + nested "github.com/Lyrics-you/sail-logrus-formatter/sailor" "github.com/huoxue1/qinglong-go/controller" + rotates "github.com/lestrrat-go/file-rotatelogs" + log "github.com/sirupsen/logrus" + "io" + "os" + "path" + "time" ) +func init() { + w, err := rotates.New(path.Join("data", "logs", "%Y-%m-%d.log"), rotates.WithRotationTime(time.Hour*24)) + if err != nil { + log.Errorf("rotates init err: %v", err) + panic(err) + } + log.SetOutput(io.MultiWriter(w, os.Stdout)) + log.SetFormatter(&nested.Formatter{ + FieldsOrder: nil, + TimeStampFormat: "2006-01-02 15:04:05", + CharStampFormat: "", + HideKeys: false, + Position: true, + Colors: true, + FieldsColors: true, + FieldsSpace: true, + ShowFullLevel: false, + LowerCaseLevel: true, + TrimMessages: true, + CallerFirst: false, + CustomCallerFormatter: nil, + }) +} + func main() { engine := controller.Router() _ = engine.Run(":8080") diff --git a/models/Apps.go b/models/Apps.go index 8a56330..fc7f664 100644 --- a/models/Apps.go +++ b/models/Apps.go @@ -1,16 +1,53 @@ package models -import ( - "time" -) - type Apps struct { - Id int `xorm:"pk autoincr INTEGER"` - Name string `xorm:"VARCHAR(255) unique"` - Scopes string `xorm:"JSON"` - ClientId string `xorm:"VARCHAR(255)"` - ClientSecret string `xorm:"VARCHAR(255)"` - Tokens string `xorm:"JSON"` - Createdat time.Time `xorm:"not null DATETIME created"` - Updatedat time.Time `xorm:"not null DATETIME updated"` + Id int `xorm:"pk autoincr INTEGER" json:"id,omitempty"` + Name string `xorm:"TEXT unique" json:"name,omitempty"` + Scopes []string `xorm:"JSON" json:"scopes,omitempty"` + ClientId string `xorm:"TEXT" json:"client_id,omitempty"` + ClientSecret string `xorm:"TEXT" json:"client_secret,omitempty"` + Tokens []string `xorm:"JSON" json:"tokens,omitempty"` + Createdat string `xorm:"not null DATETIME created" json:"createdat,omitempty"` + Updatedat string `xorm:"not null DATETIME updated" json:"updatedat,omitempty"` +} + +func QueryApp() ([]*Apps, error) { + apps := make([]*Apps, 0) + session := engine.Table(new(Apps)) + err := session.Find(&apps) + if err != nil { + return nil, err + } + return apps, err +} + +func AddApp(app *Apps) (int, error) { + _, err := engine.Table(app).Insert(app) + if err != nil { + return 0, err + } + _, _ = engine.Where("name=?", app.Name).Get(app) + return app.Id, err +} + +func GetApp(id int) (*Apps, error) { + app := new(Apps) + _, err := engine.ID(id).Get(app) + return app, err +} + +func GetAppById(clientId string) (*Apps, error) { + app := new(Apps) + _, err := engine.Table(app).Where("client_id=?", clientId).Get(app) + return app, err +} + +func UpdateApp(app *Apps) error { + _, err := engine.Table(app).ID(app.Id).AllCols().Update(app) + return err +} + +func DeleteApp(id int) error { + _, err := engine.Table(new(Apps)).Delete(&Apps{Id: id}) + return err } diff --git a/models/Crontabs.go b/models/Crontabs.go index 5162f9b..f0cb90e 100644 --- a/models/Crontabs.go +++ b/models/Crontabs.go @@ -1,6 +1,7 @@ package models import ( + "errors" "xorm.io/builder" ) @@ -56,6 +57,16 @@ func FindAllEnableCron() []*Crontabs { return crontabs } +func GetCronByCommand(command string) (*Crontabs, error) { + cron := new(Crontabs) + count, _ := engine.Where("command=?", command).Count(cron) + if count < 1 { + return nil, errors.New("not found") + } + _, err := engine.Where("command=?", command).Get(cron) + return cron, err +} + func GetCron(id int) (*Crontabs, error) { cron := new(Crontabs) _, err := engine.ID(id).Get(cron) @@ -80,3 +91,12 @@ func DeleteCron(id int) error { _, err := engine.Table(new(Crontabs)).Delete(&Crontabs{Id: id}) return err } + +func Count(searchValue string) int64 { + count, _ := engine.Table(new(Crontabs)). + Where( + builder.Like{"name", "%" + searchValue + "%"}. + Or(builder.Like{"command", "%" + searchValue + "%"})). + Count() + return count +} diff --git a/models/Dependences.go b/models/Dependences.go index b79e863..621d7d8 100644 --- a/models/Dependences.go +++ b/models/Dependences.go @@ -2,16 +2,61 @@ package models import ( "time" + "xorm.io/builder" +) + +const ( + NODE = 0 + PYTHON = 1 + LINUX = 2 ) type Dependences struct { - Id int `xorm:"pk autoincr INTEGER"` - Name string `xorm:"VARCHAR(255)"` - Type string `xorm:"NUMBER"` - Timestamp string `xorm:"VARCHAR(255)"` - Status string `xorm:"NUMBER"` - Log string `xorm:"JSON"` - Remark string `xorm:"VARCHAR(255)"` - Createdat time.Time `xorm:"not null DATETIME created"` - Updatedat time.Time `xorm:"not null DATETIME updated"` + Id int `xorm:"pk autoincr INTEGER" json:"id,omitempty"` + Name string `xorm:"TEXT" json:"name,omitempty"` + Type int `xorm:"INTEGER" json:"type,omitempty"` + Timestamp string `xorm:"TEXT" json:"timestamp,omitempty"` + Status int `xorm:"INTEGER" json:"status,omitempty"` + Log []string `xorm:"JSON" json:"log,omitempty"` + Remark string `xorm:"TEXT" json:"remark,omitempty"` + Createdat time.Time `xorm:"not null DATETIME created" json:"createdAt"` + Updatedat time.Time `xorm:"not null DATETIME updated" json:"updatedAt"` +} + +func QueryDependences(searchValue string) ([]*Dependences, error) { + dep := make([]*Dependences, 0) + session := engine.Table(new(Dependences)). + Where( + builder.Like{"name", "%" + searchValue + "%"}) + err := session.Find(&dep) + if err != nil { + return nil, err + } + return dep, err + +} + +func AddDependences(dep *Dependences) (int, error) { + _, err := engine.Table(dep).Insert(dep) + if err != nil { + return 0, err + } + _, _ = engine.Where("name=?", dep.Name).Get(dep) + return dep.Id, err +} + +func GetDependences(id int) (*Dependences, error) { + env := new(Dependences) + _, err := engine.ID(id).Get(env) + return env, err +} + +func UpdateDependences(dep *Dependences) error { + _, err := engine.Table(dep).ID(dep.Id).AllCols().Update(dep) + return err +} + +func DeleteDependences(id int) error { + _, err := engine.Table(new(Dependences)).Delete(&Dependences{Id: id}) + return err } diff --git a/models/Subscriptions.go b/models/Subscriptions.go index 7d7812f..b3caf87 100644 --- a/models/Subscriptions.go +++ b/models/Subscriptions.go @@ -1,31 +1,80 @@ package models import ( - "time" + "fmt" + "xorm.io/builder" ) type Subscriptions struct { - Id int `xorm:"pk autoincr INTEGER"` - Name string `xorm:"VARCHAR(255)"` - Url string `xorm:"VARCHAR(255)"` - Schedule string `xorm:"VARCHAR(255)"` - IntervalSchedule string `xorm:"JSON"` - Type string `xorm:"VARCHAR(255)"` - Whitelist string `xorm:"VARCHAR(255)"` - Blacklist string `xorm:"VARCHAR(255)"` - Status string `xorm:"NUMBER"` - Dependences string `xorm:"VARCHAR(255)"` - Extensions string `xorm:"VARCHAR(255)"` - SubBefore string `xorm:"VARCHAR(255)"` - SubAfter string `xorm:"VARCHAR(255)"` - Branch string `xorm:"VARCHAR(255)"` - PullType string `xorm:"VARCHAR(255)"` - PullOption string `xorm:"JSON"` - Pid string `xorm:"NUMBER"` - IsDisabled string `xorm:"NUMBER"` - LogPath string `xorm:"VARCHAR(255)"` - ScheduleType string `xorm:"VARCHAR(255)"` - Alias string `xorm:"VARCHAR(255) unique"` - Createdat time.Time `xorm:"not null DATETIME created"` - Updatedat time.Time `xorm:"not null DATETIME updated"` + Id int `xorm:"pk autoincr INTEGER" json:"id,omitempty"` + Name string `xorm:"TEXT" json:"name,omitempty"` + Url string `xorm:"TEXT" json:"url,omitempty"` + Schedule string `xorm:"TEXT" json:"schedule,omitempty"` + IntervalSchedule map[string]any `xorm:"JSON" json:"interval_schedule,omitempty"` + Type string `xorm:"TEXT" json:"type,omitempty"` + Whitelist string `xorm:"TEXT" json:"whitelist,omitempty"` + Blacklist string `xorm:"TEXT" json:"blacklist,omitempty"` + Status int `xorm:"INTEGER default(1)" json:"status,omitempty"` + Dependences string `xorm:"TEXT" json:"dependences,omitempty"` + Extensions string `xorm:"TEXT" json:"extensions,omitempty"` + SubBefore string `xorm:"TEXT" json:"sub_before,omitempty"` + SubAfter string `xorm:"TEXT" json:"sub_after,omitempty"` + Branch string `xorm:"TEXT" json:"branch,omitempty"` + PullType string `xorm:"TEXT" json:"pull_type,omitempty"` + PullOption string `xorm:"JSON" json:"pull_option,omitempty"` + Pid int `xorm:"INTEGER" json:"pid,omitempty"` + IsDisabled int `xorm:"INTEGER" json:"is_disabled,omitempty"` + LogPath string `xorm:"TEXT" json:"log_path,omitempty"` + ScheduleType string `xorm:"TEXT" json:"schedule_type,omitempty"` + Alias string `xorm:"TEXT unique" json:"alias,omitempty"` + Createdat string `xorm:"not null DATETIME created" json:"createdat,omitempty"` + Updatedat string `xorm:"not null DATETIME updated" json:"updatedat,omitempty"` +} + +func QuerySubscription(searchValue string) ([]*Subscriptions, error) { + subscription := make([]*Subscriptions, 0) + session := engine.Table(new(Subscriptions)). + Where( + builder.Like{"name", "%" + searchValue + "%"}. + Or(builder.Like{"url", "%" + searchValue + "%"})) + err := session.Find(&subscription) + if err != nil { + return nil, err + } + return subscription, err + +} + +func AddSubscription(subscription *Subscriptions) (int, error) { + _, err := engine.Table(subscription).Insert(subscription) + if err != nil { + return 0, err + } + _, _ = engine.Where("name=?", subscription.Name).Get(subscription) + return subscription.Id, err +} + +func GetSubscription(id int) (*Subscriptions, error) { + env := new(Subscriptions) + _, err := engine.ID(id).Get(env) + return env, err +} + +func UpdateSubscription(subscription *Subscriptions) error { + _, err := engine.Table(subscription).ID(subscription.Id).AllCols().Update(subscription) + return err +} + +func DeleteSubscription(id int) error { + _, err := engine.Table(new(Subscriptions)).Delete(&Subscriptions{Id: id}) + return err +} + +func (s *Subscriptions) GetCron() string { + if s.ScheduleType == "interval" { + t := s.IntervalSchedule["type"].(string) + return fmt.Sprintf("@every %v%s", s.IntervalSchedule["value"], string(t[0])) + } else { + return s.Schedule + } } diff --git a/models/models.go b/models/models.go index 5d4483c..95b78b9 100644 --- a/models/models.go +++ b/models/models.go @@ -4,6 +4,7 @@ import ( log2 "github.com/huoxue1/qinglong-go/utils/log" log "github.com/sirupsen/logrus" _ "modernc.org/sqlite" + "os" "xorm.io/xorm" ) @@ -12,7 +13,8 @@ var ( ) func init() { - en, err := xorm.NewEngine("sqlite", "./data/db/database.sqlite") + _ = os.MkdirAll("data/db", 0666) + en, err := xorm.NewEngine("sqlite", "data/db/database.sqlite") if err != nil { log.Errorln("[sql] " + err.Error()) return diff --git a/service/config/config.go b/service/config/config.go new file mode 100644 index 0000000..943bdbc --- /dev/null +++ b/service/config/config.go @@ -0,0 +1,20 @@ +package config + +import ( + "os" + "path" + "regexp" +) + +func GetKey(key string) string { + file, err := os.ReadFile(path.Join("data", "config", "config.sh")) + if err != nil { + return "" + } + compile := regexp.MustCompile(key + `="(.*?)"`) + if !compile.Match(file) { + return "" + } + datas := compile.FindAllStringSubmatch(string(file), 1) + return datas[0][1] +} diff --git a/service/config/config_test.go b/service/config/config_test.go new file mode 100644 index 0000000..cad35a8 --- /dev/null +++ b/service/config/config_test.go @@ -0,0 +1,7 @@ +package config + +import "testing" + +func TestGetKey(t *testing.T) { + println(GetKey("AutoDelCron")) +} diff --git a/service/cron/cron.go b/service/cron/cron.go index 9775515..4f4df08 100644 --- a/service/cron/cron.go +++ b/service/cron/cron.go @@ -39,7 +39,9 @@ func UpdateCron(c1 *models.Crontabs) error { crontabs.Updatedat = time.Now().Format(time.RFC3339) c, _ := manager.Load(c1.Id) - c.(*cron.Cron).Stop() + if c != nil { + c.(*cron.Cron).Stop() + } AddTask(c1) return models.UpdateCron(crontabs) diff --git a/service/cron/manager.go b/service/cron/manager.go index 5ad0711..544f709 100644 --- a/service/cron/manager.go +++ b/service/cron/manager.go @@ -1,10 +1,12 @@ package cron import ( + "context" "fmt" "github.com/google/uuid" "github.com/huoxue1/qinglong-go/models" "github.com/huoxue1/qinglong-go/service/env" + "github.com/huoxue1/qinglong-go/utils" "github.com/robfig/cron/v3" log "github.com/sirupsen/logrus" "io" @@ -48,8 +50,8 @@ func stopCron(crontabs *models.Crontabs) { if !ok { return } - cmd := value.(*exec.Cmd) - _ = cmd.Process.Kill() + cancel := value.(func()) + cancel() } func runCron(crontabs *models.Crontabs) { @@ -72,7 +74,7 @@ func runCron(crontabs *models.Crontabs) { } values := strings.Split(value, "&") for _, index := range indexs { - logFile, _ := os.OpenFile("data/log/"+time.Now().Format("2006-01-02")+"/"+crontabs.Name+".log", os.O_RDWR, 0666) + logFile, _ := os.OpenFile("data/log/"+time.Now().Format("2006-01-02")+"/"+crontabs.Name+"_"+uuid.New().String()+".log", os.O_RDWR|os.O_CREATE, 0666) e2 := exec.Command(strings.Split(ta.cmd, " ")[0], strings.Split(ta.cmd, " ")[1:]...) e2.Env = []string{e + "=" + values[index]} stdoutPipe, _ := e2.StdoutPipe() @@ -87,7 +89,7 @@ func runCron(crontabs *models.Crontabs) { if err != nil { log.Errorln(err.Error()) } - go syncLog(stdoutPipe, logFile) + go io.Copy(logFile, stdoutPipe) e2.Wait() }() @@ -101,29 +103,28 @@ func runCron(crontabs *models.Crontabs) { crontabs.LogPath = "data/log/" + time.Now().Format("2006-01-02") + "/" + crontabs.Name + "_" + uuid.New().String() + ".log" crontabs.Status = 0 models.UpdateCron(crontabs) + cancelChan := make(chan int, 1) + ctx := context.WithValue(context.Background(), "cancel", cancelChan) + execManager.Store(crontabs.Id, func() { + cancelChan <- 1 + }) + now := time.Now() _ = os.Mkdir("data/log/"+time.Now().Format("2006-01-02"), 0666) - logFile := &myWriter{crontabs.LogPath} - - e2 := exec.Command(strings.Split(ta.cmd, " ")[0], strings.Split(ta.cmd, " ")[1:]...) - execManager.Store(crontabs.Id, e2) - stdoutPipe, _ := e2.StdoutPipe() - for s, s2 := range envFromDb { - e2.Env = append(e2.Env, s+"="+s2) - } - startTime := time.Now() - logFile.Write([]byte(fmt.Sprintf("##开始执行.. %s\n\n", startTime.Format("2006-01-02 15:04:05")))) - go func() { - err := e2.Start() - if err != nil { - log.Errorln(err.Error()) - } - go syncLog(stdoutPipe, logFile) - e2.Wait() - logFile.Write([]byte(fmt.Sprintf("##执行结束.. %s,耗时%.1f秒\n\n", time.Now().Format("2006-01-02 15:04:05"), time.Now().Sub(startTime).Seconds()))) - crontabs.Status = 1 - models.UpdateCron(crontabs) - execManager.LoadAndDelete(crontabs.Id) - }() + file, _ := os.OpenFile(crontabs.LogPath, os.O_RDWR|os.O_CREATE, 0666) + go utils.RunTask(ctx, ta.cmd, envFromDb, + func(ctx context.Context) { + writer := ctx.Value("log").(io.Writer) + writer.Write([]byte(fmt.Sprintf("##开始执行.. %s\n\n", now.Format("2006-01-02 15:04:05")))) + }, func(ctx context.Context) { + writer := ctx.Value("log").(io.Writer) + writer.Write([]byte(fmt.Sprintf("\n##执行结束.. %s,耗时%.1f秒\n\n", time.Now().Format("2006-01-02 15:04:05"), time.Now().Sub(now).Seconds()))) + crontabs.Status = 1 + crontabs.LastExecutionTime = now.Unix() + crontabs.LastRunningTime = int64(time.Now().Sub(now).Seconds()) + models.UpdateCron(crontabs) + execManager.LoadAndDelete(crontabs.Id) + file.Close() + }, file) } } @@ -145,11 +146,11 @@ func handCommand(command string) *task { commands := strings.Split(command, " ") if commands[0] == "task" { if strings.HasSuffix(commands[1], ".py") { - ta.cmd = "python data/scripts/" + commands[1] + ta.cmd = "python " + commands[1] } else if strings.HasSuffix(commands[1], ".js") { - ta.cmd = "node data/scripts/" + commands[1] + ta.cmd = "node " + commands[1] } else if strings.HasSuffix(commands[1], ".sh") { - ta.cmd = "bash data/scripts/" + commands[1] + ta.cmd = "bash " + commands[1] } else if strings.HasSuffix(commands[1], ".ts") { ta.cmd = "ts-node-transpile-only data/scripts/" + commands[1] } @@ -189,31 +190,31 @@ func handCommand(command string) *task { return ta } -type myWriter struct { - fileName string -} - -func (m *myWriter) Write(p []byte) (n int, err error) { - file, _ := os.OpenFile(m.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) - n, err = file.Write(p) - file.Close() - return n, err -} - -//通过管道同步获取日志的函数 -func syncLog(reader io.ReadCloser, writer io.Writer) { - buf := make([]byte, 1) - for { - strNum, err := reader.Read(buf) - if strNum > 0 { - outputByte := buf[:strNum] - writer.Write(outputByte) - } - if err != nil { - //读到结尾 - if err == io.EOF || strings.Contains(err.Error(), "file already closed") { - return - } - } - } -} +//type myWriter struct { +// fileName string +//} +// +//func (m *myWriter) Write(p []byte) (n int, err error) { +// file, _ := os.OpenFile(m.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) +// n, err = file.Write(p) +// file.Close() +// return n, err +//} +// +////通过管道同步获取日志的函数 +//func syncLog(reader io.ReadCloser, writer io.Writer) { +// buf := make([]byte, 1) +// for { +// strNum, err := reader.Read(buf) +// if strNum > 0 { +// outputByte := buf[:strNum] +// writer.Write(outputByte) +// } +// if err != nil { +// //读到结尾 +// if err == io.EOF || strings.Contains(err.Error(), "file already closed") { +// return +// } +// } +// } +//} diff --git a/service/dependencies/dependencies.go b/service/dependencies/dependencies.go new file mode 100644 index 0000000..8277777 --- /dev/null +++ b/service/dependencies/dependencies.go @@ -0,0 +1,39 @@ +package dependencies + +import ( + "bytes" + "context" + "fmt" + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/utils" + "io" + "strings" + "time" +) + +func AddDep(dep *models.Dependences) { + if dep.Type == models.NODE { + addNodeDep(dep) + } +} + +func addNodeDep(dep *models.Dependences) { + log := "" + buffer := bytes.NewBufferString(log) + ctx := context.WithValue(context.Background(), "cancel", make(chan int, 1)) + now := time.Now() + utils.RunTask(ctx, fmt.Sprintf("yarn add %s", dep.Name), map[string]string{}, func(ctx context.Context) { + writer := ctx.Value("log").(io.Writer) + writer.Write([]byte(fmt.Sprintf("##开始执行.. %s\n\n", now.Format("2006-01-02 15:04:05")))) + }, func(ctx context.Context) { + writer := ctx.Value("log").(io.Writer) + writer.Write([]byte(fmt.Sprintf("\n##执行结束.. %s,耗时%.1f秒\n\n", time.Now().Format("2006-01-02 15:04:05"), time.Now().Sub(now).Seconds()))) + dep.Status = 1 + var logs []string + for _, i2 := range strings.Split(buffer.String(), "\n") { + logs = append(logs, i2+"\n\n") + } + dep.Log = logs + models.AddDependences(dep) + }, buffer) +} diff --git a/service/open/open.go b/service/open/open.go new file mode 100644 index 0000000..c5a726e --- /dev/null +++ b/service/open/open.go @@ -0,0 +1,48 @@ +package open + +import ( + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/utils" +) + +func AddApp(apps *models.Apps) (int, error) { + apps.ClientId = utils.RandomString(6) + apps.ClientSecret = utils.RandomString(12) + apps.Tokens = []string{} + id, err := models.AddApp(apps) + if err != nil { + return 0, err + } + return id, nil +} + +func UpdateApp(apps *models.Apps) error { + app, err := models.GetApp(apps.Id) + if err != nil { + return err + } + app.Name = apps.Name + app.Scopes = apps.Scopes + err = models.UpdateApp(app) + if err != nil { + return err + } + return nil +} + +func ResetApp(apps *models.Apps) error { + apps.ClientSecret = utils.RandomString(12) + apps.Tokens = []string{} + err := models.UpdateApp(apps) + return err +} + +func DeleteApp(ids []int) error { + for _, id := range ids { + err := models.DeleteApp(id) + if err != nil { + return err + } + } + return nil +} diff --git a/service/scripts/scripts.go b/service/scripts/scripts.go index 306651b..76ff601 100644 --- a/service/scripts/scripts.go +++ b/service/scripts/scripts.go @@ -1,6 +1,12 @@ package scripts -import "os" +import ( + "bytes" + "github.com/huoxue1/qinglong-go/utils" + "os" + "path" + "sort" +) type File struct { Key string `json:"key"` @@ -11,48 +17,38 @@ type File struct { Children []*File `json:"children"` } -func GetFiles() []*File { - var files []*File - dir, err := os.ReadDir("data/scripts/") +var ( + excludedFiles = []string{ + "node_modules", + "__pycache__", + } +) + +func GetFiles(base, p string) []*File { + var files Files + dir, err := os.ReadDir(path.Join(base, p)) if err != nil { return []*File{} } for _, entry := range dir { + if utils.In(entry.Name(), excludedFiles) { + continue + } if entry.IsDir() { f := &File{ - Key: entry.Name(), - Parent: "", + Key: path.Join(p, entry.Name()), + Parent: p, Title: entry.Name(), Type: "directory", IsLeaf: true, - Children: []*File{}, - } - twoDir, err := os.ReadDir("data/scripts/" + entry.Name()) - if err != nil { - continue - } - for _, dirEntry := range twoDir { - f.Children = append(f.Children, &File{ - Key: entry.Name() + "/" + dirEntry.Name(), - Parent: entry.Name(), - Title: dirEntry.Name(), - Type: func() string { - if dirEntry.IsDir() { - return "directory" - } else { - return "file" - } - }(), - IsLeaf: true, - Children: []*File{}, - }) + Children: GetFiles(base, path.Join(p, entry.Name())), } files = append(files, f) } else { files = append(files, &File{ - Key: entry.Name(), - Parent: "", + Key: path.Join(p, entry.Name()), + Parent: p, Title: entry.Name(), Type: "file", IsLeaf: true, @@ -60,5 +56,26 @@ func GetFiles() []*File { }) } } + sort.Sort(files) return files } + +type Files []*File + +func (a Files) Len() int { // 重写 Len() 方法 + return len(a) +} +func (a Files) Swap(i, j int) { // 重写 Swap() 方法 + a[i], a[j] = a[j], a[i] +} +func (a Files) Less(i, j int) bool { // 重写 Less() 方法, 从大到小排序 + if a[i].Type != a[j].Type { + if a[i].Type == "file" { + return false + } else { + return true + } + } else { + return bytes.Compare([]byte(a[i].Title), []byte(a[j].Title)) > 0 + } +} diff --git a/service/subscription/manager.go b/service/subscription/manager.go new file mode 100644 index 0000000..4c9b041 --- /dev/null +++ b/service/subscription/manager.go @@ -0,0 +1,163 @@ +package subscription + +import ( + "errors" + "fmt" + "github.com/google/uuid" + "github.com/huoxue1/qinglong-go/models" + "github.com/huoxue1/qinglong-go/service/config" + "github.com/huoxue1/qinglong-go/service/cron" + "github.com/huoxue1/qinglong-go/utils" + "io" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strings" + "sync" + "time" +) + +var ( + manager sync.Map +) + +func stopSubscription(sub *models.Subscriptions) { + defer func() { + _ = recover() + }() + sub.Status = 1 + _ = models.UpdateSubscription(sub) + value, ok := manager.Load(sub.Id) + if !ok { + return + } + cancel := value.(func()) + cancel() +} + +func downloadFiles(subscriptions *models.Subscriptions) { + if subscriptions.Type == "public-repo" { + os.RemoveAll(path.Join("data", "scripts", subscriptions.Alias)) + os.RemoveAll(path.Join("data", "repo", subscriptions.Alias)) + err := downloadPublicRepo(subscriptions) + if err != nil { + return + } + addScripts(subscriptions) + file, _ := os.OpenFile(subscriptions.LogPath, os.O_APPEND|os.O_RDWR, 0666) + file.WriteString(fmt.Sprintf("\n##执行结束.. %s,耗时0秒\n\n", time.Now().Format("2006-01-02 15:04:05"))) + _ = file.Close() + subscriptions.Status = 1 + models.UpdateSubscription(subscriptions) + } +} + +func downloadPublicRepo(subscriptions *models.Subscriptions) error { + subscriptions.LogPath = "data/log/" + time.Now().Format("2006-01-02") + "/" + subscriptions.Alias + "_" + uuid.New().String() + ".log" + _ = os.MkdirAll(filepath.Dir(subscriptions.LogPath), 0666) + cmd := fmt.Sprintf("clone -b %s --single-branch %s %s", subscriptions.Branch, subscriptions.Url, path.Join("data", "repo", subscriptions.Alias)) + command := exec.Command("git", strings.Split(cmd, " ")...) + pipe, err := command.StdoutPipe() + stderrPipe, _ := command.StderrPipe() + if err != nil { + return err + } + subscriptions.Status = 0 + err = models.UpdateSubscription(subscriptions) + if err != nil { + return err + } + file, _ := os.OpenFile(subscriptions.LogPath, os.O_CREATE|os.O_RDWR, 0666) + file.Write([]byte(fmt.Sprintf("##开始执行.. %s\n\n", time.Now().Format("2006-01-02 15:04:05")))) + err = command.Start() + if err != nil { + return err + } + manager.Store(subscriptions.Id, func() { + command.Process.Kill() + }) + defer manager.LoadAndDelete(subscriptions.Id) + go io.Copy(io.MultiWriter(file, os.Stdout), pipe) + go io.Copy(file, stderrPipe) + command.Wait() + return err +} + +func addScripts(subscriptions *models.Subscriptions) { + file, _ := os.OpenFile(subscriptions.LogPath, os.O_CREATE|os.O_RDWR, 0666) + defer file.Close() + var extensions []string + if subscriptions.Extensions != "" { + extensions = strings.Split(subscriptions.Extensions, " ") + } else { + extensions = strings.Split(config.GetKey("RepoFileExtensions"), " ") + } + dir, err := os.ReadDir(path.Join("data", "repo", subscriptions.Alias)) + if err != nil { + return + } + for _, entry := range dir { + // 判断文件后缀 + if !utils.In(strings.TrimPrefix(filepath.Ext(entry.Name()), "."), extensions) { + if !entry.IsDir() { + continue + } + } + // 判断黑名单 + if utils.In(entry.Name(), strings.Split(subscriptions.Blacklist, "|")) { + continue + } + compile := regexp.MustCompile(`(` + subscriptions.Whitelist + `)`) + if compile.MatchString(entry.Name()) { + name, c, _ := getSubCron(path.Join("data", "repo", subscriptions.Alias, entry.Name())) + if c != "" { + command, err := models.GetCronByCommand(fmt.Sprintf("task %s", path.Join(subscriptions.Alias, entry.Name()))) + if err != nil { + file.WriteString("已添加新的定时任务 " + name + "\n") + _, _ = cron.AddCron(&models.Crontabs{ + Name: name, + Command: fmt.Sprintf("task %s", path.Join(subscriptions.Alias, entry.Name())), + Schedule: c, + Timestamp: time.Now().Format("Mon Jan 02 2006 15:04:05 MST"), + Status: 1, + Labels: []string{}, + }) + } else { + command.Name = name + command.Schedule = c + _ = cron.UpdateCron(command) + } + + } + utils.Copy(path.Join("data", "repo", subscriptions.Alias, entry.Name()), path.Join("data", "scripts", subscriptions.Alias, entry.Name())) + } else { + depen := regexp.MustCompile(`(` + subscriptions.Dependences + `)`) + if depen.MatchString(entry.Name()) { + utils.Copy(path.Join("data", "repo", subscriptions.Alias, entry.Name()), path.Join("data", "scripts", subscriptions.Alias, entry.Name())) + } + } + } +} + +func getSubCron(filePath string) (name string, cron string, err error) { + data, err := os.ReadFile(filePath) + if err != nil { + return "", "", err + } + cronReg := regexp.MustCompile(`([0-9\-*/,]{1,} ){4,5}([0-9\-*/,]){1,}`) + nameEnv := regexp.MustCompile(`new\sEnv\(['|"](.*?)['|"]\)`) + if cronReg.Match(data) { + cron = string(cronReg.FindAll(data, 1)[0]) + cron = strings.TrimPrefix(cron, "//") + if nameEnv.Match(data) { + name = string(nameEnv.FindAllSubmatch(data, 1)[0][1]) + } else { + name = path.Base(filePath) + } + } else { + return "", "", errors.New("not found cron") + } + return +} diff --git a/service/subscription/subscription.go b/service/subscription/subscription.go new file mode 100644 index 0000000..4239036 --- /dev/null +++ b/service/subscription/subscription.go @@ -0,0 +1,82 @@ +package subscription + +import "github.com/huoxue1/qinglong-go/models" + +var ( + DISABLESTATUS = 1 + ENABLESTATUS = 0 +) + +func AddSubscription(subscriptions *models.Subscriptions) (int, error) { + subscriptions.Status = 1 + return models.AddSubscription(subscriptions) + +} + +func UpdateSubscription(subscriptions *models.Subscriptions) error { + return models.UpdateSubscription(subscriptions) + +} + +func DeleteSubscription(ids []int) error { + for _, id := range ids { + err := models.DeleteSubscription(id) + if err != nil { + return err + } + } + return nil +} + +func DisableSubscription(ids []int) error { + for _, id := range ids { + sub, err := models.GetSubscription(id) + if err != nil { + continue + } + sub.IsDisabled = 1 + err = models.UpdateSubscription(sub) + if err != nil { + return err + } + } + return nil +} + +func EnableSubscription(ids []int) error { + for _, id := range ids { + sub, err := models.GetSubscription(id) + if err != nil { + continue + } + sub.IsDisabled = 0 + err = models.UpdateSubscription(sub) + if err != nil { + return err + } + } + return nil +} + +func RunSubscription(ids []int) error { + for _, id := range ids { + sub, err := models.GetSubscription(id) + if err != nil { + continue + } + sub.IsDisabled = 0 + go downloadFiles(sub) + } + return nil +} + +func StopSubscription(ids []int) error { + for _, id := range ids { + sub, err := models.GetSubscription(id) + if err != nil { + continue + } + stopSubscription(sub) + } + return nil +} diff --git a/utils/array.go b/utils/array.go new file mode 100644 index 0000000..104e621 --- /dev/null +++ b/utils/array.go @@ -0,0 +1,10 @@ +package utils + +func In[T comparable](data T, array []T) bool { + for _, t := range array { + if t == data { + return true + } + } + return false +} diff --git a/utils/file.go b/utils/file.go new file mode 100644 index 0000000..f1449fb --- /dev/null +++ b/utils/file.go @@ -0,0 +1,50 @@ +package utils + +import ( + log "github.com/sirupsen/logrus" + "io" + "os" + "path" + "path/filepath" +) + +func Copy(src, dest string) { + srcInfo, err := os.Stat(src) + if err != nil { + return + } + _, err = os.Stat(dest) + if os.IsNotExist(err) { + if srcInfo.IsDir() { + _ = os.MkdirAll(dest, 0666) + } + } + if srcInfo.IsDir() { + log.Infoln("复制文件") + dir, err := os.ReadDir(src) + if err != nil { + return + } + for _, entry := range dir { + if entry.IsDir() { + Copy(path.Join(src, entry.Name()), path.Join(dest, entry.Name())) + continue + } else { + file, _ := os.Open(path.Join(src, entry.Name())) + newFile, _ := os.OpenFile(path.Join(dest, entry.Name()), os.O_RDWR|os.O_CREATE, 0666) + _, err := io.Copy(newFile, file) + if err != nil { + return + } + } + } + } else { + _ = os.MkdirAll(filepath.Dir(dest), 0666) + file, _ := os.Open(src) + newFile, _ := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0666) + _, err := io.Copy(newFile, file) + if err != nil { + return + } + } +} diff --git a/utils/jwt.go b/utils/jwt.go index c230a7c..887b4b1 100644 --- a/utils/jwt.go +++ b/utils/jwt.go @@ -3,14 +3,8 @@ package utils // jwt身份验证demo import ( - "encoding/json" - "errors" - jwt "github.com/dgrijalva/jwt-go" - "github.com/gin-gonic/gin" - "github.com/huoxue1/qinglong-go/models" - "github.com/huoxue1/qinglong-go/utils/res" - "os" - "path" + "github.com/dgrijalva/jwt-go" + "math/rand" "strings" "time" ) @@ -24,9 +18,9 @@ type Claims struct { } // GenerateToken 生成token的函数 -func GenerateToken(userid string) (string, error) { +func GenerateToken(userid string, hour int) (string, error) { nowTime := time.Now() - expireTime := nowTime.Add(48 * time.Hour) + expireTime := nowTime.Add(time.Duration(hour) * time.Hour) claims := Claims{ userid, // 自行添加的信息 @@ -59,68 +53,6 @@ func ParseToken(token string) (*Claims, error) { return nil, err } -var ( - unExcludedPath = []string{ - "/api/system", - "/api/user/login", - "/api/user/init", - } -) - -func Jwt() gin.HandlerFunc { - return func(ctx *gin.Context) { - for _, s := range unExcludedPath { - if strings.HasPrefix(ctx.Request.URL.Path, s) { - ctx.Next() - return - } - } - - data, err := os.ReadFile(path.Join("data", "config", "auth.json")) - if err != nil { - ctx.Abort() - return - } - auth := new(models.AuthFile) - _ = json.Unmarshal(data, auth) - tokenHeader := ctx.GetHeader("Authorization") - if tokenHeader == "" { - ctx.JSON(401, res.Err(401, errors.New("no authorization token was found"))) - ctx.Abort() - return - } - authToken := strings.Split(tokenHeader, " ")[1] - - mobile := IsMobile(ctx.GetHeader("User-Agent")) - claims, _ := ParseToken(authToken) - if claims.ExpiresAt < time.Now().Unix() { - ctx.JSON(401, res.Err(401, errors.New("the authorization token is expired"))) - ctx.Abort() - return - } - if mobile { - if authToken != auth.Tokens.Mobile && authToken != auth.Token { - ctx.JSON(401, res.Err(401, errors.New("the authorization token is error"))) - ctx.Abort() - return - } else { - ctx.Next() - return - } - } else { - if authToken != auth.Tokens.Desktop && authToken != auth.Token { - ctx.JSON(401, res.Err(401, errors.New("the authorization token is error"))) - ctx.Abort() - return - } else { - ctx.Next() - return - } - } - - } -} - func IsMobile(userAgent string) bool { if len(userAgent) == 0 { return false @@ -139,3 +71,13 @@ func IsMobile(userAgent string) bool { return isMobile } + +func RandomString(n int) string { + var letter = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-?") + rand.Seed(time.Now().UnixMilli()) + b := make([]rune, n) + for i := range b { + b[i] = letter[rand.Intn(len(letter))] + } + return string(b) +} diff --git a/utils/run.go b/utils/run.go new file mode 100644 index 0000000..d4e1847 --- /dev/null +++ b/utils/run.go @@ -0,0 +1,53 @@ +package utils + +import ( + "context" + "io" + "os" + "os/exec" + "strings" +) + +type Context struct { + process *os.Process +} + +func RunTask(ctx context.Context, command string, env map[string]string, onStart func(ctx context.Context), onEnd func(ctx context.Context), logFile io.Writer) { + cmd := exec.Command(strings.Split(command, " ")[0], strings.Split(command, " ")[1:]...) + for s, s2 := range env { + cmd.Env = append(cmd.Env, s+"="+s2) + } + stdoutPipe, _ := cmd.StdoutPipe() + stderrPipe, _ := cmd.StderrPipe() + cmd.Dir = "./data/scripts/" + onStart(context.WithValue(ctx, "log", logFile)) + ch := make(chan int, 1) + go func() { + err := cmd.Start() + if err != nil { + ch <- 1 + return + } + go io.Copy(logFile, stderrPipe) + go io.Copy(logFile, stdoutPipe) + err = cmd.Wait() + if err != nil { + ch <- 1 + return + } + ch <- 1 + }() + cancel := ctx.Value("cancel").(chan int) + select { + case <-ch: + { + onEnd(context.WithValue(ctx, "log", logFile)) + } + case <-cancel: + { + _ = cmd.Process.Kill() + onEnd(context.WithValue(context.Background(), "log", logFile)) + } + + } +}