feat: 创作者中心支持超管只读代看(未绑定不再白屏)
为什么:/creator-center 菜单同时给了超管和创作者,但接口只认"绑定了 creator.admin_user_id 的账号", 而 creator 表为空 —— 超管点进去每个接口都 403,页面全白。这是设计缺口:需要一条"超管代看"的合法路径, 且必须既能让运营排查问题,又不能变成越权入口。 - requireCurrentCreator 改三级解析:① 绑定优先(创作者角色永远走这条)② 未绑定 + creator-center:impersonate + 请求头 X-Creator-Id → 只读代看 ③ 其余给可识别错误码(200004 未绑定 / 200005 未选目标) - 目标走独立请求头而不是查询参数:creator-center 接口继续"不接收 creatorId"; 且只有具备 impersonate 权限的账号这个头才生效 —— 创作者自己带也不影响"只看自己的" - 只读硬边界:代看态下作品编辑、分集增删改/排序、上传会话与产物挂载、提交/撤回、收费配置一律 200006 (写路径统一走 requireWritableDrama/requireWritable;上传会话在 MediaController 里用 ImpersonationGuard 拦) - 代看进入写 audit_log(AuditLog 实体 + AuditLogService,同人同目标 10 分钟去重),表本来就有、之前无代码引用 - 新增 GET /admin/creator-center/impersonation/targets(仅 impersonate)供顶部下拉取数 - 权限点 creator-center:impersonate 只授予 super_admin(content_operator 第一阶段不给) - 修掉菜单 26 的路由名冲突:它和 17 号菜单(创作者管理)都叫 CreatorCenter,Vue Router 同名路由互相覆盖, 导致超管整个 /creator-center 子树注册不上(点进去 404)。改名 CreatorWorkspace,并核对全表无重名 实测:创作者带 X-Creator-Id 仍只看自己的、仍能编辑自己的作品;代看下 9 个写操作全 403; audit_log 有且仅有一条代看记录(12 次代看请求去重成 1 条)。
This commit is contained in:
1 parent
a635de7111
commit
efeb9fcfaf
14 files changed
+351
-25
No files matched your search
+72
@@ -0,0 +1,72 @@
|
||||
package com.fruitshorts.framework.security;
|
||||
|
||||
import com.fruitshorts.common.exception.ErrorCode;
|
||||
import com.fruitshorts.common.exception.ServiceException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
/**
|
||||
* 超管「代看创作者」的识别与只读闸。
|
||||
*
|
||||
* 约定(红线):
|
||||
* 1. 目标创作者走**独立请求头** `X-Creator-Id`,不做查询参数 —— 创作中心的接口永远不接收 creatorId 参数;
|
||||
* 2. 这个头**只有**持有 `creator-center:impersonate` 的账号才生效。创作者自己带上它没有任何作用,
|
||||
* 也不影响"只能看自己的"这条规则;
|
||||
* 3. 代看是**只读**:写操作(作品编辑、分集增删改、上传会话与产物挂载、提交/撤回、收费配置)一律拒绝。
|
||||
*/
|
||||
public final class ImpersonationGuard {
|
||||
|
||||
public static final String CREATOR_HEADER = "X-Creator-Id";
|
||||
public static final String PERMISSION = "creator-center:impersonate";
|
||||
|
||||
private ImpersonationGuard() {
|
||||
}
|
||||
|
||||
/** 请求头里的目标创作者 id;没带或格式不对都返回 null。 */
|
||||
public static Long requestedCreatorId() {
|
||||
HttpServletRequest request = currentRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
String raw = request.getHeader(CREATOR_HEADER);
|
||||
if (!StringUtils.hasText(raw)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
long value = Long.parseLong(raw.trim());
|
||||
return value > 0 ? value : null;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** 当前这次请求是否属于"代看":带了目标 id + 调用方确实有代看权限。 */
|
||||
public static boolean isImpersonating() {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUserOrNull();
|
||||
return loginUser != null
|
||||
&& loginUser.hasPermission(PERMISSION)
|
||||
&& requestedCreatorId() != null;
|
||||
}
|
||||
|
||||
/** 代看状态下拒绝写操作;不是代看则什么也不做。 */
|
||||
public static void rejectWrite() {
|
||||
if (isImpersonating()) {
|
||||
throw new ServiceException(ErrorCode.CREATOR_IMPERSONATE_READONLY,
|
||||
"代看模式下只读:不能修改创作者数据(当前代看创作者 #" + requestedCreatorId() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
public static String clientIp() {
|
||||
HttpServletRequest request = currentRequest();
|
||||
return request == null ? "" : com.fruitshorts.framework.web.WebUtils.getClientIp(request);
|
||||
}
|
||||
|
||||
private static HttpServletRequest currentRequest() {
|
||||
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attributes) {
|
||||
return attributes.getRequest();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user