开发颠覆者SpringBoot实战---------SpringMVC的基础学习

pom.xml文件

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>myspringboot</groupId>
  <artifactId>springmvc4</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <!-- 全局配置 -->
  <properties>
    <!-- java编译级别 -->
    <java.version>1.7</java.version>
    <!-- 编码 -->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <!-- web -->
    <jsp.version>2.2</jsp.version>
    <jstl.version>1.2</jstl.version>
    <servlet.version>3.1.0</servlet.version>
    <!-- spring -->
    <spring-framework.version>4.1.5.RELEASE</spring-framework.version>
    <!-- logging -->
    <logback.version>1.0.13</logback.version>
    <slf4j.version>1.7.5</slf4j.version>
  </properties>
  <dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-web-api</artifactId>
        <version>7.0</version>
        <scope>provided</scope>
    </dependency>
    <!-- springmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>${spring-framework.version}</version>
    </dependency>
    <!-- 其他web依赖 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>${jstl.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>${servlet.version}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>${jsp.version}</version>
        <scope>provided</scope>
    </dependency>
    <!-- spring and transactions -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${spring-framework.version}</version>
    </dependency>
    <!-- 使用sl4j和logback作为日志) -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.16</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${slf4j.version}</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>${logback.version}</version>
    </dependency>
    <!-- 对象和json或xml的转换 -->
    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.5.3</version>
    </dependency>
    <!-- 文件上传 -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    <!-- 非必要,简化io操作 -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.3</version>
    </dependency>

  </dependencies>
  <!-- maven编译级别 -->
  <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.3.2</version>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>2.3</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
            </configuration>
        </plugin>
    </plugins>
  </build>
</project>

一、简单控制器

创建一个index.jsp文件在src/main/resources/views下。

/**
 * springmvc配置
 * @author think
 */
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class MyMvcConfig {
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/classes/views/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
}

/**
 * web配置
 * @author think
 */
public class WebInitializer implements WebApplicationInitializer{
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(MyMvcConfig.class);
        context.setServletContext(servletContext);//与配置类关联
        Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册springmvc的DispatcherServlet
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }
}

/**
 * 简单的一个控制器
 * @author think
 */
@Controller
public class ControllerTest {
    @RequestMapping("/index")//配制方法与url的映射
    public String hello(){
        return "index";
    }
}

WebApplicationInitializer是spring提供用来配置servlet3.0的接口,从而替代web.xml,此接口会自动SpringServletContainerInitializer(用来启动serlvet3.0容器)获取到。

二、springmvc常用注解

1、@Controller声明是一个控制器,dispatcherservlet会自动扫描此注解的类,并将web讲求映射到有@RequestMapping的方法上。
2、在声明普通bean时,使用@Component、@Service、@Repository、@Controller是等同的,但是在作为springmvc的控制器bean时,只能使用@Controller。
3、@RequestMapping用来映射web请求、处理类和方法的,支持servlet的request和response作为参数,也支持对request和response的媒体类型进行配置。
4、@ResponseBody支持将返回值放在response体内,而不是返回一个页面,在很多基于ajax的程序时,可以用此注解返回数据,而不是页面。
5、@RequestBody允许参数再request体内,而不是在地址后面
6、@PathVariable用来接收路径参数,如/news/001,可以接收001作文参数
7、@RestController是组合注解,组合了@Controller和@ResponseBody

简单使用:

/**
 * 实体类
 * @author think
 */
public class Obj {
    private Long id;
    private String name;
    public Obj() {//对象转换json时候需要此空参构造
        super();
    }
    public Obj(Long id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Obj [id=" + id + ", name=" + name + "]";
    }
}

/**
 * 参数转化
 * @author think
 */
@Controller
@RequestMapping("/obj")//映射此类的访问路径是/obj
public class ObjController {
    @RequestMapping(produces = "text/plain;charset=UTF-8")//使用此类的访问路径,produces设置返回的response的媒体类型和字符集
    public @ResponseBody String normal(HttpServletRequest request){
        return "url:" + request.getRequestURL();
    }
    @RequestMapping(value = "/{pathVar}", produces = "text/plain;charset=UTF-8")//value接收的路径参数,结合@PathVariable获取访问路径参数
    public @ResponseBody String pathVar(@PathVariable String pathVar, HttpServletRequest request){
        return "url:" + request.getRequestURL() + ",路径参数:" + pathVar;
    }
    @RequestMapping(value = "/requestParam", produces = "text/plain;charset=UTF-8")//常规的request作为参数,获取路径后面拼接的id=xx
    public @ResponseBody String requestParam(Long id, HttpServletRequest request){
        return "url:" + request.getRequestURL() + ",id:" + id;
    }
    @RequestMapping(value = "/turn2Obj", produces = "application/json;charset=UTF-8")//对象自动转换json,返回的媒体对象为json
    @ResponseBody//可以用在方法上也可以用在方法中
    public Obj turn2Obj(Obj obj, HttpServletRequest request){
        return new Obj(obj.getId(), obj.getName());
    }
    @RequestMapping(value = {"/name1", "/name2"}, produces = "text/plain;charset=UTF-8")//不同路径映射到相同的方法
    public @ResponseBody String multiPath(HttpServletRequest request){
        return "url:" + request.getRequestURL();
    }
}

三、静态资源映射和拦截器

SpringMVC的定制配置需要我们的配置类继承一个WebMvcConfigurerAdapter类,并在此类使用@EnableWebMvc注解来开启对SpringMVC的配置支持,这个累里面有许多好用的方法,这样我们可以重写这个类的方法来完成我们的常用配置。

创建一个静态文件demo.txt在src/main/resource/assets下。

/**
 * 自定义拦截器
 * @author think
 */
public class Interceptor extends HandlerInterceptorAdapter{
    //请求发生前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        return true;
    }
    //请求发生后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        Long startTime = (Long) request.getAttribute("startTime");
        request.removeAttribute("startTime");
        Long endTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (endTime - startTime));
        request.setAttribute("handlingTime", endTime - startTime);
    }
}

/**
 * 配置
 * @author think
 */
@Configuration
@EnableWebMvc//开启springmvc支持,没有此句,重写WebMvcConfigurer无效
@ComponentScan("com")
public class MyMvcConfig implements WebMvcConfigurer{
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/classes/views/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }
    @Bean//配置拦截器的bean
    public Interceptor getInterceptor(){
        return new Interceptor();
    }
    @Override//静态资源映射
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //addResourceHandler对外暴露的访问路径,addResourceLocations文件放置的目录
        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
    }
    @Override//注册拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getInterceptor());
    }
}

这样就可以访问项目目录下的静态文件资源,同时可以对请求进行拦截。

四、@ControllerAdvice

@ControllerAdvice作为一个全局配置的控制器,其组合了@Component,自动注册为spring的bean,所有配置信息都写在本类里面,注解了@Controller类的方法可以使用@ExceptionHandler,@ModelAttribute,@InitBinder,对所有注解了@RequestMapping的控制器都有效。

创建一个处理异常的页面error.jsp文件在src/main/resources/views下。

/**
 * 全局配置
 * @author think
 */
@ControllerAdvice
public class ExceptionHandlerAdvice {
    @ExceptionHandler(value = Exception.class)//全局异常配置
    public ModelAndView exception(Exception exception, WebRequest request){
        ModelAndView modelAndView = new ModelAndView("error");
        modelAndView.addObject("errorMessage", exception.getMessage());
        return modelAndView;
    }
    @ModelAttribute//自动绑定键值对到model中,所有@RequestMapping的能获得此处的值
    public void addAttributes(Model model){
        model.addAttribute("msg", "额外信息");
    }
    @InitBinder//自动绑定前端的请求参数到model中
    public void initBinder(WebDataBinder binder){
        binder.setDisallowedFields("id");//将request参数封装为对象的时候忽略id这个属性,正常获取id参数还是可以获取到的。
    }
}

@Controller
public class AdviceController {
    @RequestMapping("advice")
    public String getGlobal(@ModelAttribute("msg") String msg, Obj obj, String id){
        throw new IllegalArgumentException("异常,msg:" + msg + ",obj:" + obj + ",id:" + id);
    }
}

发送 “springmvc4/advice?id=55&name=名字” 请求后,在error.jsp中用${errorMessage }就可以获取到:
这里写图片描述

五、其他配置

1、快捷返回页面ViewController

//简单的返回页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/index").setViewName("/index");
    super.addViewControllers(registry);
}

用上面的代码可以替代掉下面的代码,从而达到一个简化的目的,前提是这只是一个简单的页面转向,不涉及到任何的逻辑。

@Controller
public class ControllerTest {
    @RequestMapping("/index")
    public String hello(){
        return "index";
    }
}

2、路径匹配参数配置

在SpringMVC中,请求路径如果带有“.”的话,后面的值将会被忽略,例如访问“/springmvc4/obj/pathvar.33”时,是无法获取“obj/”后面的完整参数。通过重写configurePathMatch将设置为不忽略“.”后面的参数:

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.setUseSuffixPatternMatch(true);
    super.configurePathMatch(configurer);
}

六、文件上传

创建一个upload.jsp在src/main/resource/views下:

<form action="upload" enctype="multipart/form-data" method="post">
    <input type="file" name="file"><br>
    <input type="submit" value="上传">
</form>

在控制器中用MultipartFile或MultipartFile[]接受单个或多个文件:

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/toUpload").setViewName("/upload");

/**
 * 文件上传
 * @author think
 */
@Controller
public class UploadController {
    @RequestMapping(value="/upload", method=RequestMethod.POST)
    public @ResponseBody String upload(MultipartFile file){
        try {
            FileUtils.writeByteArrayToFile(new File("F://" + file.getOriginalFilename()), file.getBytes());
            return "ok";
        } catch (Exception e) {
            return "wrong";
        }
    }
}

七、HttpMessageConverter

HttpMessageConverter是用来处理request和response数据的,Spring为我们内置了大量的HttpMessageConverter,下面是自定义HttpMessageConverter。

/**
 * 自定义MessageConverter
 * @author thinkHttp
 */
public class MyMessageConverter extends AbstractHttpMessageConverter<Obj>{
    //新建一个自定义的媒体类型
    public MyMessageConverter() {
        super(new MediaType("application", "x-wisely", Charset.forName("UTF-8")));
    }
    //处理请求数据:处理由"-"隔开的数据,并转为Obj对象
    @Override
    protected Obj readInternal(Class<? extends Obj> clazz, HttpInputMessage input)
            throws IOException, HttpMessageNotReadableException {
        String temp = StreamUtils.copyToString(input.getBody(), Charset.forName("UTF-8"));
        System.out.println(temp);
        String[] sts = temp.split("-");
        return new Obj(Long.parseLong(sts[0]), sts[1]);
    }
    //表明本类只处理Obj这个类
    @Override
    protected boolean supports(Class<?> clazz) {
        boolean b = Obj.class.isAssignableFrom(clazz);
        return b;
    }
    //处理如何输出数据到response中
    @Override
    protected void writeInternal(Obj obj, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
        String out = "hello:" + obj.getId() + "-" + obj.getName();
        output.getBody().write(out.getBytes());
    }
}

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/beginConvert").setViewName("/converter");

自定义的HttpMessageConverter在SpringMVC进行注册有两种方式:

  • configureMessageConverters:重载会覆盖掉SpringMVC默认注册的多个HttpMessageConverter。
  • extendMessageConverters:仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter。

所以我们重写extendMessageConverters:

@Bean
public MyMessageConverter converter(){
    return new MyMessageConverter();
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(converter());
    super.extendMessageConverters(converters);
}

@Controller
public class ConverterController {
    @RequestMapping(value = "/convert", produces = {"application/x-wisely"})
    public @ResponseBody Obj converts(@RequestBody Obj obj){
        return obj;
    }
}

创建一个converter.jsp在src/main/resource/views下:

<body>
    <div id="resp"></div>
    <input type="button" onclick="req()" value="请求">
<script type="text/javascript">
    function req(){
        $.ajax({
            url : 'convert',
            data : '1-xiaoming',
            type : 'POST',
            contentType : 'application/x-wisely',
            success : function(data){
                alert(data);
                document.getElementById('resp').innerHTML = data;
            }
        });
    }
</script>
</body>

请求和响应数据的拦截机制在自定义的HttpMessageConverter的supports方法中:只有当控制器中@ResponseBody返回的对象类型与supports方法的对象类型相一致,才会继续readInternal(读取request的数据)和writeInternal(数据响应到response)。

八、服务端推送技术

推送方案:当客户端向服务端发送请求,服务端会抓住这个请求不放,等有数据更新才返回给客户端,当客户端接受到消息后,再向服务端发送请求,周而复始。这种方式好处是减少了服务器的请求数量,减少服务器的压力。

1、SSE

/**
 * 控制器
 * @author think
 */
@Controller
public class SSEController {
    //text/event-stream这种媒体类型是服务器端SSE的支持,每5秒向浏览器推送随机消息
    @RequestMapping(value = "/push", produces = "text/event-stream")
    public @ResponseBody String push(){
        Random random = new Random();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "data:Testing " + random.nextInt() + "\n\n";
    }
}

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/sse").setViewName("/sse");

创建一个sse.jsp在src/main/resource/views下:

<body>
    <div id="msgFromPush"></div>
    <a>ssss</a>
<script type="text/javascript">
    if (!!window.EventSource) {//!!是数据强转布尔类型,EventSource对象是新式的浏览器才有,EventSource是SSE的客户端
        var source = new EventSource('push');
        var s = '';
        source.addEventListener('message', function(e){//添加SSE客户端监听,获得服务端推送的消息
            s += e.data + '<br>';
            document.getElementById('msgFromPush').innerHTML = s;
        });
        source.addEventListener('open', function(e){
            //console.log('连接打开');
        }, false);
        source.addEventListener('error', function(e){
            if (e.readyState == EventSource.CLOSED) {
                //console.log('连接断开');
            } else {
                alert(e.readyState);
                //console.log(e.readyState);
            }
        }, false);
    } else {
        //console.log('您的浏览器不支持SSE');
    }
</script>
</body>

2、Servlet3.0+的异步方法处理

/**
 * 开启异步方法支持
 * @author think
 */
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册springmvc的DispatcherServlet
servlet.setAsyncSupported(true);

/**
 * 开启定时任务支持
 * @author think
 */
@EnableScheduling//开启定时任务支持
public class MyMvcConfig extends WebMvcConfigurerAdapter{
}

/**
 * 页面跳转
 * @author think
 */
registry.addViewController("/async").setViewName("/async");

/**
 * 控制器
 * @author think
 */
//异步任务的实现是通过控制器从另外一个线程返回一个DeferredResult
@Controller
public class AsyncController {
    @Autowired
    PushService pushService;
    @RequestMapping("/defer")
    public @ResponseBody DeferredResult<String> deferCall(){
        return pushService.getAsyncUpdate();
    }
}

/**
 * 定时任务
 * @author think
 */
@Service
public class PushService {
    private DeferredResult<String> deferredResult;
    public DeferredResult<String> getAsyncUpdate(){
        deferredResult = new DeferredResult<>();
        return deferredResult;
    }
    @Scheduled(fixedDelay = 5000)
    public void refresh(){
        if (deferredResult != null) {
            deferredResult.setResult(new Long(System.currentTimeMillis()).toString());
        }
    }
}

在PushService中产生DeferredResult给控制器使用,通过@Scheduled注解的定时任务更新DeferredResult。

创建一个async.jsp在src/main/resource/views下:

<body>
<script type="text/javascript">
    deferred();
    function deferred(){
        $.get('defer', function(data){
            console.log(data);
            deferred();
        });
    }
</script>
</body>
文档内容 一、 Spring介绍 1 1.1、SpringBoot简介 1 1.2、系统要求: 1 1.3、SpringBootSpringMVC区别 1 1.4、SpringBoot和SpringCloud区别 2 1.5常见错误 2 二、快速入门 2 2.1、创建一个Maven工程 2 2.2、pom文件引入依赖 3 2.3、编写HelloWorld服务 3 2.4、@RestController 4 2.5、@EnableAutoConfiguration 4 2.6 SpringApplication.run(HelloController.class, args); 4 2.7、SpringBoot启动方式1 4 2.8、SpringBoot启动方式2 4 2.9、SpringBoot启动方式3 5 三、 Web开发 5 3.1、静态资源访问 5 3.2、渲染Web页面 5 3.3、使用Freemarker模板引擎渲染web视图 6 3.3.1、pom文件引入: 6 3.3.2、后台代码 6 3.3.3、前台代码 6 3.3.4、Freemarker其他用法 7 3.3.5、Freemarker配置 8 3.4、使用JSP渲染Web视图 8 3.4.1、pom文件引入以下依赖 8 3.4.2、在application.properties创建以下配置 9 3.4.3、后台代码 9 3.5、全局捕获异常 10 四、 数据访问 10 4.1、springboot整合使用JdbcTemplate 10 4.2、springboot整合使用mybatis 12 4.3、springboot整合使用springjpa 18 4.4、springboot整合多数据源 19 五、 事物管理 25 5.1.1SpringBoot整合事物管理 25 5.1.2SpringBoot分布式事物管理 25 六、 日志管理 30 6.1使用log4j记录日志 30 6.2使用AOP统一处理Web请求日志 32 6.3Spring Boot集成lombok让代码更简洁 33 七、 缓存支持 35 7.1注解配置与EhCache使用 35 7.2使用Redis集成缓存 37 八、 热部署 37 8.1 什么是热部署 37 8.2 项目演示案例 37 8.3 热部署原理 37 8.4 Devtools依赖 38 8.5 Devtools原理 38 九、 监控管理 38 Actuator监控应用 38 Maven依赖 38 YML配置 39 Actuator访问路径 40 Admin-UI分布式微服务监控中心 40 Admin-UI-Server 40 Admin-UI-Client 41 十、 性能优化 43 组件自动扫描带来的问题 43 将Servlet容器变成Undertow 44 SpringBoot JVM参数调优 44 十一、 2.0版本新特性 45 以Java 8 为基准 45 内嵌容器包结构调整 45 Servlet-specific 的server properties调整 45 Actuator 默认映射 46 Spring Loaded不再支持 46 支持Quartz Scheduler 46 OAuth 2.0 支持 46 支持Spring WebFlux 46 版本要求 46 十二、 其他内容 47 12.1、使用@Scheduled创建定时任务 47 12.2、使用@Async实现异步调用 47 12.3、自定义参数 49 12.4、多环境配置 50 12.5、修改端口号 50 12.6、SpringBoot yml 使用 50 12.7、SpringBoot整合拦截器 51 12.8、发布打包 52
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值