- 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>
74 lines
2.7 KiB
Java
74 lines
2.7 KiB
Java
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);
|
||
}
|
||
}
|