mirror of
https://github.com/usual2970/certimate.git
synced 2025-10-04 13:34:52 +00:00
details improvement and unnecessary files deletion
This commit is contained in:
102
internal/certificate/service.go
Normal file
102
internal/certificate/service.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/notify"
|
||||
"github.com/usual2970/certimate/internal/repository"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
|
||||
defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
|
||||
)
|
||||
|
||||
type CertificateRepository interface {
|
||||
GetExpireSoon(ctx context.Context) ([]domain.Certificate, error)
|
||||
}
|
||||
|
||||
type certificateService struct {
|
||||
repo CertificateRepository
|
||||
}
|
||||
|
||||
func NewCertificateService(repo CertificateRepository) *certificateService {
|
||||
return &certificateService{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *certificateService) InitSchedule(ctx context.Context) error {
|
||||
scheduler := app.GetScheduler()
|
||||
|
||||
err := scheduler.Add("certificate", "0 0 * * *", func() {
|
||||
certs, err := s.repo.GetExpireSoon(context.Background())
|
||||
if err != nil {
|
||||
app.GetApp().Logger().Error("failed to get expire soon certificate", "err", err)
|
||||
return
|
||||
}
|
||||
msg := buildMsg(certs)
|
||||
if err := notify.SendToAllChannels(msg.Subject, msg.Message); err != nil {
|
||||
app.GetApp().Logger().Error("failed to send expire soon certificate", "err", err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
app.GetApp().Logger().Error("failed to add schedule", "err", err)
|
||||
return err
|
||||
}
|
||||
scheduler.Start()
|
||||
app.GetApp().Logger().Info("certificate schedule started")
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildMsg(records []domain.Certificate) *domain.NotifyMessage {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查询模板信息
|
||||
settingRepo := repository.NewSettingRepository()
|
||||
setting, err := settingRepo.GetByName(context.Background(), "templates")
|
||||
|
||||
subject := defaultExpireSubject
|
||||
message := defaultExpireMessage
|
||||
|
||||
if err == nil {
|
||||
var templates *domain.NotifyTemplates
|
||||
|
||||
json.Unmarshal([]byte(setting.Content), &templates)
|
||||
|
||||
if templates != nil && len(templates.NotifyTemplates) > 0 {
|
||||
subject = templates.NotifyTemplates[0].Title
|
||||
message = templates.NotifyTemplates[0].Content
|
||||
}
|
||||
}
|
||||
|
||||
// 替换变量
|
||||
count := len(records)
|
||||
domains := make([]string, count)
|
||||
|
||||
for i, record := range records {
|
||||
domains[i] = record.SAN
|
||||
}
|
||||
|
||||
countStr := strconv.Itoa(count)
|
||||
domainStr := strings.Join(domains, ";")
|
||||
|
||||
subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
|
||||
subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
|
||||
|
||||
message = strings.ReplaceAll(message, "{COUNT}", countStr)
|
||||
message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
|
||||
|
||||
// 返回消息
|
||||
return &domain.NotifyMessage{
|
||||
Subject: subject,
|
||||
Message: message,
|
||||
}
|
||||
}
|
@@ -6,16 +6,16 @@ var ValidityDuration = time.Hour * 24 * 10
|
||||
|
||||
type Certificate struct {
|
||||
Meta
|
||||
SAN string `json:"san"`
|
||||
Certificate string `json:"certificate"`
|
||||
PrivateKey string `json:"privateKey"`
|
||||
IssuerCertificate string `json:"issuerCertificate"`
|
||||
CertUrl string `json:"certUrl"`
|
||||
CertStableUrl string `json:"certStableUrl"`
|
||||
Output string `json:"output"`
|
||||
Workflow string `json:"workflow"`
|
||||
ExpireAt time.Time `json:"ExpireAt"`
|
||||
NodeId string `json:"nodeId"`
|
||||
SAN string `json:"san" db:"san"`
|
||||
Certificate string `json:"certificate" db:"certificate"`
|
||||
PrivateKey string `json:"privateKey" db:"privateKey"`
|
||||
IssuerCertificate string `json:"issuerCertificate" db:"issuerCertificate"`
|
||||
CertUrl string `json:"certUrl" db:"certUrl"`
|
||||
CertStableUrl string `json:"certStableUrl" db:"certStableUrl"`
|
||||
Output string `json:"output" db:"output"`
|
||||
Workflow string `json:"workflow" db:"workflow"`
|
||||
ExpireAt time.Time `json:"ExpireAt" db:"expireAt"`
|
||||
NodeId string `json:"nodeId" db:"nodeId"`
|
||||
}
|
||||
|
||||
type MetaData struct {
|
||||
|
@@ -3,7 +3,7 @@ package domain
|
||||
import "time"
|
||||
|
||||
type Meta struct {
|
||||
Id string `json:"id"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Id string `json:"id" db:"id"`
|
||||
Created time.Time `json:"created" db:"created"`
|
||||
Updated time.Time `json:"updated" db:"updated"`
|
||||
}
|
||||
|
@@ -29,3 +29,17 @@ func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type NotifyTemplates struct {
|
||||
NotifyTemplates []NotifyTemplate `json:"notifyTemplates"`
|
||||
}
|
||||
|
||||
type NotifyTemplate struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type NotifyMessage struct {
|
||||
Subject string
|
||||
Message string
|
||||
}
|
||||
|
@@ -15,11 +15,17 @@ const (
|
||||
WorkflowNodeTypeCondition = "condition"
|
||||
)
|
||||
|
||||
const (
|
||||
WorkflowTypeAuto = "auto"
|
||||
WorkflowTypeManual = "manual"
|
||||
)
|
||||
|
||||
type Workflow struct {
|
||||
Meta
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Crontab string `json:"crontab"`
|
||||
Content *WorkflowNode `json:"content"`
|
||||
Draft *WorkflowNode `json:"draft"`
|
||||
Enabled bool `json:"enabled"`
|
||||
|
@@ -1,38 +0,0 @@
|
||||
package domains
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/usual2970/certimate/internal/notify"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
)
|
||||
|
||||
func InitSchedule() {
|
||||
// 查询所有启用的域名
|
||||
records, err := app.GetApp().Dao().FindRecordsByFilter("domains", "enabled=true", "-id", 500, 0)
|
||||
if err != nil {
|
||||
app.GetApp().Logger().Error("查询所有启用的域名失败", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 加入到定时任务
|
||||
for _, record := range records {
|
||||
if err := app.GetScheduler().Add(record.Id, record.GetString("crontab"), func() {
|
||||
if err := deploy(context.Background(), record); err != nil {
|
||||
app.GetApp().Logger().Error("部署失败", "err", err)
|
||||
return
|
||||
}
|
||||
}); err != nil {
|
||||
app.GetApp().Logger().Error("加入到定时任务失败", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 过期提醒
|
||||
app.GetScheduler().Add("expire", "0 0 * * *", func() {
|
||||
notify.PushExpireMsg()
|
||||
})
|
||||
|
||||
// 启动定时任务
|
||||
app.GetScheduler().Start()
|
||||
app.GetApp().Logger().Info("定时任务启动成功", "total", app.GetScheduler().Total())
|
||||
}
|
@@ -1,96 +0,0 @@
|
||||
package notify
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
"github.com/usual2970/certimate/internal/utils/xtime"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultExpireSubject = "您有 {COUNT} 张证书即将过期"
|
||||
defaultExpireMessage = "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!"
|
||||
)
|
||||
|
||||
func PushExpireMsg() {
|
||||
// 查询即将过期的证书
|
||||
records, err := app.GetApp().Dao().FindRecordsByFilter("certificate", "expireAt<{:time}&&certUrl!=''", "-created", 500, 0,
|
||||
dbx.Params{"time": xtime.GetTimeAfter(24 * time.Hour * 20)})
|
||||
if err != nil {
|
||||
app.GetApp().Logger().Error("find expired domains by filter", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 组装消息
|
||||
msg := buildMsg(records)
|
||||
if msg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 发送通知
|
||||
if err := SendToAllChannels(msg.Subject, msg.Message); err != nil {
|
||||
app.GetApp().Logger().Error("send expire msg", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
type notifyTemplates struct {
|
||||
NotifyTemplates []notifyTemplate `json:"notifyTemplates"`
|
||||
}
|
||||
|
||||
type notifyTemplate struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type notifyMessage struct {
|
||||
Subject string
|
||||
Message string
|
||||
}
|
||||
|
||||
func buildMsg(records []*models.Record) *notifyMessage {
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查询模板信息
|
||||
templateRecord, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='templates'")
|
||||
subject := defaultExpireSubject
|
||||
message := defaultExpireMessage
|
||||
|
||||
if err == nil {
|
||||
var templates *notifyTemplates
|
||||
templateRecord.UnmarshalJSONField("content", templates)
|
||||
if templates != nil && len(templates.NotifyTemplates) > 0 {
|
||||
subject = templates.NotifyTemplates[0].Title
|
||||
message = templates.NotifyTemplates[0].Content
|
||||
}
|
||||
}
|
||||
|
||||
// 替换变量
|
||||
count := len(records)
|
||||
domains := make([]string, count)
|
||||
|
||||
for i, record := range records {
|
||||
domains[i] = record.GetString("san")
|
||||
}
|
||||
|
||||
countStr := strconv.Itoa(count)
|
||||
domainStr := strings.Join(domains, ";")
|
||||
|
||||
subject = strings.ReplaceAll(subject, "{COUNT}", countStr)
|
||||
subject = strings.ReplaceAll(subject, "{DOMAINS}", domainStr)
|
||||
|
||||
message = strings.ReplaceAll(message, "{COUNT}", countStr)
|
||||
message = strings.ReplaceAll(message, "{DOMAINS}", domainStr)
|
||||
|
||||
// 返回消息
|
||||
return ¬ifyMessage{
|
||||
Subject: subject,
|
||||
Message: message,
|
||||
}
|
||||
}
|
24
internal/repository/certificate.go
Normal file
24
internal/repository/certificate.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
)
|
||||
|
||||
type CertificateRepository struct{}
|
||||
|
||||
func NewCertificateRepository() *CertificateRepository {
|
||||
return &CertificateRepository{}
|
||||
}
|
||||
|
||||
func (c *CertificateRepository) GetExpireSoon(ctx context.Context) ([]domain.Certificate, error) {
|
||||
rs := []domain.Certificate{}
|
||||
if err := app.GetApp().Dao().DB().
|
||||
NewQuery("select * from certificate where expireAt > datetime('now') and expireAt < datetime('now', '+20 days')").
|
||||
All(&rs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs, nil
|
||||
}
|
@@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
@@ -16,6 +17,26 @@ func NewWorkflowRepository() *WorkflowRepository {
|
||||
return &WorkflowRepository{}
|
||||
}
|
||||
|
||||
func (w *WorkflowRepository) ListEnabledAuto(ctx context.Context) ([]domain.Workflow, error) {
|
||||
records, err := app.GetApp().Dao().FindRecordsByFilter(
|
||||
"workflow",
|
||||
"enabled={:enabled} && type={:type}",
|
||||
"-created", 1000, 0, dbx.Params{"enabled": true, "type": domain.WorkflowTypeAuto},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs := make([]domain.Workflow, 0)
|
||||
for _, record := range records {
|
||||
workflow, err := record2Workflow(record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs = append(rs, *workflow)
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (w *WorkflowRepository) SaveRunLog(ctx context.Context, log *domain.WorkflowRunLog) error {
|
||||
collection, err := app.GetApp().Dao().FindCollectionByNameOrId("workflow_run_log")
|
||||
if err != nil {
|
||||
@@ -40,6 +61,10 @@ func (w *WorkflowRepository) Get(ctx context.Context, id string) (*domain.Workfl
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return record2Workflow(record)
|
||||
}
|
||||
|
||||
func record2Workflow(record *models.Record) (*domain.Workflow, error) {
|
||||
content := &domain.WorkflowNode{}
|
||||
if err := record.UnmarshalJSONField("content", content); err != nil {
|
||||
return nil, err
|
||||
@@ -59,6 +84,7 @@ func (w *WorkflowRepository) Get(ctx context.Context, id string) (*domain.Workfl
|
||||
Name: record.GetString("name"),
|
||||
Description: record.GetString("description"),
|
||||
Type: record.GetString("type"),
|
||||
Crontab: record.GetString("crontab"),
|
||||
Enabled: record.GetBool("enabled"),
|
||||
HasDraft: record.GetBool("hasDraft"),
|
||||
|
||||
|
11
internal/scheduler/certificate.go
Normal file
11
internal/scheduler/certificate.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package scheduler
|
||||
|
||||
import "context"
|
||||
|
||||
type CertificateService interface {
|
||||
InitSchedule(ctx context.Context) error
|
||||
}
|
||||
|
||||
func NewCertificateScheduler(service CertificateService) error {
|
||||
return service.InitSchedule(context.Background())
|
||||
}
|
19
internal/scheduler/scheduler.go
Normal file
19
internal/scheduler/scheduler.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"github.com/usual2970/certimate/internal/certificate"
|
||||
"github.com/usual2970/certimate/internal/repository"
|
||||
"github.com/usual2970/certimate/internal/workflow"
|
||||
)
|
||||
|
||||
func Register() {
|
||||
workflowRepo := repository.NewWorkflowRepository()
|
||||
workflowSvc := workflow.NewWorkflowService(workflowRepo)
|
||||
|
||||
certificateRepo := repository.NewCertificateRepository()
|
||||
certificateSvc := certificate.NewCertificateService(certificateRepo)
|
||||
|
||||
NewCertificateScheduler(certificateSvc)
|
||||
|
||||
NewWorkflowScheduler(workflowSvc)
|
||||
}
|
11
internal/scheduler/workflow.go
Normal file
11
internal/scheduler/workflow.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package scheduler
|
||||
|
||||
import "context"
|
||||
|
||||
type WorkflowService interface {
|
||||
InitSchedule(ctx context.Context) error
|
||||
}
|
||||
|
||||
func NewWorkflowScheduler(service WorkflowService) error {
|
||||
return service.InitSchedule(context.Background())
|
||||
}
|
@@ -2,6 +2,7 @@ package app
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/cron"
|
||||
)
|
||||
@@ -13,6 +14,10 @@ var scheduler *cron.Cron
|
||||
func GetScheduler() *cron.Cron {
|
||||
schedulerOnce.Do(func() {
|
||||
scheduler = cron.New()
|
||||
location, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err == nil {
|
||||
scheduler.SetTimezone(location)
|
||||
}
|
||||
})
|
||||
|
||||
return scheduler
|
||||
|
71
internal/workflow/event.go
Normal file
71
internal/workflow/event.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/repository"
|
||||
"github.com/usual2970/certimate/internal/utils/app"
|
||||
)
|
||||
|
||||
const tableName = "workflow"
|
||||
|
||||
func AddEvent() error {
|
||||
app := app.GetApp()
|
||||
|
||||
app.OnRecordAfterCreateRequest(tableName).Add(func(e *core.RecordCreateEvent) error {
|
||||
return update(e.HttpContext.Request().Context(), e.Record)
|
||||
})
|
||||
|
||||
app.OnRecordAfterUpdateRequest(tableName).Add(func(e *core.RecordUpdateEvent) error {
|
||||
return update(e.HttpContext.Request().Context(), e.Record)
|
||||
})
|
||||
|
||||
app.OnRecordAfterDeleteRequest(tableName).Add(func(e *core.RecordDeleteEvent) error {
|
||||
return delete(e.HttpContext.Request().Context(), e.Record)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func delete(_ context.Context, record *models.Record) error {
|
||||
id := record.Id
|
||||
scheduler := app.GetScheduler()
|
||||
scheduler.Remove(id)
|
||||
scheduler.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
func update(ctx context.Context, record *models.Record) error {
|
||||
// 是不是自动
|
||||
// 是不是 enabled
|
||||
|
||||
id := record.Id
|
||||
enabled := record.GetBool("enabled")
|
||||
executeMethod := record.GetString("type")
|
||||
|
||||
scheduler := app.GetScheduler()
|
||||
if !enabled || executeMethod == domain.WorkflowTypeManual {
|
||||
scheduler.Remove(id)
|
||||
scheduler.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := scheduler.Add(id, record.GetString("crontab"), func() {
|
||||
NewWorkflowService(repository.NewWorkflowRepository()).Run(ctx, &domain.WorkflowRunReq{
|
||||
Id: id,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
app.GetApp().Logger().Error("add cron job failed", "err", err)
|
||||
return fmt.Errorf("add cron job failed: %w", err)
|
||||
}
|
||||
app.GetApp().Logger().Error("add cron job failed", "san", record.GetString("san"))
|
||||
|
||||
scheduler.Start()
|
||||
return nil
|
||||
}
|
@@ -12,6 +12,7 @@ import (
|
||||
type WorkflowRepository interface {
|
||||
Get(ctx context.Context, id string) (*domain.Workflow, error)
|
||||
SaveRunLog(ctx context.Context, log *domain.WorkflowRunLog) error
|
||||
ListEnabledAuto(ctx context.Context) ([]domain.Workflow, error)
|
||||
}
|
||||
|
||||
type WorkflowService struct {
|
||||
@@ -24,6 +25,29 @@ func NewWorkflowService(repo WorkflowRepository) *WorkflowService {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WorkflowService) InitSchedule(ctx context.Context) error {
|
||||
// 查询所有的 enabled auto workflow
|
||||
workflows, err := s.repo.ListEnabledAuto(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
scheduler := app.GetScheduler()
|
||||
for _, workflow := range workflows {
|
||||
err := scheduler.Add(workflow.Id, workflow.Crontab, func() {
|
||||
s.Run(ctx, &domain.WorkflowRunReq{
|
||||
Id: workflow.Id,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
app.GetApp().Logger().Error("failed to add schedule", "err", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
scheduler.Start()
|
||||
app.GetApp().Logger().Info("workflow schedule started")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WorkflowService) Run(ctx context.Context, req *domain.WorkflowRunReq) error {
|
||||
// 查询
|
||||
if req.Id == "" {
|
||||
|
Reference in New Issue
Block a user