《Spring in Action》第2章-创建一个Web应用

《Spring In Action》随书源码解析与应用 在本章节中,我们详细探讨了 Spring 框架中的核心概念和组件。我们从容器的生命周期、事件驱动模型、Bean 的创建和管理,深入到 Spring 应用的配置与管理,覆盖了基于 XML、注解和 Java 配置的方式。这将为我们深入理解 Spring 提供坚实的基础。 阅读详情

开发Web应用

建立Domain

Domain:一个应用的Domain就是这个应用处理的主题领域,Domain类就是对这个主题领域的抽象。在TacoCloud应用中,我们希望提供一个用户自己设计Taco的功能,那么Taco的成分类就属于该应用的Domain类。

import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data
@RequiredArgsConstructor
public class Ingredient {
    private final String id;
    private final String name;
    private final Type type;

    public static enum Type{
        WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
    }
}

上面定义了一个简单的Ingredient类,使用了@Data注解,这个注解由Lombok提供,我们需要在pom中添加lombok的依赖,并在IED中添加lombok插件。使用lombok,我们就不必再为普通的pojo手动的添加getter和setter方法以及一些必要的构造器了。在程序运行时,Lombok会自动的为我们加上这些方法。Lombok文档

创建一个Controller

@Controller
@Slf4j
@RequestMapping("/design")
 public String showDesignForm(Model model){
	    List<Ingredient> ingredients = Arrays.asList(
	            new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
	            new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
	            new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
	            new Ingredient("CARN", "Carnitas", Type.PROTEIN),
	            new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
	            new Ingredient("LETC", "Lettuce", Type.VEGGIES),
	            new Ingredient("CHED", "Cheddar", Type.CHEESE),
	            new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
	            new Ingredient("SLSA", "Salsa", Type.SAUCE),
	            new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
	    );
	    Type[] types = Ingredient.Type.values();
	    for(Type type : types){
	        model.addAttribute(type.toString().toLowerCase(),filterByType(ingredients,type));
	    }
	    model.addAttribute("design",new Taco());
	    return "design";
	}

	List<Ingredient> filterByType(List<Ingredient> ingredients, Type type){
	    List<Ingredient> result = new ArrayList<>();
	    for (int i = 0; i < ingredients.size(); i++) {
	        Ingredient ingredient = ingredients.get(i);
	        if (type == ingredient.getType()) {
	            result.add(ingredient);
	        }
	    }
	    return result;
	}

上面的代码中,@Slf4j注解是由Lombok提供的,在运行时自动生成一个SLF4J。@RequestMapping("/design")注解在class层,这个类中的方法将会处理以/design开头的请求。Model用于在Controller和view之间传递数据。放入Model的数据会被复制到Servlet响应参数中,视图使用的是Servlet响应参数的数据。

设计视图

<!DOCTYPE html>
<!-- 将表单数据存入对象中 -->
<form method="post" th:object="${design}">
    <div class="grid">
        <div class="ingredient-group" id="wraps">
            <h3>Design your wrap:</h3>
			<!-- 遍历wrap集合 -->
            <div th:each="ingredient : ${wrap}">
                <input name="ingredients" type="checkbox" th:value="${ingredient.id}">
                <span th:text="${ingredient.name}">INGREDIENT</span>
                <br>
            </div>
        </div>
    </div>
    <div>
        <h3>Name your taco creation:</h3>
        <input type="text" th:field="*{name}">
        <br>
        <button>Submit your taco</button>
    </div>
</form>
</body>
</html>

视图使用Thymeleaf模板。我们在html文件中加入th命名空间:<html xmlns:th="http://www.thymeleaf.org">,然后开始使用Thymeleaf模板。

Thymeleaf的标准表达语法

  • 基本语法
  1. 变量表达式: ${...}:

    变量表达式实际上是OGNL,在Spring MVC 应用中,OGNL被替换为SpringEL(语法与OGNL十分相似)。

  2. 选择表达式: *{...}:

    选择表达式与th:object=${objectName}一起使用,在上面的html中,<input type="text" th:field="*{name}"> 等价于<input type="text" th:field="${design.name}">。

  3. 消息表达式: #{...}:

    使用消息表达式让国际化变得简单,我们可以引用外部的.properties文件中定义的值来替换html页面内容。在Spring中使用消息表达式,需要注册org.springframework.context.support.ResourceBundleMessageSourcebean:

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("message");//这里设置properties文件名的前缀
        return messageSource;
    }
    

    我们在classpath下创建名为message_en.properties,message_cn_ZH.properties,message.properties的资源文件,并在其中定义消息。

    在SpringBoot中,我们只需要直接创建前缀为messages的properties文件即可(spring.messages.basename值默认为messages),也可以在application.properties中配置消息资源文件的前缀:

    spring.messages.basename=application
    
  4. URL表达式: @{...}

    使用URL表达式来表达一个URL,那么在URL中我们就可以嵌入其它的表达式

  5. 片段表达式: ~{...}:

    片段表达式让我们加强对HTML块的重用,我们在templates目录下创建一个fragment.html,并在其中定义一个名为header的fragment:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    			<meta charset="UTF-8">
    			<title>Title</title>
    </head>
    <body>
    <div th:fragment="header">
    			<h1 th:text="#{header.msg}"></h1>
    			<img th:src="@{/images/TacosCloud.jpg}">
    </div>
    
    </body>
    </html>
    

    然后我们以前页面中的标题和图片就可以使用片段表达式来替换:

    <div th:insert="~{fragment::header}"></div>
    <!-- 第一个值为html文件名,第二个值为文件中fragment的名字 -->
    

(未完待续。。。)

处理表单提交

前面的html表单中,method属性为post,没有设置action属性,那么我们提交表单时,浏览器将会收集表单数据并将其提交给一个HTTP POST 请求,这个请求的路径是/design。我们需要在Controller中添加一个处理Post请求的方法。

@PostMapping
public String processDesign(Taco taco){
    //do something with taco

    return "redirect:/orders/current";
}

processDesign方法也返回一个字符串,但是这个字符串有一个前缀redirect:,这表明这是一个重定向视图。当用户设计了自己的Taco并提交后,浏览器会重定向到/orders/current。

表单输入验证

Spring支持Java’s Bean Validation API(JSR-303) ,这个API使得定义校验规则变得简单,我们不需要通过编码来实现校验。

在Spring Boot应用中,不需要添加任何的依赖包。因为JSR-303 API和Hibernate实现的校验API在应用启动时被自动加入到项目中。

在Spring MVC中使用这个API,我们需要:

  • 在需要被校验的类中声明校验规则
  • 指定控制器方法来执行验证
  • 修改视图的表单来展示验证的错误结果

JSR-303 API提供了几个注解,用来声明校验规则,Hibernate实现的API提供了更多的接口。

添加校验规则

import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Data
public class Taco {
	   //不为空,长度不少于5个字符
    @NotNull
    @Size(min=5,message = "name must be at least  5 characters long")
    private String name;
				//至少有一个元素
    @Size(min=1,message="you must choose at least 1 ingredient")
    private List<String> ingredients;
}

import lombok.Data;
import org.hibernate.validator.constraints.CreditCardNumber;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@Data
public class Order {
		  //不能为空,也不能只含空格
    @NotBlank(message = "Name is required")
    private String name;
    @NotBlank(message = "Street is required")
    private String street;
    @NotBlank(message = "City is required")
    private String city;
    @NotBlank(message = "State is required")
    private String state;
    @NotBlank(message = "Zip is required")
    private String zip;
	//这是一个有Hibernate提供的注解,将检验ccNumber是否是合法的信用卡号码
    @CreditCardNumber(message = "Not a valid credit card number")
    private String ccNumber;
	//提供一个正则表达式,校验值是否与表达式匹配
    @Pattern(regexp = "(0[1-9]|1[0-2])[\\\\/]([1-9][0-9])",message = "Must be formatted MM/YY")
    private String ccExpiration;
	//值必须是数字
    @Digits(integer = 3,fraction = 0,message = "Invalid CVV")
    private String ccCVV;
}

参考文档:jakarta,Hibernate

表单绑定时完成校验

除了对domain class 添加注解集验证规则外,Controller中也需要一点修改:

@PostMapping
public String processDesing(@Valid Taco design, Errors errors){
    if (errors.hasErrors()){
        return "/design";
    }
    return "redirect:/orders/current";
}

@PostMapping
public String processOrder(@Valid Order order, Errors errors){
    if (errors.hasErrors()){
        return "orderForm";
    }
    log.info("Order submit:" + order);
    return "redirect:/";
}

在控制器方法中,我们对表单数据绑定对象添加了@Valid注解,并新增了一个org.springframework.validation.Errors实例的新入参。@Valid注解告诉Spring MVC在taco、order对象绑定了表单提交的数据之后,控制器方法被调用之前对表单数据进行验证。errors可得到校验结果。

展示校验结果

在Thymeleaf模板中,使用field和th:errors可以轻松访问Error对象。

<form method="post" th:object="${taco}">
	<label for="name">Name: </label>
	<input type="text" th:field="*{name}"/>
	<span class="validationError" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Num Error</span>
	<br/>
</form>

${#fields}就是对控制器中errors对象的引用。 th:if控制是否显示这个span,th:errors用于指定使用哪个字段上的message来展示错误信息。

值得注意的是:在页面上th:object对应的对象变量名应该是类名,或者是类名的驼峰命名,这样验证结果才会在页面显示。如上面processDesing方法我们是这样注解的:@Valid Taco design,spring运行时的入参名称将不再是design,而是与类名相关的一个名称。所以在页面上绑定对象时,应该使用如"taco"这样的对象变量名称。

使用视图控制器

到目前为止,我们都是在Controller中定义一个方法来实现对HTTP GET请求的处理。我们可以使用视图控制器来达到同样的效果。

@Configuration
public class Config implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");//请求路径为/,跳转页面的视图逻辑名为home
    }
}

WebMvcConfigurer虽然是一个接口,但是它所定义的方法都有默认的实现,我们只需要覆盖addViewController方法即可。该方法提供了一个org.springframework.web.servlet.config.annotation.ViewControllerRegistry对象,我们可以通过它注册多个视图控制器。

选择视图模板

视图模板
我们可以选择上面的视图模板,只需要加入其依赖即可。当应用启动之后,Spring Boot自动配置将探测到我们所选择的模板,并自动的配置模板bean。我们只需要在src/main/resources/templates目录下编写模板即可。

Jsp不需要任何依赖,因为Web容器(默认为Tomcat)自身已经实现了对JSP的支持。但是如果我们的项目被打包成jar包,那么Jsp将不适合,因为容器总是在/WEB-INF目录下寻找jsp页面,然而spring boot项目并没有这个目录。所以Jsp模板只在当我们将项目打包成war包,并部署在传统web容器上时才能使用。

模板缓存设置


模板缓存默认开启,在application.properties文件中,加上spring.thymeleaf.cache=false,将关闭模板缓存。

model.addAttribute()使用.pdf 后端利用model.addAttribute()传递数据,前端利用th标签接收,用于前后端传递数据,传递的参数为list时,前端利用表格或其他接收,为字符串时,同文档。 立即下载

相关推荐

解决PubMed卡在“正在检查浏览器”页面的完整排查指南

DNS解析是将域名转换为IP地址的基础网络服务,其稳定性和准确性直接影响网站访问。当DNS服务器响应缓慢或返回异常结果时,可能导致客户端与服务器建立连接时出现偏差,从而触发网站的安全验证机制,例如Cloudflare等安全服务提供的浏览器环境检查。这一机制旨在防御恶意流量,但偶尔也会误拦截正常访问。在生物医学信息检索领域,PubMed作为核心文献数据库,其稳定访问对科研工作流至关重要。针对因DNS或本地缓存问题导致的‘正在检查浏览器’页面卡顿,通过刷新DNS缓存、更换为Google Public DNS或C

weixin_29597551的博客 3642

Java开发经验

遍历枚举 public class Ingredient { @Id private final String id; private final String name; private final Type type; public static enum Type { WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE } } Type[] types = Ingredient.Type.values(); fo

flyingdream123的专栏 425

易灵思Ti60F100 FPGA的HyperRAM实战:从硬件连接到Native接口时序调试

本文详细介绍了易灵思Ti60F100 FPGA内置HyperRAM控制器的实战应用。文章从硬件设计要点(如400MHz+信号完整性、等长匹配)切入,重点解析了如何通过Interface Designer配置高效的Native接口,并分享了其时序详解与调试技巧,为开发者提供了一份从硬件连接到软件调试的深度指南。

weixin_29168153的博客 456

Spring In Action —— chapter02 —— 开发 Web 应用程序 —— 读《Spring 实战》笔记以及原文摘录转载

使用 Spring 构建的应用程序将执行各种操作,包括处理数据、从数据库中读取信息以及与其他应用程序进行交互。但是应用程序用户得到的第一印象来自于用户界面。在许多应用程序中,UI 界面是在浏览器中显示的 web 应用程序。在本章中,将开发 Taco Cloud 应用程序的第一个主要功能 —— 设计自定义 Taco 的能力。在此过程中,将深入研究 Spring MVC,并了解如何显示模型数据和处理表单输入。在 Spring web 应用程序中,获取和处理数据是控制器的工作。视图的工作是将数据渲染成 HTML

Alascanfu的博客 308

Plant Simulation 快速建模与布局(3)——自动生成的对象 代码“安全”删除管理

《PlantSimulation快速布局建模课程》教授利用SimTalk语言实现高效建模的技术要点,包括批量对象创建、阵列布局、属性管理及自动连接功能。重点讲解对象安全删除机制,通过existsObject检查对象存在性后使用deleteObject方法删除,避免程序报错。课程提供完整代码示例,展示如何通过循环结构实现矩阵布局对象的创建与安全删除,确保代码健壮性,显著提升建模效率。

qq_31964455的博客 119

【WRF湿度调整策略】WRF/ERA5湿度数据对比

【WRF湿度调整策略】WRF/ERA5湿度数据对比

WW、forever的博客 120

Java 编程问题(六)

原文:Java Coding Problems 协议:CC BY-NC-SA 4.0 八、函数式编程-基础和设计模式 原文:Java Coding Problems 协议:CC BY-NC-SA 4.0 贡献者:飞龙 本文来自【ApacheCN Java 译文集】,自豪地采用谷歌翻译。 本章包括 11 个涉及 Java 函数式编程的问题。我们将从一个问题开始,这个问题旨在提供从 0 到函数式接口的完整过程。然后,我们将继续研究 GoF 中的一套设计模式,我们将用 Java 函数风格来解释这些模式。

龙哥盟 753

车载软件架构 —— AUTOSAR Vector SIP包(三)

### 上一篇文章中介绍了AUTOSAR基础软件供应商Vector公司的开发工具链,本文将继续介绍Vector公司的AUTOSAR产品。 ### -> 1、Vector SIP包中的StartApplication ### -> 2、StartApplication如何开始使用

Soly_kun的博客 831

解决spring实战中TemplateInputException问题

这个问题主要是命名不规范造成的。在跳转的时候不能springboot不能弄清怎么跳转导致的。我的做法就是将url名称与我的页面名称区分开来。不需要额外加注解,或者是去搞html页面的头。

somemoods的博客 519

AIPex:AI + 自然语言驱动的浏览器自动化扩展

AIPex是一款革命性的Chrome扩展,通过AI技术实现自然语言驱动的浏览器自动化。它支持30多种自动化工具,包括智能标签页管理(分组、切换、批量操作)、页面内容提取(文本、链接、元数据)、交互元素操作(点击、滚动)以及表单智能填写等功能。用户只需用自然语言下达指令,即可快速完成复杂的浏览器操作,大幅提升工作效率。AIPex特别适合需要处理大量重复性网页操作的用户,如数据分析师、产品经理等。

blues_C的博客 1127

SE模块理解+SE-Resnet模块pytorch实现

文章目录SE模块理解SE实现注意力机制原因SE-resnet网络pytorch实现定义residual定义SE模块 SE模块理解 SENet是Squeeze-and-Excitation Networks的简称,拿到了ImageNet2017分类比赛冠军,其效果得到了认可,其提出的SE模块思想简单,易于实现,并且很容易可以加载到现有的网络模型框架中。SENet主要是学习了channel之间的相关性,筛选出了针对通道的注意力,稍微增加了一点计算量,但是效果比较好。 SE模块广泛应用于去噪,去雨方向,利用SE模

qq_42754919的博客 2万+

边工作边学习之thymeleaf避坑(一)

工作中很少接触thymeleaf,利用工作闲余时间跟着Spring实战这本书接触到了thymeleaf。由于小董是测试转开发,所以要比别人付出更多的时间来学习啦~ 今天跟着实战书训练,启动项目后访问对应路径产生了以下问题: Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Fri Aug 07 13:39:12 CST 2..

Asleny的博客 756

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

《Spring in Action》第1章-开始使用Spring

开始使用Spring 1、构建一个Spring应用 使用Spring Initializr构建应用: http://start.spring.io 为Eclipse安装插件,并使用Eclipse创建 在IntelliJ IDEA中创建 2、构建后的应用结构 在构建项目时我们选择以下的依赖: 生成的项目结构如下: mvnw和mvnw.cmd是maven的打包脚本,及时我们没有安装mav...

风萧水寒的博客 502

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

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

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

《Spring in action》——学习总结(一)

在网上学的很吃力,可能是前面的Java没学好吧,遂在图书馆找了本《Spring in action》看看,刚开始看,感觉还行。Spring 简述通过面向POJO编程、依赖注入、AOP和模板技术四个方面来简化Java开发的复杂性。Tips: POJO,Plain Ordinary Java Object的缩写,但是它通指没有使用Entity Beans的普通java对象,可以把POJO作为支持业务逻

LemonTe-A 3414

20210401#《Spring in action 5》

目录

qq_17079255的博客 2116

Spring in Action 5th 004开发一个Spring Web项目

我们以一个网上订购玉米饼的案例为例,完成一个基本的Web项目。 首先,我们需要在界面上显示玉米饼的制作原料。 我们需要从数据库中读取数据并显示到界面上,往往在web项目中,这是Controller的活。 所以,我们需要以下清单里的内容: 1. 一个domain类,定义玉米饼的属性 2. 一个controller类获取数据并传递给界面渲染 3. 一个显示模板,负责在用户的浏览器上渲染出原...

『程序员·小李』的博客 332

struts2 整合spring 及 由spring治理struts的Action实例

1.当指定struts.objectFactory为spring时,struts2框架就会把bean转发给spring来创建,装配,注入。但是bean创建完成之后,还是由struts容器来治理其生命周期 2. 在某些时候,你可能希望不仅仅让SPRING创建和装配Action对象,还希望让SPRING完全治理这些对象,如希望使用AOP或者希望使用acegi时。这时候,只需要在spring的配置文件中定义这些action即可。

it_man的专栏 5379

关于struts2结合spring 创建action的思考 singleton prototype

最近突然想到一个问题 以前在用struts2(注解)+spring struts的action拖给spring管了(spring的bean在Ioc容器范围内默认都是singlen的),但是没有加@scope("prototype")却从来没有出现过线程安全问题。 而一年前还在学校时做ssh2(没用注解)练习时,不在bean后加prototype都会出现线程安全问题,当时所有的ac

wangweiguang2的专栏 520

Spring Microservices in Action

关于这本书 Spring Microservices in Action是为Java / Spring开发人员编写的,他们需要动手建议以及有关如何构建和操作基于微服务的应用程序的示例。当我写这本书时,我希望它基于核心微服务模式,并与展示了实际模式的Spring Boot和Spring Cloud示例保持一致。这样,您将在几乎每一章中找到讨论的特定微服务设计模式,以及使用Spring B...

大强博客 1225

Spring实战(第4版)Spring In Action - 第6章 渲染Web视图

第6章 渲染Web视图 6.1 理解视图解析 ViewResolver视图解析器基类 public interface ViewResolver { View resolveViewName(String viewName, Locale locale) throws Exception; } View接口,接收model以及Servlet的request和response对...

alterhz的博客 327

探索《Spring Integration in Action》—— 实践驱动的集成之旅

探索《Spring Integration in Action》—— 实践驱动的集成之旅 随着企业级应用对高可扩展性和消息驱动架构需求的增长,Spring Integration作为Spring框架的重要扩展,成为了众多开发者手中的利器。今天,我们带您走进一个特别的开源项目——基于同名书籍《Spring Integration in Action》(曼宁出版社, 2012年)的代码示例集合。尽管官...

gitblog_00306的博客 1002

Spring In Action 4

在线学习连接:spring in action 4 关键词解释 名词 解释 EJB Enterprise JavaBean,企业级JavaBean POJO Plain Old Java Object,简单老式Java对象 J2EE Java 2 Enterprise Edition,Java2企业版 DI Dependency Injection,依赖注入,获取对象的过程被反转,获得依赖对象的过程由自身管理变成由IOC容器主动注入 IOC Inversion of Cont

Ranger的博客 1万+

Racket 仓库内嵌 lz4(1) 命令手册深度解读:压缩、解压、多线程与全部选项实战指南

本篇技术指南以 Racket 仓库中 ChezScheme 子系统内嵌的 lz4 项目 [programs/lz4.1.md](https://link.gitcode.com/i/79a2b7e8e9c3c3d51f1127bd6d02712b) 命令手册为核心骨架,结合同目录下的 CLI 源码、库实现与测试脚本,系统讲解 `lz4` / `unlz4` / `lz4cat` 三个命令的完整用法

gitblog_00188的博客 965

Spring In Action —— chapter01 —— Spring 入门 —— 读《Spring 实战》笔记以及原文摘录转载

Spring 的核心是一个 容器,通常称为 Spring 应用程序上下文,用于创建和管理应用程序组件。这些组件(或 bean)在 Spring 应用程序上下文中连接在一起以构成一个完整的应用程序,就像将砖、灰浆、木材、钉子、管道和电线绑在一起以组成房屋。将 bean 连接在一起的行为是基于一种称为 依赖注入(DI)的模式。依赖项注入的应用程序不是由组件自身创建和维护它们依赖的其他 bean 的生命周期,而是依赖于单独的实体(容器)来创建和维护所有组件,并将这些组件注入需要它们的 bean。通常通过构造函数参

Alascanfu的博客 594

strtus2 + spring + CXF 实现能访问action的同时还能调用spring发布的Webservice

【背景介绍】 目前正在开发一个小工具,需要采用CXF

liuzhixiong1992的专栏 1292
上一篇: 《Spring in Action》第1章-开始使用Spring
下一篇: 核心技术(卷二)03、第2章-XML
风萧水丶寒
博客等级 码龄11年 6粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值