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
+33
@@ -0,0 +1,33 @@
|
||||
package com.fruitshorts.system.dal.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 业务对象变更审计(支付、权益、定价、代看等敏感操作)。
|
||||
*
|
||||
* 与 sys_operation_log(接口级操作日志)区分:这里是"谁对哪个业务对象做了什么",
|
||||
* 表上没有 deleted 列,审计记录只增不改。
|
||||
*/
|
||||
@Data
|
||||
@TableName("audit_log")
|
||||
public class AuditLog {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
/** ORDER / ENTITLEMENT / USER / CREATOR / PRICE */
|
||||
private String bizType;
|
||||
private String bizId;
|
||||
private String action;
|
||||
private String beforeValue;
|
||||
private String afterValue;
|
||||
private Long operatorId;
|
||||
private String operatorName;
|
||||
private String ip;
|
||||
private String remark;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.fruitshorts.system.dal.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.fruitshorts.system.dal.entity.AuditLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface AuditLogMapper extends BaseMapper<AuditLog> {
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.fruitshorts.system.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fruitshorts.system.dal.entity.AuditLog;
|
||||
import com.fruitshorts.system.dal.mapper.AuditLogMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 审计写入。目前只有"代看创作者"在用,后续支付/定价等敏感操作也走这里。 */
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuditLogService {
|
||||
|
||||
public static final String BIZ_CREATOR = "CREATOR";
|
||||
public static final String ACTION_IMPERSONATE_VIEW = "IMPERSONATE_VIEW";
|
||||
|
||||
/** 同一操作人看同一个创作者,10 分钟内只记一条,避免下拉/翻页把审计表刷爆 */
|
||||
private static final int DEDUPE_MINUTES = 10;
|
||||
|
||||
private final AuditLogMapper auditLogMapper;
|
||||
|
||||
/**
|
||||
* 记录"代看进入"。
|
||||
*
|
||||
* 前端切换代看目标不是一次独立请求(只是给后续请求加了个头),所以这里做去重:
|
||||
* 同一操作人对同一创作者,10 分钟内的连续访问合并成一条 —— 既留下"谁什么时候看了谁",
|
||||
* 又不会因为列表翻页刷出几百条。
|
||||
*/
|
||||
public void recordImpersonationView(Long operatorId, String operatorName, Long creatorId,
|
||||
String creatorCode, String ip) {
|
||||
LocalDateTime since = LocalDateTime.now().minusMinutes(DEDUPE_MINUTES);
|
||||
Long recent = auditLogMapper.selectCount(Wrappers.<AuditLog>lambdaQuery()
|
||||
.eq(AuditLog::getBizType, BIZ_CREATOR)
|
||||
.eq(AuditLog::getBizId, String.valueOf(creatorId))
|
||||
.eq(AuditLog::getAction, ACTION_IMPERSONATE_VIEW)
|
||||
.eq(AuditLog::getOperatorId, operatorId == null ? 0L : operatorId)
|
||||
.ge(AuditLog::getCreatedAt, since));
|
||||
if (recent != null && recent > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
AuditLog log = new AuditLog();
|
||||
log.setBizType(BIZ_CREATOR);
|
||||
log.setBizId(String.valueOf(creatorId));
|
||||
log.setAction(ACTION_IMPERSONATE_VIEW);
|
||||
log.setOperatorId(operatorId == null ? 0L : operatorId);
|
||||
log.setOperatorName(operatorName == null ? "" : operatorName);
|
||||
log.setIp(ip == null ? "" : ip);
|
||||
log.setRemark("代看创作者 " + (creatorCode == null ? "" : creatorCode) + "(只读)");
|
||||
auditLogMapper.insert(log);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user