SpringBoot集成Mybatis双数据源+Druid连接池+集成通用mapper+分页插件

SpringBoot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源。

单数据源的mybatis + 通用mapper + pageHelper

先贴一个单数据源的mybatis + 通用mapper + pageHelper配置

application.properties

spring.datasource.druid.url=jdbc:mysql://localhost:3306/foo?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.username=bar
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=5
spring.datasource.druid.initial-size=5
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.max-wait=60000
spring.datasource.druid.filters=config,slf4j
spring.datasource.druid.filter.slf4j.statement-executable-sql-log-enable=true
spring.datasource.druid.publickey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAOvuGrxZ0Cj/ju27uWJ8VqDmY7956OGhVeefB0zw6F7OhBg9Bz+0c84RjzipqS0NQWwOb4kobfmg3WsV6ekr7TECAwEAAQ==
spring.datasource.druid.password=PNak4Yui0+2Ft6JSoKBsgNPl+A033rdLhFw+L0np1o+HDRrCo9VkCuiiXviEMYwUgpHZUFxb2FpE0YmSguuRww==
spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${spring.datasource.druid.publickey}

maven配置:

        <!--mybatis start-->
        <!-- druid 数据库连接池-->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid-spring-boot-starter</artifactId>
          <version>1.1.23</version>
        </dependency>
        <!--mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-generator</artifactId>
            <version>1.1.5</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>
        <!-- pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
        <!--mybatis end-->

MybatisConfig.java

package com.foo.bar.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import tk.mybatis.spring.annotation.MapperScan;

import javax.sql.DataSource;
import java.util.List;
import java.util.Properties;

/**
 * @MapperScan 方式可以避免重复扫描
 */
@Configuration
@MapperScan(value = "com.foo.bar.mapper",
        properties = {
                "mappers=com.foo.bar.base.BaseMapper",
                "notEmpty=true",
                "IDENTITY=MYSQL"
        }
)
public class MybatisConfig {

    /**
     * DataSource 配置
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource dataSource() {
        return DruidDataSourceBuilder.create().build();
    }


    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        //yaml中的mybatis配置会失效,这里必须设置mapper的位置,否则用不了xml的mapper
        sessionFactory.setMapperLocations(
                ((ResourcePatternResolver) new PathMatchingResourcePatternResolver())
                        .getResources("classpath:mappers/**/*.xml"));
        sessionFactory.setTypeAliasesPackage("com.foo.bar.entity");
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        //开启下划线转驼峰支持
        configuration.setMapUnderscoreToCamelCase(true);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }

    /**
     * 分页拦截器配置
     */
    @Bean
    public PageInterceptor pageInterceptors(List<SqlSessionFactory> sqlSessionFactoryList) {
        PageInterceptor pageInterceptor = new PageInterceptor();
        Properties properties = new Properties();
        properties.setProperty("helperDialect", "mysql");
        properties.setProperty("reasonable", "false");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("returnPageInfo", "count=check");
        properties.setProperty("params", "count=countSql");
        pageInterceptor.setProperties(properties);
        for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
            sqlSessionFactory.getConfiguration().addInterceptor(pageInterceptor);
        }
        return pageInterceptor;
    }

}

这样单数据源的mybatis + 通用mapper + pageHelper的环境就可以了。

多数据源添加依赖

加入Mybatis启动器,这里添加了Druid连接池、Mysql数据库驱动为例。

    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.1.3</version>
    </dependency>
    <!-- mysql 连接类 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
      <version>5.1.49</version>
    </dependency>
    <!-- druid 数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.23</version>
    </dependency>

添加启动类

package com.foo.bar;

import com.foo.bar.annotation.EnableMybatis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;


/**
 * @author Chuck
 */
@EnableMybatis
@EnableTransactionManagement
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class BarApplication {

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

}

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }):
这里用到了双数据源,需要排除数据源的自动配置,如果只有一个数据源用Spring Boot的自动配置就行。

@EnableTransactionManagement:开启事务支持。

@EnableMybatis:开启Mybatis功能

package com.foo.bar.annotation;

import com.foo.bar.config.MybatisConfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MybatisConfig.class)
public @interface EnableMybatis {

}

Mybatis配置类

package com.foo.bar.config;

import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
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.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;

@Configuration
@MapperScan(basePackages = DSConfig.BASE_PACKAGES)
public class MybatisConfig implements DSConfig {

	@Primary
	@Bean
	public DynamicDataSource dynamicDataSource(@Qualifier(DB_MASTER) DataSource master,@Qualifier(DB_SLAVE) DataSource slave) {
		Map<Object, Object> dsMap = new HashMap<>(2);
		dsMap.put(DB_MASTER, master);
		dsMap.put(DB_SLAVE, slave);

		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		dynamicDataSource.setDefaultTargetDataSource(master);
		dynamicDataSource.setTargetDataSources(dsMap);
		return dynamicDataSource;
	}

	@Bean
	public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
		return new DataSourceTransactionManager(dynamicDataSource);
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
		SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
		sessionFactory.setDataSource(dynamicDataSource);
		//yaml中的mybatis配置会失效,这里必须设置mapper的位置,否则用不了xml的mapper
		sessionFactory.setMapperLocations(
				((ResourcePatternResolver) new PathMatchingResourcePatternResolver())
						.getResources(DSConfig.MAPPER_LOCATIONS));
		sessionFactory.setTypeAliasesPackage(TYPE_ALIASES_PACKAGE);
		org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
		//开启下划线转驼峰支持
		configuration.setMapUnderscoreToCamelCase(true);
		sessionFactory.setConfiguration(configuration);
		return sessionFactory.getObject();
	}

}

DSConfig常量类:

package com.foo.bar.config;

public interface DSConfig {

	String DS_PREFIX = "spring.datasource";
	String DS_ACTIVE = "active";

	String DB_MASTER = "masterDruidDataSource";
	String DB_SLAVE = "slaveDruidDataSource";

	String DRUID = "druid";

	String BASE_PACKAGES = "com.foo.bar.**.mapper";
	String MAPPER_LOCATIONS = "mappers/**/*.xml";

	String TYPE_ALIASES_PACKAGE = "com.foo.bar.mapper.domain";

}

Druid连接池配置类

yml配置:

# DataSource
spring:
  datasource:
    active: druid
    druid:
      publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJyEm+x8bHLOGhj55ZZy8Ug1WqBjTGXuu/Rz5JZ5lTtjQ9mqwv69G8FtaBHOtKHL+ll5SXDIGFpOcU3A0+eSMpECAwEAAQ==
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        allow: 127.0.0.1
        login-username: root
        login-password: root
        reset-enable: false
      web-stat-filter:
        enabled: true
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
        url-pattern: /*
      master:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/foo?useSSL=false&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
        username: root
        password: eNDCOPEEjiLph59Hkp4oI8c/TAljTNVIFHU/YvT3ZjjC0fsXhqP5uDRygzR8APFuqNg+MCEQ3ZjMgbQ/oRC5Aw==
        connect-properties:
          config.decrypt: true
          config.decrypt.key: ${spring.datasource.druid.publickey}
        filters: stat,wall,slf4j,config
        max-active: 100
        initial-size: 10
        max-wait: 60000
        min-idle: 10
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        validation-query: select 'x'
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        pool-prepared-statements: true
        max-open-prepared-statements: 50
        max-pool-prepared-statement-per-connection-size: 20
      slave:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/bar?useSSL=false&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
        username: root
        password: eNDCOPEEjiLph59Hkp4oI8c/TAljTNVIFHU/YvT3ZjjC0fsXhqP5uDRygzR8APFuqNg+MCEQ3ZjMgbQ/oRC5Aw==
        connect-properties:
          config.decrypt: true
          config.decrypt.key: ${spring.datasource.druid.publickey}
        filters: stat,wall,slf4j,config
        max-active: 100
        initial-size: 10
        max-wait: 60000
        min-idle: 10
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        validation-query: select 'x'
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        pool-prepared-statements: true
        max-open-prepared-statements: 50
        max-pool-prepared-statement-per-connection-size: 20
logging:
  level:
    root: info

Druid连接池的自动配置类:

package com.foo.bar.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import java.util.Arrays;
import java.util.Collections;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@ConditionalOnClass(DruidDataSource.class)
@ConditionalOnProperty(prefix = DSConfig.DS_PREFIX, value = DSConfig.DS_ACTIVE, havingValue = DSConfig.DRUID)
@Slf4j
public class DruidAutoConfig implements DSConfig {


	/**
	 * DataSource 配置
	 * @return
	 */
	@ConfigurationProperties(prefix = "spring.datasource.druid.master")
	@Bean(name = DB_MASTER)
	@Primary
	public DataSource masterDruidDataSource() {
		return DruidDataSourceBuilder.create().build();
	}


	/**
	 * DataSource 配置
	 * @return
	 */
	@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
	@Bean(name = DB_SLAVE)
	public DataSource slaveDruidDataSource() {
		return DruidDataSourceBuilder.create().build();
	}


}

切换数据源

切换数据源注解:

package com.foo.bar.annotation;

import com.foo.bar.config.DSConfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
	String value() default DSConfig.DB_MASTER;
}

动态数据源类:

package com.foo.bar.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {


	@Override
	protected Object determineCurrentLookupKey() {
		log.info("当前数据源为{}", DataSourceContextHolder.getDS());
		return DataSourceContextHolder.getDS();
	}

}

动态数据源AOP实现类:

package com.foo.bar.aspect;

import com.foo.bar.annotation.DS;
import com.foo.bar.config.DataSourceContextHolder;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 保证在Spring事务切面之前
 * @author Chuck
 */
@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {

	@Before("@annotation(com.foo.bar.annotation.DS)")
	public void beforeSwitchDS(JoinPoint point) {
		Class<?> className = point.getTarget().getClass();
		String methodName = point.getSignature().getName();
		Class<?>[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
		String dataSource = DataSourceContextHolder.DEFAULT_DS;

		try {
			Method method = className.getMethod(methodName, argClass);
			if (method.isAnnotationPresent(DS.class)) {
				DS annotation = method.getAnnotation(DS.class);
				dataSource = annotation.value();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		DataSourceContextHolder.setDS(dataSource);
	}

	@After("@annotation(com.foo.bar.annotation.DS)")
	public void afterSwitchDS(JoinPoint point) {
		DataSourceContextHolder.clearDS();
	}

}

绑定当前线程数据源类:

package com.foo.bar.config;

public class DataSourceContextHolder {

	public static final String DEFAULT_DS = DSConfig.DB_MASTER;

	private static final ThreadLocal<String> DS_HOLDER = new ThreadLocal<>();

	public static void setDS(String dbType) {
		DS_HOLDER.set(dbType);
	}

	public static String getDS() {
		return (DS_HOLDER.get());
	}

	public static void clearDS() {
		DS_HOLDER.remove();
	}
}

集成通用mapper及分页插件

采用Bean方式注入,不需要增加yml配置

通用mapper与分页插件依赖:

<!-- 数据库 start -->
    <!-- 引用通用mapper后已包含mybatis依赖 -->
<!--    <dependency>-->
<!--      <groupId>org.mybatis.spring.boot</groupId>-->
<!--      <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!--      <version>2.1.3</version>-->
<!--    </dependency>-->
    <!-- mysql 连接类 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
      <version>5.1.49</version>
    </dependency>
    <!-- druid 数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.23</version>
    </dependency>
    <!--mapper-->
    <dependency>
      <groupId>tk.mybatis</groupId>
      <artifactId>mapper-spring-boot-starter</artifactId>
      <version>2.1.5</version>
    </dependency>
    <!-- pagehelper -->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.2.0</version>
    </dependency>
    <!-- 数据库 end -->

BaseMapper:

package com.foo.bar.base;

import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

public interface BaseMapper<T> extends Mapper<T>, MySqlMapper<T> {

}

MybatisConfig:

使用通用mapper需使用tk.mybatis.spring.annotation.MapperScan扫描,这种方式可以避免重复扫包

package com.foo.bar.config;

import com.github.pagehelper.PageInterceptor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;

/**
 * @MapperScan 方式可以避免重复扫描
 */
@Configuration
@MapperScan(value = DSConfig.BASE_PACKAGES,
		properties = {
				"mappers=com.foo.bar.base.BaseMapper",
				"notEmpty=true",
				"IDENTITY=MYSQL"
		}
)
public class MybatisConfig implements DSConfig {

	@Primary
	@Bean
	public DynamicDataSource dynamicDataSource(@Qualifier(DB_MASTER) DataSource master,@Qualifier(DB_SLAVE) DataSource slave) {
		Map<Object, Object> dsMap = new HashMap<>(2);
		dsMap.put(DB_MASTER, master);
		dsMap.put(DB_SLAVE, slave);

		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		dynamicDataSource.setDefaultTargetDataSource(master);
		dynamicDataSource.setTargetDataSources(dsMap);
		return dynamicDataSource;
	}

	@Bean
	public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
		return new DataSourceTransactionManager(dynamicDataSource);
	}

	@Bean
	public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
		SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
		sessionFactory.setDataSource(dynamicDataSource);
		//yaml中的mybatis配置会失效,这里必须设置mapper的位置,否则用不了xml的mapper
		sessionFactory.setMapperLocations(
				((ResourcePatternResolver) new PathMatchingResourcePatternResolver())
						.getResources(DSConfig.MAPPER_LOCATIONS));
		sessionFactory.setTypeAliasesPackage(TYPE_ALIASES_PACKAGE);
		org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
		//开启下划线转驼峰支持
		configuration.setMapUnderscoreToCamelCase(true);
		sessionFactory.setConfiguration(configuration);
		return sessionFactory.getObject();
	}

	@Bean
	@ConditionalOnMissingBean
	public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}

	/**
	 * 分页拦截器配置
	 */
	@Bean
	public PageInterceptor pageInterceptors(List<SqlSessionFactory> sqlSessionFactoryList) {
		PageInterceptor pageInterceptor = new PageInterceptor();
		Properties properties = new Properties();
		properties.setProperty("helperDialect", "mysql");
		properties.setProperty("reasonable", "false");
		properties.setProperty("supportMethodsArguments", "true");
		properties.setProperty("returnPageInfo", "count=check");
		properties.setProperty("params", "count=countSql");
		pageInterceptor.setProperties(properties);
		for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
			sqlSessionFactory.getConfiguration().addInterceptor(pageInterceptor);
		}
		return pageInterceptor;
	}

}

查看SprinBoot自动配置匹配情况日志:

logging:
  level:
    root: INFO
    org:
      springframework:
        boot:
          autoconfigure:
            logging: DEBUG

参考列表:

Druid 使用手册-使用ConfigFilter

DruidDataSource配置属性列表

Druid Spring Boot Starter

https://github.com/abel533/MyBatis-Spring-Boot

https://github.com/abel533/Mapper/wiki/1.2-spring

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值