SpringBoot+MybatisPlus+Swagger快速开发套路和总结

  1. 业务类

业务类对标我们上面的顺序

下面我严格遵守我的套路来写

1.2 建module


建立新项目的时候可以用spring initializr初始化boot项目,也可以用maven的方式创建。这里我采用maven的方式来创建

image-20220415093644304

1.3 改pom


pom出现的问题,跟网络环境有很大关系。

pom如果不断出错,可以更换网络或者删掉本地仓库中的包,重下

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd”>

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.2.2.RELEASE

com.caq

mybatisplusdemo

0.0.1-SNAPSHOT

mybatisplusdemo

Demo project for Spring Boot

<java.version>1.8</java.version>

io.springfox

springfox-swagger-ui

provided

2.7.0

io.springfox

springfox-swagger2

2.7.0

com.baomidou

mybatis-plus-boot-starter

3.0.5

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter

mysql

mysql-connector-java

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-maven-plugin

org.projectlombok

lombok

1.4 yml


这里我采用mysql8.0以上的驱动,配置url到时候?后面的值必须要加

驱动的名称和mysql5的也不一样记得区分

#端口号

server:

port: 8003

#服务名

spring:

application:

name: service-edu

#返回json的全局时间格式

jackson:

date-format: yyyy-MM-dd HH:mm:ss

time-zone: GMT+8

datasource:

username: root

password: root

url: jdbc:mysql://localhost:3306/mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8

driver-class-name: com.mysql.cj.jdbc.Driver

profiles:

active: dev

mybatis日志

mybatis-plus:

configuration:

log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

1.5 主启动


package com.caq.mybatisplusdemo;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class MybatisplusdemoApplication {

public static void main(String[] args) {

SpringApplication.run(MybatisplusdemoApplication.class, args);

}

}

1.6 业务类


下面就开始写业务类了,业务类也遵守我们的步骤来写

1.6.1 建表写sql

CREATE TABLE user01

(

id int(20) NOT NULL COMMENT ‘主键ID’ ,

name01 VARCHAR(30) NULL DEFAULT NULL COMMENT ‘姓名’,

age01 INT(11) NULL DEFAULT NULL COMMENT ‘年龄’,

email01 VARCHAR(50) NULL DEFAULT NULL COMMENT ‘邮箱’,

PRIMARY KEY (id)

);

DELETE FROM user01;

INSERT INTO user01 (id, name01, age01, email01) 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’);

1.6.2 实体类、dao、service

这里我们采用mp的插件,一键完成创建

安装插件

image-20220415095650610

idea中连接数据库

image-20220415095707544

右键你要一键生成实体类,dao,service的表格generator即可

image-20220415095756678

生成说明

image-20220415100707303

image-20220415100857091

image-20220415101005965

service接口说明

image-20220415103131974

那么我们是不是要实现这么多方法呢?

当然不用,mp给我们定义好了一个IService的实现类,我们只需要实现类继承它并实现接口即可

image-20220415103354040

1.6.3 controller

调用service,service调用mapper(dao)

开发controller,service,dao的过程就叫开发接口

为了前后端更好的沟通,我们可以定义一个统一的返回类

统一返回类

状态码定义

package com.caq.commonutils;

public interface ResultCode {

//状态码:成功

public static Integer SUCCESS = 20000;

//状态码:失败

public static Integer ERROR = 20001;

}

统一返回类型R

package com.caq.commonutils;

import io.swagger.annotations.ApiModelProperty;

import lombok.Data;

import java.util.HashMap;

import java.util.Map;

/**

  • 结果类

*/

@Data

public class R {

@ApiModelProperty(value = “是否成功”)

private Boolean success;

@ApiModelProperty(value = “返回码”)

private Integer code;

@ApiModelProperty(value = “返回消息”)

private String message;

@ApiModelProperty(value = “返回数据”)

private Map<String, Object> data = new HashMap<>();

//私有的构造器

private R() {}

//成功的静态方法

public static R ok() {

R r = new R();

r.setSuccess(true);

r.setCode(ResultCode.SUCCESS);

r.setMessage(“您的操作成功啦()”);

return r;

}

//失败的静态方法

public static R error() {

R r = new R();

r.setSuccess(false);

r.setCode(ResultCode.ERROR);

r.setMessage(“您的操作失败啦(⊙︿⊙)”);

return r;

}

//the follow methods all return this,链式编程

public R success(Boolean success){

this.setSuccess(success);

return this;

}

public R code(Integer code){

this.setCode(code);

return this;

}

public R message(String message){

this.setMessage(message);

return this;

}

public R data(String key, Object value){

this.data.put(key, value);

return this;

}

public R data(Map<String, Object> map){

this.setData(map);

return this;

}

}

crud接口

@Api 用在类上,说明该类的作用。

@ApiOperation 用在 Controller 里的方法上,说明方法的作用,每一个接口的定义。

@PathVariable 映射 URL 绑定的占位符

通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以

若方法参数名称和需要绑定的url中变量名称一致时,可以简写

public User getUser(@PathVariable(“name”) String name)

public User getUser(@PathVariable String name)

package com.caq.mybatisplusdemo.controller;

import com.caq.mybatisplusdemo.commonutils.R;

import com.caq.mybatisplusdemo.domain.User;

import com.caq.mybatisplusdemo.service.UserService;

import io.swagger.annotations.Api;

import io.swagger.annotations.ApiOperation;

import io.swagger.annotations.ApiParam;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

import javax.websocket.server.PathParam;

import java.util.List;

@Api(“crud测试”)

@RestController

@RequestMapping(“/testMP/user”)

public class crudDemo {

@Autowired

UserService userService;

@ApiOperation(“增加用户”)

@PostMapping(“save”)

public R saveUser(@RequestBody User user){

boolean save = userService.save(user);

if (save){

return R.ok();

}else {

return R.error();

}

}

@ApiOperation(“查看所有用户”)

@GetMapping(“list”)

public R listUser(){

List list = userService.list(null);

return R.ok().data(“items”,list);

}

@ApiOperation(“查看某个用户”)

@GetMapping(“getByIdUser”)

public R getByIdUser(@PathVariable String id){

User user = userService.getById(id);

return R.ok().data(“user”,user);

}

@ApiOperation(“按ID删除user”)

@DeleteMapping(“delete”)

public R removeUser(@ApiParam(name = “id”,value = “讲师ID”,required = true)@PathVariable String id){

boolean delete = userService.removeById(id);

if (delete){

return R.ok();

}else {

return R.error();

}

}

@ApiOperation(“按ID更改user”)

@PostMapping

public R updateUser(@RequestBody User user){

boolean update = userService.updateById(user);

if (update){

return R.ok();

}else {

return R.error();

}

}

}

好,让我们先来介绍下Swagger

二、Swagger

========================================================================

还是一样的套路,开局三连问

2.1 是什么?


一款接口测试工具

2.2 有什么好处?


对于后端开发人员来说

  • 不用再手写WiKi接口拼大量的参数,避免手写错误

  • 对代码侵入性低,采用全注解的方式,开发简单

  • 方法参数名修改、增加、减少参数都可以直接生效,不用手动维护

  • 缺点:增加了开发成本,写接口还得再写一套参数配置

对于前端开发来说

  • 后端只需要定义好接口,会自动生成文档,接口功能、参数一目了然

  • 联调方便,如果出问题,直接测试接口,实时检查参数和返回值,就可以快速定位是前端还是后端的问题

对于测试

  • 对于某些没有前端界面UI的功能,可以用它来测试接口

  • 操作简单,不用了解具体代码就可以操作

  • 操作简单,不用了解具体代码就可以操作

2.3 怎么用?


引入swagger的依赖

目前推荐使用2.7.0版本,因为2.6.0版本有bug

2.3.1 引入依赖

io.springfox

springfox-swagger-ui

provided

2.7.0

io.springfox

springfox-swagger2

2.7.0

2.3.2 springBoot整合swagger

@Configuration

@MapperScan(“com.caq.mybatisplusdemo.mapper”)

@EnableSwagger2

public class MpConfig {

@Bean

public Docket webApiConfig() {

return new Docket(DocumentationType.SWAGGER_2)

.groupName(“webApi”)

.apiInfo(webApiInfo())

.select()

.paths(Predicates.not(PathSelectors.regex(“/admin/.*”)))

.paths(Predicates.not(PathSelectors.regex(“/error.*”)))

.build();

}

private ApiInfo webApiInfo() {

return new ApiInfoBuilder()

.title(“mp测试”)

.description(“本文档描述了mp接口定义”)

.version(“1.0”)

.contact(new Contact(“java”, “http://java.com”, “534215342@qq.com”))

.build();

}

}

2.4 swagger的注解


@Api():用在请求的类上,表示对类的说明,也代表了这个类是swagger2的资源

参数:

tags:说明该类的作用,参数是个数组,可以填多个。

value=“该参数没什么意义,在UI界面上不显示,所以不用配置”

description = “用户基本信息操作”

@ApiOperation():用于方法,表示一个http请求访问该方法的操作

参数:

value=“方法的用途和作用”

notes=“方法的注意事项和备注”

tags:说明该方法的作用,参数是个数组,可以填多个。

格式:tags={“作用1”,“作用2”}

(在这里建议不使用这个参数,会使界面看上去有点乱,前两个常用)

@ApiModel():用于响应实体类上,用于说明实体作用

参数:

description=“描述实体的作用”

@ApiModelProperty:用在属性上,描述实体类的属性

参数:

value=“用户名” 描述参数的意义

name=“name” 参数的变量名

required=true 参数是否必选

@ApiImplicitParams:用在请求的方法上,包含多@ApiImplicitParam

@ApiImplicitParam:用于方法,表示单独的请求参数

参数:

name=“参数ming”

value=“参数说明”

dataType=“数据类型”

paramType=“query” 表示参数放在哪里

· header 请求参数的获取:@RequestHeader

· query 请求参数的获取:@RequestParam

· path(用于restful接口) 请求参数的获取:@PathVariable

· body(不常用)

· form(不常用)

defaultValue=“参数的默认值”

required=“true” 表示参数是否必须传

@ApiParam():用于方法,参数,字段说明 表示对参数的要求和说明

参数:

name=“参数名称”

value=“参数的简要说明”

defaultValue=“参数默认值”

required=“true” 表示属性是否必填,默认为false

@ApiResponses:用于请求的方法上,根据响应码表示不同响应

一个@ApiResponses包含多个@ApiResponse

@ApiResponse:用在请求的方法上,表示不同的响应

参数

code=“404” 表示响应码(int型),可自定义

message=“状态码对应的响应信息”

@ApiIgnore():用于类或者方法上,不被显示在页面上

@Profile({“dev”, “test”}):用于配置类上,表示只对开发和测试环境有用

2.5 使用swagger需要注意的问题


  • 对于只有一个HttpServletRequest参数的方法,如果参数小于5个,推荐使用 @ApiImplicitParams的方式单独封装每一个参数;如果参数大于5个,采用定义一个对象去封装所有参数的属性,然后使用@APiParam的方式

  • **默认的访问地址:**ip:port/swagger-ui.html#/,但是在shiro中,会拦截所有的请求,必须加上默认访问路径(比如项目中,就是ip:port/context/swagger-ui.html#/),然后登陆后才可以看到

  • 在GET请求中,参数在Body体里面,不能使用@RequestBody。在POST请求,可以使用@RequestBody和@RequestParam,如果使用@RequestBody,对于参数转化的配置必须统一

  • controller必须指定请求类型,否则swagger会把所有的类型(6种)都生成出来

  • swagger在生产环境不能对外暴露,可以使用@Profile({“dev”, “prod”,“pre”})指定可以使用的环境

三、Swagger测试接口

============================================================================

下面我们就开始用Swagger来测试我们写的接口

3.1 MybatisPlus补充


3.1.1 主键生成策略

下面是我对实体类字段进行的设置,

这样设置的话我的主键在每次创建新用户的时候都会自动填充为分布式全局唯一ID 字符串类型

private static final long serialVersionUID = 1L;

@ApiModelProperty(value = “讲师ID”)

/**

  • 分布式应用时,我们需要生成分布式ID,可以选择使用@TableId(type=IdType.ID_WORKER),数据库中的主键为:

  • IdType包括以下几类:

  • AUTO : 数据库主键自增

  • INPUT: 用户自行输入

  • ID_WORKER: 分布式全局唯一ID, 长整型

  • UUID: 32位UUID字符串

  • NONE: 无状态

  • ID_WORKER_STR: 分布式全局唯一ID 字符串类型

*/

@TableId(value = “id”, type = IdType.ID_WORKER_STR)

private String id;

image-20220407211824966

3.1.2 自动填充详解

自动填充一般应用在数据库创建时间或修改时间字段

[自动填充功能官网](自动填充功能 | MyBatis-Plus (baomidou.com))

定义字段

@ApiModelProperty(value = “创建时间”)

@TableField(fill= FieldFill.INSERT)

private Date gmtCreate;

@ApiModelProperty(value = “更新时间”)

@TableField(fill=FieldFill.INSERT_UPDATE)

private Date gmtModified;

配置类

package com.caq.servicebase.handle;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;

import org.apache.ibatis.reflection.MetaObject;

import org.springframework.stereotype.Component;

import java.util.Date;

@Component

public class MyMetaObjectHandler implements MetaObjectHandler {

//插入的时候填充创建和修改字段

@Override

public void insertFill(MetaObject metaObject) {

//属性名称,不是字段名称

this.setFieldValByName(“gmtCreate”,new Date(),metaObject);

this.setFieldValByName(“gmtModified”,new Date(),metaObject);

}

//修改的时候填充修改字段

@Override

public void updateFill(MetaObject metaObject) {

this.setFieldValByName(“gmtModified”,new Date(),metaObject);

}

}

测试自动填充

3.1.3 事物

什么是事物?

一组逻辑上的操作,要么都成功,要么都失败

如果不考虑事物隔离性,参数读问题?

  • 脏读(事物A读取到了事物B修改未提交的数据)

  • 不可重复读(事物A读取两次数据,在这个过程中事物B修改了数据,导致事物A两次读取的数据不一样)

  • 幻读(事物B读取两次数据,在这个过程事物A新增了符合事物B读取的数据,后一次读取的数据和前一次是不一样的这就是幻读)

3.1.4 锁

锁是针对数据冲突的解决方案

悲观锁

正如其名,它指的是对数据被外界修改持保守(悲观),因此在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制

乐观锁

相对悲观锁而言,乐观锁假设认为数据一般情况下不会有冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现了冲突,则让返回用户错误的信息,让用户决定如何去做。乐观锁的实现方式一般是记录数据版本

乐观锁的实现方式

  • 取出记录,获取当前Version

  • 更新时,带上这个version

  • 执行更新时,set version = newVersion where version = oldVersion

  • 如果version不对,就更新失败

image-20220408160411414

3.2 Swagger测试


登录swaggerUI

ip:prot/swagger-ui.html

image-20220409162626568

3.2.1 删除功能测试

逻辑删除我们是用Mp中的插件来实现的

所以在mp的配置类中添加逻辑删除插件即可

  • 插入: 不作限制
  • 查找: 追加 where 条件过滤掉已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
  • 更新: 追加 where 条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
  • 删除: 转变为 更新

例如:

  • 删除: update user set deleted=1 where id = 1 and deleted=0
  • 查找: select id,name,deleted from user where deleted=0

在开发中,我们一般做逻辑删除

所谓逻辑删除不是真正的删除,而是在逻辑上删除不是在数据库中删除

步骤一

//逻辑删除插件

@Bean

public ISqlInjector sqlInjector() {

return new LogicSqlInjector();

}

步骤二

实体类字段上加上@TableLogic注解

@TableLogic

private Integer deleted;

image-20220409163156257

3.2.2 讲师分页功能

分页功能我们也是用Mp中的插件来实现的

所以在mp的配置类中添加分页插件即可

步骤一

分页插件

//分页插件

@Bean

public PaginationInterceptor paginationInterceptor() {

return new PaginationInterceptor();

}

步骤二

分页Controller方法

@ApiOperation(“分页查询讲师功能”)

@GetMapping(“pageTeacher/{current}/{limit}”)

public R pageTeacher(@PathVariable long current,

@PathVariable long limit) {

//创建page对象

Page pageTeacher = new Page<>(current, limit);

//调用方法实现分页

//调用方法的时候,底层封装,把分页所有数据封装到pageTeacher对象里面

teacherService.page(pageTeacher, null);

long total = pageTeacher.getTotal();

List records = pageTeacher.getRecords();

Map<String, Object> map = new HashMap();

map.put(“total”, total);

map.put(“rows”, records);

return R.ok().data(map);

// 两种方式都可以

// return R.ok().data(“total”,total).data(“rows”,records);

}

img

image-20220409205459668

3.2.3 分页查询和多条件查询

@requestbody注解的作用是使用json传递数据,并把json封装到对应对象里面

面试题补充:

你经常用springboot中的那些注解?

@RequestBody、@ResponseBody、@PathVariable

前者是以json格式传递数据

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

总结:绘上一张Kakfa架构思维大纲脑图(xmind)

image

其实关于Kafka,能问的问题实在是太多了,扒了几天,最终筛选出44问:基础篇17问、进阶篇15问、高级篇12问,个个直戳痛点,不知道如果你不着急看答案,又能答出几个呢?

若是对Kafka的知识还回忆不起来,不妨先看我手绘的知识总结脑图(xmind不能上传,文章里用的是图片版)进行整体架构的梳理

梳理了知识,刷完了面试,如若你还想进一步的深入学习解读kafka以及源码,那么接下来的这份《手写“kafka”》将会是个不错的选择。

  • Kafka入门

  • 为什么选择Kafka

  • Kafka的安装、管理和配置

  • Kafka的集群

  • 第一个Kafka程序

  • Kafka的生产者

  • Kafka的消费者

  • 深入理解Kafka

  • 可靠的数据传递

  • Spring和Kafka的整合

  • SpringBoot和Kafka的整合

  • Kafka实战之削峰填谷

  • 数据管道和流式处理(了解即可)

image

image

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

3.2.3 分页查询和多条件查询

@requestbody注解的作用是使用json传递数据,并把json封装到对应对象里面

面试题补充:

你经常用springboot中的那些注解?

@RequestBody、@ResponseBody、@PathVariable

前者是以json格式传递数据

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-24Nx2ow6-1712809980115)]
[外链图片转存中…(img-4wFAQQT1-1712809980115)]
[外链图片转存中…(img-En8HnyqC-1712809980116)]
[外链图片转存中…(img-OsBN2oeM-1712809980116)]
[外链图片转存中…(img-VUnacsmm-1712809980116)]
[外链图片转存中…(img-Afn8Iusy-1712809980116)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-eJq5IS0a-1712809980117)]

总结:绘上一张Kakfa架构思维大纲脑图(xmind)

[外链图片转存中…(img-LFbArRis-1712809980117)]

其实关于Kafka,能问的问题实在是太多了,扒了几天,最终筛选出44问:基础篇17问、进阶篇15问、高级篇12问,个个直戳痛点,不知道如果你不着急看答案,又能答出几个呢?

若是对Kafka的知识还回忆不起来,不妨先看我手绘的知识总结脑图(xmind不能上传,文章里用的是图片版)进行整体架构的梳理

梳理了知识,刷完了面试,如若你还想进一步的深入学习解读kafka以及源码,那么接下来的这份《手写“kafka”》将会是个不错的选择。

  • Kafka入门

  • 为什么选择Kafka

  • Kafka的安装、管理和配置

  • Kafka的集群

  • 第一个Kafka程序

  • Kafka的生产者

  • Kafka的消费者

  • 深入理解Kafka

  • 可靠的数据传递

  • Spring和Kafka的整合

  • SpringBoot和Kafka的整合

  • Kafka实战之削峰填谷

  • 数据管道和流式处理(了解即可)

[外链图片转存中…(img-pw2FF12D-1712809980117)]

[外链图片转存中…(img-So05xiIW-1712809980117)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-YB6ncyeI-1712809980118)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值