fix(publish): 修创作链路发布 UI 两缺陷——生成回写 currentVersionId + getZones 落实(HJ-FE-WALK-001)
UI 走查逮到、阻断真人 UI 发布的两个后端缺陷(编排器走 task.versionId 旁路掩盖): Bug A: 生成成功未回写 project.current_version_id - GameVersionServiceImpl.createForPackage 建版本后调 bindCurrentVersion 回写(幂等+新建两路径) - 原先该字段只在发布方法置位→生成后未发布阶段恒 null→Publish.vue 取不到 versionId/门禁⑤失败 - 安全:feed/play 用 game_feed_rank 的 versionId 不读 currentVersionId,对已发布游戏回写无副作用 Bug B: feed/zones 返回空(getZones 是 Collections.emptyList 桩) - FeedServiceImpl.getZones 落实直查 game_zone(type∈(1,2) AND status=1,sort 升序→ZoneRespVO) - 新增最小只读 GameZoneDO + GameZoneMapper(MVP 既定"同库可直查 game_zone") - 租户:GameZoneDO @TenantIgnore + staging tenant.enable=false→tenant_id=0 种子专区可取(实证) - 原桩致发布页专区选择器恒"加载中…"、launchZoneId 必填缺失而无法发布 子代理(Opus)执行+主 agent 核验(diff/租户口径/staging tenant.enable=false 实证);待 mini 构建+重部署+重走查闭合链路②。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
17b2e6a2ca
commit
29564c8eb7
@ -0,0 +1,54 @@
|
||||
package cn.wanxiang.game.module.feed.dal.dataobject.zone;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 专区 DO(对应表 game_zone,修 HJ-FE-WALK-001 Bug B 新增)
|
||||
*
|
||||
* 背景:feed 不拥有 Zone 实体,Zone 权威归 project(owner=project,V1 DDL/种子)。
|
||||
* 但发布页专区选择器需读「启用专区」列表(getZones),MVP 既定走「同库可直查 game_zone」,
|
||||
* 故在 feed 侧建此最小只读 DO 直查,待 project -api RPC 就绪后可改 Feign(不影响表结构)。
|
||||
*
|
||||
* 租户处置(最易翻车点):game_zone 是 project 的全局基础数据、非租户隔离,V1 种子行 tenant_id=0。
|
||||
* 故本 DO 刻意 **不继承 TenantBaseDO、改继承 {@link BaseDO}** 且加 {@link TenantIgnore} 注解——
|
||||
* 使 yudao 租户拦截器 TenantDatabaseInterceptor.computeIgnoreTable 判定为「忽略租户」,
|
||||
* 查询不追加 tenant_id 条件,从而在 tenant=1 的业务请求下也能取到 tenant_id=0 的种子专区(参照 system DictTypeDO 范式)。
|
||||
*
|
||||
* @author 绘境AI
|
||||
*/
|
||||
@TableName("game_zone")
|
||||
@KeySequence("game_zone_seq") // Oracle/PostgreSQL 等主键自增用;MySQL 可忽略
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TenantIgnore // 全局基础数据,忽略租户过滤:保证读到 tenant_id=0 的种子专区(详见类注释「租户处置」)
|
||||
public class GameZoneDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 专区 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 专区名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 专区类型(双轨):1官方精选 2UGC普通
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 排序(升序)
|
||||
*/
|
||||
private Integer sort;
|
||||
/**
|
||||
* 状态:0停用 1启用
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package cn.wanxiang.game.module.feed.dal.mysql.zone;
|
||||
|
||||
import cn.wanxiang.game.module.feed.dal.dataobject.zone.GameZoneDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 专区 Mapper(修 HJ-FE-WALK-001 Bug B 新增,feed 侧只读直查 game_zone)
|
||||
*
|
||||
* 仅承载发布页专区选择器所需的「启用专区列表」查询;表权威归 project,本模块只读不写。
|
||||
*
|
||||
* @author 绘境AI
|
||||
*/
|
||||
@Mapper
|
||||
public interface GameZoneMapper extends BaseMapperX<GameZoneDO> {
|
||||
|
||||
/**
|
||||
* 取启用的双轨专区,按 sort 升序(发布页专区选择器,命中 idx_type_status)
|
||||
*
|
||||
* 过滤条件:type ∈ (1官方精选, 2UGC普通) AND status=1 启用。
|
||||
* deleted 由 MyBatis-Plus @TableLogic 自动追加;tenant 不过滤——GameZoneDO 已 @TenantIgnore,
|
||||
* 拦截器不追加 tenant_id 条件,故 tenant_id=0 的种子专区在任意租户上下文下均可取到(详见 GameZoneDO 类注释)。
|
||||
*
|
||||
* @return 启用专区列表(已按 sort 升序)
|
||||
*/
|
||||
default List<GameZoneDO> selectEnabledZones() {
|
||||
return selectList(new LambdaQueryWrapperX<GameZoneDO>()
|
||||
.in(GameZoneDO::getType, Arrays.asList(1, 2)) // 双轨类型:1官方精选 / 2UGC普通
|
||||
.eq(GameZoneDO::getStatus, 1) // 仅启用专区
|
||||
.orderByAsc(GameZoneDO::getSort)); // 排序升序出列
|
||||
}
|
||||
|
||||
}
|
||||
@ -13,8 +13,10 @@ import cn.wanxiang.game.module.feed.controller.app.feed.vo.ZoneStreamReqVO;
|
||||
import cn.wanxiang.game.module.feed.convert.feed.FeedConvert;
|
||||
import cn.wanxiang.game.module.feed.dal.dataobject.interact.FeedInteractLogDO;
|
||||
import cn.wanxiang.game.module.feed.dal.dataobject.rank.FeedRankDO;
|
||||
import cn.wanxiang.game.module.feed.dal.dataobject.zone.GameZoneDO;
|
||||
import cn.wanxiang.game.module.feed.dal.mysql.interact.FeedInteractLogMapper;
|
||||
import cn.wanxiang.game.module.feed.dal.mysql.rank.FeedRankMapper;
|
||||
import cn.wanxiang.game.module.feed.dal.mysql.zone.GameZoneMapper;
|
||||
import cn.wanxiang.game.module.feed.dto.FeedRankUpsertReqDTO;
|
||||
import cn.wanxiang.game.module.feed.enums.FeedActionEnum;
|
||||
import cn.wanxiang.game.module.project.api.ProjectApi;
|
||||
@ -30,7 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -61,6 +63,15 @@ public class FeedServiceImpl implements FeedService {
|
||||
@Resource
|
||||
private FeedInteractLogMapper feedInteractLogMapper;
|
||||
|
||||
/**
|
||||
* 专区 Mapper(修 HJ-FE-WALK-001 Bug B:发布页专区选择器读启用专区)
|
||||
*
|
||||
* MVP 既定「同库可直查 game_zone」:feed 侧只读直查,待 project -api RPC 就绪后可改 Feign。
|
||||
* GameZoneDO 已 @TenantIgnore,故种子专区(tenant_id=0)在任意租户上下文下均可取到。
|
||||
*/
|
||||
@Resource
|
||||
private GameZoneMapper gameZoneMapper;
|
||||
|
||||
/**
|
||||
* project 模块 RPC 契约(仅依赖 -api)
|
||||
*
|
||||
@ -181,9 +192,21 @@ public class FeedServiceImpl implements FeedService {
|
||||
|
||||
@Override
|
||||
public List<ZoneRespVO> getZones() {
|
||||
// TODO 跨模块对接 project:读 game_zone(type=1官方精选 2UGC普通、status=1启用),按 sort 升序返回。
|
||||
// MVP 同库可直查 game_zone,或待 project -api RPC 就绪后改 Feign。骨架阶段返回空列表占位。
|
||||
return Collections.emptyList();
|
||||
// 修 HJ-FE-WALK-001 Bug B:原桩恒返回空列表,致发布页专区选择器恒「专区加载中…」、launchZoneId 必填缺失而无法发布。
|
||||
// 实现:MVP 既定「同库可直查 game_zone」,直查启用的双轨专区(type∈(1,2) AND status=1),按 sort 升序。
|
||||
// 租户:GameZoneDO 已 @TenantIgnore,查询不追加 tenant_id 条件,故 tenant_id=0 的种子专区在 tenant=1 请求下也能取到。
|
||||
List<GameZoneDO> zones = gameZoneMapper.selectEnabledZones();
|
||||
List<ZoneRespVO> result = new ArrayList<>(zones.size());
|
||||
for (GameZoneDO zone : zones) {
|
||||
// 仅映射前端选择器所需字段(id/name/type/sort),与契约 ZoneRespVO 对齐
|
||||
ZoneRespVO vo = new ZoneRespVO();
|
||||
vo.setId(zone.getId());
|
||||
vo.setName(zone.getName());
|
||||
vo.setType(zone.getType());
|
||||
vo.setSort(zone.getSort());
|
||||
result.add(vo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================== 互动信号 ==============================
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package cn.wanxiang.game.module.project.service.version;
|
||||
|
||||
import cn.wanxiang.game.module.project.dal.dataobject.project.ProjectDO;
|
||||
import cn.wanxiang.game.module.project.dal.dataobject.version.GameVersionDO;
|
||||
import cn.wanxiang.game.module.project.dal.mysql.project.ProjectMapper;
|
||||
import cn.wanxiang.game.module.project.dal.mysql.version.GameVersionMapper;
|
||||
import cn.wanxiang.game.module.project.dto.ProjectVersionCreateForPackageReqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
@ -29,6 +31,14 @@ public class GameVersionServiceImpl implements GameVersionService {
|
||||
@Resource
|
||||
private GameVersionMapper gameVersionMapper;
|
||||
|
||||
/**
|
||||
* 游戏项目 Mapper(修 HJ-FE-WALK-001 Bug A:落包建版本后回写 project.current_version_id)
|
||||
*
|
||||
* 仅用于把刚建好的版本 ID 绑定为项目「当前生效版本」,使前端发布页据此识别「存在可发布版本」。
|
||||
*/
|
||||
@Resource
|
||||
private ProjectMapper projectMapper;
|
||||
|
||||
@Override
|
||||
public Long createForPackage(ProjectVersionCreateForPackageReqDTO req) {
|
||||
// 幂等:同 genTaskId 已建版本则直接复用,不重复建(PackageFactory 重复落包安全)
|
||||
@ -37,6 +47,8 @@ public class GameVersionServiceImpl implements GameVersionService {
|
||||
if (existing != null) {
|
||||
log.info("[createForPackage] 落包建版本幂等命中,复用已建版本 genTaskId={}, versionId={}",
|
||||
req.getGenTaskId(), existing.getId());
|
||||
// Bug A 修复:幂等命中路径也回写 current_version_id(防御重复落包但项目尚未绑定版本的场景)
|
||||
bindCurrentVersion(req.getGameId(), existing.getId());
|
||||
return existing.getId();
|
||||
}
|
||||
}
|
||||
@ -52,9 +64,38 @@ public class GameVersionServiceImpl implements GameVersionService {
|
||||
gameVersionMapper.insert(version);
|
||||
log.info("[createForPackage] 落包建版本成功 gameId={}, genTaskId={}, versionId={}",
|
||||
req.getGameId(), req.getGenTaskId(), version.getId());
|
||||
// Bug A 修复:新建版本后立即回写 current_version_id,让前端发布页识别「存在可发布版本」并取到 versionId
|
||||
bindCurrentVersion(req.getGameId(), version.getId());
|
||||
return version.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 回写项目「当前生效版本」ID(修 HJ-FE-WALK-001 Bug A)
|
||||
*
|
||||
* 背景:原先 current_version_id 仅在发布方法里才被置位,导致「生成成功→未发布」阶段恒为 null,
|
||||
* 前端发布页 Publish.vue 读 project.currentVersionId 取 versionId、门禁⑤「存在可发布版本」也读它,
|
||||
* 故真人 UI 永远拿不到 versionId、无法发起发布(编排器批跑因走 task.versionId 旁路而掩盖了该缺陷)。
|
||||
* 此处在落包建版本成功后回写,打通该死锁。
|
||||
*
|
||||
* 安全性:feed 出流 / runtime 即玩用的是 game_feed_rank 持久化的 versionId,不读 project.currentVersionId,
|
||||
* 故对「已发布游戏再次落包」回写 current_version_id 不影响线上出流与试玩,无副作用。
|
||||
*
|
||||
* @param gameId 游戏项目 ID(= GamePackage.gameId)
|
||||
* @param versionId 待绑定为当前生效版本的版本 ID
|
||||
*/
|
||||
private void bindCurrentVersion(Long gameId, Long versionId) {
|
||||
// 入参任一为空则不回写(防御:缺 gameId 无法定位项目、缺 versionId 无意义),直接返回
|
||||
if (gameId == null || versionId == null) {
|
||||
return;
|
||||
}
|
||||
// 仅更新 current_version_id 单列:其余字段不置入 update DO,MyBatis-Plus 默认策略跳过 null,不覆盖原值
|
||||
ProjectDO update = new ProjectDO();
|
||||
update.setId(gameId);
|
||||
update.setCurrentVersionId(versionId);
|
||||
projectMapper.updateById(update);
|
||||
log.info("[createForPackage] 已回写项目当前生效版本 gameId={}, currentVersionId={}", gameId, versionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameVersionDO getVersion(Long versionId) {
|
||||
return gameVersionMapper.selectById(versionId);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user