添加功能

This commit is contained in:
3343780376 2022-11-20 22:11:47 +08:00
parent 0f75278f65
commit 44c7487099
36 changed files with 1557 additions and 216 deletions

View File

@ -7,10 +7,12 @@
- [x] 环境变量的增删改查 - [x] 环境变量的增删改查
- [x] 配置文件的编辑 - [x] 配置文件的编辑
- [x] 脚本文件的增删改查 - [x] 脚本文件的增删改查
- [ ] 定时任务的视图筛选功能 - [x] 应用授权登录的实现
- [ ] 订阅功能的实现 - [x] 订阅功能的实现
- [x] 日志功能的实现
- [x] 依赖功能的实现
- [ ] 推送功能的实现 - [ ] 推送功能的实现
- [ ] 应用授权登录的实现 - [ ] 定时任务的视图筛选功能
- [ ] 热更新的实现 - [ ] 热更新的实现

View File

@ -4,8 +4,12 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/huoxue1/qinglong-go/api/config" "github.com/huoxue1/qinglong-go/api/config"
"github.com/huoxue1/qinglong-go/api/cron" "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/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/scripts"
"github.com/huoxue1/qinglong-go/api/subscription"
"github.com/huoxue1/qinglong-go/api/system" "github.com/huoxue1/qinglong-go/api/system"
"github.com/huoxue1/qinglong-go/api/user" "github.com/huoxue1/qinglong-go/api/user"
) )
@ -17,4 +21,8 @@ func Api(group *gin.RouterGroup) {
env.Api(group.Group("/envs")) env.Api(group.Group("/envs"))
config.Api(group.Group("/configs")) config.Api(group.Group("/configs"))
scripts.Api(group.Group("/scripts")) 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"))
} }

View File

@ -40,7 +40,7 @@ func get() gin.HandlerFunc {
} }
ctx.JSON(200, res.Ok(gin.H{ ctx.JSON(200, res.Ok(gin.H{
"data": crons, "data": crons,
"total": len(crons), "total": models.Count(ctx.Query("searchValue")),
})) }))
} }
} }

View File

@ -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))
}
}

32
api/logs/log.go Normal file
View File

@ -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)))
}
}

113
api/midware.go Normal file
View File

@ -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
}
}
}
}

27
api/open.go Normal file
View File

@ -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"))
}

40
api/open/auth.go Normal file
View File

@ -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(),
}))
}
}

96
api/open/open.go Normal file
View File

@ -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))
}
}

View File

@ -19,7 +19,7 @@ func Api(group *gin.RouterGroup) {
func get() gin.HandlerFunc { func get() gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
files := scripts.GetFiles() files := scripts.GetFiles(path2.Join("data", "scripts"), "")
ctx.JSON(200, res.Ok(files)) ctx.JSON(200, res.Ok(files))
} }
} }

View File

@ -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)))
}
}

View File

@ -9,15 +9,15 @@ import (
) )
func Api(group *gin.RouterGroup) { func Api(group *gin.RouterGroup) {
group.GET("/", get()) group.GET("", get())
} }
func get() gin.HandlerFunc { func get() gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
_, err := os.Stat(path.Join("data", "config", "auth.json")) _, err := os.Stat(path.Join("data", "config", "auth.json"))
exist := os.IsExist(err) exist := os.IsNotExist(err)
ctx.JSON(200, res.Ok(system.System{ ctx.JSON(200, res.Ok(system.System{
IsInitialized: exist, IsInitialized: !exist,
Version: "2.0.14", Version: "2.0.14",
LastCommitTime: "", LastCommitTime: "",
LastCommitId: "", LastCommitId: "",

163
api/user/config_sample.sh Normal file
View File

@ -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
## 下方填写素材库图片idcorpid,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 变量名= 声明即可

View File

@ -1,6 +1,7 @@
package user package user
import ( import (
_ "embed"
"encoding/json" "encoding/json"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/huoxue1/qinglong-go/models" "github.com/huoxue1/qinglong-go/models"
@ -12,6 +13,9 @@ import (
"time" "time"
) )
//go:embed config_sample.sh
var sample []byte
func Api(group *gin.RouterGroup) { func Api(group *gin.RouterGroup) {
group.GET("/", get()) group.GET("/", get())
group.PUT("/init", appInit()) group.PUT("/init", appInit())
@ -37,6 +41,12 @@ func appInit() gin.HandlerFunc {
ctx.JSON(400, res.Err(400, err)) ctx.JSON(400, res.Err(400, err))
return 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 { type Req struct {
UserName string `json:"username"` UserName string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
@ -78,7 +88,7 @@ func login() gin.HandlerFunc {
auth := new(models.AuthFile) auth := new(models.AuthFile)
_ = json.Unmarshal(data, auth) _ = json.Unmarshal(data, auth)
if auth.Username == r.UserName && auth.Password == r.Password { 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 { if err != nil {
ctx.JSON(503, res.Err(503, err)) ctx.JSON(503, res.Err(503, err))
return return

View File

@ -4,12 +4,13 @@ import (
"github.com/gin-contrib/static" "github.com/gin-contrib/static"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/huoxue1/qinglong-go/api" "github.com/huoxue1/qinglong-go/api"
"github.com/huoxue1/qinglong-go/utils"
"net/http" "net/http"
) )
func Router() *gin.Engine { func Router() *gin.Engine {
engine := gin.New() engine := gin.New()
engine.Use(gin.Logger())
engine.Use(gin.Recovery())
engine.Use(static.Serve("/", static.LocalFile("static/dist/", false))) engine.Use(static.Serve("/", static.LocalFile("static/dist/", false)))
engine.NoRoute(func(ctx *gin.Context) { engine.NoRoute(func(ctx *gin.Context) {
if ctx.Request.Method == http.MethodGet { if ctx.Request.Method == http.MethodGet {
@ -18,7 +19,8 @@ func Router() *gin.Engine {
} }
ctx.Next() 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 return engine
} }

4
go.mod
View File

@ -3,10 +3,12 @@ module github.com/huoxue1/qinglong-go
go 1.18 go 1.18
require ( require (
github.com/Lyrics-you/sail-logrus-formatter v1.3.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/static v0.0.1 github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.1
github.com/google/uuid v1.3.0 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/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
modernc.org/sqlite v1.19.4 modernc.org/sqlite v1.19.4
@ -25,12 +27,14 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/leodido/go-urn v1.2.1 // 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/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.13.0 // indirect github.com/onsi/gomega v1.13.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/stretchr/testify v1.8.1 // indirect github.com/stretchr/testify v1.8.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect

11
go.sum
View File

@ -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= 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/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/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/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/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 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.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/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/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/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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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.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 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 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.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.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.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/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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

31
main.go
View File

@ -1,9 +1,40 @@
package main package main
import ( import (
nested "github.com/Lyrics-you/sail-logrus-formatter/sailor"
"github.com/huoxue1/qinglong-go/controller" "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() { func main() {
engine := controller.Router() engine := controller.Router()
_ = engine.Run(":8080") _ = engine.Run(":8080")

View File

@ -1,16 +1,53 @@
package models package models
import (
"time"
)
type Apps struct { type Apps struct {
Id int `xorm:"pk autoincr INTEGER"` Id int `xorm:"pk autoincr INTEGER" json:"id,omitempty"`
Name string `xorm:"VARCHAR(255) unique"` Name string `xorm:"TEXT unique" json:"name,omitempty"`
Scopes string `xorm:"JSON"` Scopes []string `xorm:"JSON" json:"scopes,omitempty"`
ClientId string `xorm:"VARCHAR(255)"` ClientId string `xorm:"TEXT" json:"client_id,omitempty"`
ClientSecret string `xorm:"VARCHAR(255)"` ClientSecret string `xorm:"TEXT" json:"client_secret,omitempty"`
Tokens string `xorm:"JSON"` Tokens []string `xorm:"JSON" json:"tokens,omitempty"`
Createdat time.Time `xorm:"not null DATETIME created"` Createdat string `xorm:"not null DATETIME created" json:"createdat,omitempty"`
Updatedat time.Time `xorm:"not null DATETIME updated"` 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
} }

View File

@ -1,6 +1,7 @@
package models package models
import ( import (
"errors"
"xorm.io/builder" "xorm.io/builder"
) )
@ -56,6 +57,16 @@ func FindAllEnableCron() []*Crontabs {
return 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) { func GetCron(id int) (*Crontabs, error) {
cron := new(Crontabs) cron := new(Crontabs)
_, err := engine.ID(id).Get(cron) _, err := engine.ID(id).Get(cron)
@ -80,3 +91,12 @@ func DeleteCron(id int) error {
_, err := engine.Table(new(Crontabs)).Delete(&Crontabs{Id: id}) _, err := engine.Table(new(Crontabs)).Delete(&Crontabs{Id: id})
return err 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
}

View File

@ -2,16 +2,61 @@ package models
import ( import (
"time" "time"
"xorm.io/builder"
)
const (
NODE = 0
PYTHON = 1
LINUX = 2
) )
type Dependences struct { type Dependences struct {
Id int `xorm:"pk autoincr INTEGER"` Id int `xorm:"pk autoincr INTEGER" json:"id,omitempty"`
Name string `xorm:"VARCHAR(255)"` Name string `xorm:"TEXT" json:"name,omitempty"`
Type string `xorm:"NUMBER"` Type int `xorm:"INTEGER" json:"type,omitempty"`
Timestamp string `xorm:"VARCHAR(255)"` Timestamp string `xorm:"TEXT" json:"timestamp,omitempty"`
Status string `xorm:"NUMBER"` Status int `xorm:"INTEGER" json:"status,omitempty"`
Log string `xorm:"JSON"` Log []string `xorm:"JSON" json:"log,omitempty"`
Remark string `xorm:"VARCHAR(255)"` Remark string `xorm:"TEXT" json:"remark,omitempty"`
Createdat time.Time `xorm:"not null DATETIME created"` Createdat time.Time `xorm:"not null DATETIME created" json:"createdAt"`
Updatedat time.Time `xorm:"not null DATETIME updated"` 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
} }

View File

@ -1,31 +1,80 @@
package models package models
import ( import (
"time" "fmt"
"xorm.io/builder"
) )
type Subscriptions struct { type Subscriptions struct {
Id int `xorm:"pk autoincr INTEGER"` Id int `xorm:"pk autoincr INTEGER" json:"id,omitempty"`
Name string `xorm:"VARCHAR(255)"` Name string `xorm:"TEXT" json:"name,omitempty"`
Url string `xorm:"VARCHAR(255)"` Url string `xorm:"TEXT" json:"url,omitempty"`
Schedule string `xorm:"VARCHAR(255)"` Schedule string `xorm:"TEXT" json:"schedule,omitempty"`
IntervalSchedule string `xorm:"JSON"` IntervalSchedule map[string]any `xorm:"JSON" json:"interval_schedule,omitempty"`
Type string `xorm:"VARCHAR(255)"` Type string `xorm:"TEXT" json:"type,omitempty"`
Whitelist string `xorm:"VARCHAR(255)"` Whitelist string `xorm:"TEXT" json:"whitelist,omitempty"`
Blacklist string `xorm:"VARCHAR(255)"` Blacklist string `xorm:"TEXT" json:"blacklist,omitempty"`
Status string `xorm:"NUMBER"` Status int `xorm:"INTEGER default(1)" json:"status,omitempty"`
Dependences string `xorm:"VARCHAR(255)"` Dependences string `xorm:"TEXT" json:"dependences,omitempty"`
Extensions string `xorm:"VARCHAR(255)"` Extensions string `xorm:"TEXT" json:"extensions,omitempty"`
SubBefore string `xorm:"VARCHAR(255)"` SubBefore string `xorm:"TEXT" json:"sub_before,omitempty"`
SubAfter string `xorm:"VARCHAR(255)"` SubAfter string `xorm:"TEXT" json:"sub_after,omitempty"`
Branch string `xorm:"VARCHAR(255)"` Branch string `xorm:"TEXT" json:"branch,omitempty"`
PullType string `xorm:"VARCHAR(255)"` PullType string `xorm:"TEXT" json:"pull_type,omitempty"`
PullOption string `xorm:"JSON"` PullOption string `xorm:"JSON" json:"pull_option,omitempty"`
Pid string `xorm:"NUMBER"` Pid int `xorm:"INTEGER" json:"pid,omitempty"`
IsDisabled string `xorm:"NUMBER"` IsDisabled int `xorm:"INTEGER" json:"is_disabled,omitempty"`
LogPath string `xorm:"VARCHAR(255)"` LogPath string `xorm:"TEXT" json:"log_path,omitempty"`
ScheduleType string `xorm:"VARCHAR(255)"` ScheduleType string `xorm:"TEXT" json:"schedule_type,omitempty"`
Alias string `xorm:"VARCHAR(255) unique"` Alias string `xorm:"TEXT unique" json:"alias,omitempty"`
Createdat time.Time `xorm:"not null DATETIME created"` Createdat string `xorm:"not null DATETIME created" json:"createdat,omitempty"`
Updatedat time.Time `xorm:"not null DATETIME updated"` 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
}
} }

View File

@ -4,6 +4,7 @@ import (
log2 "github.com/huoxue1/qinglong-go/utils/log" log2 "github.com/huoxue1/qinglong-go/utils/log"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
"os"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -12,7 +13,8 @@ var (
) )
func init() { 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 { if err != nil {
log.Errorln("[sql] " + err.Error()) log.Errorln("[sql] " + err.Error())
return return

20
service/config/config.go Normal file
View File

@ -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]
}

View File

@ -0,0 +1,7 @@
package config
import "testing"
func TestGetKey(t *testing.T) {
println(GetKey("AutoDelCron"))
}

View File

@ -39,7 +39,9 @@ func UpdateCron(c1 *models.Crontabs) error {
crontabs.Updatedat = time.Now().Format(time.RFC3339) crontabs.Updatedat = time.Now().Format(time.RFC3339)
c, _ := manager.Load(c1.Id) c, _ := manager.Load(c1.Id)
c.(*cron.Cron).Stop() if c != nil {
c.(*cron.Cron).Stop()
}
AddTask(c1) AddTask(c1)
return models.UpdateCron(crontabs) return models.UpdateCron(crontabs)

View File

@ -1,10 +1,12 @@
package cron package cron
import ( import (
"context"
"fmt" "fmt"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/huoxue1/qinglong-go/models" "github.com/huoxue1/qinglong-go/models"
"github.com/huoxue1/qinglong-go/service/env" "github.com/huoxue1/qinglong-go/service/env"
"github.com/huoxue1/qinglong-go/utils"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"io" "io"
@ -48,8 +50,8 @@ func stopCron(crontabs *models.Crontabs) {
if !ok { if !ok {
return return
} }
cmd := value.(*exec.Cmd) cancel := value.(func())
_ = cmd.Process.Kill() cancel()
} }
func runCron(crontabs *models.Crontabs) { func runCron(crontabs *models.Crontabs) {
@ -72,7 +74,7 @@ func runCron(crontabs *models.Crontabs) {
} }
values := strings.Split(value, "&") values := strings.Split(value, "&")
for _, index := range indexs { 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 := exec.Command(strings.Split(ta.cmd, " ")[0], strings.Split(ta.cmd, " ")[1:]...)
e2.Env = []string{e + "=" + values[index]} e2.Env = []string{e + "=" + values[index]}
stdoutPipe, _ := e2.StdoutPipe() stdoutPipe, _ := e2.StdoutPipe()
@ -87,7 +89,7 @@ func runCron(crontabs *models.Crontabs) {
if err != nil { if err != nil {
log.Errorln(err.Error()) log.Errorln(err.Error())
} }
go syncLog(stdoutPipe, logFile) go io.Copy(logFile, stdoutPipe)
e2.Wait() 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.LogPath = "data/log/" + time.Now().Format("2006-01-02") + "/" + crontabs.Name + "_" + uuid.New().String() + ".log"
crontabs.Status = 0 crontabs.Status = 0
models.UpdateCron(crontabs) 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) _ = os.Mkdir("data/log/"+time.Now().Format("2006-01-02"), 0666)
logFile := &myWriter{crontabs.LogPath} file, _ := os.OpenFile(crontabs.LogPath, os.O_RDWR|os.O_CREATE, 0666)
go utils.RunTask(ctx, ta.cmd, envFromDb,
e2 := exec.Command(strings.Split(ta.cmd, " ")[0], strings.Split(ta.cmd, " ")[1:]...) func(ctx context.Context) {
execManager.Store(crontabs.Id, e2) writer := ctx.Value("log").(io.Writer)
stdoutPipe, _ := e2.StdoutPipe() writer.Write([]byte(fmt.Sprintf("##开始执行.. %s\n\n", now.Format("2006-01-02 15:04:05"))))
for s, s2 := range envFromDb { }, func(ctx context.Context) {
e2.Env = append(e2.Env, s+"="+s2) 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())))
startTime := time.Now() crontabs.Status = 1
logFile.Write([]byte(fmt.Sprintf("##开始执行.. %s\n\n", startTime.Format("2006-01-02 15:04:05")))) crontabs.LastExecutionTime = now.Unix()
go func() { crontabs.LastRunningTime = int64(time.Now().Sub(now).Seconds())
err := e2.Start() models.UpdateCron(crontabs)
if err != nil { execManager.LoadAndDelete(crontabs.Id)
log.Errorln(err.Error()) file.Close()
} }, file)
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)
}()
} }
} }
@ -145,11 +146,11 @@ func handCommand(command string) *task {
commands := strings.Split(command, " ") commands := strings.Split(command, " ")
if commands[0] == "task" { if commands[0] == "task" {
if strings.HasSuffix(commands[1], ".py") { 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") { } 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") { } 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") { } else if strings.HasSuffix(commands[1], ".ts") {
ta.cmd = "ts-node-transpile-only data/scripts/" + commands[1] ta.cmd = "ts-node-transpile-only data/scripts/" + commands[1]
} }
@ -189,31 +190,31 @@ func handCommand(command string) *task {
return ta return ta
} }
type myWriter struct { //type myWriter struct {
fileName string // fileName string
} //}
//
func (m *myWriter) Write(p []byte) (n int, err error) { //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) // file, _ := os.OpenFile(m.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
n, err = file.Write(p) // n, err = file.Write(p)
file.Close() // file.Close()
return n, err // return n, err
} //}
//
//通过管道同步获取日志的函数 ////通过管道同步获取日志的函数
func syncLog(reader io.ReadCloser, writer io.Writer) { //func syncLog(reader io.ReadCloser, writer io.Writer) {
buf := make([]byte, 1) // buf := make([]byte, 1)
for { // for {
strNum, err := reader.Read(buf) // strNum, err := reader.Read(buf)
if strNum > 0 { // if strNum > 0 {
outputByte := buf[:strNum] // outputByte := buf[:strNum]
writer.Write(outputByte) // writer.Write(outputByte)
} // }
if err != nil { // if err != nil {
//读到结尾 // //读到结尾
if err == io.EOF || strings.Contains(err.Error(), "file already closed") { // if err == io.EOF || strings.Contains(err.Error(), "file already closed") {
return // return
} // }
} // }
} // }
} //}

View File

@ -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)
}

48
service/open/open.go Normal file
View File

@ -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
}

View File

@ -1,6 +1,12 @@
package scripts package scripts
import "os" import (
"bytes"
"github.com/huoxue1/qinglong-go/utils"
"os"
"path"
"sort"
)
type File struct { type File struct {
Key string `json:"key"` Key string `json:"key"`
@ -11,48 +17,38 @@ type File struct {
Children []*File `json:"children"` Children []*File `json:"children"`
} }
func GetFiles() []*File { var (
var files []*File excludedFiles = []string{
dir, err := os.ReadDir("data/scripts/") "node_modules",
"__pycache__",
}
)
func GetFiles(base, p string) []*File {
var files Files
dir, err := os.ReadDir(path.Join(base, p))
if err != nil { if err != nil {
return []*File{} return []*File{}
} }
for _, entry := range dir { for _, entry := range dir {
if utils.In(entry.Name(), excludedFiles) {
continue
}
if entry.IsDir() { if entry.IsDir() {
f := &File{ f := &File{
Key: entry.Name(), Key: path.Join(p, entry.Name()),
Parent: "", Parent: p,
Title: entry.Name(), Title: entry.Name(),
Type: "directory", Type: "directory",
IsLeaf: true, IsLeaf: true,
Children: []*File{}, Children: GetFiles(base, path.Join(p, entry.Name())),
}
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{},
})
} }
files = append(files, f) files = append(files, f)
} else { } else {
files = append(files, &File{ files = append(files, &File{
Key: entry.Name(), Key: path.Join(p, entry.Name()),
Parent: "", Parent: p,
Title: entry.Name(), Title: entry.Name(),
Type: "file", Type: "file",
IsLeaf: true, IsLeaf: true,
@ -60,5 +56,26 @@ func GetFiles() []*File {
}) })
} }
} }
sort.Sort(files)
return 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
}
}

View File

@ -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
}

View File

@ -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
}

10
utils/array.go Normal file
View File

@ -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
}

50
utils/file.go Normal file
View File

@ -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
}
}
}

View File

@ -3,14 +3,8 @@ package utils
// jwt身份验证demo // jwt身份验证demo
import ( import (
"encoding/json" "github.com/dgrijalva/jwt-go"
"errors" "math/rand"
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"
"strings" "strings"
"time" "time"
) )
@ -24,9 +18,9 @@ type Claims struct {
} }
// GenerateToken 生成token的函数 // GenerateToken 生成token的函数
func GenerateToken(userid string) (string, error) { func GenerateToken(userid string, hour int) (string, error) {
nowTime := time.Now() nowTime := time.Now()
expireTime := nowTime.Add(48 * time.Hour) expireTime := nowTime.Add(time.Duration(hour) * time.Hour)
claims := Claims{ claims := Claims{
userid, // 自行添加的信息 userid, // 自行添加的信息
@ -59,68 +53,6 @@ func ParseToken(token string) (*Claims, error) {
return nil, err 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 { func IsMobile(userAgent string) bool {
if len(userAgent) == 0 { if len(userAgent) == 0 {
return false return false
@ -139,3 +71,13 @@ func IsMobile(userAgent string) bool {
return isMobile 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)
}

53
utils/run.go Normal file
View File

@ -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))
}
}
}