fix: 跨租户授权联调修复 + 新增分区自建模块

- CrossTenantServiceImpl: 有效期判断改用 apply("start_at <= now()"),规避
  OffsetDateTime 绑定 timestamptz 比较异常(见实施记录 6.5)
- CallbackServiceImpl: 集成 C2C 跨租户授权检查(额外分发+审计+is_cross_tenant),
  提取 enqueueIfConfigured
- 新增 PartitionService/Task: im_message 按月 RANGE 分区滚动建表
  (当月+下月,@Init 启动自检 + 每日 0:10)
- 新增 CrossTenantService: 正反向授权匹配 + 跨租户审计

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
yixiong
2026-07-08 23:56:32 +08:00
co-authored by Claude
parent d22fdcfaad
commit 8c9037fc4f
7 changed files with 336 additions and 13 deletions
@@ -0,0 +1,93 @@
package com.imutil.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.imutil.entity.CrossTenantAudit;
import com.imutil.entity.CrossTenantGrant;
import com.imutil.mapper.CrossTenantAuditMapper;
import com.imutil.mapper.CrossTenantGrantMapper;
import com.imutil.service.CrossTenantService;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import java.time.OffsetDateTime;
/**
* 跨租户通讯授权服务实现
* <p>
* 授权匹配:正向(from→to) 优先,未命中再查反向双向(to→from 且 direction=1)。
* user 字段 NULL 表示通配;permissions 用 LIKE 匹配是否含 send_msg。
*
* @author imutil
*/
@Slf4j
@Component
public class CrossTenantServiceImpl implements CrossTenantService {
private static final String PERM_SEND_MSG = "send_msg";
@Inject
private CrossTenantGrantMapper grantMapper;
@Inject
private CrossTenantAuditMapper auditMapper;
@Override
public Long checkSendMsgGrant(String fromTenant, String fromUser, String toTenant, String toUser) {
// 同租户或任一租户缺失,不算跨租户通讯
if (fromTenant == null || toTenant == null || fromTenant.equals(toTenant)) {
return null;
}
// 1. 正向授权 from→to
Long gid = matchDirected(fromTenant, fromUser, toTenant, toUser, false);
if (gid != null) {
return gid;
}
// 2. 反向双向授权:记录方向为 to→from 且 direction=1
return matchDirected(toTenant, toUser, fromTenant, fromUser, true);
}
@Override
public void audit(Long grantId, String msgKey, String fromUser, String toUser) {
try {
CrossTenantAudit a = new CrossTenantAudit();
a.setGrantId(grantId);
a.setMsgKey(msgKey);
a.setFromImUserId(fromUser);
a.setToImUserId(toUser);
a.setActionTime(OffsetDateTime.now());
auditMapper.insert(a);
} catch (Exception e) {
// 审计写入失败不阻塞主流程
log.warn("跨租户审计写入失败 grant={} msgKey={} : {}", grantId, msgKey, e.getMessage());
}
}
/**
* 按指定方向匹配一条 send_msg 授权
*
* @param matchFromTenant 查询条件 from_tenant
* @param matchFromUser 查询条件 from_im_userNULL 通配由 SQL 处理)
* @param matchToTenant 查询条件 to_tenant
* @param matchToUser 查询条件 to_im_user
* @param now 当前时间(校验有效期)
* @param requireBidir 是否要求 direction=1(反向匹配时)
* @return 命中的 grantId,未命中返回 null
*/
private Long matchDirected(String matchFromTenant, String matchFromUser,
String matchToTenant, String matchToUser,
boolean requireBidir) {
CrossTenantGrant g = grantMapper.selectOne(Wrappers.<CrossTenantGrant>lambdaQuery()
.eq(CrossTenantGrant::getFromTenantId, matchFromTenant)
.eq(CrossTenantGrant::getToTenantId, matchToTenant)
.eq(CrossTenantGrant::getStatus, 1)
.eq(requireBidir, CrossTenantGrant::getDirection, 1)
.like(CrossTenantGrant::getPermissions, PERM_SEND_MSG)
.and(w -> w.isNull(CrossTenantGrant::getStartAt).or().apply("start_at <= now()"))
.and(w -> w.isNull(CrossTenantGrant::getEndAt).or().apply("end_at >= now()"))
.and(w -> w.isNull(CrossTenantGrant::getFromImUserId).or().eq(CrossTenantGrant::getFromImUserId, matchFromUser))
.and(w -> w.isNull(CrossTenantGrant::getToImUserId).or().eq(CrossTenantGrant::getToImUserId, matchToUser))
.last("LIMIT 1"));
return g == null ? null : g.getGrantId();
}
}