diff --git a/assets/templates/cron_task/cron_task_add.html b/assets/templates/cron_task/cron_task_add.html new file mode 100644 index 0000000..ef5d2f8 --- /dev/null +++ b/assets/templates/cron_task/cron_task_add.html @@ -0,0 +1,299 @@ + + + + + + + + + + + + + +
+
+
+
+
+
新增后台任务
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+ + + +
+
+
+
+
+
+ + + + + + + + + diff --git a/assets/templates/cron_task/cron_task_edit.html b/assets/templates/cron_task/cron_task_edit.html new file mode 100644 index 0000000..58aab2c --- /dev/null +++ b/assets/templates/cron_task/cron_task_edit.html @@ -0,0 +1,349 @@ + + + + + + + + + + + + + +
+
+
+
+
+
编辑后台任务
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+ + + +
+
+
+
+
+
+ + + + + + + + + diff --git a/assets/templates/cron_task/cron_task_list.html b/assets/templates/cron_task/cron_task_list.html new file mode 100644 index 0000000..2122d45 --- /dev/null +++ b/assets/templates/cron_task/cron_task_list.html @@ -0,0 +1,284 @@ + + + + + + + + + + + + +
+
+ +
+
+
+
+ 新增 +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
编号任务名称cron 表达式执行方式超时限制(秒)重试次数重试间隔(秒)通知方式创建人创建日期可用状态操作
+
+
    +
      + +
    +
+
+
+
+ +
+
+ + + + + + + + + + diff --git a/assets/templates/install/install_view.html b/assets/templates/install/install_view.html index 11de33d..b0a690e 100644 --- a/assets/templates/install/install_view.html +++ b/assets/templates/install/install_view.html @@ -108,7 +108,8 @@ adminmenumenu_action、 - admin_menu; + admin_menu、 + cron_task; diff --git a/assets/templates/install/upgrade_view.html b/assets/templates/install/upgrade_view.html index 1b12b52..fb52f11 100644 --- a/assets/templates/install/upgrade_view.html +++ b/assets/templates/install/upgrade_view.html @@ -83,7 +83,16 @@ -> v1.2.7

-

......

+ +

1、源代码升级: + 拉取最新代码,覆盖旧版本代码即可。 +

+

2、数据表升级: + 创建 cron_task 表结构 +

+

3、系统管理员->菜单管理,新增侧边栏:后台任务模块,同时添加菜单栏的功能权限。

+

4、系统管理员->管理员,对管理员进行菜单授权。

+

5、退出重新登录,即可以看到自己新增的模块。


diff --git a/configs/constants.go b/configs/constants.go index 5aa1dea..39cc08d 100644 --- a/configs/constants.go +++ b/configs/constants.go @@ -2,7 +2,7 @@ package configs const ( // ProjectVersion 项目版本 - ProjectVersion = "v1.2.6" + ProjectVersion = "v1.2.7" // ProjectName 项目名称 ProjectName = "go-gin-api" diff --git a/internal/api/controller/cron_handler/func_create.go b/internal/api/controller/cron_handler/func_create.go new file mode 100755 index 0000000..7c41f34 --- /dev/null +++ b/internal/api/controller/cron_handler/func_create.go @@ -0,0 +1,99 @@ +package cron_handler + +import ( + "net/http" + + "github.com/xinliangnote/go-gin-api/internal/api/service/cron_service" + "github.com/xinliangnote/go-gin-api/internal/pkg/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/validation" + "github.com/xinliangnote/go-gin-api/pkg/errno" +) + +type createRequest struct { + Name string `form:"name" binding:"required"` // 任务名称 + Spec string `form:"spec" binding:"required"` // crontab 表达式 + Command string `form:"command" binding:"required"` // 执行命令 + Protocol int32 `form:"protocol" binding:"required"` // 执行方式 1:shell 2:http + HttpMethod int32 `form:"http_method"` // http 请求方式 1:get 2:post + Timeout int32 `form:"timeout" binding:"required"` // 超时时间(单位:秒) + RetryTimes int32 `form:"retry_times" binding:"required"` // 重试次数 + RetryInterval int32 `form:"retry_interval" binding:"required"` // 重试间隔(单位:秒) + NotifyStatus int32 `form:"notify_status" binding:"required"` // 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 + NotifyType int32 `form:"notify_type"` // 通知类型 1:邮件 2:webhook + NotifyReceiverEmail string `form:"notify_receiver_email"` // 通知者邮箱地址(多个用,分割) + NotifyKeyword string `form:"notify_keyword"` // 通知匹配关键字(多个用,分割) + Remark string `form:"remark"` // 备注 + IsUsed int32 `form:"is_used" binding:"required"` // 是否启用 1:是 -1:否 +} + +type createResponse struct { + Id int32 `json:"id"` // 主键ID +} + +// Create 创建任务 +// @Summary 创建任务 +// @Description 创建任务 +// @Tags API.cron +// @Accept multipart/form-data +// @Produce json +// @Param name formData string true "任务名称" +// @Param spec formData string true "crontab 表达式" +// @Param command formData string true "执行命令" +// @Param protocol formData int true "执行方式 1:shell 2:http" +// @Param http_method formData int false "http 请求方式 1:get 2:post" +// @Param timeout formData int true "超时时间(单位:秒)" +// @Param retry_times formData int true "重试次数" +// @Param retry_interval formData int true "重试间隔(单位:秒)" +// @Param notify_status formData int true "执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知" +// @Param notify_type formData int false "通知类型 1:邮件 2:webhook" +// @Param notify_receiver_email formData string false "通知者邮箱地址(多个用,分割)" +// @Param notify_keyword formData string false "通知匹配关键字(多个用,分割)" +// @Param remark formData string false "备注" +// @Param is_used formData int true "是否启用 1:是 -1:否" +// @Success 200 {object} createResponse +// @Failure 400 {object} code.Failure +// @Router /api/cron [post] +func (h *handler) Create() core.HandlerFunc { + return func(ctx core.Context) { + req := new(createRequest) + res := new(createResponse) + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)).WithErr(err), + ) + return + } + + createData := new(cron_service.CreateCronTaskData) + createData.Name = req.Name + createData.Spec = req.Spec + createData.Command = req.Command + createData.Protocol = req.Protocol + createData.HttpMethod = req.HttpMethod + createData.Timeout = req.Timeout + createData.RetryTimes = req.RetryTimes + createData.RetryInterval = req.RetryInterval + createData.NotifyStatus = req.NotifyStatus + createData.NotifyType = req.NotifyType + createData.NotifyReceiverEmail = req.NotifyReceiverEmail + createData.NotifyKeyword = req.NotifyKeyword + createData.Remark = req.Remark + createData.IsUsed = req.IsUsed + + id, err := h.cronService.Create(ctx, createData) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.CronCreateError, + code.Text(code.CronCreateError)).WithErr(err), + ) + return + } + + res.Id = id + ctx.Payload(res) + } +} diff --git a/internal/api/controller/cron_handler/func_detail.go b/internal/api/controller/cron_handler/func_detail.go new file mode 100755 index 0000000..c3ff66f --- /dev/null +++ b/internal/api/controller/cron_handler/func_detail.go @@ -0,0 +1,99 @@ +package cron_handler + +import ( + "github.com/xinliangnote/go-gin-api/internal/pkg/validation" + "net/http" + + "github.com/xinliangnote/go-gin-api/internal/api/service/cron_service" + "github.com/xinliangnote/go-gin-api/internal/pkg/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/pkg/errno" + + "github.com/spf13/cast" +) + +type detailRequest struct { + Id string `uri:"id"` // HashID +} + +type detailResponse struct { + Name string `json:"name"` // 任务名称 + Spec string `json:"spec"` // crontab 表达式 + Command string `json:"command"` // 执行命令 + Protocol int32 `json:"protocol"` // 执行方式 1:shell 2:http + HttpMethod int32 `json:"http_method"` // http 请求方式 1:get 2:post + Timeout int32 `json:"timeout"` // 超时时间(单位:秒) + RetryTimes int32 `json:"retry_times"` // 重试次数 + RetryInterval int32 `json:"retry_interval"` // 重试间隔(单位:秒) + NotifyStatus int32 `json:"notify_status"` // 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 + NotifyType int32 `json:"notify_type"` // 通知类型 1:邮件 2:webhook + NotifyReceiverEmail string `json:"notify_receiver_email"` // 通知者邮箱地址(多个用,分割) + NotifyKeyword string `json:"notify_keyword"` // 通知匹配关键字(多个用,分割) + Remark string `json:"remark"` // 备注 + IsUsed int32 `json:"is_used"` // 是否启用 1:是 -1:否 +} + +// Detail 获取单条任务详情 +// @Summary 获取单条任务详情 +// @Description 获取单条任务详情 +// @Tags API.cron +// @Accept json +// @Produce json +// @Param id path string true "hashId" +// @Success 200 {object} detailResponse +// @Failure 400 {object} code.Failure +// @Router /api/cron/:id [get] +func (h *handler) Detail() core.HandlerFunc { + return func(ctx core.Context) { + req := new(detailRequest) + res := new(detailResponse) + if err := ctx.ShouldBindURI(req); err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)).WithErr(err), + ) + return + } + + ids, err := h.hashids.HashidsDecode(req.Id) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.HashIdsDecodeError, + code.Text(code.HashIdsDecodeError)).WithErr(err), + ) + return + } + + searchOneData := new(cron_service.SearchOneData) + searchOneData.Id = cast.ToInt32(ids[0]) + + info, err := h.cronService.Detail(ctx, searchOneData) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.CronDetailError, + code.Text(code.CronDetailError)).WithErr(err), + ) + return + } + + res.Name = info.Name + res.Spec = info.Spec + res.Command = info.Command + res.Protocol = info.Protocol + res.HttpMethod = info.HttpMethod + res.Timeout = info.Timeout + res.RetryTimes = info.RetryTimes + res.RetryInterval = info.RetryInterval + res.NotifyStatus = info.NotifyStatus + res.NotifyType = info.NotifyType + res.NotifyReceiverEmail = info.NotifyReceiverEmail + res.NotifyKeyword = info.NotifyKeyword + res.Remark = info.Remark + res.IsUsed = info.IsUsed + + ctx.Payload(res) + } +} diff --git a/internal/api/controller/cron_handler/func_list.go b/internal/api/controller/cron_handler/func_list.go new file mode 100755 index 0000000..c547b73 --- /dev/null +++ b/internal/api/controller/cron_handler/func_list.go @@ -0,0 +1,165 @@ +package cron_handler + +import ( + "github.com/xinliangnote/go-gin-api/internal/pkg/validation" + "net/http" + + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/api/service/cron_service" + "github.com/xinliangnote/go-gin-api/internal/pkg/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/pkg/errno" + "github.com/xinliangnote/go-gin-api/pkg/time_parse" + + "github.com/spf13/cast" +) + +type listRequest struct { + Page int `form:"page"` // 第几页 + PageSize int `form:"page_size"` // 每页显示条数 + Name string `form:"name"` // 任务名称 + Protocol int `form:"protocol"` // 执行方式 1:shell 2:http + IsUsed int `form:"is_used"` // 是否启用 1:是 -1:否 +} + +type listData struct { + Id int `json:"id"` // ID + HashID string `json:"hashid"` // hashid + Name string `json:"name"` // 任务名称 + Protocol int `json:"protocol"` // 执行方式 1:shell 2:http + ProtocolText string `json:"protocol_text"` // 执行方式 + Spec string `json:"spec"` // crontab 表达式 + Command string `json:"command"` // 执行命令 + HttpMethod int `json:"http_method"` // http 请求方式 1:get 2:post + HttpMethodText string `json:"http_method_text"` // http 请求方式 + Timeout int `json:"timeout"` // 超时时间(单位:秒) + RetryTimes int `json:"retry_times"` // 重试次数 + RetryInterval int `json:"retry_interval"` // 重试间隔(单位:秒) + NotifyStatus int `json:"notify_status"` // 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 + NotifyStatusText string `json:"notify_status_text"` // 执行结束是否通知 + IsUsed int `json:"is_used"` // 是否启用 1=启用 2=禁用 + IsUsedText string `json:"is_used_text"` // 是否启用 + CreatedAt string `json:"created_at"` // 创建时间 + CreatedUser string `json:"created_user"` // 创建人 + UpdatedAt string `json:"updated_at"` // 更新时间 + UpdatedUser string `json:"updated_user"` // 更新人 +} + +type listResponse struct { + List []listData `json:"list"` + Pagination struct { + Total int `json:"total"` + CurrentPage int `json:"current_page"` + PrePageCount int `json:"pre_page_count"` + } `json:"pagination"` +} + +// List 任务列表 +// @Summary 任务列表 +// @Description 任务列表 +// @Tags API.cron +// @Accept multipart/form-data +// @Produce json +// @Param page query int false "第几页" +// @Param page_size query string false "每页显示条数" +// @Param name query string false "任务名称" +// @Param protocol query int false "执行方式 1:shell 2:http" +// @Param is_used query int false "是否启用 1:是 -1:否" +// @Success 200 {object} listResponse +// @Failure 400 {object} code.Failure +// @Router /api/cron [get] +func (h *handler) List() core.HandlerFunc { + return func(ctx core.Context) { + req := new(listRequest) + res := new(listResponse) + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)).WithErr(err), + ) + return + } + + page := req.Page + if page == 0 { + page = 1 + } + + pageSize := req.PageSize + if pageSize == 0 { + pageSize = 10 + } + + searchData := new(cron_service.SearchData) + searchData.Page = req.Page + searchData.PageSize = req.PageSize + searchData.Name = req.Name + searchData.Protocol = cast.ToInt32(req.Protocol) + searchData.IsUsed = cast.ToInt32(req.IsUsed) + + resListData, err := h.cronService.PageList(ctx, searchData) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.CronListError, + code.Text(code.CronListError)).WithErr(err), + ) + return + } + + resCountData, err := h.cronService.PageListCount(ctx, searchData) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.CronListError, + code.Text(code.CronListError)).WithErr(err), + ) + return + } + + res.Pagination.Total = cast.ToInt(resCountData) + res.Pagination.PrePageCount = pageSize + res.Pagination.CurrentPage = page + res.List = make([]listData, len(resListData)) + + for k, v := range resListData { + hashId, err := h.hashids.HashidsEncode([]int{cast.ToInt(v.Id)}) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.HashIdsEncodeError, + code.Text(code.HashIdsEncodeError)).WithErr(err), + ) + return + } + + data := listData{ + Id: cast.ToInt(v.Id), + HashID: hashId, + Name: v.Name, + Protocol: cast.ToInt(v.Protocol), + ProtocolText: cron_task_repo.ProtocolText[v.Protocol], + Spec: v.Spec, + Command: v.Command, + HttpMethod: cast.ToInt(v.HttpMethod), + HttpMethodText: cron_task_repo.HttpMethodText[v.HttpMethod], + Timeout: cast.ToInt(v.Timeout), + RetryTimes: cast.ToInt(v.RetryTimes), + RetryInterval: cast.ToInt(v.RetryInterval), + NotifyStatus: cast.ToInt(v.NotifyStatus), + NotifyStatusText: cron_task_repo.NotifyStatusText[v.NotifyStatus], + IsUsed: cast.ToInt(v.IsUsed), + IsUsedText: cron_task_repo.IsUsedText[v.IsUsed], + CreatedAt: v.CreatedAt.Format(time_parse.CSTLayout), + CreatedUser: v.CreatedUser, + UpdatedAt: v.UpdatedAt.Format(time_parse.CSTLayout), + UpdatedUser: v.UpdatedUser, + } + + res.List[k] = data + } + + ctx.Payload(res) + } +} diff --git a/internal/api/controller/cron_handler/func_modify.go b/internal/api/controller/cron_handler/func_modify.go new file mode 100755 index 0000000..e22298c --- /dev/null +++ b/internal/api/controller/cron_handler/func_modify.go @@ -0,0 +1,111 @@ +package cron_handler + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/service/cron_service" + "github.com/xinliangnote/go-gin-api/internal/pkg/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/validation" + "github.com/xinliangnote/go-gin-api/pkg/errno" + "net/http" +) + +type modifyRequest struct { + Id string `form:"id" binding:"required"` // 任务ID + Name string `form:"name" binding:"required"` // 任务名称 + Spec string `form:"spec" binding:"required"` // crontab 表达式 + Command string `form:"command" binding:"required"` // 执行命令 + Protocol int32 `form:"protocol" binding:"required"` // 执行方式 1:shell 2:http + HttpMethod int32 `form:"http_method"` // http 请求方式 1:get 2:post + Timeout int32 `form:"timeout" binding:"required"` // 超时时间(单位:秒) + RetryTimes int32 `form:"retry_times" binding:"required"` // 重试次数 + RetryInterval int32 `form:"retry_interval" binding:"required"` // 重试间隔(单位:秒) + NotifyStatus int32 `form:"notify_status" binding:"required"` // 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 + NotifyType int32 `form:"notify_type"` // 通知类型 1:邮件 2:webhook + NotifyReceiverEmail string `form:"notify_receiver_email"` // 通知者邮箱地址(多个用,分割) + NotifyKeyword string `form:"notify_keyword"` // 通知匹配关键字(多个用,分割) + Remark string `form:"remark"` // 备注 + IsUsed int32 `form:"is_used" binding:"required"` // 是否启用 1:是 -1:否 +} + +type modifyResponse struct { + Id int32 `json:"id"` // 主键ID +} + +// Modify 编辑任务 +// @Summary 编辑任务 +// @Description 编辑任务 +// @Tags API.cron +// @Accept multipart/form-data +// @Produce json +// @Param id formData string true "Hashid" +// @Param name formData string true "任务名称" +// @Param spec formData string true "crontab 表达式" +// @Param command formData string true "执行命令" +// @Param protocol formData int true "执行方式 1:shell 2:http" +// @Param http_method formData int false "http 请求方式 1:get 2:post" +// @Param timeout formData int true "超时时间(单位:秒)" +// @Param retry_times formData int true "重试次数" +// @Param retry_interval formData int true "重试间隔(单位:秒)" +// @Param notify_status formData int true "执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知" +// @Param notify_type formData int false "通知类型 1:邮件 2:webhook" +// @Param notify_receiver_email formData string false "通知者邮箱地址(多个用,分割)" +// @Param notify_keyword formData string false "通知匹配关键字(多个用,分割)" +// @Param remark formData string false "备注" +// @Param is_used formData int true "是否启用 1:是 -1:否" +// @Success 200 {object} modifyResponse +// @Failure 400 {object} code.Failure +// @Router /api/cron/{id} [post] +func (h *handler) Modify() core.HandlerFunc { + return func(ctx core.Context) { + req := new(modifyRequest) + res := new(modifyResponse) + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)).WithErr(err), + ) + return + } + + ids, err := h.hashids.HashidsDecode(req.Id) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.HashIdsDecodeError, + code.Text(code.HashIdsDecodeError)).WithErr(err), + ) + return + } + + id := int32(ids[0]) + + modifyData := new(cron_service.ModifyCronTaskData) + modifyData.Name = req.Name + modifyData.Spec = req.Spec + modifyData.Command = req.Command + modifyData.Protocol = req.Protocol + modifyData.HttpMethod = req.HttpMethod + modifyData.Timeout = req.Timeout + modifyData.RetryTimes = req.RetryTimes + modifyData.RetryInterval = req.RetryInterval + modifyData.NotifyStatus = req.NotifyStatus + modifyData.NotifyType = req.NotifyType + modifyData.NotifyReceiverEmail = req.NotifyReceiverEmail + modifyData.NotifyKeyword = req.NotifyKeyword + modifyData.Remark = req.Remark + modifyData.IsUsed = req.IsUsed + + if err := h.cronService.Modify(ctx, id, modifyData); err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.CronUpdateError, + code.Text(code.CronUpdateError)).WithErr(err), + ) + return + } + + res.Id = id + ctx.Payload(res) + } +} diff --git a/internal/api/controller/cron_handler/func_updateused.go b/internal/api/controller/cron_handler/func_updateused.go new file mode 100755 index 0000000..e974c72 --- /dev/null +++ b/internal/api/controller/cron_handler/func_updateused.go @@ -0,0 +1,70 @@ +package cron_handler + +import ( + "github.com/xinliangnote/go-gin-api/internal/pkg/validation" + "net/http" + + "github.com/xinliangnote/go-gin-api/internal/pkg/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/pkg/errno" +) + +type updateUsedRequest struct { + Id string `form:"id"` // 主键ID + Used int32 `form:"used"` // 是否启用 1:是 -1:否 +} + +type updateUsedResponse struct { + Id int32 `json:"id"` // 主键ID +} + +// UpdateUsed 更新任务为启用/禁用 +// @Summary 更新任务为启用/禁用 +// @Description 更新任务为启用/禁用 +// @Tags API.cron +// @Accept multipart/form-data +// @Produce json +// @Param id formData string true "Hashid" +// @Param used formData int true "是否启用 1:是 -1:否" +// @Success 200 {object} updateUsedResponse +// @Failure 400 {object} code.Failure +// @Router /api/cron/used [patch] +func (h *handler) UpdateUsed() core.HandlerFunc { + return func(ctx core.Context) { + req := new(updateUsedRequest) + res := new(updateUsedResponse) + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ParamBindError, + validation.Error(err)).WithErr(err), + ) + return + } + + ids, err := h.hashids.HashidsDecode(req.Id) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.HashIdsDecodeError, + code.Text(code.HashIdsDecodeError)).WithErr(err), + ) + return + } + + id := int32(ids[0]) + + err = h.cronService.UpdateUsed(ctx, id, req.Used) + if err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.AdminUpdateError, + code.Text(code.AdminUpdateError)).WithErr(err), + ) + return + } + + res.Id = id + ctx.Payload(res) + } +} diff --git a/internal/api/controller/cron_handler/handler.go b/internal/api/controller/cron_handler/handler.go new file mode 100644 index 0000000..796c79e --- /dev/null +++ b/internal/api/controller/cron_handler/handler.go @@ -0,0 +1,61 @@ +package cron_handler + +import ( + "github.com/xinliangnote/go-gin-api/configs" + "github.com/xinliangnote/go-gin-api/internal/api/service/cron_service" + "github.com/xinliangnote/go-gin-api/internal/pkg/cache" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/db" + "github.com/xinliangnote/go-gin-api/pkg/hash" + + "go.uber.org/zap" +) + +var _ Handler = (*handler)(nil) + +type Handler interface { + i() + + // Create 创建任务 + // @Tags API.cron + // @Router /api/cron [post] + Create() core.HandlerFunc + + // Modify 编辑任务 + // @Tags API.cron + // @Router /api/cron/{id} [post] + Modify() core.HandlerFunc + + // List 任务列表 + // @Tags API.cron + // @Router /api/cron [get] + List() core.HandlerFunc + + // UpdateUsed 更新任务为启用/禁用 + // @Tags API.cron + // @Router /api/cron/used [patch] + UpdateUsed() core.HandlerFunc + + // Detail 获取单条任务详情 + // @Tags API.cron + // @Router /api/cron/:id [get] + Detail() core.HandlerFunc +} + +type handler struct { + logger *zap.Logger + cache cache.Repo + hashids hash.Hash + cronService cron_service.Service +} + +func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler { + return &handler{ + logger: logger, + cache: cache, + hashids: hash.New(configs.Get().HashIds.Secret, configs.Get().HashIds.Length), + cronService: cron_service.New(db, cache), + } +} + +func (h *handler) i() {} diff --git a/internal/api/repository/db_repo/cron_task_repo/gen_cron_task.go b/internal/api/repository/db_repo/cron_task_repo/gen_cron_task.go new file mode 100755 index 0000000..f846da2 --- /dev/null +++ b/internal/api/repository/db_repo/cron_task_repo/gen_cron_task.go @@ -0,0 +1,937 @@ +/////////////////////////////////////////////////////////// +// THIS FILE IS AUTO GENERATED by gormgen, DON'T EDIT IT // +// ANY CHANGES DONE HERE WILL BE LOST // +/////////////////////////////////////////////////////////// + +package cron_task_repo + +import ( + "fmt" + "time" + + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + + "github.com/pkg/errors" + "gorm.io/gorm" +) + +func NewModel() *CronTask { + return new(CronTask) +} + +func NewQueryBuilder() *cronTaskRepoQueryBuilder { + return new(cronTaskRepoQueryBuilder) +} + +func (t *CronTask) Create(db *gorm.DB) (id int32, err error) { + if err = db.Create(t).Error; err != nil { + return 0, errors.Wrap(err, "create err") + } + return t.Id, nil +} + +type cronTaskRepoQueryBuilder struct { + order []string + where []struct { + prefix string + value interface{} + } + limit int + offset int +} + +func (qb *cronTaskRepoQueryBuilder) buildQuery(db *gorm.DB) *gorm.DB { + ret := db + for _, where := range qb.where { + ret = ret.Where(where.prefix, where.value) + } + for _, order := range qb.order { + ret = ret.Order(order) + } + ret = ret.Limit(qb.limit).Offset(qb.offset) + return ret +} + +func (qb *cronTaskRepoQueryBuilder) Updates(db *gorm.DB, m map[string]interface{}) (err error) { + db = db.Model(&CronTask{}) + + for _, where := range qb.where { + db.Where(where.prefix, where.value) + } + + if err = db.Updates(m).Error; err != nil { + return errors.Wrap(err, "updates err") + } + return nil +} + +func (qb *cronTaskRepoQueryBuilder) Delete(db *gorm.DB) (err error) { + for _, where := range qb.where { + db = db.Where(where.prefix, where.value) + } + + if err = db.Delete(&CronTask{}).Error; err != nil { + return errors.Wrap(err, "delete err") + } + return nil +} + +func (qb *cronTaskRepoQueryBuilder) Count(db *gorm.DB) (int64, error) { + var c int64 + res := qb.buildQuery(db).Model(&CronTask{}).Count(&c) + if res.Error != nil && res.Error == gorm.ErrRecordNotFound { + c = 0 + } + return c, res.Error +} + +func (qb *cronTaskRepoQueryBuilder) First(db *gorm.DB) (*CronTask, error) { + ret := &CronTask{} + res := qb.buildQuery(db).First(ret) + if res.Error != nil && res.Error == gorm.ErrRecordNotFound { + ret = nil + } + return ret, res.Error +} + +func (qb *cronTaskRepoQueryBuilder) QueryOne(db *gorm.DB) (*CronTask, error) { + qb.limit = 1 + ret, err := qb.QueryAll(db) + if len(ret) > 0 { + return ret[0], err + } + return nil, err +} + +func (qb *cronTaskRepoQueryBuilder) QueryAll(db *gorm.DB) ([]*CronTask, error) { + var ret []*CronTask + err := qb.buildQuery(db).Find(&ret).Error + return ret, err +} + +func (qb *cronTaskRepoQueryBuilder) Limit(limit int) *cronTaskRepoQueryBuilder { + qb.limit = limit + return qb +} + +func (qb *cronTaskRepoQueryBuilder) Offset(offset int) *cronTaskRepoQueryBuilder { + qb.offset = offset + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereId(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "id", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereIdIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "id", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereIdNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "id", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderById(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "id "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereName(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "name", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNameIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "name", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNameNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "name", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByName(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "name "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereSpec(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "spec", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereSpecIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "spec", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereSpecNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "spec", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderBySpec(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "spec "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCommand(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "command", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCommandIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "command", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCommandNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "command", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByCommand(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "command "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereProtocol(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "protocol", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereProtocolIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "protocol", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereProtocolNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "protocol", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByProtocol(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "protocol "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereHttpMethod(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "http_method", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereHttpMethodIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "http_method", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereHttpMethodNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "http_method", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByHttpMethod(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "http_method "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereTimeout(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "timeout", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereTimeoutIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "timeout", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereTimeoutNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "timeout", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByTimeout(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "timeout "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRetryTimes(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "retry_times", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRetryTimesIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "retry_times", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRetryTimesNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "retry_times", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByRetryTimes(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "retry_times "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRetryInterval(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "retry_interval", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRetryIntervalIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "retry_interval", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRetryIntervalNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "retry_interval", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByRetryInterval(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "retry_interval "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyStatus(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_status", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyStatusIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_status", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyStatusNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_status", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByNotifyStatus(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "notify_status "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyType(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_type", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyTypeIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_type", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyTypeNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_type", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByNotifyType(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "notify_type "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyReceiverEmail(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_receiver_email", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyReceiverEmailIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_receiver_email", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyReceiverEmailNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_receiver_email", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByNotifyReceiverEmail(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "notify_receiver_email "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyKeyword(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_keyword", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyKeywordIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_keyword", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereNotifyKeywordNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "notify_keyword", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByNotifyKeyword(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "notify_keyword "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRemark(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "remark", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRemarkIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "remark", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereRemarkNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "remark", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByRemark(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "remark "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereIsUsed(p db_repo.Predicate, value int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "is_used", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereIsUsedIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "is_used", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereIsUsedNotIn(value []int32) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "is_used", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByIsUsed(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "is_used "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCreatedAt(p db_repo.Predicate, value time.Time) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "created_at", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCreatedAtIn(value []time.Time) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "created_at", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCreatedAtNotIn(value []time.Time) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "created_at", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByCreatedAt(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "created_at "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCreatedUser(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "created_user", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCreatedUserIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "created_user", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereCreatedUserNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "created_user", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByCreatedUser(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "created_user "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereUpdatedAt(p db_repo.Predicate, value time.Time) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "updated_at", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereUpdatedAtIn(value []time.Time) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "updated_at", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereUpdatedAtNotIn(value []time.Time) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "updated_at", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByUpdatedAt(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "updated_at "+order) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereUpdatedUser(p db_repo.Predicate, value string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "updated_user", p), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereUpdatedUserIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "updated_user", "IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) WhereUpdatedUserNotIn(value []string) *cronTaskRepoQueryBuilder { + qb.where = append(qb.where, struct { + prefix string + value interface{} + }{ + fmt.Sprintf("%v %v ?", "updated_user", "NOT IN"), + value, + }) + return qb +} + +func (qb *cronTaskRepoQueryBuilder) OrderByUpdatedUser(asc bool) *cronTaskRepoQueryBuilder { + order := "DESC" + if asc { + order = "ASC" + } + + qb.order = append(qb.order, "updated_user "+order) + return qb +} diff --git a/internal/api/repository/db_repo/cron_task_repo/gen_model.go b/internal/api/repository/db_repo/cron_task_repo/gen_model.go new file mode 100755 index 0000000..d780b6c --- /dev/null +++ b/internal/api/repository/db_repo/cron_task_repo/gen_model.go @@ -0,0 +1,27 @@ +package cron_task_repo + +import "time" + +// CronTask 后台任务表 +//go:generate gormgen -structs CronTask -input . +type CronTask struct { + Id int32 // 主键 + Name string // 任务名称 + Spec string // crontab 表达式 + Command string // 执行命令 + Protocol int32 // 执行方式 1:shell 2:http + HttpMethod int32 // http 请求方式 1:get 2:post + Timeout int32 // 超时时间(单位:秒) + RetryTimes int32 // 重试次数 + RetryInterval int32 // 重试间隔(单位:秒) + NotifyStatus int32 // 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 + NotifyType int32 // 通知类型 1:邮件 2:webhook + NotifyReceiverEmail string // 通知者邮箱地址(多个用,分割) + NotifyKeyword string // 通知匹配关键字(多个用,分割) + Remark string // 备注 + IsUsed int32 // 是否启用 1:是 -1:否 + CreatedAt time.Time `gorm:"time"` // 创建时间 + CreatedUser string // 创建人 + UpdatedAt time.Time `gorm:"time"` // 更新时间 + UpdatedUser string // 更新人 +} diff --git a/internal/api/repository/db_repo/cron_task_repo/gen_table.md b/internal/api/repository/db_repo/cron_task_repo/gen_table.md new file mode 100755 index 0000000..7580914 --- /dev/null +++ b/internal/api/repository/db_repo/cron_task_repo/gen_table.md @@ -0,0 +1,24 @@ +#### go_gin_api.cron_task +后台任务表 + +| 序号 | 名称 | 描述 | 类型 | 键 | 为空 | 额外 | 默认值 | +| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | +| 1 | id | 主键 | int unsigned | PRI | NO | auto_increment | | +| 2 | name | 任务名称 | varchar(64) | MUL | NO | | | +| 3 | spec | crontab 表达式 | varchar(64) | | NO | | | +| 4 | command | 执行命令 | varchar(255) | | NO | | | +| 5 | protocol | 执行方式 1:shell 2:http | tinyint unsigned | | NO | | 1 | +| 6 | http_method | http 请求方式 1:get 2:post | tinyint unsigned | | NO | | 1 | +| 7 | timeout | 超时时间(单位:秒) | int unsigned | | NO | | 60 | +| 8 | retry_times | 重试次数 | tinyint(1) | | NO | | 3 | +| 9 | retry_interval | 重试间隔(单位:秒) | int | | NO | | 60 | +| 10 | notify_status | 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 | tinyint unsigned | | NO | | 0 | +| 11 | notify_type | 通知类型 1:邮件 2:webhook | tinyint unsigned | | NO | | 1 | +| 12 | notify_receiver_email | 通知者邮箱地址(多个用,分割) | varchar(255) | | NO | | | +| 13 | notify_keyword | 通知匹配关键字(多个用,分割) | varchar(255) | | NO | | | +| 14 | remark | 备注 | varchar(100) | | NO | | | +| 15 | is_used | 是否启用 1:是 -1:否 | tinyint(1) | | NO | | 1 | +| 16 | created_at | 创建时间 | timestamp | | NO | DEFAULT_GENERATED | CURRENT_TIMESTAMP | +| 17 | created_user | 创建人 | varchar(60) | | NO | | | +| 18 | updated_at | 更新时间 | timestamp | | NO | DEFAULT_GENERATED on update CURRENT_TIMESTAMP | CURRENT_TIMESTAMP | +| 19 | updated_user | 更新人 | varchar(60) | | NO | | | diff --git a/internal/api/repository/db_repo/cron_task_repo/model.go b/internal/api/repository/db_repo/cron_task_repo/model.go new file mode 100644 index 0000000..23db5c7 --- /dev/null +++ b/internal/api/repository/db_repo/cron_task_repo/model.go @@ -0,0 +1,47 @@ +package cron_task_repo + +const ( + ProtocolShell = 1 + ProtocolHTTP = 2 + + HttpMethodGet = 1 + HttpMethodPost = 2 + + NotifyStatusNo = 1 + NotifyStatusFailed = 2 + NotifyStatusStopped = 3 + NotifyStatusKeyword = 4 + + NotifyTypeEmail = 1 + NotifyTypeWebhook = 2 + + IsUsedYES = 1 + IsUsedNo = -1 +) + +var ProtocolText = map[int32]string{ + ProtocolShell: "SHELL", + ProtocolHTTP: "HTTP", +} + +var HttpMethodText = map[int32]string{ + HttpMethodGet: "GET", + HttpMethodPost: "POST", +} + +var NotifyStatusText = map[int32]string{ + NotifyStatusNo: "不通知", + NotifyStatusFailed: "失败通知", + NotifyStatusStopped: "结束通知", + NotifyStatusKeyword: "结果关键字匹配通知", +} + +var NotifyTypeText = map[int32]string{ + NotifyTypeEmail: "邮件", + NotifyTypeWebhook: "Webhook", +} + +var IsUsedText = map[int32]string{ + IsUsedYES: "启用", + IsUsedNo: "禁用", +} diff --git a/internal/api/service/cron_service/service.go b/internal/api/service/cron_service/service.go new file mode 100644 index 0000000..37b3352 --- /dev/null +++ b/internal/api/service/cron_service/service.go @@ -0,0 +1,35 @@ +package cron_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/cache" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/db" +) + +var _ Service = (*service)(nil) + +type Service interface { + i() + + Create(ctx core.Context, createData *CreateCronTaskData) (id int32, err error) + Modify(ctx core.Context, id int32, modifyData *ModifyCronTaskData) (err error) + PageList(ctx core.Context, searchData *SearchData) (listData []*cron_task_repo.CronTask, err error) + PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error) + UpdateUsed(ctx core.Context, id int32, used int32) (err error) + Detail(ctx core.Context, searchOneData *SearchOneData) (info *cron_task_repo.CronTask, err error) +} + +type service struct { + db db.Repo + cache cache.Repo +} + +func New(db db.Repo, cache cache.Repo) Service { + return &service{ + db: db, + cache: cache, + } +} + +func (s *service) i() {} diff --git a/internal/api/service/cron_service/service_create.go b/internal/api/service/cron_service/service_create.go new file mode 100644 index 0000000..b58e5c6 --- /dev/null +++ b/internal/api/service/cron_service/service_create.go @@ -0,0 +1,49 @@ +package cron_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) + +type CreateCronTaskData struct { + Name string // 任务名称 + Spec string // crontab 表达式 + Command string // 执行命令 + Protocol int32 // 执行方式 1:shell 2:http + HttpMethod int32 // http 请求方式 1:get 2:post + Timeout int32 // 超时时间(单位:秒) + RetryTimes int32 // 重试次数 + RetryInterval int32 // 重试间隔(单位:秒) + NotifyStatus int32 // 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 + NotifyType int32 // 通知类型 1:邮件 2:webhook + NotifyReceiverEmail string // 通知者邮箱地址(多个用,分割) + NotifyKeyword string // 通知匹配关键字(多个用,分割) + Remark string // 备注 + IsUsed int32 // 是否启用 1:是 -1:否 +} + +func (s *service) Create(ctx core.Context, createData *CreateCronTaskData) (id int32, err error) { + model := cron_task_repo.NewModel() + model.Name = createData.Name + model.Spec = createData.Spec + model.Command = createData.Command + model.Protocol = createData.Protocol + model.HttpMethod = createData.HttpMethod + model.Timeout = createData.Timeout + model.RetryTimes = createData.RetryTimes + model.RetryInterval = createData.RetryInterval + model.NotifyStatus = createData.NotifyStatus + model.NotifyType = createData.NotifyType + model.NotifyReceiverEmail = createData.NotifyReceiverEmail + model.NotifyKeyword = createData.NotifyKeyword + model.Remark = createData.Remark + model.IsUsed = createData.IsUsed + model.CreatedUser = ctx.UserName() + + id, err = model.Create(s.db.GetDbW().WithContext(ctx.RequestContext())) + if err != nil { + return 0, err + } + + return +} diff --git a/internal/api/service/cron_service/service_detail.go b/internal/api/service/cron_service/service_detail.go new file mode 100644 index 0000000..88ea7f1 --- /dev/null +++ b/internal/api/service/cron_service/service_detail.go @@ -0,0 +1,26 @@ +package cron_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) + +type SearchOneData struct { + Id int32 // 任务ID +} + +func (s *service) Detail(ctx core.Context, searchOneData *SearchOneData) (info *cron_task_repo.CronTask, err error) { + qb := cron_task_repo.NewQueryBuilder() + + if searchOneData.Id != 0 { + qb.WhereId(db_repo.EqualPredicate, searchOneData.Id) + } + + info, err = qb.QueryOne(s.db.GetDbR().WithContext(ctx.RequestContext())) + if err != nil { + return nil, err + } + + return +} diff --git a/internal/api/service/cron_service/service_modify.go b/internal/api/service/cron_service/service_modify.go new file mode 100644 index 0000000..0ca9fae --- /dev/null +++ b/internal/api/service/cron_service/service_modify.go @@ -0,0 +1,53 @@ +package cron_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) + +type ModifyCronTaskData struct { + Name string // 任务名称 + Spec string // crontab 表达式 + Command string // 执行命令 + Protocol int32 // 执行方式 1:shell 2:http + HttpMethod int32 // http 请求方式 1:get 2:post + Timeout int32 // 超时时间(单位:秒) + RetryTimes int32 // 重试次数 + RetryInterval int32 // 重试间隔(单位:秒) + NotifyStatus int32 // 执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知 + NotifyType int32 // 通知类型 1:邮件 2:webhook + NotifyReceiverEmail string // 通知者邮箱地址(多个用,分割) + NotifyKeyword string // 通知匹配关键字(多个用,分割) + Remark string // 备注 + IsUsed int32 // 是否启用 1:是 -1:否 +} + +func (s *service) Modify(ctx core.Context, id int32, modifyData *ModifyCronTaskData) (err error) { + data := map[string]interface{}{ + "name": modifyData.Name, + "spec": modifyData.Spec, + "command": modifyData.Command, + "protocol": modifyData.Protocol, + "http_method": modifyData.HttpMethod, + "timeout": modifyData.Timeout, + "retry_times": modifyData.RetryTimes, + "retry_interval": modifyData.RetryInterval, + "notify_status": modifyData.NotifyStatus, + "notify_type": modifyData.NotifyType, + "notify_receiver_email": modifyData.NotifyReceiverEmail, + "notify_keyword": modifyData.NotifyKeyword, + "remark": modifyData.Remark, + "is_used": modifyData.IsUsed, + "updated_user": ctx.UserName(), + } + + qb := cron_task_repo.NewQueryBuilder() + qb.WhereId(db_repo.EqualPredicate, id) + err = qb.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data) + if err != nil { + return err + } + + return +} diff --git a/internal/api/service/cron_service/service_pagelist.go b/internal/api/service/cron_service/service_pagelist.go new file mode 100644 index 0000000..f2c6693 --- /dev/null +++ b/internal/api/service/cron_service/service_pagelist.go @@ -0,0 +1,54 @@ +package cron_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) + +type SearchData struct { + Page int // 第几页 + PageSize int // 每页显示条数 + Name string // 任务名称 + Protocol int32 // 执行方式 + IsUsed int32 // 是否启用 +} + +func (s *service) PageList(ctx core.Context, searchData *SearchData) (listData []*cron_task_repo.CronTask, err error) { + page := searchData.Page + if page == 0 { + page = 1 + } + + pageSize := searchData.PageSize + if pageSize == 0 { + pageSize = 10 + } + + offset := (page - 1) * pageSize + + qb := cron_task_repo.NewQueryBuilder() + + if searchData.Name != "" { + qb.WhereName(db_repo.EqualPredicate, searchData.Name) + } + + if searchData.Protocol != 0 { + qb.WhereProtocol(db_repo.EqualPredicate, searchData.Protocol) + } + + if searchData.IsUsed != 0 { + qb.WhereIsUsed(db_repo.EqualPredicate, searchData.IsUsed) + } + + listData, err = qb. + Limit(pageSize). + Offset(offset). + OrderById(false). + QueryAll(s.db.GetDbR().WithContext(ctx.RequestContext())) + if err != nil { + return nil, err + } + + return +} diff --git a/internal/api/service/cron_service/service_pagelistcount.go b/internal/api/service/cron_service/service_pagelistcount.go new file mode 100644 index 0000000..f31f61d --- /dev/null +++ b/internal/api/service/cron_service/service_pagelistcount.go @@ -0,0 +1,30 @@ +package cron_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) + +func (s *service) PageListCount(ctx core.Context, searchData *SearchData) (total int64, err error) { + qb := cron_task_repo.NewQueryBuilder() + + if searchData.Name != "" { + qb.WhereName(db_repo.EqualPredicate, searchData.Name) + } + + if searchData.Protocol != 0 { + qb.WhereProtocol(db_repo.EqualPredicate, searchData.Protocol) + } + + if searchData.IsUsed != 0 { + qb.WhereIsUsed(db_repo.EqualPredicate, searchData.IsUsed) + } + + total, err = qb.Count(s.db.GetDbR().WithContext(ctx.RequestContext())) + if err != nil { + return 0, err + } + + return +} diff --git a/internal/api/service/cron_service/service_updateused.go b/internal/api/service/cron_service/service_updateused.go new file mode 100644 index 0000000..7bb1953 --- /dev/null +++ b/internal/api/service/cron_service/service_updateused.go @@ -0,0 +1,23 @@ +package cron_service + +import ( + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo" + "github.com/xinliangnote/go-gin-api/internal/api/repository/db_repo/cron_task_repo" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" +) + +func (s *service) UpdateUsed(ctx core.Context, id int32, used int32) (err error) { + data := map[string]interface{}{ + "is_used": used, + "updated_user": ctx.UserName(), + } + + qb := cron_task_repo.NewQueryBuilder() + qb.WhereId(db_repo.EqualPredicate, id) + err = qb.Updates(s.db.GetDbW().WithContext(ctx.RequestContext()), data) + if err != nil { + return err + } + + return +} diff --git a/internal/pkg/code/code.go b/internal/pkg/code/code.go index d9a35a6..64feffc 100644 --- a/internal/pkg/code/code.go +++ b/internal/pkg/code/code.go @@ -60,6 +60,11 @@ const ( MenuCreateActionError = 20306 MenuListActionError = 20307 MenuDeleteActionError = 20308 + + CronCreateError = 20401 + CronUpdateError = 20402 + CronListError = 20403 + CronDetailError = 20404 ) func Text(code int) string { diff --git a/internal/pkg/code/en-us.go b/internal/pkg/code/en-us.go index a700c53..77b0094 100644 --- a/internal/pkg/code/en-us.go +++ b/internal/pkg/code/en-us.go @@ -52,4 +52,9 @@ var enUSText = map[int]string{ MenuCreateActionError: "Failed to create menu action", MenuListActionError: "Failed to get menu action list", MenuDeleteActionError: "Failed to delete menu action", + + CronCreateError: "Failed to create cron", + CronUpdateError: "Failed to update menu", + CronListError: "Failed to get cron list", + CronDetailError: "Failed to get cron detail", } diff --git a/internal/pkg/code/zh-cn.go b/internal/pkg/code/zh-cn.go index a200586..a055b01 100644 --- a/internal/pkg/code/zh-cn.go +++ b/internal/pkg/code/zh-cn.go @@ -52,4 +52,9 @@ var zhCNText = map[int]string{ MenuCreateActionError: "创建菜单栏功能权限失败", MenuListActionError: "获取菜单栏功能权限列表失败", MenuDeleteActionError: "删除菜单栏功能权限失败", + + CronCreateError: "创建后台任务失败", + CronUpdateError: "更新后台任务失败", + CronListError: "获取定时任务列表失败", + CronDetailError: "获取定时任务详情失败", } diff --git a/internal/router/router_api.go b/internal/router/router_api.go index 5a2532f..b6be032 100644 --- a/internal/router/router_api.go +++ b/internal/router/router_api.go @@ -4,6 +4,7 @@ import ( "github.com/xinliangnote/go-gin-api/internal/api/controller/admin_handler" "github.com/xinliangnote/go-gin-api/internal/api/controller/authorized_handler" "github.com/xinliangnote/go-gin-api/internal/api/controller/config_handler" + "github.com/xinliangnote/go-gin-api/internal/api/controller/cron_handler" "github.com/xinliangnote/go-gin-api/internal/api/controller/menu_handler" "github.com/xinliangnote/go-gin-api/internal/api/controller/tool_handler" "github.com/xinliangnote/go-gin-api/internal/pkg/core" @@ -79,5 +80,13 @@ func setApiRouter(r *resource) { configHandler := config_handler.New(r.logger, r.db, r.cache) api.PATCH("/config/email", configHandler.Email()) + // cron + cronHandler := cron_handler.New(r.logger, r.db, r.cache) + api.POST("/cron", cronHandler.Create()) + api.GET("/cron", cronHandler.List()) + api.GET("/cron/:id", cronHandler.Detail()) + api.POST("/cron/:id", cronHandler.Modify()) + api.PATCH("/cron/used", cronHandler.UpdateUsed()) + } } diff --git a/internal/router/router_web.go b/internal/router/router_web.go index c347bd3..0722d31 100644 --- a/internal/router/router_web.go +++ b/internal/router/router_web.go @@ -4,6 +4,7 @@ import ( "github.com/xinliangnote/go-gin-api/internal/web/controller/admin_handler" "github.com/xinliangnote/go-gin-api/internal/web/controller/authorized_handler" "github.com/xinliangnote/go-gin-api/internal/web/controller/config_handler" + "github.com/xinliangnote/go-gin-api/internal/web/controller/cron_handler" "github.com/xinliangnote/go-gin-api/internal/web/controller/dashboard_handler" "github.com/xinliangnote/go-gin-api/internal/web/controller/generator_handler" "github.com/xinliangnote/go-gin-api/internal/web/controller/index_handler" @@ -23,6 +24,7 @@ func setWebRouter(r *resource) { toolHandler := tool_handler.New(r.logger, r.db, r.cache) adminHandler := admin_handler.New(r.logger, r.db, r.cache) upgradeHandler := upgrade_handler.New(r.logger, r.db, r.cache) + cronTaskHandler := cron_handler.New(r.logger, r.db, r.cache) // 无需记录日志,无需 RBAC 权限验证 notRBAC := r.mux.Group("", r.middles.DisableLog()) @@ -80,5 +82,9 @@ func setWebRouter(r *resource) { web.GET("/tool/cache", toolHandler.CacheView()) web.GET("/tool/data", toolHandler.DataView()) + // 后台任务 + web.GET("/cron/list", cronTaskHandler.ListView()) + web.GET("/cron/add", cronTaskHandler.AddView()) + web.GET("/cron/edit/:id", cronTaskHandler.EditView()) } } diff --git a/internal/web/controller/cron_handler/func_addview.go b/internal/web/controller/cron_handler/func_addview.go new file mode 100644 index 0000000..d1380c6 --- /dev/null +++ b/internal/web/controller/cron_handler/func_addview.go @@ -0,0 +1,9 @@ +package cron_handler + +import "github.com/xinliangnote/go-gin-api/internal/pkg/core" + +func (h *handler) AddView() core.HandlerFunc { + return func(ctx core.Context) { + ctx.HTML("cron_task_add", nil) + } +} diff --git a/internal/web/controller/cron_handler/func_editview.go b/internal/web/controller/cron_handler/func_editview.go new file mode 100644 index 0000000..76f0fe6 --- /dev/null +++ b/internal/web/controller/cron_handler/func_editview.go @@ -0,0 +1,35 @@ +package cron_handler + +import ( + "net/http" + + "github.com/xinliangnote/go-gin-api/internal/pkg/code" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/pkg/errno" +) + +type editViewRequest struct { + Id string `uri:"id"` // 主键ID +} + +type editViewResponse struct { + HashID string `json:"hash_id"` // hashID +} + +func (h *handler) EditView() core.HandlerFunc { + return func(ctx core.Context) { + req := new(editViewRequest) + if err := ctx.ShouldBindURI(req); err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.ParamBindError, + code.Text(code.ParamBindError)).WithErr(err), + ) + return + } + + obj := new(editViewResponse) + obj.HashID = req.Id + ctx.HTML("cron_task_edit", obj) + } +} diff --git a/internal/web/controller/cron_handler/func_listview.go b/internal/web/controller/cron_handler/func_listview.go new file mode 100644 index 0000000..1a14c4b --- /dev/null +++ b/internal/web/controller/cron_handler/func_listview.go @@ -0,0 +1,9 @@ +package cron_handler + +import "github.com/xinliangnote/go-gin-api/internal/pkg/core" + +func (h *handler) ListView() core.HandlerFunc { + return func(ctx core.Context) { + ctx.HTML("cron_task_list", nil) + } +} diff --git a/internal/web/controller/cron_handler/handler.go b/internal/web/controller/cron_handler/handler.go new file mode 100644 index 0000000..0323d2f --- /dev/null +++ b/internal/web/controller/cron_handler/handler.go @@ -0,0 +1,33 @@ +package cron_handler + +import ( + "github.com/xinliangnote/go-gin-api/internal/pkg/cache" + "github.com/xinliangnote/go-gin-api/internal/pkg/core" + "github.com/xinliangnote/go-gin-api/internal/pkg/db" + + "go.uber.org/zap" +) + +var _ Handler = (*handler)(nil) + +type Handler interface { + i() + + AddView() core.HandlerFunc + EditView() core.HandlerFunc + ListView() core.HandlerFunc +} + +type handler struct { + logger *zap.Logger + cache cache.Repo +} + +func New(logger *zap.Logger, db db.Repo, cache cache.Repo) Handler { + return &handler{ + logger: logger, + cache: cache, + } +} + +func (h *handler) i() {} diff --git a/internal/web/controller/install_handler/func_execute.go b/internal/web/controller/install_handler/func_execute.go index 1ee3897..d0e886a 100644 --- a/internal/web/controller/install_handler/func_execute.go +++ b/internal/web/controller/install_handler/func_execute.go @@ -34,10 +34,42 @@ type initExecuteRequest struct { } func (h *handler) Execute() core.HandlerFunc { - return func(c core.Context) { + + installTableList := map[string]map[string]string{ + "authorized": { + "table_sql": mysql_table.CreateAuthorizedTableSql(), + "table_data_sql": mysql_table.CreateAuthorizedTableDataSql(), + }, + "authorized_api": { + "table_sql": mysql_table.CreateAuthorizedAPITableSql(), + "table_data_sql": mysql_table.CreateAuthorizedAPITableDataSql(), + }, + "admin": { + "table_sql": mysql_table.CreateAdminTableSql(), + "table_data_sql": mysql_table.CreateAdminTableDataSql(), + }, + "admin_menu": { + "table_sql": mysql_table.CreateAdminMenuTableSql(), + "table_data_sql": mysql_table.CreateAdminMenuTableDataSql(), + }, + "menu": { + "table_sql": mysql_table.CreateMenuTableSql(), + "table_data_sql": mysql_table.CreateMenuTableDataSql(), + }, + "menu_action": { + "table_sql": mysql_table.CreateMenuActionTableSql(), + "table_data_sql": mysql_table.CreateMenuActionTableDataSql(), + }, + "cron_task": { + "table_sql": mysql_table.CreateCronTaskTableSql(), + "table_data_sql": "", + }, + } + + return func(ctx core.Context) { req := new(initExecuteRequest) - if err := c.ShouldBindForm(req); err != nil { - c.AbortWithError(errno.NewError( + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.ParamBindError, code.Text(code.ParamBindError)).WithErr(err), @@ -45,19 +77,20 @@ func (h *handler) Execute() core.HandlerFunc { return } + // region 验证 version versionStr := runtime.Version() version := cast.ToFloat32(versionStr[2:6]) if version < 1.15 { - c.AbortWithError(errno.NewError( + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.GoVersionError, code.Text(code.GoVersionError)), ) return } + // endregion - outPutString := "" - + // region 验证 Redis 配置 cfg := configs.Get() redisClient := redis.NewClient(&redis.Options{ Addr: req.RedisAddr, @@ -69,7 +102,7 @@ func (h *handler) Execute() core.HandlerFunc { }) if err := redisClient.Ping().Err(); err != nil { - c.AbortWithError(errno.NewError( + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.RedisConnectError, code.Text(code.RedisConnectError)).WithErr(err), @@ -79,8 +112,10 @@ func (h *handler) Execute() core.HandlerFunc { defer redisClient.Close() - outPutString += "已检测 Redis 配置可用。\n" + outPutString := "已检测 Redis 配置可用。\n" + // endregion + // region 验证 MySQL 配置 dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s", req.MySQLUser, req.MySQLPass, @@ -97,7 +132,7 @@ func (h *handler) Execute() core.HandlerFunc { }) if err != nil { - c.AbortWithError(errno.NewError( + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.MySQLConnectError, code.Text(code.MySQLConnectError)).WithErr(err), @@ -111,7 +146,9 @@ func (h *handler) Execute() core.HandlerFunc { defer dbClient.Close() outPutString += "已检测 MySQL 配置可用。\n" + // endregion + // region 写入配置文件 viper.SetConfigName(env.Active().Value() + "_configs") viper.SetConfigType("toml") viper.AddConfigPath("./configs") @@ -133,7 +170,7 @@ func (h *handler) Execute() core.HandlerFunc { viper.Set("mysql.write.name", req.MySQLName) if viper.WriteConfig() != nil { - c.AbortWithError(errno.NewError( + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.WriteConfigError, code.Text(code.WriteConfigError)).WithErr(err), @@ -143,131 +180,46 @@ func (h *handler) Execute() core.HandlerFunc { outPutString += "语言包 " + req.Language + " 配置成功。\n" outPutString += "配置项 Redis、MySQL 配置成功。\n" + // endregion - if err = db.Exec(mysql_table.CreateAuthorizedTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return + // region 初始化表结构 + 默认数据 + for k, v := range installTableList { + if v["table_sql"] != "" { + // region 初始化表结构 + if err = db.Exec(v["table_sql"]).Error; err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.MySQLExecError, + code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), + ) + return + } + + outPutString += "初始化 MySQL 数据表:" + k + " 成功。\n" + // endregion + + // region 初始化默认数据 + if v["table_data_sql"] != "" { + if err = db.Exec(v["table_data_sql"]).Error; err != nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.MySQLExecError, + code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), + ) + return + } + + outPutString += "初始化 MySQL 数据表:" + k + " 默认数据成功。\n" + } + // endregion + } } - outPutString += "初始化 MySQL 数据表:authorized 成功。\n" + // endregion - if err = db.Exec(mysql_table.CreateAuthorizedTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:authorized 默认数据成功。\n" - - if err = db.Exec(mysql_table.CreateAuthorizedAPITableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:authorized_api 成功。\n" - - if err = db.Exec(mysql_table.CreateAuthorizedAPITableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:authorized_api 默认数据成功。\n" - - if err = db.Exec(mysql_table.CreateAdminTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:admin 成功。\n" - - if err = db.Exec(mysql_table.CreateAdminTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:admin 默认数据成功。\n" - - if err = db.Exec(mysql_table.CreateMenuTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:menu 成功。\n" - - if err = db.Exec(mysql_table.CreateMenuTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:menu 默认数据成功。\n" - - if err = db.Exec(mysql_table.CreateMenuActionTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:menu_action 成功。\n" - - if err = db.Exec(mysql_table.CreateMenuActionTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:menu_action 默认数据成功。\n" - - if err = db.Exec(mysql_table.CreateAdminMenuTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:admin_menu 成功。\n" - - if err = db.Exec(mysql_table.CreateAdminMenuTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString += "初始化 MySQL 数据表:admin_menu 默认数据成功。\n" - - // 生成 install 完成标识 + // region 生成 install 完成标识 f, err := os.Create(configs.ProjectInstallMark) if err != nil { - c.AbortWithError(errno.NewError( + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.MySQLExecError, code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), @@ -275,7 +227,8 @@ func (h *handler) Execute() core.HandlerFunc { return } defer f.Close() + // endregion - c.Payload(outPutString) + ctx.Payload(outPutString) } } diff --git a/internal/web/controller/install_handler/mysql_table/table_admin_menu.go b/internal/web/controller/install_handler/mysql_table/table_admin_menu.go index dccdce9..5bdc6ab 100644 --- a/internal/web/controller/install_handler/mysql_table/table_admin_menu.go +++ b/internal/web/controller/install_handler/mysql_table/table_admin_menu.go @@ -47,7 +47,9 @@ func CreateAdminMenuTableDataSql() (sql string) { sql += "(19, 1, 1, 'init')," sql += "(20, 1, 3, 'init')," sql += "(21, 1, 2, 'init')," - sql += "(22, 1, 22, 'init');" + sql += "(22, 1, 22, 'init')," + sql += "(23, 1, 23, 'init')," + sql += "(24, 1, 24, 'init');" return } diff --git a/internal/web/controller/install_handler/mysql_table/table_cron_task.go b/internal/web/controller/install_handler/mysql_table/table_cron_task.go new file mode 100644 index 0000000..43d45b8 --- /dev/null +++ b/internal/web/controller/install_handler/mysql_table/table_cron_task.go @@ -0,0 +1,53 @@ +package mysql_table + +//CREATE TABLE `cron_task` ( +//`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', +//`name` varchar(64) NOT NULL DEFAULT '' COMMENT '任务名称', +//`spec` varchar(64) NOT NULL DEFAULT '' COMMENT 'crontab 表达式', +//`command` varchar(255) NOT NULL DEFAULT '' COMMENT '执行命令', +//`protocol` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '执行方式 1:shell 2:http', +//`http_method` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT 'http 请求方式 1:get 2:post', +//`timeout` int(11) unsigned NOT NULL DEFAULT '60' COMMENT '超时时间(单位:秒)', +//`retry_times` tinyint(1) NOT NULL DEFAULT '3' COMMENT '重试次数', +//`retry_interval` int(11) NOT NULL DEFAULT '60' COMMENT '重试间隔(单位:秒)', +//`notify_status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知', +//`notify_type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '通知类型 1:邮件 2:webhook', +//`notify_receiver_email` varchar(255) NOT NULL DEFAULT '' COMMENT '通知者邮箱地址(多个用,分割)', +//`notify_keyword` varchar(255) NOT NULL DEFAULT '' COMMENT '通知匹配关键字(多个用,分割)', +//`remark` varchar(100) NOT NULL DEFAULT '' COMMENT '备注', +//`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否', +//`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', +//`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人', +//`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', +//`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人', +//PRIMARY KEY (`id`), +//KEY `idx_name` (`name`) +//) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='后台任务表'; + +func CreateCronTaskTableSql() (sql string) { + sql = "CREATE TABLE `cron_task` (" + sql += "`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键'," + sql += "`name` varchar(64) NOT NULL DEFAULT '' COMMENT '任务名称'," + sql += "`spec` varchar(64) NOT NULL DEFAULT '' COMMENT 'crontab 表达式'," + sql += "`command` varchar(255) NOT NULL DEFAULT '' COMMENT '执行命令'," + sql += "`protocol` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '执行方式 1:shell 2:http'," + sql += "`http_method` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT 'http 请求方式 1:get 2:post'," + sql += "`timeout` int(11) unsigned NOT NULL DEFAULT '60' COMMENT '超时时间(单位:秒)'," + sql += "`retry_times` tinyint(1) NOT NULL DEFAULT '3' COMMENT '重试次数'," + sql += "`retry_interval` int(11) NOT NULL DEFAULT '60' COMMENT '重试间隔(单位:秒)'," + sql += "`notify_status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '执行结束是否通知 1:不通知 2:失败通知 3:结束通知 4:结果关键字匹配通知'," + sql += "`notify_type` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '通知类型 1:邮件 2:webhook'," + sql += "`notify_receiver_email` varchar(255) NOT NULL DEFAULT '' COMMENT '通知者邮箱地址(多个用,分割)'," + sql += "`notify_keyword` varchar(255) NOT NULL DEFAULT '' COMMENT '通知匹配关键字(多个用,分割)'," + sql += "`remark` varchar(100) NOT NULL DEFAULT '' COMMENT '备注'," + sql += "`is_used` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用 1:是 -1:否'," + sql += "`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'," + sql += "`created_user` varchar(60) NOT NULL DEFAULT '' COMMENT '创建人'," + sql += "`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'," + sql += "`updated_user` varchar(60) NOT NULL DEFAULT '' COMMENT '更新人'," + sql += "PRIMARY KEY (`id`)," + sql += "KEY `idx_name` (`name`)" + sql += ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='后台任务表';" + + return +} diff --git a/internal/web/controller/install_handler/mysql_table/table_menu.go b/internal/web/controller/install_handler/mysql_table/table_menu.go index 18bc03a..132c8a5 100644 --- a/internal/web/controller/install_handler/mysql_table/table_menu.go +++ b/internal/web/controller/install_handler/mysql_table/table_menu.go @@ -40,28 +40,30 @@ func CreateMenuTableSql() (sql string) { func CreateMenuTableDataSql() (sql string) { sql = "INSERT INTO `menu` (`id`, `pid`, `name`, `link`, `icon`, `level`, `sort`, `created_user`) VALUES" - sql += "(1, 0, '配置信息', '', 'mdi-settings-box', 1, 1, 'init')," - sql += "(2, 1, '告警邮箱', '/config/email', '', 2, 11, 'init')," - sql += "(3, 1, '错误码', '/config/code', '', 2, 12, 'init')," - sql += "(4, 0, '代码生成器', '', 'mdi-code-not-equal-variant', 1, 2, 'init')," - sql += "(5, 4, '生成数据表 CURD', '/generator/gorm', '', 2, 21, 'init')," - sql += "(6, 4, '生成控制器方法', '/generator/handler', '', 2, 22, 'init')," - sql += "(7, 0, '授权调用方', '', 'mdi-playlist-check', 1, 3, 'init')," - sql += "(8, 7, '调用方', '/authorized/list', '', 2, 31, 'init')," - sql += "(9, 7, '使用说明', '/authorized/demo', '', 2, 32, 'init')," - sql += "(10, 0, '系统管理员', '', 'mdi-account', 1, 4, 'init')," - sql += "(11, 10, '管理员', '/admin/list', '', 2, 41, 'init')," - sql += "(12, 10, '菜单管理', '/admin/menu', '', 2, 42, 'init')," - sql += "(13, 0, '查询小助手', '', 'mdi-database-search', 1, 5, 'init')," - sql += "(14, 13, '查询缓存', '/tool/cache', '', 2, 51, 'init')," - sql += "(15, 13, '查询数据', '/tool/data', '', 2, 52, 'init')," - sql += "(16, 0, '实用工具箱', '', 'mdi-tools', 1, 6, 'init')," - sql += "(17, 16, 'Hashids', '/tool/hashids', '', 2, 62, 'init')," - sql += "(18, 16, '调用日志', '/tool/logs', '', 2, 63, 'init')," - sql += "(19, 16, '接口文档', '/swagger/index.html', '', 2, 64, 'init')," - sql += "(20, 16, 'GraphQL', '/graphql', '', 2, 65, 'init')," - sql += "(21, 16, '接口指标', '/metrics', '', 2, 66, 'init')," - sql += "(22, 16, '服务升级', '/upgrade', '', 2, 61, 'init');" + sql += "(1, 0, '配置信息', '', 'mdi-settings-box', 1, 10, 'init')," + sql += "(2, 1, '告警邮箱', '/config/email', '', 2, 101, 'init')," + sql += "(3, 1, '错误码', '/config/code', '', 2, 102, 'init')," + sql += "(4, 0, '代码生成器', '', 'mdi-code-not-equal-variant', 1, 20, 'init')," + sql += "(5, 4, '生成数据表 CURD', '/generator/gorm', '', 2, 201, 'init')," + sql += "(6, 4, '生成控制器方法', '/generator/handler', '', 2, 202, 'init')," + sql += "(7, 0, '授权调用方', '', 'mdi-playlist-check', 1, 30, 'init')," + sql += "(8, 7, '调用方', '/authorized/list', '', 2, 301, 'init')," + sql += "(9, 7, '使用说明', '/authorized/demo', '', 2, 302, 'init')," + sql += "(10, 0, '系统管理员', '', 'mdi-account', 1, 50, 'init')," + sql += "(11, 10, '管理员', '/admin/list', '', 2, 501, 'init')," + sql += "(12, 10, '菜单管理', '/admin/menu', '', 2, 502, 'init')," + sql += "(13, 0, '查询小助手', '', 'mdi-database-search', 1, 60, 'init')," + sql += "(14, 13, '查询缓存', '/tool/cache', '', 2, 601, 'init')," + sql += "(15, 13, '查询数据', '/tool/data', '', 2, 602, 'init')," + sql += "(16, 0, '实用工具箱', '', 'mdi-tools', 1, 70, 'init')," + sql += "(17, 16, 'Hashids', '/tool/hashids', '', 2, 702, 'init')," + sql += "(18, 16, '调用日志', '/tool/logs', '', 2, 703, 'init')," + sql += "(19, 16, '接口文档', '/swagger/index.html', '', 2, 704, 'init')," + sql += "(20, 16, 'GraphQL', '/graphql', '', 2, 705, 'init')," + sql += "(21, 16, '接口指标', '/metrics', '', 2, 706, 'init')," + sql += "(22, 16, '服务升级', '/upgrade', '', 2, 701, 'init')," + sql += "(23, 0, '后台任务', '', 'mdi-av-timer', 1, 40, 'init')," + sql += "(24, 23, '任务列表', '/cron/list', '', 2, 401, 'init');" return } diff --git a/internal/web/controller/install_handler/mysql_table/table_menu_action.go b/internal/web/controller/install_handler/mysql_table/table_menu_action.go index 1ae911b..5e628ad 100644 --- a/internal/web/controller/install_handler/mysql_table/table_menu_action.go +++ b/internal/web/controller/install_handler/mysql_table/table_menu_action.go @@ -72,7 +72,14 @@ func CreateMenuActionTableDataSql() (sql string) { sql += "(36, 12, 'DELETE', '/api/menu_action/*', 'init')," sql += "(37, 22, 'POST', '/upgrade/execute', 'init')," sql += "(38, 11, 'PATCH', '/api/admin/offline', 'init')," - sql += "(39, 12, 'PATCH', '/api/menu/sort', 'init');" + sql += "(39, 12, 'PATCH', '/api/menu/sort', 'init')," + sql += "(40, 24, 'GET', '/cron/add', 'init')," + sql += "(41, 24, 'GET', '/cron/edit/*', 'init')," + sql += "(42, 24, 'POST', '/api/cron', 'init')," + sql += "(43, 24, 'POST', '/api/cron/*', 'init')," + sql += "(44, 24, 'GET', '/api/cron', 'init')," + sql += "(45, 24, 'GET', '/api/cron/*', 'init')," + sql += "(46, 24, 'PATCH', '/api/cron/used', 'init');" return } diff --git a/internal/web/controller/upgrade_handler/func_upgradeexecute.go b/internal/web/controller/upgrade_handler/func_upgradeexecute.go index 18596ed..3ba7490 100644 --- a/internal/web/controller/upgrade_handler/func_upgradeexecute.go +++ b/internal/web/controller/upgrade_handler/func_upgradeexecute.go @@ -7,6 +7,7 @@ import ( "github.com/xinliangnote/go-gin-api/internal/pkg/core" "github.com/xinliangnote/go-gin-api/internal/web/controller/install_handler/mysql_table" "github.com/xinliangnote/go-gin-api/pkg/errno" + "github.com/xinliangnote/go-gin-api/pkg/errors" ) type upgradeExecuteRequest struct { @@ -15,10 +16,47 @@ type upgradeExecuteRequest struct { } func (h *handler) UpgradeExecute() core.HandlerFunc { - return func(c core.Context) { + + upgradeTableList := map[string]map[string]string{ + "authorized": { + "table_sql": mysql_table.CreateAuthorizedTableSql(), + "table_data_sql": mysql_table.CreateAuthorizedTableDataSql(), + }, + "authorized_api": { + "table_sql": mysql_table.CreateAuthorizedAPITableSql(), + "table_data_sql": mysql_table.CreateAuthorizedAPITableDataSql(), + }, + "admin": { + "table_sql": mysql_table.CreateAdminTableSql(), + "table_data_sql": mysql_table.CreateAdminTableDataSql(), + }, + "admin_menu": { + "table_sql": mysql_table.CreateAdminMenuTableSql(), + "table_data_sql": mysql_table.CreateAdminMenuTableDataSql(), + }, + "menu": { + "table_sql": mysql_table.CreateMenuTableSql(), + "table_data_sql": mysql_table.CreateMenuTableDataSql(), + }, + "menu_action": { + "table_sql": mysql_table.CreateMenuActionTableSql(), + "table_data_sql": mysql_table.CreateMenuActionTableDataSql(), + }, + "cron_task": { + "table_sql": mysql_table.CreateCronTaskTableSql(), + "table_data_sql": "", + }, + } + + upgradeTableOp := map[string]bool{ + "table": true, + "table_data": true, + } + + return func(ctx core.Context) { req := new(upgradeExecuteRequest) - if err := c.ShouldBindForm(req); err != nil { - c.AbortWithError(errno.NewError( + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.ParamBindError, code.Text(code.ParamBindError)).WithErr(err), @@ -29,150 +67,48 @@ func (h *handler) UpgradeExecute() core.HandlerFunc { outPutString := "" db := h.db.GetDbW() - if req.TableName == "authorized" && req.Op == "table" { - if err := db.Exec(mysql_table.CreateAuthorizedTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( + if upgradeTableList[req.TableName] == nil { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.MySQLExecError, + code.Text(code.MySQLExecError)).WithErr(errors.New("数据表不存在")), + ) + return + } + + if !upgradeTableOp[req.Op] { + ctx.AbortWithError(errno.NewError( + http.StatusBadRequest, + code.MySQLExecError, + code.Text(code.MySQLExecError)).WithErr(errors.New("非法操作")), + ) + return + } + + if req.Op == "table" { + if err := db.Exec(upgradeTableList[req.TableName]["table_sql"]).Error; err != nil { + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.MySQLExecError, code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), ) return } - outPutString = "初始化 MySQL 数据表:authorized 成功。" - } - if req.TableName == "authorized" && req.Op == "table_data" { - if err := db.Exec(mysql_table.CreateAuthorizedTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( + outPutString = "初始化 MySQL 数据表:" + req.TableName + " 成功。" + } else if req.Op == "table_data" { + if err := db.Exec(upgradeTableList[req.TableName]["table_data_sql"]).Error; err != nil { + ctx.AbortWithError(errno.NewError( http.StatusBadRequest, code.MySQLExecError, code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), ) return } - outPutString = "初始化 MySQL 数据表:authorized 默认数据成功。" + + outPutString = "初始化 MySQL 数据表:" + req.TableName + " 默认数据成功。" } - if req.TableName == "authorized_api" && req.Op == "table" { - if err := db.Exec(mysql_table.CreateAuthorizedAPITableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:authorized_api 成功。" - } - - if req.TableName == "authorized_api" && req.Op == "table_data" { - if err := db.Exec(mysql_table.CreateAuthorizedAPITableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:authorized_api 默认数据成功。" - } - - if req.TableName == "admin" && req.Op == "table" { - if err := db.Exec(mysql_table.CreateAdminTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:admin 成功。" - } - - if req.TableName == "admin" && req.Op == "table_data" { - if err := db.Exec(mysql_table.CreateAdminTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:admin 默认数据成功。" - } - - if req.TableName == "menu" && req.Op == "table" { - if err := db.Exec(mysql_table.CreateMenuTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:menu 成功。" - } - - if req.TableName == "menu" && req.Op == "table_data" { - if err := db.Exec(mysql_table.CreateMenuTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:menu 默认数据成功。" - } - - if req.TableName == "menu_action" && req.Op == "table" { - if err := db.Exec(mysql_table.CreateMenuActionTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:menu_action 成功。" - } - - if req.TableName == "menu_action" && req.Op == "table_data" { - if err := db.Exec(mysql_table.CreateMenuActionTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:menu_action 默认数据成功。" - } - - if req.TableName == "admin_menu" && req.Op == "table" { - if err := db.Exec(mysql_table.CreateAdminMenuTableSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:admin_menu 成功。" - } - - if req.TableName == "admin_menu" && req.Op == "table_data" { - if err := db.Exec(mysql_table.CreateAdminMenuTableDataSql()).Error; err != nil { - c.AbortWithError(errno.NewError( - http.StatusBadRequest, - code.MySQLExecError, - code.Text(code.MySQLExecError)+" "+err.Error()).WithErr(err), - ) - return - } - outPutString = "初始化 MySQL 数据表:admin_menu 默认数据成功。" - } - - c.Payload(outPutString) + ctx.Payload(outPutString) } }