feat: add Go backend

This commit is contained in:
richjohnrambo
2026-10-10 20:56:17 +08:00
parent 50fcfd2d3d
commit f6024f0964
1040 changed files with 107169 additions and 0 deletions
+586
View File
@@ -0,0 +1,586 @@
package sys
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
"strings"
"hotgo/internal/consts"
"hotgo/internal/dao"
"hotgo/internal/library/contexts"
"hotgo/internal/library/hgorm/handler"
"hotgo/internal/library/hgorm/hook"
"hotgo/internal/model/entity"
"hotgo/internal/model/input/sysin"
"hotgo/internal/service"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/grand"
)
type sSysPortal struct{}
func NewSysPortal() *sSysPortal { return &sSysPortal{} }
func init() { service.RegisterSysPortal(NewSysPortal()) }
func (s *sSysPortal) siteModel(ctx context.Context) *gdb.Model {
return handler.Model(dao.SiteConfigVersion.Ctx(ctx), &handler.Option{FilterTenant: true})
}
func (s *sSysPortal) contentModel(ctx context.Context) *gdb.Model {
return handler.Model(dao.Content.Ctx(ctx), &handler.Option{FilterTenant: true})
}
func (s *sSysPortal) contentVersionModel(ctx context.Context) *gdb.Model {
return handler.Model(dao.ContentVersion.Ctx(ctx), &handler.Option{FilterTenant: true})
}
func (s *sSysPortal) bannerModel(ctx context.Context) *gdb.Model {
return handler.Model(dao.Banner.Ctx(ctx), &handler.Option{FilterTenant: true})
}
func (s *sSysPortal) productModel(ctx context.Context) *gdb.Model {
return handler.Model(dao.Product.Ctx(ctx), &handler.Option{FilterTenant: true})
}
func (s *sSysPortal) SiteConfigCurrent(ctx context.Context, in *sysin.SiteConfigCurrentInp) (res *sysin.SiteConfigCurrentModel, err error) {
res = new(sysin.SiteConfigCurrentModel)
c := dao.SiteConfigVersion.Columns()
if err = s.siteModel(ctx).Where(c.ConfigGroup, in.ConfigGroup).WhereIn(c.PublishStatus, []int{consts.BizPublishDraft, consts.BizPublishPending}).OrderDesc(c.Id).Limit(1).Scan(&res.Draft); err != nil {
return nil, gerror.Wrap(err, "获取站点配置草稿失败")
}
if err = s.siteModel(ctx).Where(c.ConfigGroup, in.ConfigGroup).Where(c.PublishStatus, consts.BizPublishPublished).OrderDesc(c.Id).Limit(1).Scan(&res.Published); err != nil {
return nil, gerror.Wrap(err, "获取已发布站点配置失败")
}
return res, nil
}
func (s *sSysPortal) SiteConfigHistory(ctx context.Context, in *sysin.SiteConfigHistoryInp) (list []*sysin.SiteConfigHistoryModel, totalCount int, err error) {
c := dao.SiteConfigVersion.Columns()
err = s.siteModel(ctx).Fields(sysin.SiteConfigHistoryModel{}).Where(c.ConfigGroup, in.ConfigGroup).Page(in.Page, in.PerPage).OrderDesc(c.Id).ScanAndCount(&list, &totalCount, false)
if err != nil {
err = gerror.Wrap(err, "获取配置版本记录失败")
}
return
}
func (s *sSysPortal) SiteConfigSave(ctx context.Context, in *sysin.SiteConfigSaveInp) (id uint64, err error) {
raw, err := json.Marshal(in.Config)
if err != nil {
return 0, gerror.Wrap(err, "序列化站点配置失败")
}
sum := sha256.Sum256(raw)
digest := hex.EncodeToString(sum[:])
userId := uint64(contexts.GetUserId(ctx))
c := dao.SiteConfigVersion.Columns()
now := gtime.Now()
if in.Id > 0 {
_, _ = s.siteModel(ctx).
Where(c.ConfigGroup, in.ConfigGroup).
Where(c.PublishStatus, consts.BizPublishPublished).
WhereNot(c.Id, in.Id).
Data(gdb.Map{c.PublishStatus: consts.BizPublishOffline, c.UpdatedBy: userId}).
Update()
result, updateErr := s.siteModel(ctx).
WherePri(in.Id).
Where(c.Version, in.Version).
Data(gdb.Map{
c.ConfigJson: gjson.New(raw),
c.ConfigDigest: digest,
c.PublishStatus: consts.BizPublishPublished,
c.EffectiveAt: in.EffectiveAt,
c.PublishedAt: now,
c.ApprovalRequestId: 0,
c.Remark: in.Remark,
c.UpdatedBy: userId,
c.Version: gdb.Raw(c.Version + "+1"),
}).Update()
if updateErr != nil {
return 0, gerror.Wrap(updateErr, "保存站点配置失败")
}
rows, _ := result.RowsAffected()
if rows == 0 {
return 0, gerror.New("配置已更新,请刷新后重试")
}
return in.Id, nil
}
_, _ = s.siteModel(ctx).
Where(c.ConfigGroup, in.ConfigGroup).
Where(c.PublishStatus, consts.BizPublishPublished).
Data(gdb.Map{c.PublishStatus: consts.BizPublishOffline, c.UpdatedBy: userId}).
Update()
configNo := "CFG" + gtime.Now().Format("YmdHis") + grand.S(5)
result, err := s.siteModel(ctx).
Fields(c.ConfigNo, c.ConfigGroup, c.SchemaVersion, c.ConfigJson, c.ConfigDigest, c.PublishStatus, c.EffectiveAt, c.PublishedAt, c.BaseVersionId, c.Version, c.Remark, c.CreatedBy, c.UpdatedBy).
Hook(hook.SaveTenant).
Data(gdb.Map{
c.ConfigNo: configNo,
c.ConfigGroup: in.ConfigGroup,
c.SchemaVersion: "1.0",
c.ConfigJson: gjson.New(raw),
c.ConfigDigest: digest,
c.PublishStatus: consts.BizPublishPublished,
c.EffectiveAt: in.EffectiveAt,
c.PublishedAt: now,
c.CreatedBy: userId,
c.UpdatedBy: userId,
}).Insert()
if err != nil {
return 0, gerror.Wrap(err, "保存站点配置失败")
}
last, _ := result.LastInsertId()
return uint64(last), nil
}
func (s *sSysPortal) SiteConfigSubmit(ctx context.Context, in *sysin.SiteConfigSubmitInp) (res *sysin.PortalApprovalModel, err error) {
var row *entity.SiteConfigVersion
c := dao.SiteConfigVersion.Columns()
if err = s.siteModel(ctx).WherePri(in.Id).Scan(&row); err != nil {
return nil, gerror.Wrap(err, "读取配置草稿失败")
}
if row == nil || row.Id == 0 {
return nil, gerror.New("配置草稿不存在")
}
if row.PublishStatus != consts.BizPublishDraft {
return nil, gerror.New("仅草稿可以提交审批")
}
if row.Version != in.Version {
return nil, gerror.New("配置已更新,请刷新后重试")
}
payload := gjson.New(row)
approval, err := service.SysApproval().Create(ctx, &sysin.ApprovalCreateInp{ApprovalType: "site_config_publish", TargetType: "site_config_version", TargetId: row.Id, Action: "publish", Title: "发布站点配置 " + row.ConfigNo, PayloadJson: payload, RiskLevel: consts.ApprovalRiskHigh, RequiredApprovals: 1, Remark: in.Reason})
if err != nil {
return nil, err
}
if err = service.SysApproval().Submit(ctx, &sysin.ApprovalSubmitInp{Id: approval.Id, Version: 0}); err != nil {
return nil, err
}
result, err := s.siteModel(ctx).WherePri(row.Id).Where(c.Version, row.Version).Data(gdb.Map{c.PublishStatus: consts.BizPublishPending, c.ApprovalRequestId: approval.Id, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
if err != nil {
return nil, gerror.Wrap(err, "更新配置审批状态失败")
}
rows, _ := result.RowsAffected()
if rows == 0 {
return nil, gerror.New("配置已更新,请刷新后重试")
}
return &sysin.PortalApprovalModel{ApprovalId: approval.Id, ApprovalNo: approval.ApprovalNo}, nil
}
func (s *sSysPortal) SiteConfigPublish(ctx context.Context, in *sysin.SiteConfigPublishInp) error {
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
var row *entity.SiteConfigVersion
c := dao.SiteConfigVersion.Columns()
if err := s.siteModel(ctx).WherePri(in.Id).LockUpdate().Scan(&row); err != nil {
return gerror.Wrap(err, "读取配置版本失败")
}
if row == nil {
return gerror.New("配置版本不存在")
}
if row.Version != in.Version {
return gerror.New("配置已更新,请刷新后重试")
}
if row.PublishStatus != consts.BizPublishPending {
return gerror.New("仅待审配置可以发布")
}
var approval *entity.ApprovalRequest
if err := service.SysApproval().Model(ctx).WherePri(row.ApprovalRequestId).Scan(&approval); err != nil {
return err
}
if approval == nil || approval.ApprovalStatus != consts.ApprovalStatusApproved {
return gerror.New("审批尚未通过,不能发布")
}
now := gtime.Now()
if row.EffectiveAt != nil && row.EffectiveAt.After(now) {
return gerror.New("计划生效时间尚未到达")
}
_, _ = s.siteModel(ctx).Where(c.ConfigGroup, row.ConfigGroup).Where(c.PublishStatus, consts.BizPublishPublished).Data(gdb.Map{c.PublishStatus: consts.BizPublishOffline, c.UpdatedBy: contexts.GetUserId(ctx)}).Update()
_, err := s.siteModel(ctx).WherePri(row.Id).Where(c.Version, row.Version).Data(gdb.Map{c.PublishStatus: consts.BizPublishPublished, c.PublishedAt: now, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
if err != nil {
return gerror.Wrap(err, "发布站点配置失败")
}
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "portal", Action: "publish", TargetType: "site_config_version", TargetId: row.Id, After: row, ApprovalNo: approval.ApprovalNo, Reason: "审批通过后发布"})
})
}
func (s *sSysPortal) SiteConfigRollback(ctx context.Context, in *sysin.SiteConfigRollbackInp) (id uint64, err error) {
var source *entity.SiteConfigVersion
if err = s.siteModel(ctx).WherePri(in.Id).Scan(&source); err != nil {
return 0, gerror.Wrap(err, "读取历史配置失败")
}
if source == nil {
return 0, gerror.New("历史配置不存在")
}
save := &sysin.SiteConfigSaveInp{ConfigGroup: source.ConfigGroup, Remark: "回滚自 " + source.ConfigNo + ":" + in.Reason}
if err = source.ConfigJson.Scan(&save.Config); err != nil {
return 0, gerror.Wrap(err, "解析历史配置失败")
}
return s.SiteConfigSave(ctx, save)
}
func (s *sSysPortal) ContentList(ctx context.Context, in *sysin.ContentListInp) (list []*sysin.ContentListModel, totalCount int, err error) {
c := dao.Content.Columns()
mod := s.contentModel(ctx).Fields(sysin.ContentListModel{})
if in.Title != "" {
mod = mod.WhereLike(c.Title, "%"+in.Title+"%")
}
if in.ContentType != "" {
mod = mod.Where(c.ContentType, in.ContentType)
}
if err = mod.Page(in.Page, in.PerPage).OrderDesc(c.Id).ScanAndCount(&list, &totalCount, false); err != nil {
return nil, 0, gerror.Wrap(err, "获取文本内容列表失败")
}
vc := dao.ContentVersion.Columns()
for _, item := range list {
var v *entity.ContentVersion
_ = s.contentVersionModel(ctx).Where(vc.ContentId, item.Id).OrderDesc(vc.VersionNo).Limit(1).Scan(&v)
if v != nil {
item.LatestVersionId = v.Id
item.LatestVersionNo = v.VersionNo
item.Summary = v.Summary
item.CoverUrl = v.CoverUrl
}
}
return
}
func (s *sSysPortal) ContentView(ctx context.Context, in *sysin.ContentViewInp) (res *sysin.ContentViewModel, err error) {
res = new(sysin.ContentViewModel)
if err = s.contentModel(ctx).WherePri(in.Id).Scan(&res.Content); err != nil {
return nil, gerror.Wrap(err, "获取文本内容失败")
}
if res.Id == 0 {
return nil, gerror.New("文本内容不存在")
}
vc := dao.ContentVersion.Columns()
if err = s.contentVersionModel(ctx).Where(vc.ContentId, in.Id).OrderDesc(vc.VersionNo).Scan(&res.Versions); err != nil {
return nil, gerror.Wrap(err, "获取内容版本失败")
}
if len(res.Versions) > 0 {
res.LatestVersion = res.Versions[0]
}
return res, nil
}
func (s *sSysPortal) ContentEdit(ctx context.Context, in *sysin.ContentEditInp) (id uint64, err error) {
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
userId := uint64(contexts.GetUserId(ctx))
c := dao.Content.Columns()
if in.Id == 0 {
result, e := s.contentModel(ctx).Fields(c.ContentKey, c.ContentType, c.Title, c.PublishStatus, c.Sort, c.Version, c.Remark, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(gdb.Map{c.ContentKey: in.ContentKey, c.ContentType: in.ContentType, c.Title: in.Title, c.PublishStatus: consts.BizPublishDraft, c.Sort: in.Sort, c.Remark: in.Remark, c.CreatedBy: userId, c.UpdatedBy: userId}).Insert()
if e != nil {
return gerror.Wrap(e, "创建文本内容失败")
}
last, _ := result.LastInsertId()
id = uint64(last)
} else {
id = in.Id
result, e := s.contentModel(ctx).WherePri(in.Id).Where(c.Version, in.Version).Data(gdb.Map{c.ContentType: in.ContentType, c.Title: in.Title, c.Sort: in.Sort, c.Remark: in.Remark, c.UpdatedBy: userId, c.Version: gdb.Raw(c.Version + "+1")}).Update()
if e != nil {
return gerror.Wrap(e, "更新文本内容失败")
}
rows, _ := result.RowsAffected()
if rows == 0 {
return gerror.New("内容已更新,请刷新后重试")
}
}
vc := dao.ContentVersion.Columns()
maxNo, e := s.contentVersionModel(ctx).Where(vc.ContentId, id).Max(vc.VersionNo)
if e != nil {
return gerror.Wrap(e, "读取内容版本号失败")
}
_, e = s.contentVersionModel(ctx).Fields(vc.ContentId, vc.VersionNo, vc.Title, vc.Summary, vc.BodyHtml, vc.BodyText, vc.CoverUrl, vc.PublishStatus, vc.EffectiveAt, vc.CreatedBy).Hook(hook.SaveTenant).Data(gdb.Map{vc.ContentId: id, vc.VersionNo: uint(maxNo) + 1, vc.Title: in.Title, vc.Summary: in.Summary, vc.BodyHtml: in.BodyHtml, vc.BodyText: in.BodyText, vc.CoverUrl: in.CoverUrl, vc.PublishStatus: consts.BizPublishDraft, vc.EffectiveAt: in.EffectiveAt, vc.CreatedBy: userId}).Insert()
if e != nil {
return gerror.Wrap(e, "创建内容版本失败")
}
return nil
})
return
}
func (s *sSysPortal) ContentDelete(ctx context.Context, in *sysin.ContentDeleteInp) error {
var row *entity.Content
if err := s.contentModel(ctx).WherePri(in.Id).Scan(&row); err != nil {
return err
}
if row == nil {
return gerror.New("文本内容不存在")
}
c := dao.Content.Columns()
if row.PublishStatus == consts.BizPublishPublished {
_, err := s.contentModel(ctx).WherePri(in.Id).Data(gdb.Map{c.PublishStatus: consts.BizPublishOffline, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
if err != nil {
return gerror.Wrap(err, "下线文本内容失败")
}
return nil
}
_, err := s.contentModel(ctx).WherePri(in.Id).Data(gdb.Map{c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx)}).Unscoped().Update()
if err != nil {
return gerror.Wrap(err, "删除文本内容失败")
}
return nil
}
func (s *sSysPortal) ContentPublish(ctx context.Context, in *sysin.ContentPublishInp) error {
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
var content *entity.Content
var version *entity.ContentVersion
if err := s.contentModel(ctx).WherePri(in.Id).LockUpdate().Scan(&content); err != nil {
return err
}
if content == nil {
return gerror.New("文本内容不存在")
}
vc := dao.ContentVersion.Columns()
if err := s.contentVersionModel(ctx).WherePri(in.VersionId).Where(vc.ContentId, in.Id).Scan(&version); err != nil {
return err
}
if version == nil {
return gerror.New("内容版本不存在")
}
if version.EffectiveAt != nil && version.EffectiveAt.After(gtime.Now()) {
return gerror.New("计划发布时间尚未到达")
}
_, _ = s.contentVersionModel(ctx).Where(vc.ContentId, in.Id).Where(vc.PublishStatus, consts.BizPublishPublished).Data(gdb.Map{vc.PublishStatus: consts.BizPublishOffline}).Update()
_, err := s.contentVersionModel(ctx).WherePri(version.Id).Data(gdb.Map{vc.PublishStatus: consts.BizPublishPublished}).Update()
if err != nil {
return gerror.Wrap(err, "发布内容版本失败")
}
c := dao.Content.Columns()
_, err = s.contentModel(ctx).WherePri(in.Id).Data(gdb.Map{c.PublishedVersionId: version.Id, c.PublishStatus: consts.BizPublishPublished, c.PublishedAt: gtime.Now(), c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
return gerror.Wrap(err, "发布文本内容失败")
})
}
func (s *sSysPortal) ContentOffline(ctx context.Context, in *sysin.ContentOfflineInp) error {
c := dao.Content.Columns()
_, err := s.contentModel(ctx).WherePri(in.Id).Data(gdb.Map{c.PublishStatus: consts.BizPublishOffline, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
if err != nil {
return gerror.Wrap(err, "下线文本内容失败")
}
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "portal", Action: "offline", TargetType: "content", TargetId: in.Id, Reason: in.Reason})
}
func (s *sSysPortal) BannerList(ctx context.Context, in *sysin.BannerListInp) (list []*sysin.BannerListModel, totalCount int, err error) {
c := dao.Banner.Columns()
mod := s.bannerModel(ctx).Fields(sysin.BannerListModel{})
if in.Name != "" {
mod = mod.WhereLike(c.Name, "%"+in.Name+"%")
}
if in.Position != "" {
mod = mod.Where(c.Position, in.Position)
}
if in.PublishStatus > 0 {
mod = mod.Where(c.PublishStatus, in.PublishStatus)
}
err = mod.Page(in.Page, in.PerPage).OrderAsc(c.Sort).OrderDesc(c.Id).ScanAndCount(&list, &totalCount, false)
if err != nil {
err = gerror.Wrap(err, "获取Banner列表失败")
}
return
}
func (s *sSysPortal) BannerView(ctx context.Context, in *sysin.BannerViewInp) (res *entity.Banner, err error) {
err = s.bannerModel(ctx).WherePri(in.Id).Scan(&res)
if err != nil {
return nil, gerror.Wrap(err, "获取Banner失败")
}
if res == nil {
return nil, gerror.New("Banner不存在")
}
return
}
func (s *sSysPortal) BannerEdit(ctx context.Context, in *sysin.BannerEditInp) (id uint64, err error) {
c := dao.Banner.Columns()
userId := uint64(contexts.GetUserId(ctx))
if in.Id > 0 {
result, e := s.bannerModel(ctx).WherePri(in.Id).Where(c.Version, in.Version).Data(gdb.Map{
c.Name: in.Name, c.Position: in.Position, c.ImageAttachmentId: in.ImageAttachmentId,
c.ImageUrl: in.ImageUrl, c.AltText: in.AltText, c.TargetType: in.TargetType,
c.TargetValue: in.TargetValue, c.StartAt: in.StartAt, c.EndAt: in.EndAt,
c.Sort: in.Sort, c.Remark: in.Remark, c.UpdatedBy: userId, c.Version: gdb.Raw(c.Version + "+1"),
}).Update()
if e != nil {
return 0, gerror.Wrap(e, "更新Banner失败")
}
rows, _ := result.RowsAffected()
if rows == 0 {
return 0, gerror.New("Banner已更新,请刷新后重试")
}
return in.Id, nil
}
in.PublishStatus = consts.BizPublishDraft
in.CreatedBy = userId
in.UpdatedBy = userId
result, e := s.bannerModel(ctx).Fields(c.Name, c.Position, c.ImageAttachmentId, c.ImageUrl, c.AltText, c.TargetType, c.TargetValue, c.StartAt, c.EndAt, c.PublishStatus, c.Sort, c.Version, c.Remark, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(in).Insert()
if e != nil {
return 0, gerror.Wrap(e, "创建Banner失败")
}
last, _ := result.LastInsertId()
return uint64(last), nil
}
func (s *sSysPortal) BannerDelete(ctx context.Context, in *sysin.BannerDeleteInp) error {
var row *entity.Banner
if err := s.bannerModel(ctx).WherePri(in.Id).Scan(&row); err != nil {
return err
}
if row == nil {
return gerror.New("Banner不存在")
}
c := dao.Banner.Columns()
if row.PublishStatus == consts.BizPublishPublished {
_, err := s.bannerModel(ctx).WherePri(in.Id).Data(gdb.Map{c.PublishStatus: consts.BizPublishOffline, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
return err
}
_, err := s.bannerModel(ctx).WherePri(in.Id).Data(gdb.Map{c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx)}).Unscoped().Update()
return gerror.Wrap(err, "删除Banner失败")
}
func (s *sSysPortal) BannerStatus(ctx context.Context, in *sysin.BannerStatusInp) error {
c := dao.Banner.Columns()
result, err := s.bannerModel(ctx).WherePri(in.Id).Where(c.Version, in.Version).Data(gdb.Map{c.PublishStatus: in.PublishStatus, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
if err != nil {
return gerror.Wrap(err, "更新Banner状态失败")
}
rows, _ := result.RowsAffected()
if rows == 0 {
return gerror.New("Banner已更新,请刷新后重试")
}
return nil
}
func (s *sSysPortal) ManualProductList(ctx context.Context, in *sysin.ManualProductListInp) (list []*sysin.ManualProductListModel, totalCount int, err error) {
c := dao.Product.Columns()
mod := s.productModel(ctx).Fields(sysin.ManualProductListModel{}).Where(c.ProductType, consts.ProductTypeManual)
if in.Name != "" {
mod = mod.WhereLike(c.Name, "%"+in.Name+"%")
}
if in.PublishStatus > 0 {
mod = mod.Where(c.PublishStatus, in.PublishStatus)
}
if err = mod.Page(in.Page, in.PerPage).OrderAsc(c.Sort).OrderDesc(c.Id).ScanAndCount(&list, &totalCount, false); err != nil {
return nil, 0, gerror.Wrap(err, "获取手工商城列表失败")
}
for _, item := range list {
item.Price = formatCents(item.PriceAmount)
}
return
}
func (s *sSysPortal) ManualProductView(ctx context.Context, in *sysin.ManualProductViewInp) (res *sysin.ManualProductViewModel, err error) {
c := dao.Product.Columns()
if err = s.productModel(ctx).WherePri(in.Id).Where(c.ProductType, consts.ProductTypeManual).Scan(&res); err != nil {
return nil, gerror.Wrap(err, "获取手工商城商品失败")
}
if res == nil {
return nil, gerror.New("商品不存在")
}
res.Price = formatCents(res.PriceAmount)
return
}
func (s *sSysPortal) ManualProductEdit(ctx context.Context, in *sysin.ManualProductEditInp) (id uint64, err error) {
amount, err := parseCents(in.Price)
if err != nil {
return 0, err
}
in.PriceAmount = amount
in.ProductType = consts.ProductTypeManual
if in.PaymentType == "" {
in.PaymentType = "balance"
}
if in.Currency == "" {
in.Currency = "CNY"
}
c := dao.Product.Columns()
userId := uint64(contexts.GetUserId(ctx))
if in.Id > 0 {
result, e := s.productModel(ctx).WherePri(in.Id).Where(c.Version, in.Version).Data(gdb.Map{
c.Name: in.Name,
c.PriceAmount: in.PriceAmount,
c.CoverAttachmentId: in.CoverAttachmentId,
c.CoverUrl: in.CoverUrl,
c.PublishStatus: consts.BizPublishPublished,
c.UpdatedBy: userId,
c.Version: gdb.Raw(c.Version + "+1"),
}).Update()
if e != nil {
return 0, gerror.Wrap(e, "更新手工商城商品失败")
}
rows, _ := result.RowsAffected()
if rows == 0 {
return 0, gerror.New("商品已更新,请刷新后重试")
}
return in.Id, nil
}
in.ProductNo = "MP" + gtime.Now().Format("YmdHis") + grand.S(5)
in.PublishStatus = consts.BizPublishPublished
in.CreatedBy = userId
in.UpdatedBy = userId
result, e := s.productModel(ctx).Fields(c.ProductNo, c.CategoryId, c.ProductType, c.PaymentType, c.Name, c.ShopName, c.PriceAmount, c.Currency, c.CoverAttachmentId, c.CoverUrl, c.DetailHtml, c.IsRecommended, c.PublishStatus, c.StartAt, c.EndAt, c.Sort, c.Version, c.Remark, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(in).Insert()
if e != nil {
return 0, gerror.Wrap(e, "创建手工商城商品失败")
}
last, _ := result.LastInsertId()
return uint64(last), nil
}
func (s *sSysPortal) ManualProductDelete(ctx context.Context, in *sysin.ManualProductDeleteInp) error {
c := dao.Product.Columns()
_, err := s.productModel(ctx).
WhereIn(c.Id, in.Ids).
Where(c.ProductType, consts.ProductTypeManual).
Data(gdb.Map{c.DeletedAt: gtime.Now(), c.DeletedBy: contexts.GetUserId(ctx)}).
Unscoped().
Update()
return gerror.Wrap(err, "删除商品失败")
}
func (s *sSysPortal) ManualProductStatus(ctx context.Context, in *sysin.ManualProductStatusInp) error {
c := dao.Product.Columns()
result, err := s.productModel(ctx).WherePri(in.Id).Where(c.Version, in.Version).Where(c.ProductType, consts.ProductTypeManual).Data(gdb.Map{c.PublishStatus: in.PublishStatus, c.UpdatedBy: contexts.GetUserId(ctx), c.Version: gdb.Raw(c.Version + "+1")}).Update()
if err != nil {
return gerror.Wrap(err, "更新商品状态失败")
}
rows, _ := result.RowsAffected()
if rows == 0 {
return gerror.New("商品已更新,请刷新后重试")
}
return nil
}
func parseCents(value string) (int64, error) {
value = strings.TrimSpace(value)
if value == "" {
return 0, gerror.New("价格不能为空")
}
if strings.HasPrefix(value, "-") {
return 0, gerror.New("价格不能为负数")
}
parts := strings.Split(value, ".")
if len(parts) > 2 {
return 0, gerror.New("价格格式不正确")
}
yuan, err := strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return 0, gerror.New("价格格式不正确")
}
fraction := ""
if len(parts) == 2 {
fraction = parts[1]
}
if len(fraction) > 2 {
return 0, gerror.New("价格最多保留两位小数")
}
for len(fraction) < 2 {
fraction += "0"
}
fen := int64(0)
if fraction != "" {
fen, err = strconv.ParseInt(fraction, 10, 64)
if err != nil {
return 0, gerror.New("价格格式不正确")
}
}
if yuan > (1<<63-1-fen)/100 {
return 0, gerror.New("价格超出允许范围")
}
return yuan*100 + fen, nil
}
func formatCents(value int64) string { return fmt.Sprintf("%d.%02d", value/100, value%100) }