Files
36Wisdom/biz/service/scene_node.go
T
2026-08-14 16:11:10 +08:00

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
}