Spring MVC笔记

1 SpringMCV基本说明

  1. 简介:spring为展现层提供的基于MVC设计理念的优秀Web框架
  2. SpringMVC是一个容器,用来管理对象的,使用IOC核心技术。springmvc管理界面层中的控制器对象
  3. SpringMVC底层也是servlet,以servlet为核心,接收请求,处理请求,显示处理结果给用户

2 SpringMVC中的核心Servlet – DispatcherServlet

  1. DispatcherServlet是框架中的一个Servlet对象,负责接收请求,相应处理结果。它的父类是HttpServlet。
  2. DispatcherServlet也叫前端控制器(front controller)
  3. SpringMVC是管理控制器对象,原来没有SpringMVC之前使用Servlet作为控制器对象使用,现在通过SpringMVC容器创建一种叫做控制器的对象,代替Servlet行使控制器的角色,功能。

3 SpringMVC的使用方式

3.1 配置文件

web.xml:

<servlet>
	<servlet-name>myweb</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    	<param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>myweb</servlet-name>
    <!--
		url-pattern作用:把一些请求交给指定的servlet处理,这里定义的就是那些请求需要使用中央调度器来处理
		1.使用拓展名的方式,格式*.xxx,xxx是自定义的拓展名,如*.do,*.action,*.mvc等等
		2.使用斜杠"/"
			"/":拦截除静态资源外的所有请求
			"/*":拦截所有请求,包括静态资源(一般不这么写)
	-->
    <url-pattern>/</url-patter>
</servlet-mapping>
springmvc.xml:

<!--声明组件扫描器-->
<context:conponet-scan base-package="com.huhx.controller" />

3.2 创建后端控制器(Controller)

创建一个普通类,当做servelet使用,在类的上方加入@Controller注解

/**
* @Controller:创建控制器(处理器)对象
* 控制器:叫做后端控制器,自定义的类处理请求的。
* 位置:在类的上面,表示创建此类的对象,对象放在springmvc的容器中
*/

@Controller
public class Mycontroller {
    //定义方法,处理请求,public void doGet(){}
    
    /**
    * springmvc框架,使用控制器类中的方法处理请求。
    * 方法特点:
    * 	1.方法的形参,表示请求中的参数
    *	2.方法的返回值,表示本次请求的处理请求
	*/
    
    /**
    * @RequestMapping:请求映射
    * 属性:value  请求中的uri地址,唯一值,以“/”开头
    * 位置:1、在方法的上面(必须)2.在类定义的上面(可选)
    * 作用把指定的请求,交给指定的方法来处理,等同于url-pattern
    * 返回的ModelAndView:表示本次请求的处理结果(数据和视图)
    * 	Model:表示数据
    * 	View:表示视图
	*/
    @RequestMapping("/test")
    public ModelAndView doSome(){
        //使用这个方法处理请求,能处理请求的方法叫做控制器方法,可以有参数,也可以有返回值
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "处理了请求");
        mv.addObject("fun", "执行了方法");
        
        //指定视图,setViewName("视图的完整路径")
        mv.setViewName("/index.jsp");
        return mv;
    }
    
    //上述方法可以直接简化成下面这种这种形式
    @RequestMapping("/test")
    public String doSome(){
        return "/index.jsp";
    }
    
    /**
    * 当框架调用完doSome()方法后,得到返回的ModelAndView。
    * 框架会在后续的处理逻辑中,处理mv对象里面的数据和视图
    * 对数据执行request.setAttribute("msg","处理了请求"),把数据放入了request的作用域中
    * 对视图执行了forward转发操作,等同于request.getRequestDispatcher("index.jsp").forward(..)
	*/
}

3.3 SpringMVC请求的处理方式

简单处理过程:

  • 用户发起请求/xxx——>Tomcat接收请求---->将请求交给中央处理器DispaterServlet------->将请求交给后端控制器MyController(doSome()方法处理了请求)

3.4 使用视图解析器

当配置了视图解析器之后,我们可以直接将文件名作为视图名来使用,叫做视图的逻辑名称

使用方式:在springmvc.xml文件中配置视图解析器

<bean class="org.springframework.web.servlet.view.InternalResourceView">
	<property name="prefix" value="/WEB-INF/view"/>
    <property name="suffix" value=".jsp" />
</bean>

在doSome()方法中,可以在设置视图的时候直接使用如下代码:

mv.setViewName("index");

4 路径映射与请求参数携带

4.1 @RequestMapping

4.1.1 指定模块名称

  • 在方法上标注:指定独有的访问路径
  • 在类上标注:指定一类访问路径,类中所有方法的请求路径都是以该前缀开头的,不用在方法上重复声明

4.1.2 其他属性

  • method:限定请求方式

    ​ method = RequestMethod.POST; 表示只接收post请求,如果请求类型不对就会报错

  • params:限定请求参数,支持简单的表达式

    ​ params={“username”}:发起请求时必须带上一个username的参数,如果没带就会报错(404)

    ​ params={“!username”}:发起请求时必须不带上username的参数,如果携带就会报错(404)

    ​ params={“username=123”}:发起请求时username参数的值必须是123

    ​ params={“username=123”, “pwd”, “!age”}:发起请求时三个参数要求必须全部满足才能访问成功

  • headers:限定请求头,同样支持简单的表达式

  • consumes:只接收内容类型是哪种的请求,规定请求头中的Content-Type

  • produces:告诉浏览器返回的内容类型是什么,给响应头中加上Content-Type

4.1.3 RequestMapping的模糊匹配功能

URL地址可以写模糊的通配符(?、*、**)

?:能替代任意一个字符

*:能替代多个字符和一层路径

**:能替代多层路径

@RequestMapping("/test01"):只能处理/test01请求
@RequestMapping("/test0?"):能处理/test02、/test0a等等请求,存在多个匹配路径情况下,精确路径优先
@RequestMapping("/test0*"):能处理/test0、/test000等等请求
@RequestMapping("/**/test01"):能处理/a/test01、/a/abc/test01等等请求

4.2 @PathVariable

可以与@RequestMapping一同使用,@RequestMapping给定的路径上可以存在占位符,占位符的语法就是可以在任意路径的地方写一个{变量名}

@RequestMapping("/user/{id}")
public String hello(@PathVariable("id")String id) {
    System.out.println(id);
    return "success"}

4.3 Restful风格URL

REST:即Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。

资源:是网络上的一个实体,或者说是网络上的一个具体信息,可以是一段文本、一张图片、一种服务,总之是一个具体的存在,可以利用一个URI指向它,每种资源对应一个特定的URI,因此URI即为每一个资源的独一无二的识别符

表现层:把资源具体呈现出来的形式,叫做它的表现层,例如文本可以用txt格式表示,可以用JSON格式表示等

状态转化:每发送一个请求,就代表了客户端和服务器的一次交互过程,HTTP协议,是一种无状态协议,即所有的状态都保存在服务器端,因此如果客户端想要操作服务器,就必须通过某种手段,让服务器端发生“状态转化”。而这种转化是建立在表现层之上的。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源、PUT用来更新资源、DELETE用来删除资源

Restful风格的URL地址约束希望以非常简洁的URL地址来发送请求,怎样表示对一个资源的增删改查使用请求的方式来区分

Rest风格的url地址的写法:/资源名/资源标识符

相同的url,发起不同的请求来表示对资源的不同操作:

/book/1    GET请求获取1号图书
/book/1	   PUT请求更新1号图书
/book/1    DELETE请求删除1号图书
/book      POST请求添加图书

问题1:如何从页面发起DELETE和PUT请求?

1、SpringMVC中有一个Filter,它可以把普通的请求转化为规定形式的请求,我们需要在web.xml中配置这个filter

<filter>
	<filter-name>HiddenHttpMethodFilter</filter-name>
    <fiter-class>org.springframework.web.filter.HiddenHttpMethodFilter</fiter-class>
</filter>
<filter-mapping>
	<filter-name>HiddenHttpMethodFilter</filter-name>
    <url-patter>/*</url-patter>
</filter-mapping>

2、在页面按照以下要求发起一个请求

  • 创建一个post类型的表单
  • 表单项中携带一个_method的参数,将其value设置为“put”或者“delete”
<form action="book/1" method="post">
	<input name="_method" value="put" />
    <input type="submit" value="更新一号图书" />
</form>

问题2:高版本的Tomcat不接收DELETE和PUT类型的请求

4.4 @RequesParam、@RequestHeader、@CookieValue(请求参数获取)

springmvc获取请求参数的默认方式:

​ 直接给方法入参上写一个和请求参数名相同的变量。这个变量就来接收请求参数的值

@RequesParam:用于获取请求参数:

​ value:指定要获取的参数的key

​ required:这个参数是否是必须的,如果是true,没传入该参数时会报错,如果是false,则为默认值

​ defaultValue:默认值。没带默认值是null

@RequestMapping("/test")
public String hello(@RequesParam("user")String username) {
    System.out.println(username);
    return "success"}

@RequestHeader:获取请求头中某个key的值

​ value:指定要获取的参数的key

​ required:这个参数是否是必须的,如果是true,没传入该参数时会报错,如果是false,则为默认值

​ defaultValue:默认值。没带默认值是null

@RequestMapping("/test")
public String hello(@RequesParam("user")String username, @RequestHeader("User-Agent")String userAgent) {
    System.out.println(username);
    System.out.println(userAgent);
    return "success"}

@CookieValue:获取某个cookie的值

​ value:指定要获取的参数的key

​ required:这个参数是否是必须的,如果是true,没传入该参数时会报错,如果是false,则为默认值

​ defaultValue:默认值。没带默认值是null

@RequestMapping("/test")
public String hello(@RequesParam("user")String username, @RequestHeader("User-Agent")String userAgent, @CookieValue("JSESSIONID")String jid) {
    System.out.println(username);
    System.out.println(userAgent);
    System.out.println(jid);
    return "success"}

传入POJO类-springmvc可以自动封装:

  1. 将POJO中的每一个属性,从request参数中长时获取出来,并封装
  2. 还可以级联封装:对象中包含其他对象,包含的对象的属性也可以封装
  3. 请求参数的参数名要跟对象中的属性名称一一对应
public class Book {
    private String name;
    private String author;
    private Double price;
}

@RequestMapping("/book")
public String hello(Book book) {
    System.out.println(book);
    return "success"}

5 响应数据的返回

  • Model:
  • ModelAndView:
  • Map
  • 原生API,HttpResponse,HttpRequest,HttpSession

6 SpringMVC源码

6.1 DispatcherServlet结构

6.2 DispatcherServlet工作流程

  1. 所有请求过来DispatcherServlet收到请求

  2. 调用DispatcherServlet中的doDispatch()方法进行处理

    1. getHandler():根据当前请求地址找到能处理这个请求的目标处理器类(controller)
    2. getHandlerAdapter():根据当前处理器类获取到能执行这个处理器方法的适配器
    3. 使用刚才获取到的额适配器(AnnotationMethodHandlerAdapter)执行目标方法
    4. 目标方法执行后会返回一个ModelAndView对象
    5. 根据ModelAndView的信息转发到具体的页面,并可以在请求域中取出ModelAndView中的模型数据
  3. getHandler()的细节:如何根据当前请求就能找到哪个controller能来处理
    在这里插入图片描述在这里插入图片描述

    • HandlerMapping:处理器的映射(运行时实际得到的是RequestMappingHandlerMapping),里边保存了每一个请求其对应的处理器,其中包含有mappingRegistry属性,里边保存有请求路径与Controller的映射关系

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7skkqaEK-1620823776393)(C:\Users\HHX\Desktop\1620733480625.png)]

    • 在spring容器启动创建Controller对象的时候会扫描每个处理器上的@RequestMapping注解,并将其中携带的路径信息保存在handlerMap的属性中,就形成了Controller的映射

  4. getHandlerAdapter()的细节:如何找到目标处理器类的适配器。要拿适配器去执行目标方法在这里插入图片描述

    • HandlerAdapter:处理器适配器,其具体的实现类如下图所示:
      在这里插入图片描述
  5. 探究HandlerMapping与HandlerAdapter的实现类是怎么拿到的:DispatcherServlet中的引用属(SpringMVC的九大组件)

    	@Nullable
    	private MultipartResolver multipartResolver; //文件上传相关的解析器
    
    	/** LocaleResolver used by this servlet. */
    	@Nullable
    	private LocaleResolver localeResolver;  //区域信息解析器,国际化相关
    
    	/** ThemeResolver used by this servlet. */
    	@Nullable
    	private ThemeResolver themeResolver;   //主题效果更换
    
    	/** List of HandlerMappings used by this servlet. */
    	@Nullable
    	private List<HandlerMapping> handlerMappings;  //处理器映射器
    
    	/** List of HandlerAdapters used by this servlet. */
    	@Nullable
    	private List<HandlerAdapter> handlerAdapters;  //处理器适配器
    
    	/** List of HandlerExceptionResolvers used by this servlet. */
    	@Nullable
    	private List<HandlerExceptionResolver> handlerExceptionResolvers;  //异常处理解析器
    
    	/** RequestToViewNameTranslator used by this servlet. */
    	@Nullable
    	private RequestToViewNameTranslator viewNameTranslator;  
    
    	/** FlashMapManager used by this servlet. */
    	@Nullable
    	private FlashMapManager flashMapManager;
    
    	/** List of ViewResolvers used by this servlet. */
    	@Nullable
    	private List<ViewResolver> viewResolvers;
    
    
  6. DispatcherServlet中九大组件初始化的地方:onRefresh()方法,该方法在spring容器初始化时是留给子类重写的,因此在容器启动时,该方法会执行,完成九大组件的初始化

        protected void onRefresh(ApplicationContext context) {
            initStrategies(context);
        }
    
    	protected void initStrategies(ApplicationContext context) {
    		initMultipartResolver(context);
    		initLocaleResolver(context);
    		initThemeResolver(context);
    		initHandlerMappings(context);
    		initHandlerAdapters(context);
    		initHandlerExceptionResolvers(context);
    		initRequestToViewNameTranslator(context);
    		initViewResolvers(context);
    		initFlashMapManager(context);
    	}
    
    	private void initLocaleResolver(ApplicationContext context) {
    		try {
    			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
    			if (logger.isTraceEnabled()) {
    				logger.trace("Detected " + this.localeResolver);
    			}
    			else if (logger.isDebugEnabled()) {
    				logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
    			}
    		}
    		catch (NoSuchBeanDefinitionException ex) {
    			// We need to use the default.
    			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
    			if (logger.isTraceEnabled()) {
    				logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
    						"': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
    			}
    		}
    	}
    
    

    组件的初始化,就是去容器中找这个组件,如果没有找到就用默认的配置。组件初始化完成之后,容器中就存在了相应组件的实现类了。

  7. 探究处理器的目标方法如何执行

    执行handler中的方法是通过适配器来反射执行的,在doDispatch方法中的具体语句为:

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    

    其中ha就是上边我们获取到的HandlerMappingAdapter,进入handle方法,最终会来到

7 视图与视图解析器

7.1 forward前缀转发

@RequestMapping("/test")
public String handle() {
    return "forward:/hello.jsp"; //一定要加“/”,不加是相对路径,容易出问题
}

7.2 redirect前缀指定重定向

@RequestMapping("/test")
public String handle() {
    return "redirect:/hello.jsp"; 
}

【注】:带有前缀的返回值(forward操作与redirect操作)都不会触发视图解析器的拼串操作

7.3 视图解析的流程

  1. 任何方法的返回值都会被包装成ModelAndView对象

8 SpringMVC返回json数据

使用@ResponseBody注解

@RequestMapping("/test")
@ResponseBody
public String handle() {
    return "success"; //一定要加“/”,不加是相对路径,容易出问题
}


【注】如果返回值为对象或者复杂对象,SpringMVC内部会通过Jackson包将复杂对象序列化为json格式的数据,然后返回。

9 SpringMVC拦截器

SrpingMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作,或者目标方法运行之后进行一些其他处理,SpringMVC中的拦截器是一个接口:HandlerInterceptor

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3U4zchO0-1620823776400)(C:\Users\HHX\Desktop\1620787434154.png)]

  • preHandle:在目标方法运行之前调用,返回boolean,返回为true时放行,返回为false时不放行
  • postHandle:在目标方法运行之后调用
  • afterCompletion:在请求整个完成之后,来到目标页面之后,放行或者拒绝请求

9.1 单拦截器运行流程

  1. 定义自己的拦截器类,实现HandlerInterceptor接口,并实现其中的方法

  2. 在Spring容器中注册自己的拦截器类

    <mvc:interceptors>
        <!--配置一个拦截器,拦截所有请求-->
    	<bean class="自己定义的拦截器类" />
        <mvc:interceptor>
             <!--配置一个拦截器,只拦截/test请求-->
        	<mvc:mapping path="/test"></mvc:mapping>
            <bean class="自己定义的拦截器类" />
        </mvc:interceptor>
    </mvc:interceptors>
    
    
  3. 拦截器的正常运行流程:

    拦截器的preHandle->目标方法->postHandle->页面->afterCompletion

    只要preHandle不放行就没有以后的流程

    只要放行了,afterCompletion就会执行

9.2 多拦截器运行流程

在这里插入图片描述
拦截器的preHandle是按照顺序执行的

拦截器的postHandle是按照逆序执行的

拦截器的afterCompletion是按照逆序执行的

已经放行的拦截器的afterCompletion总会执行

9.3 拦截器源码分析

10 SpringMVC异常处理

SpringMVC通过HandlerExceptionResolver来处理程序,在页面渲染之前(获取View对象之前),如果出现异常,SpringMVC会使用HandlerExceptionResolver对异常进行处理,如果所有的HandlerExceptionResolver都无法处理异常,则将异常抛给Tomcat

  • ExceptionHandlerExceptionResolver:与@ExceptionHandler注解联系使用

    //告诉SpringMVC下面这个方法专门用来处理xxxException异常
    //1.可以给方法上添加一个Exception参数,用来接收发生的异常
    //2.直接返回ModelAndView也可以携带异常信息
    
    @ExceptionHandler(value = {xxxException.class, xxException.class, ...}) 
    public String handleException(Exception ex) {
        //视图解析器拼串,来到我们自己定义的错误页面
        return "myerror";
    }
    
    @ExceptionHandler(value = {xxxException.class, xxException.class, ...}) 
    public String handleException(Exception ex) {
        //直接返回ModelAndView来携带异常信息
        ModelAndView mv = new ModelAndView("myerror");
        mv.addObject("ex", ex);
        return mv;
    }
    
    

    @ExceptionHandler全局异常处理:@ControllerAdvice注解

    1. 自定义全局异常处理类,一般新建一个Exception包,在该包下新建类
    2. 在这个类上加上@ControllerAdvice注解
    3. 在该类中编写方法,方法上加注@ExceptionHandler注解
    @ControllerAdvice
    public class MyExceptionHandler {
        @ExceptionHandler(value = {xxxException.class, xxException.class, ...}) 
        public String handleException(Exception ex) {
            //视图解析器拼串,来到我们自己定义的错误页面
            return "myerror";
        }
    }
    
    
  • ResponseStatusExceptionResolver:与@ResponseStatus注解联系使用

    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR,reason = "not  an error , just info")
    public class MyException extends RuntimeException {
        public MyException() {
        }
     
        public MyException(String message) {
            super(message);
        }
    }
    
    

    这样子,在SpringMvc中如果有某个@RequestMapping方法抛出该异常, 只要开启mvc:annotation-driven/,异常自动展示的界面都是如下的:
    img

  • DefaultHandlerExceptionResolver:如果前两个异常处理解析器都没有工作的情况下,SpringMVC会默认使用该异常处理解析器来解析发生的异常,即跳转至Spring自己的错误页面

    SpringMVC中自己定义的异常有以下几种,可以直接在源代码中找到

    	protected ModelAndView doResolveException(
    			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    
    		try {
    			if (ex instanceof HttpRequestMethodNotSupportedException) {
    				return handleHttpRequestMethodNotSupported(
    						(HttpRequestMethodNotSupportedException) ex, request, response, handler);
    			}
    			else if (ex instanceof HttpMediaTypeNotSupportedException) {
    				return handleHttpMediaTypeNotSupported(
    						(HttpMediaTypeNotSupportedException) ex, request, response, handler);
    			}
    			else if (ex instanceof HttpMediaTypeNotAcceptableException) {
    				return handleHttpMediaTypeNotAcceptable(
    						(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
    			}
    			else if (ex instanceof MissingPathVariableException) {
    				return handleMissingPathVariable(
    						(MissingPathVariableException) ex, request, response, handler);
    			}
    			else if (ex instanceof MissingServletRequestParameterException) {
    				return handleMissingServletRequestParameter(
    						(MissingServletRequestParameterException) ex, request, response, handler);
    			}
    			else if (ex instanceof ServletRequestBindingException) {
    				return handleServletRequestBindingException(
    						(ServletRequestBindingException) ex, request, response, handler);
    			}
    			else if (ex instanceof ConversionNotSupportedException) {
    				return handleConversionNotSupported(
    						(ConversionNotSupportedException) ex, request, response, handler);
    			}
    			else if (ex instanceof TypeMismatchException) {
    				return handleTypeMismatch(
    						(TypeMismatchException) ex, request, response, handler);
    			}
    			else if (ex instanceof HttpMessageNotReadableException) {
    				return handleHttpMessageNotReadable(
    						(HttpMessageNotReadableException) ex, request, response, handler);
    			}
    			else if (ex instanceof HttpMessageNotWritableException) {
    				return handleHttpMessageNotWritable(
    						(HttpMessageNotWritableException) ex, request, response, handler);
    			}
    			else if (ex instanceof MethodArgumentNotValidException) {
    				return handleMethodArgumentNotValidException(
    						(MethodArgumentNotValidException) ex, request, response, handler);
    			}
    			else if (ex instanceof MissingServletRequestPartException) {
    				return handleMissingServletRequestPartException(
    						(MissingServletRequestPartException) ex, request, response, handler);
    			}
    			else if (ex instanceof BindException) {
    				return handleBindException((BindException) ex, request, response, handler);
    			}
    			else if (ex instanceof NoHandlerFoundException) {
    				return handleNoHandlerFoundException(
    						(NoHandlerFoundException) ex, request, response, handler);
    			}
    			else if (ex instanceof AsyncRequestTimeoutException) {
    				return handleAsyncRequestTimeoutException(
    						(AsyncRequestTimeoutException) ex, request, response, handler);
    			}
    		}
    		catch (Exception handlerEx) {
    			if (logger.isWarnEnabled()) {
    				logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
    			}
    		}
    		return null;
    	}
    
    

    点开其中一个异常的处理函数,可以看到就是设置了一个响应头并重定向到一个错误页面,并给页面设置了一些信息。
    在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值