分类 技术 下的文章

需要提前配置 xxl-job 的数据库 在 \xxl-job-3.1.0\xxl-job-admin\src\main\resources\application.properties

#spring.datasource.url=jdbc:mysql://127.0.0.1:6003/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
#spring.datasource.username=root
#spring.datasource.password=JAmz5wFC
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

2025-07-05T07:06:46.png

springboot

        <!-- Job 相关 -->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
spring:
  application:
    name: xxl-job-combat-project

xxl:
  job:
    # 调度中心配置
    admin:
      addresses: http://127.0.0.1:58080/xxl-job-admin # 调度中心地址
    # 执行器配置
    executor:
      appname: ${spring.application.name} # 执行器名称,必须全局唯一,在调度中心配置时使用
      logpath: ./xxl-job/log # 任务日志存放路径
      logretentiondays: 30 # 日志保留天数
    # 通信令牌
    accessToken: QMLnVWvRCkswUQ6CrT1xmJ86i # 必须与调度中心配置的 accessToken 一致
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class XxlJobConfig {

    @Value("${xxl.job.admin.addresses:http://127.0.0.1:8080/xxl-job-admin}")
    private String adminAddresses;
    @Value("${xxl.job.accessToken}")
    private String accessToken;
    @Value("${xxl.job.executor.appname}")
    private String appname;
    @Value("${xxl.job.executor.logpath}")
    private String logPath;
    @Value("${xxl.job.executor.logretentiondays}")
    private int    logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        log.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        return xxlJobSpringExecutor;
    }
}

简单的 bean 模式

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;

@Component
public class AlarmClockHandler {

    @XxlJob("wakeUpTheAlarmClock")
    public void wakeUpTheAlarmClock() throws Exception {
        XxlJobHelper.log("要起床了哦"); // 在调度中心可以看到这个日志
    }
}

2025-07-05T07:07:12.png

![image](assets/image-20250617111605-mgl4gbu.png)

2025-06-17 11:14:03 [com.xxl.job.core.thread.JobThread#run]-[133]-[xxl-job, JobThread-5-1750130043336] 
----------- xxl-job job execute start -----------
----------- Param:
2025-06-17 11:14:03 [com.runbrick.job.handler.AlarmClockHandler#wakeUpTheAlarmClock]-[14]-[xxl-job, JobThread-5-1750130043336] 要起床了哦
2025-06-17 11:14:03 [com.xxl.job.core.thread.JobThread#run]-[179]-[xxl-job, JobThread-5-1750130043336] 
----------- xxl-job job execute end(finish) -----------
----------- Result: handleCode=200, handleMsg = null
2025-06-17 11:14:03 [com.xxl.job.core.thread.TriggerCallbackThread#callbackLog]-[197]-[xxl-job, executor TriggerCallbackThread] 
----------- xxl-job job callback finish.

[Load Log Finish]

简单的 bean 传参模式

![image](assets/image-20250617113316-yx1f1v9.png)

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;


@Component
public class AlarmClockHandler {

    @XxlJob("wakeUpTheAlarmClockParam")
    public void wakeUpTheAlarmClockParam() throws Exception {
        String param = XxlJobHelper.getJobParam();
        // 提取 param 中的 name
        Map<String, Object> paramMap = objectMapper.readValue(param, new TypeReference<Map<String, Object>>() {});
        XxlJobHelper.log("{}该起床了哦", paramMap.get("name")); // 在调度中心可以看到这个日志
    }

}

![image](assets/image-20250617113347-aeobsee.png)

{"name":"张三"}
2025-06-17 11:34:01 [com.xxl.job.core.thread.JobThread#run]-[133]-[xxl-job, JobThread-6-1750131241860] 
----------- xxl-job job execute start -----------
----------- Param:{"name":"张三"}
2025-06-17 11:34:01 [com.runbrick.job.handler.AlarmClockHandler#wakeUpTheAlarmClockParam]-[30]-[xxl-job, JobThread-6-1750131241860] 张三该起床了哦
2025-06-17 11:34:01 [com.xxl.job.core.thread.JobThread#run]-[179]-[xxl-job, JobThread-6-1750131241860] 
----------- xxl-job job execute end(finish) -----------
----------- Result: handleCode=200, handleMsg = null
2025-06-17 11:34:01 [com.xxl.job.core.thread.TriggerCallbackThread#callbackLog]-[197]-[xxl-job, executor TriggerCallbackThread] 
----------- xxl-job job callback finish.

[Load Log Finish]

Spring Event 是 Spring 框架内置的事件/监听器机制,是观察者模式的一种实现。它允许一个组件(发布者)发布一个事件,而其他一个或多个组件(监听器)可以订阅并响应该事件,而发布者和监听者之间没有直接的类依赖。

实现下面的实例:工人提交了维修工单后 ①发送邮件给维修站点师傅、②如果提交的是停机维修则要修改设备为停机、③增加一个操作记录

创建一个 springboot 项目,导入 web 坐标用来实现访问接口

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
     <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

启动类中添加 @EnableAsync 注解

@SpringBootApplication
@EnableAsync
public class BootEventProjectApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootEventProjectApplication.class, args);
    }

}

创建一个 dataobject 来模拟数据库的 DO

import lombok.Data;

@Data
public class WorkOrderDO {

    private String code;

    private String submitter;

    private String description;

    /**
     * 是否为停机维修
     */
    private Boolean isStop;
}

创建一个RepairOrderEvent 类继承 ApplicationEvent

ApplicationEvent 是Spring 框架中的一个核心类,用于实现事件驱动的应用程序。它是一个抽象类,用于发布和监听事件,是 Spring 中事件机制的基础。

import com.runbrick.event.dataobject.WorkOrderDO;
import lombok.Getter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;

@Getter
@ToString
public class RepairOrderEvent extends ApplicationEvent {

    private WorkOrderDO workOrderDO;

    public RepairOrderEvent(Object source) {
        super(source);
    }

    public RepairOrderEvent(Object source, WorkOrderDO workOrderDO) {
        super(source);
        this.workOrderDO = workOrderDO;
    }

}

实现 ①发送邮件给维修站点师傅、②如果提交的是停机维修则要修改设备为停机、③增加一个操作记录 这三个 Listener 用来实现在提交报修的时候来执行这几个操作


import com.runbrick.event.event.RepairOrderEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 设备停机处理监听器
 */
@Slf4j
@Component
public class ShutdownListener {

    /**
     * 通过条件判断是否执行停机处理
     * @param event
     */
    @EventListener(condition = "#event.workOrderDO.isStop == true ")
    @Async
    public void equipmentShutdown(RepairOrderEvent event) {
        log.info("[repair][设备停机处理]");
    }
}

import com.runbrick.event.event.RepairOrderEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 发送邮件
 */
@Slf4j
@Component
public class RepairEmailListener implements ApplicationListener<RepairOrderEvent> {
    @Override
    @Async
    public void onApplicationEvent(RepairOrderEvent event) {
        log.info("[onApplicationEvent][给用户({}) 发送邮件:{}]", event.getWorkOrderDO().getSubmitter(), event.getWorkOrderDO().getDescription());
    }
}
import com.runbrick.event.event.RepairOrderEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class LogListener implements ApplicationListener<RepairOrderEvent> {
    @Override
    @Async
    @Order(10)
    public void onApplicationEvent(RepairOrderEvent event) {
        log.info("[log][记录用户({}) 的提报逻辑]", event.getWorkOrderDO());
    }
}

注:

  • 实现监听器有两种方式一种是直接 implements ApplicationListener 另一种是 通过注解 @EventListener 的方式 。 这两种方式都可以,不过我更推荐使用注解的方式来实现
  • 通过 @Order() 可以实现 Listener 顺序
  • 通过 @EnableAsync@Async 实现异步,这是最主要的内容。通过 @Async 可以实现基于内存的消息队列的功能
  • @EventListener() 增加判断条件 condition = "#event.workOrderDO.isStop == true " 。用 SpEL 表达式来决定是否执行该监听器

创建一个 Controller 用来测试数据是否是我想的那样

import com.runbrick.event.dataobject.WorkOrderDO;
import com.runbrick.event.publisher.RepairService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/repair")
@AllArgsConstructor
@Slf4j
public class RepairController {
    private RepairService repairService;

    /**
     * 提交维修工单
     */
    @PostMapping("/submit")
    public void submit(@RequestBody WorkOrderDO workOrderDO) {
        repairService.submit(workOrderDO); // 提交工单
        log.info("[repair][提交工单成功]");
    }
}

通过 IDEA 提交测试

  1. 测试非停机
### 
POST http://localhost:18080/repair/submit
Content-Type: application/json

{
  "code": "30021",
  "submitter": "张三",
  "description": "设备出问题了",
  "isStop": false
}


2025-06-26T13:44:36.855+08:00  INFO 33352 --- [io-18080-exec-2] c.r.event.publisher.RepairService        : [repair][执行用户(WorkOrderDO(code=30021, submitter=张三, description=设备出问题了, isStop=false)) 的提报逻辑]
2025-06-26T13:44:36.857+08:00  INFO 33352 --- [io-18080-exec-2] c.r.event.controller.RepairController    : [repair][提交工单成功]
2025-06-26T13:44:36.857+08:00  INFO 33352 --- [         task-6] c.runbrick.event.listener.LogListener    : [log][记录用户(WorkOrderDO(code=30021, submitter=张三, description=设备出问题了, isStop=false)) 的提报逻辑]
2025-06-26T13:44:36.857+08:00  INFO 33352 --- [         task-7] c.r.event.listener.RepairEmailListener   : [onApplicationEvent][给用户(张三) 发送邮件:设备出问题了]
2025-06-26T17:02:37.155+08:00  INFO 33352 --- [io-18080-exec-7] c.r.event.publisher.RepairService        : [repair][执行用户(WorkOrderDO(code=30021, submitter=张三, description=设备出问题了, isStop=false)) 的提报逻辑]
2025-06-26T17:02:37.163+08:00  INFO 33352 --- [         task-8] c.runbrick.event.listener.LogListener    : [log][记录用户(WorkOrderDO(code=30021, submitter=张三, description=设备出问题了, isStop=false)) 的提报逻辑]
2025-06-26T17:02:37.163+08:00  INFO 33352 --- [         task-9] c.r.event.listener.RepairEmailListener   : [onApplicationEvent][给用户(张三) 发送邮件:设备出问题了]
2025-06-26T17:02:37.166+08:00  INFO 33352 --- [io-18080-exec-7] c.r.event.controller.RepairController    : [repair][提交工单成功]
  1. 测试停机
### 
POST http://localhost:18080/repair/submit
Content-Type: application/json

{
  "code": "30021",
  "submitter": "张三",
  "description": "设备出问题了",
  "isStop": true
}

2025-06-26T17:03:17.885+08:00  INFO 33352 --- [io-18080-exec-8] c.r.event.publisher.RepairService        : [repair][执行用户(WorkOrderDO(code=30021, submitter=张三, description=设备出问题了, isStop=true)) 的提报逻辑]
2025-06-26T17:03:17.886+08:00  INFO 33352 --- [        task-10] c.runbrick.event.listener.LogListener    : [log][记录用户(WorkOrderDO(code=30021, submitter=张三, description=设备出问题了, isStop=true)) 的提报逻辑]
2025-06-26T17:03:17.886+08:00  INFO 33352 --- [        task-11] c.r.event.listener.RepairEmailListener   : [onApplicationEvent][给用户(张三) 发送邮件:设备出问题了]
2025-06-26T17:03:17.886+08:00  INFO 33352 --- [io-18080-exec-8] c.r.event.controller.RepairController    : [repair][提交工单成功]
2025-06-26T17:03:17.887+08:00  INFO 33352 --- [        task-12] c.r.event.listener.ShutdownListener      : [repair][设备停机处理]

从这两个测试来看证明 在 @EventListener() 增加判断条件 condition = "#event.workOrderDO.isStop == true " 是可以的。

修改 Windows系统中C:\users\用户名 的 .condarc 文件

show_channel_urls: true
default_channels:
  - https://mirrors.ustc.edu.cn/anaconda/pkgs/main
  - https://mirrors.ustc.edu.cn/anaconda/pkgs/r
  - https://mirrors.ustc.edu.cn/anaconda/pkgs/msys2
custom_channels:
  conda-forge: https://mirrors.ustc.edu.cn/anaconda/cloud
  bioconda: https://mirrors.ustc.edu.cn/anaconda/cloud

int n = 10
String repeat = ".".repeat(n);
System.out.println(repeat);
// ..........

返回一个字符串,其 repeat() 括号中的值为该字符串重复指定次数后的拼接结果。若原字符串为空或重复次数为零,则返回空字符串。

会有异常:IllegalArgumentException – 若重复次数为负数

当你在 UaExpert 中看到 Bad Certificate: Untrusted 这说明 UaExpert 拒绝连接 OPC UA 服务器,因为服务器的证书尚未被信任。

  • 如果服务器使用的是一个“未知的”证书(即 UaExpert 没见过),它会被标记为 Untrusted
  • UaExpert 出于安全原因不会直接信任这个证书,除非你手动接受它。

弹窗后直接点击 “Trust Server Certificate” (或类似按钮)再次连接时就不会再报错了。

2025-07-05T08:09:20.png