feat:重构"食堂菜品购买统计"+"食堂菜品部门统计"菜单统计预约核销订单+线下购买订单

feat:增加机器的每日菜谱库存扣款
feat:增加订单"退回到核销前"按钮
This commit is contained in:
dzc committed 2026-08-24 18:39:59 +08:00
1 parent 8d31f20cec
commit d1cd86dfa4
92 files changed
+9628 -454

No files matched your search

@@ -0,0 +1,96 @@
package com.bsd.pjcloud.daemon.quartz.config;
import com.bsd.pjcloud.daemon.quartz.job.ThirdPaymentDispatchJob;
import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPaymentTaskTypeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* 代码托管的支付任务触发器,不受后台 {@code sys_job} 增删、暂停或频率修改影响。
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class ThirdPaymentQuartzInitializer implements InitializingBean {
/** 支付固定任务所属 Quartz 分组。 */
private static final String GROUP = "THIRD_PAYMENT_SYSTEM";
/** 每轮默认最大领取数量。 */
private static final int DEFAULT_BATCH_SIZE = 100;
/** Quartz调度器。 */
private final Scheduler scheduler;
/**
* 支付任务与固定触发间隔;同一 JobKey 在 Quartz 集群中同一时刻只会由一个节点执行。
*/
private static final List<PaymentSchedule> SCHEDULES = List.of(
new PaymentSchedule(ThirdPaymentTaskTypeEnum.PAY_CREATE, 5),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.PAY_QUERY, 5),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.REFUND_CREATE, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.REFUND_QUERY, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.FZ_CREATE, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.FZ_SETTLE_CONFIRM, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.FZ_ROYALTY, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.FZ_ROYALTY_QUERY, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.FZ_QUERY, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.FZ_REFUND_QUERY, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.BIZ_CALLBACK, 10),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.AGGREGATE_SESSION, 5),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.PAY_CLOSE, 300),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.AGGREGATE_CLOSE, 300),
new PaymentSchedule(ThirdPaymentTaskTypeEnum.RECONCILE, 300));
/**
* 幂等注册固定任务。多副本同时启动时,Quartz数据库唯一键裁决创建竞争。
* @throws SchedulerException Quartz持久化失败
*/
@Override
public void afterPropertiesSet() throws SchedulerException {
for (PaymentSchedule schedule : SCHEDULES) {
register(schedule);
}
}
/**
* 创建或覆盖一个代码托管 Job,并重置其固定频率。
* @param schedule 任务类型和秒级间隔
* @throws SchedulerException Quartz持久化失败
*/
private void register(PaymentSchedule schedule) throws SchedulerException {
String name = "third-payment-" + schedule.taskType().getCode().toLowerCase().replace('_', '-');
JobKey jobKey = JobKey.jobKey(name, GROUP);
TriggerKey triggerKey = TriggerKey.triggerKey(name + "-trigger", GROUP);
JobDetail job = JobBuilder.newJob(ThirdPaymentDispatchJob.class).withIdentity(jobKey)
.usingJobData(ThirdPaymentDispatchJob.TASK_TYPE_KEY, schedule.taskType().getCode())
.usingJobData(ThirdPaymentDispatchJob.BATCH_SIZE_KEY, DEFAULT_BATCH_SIZE)
.requestRecovery(true).storeDurably(true).build();
SimpleScheduleBuilder simpleSchedule = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(schedule.intervalSeconds()).repeatForever()
.withMisfireHandlingInstructionNextWithRemainingCount();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).forJob(jobKey)
.startNow().withSchedule(simpleSchedule).build();
try {
scheduler.addJob(job, true);
if (scheduler.checkExists(triggerKey)) {
scheduler.rescheduleJob(triggerKey, trigger);
} else {
scheduler.scheduleJob(trigger);
}
} catch (ObjectAlreadyExistsException exception) {
// 多节点同时首次部署时另一个实例已完成注册,读取共享QRTZ表即可。
log.info("支付Quartz任务已由其他集群节点初始化,jobKey:{}", jobKey);
}
}
/**
* 固定任务频率配置。
* @param taskType 支付任务类型
* @param intervalSeconds 触发间隔,单位秒
*/
private record PaymentSchedule(ThirdPaymentTaskTypeEnum taskType, int intervalSeconds) {
}
}
@@ -0,0 +1,58 @@
package com.bsd.pjcloud.daemon.quartz.job;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.thirdparty.api.feign.RemoteThirdPaymentTaskService;
import com.bsd.pjcloud.thirdparty.dto.pay.ThirdPaymentTaskDispatchDto;
import com.bsd.pjcloud.thirdparty.vo.pay.ThirdPaymentTaskDispatchVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.LocalDateTime;
/**
* 支付任务固定 Quartz Job,只同步确认 thirdparty 接受分发,不直接访问支付 SDK。
*/
@Slf4j
@DisallowConcurrentExecution
public class ThirdPaymentDispatchJob implements Job {
/** JobDataMap中的任务类型键。 */
public static final String TASK_TYPE_KEY = "taskType";
/** JobDataMap中的批量大小键。 */
public static final String BATCH_SIZE_KEY = "batchSize";
/** thirdparty内部任务分发 Feign 客户端,由 Quartz JobFactory 注入。 */
@Autowired
private RemoteThirdPaymentTaskService paymentTaskService;
/**
* 调用服务发现后的 thirdparty 内部接口;业务响应失败会让 Quartz 记录本次执行失败。
* @param context Quartz触发上下文
* @throws JobExecutionException thirdparty未接受或调用异常
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String taskType = context.getMergedJobDataMap().getString(TASK_TYPE_KEY);
int batchSize = context.getMergedJobDataMap().getIntValue(BATCH_SIZE_KEY);
ThirdPaymentTaskDispatchDto dto = new ThirdPaymentTaskDispatchDto();
dto.setTaskType(taskType);
dto.setBatchSize(batchSize);
dto.setTriggerSource("QUARTZ");
dto.setTriggerTime(LocalDateTime.now());
try {
R<ThirdPaymentTaskDispatchVo> result = paymentTaskService.dispatch(dto);
if (result == null || !result.isOk() || result.getData() == null) {
throw new JobExecutionException("thirdparty未接受支付任务分发:" + taskType);
}
ThirdPaymentTaskDispatchVo data = result.getData();
if (data.isBusy() || data.getBacklogCount() > 0) {
log.warn("支付任务分发存在积压,taskType:{},claimed:{},backlog:{},busy:{}",
taskType, data.getClaimedCount(), data.getBacklogCount(), data.isBusy());
}
} catch (JobExecutionException exception) {
throw exception;
} catch (Exception exception) {
throw new JobExecutionException("调用thirdparty支付任务分发失败:" + taskType, exception, false);
}
}
}
@@ -11,12 +11,14 @@ spring:
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore # 数据库存储
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 数据库代理
tablePrefix: QRTZ_ # 表前缀
isClustered: false # 集群
clusterCheckinInterval: 30000 # 集群检查间隔
isClustered: true # 支付任务只由Quartz集群触发,生产实例必须共享同一套QRTZ表
clusterCheckinInterval: 10000 # 集群心跳,节点失联后由存活实例接管
misfireThreshold: 60000 # 允许一分钟短暂抖动,恢复后按misfire策略继续触发
acquireTriggersWithinLock: true # 多节点批量获取Trigger时在数据库锁内完成
useProperties: false
threadPool:
class: org.quartz.simpl.SimpleThreadPool # 线程池
threadCount: 3 # 线程数量
threadCount: 8 # 支付固定触发器与既有通用任务共享,外部支付调用不在Quartz线程执行
threadPriority: 5 # 线程优先级
threadsInheritContextClassLoaderOfInitializingThread: true # 是否继承类加载器
job-store-type: jdbc # 持久化到数据库
@@ -0,0 +1,59 @@
package com.bsd.pjcloud.daemon.quartz.config;
import com.bsd.pjcloud.daemon.quartz.job.ThirdPaymentDispatchJob;
import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPaymentTaskTypeEnum;
import org.junit.jupiter.api.Test;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
/**
* 支付固定 Quartz 触发器初始化测试,验证服务重复启动不会产生重复调度记录。
*/
class ThirdPaymentQuartzInitializerTest {
/**
* 使用内存 JobStore 连续初始化两次,确认 Job、Trigger、任务参数和固定频率均保持稳定。
*
* @throws Exception Quartz 调度器创建、查询或关闭失败
*/
@Test
void shouldRegisterManagedPaymentSchedulesIdempotently() throws Exception {
Properties properties = new Properties();
properties.setProperty("org.quartz.scheduler.instanceName", "thirdPaymentInitializerTest");
properties.setProperty("org.quartz.threadPool.threadCount", "1");
properties.setProperty("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
Scheduler scheduler = new StdSchedulerFactory(properties).getScheduler();
try {
ThirdPaymentQuartzInitializer initializer = new ThirdPaymentQuartzInitializer(scheduler);
initializer.afterPropertiesSet();
initializer.afterPropertiesSet();
String group = "THIRD_PAYMENT_SYSTEM";
assertThat(scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group))).hasSize(15);
assertThat(scheduler.getTriggerKeys(GroupMatcher.triggerGroupEquals(group))).hasSize(15);
JobKey jobKey = JobKey.jobKey("third-payment-pay-create", group);
assertThat(scheduler.getJobDetail(jobKey).getJobClass()).isEqualTo(ThirdPaymentDispatchJob.class);
assertThat(scheduler.getJobDetail(jobKey).getJobDataMap().getString(ThirdPaymentDispatchJob.TASK_TYPE_KEY))
.isEqualTo(ThirdPaymentTaskTypeEnum.PAY_CREATE.getCode());
assertThat(scheduler.getJobDetail(jobKey).getJobDataMap().getInt(ThirdPaymentDispatchJob.BATCH_SIZE_KEY))
.isEqualTo(100);
TriggerKey triggerKey = TriggerKey.triggerKey("third-payment-pay-create-trigger", group);
Trigger trigger = scheduler.getTrigger(triggerKey);
assertThat(trigger).isInstanceOf(SimpleTrigger.class);
assertThat(((SimpleTrigger) trigger).getRepeatInterval()).isEqualTo(5_000L);
} finally {
scheduler.shutdown(true);
}
}
}