354 lines
12 KiB
Go
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"`
|
|
}
|