- 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>
94 lines
3.9 KiB
Java
94 lines
3.9 KiB
Java
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_user(NULL 通配由 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();
|
||
}
|
||
}
|