package sysin import ( "context" "net/url" "regexp" "strings" "hotgo/internal/consts" "hotgo/internal/library/dict" "hotgo/internal/model/entity" "hotgo/internal/model/input/form" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" "github.com/microcosm-cc/bluemonday" ) type SiteConfigData struct { LogoUrl string `json:"logoUrl"` SiteName string `json:"siteName"` PrivacyPolicyUrl string `json:"privacyPolicyUrl"` TradeBalanceAmount int64 `json:"tradeBalanceAmount"` TradeWaitSeconds int `json:"tradeWaitSeconds"` DailyTradeLimit int `json:"dailyTradeLimit"` DailyRewardLimit int `json:"dailyRewardLimit"` CommissionRates []float64 `json:"commissionRates"` WithdrawTimeRanges []string `json:"withdrawTimeRanges"` RechargeTimeRanges []string `json:"rechargeTimeRanges"` TradeTimeRanges []string `json:"tradeTimeRanges"` SignTimeRanges []string `json:"signTimeRanges"` MinimumRechargeAmount int64 `json:"minimumRechargeAmount"` AutoOrderDelaySeconds int `json:"autoOrderDelaySeconds"` AutoOrderMinAmount int64 `json:"autoOrderMinAmount"` AutoOrderMaxAmount int64 `json:"autoOrderMaxAmount"` FreeClaimEnabled int `json:"freeClaimEnabled"` ManualMallEnabled int `json:"manualMallEnabled"` HomeTaskEnabled int `json:"homeTaskEnabled"` SignEnabled int `json:"signEnabled"` ProductModeEnabled int `json:"productModeEnabled"` CaptchaEnabled int `json:"captchaEnabled"` } type SiteConfigSaveInp struct { Id uint64 `json:"id"` Version uint `json:"version"` ConfigGroup string `json:"configGroup" d:"portal" v:"required|max-length:64#配置组不能为空|配置组过长"` Config SiteConfigData `json:"config" v:"required#配置不能为空"` EffectiveAt *gtime.Time `json:"effectiveAt"` Remark string `json:"remark" v:"max-length:500#备注不能超过500个字符"` } func (in *SiteConfigSaveInp) Filter(ctx context.Context) error { if strings.TrimSpace(in.Config.SiteName) == "" { return gerror.New("站点名称不能为空") } u, err := url.Parse(in.Config.PrivacyPolicyUrl) if err != nil || u.Scheme != "https" || u.Host == "" { return gerror.New("隐私政策地址不能为空且必须是有效的 HTTPS 地址") } if in.Config.TradeBalanceAmount <= 0 || in.Config.TradeWaitSeconds <= 0 || in.Config.DailyTradeLimit <= 0 || in.Config.DailyRewardLimit <= 0 || in.Config.MinimumRechargeAmount <= 0 { return gerror.New("交易余额、支付等待时长、每日次数和最低充值金额必须大于0") } if len(in.Config.CommissionRates) != 6 { return gerror.New("必须配置 0 至 5 级共 6 个佣金比例") } total := float64(0) for _, rate := range in.Config.CommissionRates { if rate < 0 || rate > 1 { return gerror.New("佣金比例必须在 0 至 1 之间") } total += rate } if total > 1 { return gerror.New("各级佣金比例总和不能超过 100%") } if in.Config.AutoOrderDelaySeconds <= 0 { return gerror.New("自动派单时间必须大于0") } if in.Config.AutoOrderMinAmount <= 0 || in.Config.AutoOrderMaxAmount < in.Config.AutoOrderMinAmount { return gerror.New("自动派单金额必须大于0,且最大金额不能小于最小金额") } for _, ranges := range [][]string{in.Config.WithdrawTimeRanges, in.Config.RechargeTimeRanges, in.Config.TradeTimeRanges, in.Config.SignTimeRanges} { for _, value := range ranges { parts := strings.Split(value, "-") if len(parts) != 2 || parts[0] >= parts[1] { return gerror.Newf("时间区间 %s 不合法,结束时间必须晚于开始时间", value) } } } return nil } type SiteConfigCurrentInp struct { ConfigGroup string `json:"configGroup" d:"portal"` } func (in *SiteConfigCurrentInp) Filter(ctx context.Context) error { return nil } type SiteConfigCurrentModel struct { Draft *entity.SiteConfigVersion `json:"draft"` Published *entity.SiteConfigVersion `json:"published"` } type SiteConfigSubmitInp struct { Id uint64 `json:"id" v:"required|min:1#配置版本不能为空|配置版本不能为空"` Version uint `json:"version"` Reason string `json:"reason" v:"required|max-length:500#提交原因不能为空|提交原因过长"` } func (in *SiteConfigSubmitInp) Filter(ctx context.Context) error { return nil } type SiteConfigPublishInp struct { Id uint64 `json:"id" v:"required|min:1#配置版本不能为空|配置版本不能为空"` Version uint `json:"version"` } func (in *SiteConfigPublishInp) Filter(ctx context.Context) error { return nil } type SiteConfigRollbackInp struct { Id uint64 `json:"id" v:"required|min:1#历史版本不能为空|历史版本不能为空"` Reason string `json:"reason" v:"required|max-length:500#回滚原因不能为空|回滚原因过长"` } func (in *SiteConfigRollbackInp) Filter(ctx context.Context) error { return nil } type SiteConfigHistoryInp struct { form.PageReq ConfigGroup string `json:"configGroup" d:"portal"` } func (in *SiteConfigHistoryInp) Filter(ctx context.Context) error { return nil } type SiteConfigHistoryModel struct{ entity.SiteConfigVersion } type ContentListInp struct { form.PageReq Title string `json:"title"` ContentType string `json:"contentType"` PublishStatus int `json:"publishStatus"` } func (in *ContentListInp) Filter(ctx context.Context) error { return nil } type ContentListModel struct { entity.Content LatestVersionId uint64 `json:"latestVersionId"` LatestVersionNo uint `json:"latestVersionNo"` Summary string `json:"summary"` CoverUrl string `json:"coverUrl"` } type ContentViewInp struct { Id uint64 `json:"id" v:"required|min:1#内容ID不能为空|内容ID不能为空"` } func (in *ContentViewInp) Filter(ctx context.Context) error { return nil } type ContentViewModel struct { entity.Content LatestVersion *entity.ContentVersion `json:"latestVersion"` Versions []*entity.ContentVersion `json:"versions"` } type ContentEditInp struct { Id uint64 `json:"id"` Version uint `json:"version"` ContentKey string `json:"contentKey" v:"required|max-length:64#内容标识不能为空|内容标识过长"` ContentType string `json:"contentType" v:"required#内容类型不能为空"` Title string `json:"title" v:"required|max-length:200#标题不能为空|标题过长"` Summary string `json:"summary" v:"max-length:500#摘要过长"` BodyHtml string `json:"bodyHtml" v:"required#正文不能为空"` BodyText string `json:"bodyText"` CoverUrl string `json:"coverUrl"` EffectiveAt *gtime.Time `json:"effectiveAt"` Sort int `json:"sort"` Remark string `json:"remark" v:"max-length:500#备注过长"` } func (in *ContentEditInp) Filter(ctx context.Context) error { if !regexp.MustCompile(`^[a-z][a-z0-9_]{1,63}$`).MatchString(in.ContentKey) { return gerror.New("内容标识仅支持小写字母、数字和下划线,且必须以字母开头") } if unsafeHTML(in.BodyHtml) { return gerror.New("正文包含 script、事件属性、javascript 链接或 iframe 等不安全内容") } in.BodyHtml = bluemonday.UGCPolicy().Sanitize(in.BodyHtml) return nil } type ContentDeleteInp struct { Id uint64 `json:"id" v:"required|min:1#内容ID不能为空|内容ID不能为空"` } func (in *ContentDeleteInp) Filter(ctx context.Context) error { return nil } type ContentPublishInp struct { Id uint64 `json:"id" v:"required|min:1#内容ID不能为空|内容ID不能为空"` VersionId uint64 `json:"versionId" v:"required|min:1#内容版本不能为空|内容版本不能为空"` } func (in *ContentPublishInp) Filter(ctx context.Context) error { return nil } type ContentOfflineInp struct { Id uint64 `json:"id" v:"required|min:1#内容ID不能为空|内容ID不能为空"` Reason string `json:"reason" v:"required#下线原因不能为空"` } func (in *ContentOfflineInp) Filter(ctx context.Context) error { return nil } type BannerListInp struct { form.PageReq Name string `json:"name"` Position string `json:"position"` PublishStatus int `json:"publishStatus"` } func (in *BannerListInp) Filter(ctx context.Context) error { return nil } type BannerListModel struct{ entity.Banner } type BannerViewInp struct { Id uint64 `json:"id" v:"required|min:1#Banner ID不能为空|Banner ID不能为空"` } func (in *BannerViewInp) Filter(ctx context.Context) error { return nil } type BannerEditInp struct{ entity.Banner } func (in *BannerEditInp) Filter(ctx context.Context) error { if strings.TrimSpace(in.Name) == "" || strings.TrimSpace(in.ImageUrl) == "" || strings.TrimSpace(in.AltText) == "" { return gerror.New("名称、图片和替代文本不能为空") } if !dict.HasOptionKey(consts.BannerPositionOptions, in.Position) { return gerror.New("Banner 投放位置不合法") } if !dict.HasOptionKey(consts.BannerTargetTypeOptions, in.TargetType) { return gerror.New("跳转类型不合法") } if in.TargetType != consts.BannerTargetNone && strings.TrimSpace(in.TargetValue) == "" { return gerror.New("当前跳转类型必须填写跳转目标") } if in.TargetType == consts.BannerTargetExternal { u, err := url.Parse(in.TargetValue) if err != nil || u.Scheme != "https" || u.Host == "" { return gerror.New("外部跳转必须是有效的 HTTPS 地址") } } if in.StartAt != nil && in.EndAt != nil && !in.EndAt.After(in.StartAt) { return gerror.New("结束时间必须晚于开始时间") } return nil } type BannerDeleteInp struct { Id uint64 `json:"id" v:"required|min:1#Banner ID不能为空|Banner ID不能为空"` } func (in *BannerDeleteInp) Filter(ctx context.Context) error { return nil } type BannerStatusInp struct { Id uint64 `json:"id" v:"required|min:1#Banner ID不能为空|Banner ID不能为空"` PublishStatus int `json:"publishStatus"` Version uint `json:"version"` } func (in *BannerStatusInp) Filter(ctx context.Context) error { if !dict.HasOptionKey(consts.BizPublishStatusOptions, in.PublishStatus) { return gerror.New("发布状态不合法") } return nil } type ManualProductListInp struct { form.PageReq Name string `json:"name"` PublishStatus int `json:"publishStatus"` } func (in *ManualProductListInp) Filter(ctx context.Context) error { return nil } type ManualProductListModel struct { entity.Product Price string `json:"price"` } type ManualProductViewInp struct { Id uint64 `json:"id" v:"required|min:1#商品ID不能为空|商品ID不能为空"` } func (in *ManualProductViewInp) Filter(ctx context.Context) error { return nil } type ManualProductViewModel struct { entity.Product Price string `json:"price"` } type ManualProductEditInp struct { entity.Product Price string `json:"price" v:"required#价格不能为空"` } func (in *ManualProductEditInp) Filter(ctx context.Context) error { if strings.TrimSpace(in.Name) == "" || strings.TrimSpace(in.CoverUrl) == "" { return gerror.New("商品名称和图片不能为空") } return nil } type ManualProductDeleteInp struct { Id uint64 `json:"id"` Ids []uint64 `json:"ids"` } func (in *ManualProductDeleteInp) Filter(ctx context.Context) error { if len(in.Ids) == 0 && in.Id > 0 { in.Ids = []uint64{in.Id} } if len(in.Ids) == 0 { return gerror.New("请选择要删除的商品") } return nil } type ManualProductStatusInp struct { Id uint64 `json:"id" v:"required|min:1#商品ID不能为空|商品ID不能为空"` PublishStatus int `json:"publishStatus"` Version uint `json:"version"` } func (in *ManualProductStatusInp) Filter(ctx context.Context) error { if !dict.HasOptionKey(consts.BizPublishStatusOptions, in.PublishStatus) { return gerror.New("发布状态不合法") } return nil } func unsafeHTML(value string) bool { lower := strings.ToLower(value) patterns := []string{"