SpringBoot 之设计API接口实现统一格式返回

SpringBoot RESTful API返回统一数据格式还不懂? 关于 Spring 的全局处理,我有两方面要说: 统一数据返回格式 统一异常处理为了将两个问题说明清楚,将分两个章节分别说明,本章主要说第一点 有童鞋说,我们项目都做了这种处理,就是在每个 API 都单独工具类将返回值进行封装,但这种不够优雅;我想写最少的代码完成这件事,也许有童鞋说,加几个注解就解决问题了,说的没错,但这篇文章主要是为了说明为什么加了几个注解就解决问题了,目的是希... 阅读详情

前言

在移动互联网,分布式、微服务盛行的今天,现在项目绝大部分都采用的微服务框架,前后端分离方式。
前后端的工作职责越来越明确。

一般系统的大致整体架构图如下:
在这里插入图片描述

接口交互

前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。

返回格式

后端返回给前端我们一般用JSON体方式,定义如下:

{
  #返回状态码
  code:integer,
  #返回信息描述
  message:string,
  #返回值
  data:object
}

CODE状态码

code返回状态码可以参考HTTP请求返回的状态码来设计,这样的好处就把错误类型归类到某个区间内。

#1000~1999 区间表示参数错误
#2000~2999 区间表示用户错误
#3000~3999 区间表示接口异常

这样前端开发人员在得到返回值后,根据状态码就可以知道,大概什么错误,再根据message相关的信息描述,可以快速定位。

Message信息提示

错误信息提示。一般和code状态码一起设计,如:

public enum ResultCode {
	/*成功状态码*/
	SUCCESS(1, "成功"),
	SYSTEM_ERROR(400, "系统繁忙,请稍后重试"),
	/*参数错误: 1001-1999 */
	PARAM_IS_INVALID(1001, "参数无效"),
	PARAM_IS_BLANK(1002, "参数为空"),
	PARAM_TYPE_BIND_ERROR(1003, "参数类型错误"),
	PARAM_NOT_COMPLETE(1004, "参数缺失"),
	/*用户错误: 2001-2999*/
	USER_NOT_LOGGED_IN(2001, "用户未登录,访问的路径需要验证,请登录"),
	USER_LOGIN_ERROR(2002, "账号不存在或密码错误"),
	USER_ACCOUNT_FORBIDDEN(2003, "账号已被禁用"),
	USER_NOT_EXIST(2004, "用户不存在"),
	USER_HAS_EXISTED(2005, "用户已存在");
	
	private Integer code;
	private String message;
	
	ResultCode(Integer code, String message) {
		this.code = code;
		this.message = message;
	}
	
	public Integer code() {
		return this.code;
	}
	
	public String message(){
		return this.message;
	}
}

Data数据体

返回数据体,JSON格式,返回体类Result。

// 成功结果实体
@Data
public class Result implements Serializable {

	private Integer code;
	private String message;
	private Object data;

	public void setResultCode(ResultCode resultCode) {
		this.code = resultCode.code();
	}

	public static Result success() {
		Result result = new Result();
		result.setResultCode(ResultCode.SUCCESS);
		return result;
	}

	public static Result success(Object data) {
		Result result = new Result();
		result.setResultCode(ResultCode.SUCCESS);
		result.setData(data);
		return result;
	}

	public static Result failure(Integer code, String message) {
		Result result = new Result();
		result.setCode(code);
		result.setMessage(message);
		return result;
	}
}

// 错误结果实体
@Data
public class ErrorResult {
	private Integer code;
	private String message;
	
	public ErrorResult(Integer code, String message){
		this.code = code;
		this.message = message;
	}
}

控制层Controller

后端会在controller层处理业务请求,并返回给前端。

@RestController
@RequestMapping("/demo")
public class DemoController {
	@GetMapping("/ok")
	public Result ok() {
		return Result.success("ok...");
	}
}

返回体统一处理

@ExceptionHandler

接受请求处理方法抛出的异常。

@RestController
@RequestMapping("/demo")
public class DemoController {

	@GetMapping("/ok")
	public String ok2() {
		int i = 10 / 0;
		return "ok..";
	}

	@ExceptionHandler(RuntimeException.class)
	public String runtimeExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
		return e.getMessage();
	}

}

在这里插入图片描述

@ModelAttribute

往请求的Model里加数据。

@RestController
@RequestMapping("/demo")
public class DemoController {

	@GetMapping("/ok")
	public String ok(ModelMap modelMap) {
		return modelMap.get("name").toString();
	}

	@GetMapping("/ok2")
	public String ok2(@ModelAttribute("name") String name) {
		return name;
	}

	@ModelAttribute
	public void model(Model model) {
		model.addAttribute("name", "James");
	}
}

@RestControllerAdvice

将@ExceptionHandler和@ModelAttribute注解应用到所有的控制器上。是@ControllerAdvice和@ResponseBody注解的结合体。
因为@RestControllerAdvice被元注解@Component标记,所以它也是可以被组件扫描扫到并放入Spring容器的。

如果只想对一部分控制器添加通知,比如某个包下的控制器,可以这样写:
在这里插入图片描述
如果只想对某几个控制器添加通知,可以这样写:
在这里插入图片描述

标记注解类

用来标记方法的返回值,是否需要包装。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface ResponseResult {
}

请求拦截器

拦截请求,是否此请求返回的值需要包装,其实就是运行的时候,解析@ResponseResult注解。

import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
/**
 * 响应处理拦截器
 */
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {

	public static final String RESPONSE_RESULT_ANN = "RESPONSE_RESULT_ANN";

	/**
	 * 解析控制器类或方法上是否有@ResponseResult注解,如果有表示返回值需要包装
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		if (handler instanceof HandlerMethod) {
			final HandlerMethod handlerMethod = (HandlerMethod) handler;
			final Class<?> clazz = handlerMethod.getBeanType();
			final Method method = handlerMethod.getMethod();
			if (clazz.isAnnotationPresent(ResponseResult.class)) {
				// 判断是否在类对象上加了注解
				request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
			} else if (method.isAnnotationPresent(ResponseResult.class)) {
				// 判断是否在方法上加了注解
				request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
			}
		}
		return true;
	}
}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
	@Autowired
	private ResponseResultInterceptor responseResultInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		InterceptorRegistration registration = registry.addInterceptor(responseResultInterceptor);
		// 拦截所有请求
		registration.addPathPatterns("/**");
		// 添加不拦截路径
		registration.excludePathPatterns("/login", "/error", "/logout", "/login.html");
	}
}

此代码核心思想,就是获取此请求,是否需要返回值包装,设置一个属性标记。

重写返回体

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {

	public static final String RESPONSE_RESULT_ANN = "RESPONSE_RESULT_ANN";

	/**
	 * 捕获 CustomException异常
	 */
	@ExceptionHandler(CustomException.class)
	public ErrorResult runtimeExceptionHandler(HttpServletRequest request, final Exception e,
			HttpServletResponse response) {
		CustomException customException = (CustomException) e;
		return new ErrorResult(customException.getCode(), customException.getMessage());
	}

	/**
	 * 捕获 Exception异常
	 */
	@ExceptionHandler(Exception.class)
	public ErrorResult exceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
		response.setStatus(HttpStatus.BAD_REQUEST.value());
		return new ErrorResult(HttpStatus.BAD_REQUEST.value(), e.getMessage());
	}

	/**
	 * 获取请求中是否有包装注解标记,没有直接返回,如果有则需要重写返回体
	 */
	@Override
	public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
		ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = sra.getRequest();
		// 获取请求是否有包装标记
		ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
		return responseResultAnn == null ? false : true;
	}

	/**
	 * 重写返回体
	 */
	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
			ServerHttpResponse response) {
		log.info("进入返回体处理");
		if (body instanceof ErrorResult) {
			ErrorResult errorResult = (ErrorResult) body;
			return Result.failure(errorResult.getCode(), errorResult.getMessage());
		}
		return Result.success(body);
	}

}

上面代码首先做了全局的异常处理,然后判断是否需要返回值包装,如果需要就直接包装。

重写Controller

最后在控制器类上或者方法体上加上@ResponseResult注解,就可以进行返回值统一处理了。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class DemoController {
	@ResponseResult
	@GetMapping("/ok2")
	public String ok2() {
		try {
			int i = 10 / 0;
		} catch (Exception e) {
			throw new CustomException(ResultCode.SYSTEM_ERROR.code(), ResultCode.SYSTEM_ERROR.message());
		}
		return "ok..";
	}
}

参考:
Java项目构建基础:统一结果,统一异常,统一日志

SpringBoot实现接口:统一返回值、全局异常处理、Swagger接口文档 在 Spring Boot 应用中实现统一返回值和全局异常处理可以带来多方面的好处,这些好处不仅提升了代码的可读性和可维护性,还增强了应用的健壮性和用户体验。通过定义统一的返回值格式,可以确保整个应用中的API响应结构保持一致。统一的异常处理机制使得错误信息的返回方式也保持一致,便于前端开发者理解和处理。 阅读详情

相关推荐

Java接口统一样式返回模板的实现

主要介绍了Java接口统一样式返回模板的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

如何设计 API 接口,实现统一格式返回?

统一在一个文件中定义错误码;错误码长度为 5 位;第 1 位表示错误是哪种级别?例如:1 为系统级错误,2 为业务模块错误,可标记 9 种错误级别。第 2 位和第 3 位表示错误是哪个模块?例如:01 为用户模块,02 为订单模块,可标记 99 个模块。第 4 位和第 5 为表示具体是什么错误?例如:01 为手机号不合法,02 为验证码输入错误,可标记 99 个错误。以上代码供大家参考,还有优化的空间,欢迎大家使用并提出宝贵意见。

APItesterCris的博客 810

基于CPCC_S_TASK_LIST_MAINTAIN的工艺路线组件批量分配实战与避坑指南

本文详细解析了SAP PP模块中基于CPCC_S_TASK_LIST_MAINTAIN函数实现工艺路线组件批量分配的实战技巧与避坑指南。针对批量分配的核心挑战,提供了参数配置、BAPI协同使用、Excel导入等解决方案,并分享了大容量数据处理优化经验,帮助用户高效完成工艺路线组件分配任务。

weixin_33563728的博客 306

SpringBoot之统一返回格式与统一异常处理

文章目录导入Jar包配置统一结果返回配置全局异常处理效果测试 在任何接口返回数据时,正确的返回格式: code:状态码 data:数据 message:接口响应信息,一般接口需要的就是这三个数据。 { "code": 200, "data": "success", "message": "成功" } 但是如果在每一个接口都这样写一个Map返回,或者一个工具类返回,但是这样代码的重复量太复杂。 统一返回的好处:接口返回的东西直接返回,接口的中不用管状态码和响应信息 导入Jar包 &l

不要用过去的错误约束现在的自己。 2668

保姆级教程:用Zemax 18.9复现单模光纤耦合仿真(附康宁SMF-28e参数)

本文提供了一份详细的Zemax 18.9单模光纤耦合仿真教程,以康宁SMF-28e为例,涵盖从软件设置、材料参数输入到系统构建和优化的全流程。通过光束耦合分析和物理光学传播(POP)技术,帮助用户准确模拟光纤耦合效率,并解决常见问题,适用于光学设计初学者和专业人士。

weixin_27330481的博客 313

Eureka自我保护机制

Eureka自我保护机制 默认情况下,当eureka server在一定时间内没有收到实例的心跳,便会把该实例从注册表中删除(默认是90秒),但是,如果短时间内丢失大量的实例心跳,便会触发eureka server的自我保护机制,比如在开发测试时,需要频繁地重启微服务实例,但是我们很少会把eureka server一起重启(因为在开发过程中不会修改eureka注册中心),当一分钟内收到的心跳数大量减少时,会触发该保护机制。可以在eureka管理界面看到Renews threshold和Renews(last

Soul_Ming的博客 542

SpringBoot实现RESTful API返回统一数据格式

关于 Spring 的全局处理,有两方面要说: 统一数据返回格式 统一异常处理 通用返回值类定义: public class GlobalResponse<T> implements POJO { /** * */ private static final long serialVersionUID = 1L; @ApiModelPro...

Crystalqy的博客 3万+

HIL(硬件在环)技术汇总梳理

HIL(Hardware-in-the-Loop)测试领域的知名公司有dSPACE、NI、Vector和speedgoat等,本文是针对这几家HIL技术的对比分析。

Mr.Cssust的博客 3686

API统一返回数据格式

如今的项目,基本上都是前后端分离,而且前端的生态也已经很强大。后端基本上只要保证数据响应就可以了。 那么,接口交互就成了前后端对接最重要也是最耗时的工作。而提高交互效率的最好的方法就是统一数据格式,后端把数据丢到一个统一的格式中去,前端解析数据也从固定的格式中去取。这样,前端就省去复杂的无关数据解析,能直接拿到想要的数据。后端也只需要把数据丢到固定的格式中去,不用每次都要包装一下。 格式统一 达到...

meiyouya优快云的博客 2646

Gromacs空蛋白模拟实战:从PDB文件到动力学轨迹的完整流程解析

本文详细解析了使用Gromacs进行空蛋白分子动力学模拟的完整实战流程。从PDB文件处理、力场选择与拓扑构建,到模拟盒子创建、溶剂化与电荷中和,再到能量最小化、NVT/NPT平衡及最终的生产模拟与基础分析,提供了每一步的命令详解和关键注意事项,旨在帮助计算生物学研究者系统掌握这一核心研究工具。

weixin_28454475的博客 275

基于 STM32 与压电薄膜的婴儿睡眠呼吸暂停监测床垫设计

本设计以 STM32F103C8T6 为主控,结合 PVDF 压电薄膜传感器,通过模拟信号调理与数字峰值检测算法,实现了对婴儿睡眠呼吸状态的实时监测。系统结构简单、成本低廉、无接触无束缚,当检测到 20 秒以上无呼吸信号时自动触发声光报警,可有效降低婴儿睡眠呼吸暂停带来的安全风险。后续可扩展蓝牙 / WiFi 模块,将报警信息推送至家长手机端,实现远程监护。

weixin_42300449的博客 149

SpringBoot统一接口返回和全局异常

写在最前:后端接口大致分为四个部分组成:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response)。如何构建,这是我们应该深入思考的问题,公司不同,规范不同,要求不同。不管公司有没有要求,我们对自己必须要有要求。 今天的这篇文章我们主要介绍,怎么统一处理下接口的返回格式问题。 在写后端接口,思考下药做些什么:请求,逻辑处理,返回。 这似乎已经可以满足对用户管理操作的需求,但是对于一个查询用户方法来讲代码量还是可以接受的,但是对于一个简单的更

拾一二的博客 8277

基于PUMA实现DOA估计-附Matlab代码

具体而言,PUMA算法将接收的信号分别在时域和频域上进行处理,并结合平稳合成(SS)和平稳子空间(SSP)方法,以提高估计精度。该算法将传统的基于协方差矩阵的最大似然(ML)和最小二乘(LS)方法扩展到了非均匀线性阵列(ULA)上,并通过对信号分离和DOA估计的优化,达到了高精度和高分辨率的效果。方位角(DOA)估计是信号处理中的一个重要问题,它的目的是确定从多个传感器接收到的信号的方向。本文将介绍如何使用基于PUMA的方位角估计算法来实现DOA估计,并附有相应的Matlab代码实现。

Byte_O_O的博客 496

Springboot如何设计出优雅的后端(API)接口(一)

项目背景 现在绝大部分项目都是采用前后端分离的模式,对于前端来说,后端如果能有一个规范的、优雅的设计的api模式,那么前端的开发将会事倍功半,同时对于后端来说,统一的格式也有利于后期的维护和扩展(其实主要是甩手的时候,不至于下一个人看不懂=。=)。 一个后端的接口分为四个部分:接口地址(url)、接口请求方式(get、post等)、请求数据(request)、响应数据(response)。下面我们就从这四个部分分析需要的注意的点。 后续我会把项目上传到github上,感兴趣的同学可以帮忙点个爱心。 需要思考

我是你妹她哥的博客 5043

如何解决群晖Docker注册表查询失败/无法拉取镜像等问题

如何解决群晖Docker注册表查询失败/无法拉取镜像等问题

XiaoqiangClub的博客 1万+

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

API接口的RESTful设计

前言:在移动互联网、分布式和微服务盛行的今天,现在好多大点的项目都会采用的微服务框架,前后端分离方式。前后端的工作职责越来越明确,现在的前端都称之为大前端,技术栈以及生态圈都已经非常成熟,可以进行移动端跨平台开发,也可以通过node.js写服务端的Controller。 一般系统的大致整体架构图如下: 需要说明的是,这个架构也太简单了吧,什么网关、Redis缓存、MQ消息中间件都没有。因为这篇主要介绍的是API接口,所以我们聚焦点,其他的模块小伙伴们自行去补充。 1、前后端接口交互 前端和后端进

何哥的博客 3789

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

使用统一返回结果类优化API设计

在开发Web应用的过程中,API接口的设计是重要的一环。这些API接口返回的数据格式必须清晰且一致,方便客户端进行处理。统一返回结果类就是这样一种工具,它规定了所有API接口返回数据的通用格式。code:响应状态码。一般来说,我们会规定1代表操作成功,0代表操作失败。msg:响应的消息,用于简单描述本次请求的结果或错误信息。data:返回的数据。这个字段通常在请求成功,并且需要返回额外数据时使用。

weixin_53027065的博客 555

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Spring boot项目设定返回数据为JSON固定格式

1.配置处理json的配置类 package com.test.config; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.s

HLoach的博客 6365

Springboot 套餐接口

【代码】Springboot 套餐接口。

2401_87471892的博客 31

尚医通(三)医院设置模块后端 | swagger | 统一日志 | 统一返回结果

尚医通(三)医院设置模块后端 | swagger | 统一日志 | 统一返回结果

Knight 1576

面试官:说说你对 RESTful 的理解?

REST涉及一些概念性的东西可能比较多,在实战RESTful API之前,要对REST相关的知识有个系统的认知。RESTful风格的API 固然很好很规范,但大多数互联网公司并没有按照或者完全按照其规则来设计,因为REST是一种风格,而不是一种约束或规则,过于理想的RESTful API 会付出太多的成本。比如RESTful API也有一些缺点。

cxytony的博客 162

Spring Boot 无侵入式 实现API接口统一JSON格式返回

无侵入式 统一返回JSON格式 其实本没有没打算写这篇博客的,但还是要写一下写这篇博客的起因是因为,现在呆着的这家公司居然没有统一的API返回格式????,询问主管他居然告诉我用HTTP状态码就够用了(fxxk),天哪HTTP状态码真的够用吗?在仔细的阅读了项目源码后发现,在API请求的是居然没有业务异常(黑人问好)。好吧 居然入坑了只能遵照项目风格了,懒得吐槽了。 因为项目已经开发了半年多了, 要是全...

小魏的奇妙世界 1万+

SpringBoot之统一数据返回格式

详解SpringBoot的统一数据返回格式,特别是结合源码讲解了返回类型为String时的异常问题。

. 1万+

SpringBoot 使用 beforeBodyWrite 实现统一的接口返回类型

通常我们在 Spring Boot 的项目中,会使用一个类来作为统一的接口返回,比如这样: import lombok.Data; @Data public class Result<T> { private int code; private String message; private T data; public static <T> Result<T> success(T t) { Result<T&gt

qq_37170583的博客 1万+

Spring Boot 系列:RestFul API 接口实现统一格式返回

文章目录背景一、统一格式设计1.1 返回格式1.2 状态码1.3 错误信息1.4 返回对象1.5 成功标示二、实现方案2.1 错误码枚举2.2 统一结果集封装三、测试及总结3.1 简单的测试3.2 其他3.3 技术交流 背景 在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,...

风尘博客 5775

SpringBoot使用validation实现接口参数统一校验、统一返回值、自定义注解

SpringBoot接口参数统一校验、统一返回值、自定义注解看这一篇就够了

weixin_44506280的博客 2495
上一篇: Java开发 之Spring中内置工具类
下一篇: Redis 之Jedis客户端
yyangqqian
博客等级 码龄15年 406粉丝 406原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值