Легко обрабатывать длительные задания с помощью Job Run В этом руководстве мы будем работать в вымышленной компании Acme Corp, и нам нужно сгенерировать квитанции о заработной плате для всех сотрудников Acme Corp.
Легко обрабатывать длительные задания с помощью Job Run В этом руководстве мы будем работать в вымышленной компании Acme Corp, и нам нужно сгенерировать квитанции о заработной плате для всех сотрудников Acme Corp. TLDR; вы можете найти полный проект в нашем репозитории Github: https://github.com/jobrunr/example-salary-slip
Легко обрабатывать длительные задания с помощью Job Run В этом руководстве мы будем работать в вымышленной компании Acme Corp, и нам нужно сгенерировать квитанции о заработной плате для всех сотрудников Acme Corp. TLDR; вы можете найти полный проект в нашем репозитории Github: || https://github .Технический директор для этого мы будем использовать 3 компонента с открытым исходным кодом: om/jobrunr/example-salary-slip
- Легко обрабатывать длительные задания с помощью Job Run В этом руководстве мы будем работать в вымышленной компании Acme Corp, и нам нужно сгенерировать квитанции о заработной плате для всех сотрудников Acme Corp. TLDR; вы можете найти полный проект в нашем репозитории Github: https://github .Технический директор для этого мы будем использовать 3 компонента с открытым исходным кодом: om/jobrJobRunr
- : JobRunr позволяет легко планировать и обрабатывать фоновые задания с помощью Java 8 lambda. unr/example-salary-slip Легко обрабатывать длительные задания с помощью Job Run В этом руководстве мы будем работать в вымышленной компании Acme Corp, и нам нужно сгенерировать квитанции о заработной плате для всех сотрудников Acme Corp. TLDR; вы можете найти полный проект в нашем репозитории Github: https://github .Технический директор для этого мы будем использовать 3 компонента с открытым исходным кодом: om/jobrJobRunr
- : JobRunr позволяет легко планировать и обрабатывать backgrIt, поддерживается постоянным хранилищем и может обрабатывать задания параллельным и распределенным образом. поиск вакансий с использованием Java 8 lambda. unr/example-salary-slip Легко обрабатывать длительные задания с помощью Job Run В этом руководстве мы будем работать в вымышленной компании Acme Corp, и нам нужно сгенерировать квитанции о заработной плате для всех сотрудников Acme Corp. TLDR; вы можете найти полный проект в нашем репозитории Github: https://github .Технический директор для этого мы будем использовать 3 компонента с открытым исходным кодом: om/jobrJobRunr
Во время этого урока мы сгенерируем еженедельные квитанции о заработной плате всех сотрудников Acme Corp и отправим их им по электронной почте. Как? Что ж, около
- создание повторяющегося задания с помощью Job Run, которое будет выполняться каждую неделю – все сотрудники Acme Corp будут использовать Spring Data Jpa, и для каждого из этих сотрудников запланируйте новое фоновое задание для создания квитанция о заработной плате
- каждое из этих фоновых заданий приведет к получению сотрудника и
- потреблять
Служба временных часов
, которая показывает количество часов, отработанных сотрудником за данную неделю. - создайте документ квитанции о заработной плате с помощью
Службы генерации документов
, который будет содержать создайте документ квитанции о заработной плате с помощью - Службы генерации документов
, который будет содержать
отправьте электронное письмо сотруднику с его квитанцией о заработной плате, используя
- потреблять
В этом руководстве мы опускаем весь импорт Java для краткости – чтобы найти их, просто посетите пример проекта на https://github.com/jobrunr/example-salary-slip
Для создания этой службы расчета заработной платы мы используем gradle и наш файл build.gradle
plugins { id 'java' id 'idea' id 'org.springframework.boot' version '2.2.2.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' } group 'org.paycheck' version '1.0-SNAPSHOT' repositories { mavenCentral() jcenter() mavenLocal() } configurations.all { exclude group: 'org.slf4j', module: 'slf4j-log4j12' } dependencies { implementation 'org.jobrunr:jobrunr:0.9.2' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' implementation 'org.wickedsource.docx-stamper:docx-stamper:1.4.0' implementation 'org.docx4j:docx4j-core:8.1.6' implementation 'org.docx4j:docx4j-JAXB-ReferenceImpl:11.1.3' implementation 'org.docx4j:docx4j-export-fo:8.1.6' implementation 'com.github.javafaker:javafaker:1.0.2' implementation 'com.h2database:h2' testImplementation 'org.junit.jupiter:junit-jupiter:5.6.1' testImplementation 'org.awaitility:awaitility:4.0.2' } test { useJUnitPlatform() }
Организация-сотрудник
Поскольку нам нужно создать квитанции о заработной плате для всех сотрудников, давайте начнем с класса Employee
– это простая сущность с некоторыми полями, такими как Имя
, Фамилия
и электронная почта
.
package org.jobrunr.example.employee; @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; protected Employee() { } public Employee(String firstName, String lastName, String email) { this(null, firstName, lastName, email); } public Employee(Long id, String firstName, String lastName, String email) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.email = email; } @Override public String toString() { return String.format( "Employee[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName); } public Long getId() { return id; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public String getEmail() { return email; } }
Должность сотрудника
EmployeeRepository
расширяет данные Spring CrudRepository
и добавляет дополнительный метод для извлечения всех i двойки сотрудников
package org.jobrunr.example.employee; public interface EmployeeRepository extends CrudRepository{ @Query("select e.id from Employee e") Stream getAllEmployeeIds(); }
Класс домена рабочей недели
Поскольку квитанция о заработной плате генерируется один раз в неделю, нам нужен класс, представляющий количество времени, которое сотрудник проработал на этой неделе, – Рабочая неделя
класс. В нем есть несколько дополнительных полей, таких как выходные и дата с и по, которые мы будем использовать для нашего сгенерированного документа о заработной плате.
package org.jobrunr.example.timeclock; public class WorkWeek { private final int weekNbr; private final BigDecimal workHoursMonday; private final BigDecimal workHoursTuesday; private final BigDecimal workHoursWednesday; private final BigDecimal workHoursThursday; private final BigDecimal workHoursFriday; private final LocalDate from; private final LocalDate to; public WorkWeek(BigDecimal workHoursMonday, BigDecimal workHoursTuesday, BigDecimal workHoursWednesday, BigDecimal workHoursThursday, BigDecimal workHoursFriday) { this.workHoursMonday = workHoursMonday; this.workHoursTuesday = workHoursTuesday; this.workHoursWednesday = workHoursWednesday; this.workHoursThursday = workHoursThursday; this.workHoursFriday = workHoursFriday; WeekFields weekFields = WeekFields.of(Locale.getDefault()); weekNbr = now().get(weekFields.weekOfWeekBasedYear()); this.from = now().with(TemporalAdjusters.previous(DayOfWeek.MONDAY)); this.to = now().with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); } public BigDecimal getWorkHoursMonday() { return workHoursMonday; } public BigDecimal getWorkHoursTuesday() { return workHoursTuesday; } public BigDecimal getWorkHoursWednesday() { return workHoursWednesday; } public BigDecimal getWorkHoursThursday() { return workHoursThursday; } public BigDecimal getWorkHoursFriday() { return workHoursFriday; } public int getWeekNbr() { return weekNbr; } public LocalDate getFrom() { return from; } public LocalDate getTo() { return to; } public BigDecimal getTotal() { return workHoursMonday .add(workHoursTuesday) .add(workHoursWednesday) .add(workHoursThursday) .add(workHoursFriday); } }
Служба часов Времени
Чтобы получить Рабочую неделю
класс для определенного сотрудника, мы создаем Служба временных часов
, которая является компонентом Spring. Служба временных часов
package org.jobrunr.example.timeclock; @Component public class TimeClockService { public WorkWeek getWorkWeekForEmployee(Long employeeId) { try { //simulate a long-during call Thread.sleep(ThreadLocalRandom.current().nextInt(3, 5 + 1) * 1000); return new WorkWeek( BigDecimal.valueOf(getRandomHours()), BigDecimal.valueOf(getRandomHours()), BigDecimal.valueOf(getRandomHours()), BigDecimal.valueOf(getRandomHours()), BigDecimal.valueOf(getRandomHours()) ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } private int getRandomHours() { Random r = new Random(); return r.nextInt((8 - 5) + 1) + 5; } }
Квитанция о зарплате
Теперь у нас есть все необходимые данные для создания вашей квитанции о заработной плате – за исключением самого класса Квитанция о заработной плате
:
package org.jobrunr.example.paycheck; public class SalarySlip { private final Employee employee; private final WorkWeek workWeek; public SalarySlip(Employee employee, WorkWeek workWeek) { this.employee = employee; this.workWeek = workWeek; } public Employee getEmployee() { return employee; } public WorkWeek getWorkWeek() { return workWeek; } public BigDecimal getTotal() { BigDecimal totalPerHour = getTotalPerHour(); BigDecimal amountOfWorkedHours = getAmountOfWorkedHours(); return totalPerHour.multiply(amountOfWorkedHours).setScale(2, RoundingMode.HALF_UP); } private BigDecimal getAmountOfWorkedHours() { return workWeek.getTotal(); } private BigDecimal getTotalPerHour() { return BigDecimal.valueOf(21.50); } }
Класс Квитанция о заработной плате
содержит все данные, необходимые для создания квитанции о заработной плате, расчета общей суммы и будет использоваться DocumentGenerationService
для создания квитанций о заработной плате в виде документов PDF.
Служба Создания документов
Служба Генерации документов
также является компонентом Spring и отвечает за создание фактических документов о заработной плате на основе шаблона word. Шаблон word содержит много заполнителей, таких как ${employee.Имя}, ${сотрудник.Фамилия} и ${Рабочая неделя.часы работы в понедельник.setScale(2)}, который будет заменен на DocxStamper с использованием данного объекта контекста – в нашем случае объекта SalarySlip. Наконец, документ Word со всеми заполненными полями преобразуется в
package org.jobrunr.example.paycheck; @Component public class DocumentGenerationService { public void generateDocument(Path wordTemplatePath, Path wordOutputPath, Object context) throws IOException, Docx4JException { Files.createDirectories(wordOutputPath.getParent().toAbsolutePath()); try(InputStream template = Files.newInputStream(wordTemplatePath); OutputStream out = Files.newOutputStream(wordOutputPath)) { final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); final DocxStamper stamper = new DocxStamperConfiguration().setFailOnUnresolvedExpression(true).build(); stamper.stamp(template, context, byteArrayOutputStream); Docx4J.toPDF(WordprocessingMLPackage.load(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())), out); } } }
Наконец, документ Word со всеми заполненными полями преобразуется в
Служба электронной почты снова является компонентом Spring и несет ответственность за отправку по электронной почте окончательного документа word о заработной плате сотруднику - она использует электронную почту Spring Boot Starter и использует
MimeMessage , созданный
JavaMailSender . В нем есть метод с именем
отправить квитанцию о заработной плате с двумя аргументами - классом
Employee и путем к PDF-документу квитанции о заработной плате для этого сотрудника. Используя эти аргументы, мы можем отправить как персонализированное текстовое электронное письмо, так и прикрепить фактическую квитанцию о заработной плате в качестве вложения.
JavaMailSender - это класс, предоставляемый Spring Boot Starter Mail и настроенный с помощью файла свойств. Он настраивается с помощью файла свойств, который вы можете найти
здесь
package org.jobrunr.example.email; @Component public class EmailService { public JavaMailSender emailSender; public EmailService(JavaMailSender emailSender) { this.emailSender = emailSender; } public void sendSalarySlip(Employee employee, Path salarySlipPath) throws MessagingException { MimeMessage message = emailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setTo(employee.getEmail()); helper.setSubject("Your weekly salary slip"); helper.setText(String.format("Dear %s,\n\nhere you can find your weekly salary slip. \n \nThanks again for your hard work,\nAcme corp", employee.getFirstName())); FileSystemResource file = new FileSystemResource(salarySlipPath); helper.addAttachment("Salary Slip", file); emailSender.send(message); } }
И, наконец,, служба квитанций о заработной плате
Служба квитанций о заработной плате – это последний шаг головоломки, который связывает все воедино:
- Служба квитанций о заработной плате – это последний шаг головоломки, который связывает все воедино:
- он использует другие компоненты, которые мы уже создали:
-
EmployeeRepository
для получения всех сотрудников из базы данных - служба
Time Clock служба
Time Clock сотрудник работал - служба
Создания документов
для создания квитанции о заработной плате из заданного шаблона Word - служба
электронной почты
для отправки персонального электронного письма с квитанцией о заработной плате в качестве приложения сотруднику
-
Это также два важных общедоступных метода:
Генерировать и отправлять повторяющийся слайд
Генерировать и отправлять повторяющийся слайд генерировать И отправлять квитанции о заработной плате использует идентификатор сотрудника для получения фактических данных о сотруднике, генерирует Это будет фоновое задание для выполнения задания, и оно вызывается из метода
генерировать и отправлять Квитанции о заработной плате Всем Сотрудникам . Это будет фоновое задание для выполнения задания, и оно вызывается из метода
генерировать и отправлять квитанции о заработной плате Всем сотрудникам. Мы аннотируем его аннотацией задания (это необязательно), чтобы иметь значимые имена в панели инструментов JobRunr.
@Job(name = "Generate and send salary slip to employee %0") public void generateAndSendSalarySlip(Long employeeId) throws Exception { final Employee employee = getEmployee(employeeId); Path salarySlipPath = generateSalarySlip(employee); emailService.sendSalarySlip(employee, salarySlipPath); }
Это будет фоновое задание для выполнения задания, и оно вызывается из метода || генерировать и отправлять квитанции о заработной плате Всем сотрудникам. Мы аннотируем его аннотацией задания (это необязательно), чтобы иметь значимые имена в панели инструментов JobRunr. Генерировать и отправлять вознаграждение всем сотрудникам
Это будет фоновое задание для выполнения задания, и оно вызывается из метода генерировать и отправлять квитанции о заработной плате Всем сотрудникам. Мы аннотируем его аннотацией задания (это необязательно), чтобы иметь значимые имена в панели инструментов JobRunr. generateAndSendSalarySlipToAllEmployees Наш основной метод, который будет запланирован каждую неделю - он получает поток идентификаторов сотрудников и, используя
BackgroundJob.enqueue метод JobRunr, мы создаем фоновое задание
Это будет фоновое задание для выполнения задания, и оно вызывается из метода генерировать и отправлять квитанции о заработной плате Всем сотрудникам. Мы аннотируем его аннотацией задания (это необязательно), чтобы иметь значимые имена в панели инструментов JobRunr. Generateandsendsalaryslip для всех сотрудников Наш основной метод, который будет планироваться каждую неделю – он получает поток идентификаторов сотрудников и использует метод BackgroundJob.generateAndSendSalarySlip
для каждого сотрудника. ставим в очередь
@Transactional(readOnly = true) @Job(name = "Generate and send salary slip to all employees") public void generateAndSendSalarySlipToAllEmployees() { final StreamallEmployees = employeeRepository.getAllEmployeeIds(); BackgroundJob. enqueue(allEmployees, (salarySlipService, employeeId) -> salarySlipService.generateAndSendSalarySlip(employeeId)); }
Полное Обслуживание квитанций о заработной плате
выглядит следующим образом:
package org.jobrunr.example.paycheck; @Component public class SalarySlipService { private static final Path salarySlipTemplatePath = Path.of("src/main/resources/templates/salary-slip-template.docx"); private final EmployeeRepository employeeRepository; private final TimeClockService timeClockService; private final DocumentGenerationService documentGenerationService; private final EmailService emailService; public SalarySlipService(EmployeeRepository employeeRepository, TimeClockService timeClockService, DocumentGenerationService documentGenerationService, EmailService emailService) { this.employeeRepository = employeeRepository; this.timeClockService = timeClockService; this.documentGenerationService = documentGenerationService; this.emailService = emailService; } @Transactional(readOnly = true) @Job(name = "Generate and send salary slip to all employees") public void generateAndSendSalarySlipToAllEmployees() { final StreamallEmployees = employeeRepository.getAllEmployeeIds(); BackgroundJob. enqueue(allEmployees, (salarySlipService, employeeId) -> salarySlipService.generateAndSendSalarySlip(employeeId)); } @Job(name = "Generate and send salary slip to employee %0") public void generateAndSendSalarySlip(Long employeeId) throws Exception { final Employee employee = getEmployee(employeeId); Path salarySlipPath = generateSalarySlip(employee); emailService.sendSalarySlip(employee, salarySlipPath); } private Path generateSalarySlip(Employee employee) throws Exception { final WorkWeek workWeek = getWorkWeekForEmployee(employee.getId()); final SalarySlip salarySlip = new SalarySlip(employee, workWeek); return generateSalarySlipDocumentUsingTemplate(salarySlip); } private Path generateSalarySlipDocumentUsingTemplate(SalarySlip salarySlip) throws Exception { Path salarySlipPath = Paths.get(System.getProperty("java.io.tmpdir"), String.valueOf(now().getYear()), format("workweek-%d", salarySlip.getWorkWeek().getWeekNbr()), format("salary-slip-employee-%d.docx", salarySlip.getEmployee().getId())); documentGenerationService.generateDocument(salarySlipTemplatePath, salarySlipPath, salarySlip); return salarySlipPath; } private WorkWeek getWorkWeekForEmployee(Long employeeId) { return timeClockService.getWorkWeekForEmployee(employeeId); } private Employee getEmployee(Long employeeId) { return employeeRepository.findById(employeeId).orElseThrow(() -> new IllegalArgumentException(format("Employee with id '%d' does not exist", employeeId))); } }
И последнее, но не менее важное – наше приложение Spring Boot
Приложение Spring Boot загружает наше приложение и содержит один важный фрагмент кода:
BackgroundJob.scheduleRecurringly( "generate-and-send-salary-slip", SalarySlipService::generateAndSendSalarySlipToAllEmployees, Cron.weekly(DayOfWeek.SUNDAY, 22) );
Этот вызов метода гарантирует, что метод генерировать и отправлять квитанции о заработной плате Всем сотрудникам
вашего SalarySlipService
будет запускаться каждое воскресенье в 10 вечера. В этом приложении SpringBootApplication мы создаем несколько поддельных сотрудников, определяем Источник данных
(в нашем случае простая база данных H2) и инициализируйте JobRunr, используя его fluent-api.
package org.jobrunr.example; @SpringBootApplication public class SalarySlipMicroService { public static void main(String[] args) { SpringApplication.run(SalarySlipMicroService.class, args); } @Bean public CommandLineRunner demo(EmployeeRepository repository) { final Faker faker = new Faker(); return (args) -> { for(int i = 0; i < 1000; i++) { repository.save(new Employee(faker.name().firstName(), faker.name().lastName(), faker.internet().emailAddress())); } BackgroundJob.scheduleRecurringly( "generate-and-send-salary-slip", SalarySlipService::generateAndSendSalarySlipToAllEmployees, Cron.weekly(DayOfWeek.SUNDAY, 22) ); Thread.currentThread().join(); }; } @Bean public DataSource dataSource() { final JdbcDataSource ds = new JdbcDataSource(); ds.setURL("jdbc:h2:" + Paths.get(System.getProperty("java.io.tmpdir"), "paycheck")); ds.setUser("sa"); ds.setPassword("sa"); return ds; } @Bean public JobScheduler initJobRunr(ApplicationContext applicationContext) { return JobRunr.configure() .useStorageProvider(SqlStorageProviderFactory .using(applicationContext.getBean(DataSource.class))) .useJobActivator(applicationContext::getBean) .useDefaultBackgroundJobServer() .useDashboard() .initialize(); } }
Как только вы запустите микросервисное приложение Salary Slip, вы можете открыть свой браузер, используя URL-адрес http://localhost:8000/dashboard/ и перейдите на вкладку Повторяющиеся задания.
Чтобы протестировать его, мы запускаем его сейчас вручную. Задание обрабатывается и назначается новое задание для создания квитанции о заработной плате для каждого сотрудника. В течение 15 секунд начнется обработка этих заданий, и мы увидим сгенерированные PDF-документы в вашей папке tmp.
Мы можем проверить задание и увидеть, что оно выполнено успешно – если по какой-то причине оно завершится неудачей, оно будет автоматически повторено.
Мы можем проверить задание и увидеть, что оно выполнено успешно – если по какой-то причине оно завершится неудачей, оно будет автоматически повторено.
- Job run и Spring Data очень хорошо интегрируются, и оба они очень просты в использовании. Возможность планировать Java 8 lambda и запускать их в фоновом режиме – действительно приятная особенность Jobrunner.
- Чтобы преобразовать документ Word в PDF, в шаблоне word есть некоторые неприятные вещи (например, белый текст), чтобы иметь ОК-макет. Docx-Stamper – отличная библиотека и зависит от Docx4J. Docx4J позволяет конвертировать документы Word в PDF, но это все еще требует некоторой работы, так как для правильного макета было сделано несколько взломов. Преобразование из Word в PDF также занимает много времени, что идеально подходит для Jobrunner, поскольку это длительная фоновая работа.
Оригинал: “https://dev.to/rdehuyss/easily-process-long-running-jobs-with-jobrunr-5gkh”