details improvement and unnecessary files deletion

This commit is contained in:
yoan
2024-11-24 13:36:17 +08:00
parent 37df882ed3
commit 9ff3e22c80
57 changed files with 978 additions and 5047 deletions

View 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,
}
}

View File

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

View File

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

View File

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

View File

@@ -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"`

View File

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

View File

@@ -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 &notifyMessage{
Subject: subject,
Message: message,
}
}

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

View File

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

View 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())
}

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

View 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())
}

View File

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

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

View File

@@ -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 == "" {