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,73 @@
package com.imutil.service.impl;
import com.imutil.mapper.PartitionMapper;
import com.imutil.service.PartitionService;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Init;
import org.noear.solon.annotation.Inject;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
/**
* im_message 分区管理服务实现
* <p>
* 每次建当月+下月分区(CREATE TABLE IF NOT EXISTS 幂等),保证:
* - 启动时当月分区就绪(init.sql 仅建了 default 兜底分区)
* - 跨月前下月分区已存在,消息不落 default 分区(避免查询性能退化)
* 分区按自然月 RANGE(msg_time)+08 时区边界。
*
* @author imutil
*/
@Slf4j
@Component
public class PartitionServiceImpl implements PartitionService {
private static final ZoneId ZONE = ZoneId.of("+08");
private static final DateTimeFormatter MONTH_FMT = DateTimeFormatter.ofPattern("yyyyMM");
/** PG timestamptz 字面量格式:2026-07-01 00:00:00+08:00 */
private static final DateTimeFormatter TS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxxx");
@Inject
private PartitionMapper partitionMapper;
/**
* 应用启动后自检一次:首次启动当月分区缺失则补建
*/
@Init
public void init() {
try {
createCurrentAndNextMonth();
log.info("启动分区自检完成(当月+下月)");
} catch (Throwable e) {
log.error("启动分区自检异常", e);
}
}
@Override
public void createCurrentAndNextMonth() {
ZonedDateTime now = ZonedDateTime.now(ZONE);
ensureMonth(now.getYear(), now.getMonthValue());
// 下月:用 plusMonths 计算避免年底跨月
ZonedDateTime next = now.plusMonths(1);
ensureMonth(next.getYear(), next.getMonthValue());
}
/**
* 确保指定年月的分区存在(不存在则创建,幂等)
*/
private void ensureMonth(int year, int month) {
// 用 YearMonth 拼表名与边界,规避 1月/12月 跨年的日历计算坑
java.time.YearMonth ym = java.time.YearMonth.of(year, month);
String name = "im_message_" + ym.format(MONTH_FMT);
ZonedDateTime start = ym.atDay(1).atStartOfDay(ZONE);
ZonedDateTime end = ym.plusMonths(1).atDay(1).atStartOfDay(ZONE);
String sql = "CREATE TABLE IF NOT EXISTS " + name +
" PARTITION OF im_message FOR VALUES FROM ('" +
start.format(TS_FMT) + "') TO ('" + end.format(TS_FMT) + "')";
partitionMapper.execute(sql);
log.debug("分区就绪 {}", name);
}
}