统一修改ruoyi->bms
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package com.bms.price.mq;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** MQ payload: only taskId; consumer loads full row from MySQL. */
|
||||
public class BillingTaskMessage implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long taskId;
|
||||
|
||||
public BillingTaskMessage()
|
||||
{
|
||||
}
|
||||
|
||||
public BillingTaskMessage(Long taskId)
|
||||
{
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
public Long getTaskId()
|
||||
{
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public void setTaskId(Long taskId)
|
||||
{
|
||||
this.taskId = taskId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.bms.price.mq;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.config.BillingProperties;
|
||||
import com.bms.price.service.IPrcBillingTaskService;
|
||||
|
||||
/**
|
||||
* Consume billing task MQ messages and call billing interface.
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "bms.billing", name = "consumer-enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class BillingTaskMqConsumer
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(BillingTaskMqConsumer.class);
|
||||
|
||||
private final AtomicInteger workerSeq = new AtomicInteger();
|
||||
private volatile ExecutorService workerPool;
|
||||
|
||||
@Autowired
|
||||
private IPrcBillingTaskService billingTaskService;
|
||||
|
||||
@Autowired
|
||||
private BillingProperties billingProperties;
|
||||
|
||||
@Autowired(required = false)
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/** Local MQ mode: async event listener. */
|
||||
@Async
|
||||
@EventListener
|
||||
public void onLocalEvent(BillingTaskMqEvent event)
|
||||
{
|
||||
if (!isMode("local"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (event == null || event.getMessage() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
billingTaskService.processTask(event.getMessage().getTaskId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis MQ mode: pop a batch each tick and process concurrently.
|
||||
* Previously only 1 message / schedule cycle — queue backlog grew while inserts were fast.
|
||||
*/
|
||||
@Scheduled(fixedDelayString = "${bms.billing.consumer-delay-ms:200}")
|
||||
public void pollRedisQueue()
|
||||
{
|
||||
if (!isMode("redis"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (stringRedisTemplate == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
String key = billingProperties.getMqQueueKey();
|
||||
int batch = Math.max(1, billingProperties.getConsumerBatchSize());
|
||||
ExecutorService pool = workerPool();
|
||||
int submitted = 0;
|
||||
for (int i = 0; i < batch; i++)
|
||||
{
|
||||
String payload = stringRedisTemplate.opsForList().rightPop(key);
|
||||
if (payload == null || payload.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
final String body = payload;
|
||||
pool.execute(() -> handlePayload(body));
|
||||
submitted++;
|
||||
}
|
||||
if (submitted > 0 && log.isDebugEnabled())
|
||||
{
|
||||
log.debug("billing mq popped batchSize={}", submitted);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePayload(String payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
BillingTaskMessage msg = JSON.parseObject(payload, BillingTaskMessage.class);
|
||||
if (msg == null || msg.getTaskId() == null)
|
||||
{
|
||||
log.warn("invalid billing mq payload={}", payload);
|
||||
return;
|
||||
}
|
||||
billingTaskService.processTask(msg.getTaskId());
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
log.error("billing mq consume failed payload={}", payload, e);
|
||||
}
|
||||
}
|
||||
|
||||
private ExecutorService workerPool()
|
||||
{
|
||||
ExecutorService existing = workerPool;
|
||||
if (existing != null)
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
synchronized (this)
|
||||
{
|
||||
if (workerPool == null)
|
||||
{
|
||||
int threads = Math.max(1, billingProperties.getConsumerThreads());
|
||||
AtomicInteger seq = workerSeq;
|
||||
workerPool = new ThreadPoolExecutor(
|
||||
threads,
|
||||
threads,
|
||||
60L,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(threads * 4),
|
||||
r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("billing-mq-" + seq.incrementAndGet());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
},
|
||||
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
}
|
||||
return workerPool;
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown()
|
||||
{
|
||||
ExecutorService pool = workerPool;
|
||||
if (pool != null)
|
||||
{
|
||||
pool.shutdown();
|
||||
try
|
||||
{
|
||||
pool.awaitTermination(30, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMode(String expected)
|
||||
{
|
||||
String mode = billingProperties.getMqMode();
|
||||
if (StringUtils.isEmpty(mode))
|
||||
{
|
||||
mode = "redis";
|
||||
}
|
||||
return expected.equalsIgnoreCase(mode.trim());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.bms.price.mq;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class BillingTaskMqEvent extends ApplicationEvent
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final BillingTaskMessage message;
|
||||
|
||||
public BillingTaskMqEvent(Object source, BillingTaskMessage message)
|
||||
{
|
||||
super(source);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BillingTaskMessage getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.bms.price.mq;
|
||||
|
||||
/** Publish billing task id to MQ after warehouse inbound insert. */
|
||||
public interface IBillingTaskMqPublisher
|
||||
{
|
||||
void publish(BillingTaskMessage message);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.bms.price.mq;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Local MQ: Spring application event (dev / no Redis broker).
|
||||
* Enable with bms.billing.mq-mode=local
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "bms.billing", name = "mq-mode", havingValue = "local")
|
||||
public class LocalBillingTaskMqPublisher implements IBillingTaskMqPublisher
|
||||
{
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public LocalBillingTaskMqPublisher(ApplicationEventPublisher eventPublisher)
|
||||
{
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(BillingTaskMessage message)
|
||||
{
|
||||
eventPublisher.publishEvent(new BillingTaskMqEvent(this, message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.bms.price.mq;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.bms.price.config.BillingProperties;
|
||||
|
||||
/**
|
||||
* Redis list as MQ queue (default). LPUSH on publish; consumer BRPOP/RPOP.
|
||||
* Enable with bms.billing.mq-mode=redis (default).
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "bms.billing", name = "mq-mode", havingValue = "redis", matchIfMissing = true)
|
||||
public class RedisBillingTaskMqPublisher implements IBillingTaskMqPublisher
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(RedisBillingTaskMqPublisher.class);
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
private final BillingProperties billingProperties;
|
||||
|
||||
public RedisBillingTaskMqPublisher(StringRedisTemplate stringRedisTemplate, BillingProperties billingProperties)
|
||||
{
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
this.billingProperties = billingProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publish(BillingTaskMessage message)
|
||||
{
|
||||
String key = billingProperties.getMqQueueKey();
|
||||
String payload = JSON.toJSONString(message);
|
||||
stringRedisTemplate.opsForList().leftPush(key, payload);
|
||||
log.info("billing task mq published taskId={} queue={}", message.getTaskId(), key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user