【项目记录】部门增删改及日志技术

1 删除部门

1.1 需求

删除部门数据。在点击 "删除" 按钮,会根据ID删除部门数据。

了解了需求之后,我们再看看接口文档中,关于删除部门的接口的描述,然后根据接口文档进行服务端接口的开发。

1.2 接口描述

1.2.1 基本信息

请求路径:/depts

请求方式:DELETE

接口描述:该接口用于根据ID删除部门数据

1.2.2 请求参数

参数说明:

参数名类型是否必须备注
idnumber必须部门ID

请求参数样例:

/depts?id=1  

/depts?id=2

1.2.3 响应数据

参数格式:application/json

参数说明:

参数名类型是否必须备注
codenumber必须响应码,1 代表成功,0 代表失败
msgstring非必须提示信息
dataobject非必须返回的数据

 1.3 思路分析

Controller:接收客户端请求参数(如/depts?id=8),调用 Service 层处理业务,再将结果响应给客户端 。

Service:承接 Controller 调用,调用 Mapper 接口方法,实现业务逻辑 。

Mapper:执行 SQL 语句(delete from dept where id =?),负责与数据库交互,完成数据的增删改查 。

1.4 简单参数接收

我们看到,在controller中,需要接收前端传递的请求参数。 那接下来,我们就先来看看在服务器端的Controller程序中,如何获取这类简单参数。 具体的方案有如下三种:

方案一:通过原始的 HttpServletRequest 对象获取请求参数

/**
* 根据ID删除部门 - 简单参数接收: 方式一 (HttpServletRequest)
*/
@DeleteMapping("/depts")
public Result delete(HttpServletRequest request){
    String idStr = request.getParameter("id");
    int id = Integer.parseInt(idStr);
    
    System.out.println("根据ID删除部门: " + id);
    return Result.success();
}

这种方案实现较为繁琐,而且还需要进行手动类型转换。【项目开发很少用】

方案二:通过Spring提供的 @RequestParam 注解,将请求参数绑定给方法形参

@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer deptId){
    System.out.println("根据ID删除部门: " + deptId);
    return Result.success();
}

@RequestParam 注解的value属性,需要与前端传递的参数名保持一致 。

注:@RequestParam注解required属性默认为true,代表该参数必须传递,如果不传递将报错。 如果参数可选,可以将属性设置为false

方案三:如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam)

@DeleteMapping("/depts")
public Result delete(Integer id){
    System.out.println("根据ID删除部门: " + deptId);
    return Result.success();
}

对于以上的这三种方案,推荐第三种方案

1.5 代码实现

1) Controller层

在 DeptMapper 中,增加 delete 方法,代码实现如下:

/**
 * 根据id删除部门 - delete http://localhost:8080/depts?id=1
 */
@DeleteMapping("/depts")
public Result delete(Integer id){
    System.out.println("根据id删除部门, id=" + id);
    deptService.deleteById(id);
    return Result.success();
}
2) Service层

在 DeptService 中,增加 deleteById 方法,代码实现如下:

/**
 * 根据id删除部门
 */
void deleteById(Integer id);

 在 DeptServiceImpl 中,增加 deleteById 方法,代码实现如下:

public void deleteById(Integer id) {
    deptMapper.deleteById(id);
}
3) Mapper层

在 DeptMapper 中,增加 deleteById 方法,代码实现如下:

/**
 * 根据id删除部门
 */
@Delete("delete from dept where id = #{id}")
void deleteById(Integer id);

如果mapper接口方法形参只有一个普通类型的参数,#{…} 里面的属性名可以随便写,如:#{id}、#{value}。

对于 DML 语句来说,执行完毕也是有返回值的,返回值代表的是增删改操作,影响的记录数,所以可以将执行 DML 语句的方法返回值设置为 Integer。 但一般开发时不需要这个返回值,所以也可以设置为void。

代码编写完毕后,就可以启动服务进行测试了。

2 新增部门

2.1 需求

点击 "新增部门" 的按钮之后,弹出新增部门表单,填写部门名称后,点击确定,保存部门数据。

了解了需求后,再看看接口文档中,关于新增部门的接口的描述,然后根据接口文档进行服务端接口的开发 。

2.2 接口描述

2.2.1 基本信息

请求路径:/depts

请求方式:POST

接口描述:该接口用于添加部门数据

2.2.2 请求参数

参数格式:application/json

参数说明:

参数名类型是否必须备注
namestring必须部门名称

2.3 思路分析

Controller:接收客户端/depts 请求及{"name":"教研部"} 参数,调用 Service 层,再响应结果。

Service:补全基础属性,调用 Mapper 接口方法,处理业务逻辑。

Mapper:执行insert into dept (name,create_time,update_time) values (?,?,?) SQL 语句,与数据库交互完成数据插入 。

2.4 json参数接收

可以看到,在controller中,需要接收前端传递的请求参数。 那接下来,就先来看看在服务器端的Controller程序中,如何获取json格式的参数。

JSON格式的参数,通常会使用一个实体对象进行接收 。

规则:JSON数据的键名与方法形参对象的属性名相同,并需要使用@RequestBody注解标识

前端传递的请求参数格式为json,内容如下:{"name":"研发部"}。这里可以通过一个对象来接收,只需要保证对象中有name属性即可。

2.5 代码实现

1) Conteoller层

在DeptController中增加方法save,具体代码如下:

/**
 * 新增部门 - POST http://localhost:8080/depts   请求参数:{"name":"研发部"}
 */
@PostMapping("/depts")
public Result save(@RequestBody Dept dept){
    System.out.println("新增部门, dept=" + dept);
    deptService.save(dept);
    return Result.success();
}

2) Service层 

在DeptServiceImpl中增加save方法,完成添加部门操作,具体代码如下:

public void save(Dept dept) {
    //补全基础属性
    dept.setCreateTime(LocalDateTime.now());
    dept.setUpdateTime(LocalDateTime.now());
    //保存部门
    deptMapper.insert(dept);
}

3) Mapper层

/**
 * 保存部门
 */
@Insert("insert into dept(name,create_time,update_time) values(#{name},#{createTime},#{updateTime})")
void insert(Dept dept);

如果在mapper接口中需要传递多个参数,可以把多个参数封装到一个对象中。 在SQL语句中获取参数的时候,#{...} 里面写的是对象的属性名【注意是属性名,不是表的字段名】

代码编写完毕后,即可启动服务进行测试。

3 修改部门

对于任何业务的修改功能来说,一般都会分为两步进行:查询回显修改数据

3.1 查询回显

3.1.1 需求

当点击 "编辑" 时,需要根据ID查询部门数据用于页面回显展示。

3.1.2 接口描述

3.1.2.1 基本信息

请求路径:/depts/{id}

请求方式:GET

接口描述:该接口用于根据ID查询部门数据

3.1.2.2 请求参数

参数格式:路径参数

参数说明:

参数名类型是否必须备注
idnumber必须部门ID

请求参数样例:

/depts/1

/depts/3

3.1.2.3 响应数据

参数格式:application/json

参数说明:

参数名类型是否必须备注
codenumber必须响应码,1 代表成功,0 代表失败
msgstring非必须提示信息
dataobject非必须返回的数据
|- idnumber非必须id
|- namestring非必须部门名称
|- createTimestring非必须创建时间
|- updateTimestring非必须修改时间

3.1.3 思路分析

明确了根据ID查询部门的需求后,再梳理一下实现该功能时三层架构每一层的职责:

Controller:接收客户端路径参数/depts/1请求,调用 Service 层,再响应结果。

Service:调用 Mapper 接口方法,衔接 Controller 与 Mapper 。

Mapper:执行select * from dept where id = 1 语句,从数据库查询对应部门数据 。

3.1.4 路径参数接收

/depts/1,/depts/3这种在url中传递的参数,称之为路径参数。 如何接收这样的路径参数呢 ?

路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数。

如果路径参数名与controller方法形参名称一致,@PathVariable注解的value属性是可以省略的。

3.1.5 代码实现

1) Controller层

在 DeptController 中增加 getById方法,具体代码如下:

/**
 * 根据ID查询 - GET http://localhost:8080/depts/1
 */
@GetMapping("/depts/{id}")
public Result getById(@PathVariable Integer id){
    System.out.println("根据ID查询, id=" + id);
    Dept dept = deptService.getById(id);
    return Result.success(dept);
}
2) Service层

在 DeptService 中增加 getById方法,具体代码如下:

/**
 * 根据id查询部门
 */
Dept getById(Integer id);

在 DeptServiceImpl 中增加 getById方法,具体代码如下:

public Dept getById(Integer id) {
    return deptMapper.getById(id);
}
3) Mapper层

在 DeptMapper 中增加 getById 方法,具体代码如下:

/**
* 根据ID查询部门数据
*/
@Select("select id, name, create_time, update_time from dept where id = #{id}")
Dept getById(Integer id);

代码编写完毕后,就可以启动服务进行测试了。

3.2 修改数据

3.2.1 需求

查询回显回来后就可以对部门信息进行修改了,修改完毕后,点击确定,此时就需要根据ID修改部门的数据。

了解需求后,再看接口文档中关于修改部门的接口的描述,进行服务端接口的开发 。

3.2.2 接口描述

3.2.2.1 基本信息

请求路径:/depts

请求方式:PUT

接口描述:该接口用于修改部门数据

3.2.2.2 请求参数

参数格式:application/json

参数说明:

参数名类型是否必须备注
idnumber必须部门ID
namestring必须部门名称

3.2.3 思路分析

参照接口文档,梳理三层架构每一层的职责:

Controller :接收来自客户端的 JSON 格式请求参数{"id":1,"name":"教研部"},然后调用 Service 层的方法进行业务处理,最后将处理结果响应给客户端。

Service :承接 Controller 层的调用,首先补全基础属性,之后调用 Mapper 接口方法 。

Mapper :执行 update dept set name =?, update_time =? where id =? 语句,与数据库交互,完成部门信息的更新操作。

通过接口文档,可以看到前端传递的请求参数是json格式的请求参数,在Controller的方法中,可以通过@RequestBody 注解来接收,并将其封装到一个对象中。

3.2.4 代码实现

1) Controller层

在 DeptController 中增加 update 方法,具体代码如下:

/**
 * 修改部门 - PUT http://localhost:8080/depts  请求参数:{"id":1,"name":"研发部"}
 */
@PutMapping("/depts")
public Result update(@RequestBody Dept dept){
    System.out.println("修改部门, dept=" + dept);
    deptService.update(dept);
    return Result.success();
}
2) Service层

在 DeptService 中增加 update 方法。

/**
 * 修改部门
 */
void update(Dept dept);

在 DeptServiceImpl 中增加 update 方法。 由于是修改操作,每一次修改数据,都需要更新updateTime。所以,具体代码如下:

public void update(Dept dept) {
    //补全基础属性
    dept.setUpdateTime(LocalDateTime.now());
    //保存部门
    deptMapper.update(dept);
}
3) Mapper层

在 DeptMapper 中增加 update 方法,具体代码如下:

/**
 * 更新部门
 */
@Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}")
void update(Dept dept);

代码编写完毕之后,我们就可以启动服务,进行测试了。

3.2.5 @RequestMapping

到此,关于基本的部门的增删改查功能已经实现了。 会发现,在 DeptController 中所定义的方法,所有的请求路径,都是 /depts 开头的,只要操作的是部门数据,请求路径都是 /depts 开头。

此时可以把这个公共的路径 /depts 抽取到类上的,在各个方法上就可以省略了这个 /depts 路径。 代码如下:

package org.example.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.example.pojo.Dept;
import org.example.pojo.Result;
import org.example.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping("/depts")
@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    /**
     * 查询部门列表
     * @return
     */
    //@RequestMapping(value = "/depts",method = RequestMethod.GET) //method:指定请求方式
    @GetMapping
    public Result list() {
        System.out.println("查询全部部门数据");
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }

    /**
     * 删除部门
     */
    //方式一:基于HttpServletRequest对象获取参数
    /*@DeleteMapping
    public Result delete(HttpServletRequest request){
        String idStr = request.getParameter("id");
        int id = Integer.parseInt(idStr);
        System.out.println("删除部门:"+id);
        return Result.success();
    }*/
    //方式二:通过 @RequestParam 注解将请求参数绑定给方法形参
    /*@DeleteMapping
    public Result delete(@RequestParam (value = "id",required = false) Integer deptId ){
        System.out.println("删除部门:"+ deptId);
        return Result.success();
    }*/
    //方式三:如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam)
    @DeleteMapping
    public Result delete(Integer id ){
        System.out.println("删除部门:"+ id);
        deptService.deleteById(id);
        return Result.success();
    }

    /**
     * 新增部门
     */
    @PostMapping
    public Result add(@RequestBody Dept dept){
        System.out.println("新增部门:" + dept);
        deptService.add(dept);
        return Result.success();
    }

    /**
     * 根据ID查询部门
     */
    /*@GetMapping("/{id}")
    public Result getInfo(@PathVariable("id") Integer deptId){
        System.out.println("根据id查询部门:" + deptId);
        return Result.success();
    }*/
    @GetMapping("/{id}")
    public Result getInfo(@PathVariable Integer id){
        System.out.println("根据id查询部门:" + id);
        Dept dept = deptService.getById(id);
        return Result.success(dept);
    }

    /**
     * 修改部门信息
     */
    @PutMapping
    public Result update(@RequestBody Dept dept){
        System.out.println("修改部门:" + dept);
        deptService.update(dept);
        return Result.success();
    }

}

4 日志技术

4.1 概述

什么是日志?

日志就好比生活中的日记,可以随时随地记录你生活中的点点滴滴。

程序中的日志,是用来记录应用程序的运行信息、状态信息、错误信息的。

为什么要在程序中记录日志呢?

  • 便于追踪应用程序中的数据信息、程序的执行过程。

  • 便于对应用程序的性能进行优化。

  • 便于应用程序出现问题之后,排查问题,解决问题。

  • 便于监控系统的运行状态。

  • ... ...

之前编写程序时,也可以通过System.out.println(...) 来输出日志,为什么还要学习日志技术呢?

这是因为,如果通过System.out.println(...)记录日志,会存在以下几点问题:

  • 硬编码。所有的记录日志的代码,都是硬编码,没有办法做到灵活控制,要想不输出这个日志了,只能删除掉记录日志的代码。

  • 只能输出日志到控制台。

  • 不便于程序的扩展、维护。

所以,在现在的项目开发中,一般都会使用专业的日志框架来解决这些问题。

4.2 日志框架

  • JUL:这是JavaSE平台提供的官方日志框架,也被称为JUL。配置相对简单,但不够灵活,性能较差。

  • Log4j:一个流行的日志框架,提供了灵活的配置选项,支持多种输出目标。

  • Logback:基于Log4j升级而来,提供了更多的功能和配置选项,性能由于Log4j。

  • Slf4j(Simple Logging Facade for Java)简单日志门面,提供了一套日志操作的标准接口及抽象类,允许应用程序使用不同的底层日志框架。

4.3 Logback入门

1) 准备工作:引入logback依赖(springboot中无需引入,在springboot中已经传递了此依赖

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.4.11</version>
</dependency>

2) 引入配置文件 logback.xml ,放在 src/main/resources 目录下

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<!-- 控制台输出 -->
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%logger显示日志记录器的名称, %msg表示日志消息,%n表示换行符 -->
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
		</encoder>
	</appender>

	<!-- 系统文件输出 -->
	<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
			<!-- 日志文件输出的文件名, %i表示序号 -->
			<FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
			<!-- 最多保留的历史日志文件数量 -->
			<MaxHistory>30</MaxHistory>
			<!-- 最大文件大小,超过这个大小会触发滚动到新文件,默认为 10MB -->
			<maxFileSize>10MB</maxFileSize>
		</rollingPolicy>

		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 -->
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
		</encoder>
	</appender>

	<!-- 日志输出级别 -->
	<root level="info">
		<appender-ref ref="STDOUT" />
		<appender-ref ref="FILE" />
	</root>
</configuration>

3) 记录日志:定义日志记录对象Logger,记录日志

public class LogTest {
    
    //定义日志记录对象
    private static final Logger log = LoggerFactory.getLogger(LogTest.class);

    @Test
    public void testLog(){
        log.debug("开始计算...");
        int sum = 0;
        int[] nums = {1, 5, 3, 2, 1, 4, 5, 4, 6, 7, 4, 34, 2, 23};
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        log.info("计算结果为: "+sum);
        log.debug("结束计算...");
    }

}

运行单元测试,可以在控制台中看到输出的日志,如下所示:

可以看到在输出的日志信息中,不仅输出了日志的信息,还包括:日志的输出时间、线程名、具体在哪个类输出。

4.4 Logback配置文件

Logback日志框架的配置文件叫 logback.xml 。 该配置文件是对Logback日志框架输出的日志进行控制的,可以来配置输出的格式、位置及日志开关等。 常用的两种输出日志的位置:控制台、系统文件。

1) 如果需要输出日志到控制台。添加如下配置:

<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
    </encoder>
</appender>

2) 如果需要输出日志到文件。添加如下配置:

<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <!-- 日志文件输出的文件名, %i表示序号 -->
        <FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
        <!-- 最多保留的历史日志文件数量 -->
        <MaxHistory>30</MaxHistory>
        <!-- 最大文件大小,超过这个大小会触发滚动到新文件,默认为 10MB -->
        <maxFileSize>10MB</maxFileSize>
    </rollingPolicy>

    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
        <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 -->
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
    </encoder>
</appender>

3) 日志开关配置 (开启日志(ALL),取消日志(OFF))

<!-- 日志输出级别 -->
<root level="ALL">
    <!--输出到控制台-->
    <appender-ref ref="STDOUT" />
    <!--输出到文件-->
    <appender-ref ref="FILE" />
</root>

 4.5 Logback日志级别

日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下(优先级由低到高):

日志级别说明记录方式
trace追踪,记录程序运行轨迹 【使用很少】log.trace("...")
debug调试,记录程序调试过程中的信息,实际应用中一般将其视为最低级别 【使用较多】log.debug("...")
info记录一般信息,描述程序运行的关键事件,如:网络连接、io操作 【使用较多】log.info("...")
warn警告信息,记录潜在有害的情况 【使用较多】log.warn("...")
error错误信息 【使用较多】log.error("...")

可以在配置文件logback.xml中,灵活的控制输出那些类型的日志。(大于等于配置的日志级别的日志才会输出)

4.6 案例日志记录

package org.example.controller;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.log4j.Log4j;
import lombok.extern.slf4j.Slf4j;
import org.example.pojo.Dept;
import org.example.pojo.Result;
import org.example.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Slf4j
@RequestMapping("/depts")
@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    /**
     * 查询部门列表
     * @return
     */
    //@RequestMapping(value = "/depts",method = RequestMethod.GET) //method:指定请求方式
    @GetMapping
    public Result list() {
        //System.out.println("查询全部部门数据");
        log.info("查询全部部门数据");
        List<Dept> deptList = deptService.findAll();
        return Result.success(deptList);
    }

    /**
     * 删除部门
     */
    //方式一:基于HttpServletRequest对象获取参数
    /*@DeleteMapping
    public Result delete(HttpServletRequest request){
        String idStr = request.getParameter("id");
        int id = Integer.parseInt(idStr);
        System.out.println("删除部门:"+id);
        return Result.success();
    }*/
    //方式二:通过 @RequestParam 注解将请求参数绑定给方法形参
    /*@DeleteMapping
    public Result delete(@RequestParam (value = "id",required = false) Integer deptId ){
        System.out.println("删除部门:"+ deptId);
        return Result.success();
    }*/
    //方式三:如果请求参数名与形参变量名相同,直接定义方法形参即可接收。(省略@RequestParam)
    @DeleteMapping
    public Result delete(Integer id ){
        //System.out.println("删除部门:"+ id);
        log.info("删除部门:{}",  id);
        deptService.deleteById(id);
        return Result.success();
    }

    /**
     * 新增部门
     */
    @PostMapping
    public Result add(@RequestBody Dept dept){
        //System.out.println("新增部门:" + dept);
        log.info("新增部门:{}", dept);
        deptService.add(dept);
        return Result.success();
    }

    /**
     * 根据ID查询部门
     */
    /*@GetMapping("/{id}")
    public Result getInfo(@PathVariable("id") Integer deptId){
        System.out.println("根据id查询部门:" + deptId);
        return Result.success();
    }*/
    @GetMapping("/{id}")
    public Result getInfo(@PathVariable Integer id){
        //System.out.println("根据id查询部门:" + id);
        log.info("根据id查询部门:{}", id);
        Dept dept = deptService.getById(id);
        return Result.success(dept);
    }

    /**
     * 修改部门信息
     */
    @PutMapping
    public Result update(@RequestBody Dept dept){
        //System.out.println("修改部门:" + dept);
        log.info("修改部门:{}", dept);
        deptService.update(dept);
        return Result.success();
    }

}

注:lombok中提供的@Slf4j注解,可以简化定义日志记录器这步操作。添加了该注解,就相当于在类中定义了日志记录器,即如下代码:

private static Logger log = LoggerFactory. getLogger(Xxx. class);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值