SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(多数据源配置)

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(多数据源配置)

  • 前言
  • 多数据源配置
    • 引入aop依赖
    • 1. properties配置多数据源
    • 2. 创建数据源枚举类
    • 3. 线程参数配置类
    • 4. 数据源动态切换类
    • 5. 多数据源配置类
      • HikariCP 版本
      • Druid 版本
    • 6. 自定义多数据源切换注解
    • 7. 数据源注解截面AOP
    • 8. 测试 多数据源 是否起效

前言

我这边的SpringBoot的版本为2.6.13,其中Quartz是使用的以下方式引入

<!--quartz定时任务-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

由于我目前需要使用@QuartzDataSource去指定数据源,所以就不通过@DS注解去实现多数据源
而是通过自定义DynamicDataSource 去实现了多数据源

多数据源配置

引入aop依赖

<!--spring切面aop依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

1. properties配置多数据源

# 应用服务 WEB 访问端口
server.port=9081# 主数据源
spring.datasource.db1.url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db1.jdbc-url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db1.username=root
spring.datasource.db1.password=root
spring.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver# 次数据源
spring.datasource.db2.url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db2.jdbc-url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db2.username=root
spring.datasource.db2.password=root
spring.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driver# quartz 数据源
#spring.datasource.task.url=jdbc:mysql://localhost:3306/quartz
spring.datasource.task.jdbc-url=jdbc:mysql://localhost:3306/quartz
spring.datasource.task.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.task.username=root
spring.datasource.task.password=root# Quartz Scheduler 配置
# 指定作业存储类型为JDBC,使用数据库来存储作业和调度信息
spring.quartz.job-store-type=jdbc
# 在关闭时等待作业完成
spring.quartz.wait-for-jobs-to-complete-on-shutdown=true
# 不初始化数据库架构,假设数据库架构已经存在
spring.quartz.jdbc.initialize-schema=never
# Quartz Scheduler 属性配置
spring.quartz.properties.org.quartz.jobStore.dataSource=quartz_jobs
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
# 启用集群模式
spring.quartz.properties.org.quartz.jobStore.isClustered=true
# 集群检查间隔时间(毫秒)
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=1000
# 不使用属性来存储作业数据,改为使用BLOB字段
spring.quartz.properties.org.quartz.jobStore.useProperties=false
# 指定作业存储的类,这里使用Spring提供的LocalDataSourceJobStore,但通常这个配置是隐式的,
# 除非你有特殊的实现需求,否则通常不需要显式设置这个属性。
# spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
# 注意:上面的行已被注释掉,因为通常不需要显式设置
# 调度器实例名称和ID(org.quartz.scheduler.instanceName 这个是保证属于同一个集群)
spring.quartz.properties.org.quartz.scheduler.instanceName=SC_Scheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
# 线程池配置
# 线程池中线程的数量
spring.quartz.properties.org.quartz.threadPool.threadCount=25
# 线程优先级
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
# 线程池实现类
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool

2. 创建数据源枚举类

package com.example.springbootfull.quartztest.enums;/*** 数据源*/
public enum DataSourceType {/*** 数据源1* */DB1,/*** 数据源2* */DB2
}

3. 线程参数配置类

定义一个工具类来设置当前线程的数据源枚举值

package com.example.springbootfull.quartztest.datasource;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** 数据源切换处理*/
public class DynamicDataSourceContextHolder {public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);/*** 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。*/private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();/*** 设置数据源的变量*/public static void setDataSourceType(String dsType) {log.info("切换到{}数据源", dsType);CONTEXT_HOLDER.set(dsType);}/*** 获得数据源的变量*/public static String getDataSourceType() {return CONTEXT_HOLDER.get();}/*** 清空数据源变量*/public static void clearDataSourceType() {CONTEXT_HOLDER.remove();}
}

4. 数据源动态切换类

package com.example.springbootfull.quartztest.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** Spring的AbstractRoutingDataSource抽象类,实现动态数据源(他的作用就是动态切换数据源)* AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心,* 这里对该方法进行Override。【上下文DynamicDataSourceContextHolder为一线程安全的ThreadLocal】*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 取得当前使用哪个数据源* @return dbTypeEnum*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}
}

5. 多数据源配置类

HikariCP 版本

package com.example.springbootfull.quartztest.config;import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.example.springbootfull.quartztest.datasource.DynamicDataSource;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 多数据源配置*/
@Configuration
public class DataSourceConfig {/*** 创建第一个数据源** @return dataSource*/@Bean(name = "dataSource1")@ConfigurationProperties(prefix = "spring.datasource.db1")public DataSource dataSource1() {return DataSourceBuilder.create().build();}/*** 创建第二个数据源** @return dataSource*/@Bean(name = "dataSource2")@ConfigurationProperties(prefix = "spring.datasource.db2")public DataSource dataSource2() {return DataSourceBuilder.create().build();}//quartz数据库 dataSourceTask数据源//使用@QuartzDataSource后,不需要动态配置@Bean(name = "dataSourceTask")@ConfigurationProperties(prefix = "spring.datasource.task")@QuartzDataSourcepublic DataSource dataSourceTask() {return DataSourceBuilder.create().build();}/*** 动态数据源配置** @return dataSource*/@Primary@Bean("multipleDataSource")public DataSource multipleDataSource(@Qualifier("dataSource1") DataSource db1,@Qualifier("dataSource2") DataSource db2) {DynamicDataSource dynamicDataSource = new DynamicDataSource();Map<Object, Object> dataSources = new HashMap<>();dataSources.put(DataSourceType.DB1, db1);dataSources.put(DataSourceType.DB2, db2);dynamicDataSource.setTargetDataSources(dataSources);//默认数据源dynamicDataSource.setDefaultTargetDataSource(db1);return dynamicDataSource;}@Bean("sqlSessionFactory")public SqlSessionFactory sqlSessionFactory(@Qualifier("multipleDataSource") DataSource multipleDataSource) throws Exception {// 导入mybatissqlsession配置MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();// 指明数据源sessionFactory.setDataSource(multipleDataSource);// 设置mapper.xml的位置路径Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml");sessionFactory.setMapperLocations(resources);//指明实体扫描(多个package用逗号或者分号分隔)//sessionFactory.setTypeAliasesPackage("com.szylt.projects.project.entity");// 导入mybatis配置MybatisConfiguration configuration = new MybatisConfiguration();configuration.setJdbcTypeForNull(JdbcType.NULL);configuration.setMapUnderscoreToCamelCase(true);configuration.setCacheEnabled(false);sessionFactory.setConfiguration(configuration);return sessionFactory.getObject();}//数据源事务配置@Beanpublic PlatformTransactionManager transactionManager(DataSource multipleDataSource) {return new DataSourceTransactionManager(multipleDataSource);}}

Druid 版本

需要先引入 Druid 依赖,这里使用的是 Druid 官方的 Starter

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version>
</dependency>

然后,在properties配置文件中 为每个数据源配置DruidDataSour数据库连接池

spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.task.type=com.alibaba.druid.pool.DruidDataSour

接着把一下DataSourceConfig 类里面的
DataSource 对象 换成 DruidDataSource 对象
DataSourceBuilder 对象 换成 DruidDataSourceBuilder 对象
就好了

6. 自定义多数据源切换注解

package com.example.springbootfull.quartztest.annotation;import com.example.springbootfull.quartztest.enums.DataSourceType;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义多数据源切换注解* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{/*** 切换数据源名称*/public DataSourceType value() default DataSourceType.DB1;
}

7. 数据源注解截面AOP

package com.example.springbootfull.quartztest.aspectj;import java.util.Objects;import com.example.springbootfull.quartztest.annotation.DataSource;
import com.example.springbootfull.quartztest.datasource.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** 多数据源处理**/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {protected Logger logger = LoggerFactory.getLogger(getClass());@Pointcut("@annotation(com.example.springbootfull.quartztest.annotation.DataSource)"+ "|| @within(com.example.springbootfull.quartztest.annotation.DataSource)")public void dsPointCut() {}@Around("dsPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {DataSource dataSource = getDataSource(point);if (dataSource != null) {DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());}try {return point.proceed();} finally {// 销毁数据源 在执行方法之后DynamicDataSourceContextHolder.clearDataSourceType();}}/*** 获取需要切换的数据源*/public DataSource getDataSource(ProceedingJoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature();DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);if (Objects.nonNull(dataSource)) {return dataSource;}return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);}
}

8. 测试 多数据源 是否起效

创建了一个控制层测试类

package com.example.springbootfull.mybatisplustest.controller;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.springbootfull.mybatisplustest.entity.SysUser;
import com.example.springbootfull.mybatisplustest.mapper.SysUserMapper;
import com.example.springbootfull.quartztest.annotation.DataSource;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;/*** <p>*  前端控制器* </p>** @author jiang* @since 2024-09-13*/
@Controller
@RequestMapping("/mybatisplustest/sysUser")
public class SysUserController {@Autowiredprivate SysUserMapper sysUserMapper;@RequestMapping("/selectAll")@DataSource(value = DataSourceType.DB1)public void contextLoads() {//第一页,10个Page<SysUser> page = new Page<>(1, 10);QueryWrapper<SysUser> wrapper = new QueryWrapper<>();//查询liek人1的//wrapper.eq("enabled", true).like("account", "小明机器人10号");//查询授权=true的;wrapper.like("account", "人1");IPage<SysUser> iPage = sysUserMapper.selectPage(page, wrapper);//总页数System.out.println("总页数:"+iPage.getPages());//总条数System.out.println("总条数:"+iPage.getTotal());//每页条数System.out.println("每页条数:"+iPage.getSize());//当前页的结果集System.out.println("当前页的结果集:"+iPage.getRecords());//当前页号System.out.println("当前页号:"+iPage.getCurrent());//        // 创建实体类
//        SysUser sysUser = new SysUser();
//        sysUser.setAccount("mybtis呀");
//        sysUser.setEnabled(Boolean.TRUE);
//        sysUser.setCreateAt(new Date());
//        sysUserDao.insertSelective(sysUser);
//
//        // 根据自增ID检索实体
//        SysUser sysUser1 = sysUserDao.selectByPrimaryKey(sysUser.getId());//        logger.info("user={}", sysUser1);}}

运行如下截图:
两者都有触发
在这里插入图片描述
参考文章如下:
【1】Springboot+Mybatis+MySql整合多数据源及其使用
【2】Springboot定时任务quartz整合(多数据源+quartz持久化到数据库)
【3】springboot+MybatisPlus+HikariCP多数据源动态配置(实战篇)
【4】springboot+mybatis-plus+quartz多数据源操作,亲测可用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/56154.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何用好 CloudFlare 的速率限制防御攻击

最近也不知道咋回事儿,群里好多站长都反映被CC 攻击了。有人说依旧是 PCDN 干的,但明月感觉不像,因为有几个站长被 CC 攻击都是各种动态请求(这里的动态请求指的是.php 文件的请求)。经常被攻击的站长们都知道,WordPress /Typecho 这类动态博客系统最怕的就是这种动态请求…

删除 Word 空白页的 3 种方法总结

在使用 Word 进行文档编辑的时候&#xff0c;空白页的出现常常会让用户感到困扰&#xff0c;Word 空白页的出现可能是由于分页、段落设置以及格式问题&#xff0c;空白页可能会出现在文档的开始、中间及结尾&#xff0c;如果需要打印文档还会影响打印效果&#xff0c;那么 Word…

Visual Studio--VS安装配置使用教程

Visual Studio Visual Studio 是一款功能强大的开发人员工具&#xff0c;可用于在一个位置完成整个开发周期。 它是一种全面的集成开发环境 (IDE)。对新手特别友好&#xff0c;使用方便&#xff0c;不需要复杂的去配置环境。用它学习很方便。 Studio安装教程 Visual Studio官…

基于Data+AI构建真正的流批一体!

基于DataAI构建真正的流批一体&#xff01; 前言流批一体的前世今生如何构建真正的流批一体架构数据架构统一数据处理引擎的选择数据治理与数据质量 流批一体的实践案例结语 前言 你是不是也有过这种感觉&#xff1f; 当你坐在电脑前&#xff0c;面对海量数据&#xff0c;心里…

第十四届单片机嵌入式蓝桥杯

一、CubeMx配置 &#xff08;1&#xff09;LED配置 &#xff08;1&#xff09;LED灯里面用到了SN74HC573ADWR锁存器&#xff0c;这个锁存器有一个LE引脚,这个是我们芯片的锁存引脚&#xff08;使能引脚&#xff09;&#xff0c;由PD2这个端口来控制的 &#xff08;2&#xff…

Nullinux:一款针对Linux操作系统的安全检测工具

关于Nullinux Nullinux是一款针对Linux操作系统的安全检测工具&#xff0c;广大研究人员可以利用该工具针对Linux目标设备执行网络侦查和安全检测。 该工具可以通过SMB枚举目标设备的安全状况信息&#xff0c;其中包括操作系统信息、域信息、共享信息、目录信息和用户信息。如…

面对服务器掉包的时刻困扰,如何更好的解决

在数字化时代&#xff0c;服务器的稳定运行是企业业务连续性的基石。然而&#xff0c;服务器“掉包”现象&#xff0c;即数据包在传输过程中丢失或未能正确到达目的地的情况&#xff0c;却时常成为IT运维人员头疼的问题。它不仅影响用户体验&#xff0c;还可能导致数据不一致、…

PyQt 入门教程(2)搭建开发环境

文章目录 一、搭建开发环境1、安装PyQt6与pyqt6-tools2、配置外部工具QtDesigner与PYUIC 一、搭建开发环境 1、安装PyQt6与pyqt6-tools PyQt6&#xff1a; PyQt的开发库。pyqt6-tools&#xff1a; QtDesigner 设计器支撑库。 通过PyCharm安装开发库&#xff0c;命令如下&…

基于STM32 ARM+FPGA+AD的电能质量分析仪方案设计(一)硬件设计

电能质量分析系统硬件设计 3.1 电能质量分析系统设计要求 本系统实现对电能质量的高精度测量&#xff0c;根据国家相关电能质量分析仪器规定 标准以及对市场电能质量分析仪的分析&#xff0c;指定以下设计目标。 &#xff08; 1 &#xff09;电能质量参数测量精度&#xf…

go发送邮件:在Go语言中实现发邮件的教程?

go发送邮件的教程指南&#xff1f;怎么使用Go语言发送电子邮件&#xff1f; Go语言&#xff0c;作为一种简洁、高效且并发性强的编程语言&#xff0c;自然也提供了丰富的库来支持邮件发送功能。AokSend将详细介绍如何在Go语言中实现发送邮件的功能&#xff0c;帮助你快速掌握这…

关于Git Bash中如何定义alias

一、在一次临时Bash会话中使用alias 在Bash中直接输入alias xxdddd&#xff0c;xx为对应要执行的命令的缩写&#xff0c;dddd为要执行的命令&#xff0c;如alias ddcd /d&#xff0c;输入完成后&#xff0c;在Bash中输入dd&#xff0c;即可切换至D盘。 此种设置方式&#xff…

RabbitMQ 入门(六)SpringAMQP五种消息类型

一、发布订阅-DirectExchange&#xff08;路由模式&#xff09; 在Fanout模式中&#xff0c;一条消息&#xff0c;会被所有订阅的队列都消费。但是&#xff0c;在某些场景下&#xff0c;我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。 Direct Exchan…

个性化图像生成新SOTA!阿里开源MIP-Adapter,可将无需微调的IP-Adapter推广到同时合并多个参考图像。

今天给大家介绍阿里最近开源的个性化图像生成的新方法MIP-Adapter&#xff0c;将无需微调的预训练模型&#xff08;IP-Adapter&#xff09;推广到同时合并多个参考图像。MIP-Adapter会根据每个参考图像与目标对象的相关性来给这些图像分配不同的“重要性分数”。这样&#xff0…

Ngin入门套餐

快速了解Nginx 一、代理1.1 正向代理1.2 反向代理1.3 正向代理和反向代理的区别 二、Nginx负载均衡策略2.1 轮询&#xff08;Round Robin&#xff09;2.2 加权轮询&#xff08;Weighted Round Robin&#xff09;2.3 IP 哈希&#xff08;IP Hash&#xff09;2.4 最少连接&#x…

机器人大功率主轴SycoTec 4060 ER-S汽车电机机芯焊缝铣削打磨加工

在汽车制造的精密领域&#xff0c;每一个细节都关乎着整车的性能与品质&#xff0c;而汽车电机机芯的焊缝加工更是其中至关重要的一环。在机器人末端加装德国进口电主轴 SycoTec 4060 ER-S&#xff0c;为汽车电机机芯焊缝铣削打磨加工带来全新的解决方案。 SycoTec 4060 ER-S转…

【SEO】什么是SEO?

什么是SEO&#xff08;搜索引擎优化&#xff09;&#xff1f;为什么SEO对于⼀个⽹站⾄关重要&#xff1f; SEO 全称是搜索引擎优化&#xff08;Search Engine Optimization&#xff09; 因为我们目前开发的网址&#xff0c;需要人看到&#xff0c;除了通过宣传营销的方式展现…

电能表预付费系统-标准传输规范(STS)(4)

5.4 TokenCarrier 到 MeterApplicationProcess 的数据流 The flow of data from the TokenCarrier to the MeterApplicationProcess is shown in Figure 4.此数据流见图 4。 图 4 TokenCarrier 到 MeterApplicationProcess 的数据 The token entry process from the TokenCarr…

【OSCP Proving Grounds 靶场系列】Slort

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。我的…

使用LSPatch+PlusNE修改手机软件

一、问题概述 国内使用一些软件&#xff0c;即使科学上网&#xff0c;打开都是网络错误&#xff0c;更换节点同样如此。 二、软件下载 通过官网或者正规商店(如Google play)下载并且安装。 是的&#xff0c;先要下载一个无法使用的版本&#xff0c;后续对其进行修改。 三、下…

中标麒麟v5安装qt512.12开发软件

注意 需要联网操作 遇到问题1&#xff1a;yum提示没有可用软件包问题 终端执行如下命令 CentOS7将yum源更换为国内源保姆级教程 中标麒麟V7-yum源的更换&#xff08;阿里云源&#xff09; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Cento…