241 lines
7.7 KiB
Go
241 lines
7.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
|
|
"36wisdom/biz/consts"
|
|
"36wisdom/biz/dao"
|
|
"36wisdom/biz/model/dto"
|
|
"36wisdom/common"
|
|
)
|
|
|
|
// ---------- 后台管理 ----------
|
|
|
|
type adminSceneNode struct{}
|
|
|
|
var AdminSceneNode = &adminSceneNode{}
|
|
|
|
// List 指定关卡下全部节点(含下架),按序号排序。
|
|
func (s *adminSceneNode) List(ctx context.Context, req *dto.AdminSceneNodeListReq) (*dto.AdminSceneNodeListRes, error) {
|
|
recs, err := dao.SceneNode.ListByLevel(ctx, req.LevelId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ids := make([]int64, 0, len(recs))
|
|
characterIds := make([]int64, 0, len(recs))
|
|
for _, r := range recs {
|
|
ids = append(ids, r.Id)
|
|
characterIds = append(characterIds, r.CharacterId)
|
|
}
|
|
counts, err := dao.NodeOption.CountByNodeIds(ctx, ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
names, err := elementNamesAll(ctx, characterIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
items := make([]*dto.AdminSceneNodeItem, 0, len(recs))
|
|
for _, r := range recs {
|
|
items = append(items, &dto.AdminSceneNodeItem{
|
|
Id: r.Id, LevelId: r.LevelId, Title: r.Title,
|
|
CharacterId: r.CharacterId, CharacterName: names[r.CharacterId],
|
|
Content: r.Content, Image: r.Image, Audio: r.Audio,
|
|
NodeType: r.NodeType, InteractionType: r.InteractionType,
|
|
Config: r.Config, Script: r.Script,
|
|
ResultType: r.ResultType, IsEntry: r.IsEntry,
|
|
SortOrder: r.SortOrder, Status: r.Status,
|
|
OptionCount: counts[r.Id],
|
|
})
|
|
}
|
|
return &dto.AdminSceneNodeListRes{List: items}, nil
|
|
}
|
|
|
|
// elementNamesAll 批量元素名(含下架,后台列表展示用)。
|
|
func elementNamesAll(ctx context.Context, ids []int64) (map[int64]string, error) {
|
|
m := make(map[int64]string, len(ids))
|
|
ids = uniqueInt64(ids)
|
|
if len(ids) == 0 {
|
|
return m, nil
|
|
}
|
|
recs, err := dao.Element.ListByIds(ctx, ids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range recs {
|
|
m[r.Id] = r.Name
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// checkLevel 关卡存在性校验。
|
|
func checkLevel(ctx context.Context, levelId int64) error {
|
|
rec, err := dao.Level.GetByPk(ctx, levelId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rec == nil {
|
|
return gerror.New("关卡不存在")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkCharacter 角色元素存在性校验(character_id>0 时)。
|
|
func checkCharacter(ctx context.Context, characterId int64) error {
|
|
if characterId <= 0 {
|
|
return nil
|
|
}
|
|
rec, err := dao.Element.GetByPk(ctx, characterId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rec == nil {
|
|
return gerror.New("角色元素不存在")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkInteractionType 互动形态校验:决策节点须为有效形态,终局节点允许 0。
|
|
func checkInteractionType(nodeType, interactionType int) error {
|
|
if nodeType == consts.NodeDecision && (interactionType < consts.InteractionOption || interactionType > consts.InteractionConnect) {
|
|
return gerror.New("决策节点的互动形态须在 1-8")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Create 新增节点:事务内(校验 → 入口清位 → 写节点 → bump 关卡版本 → 失效缓存)。
|
|
func (s *adminSceneNode) Create(ctx context.Context, req *dto.AdminSceneNodeCreateReq) (*dto.AdminSceneNodeCreateRes, error) {
|
|
if err := checkLevel(ctx, req.LevelId); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkCharacter(ctx, req.CharacterId); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkInteractionType(req.NodeType, req.InteractionType); err != nil {
|
|
return nil, err
|
|
}
|
|
var id int64
|
|
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
if req.IsEntry == 1 {
|
|
if _, err := tx.Model(consts.TableSceneNode).Ctx(ctx).Where("level_id", req.LevelId).
|
|
Data(g.Map{"is_entry": 0}).Update(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
res, err := tx.Model(consts.TableSceneNode).Ctx(ctx).Data(g.Map{
|
|
"level_id": req.LevelId, "title": req.Title, "character_id": req.CharacterId,
|
|
"content": req.Content, "content_pinyin": common.AnnotatePinyin(req.Content),
|
|
"image": req.Image, "audio": req.Audio,
|
|
"node_type": req.NodeType, "interaction_type": req.InteractionType,
|
|
"config": req.Config, "script": req.Script, "result_type": req.ResultType,
|
|
"is_entry": req.IsEntry, "sort_order": req.SortOrder, "status": consts.StatusEnabled,
|
|
}).Insert()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, err = res.LastInsertId()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = tx.Model(consts.TableLevel).Ctx(ctx).WherePri(req.LevelId).
|
|
Data(g.Map{"content_version": gdb.Raw("content_version+1")}).Update()
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
common.InvalidateContentCache(ctx, consts.TableSceneNode, consts.TableLevel)
|
|
return &dto.AdminSceneNodeCreateRes{Id: id}, nil
|
|
}
|
|
|
|
// Update 编辑节点:全字段覆盖,入口清位,bump 关卡版本,清内容缓存。
|
|
func (s *adminSceneNode) Update(ctx context.Context, req *dto.AdminSceneNodeUpdateReq) (*dto.AdminSceneNodeUpdateRes, error) {
|
|
rec, err := dao.SceneNode.GetByPk(ctx, req.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if rec == nil {
|
|
return nil, gerror.New("节点不存在")
|
|
}
|
|
if err = checkLevel(ctx, req.LevelId); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = checkCharacter(ctx, req.CharacterId); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = checkInteractionType(req.NodeType, req.InteractionType); err != nil {
|
|
return nil, err
|
|
}
|
|
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
if req.IsEntry == 1 {
|
|
if _, err := tx.Model(consts.TableSceneNode).Ctx(ctx).Where("level_id", req.LevelId).
|
|
WhereNot("id", req.Id).Data(g.Map{"is_entry": 0}).Update(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := tx.Model(consts.TableSceneNode).Ctx(ctx).WherePri(req.Id).Data(g.Map{
|
|
"level_id": req.LevelId, "title": req.Title, "character_id": req.CharacterId,
|
|
"content": req.Content, "content_pinyin": common.AnnotatePinyin(req.Content),
|
|
"image": req.Image, "audio": req.Audio,
|
|
"node_type": req.NodeType, "interaction_type": req.InteractionType,
|
|
"config": req.Config, "script": req.Script, "result_type": req.ResultType,
|
|
"is_entry": req.IsEntry, "sort_order": req.SortOrder,
|
|
}).Update(); err != nil {
|
|
return err
|
|
}
|
|
_, err := tx.Model(consts.TableLevel).Ctx(ctx).WherePri(req.LevelId).
|
|
Data(g.Map{"content_version": gdb.Raw("content_version+1")}).Update()
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
common.InvalidateContentCache(ctx, consts.TableSceneNode, consts.TableLevel)
|
|
return &dto.AdminSceneNodeUpdateRes{}, nil
|
|
}
|
|
|
|
// setStatus 上下架(软删除):bump 关卡版本。
|
|
func (s *adminSceneNode) setStatus(ctx context.Context, id int64, status int) error {
|
|
rec, err := dao.SceneNode.GetByPk(ctx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if rec == nil {
|
|
return gerror.New("节点不存在")
|
|
}
|
|
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
if _, err := tx.Model(consts.TableSceneNode).Ctx(ctx).WherePri(id).
|
|
Data(g.Map{"status": status}).Update(); err != nil {
|
|
return err
|
|
}
|
|
_, err := tx.Model(consts.TableLevel).Ctx(ctx).WherePri(rec.LevelId).
|
|
Data(g.Map{"content_version": gdb.Raw("content_version+1")}).Update()
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
common.InvalidateContentCache(ctx, consts.TableSceneNode, consts.TableLevel)
|
|
return nil
|
|
}
|
|
|
|
// Disable 下架节点。
|
|
func (s *adminSceneNode) Disable(ctx context.Context, req *dto.AdminSceneNodeDisableReq) (*dto.AdminSceneNodeDisableRes, error) {
|
|
if err := s.setStatus(ctx, req.Id, consts.StatusDisabled); err != nil {
|
|
return nil, err
|
|
}
|
|
return &dto.AdminSceneNodeDisableRes{}, nil
|
|
}
|
|
|
|
// Enable 上架节点。
|
|
func (s *adminSceneNode) Enable(ctx context.Context, req *dto.AdminSceneNodeEnableReq) (*dto.AdminSceneNodeEnableRes, error) {
|
|
if err := s.setStatus(ctx, req.Id, consts.StatusEnabled); err != nil {
|
|
return nil, err
|
|
}
|
|
return &dto.AdminSceneNodeEnableRes{}, nil
|
|
}
|