mirror of
https://github.com/ouqiang/gocron.git
synced 2024-04-21 12:31:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94197e0511 | ||
|
|
519c6613c7 | ||
|
|
4a77958556 | ||
|
|
95278d373e | ||
|
|
d91ae4fa44 | ||
|
|
5e8f2da70e | ||
|
|
8779b3ad8b | ||
|
|
28eda835c0 |
@@ -48,11 +48,16 @@
|
||||
3. 编译 `go build`
|
||||
4. 启动、访问方式同上
|
||||
|
||||
### 启动可选参数
|
||||
### 命令
|
||||
|
||||
* -p 端口, 指定端口, 默认5920
|
||||
* -e 指定运行环境, dev|test|prod, dev模式下可查看更多日志信息, 默认prod
|
||||
* -h 查看帮助
|
||||
* gocron web
|
||||
* -p 端口, 指定端口, 默认5920
|
||||
* -e 指定运行环境, dev|test|prod, dev模式下可查看更多日志信息, 默认prod
|
||||
* -d 后台运行
|
||||
* -h 查看帮助
|
||||
* gocron serv
|
||||
* -s stop|status stop:停止gocron status:查看运行状态
|
||||
|
||||
|
||||
## 安全
|
||||
* 使用`https`访问保证数据传输安全, 可在web服务器如nginx中配置https,通过反向代理,访问内部的gocron
|
||||
|
||||
@@ -53,8 +53,13 @@ if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
|
||||
fi
|
||||
|
||||
echo '开始编译'
|
||||
GOOS=$OS GOARCH=$ARCH go build
|
||||
if [[ ! $? ]];then
|
||||
if [[ $OS = 'windows' ]];then
|
||||
GOOS=$OS GOARCH=$ARCH go build -ldflags '-w -H windowsgui'
|
||||
else
|
||||
GOOS=$OS GOARCH=$ARCH go build -ldflags '-w'
|
||||
fi
|
||||
|
||||
if [[ $? != 0 ]];then
|
||||
exit 1
|
||||
fi
|
||||
echo '编译完成'
|
||||
@@ -68,7 +73,7 @@ else
|
||||
fi
|
||||
|
||||
mkdir -p $TEMP_DIR/$APP_NAME
|
||||
if [[ ! $? ]]; then
|
||||
if [[ $? != 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
"fmt"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var CmdServ = cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "manage gocron, ./gocron serv -s stop|status",
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "s",
|
||||
Value:"",
|
||||
Usage: "stop|status",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runServ(ctx *cli.Context) {
|
||||
if utils.IsWindows() {
|
||||
fmt.Println("not support on windows")
|
||||
return
|
||||
}
|
||||
option := ctx.String("s")
|
||||
if !utils.InStringSlice([]string{"stop", "status"}, option) {
|
||||
fmt.Println("invalid option")
|
||||
return
|
||||
}
|
||||
app.InitEnv()
|
||||
pid := app.GetPid()
|
||||
if pid <= 0 {
|
||||
fmt.Println("not running")
|
||||
return
|
||||
}
|
||||
process ,err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
fmt.Println("not running", err)
|
||||
return
|
||||
}
|
||||
switch option {
|
||||
case "stop":
|
||||
stop(process)
|
||||
case "status":
|
||||
status(process)
|
||||
}
|
||||
}
|
||||
|
||||
func stop(process *os.Process) {
|
||||
fmt.Println("stopping gocron......")
|
||||
err := process.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
fmt.Println("failed to kill process", err)
|
||||
} else {
|
||||
fmt.Println("stopped")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func status(process *os.Process) {
|
||||
fmt.Printf("running, pid-[%d]\n", process.Pid)
|
||||
}
|
||||
+66
-7
@@ -13,48 +13,88 @@ import (
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/setting"
|
||||
"time"
|
||||
"io"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"os/exec"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
)
|
||||
|
||||
// web服务器默认端口
|
||||
const DefaultPort = 5920
|
||||
|
||||
const InitProcess = 1
|
||||
|
||||
var CmdWeb = cli.Command{
|
||||
Name: "web",
|
||||
Usage: "run web server",
|
||||
Action: run,
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "port,p",
|
||||
Value: DefaultPort,
|
||||
Usage: "bind port number",
|
||||
Usage: "bind port",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "env,e",
|
||||
Value: "prod",
|
||||
Usage: "runtime environment, dev|test|prod",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "d",
|
||||
Usage: "-d=true, run as daemon process",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func run(ctx *cli.Context) {
|
||||
func runWeb(ctx *cli.Context) {
|
||||
// 设置守护进程
|
||||
becomeDaemon(ctx)
|
||||
// 设置运行环境
|
||||
setEnvironment(ctx)
|
||||
// 初始化应用
|
||||
app.InitEnv()
|
||||
app.WritePid()
|
||||
// 初始化模块 DB、定时任务等
|
||||
initModule()
|
||||
// 捕捉信号,配置热更新等
|
||||
go catchSignal()
|
||||
m := macaron.Classic()
|
||||
m := macaron.NewWithLogger(getWebLogWriter())
|
||||
|
||||
// 注册路由
|
||||
routers.Register(m)
|
||||
// 注册中间件.
|
||||
routers.RegisterMiddleware(m)
|
||||
port := parsePort(ctx)
|
||||
fmt.Println("server start")
|
||||
m.Run(port)
|
||||
}
|
||||
|
||||
func becomeDaemon(ctx *cli.Context) {
|
||||
// 不支持windows
|
||||
if utils.IsWindows() {
|
||||
return
|
||||
}
|
||||
if !ctx.IsSet("d") {
|
||||
return
|
||||
}
|
||||
|
||||
if os.Getppid() == InitProcess {
|
||||
// 子进程不再处理
|
||||
return
|
||||
}
|
||||
|
||||
filePath, _:= filepath.Abs(os.Args[0])
|
||||
cmd := exec.Command(filePath, os.Args[1:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Start()
|
||||
|
||||
// 父进程退出, 子进程由init-1号进程收养
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func initModule() {
|
||||
if !app.Installed {
|
||||
return
|
||||
@@ -140,9 +180,30 @@ func catchSignal() {
|
||||
}
|
||||
}
|
||||
|
||||
func getWebLogWriter() io.Writer {
|
||||
if macaron.Env == macaron.DEV {
|
||||
return os.Stdout
|
||||
}
|
||||
logFile := app.LogDir + "/access.log"
|
||||
var err error
|
||||
w, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND ,0666)
|
||||
if err != nil {
|
||||
fmt.Printf("日志文件[%s]打开失败", logFile)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// 应用退出
|
||||
func shutdown() {
|
||||
if !app.Installed {
|
||||
defer func() {
|
||||
app.RemovePid()
|
||||
logger.Info("已退出")
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
if !app.Installed {
|
||||
return
|
||||
}
|
||||
logger.Info("应用准备退出")
|
||||
@@ -168,6 +229,4 @@ func shutdown() {
|
||||
time.Sleep(3 * time.Second)
|
||||
taskNumInRunning = service.TaskNum.Num()
|
||||
}
|
||||
logger.Info("已退出")
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/ouqiang/gocron/cmd"
|
||||
)
|
||||
|
||||
const AppVersion = "0.2"
|
||||
const AppVersion = "0.3"
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
@@ -22,6 +22,7 @@ func main() {
|
||||
app.Version = AppVersion
|
||||
app.Commands = []cli.Command{
|
||||
cmd.CmdWeb,
|
||||
cmd.CmdServ,
|
||||
}
|
||||
app.Flags = append(app.Flags, []cli.Flag{}...)
|
||||
app.Run(os.Args)
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"gopkg.in/ini.v1"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,8 +19,10 @@ var (
|
||||
AppConfig string // 应用配置文件
|
||||
Installed bool // 应用是否安装过
|
||||
Setting *ini.Section // 应用配置
|
||||
PidFile string
|
||||
)
|
||||
|
||||
|
||||
func InitEnv() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
logger.InitLogger()
|
||||
@@ -31,6 +35,7 @@ func InitEnv() {
|
||||
LogDir = AppDir + "/log"
|
||||
DataDir = AppDir + "/data"
|
||||
AppConfig = ConfDir + "/app.ini"
|
||||
PidFile = LogDir + "/gocron.pid"
|
||||
checkDirExists(ConfDir, LogDir, DataDir)
|
||||
Installed = IsInstalled()
|
||||
}
|
||||
@@ -45,6 +50,33 @@ func IsInstalled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func WritePid() {
|
||||
pid := os.Getpid()
|
||||
pidStr := strconv.Itoa(pid)
|
||||
err := ioutil.WriteFile(PidFile, []byte(pidStr), 0644)
|
||||
if err != nil {
|
||||
logger.Fatalf("写入pid文件失败", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetPid() int {
|
||||
bytes, err := ioutil.ReadFile(PidFile)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
pidStr := string(bytes)
|
||||
pid, err := strconv.Atoi(pidStr)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return pid
|
||||
}
|
||||
|
||||
func RemovePid() {
|
||||
os.Remove(PidFile)
|
||||
}
|
||||
|
||||
// 创建安装锁文件
|
||||
func CreateInstallLock() error {
|
||||
_, err := os.Create(ConfDir + "/install.lock")
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 108 KiB |
+16
-6
@@ -16,7 +16,7 @@ type DelayTask struct {}
|
||||
|
||||
// 从数据库中取出所有延迟任务
|
||||
func (task *DelayTask) Initialize(tick time.Duration, slots int) {
|
||||
tw = timewheel.New(tick, slots)
|
||||
tw = timewheel.New(tick, slots, task.Run)
|
||||
tw.Start()
|
||||
taskModel := new(models.DelayTask)
|
||||
currentTime := time.Now()
|
||||
@@ -57,18 +57,28 @@ func (task *DelayTask) Add(taskModel models.DelayTask) {
|
||||
currentTimestamp := time.Now().Unix()
|
||||
execTimestamp := taskModel.Created.Unix() + int64(taskModel.Delay)
|
||||
// 时间过期, 立即执行任务
|
||||
data := []interface{}{taskModel.Id, taskModel.Url, taskModel.Params}
|
||||
if execTimestamp <= currentTimestamp {
|
||||
go task.Run(taskModel.Id, taskModel.Url, taskModel.Params)
|
||||
go task.Run(data)
|
||||
return
|
||||
}
|
||||
delay := execTimestamp - currentTimestamp
|
||||
tw.Add(time.Duration(delay) * time.Second, func() {
|
||||
task.Run(taskModel.Id, taskModel.Url, taskModel.Params)
|
||||
})
|
||||
tw.Add(time.Duration(delay) * time.Second, data)
|
||||
}
|
||||
|
||||
// 运行任务
|
||||
func (task *DelayTask) Run(id int64, url, params string) {
|
||||
func (task *DelayTask) Run(data []interface{}) {
|
||||
if len(data) < 3 {
|
||||
logger.Errorf("延时任务开始执行#参数不足#%+v", data)
|
||||
return
|
||||
}
|
||||
id := data[0].(int64)
|
||||
url := data[1].(string)
|
||||
params := data[2].(string)
|
||||
if id <= 0 || url == "" {
|
||||
logger.Errorf("延时任务开始执行#参数为空#%+v", data)
|
||||
return
|
||||
}
|
||||
taskModel := new(models.DelayTask)
|
||||
_, err := taskModel.UpdateStatus(id, models.Running)
|
||||
if err != nil {
|
||||
|
||||
+45
-38
@@ -41,7 +41,7 @@
|
||||
<div class="field">
|
||||
<select name="status">
|
||||
<option value="0">状态</option>
|
||||
<option value="1" {{{if eq .Params.Status 0}}}selected{{{end}}} >暂停</option>
|
||||
<option value="1" {{{if eq .Params.Status 0}}}selected{{{end}}} >停止</option>
|
||||
<option value="2" {{{if eq .Params.Status 1}}}selected{{{end}}}>激活</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -50,43 +50,50 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="task-list">
|
||||
{{{range $i, $v := .Tasks}}}
|
||||
<div class="ui device two column middle aligned vertical grid segment">
|
||||
<div class="column verborder">
|
||||
<div class="ui info segment">
|
||||
<h5 class="ui header">{{{.Task.Name}}} {{{if eq .Status 1}}}<i class="large checkmark blue icon"></i> {{{else}}} <i class="large red minus icon"></i> {{{end}}}
|
||||
</h5>
|
||||
<p>任务ID: <span class="stress">{{{.Id}}}</span></p>
|
||||
<p>状态: <span class="stress">{{{if eq .Status 1}}}激活{{{else}}}停止{{{end}}}</span></p>
|
||||
<p>cron表达式: {{{.Spec}}}</p>
|
||||
<p>执行方式: {{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SSH {{{else if eq .Protocol 3}}}本地命令{{{end}}}</p>
|
||||
<p class="sensorStatus">命令:{{{.Command}}}</p>
|
||||
<p class="sensorStatus">超时时间:{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</p>
|
||||
<p>重试次数: {{{.RetryTimes}}}</p>
|
||||
<p class="sensorStatus">是否允许多实例运行:{{{if gt .Multi 0}}}是{{{else}}}否{{{end}}}</p>
|
||||
{{{if eq .Protocol 2}}}
|
||||
<p>主机: {{{.Alias}}}-{{{.Name}}}</p>
|
||||
{{{end}}}
|
||||
<p>备注: {{{.Remark}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center aligned column">
|
||||
<div class="ui buttons operation">
|
||||
<a class="ui purple button" href="/task/edit/{{{.Id}}}">编辑</a>
|
||||
{{{if eq .Status 1}}}
|
||||
<button class="ui primary button" @click="changeStatus({{{.Id}}},{{{.Status}}})">停止</button>
|
||||
{{{else}}}
|
||||
<button class="ui blue button" @click="changeStatus({{{.Id}}},{{{.Status}}})">激活 </button>
|
||||
{{{end}}}
|
||||
<button class="ui positive button" @click="remove({{{.Id}}})">删除</button> <br>
|
||||
<button class="ui twitter button" @click="run({{{.Id}}})">手动运行</button>
|
||||
<a class="ui instagram button" href="/task/log?task_id={{{.Id}}}">查看日志</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
</div>
|
||||
<table class="ui pink table task-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>任务ID</th>
|
||||
<th>任务名称</th>
|
||||
<th>cron表达式</th>
|
||||
<th>执行方式</th>
|
||||
<th>超时时间</th>
|
||||
<th>重试次数</th>
|
||||
<th>单实例运行</th>
|
||||
<th>主机</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .Tasks}}}
|
||||
<tr>
|
||||
<td>{{{.Id}}}</td>
|
||||
<td>{{{.Task.Name}}}</td>
|
||||
<td>{{{.Spec}}}</td>
|
||||
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SSH {{{else if eq .Protocol 3}}}本地命令{{{end}}}</td>
|
||||
<td>{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</td>
|
||||
<td>{{{.RetryTimes}}}</td>
|
||||
<td>{{{if gt .Multi 0}}}否{{{else}}}是{{{end}}}</td>
|
||||
<td>{{{.Alias}}}-{{{.Name}}}</td>
|
||||
<td>{{{if eq .Status 1}}}<span style="color: green;">激活</span>{{{else}}}<span style="color: red;">停止<span>{{{end}}}</td>
|
||||
<td>
|
||||
<div class="ui buttons operation">
|
||||
<a class="ui purple button" href="/task/edit/{{{.Id}}}">编辑</a>
|
||||
{{{if eq .Status 1}}}
|
||||
<button class="ui primary button" @click="changeStatus({{{.Id}}},{{{.Status}}})">停止</button>
|
||||
{{{else}}}
|
||||
<button class="ui blue button" @click="changeStatus({{{.Id}}},{{{.Status}}})">激活 </button>
|
||||
{{{end}}}
|
||||
<button class="ui positive button" @click="remove({{{.Id}}})">删除</button> <br>
|
||||
<button class="ui twitter button" @click="run({{{.Id}}})">手动运行</button>
|
||||
<a class="ui instagram button" href="/task/log?task_id={{{.Id}}}">查看日志</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ fi
|
||||
for i in linux darwin windows
|
||||
do
|
||||
./build.sh -p $i
|
||||
if [[ ! $? ]];then
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
done
|
||||
@@ -25,7 +25,7 @@ do
|
||||
# 上传文件 qrsctl put bucket key srcFile
|
||||
KEY=gocron/$i
|
||||
qrsctl put github $KEY $i
|
||||
if [[ ! $? ]];then
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
echo "刷新七牛CDN-" $QINIU_URL/$KEY
|
||||
|
||||
+3
-1
@@ -252,7 +252,9 @@ func (engine *Engine) Close() error {
|
||||
func (engine *Engine) Ping() error {
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
engine.logger.Infof("PING DATABASE %v", engine.DriverName())
|
||||
if engine.showSQL {
|
||||
engine.logger.Infof("PING DATABASE %v", engine.DriverName())
|
||||
}
|
||||
return session.Ping()
|
||||
}
|
||||
|
||||
|
||||
+11
-9
@@ -7,12 +7,15 @@ import (
|
||||
|
||||
// @author qiang.ou<qingqianludao@gmail.com>
|
||||
|
||||
type Job func([]interface{})
|
||||
|
||||
type TimeWheel struct {
|
||||
interval time.Duration
|
||||
ticker *time.Ticker
|
||||
slots []*list.List
|
||||
currentPos int
|
||||
slotNum int
|
||||
job Job
|
||||
taskChannel chan Task
|
||||
stopChannel chan bool
|
||||
}
|
||||
@@ -21,19 +24,18 @@ type TimeWheel struct {
|
||||
type Task struct {
|
||||
delay time.Duration
|
||||
circle int
|
||||
job Job
|
||||
data []interface{}
|
||||
}
|
||||
|
||||
type Job func()
|
||||
|
||||
func New(interval time.Duration, slotNum int) *TimeWheel {
|
||||
if interval <= 0 || slotNum <= 0 {
|
||||
func New(interval time.Duration, slotNum int, job Job) *TimeWheel {
|
||||
if interval <= 0 || slotNum <= 0 || job == nil {
|
||||
return nil
|
||||
}
|
||||
tw := &TimeWheel{
|
||||
interval: interval,
|
||||
slots: make([]*list.List, slotNum),
|
||||
currentPos: 0,
|
||||
job: job,
|
||||
slotNum: slotNum,
|
||||
taskChannel: make(chan Task),
|
||||
stopChannel: make(chan bool),
|
||||
@@ -55,11 +57,11 @@ func (tw *TimeWheel) Start() {
|
||||
go tw.start()
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) Add(delay time.Duration, job Job) {
|
||||
if delay < 0 || job == nil {
|
||||
func (tw *TimeWheel) Add(delay time.Duration, data []interface{}) {
|
||||
if delay <= 0 {
|
||||
return
|
||||
}
|
||||
tw.taskChannel <- Task{delay:delay, job: job}
|
||||
tw.taskChannel <- Task{delay:delay, data: data}
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) Stop() {
|
||||
@@ -99,7 +101,7 @@ func (tw *TimeWheel) scanAndRunTask(l *list.List) {
|
||||
continue
|
||||
}
|
||||
|
||||
go task.job()
|
||||
go tw.job(task.data)
|
||||
next := e.Next()
|
||||
l.Remove(e)
|
||||
e = next
|
||||
|
||||
Reference in New Issue
Block a user