Files
olons_admin/server/internal/model/input/sysin/portal.go
T
2026-10-10 20:56:17 +08:00

354 lines
12 KiB
Go

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("站点名称不能为空")
}
if in.Config.PrivacyPolicyUrl != "" {
u, err := url.Parse(in.Config.PrivacyPolicyUrl)
if err != nil || u.Scheme != "https" || u.Host == "" {
return gerror.New("隐私政策地址必须是有效的 HTTPS 地址")
}
}
if in.Config.TradeWaitSeconds < 0 || in.Config.DailyTradeLimit < 0 || in.Config.DailyRewardLimit < 0 {
return gerror.New("交易等待时间和每日次数不能为负数")
}
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("自动派单时间不能为负数")
}
if in.Config.AutoOrderMinAmount < 0 || in.Config.AutoOrderMaxAmount < in.Config.AutoOrderMinAmount {
return gerror.New("自动派单最大金额不能小于最小金额")
}
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{"<script", "javascript:", "<iframe", " onerror=", " onclick=", " onload="}
for _, pattern := range patterns {
if strings.Contains(lower, pattern) {
return true
}
}
return false
}
type PortalApprovalModel struct {
ApprovalId uint64 `json:"approvalId"`
ApprovalNo string `json:"approvalNo"`
}
type JsonPayload struct {
Value *gjson.Json `json:"value"`
}