mybatisPlus应用

MybatisPlus

现在用的最多的三个自动完成CRUD代码的插件:

JPA、tk-Mapper、MybatisPlus


mybatisPlus概述

简介

​ Mybatis本来就是简化JDBC操作的!

官网:https://mp.baomidou.com/ Mybatis Plus,简化Mybatis!

在这里插入图片描述

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作

  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库

  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作


快速入门

​ 地址:https://mp.baomidou.com/guide/quick-start.html

​ 使用第三方组件:

​ 1.导入对应依赖

​ 2.研究依赖如何配置

​ 3.代码如何编写

  		4. 提高扩展技术能力!

步骤

1、创建数据库mybatis_plus

2、创建user表

DROP TABLE IF EXISTS user;

CREATE TABLE user
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
	PRIMARY KEY (id)
);

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
--真实开发中,version(乐观锁),deleted(逻辑删除),gmt_create(创建时间),gmt_modified(修改时间)这些是必须有的

3、编写项目,初始化项目,使用spring boot初始化!

4、导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<!--数据库驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--mybatis plus   是自己开发的,并非官方的-->
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.2.0</version>
 </dependency>

说明:使用mybatis_plus可以节省大量代码,但是尽量不要同时导入mybatis和mybatis_plus!版本差异。依赖问题。

5、连接数据库。和mybatis相同。

#mysql 5 驱动不同   com.mysql.jdbc.Driver
#spring.datasource.username=root
#spring.datasource.password= root
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=UTF-8

#mysql 8  驱动不同 com.mysql.cj.jdbc.Driver(兼容mysql 5)需要增加时区的配置 serverTimezone=GMT%2B8(东八区)
spring.datasource.username= root
spring.datasource.password= root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8

6、传统方式 pojo-dao(连接mybatis,配置.xml文件)-server-controller

6、使用了mybatis_plus之后

  • pojo

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    
    
  • mapper接口

    //在对应的Mapper上继承基本的类BaseMapper<T>
    @Repository   //代表dao持久层  (Mapper也行)
    public interface UserMapper extends BaseMapper<User> {
        //所有的CRUD操作就已经完成了
    }
    
  • 注意:主启动类上要加扫描注解@MapperScan(“cn.kay.mapper”) //扫描mapper文件夹下的所有接口

  • 测试类中测试

    @SpringBootTest
    class MybatisPlusApplicationTests {
    //继承了BaseMapper,方法都是BaseMapper类里边的,自己可以扩展方法
        @Autowired
        private UserMapper userMapper;
     @Test
     void contextLoads() {
       //参数是一个wrapper,条件构造    先不用  null
       //查询所有用户
      List<User> users = userMapper.selectList(null);
       users.forEach(System.out::println);
    //   for (User list:users){
    //           System.out.println(list);
    //       }
        }
    }
    
  • 结果

    在这里插入图片描述


配置日志

用mybatis_plus后,看不到sql时如何执行的 ,所以我们要配置日志查看

#配置日志
#默认控制台输出  用其他的日志输出的话,需要导入相应的依赖
mybatis-plus.configuration.log-impl= org.apache.ibatis.logging.stdout.StdOutImpl

在这里插入图片描述


CRUD的扩展

测试插入

在这里插入图片描述

数据库插入的id默认值为:全局的唯一id


主键生成策略

默认 ID_WORKER 全局唯一id

分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法:

​ snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake

主键自增

​ 需要配置主键自增

​ 1>实体类字段上 @TableId(type = IdType.AUTO)

​ 2>数据库中id字段必须是自增的,否则报错
在这里插入图片描述

其他类型主键配置 源码解释

public enum IdType {
    AUTO(0),   //数据库id自增
    NONE(1),   //未设置主键(数据库没有主键)
    INPUT(2),   //手动输入id
    ID_WORKER(3), //默认的全局唯一id
    UUID(4),	//全局唯一id uuid
    ID_WORKER_STR(5);   //ID_WORKER字符串表示法
}

更新操作

 //测试更新
    @Test
    public void testupdate(){
        User user = new User();
        //mybatis_plus通过条件自动帮我们拼接动态sql
      	user.setId(1242336868309585922L);
        user.setName("王五");
        user.setAge(19);
      	user.setEmail("111111@qq.com");
        //注意:这块的参数实际上是user对象  不是id
        userMapper.updateById(user);
        System.out.println(user);
    }

在这里插入图片描述

所有的sql都是自动帮你动态配置的!


自动填充

创建时间,修改时间!这些操作一般都是自动化完成的,不希望手动更新

阿里巴巴开发手册:所有的数据表,gmt_create,gmt_modified 几乎所有的表都要配置上,而且需要自动化。

方式一:数据库级别的(工作中不允许修改数据库)

​ 1、在数据库中添加create_time,update_time这两列

在这里插入图片描述

​ 2、实体类上加上对应字段,然后测试插入

private Date createTime;
private Date updateTime;

​ 3、再次更新查看结果即可

方式二:代码级别

​ 1、删除数据库默认值,触发器等操作|就是一个干净的表(当然create_time,update_time这两列还在)

在这里插入图片描述

​ 2、实体类字段属性上需要增加注解

//字段添加填充内容|(这里标记为填充字段)
@TableField(fill = FieldFill.INSERT)//插入时操作(填充时间)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//插入和更新时操作9填充时间)
private Date updateTime;

​ 3、编写处理器来处理这个注解即可。

package cn.kay.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Slf4j
@Component   //把处理器加入到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill...");
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill...");
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

​ 4、测试插入

​ 5、测试更新即可!


乐观锁

乐观锁:顾名思义 十分乐观,总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,则再次更新测试。

悲观锁:顾名思义 十分悲观,总是认为会出现问题,无论干什么都去上锁!再去操作!

​ 乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version (where条件后在加上version=当前版本号)
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

测试MP(mybatis_plus)中的乐观锁插件

​ 1、先给数据库中添加相对应的version字段。
在这里插入图片描述

​ 2、实体类加对用的字段属性。

@Version     //乐观锁version注解(MP中的)
private Integer version;

​ 3、注册组件。

//扫描mapper文件夹
@MapperScan("cn.kay.mapper")
@EnableTransactionManagement    //自动管理事务(默认也是开启的)
@Configuration  //配置类注解
public class MyBatisPlusConfig {
    //注册乐观锁插件
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }
}

​ 4、测试乐观锁

 //测试乐观锁成功
    @Test
    public void testOptimisticLocker(){
        //1、查询用户信息
        User user = userMapper.selectById(1242393352707969026L);
        //2、修改用户信息
        user.setName("赵六");
        user.setEmail("666666@qq.com");
        userMapper.updateById(user);
    }
    //测试乐观锁失败|多线程下
    @Test
    public void testOptimisticLocker2(){
        //线程1
        User user = userMapper.selectById(1242393352707969026L);
        user.setName("赵六111");
        user.setEmail("666666@qq.com");
        //线程2     模拟线程2执行了插队操作
        User user2 = userMapper.selectById(1242393352707969026L);
        user2.setName("赵六222");
        user2.setEmail("6666662@qq.com");
        userMapper.updateById(user2);

       userMapper.updateById(user);   //如果没有乐观锁,最后的值(user)就会覆盖插队线程的值(user2)
    }

测试查询

//测试单个查询
    @Test
    public void testSelectById(){
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }
    //测试批量查询
    @Test
    public void testSelectBatchId(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2L, 3L, 1242393352707969026L));
        users.forEach(System.out::println);
    }
    //按条件查询之一,使用map操作
    @Test
    public void testSelectBatchIds(){
        HashMap<String, Object> map = new HashMap<>();
        //自定义查询
        map.put("name","赵六222");
        map.put("age",19);

        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }


分页查询

分页在网站查询使用很多

​ 1.原始的使用limit进行分页

​ 2.pageHelper 第三方插件

​ 3.Mp(mhybatis_plus)内也内置了分页插件

如何使用?

​ 1、在配置类中配置分页组件

  //注册分页组件
@Bean
public PaginationInterceptor paginationInterceptor() {
  return new PaginationInterceptor();
}

​ 2、直接使用Page对象即可

 //测试分页查询
    @Test
    public void testPage(){
        Page<User> page = new Page<>(1,5);
        userMapper.selectPage(page,null); //queryWrapper 高级查询|
    }
//扩展
 //page.getRecords().forEach(System.out::println); //遍历出查询的数据
 // System.out.println(page.getTotal());//数据总条数
//        page.getRecords();      //获得所有的记录
//        page.getCurrent();      //当前参数
//        page.getSize();        //大小
//        page.getTotal();      //数据总数
//        page.hasNext();      //是否有下一页
//        page.hasPrevious();  //是否有上一页

删除操作

基本的删除操作(和查询的方法基本类似)

    //根据id删除
    @Test
    public void testDeleteById(){
        userMapper.deleteById(1242393352707969026L);
    }
    //根据id批量删除
    @Test
    public void testDeleteBatchIds(){
        userMapper.deleteBatchIds(Arrays.asList(1242336868309585922L,1242368872275177474L));
    }
    //通过map删除
    @Test
    public void testDeleteMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","二");
        userMapper.deleteByMap(map);
    }

逻辑删除

物理删除:从数据库中直接移除

逻辑删除:在数据库中依然存在,只是通过一个变量让他失效!deleted=0=>deleted=1

逻辑删除后,管理员还是能看到你删除的数据记录,防止数据丢失,类似于回收站。

步骤:

​ 1、在数据库中添加一个deleted字段。

在这里插入图片描述

​ 2、在实体类上添加对应的字段属性。

@TableLogic    //逻辑删除的注解(MP中的)
private Integer deleted;

​ 3、在.yml或者。properties文件中配置

#mybatis-plus.global-config.db-config.logic-delete-field: flag  
#全局逻辑删除字段值 3.3.0开始支持,详情看https://mp.baomidou.com/guide/logic-delete.html
mybatis-plus.global-config.db-config.logic-delete-value=1   #逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-not-delete-value=0   #逻辑未删除值(默认为 0)

​ 4、在配置类中注册逻辑删除组件(3.1.1开始不再需要这一步):

//注册逻辑删除组件
@Bean
public ISqlInjector sqlInjector() {
  return new LogicSqlInjector();
}

​ 5、测试删除(根据id)

在这里插入图片描述

记录还在数据库,但是已经失效了。再次查询

在这里插入图片描述

以上的CRUD操作以及扩展都必须精通掌握!

性能分析插件

作用:性能分析拦截器,用于输出每条 SQL 语句及其执行时间

在开发中,会遇到一些慢sql(查询慢)。测试时那条语句慢,找出来然后优化。

​ 1、在配置类中注册性能分析组件

//注册性能分析组件
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
  PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
  performanceInterceptor.setMaxTime(100);//ms设置sql最大执行时间,如果超过了则不执行该条sql(报错)
  performanceInterceptor.setFormat(true);//sql格式化
  return performanceInterceptor;
}

注意.yml或者.properties中配置环境为dev或者test

spring:
  profiles:
    active: dev     #设置开发环境

​ 2、测试使用

在这里插入图片描述

条件构造器

测试一:

@Test
void contextLoads() {
  //查询name不为空的,并且邮箱不为空的,年龄大于20的
  QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  queryWrapper.isNotNull("name")
    .isNotNull("email")
    .ge("age",20);
  userMapper.selectList(queryWrapper)
    .forEach(System.out::println);//在控制台输出     可不写
}

注意:有的会抛异常,是因为我们刚才设置了sql的最大执行时间

解决:把刚才设置的语句注掉,或者把时间调大一点。

测试二:

@Test
void test2() {
  //查询名字是Tom
  QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  queryWrapper.eq("name","Tom");
  User user = userMapper.selectOne(queryWrapper); //查询一个数据,如果出现多个结果用List或Map接收
  System.out.println(user);
}

测试三:

@Test
void test3() {
  //查询年龄20-30之间的
  QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  queryWrapper.between("age",20,30);//区间
  Integer count = userMapper.selectCount(queryWrapper); //查询结果数
  System.out.println(count);
  userMapper.selectList(queryWrapper).forEach(System.out::println);
}

测试四:

//模糊查询
@Test
void test4() {
  QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  //左 [%e]和右  [e%]
  queryWrapper
    .notLike("name","e")    //name中不带e的
    .likeRight("email","t");  //email中以t开头  t%
  List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
  maps.forEach(System.out::println);
}

测试五:

@Test
void test5() {
  QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  //id是在子查询中查出来的
  queryWrapper.inSql("id","select id from user where id<3");
  List<Object> objects = userMapper.selectObjs(queryWrapper);
  objects.forEach(System.out::println);
}

测试六:

@Test
void test6() {
  QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  //通过id降序排列
  queryWrapper.orderByDesc("id");
  List<User> users = userMapper.selectList(queryWrapper);  //List
  users.forEach(System.out::println);  //正常降序输出
  //List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//Map
 // maps.forEach(System.out::println);  //这块输出,把表的列顺序也颠倒了,自己可以打印出来查看
}

代码自动生成器

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

package cn.kay;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName CodeAutoGeneration 代码自动生成
 * @Description TODO
 * @Author Zk
 * @Date 2020/3/26
 * @Version 1.0
 */

public class CodeAutoGeneration {
    public static void main(String[] args) {
        //构建一个  代码自动生成器  对象
        AutoGenerator mpg = new AutoGenerator();

        // 1、全局配置
        GlobalConfig gc = new GlobalConfig();
            String projectPath = System.getProperty("user.dir");//当前项目路径
            gc.setOutputDir(projectPath + "/src/main/java");
            gc.setAuthor("Zk");   //设置作者名
            gc.setOpen(false);  //是否打开资源管理器
            gc.setFileOverride(false);  //是否覆盖原有的
            gc.setIdType(IdType.ASSIGN_UUID);  //默认id算法|自3.3.0开始,默认使用雪花算法+UUID
            gc.setDateType(DateType.ONLY_DATE); //日期类型
            gc.setSwagger2(true); //配置 Swagger2 注解文档

            //自定义文件命名,注意%s 会自动填充表实体属性
            gc.setControllerName("%sController");
            gc.setServiceName("%sService");
            gc.setServiceImplName("%sServiceImpl");
            gc.setMapperName("%sMapper");
            gc.setXmlName("%sMapper");
        mpg.setGlobalConfig(gc);    //将全局配置放入自动生成器 对象中

        //2、设置数据源
        DataSourceConfig dsc = new DataSourceConfig();
            dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_auto_core?			    useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8");
            // dsc.setSchemaName("public"); //设置实体类中字段的访问权限
            dsc.setDriverName("com.mysql.cj.jdbc.Driver");
            dsc.setUsername("root");
            dsc.setPassword("root");
            dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        //3、包配置
        PackageConfig pc = new PackageConfig();
           // pc.setModuleName("code");//子模块  //cn.kay.code
            pc.setParent("cn.kay");  //父包名
            pc.setEntity("entity");  //实体类模块名
            pc.setController("controller");
            pc.setService("service");
            pc.setServiceImpl("service.impl");//可以不写
            pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

          // 自定义配置
          InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
              // to do nothing
            }
          };
            // 如果模板引擎是 velocity
            String templatePath = "/templates/mapper.xml.vm";
            // 自定义输出配置
            List<FileOutConfig> focList = new ArrayList<>();
            // 自定义配置会被优先输出
            focList.add(new FileOutConfig(templatePath) {
                @Override
                public String outputFile(TableInfo tableInfo) {
            //自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!
                  return projectPath + "/src/main/resources/mapper/"+
                               tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
                }
            });
          cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);
            // 配置模板
            TemplateConfig templateConfig = new TemplateConfig();
            templateConfig.setXml(null); //此处设置为null,就不会再java下创建xml的文件夹了
        mpg.setTemplate(templateConfig);
        //4、策略配置
        StrategyConfig strategy = new StrategyConfig();
            strategy.setInclude("users");//设置要映射的表名,多个表("users","dept","...")
            strategy.setNaming(NamingStrategy.underline_to_camel);  //包命名下划线转驼峰命名
            strategy.setColumnNaming(NamingStrategy.underline_to_camel); 
      															//列命名下划线转驼峰命名
            strategy.setEntityLombokModel(true); //是否启用lombok注解
            strategy.setLogicDeleteFieldName("deleted");//设置逻辑删除字段名
            strategy.setVersionFieldName("version");  //设置乐观锁字段
            strategy.setRestControllerStyle(true);  //开启驼峰命名方式
                //自动填充
                TableFill gmtCreate =  new TableFill("gmt_create",FieldFill.INSERT);
                TableFill gmtModified = new TableFill("gmt_modified", 																				FieldFill.INSERT_UPDATE);
                ArrayList<TableFill> tableFills = new ArrayList<>();
                tableFills.add(gmtCreate);
                tableFills.add(gmtModified);
            strategy.setTableFillList(tableFills);
            strategy.setControllerMappingHyphenStyle(true);  //请求连接可以用下划线
        mpg.setStrategy(strategy);

        mpg.execute();//执行
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值