2024年HarmonyOS鸿蒙最新NestJs的基础使用_nestjs命令(2),2024年最新鸿蒙开发工程师面试

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

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

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

需要这份系统化的资料的朋友,可以戳这里获取

// 浏览器访问:http://localhost:3000/cat/say4/99
// 打印如下: param {id:‘99’}
@Get(‘/say4/:id’)
// Param参数为空
say4(@Param() param: { id: number }) {
console.log(‘param’, param);
return this.catService.say1();
}

// 5.Delete 删除接口
// APIFOX访问:http://localhost:3000/cat/say5/1 接口类型换成:Delete
// 打印如下: param { id: ‘1’ }
@Delete(‘/say5/:id’)
// Param参数为空
say5(@Param() param: { id: number }) {
console.log(‘param’, param);
return this.catService.say1();
}

// 6.Put 新增接口
// APIFOX访问:http://localhost:3000/cat/say5/1 接口类型换成:Put
// 打印如下: param { id: ‘1’ }
@Put(‘/say6/:id’)
// Param参数为空
say6(@Param() param: { id: number }) {
console.log(‘param’, param);
return this.catService.say1();
}

// 7.Post 新增接口
// APIFOX访问:http://localhost:3000/cat 接口类型换成:Post
// 参数在接口测试工具的body的json类型加入以下参数: { id: 1, name: ‘萧寂’ }
// 打印如下: cat { id: 1, name: ‘萧寂’ }
@Post()
say7(@Body() cat: any) {
console.log(‘cat’, cat);
return this.catService.say1();
}
}


## 提供者(也就是Java中的实体类)


**在上面创建的cat文件夹内创建interface文件夹,在文件夹内创建cat.interface.ts文件**  
 **在上面创建的cat文件夹内创建dto文件夹,在文件夹内创建create-cat.dto.ts文件**



// cat.interface.ts
export interface Cat {
name: string;
age: number;
breed: string;
}

// create-cat.dto.ts
import { Cat } from “…/interface/cat.interface”;
export class CreateCatDto implements Cat{
name: string;
age: number;
breed: string;
}

// cat.controller.ts
import {
Body,
Controller,
Post,
} from ‘@nestjs/common’;
import { CatService } from ‘./cat.service’;
import { CreateCatDto } from ‘./dto/create-cat.dto’;

@Controller(‘cat’)
export class CatController {
// 私有的,只读的service
constructor(private readonly catService: CatService) {}
// APIFOX访问:http://localhost:3000/cat 接口类型换成:Post
// 参数在接口测试工具的body的json类型加入以下参数: { id: 1, name: ‘萧寂’ }
// 打印如下: cat { id: 1, name: ‘萧寂’ }
@Post()
say(@Body() cat: CreateCatDto) {
console.log(‘cat’, cat); // 在apiFox打印结果和上面一样,只是加了类型校验,但是由于ts只进行类型约束,并不要求参数限定必须一致,因此这里请求的参数与dao和接口类不一致也可以拿到参数,并不会报错,只是不存在的参数不会对其类型进行校验而已了
return this.catService.say1();
}
}


## 中间件


### 创建中间件



nest g mi logger
// 安装完成后在目录中会多出一个logger文件夹

//在app.mudule.ts放入以下内容进行注册绑定中间件
import { MiddlewareConsumer, Module } from ‘@nestjs/common’;
import { AppController } from ‘./app.controller’;
import { AppService } from ‘./app.service’;
import { CatController } from ‘./cat/cat.controller’;
import { CatService } from ‘./cat/cat.service’;
import { LoggerMiddleware } from ‘./logger/logger.middleware’;

@Module({
imports: [],
controllers: [AppController, CatController],
providers: [AppService, CatService],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
// 下面这段代码代表先绑定中间件再应用到路由上,当访问cat或者带cat子接口时会被拦截到
consumer.apply(LoggerMiddleware).forRoutes(‘cat’);
// 由于同一个接口名可能会有get,put,delete,post等方法,因此可以使用下面方式对指定类型的接口进行拦截
consumer.apply(LoggerMiddleware).forRoutes({path:‘cat’,method:RequestMethod.GET}); // 只拦截get请求的cat接口
// 也可以直接将controller作为参数(这样的话,在当前控制器下面所有的请求都会被拦截到的)
consumer.apply(LoggerMiddleware).forRoutes(CatController);
// 如果当前需要拦截的请求较多,可以将controller作为参数,但又不希望其中个别接口被拦截到,可以使用下面方式忽略需要拦截的接口
consumer.apply(LoggerMiddleware).exclude({path:‘cat/:id’,method:RequestMethod.GET},{path:‘cat’,method:RequestMethod.POST}).forRoutes(CatController);
}
}

// ------------------------------------------------------------------------------------------------
// 在logger/lagger.middleware.tss中添加如下内容
import { Injectable, NestMiddleware } from ‘@nestjs/common’;

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
// 每次访问cat接口都会被拦截到,并打印出一下日志
console.log(‘正在访问cat接口’);
next();
}
}


**以上就是中间件基础使用方法了**


### 函数式中间件



// 在logger/lagger.middleware.tss中添加如下内容
import { Injectable, NestMiddleware } from ‘@nestjs/common’;

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log(‘111222’, 111222);
next();
}
}

// 下面这个是函数式中间件(只有函数式中间件可以注册成为全局中间件)
export function logger(req, res, next) {
console.log(‘Request_logger’);
next();
}


**注册成为全局中间件,只有函数式中间件可以注册成为全局中间件**



// 在main.ts进行注册(代码如下)
import { NestFactory } from ‘@nestjs/core’;
import { AppModule } from ‘./app.module’;
import { logger } from ‘./logger/logger.middleware’;

async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 将中间件注册成为全局中间件
app.use(logger);
await app.listen(3000);
}
bootstrap();


## 异常处理


### 异常抛出


**例如我们输入了一个不存在的地址,就会响应一个404,这个是框架给我们默认返回的一个异常,有时候我们需要手动去抛出一个异常**  
 **Nest提供了一个内置的HttpException类,为基础异常类,可以很好的帮助我们进行异常的处理**



// 在controller内其中一个接口如下:
import {Get,HttpException,HttpStatus} from ‘@nestjs/common’;

@Get(‘/xiaoji’)
xiaoji(): string {
throw new HttpException(‘服务器异常’, HttpStatus.FORBIDDEN);
}
// 浏览器访问:localhost:3000/cat/xiaoji
// 返回值为: {“statusCode”:403,“message”:“服务器异常”}


### 自定义异常及异常的处理


#### 创建并配置异常处理器



// 创建异常过滤器
nest g f http-exception
// 创建完成后目录会多出一个http-exception文件夹

// 在新创建的http-exception.filter.ts文件放入以下内容(已经处理好的)
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from ‘@nestjs/common’;
import { Request, Response } from ‘express’;
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();

response.status(status).json({
  statusCode: status,
  timestamp: new Date().toISOString(),
  path: request.url,
});

}
}


#### 使用异常处理器


**在cat.controller.ts内使用**



import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
UseFilters,
} from ‘@nestjs/common’;
import { HttpExceptionFilter } from ‘src/http-exception/http-exception.filter’;

// 在接口使用
@Get(‘/xiaoji’)
// 访问到这个接口直接抛出异常(异常的格式为我们上面定制的格式(状态码,时间和接口路径))
@UseFilters(new HttpExceptionFilter())
xiaoji(): string {
// 如果这里不抛出异常,上面这个异常过滤器不会执行,只有手动抛出异常则异常过滤器才会执行
throw new HttpException(‘服务器异常’, HttpStatus.FORBIDDEN);
}


**全局使用异常过滤器**



// 例如有的情况有异常,但是我们不能一个个来抛出,例如全局的404异常,因此需要使用全局异常过滤器,方式如下

// main.ts
import { NestFactory } from ‘@nestjs/core’;
import { AppModule } from ‘./app.module’;
import { logger } from ‘./logger/logger.middleware’;
import { HttpExceptionFilter } from ‘./http-exception/http-exception.filter’;

async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 将中间件注册成为全局中间件
app.use(logger);
// 全局注册异常过滤器
app.useGlobalFilters(new HttpExceptionFilter());
//
await app.listen(3000);
}
bootstrap();

// 当访问一个不存在的接口会返回404状态码和时间以及异常的接口路径(改写了默认的样式)


## 管道



管道有两个典型的应用场景:
转换:管道将输入数据转换为所需的数据输出(例如,将字符串转换为整数)
验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常


Nest 自带九个开箱即用的管道,即


* ValidationPipe
* ParseIntPipe
* ParseFloatPipe
* ParseBoolPipe
* ParseArrayPipe
* ParseUUIDPipe
* ParseEnumPipe
* DefaultValuePipe
* ParseFilePipe


### 转换


**这里以ParseIntPipe为例,进行数据的转化**



// 在cat.controller.ts其中一个接口如下内容
import {Get,Param,ParseIntPipe} from ‘@nestjs/common’;
//这个管道可以将接收的参数转为number类型,否则返回的是{id:“123”},转换之后变成{id:123}
//如果参数给个abc导致不能转化成number,则会抛出400错误的异常,代表转化失败
@Get(‘:id’)
say1(@Param(‘id’, new ParseIntPipe()) id: number) {
return {
id: id,
};
}


### 验证(守卫)


#### 基础使用



> 
> 守卫在所有中间件之后执行,但在拦截器或管道之前执行。
> 
> 
> 



// 创建守卫
nest g gu role
// 在新建的role.guard.ts内放入以下内容
import { CanActivate, ExecutionContext, Injectable } from ‘@nestjs/common’;
import { Observable } from ‘rxjs’;

@Injectable()
export class RoleGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise | Observable {
console.log(‘RoleGuard’);
return true;
}
}

// 再新建一个controller
nest g co user
// controller内放入以下内容
import {
Controller,
Get,
Param,
ParseIntPipe,
UseGuards,
} from ‘@nestjs/common’;
import { RoleGuard } from ‘src/role/role.guard’;

@Controller(‘user’)
@UseGuards(RoleGuard) // 使用守卫,当访问下面任意接口时就会触发守卫,并打印(‘RoleGuard’)
export class UserController {
@Get(‘:id’)
// @UseGuards(RoleGuard) // 使用守卫,当访问当前接口时就会触发守卫,并打印(‘RoleGuard’)
getUserDetial(@Param(‘id’, ParseIntPipe) id: number) {
return {
id,
};
}
}


#### 定义权限,进行路由比对



// controller.ts内加入下面代码
import {
Controller,
Get,
Param,
ParseIntPipe,
UseGuards,
SetMetadata,
} from ‘@nestjs/common’;
import { RoleGuard } from ‘src/role/role.guard’;

@Controller(‘user’)
export class UserController {
@Get(‘:id’)
@SetMetadata(‘role’, [‘access1’, ‘access2’])
@UseGuards(RoleGuard) // 使用守卫
getUserDetial(@Param(‘id’, ParseIntPipe) id: number) {
return {
id,
};
}
}

// role.guard.ts加入下面代码
import { CanActivate, ExecutionContext, Injectable } from ‘@nestjs/common’;
import { Reflector } from ‘@nestjs/core’;
import { Observable } from ‘rxjs’;

@Injectable()
export class RoleGuard implements CanActivate {
// 加上反射(可以拿到接口对应相关的一些信息)
constructor(private reflector: Reflector) {}

canActivate(
context: ExecutionContext,
): boolean | Promise | Observable {
console.log(‘RoleGuard’);
// 下面的role与controller接口的@SetMetadata(‘role’, [‘access1’,‘access2’])第一个参数保持一致
const roles = this.reflector.get<string[]>(‘role’, context.getHandler());
console.log(‘roles’, roles); // 打印的roles就是[‘access1’,‘access2’],刚刚的第二个参数
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
// 上面那个可以在接口上定义权限,然后拿到定义的权限,这里的user代表用户发请求携带的权限,这里可以进行路由比对,有权限放行,没权限返回false
// 这里是拿到了用户的标识,我们的系统可以查询到用户的权限
console.log(‘user’,user);
return true;
}
}


#### 第二种方式(装饰器模式)



// 创建装饰器
nest g d role
// 创建完成后会发现多了个role.decorator.ts文件

// controller.ts
import {
Controller,
Get,
UseGuards,
} from ‘@nestjs/common’;
import { Role } from ‘src/role/role.decorator’; // 引入装饰器
import { RoleGuard } from ‘src/role/role.guard’; // 引入守卫

@Controller(‘user’)
@UseGuards(RoleGuard) // 使用守卫,下面的所有接口都会触发守卫
export class UserController {
@Get(‘user/list’)
@Role(‘access1’, ‘access2’,‘access3’, ‘access4’) // 装饰器内的参数就是权限(个人感觉较麻烦不如上面那个)
getUserList() {
return [1, 2, 3, 4];
}
}

// 访问接口得到-----roles [ ‘access1’, ‘access2’, ‘access3’, ‘access4’ ]


## 数据库


### 连接数据库



// 安装依赖
yarn add --save @nestjs/typeorm typeorm mysql2
// @nestjs/typeorm这个本人运行上面命令一直安装不上,只好单独安装一下就成功了(yarn add @nestjs/typeorm)

// 安装完成后在app.module.ts进行导入
import { Module } from ‘@nestjs/common’;
import { TypeOrmModule } from ‘@nestjs/typeorm’;
@Module({
imports: [
TypeOrmModule.forRoot({
type: ‘mysql’,
host: ‘localhost’,
port: 3306,
username: ‘root’,
password: ‘admin’,
database: ‘shop’,
// entities: [], // 实体类文件,下面创建完curd会讲怎么写的
synchronize: true, //是否将实体同步到数据库
retryDelay:500, // 重试连接数据库的间隔
retryAttempts: 10, // 重试连接数据库的次数
autoLoadEntities: true, // 自动加载实体(加上这个就不用上面的entities了)
logging: true, // 是否开启日志
logger: ‘advanced-console’, // 日志
}),
],
})
export class AppModule {}


### 创建CURD



nest g res test
// 选择第一个REST API(遵循rest api的规范)
// 选择y 确定创建curd(增删改查)

// 找到test/entities/test.entity.ts(只要创建了CURD这种文件,一般实体类都是这个位置,那么就可以动态加载实体类了,找到刚刚创建数据库的地方,将entities: [],填充以下内容,用于动态获取实体类),如果autoLoadEntities设置为true了,这个属性可以去掉,autoLoadEntities用于自动加载实体
entities: [__dirname + ‘/**/*.entity{.ts,.js}’],


#### 在创建完的CURD文件内的实体类编写如下代码


**test/entities/test.entity.ts**



// 定义实体,定义列,定义自增主键
import { Entity, Column, PrimaryGeneratedColumn } from ‘typeorm’;

@Entity() // 定义实体类
export class Test {
@PrimaryGeneratedColumn()
id: number; // 自增id列

@Column()
name: string; // 列名

@Column()
password: string;

@Column()
age: number;
}


#### 在test/test.model.ts里面编写如下代码



import { Module } from ‘@nestjs/common’;
import { TestService } from ‘./test.service’;
import { TestController } from ‘./test.controller’;
// 引入实体
import { Test } from ‘./entities/test.entity’;
// 引入orm框架
import {TypeOrmModule} from ‘@nestjs/typeorm’
@Module({
// 引入注册
imports:[TypeOrmModule.forFeature([Test])],
controllers: [TestController],
providers: [TestService],
})
export class TestModule {}


**保存重启项目会发现MySQL里面的shop这个数据库多了一个test这张表,表内的字段和实体类一一对应**


### 实体类介绍



// 定义实体,定义列,定义自增主键
import { Entity, Column, PrimaryGeneratedColumn,CreateDateColumn,Generated } from ‘typeorm’;

@Entity() // 定义实体类
export class Test {
@PrimaryGeneratedColumn(“uuid”) // 自增的uuid
id: number; // 自增id列

@Column({type:“varchar”,length:255}) // 列类型
name: string;

@Column()
password: string;

@Column()
age: number;

// @CreateDateColumn() // 自增日期
@CreateDateColumn({ type: ‘timestamp’ }) // 时间戳类型
createTime: Date;

@Generated(‘uuid’) // 自动生成列
uuid: string;

@Column({ // 定义枚举类型
type: ‘enum’,
enum: [1, 2, 3, 4],
default: 1,
})
xiaoji: number;
}



mysql 所有类型

int, tinyint, smallint, mediumint, bigint, float, double, dec, decimal, numeric, date, datetime, timestamp, time, year, char, varchar, nvarchar, text, tinytext, mediumtext, blob, longtext, tinyblob, mediumblob, longblob, enum, json, binary, geometry, point, linestring, polygon, multipoint, multilinestring, multipolygon, geometrycollection


**列选项**



@Column({
    type:"varchar",
    name:"ipaaa", //数据库表中的列名
    nullable:true, //在数据库中使列NULL或NOT NULL。 默认情况下,列是nullable:false
    comment:"注释",
    select:true,  //定义在进行查询时是否默认隐藏此列。 设置为false时,列数据不会显示标准查询,适合用于密码。 默认情况下,列是select:true
    default:"xxxx", //加数据库级列的DEFAULT值
    primary:false, //将列标记为主要列。 使用方式和@ PrimaryColumn相同。
    update:true, //指示"save"操作是否更新列值。如果为false,则只能在第一次插入对象时编写该值。 默认值为"true"
    collation:"", //定义列排序规则。
})
ip:string

**simple-array 列类型  
 有一种称为simple-array的特殊列类型,它可以将原始数组值存储在单个字符串列中。 所有值都以逗号分隔**



@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column("simple-array")
names: string[];

}


\*\*simple-json列类型  
 还有一个名为simple-json的特殊列类型,它可以存储任何可以通过 JSON.stringify 存储在数据库中的值。 当你的数据库中没有 json 类型而你又想存储和加载对象,该类型就很有用了。 例如:  
 \*\*



@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column("simple-json")
profile: { name: string; nickname: string };

}


## 数据库的增删改查的使用


### axios 接口定义



import axios from ‘axios’
axios.defaults.baseURL = ‘http://localhost:3000’
// 增
export const addUser = (data) => axios.post(‘/user’,data).then(res => res.data)
// 查
export const getList = (data) => axios.get(‘/user’,{params:data}).then(res => res.data)
// 删
export const delUser = (data) => axios.delete(/user/${data.id}).then(res => res.data)
// 改
export const updateUser = (data) => axios.patch(/user/${data.id},data).then(res => res.data)


### nest后端代码为


#### 实体类为



import { Entity, Column, PrimaryGeneratedColumn } from ‘typeorm’;
@Entity()
export class CreateUserDto {
@Column()
name:string
@Column()
desc:string
}


#### controller代码如下



import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from ‘@nestjs/common’;
import { UserService } from ‘./user.service’;
import { CreateUserDto } from ‘./dto/create-user.dto’;
import { UpdateUserDto } from ‘./dto/update-user.dto’;

@Controller(‘user’)
export class UserController {
constructor(private readonly userService: UserService) {}

// 增
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
// 查
@Get()
findAll(@Query() query:{keyWord:string,page:number,pageSize:number}) {
return this.userService.findAll(query);
}
// 改
@Patch(‘:id’)
update(@Param(‘id’) id: string, @Body() updateUserDto: UpdateUserDto) {
return this.userService.update(+id, updateUserDto);
}
// 删
@Delete(‘:id’)
remove(@Param(‘id’) id: string) {
return this.userService.remove(+id);
}
}


### service代码如下



> 
> 1.引入 InjectRepository typeOrm 依赖注入 接受一个实体  
>  2.引入类型 Repository 接受实体泛型  
>  3.Like 用于模糊查询  
>  4.save 保存 find 查询 update 更新 delete 删除
> 
> 
> 



import { Injectable } from ‘@nestjs/common’;
import { CreateUserDto } from ‘./dto/create-user.dto’;
import { UpdateUserDto } from ‘./dto/update-user.dto’;
import { Repository, Like } from ‘typeorm’;
import { InjectRepository } from ‘@nestjs/typeorm’;
import { User } from ‘./entities/user.entity’;
@Injectable()
export class UserService {
constructor(@InjectRepository(User) private readonly user: Repository) { }
// 增
create(createUserDto: CreateUserDto) {
const data = new User() // 创建实体类对象
data.name = createUserDto.name // 实体类的name赋值为传来的name
data.desc = createUserDto.desc // 实体类的desc赋值为传来的desc
return this.user.save(data) // 保存(每次保存都相当于添加一次)
}
// 查
async findAll(query: { keyWord: string, page: number, pageSize: number }) {
const data = await this.user.find({ // 查询
where: {
name: Like(%${query.keyWord}%) //模糊查询
},
select:[‘id’,‘name’], // 指定查询出来的字段
order: {
id: “DESC” // 倒序
},
skip: (query.page - 1)* query.pageSize, // skip跳过,从0开始
take:query.pageSize, // 需要展示的每页的数量
})
const total = await this.user.count({ // 总数量
where: {
name: Like(%${query.keyWord}%)
},
})
return {

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

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

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

需要这份系统化的资料的朋友,可以戳这里获取

where: {
name: Like(%${query.keyWord}%) //模糊查询
},
select:[‘id’,‘name’], // 指定查询出来的字段
order: {
id: “DESC” // 倒序
},
skip: (query.page - 1)* query.pageSize, // skip跳过,从0开始
take:query.pageSize, // 需要展示的每页的数量
})
const total = await this.user.count({ // 总数量
where: {
name: Like(%${query.keyWord}%)
},
})
return {

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


[外链图片转存中…(img-GSInbE2n-1715631105927)]
[外链图片转存中…(img-KTaI3eXo-1715631105927)]

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

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

需要这份系统化的资料的朋友,可以戳这里获取

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值