260 lines
11 KiB
Go
260 lines
11 KiB
Go
package sys
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"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/net/ghttp"
|
|
"github.com/gogf/gf/v2/os/gctx"
|
|
|
|
"hotgo/internal/dao"
|
|
"hotgo/internal/library/contexts"
|
|
"hotgo/internal/library/imadmin"
|
|
"hotgo/internal/library/imtenant"
|
|
"hotgo/internal/library/location"
|
|
"hotgo/internal/model/entity"
|
|
"hotgo/internal/model/input/sysin"
|
|
"hotgo/internal/service"
|
|
)
|
|
|
|
type sSysCultivateFlower struct{}
|
|
|
|
func NewSysCultivateFlower() *sSysCultivateFlower { return &sSysCultivateFlower{} }
|
|
func init() { service.RegisterSysCultivateFlower(NewSysCultivateFlower()) }
|
|
|
|
func cultivateTenantID(ctx context.Context) int64 {
|
|
id := contexts.GetTenantId(ctx)
|
|
if id <= 0 {
|
|
id = g.Cfg().MustGet(ctx, "cultivate.defaultTenantId", 1).Int64()
|
|
}
|
|
return id
|
|
}
|
|
|
|
func (s *sSysCultivateFlower) List(ctx context.Context, in *sysin.CultivateFlowerListInp) (list []*sysin.CultivateFlowerListModel, totalCount int, err error) {
|
|
c := dao.FlowerCatalog.Columns()
|
|
mod := dao.FlowerCatalog.Ctx(ctx).Fields("cv_flower_catalog.*, COUNT(cv_flower_stage_rule.id) AS stage_count").LeftJoin(dao.FlowerStageRule.Table(), "cv_flower_stage_rule.flower_catalog_id=cv_flower_catalog.id AND cv_flower_stage_rule.tenant_id=cv_flower_catalog.tenant_id").Where("cv_flower_catalog."+c.TenantId, cultivateTenantID(ctx)).Group("cv_flower_catalog." + c.Id)
|
|
if in.FlowerCode != "" {
|
|
mod = mod.WhereLike("cv_flower_catalog."+c.FlowerCode, "%"+strings.TrimSpace(in.FlowerCode)+"%")
|
|
}
|
|
if in.FlowerName != "" {
|
|
mod = mod.WhereLike("cv_flower_catalog."+c.FlowerName, "%"+strings.TrimSpace(in.FlowerName)+"%")
|
|
}
|
|
if in.Status != "" {
|
|
mod = mod.Where("cv_flower_catalog."+c.Status, in.Status)
|
|
}
|
|
if in.ConfigVersion != "" {
|
|
mod = mod.Where("cv_flower_catalog."+c.ConfigVersion, in.ConfigVersion)
|
|
}
|
|
if len(in.CreatedAt) == 2 {
|
|
mod = mod.WhereBetween("cv_flower_catalog."+c.CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
|
}
|
|
mod = mod.Page(in.Page, in.PerPage).OrderAsc("cv_flower_catalog." + c.SortNo).OrderDesc("cv_flower_catalog." + c.Id)
|
|
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
|
err = gerror.Wrap(err, "获取花种列表失败")
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysCultivateFlower) View(ctx context.Context, in *sysin.CultivateFlowerViewInp) (res *sysin.CultivateFlowerViewModel, err error) {
|
|
c := dao.FlowerCatalog.Columns()
|
|
var catalog entity.FlowerCatalog
|
|
if err = dao.FlowerCatalog.Ctx(ctx).Where(g.Map{c.Id: in.Id, c.TenantId: cultivateTenantID(ctx)}).Scan(&catalog); err != nil {
|
|
return
|
|
}
|
|
if catalog.Id == 0 {
|
|
return nil, gerror.New("花种不存在")
|
|
}
|
|
res = &sysin.CultivateFlowerViewModel{CultivateFlowerEditInp: sysin.CultivateFlowerEditInp{Id: catalog.Id, FlowerCode: catalog.FlowerCode, FlowerName: catalog.FlowerName, FlowerDesc: catalog.FlowerDesc, IconFileId: catalog.IconFileId, Status: catalog.Status, DailyWaterLimit: catalog.DailyWaterLimit, DailyFertilizeLimit: catalog.DailyFertilizeLimit, ShareConfig: catalog.ShareConfig, SortNo: catalog.SortNo}, ConfigVersion: catalog.ConfigVersion, CreatedAt: catalog.CreatedAt, UpdatedAt: catalog.UpdatedAt}
|
|
rc := dao.FlowerStageRule.Columns()
|
|
var rules []*entity.FlowerStageRule
|
|
if err = dao.FlowerStageRule.Ctx(ctx).Where(g.Map{rc.TenantId: cultivateTenantID(ctx), rc.FlowerCatalogId: catalog.Id}).OrderAsc(rc.SortNo).OrderAsc(rc.Id).Scan(&rules); err != nil {
|
|
return
|
|
}
|
|
for _, rule := range rules {
|
|
actions := []string{}
|
|
if rule.AllowedActions != nil {
|
|
_ = json.Unmarshal(rule.AllowedActions.MustToJson(), &actions)
|
|
}
|
|
res.StageRules = append(res.StageRules, &sysin.CultivateStageRuleEditInp{Id: rule.Id, Stage: rule.Stage, NextStage: rule.NextStage, RequiredPoints: rule.RequiredPoints, WaterPoints: rule.WaterPoints, FertilizePoints: rule.FertilizePoints, AllowedActions: actions, ImageFileId: rule.ImageFileId, AnimationFileId: rule.AnimationFileId, ProgressTextTemplate: rule.ProgressTextTemplate, CompletionText: rule.CompletionText, SortNo: rule.SortNo})
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *sSysCultivateFlower) Edit(ctx context.Context, in *sysin.CultivateFlowerEditInp) (err error) {
|
|
if err = in.Filter(ctx); err != nil {
|
|
return err
|
|
}
|
|
tenantID := cultivateTenantID(ctx)
|
|
if err = validateCultivateAssets(ctx, tenantID, in); err != nil {
|
|
return err
|
|
}
|
|
operatorID := contexts.GetUserId(ctx)
|
|
now := time.Now().UTC()
|
|
version := "cat_" + now.Format("20060102_150405")
|
|
return dao.FlowerCatalog.Transaction(ctx, func(txCtx context.Context, tx gdb.TX) error {
|
|
c := dao.FlowerCatalog.Columns()
|
|
var before entity.FlowerCatalog
|
|
if in.Id > 0 {
|
|
if e := tx.Model(dao.FlowerCatalog.Table()).Where(g.Map{c.Id: in.Id, c.TenantId: tenantID}).LockUpdate().Scan(&before); e != nil {
|
|
return e
|
|
}
|
|
if before.Id == 0 {
|
|
return gerror.New("花种不存在")
|
|
}
|
|
if before.FlowerCode != in.FlowerCode {
|
|
return gerror.New("花种编码创建后不可修改")
|
|
}
|
|
}
|
|
shareJSON := "{}"
|
|
if in.ShareConfig != nil {
|
|
shareJSON = in.ShareConfig.MustToJsonString()
|
|
}
|
|
data := g.Map{c.FlowerName: in.FlowerName, c.FlowerDesc: in.FlowerDesc, c.IconFileId: in.IconFileId, c.Status: in.Status, c.ConfigVersion: version, c.DailyWaterLimit: in.DailyWaterLimit, c.DailyFertilizeLimit: in.DailyFertilizeLimit, c.ShareConfig: shareJSON, c.SortNo: in.SortNo, c.UpdatedAt: now}
|
|
if in.Status == "online" {
|
|
data[c.OnlineAt] = now
|
|
data[c.OfflineAt] = nil
|
|
} else if in.Status == "offline" {
|
|
data[c.OfflineAt] = now
|
|
}
|
|
if in.Id == 0 {
|
|
id, e := imtenant.NextSnowflakeID(txCtx)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
in.Id = uint64(id)
|
|
data[c.Id] = id
|
|
data[c.TenantId] = tenantID
|
|
data[c.FlowerCode] = in.FlowerCode
|
|
data[c.CreatedAt] = now
|
|
if _, e = tx.Model(dao.FlowerCatalog.Table()).Data(data).Insert(); e != nil {
|
|
return gerror.Wrap(e, "新增花种失败")
|
|
}
|
|
} else {
|
|
if _, e := tx.Model(dao.FlowerCatalog.Table()).Where(g.Map{c.Id: in.Id, c.TenantId: tenantID}).Data(data).Update(); e != nil {
|
|
return gerror.Wrap(e, "修改花种失败")
|
|
}
|
|
}
|
|
rc := dao.FlowerStageRule.Columns()
|
|
if _, e := tx.Model(dao.FlowerStageRule.Table()).Where(g.Map{rc.TenantId: tenantID, rc.FlowerCatalogId: in.Id}).Delete(); e != nil {
|
|
return e
|
|
}
|
|
for _, rule := range in.StageRules {
|
|
id, e := imtenant.NextSnowflakeID(txCtx)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
actions, _ := json.Marshal(rule.AllowedActions)
|
|
_, e = tx.Model(dao.FlowerStageRule.Table()).Data(g.Map{rc.Id: id, rc.TenantId: tenantID, rc.FlowerCatalogId: in.Id, rc.Stage: rule.Stage, rc.NextStage: rule.NextStage, rc.RequiredPoints: rule.RequiredPoints, rc.WaterPoints: rule.WaterPoints, rc.FertilizePoints: rule.FertilizePoints, rc.AllowedActions: string(actions), rc.ImageFileId: rule.ImageFileId, rc.AnimationFileId: rule.AnimationFileId, rc.ProgressTextTemplate: rule.ProgressTextTemplate, rc.CompletionText: rule.CompletionText, rc.SortNo: rule.SortNo, rc.CreatedAt: now, rc.UpdatedAt: now}).Insert()
|
|
if e != nil {
|
|
return gerror.Wrap(e, "保存成长阶段失败")
|
|
}
|
|
}
|
|
return writeCultivateOperation(txCtx, tx, tenantID, operatorID, map[bool]string{true: "flower.edit", false: "flower.create"}[before.Id > 0], "flower", fmt.Sprint(in.Id), before, in, "", now)
|
|
})
|
|
}
|
|
|
|
func validateCultivateAssets(ctx context.Context, tenantID int64, in *sysin.CultivateFlowerEditInp) error {
|
|
seen := make(map[string]struct{})
|
|
fileIDs := make([]string, 0, 1+len(in.StageRules)*2)
|
|
add := func(fileID string) {
|
|
fileID = strings.TrimSpace(fileID)
|
|
if fileID == "" {
|
|
return
|
|
}
|
|
if _, ok := seen[fileID]; ok {
|
|
return
|
|
}
|
|
seen[fileID] = struct{}{}
|
|
fileIDs = append(fileIDs, fileID)
|
|
}
|
|
add(in.IconFileId)
|
|
for _, rule := range in.StageRules {
|
|
add(rule.ImageFileId)
|
|
add(rule.AnimationFileId)
|
|
}
|
|
if len(fileIDs) == 0 {
|
|
return nil
|
|
}
|
|
tenant, err := resolveImAdminTenant(ctx, tenantID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := "/internal/cultivate/tenants/" + url.PathEscape(tenant.Code) + "/files/validate"
|
|
for offset := 0; offset < len(fileIDs); offset += 20 {
|
|
end := offset + 20
|
|
if end > len(fileIDs) {
|
|
end = len(fileIDs)
|
|
}
|
|
var out struct {
|
|
Items []struct {
|
|
FileId string `json:"file_id"`
|
|
ReviewStatus string `json:"review_status"`
|
|
} `json:"items"`
|
|
}
|
|
if err = imadmin.DefaultClient(ctx).InternalPost(ctx, path, map[string]any{"user_id": 0, "file_ids": fileIDs[offset:end]}, &out); err != nil {
|
|
return gerror.Wrap(err, "校验花种素材失败")
|
|
}
|
|
for _, item := range out.Items {
|
|
if item.ReviewStatus == "risky" {
|
|
return gerror.Newf("素材 %s 审核未通过", item.FileId)
|
|
}
|
|
if item.ReviewStatus == "pending" {
|
|
return gerror.Newf("素材 %s 尚在审核中", item.FileId)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *sSysCultivateFlower) Status(ctx context.Context, in *sysin.CultivateFlowerStatusInp) error {
|
|
tenantID := cultivateTenantID(ctx)
|
|
operatorID := contexts.GetUserId(ctx)
|
|
now := time.Now().UTC()
|
|
return dao.FlowerCatalog.Transaction(ctx, func(txCtx context.Context, tx gdb.TX) error {
|
|
c := dao.FlowerCatalog.Columns()
|
|
var before entity.FlowerCatalog
|
|
if e := tx.Model(dao.FlowerCatalog.Table()).Where(g.Map{c.Id: in.Id, c.TenantId: tenantID}).LockUpdate().Scan(&before); e != nil {
|
|
return e
|
|
}
|
|
if before.Id == 0 {
|
|
return gerror.New("花种不存在")
|
|
}
|
|
data := g.Map{c.Status: in.Status, c.ConfigVersion: "cat_" + now.Format("20060102_150405"), c.UpdatedAt: now}
|
|
if in.Status == "online" {
|
|
data[c.OnlineAt] = now
|
|
data[c.OfflineAt] = nil
|
|
} else if in.Status == "offline" {
|
|
data[c.OfflineAt] = now
|
|
}
|
|
if _, e := tx.Model(dao.FlowerCatalog.Table()).Where(c.Id, in.Id).Data(data).Update(); e != nil {
|
|
return e
|
|
}
|
|
return writeCultivateOperation(txCtx, tx, tenantID, operatorID, "flower.status", "flower", fmt.Sprint(in.Id), before, data, in.Reason, now)
|
|
})
|
|
}
|
|
|
|
func writeCultivateOperation(ctx context.Context, tx gdb.TX, tenantID, operatorID int64, operation, targetType, targetID string, before, after any, reason string, now time.Time) error {
|
|
id, e := imtenant.NextSnowflakeID(ctx)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
beforeJSON, _ := json.Marshal(before)
|
|
afterJSON, _ := json.Marshal(after)
|
|
ipAddress, userAgent := "", ""
|
|
if request := ghttp.RequestFromCtx(ctx); request != nil {
|
|
ipAddress = location.GetClientIp(request)
|
|
userAgent = request.UserAgent()
|
|
}
|
|
c := dao.AdminOperationLog.Columns()
|
|
_, e = tx.Model(dao.AdminOperationLog.Table()).Data(g.Map{c.Id: id, c.TenantId: tenantID, c.OperatorId: operatorID, c.OperationType: operation, c.TargetType: targetType, c.TargetId: targetID, c.RequestId: gctx.CtxId(ctx), c.BeforeSnapshot: gjson.New(beforeJSON).MustToJsonString(), c.AfterSnapshot: gjson.New(afterJSON).MustToJsonString(), c.Reason: reason, c.IpAddress: ipAddress, c.UserAgent: userAgent, c.CreatedAt: now}).Insert()
|
|
return e
|
|
}
|