展开菜单
首页 精品内容 本月促销 装机必备 Windows macOS软件 IOS软件 Android AI PDF教程 专题
全部分类

当前位置:

首页 > 编程开发 > SpringBoot Schedule调度任务的动态管理方法是什么

SpringBoot Schedule调度任务的动态管理方法是什么

前言定时任务动态管理分为两种方式:方式一:Web前台配置Trigger触发器(关联Cron)、ThreadPoolTaskScheduler类创建Scheduler方式下进行Schedule调度任务的动态管理方式二:基于已创建的Schedule调度任务的动态管理,即以组件类@Scheduled注解声明Schedule调度,在启动程序前一次性初始化,如:@ComponentpublicclassTestTask{privateDateTimeFormatterdf=DateTimeFormatter.ofP

前言

定时任务动态管理分为两种方式:

方式一:Web前台配置Trigger触发器(关联Cron)、ThreadPoolTaskScheduler类创建Scheduler方式下进行Schedule调度任务的动态管理

方式二:基于已创建的Schedule调度任务的动态管理,即以组件类 @Scheduled注解声明Schedule调度,在启动程序前一次性初始化,如:

@Component
public class TestTask {
    private DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Scheduled(cron = "0/2 * * * * ?")
    public void robReceiveExpireTask() {
        System.out.println(df.format(LocalDateTime.now()) + "测试测试");
    }
}

缺陷:目前无法在运行期间增加Schedule以及stop、Start、Reset等管理。

一、架构流程图

SpringBoot Schedule调度任务的动态管理方法是什么

二、代码实现流程

架构为SpringBoot + Spring + mybatis-plus

1.引入库

pom.xml



    
        merak-hyper-automation-boot
        com.merak.automation
        1.0.0
    
    4.0.0
    automation-quartz
    jar
    
        
            aliyun
            aliyun Repository
            http://maven.aliyun.com/nexus/content/groups/public
            
                false
            
        
    
    
        
        
            org.springframework
            spring-context-support
        
        
        
            org.springframework
            spring-web
        
        
        
            mysql
            mysql-connector-java
        
        
        
            com.alibaba
            druid-spring-boot-starter
        
        
            org.apache.commons
            commons-lang3
        
        
            jakarta.validation
            jakarta.validation-api
        
        
            com.alibaba
            fastjson
        
        
            commons-io
            commons-io
        
        
            com.fasterxml.jackson.core
            jackson-annotations
        
        
        
            org.springframework.boot
            spring-boot-starter-quartz
            2.2.5.RELEASE
        
    
    
        
            
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

resources目录下文件/application.yml:

spring:

profiles:

active: dev

resources目录下文件/application-dev.yml:

server:
  port: 12105
  servlet:
    context-path: /automation-quartz

management:
  endpoints:
    web:
      exposure:
        include: '*'

# Spring配置
spring:
  resources:
    static-locations: classpath:/static/,classpath:/templates/
  mvc:
    throw-exception-if-no-handler-found: true
    static-path-pattern: /**
  application:
    name: automation-workflow
  main:
    allow-bean-definition-overriding: true
  # 文件上传
  servlet:
    multipart:
      # 单个文件大小
      max-file-size: 2000MB
      # 设置总上传的文件大小
      max-request-size: 4000MB
  #json 时间戳统一转换
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  aop:
    proxy-target-class: true
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
  datasource:
    dynamic:
      druid:
        # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
        # 连接池的配置信息
        # 初始化大小,最小,最大
        initial-size: 1
        min-idle: 1
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        filters: stat,wall,slf4j
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      datasource:
        master:
          url: jdbc:mysql://127.0.0.1:3308/merak_dev?characterEncoding=UTF-8&useUnicode=true&useSSL=false
          username: root
          password: root
          driver-class-name: com.mysql.jdbc.Driver

#mybatis plus 设置
mybatis-plus:
  mapper-locations: classpath*:com/merak/hyper/automation/persist/**/xml/*Mapper.xml
  global-config:
    # 关闭MP3.0自带的banner
    banner: false
    db-config:
      id-type: ID_WORKER_STR
      # 默认数据库表下划线命名
      table-underline: true
  configuration:
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  level:
    com.merar.hyper: debug
    com.merak.hyper.automation.persist.**.mapper: debug
    org.springframework: warn

2.代码流程

启动MerakQuartzApplication类

package com.merak.hyper.automation;
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.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
 * @author chenjun
 * @version 1.0
 * @ClassName: MerakQuartzApplication
 * @description: 工单任务调度
 * @date 2022/9/22 10:30
 */
@EnableScheduling
@EnableAsync
@MapperScan(basePackages = {"com.merak.hyper.automation.persist.**.mapper"})
@SpringBootApplication(scanBasePackages = {"com.merak.hyper.automation.**"}, exclude = {SecurityAutoConfiguration.class})
public class MerakQuartzApplication {
    public static final Logger log = LoggerFactory.getLogger(MerakQuartzApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(MerakQuartzApplication.class, args);
    }
    private int taskSchedulerCorePoolSize = 15;
    private int awaitTerminationSeconds = 60;
    private String threadNamePrefix = "taskExecutor-";
    /**
     * @description: 实例化ThreadPoolTaskScheduler对象,用于创建ScheduledFuture scheduledFuture
     */
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(taskSchedulerCorePoolSize);
        taskScheduler.setThreadNamePrefix(threadNamePrefix);
        taskScheduler.setWaitForTasksToCompleteOnShutdown(false);
        taskScheduler.setAwaitTerminationSeconds(awaitTerminationSeconds);
        /**需要实例化线程*/
        taskScheduler.initialize();
//        isinitialized = true;
        log.info("初始化ThreadPoolTaskScheduler ThreadNamePrefix=" + threadNamePrefix + ",PoolSize=" + taskSchedulerCorePoolSize
                + ",awaitTerminationSeconds=" + awaitTerminationSeconds);
        return taskScheduler;
    }
    /**
     * @description: 实例化ThreadPoolTaskExecutor对象,管理asyncTask启动的线程,应用类为 ScheduledHelper 
     */
    @Bean("asyncTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("asyncTaskExecutor-");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        //修改拒绝策略为使用当前线程执行
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }
}

一、启动时项目启动时,加载任务关联的触发器,并全量执行流程。

initLineRunner类:

package com.merak.hyper.automation.Scheduling;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.merak.hyper.automation.persist.entity.AutoTriggerInfo;
import com.merak.hyper.automation.persist.entity.BusWorkflow;
import com.merak.hyper.automation.persist.service.IAutoTriggerInfoService;
import com.merak.hyper.automation.persist.service.IBusWorkflowService;
import com.merak.hyper.automation.util.CommonUtil;
import com.merak.hyper.automation.util.ScheduleUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
 * 项目启动时,加载数字员工关联的触发器,并全量执行
 * @Date: 2020/12/25:16:00
 **/
@Component
@Order(1)
public class initLineRunner implements CommandLineRunner {
    public static final Logger log = LoggerFactory.getLogger(initLineRunner.class);
    private DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Autowired
    private TaskService taskService;
    @Autowired
    private IAutoTriggerInfoService triggerInfoService;
    @Autowired
    private IBusWorkflowService workflowService;
    @Override
    public void run(String... args) {
        log.info("项目启动:加载数字员工关联的触发器信息并全量执行," + df.format(LocalDateTime.now()));
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.eq("wf_type", "3");//3:云托管
        wrapper.eq("wf_state", "1");
        List busWorkflows = workflowService.list(wrapper);
        List triggerInfos =  triggerInfoService.list();
        if( 0 == busWorkflows.size() || 0 == triggerInfos.size() ){
            log.info("数字员工关联的触发器信息不正确,员工记录数:"+busWorkflows.size()+",触发器记录数:"+triggerInfos.size());
        }
        else{
            //数字员工关联的触发器信息
            Map loadWfidAndTriggerInfo = CommonUtil.loadWfidAndTriggerInfo(busWorkflows,triggerInfos);
            Iterator> entries = loadWfidAndTriggerInfo.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry entry = entries.next();
                String wfId = entry.getKey();
                BusWorkflow workflow = busWorkflows.stream().filter( t -> wfId.equals(t.getWfId()) ).findAny().orElse(null);
                if( null != workflow ){
                    ScheduleUtil.start(new ScheduleTask(wfId,String.valueOf(workflow.getWfCreateuserId()),taskService), entry.getValue());
                }
            }
            log.info("数字员工关联的触发器信息全量执行完成,数字员工定时个数:"+loadWfidAndTriggerInfo.size()+","+df.format(LocalDateTime.now()));
        }
    }
}
核心代码:
```java
  ScheduleUtil.start(new ScheduleTask(wfId,String.valueOf(workflow.getWfCreateuserId()),taskService), entry.getValue());

Scheduler管理工具类:启动、取消、修改等管理

package com.merak.hyper.automation.util;
import com.merak.hyper.automation.Scheduling.ScheduleTask;
import com.merak.hyper.automation.persist.entity.AutoTriggerInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
/**
 * @version 1.0
 * @ClassName: ScheduleUtil
 * @description: Scheduler管理工具类:启动、取消、修改等管理
 */
public class ScheduleUtil {
    public static final Logger log = LoggerFactory.getLogger(ScheduleUtil.class);
    private static ThreadPoolTaskScheduler threadPoolTaskScheduler = SpringContextUtils.getBean(ThreadPoolTaskScheduler.class);
    //存储[数字员工wfI,dScheduledFuture]集合
    private static Map> scheduledFutureMap = new HashMap<>();
    /**
     * 启动
     *
     * @param scheduleTask 定时任务
     * @param triggerInfo
     */
    public static boolean start(ScheduleTask scheduleTask, AutoTriggerInfo triggerInfo) {
        String wfId = scheduleTask.getId();
        log.info("启动数字员工"+wfId+"定时任务线程" + scheduleTask.getId());
        ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(scheduleTask, new CronTrigger(triggerInfo.getLogicConfig()));
        scheduledFutureMap.put(wfId, scheduledFuture);
        return true;
    }
    /**
     * 取消
     *
     * @param scheduleTask 定时任务
     */
    public static boolean cancel(ScheduleTask scheduleTask) {
        log.info("关闭定时任务线程 taskId " + scheduleTask.getId());
        ScheduledFuture scheduledFuture = scheduledFutureMap.get(scheduleTask.getId());
        if (scheduledFuture != null && !scheduledFuture.isCancelled()) {
            scheduledFuture.cancel(false);
        }
        scheduledFutureMap.remove(scheduleTask.getId());
        return true;
    }
    /**
     * 修改
     *
     * @param scheduleTask 定时任务
     * @param triggerInfo
     */
    public static boolean reset(ScheduleTask scheduleTask, AutoTriggerInfo triggerInfo) {
        //先取消定时任务
        cancel(scheduleTask);
        //然后启动新的定时任务
        start(scheduleTask, triggerInfo);
        return true;
    }
}

ScheduleTask类:ScheduleTask任务类

package com.merak.hyper.automation.Scheduling;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * @version 1.0
 * @ClassName: ScheduleTask
 * @description: ScheduleTask,关联任务id、用户id和具体执行的TaskService类,实现Runnable类
 */
public class ScheduleTask implements Runnable {
    private static final int TIMEOUT = 30000;
    private String id;
    private String userId;
    private TaskService service;
    public static final Logger log = LoggerFactory.getLogger(ScheduleTask.class);
    public String getId() {
        return id;
    }
    /**
     * @param id      任务ID
     * @param service 业务类
     */
    public ScheduleTask(String id, String userId, TaskService service) {
        this.id = id;
        this.userId = userId;
        this.service = service;
    }
    @Override
    public void run() {
        log.info("ScheduleTask-执行数字员工消息的发送,id:"+ this.id + ",用户id:"+userId);
        service.work(this.id,this.userId);
    }
}
/**
 * @version 1.0
 * @ClassName: TaskService
 * @description: TaskService
 */
public interface TaskService {
    /**
     * 业务处理方法
     * @param keyword 关键参数
     * @param userId
     */
    void work(String keyword,String userId);
}
/**
 * @description: TaskService实现类,具体执行定时调度的业务
 */
@Service
public class TaskServiceImpl implements TaskService {
    public static final Logger log = LoggerFactory.getLogger(TaskServiceImpl.class);
    @Autowired
    private IAutoDeviceInfoService deviceInfoService;
    @Override
    public void work(String wfId,String userId) {
        try {
            log.info("定时任务:根据数字员工wfId"+ wfId +",用户id:"+userId+",发送消息...");
            //sendRobotMsg(wfId,userId);
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

二、通过WEB配置的变更,动态管理定时任务

ScheduledController类:scheduled Web业务层:启动、取消、修改等管理schedule

调度任务信息变更(如1:Trigger Cron变更 2:任务停止 3:任务新增加等)

package com.merak.hyper.automation.controller;
import com.merak.hyper.automation.common.core.domain.AjaxResult;
import com.merak.hyper.automation.common.core.vo.ScheduledApiVo;
import com.merak.hyper.automation.persist.entity.AutoTriggerInfo;
import com.merak.hyper.automation.persist.service.IAutoTriggerInfoService;
import com.merak.hyper.automation.util.ScheduledHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * @version 1.0
 * @ClassName: ScheduledController
 * @description: scheduled Web业务层:启动、取消、修改等管理schedule
 */
@RestController
@RequestMapping("/api/scheduled")
public class ScheduledController {
    public static final Logger log = LoggerFactory.getLogger(ScheduledController.class);
    @Autowired
    private IAutoTriggerInfoService triggerInfoService;
    @Autowired
    private ScheduledHelper scheduledHelper;
    @PostMapping("/add")
    public AjaxResult addScheduleds(@RequestBody ScheduledApiVo scheduledApiVo){
        AutoTriggerInfo autoTriggerInfo = triggerInfoService.getById(scheduledApiVo.getTriggerId());
        scheduledHelper.addScheduleds(scheduledApiVo,autoTriggerInfo);
        return AjaxResult.success();
    }
    @PostMapping("/reset")
    public AjaxResult resetScheduleds(@RequestBody ScheduledApiVo scheduledApiVo){
        AutoTriggerInfo autoTriggerInfo = triggerInfoService.getById(scheduledApiVo.getTriggerId());
        scheduledHelper.resetScheduleds(scheduledApiVo,autoTriggerInfo);
        return AjaxResult.success();
    }
    @PostMapping("/stop")
    public AjaxResult stopScheduleds(@RequestBody ScheduledApiVo scheduledApiVo){
        AutoTriggerInfo autoTriggerInfo = triggerInfoService.getById(scheduledApiVo.getTriggerId());
        scheduledHelper.stopScheduleds(scheduledApiVo);
        return AjaxResult.success();
    }
}
ScheduledHelper类:对外提供ScheduledHelper管理:创建、变更、停止
```java
package com.merak.hyper.automation.util;
import com.merak.hyper.automation.Scheduling.ScheduleTask;
import com.merak.hyper.automation.Scheduling.TaskService;
import com.merak.hyper.automation.common.core.vo.ScheduledApiVo;
import com.merak.hyper.automation.persist.entity.AutoTriggerInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
 * @version 1.0
 * @ClassName: ScheduledHelper
 * @description:对外提供ScheduledHelper管理:创建、变更、停止
 */
@Component
public class ScheduledHelper {
    public static final Logger log = LoggerFactory.getLogger(ScheduledHelper.class);
    /**
     * @description: 对外(Web)提供异步的Scheduleds增加操作
     */
    @Async("asyncTaskExecutor")
    public void addScheduleds(ScheduledApiVo scheduledApiVo, AutoTriggerInfo triggerInfo) {
        //addSchedule任务
        log.warn("创建原数字员工["+scheduledApiVo.getWfId()+"],同步启动Schedule任务");
        TaskService taskService = SpringContextUtils.getBean(TaskService.class);
        ScheduleUtil.start(new ScheduleTask(scheduledApiVo.getWfId(), scheduledApiVo.getUserId(), taskService), triggerInfo);
    }
    @Async("asyncTaskExecutor")
    public void resetScheduleds(ScheduledApiVo scheduledApiVo,AutoTriggerInfo triggerInfo) {
        //cron值改变,变更Schedule任务
        log.warn("数字员工["+scheduledApiVo.getWfId()+"]关联的触发器信息cron值改变,变更Schedule任务");
        TaskService taskService = SpringContextUtils.getBean(TaskService.class);
        ScheduleUtil.reset(new ScheduleTask(scheduledApiVo.getWfId(), scheduledApiVo.getUserId(), taskService), triggerInfo);
    }
    @Async("asyncTaskExecutor")
    public void stopScheduleds(ScheduledApiVo scheduledApiVo) {
        //移除Wfid,停止原Schedule任务
        log.warn("原数字员工["+scheduledApiVo.getWfId()+"]无效,同步停止Schedule任务");
        TaskService taskService = SpringContextUtils.getBean(TaskService.class);
        ScheduleUtil.cancel(new ScheduleTask(scheduledApiVo.getWfId(), scheduledApiVo.getUserId(), taskService));
    }
}

SpringContextUtils类:

package com.merak.hyper.automation.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @version 1.0
 * @ClassName: SpringContextUtils
 * @description: 加载Class对象
 */
@Component
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }
    public static  T getBean(Class requiredType) {
        return applicationContext.getBean(requiredType);
    }
    public static  T getBean(String name, Class requiredType) {
        return applicationContext.getBean(name, requiredType);
    }
    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }
    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }
    public static Class getType(String name) {
        return applicationContext.getType(name);
    }
}

ScheduledApiVo类:

import java.io.Serializable;
/**
 * @version 1.0
 * @ClassName: ScheduledApiVo
 * @description: scheduled Web业务层Api传递参数Vo类
 */
public class ScheduledApiVo implements Serializable {
    private String wfId;
    private String userId;
    private String triggerId;
    //set get 略
}

最终:Web端通过发送Http请求 ,调用ScheduledHelper管理类接口,实现Scheduled创建、变更、停止操作

  log.info("3:云托管更新启动数字员工操作");
  ScheduledApiVo scheduledApiVo = new ScheduledApiVo();
  scheduledApiVo.setWfId(wfId);
  scheduledApiVo.setUserId(String.valueOf(updateUserId));
  scheduledApiVo.setTriggerId(newTriggerInfo.getId());
  String webHookBody = JSON.toJSONString(scheduledApiVo);
  EmsApiUtil.SendQuartzMessage(url, "add", webHookBody);
 ******************** 分隔     ************************
  public static boolean SendQuartzMessage(String quartzUrl, String method, String webHookBody){
      boolean result = false;
      try{
          //org.apache.httpcomponents.httpclient sendPost,pom依赖如下dependency
          String resp = HttpClientUtil.sendPostByJson(quartzUrl+"/"+method, webHookBody,0);
          if( "error".equals(resp) || resp.contains("405 Not Allowed")){
              log.error("调用任务调度中心消息发送失败,地址:"+quartzUrl);
          }
          else {
              JSONObject jsonObject = JSON.parseObject(resp);
              if( "200".equals(String.valueOf(jsonObject.get("code"))) ){
                  result = true;
              }
              else{
                  log.error("调用任务调度中心失败,msg:"+String.valueOf(jsonObject.get("msg")));
              }
          }
      }catch (Exception e){
          log.error("调用任务调度中心失败,msg:"+e.getMessage());
      }
      return result;
  }
    
        org.apache.httpcomponents
        httpclient
        4.5.2
    
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
精品专题 更多
本月促销

正软商城本月促销专区,汇集办公、设计、安全、影音、系统工具及AI软件等正版软件优惠活动,提供限时折扣、特价授权和优惠购买信息,活动库存及价格以页面实时展示为准。

装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

Mac软件 更多
灵活计算器
灵活计算器

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

图几
图几

图几是一款适用于 macOS 的截图、标注与美化工具,支持离线操作保障隐私。界面整理和高频系统操作被放到一起考虑,桌面或窗口内容一多时,管理起来会更省心。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Menuist
Menuist

Menuist 是一款面向 macOS 的 Finder 右键菜单增强工具,主要用来补充新建文件、快捷导航等常用操作,让日常文件管理和访问路径时更高效、更顺手。

Mole
Mole

Mole 是一款专为 Mac 设计的深度清理优化工具,涵盖缓存清理、应用管理及实时状态监控等功能。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

WINDOWS 更多
Windows 10
Windows 10

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

思源笔记
思源笔记

思源笔记是一款本地笔记软件,提供所见即所得的编辑方式,为长文写作带来顺滑的体验。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

傲梅轻松备份
傲梅轻松备份

傲梅轻松备份是一款专业易用的数据备份软件,为重要数据提供安全保障。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

Office 365 简体中文
Office 365 简体中文

一款文字处理软件,一种订阅式的跨平台办公软件,基于云平台提供多种服务,通过将 Excel 和 Outlook 等应用与 OneDrive 和 Microsoft Teams 等强大的云服务相结合,Office 365 可让任何人使用任何设备随时随地创建和共享内容。

Wise Folder Hider Pro
Wise Folder Hider Pro

Wise Folder Hider Pro 是一款专业级文件和文件夹隐藏加密软件,为私密数据添加多重保护。高频操作更强调就近处理,浏览、整理和跨目录移动文件时,来回切换和重复点击都会少很多。

WALTR PRO
WALTR PRO

WALTR是一款电脑至iOS文件传输转换工具,操作简单,快速实现文件识别与传送。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

CodeExpander
CodeExpander

CodeExpander 是一款快捷短语输入增强工具,通过键入缩写自动展开为自定义文段,提升工作效率。任务管理和过程控制会更完整,持续下载、批量同步或需要稳定传输流程的场景会更适合它。

PinStack
PinStack

PinStack是一款轻量级的Windows平台剪贴板管理工具,优化您的剪贴板使用体验。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

Mountain Duck
Mountain Duck

Mountain Duck 是一款能将多个网盘挂载到本地的工具,像本地磁盘一样使用网盘。清理链路的完整性会更好一些,做应用卸载、残留处理和空间整理时,通常能少走很多手动排查步骤。

Seer
Seer

Seer是一款在Win平台下的空格键功能增强效率工具,只需轻敲空格键,就能预览几乎任何格式的文件。它更适合把零散的小功能集中起来使用,处理高频琐碎任务时会更省事。