This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ssdomei232/goodBaby/api/response"
|
||||
"github.com/ssdomei232/goodBaby/api/user"
|
||||
"github.com/ssdomei232/goodBaby/handler/db"
|
||||
"github.com/ssdomei232/goodBaby/handler/runner"
|
||||
"github.com/ssdomei232/goodBaby/internal/gateway"
|
||||
"github.com/ssdomei232/goodBaby/internal/meta"
|
||||
"github.com/ssdomei232/goodBaby/internal/retry"
|
||||
"github.com/ssdomei232/goodBaby/model"
|
||||
)
|
||||
|
||||
// HandleGetAllGatewayRules 获取用户的消息网关规则,支持按 gateway_id 过滤
|
||||
//
|
||||
// 消息网关规则和定时器规则是两张表,互不影响,因此这里单独一组接口。
|
||||
func HandleGetAllGatewayRules(c *gin.Context) {
|
||||
userInfo, err := user.GetUserInfoByGinCtx(c)
|
||||
if err != nil {
|
||||
response.Unauthorized(c, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
response.ServerError(c, "服务器内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
query := gormDB.Where("uid = ?", userInfo.ID)
|
||||
if raw := c.Query("gateway_id"); raw != "" {
|
||||
gatewayID, err := parseID(raw)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "gateway_id 格式错误")
|
||||
return
|
||||
}
|
||||
query = query.Where("gateway_id = ?", gatewayID)
|
||||
}
|
||||
|
||||
rules := []model.GatewayRule{}
|
||||
if err := query.Order("id DESC").Find(&rules).Error; err != nil {
|
||||
response.ServerError(c, "获取网关规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, maskGatewayRules(rules))
|
||||
}
|
||||
|
||||
// HandleCreateGatewayRule 创建消息网关规则
|
||||
func HandleCreateGatewayRule(c *gin.Context) {
|
||||
userInfo, err := user.GetUserInfoByGinCtx(c)
|
||||
if err != nil {
|
||||
response.Unauthorized(c, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
var req model.GatewayRuleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "输入参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
newRule := model.GatewayRule{
|
||||
UID: userInfo.ID,
|
||||
Name: req.Name,
|
||||
GatewayID: req.GatewayID,
|
||||
AccountID: req.AccountID,
|
||||
Type: req.Type,
|
||||
ConfigJson: req.ConfigJson,
|
||||
Enabled: boolOr(req.Enabled, true),
|
||||
CreateAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
if err := validateGatewayRule(&req, &newRule); err != nil {
|
||||
response.FromError(c, err, "创建网关规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
response.ServerError(c, "服务器内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := gormDB.Create(&newRule).Error; err != nil {
|
||||
response.ServerError(c, "创建网关规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, maskGatewayRule(newRule))
|
||||
}
|
||||
|
||||
// HandleEditGatewayRule 编辑消息网关规则
|
||||
func HandleEditGatewayRule(c *gin.Context) {
|
||||
userInfo, err := user.GetUserInfoByGinCtx(c)
|
||||
if err != nil {
|
||||
response.Unauthorized(c, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
ruleID, err := parseID(c.Param("ruleID"))
|
||||
if err != nil {
|
||||
response.BadRequest(c, "规则 ID 格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := findGatewayRule(ruleID, userInfo.ID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "网关规则不存在")
|
||||
return
|
||||
}
|
||||
|
||||
var req model.GatewayRuleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "输入参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
updated := *existing
|
||||
updated.Name = req.Name
|
||||
updated.Type = req.Type
|
||||
updated.GatewayID = req.GatewayID
|
||||
updated.AccountID = req.AccountID
|
||||
updated.Enabled = boolOr(req.Enabled, existing.Enabled)
|
||||
// 前端提交的敏感字段可能是掩码占位符,用旧配置补回
|
||||
updated.ConfigJson = unmaskRuleConfig(req.Type, req.ConfigJson, existing.ConfigJson)
|
||||
|
||||
req.ConfigJson = updated.ConfigJson
|
||||
if err := validateGatewayRule(&req, &updated); err != nil {
|
||||
response.FromError(c, err, "更新网关规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
response.ServerError(c, "服务器内部错误")
|
||||
return
|
||||
}
|
||||
|
||||
if err := gormDB.Save(&updated).Error; err != nil {
|
||||
response.ServerError(c, "更新网关规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, maskGatewayRule(updated))
|
||||
}
|
||||
|
||||
// HandleDeleteGatewayRule 删除消息网关规则
|
||||
func HandleDeleteGatewayRule(c *gin.Context) {
|
||||
userInfo, err := user.GetUserInfoByGinCtx(c)
|
||||
if err != nil {
|
||||
response.Unauthorized(c, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
ruleID, err := parseID(c.Param("ruleID"))
|
||||
if err != nil {
|
||||
response.BadRequest(c, "规则 ID 格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := findGatewayRule(ruleID, userInfo.ID); err != nil {
|
||||
response.NotFound(c, "网关规则不存在")
|
||||
return
|
||||
}
|
||||
|
||||
if err := DeleteGatewayRuleByID(ruleID, userInfo.ID); err != nil {
|
||||
response.ServerError(c, "删除网关规则失败")
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, "网关规则删除成功")
|
||||
}
|
||||
|
||||
// HandleTestGatewayRule 立即执行一次网关规则,用于验证配置
|
||||
//
|
||||
// 与投递不同:这里用规则里保存的配置,不覆盖外部消息。
|
||||
func HandleTestGatewayRule(c *gin.Context) {
|
||||
userInfo, err := user.GetUserInfoByGinCtx(c)
|
||||
if err != nil {
|
||||
response.Unauthorized(c, "获取用户信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
ruleID, err := parseID(c.Param("ruleID"))
|
||||
if err != nil {
|
||||
response.BadRequest(c, "规则 ID 格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
target, err := findGatewayRule(ruleID, userInfo.ID)
|
||||
if err != nil {
|
||||
response.NotFound(c, "网关规则不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 用一条示例消息走一次真实的投递覆盖,让用户看到规则最终会发出什么内容
|
||||
if configJSON, applied, err := gateway.ApplyMessage(target.ConfigJson, gateway.TestMessage); err == nil && applied {
|
||||
target.ConfigJson = configJSON
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), retry.TestTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := runner.ExecuteGatewayRuleWithContext(ctx, target, model.TriggerManual); err != nil {
|
||||
response.BadRequest(c, fmt.Sprintf("规则执行失败: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, "规则执行成功")
|
||||
}
|
||||
|
||||
// validateGatewayRule 校验网关规则的通用字段、关联对象与类型专属配置
|
||||
func validateGatewayRule(req *model.GatewayRuleRequest, rule *model.GatewayRule) error {
|
||||
if err := req.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := checkGatewayExists(rule.GatewayID, rule.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ruleMeta, err := ruleMetaOf(req.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 标题/内容由投递请求提供,页面不填,这里补占位值后再校验
|
||||
rule.ConfigJson = meta.FillGatewayMessages(rule.ConfigJson, ruleMeta.Fields)
|
||||
if err := validateRuleConfig(req.Type, rule.ConfigJson); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accountID, err := checkAccount(rule.AccountID, rule.UID, ruleMeta.AccountType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rule.AccountID = accountID
|
||||
|
||||
return nil
|
||||
}
|
||||
+14
-11
@@ -67,7 +67,6 @@ func HandleCreateRule(c *gin.Context) {
|
||||
UID: userInfo.ID,
|
||||
Name: req.Name,
|
||||
TimerID: req.TimerID,
|
||||
GatewayID: req.GatewayID,
|
||||
AccountID: req.AccountID,
|
||||
Type: req.Type,
|
||||
ConfigJson: req.ConfigJson,
|
||||
@@ -124,7 +123,6 @@ func HandleEditRule(c *gin.Context) {
|
||||
updated.Name = req.Name
|
||||
updated.Type = req.Type
|
||||
updated.TimerID = req.TimerID
|
||||
updated.GatewayID = req.GatewayID
|
||||
updated.AccountID = req.AccountID
|
||||
updated.Enabled = boolOr(req.Enabled, existing.Enabled)
|
||||
// 前端提交的敏感字段可能是掩码占位符,用旧配置补回
|
||||
@@ -221,19 +219,24 @@ func validateRule(req *model.RuleRequest, rule *model.Rule) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ruleMeta, ok := ruleConfigChecker.InitValidatorRegistry().MetaOf(req.Type)
|
||||
if !ok {
|
||||
return model.ErrValidation(fmt.Sprintf("不支持的规则类型: %s", req.Type))
|
||||
}
|
||||
|
||||
// 检查关联的 Timer 与账号是否存在且属于当前用户
|
||||
if err := checkRuleConfigAccountAndTimerExist(rule, ruleMeta.AccountType); err != nil {
|
||||
// 检查关联的 Timer 是否存在、账号是否可用
|
||||
if err := checkTimerExists(rule.TimerID, rule.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ruleConfigChecker.InitValidatorRegistry().Validate(req.Type, rule.ConfigJson); err != nil {
|
||||
return model.ErrValidation(fmt.Sprintf("规则配置验证失败: %s", err.Error()))
|
||||
ruleMeta, err := ruleMetaOf(req.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateRuleConfig(req.Type, rule.ConfigJson); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accountID, err := checkAccount(rule.AccountID, rule.UID, ruleMeta.AccountType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rule.AccountID = accountID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+136
-46
@@ -25,7 +25,7 @@ func boolOr(v *bool, fallback bool) bool {
|
||||
return *v
|
||||
}
|
||||
|
||||
// findRule 查找属于该用户的规则
|
||||
// findRule 查找属于该用户的定时器规则
|
||||
func findRule(ruleID, uid uint) (*model.Rule, error) {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
@@ -39,7 +39,21 @@ func findRule(ruleID, uid uint) (*model.Rule, error) {
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// DeleteRuleByID 根据 ID 删除规则
|
||||
// findGatewayRule 查找属于该用户的消息网关规则
|
||||
func findGatewayRule(ruleID, uid uint) (*model.GatewayRule, error) {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var target model.GatewayRule
|
||||
if err := gormDB.Where("id = ? AND uid = ?", ruleID, uid).First(&target).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// DeleteRuleByID 根据 ID 删除定时器规则
|
||||
func DeleteRuleByID(id uint, uid uint) error {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
@@ -49,58 +63,99 @@ func DeleteRuleByID(id uint, uid uint) error {
|
||||
return gormDB.Where("id = ? AND uid = ?", id, uid).Delete(&model.Rule{}).Error
|
||||
}
|
||||
|
||||
// checkRuleConfigAccountAndTimerExist 检查关联的 Timer 与账号是否存在且归属当前用户
|
||||
//
|
||||
// requiredAccountType 为空表示该规则类型不需要账号。
|
||||
func checkRuleConfigAccountAndTimerExist(rule *model.Rule, requiredAccountType string) error {
|
||||
// DeleteGatewayRuleByID 根据 ID 删除消息网关规则
|
||||
func DeleteGatewayRuleByID(id uint, uid uint) error {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gormDB.Where("id = ? AND uid = ?", id, uid).Delete(&model.GatewayRule{}).Error
|
||||
}
|
||||
|
||||
// checkTimerExists 检查关联的 Timer 是否存在且归属当前用户
|
||||
func checkTimerExists(timerID, uid uint) error {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var count int64
|
||||
if rule.TimerID != 0 {
|
||||
if err := gormDB.Model(&model.Timer{}).Where("id = ? AND uid = ?", rule.TimerID, rule.UID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return model.ErrValidation("关联的 Timer 不存在")
|
||||
}
|
||||
} else {
|
||||
if err := gormDB.Model(&model.MessageGateway{}).Where("id = ? AND uid = ?", rule.GatewayID, rule.UID).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return model.ErrValidation("关联的消息网关不存在")
|
||||
}
|
||||
if err := gormDB.Model(&model.Timer{}).Where("id = ? AND uid = ?", timerID, uid).
|
||||
Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if requiredAccountType == "" {
|
||||
// 该类型不需要账号,忽略前端可能传来的 account_id
|
||||
rule.AccountID = 0
|
||||
return nil
|
||||
if count == 0 {
|
||||
return model.ErrValidation("关联的 Timer 不存在")
|
||||
}
|
||||
|
||||
if rule.AccountID == 0 {
|
||||
return model.ErrValidation("该规则类型必须关联一个账号")
|
||||
}
|
||||
|
||||
var account model.Account
|
||||
if err := gormDB.Where("id = ? AND uid = ?", rule.AccountID, rule.UID).First(&account).Error; err != nil {
|
||||
return model.ErrValidation("关联的账号不存在")
|
||||
}
|
||||
if account.Type != requiredAccountType {
|
||||
return model.ErrValidation(fmt.Sprintf("规则需要 %s 类型的账号,但关联的是 %s", requiredAccountType, account.Type))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// maskRule 掩码规则配置中的敏感字段
|
||||
func maskRule(target model.Rule) model.Rule {
|
||||
if ruleMeta, ok := ruleConfigChecker.InitValidatorRegistry().MetaOf(target.Type); ok {
|
||||
target.ConfigJson = meta.Mask(target.ConfigJson, ruleMeta.Fields)
|
||||
// checkGatewayExists 检查关联的消息网关是否存在且归属当前用户
|
||||
func checkGatewayExists(gatewayID, uid uint) error {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := gormDB.Model(&model.MessageGateway{}).Where("id = ? AND uid = ?", gatewayID, uid).
|
||||
Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return model.ErrValidation("关联的消息网关不存在")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAccount 校验规则关联的账号存在、归属当前用户且类型匹配。
|
||||
//
|
||||
// requiredType 为空表示该规则类型不需要账号,此时返回 0,调用方据此清掉 account_id。
|
||||
func checkAccount(accountID, uid uint, requiredType string) (uint, error) {
|
||||
if requiredType == "" {
|
||||
return 0, nil
|
||||
}
|
||||
if accountID == 0 {
|
||||
return 0, model.ErrValidation("该规则类型必须关联一个账号")
|
||||
}
|
||||
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var account model.Account
|
||||
if err := gormDB.Where("id = ? AND uid = ?", accountID, uid).First(&account).Error; err != nil {
|
||||
return 0, model.ErrValidation("关联的账号不存在")
|
||||
}
|
||||
if account.Type != requiredType {
|
||||
return 0, model.ErrValidation(
|
||||
fmt.Sprintf("规则需要 %s 类型的账号,但关联的是 %s", requiredType, account.Type))
|
||||
}
|
||||
return accountID, nil
|
||||
}
|
||||
|
||||
// ruleMetaOf 取规则类型的元数据,不支持的类型返回校验错误
|
||||
func ruleMetaOf(ruleType string) (meta.RuleMeta, error) {
|
||||
ruleMeta, ok := ruleConfigChecker.InitValidatorRegistry().MetaOf(ruleType)
|
||||
if !ok {
|
||||
return meta.RuleMeta{}, model.ErrValidation(fmt.Sprintf("不支持的规则类型: %s", ruleType))
|
||||
}
|
||||
return ruleMeta, nil
|
||||
}
|
||||
|
||||
// validateRuleConfig 校验类型专属的规则配置
|
||||
func validateRuleConfig(ruleType, configJSON string) error {
|
||||
if err := ruleConfigChecker.InitValidatorRegistry().Validate(ruleType, configJSON); err != nil {
|
||||
return model.ErrValidation(fmt.Sprintf("规则配置验证失败: %s", err.Error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// maskRule 掩码定时器规则配置中的敏感字段
|
||||
func maskRule(target model.Rule) model.Rule {
|
||||
target.ConfigJson = maskConfig(target.Type, target.ConfigJson)
|
||||
return target
|
||||
}
|
||||
|
||||
@@ -112,17 +167,52 @@ func maskRules(rules []model.Rule) []model.Rule {
|
||||
return masked
|
||||
}
|
||||
|
||||
// getRuleOwnerUID 获取规则所属用户的 UID
|
||||
// maskGatewayRule 掩码消息网关规则配置中的敏感字段
|
||||
func maskGatewayRule(target model.GatewayRule) model.GatewayRule {
|
||||
target.ConfigJson = maskConfig(target.Type, target.ConfigJson)
|
||||
return target
|
||||
}
|
||||
|
||||
func maskGatewayRules(rules []model.GatewayRule) []model.GatewayRule {
|
||||
masked := make([]model.GatewayRule, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
masked = append(masked, maskGatewayRule(r))
|
||||
}
|
||||
return masked
|
||||
}
|
||||
|
||||
// maskConfig 掩码规则配置中的敏感字段
|
||||
func maskConfig(ruleType, configJSON string) string {
|
||||
if ruleMeta, ok := ruleConfigChecker.InitValidatorRegistry().MetaOf(ruleType); ok {
|
||||
return meta.Mask(configJSON, ruleMeta.Fields)
|
||||
}
|
||||
return configJSON
|
||||
}
|
||||
|
||||
// getRuleOwnerUID 获取定时器规则所属用户的 UID
|
||||
func getRuleOwnerUID(ruleID uint) (uint, error) {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var rule model.Rule
|
||||
if err := gormDB.Select("uid").Where("id = ?", ruleID).First(&rule).Error; err != nil {
|
||||
var target model.Rule
|
||||
if err := gormDB.Select("uid").Where("id = ?", ruleID).First(&target).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return target.UID, nil
|
||||
}
|
||||
|
||||
// getGatewayRuleOwnerUID 获取消息网关规则所属用户的 UID
|
||||
func getGatewayRuleOwnerUID(ruleID uint) (uint, error) {
|
||||
gormDB, err := db.GetGormDB()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return rule.UID, nil
|
||||
var target model.GatewayRule
|
||||
if err := gormDB.Select("uid").Where("id = ?", ruleID).First(&target).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return target.UID, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user