feat: 初始化腾讯IM分发工具骨架,含回调分发链路与历史消息补拉
- 应用骨架: Solon + MyBatis-Plus + PostgreSQL + Redis + Sa-Token - 核心链路: 回调网关 → 消息落库(幂等) → 分发队列(FOR UPDATE SKIP LOCKED) → worker消费/重试/死信 - 多租户隔离: 前缀法账号映射, 所有业务表带 tenant_id - 管理后台: FreeMarker 渲染, 租户/授权/队列/用量管理页面 - 历史消息补拉: 水位线驱动, getRoamMsg/getGroupMsg 增量拉取, 兜底回调丢失 - msg_key 统一算法(回调/补拉共享, 跨路径去重) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
package com.imutil.worker;
|
||||
|
||||
import com.imutil.entity.DistQueue;
|
||||
import com.imutil.service.DispatchService;
|
||||
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.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 分发工作线程
|
||||
* <p>
|
||||
* 应用启动后({@code @Init})创建 daemon 线程池,每个线程循环:
|
||||
* 抢占一批 pending → 逐条 HTTP 转发业务系统 → 回执更新状态。
|
||||
* 空闲时按 pollIntervalMs 休眠,避免空转。
|
||||
* <p>
|
||||
* daemon 线程不阻止 JVM 退出;业务系统宕机时由回执失败+重试+死信兜底(见 DispatchService)。
|
||||
*
|
||||
* @author imutil
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DispatchWorker {
|
||||
|
||||
@Inject
|
||||
private DispatchService dispatchService;
|
||||
|
||||
@Inject("${imutil.dispatch.workerCount:4}")
|
||||
private int workerCount;
|
||||
|
||||
@Inject("${imutil.dispatch.pollIntervalMs:1000}")
|
||||
private long pollIntervalMs;
|
||||
|
||||
private final AtomicInteger seq = new AtomicInteger(0);
|
||||
|
||||
private ExecutorService pool;
|
||||
|
||||
/**
|
||||
* 应用启动后启动工作线程
|
||||
*/
|
||||
@Init
|
||||
public void start() {
|
||||
pool = Executors.newFixedThreadPool(workerCount, r -> {
|
||||
Thread t = new Thread(r, "dispatch-worker-" + seq.incrementAndGet());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
for (int i = 0; i < workerCount; i++) {
|
||||
pool.submit(this::loop);
|
||||
}
|
||||
log.info("分发工作线程已启动 count={}", workerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作循环
|
||||
*/
|
||||
private void loop() {
|
||||
String workerName = "w-" + seq.get();
|
||||
while (true) {
|
||||
try {
|
||||
List<DistQueue> tasks = dispatchService.fetchAndLock(workerName);
|
||||
if (tasks.isEmpty()) {
|
||||
Thread.sleep(pollIntervalMs);
|
||||
continue;
|
||||
}
|
||||
for (DistQueue task : tasks) {
|
||||
try {
|
||||
dispatchService.dispatch(task, workerName);
|
||||
} catch (Throwable e) {
|
||||
log.error("分发单条异常 id={}", task.getId(), e);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (Throwable e) {
|
||||
log.error("分发工作循环异常", e);
|
||||
try {
|
||||
Thread.sleep(pollIntervalMs);
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user