新增任务失败邮件提醒

任务异常提醒软件
This commit is contained in:
george
2018-08-09 11:34:46 +08:00
parent 985020b424
commit e39d37704b
11 changed files with 548 additions and 20 deletions

View File

@@ -10,6 +10,9 @@ jobs.pool = 1000
# 站点名称
site.name = 定时任务管理器
#通知方式 0=邮件1=信息
notify.type = 0
# 数据库配置
db.host = 127.0.0.1
@@ -19,3 +22,16 @@ db.port = 3306
db.name = ppgo_job2
db.prefix = pp_
db.timezone = Asia/Shanghai
# 邮件通知配置
email.host = smtp.mxhichina.com
email.port = 25
email.from = ci@xxx.cn
email.user = ci@xxx.cn
email.password = "xxx@123"
email.pool = 10
# 其他通知方式
msg.url = http://xxx.com/sms/url?id=12&msg=12121

View File

@@ -285,6 +285,42 @@ func serverListByGroupId(groupId int) []string {
return servers
}
type adminInfo struct {
Id int
Email string
Phone string
RealName string
}
func AllAdminInfo(adminIds string) []*adminInfo {
Filters := make([]interface{}, 0)
Filters = append(Filters, "status", 1)
//Filters = append(Filters, "id__gt", 1)
var notifyUserIds []int
if adminIds != "0" && adminIds != "" {
notifyUserIdsStr := strings.Split(adminIds, ",")
for _, v := range notifyUserIdsStr {
i, _ := strconv.Atoi(v)
notifyUserIds = append(notifyUserIds, i)
}
Filters = append(Filters, "id__in", notifyUserIds)
}
Result, _ := models.AdminGetList(1, 1000, Filters...)
adminInfos := make([]*adminInfo, 0)
for _, v := range Result {
ai := adminInfo{
Id: v.Id,
Email: v.Email,
Phone: v.Phone,
RealName: v.RealName,
}
adminInfos = append(adminInfos, &ai)
}
return adminInfos
}
type serverList struct {
GroupId int
GroupName string

View File

@@ -46,7 +46,6 @@ func (self *RoleController) Edit() {
row["id"] = role.Id
row["role_name"] = role.RoleName
row["detail"] = role.Detail
row["detail"] = role.Detail
row["task_group_ids"] = role.TaskGroupIds
row["server_group_ids"] = role.ServerGroupIds
self.Data["role"] = row

View File

@@ -12,6 +12,7 @@ import (
"strings"
"time"
"fmt"
"github.com/astaxie/beego"
"github.com/george518/PPGo_Job/jobs"
"github.com/george518/PPGo_Job/models"
@@ -38,6 +39,9 @@ func (self *TaskController) Add() {
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
self.Data["isAdmin"] = self.userId
self.Data["adminInfo"] = AllAdminInfo("")
fmt.Println(self.Data["adminInfo"])
self.display()
}
@@ -55,15 +59,28 @@ func (self *TaskController) Edit() {
}
self.Data["task"] = task
self.Data["adminInfo"] = AllAdminInfo("")
// 分组列表
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
self.Data["isAdmin"] = self.userId
var notifyUserIds []int
if task.NotifyUserIds != "0" {
notifyUserIdsStr := strings.Split(task.NotifyUserIds, ",")
for _, v := range notifyUserIdsStr {
i, _ := strconv.Atoi(v)
notifyUserIds = append(notifyUserIds, i)
}
}
self.Data["notify_user_ids"] = notifyUserIds
self.display()
}
func (self *TaskController) Copy() {
self.Data["pageTitle"] = "复制任务"
self.Data["adminInfo"] = AllAdminInfo("")
id, _ := self.GetInt("id")
task, err := models.TaskGetById(id)
@@ -75,6 +92,15 @@ func (self *TaskController) Copy() {
// 分组列表
self.Data["taskGroup"] = taskGroupLists(self.taskGroups, self.userId)
self.Data["serverGroup"] = serverLists(self.serverGroups, self.userId)
var notifyUserIds []int
if task.NotifyUserIds != "0" {
notifyUserIdsStr := strings.Split(task.NotifyUserIds, ",")
for _, v := range notifyUserIdsStr {
i, _ := strconv.Atoi(v)
notifyUserIds = append(notifyUserIds, i)
}
}
self.Data["notify_user_ids"] = notifyUserIds
self.display()
}
@@ -138,6 +164,13 @@ func (self *TaskController) Detail() {
updateName = admin.RealName
}
}
//是否出错通知
self.Data["adminInfo"] = []int{0}
fmt.Println(task.NotifyUserIds)
if task.NotifyUserIds != "0" && task.NotifyUserIds != "" {
self.Data["adminInfo"] = AllAdminInfo(task.NotifyUserIds)
}
self.Data["CreateName"] = createName
self.Data["UpdateName"] = updateName
self.Data["serverName"] = serverName
@@ -157,6 +190,9 @@ func (self *TaskController) AjaxSave() {
task.CronSpec = strings.TrimSpace(self.GetString("cron_spec"))
task.Command = strings.TrimSpace(self.GetString("command"))
task.Timeout, _ = self.GetInt("timeout")
task.IsNotify, _ = self.GetInt("is_notify")
task.NotifyType, _ = self.GetInt("notify_type")
task.NotifyUserIds = strings.TrimSpace(self.GetString("notify_user_ids"))
msg, isBan := checkCommand(task.Command)
if !isBan {
@@ -194,6 +230,9 @@ func (self *TaskController) AjaxSave() {
task.CronSpec = strings.TrimSpace(self.GetString("cron_spec"))
task.Command = strings.TrimSpace(self.GetString("command"))
task.Timeout, _ = self.GetInt("timeout")
task.IsNotify, _ = self.GetInt("is_notify")
task.NotifyType, _ = self.GetInt("notify_type")
task.NotifyUserIds = strings.TrimSpace(self.GetString("notify_user_ids"))
task.UpdateId = self.userId
task.Status = 2 //审核中,超级管理员不需要
if self.userId == 1 {

View File

@@ -18,7 +18,10 @@ import (
"github.com/astaxie/beego"
"github.com/george518/PPGo_Job/models"
"github.com/george518/PPGo_Job/notify"
"golang.org/x/crypto/ssh"
"strconv"
"strings"
)
type Job struct {
@@ -187,7 +190,6 @@ func RemoteCommandJobByPassword(id int, name string, command string, servers *mo
var c bytes.Buffer
session.Stdout = &b
session.Stderr = &c
//session.Output(command)
if err := session.Run(command); err != nil {
return "", "", err, false
@@ -264,6 +266,90 @@ func (j *Job) Run() {
log.Status = models.TASK_ERROR
log.Error = err.Error() + ":" + cmdErr
}
fmt.Println()
fmt.Println()
fmt.Println(log.Status, j.task.IsNotify)
if log.Status < 0 && j.task.IsNotify == 1 {
fmt.Println()
fmt.Println()
fmt.Println(j.task.NotifyUserIds)
fmt.Println()
fmt.Println(j.task.NotifyUserIds != "0")
fmt.Println(j.task.NotifyUserIds != "")
if j.task.NotifyUserIds != "0" && j.task.NotifyUserIds != "" {
admin_info := AllAdminInfo(j.task.NotifyUserIds)
fmt.Println("ADMIN:", admin_info)
phone := make([]string, 0)
toEmail := ""
for _, v := range admin_info {
if v.Phone != "0" && v.Phone != "" {
phone = append(phone, v.Phone)
}
if v.Email != "0" && v.Email != "" {
toEmail += v.Email + ";"
}
}
toEmail = strings.TrimRight(toEmail, ";")
fmt.Println("EMAIL:", toEmail)
fmt.Println("TYPE:", j.task.NotifyType)
TextStatus := []string{
"<font color='red'>超时</font>",
"<font color='red'>错误</font>",
"<font color='green'>正常</font>",
}
status := log.Status + 2
if j.task.NotifyType == 0 && toEmail != "" {
//邮件
//SendToChan(to, subject, body, mailtype string) bool
subject := fmt.Sprintf("PPGo_Job定时任务异常%s", j.task.TaskName)
body := fmt.Sprintf(
`Hello,定时任务出问题了:
<p style="font-size:16px;">任务执行详情:</p>
<p style="display:block; padding:10px; background:#efefef;border:1px solid #e4e4e4">
任务 ID%d<br/>
任务名称:%s<br/>
执行时间:%s<br/>
执行耗时:%f秒<br/>
执行状态:%s
</p>
<p style="font-size:16px;">任务执行输出</p>
<p style="display:block; padding:10px; background:#efefef;border:1px solid #e4e4e4">
%s
</p>
<br/>
<br/>
<p>-----------------------------------------------------------------<br />
本邮件由PPGo_Job定时系统自动发出请勿回复<br />
如果要取消邮件通知,请登录到系统进行设置<br />
</p>
`, j.task.Id,
j.task.TaskName,
beego.Date(time.Unix(log.CreateTime, 0), "Y-m-d H:i:s"),
float64(log.ProcessTime)/1000,
TextStatus[status],
log.Error)
mailtype := "html"
ok := notify.SendToChan(toEmail, subject, body, mailtype)
if !ok {
fmt.Println("发送邮件错误", toEmail)
}
} else if j.task.NotifyType == 1 {
//信息
}
}
}
j.logId, _ = models.TaskLogAdd(log)
// 更新上次执行时间
@@ -271,3 +357,40 @@ func (j *Job) Run() {
j.task.ExecuteTimes++
j.task.Update("PrevTime", "ExecuteTimes")
}
//冗余代码
type adminInfo struct {
Id int
Email string
Phone string
RealName string
}
func AllAdminInfo(adminIds string) []*adminInfo {
Filters := make([]interface{}, 0)
Filters = append(Filters, "status", 1)
//Filters = append(Filters, "id__gt", 1)
var notifyUserIds []int
if adminIds != "0" && adminIds != "" {
notifyUserIdsStr := strings.Split(adminIds, ",")
for _, v := range notifyUserIdsStr {
i, _ := strconv.Atoi(v)
notifyUserIds = append(notifyUserIds, i)
}
Filters = append(Filters, "id__in", notifyUserIds)
}
Result, _ := models.AdminGetList(1, 1000, Filters...)
adminInfos := make([]*adminInfo, 0)
for _, v := range Result {
ai := adminInfo{
Id: v.Id,
Email: v.Email,
Phone: v.Phone,
RealName: v.RealName,
}
adminInfos = append(adminInfos, &ai)
}
return adminInfos
}

View File

@@ -33,6 +33,9 @@ type Task struct {
ExecuteTimes int
PrevTime int64
Status int
IsNotify int
NotifyType int
NotifyUserIds string
CreateId int
UpdateId int
CreateTime int64

104
notify/email.go Normal file
View File

@@ -0,0 +1,104 @@
/************************************************************
** @Description: notify
** @Author: george hao
** @Date: 2018-08-08 12:59
** @Last Modified by: george hao
** @Last Modified time: 2018-08-08 12:59
*************************************************************/
package notify
import (
"github.com/astaxie/beego"
"net/smtp"
"strings"
"time"
)
type PEmailConfig struct {
Host string
Port string
User string
Pwd string
From string
}
type PEmail struct {
Config *PEmailConfig
Subject string
Body string
To string
Format string
}
var (
mailChan chan *PEmail
config *PEmailConfig
)
func init() {
poolSize, _ := beego.AppConfig.Int("email.pool")
host := beego.AppConfig.String("email.host")
port := beego.AppConfig.String("email.port")
user := beego.AppConfig.String("email.user")
pwd := beego.AppConfig.String("email.password")
from := beego.AppConfig.String("email.from")
config = &PEmailConfig{
Host: host,
From: from,
Port: port,
User: user,
Pwd: pwd,
}
//创建通道
mailChan = make(chan *PEmail, poolSize)
go func() {
for {
select {
case m, ok := <-mailChan:
if !ok {
return
}
if err := m.SendToEmail(); err != nil {
beego.Error("SendMail:", err.Error())
}
}
}
}()
}
func SendToChan(to, subject, body, mailtype string) bool {
email := &PEmail{
Config: config,
Body: body,
Subject: subject,
Format: mailtype,
To: to,
}
select {
case mailChan <- email:
return true
case <-time.After(time.Second * 3):
return false
}
}
func (pe *PEmail) SendToEmail() error {
auth := smtp.PlainAuth("", pe.Config.User, pe.Config.Pwd, pe.Config.Host)
var contentType string
if pe.Format == "html" {
contentType = "Content-Type: text/" + pe.Format + "; charset=UTF-8"
} else {
contentType = "Content-Type: text/plain" + "; charset=UTF-8"
}
msg := []byte("To: " + pe.To + "\r\nFrom: " + pe.Config.User +
"\r\nSubject: " + pe.Subject + "\r\n" + contentType + "\r\n\r\n" + pe.Body)
sendTo := strings.Split(pe.To, ";")
err := smtp.SendMail(pe.Config.Host+":"+pe.Config.Port, auth, pe.Config.User, sendTo, msg)
return err
}

View File

@@ -1,3 +1,8 @@
<style>
.notify{
display: none;
}
</style>
<div class="layui-layout layui-layout-admin" style="padding-left: 40px;margin-top: 20px;">
<form class="layui-form" action="" method="post" >
<div class="layui-form-item">
@@ -49,8 +54,8 @@
<label class="layui-form-label mw200">是否单例</label>
<div class="layui-input-inline ">
<input type="radio" name="concurrent" lay-verify="concurrent" value="0" title="是" checked>
<input type="radio" name="concurrent" lay-verify="concurrent" value="1" title="否" >
<input type="radio" name="concurrent" lay-verify="required" value="0" title="是" checked>
<input type="radio" name="concurrent" lay-verify="required" value="1" title="否" >
</div>
<div class="layui-form-mid layui-word-aux"><i class="fa fa-info-circle" aria-hidden="true" id="des"></i></div>
@@ -78,6 +83,38 @@
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label mw200">出错通知</label>
<div class="layui-input-inline ">
<input type="radio" name="is_notify" lay-verify="required" value="1" lay-filter="is_notify" title="是" >
<input type="radio" name="is_notify" lay-verify="required" value="0" lay-filter="is_notify" title="否" checked>
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item notify">
<hr>
<label class="layui-form-label mw200">通知类型</label>
<div class="layui-input-inline ">
<input type="radio" name="notify_type" lay-verify="required" value="0" title="邮件" checked>
<input type="radio" name="notify_type" lay-verify="required" value="1" title="短信" >
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item notify">
<label class="layui-form-label mw200">通知用户</label>
<div class="layui-input-inline mw400 ">
{{range $k, $v := .adminInfo}}
<input type="checkbox" name="notify_user" lay-filter="notify_user" title="{{$v.RealName}}" value="{{$v.Id}}" lay-skin="primary">
{{end}}
<input type="hidden" name="notify_user_ids" id="notify_user_ids" value="">
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<input type="hidden" name="id" id="id" value="0">
<div class="layui-form-item">
<label class="layui-form-label mw200"></label>
@@ -106,6 +143,30 @@
})
var notify_user_ids = [];
form.on('checkbox(notify_user)', function(data){
if(data.elem.checked==true){
notify_user_ids.push(data.value)
}else{
$.each(notify_user_ids,function(index,item){
// index是索引值即下标 item是每次遍历得到的值
if(item==data.value){
notify_user_ids.splice(index,1);
}
});
}
$("#notify_user_ids").val(notify_user_ids.join(","));
});
form.on('radio(is_notify)', function(data){
if(data.value==1){
$(".notify").show()
}else{
$(".notify").hide()
}
});
form.on('submit(sub)', function(data){
var form_data = data.field;
$.post('{{urlfor "TaskController.AjaxSave"}}', form_data, function (out) {

View File

@@ -1,3 +1,8 @@
<style>
.notify{
display: none;
}
</style>
<div class="layui-layout layui-layout-admin" style="padding-left: 40px;margin-top: 20px;">
<form class="layui-form" action="" method="post" >
<div class="layui-form-item">
@@ -79,6 +84,36 @@
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label mw200">出错通知</label>
<div class="layui-input-inline ">
<input type="radio" name="is_notify" lay-verify="required" value="1" lay-filter="is_notify" title="是" {{if eq .task.IsNotify 1}}checked{{end}}>
<input type="radio" name="is_notify" lay-verify="required" value="0" lay-filter="is_notify" title="否" {{if eq .task.IsNotify 0}}checked{{end}}>
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item notify">
<hr>
<label class="layui-form-label mw200">通知类型</label>
<div class="layui-input-inline ">
<input type="radio" name="notify_type" lay-verify="required" value="0" title="邮件" {{if eq .task.NotifyType 0}}checked{{end}}>
<input type="radio" name="notify_type" lay-verify="required" value="1" title="短信" {{if eq .task.NotifyType 1}}checked{{end}}>
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item notify">
<label class="layui-form-label mw200">通知用户</label>
<div class="layui-input-inline mw400 ">
{{range $k, $v := .adminInfo}}
<input type="checkbox" name="notify_user" lay-filter="notify_user" title="{{$v.RealName}}" value="{{$v.Id}}" lay-skin="primary" {{range $ks,$vs:=$.notify_user_ids}} {{if eq $v.Id $vs}}checked{{end}}{{end}}>
{{end}}
<input type="hidden" name="notify_user_ids" id="notify_user_ids" value="{{.task.NotifyUserIds}}">
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<input type="hidden" name="id" id="id" value="0">
<div class="layui-form-item">
<label class="layui-form-label mw200"></label>
@@ -106,6 +141,32 @@
});
})
if ($("input[name=is_notify]:checked").val()==1){
$(".notify").show();
}
form.on('radio(is_notify)', function(data){
if(data.value==1){
$(".notify").show()
}else{
$(".notify").hide()
}
});
var notify_user_ids = [];
form.on('checkbox(notify_user)', function(data){
if(data.elem.checked==true){
notify_user_ids.push(data.value)
}else{
$.each(notify_user_ids,function(index,item){
// index是索引值即下标 item是每次遍历得到的值
if(item==data.value){
notify_user_ids.splice(index,1);
}
});
}
$("#notify_user_ids").val(notify_user_ids.join(","));
});
form.on('submit(sub)', function(data){
var form_data = data.field;
$.post('{{urlfor "TaskController.AjaxSave"}}', form_data, function (out) {

View File

@@ -90,6 +90,31 @@
<td></td>
</tr>
<tr>
<td>出错通知</td>
<td>{{if eq .task.IsNotify 0}}{{end}} {{if eq .task.IsNotify 1}}{{end}}</td>
<td></td>
</tr>
{{if eq .task.IsNotify 1}}
<tr>
<td>通知类型</td>
<td>{{if eq .task.NotifyType 1}}短信{{end}} {{if eq .task.NotifyType 0}}邮件{{end}}</td>
<td></td>
</tr>
<tr>
<td>通知用户</td>
<td>
{{range $k, $v := .adminInfo}}
{{$v.RealName}} &nbsp;&nbsp;&nbsp;
{{end}}
</td>
<td></td>
</tr>
{{end}}
<tr>
<td>创建时间</td>
<td>{{.CreateTime}}</td>

View File

@@ -1,3 +1,8 @@
<style>
.notify{
display: none;
}
</style>
<div class="layui-layout layui-layout-admin" style="padding-left: 40px;margin-top: 20px;">
<form class="layui-form" action="javascript:return false;" method="post" >
<div class="layui-form-item">
@@ -78,6 +83,36 @@
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item">
<label class="layui-form-label mw200">出错通知</label>
<div class="layui-input-inline ">
<input type="radio" name="is_notify" lay-verify="required" value="1" lay-filter="is_notify" title="是" {{if eq .task.IsNotify 1}}checked{{end}}>
<input type="radio" name="is_notify" lay-verify="required" value="0" lay-filter="is_notify" title="否" {{if eq .task.IsNotify 0}}checked{{end}}>
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item notify">
<hr>
<label class="layui-form-label mw200">通知类型</label>
<div class="layui-input-inline ">
<input type="radio" name="notify_type" lay-verify="required" value="0" title="邮件" {{if eq .task.NotifyType 0}}checked{{end}}>
<input type="radio" name="notify_type" lay-verify="required" value="1" title="短信" {{if eq .task.NotifyType 1}}checked{{end}}>
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<div class="layui-form-item notify">
<label class="layui-form-label mw200">通知用户</label>
<div class="layui-input-inline mw400 ">
{{range $k, $v := .adminInfo}}
<input type="checkbox" name="notify_user" lay-filter="notify_user" title="{{$v.RealName}}" value="{{$v.Id}}" lay-skin="primary" {{range $ks,$vs:=$.notify_user_ids}} {{if eq $v.Id $vs}}checked{{end}}{{end}}>
{{end}}
<input type="hidden" name="notify_user_ids" id="notify_user_ids" value="{{.task.NotifyUserIds}}">
</div>
<div class="layui-form-mid layui-word-aux"></div>
</div>
<input type="hidden" name="id" id="id" value="{{.task.Id}}">
<div class="layui-form-item">
<label class="layui-form-label mw200"></label>
@@ -107,6 +142,32 @@
});
})
if ($("input[name=is_notify]:checked").val()==1){
$(".notify").show();
}
form.on('radio(is_notify)', function(data){
if(data.value==1){
$(".notify").show()
}else{
$(".notify").hide()
}
});
var notify_user_ids = [];
form.on('checkbox(notify_user)', function(data){
if(data.elem.checked==true){
notify_user_ids.push(data.value)
}else{
$.each(notify_user_ids,function(index,item){
// index是索引值即下标 item是每次遍历得到的值
if(item==data.value){
notify_user_ids.splice(index,1);
}
});
}
$("#notify_user_ids").val(notify_user_ids.join(","));
});
form.on('submit(sub)', function(data){
var isAdmin = "{{.isAdmin}}";
var msg = "编辑任务需要重新审核,是否确认需要编辑?";