Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d428a7db2e | ||
|
|
8163d0d9d4 |
@@ -1,14 +1,13 @@
|
|||||||
# goodBaby v2
|
# goodBaby v2
|
||||||
|
|
||||||

|
  
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
根据《中国心血管健康与疾病报告2023》,中国 每年 猝死总人数(含心源性和非心源性)或达80万-100万,18-35岁人群占比为38%,也就是说,每天18-35岁人群有近1000人猝死
|
根据《中国心血管健康与疾病报告2023》,中国 每年 猝死总人数(含心源性和非心源性)或达80万-100万,18-35岁人群占比为38%,也就是说,每天18-35岁人群有近1000人猝死
|
||||||
|
|
||||||
伴随着中国人口老龄化加剧,年轻人的牵挂越来越多,面对着可能的猝死,我们或许真的需要做好准备
|
伴随着中国人口老龄化加剧,年轻人的牵挂越来越多,面对着可能的猝死,我们或许真的需要做好准备
|
||||||
|
|
||||||
所以我写了goodBaby来解决这个问题,他和apple app store里的“死了么”形式上有些接近,不过goodBaby支持了更多好玩的通知方式,比如bilibili动态,github仓库,邮件,OneBot,还可以方便的自托管
|
所以我写了goodBaby来解决这个问题,他和apple app store里的“死了么”形式上有些接近,不过goodBaby支持了更多好玩的通知方式,比如bilibili动态,github仓库,邮件,OneBot,还可以方便的自托管
|
||||||
|
|
||||||
[](https://app.rainyun.com/apps/rca/store/7125/cat_)
|
[](https://app.rainyun.com/apps/rca/store/7125/cat_)
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ git push origin v2.2.0
|
|||||||
|
|
||||||
编译时通过 ldflags 注入,启动日志里能看到:
|
编译时通过 ldflags 注入,启动日志里能看到:
|
||||||
|
|
||||||
```
|
```log
|
||||||
goodBaby v2.2.0 (a1b2c3d, 2026-07-27T07:00:00Z) 启动中...
|
goodBaby v2.2.0 (a1b2c3d, 2026-07-27T07:00:00Z) 启动中...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ssdomei232/goodBaby/internal/meta"
|
||||||
|
rain "github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/common"
|
||||||
|
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RainyunAccountConfigValidator Rainyun账号配置验证器
|
||||||
|
type RainyunAccountConfigValidator struct{}
|
||||||
|
|
||||||
|
func (v *RainyunAccountConfigValidator) GetType() string {
|
||||||
|
return AccountType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *RainyunAccountConfigValidator) Validate(config string) error {
|
||||||
|
// 解析配置
|
||||||
|
cfg, err := parseAccount(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.APIKey == "" {
|
||||||
|
return fmt.Errorf("雨云账号配置中APIKey不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAccount(config string) (*RainyunAccount, error) {
|
||||||
|
var cfg RainyunAccount
|
||||||
|
if err := json.Unmarshal([]byte(config), &cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析雨云账号配置失败: %v", err)
|
||||||
|
}
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *RainyunAccountConfigValidator) Test(config string) error {
|
||||||
|
cfg, err := parseAccount(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := rain.NewClient(cfg.APIKey)
|
||||||
|
userClient := user.Client{Client: client}
|
||||||
|
_, err = userClient.GetUserInfo()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("测试雨云账号配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *RainyunAccountConfigValidator) Meta() meta.AccountMeta {
|
||||||
|
return meta.AccountMeta{
|
||||||
|
Type: AccountType,
|
||||||
|
Label: "雨云",
|
||||||
|
Description: "对接雨云,方便各位代理/运维删库跑路",
|
||||||
|
Fields: []meta.Field{
|
||||||
|
{Key: "api_key", Label: "API Key", Type: meta.FieldPassword, Required: true, Secret: true, Placeholder: "xxxxxxxxx"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccountType Rainyun 账号类型标识
|
||||||
|
AccountType = "rainyun"
|
||||||
|
// RuleType 发送 Rainyun 消息的规则类型标识
|
||||||
|
RuleType = "rainyun-workorder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RainyunWorkOrderRule struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RainyunAccount struct {
|
||||||
|
APIKey string `json:"api_key"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ssdomei232/goodBaby/model"
|
||||||
|
rain "github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/common"
|
||||||
|
"github.com/ssdomei232/rainyun-go-sdk/v2/rainyun/workorder"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendRainyunWorkorderMsg(ctx context.Context, rule *model.Rule) error {
|
||||||
|
rainyunConfig, err := getRainyunConfigFromRule(rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rainyunAccount, err := getRainyunAccountFromRule(rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := rain.NewClient(rainyunAccount.APIKey)
|
||||||
|
workorderClient := workorder.Client{Client: client}
|
||||||
|
|
||||||
|
_, err = workorderClient.CreateWorkOrder(&workorder.CreateWorkerorderRequest{
|
||||||
|
Content: rainyunConfig.Msg,
|
||||||
|
IsAuthed: false,
|
||||||
|
IsUrgent: 1,
|
||||||
|
Title: rainyunConfig.Title,
|
||||||
|
Type: "reward",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ssdomei232/goodBaby/internal/meta"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rainyun规则验证器
|
||||||
|
type RainyunWorkorderRuleValidator struct{}
|
||||||
|
|
||||||
|
func (v *RainyunWorkorderRuleValidator) GetType() string {
|
||||||
|
return RuleType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *RainyunWorkorderRuleValidator) Validate(configJSON string) error {
|
||||||
|
var config RainyunWorkOrderRule
|
||||||
|
if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
|
||||||
|
return fmt.Errorf("解析雨云规则配置失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Title == "" {
|
||||||
|
return fmt.Errorf("雨云规则配置中 title 不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Msg == "" {
|
||||||
|
return fmt.Errorf("雨云规则配置中 msg 不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *RainyunWorkorderRuleValidator) Meta() meta.RuleMeta {
|
||||||
|
return meta.RuleMeta{
|
||||||
|
Type: RuleType,
|
||||||
|
Label: "发送雨云工单",
|
||||||
|
Description: "触发时通过雨云向指定的工单系统发送工单。",
|
||||||
|
AccountType: AccountType,
|
||||||
|
Fields: []meta.Field{
|
||||||
|
{Key: "title", Label: "工单标题", Type: meta.FieldTextarea, Required: true},
|
||||||
|
{Key: "msg", Label: "工单内容", Type: meta.FieldTextarea, Required: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/ssdomei232/goodBaby/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RainyunExecutor 雨云执行器
|
||||||
|
type RainyunWorkorderExecutor struct{}
|
||||||
|
|
||||||
|
func (e *RainyunWorkorderExecutor) GetType() string {
|
||||||
|
return RuleType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RainyunWorkorderExecutor) Execute(ctx context.Context, rule *model.Rule) error {
|
||||||
|
log.Printf("执行雨云工单规则: %s", rule.Name)
|
||||||
|
|
||||||
|
return sendRainyunWorkorderMsg(ctx, rule)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ssdomei232/goodBaby/handler/db"
|
||||||
|
"github.com/ssdomei232/goodBaby/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 从 Rule 中获取 RainyunConfig
|
||||||
|
func getRainyunConfigFromRule(rule *model.Rule) (*RainyunWorkOrderRule, error) {
|
||||||
|
var rainyunConfig RainyunWorkOrderRule
|
||||||
|
if err := json.Unmarshal([]byte(rule.ConfigJson), &rainyunConfig); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析Rainyun规则配置失败: %w", err)
|
||||||
|
}
|
||||||
|
return &rainyunConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 Rule 中获取 RainyunAccount
|
||||||
|
func getRainyunAccountFromRule(rule *model.Rule) (*RainyunAccount, error) {
|
||||||
|
var rainyunAccount RainyunAccount
|
||||||
|
if err := db.LoadAccountConfig(rule.AccountID, &rainyunAccount); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &rainyunAccount, nil
|
||||||
|
}
|
||||||
@@ -9,11 +9,14 @@ require (
|
|||||||
github.com/gin-contrib/sessions v1.0.4
|
github.com/gin-contrib/sessions v1.0.4
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
|
github.com/go-resty/resty/v2 v2.17.2
|
||||||
github.com/google/go-github/v84 v84.0.0
|
github.com/google/go-github/v84 v84.0.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/ssdomei232/napcat-http-go-sdk v1.0.0
|
github.com/ssdomei232/napcat-http-go-sdk v1.0.0
|
||||||
|
github.com/ssdomei232/rainyun-go-sdk/v2 v2.0.5
|
||||||
github.com/wneessen/go-mail v0.7.2
|
github.com/wneessen/go-mail v0.7.2
|
||||||
golang.org/x/crypto v0.48.0
|
golang.org/x/crypto v0.48.0
|
||||||
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,7 +32,6 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
@@ -68,7 +70,6 @@ require (
|
|||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/tools v0.42.0 // indirect
|
golang.org/x/tools v0.42.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
gorm.io/driver/postgres v1.6.0 // indirect
|
|
||||||
modernc.org/libc v1.22.5 // indirect
|
modernc.org/libc v1.22.5 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.5.0 // indirect
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
|||||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
github.com/ssdomei232/napcat-http-go-sdk v1.0.0 h1:BY3iqqlZw5letiCwTmfMrhyqsx0a96eL5LpDuinnOSE=
|
github.com/ssdomei232/napcat-http-go-sdk v1.0.0 h1:BY3iqqlZw5letiCwTmfMrhyqsx0a96eL5LpDuinnOSE=
|
||||||
github.com/ssdomei232/napcat-http-go-sdk v1.0.0/go.mod h1:yPsSaMs3OyKMVPXwIs5ZB5G532vRi1GvL/WiWUEwblU=
|
github.com/ssdomei232/napcat-http-go-sdk v1.0.0/go.mod h1:yPsSaMs3OyKMVPXwIs5ZB5G532vRi1GvL/WiWUEwblU=
|
||||||
|
github.com/ssdomei232/rainyun-go-sdk/v2 v2.0.5 h1:T30N4npW+U10IlZeQ8SooJi9V42uM+f+lrFPRRftHeg=
|
||||||
|
github.com/ssdomei232/rainyun-go-sdk/v2 v2.0.5/go.mod h1:Wf9X0qEpKQ1/uD4iUGGoPLHydiCxULqCVk+JCoVqdIg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/ssdomei232/goodBaby/drivers/fwalert"
|
"github.com/ssdomei232/goodBaby/drivers/fwalert"
|
||||||
"github.com/ssdomei232/goodBaby/drivers/github"
|
"github.com/ssdomei232/goodBaby/drivers/github"
|
||||||
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
||||||
|
"github.com/ssdomei232/goodBaby/drivers/rainyun"
|
||||||
"github.com/ssdomei232/goodBaby/model"
|
"github.com/ssdomei232/goodBaby/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,6 +79,7 @@ func InitExecutorRegistry() *ExecutorRegistry {
|
|||||||
registry.Register(&onebot.OneBotExecutor{})
|
registry.Register(&onebot.OneBotExecutor{})
|
||||||
registry.Register(&dingtalk.DingTalkExecutor{})
|
registry.Register(&dingtalk.DingTalkExecutor{})
|
||||||
registry.Register(&fwalert.FwalertExecutor{})
|
registry.Register(&fwalert.FwalertExecutor{})
|
||||||
|
registry.Register(&rainyun.RainyunWorkorderExecutor{})
|
||||||
// 未来添加新规则类型时,在这里注册即可
|
// 未来添加新规则类型时,在这里注册即可
|
||||||
|
|
||||||
globalExecutorRegistry = registry
|
globalExecutorRegistry = registry
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/ssdomei232/goodBaby/drivers/email"
|
"github.com/ssdomei232/goodBaby/drivers/email"
|
||||||
"github.com/ssdomei232/goodBaby/drivers/github"
|
"github.com/ssdomei232/goodBaby/drivers/github"
|
||||||
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
||||||
|
"github.com/ssdomei232/goodBaby/drivers/rainyun"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -24,6 +25,7 @@ func InitValidatorRegistry() *ValidatorRegistry {
|
|||||||
r.Register(&email.EmailAccountConfigValidator{})
|
r.Register(&email.EmailAccountConfigValidator{})
|
||||||
r.Register(&github.GitHubAccountConfigValidator{})
|
r.Register(&github.GitHubAccountConfigValidator{})
|
||||||
r.Register(&onebot.OneBotAccountConfigValidator{})
|
r.Register(&onebot.OneBotAccountConfigValidator{})
|
||||||
|
r.Register(&rainyun.RainyunAccountConfigValidator{})
|
||||||
|
|
||||||
registry = r
|
registry = r
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/ssdomei232/goodBaby/drivers/fwalert"
|
"github.com/ssdomei232/goodBaby/drivers/fwalert"
|
||||||
"github.com/ssdomei232/goodBaby/drivers/github"
|
"github.com/ssdomei232/goodBaby/drivers/github"
|
||||||
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
"github.com/ssdomei232/goodBaby/drivers/onebot"
|
||||||
|
"github.com/ssdomei232/goodBaby/drivers/rainyun"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -29,6 +30,7 @@ func InitValidatorRegistry() *ValidatorRegistry {
|
|||||||
r.Register(&dingtalk.DingTalkRuleValidator{})
|
r.Register(&dingtalk.DingTalkRuleValidator{})
|
||||||
r.Register(&github.GithubMakeRepositoryPublicRuleValidator{})
|
r.Register(&github.GithubMakeRepositoryPublicRuleValidator{})
|
||||||
r.Register(&fwalert.FwalertRuleValidator{})
|
r.Register(&fwalert.FwalertRuleValidator{})
|
||||||
|
r.Register(&rainyun.RainyunWorkorderRuleValidator{})
|
||||||
|
|
||||||
registry = r
|
registry = r
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user