package app import ( "context" "strings" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-app/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" ) const ( appWarningItemTypeCard = 1 appWarningUnitMb = 1 appWarningTypeCardTraffic = 1 appWarningMessageMethod = "3" // 微信订阅消息模板 ID 默认值:物联卡预警提醒 appWarningWechatTemplateIdDefault = "DMDP2PKktdtG1ES_3Ic1JcR0LGELQmWE6LunYyerHCY" ) type sAppWarning struct{} type appWarningConfigRow = entity.TWarnConfig func NewAppWarning() *sAppWarning { return &sAppWarning{} } func init() { service.RegisterAppWarning(NewAppWarning()) } func (s *sAppWarning) ConfigList(ctx context.Context, param *model.AppWarningConfigListParam) (res *model.AppWarningConfigListModel, err error) { if param == nil { return nil, gerror.New("预警配置查询参数不能为空") } param.CardValue = strings.TrimSpace(param.CardValue) user, cardRow, cardModel, err := s.resolveWarningCard(ctx, param.CardValue) if err != nil { return nil, err } var rows []*appWarningConfigRow err = s.configModel(ctx). Where(dao.TWarnConfig.Columns().TenantId, user.TenantId). Where(dao.TWarnConfig.Columns().ItemType, appWarningItemTypeCard). Where(dao.TWarnConfig.Columns().ItemId, cardRow.Id). Where(dao.TWarnConfig.Columns().WarnUnit, appWarningUnitMb). OrderAsc(dao.TWarnConfig.Columns().WarnUnit). Scan(&rows) if err != nil { return nil, gerror.Wrap(err, "查询预警配置失败,请稍后重试") } wechatBound, wechatBindText, err := appUserWechatBindingStatus(ctx, user) if err != nil { return nil, err } return &model.AppWarningConfigListModel{ Card: cardModel, Items: s.configRowsToModels(rows), TemplateId: appWarningWechatTemplateId(ctx), TemplateReady: appWarningTemplateReady(ctx), WechatBound: wechatBound, WechatBindText: wechatBindText, }, nil } func (s *sAppWarning) ConfigMeta(ctx context.Context) (res *model.AppWarningConfigMetaModel, err error) { user, err := appCurrentUser(ctx) if err != nil { return nil, err } wechatBound, wechatBindText, err := appUserWechatBindingStatus(ctx, user) if err != nil { return nil, err } return &model.AppWarningConfigMetaModel{ TemplateId: appWarningWechatTemplateId(ctx), TemplateReady: appWarningTemplateReady(ctx), WechatBound: wechatBound, WechatBindText: wechatBindText, AppNotifyEnabled: appWarningNotifyEnabledValue(nil), }, nil } func (s *sAppWarning) ConfigSave(ctx context.Context, param *model.AppWarningConfigSaveParam) (res *model.AppWarningConfigSaveModel, err error) { if err = normalizeAppWarningSaveParam(param); err != nil { return nil, err } user, cardRow, _, err := s.resolveWarningCard(ctx, param.CardValue) if err != nil { return nil, err } saveParam, err := s.prepareWarningSaveParam(ctx, nil, user, cardRow, param) if err != nil { return nil, err } now := gtime.Now() batchNo, err := appNextBatchNo(ctx, consts.BatchNoPrefixWarning) if err != nil { return nil, err } data := appWarningBuildSaveRow(ctx, user, cardRow, saveParam, batchNo, now) _, err = dao.TWarnConfig.Ctx(ctx). Data(data). OnConflict( dao.TWarnConfig.Columns().TenantId, dao.TWarnConfig.Columns().ItemType, dao.TWarnConfig.Columns().ItemId, dao.TWarnConfig.Columns().WarnUnit, ). OnDuplicateEx(appWarningSaveDuplicateExcludeFields()). Save() if err != nil { return nil, gerror.Wrap(err, "保存预警配置失败,请稍后重试") } config, err := s.findConfigByItem(ctx, user.TenantId, cardRow.Id, param.WarnUnit) if err != nil { return nil, err } wechatBound, wechatBindText, err := appUserWechatBindingStatus(ctx, user) if err != nil { return nil, err } if config == nil || config.Id == 0 { return nil, gerror.New("保存预警配置后未查询到配置") } return &model.AppWarningConfigSaveModel{ Id: config.Id, Config: s.configRowToModel(config), TemplateId: appWarningWechatTemplateId(ctx), TemplateReady: appWarningTemplateReady(ctx), WechatBound: wechatBound, WechatBindText: wechatBindText, }, nil } func (s *sAppWarning) ConfigBatchSave(ctx context.Context, param *model.AppWarningConfigBatchSaveParam) (res *model.AppWarningConfigBatchSaveModel, err error) { user, err := appCurrentUser(ctx) if err != nil { return nil, err } cardValues, configs, err := normalizeAppWarningBatchSaveParam(param) if err != nil { return nil, err } cardLogic := NewAppCard() type validCard struct { Value string Row *appCardQueryRow } validCards := make([]validCard, 0, len(cardValues)) failItems := make([]*model.AppWarningConfigBatchFailItem, 0) seenCardIds := make(map[int64]struct{}, len(cardValues)) for _, cardValue := range cardValues { cardRow, err := cardLogic.findVisibleCard(ctx, user, cardValue) if err != nil { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: "查询卡片失败"}) continue } if cardRow == nil || cardRow.Id == 0 { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: "卡片不存在或无权配置"}) continue } if _, exists := seenCardIds[cardRow.Id]; exists { continue } if err = cardLogic.ensureOperateCard(ctx, user, cardRow, "配置预警"); err != nil { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: err.Error()}) continue } seenCardIds[cardRow.Id] = struct{}{} validCards = append(validCards, validCard{Value: cardValue, Row: cardRow}) } if len(validCards) == 0 { return nil, gerror.New("没有可配置预警的卡片") } now := gtime.Now() batchNo, err := appNextBatchNo(ctx, consts.BatchNoPrefixWarning) if err != nil { return nil, err } err = dao.TWarnConfig.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { for _, card := range validCards { for _, config := range configs { saveParam, err := s.prepareWarningSaveParam(ctx, tx, user, card.Row, config) if err != nil { return err } data := appWarningBuildSaveRow(ctx, user, card.Row, saveParam, batchNo, now) if _, err = dao.TWarnConfig.Ctx(ctx).TX(tx). Data(data). OnConflict( dao.TWarnConfig.Columns().TenantId, dao.TWarnConfig.Columns().ItemType, dao.TWarnConfig.Columns().ItemId, dao.TWarnConfig.Columns().WarnUnit, ). OnDuplicateEx(appWarningSaveDuplicateExcludeFields()). Save(); err != nil { return err } } } return nil }) if err != nil { return nil, gerror.Wrap(err, "批量保存预警配置失败,请稍后重试") } wechatBound, wechatBindText, err := appUserWechatBindingStatus(ctx, user) if err != nil { return nil, err } return &model.AppWarningConfigBatchSaveModel{ BatchNo: batchNo, TotalCount: len(cardValues), SuccessCount: len(validCards), FailCount: len(failItems), FailItems: failItems, TemplateId: appWarningWechatTemplateId(ctx), TemplateReady: appWarningTemplateReady(ctx), WechatBound: wechatBound, WechatBindText: wechatBindText, }, nil } func (s *sAppWarning) ConfigNotify(ctx context.Context, param *model.AppWarningConfigNotifyParam) (res *model.AppWarningConfigNotifyModel, err error) { if err = normalizeAppWarningNotifyParam(param); err != nil { return nil, err } user, cardRow, _, err := s.resolveWarningCard(ctx, param.CardValue) if err != nil { return nil, err } config, err := s.findConfigByItem(ctx, user.TenantId, cardRow.Id, param.WarnUnit) if err != nil { return nil, err } if config == nil || config.Id == 0 { return nil, gerror.New("请先保存预警阈值后再设置小程序提醒") } now := gtime.Now() _, err = dao.TWarnConfig.Ctx(ctx). Where(dao.TWarnConfig.Columns().TenantId, user.TenantId). Where(dao.TWarnConfig.Columns().ItemType, appWarningItemTypeCard). Where(dao.TWarnConfig.Columns().ItemId, cardRow.Id). Where(dao.TWarnConfig.Columns().WarnUnit, param.WarnUnit). Data(do.TWarnConfig{ AppNotifyEnabled: appWarningNotifyEnabledValue(param.AppNotifyEnabled), UpdatedBy: user.UserId, UpdatedAt: now, }). Update() if err != nil { return nil, gerror.Wrap(err, "设置小程序预警通知失败,请稍后重试") } config, err = s.findConfigByItem(ctx, user.TenantId, cardRow.Id, param.WarnUnit) if err != nil { return nil, err } wechatBound, wechatBindText, err := appUserWechatBindingStatus(ctx, user) if err != nil { return nil, err } return &model.AppWarningConfigNotifyModel{ AppNotifyEnabled: appWarningNotifyEnabledValue(param.AppNotifyEnabled), Config: s.configRowToModel(config), TemplateId: appWarningWechatTemplateId(ctx), TemplateReady: appWarningTemplateReady(ctx), WechatBound: wechatBound, WechatBindText: wechatBindText, }, nil } func (s *sAppWarning) ConfigBatchNotify(ctx context.Context, param *model.AppWarningConfigBatchNotifyParam) (res *model.AppWarningConfigBatchNotifyModel, err error) { user, err := appCurrentUser(ctx) if err != nil { return nil, err } cardValues, err := normalizeAppWarningBatchNotifyParam(param) if err != nil { return nil, err } cardLogic := NewAppCard() type validCard struct { Value string Row *appCardQueryRow } validCards := make([]validCard, 0, len(cardValues)) failItems := make([]*model.AppWarningConfigBatchFailItem, 0) seenCardIds := make(map[int64]struct{}, len(cardValues)) for _, cardValue := range cardValues { cardRow, err := cardLogic.findVisibleCard(ctx, user, cardValue) if err != nil { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: "查询卡片失败"}) continue } if cardRow == nil || cardRow.Id == 0 { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: "卡片不存在或无权配置"}) continue } if _, exists := seenCardIds[cardRow.Id]; exists { continue } if err = cardLogic.ensureOperateCard(ctx, user, cardRow, "配置预警"); err != nil { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: err.Error()}) continue } config, err := s.findConfigByItem(ctx, user.TenantId, cardRow.Id, param.WarnUnit) if err != nil { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: "查询预警配置失败"}) continue } if config == nil || config.Id == 0 { failItems = append(failItems, &model.AppWarningConfigBatchFailItem{CardValue: cardValue, Reason: "请先保存预警配置"}) continue } seenCardIds[cardRow.Id] = struct{}{} validCards = append(validCards, validCard{Value: cardValue, Row: cardRow}) } now := gtime.Now() err = dao.TWarnConfig.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { for _, card := range validCards { if _, err = dao.TWarnConfig.Ctx(ctx).TX(tx). Where(dao.TWarnConfig.Columns().TenantId, user.TenantId). Where(dao.TWarnConfig.Columns().ItemType, appWarningItemTypeCard). Where(dao.TWarnConfig.Columns().ItemId, card.Row.Id). Where(dao.TWarnConfig.Columns().WarnUnit, param.WarnUnit). Data(do.TWarnConfig{ AppNotifyEnabled: appWarningNotifyEnabledValue(param.AppNotifyEnabled), UpdatedBy: user.UserId, UpdatedAt: now, }). Update(); err != nil { return err } } return nil }) if err != nil { return nil, gerror.Wrap(err, "批量设置小程序预警通知失败,请稍后重试") } wechatBound, wechatBindText, err := appUserWechatBindingStatus(ctx, user) if err != nil { return nil, err } return &model.AppWarningConfigBatchNotifyModel{ AppNotifyEnabled: appWarningNotifyEnabledValue(param.AppNotifyEnabled), TotalCount: len(cardValues), SuccessCount: len(validCards), FailCount: len(failItems), FailItems: failItems, TemplateId: appWarningWechatTemplateId(ctx), TemplateReady: appWarningTemplateReady(ctx), WechatBound: wechatBound, WechatBindText: wechatBindText, }, nil } func (s *sAppWarning) resolveWarningCard(ctx context.Context, cardValue string) (*entity.SysUser, *appCardQueryRow, *model.AppCardModel, error) { user, err := appCurrentUser(ctx) if err != nil { return nil, nil, nil, err } cardLogic := NewAppCard() cardRow, err := cardLogic.findVisibleCard(ctx, user, cardValue) if err != nil { return nil, nil, nil, gerror.Wrap(err, "查询卡片失败,请稍后重试") } if cardRow == nil || cardRow.Id == 0 { return nil, nil, nil, gerror.New("卡片不存在或不在当前账号可配置范围内") } if err = cardLogic.ensureOperateCard(ctx, user, cardRow, "配置预警"); err != nil { return nil, nil, nil, err } return user, cardRow, cardLogic.cardRowToModel(ctx, cardRow, user, cardLogic.ownerType(ctx, user), true), nil } func (s *sAppWarning) configModel(ctx context.Context) *gdb.Model { return dao.TWarnConfig.Ctx(ctx) } func (s *sAppWarning) findConfigByItem(ctx context.Context, tenantId string, itemId int64, warnUnit int) (*appWarningConfigRow, error) { return s.findConfigByItemWithModel( dao.TWarnConfig.Ctx(ctx), tenantId, itemId, warnUnit, ) } func (s *sAppWarning) findConfigByItemWithTx(ctx context.Context, tx gdb.TX, tenantId string, itemId int64, warnUnit int) (*appWarningConfigRow, error) { return s.findConfigByItemWithModel( dao.TWarnConfig.Ctx(ctx).TX(tx), tenantId, itemId, warnUnit, ) } func (s *sAppWarning) findConfigByItemWithModel(mod *gdb.Model, tenantId string, itemId int64, warnUnit int) (*appWarningConfigRow, error) { var row *appWarningConfigRow err := mod. Where(dao.TWarnConfig.Columns().TenantId, tenantId). Where(dao.TWarnConfig.Columns().ItemType, appWarningItemTypeCard). Where(dao.TWarnConfig.Columns().ItemId, itemId). Where(dao.TWarnConfig.Columns().WarnUnit, warnUnit). Limit(1). Scan(&row) if err != nil { return nil, gerror.Wrap(err, "查询预警配置失败,请稍后重试") } return row, nil } func (s *sAppWarning) prepareWarningSaveParam( ctx context.Context, tx gdb.TX, user *entity.SysUser, cardRow *appCardQueryRow, param *model.AppWarningConfigSaveParam, ) (*model.AppWarningConfigSaveParam, error) { if param == nil { return nil, gerror.New("预警配置参数不能为空") } saveParam := *param var ( config *appWarningConfigRow err error ) if tx != nil { config, err = s.findConfigByItemWithTx(ctx, tx, user.TenantId, cardRow.Id, param.WarnUnit) } else { config, err = s.findConfigByItem(ctx, user.TenantId, cardRow.Id, param.WarnUnit) } if err != nil { return nil, err } if config != nil { if saveParam.IsEnabled == nil { value := config.IsEnabled saveParam.IsEnabled = &value } if saveParam.AppNotifyEnabled == nil { value := config.AppNotifyEnabled saveParam.AppNotifyEnabled = &value } } return &saveParam, nil } func (s *sAppWarning) configRowsToModels(rows []*appWarningConfigRow) []*model.AppWarningConfigModel { items := make([]*model.AppWarningConfigModel, 0, len(rows)) for _, row := range rows { items = append(items, s.configRowToModel(row)) } return items } func (s *sAppWarning) configRowToModel(row *appWarningConfigRow) *model.AppWarningConfigModel { if row == nil { return nil } warnType := appWarningTypeByUnit(row.WarnUnit) return &model.AppWarningConfigModel{ Id: row.Id, ItemType: row.ItemType, ItemId: row.ItemId, ItemCode: row.ItemCode, WarnType: warnType, WarnUnit: row.WarnUnit, WarnTitle: appWarningTitle(row.WarnUnit), UnitText: appWarningUnitText(row.WarnUnit), FirstWarn: row.FirstWarn, SecondWarn: row.SecondWarn, ThirdWarn: row.ThirdWarn, AutoAction: row.AutoAction, IsEnabled: row.IsEnabled, AppNotifyEnabled: row.AppNotifyEnabled, WxTemplateId: row.WxTemplateId, Remark: row.Remark, UpdatedAt: row.UpdatedAt, } } func normalizeAppWarningSaveParam(param *model.AppWarningConfigSaveParam) error { if param == nil { return gerror.New("预警配置参数不能为空") } param.CardValue = strings.TrimSpace(param.CardValue) param.Remark = strings.TrimSpace(param.Remark) if param.IsEnabled != nil && *param.IsEnabled != 0 && *param.IsEnabled != 1 { return gerror.New("启停状态无效") } if param.AppNotifyEnabled != nil && *param.AppNotifyEnabled != 0 && *param.AppNotifyEnabled != 1 { return gerror.New("小程序预警通知状态无效") } switch param.WarnUnit { case appWarningUnitMb: return validateAppWarningTrafficMb(param.FirstWarn, param.SecondWarn, param.ThirdWarn) default: return gerror.New("小程序仅支持按MB配置流量预警") } } func normalizeAppWarningBatchSaveParam(param *model.AppWarningConfigBatchSaveParam) ([]string, []*model.AppWarningConfigSaveParam, error) { if param == nil { return nil, nil, gerror.New("批量预警配置参数不能为空") } cardValues, err := normalizeAppWarningCardValues(param.CardValues) if err != nil { return nil, nil, err } if len(cardValues) > 100 { return nil, nil, gerror.New("单次批量预警配置最多支持100张卡") } configs := make([]*model.AppWarningConfigSaveParam, 0, len(param.Configs)) seenUnits := make(map[int]struct{}, len(param.Configs)) for _, config := range param.Configs { if config == nil { continue } copied := model.AppWarningConfigSaveParam{ WarnUnit: config.WarnUnit, FirstWarn: config.FirstWarn, SecondWarn: config.SecondWarn, ThirdWarn: config.ThirdWarn, IsEnabled: config.IsEnabled, AppNotifyEnabled: config.AppNotifyEnabled, Remark: config.Remark, } if _, exists := seenUnits[copied.WarnUnit]; exists { return nil, nil, gerror.New("同一预警类型不能重复配置") } if err := normalizeAppWarningSaveParam(&copied); err != nil { return nil, nil, err } seenUnits[copied.WarnUnit] = struct{}{} configs = append(configs, &copied) } if len(configs) == 0 { return nil, nil, gerror.New("请至少填写一项预警配置") } return cardValues, configs, nil } func normalizeAppWarningNotifyParam(param *model.AppWarningConfigNotifyParam) error { if param == nil { return gerror.New("小程序预警通知参数不能为空") } param.CardValue = strings.TrimSpace(param.CardValue) if param.WarnUnit == 0 { param.WarnUnit = appWarningUnitMb } if param.AppNotifyEnabled == nil { return gerror.New("请选择是否开启小程序预警通知") } if *param.AppNotifyEnabled != 0 && *param.AppNotifyEnabled != 1 { return gerror.New("小程序预警通知状态无效") } if param.WarnUnit != appWarningUnitMb { return gerror.New("小程序仅支持配置MB流量预警通知") } return nil } func normalizeAppWarningBatchNotifyParam(param *model.AppWarningConfigBatchNotifyParam) ([]string, error) { if param == nil { return nil, gerror.New("批量小程序预警通知参数不能为空") } if param.WarnUnit == 0 { param.WarnUnit = appWarningUnitMb } if param.AppNotifyEnabled == nil { return nil, gerror.New("请选择是否开启小程序预警通知") } if *param.AppNotifyEnabled != 0 && *param.AppNotifyEnabled != 1 { return nil, gerror.New("小程序预警通知状态无效") } if param.WarnUnit != appWarningUnitMb { return nil, gerror.New("小程序仅支持配置MB流量预警通知") } cardValues, err := normalizeAppWarningCardValues(param.CardValues) if err != nil { return nil, err } if len(cardValues) > 100 { return nil, gerror.New("单次批量预警通知最多支持100张卡") } return cardValues, nil } func normalizeAppWarningCardValues(values []string) ([]string, error) { cardValues := make([]string, 0, len(values)) seenCards := make(map[string]struct{}, len(values)) for _, value := range values { value = strings.TrimSpace(value) if value == "" { continue } if _, exists := seenCards[value]; exists { continue } seenCards[value] = struct{}{} cardValues = append(cardValues, value) } if len(cardValues) == 0 { return nil, gerror.New("请选择需要配置预警的卡片") } return cardValues, nil } func appWarningBuildSaveRow(ctx context.Context, user *entity.SysUser, cardRow *appCardQueryRow, param *model.AppWarningConfigSaveParam, batchNo string, now *gtime.Time) do.TWarnConfig { return do.TWarnConfig{ TenantId: user.TenantId, BatchNo: batchNo, ItemType: appWarningItemTypeCard, ItemId: cardRow.Id, ItemCode: appWarningCardCode(cardRow), WarnUnit: param.WarnUnit, FirstWarn: param.FirstWarn, SecondWarn: param.SecondWarn, ThirdWarn: param.ThirdWarn, WarnMethods: appWarningMessageMethod, WarnPhones: "", WarnEmails: "", AutoAction: 0, IsEnabled: appWarningEnabledValue(param.IsEnabled), AppNotifyEnabled: appWarningNotifyEnabledValue(param.AppNotifyEnabled), WxTemplateId: appWarningWechatTemplateId(ctx), Remark: strings.TrimSpace(param.Remark), CreatedDept: appUserDeptOrDefault(user), CreatedBy: user.UserId, CreatedAt: now, UpdatedBy: user.UserId, UpdatedAt: now, } } func appWarningSaveDuplicateExcludeFields() string { columns := dao.TWarnConfig.Columns() return strings.Join([]string{ columns.Id, columns.TenantId, columns.ItemType, columns.ItemId, columns.WarnUnit, columns.CreatedDept, columns.CreatedBy, columns.CreatedAt, }, ",") } func validateAppWarningTrafficMb(first, second, third float64) error { if first <= 0 { return gerror.New("第一档阈值必须大于0") } if second < 0 || third < 0 { return gerror.New("预警阈值不能小于0") } if second > 0 && second <= first { return gerror.New("第二档阈值必须大于第一档") } if third > 0 { if second <= 0 { return gerror.New("配置第三档时必须先配置第二档") } if third <= second { return gerror.New("第三档阈值必须大于第二档") } } return nil } func appWarningEnabledValue(value *int) int { if value == nil { return 1 } return *value } func appWarningNotifyEnabledValue(value *int) int { if value == nil { return 0 } return *value } func appWarningTypeByUnit(warnUnit int) int { switch warnUnit { case appWarningUnitMb: return appWarningTypeCardTraffic default: return 0 } } func appWarningTitle(warnUnit int) string { switch warnUnit { case appWarningUnitMb: return "流量使用预警" default: return "预警配置" } } func appWarningUnitText(warnUnit int) string { switch warnUnit { case appWarningUnitMb: return "MB" default: return "" } } func appWarningCardCode(card *appCardQueryRow) string { if card == nil { return "" } return appFirstNotEmpty(card.Iccid, card.Msisdn, card.Imsi, card.Imei) } func appWarningWechatTemplateId(ctx context.Context) string { return strings.TrimSpace(g.Cfg().MustGet(ctx, "appWarning.wechatSubscribeTemplateId", appWarningWechatTemplateIdDefault).String()) } func appWarningTemplateReady(ctx context.Context) bool { value := appWarningWechatTemplateId(ctx) return value != "" && !strings.Contains(strings.ToUpper(value), "TODO") && !strings.Contains(strings.ToUpper(value), "REPLACE") }