SpringBoot MongoTemplate 使用总结

一、基础配置

1. pom 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

2.配置文件

spring:
  data:
    mongodb:
      username: root
      password: 123456
      host: 127.0.0.1
      port: 27017
      authentication-database: admin
      database: demo

3. 常用注解

注解名称作用对象功能
@Document实体类类似于 hibernate 的 entity 注解,标明由 mongo 来维护该表
@CompoundIndex实体类复合索引
@Id字段和 mongoDB 中 _id 字段对应
@Field字段可以通过该注解自定义在 mongo 中的名称
@Indexed字段声明该字段需要加索引,加索引后以该字段为条件检索将大大提高速度
@Transient字段表示该字段不在 mongo 中存储,既忽略该字段

二、基础操作

以图书管理为例,定义实体类

Book.java

package com.ming.springbootmongo.model;

import org.springframework.data.annotation.Id;

import java.util.Arrays;

public class Book {

    /**
     * mongo 自增 id
     */
    @Id
    private String id;

    /**
     * 图书名称
     */
    private String name;

    /**
     * 价格
     */
    private double price;

    /**
     * 出版社
     */
    private String press;

    /**
     * 作者
     */
    private Author[] authors;

    /**
     * 标签
     */
    private String[] tags;

    public Book() {
    }

    public Book(String name, double price, String press, Author[] authors, String[] tags) {
        this.name = name;
        this.price = price;
        this.press = press;
        this.authors = authors;
        this.tags = tags;
    }
    
    ......
     getter()
     setter()
    ......
}

Author.java

public class Author {

    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private String sex;

    public Author() {
    }

    public Author(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
    
    ......
     getter()
     setter()
    ......
}

SprintBoot 测试类文件 BookServiceTest.java

import com.ming.springbootmongo.model.Author;
import com.ming.springbootmongo.model.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

@SpringBootTest
public class BookServiceTest {
    /**
     * 文档集合名称
     */
    private static final String BOOK_COLLECTION = "book";

    private final MongoTemplate mongoTemplate;

    @Autowired
    public BookServiceTest(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }
}

1. 插入

1.1 插入单条
@Test
public void testSaveOne() {
    Book book = new Book("高等数学上", 37.70, "高等教育出版社", new Author[]{
        new Author("同济大学数学系", "")}, new String[]{"数学", "大学数学", "高等数学"});

    final Book ret = mongoTemplate.insert(book, BOOK_COLLECTION);
    System.out.println(ret);
}
1.2 插入多条
@Test
public void testSaveBatch() {
    List<Book> bookList = Arrays.asList(
        new Book("TCP/IP详解卷1:协议", 45.00, "机械工业出版社", new Author[]{
            new Author("W.Richard Stevens", "男"),
            new Author("范建华", "男"),
            new Author("胥光辉", "男"),
            new Author("张涛", "男"),
            new Author("谢希仁", "男")
        }, new String[]{"计算机网络", "网络协议", "编程"}),

        new Book("Python程序设计开发宝典", 69.00, "清华大学出版社", new Author[]{new Author(
            "董付国", "男")}, new String[]{"程序设计", "编程", "python"}),

        new Book("汇编语言", 36.00, "清华大学出版社", new Author[]{new Author("王爽", "")},
                 new String[]{"程序设计", "编程", "汇编"}),

        new Book("SparkSQL入门与实践指南", 49.00, "清华大学出版社", new Author[]{
            new Author("纪涵", "女"),
            new Author("靖晓文", "女"),
            new Author("赵政达", "男")
        }, new String[]{"程序设计", "编程", "spark", "spark sql"})
    );

    final Collection<Book> ret = mongoTemplate.insert(bookList, BOOK_COLLECTION);
    System.out.println(ret.size());
}
1.3 去掉 _class 字段

直接使用 insert 进行插入,会在 mongo 中出现 _class 字段,如果想去掉此字段,可以自定义 MappingMongoConverter 配置

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

@Configuration
public class MongoConfig {
    @Bean
    public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory factory, MongoMappingContext context, BeanFactory beanFactory) {
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
        MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
        try {
            mappingConverter.setCustomConversions(beanFactory.getBean(CustomConversions.class));
        } catch (NoSuchBeanDefinitionException ignore) {
        }

        // Don't save _class to mongo
        mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));

        return mappingConverter;
    }
}

2. 查询

MognoTemplate 中使用 Criteria + Query 对象 进行查询条件封装

// 创建 Critera 方式
// 第一种 使用静态方法创建 Criteria
Criteria criteria = Criteria.where("name").is("Python程序设计开发宝典");

// 第二种 直接 new 实例,然后设置变量参数
Criteria criteria = new Criteria();
criteria.and("name").is("Python程序设计开发宝典");

2.1 简单查询
2.1.1 等于
@Test
public void testEqual1() {
    // 使用静态方法创建 Criteria
    Criteria criteria = Criteria.where("name").is("Python程序设计开发宝典");

    // new Criteria 实例,然后设置变量参数
    //  Criteria criteria = new Criteria();
    //  criteria.and("name").is("Python程序设计开发宝典");

    Query query = new Query(criteria);
    // 如果只想查询一条或者明确知道仅有一条, 使用 findOne,否则,可以使用 find 或 findAll
    final Book ret = mongoTemplate.findOne(query, Book.class, BOOK_COLLECTION);
    assert ret != null;
}

@Test
public void testEqual2() {
    // 使用静态方法创建 Criteria
    Criteria criteria = Criteria.where("press").is("清华大学出版社");

    Query query = new Query(criteria);
    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 3;
}


2.1.2 大于
@Test
public void testGt() {
    Criteria criteria = Criteria.where("price").gt(30);
    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 5;
}
2.1.3 大于等于
@Test
public void testGte() {
    Criteria criteria = Criteria.where("price").gte(45);
    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 3;
}
2.1.4 小于
@Test
public void testLt() {
    Criteria criteria = Criteria.where("price").lt(45);
    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 2;
}
2.1.5 小于等于
@Test
public void testLte() {
    Criteria criteria = Criteria.where("price").lte(45);
    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 3;
}
2.16 不等于
@Test
public void testNe() {
    Criteria criteria = Criteria.where("price").ne(45);
    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 4;
}
2.2 多条件查询
2.2.1 and
@Test
public void testAnd() {
    Criteria criteria = new Criteria();
    criteria.and("price").gt(45)
        .and("press").is("清华大学出版社");

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 2;
}
2.2.2 or
@Test
public void testOr() {
    Criteria criteria = new Criteria();

    // 每个条件构建一个 criteria 对象
    Criteria criteria1 = Criteria.where("price").lt(45);
    Criteria criteria2 = Criteria.where("press").is("清华大学出版社");

    // 将多个 criteria 对象使用  or 操作关联起来
    criteria.orOperator(criteria1, criteria2);

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 4;
}
2.2.3 not
// 相当于 not(price < 45 or press='清华大学出版社')
@Test
public void testNot(){
    Criteria criteria = new Criteria();

    Criteria criteria1 = Criteria.where("price").lt(45);
    Criteria criteria2 = Criteria.where("press").is("清华大学出版社");

    criteria.norOperator(criteria1, criteria2);

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
}
2.2.4 in
@Test
public void testIn(){
    Criteria criteria = Criteria.where("press").in("机械工业出版社","清华大学出版社");

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
}
2.2.5 nin
@Test
public void testNin() {
    Criteria criteria = Criteria.where("press").nin("机械工业出版社", "清华大学出版社");

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 1;
}
2.3 正则查询
2.3.1 开头包含字符串
@Test
public void testBeginWith(){
    // 定义正则表达式
    String name = "高等";
    Pattern pattern = Pattern.compile("^" + name + ".*$", Pattern.CASE_INSENSITIVE);

    Criteria criteria = Criteria.where("name").regex(pattern);

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 1;
}
2.3.2 包含
@Test
public void testContain(){
    String name = "开发";
    Pattern pattern = Pattern.compile("^.*" + name + ".*$", Pattern.CASE_INSENSITIVE);

    Criteria criteria = Criteria.where("name").regex(pattern);

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 1;
}
2.3.3 结尾包含字符串
@Test
public void testEndWith(){
    String name = "指南";
    Pattern pattern = Pattern.compile("^.*" + name + "$", Pattern.CASE_INSENSITIVE);

    Criteria criteria = Criteria.where("name").regex(pattern);

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 1;
}
2.4 数组查询
2.4.1 匹配数组中单个值
// 数组中包含查询单值
@Test
public void testArrayMatchSingle() {
    Criteria criteria = Criteria.where("tags").is("编程");

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 4;
}
2.4.2 匹配数组中多个值
// 数组中同时包含指定的多个值,不要求顺序
@Test
public void testArrayMatchAll() {
    Criteria criteria = Criteria.where("tags").all("编程", "程序设计");

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 3;
}
2.4.3 匹配数组中元素个数
@Test
public void testArrayMatchSize() {
    Criteria criteria = Criteria.where("tags").size(4);

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 1;
}
2.4.4 匹配指定位置元素
// 满足特定索引下条件
// 数组索引从 0 开始,匹配第二项就用 tags.1 作为键
@Test
public void testMatchIndex() {
    Criteria criteria = Criteria.where("tags.1").is("编程");

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 3;
}
2.4.5 匹配整个数组
// 元素个数和顺序都要匹配
@Test
public void testArrayMatch() {
    Criteria criteria = Criteria.where("tags").is(new String[]{"程序设计", "编程", "python"});

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert books.size() == 1;
}
2.4.6 匹配子文档
@Test
public void testMatchSubDoc() {
    Criteria criteria = Criteria.where("authors.name").is("纪涵");

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);

    assert books.size() == 1;
}
2.4.7 elementMatch
@Test
public void testElementMatch() {
    Criteria criteria = Criteria.where("authors").elemMatch(Criteria.where("name").is("谢希仁").and("sex").is("男"));

    Query query = new Query(criteria);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);

    assert books.size()== 1;
}
2.5 聚合查询

mongoDB 聚合查询语法,这些命令均在 Aggregation 中实现
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
m a t c h : 用 于 过 滤 数 据 , 只 输 出 符 合 条 件 的 文 档 。 match:用于过滤数据,只输出符合条件的文档。 matchmatch使用MongoDB的标准查询操作。
$limit:用来限制MongoDB聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。
$geoNear:输出接近某一地理位置的有序文档。

Spring MongoDB 聚合操作

聚合操作方法列表
Pipeline Aggregation Operatorsbucket, bucketAuto, count, facet, geoNear, graphLookup, group, limit, lookup, match, project, rand, replaceRoot, skip, sort, unwind
Set Aggregation OperatorssetEquals, setIntersection, setUnion, setDifference, setIsSubset, anyElementTrue, allElementsTrue
Group/Accumulator Aggregation OperatorsaddToSet, covariancePop, covarianceSamp, expMovingAvg, first, last, max, min, avg, push, sum, count (*), stdDevPop, stdDevSamp
Arithmetic Aggregation Operatorsabs, add (* via plus), asin, asin, atan, atan2, atanh, ceil, cos, cosh, derivative, divide, exp, floor, integral, ln, log, log10, mod, multiply, pow, round, sqrt, subtract (* via minus), sin, sinh, tan, tanh, trunc
String Aggregation Operatorsconcat, substr, toLower, toUpper, strcasecmp, indexOfBytes, indexOfCP, regexFind, regexFindAll, regexMatch, split, strLenBytes, strLenCP, substrCP, trim, ltrim, rtim
Comparison Aggregation Operatorseq (* via is), gt, gte, lt, lte, ne
Array Aggregation OperatorsarrayElementAt, arrayToObject, concatArrays, filter, in, indexOfArray, isArray, range, reverseArray, reduce, size, slice, zip
Literal Operatorsliteral
Date Aggregation OperatorsdayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateAdd, dateDiff, dateToString, dateFromString, dateFromParts, dateToParts, isoDayOfWeek, isoWeek, isoWeekYear
Variable Operatorsmap
Conditional Aggregation Operatorscond, ifNull, switch
Type Aggregation Operatorstype
Convert Aggregation Operatorsconvert, degreesToRadians, toBool, toDate, toDecimal, toDouble, toInt, toLong, toObjectId, toString
Object Aggregation OperatorsobjectToArray, mergeObjects
Script Aggregation Operatorsfunction, accumulator
2.5.1 count
@Test
public void testCount() {
    Query query = new Query();
    final long ret = mongoTemplate.count(query, BOOK_COLLECTION);
    System.out.println(ret);
}
2.5.2 distinct
@Test
public void testDistinct(){
    final List<String> distinctPress = mongoTemplate.findDistinct(new Query(), "press", BOOK_COLLECTION, String.class);
    System.out.println(distinctPress);
}
2.5.3 sum、count、avg
@Test
public void testSum() {
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.project("_id", "press", "price"),
        Aggregation.group("press")
        .count().as("count")
        .sum("price").as("total")
        .avg("price").as("avg")
    );

    final AggregationResults<Map> aggregationResults = mongoTemplate.aggregate(aggregation, BOOK_COLLECTION, Map.class);
    final List<Map> mappedResults = aggregationResults.getMappedResults();
    System.out.println(mappedResults);
}
2.5.4 带有表达式的计算
@Test
public void testSum2() {
    // 定义乘法表达式, 可以是两个字段相乘或者字段与数字相乘,同理,ArithmeticOperators 包含 加减乘除模等操作
    final ArithmeticOperators.Multiply price = ArithmeticOperators.valueOf("PRICE").multiplyBy(5);
   
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.project("_id", "press", "PRICE"),
        Aggregation.group("press")
        .count().as("count")
        .sum(price).as("total")
        .avg(price).as("avg")
    );


    final AggregationResults<Map> aggregationResults = mongoTemplate.aggregate(aggregation, BOOK_COLLECTION, Map.class);
    final List<Map> mappedResults = aggregationResults.getMappedResults();
    System.out.println(mappedResults);
}
2.6 排序与分页

排序与分页功能在 Query 对象中设置

@Test
public void testSortAndLimit() {
    Criteria criteria = Criteria.where("press").is("清华大学出版社");
    Query query = new Query(criteria);

    // with 方法设置排序
    query.with(Sort.by(Sort.Direction.ASC, "price"));
    // skip limit 方法设置分页
    query.skip(1).limit(10);

    final List<Book> books = mongoTemplate.find(query, Book.class, BOOK_COLLECTION);
    assert  books.size() ==2;
}

3. 更新

定义 Query 对象,定义要更新的数据范围

定义 Update 对象,定义更新内容

3.1 基础更新
3.1.1 更新基础字段
@Test
public void testUpdateField() {
    Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));

    Update update = new Update();
    update.set("name", "高等数学");

    final UpdateResult result = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
    assert result.wasAcknowledged();
}
3.1.2 加减数字
@Test
public void testUpdateNumber() {
    Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));

    Update update = new Update();
    // inc 方法指定数字增加多少
    update.inc("price",30.04);

    final UpdateResult result = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
    assert result.wasAcknowledged();
}
3.1.3 乘除数字
@Test
public void testMultiply() {
    Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));
    Update update = new Update().multiply("price", 0.3);

    final UpdateResult result = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
    assert result.wasAcknowledged();
}
3.2 字段更新
3.2.1 重命名字段
@Test
public void testRenameFiled() {
    Query query = new Query();
    Update update = new Update().rename("price", "PRICE");

    final UpdateResult result = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
    assert result.wasAcknowledged();
}
3.2.2 增加字段

set 方法在没有字段时,会自动增加字段

@Test
public void testAddField() {
    Query query = new Query();
    Update update = new Update().set("workspaceId", "default");

    final UpdateResult result = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
    assert result.wasAcknowledged();
}
3.2.3 删除字段
@Test
public void testRemoveField() {
    Query query = new Query();
    Update update = new Update().unset("_class");

    final UpdateResult result = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
    assert result.wasAcknowledged();
}
3.3 数组更新
3.3.1 插入数组内容
@Test
public void testAddToArray() {
    Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));

    Update update = new Update();
    // 添加一个,数组中存在元素在忽略添加
    //update.addToSet("tags").value("a");
    // 添加多个,数组中存在元素则忽略添加
    //update.addToSet("tags").each("a", "b", "c", "c");

    // 无论是否存在,都添加到数组, 可以通过 atPosition 指定存放位置
    update.push("tags").atPosition(1).value("101");

    final UpdateResult ret = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
    assert ret.wasAcknowledged();
}
3.3.2 移除数组内容
@Test
public void testRemoveArray() {
    Query query = new Query(Criteria.where("_id").is("614303980f84fe24d7618f28"));

    Update update = new Update();
    // 移除单个数据
    //update.pull("tags","a");
    // 移除多个数据
    update.pullAll("tags", new Object[]{"a", "b", "101"});

    final UpdateResult ret = mongoTemplate.updateFirst(query, update, BOOK_COLLECTION);
    assert ret.wasAcknowledged();
}
3.3.3 更新替换数组内容
// 数组内字符串更新
@Test
public void testUpdateArray1() {
    Query query = new Query();
    
    // 将标签数组中,所有 "编程" 替换为 "程序设计"
    Update update = new Update();
    update.set("tags.$[element]", "程序设计");
    update.filterArray(Criteria.where("element").is("编程"));

    final UpdateResult ret = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
    assert ret.wasAcknowledged();
}

// 数组内子文档更新
@Test
public void testUpdateArray2() {
    Query query = new Query();
    Update update = new Update();
    // 将作者姓名为 "王爽" 替换为 "王爽爽"
    update.set("authors.$[element].name", "王爽爽");
    update.filterArray(Criteria.where("element.name").is("王爽"));

    final UpdateResult ret = mongoTemplate.updateMulti(query, update, BOOK_COLLECTION);
    assert ret.wasAcknowledged();
}
3.4 复杂的更新操作

使用算数操作和条件操作,根据文档实际数据选择更新内容

AggregationUpdate update = Aggregation.newUpdate()
    .set("average").toValue(ArithmeticOperators.valueOf("tests").avg())     
    .set("grade").toValue(ConditionalOperators.switchCases(                 
    when(valueOf("average").greaterThanEqualToValue(90)).then("A"),
    when(valueOf("average").greaterThanEqualToValue(80)).then("B"),
    when(valueOf("average").greaterThanEqualToValue(70)).then("C"),
    when(valueOf("average").greaterThanEqualToValue(60)).then("D"))
                          .defaultTo("F")
                         );

4. 删除

@Test
public void testDelete() {
    Criteria criteria = Criteria.where("authors.name").is("王爽爽");
    Query query = new Query(criteria);
    final DeleteResult ret = mongoTemplate.remove(query, BOOK_COLLECTION);

    assert ret.wasAcknowledged();
}

附录

Srping MongoDB 文档

https://docs.spring.io/spring-data/mongodb/docs/current-SNAPSHOT/reference/html/#mongodb-template-update
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值