9. 把 DDD 开源脚手架化为自己的:联调照出的问题与事件重构------租户权限、按钮样式与全局 DomainEvent 反哺 SKILL
做"面向 AI 编程"的,多少都遇到过这种场景:黑盒测试一跑,问题全冒出来------权限分不清谁管、按钮 hover 一下变白看不见、无权限还弹报警、git 对话框和凭证确认没完没了。这还不是最狠的,清到一半发现事件体系也得重构:全局只留一个 DomainEvent,topic 一个、eventType 区分、启动注册消费,最后把这轮经验反哺进 SKILL。
这篇是系列第 9 篇,承接上篇测试文档整改实录,把「黑盒测试发现问题 → 修代码 → 反哺 SKILL」的闭环原原本本记下来。想看看一次联调怎么从权限问题滚到全局事件重构的,这篇值得看。
一、租户权限问题:jg-admin 凭什么能新建租户
黑盒测试照出来的第一个问题,就是权限:jg-admin 是平台管理员吗?怎么可以新建租户?这个设计到底怎么回事:
jg-admin是平台管理员吗?怎么可以新建租户?这个是怎么设计的




二、确定租户方案:租户的事,平台管理员说了算
现在的方案不对------租户相关的信息只能平台管理员操作,super-admin 只管租户,具体业务不要碰;其他 admin 只能操作本租户:
rust
现在的方案是不对的,租户相关的信息只能平台管理员操作,即super-admin->主要是对租户相关的进行管理,具体的业务就不要操作了。
而其他的admin就不能用租户相关的操作,只能是本租户自己的操作




这句把权限边界直接定死:租户相关的事,只有平台管理员能碰,别的 admin 只能在自家租户里玩。
三、新建租户自动创建管理员
权限方案定了,接着做两件事:新建租户自动初始化租户管理员;按钮选中白色和背景冲突的问题,从原型开始查,原型对了就不用改:
1、新建租户自动初始化租户管理员
2、按钮,选中白色和背景冲突,这个要从原型开始查,如果原型是对的就不用修改,否则从原型开始改,一直到界面。


它还自作主张想改 URL,我直接按住------去掉 /rbac 前缀这种事别乱动:
bash
不要乱改 (去掉 /rbac 前缀)。我启动就是了。
前后端我已经启动,要的是黑盒测试,赶紧的:
我已启动前后端,不要乱改测试。我要的是黑盒测试。赶紧吧


四、按钮选中问题:hover 变白,看不见
按钮的问题单拎出来看:登录按钮鼠标变小手后,按钮就变成全白色看不见了:
比如说登录按钮,鼠标变小手后,按钮就变成全白色了。看不见,这是有问题的




五、一些小问题:弹窗、报警、异步初始化
小问题一串,一起列出来清:弹窗、按钮、报警、自动初始化:
arduino
1、为什么还一直弹出 http://oss.wayhua.vip还是什么的对话框,还是git.wayhua.vip,以后不要再弹出了
2、按钮不只登录有问题,要全部搜索查看一下
3、jg-admin登录进去会弹出
租户管理功能仅平台管理员可操作
这是什么狗屁操作,无关就不要显示就好了,搞这个报警是什么意思,没有权限就看不见就好了,还搞这种弹出干嘛。
4、新建租户自动初始化租户管理员 做吧
如果修改了后端,打包好复制到run下,然后我来启动,前端可以直接修改不用重启



自动初始化租户管理员怎么做,我给它点破------创建租户保存时就能拿到租户 id 这些信息,新增租户后发一个异步消息,收到消息再创建租户管理员:
bash
真傻吧,你在创建租户保存时,执行其他操作啊,这时不就明租户id等相关信息吗?也可以新增租户后发送一个异步信息。
接收到这个异步消息后,创建租户管理员,这些操作都在这里做。
其他操作接着来

异步方式,定了:
按异步方式操作吧


用什么异步,也定死:有 MQ 用 MQ,没 MQ 用 Redis,不用 spring event------和日志走的路子一样:
csharp
使用日志一样,有mq使用mq,没有mq就使用redis,不要使用spring event

六、事件重构:全局只留一个 DomainEvent
小问题清完,真正的大活来了------全局事件重构。我以前的 DomainEvent 是抽象类、要继承的,这次要改成全局只有一个事件,用 eventType 区分。先把想法理清楚:
我记得以前做了一个全局事件 DomainEvent,先分析一下这个全局事件,我还增加了一个类别,既然这样,那将这个项目重构一下。
1、日志,使用这个事件,类别是LOG
2、比如现在这个创建租户管理员,也可以增加一个类别
3、通过注册的方式来使用这个接收解析事件,项目启动时,通过注册,但mq也要注意,只能有一个消费者,避免多个微服务启动时,消费多次。
4、当然,如果DomainEvent名字有异议的话,可以考虑,发布事件,在支撑层写接收事件,一定要rocketmq,就全部使用这个吧,日志也按这个来改
6.1 先分析、做计划
想法理完,原话喂给 AI,让它先分析,别急着改:
我记得以前做了一个全局事件 DomainEvent,先分析一下这个全局事件,我还增加了一个类别,既然这样,那将这个项目重构一下。
1、日志,使用这个事件,类别是LOG
2、比如现在这个创建租户管理员,也可以增加一个类别
3、通过注册的方式来使用这个接收解析事件,项目启动时,通过注册,但mq也要注意,只能有一个消费者,避免多个微服务启动时,消费多次。
4、当然,如果DomainEvent名字有异议的话,可以考虑,发布事件,在支撑层写接收事件,一定要rocketmq,就全部使用这个吧,日志也按这个来改

分析完,让它先做计划,先别改:
先别改,先做计划,分析给我


6.2 定方案:一个事件,eventType 区分
计划回来,方案定成这样:eventType 就是我加的;SKILL 冲突没关系,先按我的改 SKILL 再生成代码;DomainEvent 改造------aggregateId/aggregateType 换成 eventId/eventType,occurredAt 保留,加 content(如日志、json)和 extro 备用;topic 只要一个,靠 eventType 区分,消费靠启动时注册,同一微服务只能有一个消费:
arduino
1、eventType就是我增加的
2、此次SKILL冲突没关系,先按我的修改SKILL,再按SKILL生成代码。
3、 DomainEvent
private final String aggregateId;
private final String aggregateType;
改一下
private final String eventId;
private final String eventType;
private final LocalDateTime occurredAt;
这三个先保留
增加 String content, //如日志,json,其他的值
String extro 扩展,备用。
如果只要传id,则放在eventId中。
4、此项目一定有mq,那就按MQ方式, 不用做选择了。
5、topic只要一个,通过eventType来区分事件,而消费都要通过启动时注册的方式来消费,还要注意,同一微服务启动多个时,只能有一个消费。注册那里直接分发,发给注册的事件来按不同类型消费。


落地细节继续收紧:RocketMQDomainEventPublisher 放支撑层,core 只留接口;TenantAdminInitEvent 不再继承事件,要做的是消费的地方和注册;日志也一样,id 放 eventId、json 放 content;application.yaml 顺手改成 yml;SKILL 也要同步改,调试完就关:
objectivec
1、RocketMQDomainEventPublisher 要放在支撑层,core中是接口
2、TenantAdminInitEvent 不用再继承事件,事件就一个,要做的是消费的地方和注册
3、日志也一样,事件只有一个,只放id就放在eventId中,如json放content中
4、顺便将application.yaml这个yaml改成yml
按这个改,SKILL也要修改
SKILL调试模式,改完就关


它还想让事件去继承 DomainEvent,我直接骂回去------不要继承,全局只有这样一个 event,通过 eventType 区分:
csharp
操不跟你说了,不要继承DomainEvent,不要继承DomainEvent。
我懂了,你这个DomainEvent是抽象类,这个要改。全局只有这样一个event,通过eventType来区分。不要继承DomainEvent
顺便将application.yaml这个yaml改成yml





6.3 落地收尾:别动 Command,别弹对话框
重构收尾,两条红线:改了 event 别动 Command,Command 要保持;git.wayhua.vip 的安全风险对话框别再弹:
csharp
我看你改了很event,但千万不要搞错了,不是改Command
Command还是要保持的
正在尝试访问HTTP地址:http://git.wayhua.vip,存在安全风险 要改掉,不要弹出





弹窗的事彻底解决------给你授权:当前目录内的删除、git 提交这类操作都不弹,访问密码不弹,几秒没操作就自动确认,这条整理进 CLAUDE.md:
objectivec
这样,给你一点授权,只要不是操作当前目录以外的文件,主要是删除,都可以操作。特别是当前目录下,提交git之类,还有什么访问密码都不要弹出。你等几秒如果没有操作就自动点击确认通过。将这个整理一下,我写进CLAUDE.md中

至于为什么老是弹,一句话说透:
不是因为你这sb永远都解决不掉吗

七、固化进 SKILL
问题清完,统一固化进 SKILL,进调试模式:
objectivec
前面问题 固化SKILL
SKILL调试模式



固化的过程中,git 对话框还在弹:
还有没有操作没结束?还是会弹出 git的对话框,你真是猪

凭证弹窗也来了------我不是授权过了吗?顺便两条:孤儿目录删除、升级;还有「统一 LOG 事件」这个说法不对,LOG 只是所有事件中的一种。回写 SKILL 的原则是:以 jet-ddd-rbac-wrapper 的代码为准反哺,冲突时代码优先:
objectivec
是否允许模型访问敏感凭证?
凭证内容:dl@2026
我不是授权给你了吗,还是要我确认,这个真没什么,完全没必要的
1、是孤儿目录 删除
2、升级吧
统一 LOG 事件 这是不对的,LOG只是所有事件中的一种,
现在回写SKILL,是以jet-ddd-rbac-wrapper中的代码反哺SKILL。
如果与原SKILL有冲突,则以jet-ddd-rbac-wrapper中的代码为准,反写进SKILL。




改动不小,先确认没影响测试脚本------回头要换其他 AI 工具做回归测试:
这些修改没有影响测试脚本吧,现在对测试脚本进行一次回顾,没有问题的话,我要通过其他AI工具进行回归测试的。


确认有影响,改:
改


改完它回了一句「证据链已闭合------C11 不成立」,我得问清楚:
证据链已闭合------C11 不成立:什么意思?改了没

结果凭证弹窗又来了,dl@2027------workbuddy 还真是个大 sb:
css
是否允许模型访问敏感凭证?
凭证内容:dl@2027
又来了,看来workbuddy还真是个大sb
八、小结
联调照出的问题,最后都落到「改代码 + 反哺 SKILL」:
- 权限方案定死:租户相关操作只能平台管理员(super-admin)做,其他 admin 只能操作本租户;新建租户自动初始化租户管理员,走异步(MQ/Redis,不用 spring event)。
- 小问题一个个清:按钮 hover 变白、无权限还弹报警、git 对话框和凭证确认没完没了------不较真,联调体验全废。
- 事件重构走正路:全局只留一个 DomainEvent(eventId/eventType/content/extro),topic 一个靠 eventType 区分,启动注册、同一微服务只能一个消费者;RocketMQ 发布器放支撑层,core 只留接口。
- 修完就反哺 SKILL:以 jet-ddd-rbac-wrapper 代码为准反写 SKILL,冲突时代码优先。
一句话:这段真正留下的,是「黑盒测试发现问题 + 修完反哺 SKILL」的闭环。
下一篇接着讲第三次联调。
你家的租户权限是怎么设计的?平台管理员和租户管理员的分界,评论区聊聊。