本文参考自
本文是仿12306项目实战第(二)章——项目实现 的第四篇,本篇讲解该项目中关于调度框架quartz的使用,以及通过火车基础数据生成每日火车数据的实现过程
一、定时任务介绍
- 为什么要有定时任务
-
本项目定时任务
- 集成quartz(为什么不用SpringBoot自带定时任务,区别?)
- 使用控台来操作定时任务:新增、暂停、重启、删除
二、项目增加batch定时任务调度模块
-
新增batch模块
操作同之前增加business模块
-
代码展示
-
pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>train</artifactId> <groupId>com.neilxu</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>batch</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.neilxu</groupId> <artifactId>common</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
TestController.java
package com.neilxu.train.batch.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @GetMapping("/hello") public String hello() { return "Hello World! Batch!"; } }
-
application.properties
server.port=8003 server.servlet.context-path=/batch # 数据库连接 spring.datasource.url=jdbc:mysql://localhost/train_batch?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai spring.datasource.username=train_batch spring.datasource.password=train_batch spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # mybatis xml路径 mybatis.mapper-locations=classpath:/mapper/**/*.xml logging.level.com.neilxu.train.batch.mapper=trace
-
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 修改一下路径--> <property name="PATH" value="./log/batch"></property> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>--> <Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern> </encoder> </appender> <appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${PATH}/trace.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <layout> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern> </layout> </appender> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${PATH}/error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>10MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <layout> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern> </layout> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <root level="ERROR"> <appender-ref ref="ERROR_FILE" /> </root> <root level="TRACE"> <appender-ref ref="TRACE_FILE" /> </root> <root level="INFO"> <appender-ref ref="STDOUT" /> </root> </configuration>
-
BatchApplication.java
package com.neilxu.train.batch.config; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.core.env.Environment; @SpringBootApplication @ComponentScan("com.neilxu") @MapperScan("com.neilxu.train.*.mapper") public class BatchApplication { private static final Logger LOG = LoggerFactory.getLogger(BatchApplication.class); public static void main(String[] args) { SpringApplication app = new SpringApplication(BatchApplication.class); Environment env = app.run(args).getEnvironment(); LOG.info("启动成功!!"); LOG.info("测试地址: \thttp://127.0.0.1:{}{}/hello", env.getProperty("server.port"), env.getProperty("server.servlet.context-path")); } }
-
gateway 的application.properties
# 路由转发,将/batch/...的请求转发了batch模块 spring.cloud.gateway.routes[2].id=batch spring.cloud.gateway.routes[2].uri=http://127.0.0.1:8003 #spring.cloud.gateway.routes[2].uri=lb://batch spring.cloud.gateway.routes[2].predicates[0]=Path=/batch/**
-
batch-test.http
GET http://localhost:8003/batch/hello Accept: application/json ### GET http://localhost:8000/batch/hello Accept: application/json ###
-
-
测试
三、为batch模块配置持久层生成器
1.新建数据库,新建用户,新建连接
操作同之前一样
2.新建表测试生成代码
同之前,用member表测试生成代码
-
batch.sql
drop table if exists `member`; create table `member` ( `id` bigint not null comment 'id', `mobile` varchar(11) comment '手机号', primary key (`id`), unique key `mobile_unique` (`mobile`) ) engine=innodb default charset=utf8mb4 comment='会员';
-
generator-config-batch.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat"> <!-- 自动检查关键字,为关键字增加反引号 --> <property name="autoDelimitKeywords" value="true"/> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <!--覆盖生成XML文件--> <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" /> <!-- 生成的实体类添加toString()方法 --> <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/> <!-- 不生成注释 --> <commentGenerator> <property name="suppressAllComments" value="true"/> </commentGenerator> <!-- 配置数据源,需要根据自己的项目修改 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost/train_batch?serverTimezone=Asia/Shanghai" userId="train_batch" password="train_batch"> </jdbcConnection> <!-- domain类的位置 targetProject是相对pom.xml的路径--> <javaModelGenerator targetProject="..\batch\src\main\java" targetPackage="com.neilxu.train.batch.domain"/> <!-- mapper xml的位置 targetProject是相对pom.xml的路径 --> <sqlMapGenerator targetProject="..\batch\src\main\resources" targetPackage="mapper"/> <!-- mapper类的位置 targetProject是相对pom.xml的路径 --> <javaClientGenerator targetProject="..\batch\src\main\java" targetPackage="com.neilxu.train.batch.mapper" type="XMLMAPPER"/> <table tableName="member" domainObjectName="Member"/> </context> </generatorConfiguration>
-
generator/pom.xml
<!--<configurationFile>src/main/resources/generator-config-member.xml</configurationFile>--> <!-- <configurationFile>src/main/resources/generator-config-business.xml</configurationFile>--> <configurationFile>src/main/resources/generator-config-batch.xml</configurationFile>
-
测试生成
没有问题
四、演示SpringBoot自带的定时任务
1.cron表达式
在线Cron表达式生成器:https://www.matools.com/cron/
定时任务三大要素:
- 执行的内容:功能逻辑
- 执行的策略:cron表达式
- 开关:开启定时任务
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:
(1) Seconds Minutes Hours DayofMonth Month DayofWeek Year
(2)Seconds Minutes Hours DayofMonth Month DayofWeekcron从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份
举例说明:
下图是2024/3/17 生成
0 0 1 1/5 * ? * 或者 0 0 1 1/5 * ?
含义:每一年每个月,从1号开始每5天执行一次,执行时间是01:00:00
2.springboot自带定时任务
-
SpringBootTestJob.java
package com.neilxu.train.batch.job; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * 适合单体应用,不适合集群 * 没法实时更改定时任务状态和策略 */ @Component @EnableScheduling public class SpringBootTestJob { @Scheduled(cron = "0/5 * * * * ?") private void test() { // 增加分布式锁,解决集群问题 System.out.println("SpringBootTestJob TEST"); } }
-
效果
-
问题
- 在不增加分布式锁的情况下,集群部署时,容易造成多个应用同时都跑了任务
- 不能实时更新定时任务状态和策略
五、定时调度模块集成quartz
1.引入依赖
batch的 pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2.编写TestJob.java
package com.neilxu.train.batch.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
public class TestJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("TestJob TEST");
}
}
3.编写QuartzConfig.java
package com.neilxu.train.batch.config;
import com.neilxu.train.batch.job.TestJob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
/**
* 声明一个任务
* @return
*/
@Bean
public JobDetail jobDetail() {
return JobBuilder.newJob(TestJob.class)
.withIdentity("TestJob", "test")
.storeDurably()
.build();
}
/**
* 声明一个触发器,什么时候触发这个任务
* @return
*/
@Bean
public Trigger trigger() {
return TriggerBuilder.newTrigger()
.forJob(jobDetail())
.withIdentity("trigger", "trigger")
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("*/2 * * * * ?"))
.build();
}
}
4.测试
重启
定时任务成功
这里我把之前springboot的定时给注释掉了,其实他们两个是可以同时进行的,但是实际项目中一般只选一种定时框架
六、关于调度任务的并发执行
并发执行:上一周期还没执行完,下一周期又开始了
使用@DisallowConcurrentExecution禁用并发执行
-
禁止任务并发执行
TestJob.java
package com.neilxu.train.batch.job; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @DisallowConcurrentExecution public class TestJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("TestJob TEST开始"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("TestJob TEST结束"); } }
-
测试
不会出现任务没执行完,就开始下个任务的情况了
七、使用数据库配置quartz调度任务
1.增加quartz相关的12张表
-
batch.sql
# # Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar # # PLEASE consider using mysql with innodb tables to avoid locking issues # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', JOB_NAME VARCHAR(200) NOT NULL comment 'job名称', JOB_GROUP VARCHAR(200) NOT NULL comment 'job组', DESCRIPTION VARCHAR(250) NULL comment '描述', JOB_CLASS_NAME VARCHAR(250) NOT NULL comment 'job类名', IS_DURABLE VARCHAR(1) NOT NULL comment '是否持久化', IS_NONCONCURRENT VARCHAR(1) NOT NULL comment '是否非同步', IS_UPDATE_DATA VARCHAR(1) NOT NULL comment '是否更新数据', REQUESTS_RECOVERY VARCHAR(1) NOT NULL comment '请求是否覆盖', JOB_DATA BLOB NULL comment 'job数据', PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称', TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组', JOB_NAME VARCHAR(200) NOT NULL comment 'job名称', JOB_GROUP VARCHAR(200) NOT NULL comment 'job组', DESCRIPTION VARCHAR(250) NULL comment '描述', NEXT_FIRE_TIME BIGINT(13) NULL comment '下一次触发时间', PREV_FIRE_TIME BIGINT(13) NULL comment '前一次触发时间', PRIORITY INTEGER NULL comment '等级', TRIGGER_STATE VARCHAR(16) NOT NULL comment '触发状态', TRIGGER_TYPE VARCHAR(8) NOT NULL comment '触发类型', START_TIME BIGINT(13) NOT NULL comment '开始时间', END_TIME BIGINT(13) NULL comment '结束时间', CALENDAR_NAME VARCHAR(200) NULL comment '日程名称', MISFIRE_INSTR SMALLINT(2) NULL comment '未触发实例', JOB_DATA BLOB NULL comment 'job数据', PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称', TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组', REPEAT_COUNT BIGINT(7) NOT NULL comment '重复执行次数', REPEAT_INTERVAL BIGINT(12) NOT NULL comment '重复执行间隔', TIMES_TRIGGERED BIGINT(10) NOT NULL comment '已经触发次数', PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称', TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组', CRON_EXPRESSION VARCHAR(200) NOT NULL comment 'cron表达式', TIME_ZONE_ID VARCHAR(80) comment '时区', PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称', TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组', STR_PROP_1 VARCHAR(512) NULL comment '开始配置1', STR_PROP_2 VARCHAR(512) NULL comment '开始配置2', STR_PROP_3 VARCHAR(512) NULL comment '开始配置3', INT_PROP_1 INT NULL comment 'int配置1', INT_PROP_2 INT NULL comment 'int配置2', LONG_PROP_1 BIGINT NULL comment 'long配置1', LONG_PROP_2 BIGINT NULL comment 'long配置2', DEC_PROP_1 NUMERIC(13,4) NULL comment '配置描述1', DEC_PROP_2 NUMERIC(13,4) NULL comment '配置描述2', BOOL_PROP_1 VARCHAR(1) NULL comment 'bool配置1', BOOL_PROP_2 VARCHAR(1) NULL comment 'bool配置2', PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称', TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组', BLOB_DATA BLOB NULL comment '数据', PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', CALENDAR_NAME VARCHAR(200) NOT NULL comment '日程名称', CALENDAR BLOB NOT NULL comment '日程数据', PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组', PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', ENTRY_ID VARCHAR(95) NOT NULL comment 'entryId', TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称', TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组', INSTANCE_NAME VARCHAR(200) NOT NULL comment '实例名称', FIRED_TIME BIGINT(13) NOT NULL comment '执行时间', SCHED_TIME BIGINT(13) NOT NULL comment '定时任务时间', PRIORITY INTEGER NOT NULL comment '等级', STATE VARCHAR(16) NOT NULL comment '状态', JOB_NAME VARCHAR(200) NULL comment 'job名称', JOB_GROUP VARCHAR(200) NULL comment 'job组', IS_NONCONCURRENT VARCHAR(1) NULL comment '是否异步', REQUESTS_RECOVERY VARCHAR(1) NULL comment '是否请求覆盖', PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', INSTANCE_NAME VARCHAR(200) NOT NULL comment '实例名称', LAST_CHECKIN_TIME BIGINT(13) NOT NULL comment '最近检入时间', CHECKIN_INTERVAL BIGINT(13) NOT NULL comment '检入间隔', PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称', LOCK_NAME VARCHAR(40) NOT NULL comment 'lock名称', PRIMARY KEY (SCHED_NAME,LOCK_NAME) );
-
注释原来的QuartzConfig.java
注释掉QuartzConfig.java,使用数据库方式来配置quartz
-
新增MyJobFactory.java和SchedulerConfig.java
固定做法
-
MyJobFactory.java
package com.neilxu.train.batch.config; import jakarta.annotation.Resource; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.SpringBeanJobFactory; import org.springframework.stereotype.Component; @Component public class MyJobFactory extends SpringBeanJobFactory { @Resource private AutowireCapableBeanFactory beanFactory; /** * 这里覆盖了super的createJobInstance方法,对其创建出来的类再进行autowire。 */ @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Object jobInstance = super.createJobInstance(bundle); beanFactory.autowireBean(jobInstance); return jobInstance; } }
-
SchedulerConfig.java
package com.neilxu.train.batch.config; import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import javax.sql.DataSource; import java.io.IOException; @Configuration public class SchedulerConfig { @Resource private MyJobFactory myJobFactory; @Bean public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("dataSource") DataSource dataSource) throws IOException { SchedulerFactoryBean factory = new SchedulerFactoryBean(); factory.setDataSource(dataSource); factory.setJobFactory(myJobFactory); factory.setStartupDelay(2); return factory; } }
-
2.增加接口来操作quartz
-
CronJobReq.java
package com.neilxu.train.batch.req; public class CronJobReq { private String group; private String name; private String description; private String cronExpression; @Override public String toString() { final StringBuffer sb = new StringBuffer("CronJobDto{"); sb.append("cronExpression='").append(cronExpression).append('\''); sb.append(", group='").append(group).append('\''); sb.append(", name='").append(name).append('\''); sb.append(", description='").append(description).append('\''); sb.append('}'); return sb.toString(); } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
CronJobResp.java
package com.neilxu.train.batch.resp; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import java.util.Date; @JsonInclude(JsonInclude.Include.NON_EMPTY) public class CronJobResp { private String group; private String name; private String description; private String state; private String cronExpression; @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date nextFireTime; @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date preFireTime; @Override public String toString() { final StringBuffer sb = new StringBuffer("CronJobDto{"); sb.append("cronExpression='").append(cronExpression).append('\''); sb.append(", group='").append(group).append('\''); sb.append(", name='").append(name).append('\''); sb.append(", description='").append(description).append('\''); sb.append(", state='").append(state).append('\''); sb.append(", nextFireTime=").append(nextFireTime); sb.append(", preFireTime=").append(preFireTime); sb.append('}'); return sb.toString(); } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getNextFireTime() { return nextFireTime; } public void setNextFireTime(Date nextFireTime) { this.nextFireTime = nextFireTime; } public Date getPreFireTime() { return preFireTime; } public void setPreFireTime(Date preFireTime) { this.preFireTime = preFireTime; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
-
JobController.java
package com.neilxu.train.batch.controller; import com.neilxu.train.batch.req.CronJobReq; import com.neilxu.train.batch.resp.CronJobResp; import com.neilxu.train.common.resp.CommonResp; import org.quartz.*; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.triggers.CronTriggerImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Date; import java.util.List; @RestController @RequestMapping(value = "/admin/job") public class JobController { private static Logger LOG = LoggerFactory.getLogger(JobController.class); @Autowired private SchedulerFactoryBean schedulerFactoryBean; @RequestMapping(value = "/add") public CommonResp add(@RequestBody CronJobReq cronJobReq) { String jobClassName = cronJobReq.getName(); String jobGroupName = cronJobReq.getGroup(); String cronExpression = cronJobReq.getCronExpression(); String description = cronJobReq.getDescription(); LOG.info("创建定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description); CommonResp commonResp = new CommonResp(); try { // 通过SchedulerFactory获取一个调度器实例 Scheduler sched = schedulerFactoryBean.getScheduler(); // 启动调度器 sched.start(); //构建job信息 JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobClassName)).withIdentity(jobClassName, jobGroupName).build(); //表达式调度构建器(即任务执行的时间) CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); //按新的cronExpression表达式构建一个新的trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName).withDescription(description).withSchedule(scheduleBuilder).build(); sched.scheduleJob(jobDetail, trigger); } catch (SchedulerException e) { LOG.error("创建定时任务失败:" + e); commonResp.setSuccess(false); commonResp.setMessage("创建定时任务失败:调度异常"); } catch (ClassNotFoundException e) { LOG.error("创建定时任务失败:" + e); commonResp.setSuccess(false); commonResp.setMessage("创建定时任务失败:任务类不存在"); } LOG.info("创建定时任务结束:{}", commonResp); return commonResp; } @RequestMapping(value = "/pause") public CommonResp pause(@RequestBody CronJobReq cronJobReq) { String jobClassName = cronJobReq.getName(); String jobGroupName = cronJobReq.getGroup(); LOG.info("暂停定时任务开始:{},{}", jobClassName, jobGroupName); CommonResp commonResp = new CommonResp(); try { Scheduler sched = schedulerFactoryBean.getScheduler(); sched.pauseJob(JobKey.jobKey(jobClassName, jobGroupName)); } catch (SchedulerException e) { LOG.error("暂停定时任务失败:" + e); commonResp.setSuccess(false); commonResp.setMessage("暂停定时任务失败:调度异常"); } LOG.info("暂停定时任务结束:{}", commonResp); return commonResp; } @RequestMapping(value = "/resume") public CommonResp resume(@RequestBody CronJobReq cronJobReq) { String jobClassName = cronJobReq.getName(); String jobGroupName = cronJobReq.getGroup(); LOG.info("重启定时任务开始:{},{}", jobClassName, jobGroupName); CommonResp commonResp = new CommonResp(); try { Scheduler sched = schedulerFactoryBean.getScheduler(); sched.resumeJob(JobKey.jobKey(jobClassName, jobGroupName)); } catch (SchedulerException e) { LOG.error("重启定时任务失败:" + e); commonResp.setSuccess(false); commonResp.setMessage("重启定时任务失败:调度异常"); } LOG.info("重启定时任务结束:{}", commonResp); return commonResp; } @RequestMapping(value = "/reschedule") public CommonResp reschedule(@RequestBody CronJobReq cronJobReq) { String jobClassName = cronJobReq.getName(); String jobGroupName = cronJobReq.getGroup(); String cronExpression = cronJobReq.getCronExpression(); String description = cronJobReq.getDescription(); LOG.info("更新定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description); CommonResp commonResp = new CommonResp(); try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName); // 表达式调度构建器 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression); CronTriggerImpl trigger1 = (CronTriggerImpl) scheduler.getTrigger(triggerKey); trigger1.setStartTime(new Date()); // 重新设置开始时间 CronTrigger trigger = trigger1; // 按新的cronExpression表达式重新构建trigger trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withDescription(description).withSchedule(scheduleBuilder).build(); // 按新的trigger重新设置job执行 scheduler.rescheduleJob(triggerKey, trigger); } catch (Exception e) { LOG.error("更新定时任务失败:" + e); commonResp.setSuccess(false); commonResp.setMessage("更新定时任务失败:调度异常"); } LOG.info("更新定时任务结束:{}", commonResp); return commonResp; } @RequestMapping(value = "/delete") public CommonResp delete(@RequestBody CronJobReq cronJobReq) { String jobClassName = cronJobReq.getName(); String jobGroupName = cronJobReq.getGroup(); LOG.info("删除定时任务开始:{},{}", jobClassName, jobGroupName); CommonResp commonResp = new CommonResp(); try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName)); scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName)); scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName)); } catch (SchedulerException e) { LOG.error("删除定时任务失败:" + e); commonResp.setSuccess(false); commonResp.setMessage("删除定时任务失败:调度异常"); } LOG.info("删除定时任务结束:{}", commonResp); return commonResp; } @RequestMapping(value="/query") public CommonResp query() { LOG.info("查看所有定时任务开始"); CommonResp commonResp = new CommonResp(); List<CronJobResp> cronJobDtoList = new ArrayList(); try { Scheduler scheduler = schedulerFactoryBean.getScheduler(); for (String groupName : scheduler.getJobGroupNames()) { for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) { CronJobResp cronJobResp = new CronJobResp(); cronJobResp.setName(jobKey.getName()); cronJobResp.setGroup(jobKey.getGroup()); //get job's trigger List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey); CronTrigger cronTrigger = (CronTrigger) triggers.get(0); cronJobResp.setNextFireTime(cronTrigger.getNextFireTime()); cronJobResp.setPreFireTime(cronTrigger.getPreviousFireTime()); cronJobResp.setCronExpression(cronTrigger.getCronExpression()); cronJobResp.setDescription(cronTrigger.getDescription()); Trigger.TriggerState triggerState = scheduler.getTriggerState(cronTrigger.getKey()); cronJobResp.setState(triggerState.name()); cronJobDtoList.add(cronJobResp); } } } catch (SchedulerException e) { LOG.error("查看定时任务失败:" + e); commonResp.setSuccess(false); commonResp.setMessage("查看定时任务失败:调度异常"); } commonResp.setContent(cronJobDtoList); LOG.info("查看定时任务结束:{}", commonResp); return commonResp; } }
3.测试
http/batch-job.http
POST http://localhost:8000/batch/admin/job/add
Content-Type: application/json
{
"name": "com.neilxu.train.batch.job.TestJob",
"jobGroupName": "default",
"cronExpression": "*/2 * * * * ?",
"desc": "test job"
}
###
GET http://localhost:8000/batch/admin/job/query
###
POST http://localhost:8000/batch/admin/job/pause
Content-Type: application/json
{
"name": "com.neilxu.train.batch.job.TestJob",
"jobGroupName": "default"
}
###
POST http://localhost:8000/batch/admin/job/resume
Content-Type: application/json
{
"name": "com.neilxu.train.batch.job.TestJob",
"jobGroupName": "default"
}
###
POST http://localhost:8000/batch/admin/job/reschedule
Content-Type: application/json
{
"name": "com.neilxu.train.batch.job.TestJob",
"jobGroupName": "default",
"cronExpression": "*/5 * * * * ?",
"desc": "test job"
}
###
POST http://localhost:8000/batch/admin/job/delete
Content-Type: application/json
{
"name": "com.neilxu.train.batch.job.TestJob",
"jobGroupName": "default"
}
依次测试,结果正常
八、通过控台界面操作定时任务
1.新增控台页面
-
job.vue
<template> <div class="job"> <p> <a-button type="primary" @click="handleAdd()"> 新增 </a-button> <a-button type="primary" @click="handleQuery()"> 刷新 </a-button> </p> <a-table :dataSource="jobs" :columns="columns" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="确定重启?" ok-text="是" cancel-text="否" @confirm="handleResume(record)" > <a-button v-show="record.state === 'PAUSED' || record.state === 'ERROR'" type="primary" size="small"> 重启 </a-button> </a-popconfirm> <a-popconfirm title="确定暂停?" ok-text="是" cancel-text="否" @confirm="handlePause(record)" > <a-button v-show="record.state === 'NORMAL' || record.state === 'BLOCKED'" type="primary" size="small"> 暂停 </a-button> </a-popconfirm> <a-button type="primary" @click="handleEdit(record)" size="small"> 编辑 </a-button> <a-popconfirm title="删除后不可恢复,确认删除?" ok-text="是" cancel-text="否" @confirm="handleDelete(record)" > <a-button type="danger" size="small"> 删除 </a-button> </a-popconfirm> </a-space> </template> </template> </a-table> <a-modal title="用户" v-model:visible="modalVisible" :confirm-loading="modalLoading" @ok="handleModalOk" > <a-form :model="job" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }"> <a-form-item label="类名"> <a-input v-model:value="job.name" /> </a-form-item> <a-form-item label="描述"> <a-input v-model:value="job.description" /> </a-form-item> <a-form-item label="分组"> <a-input v-model:value="job.group" :disabled="!!job.state"/> </a-form-item> <a-form-item label="表达式"> <a-input v-model:value="job.cronExpression" /> <div class="ant-alert ant-alert-success"> 每5秒执行一次:0/5 * * * * ? <br> 每5分钟执行一次:* 0/5 * * * ? </div> </a-form-item> </a-form> </a-modal> </div> </template> <script> import { defineComponent, onMounted, ref } from 'vue'; import axios from 'axios'; import { notification } from 'ant-design-vue'; export default defineComponent({ name: 'batch-job-view', setup () { const jobs = ref(); const loading = ref(); const columns = [{ title: '分组', dataIndex: 'group', }, { title: '类名', dataIndex: 'name', }, { title: '描述', dataIndex: 'description', }, { title: '状态', dataIndex: 'state', }, { title: '表达式', dataIndex: 'cronExpression', }, { title: '上次时间', dataIndex: 'preFireTime', }, { title: '下次时间', dataIndex: 'nextFireTime', }, { title: '操作', dataIndex: 'operation' }]; const handleQuery = () => { loading.value = true; jobs.value = []; axios.get('/batch/admin/job/query').then((response) => { loading.value = false; const data = response.data; if (data.success) { jobs.value = data.content; } else { notification.error({description: data.message}); } }); }; // -------- 表单 --------- const job = ref(); job.value = {}; const modalVisible = ref(false); const modalLoading = ref(false); const handleModalOk = () => { modalLoading.value = true; let url = "add"; if (job.value.state) { url = "reschedule"; } axios.post('/batch/admin/job/' + url, job.value).then((response) => { modalLoading.value = false; const data = response.data; if (data.success) { modalVisible.value = false; notification.success({description: "保存成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; /** * 新增 */ const handleAdd = () => { modalVisible.value = true; job.value = { group: 'DEFAULT' }; }; /** * 编辑 */ const handleEdit = (record) => { modalVisible.value = true; job.value = Tool.copy(record); }; /** * 删除 */ const handleDelete = (record) => { axios.post('/batch/admin/job/delete', { name: record.name, group: record.group }).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; /** * 暂停 */ const handlePause = (record) => { axios.post('/batch/admin/job/pause', { name: record.name, group: record.group }).then((response) => { const data = response.data; if (data.success) { notification.success({description: "暂停成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; /** * 重启 */ const handleResume = (record) => { axios.post('/batch/admin/job/reschedule', record).then((response) => { modalLoading.value = false; const data = response.data; if (data.success) { modalVisible.value = false; notification.success({description: "重启成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; const getEnumValue = (key, obj) => { return Tool.getEnumValue(key, obj); }; onMounted(() => { console.log('index mounted!'); handleQuery(); }); return { columns, jobs, loading, handleQuery, handleAdd, handleEdit, handleDelete, handleResume, handlePause, job, modalVisible, modalLoading, handleModalOk, getEnumValue }; } }) </script> <style scoped> </style>
-
路由、侧边栏
import { createRouter, createWebHistory } from 'vue-router' const routes = [{ path: '/', component: () => import('../views/main.vue'), children: [{ path: 'welcome', component: () => import('../views/main/welcome.vue'), }, { path: 'about', component: () => import('../views/main/about.vue'), }, { path: 'station', component: () => import('../views/main/station.vue'), }, { path: 'train', component: () => import('../views/main/train.vue'), }, { path: 'train-station', component: () => import('../views/main/train-station.vue'), }, { path: 'train-carriage', component: () => import('../views/main/train-carriage.vue'), }, { path: 'train-seat', component: () => import('../views/main/train-seat.vue'), }, { path: 'batch/job', name: 'batch/job', component: () => import('../views/main/job.vue') }] }, { path: '', redirect: '/welcome' }]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
<template> <a-layout-sider width="200" style="background: #fff"> <a-menu v-model:selectedKeys="selectedKeys" mode="inline" :style="{ height: '100%', borderRight: 0 }" > <a-menu-item key="/welcome"> <router-link to="/welcome"> <coffee-outlined /> 欢迎 </router-link> </a-menu-item> <a-menu-item key="/about"> <router-link to="/about"> <user-outlined /> 关于 </router-link> </a-menu-item> <a-menu-item key="/batch/job"> <router-link to="/batch/job"> <MenuUnfoldOutlined /> 任务管理 </router-link> </a-menu-item> <a-menu-item key="/station"> <router-link to="/station"> <user-outlined /> 车站管理 </router-link> </a-menu-item> <a-menu-item key="/train"> <router-link to="/train"> <user-outlined /> 火车管理 </router-link> </a-menu-item> <a-menu-item key="/train-station"> <router-link to="/train-station"> <user-outlined /> 火车车站 </router-link> </a-menu-item> <a-menu-item key="/train-carriage"> <router-link to="/train-carriage"> <user-outlined /> 火车车厢 </router-link> </a-menu-item> <a-menu-item key="/train-seat"> <router-link to="/train-seat"> <user-outlined /> 火车座位 </router-link> </a-menu-item> </a-menu> </a-layout-sider> </template> <script> import {defineComponent, ref, watch} from 'vue'; import router from "@/router"; export default defineComponent({ name: "the-sider-view", setup() { const selectedKeys = ref([]); watch(() => router.currentRoute.value.path, (newValue) => { console.log('watch', newValue); selectedKeys.value = []; selectedKeys.value.push(newValue); }, {immediate: true}); return { selectedKeys }; }, }); </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
-
测试
2.对菜单作分类,改为两级菜单;路由也改为多级路由
-
the-sider.vue
<template> <a-layout-sider width="200" style="background: #fff"> <a-menu v-model:selectedKeys="selectedKeys" :openKeys="['batch', 'base']" mode="inline" :style="{ height: '100%', borderRight: 0 }" > <a-menu-item key="/welcome"> <router-link to="/welcome"> <coffee-outlined /> 欢迎 </router-link> </a-menu-item> <a-menu-item key="/about"> <router-link to="/about"> <user-outlined /> 关于 </router-link> </a-menu-item> <a-sub-menu key="batch"> <template #title> <span> <UnorderedListOutlined /> 跑批管理 </span> </template> <a-menu-item key="/batch/job"> <router-link to="/batch/job"> <MenuUnfoldOutlined /> 任务管理 </router-link> </a-menu-item> </a-sub-menu> <a-sub-menu key="base"> <template #title> <span> <UnorderedListOutlined /> 基础数据 </span> </template> <a-menu-item key="/base/station"> <router-link to="/base/station"> <user-outlined /> 车站管理 </router-link> </a-menu-item> <a-menu-item key="/base/train"> <router-link to="/base/train"> <user-outlined /> 火车管理 </router-link> </a-menu-item> <a-menu-item key="/base/train-station"> <router-link to="/base/train-station"> <user-outlined /> 火车车站 </router-link> </a-menu-item> <a-menu-item key="/base/train-carriage"> <router-link to="/base/train-carriage"> <user-outlined /> 火车车厢 </router-link> </a-menu-item> <a-menu-item key="/base/train-seat"> <router-link to="/base/train-seat"> <user-outlined /> 火车座位 </router-link> </a-menu-item> </a-sub-menu> </a-menu> </a-layout-sider> </template> <script> import {defineComponent, ref, watch} from 'vue'; import router from "@/router"; export default defineComponent({ name: "the-sider-view", setup() { const selectedKeys = ref([]); watch(() => router.currentRoute.value.path, (newValue) => { console.log('watch', newValue); selectedKeys.value = []; selectedKeys.value.push(newValue); }, {immediate: true}); return { selectedKeys }; }, }); </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
-
/router/index.js
import { createRouter, createWebHistory } from 'vue-router' const routes = [{ path: '/', component: () => import('../views/main.vue'), children: [{ path: 'welcome', component: () => import('../views/main/welcome.vue'), }, { path: 'about', component: () => import('../views/main/about.vue'), }, { path: 'base/', children: [{ path: 'station', component: () => import('../views/main/station.vue'), }, { path: 'train', component: () => import('../views/main/train.vue'), }, { path: 'train-station', component: () => import('../views/main/train-station.vue'), }, { path: 'train-carriage', component: () => import('../views/main/train-carriage.vue'), }, { path: 'train-seat', component: () => import('../views/main/train-seat.vue'), }] }, { path: 'batch/', children: [{ path: 'job', component: () => import('../views/main/job.vue') }] }] }, { path: '', redirect: '/welcome' }]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
-
测试
3.修改vue文件位置,保持层级关系和路由一致。
修改方法:只修改移动文件位置,路由index.ts自动会修改为新位置
九、增加任务手工补偿功能
增加手动执行定时任务的功能
-
JobController.java
@RequestMapping(value = "/run") public CommonResp<Object> run(@RequestBody CronJobReq cronJobReq) throws SchedulerException { String jobClassName = cronJobReq.getName(); String jobGroupName = cronJobReq.getGroup(); LOG.info("手动执行任务开始:{}, {}", jobClassName, jobGroupName); schedulerFactoryBean.getScheduler().triggerJob(JobKey.jobKey(jobClassName, jobGroupName)); return new CommonResp<>(); }
-
job.vue
<template> <div class="job"> <p> <a-button type="primary" @click="handleAdd()"> 新增 </a-button> <a-button type="primary" @click="handleQuery()"> 刷新 </a-button> </p> <a-table :dataSource="jobs" :columns="columns" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="手动执行会立即执行一次,确定执行?" ok-text="是" cancel-text="否" @confirm="handleRun(record)" > <a-button type="primary" size="small"> 手动执行 </a-button> </a-popconfirm> <a-popconfirm title="确定重启?" ok-text="是" cancel-text="否" @confirm="handleResume(record)" > <a-button v-show="record.state === 'PAUSED' || record.state === 'ERROR'" type="primary" size="small"> 重启 </a-button> </a-popconfirm> <a-popconfirm title="确定暂停?" ok-text="是" cancel-text="否" @confirm="handlePause(record)" > <a-button v-show="record.state === 'NORMAL' || record.state === 'BLOCKED'" type="primary" size="small"> 暂停 </a-button> </a-popconfirm> <a-button type="primary" @click="handleEdit(record)" size="small"> 编辑 </a-button> <a-popconfirm title="删除后不可恢复,确认删除?" ok-text="是" cancel-text="否" @confirm="handleDelete(record)" > <a-button type="danger" size="small"> 删除 </a-button> </a-popconfirm> </a-space> </template> </template> </a-table> <a-modal title="用户" v-model:visible="modalVisible" :confirm-loading="modalLoading" @ok="handleModalOk" > <a-form :model="job" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }"> <a-form-item label="类名"> <a-input v-model:value="job.name" /> </a-form-item> <a-form-item label="描述"> <a-input v-model:value="job.description" /> </a-form-item> <a-form-item label="分组"> <a-input v-model:value="job.group" :disabled="!!job.state"/> </a-form-item> <a-form-item label="表达式"> <a-input v-model:value="job.cronExpression" /> <div class="ant-alert ant-alert-success"> 每5秒执行一次:0/5 * * * * ? <br> 每5分钟执行一次:* 0/5 * * * ? </div> </a-form-item> </a-form> </a-modal> </div> </template> <script> import { defineComponent, onMounted, ref } from 'vue'; import axios from 'axios'; import { notification } from 'ant-design-vue'; export default defineComponent({ name: 'batch-job-view', setup () { const jobs = ref(); const loading = ref(); const columns = [{ title: '分组', dataIndex: 'group', }, { title: '类名', dataIndex: 'name', }, { title: '描述', dataIndex: 'description', }, { title: '状态', dataIndex: 'state', }, { title: '表达式', dataIndex: 'cronExpression', }, { title: '上次时间', dataIndex: 'preFireTime', }, { title: '下次时间', dataIndex: 'nextFireTime', }, { title: '操作', dataIndex: 'operation' }]; const handleQuery = () => { loading.value = true; jobs.value = []; axios.get('/batch/admin/job/query').then((response) => { loading.value = false; const data = response.data; if (data.success) { jobs.value = data.content; } else { notification.error({description: data.message}); } }); }; // -------- 表单 --------- const job = ref(); job.value = {}; const modalVisible = ref(false); const modalLoading = ref(false); const handleModalOk = () => { modalLoading.value = true; let url = "add"; if (job.value.state) { url = "reschedule"; } axios.post('/batch/admin/job/' + url, job.value).then((response) => { modalLoading.value = false; const data = response.data; if (data.success) { modalVisible.value = false; notification.success({description: "保存成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; /** * 新增 */ const handleAdd = () => { modalVisible.value = true; job.value = { group: 'DEFAULT' }; }; /** * 编辑 */ const handleEdit = (record) => { modalVisible.value = true; job.value = Tool.copy(record); }; /** * 删除 */ const handleDelete = (record) => { axios.post('/batch/admin/job/delete', { name: record.name, group: record.group }).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; /** * 暂停 */ const handlePause = (record) => { axios.post('/batch/admin/job/pause', { name: record.name, group: record.group }).then((response) => { const data = response.data; if (data.success) { notification.success({description: "暂停成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; /** * 重启 */ const handleResume = (record) => { axios.post('/batch/admin/job/reschedule', record).then((response) => { modalLoading.value = false; const data = response.data; if (data.success) { modalVisible.value = false; notification.success({description: "重启成功!"}); handleQuery(); } else { notification.error({description: data.message}); } }); }; /** * 手动执行 */ const handleRun = (record) => { axios.post('/batch/admin/job/run', record).then((response) => { const data = response.data; if (data.success) { notification.success({description: "手动执行成功!"}); } else { notification.error({description: data.message}); } }); }; const getEnumValue = (key, obj) => { return Tool.getEnumValue(key, obj); }; onMounted(() => { console.log('index mounted!'); handleQuery(); }); return { columns, jobs, loading, handleQuery, handleAdd, handleEdit, handleDelete, handleResume, handlePause, job, modalVisible, modalLoading, handleModalOk, getEnumValue, handleRun }; } }) </script> <style scoped> </style>
-
测试
执行成功
十、演示多节点场景中quartz的调度情况
- 创建18003节点
启动两个应用
-
新增job
TestJob2.java
package com.neilxu.train.batch.job; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @DisallowConcurrentExecution public class TestJob2 implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("TestJob222 TEST开始"); // try { // Thread.sleep(3000); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println("TestJob222 TEST结束"); } }
TestJob3.java
package com.neilxu.train.batch.job; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @DisallowConcurrentExecution public class TestJob3 implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("TestJob333 TEST开始"); // try { // Thread.sleep(3000); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println("TestJob333 TEST结束"); } }
-
测试
效果:多个节点轮询执行
十一、通过火车基础数据生成每日火车数据
使用Quartz来实现如下的内容
1.快速生成每日车次数据管理功能
-
新增每日车次表
sql/business.sql
drop table if exists `daily_train`; create table `daily_train` ( `id` bigint not null comment 'id', `date` date not null comment '日期', `code` varchar(20) not null comment '车次编号', `type` char(1) not null comment '车次类型|枚举[TrainTypeEnum]', `start` varchar(20) not null comment '始发站', `start_pinyin` varchar(50) not null comment '始发站拼音', `start_time` time not null comment '出发时间', `end` varchar(20) not null comment '终点站', `end_pinyin` varchar(50) not null comment '终点站拼音', `end_time` time not null comment '到站时间', `create_time` datetime(3) comment '新增时间', `update_time` datetime(3) comment '修改时间', primary key (`id`), unique key `date_code_unique` (`date`, `code`) ) engine=innodb default charset=utf8mb4 comment='每日车次';
-
修改generator-config-business.xml
<!--<table tableName="station" domainObjectName="Station"/>--> <!--<table tableName="train" domainObjectName="Train"/>--> <!-- <table tableName="train_station" domainObjectName="TrainStation"/>--> <!-- <table tableName="train_carriage" domainObjectName="TrainCarriage"/>--> <!-- <table tableName="train_seat" domainObjectName="TrainSeat"/>--> <table tableName="daily_train" domainObjectName="DailyTrain"/>
-
修改ServerGenerator.java
package com.neilxu.train.generator.gen; import com.neilxu.train.generator.util.DbUtil; import com.neilxu.train.generator.util.Field; import com.neilxu.train.generator.util.FreemarkerUtil; import freemarker.template.TemplateException; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Node; import org.dom4j.io.SAXReader; import java.io.File; import java.io.IOException; import java.util.*; public class ServerGenerator { static boolean readOnly = true; // static String vuePath = "web/src/views/main/"; static String vuePath = "admin/src/views/main/"; static String serverPath = "[module]/src/main/java/com/neilxu/train/[module]/"; static String pomPath = "generator\\pom.xml"; static String module = ""; static { new File(serverPath).mkdirs(); } public static void main(String[] args) throws Exception { // 获取mybatis-generator String generatorPath = getGeneratorPath(); // 比如generator-config-member.xml,得到module = member module = generatorPath.replace("src/main/resources/generator-config-", "").replace(".xml", ""); System.out.println("module: " + module); serverPath = serverPath.replace("[module]", module); // new File(servicePath).mkdirs(); System.out.println("servicePath: " + serverPath); // 读取table节点 Document document = new SAXReader().read("generator/" + generatorPath); Node table = document.selectSingleNode("//table"); System.out.println(table); Node tableName = table.selectSingleNode("@tableName"); Node domainObjectName = table.selectSingleNode("@domainObjectName"); System.out.println(tableName.getText() + "/" + domainObjectName.getText()); // 为DbUtil设置数据源 Node connectionURL = document.selectSingleNode("//@connectionURL"); Node userId = document.selectSingleNode("//@userId"); Node password = document.selectSingleNode("//@password"); System.out.println("url: " + connectionURL.getText()); System.out.println("user: " + userId.getText()); System.out.println("password: " + password.getText()); DbUtil.url = connectionURL.getText(); DbUtil.user = userId.getText(); DbUtil.password = password.getText(); // 示例:表名 neilxu_test // Domain = neilxuTest String Domain = domainObjectName.getText(); // domain = neilxuTest String domain = Domain.substring(0, 1).toLowerCase() + Domain.substring(1); // do_main = neilxu-test String do_main = tableName.getText().replaceAll("_", "-"); // 表中文名 String tableNameCn = DbUtil.getTableComment(tableName.getText()); List<Field> fieldList = DbUtil.getColumnByTableName(tableName.getText()); Set<String> typeSet = getJavaTypes(fieldList); // 组装参数 Map<String, Object> param = new HashMap<>(); param.put("module", module); param.put("Domain", Domain); param.put("domain", domain); param.put("do_main", do_main); param.put("tableNameCn", tableNameCn); param.put("fieldList", fieldList); param.put("typeSet", typeSet); param.put("readOnly", readOnly); System.out.println("组装参数:" + param); gen(Domain, param, "service", "service"); gen(Domain, param, "controller/admin", "adminController"); // gen(Domain, param, "controller", "controller"); gen(Domain, param, "req", "saveReq"); gen(Domain, param, "req", "queryReq"); gen(Domain, param, "resp", "queryResp"); genVue(do_main, param); } private static void gen(String Domain, Map<String, Object> param, String packageName, String target) throws IOException, TemplateException { FreemarkerUtil.initConfig(target + ".ftl"); String toPath = serverPath + packageName + "/"; new File(toPath).mkdirs(); String Target = target.substring(0, 1).toUpperCase() + target.substring(1); String fileName = toPath + Domain + Target + ".java"; System.out.println("开始生成:" + fileName); FreemarkerUtil.generator(fileName, param); } private static void genVue(String do_main, Map<String, Object> param) throws IOException, TemplateException { FreemarkerUtil.initConfig("vue.ftl"); new File(vuePath + module).mkdirs(); String fileName = vuePath + module + "/" + do_main + ".vue"; System.out.println("开始生成:" + fileName); FreemarkerUtil.generator(fileName, param); } private static String getGeneratorPath() throws DocumentException { SAXReader saxReader = new SAXReader(); Map<String, String> map = new HashMap<String, String>(); map.put("pom", "http://maven.apache.org/POM/4.0.0"); saxReader.getDocumentFactory().setXPathNamespaceURIs(map); Document document = saxReader.read(pomPath); Node node = document.selectSingleNode("//pom:configurationFile"); System.out.println(node.getText()); return node.getText(); } /** * 获取所有的Java类型,使用Set去重 */ private static Set<String> getJavaTypes(List<Field> fieldList) { Set<String> set = new HashSet<>(); for (int i = 0; i < fieldList.size(); i++) { Field field = fieldList.get(i); set.add(field.getJavaType()); } return set; } }
-
修改generator/pom.xml
<configuration> <!--<configurationFile>src/main/resources/generator-config-member.xml</configurationFile>--> <configurationFile>src/main/resources/generator-config-business.xml</configurationFile> <!-- <configurationFile>src/main/resources/generator-config-batch.xml</configurationFile>--> <overwrite>true</overwrite> <verbose>true</verbose> </configuration>
-
生成持久层代码、前后端代码
-
修改路由、侧边栏
import { createRouter, createWebHistory } from 'vue-router' const routes = [{ path: '/', component: () => import('../views/main.vue'), children: [{ path: 'welcome', component: () => import('../views/main/welcome.vue'), }, { path: 'about', component: () => import('../views/main/about.vue'), }, { path: 'base/', children: [{ path: 'station', component: () => import('../views/main/base/station.vue'), }, { path: 'train', component: () => import('../views/main/base/train.vue'), }, { path: 'train-station', component: () => import('../views/main/base/train-station.vue'), }, { path: 'train-carriage', component: () => import('../views/main/base/train-carriage.vue'), }, { path: 'train-seat', component: () => import('../views/main/base/train-seat.vue'), }] }, { path: 'business/', children: [{ path: 'daily-train', component: () => import('../views/main/business/daily-train.vue'), }] }, { path: 'batch/', children: [{ path: 'job', component: () => import('../views/main/batch/job.vue') }] }] }, { path: '', redirect: '/welcome' }]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
<template> <a-layout-sider width="200" style="background: #fff"> <a-menu v-model:selectedKeys="selectedKeys" :openKeys="['batch', 'base', 'business']" mode="inline" :style="{ height: '100%', borderRight: 0 }" > <a-menu-item key="/welcome"> <router-link to="/welcome"> <coffee-outlined /> 欢迎 </router-link> </a-menu-item> <a-menu-item key="/about"> <router-link to="/about"> <user-outlined /> 关于 </router-link> </a-menu-item> <a-sub-menu key="business"> <template #title> <span> <UnorderedListOutlined /> 业务管理 </span> </template> <a-menu-item key="/business/daily-train"> <router-link to="/business/daily-train"> <user-outlined /> 每日车次 </router-link> </a-menu-item> </a-sub-menu> <a-sub-menu key="base"> <template #title> <span> <UnorderedListOutlined /> 基础数据 </span> </template> <a-menu-item key="/base/station"> <router-link to="/base/station"> <user-outlined /> 车站管理 </router-link> </a-menu-item> <a-menu-item key="/base/train"> <router-link to="/base/train"> <user-outlined /> 火车管理 </router-link> </a-menu-item> <a-menu-item key="/base/train-station"> <router-link to="/base/train-station"> <user-outlined /> 火车车站 </router-link> </a-menu-item> <a-menu-item key="/base/train-carriage"> <router-link to="/base/train-carriage"> <user-outlined /> 火车车厢 </router-link> </a-menu-item> <a-menu-item key="/base/train-seat"> <router-link to="/base/train-seat"> <user-outlined /> 火车座位 </router-link> </a-menu-item> </a-sub-menu> <a-sub-menu key="batch"> <template #title> <span> <UnorderedListOutlined /> 跑批管理 </span> </template> <a-menu-item key="/batch/job"> <router-link to="/batch/job"> <MenuUnfoldOutlined /> 任务管理 </router-link> </a-menu-item> </a-sub-menu> </a-menu> </a-layout-sider> </template> <script> import {defineComponent, ref, watch} from 'vue'; import router from "@/router"; export default defineComponent({ name: "the-sider-view", setup() { const selectedKeys = ref([]); watch(() => router.currentRoute.value.path, (newValue) => { console.log('watch', newValue); selectedKeys.value = []; selectedKeys.value.push(newValue); }, {immediate: true}); return { selectedKeys }; }, }); </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
-
测试
2.完善每日车次管理页面功能
1.为每日车次表单增加车次下拉选择,并自动填充车次相关数据
-
daily-train.vue
<template> <p> <a-space> <a-button type="primary" @click="handleQuery()">刷新</a-button> <a-button type="primary" @click="onAdd">新增</a-button> </a-space> </p> <a-table :dataSource="dailyTrains" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="删除后不可恢复,确认删除?" @confirm="onDelete(record)" ok-text="确认" cancel-text="取消"> <a style="color: red">删除</a> </a-popconfirm> <a @click="onEdit(record)">编辑</a> </a-space> </template> <template v-else-if="column.dataIndex === 'type'"> <span v-for="item in TRAIN_TYPE_ARRAY" :key="item.code"> <span v-if="item.code === record.type"> {{item.desc}} </span> </span> </template> </template> </a-table> <a-modal v-model:visible="visible" title="每日车次" @ok="handleOk" ok-text="确认" cancel-text="取消"> <a-form :model="dailyTrain" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="dailyTrain.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> </a-form-item> <a-form-item label="车次编号"> <train-select-view v-model="dailyTrain.code" @change="onChangeCode"></train-select-view> </a-form-item> <a-form-item label="车次类型"> <a-select v-model:value="dailyTrain.type"> <a-select-option v-for="item in TRAIN_TYPE_ARRAY" :key="item.code" :value="item.code"> {{item.desc}} </a-select-option> </a-select> </a-form-item> <a-form-item label="始发站"> <a-input v-model:value="dailyTrain.start" /> </a-form-item> <a-form-item label="始发站拼音"> <a-input v-model:value="dailyTrain.startPinyin" /> </a-form-item> <a-form-item label="出发时间"> <a-time-picker v-model:value="dailyTrain.startTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> <a-form-item label="终点站"> <a-input v-model:value="dailyTrain.end" /> </a-form-item> <a-form-item label="终点站拼音"> <a-input v-model:value="dailyTrain.endPinyin" /> </a-form-item> <a-form-item label="到站时间"> <a-time-picker v-model:value="dailyTrain.endTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> </a-form> </a-modal> </template> <script> import { defineComponent, ref, onMounted } from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; import TrainSelectView from "@/components/train-select"; export default defineComponent({ name: "daily-train-view", components: {TrainSelectView}, setup() { const TRAIN_TYPE_ARRAY = window.TRAIN_TYPE_ARRAY; const visible = ref(false); let dailyTrain = ref({ id: undefined, date: undefined, code: undefined, type: undefined, start: undefined, startPinyin: undefined, startTime: undefined, end: undefined, endPinyin: undefined, endTime: undefined, createTime: undefined, updateTime: undefined, }); const dailyTrains = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); const columns = [ { title: '日期', dataIndex: 'date', key: 'date', }, { title: '车次编号', dataIndex: 'code', key: 'code', }, { title: '车次类型', dataIndex: 'type', key: 'type', }, { title: '始发站', dataIndex: 'start', key: 'start', }, { title: '始发站拼音', dataIndex: 'startPinyin', key: 'startPinyin', }, { title: '出发时间', dataIndex: 'startTime', key: 'startTime', }, { title: '终点站', dataIndex: 'end', key: 'end', }, { title: '终点站拼音', dataIndex: 'endPinyin', key: 'endPinyin', }, { title: '到站时间', dataIndex: 'endTime', key: 'endTime', }, { title: '操作', dataIndex: 'operation' } ]; const onAdd = () => { dailyTrain.value = {}; visible.value = true; }; const onEdit = (record) => { dailyTrain.value = window.Tool.copy(record); visible.value = true; }; const onDelete = (record) => { axios.delete("/business/admin/daily-train/delete/" + record.id).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); } else { notification.error({description: data.message}); } }); }; const handleOk = () => { axios.post("/business/admin/daily-train/save", dailyTrain.value).then((response) => { let data = response.data; if (data.success) { notification.success({description: "保存成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/business/admin/daily-train/query-list", { params: { page: param.page, size: param.size } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { dailyTrains.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (pagination) => { // console.log("看看自带的分页参数都有啥:" + pagination); handleQuery({ page: pagination.current, size: pagination.pageSize }); }; const onChangeCode = (train) => { console.log("车次下拉组件选择:", train); let t = Tool.copy(train); delete t.id; // 用assign可以合并 dailyTrain.value = Object.assign(dailyTrain.value, t); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { TRAIN_TYPE_ARRAY, dailyTrain, visible, dailyTrains, pagination, columns, handleTableChange, handleQuery, loading, onAdd, handleOk, onEdit, onDelete, onChangeCode }; }, }); </script>
-
测试
2.为每日车次页面增加日期和车次查询条件
注意:GET请求的日期是拼接在URL里的,需要用spring自带的@DateTimeFormat(pattern = "yyyy-MM-dd"),后端才能接收到参数
-
修改DailyTrainQueryReq.java
@Data public class DailyTrainQueryReq extends PageReq { @DateTimeFormat(pattern = "yyyy-MM-dd") private Date date; private String code; }
-
修改DailyTrainService.java
public PageResp<DailyTrainQueryResp> queryList(DailyTrainQueryReq req) { DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.setOrderByClause("date desc, code asc"); DailyTrainExample.Criteria criteria = dailyTrainExample.createCriteria(); if (ObjectUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjectUtil.isNotEmpty(req.getCode())) { criteria.andCodeEqualTo(req.getCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrain> dailyTrainList = dailyTrainMapper.selectByExample(dailyTrainExample); PageInfo<DailyTrain> pageInfo = new PageInfo<>(dailyTrainList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainQueryResp> list = BeanUtil.copyToList(dailyTrainList, DailyTrainQueryResp.class); PageResp<DailyTrainQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; }
-
daily-train.vue
<template> <p> <a-space> <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> <train-select-view v-model="params.code" width="200px"></train-select-view> <a-button type="primary" @click="handleQuery()">查找</a-button> <a-button type="primary" @click="onAdd">新增</a-button> </a-space> </p> <a-table :dataSource="dailyTrains" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="删除后不可恢复,确认删除?" @confirm="onDelete(record)" ok-text="确认" cancel-text="取消"> <a style="color: red">删除</a> </a-popconfirm> <a @click="onEdit(record)">编辑</a> </a-space> </template> <template v-else-if="column.dataIndex === 'type'"> <span v-for="item in TRAIN_TYPE_ARRAY" :key="item.code"> <span v-if="item.code === record.type"> {{item.desc}} </span> </span> </template> </template> </a-table> <a-modal v-model:visible="visible" title="每日车次" @ok="handleOk" ok-text="确认" cancel-text="取消"> <a-form :model="dailyTrain" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="dailyTrain.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> </a-form-item> <a-form-item label="车次编号"> <train-select-view v-model="dailyTrain.code" @change="onChangeCode"></train-select-view> </a-form-item> <a-form-item label="车次类型"> <a-select v-model:value="dailyTrain.type"> <a-select-option v-for="item in TRAIN_TYPE_ARRAY" :key="item.code" :value="item.code"> {{item.desc}} </a-select-option> </a-select> </a-form-item> <a-form-item label="始发站"> <a-input v-model:value="dailyTrain.start" /> </a-form-item> <a-form-item label="始发站拼音"> <a-input v-model:value="dailyTrain.startPinyin" /> </a-form-item> <a-form-item label="出发时间"> <a-time-picker v-model:value="dailyTrain.startTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> <a-form-item label="终点站"> <a-input v-model:value="dailyTrain.end" /> </a-form-item> <a-form-item label="终点站拼音"> <a-input v-model:value="dailyTrain.endPinyin" /> </a-form-item> <a-form-item label="到站时间"> <a-time-picker v-model:value="dailyTrain.endTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> </a-form> </a-modal> </template> <script> import { defineComponent, ref, onMounted } from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; import TrainSelectView from "@/components/train-select"; export default defineComponent({ name: "daily-train-view", components: {TrainSelectView}, setup() { const TRAIN_TYPE_ARRAY = window.TRAIN_TYPE_ARRAY; const visible = ref(false); let dailyTrain = ref({ id: undefined, date: undefined, code: undefined, type: undefined, start: undefined, startPinyin: undefined, startTime: undefined, end: undefined, endPinyin: undefined, endTime: undefined, createTime: undefined, updateTime: undefined, }); const dailyTrains = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); let params = ref({ code: null }); const columns = [ { title: '日期', dataIndex: 'date', key: 'date', }, { title: '车次编号', dataIndex: 'code', key: 'code', }, { title: '车次类型', dataIndex: 'type', key: 'type', }, { title: '始发站', dataIndex: 'start', key: 'start', }, { title: '始发站拼音', dataIndex: 'startPinyin', key: 'startPinyin', }, { title: '出发时间', dataIndex: 'startTime', key: 'startTime', }, { title: '终点站', dataIndex: 'end', key: 'end', }, { title: '终点站拼音', dataIndex: 'endPinyin', key: 'endPinyin', }, { title: '到站时间', dataIndex: 'endTime', key: 'endTime', }, { title: '操作', dataIndex: 'operation' } ]; const onAdd = () => { dailyTrain.value = {}; visible.value = true; }; const onEdit = (record) => { dailyTrain.value = window.Tool.copy(record); visible.value = true; }; const onDelete = (record) => { axios.delete("/business/admin/daily-train/delete/" + record.id).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); } else { notification.error({description: data.message}); } }); }; const handleOk = () => { axios.post("/business/admin/daily-train/save", dailyTrain.value).then((response) => { let data = response.data; if (data.success) { notification.success({description: "保存成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/business/admin/daily-train/query-list", { params: { page: param.page, size: param.size, code: params.value.code, date: params.value.date } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { dailyTrains.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (pagination) => { // console.log("看看自带的分页参数都有啥:" + pagination); handleQuery({ page: pagination.current, size: pagination.pageSize }); }; const onChangeCode = (train) => { console.log("车次下拉组件选择:", train); let t = Tool.copy(train); delete t.id; // 用assign可以合并 dailyTrain.value = Object.assign(dailyTrain.value, t); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { TRAIN_TYPE_ARRAY, dailyTrain, visible, dailyTrains, pagination, columns, handleTableChange, handleQuery, loading, onAdd, handleOk, onEdit, onDelete, onChangeCode, params }; }, }); </script>
-
测试
2.快速生成每日车站数据管理功能
步骤同上
-
新增表
drop table if exists `daily_train_station`; create table `daily_train_station` ( `id` bigint not null comment 'id', `date` date not null comment '日期', `train_code` varchar(20) not null comment '车次编号', `index` int not null comment '站序', `name` varchar(20) not null comment '站名', `name_pinyin` varchar(50) not null comment '站名拼音', `in_time` time comment '进站时间', `out_time` time comment '出站时间', `stop_time` time comment '停站时长', `km` decimal(8, 2) not null comment '里程(公里)|从上一站到本站的距离', `create_time` datetime(3) comment '新增时间', `update_time` datetime(3) comment '修改时间', primary key (`id`), unique key `date_train_code_index_unique` (`date`, `train_code`, `index`), unique key `date_train_code_name_unique` (`date`, `train_code`, `name`) ) engine=innodb default charset=utf8mb4 comment='每日车站';
-
generator-config-business.xml
<!--<table tableName="station" domainObjectName="Station"/>--> <!--<table tableName="train" domainObjectName="Train"/>--> <!-- <table tableName="train_station" domainObjectName="TrainStation"/>--> <!-- <table tableName="train_carriage" domainObjectName="TrainCarriage"/>--> <!-- <table tableName="train_seat" domainObjectName="TrainSeat"/>--> <!-- <table tableName="daily_train" domainObjectName="DailyTrain"/>--> <table tableName="daily_train_station" domainObjectName="DailyTrainStation"/>
-
生成持久层、前后端代码
同上
-
修改路由、侧边栏
同上
<template> <a-layout-sider width="200" style="background: #fff"> <a-menu v-model:selectedKeys="selectedKeys" :openKeys="['batch', 'base', 'business']" mode="inline" :style="{ height: '100%', borderRight: 0 }" > <a-menu-item key="/welcome"> <router-link to="/welcome"> <coffee-outlined /> 欢迎 </router-link> </a-menu-item> <a-menu-item key="/about"> <router-link to="/about"> <user-outlined /> 关于 </router-link> </a-menu-item> <a-sub-menu key="business"> <template #title> <span> <UnorderedListOutlined /> 业务管理 </span> </template> <a-menu-item key="/business/daily-train"> <router-link to="/business/daily-train"> <user-outlined /> 每日车次 </router-link> </a-menu-item> <a-menu-item key="/business/daily-train-station"> <router-link to="/business/daily-train-station"> <user-outlined /> 每日车站 </router-link> </a-menu-item> </a-sub-menu> <a-sub-menu key="base"> <template #title> <span> <UnorderedListOutlined /> 基础数据 </span> </template> <a-menu-item key="/base/station"> <router-link to="/base/station"> <user-outlined /> 车站管理 </router-link> </a-menu-item> <a-menu-item key="/base/train"> <router-link to="/base/train"> <user-outlined /> 火车管理 </router-link> </a-menu-item> <a-menu-item key="/base/train-station"> <router-link to="/base/train-station"> <user-outlined /> 火车车站 </router-link> </a-menu-item> <a-menu-item key="/base/train-carriage"> <router-link to="/base/train-carriage"> <user-outlined /> 火车车厢 </router-link> </a-menu-item> <a-menu-item key="/base/train-seat"> <router-link to="/base/train-seat"> <user-outlined /> 火车座位 </router-link> </a-menu-item> </a-sub-menu> <a-sub-menu key="batch"> <template #title> <span> <UnorderedListOutlined /> 跑批管理 </span> </template> <a-menu-item key="/batch/job"> <router-link to="/batch/job"> <MenuUnfoldOutlined /> 任务管理 </router-link> </a-menu-item> </a-sub-menu> </a-menu> </a-layout-sider> </template> <script> import {defineComponent, ref, watch} from 'vue'; import router from "@/router"; export default defineComponent({ name: "the-sider-view", setup() { const selectedKeys = ref([]); watch(() => router.currentRoute.value.path, (newValue) => { console.log('watch', newValue); selectedKeys.value = []; selectedKeys.value.push(newValue); }, {immediate: true}); return { selectedKeys }; }, }); </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
import { createRouter, createWebHistory } from 'vue-router' const routes = [{ path: '/', component: () => import('../views/main.vue'), children: [{ path: 'welcome', component: () => import('../views/main/welcome.vue'), }, { path: 'about', component: () => import('../views/main/about.vue'), }, { path: 'base/', children: [{ path: 'station', component: () => import('../views/main/base/station.vue'), }, { path: 'train', component: () => import('../views/main/base/train.vue'), }, { path: 'train-station', component: () => import('../views/main/base/train-station.vue'), }, { path: 'train-carriage', component: () => import('../views/main/base/train-carriage.vue'), }, { path: 'train-seat', component: () => import('../views/main/base/train-seat.vue'), }] }, { path: 'business/', children: [{ path: 'daily-train', component: () => import('../views/main/business/daily-train.vue'), }, { path: 'daily-train-station', component: () => import('../views/main/business/daily-train-station.vue'), }] }, { path: 'batch/', children: [{ path: 'job', component: () => import('../views/main/batch/job.vue') }] }] }, { path: '', redirect: '/welcome' }]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
-
测试
3.完善每日车站页面,增加日期和车次查询条件
-
DailyTrainStationQueryReq.java
@Data public class DailyTrainStationQueryReq extends PageReq { /** * 日期 */ @DateTimeFormat(pattern = "yyyy-MM-dd") private Date date; /** * 车次编号 */ private String trainCode; }
-
DailyTrainStationService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrainStation; import com.neilxu.train.business.domain.DailyTrainStationExample; import com.neilxu.train.business.mapper.DailyTrainStationMapper; import com.neilxu.train.business.req.DailyTrainStationQueryReq; import com.neilxu.train.business.req.DailyTrainStationSaveReq; import com.neilxu.train.business.resp.DailyTrainStationQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.List; @Service public class DailyTrainStationService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainStationService.class); @Resource private DailyTrainStationMapper dailyTrainStationMapper; public void save(DailyTrainStationSaveReq req) { DateTime now = DateTime.now(); DailyTrainStation dailyTrainStation = BeanUtil.copyProperties(req, DailyTrainStation.class); if (ObjectUtil.isNull(dailyTrainStation.getId())) { dailyTrainStation.setId(SnowUtil.getSnowflakeNextId()); dailyTrainStation.setCreateTime(now); dailyTrainStation.setUpdateTime(now); dailyTrainStationMapper.insert(dailyTrainStation); } else { dailyTrainStation.setUpdateTime(now); dailyTrainStationMapper.updateByPrimaryKey(dailyTrainStation); } } public PageResp<DailyTrainStationQueryResp> queryList(DailyTrainStationQueryReq req) { DailyTrainStationExample dailyTrainStationExample = new DailyTrainStationExample(); dailyTrainStationExample.setOrderByClause("date desc, train_code asc, `index` asc"); DailyTrainStationExample.Criteria criteria = dailyTrainStationExample.createCriteria(); if (ObjUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjUtil.isNotEmpty(req.getTrainCode())) { criteria.andTrainCodeEqualTo(req.getTrainCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrainStation> dailyTrainStationList = dailyTrainStationMapper.selectByExample(dailyTrainStationExample); PageInfo<DailyTrainStation> pageInfo = new PageInfo<>(dailyTrainStationList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainStationQueryResp> list = BeanUtil.copyToList(dailyTrainStationList, DailyTrainStationQueryResp.class); PageResp<DailyTrainStationQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainStationMapper.deleteByPrimaryKey(id); } }
-
daily-train-station.vue
<template> <p> <a-space> <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> <train-select-view v-model="params.trainCode" width="200px"></train-select-view> <a-button type="primary" @click="handleQuery()">刷新</a-button> <a-button type="primary" @click="onAdd">新增</a-button> </a-space> </p> <a-table :dataSource="dailyTrainStations" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="删除后不可恢复,确认删除?" @confirm="onDelete(record)" ok-text="确认" cancel-text="取消"> <a style="color: red">删除</a> </a-popconfirm> <a @click="onEdit(record)">编辑</a> </a-space> </template> </template> </a-table> <a-modal v-model:visible="visible" title="每日车站" @ok="handleOk" ok-text="确认" cancel-text="取消"> <a-form :model="dailyTrainStation" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="dailyTrainStation.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> </a-form-item> <a-form-item label="车次编号"> <train-select-view v-model="dailyTrainStation.trainCode"></train-select-view> </a-form-item> <a-form-item label="站序"> <a-input v-model:value="dailyTrainStation.index" /> </a-form-item> <a-form-item label="站名"> <a-input v-model:value="dailyTrainStation.name" /> </a-form-item> <a-form-item label="站名拼音"> <a-input v-model:value="dailyTrainStation.namePinyin" disabled/> </a-form-item> <a-form-item label="进站时间"> <a-time-picker v-model:value="dailyTrainStation.inTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> <a-form-item label="出站时间"> <a-time-picker v-model:value="dailyTrainStation.outTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> <a-form-item label="停站时长"> <a-time-picker v-model:value="dailyTrainStation.stopTime" valueFormat="HH:mm:ss" placeholder="请选择时间" disabled/> </a-form-item> <a-form-item label="里程(公里)"> <a-input v-model:value="dailyTrainStation.km" /> </a-form-item> </a-form> </a-modal> </template> <script> import {defineComponent, ref, onMounted, watch} from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; import {pinyin} from "pinyin-pro"; import dayjs from "dayjs"; import TrainSelectView from "@/components/train-select"; export default defineComponent({ name: "daily-train-station-view", components: {TrainSelectView}, setup() { const visible = ref(false); let dailyTrainStation = ref({ id: undefined, date: undefined, trainCode: undefined, index: undefined, name: undefined, namePinyin: undefined, inTime: undefined, outTime: undefined, stopTime: undefined, km: undefined, createTime: undefined, updateTime: undefined, }); const dailyTrainStations = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); let params = ref({ trainCode: null, date: null }); const columns = [ { title: '日期', dataIndex: 'date', key: 'date', }, { title: '车次编号', dataIndex: 'trainCode', key: 'trainCode', }, { title: '站序', dataIndex: 'index', key: 'index', }, { title: '站名', dataIndex: 'name', key: 'name', }, { title: '站名拼音', dataIndex: 'namePinyin', key: 'namePinyin', }, { title: '进站时间', dataIndex: 'inTime', key: 'inTime', }, { title: '出站时间', dataIndex: 'outTime', key: 'outTime', }, { title: '停站时长', dataIndex: 'stopTime', key: 'stopTime', }, { title: '里程(公里)', dataIndex: 'km', key: 'km', }, { title: '操作', dataIndex: 'operation' } ]; watch(() => dailyTrainStation.value.name, ()=>{ if (Tool.isNotEmpty(dailyTrainStation.value.name)) { dailyTrainStation.value.namePinyin = pinyin(dailyTrainStation.value.name, { toneType: 'none'}).replaceAll(" ", ""); } else { dailyTrainStation.value.namePinyin = ""; } }, {immediate: true}); // 自动计算停车时长 watch(() => dailyTrainStation.value.inTime, ()=>{ let diff = dayjs(dailyTrainStation.value.outTime, 'HH:mm:ss').diff(dayjs(dailyTrainStation.value.inTime, 'HH:mm:ss'), 'seconds'); dailyTrainStation.value.stopTime = dayjs('00:00:00', 'HH:mm:ss').second(diff).format('HH:mm:ss'); }, {immediate: true}); // 自动计算停车时长 watch(() => dailyTrainStation.value.outTime, ()=>{ let diff = dayjs(dailyTrainStation.value.outTime, 'HH:mm:ss').diff(dayjs(dailyTrainStation.value.inTime, 'HH:mm:ss'), 'seconds'); dailyTrainStation.value.stopTime = dayjs('00:00:00', 'HH:mm:ss').second(diff).format('HH:mm:ss'); }, {immediate: true}); const onAdd = () => { dailyTrainStation.value = {}; visible.value = true; }; const onEdit = (record) => { dailyTrainStation.value = window.Tool.copy(record); visible.value = true; }; const onDelete = (record) => { axios.delete("/business/admin/daily-train-station/delete/" + record.id).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); } else { notification.error({description: data.message}); } }); }; const handleOk = () => { axios.post("/business/admin/daily-train-station/save", dailyTrainStation.value).then((response) => { let data = response.data; if (data.success) { notification.success({description: "保存成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/business/admin/daily-train-station/query-list", { params: { page: param.page, size: param.size, trainCode: params.value.trainCode, date: params.value.date } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { dailyTrainStations.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (pagination) => { // console.log("看看自带的分页参数都有啥:" + pagination); handleQuery({ page: pagination.current, size: pagination.pageSize }); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { dailyTrainStation, visible, dailyTrainStations, pagination, columns, handleTableChange, handleQuery, loading, onAdd, handleOk, onEdit, onDelete, params }; }, }); </script>
-
测试
4.快速生成每日车厢数据管理功能
操作同上
-
新建表
drop table if exists `daily_train_carriage`; create table `daily_train_carriage` ( `id` bigint not null comment 'id', `date` date not null comment '日期', `train_code` varchar(20) not null comment '车次编号', `index` int not null comment '箱序', `seat_type` char(1) not null comment '座位类型|枚举[SeatTypeEnum]', `seat_count` int not null comment '座位数', `row_count` int not null comment '排数', `col_count` int not null comment '列数', `create_time` datetime(3) comment '新增时间', `update_time` datetime(3) comment '修改时间', primary key (`id`), unique key `date_train_code_index_unique` (`date`, `train_code`, `index`) ) engine=innodb default charset=utf8mb4 comment='每日车厢';
-
generator-config-business.xml
<!--<table tableName="station" domainObjectName="Station"/>--> <!--<table tableName="train" domainObjectName="Train"/>--> <!-- <table tableName="train_station" domainObjectName="TrainStation"/>--> <!-- <table tableName="train_carriage" domainObjectName="TrainCarriage"/>--> <!-- <table tableName="train_seat" domainObjectName="TrainSeat"/>--> <!-- <table tableName="daily_train" domainObjectName="DailyTrain"/>--> <!-- <table tableName="daily_train_station" domainObjectName="DailyTrainStation"/>--> <table tableName="daily_train_carriage" domainObjectName="DailyTrainCarriage"/>
-
生成持久层、前后端代码
同上
-
修改路由、侧边栏
同上
import { createRouter, createWebHistory } from 'vue-router' const routes = [{ path: '/', component: () => import('../views/main.vue'), children: [{ path: 'welcome', component: () => import('../views/main/welcome.vue'), }, { path: 'about', component: () => import('../views/main/about.vue'), }, { path: 'base/', children: [{ path: 'station', component: () => import('../views/main/base/station.vue'), }, { path: 'train', component: () => import('../views/main/base/train.vue'), }, { path: 'train-station', component: () => import('../views/main/base/train-station.vue'), }, { path: 'train-carriage', component: () => import('../views/main/base/train-carriage.vue'), }, { path: 'train-seat', component: () => import('../views/main/base/train-seat.vue'), }] }, { path: 'business/', children: [{ path: 'daily-train', component: () => import('../views/main/business/daily-train.vue'), }, { path: 'daily-train-station', component: () => import('../views/main/business/daily-train-station.vue'), }, { path: 'daily-train-carriage', component: () => import('../views/main/business/daily-train-carriage.vue'), }] }, { path: 'batch/', children: [{ path: 'job', component: () => import('../views/main/batch/job.vue') }] }] }, { path: '', redirect: '/welcome' }]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
-
测试
-
完善每日车厢界面,增加日期和车次查询条件
-
DailyTrainCarriageSaveReq.java
/** * 座位数 */ // @NotNull(message = "【座位数】不能为空") private Integer seatCount; /** * 排数 */ @NotNull(message = "【排数】不能为空") private Integer rowCount; /** * 列数 */ // @NotNull(message = "【列数】不能为空") private Integer colCount;
-
DailyTrainCarriageQueryReq.java
@Data public class DailyTrainCarriageQueryReq extends PageReq { /** * 日期 */ @DateTimeFormat(pattern = "yyyy-MM-dd") private Date date; /** * 车次编号 */ private String trainCode; }
-
DailyTrainCarriageService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrainCarriage; import com.neilxu.train.business.domain.DailyTrainCarriageExample; import com.neilxu.train.business.enums.SeatColEnum; import com.neilxu.train.business.mapper.DailyTrainCarriageMapper; import com.neilxu.train.business.req.DailyTrainCarriageQueryReq; import com.neilxu.train.business.req.DailyTrainCarriageSaveReq; import com.neilxu.train.business.resp.DailyTrainCarriageQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.List; @Service public class DailyTrainCarriageService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainCarriageService.class); @Resource private DailyTrainCarriageMapper dailyTrainCarriageMapper; public void save(DailyTrainCarriageSaveReq req) { DateTime now = DateTime.now(); // 自动计算出列数和总座位数 List<SeatColEnum> seatColEnums = SeatColEnum.getColsByType(req.getSeatType()); req.setColCount(seatColEnums.size()); req.setSeatCount(req.getColCount() * req.getRowCount()); DailyTrainCarriage dailyTrainCarriage = BeanUtil.copyProperties(req, DailyTrainCarriage.class); if (ObjectUtil.isNull(dailyTrainCarriage.getId())) { dailyTrainCarriage.setId(SnowUtil.getSnowflakeNextId()); dailyTrainCarriage.setCreateTime(now); dailyTrainCarriage.setUpdateTime(now); dailyTrainCarriageMapper.insert(dailyTrainCarriage); } else { dailyTrainCarriage.setUpdateTime(now); dailyTrainCarriageMapper.updateByPrimaryKey(dailyTrainCarriage); } } public PageResp<DailyTrainCarriageQueryResp> queryList(DailyTrainCarriageQueryReq req) { DailyTrainCarriageExample dailyTrainCarriageExample = new DailyTrainCarriageExample(); dailyTrainCarriageExample.setOrderByClause("date desc, train_code asc, `index` asc"); DailyTrainCarriageExample.Criteria criteria = dailyTrainCarriageExample.createCriteria(); if (ObjUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjUtil.isNotEmpty(req.getTrainCode())) { criteria.andTrainCodeEqualTo(req.getTrainCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrainCarriage> dailyTrainCarriageList = dailyTrainCarriageMapper.selectByExample(dailyTrainCarriageExample); PageInfo<DailyTrainCarriage> pageInfo = new PageInfo<>(dailyTrainCarriageList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainCarriageQueryResp> list = BeanUtil.copyToList(dailyTrainCarriageList, DailyTrainCarriageQueryResp.class); PageResp<DailyTrainCarriageQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainCarriageMapper.deleteByPrimaryKey(id); } }
-
daily-train-carriage.vue
<template> <p> <a-space> <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> <train-select-view v-model="params.trainCode" width="200px"></train-select-view> <a-button type="primary" @click="handleQuery()">查询</a-button> <a-button type="primary" @click="onAdd">新增</a-button> </a-space> </p> <a-table :dataSource="dailyTrainCarriages" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="删除后不可恢复,确认删除?" @confirm="onDelete(record)" ok-text="确认" cancel-text="取消"> <a style="color: red">删除</a> </a-popconfirm> <a @click="onEdit(record)">编辑</a> </a-space> </template> <template v-else-if="column.dataIndex === 'seatType'"> <span v-for="item in SEAT_TYPE_ARRAY" :key="item.code"> <span v-if="item.code === record.seatType"> {{item.desc}} </span> </span> </template> </template> </a-table> <a-modal v-model:visible="visible" title="每日车箱" @ok="handleOk" ok-text="确认" cancel-text="取消"> <a-form :model="dailyTrainCarriage" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="dailyTrainCarriage.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> </a-form-item> <a-form-item label="车次编号"> <train-select-view v-model="dailyTrainCarriage.trainCode" width="200px"></train-select-view> </a-form-item> <a-form-item label="箱序"> <a-input v-model:value="dailyTrainCarriage.index" /> </a-form-item> <a-form-item label="座位类型"> <a-select v-model:value="dailyTrainCarriage.seatType"> <a-select-option v-for="item in SEAT_TYPE_ARRAY" :key="item.code" :value="item.code"> {{item.desc}} </a-select-option> </a-select> </a-form-item> <!--<a-form-item label="座位数">--> <!-- <a-input v-model:value="dailyTrainCarriage.seatCount" />--> <!--</a-form-item>--> <a-form-item label="排数"> <a-input v-model:value="dailyTrainCarriage.rowCount" /> </a-form-item> <!--<a-form-item label="列数">--> <!-- <a-input v-model:value="dailyTrainCarriage.colCount" />--> <!--</a-form-item>--> </a-form> </a-modal> </template> <script> import { defineComponent, ref, onMounted } from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; import TrainSelectView from "@/components/train-select"; export default defineComponent({ name: "daily-train-carriage-view", components: {TrainSelectView}, setup() { const SEAT_TYPE_ARRAY = window.SEAT_TYPE_ARRAY; const visible = ref(false); let dailyTrainCarriage = ref({ id: undefined, date: undefined, trainCode: undefined, index: undefined, seatType: undefined, seatCount: undefined, rowCount: undefined, colCount: undefined, createTime: undefined, updateTime: undefined, }); const dailyTrainCarriages = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); let params = ref({ trainCode: null, date: null }); const columns = [ { title: '日期', dataIndex: 'date', key: 'date', }, { title: '车次编号', dataIndex: 'trainCode', key: 'trainCode', }, { title: '箱序', dataIndex: 'index', key: 'index', }, { title: '座位类型', dataIndex: 'seatType', key: 'seatType', }, { title: '座位数', dataIndex: 'seatCount', key: 'seatCount', }, { title: '排数', dataIndex: 'rowCount', key: 'rowCount', }, { title: '列数', dataIndex: 'colCount', key: 'colCount', }, { title: '操作', dataIndex: 'operation' } ]; const onAdd = () => { dailyTrainCarriage.value = {}; visible.value = true; }; const onEdit = (record) => { dailyTrainCarriage.value = window.Tool.copy(record); visible.value = true; }; const onDelete = (record) => { axios.delete("/business/admin/daily-train-carriage/delete/" + record.id).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); } else { notification.error({description: data.message}); } }); }; const handleOk = () => { axios.post("/business/admin/daily-train-carriage/save", dailyTrainCarriage.value).then((response) => { let data = response.data; if (data.success) { notification.success({description: "保存成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/business/admin/daily-train-carriage/query-list", { params: { page: param.page, size: param.size, trainCode: params.value.trainCode, date: params.value.date } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { dailyTrainCarriages.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (pagination) => { // console.log("看看自带的分页参数都有啥:" + pagination); handleQuery({ page: pagination.current, size: pagination.pageSize }); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { SEAT_TYPE_ARRAY, dailyTrainCarriage, visible, dailyTrainCarriages, pagination, columns, handleTableChange, handleQuery, loading, onAdd, handleOk, onEdit, onDelete, params }; }, }); </script>
-
测试
-
5.快速生成每日座位数据管理功能
操作同上
-
将生成器中的\改成/,兼容MAC
这里是为了兼容mac,把之前generator-config-member.xml、generator-config-business.xml、generator-config-batch.xml、FreemarkerUtil.java、ServerGenerator.java所有的
\\
或\
都改成了/
-
新增表
这里注意多了关于售卖的字段
drop table if exists `daily_train_seat`; create table `daily_train_seat` ( `id` bigint not null comment 'id', `date` date not null comment '日期', `train_code` varchar(20) not null comment '车次编号', `carriage_index` int not null comment '箱序', `row` char(2) not null comment '排号|01, 02', `col` char(1) not null comment '列号|枚举[SeatColEnum]', `seat_type` char(1) not null comment '座位类型|枚举[SeatTypeEnum]', `carriage_seat_index` int not null comment '同车箱座序', `sell` varchar(50) not null comment '售卖情况|将经过的车站用01拼接,0表示可卖,1表示已卖', `create_time` datetime(3) comment '新增时间', `update_time` datetime(3) comment '修改时间', primary key (`id`) ) engine=innodb default charset=utf8mb4 comment='每日座位';
sell存的值例如:1011,表示车站2到车站3未卖,其余都卖了
-
生成持久层、前后端代码;修改路由、侧边栏
过程同上
-
每日座位改成只读
修改ServerGenerator.java,重新生成vue
-
修改每日座位页面,增加车次查询条件,修改列号的显示
过程同上,列号问题参考之前的座位页面
-
测试
这里由于改了只读,不方便测试,直接放后面做完定时任务后测试
6.增加生成每日车次定时任务
功能如下:
1.增加生成每日车次定时任务
-
DailyTrainJob.java
package com.neilxu.train.batch.job; import cn.hutool.core.util.RandomUtil; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @DisallowConcurrentExecution public class DailyTrainJob implements Job { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainJob.class); @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 增加日志流水号 MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3)); LOG.info("生成每日车次数据开始"); LOG.info("生成每日车次数据结束"); } }
-
页面配置
2.集成openfeign,实现服务间调用。给每个应用增加应用名,可搭配配置中心使用
-
修改properties,增加应用名
例如:member/src/main/resources/application.properties
spring.application.name=member
其余同上
-
新增openfeign依赖
<!--远程调用openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--openfeign默认使用的是loadBalance的负载均衡器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-loadbalancer</artifactId> </dependency>
-
BusinessFeign.java
package com.neilxu.train.batch.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient("business") // @FeignClient(name = "business", url = "http://127.0.0.1:8002/business") public interface BusinessFeign { @GetMapping("/hello") String hello(); }
-
TestController.java
测试openfeign
package com.neilxu.train.batch.controller; import com.neilxu.train.batch.feign.BusinessFeign; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { private static final Logger LOG = LoggerFactory.getLogger(TestController.class); @Resource BusinessFeign businessFeign; @GetMapping("/hello") public String hello() { String businessHello = businessFeign.hello(); LOG.info(businessHello); return "Hello World! Batch!"; } }
-
开启openfeign
BatchApplication.java
package com.neilxu.train.batch.config; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.core.env.Environment; @SpringBootApplication @ComponentScan("com.neilxu") @MapperScan("com.neilxu.train.*.mapper") @EnableFeignClients("com.neilxu.train.batch.feign") public class BatchApplication { private static final Logger LOG = LoggerFactory.getLogger(BatchApplication.class); public static void main(String[] args) { SpringApplication app = new SpringApplication(BatchApplication.class); Environment env = app.run(args).getEnvironment(); LOG.info("启动成功!!"); LOG.info("测试地址: \thttp://127.0.0.1:{}{}/hello", env.getProperty("server.port"), env.getProperty("server.servlet.context-path")); } }
-
重启测试
GET http://localhost:8000/batch/hello Accept: application/json ###
3.实现job——增加生成每日车次功能
-
修改TrainService.java
public List<TrainQueryResp> queryAll() { List<Train> trainList = selectAll(); return BeanUtil.copyToList(trainList, TrainQueryResp.class); } public List<Train> selectAll() { TrainExample trainExample = new TrainExample(); trainExample.setOrderByClause("code asc"); return trainMapper.selectByExample(trainExample); }
-
DailyTrainService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrain; import com.neilxu.train.business.domain.DailyTrainExample; import com.neilxu.train.business.domain.Train; import com.neilxu.train.business.mapper.DailyTrainMapper; import com.neilxu.train.business.req.DailyTrainQueryReq; import com.neilxu.train.business.req.DailyTrainSaveReq; import com.neilxu.train.business.resp.DailyTrainQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; @Service public class DailyTrainService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainService.class); @Resource private DailyTrainMapper dailyTrainMapper; @Resource private TrainService trainService; public void save(DailyTrainSaveReq req) { DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(req, DailyTrain.class); if (ObjectUtil.isNull(dailyTrain.getId())) { dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrainMapper.insert(dailyTrain); } else { dailyTrain.setUpdateTime(now); dailyTrainMapper.updateByPrimaryKey(dailyTrain); } } public PageResp<DailyTrainQueryResp> queryList(DailyTrainQueryReq req) { DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.setOrderByClause("date desc, code asc"); DailyTrainExample.Criteria criteria = dailyTrainExample.createCriteria(); if (ObjectUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjectUtil.isNotEmpty(req.getCode())) { criteria.andCodeEqualTo(req.getCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrain> dailyTrainList = dailyTrainMapper.selectByExample(dailyTrainExample); PageInfo<DailyTrain> pageInfo = new PageInfo<>(dailyTrainList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainQueryResp> list = BeanUtil.copyToList(dailyTrainList, DailyTrainQueryResp.class); PageResp<DailyTrainQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainMapper.deleteByPrimaryKey(id); } /** * 生成某日所有车次信息,包括车次、车站、车厢、座位 * @param date */ public void genDaily(Date date) { List<Train> trainList = trainService.selectAll(); if (CollUtil.isEmpty(trainList)) { LOG.info("没有车次基础数据,任务结束"); return; } for (Train train : trainList) { genDailyTrain(date, train); } } public void genDailyTrain(Date date, Train train) { // 删除该车次已有的数据 DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.createCriteria() .andDateEqualTo(date) .andCodeEqualTo(train.getCode()); dailyTrainMapper.deleteByExample(dailyTrainExample); // 生成该车次的数据 DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(train, DailyTrain.class); dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrain.setDate(date); dailyTrainMapper.insert(dailyTrain); } }
-
DailyTrainAdminController.java
package com.neilxu.train.business.controller.admin; import com.neilxu.train.business.req.DailyTrainQueryReq; import com.neilxu.train.business.req.DailyTrainSaveReq; import com.neilxu.train.business.resp.DailyTrainQueryResp; import com.neilxu.train.business.service.DailyTrainService; import com.neilxu.train.common.resp.CommonResp; import com.neilxu.train.common.resp.PageResp; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; import java.util.Date; @RestController @RequestMapping("/admin/daily-train") public class DailyTrainAdminController { @Resource private DailyTrainService dailyTrainService; @PostMapping("/save") public CommonResp<Object> save(@Valid @RequestBody DailyTrainSaveReq req) { dailyTrainService.save(req); return new CommonResp<>(); } @GetMapping("/query-list") public CommonResp<PageResp<DailyTrainQueryResp>> queryList(@Valid DailyTrainQueryReq req) { PageResp<DailyTrainQueryResp> list = dailyTrainService.queryList(req); return new CommonResp<>(list); } @DeleteMapping("/delete/{id}") public CommonResp<Object> delete(@PathVariable Long id) { dailyTrainService.delete(id); return new CommonResp<>(); } @GetMapping("/gen-daily/{date}") public CommonResp<Object> genDaily(@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) { dailyTrainService.genDaily(date); return new CommonResp<>(); } }
-
BusinessFeign.java
package com.neilxu.train.batch.feign; import com.neilxu.train.common.resp.CommonResp; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import java.util.Date; // @FeignClient("business") @FeignClient(name = "business", url = "http://127.0.0.1:8002/business") public interface BusinessFeign { @GetMapping("/hello") String hello(); @GetMapping("/admin/daily-train/gen-daily/{date}") CommonResp<Object> genDaily(@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") Date date); }
-
DailyTrainJob.java
package com.neilxu.train.batch.job; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.RandomUtil; import com.neilxu.train.batch.feign.BusinessFeign; import com.neilxu.train.common.resp.CommonResp; import jakarta.annotation.Resource; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import java.util.Date; @DisallowConcurrentExecution public class DailyTrainJob implements Job { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainJob.class); @Resource BusinessFeign businessFeign; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 增加日志流水号 MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3)); LOG.info("生成15天后的车次数据开始"); Date date = new Date(); DateTime dateTime = DateUtil.offsetDay(date, 15); Date offsetDate = dateTime.toJdkDate(); CommonResp<Object> commonResp = businessFeign.genDaily(offsetDate); LOG.info("生成15天后的车次数据结束,结果:{}", commonResp); } }
-
测试
http/batch-job.http
POST http://localhost:8000/batch/admin/job/run Content-Type: application/json { "name": "com.neilxu.train.batch.job.DailyTrainJob", "jobGroupName": "default" }
7.增加生成每日车站功能
知识点:
Mybatis的查询列表,查不到返回是空集合,不是null
-
TrainStationService.java
public List<TrainStation> selectByTrainCode(String trainCode) { TrainStationExample trainStationExample = new TrainStationExample(); trainStationExample.setOrderByClause("`index` asc"); trainStationExample.createCriteria().andTrainCodeEqualTo(trainCode); return trainStationMapper.selectByExample(trainStationExample); }
-
DailyTrainStationService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrainStation; import com.neilxu.train.business.domain.DailyTrainStationExample; import com.neilxu.train.business.domain.TrainStation; import com.neilxu.train.business.mapper.DailyTrainStationMapper; import com.neilxu.train.business.req.DailyTrainStationQueryReq; import com.neilxu.train.business.req.DailyTrainStationSaveReq; import com.neilxu.train.business.resp.DailyTrainStationQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; @Service public class DailyTrainStationService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainStationService.class); @Resource private DailyTrainStationMapper dailyTrainStationMapper; @Resource private TrainStationService trainStationService; public void save(DailyTrainStationSaveReq req) { DateTime now = DateTime.now(); DailyTrainStation dailyTrainStation = BeanUtil.copyProperties(req, DailyTrainStation.class); if (ObjectUtil.isNull(dailyTrainStation.getId())) { dailyTrainStation.setId(SnowUtil.getSnowflakeNextId()); dailyTrainStation.setCreateTime(now); dailyTrainStation.setUpdateTime(now); dailyTrainStationMapper.insert(dailyTrainStation); } else { dailyTrainStation.setUpdateTime(now); dailyTrainStationMapper.updateByPrimaryKey(dailyTrainStation); } } public PageResp<DailyTrainStationQueryResp> queryList(DailyTrainStationQueryReq req) { DailyTrainStationExample dailyTrainStationExample = new DailyTrainStationExample(); dailyTrainStationExample.setOrderByClause("date desc, train_code asc, `index` asc"); DailyTrainStationExample.Criteria criteria = dailyTrainStationExample.createCriteria(); if (ObjUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjUtil.isNotEmpty(req.getTrainCode())) { criteria.andTrainCodeEqualTo(req.getTrainCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrainStation> dailyTrainStationList = dailyTrainStationMapper.selectByExample(dailyTrainStationExample); PageInfo<DailyTrainStation> pageInfo = new PageInfo<>(dailyTrainStationList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainStationQueryResp> list = BeanUtil.copyToList(dailyTrainStationList, DailyTrainStationQueryResp.class); PageResp<DailyTrainStationQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainStationMapper.deleteByPrimaryKey(id); } public void genDaily(Date date, String trainCode) { LOG.info("生成日期【{}】车次【{}】的车站信息开始", DateUtil.formatDate(date), trainCode); // 删除某日某车次的车站信息 DailyTrainStationExample dailyTrainStationExample = new DailyTrainStationExample(); dailyTrainStationExample.createCriteria() .andDateEqualTo(date) .andTrainCodeEqualTo(trainCode); dailyTrainStationMapper.deleteByExample(dailyTrainStationExample); // 查出某车次的所有的车站信息 List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode); if (CollUtil.isEmpty(stationList)) { LOG.info("该车次没有车站基础数据,生成该车次的车站信息结束"); return; } for (TrainStation trainStation : stationList) { DateTime now = DateTime.now(); DailyTrainStation dailyTrainStation = BeanUtil.copyProperties(trainStation, DailyTrainStation.class); dailyTrainStation.setId(SnowUtil.getSnowflakeNextId()); dailyTrainStation.setCreateTime(now); dailyTrainStation.setUpdateTime(now); dailyTrainStation.setDate(date); dailyTrainStationMapper.insert(dailyTrainStation); } LOG.info("生成日期【{}】车次【{}】的车站信息结束", DateUtil.formatDate(date), trainCode); } }
-
DailyTrainService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrain; import com.neilxu.train.business.domain.DailyTrainExample; import com.neilxu.train.business.domain.Train; import com.neilxu.train.business.mapper.DailyTrainMapper; import com.neilxu.train.business.req.DailyTrainQueryReq; import com.neilxu.train.business.req.DailyTrainSaveReq; import com.neilxu.train.business.resp.DailyTrainQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; @Service public class DailyTrainService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainService.class); @Resource private DailyTrainMapper dailyTrainMapper; @Resource private TrainService trainService; @Resource private DailyTrainStationService dailyTrainStationService; public void save(DailyTrainSaveReq req) { DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(req, DailyTrain.class); if (ObjectUtil.isNull(dailyTrain.getId())) { dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrainMapper.insert(dailyTrain); } else { dailyTrain.setUpdateTime(now); dailyTrainMapper.updateByPrimaryKey(dailyTrain); } } public PageResp<DailyTrainQueryResp> queryList(DailyTrainQueryReq req) { DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.setOrderByClause("date desc, code asc"); DailyTrainExample.Criteria criteria = dailyTrainExample.createCriteria(); if (ObjectUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjectUtil.isNotEmpty(req.getCode())) { criteria.andCodeEqualTo(req.getCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrain> dailyTrainList = dailyTrainMapper.selectByExample(dailyTrainExample); PageInfo<DailyTrain> pageInfo = new PageInfo<>(dailyTrainList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainQueryResp> list = BeanUtil.copyToList(dailyTrainList, DailyTrainQueryResp.class); PageResp<DailyTrainQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainMapper.deleteByPrimaryKey(id); } /** * 生成某日所有车次信息,包括车次、车站、车厢、座位 * @param date */ public void genDaily(Date date) { List<Train> trainList = trainService.selectAll(); if (CollUtil.isEmpty(trainList)) { LOG.info("没有车次基础数据,任务结束"); return; } for (Train train : trainList) { genDailyTrain(date, train); } } public void genDailyTrain(Date date, Train train) { LOG.info("生成日期【{}】车次【{}】的信息开始", DateUtil.formatDate(date), train.getCode()); // 删除该车次已有的数据 DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.createCriteria() .andDateEqualTo(date) .andCodeEqualTo(train.getCode()); dailyTrainMapper.deleteByExample(dailyTrainExample); // 生成该车次的数据 DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(train, DailyTrain.class); dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrain.setDate(date); dailyTrainMapper.insert(dailyTrain); // 生成该车次的车站数据 dailyTrainStationService.genDaily(date, train.getCode()); LOG.info("生成日期【{}】车次【{}】的信息结束", DateUtil.formatDate(date), train.getCode()); } }
-
测试
8.增加生成每日车厢功能
操作同上
-
DailyTrainCarriageService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.*; import com.neilxu.train.business.enums.SeatColEnum; import com.neilxu.train.business.mapper.DailyTrainCarriageMapper; import com.neilxu.train.business.req.DailyTrainCarriageQueryReq; import com.neilxu.train.business.req.DailyTrainCarriageSaveReq; import com.neilxu.train.business.resp.DailyTrainCarriageQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; @Service public class DailyTrainCarriageService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainCarriageService.class); @Resource private DailyTrainCarriageMapper dailyTrainCarriageMapper; @Resource private TrainCarriageService trainCarriageService; public void save(DailyTrainCarriageSaveReq req) { DateTime now = DateTime.now(); // 自动计算出列数和总座位数 List<SeatColEnum> seatColEnums = SeatColEnum.getColsByType(req.getSeatType()); req.setColCount(seatColEnums.size()); req.setSeatCount(req.getColCount() * req.getRowCount()); DailyTrainCarriage dailyTrainCarriage = BeanUtil.copyProperties(req, DailyTrainCarriage.class); if (ObjectUtil.isNull(dailyTrainCarriage.getId())) { dailyTrainCarriage.setId(SnowUtil.getSnowflakeNextId()); dailyTrainCarriage.setCreateTime(now); dailyTrainCarriage.setUpdateTime(now); dailyTrainCarriageMapper.insert(dailyTrainCarriage); } else { dailyTrainCarriage.setUpdateTime(now); dailyTrainCarriageMapper.updateByPrimaryKey(dailyTrainCarriage); } } public PageResp<DailyTrainCarriageQueryResp> queryList(DailyTrainCarriageQueryReq req) { DailyTrainCarriageExample dailyTrainCarriageExample = new DailyTrainCarriageExample(); dailyTrainCarriageExample.setOrderByClause("date desc, train_code asc, `index` asc"); DailyTrainCarriageExample.Criteria criteria = dailyTrainCarriageExample.createCriteria(); if (ObjUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjUtil.isNotEmpty(req.getTrainCode())) { criteria.andTrainCodeEqualTo(req.getTrainCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrainCarriage> dailyTrainCarriageList = dailyTrainCarriageMapper.selectByExample(dailyTrainCarriageExample); PageInfo<DailyTrainCarriage> pageInfo = new PageInfo<>(dailyTrainCarriageList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainCarriageQueryResp> list = BeanUtil.copyToList(dailyTrainCarriageList, DailyTrainCarriageQueryResp.class); PageResp<DailyTrainCarriageQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainCarriageMapper.deleteByPrimaryKey(id); } public void genDaily(Date date, String trainCode) { LOG.info("生成日期【{}】车次【{}】的车厢信息开始", DateUtil.formatDate(date), trainCode); // 删除某日某车次的车厢信息 DailyTrainCarriageExample dailyTrainCarriageExample = new DailyTrainCarriageExample(); dailyTrainCarriageExample.createCriteria() .andDateEqualTo(date) .andTrainCodeEqualTo(trainCode); dailyTrainCarriageMapper.deleteByExample(dailyTrainCarriageExample); // 查出某车次的所有的车厢信息 List<TrainCarriage> carriageList = trainCarriageService.selectByTrainCode(trainCode); if (CollUtil.isEmpty(carriageList)) { LOG.info("该车次没有车厢基础数据,生成该车次的车厢信息结束"); return; } for (TrainCarriage trainCarriage : carriageList) { DateTime now = DateTime.now(); DailyTrainCarriage dailyTrainCarriage = BeanUtil.copyProperties(trainCarriage, DailyTrainCarriage.class); dailyTrainCarriage.setId(SnowUtil.getSnowflakeNextId()); dailyTrainCarriage.setCreateTime(now); dailyTrainCarriage.setUpdateTime(now); dailyTrainCarriage.setDate(date); dailyTrainCarriageMapper.insert(dailyTrainCarriage); } LOG.info("生成日期【{}】车次【{}】的车厢信息结束", DateUtil.formatDate(date), trainCode); } }
-
DailyTrainService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrain; import com.neilxu.train.business.domain.DailyTrainExample; import com.neilxu.train.business.domain.Train; import com.neilxu.train.business.mapper.DailyTrainMapper; import com.neilxu.train.business.req.DailyTrainQueryReq; import com.neilxu.train.business.req.DailyTrainSaveReq; import com.neilxu.train.business.resp.DailyTrainQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; @Service public class DailyTrainService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainService.class); @Resource private DailyTrainMapper dailyTrainMapper; @Resource private TrainService trainService; @Resource private DailyTrainStationService dailyTrainStationService; @Resource private DailyTrainCarriageService dailyTrainCarriageService; public void save(DailyTrainSaveReq req) { DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(req, DailyTrain.class); if (ObjectUtil.isNull(dailyTrain.getId())) { dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrainMapper.insert(dailyTrain); } else { dailyTrain.setUpdateTime(now); dailyTrainMapper.updateByPrimaryKey(dailyTrain); } } public PageResp<DailyTrainQueryResp> queryList(DailyTrainQueryReq req) { DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.setOrderByClause("date desc, code asc"); DailyTrainExample.Criteria criteria = dailyTrainExample.createCriteria(); if (ObjectUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjectUtil.isNotEmpty(req.getCode())) { criteria.andCodeEqualTo(req.getCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrain> dailyTrainList = dailyTrainMapper.selectByExample(dailyTrainExample); PageInfo<DailyTrain> pageInfo = new PageInfo<>(dailyTrainList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainQueryResp> list = BeanUtil.copyToList(dailyTrainList, DailyTrainQueryResp.class); PageResp<DailyTrainQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainMapper.deleteByPrimaryKey(id); } /** * 生成某日所有车次信息,包括车次、车站、车厢、座位 * @param date */ public void genDaily(Date date) { List<Train> trainList = trainService.selectAll(); if (CollUtil.isEmpty(trainList)) { LOG.info("没有车次基础数据,任务结束"); return; } for (Train train : trainList) { genDailyTrain(date, train); } } public void genDailyTrain(Date date, Train train) { LOG.info("生成日期【{}】车次【{}】的信息开始", DateUtil.formatDate(date), train.getCode()); // 删除该车次已有的数据 DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.createCriteria() .andDateEqualTo(date) .andCodeEqualTo(train.getCode()); dailyTrainMapper.deleteByExample(dailyTrainExample); // 生成该车次的数据 DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(train, DailyTrain.class); dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrain.setDate(date); dailyTrainMapper.insert(dailyTrain); // 生成该车次的车站数据 dailyTrainStationService.genDaily(date, train.getCode()); // 生成该车次的车厢数据 dailyTrainCarriageService.genDaily(date, train.getCode()); LOG.info("生成日期【{}】车次【{}】的信息结束", DateUtil.formatDate(date), train.getCode()); } }
-
测试
结果正常
9.增加生成每日座位功能
操作同上,注意sell字段的逻辑,补0,个数是 经过车站数-1
-
TrainSeatService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.TrainCarriage; import com.neilxu.train.business.domain.TrainSeat; import com.neilxu.train.business.domain.TrainSeatExample; import com.neilxu.train.business.enums.SeatColEnum; import com.neilxu.train.business.mapper.TrainSeatMapper; import com.neilxu.train.business.req.TrainSeatQueryReq; import com.neilxu.train.business.req.TrainSeatSaveReq; import com.neilxu.train.business.resp.TrainSeatQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class TrainSeatService { private static final Logger LOG = LoggerFactory.getLogger(TrainSeatService.class); @Resource private TrainSeatMapper trainSeatMapper; @Resource private TrainCarriageService trainCarriageService; public void save(TrainSeatSaveReq req) { DateTime now = DateTime.now(); TrainSeat trainSeat = BeanUtil.copyProperties(req, TrainSeat.class); if (ObjectUtil.isNull(trainSeat.getId())) { trainSeat.setId(SnowUtil.getSnowflakeNextId()); trainSeat.setCreateTime(now); trainSeat.setUpdateTime(now); trainSeatMapper.insert(trainSeat); } else { trainSeat.setUpdateTime(now); trainSeatMapper.updateByPrimaryKey(trainSeat); } } public PageResp<TrainSeatQueryResp> queryList(TrainSeatQueryReq req) { TrainSeatExample trainSeatExample = new TrainSeatExample(); trainSeatExample.setOrderByClause("train_code asc, carriage_index asc, carriage_seat_index asc"); TrainSeatExample.Criteria criteria = trainSeatExample.createCriteria(); if (ObjectUtil.isNotEmpty(req.getTrainCode())) { criteria.andTrainCodeEqualTo(req.getTrainCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<TrainSeat> trainSeatList = trainSeatMapper.selectByExample(trainSeatExample); PageInfo<TrainSeat> pageInfo = new PageInfo<>(trainSeatList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<TrainSeatQueryResp> list = BeanUtil.copyToList(trainSeatList, TrainSeatQueryResp.class); PageResp<TrainSeatQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { trainSeatMapper.deleteByPrimaryKey(id); } @Transactional public void genTrainSeat(String trainCode) { DateTime now = DateTime.now(); // 清空当前车次下的所有的座位记录 TrainSeatExample trainSeatExample = new TrainSeatExample(); TrainSeatExample.Criteria criteria = trainSeatExample.createCriteria(); criteria.andTrainCodeEqualTo(trainCode); trainSeatMapper.deleteByExample(trainSeatExample); // 查找当前车次下的所有的车厢 List<TrainCarriage> carriageList = trainCarriageService.selectByTrainCode(trainCode); LOG.info("当前车次下的车厢数:{}", carriageList.size()); // 循环生成每个车厢的座位 for (TrainCarriage trainCarriage : carriageList) { // 拿到车厢数据:行数、座位类型(得到列数) Integer rowCount = trainCarriage.getRowCount(); String seatType = trainCarriage.getSeatType(); int seatIndex = 1; // 根据车厢的座位类型,筛选出所有的列,比如车箱类型是一等座,则筛选出columnList={ACDF} List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(seatType); LOG.info("根据车厢的座位类型,筛选出所有的列:{}", colEnumList); // 循环行数 for (int row = 1; row <= rowCount; row++) { // 循环列数 for (SeatColEnum seatColEnum : colEnumList) { // 构造座位数据并保存数据库 TrainSeat trainSeat = new TrainSeat(); trainSeat.setId(SnowUtil.getSnowflakeNextId()); trainSeat.setTrainCode(trainCode); trainSeat.setCarriageIndex(trainCarriage.getIndex()); trainSeat.setRow(StrUtil.fillBefore(String.valueOf(row), '0', 2)); trainSeat.setCol(seatColEnum.getCode()); trainSeat.setSeatType(seatType); trainSeat.setCarriageSeatIndex(seatIndex++); trainSeat.setCreateTime(now); trainSeat.setUpdateTime(now); trainSeatMapper.insert(trainSeat); } } } } public List<TrainSeat> selectByTrainCode(String trainCode) { TrainSeatExample trainSeatExample = new TrainSeatExample(); trainSeatExample.setOrderByClause("`id` asc"); TrainSeatExample.Criteria criteria = trainSeatExample.createCriteria(); criteria.andTrainCodeEqualTo(trainCode); return trainSeatMapper.selectByExample(trainSeatExample); } }
-
DailyTrainSeatService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.*; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import com.neilxu.train.business.mapper.DailyTrainSeatMapper; import com.neilxu.train.business.req.DailyTrainSeatQueryReq; import com.neilxu.train.business.req.DailyTrainSeatSaveReq; import com.neilxu.train.business.resp.DailyTrainSeatQueryResp; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; @Service public class DailyTrainSeatService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainSeatService.class); @Resource private DailyTrainSeatMapper dailyTrainSeatMapper; @Resource private TrainSeatService trainSeatService; @Resource private TrainStationService trainStationService; public void save(DailyTrainSeatSaveReq req) { DateTime now = DateTime.now(); DailyTrainSeat dailyTrainSeat = BeanUtil.copyProperties(req, DailyTrainSeat.class); if (ObjectUtil.isNull(dailyTrainSeat.getId())) { dailyTrainSeat.setId(SnowUtil.getSnowflakeNextId()); dailyTrainSeat.setCreateTime(now); dailyTrainSeat.setUpdateTime(now); dailyTrainSeatMapper.insert(dailyTrainSeat); } else { dailyTrainSeat.setUpdateTime(now); dailyTrainSeatMapper.updateByPrimaryKey(dailyTrainSeat); } } public PageResp<DailyTrainSeatQueryResp> queryList(DailyTrainSeatQueryReq req) { DailyTrainSeatExample dailyTrainSeatExample = new DailyTrainSeatExample(); dailyTrainSeatExample.setOrderByClause("train_code asc, carriage_index asc, carriage_seat_index asc"); DailyTrainSeatExample.Criteria criteria = dailyTrainSeatExample.createCriteria(); if (ObjectUtil.isNotEmpty(req.getTrainCode())) { criteria.andTrainCodeEqualTo(req.getTrainCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrainSeat> dailyTrainSeatList = dailyTrainSeatMapper.selectByExample(dailyTrainSeatExample); PageInfo<DailyTrainSeat> pageInfo = new PageInfo<>(dailyTrainSeatList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainSeatQueryResp> list = BeanUtil.copyToList(dailyTrainSeatList, DailyTrainSeatQueryResp.class); PageResp<DailyTrainSeatQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainSeatMapper.deleteByPrimaryKey(id); } public void genDaily(Date date, String trainCode) { LOG.info("生成日期【{}】车次【{}】的座位信息开始", DateUtil.formatDate(date), trainCode); // 删除某日某车次的座位信息 DailyTrainSeatExample dailyTrainSeatExample = new DailyTrainSeatExample(); dailyTrainSeatExample.createCriteria() .andDateEqualTo(date) .andTrainCodeEqualTo(trainCode); dailyTrainSeatMapper.deleteByExample(dailyTrainSeatExample); List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode); String sell = StrUtil.fillBefore("", '0', stationList.size() - 1); // 查出某车次的所有的座位信息 List<TrainSeat> seatList = trainSeatService.selectByTrainCode(trainCode); if (CollUtil.isEmpty(seatList)) { LOG.info("该车次没有座位基础数据,生成该车次的座位信息结束"); return; } for (TrainSeat trainSeat : seatList) { DateTime now = DateTime.now(); DailyTrainSeat dailyTrainSeat = BeanUtil.copyProperties(trainSeat, DailyTrainSeat.class); dailyTrainSeat.setId(SnowUtil.getSnowflakeNextId()); dailyTrainSeat.setCreateTime(now); dailyTrainSeat.setUpdateTime(now); dailyTrainSeat.setDate(date); dailyTrainSeat.setSell(sell); dailyTrainSeatMapper.insert(dailyTrainSeat); } LOG.info("生成日期【{}】车次【{}】的座位信息结束", DateUtil.formatDate(date), trainCode); } }
-
DailyTrainService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrain; import com.neilxu.train.business.domain.DailyTrainExample; import com.neilxu.train.business.domain.Train; import com.neilxu.train.business.mapper.DailyTrainMapper; import com.neilxu.train.business.req.DailyTrainQueryReq; import com.neilxu.train.business.req.DailyTrainSaveReq; import com.neilxu.train.business.resp.DailyTrainQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.Date; import java.util.List; @Service public class DailyTrainService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainService.class); @Resource private DailyTrainMapper dailyTrainMapper; @Resource private TrainService trainService; @Resource private DailyTrainStationService dailyTrainStationService; @Resource private DailyTrainCarriageService dailyTrainCarriageService; @Resource private DailyTrainSeatService dailyTrainSeatService; public void save(DailyTrainSaveReq req) { DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(req, DailyTrain.class); if (ObjectUtil.isNull(dailyTrain.getId())) { dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrainMapper.insert(dailyTrain); } else { dailyTrain.setUpdateTime(now); dailyTrainMapper.updateByPrimaryKey(dailyTrain); } } public PageResp<DailyTrainQueryResp> queryList(DailyTrainQueryReq req) { DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.setOrderByClause("date desc, code asc"); DailyTrainExample.Criteria criteria = dailyTrainExample.createCriteria(); if (ObjectUtil.isNotNull(req.getDate())) { criteria.andDateEqualTo(req.getDate()); } if (ObjectUtil.isNotEmpty(req.getCode())) { criteria.andCodeEqualTo(req.getCode()); } LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrain> dailyTrainList = dailyTrainMapper.selectByExample(dailyTrainExample); PageInfo<DailyTrain> pageInfo = new PageInfo<>(dailyTrainList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainQueryResp> list = BeanUtil.copyToList(dailyTrainList, DailyTrainQueryResp.class); PageResp<DailyTrainQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainMapper.deleteByPrimaryKey(id); } /** * 生成某日所有车次信息,包括车次、车站、车厢、座位 * @param date */ public void genDaily(Date date) { List<Train> trainList = trainService.selectAll(); if (CollUtil.isEmpty(trainList)) { LOG.info("没有车次基础数据,任务结束"); return; } for (Train train : trainList) { genDailyTrain(date, train); } } public void genDailyTrain(Date date, Train train) { LOG.info("生成日期【{}】车次【{}】的信息开始", DateUtil.formatDate(date), train.getCode()); // 删除该车次已有的数据 DailyTrainExample dailyTrainExample = new DailyTrainExample(); dailyTrainExample.createCriteria() .andDateEqualTo(date) .andCodeEqualTo(train.getCode()); dailyTrainMapper.deleteByExample(dailyTrainExample); // 生成该车次的数据 DateTime now = DateTime.now(); DailyTrain dailyTrain = BeanUtil.copyProperties(train, DailyTrain.class); dailyTrain.setId(SnowUtil.getSnowflakeNextId()); dailyTrain.setCreateTime(now); dailyTrain.setUpdateTime(now); dailyTrain.setDate(date); dailyTrainMapper.insert(dailyTrain); // 生成该车次的车站数据 dailyTrainStationService.genDaily(date, train.getCode()); // 生成该车次的车厢数据 dailyTrainCarriageService.genDaily(date, train.getCode()); // 生成该车次的座位数据 dailyTrainSeatService.genDaily(date, train.getCode()); LOG.info("生成日期【{}】车次【{}】的信息结束", DateUtil.formatDate(date), train.getCode()); } }
-
测试
注意:要先生成火车座位,再生成每日座位
10.增加手动生成某日车次数据功能
前端加按钮,选择日期,手动生成某日的车次数据
-
daily-train.vue
<template> <p> <a-space> <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> <train-select-view v-model="params.code" width="200px"></train-select-view> <a-button type="primary" @click="handleQuery()">查找</a-button> <a-button type="primary" @click="onAdd">新增</a-button> <a-button type="danger" @click="onClickGenDaily">手动生成车次信息</a-button> </a-space> </p> <a-table :dataSource="dailyTrains" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="删除后不可恢复,确认删除?" @confirm="onDelete(record)" ok-text="确认" cancel-text="取消"> <a style="color: red">删除</a> </a-popconfirm> <a @click="onEdit(record)">编辑</a> </a-space> </template> <template v-else-if="column.dataIndex === 'type'"> <span v-for="item in TRAIN_TYPE_ARRAY" :key="item.code"> <span v-if="item.code === record.type"> {{item.desc}} </span> </span> </template> </template> </a-table> <a-modal v-model:visible="visible" title="每日车次" @ok="handleOk" ok-text="确认" cancel-text="取消"> <a-form :model="dailyTrain" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="dailyTrain.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> </a-form-item> <a-form-item label="车次编号"> <train-select-view v-model="dailyTrain.code" @change="onChangeCode"></train-select-view> </a-form-item> <a-form-item label="车次类型"> <a-select v-model:value="dailyTrain.type"> <a-select-option v-for="item in TRAIN_TYPE_ARRAY" :key="item.code" :value="item.code"> {{item.desc}} </a-select-option> </a-select> </a-form-item> <a-form-item label="始发站"> <a-input v-model:value="dailyTrain.start" /> </a-form-item> <a-form-item label="始发站拼音"> <a-input v-model:value="dailyTrain.startPinyin" /> </a-form-item> <a-form-item label="出发时间"> <a-time-picker v-model:value="dailyTrain.startTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> <a-form-item label="终点站"> <a-input v-model:value="dailyTrain.end" /> </a-form-item> <a-form-item label="终点站拼音"> <a-input v-model:value="dailyTrain.endPinyin" /> </a-form-item> <a-form-item label="到站时间"> <a-time-picker v-model:value="dailyTrain.endTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> </a-form> </a-modal> <a-modal v-model:visible="genDailyVisible" title="生成车次" @ok="handleGenDailyOk" ok-text="确认" cancel-text="取消"> <a-form :model="genDaily" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="genDaily.date" placeholder="请选择日期"/> </a-form-item> </a-form> </a-modal> </template> <script> import { defineComponent, ref, onMounted } from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; import TrainSelectView from "@/components/train-select"; import dayjs from 'dayjs'; export default defineComponent({ name: "daily-train-view", components: {TrainSelectView}, setup() { const TRAIN_TYPE_ARRAY = window.TRAIN_TYPE_ARRAY; const visible = ref(false); let dailyTrain = ref({ id: undefined, date: undefined, code: undefined, type: undefined, start: undefined, startPinyin: undefined, startTime: undefined, end: undefined, endPinyin: undefined, endTime: undefined, createTime: undefined, updateTime: undefined, }); const dailyTrains = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); let params = ref({ code: null }); const genDaily = ref({ date: null }); const genDailyVisible = ref(false); const columns = [ { title: '日期', dataIndex: 'date', key: 'date', }, { title: '车次编号', dataIndex: 'code', key: 'code', }, { title: '车次类型', dataIndex: 'type', key: 'type', }, { title: '始发站', dataIndex: 'start', key: 'start', }, { title: '始发站拼音', dataIndex: 'startPinyin', key: 'startPinyin', }, { title: '出发时间', dataIndex: 'startTime', key: 'startTime', }, { title: '终点站', dataIndex: 'end', key: 'end', }, { title: '终点站拼音', dataIndex: 'endPinyin', key: 'endPinyin', }, { title: '到站时间', dataIndex: 'endTime', key: 'endTime', }, { title: '操作', dataIndex: 'operation' } ]; const onAdd = () => { dailyTrain.value = {}; visible.value = true; }; const onEdit = (record) => { dailyTrain.value = window.Tool.copy(record); visible.value = true; }; const onDelete = (record) => { axios.delete("/business/admin/daily-train/delete/" + record.id).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); } else { notification.error({description: data.message}); } }); }; const handleOk = () => { axios.post("/business/admin/daily-train/save", dailyTrain.value).then((response) => { let data = response.data; if (data.success) { notification.success({description: "保存成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/business/admin/daily-train/query-list", { params: { page: param.page, size: param.size, code: params.value.code, date: params.value.date } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { dailyTrains.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (pagination) => { // console.log("看看自带的分页参数都有啥:" + pagination); handleQuery({ page: pagination.current, size: pagination.pageSize }); }; const onChangeCode = (train) => { console.log("车次下拉组件选择:", train); let t = Tool.copy(train); delete t.id; // 用assign可以合并 dailyTrain.value = Object.assign(dailyTrain.value, t); }; const onClickGenDaily = () => { genDailyVisible.value = true; }; const handleGenDailyOk = () => { let date = dayjs(genDaily.value.date).format("YYYY-MM-DD"); axios.get("/business/admin/daily-train/gen-daily/" + date).then((response) => { let data = response.data; if (data.success) { notification.success({description: "生成成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { TRAIN_TYPE_ARRAY, dailyTrain, visible, dailyTrains, pagination, columns, handleTableChange, handleQuery, loading, onAdd, handleOk, onEdit, onDelete, onChangeCode, params, genDaily, genDailyVisible, handleGenDailyOk, onClickGenDaily }; }, }); </script>
-
测试
生成也没有问题
-
修改生成器,前端支持自定义分页pageSize
优化下分页
-
daily-train-seat.vue
<template> <p> <a-space> <train-select-view v-model="params.trainCode" width="200px"></train-select-view> <a-button type="primary" @click="handleQuery()">查找</a-button> </a-space> </p> <a-table :dataSource="dailyTrainSeats" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> </template> <template v-else-if="column.dataIndex === 'col'"> <span v-for="item in SEAT_COL_ARRAY" :key="item.code"> <span v-if="item.code === record.col && item.type === record.seatType"> {{item.desc}} </span> </span> </template> <template v-else-if="column.dataIndex === 'seatType'"> <span v-for="item in SEAT_TYPE_ARRAY" :key="item.code"> <span v-if="item.code === record.seatType"> {{item.desc}} </span> </span> </template> </template> </a-table> </template> <script> import { defineComponent, ref, onMounted } from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; import TrainSelectView from "@/components/train-select"; export default defineComponent({ name: "daily-train-seat-view", components: {TrainSelectView}, setup() { const SEAT_COL_ARRAY = window.SEAT_COL_ARRAY; const SEAT_TYPE_ARRAY = window.SEAT_TYPE_ARRAY; const visible = ref(false); let dailyTrainSeat = ref({ id: undefined, date: undefined, trainCode: undefined, carriageIndex: undefined, row: undefined, col: undefined, seatType: undefined, carriageSeatIndex: undefined, sell: undefined, createTime: undefined, updateTime: undefined, }); const dailyTrainSeats = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); let params = ref({ trainCode: null }); const columns = [ { title: '日期', dataIndex: 'date', key: 'date', }, { title: '车次编号', dataIndex: 'trainCode', key: 'trainCode', }, { title: '箱序', dataIndex: 'carriageIndex', key: 'carriageIndex', }, { title: '排号', dataIndex: 'row', key: 'row', }, { title: '列号', dataIndex: 'col', key: 'col', }, { title: '座位类型', dataIndex: 'seatType', key: 'seatType', }, { title: '同车箱座序', dataIndex: 'carriageSeatIndex', key: 'carriageSeatIndex', }, { title: '售卖情况', dataIndex: 'sell', key: 'sell', }, ]; const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/business/admin/daily-train-seat/query-list", { params: { page: param.page, size: param.size, trainCode: params.value.trainCode } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { dailyTrainSeats.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (page) => { // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page)); pagination.value.pageSize = page.pageSize; handleQuery({ page: page.current, size: page.pageSize }); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { SEAT_COL_ARRAY, SEAT_TYPE_ARRAY, dailyTrainSeat, visible, dailyTrainSeats, pagination, columns, handleTableChange, handleQuery, loading, params }; }, }); </script>
-
vue.ftl
<template> <p> <a-space> <a-button type="primary" @click="handleQuery()">刷新</a-button> <#if !readOnly><a-button type="primary" @click="onAdd">新增</a-button></#if> </a-space> </p> <a-table :dataSource="${domain}s" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <#if !readOnly> <a-space> <a-popconfirm title="删除后不可恢复,确认删除?" @confirm="onDelete(record)" ok-text="确认" cancel-text="取消"> <a style="color: red">删除</a> </a-popconfirm> <a @click="onEdit(record)">编辑</a> </a-space> </#if> </template> <#list fieldList as field> <#if field.enums> <template v-else-if="column.dataIndex === '${field.nameHump}'"> <span v-for="item in ${field.enumsConst}_ARRAY" :key="item.code"> <span v-if="item.code === record.${field.nameHump}"> {{item.desc}} </span> </span> </template> </#if> </#list> </template> </a-table> <#if !readOnly> <a-modal v-model:visible="visible" title="${tableNameCn}" @ok="handleOk" ok-text="确认" cancel-text="取消"> <a-form :model="${domain}" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <#list fieldList as field> <#if field.name!="id" && field.nameHump!="createTime" && field.nameHump!="updateTime"> <a-form-item label="${field.nameCn}"> <#if field.enums> <a-select v-model:value="${domain}.${field.nameHump}"> <a-select-option v-for="item in ${field.enumsConst}_ARRAY" :key="item.code" :value="item.code"> {{item.desc}} </a-select-option> </a-select> <#elseif field.javaType=='Date'> <#if field.type=='time'> <a-time-picker v-model:value="${domain}.${field.nameHump}" valueFormat="HH:mm:ss" placeholder="请选择时间" /> <#elseif field.type=='date'> <a-date-picker v-model:value="${domain}.${field.nameHump}" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> <#else> <a-date-picker v-model:value="${domain}.${field.nameHump}" valueFormat="YYYY-MM-DD HH:mm:ss" show-time placeholder="请选择日期" /> </#if> <#else> <a-input v-model:value="${domain}.${field.nameHump}" /> </#if> </a-form-item> </#if> </#list> </a-form> </a-modal> </#if> </template> <script> import { defineComponent, ref, onMounted } from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; export default defineComponent({ name: "${do_main}-view", setup() { <#list fieldList as field> <#if field.enums> const ${field.enumsConst}_ARRAY = window.${field.enumsConst}_ARRAY; </#if> </#list> const visible = ref(false); let ${domain} = ref({ <#list fieldList as field> ${field.nameHump}: undefined, </#list> }); const ${domain}s = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); const columns = [ <#list fieldList as field> <#if field.name!="id" && field.nameHump!="createTime" && field.nameHump!="updateTime"> { title: '${field.nameCn}', dataIndex: '${field.nameHump}', key: '${field.nameHump}', }, </#if> </#list> <#if !readOnly> { title: '操作', dataIndex: 'operation' } </#if> ]; <#if !readOnly> const onAdd = () => { ${domain}.value = {}; visible.value = true; }; const onEdit = (record) => { ${domain}.value = window.Tool.copy(record); visible.value = true; }; const onDelete = (record) => { axios.delete("/${module}/admin/${do_main}/delete/" + record.id).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); } else { notification.error({description: data.message}); } }); }; const handleOk = () => { axios.post("/${module}/admin/${do_main}/save", ${domain}.value).then((response) => { let data = response.data; if (data.success) { notification.success({description: "保存成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; </#if> const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/${module}/admin/${do_main}/query-list", { params: { page: param.page, size: param.size } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { ${domain}s.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (page) => { // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page)); pagination.value.pageSize = page.pageSize; handleQuery({ page: page.current, size: page.pageSize }); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { <#list fieldList as field> <#if field.enums> ${field.enumsConst}_ARRAY, </#if> </#list> ${domain}, visible, ${domain}s, pagination, columns, handleTableChange, handleQuery, loading, <#if !readOnly> onAdd, handleOk, onEdit, onDelete </#if> }; }, }); </script>
-
效果:
-
-
手动生成车次功能增加loading,防止重复点击
-
daily-train.vue
<template> <p> <a-space> <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> <train-select-view v-model="params.code" width="200px"></train-select-view> <a-button type="primary" @click="handleQuery()">查询</a-button> <a-button type="primary" @click="onAdd">新增</a-button> <a-button type="danger" @click="onClickGenDaily">手动生成车次信息</a-button> </a-space> </p> <a-table :dataSource="dailyTrains" :columns="columns" :pagination="pagination" @change="handleTableChange" :loading="loading"> <template #bodyCell="{ column, record }"> <template v-if="column.dataIndex === 'operation'"> <a-space> <a-popconfirm title="删除后不可恢复,确认删除?" @confirm="onDelete(record)" ok-text="确认" cancel-text="取消"> <a style="color: red">删除</a> </a-popconfirm> <a @click="onEdit(record)">编辑</a> </a-space> </template> <template v-else-if="column.dataIndex === 'type'"> <span v-for="item in TRAIN_TYPE_ARRAY" :key="item.code"> <span v-if="item.code === record.type"> {{item.desc}} </span> </span> </template> </template> </a-table> <a-modal v-model:visible="visible" title="每日车次" @ok="handleOk" ok-text="确认" cancel-text="取消"> <a-form :model="dailyTrain" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="dailyTrain.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期" /> </a-form-item> <a-form-item label="车次编号"> <train-select-view v-model="dailyTrain.code" @change="onChangeCode"></train-select-view> </a-form-item> <a-form-item label="车次类型"> <a-select v-model:value="dailyTrain.type"> <a-select-option v-for="item in TRAIN_TYPE_ARRAY" :key="item.code" :value="item.code"> {{item.desc}} </a-select-option> </a-select> </a-form-item> <a-form-item label="始发站"> <a-input v-model:value="dailyTrain.start" /> </a-form-item> <a-form-item label="始发站拼音"> <a-input v-model:value="dailyTrain.startPinyin" /> </a-form-item> <a-form-item label="出发时间"> <a-time-picker v-model:value="dailyTrain.startTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> <a-form-item label="终点站"> <a-input v-model:value="dailyTrain.end" /> </a-form-item> <a-form-item label="终点站拼音"> <a-input v-model:value="dailyTrain.endPinyin" /> </a-form-item> <a-form-item label="到站时间"> <a-time-picker v-model:value="dailyTrain.endTime" valueFormat="HH:mm:ss" placeholder="请选择时间" /> </a-form-item> </a-form> </a-modal> <a-modal v-model:visible="genDailyVisible" title="生成车次" @ok="handleGenDailyOk" :confirm-loading="genDailyLoading" ok-text="确认" cancel-text="取消"> <a-form :model="genDaily" :label-col="{span: 4}" :wrapper-col="{ span: 20 }"> <a-form-item label="日期"> <a-date-picker v-model:value="genDaily.date" placeholder="请选择日期"/> </a-form-item> </a-form> </a-modal> </template> <script> import { defineComponent, ref, onMounted } from 'vue'; import {notification} from "ant-design-vue"; import axios from "axios"; import TrainSelectView from "@/components/train-select"; import dayjs from 'dayjs'; export default defineComponent({ name: "daily-train-view", components: {TrainSelectView}, setup() { const TRAIN_TYPE_ARRAY = window.TRAIN_TYPE_ARRAY; const visible = ref(false); let dailyTrain = ref({ id: undefined, date: undefined, code: undefined, type: undefined, start: undefined, startPinyin: undefined, startTime: undefined, end: undefined, endPinyin: undefined, endTime: undefined, createTime: undefined, updateTime: undefined, }); const dailyTrains = ref([]); // 分页的三个属性名是固定的 const pagination = ref({ total: 0, current: 1, pageSize: 10, }); let loading = ref(false); let params = ref({ code: null }); const genDaily = ref({ date: null }); const genDailyVisible = ref(false); const genDailyLoading = ref(false); const columns = [ { title: '日期', dataIndex: 'date', key: 'date', }, { title: '车次编号', dataIndex: 'code', key: 'code', }, { title: '车次类型', dataIndex: 'type', key: 'type', }, { title: '始发站', dataIndex: 'start', key: 'start', }, { title: '始发站拼音', dataIndex: 'startPinyin', key: 'startPinyin', }, { title: '出发时间', dataIndex: 'startTime', key: 'startTime', }, { title: '终点站', dataIndex: 'end', key: 'end', }, { title: '终点站拼音', dataIndex: 'endPinyin', key: 'endPinyin', }, { title: '到站时间', dataIndex: 'endTime', key: 'endTime', }, { title: '操作', dataIndex: 'operation' } ]; const onAdd = () => { dailyTrain.value = {}; visible.value = true; }; const onEdit = (record) => { dailyTrain.value = window.Tool.copy(record); visible.value = true; }; const onDelete = (record) => { axios.delete("/business/admin/daily-train/delete/" + record.id).then((response) => { const data = response.data; if (data.success) { notification.success({description: "删除成功!"}); handleQuery({ page: pagination.value.current, size: pagination.value.pageSize, }); } else { notification.error({description: data.message}); } }); }; const handleOk = () => { axios.post("/business/admin/daily-train/save", dailyTrain.value).then((response) => { let data = response.data; if (data.success) { notification.success({description: "保存成功!"}); visible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; const handleQuery = (param) => { if (!param) { param = { page: 1, size: pagination.value.pageSize }; } loading.value = true; axios.get("/business/admin/daily-train/query-list", { params: { page: param.page, size: param.size, code: params.value.code, date: params.value.date } }).then((response) => { loading.value = false; let data = response.data; if (data.success) { dailyTrains.value = data.content.list; // 设置分页控件的值 pagination.value.current = param.page; pagination.value.total = data.content.total; } else { notification.error({description: data.message}); } }); }; const handleTableChange = (pagination) => { // console.log("看看自带的分页参数都有啥:" + pagination); handleQuery({ page: pagination.current, size: pagination.pageSize }); }; const onChangeCode = (train) => { console.log("车次下拉组件选择:", train); let t = Tool.copy(train); delete t.id; // 用assign可以合并 dailyTrain.value = Object.assign(dailyTrain.value, t); }; const onClickGenDaily = () => { genDailyVisible.value = true; }; const handleGenDailyOk = () => { let date = dayjs(genDaily.value.date).format("YYYY-MM-DD"); genDailyLoading.value = true; axios.get("/business/admin/daily-train/gen-daily/" + date).then((response) => { genDailyLoading.value = false; let data = response.data; if (data.success) { notification.success({description: "生成成功!"}); genDailyVisible.value = false; handleQuery({ page: pagination.value.current, size: pagination.value.pageSize }); } else { notification.error({description: data.message}); } }); }; onMounted(() => { handleQuery({ page: 1, size: pagination.value.pageSize }); }); return { TRAIN_TYPE_ARRAY, dailyTrain, visible, dailyTrains, pagination, columns, handleTableChange, handleQuery, loading, onAdd, handleOk, onEdit, onDelete, onChangeCode, params, genDaily, genDailyVisible, handleGenDailyOk, onClickGenDaily, genDailyLoading }; }, }); </script>
-
效果:
转圈
-