mapengpeng1999@163.com 国际化,文件上传,拦截器,异常处理

国际化

国际化概述

国际化并不是对内容进行国际化,而是对本身要显示的文字进行国际化,需要在SpringIOC容器中配置国际化资源文件的bean,国际化是具体通过Locale完成。SpringMVC为国际化提供了本地化拦截器和本地化解析器,以便完成通过超链接切换方式完成国际化。
- 本地化解析器:SessionLocaleResovler
- 本地化拦截器:LocaleChangeInterceptor

在这里插入图片描述

通过超链接切换国际化

配置国际化资源文件:
基础文件,i18n.properties
username=mapengpeng
usergender=male
国际化中文文件,i18n_zh_CN.properties
username=\u9A6C\u9E4F\u9E4F
usergender=\u7537
国际化英文文件,i18n_en_US.properties
username=pengpengma
usergender=Man
配置国际化资源文件的bean
 <!-- 配置国际化资源文件的bean -->
	  <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
	  	<property name="basename" value="i18n"></property>
	  </bean>
配置本地化解析器和本地化拦截器
<!-- 本地化解析器 -->
<bean id="localeResolver" 		                   class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
</bean>
	<!-- 本地化拦截器,这个bean被mvc标签包起来了,外部bean不能引用它,即就是内部bean不用bean id -->
	<mvc:interceptors>
	<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
	</mvc:interceptors>
编写index.jsp
<a href=""${pageContext.request.contextPath}/testChangeLanguage?locale=zh_CN">中文</a>
<br>
<a href=""${pageContext.request.contextPath}/testChangeLanguage?locale=en_US">English</a>
<br>
编写请求处理器:
package com.wanbangee.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LocaleController {
	@RequestMapping("testChangeLanguage")
	public String testChangeLanguage() {
		return "success";
	}
}
编写success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib  prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<fmt:message key="username"></fmt:message>
	<fmt:message key="usergender"></fmt:message>
</body>
</html>

总结

掌握通过切换超链接完成国际化操作:
·在之前的国际化基础上配置SessionLocaleResolver的bean
·配置 Interceptor:LocaleChangeInterceptor
·需要添加请求参数,参数名称为locale,值为本地化语言,比如zh_CN,表示简体中文,en_US,表示美式英文

文件上传

掌握在SpringMVC中使用FileUpload上传组件

SpringMVC中,为文件的上传提供了直接的支持,这种支持是使用可插拔的组件MultipartResolver实现的,
SPring使用FileUpload实现了MultipartResolver接口,实现类名称叫做CommonsMultipartResolver,SpringMVC上下文中并没有默认装配MultipartResolver,因此默认情况是进行不了文件上传的,
需要我们在SpringMVC上下文中配置MultipartResolver这个bean。
1.配置文件上传的解析器:在springmvc-servlet.xml配置文件中配置。
<!-- 配置文件上传的解析器MultipartResolver -->
	  <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	  	<property name="defaultEncoding" value="utf-8"></property>
	  	<!-- 文件上传的最大字节数"#{5*1024*1024}"使用SpringMVC的EL表达式 -->
	  	<property name="maxUploadSize" value="#{5*1024*1024}"></property>
	  </bean>
2.添加jar包
commons-fileupload-1.2.1.jar
commons-io-2.0.jar
3.编写文件上传的表单
	<form action="testFileUpLoad" method="post" enctype="multipart/form-data">
		empName:<input type="text" name="empName">
		empMail:<input type="text" name="empMail">
		empPic:<input type="file" name="empPic1">
		empPic:<input type="file" name="empPic2">
		empPic:<input type="file" name="empPic3">
		<input type="submit" value="Submit">
	</form>
编写请求处理器
package com.wanbangee.controller;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import com.wanbangee.util.NewFileName;
@Controller
public class FileUploadConttroller {
	@RequestMapping("testFileUpLoad")
	public String testFileUpLoad(String empName,String empMail,MultipartFile empPic1,MultipartFile empPic2,MultipartFile empPic3,HttpServletRequest request) throws Exception {
		System.out.println(empName);
		System.out.println(empMail);		
		System.out.println(empPic1.getName()); //获得请求参数名(就是文本框名称)
		System.out.println(empPic2.getOriginalFilename());//获得文件名称(文件名加文件的后缀名)
		System.out.println(empPic3.getSize());//获得文件的大小
		//获得上传文件的后缀名
		String names1[] = empPic1.getOriginalFilename().split("\\.");
		String name1 = names1[names1.length-1];//取得数组中最后一位元素
		String names2[] = empPic2.getOriginalFilename().split("\\.");
		String name2 = names2[names2.length-1];
		String names3[] = empPic3.getOriginalFilename().split("\\.");
		String name3 = names3[names3.length-1];
		
		String path = request.getServletContext().getRealPath("/file");
		//获得file文件夹的真实路径,就是在硬盘中位置,如D:\Tomcat\apache-tomcat-8.5.55\wtpwebapps\SpringMVC02\file
		File file1 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name1);
		File file2 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name2);
		File file3 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name3);
		empPic1.transferTo(file1);//将上传文件另存为一个新的文件
		empPic2.transferTo(file2);//将上传文件另存为一个新的文件
		empPic3.transferTo(file3);//将上传文件另存为一个新的文件
		return "success";
	}
}

在util包下写个工具类NewFileName用来获取新的文件名
package com.wanbangee.util;
import javax.servlet.http.HttpServletRequest;
public class NewFileName {
	public static String getNewFileName(HttpServletRequest request) {
		StringBuffer sb = new StringBuffer();
		// IPV6 地址是以冒号分割,IPV4以.分割
	String localAddr = request.getLocalAddr().replaceAll(":", "").replaceAll("\\.", "");
		sb.append(localAddr);
		Long currentTime = System.currentTimeMillis();//取得时间戳
		sb.append(currentTime);
		//三位随机数
		for(int i = 0;i<3;i++) {
			int random = (int) Math.round(Math.random()*9);
			sb.append(random);
		}
		return sb.toString();
	}
}
使用字节输出流方式,
package com.wanbangee.controller;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import com.wanbangee.util.NewFileName;
@Controller
public class FileUploadConttroller {
	@RequestMapping("testFileUpLoad")
	public String testFileUpLoad(String empName,String empMail,MultipartFile empPic1,MultipartFile empPic2,MultipartFile empPic3,HttpServletRequest request) throws Exception {
		System.out.println(empName);
		System.out.println(empMail);		
		System.out.println(empPic1.getName()); //获得请求参数名(就是文本框名称)
		System.out.println(empPic2.getOriginalFilename());//获得文件名称(文件名加文件的后缀名)
		System.out.println(empPic3.getSize());//获得文件的大小
		//获得上传文件的后缀名
		String names1[] = empPic1.getOriginalFilename().split("\\.");
		String name1 = names1[names1.length-1];//取得数组中最后一位元素
		String names2[] = empPic2.getOriginalFilename().split("\\.");
		String name2 = names2[names2.length-1];
		String names3[] = empPic3.getOriginalFilename().split("\\.");
		String name3 = names3[names3.length-1];
		
		String path = request.getServletContext().getRealPath("/file");
		//获得file文件夹的真实路径,就是在硬盘中位置,如D:\Tomcat\apache-tomcat-8.5.55\wtpwebapps\SpringMVC02\file
		File file1 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name1);
		File file2 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name2);
		File file3 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name3);
		
		InputStream in1 = empPic1.getInputStream();
//		byte b1[] = new byte[(int)empPic1.getSize()];
//		byte b1[] = empPic1.getBytes();
//		in1.read(b1);
		OutputStream ps1 = new FileOutputStream(file1,true);
		int temp = 0;
		while((temp = in1.read()) != -1) {
			ps1.write(temp);
		}
				
		InputStream in2 = empPic2.getInputStream();
//		byte b2[] = new byte[(int)empPic2.getSize()];
//		byte b2[] = empPic2.getBytes();
//		in2.read(b2);
		OutputStream ps2 = new FileOutputStream(file2,true);
		temp = 0;
		while((temp = in2.read()) != -1) {
			ps2.write(temp);
		}
			
		InputStream in3 = empPic3.getInputStream();
//		byte b3[] = new byte[(int)empPic3.getSize()];
//		byte b3[] = empPic3.getBytes();
//		in3.read(b3);
		OutputStream ps3 = new FileOutputStream(file3,true);
		temp = 0;
		while((temp = in3.read()) != -1) {
			ps3.write(temp);
		}
		return "success";
	}
}

总结

对于文件上传来说,主要的就是要懂得如何配置,及之前的IO的操作需要熟悉。

拦截器

最擅长使用拦截器框架就是Struts2,因为Struts2的几乎所有的功能都是靠Struts2的拦截器完成的。
SpringMVC里面也有定义一些拦截器,完成一些功能,比如国际化定义的本地化拦截器。
SPringMVC的拦截器会对请求进行拦截,用户也可以自定义拦截器来实现特定的功能,
SpringMVC中提供了一个拦截器的接口:HandlerInterceptor,所有的拦截器都必须实现这个接口:
- preHandle():在执行处理器的目标方法之前执行,可以对用户的请求进行处理。
- postHandle() : 这个方法在执行处理器的目标方法之后,渲染视图之前执行,可以对请求的响应进行处理。
- afterCompletion():这个方法是在执行目标方法和渲染视图之后执行,这个方法可以进行请求资源的清理。
1.拦截器配置,在springmvc-servlet.xml配置文件中配置。
<!-- 本地化拦截器 -->
	<mvc:interceptors>
	<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
		<!-- 自定义拦截器配置,放在mvc:interceptors里面的拦截器,会拦截所有的请求 -->
		<bean class="com.wanbangee.interceptor.Interceptor01"></bean>
	</mvc:interceptors>
2.编写拦截器(注意过滤器和拦截器的比较)
package com.wanbangee.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class Interceptor01 implements HandlerInterceptor {
	//preHandle : 在执行处理器的目标方法之前执行,可以对用户的请求进行处理
	//该方法的返回值如果为false,表示请求不再向下执行。return true;请求继续向下执行
	//这里有request对象和response对象,可以在这里做一些验证,非法参数,统一编码....
		@Override
		public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
				throws Exception {
			System.out.println("preHandle---------------------Interceptor01");
			return true;
		}
	//postHandle  这个方法在执行处理器的目标方法之后,渲染视图之前执行,可以对请求的响应进行处理。
	//可以对数据模型或视图信息进行一些业务上的处理。
		@Override
		public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
				ModelAndView modelAndView) throws Exception {
			System.out.println("postHandle------------Interceptor01");
		}
	//afterCompletion 这个方法是在执行目标方法和渲染视图之后执行,可以进行请求资源的清理,释放一些资源。
		@Override
		public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
				throws Exception {
			System.out.println("afterCompletion---------------Interceptor01");
		}
}

3.为了便于测试三个方法之间的关系,在文件上传类FileUploadConttroller中加上两行输出语句。
package com.wanbangee.controller;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import com.wanbangee.util.NewFileName;
@Controller
public class FileUploadConttroller {
	@RequestMapping("testFileUpLoad")
	public String testFileUpLoad(String empName,String empMail,MultipartFile empPic1,MultipartFile empPic2,MultipartFile empPic3,HttpServletRequest request) throws Exception {
		System.out.println("FileUpLoadController..... start");
		//获得上传文件的后缀名
		String names1[] = empPic1.getOriginalFilename().split("\\.");
		String name1 = names1[names1.length-1];
		String names2[] = empPic2.getOriginalFilename().split("\\.");
		String name2 = names2[names2.length-1];
		String names3[] = empPic3.getOriginalFilename().split("\\.");
		String name3 = names3[names3.length-1];
		String path = request.getServletContext().getRealPath("/file");
		File file1 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name1);
		File file2 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name2);
		File file3 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name3);
		empPic1.transferTo(file1);//另存为
		empPic2.transferTo(file2);//另存为
		empPic3.transferTo(file3);//另存为
		System.out.println("FileUpLoadController..... end");
		return "success";
	}
}
运行结果发现:
//preHandle : 在执行处理器的目标方法之前执行,可以对用户的请求进行处理
//该方法的返回值如果为false,表示请求不再向下执行。return true;请求继续向下执行
//postHandle() : 这个方法在执行处理器的目标方法之后,渲染视图之前执行,可以对请求的响应进行处理。
//afterCompletion():这个方法是在执行目标方法和渲染视图之后执行,这个方法可以进行请求资源的清理。

一个拦截器的执行顺序:

在这里插入图片描述

多个拦截器执行顺序

如果有两个拦截器呢,而且preHandler都返回true?

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第一个拦截器的代码,Interceptor01
package com.wanbangee.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class Interceptor01 implements HandlerInterceptor {
	//preHandle : 在执行处理器的目标方法之前执行,可以对用户的请求进行处理
	//该方法的返回值如果为false,表示请求不再向下执行。return true;请求继续向下执行
		@Override
		public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
				throws Exception {
			System.out.println("preHandle1---------------------Interceptor01");
			return true;
		}
	//postHandle  这个方法在执行处理器的目标方法之后,渲染视图之前执行,可以对请求的响应进行处理。
		@Override
		public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
				ModelAndView modelAndView) throws Exception {
			System.out.println("postHandle1------------Interceptor01");
		}
	//afterCompletion 这个方法是在执行目标方法和渲染视图之后执行,这个方法可以进行请求资源的清理。
		@Override
		public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
				throws Exception {
			System.out.println("afterCompletion1---------------Interceptor01");
		}
}
第二个拦截器的代码,Interceptor02
package com.wanbangee.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class Interceptor02 implements HandlerInterceptor {
	//preHandle : 在执行处理器的目标方法之前执行,可以对用户的请求进行处理
		//该方法的返回值如果为false,表示请求不再向下执行。return true;请求继续向下执行
			@Override
			public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
					throws Exception {
				System.out.println("preHandle2---------------------Interceptor02");
				return true;
			}
		//postHandle  这个方法在执行处理器的目标方法之后,渲染视图之前执行,可以对请求的响应进行处理。
			@Override
			public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
					ModelAndView modelAndView) throws Exception {
				System.out.println("postHandle2------------Interceptor02");
			}
		//afterCompletion 这个方法是在执行目标方法和渲染视图之后执行,这个方法可以进行请求资源的清理。
			@Override
			public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
					throws Exception {
				System.out.println("afterCompletion2---------------Interceptor02");
			}
}
//将这两个自定义拦截器放到本地化拦截器中
<!-- 本地化拦截器 -->
	<mvc:interceptors>
	<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
		<!-- 自定义拦截器配置 -->
		<bean class="com.wanbangee.interceptor.Interceptor01"></bean>
		<bean class="com.wanbangee.interceptor.Interceptor02"></bean>
	</mvc:interceptors>

3.为了便于测试三个方法之间的关系,在文件上传类FileUploadConttroller中加上两行输出语句。
package com.wanbangee.controller;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import com.wanbangee.util.NewFileName;
@Controller
public class FileUploadConttroller {
	@RequestMapping("testFileUpLoad")
	public String testFileUpLoad(String empName,String empMail,MultipartFile empPic1,MultipartFile empPic2,MultipartFile empPic3,HttpServletRequest request) throws Exception {
		System.out.println("FileUpLoadController..... start");
		//获得上传文件的后缀名
		String names1[] = empPic1.getOriginalFilename().split("\\.");
		String name1 = names1[names1.length-1];
		String names2[] = empPic2.getOriginalFilename().split("\\.");
		String name2 = names2[names2.length-1];
		String names3[] = empPic3.getOriginalFilename().split("\\.");
		String name3 = names3[names3.length-1];
		String path = request.getServletContext().getRealPath("/file");
		File file1 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name1);
		File file2 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name2);
		File file3 = new File(path + File.separator + NewFileName.getNewFileName(request) + "." + name3);
		empPic1.transferTo(file1);//另存为
		empPic2.transferTo(file2);//另存为
		empPic3.transferTo(file3);//另存为
		System.out.println("FileUpLoadController..... end");
		return "success";
	}
}

如果有两个拦截器呢,而且第一个拦截器的preHandle方法返回false?
在这里插入图片描述

如果有两个拦截器呢,而且第一个拦截器的preHandle方法返回true,第二个拦截器的preHandler返回false?
在这里插入图片描述

由此得出结论:preHandle方法执行,并且返回true,则一定会执行对象拦截器的afterCompletion方法。

拦截器配置细节

<!-- 本地化拦截器 -->
	<mvc:interceptors>
	<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
		<!-- 自定义拦截器配置 -->
		<bean class="com.wanbangee.interceptor.Interceptor01"></bean>
		<bean class="com.wanbangee.interceptor.Interceptor02"></bean>
	</mvc:interceptors>
	
以上的配置,表示所有经过DispatcherServlet的请求都会执行配置的三个拦截器,
但是实际情况可能是,这个拦截器只对特定的请求才进行拦截。

<!-- 拦截器的配置:mvc:interceptors : 配置一组拦截器,此组拦截器会拦截所有的请求-->
<mvc:interceptors>
	<!-- 本地化拦截器 -->
	<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>
	<!-- 自定义拦截器配置
			- 定义内部bean
			- 引用外部bean 
	-->
<!-- 配置单个拦截器的细节,拦截器只会拦截符合配置规范的请求 
		- mvc:mapping 表示拦截器会拦截的请求
			- 可以是具体的请求地址
			- 可以是/* 表示所有请求
		- mvc:exclude-mapping 表示不拦截的请求
				
	- bean 表示配置一个内部bean ,必须是一个拦截器,但是不能和 ref 同时配置
				
	- 配置顺序 
		mvc:mapping 前
		mvc:exclude-mapping 中
		bean/ref 后
-->
		<mvc:interceptor>
			<mvc:mapping path="/*"/>
			<mvc:exclude-mapping path="/testFileUpLoad"/>
			<bean class="com.wanbangee.interceptor.FirstInterceptor"></bean>
		</mvc:interceptor>
		
		<mvc:interceptor>
			<mvc:mapping path="/testFileUpLoad"/>
			<bean class="com.wanbangee.interceptor.FirstInterceptor2"></bean>
		</mvc:interceptor>
	</mvc:interceptors>

异常处理

ExceptionHandler注解(重要)

在程序开发的时候,控制层难免会出现异常,出现异常之后直接在页面显示500肯定不合适,如果显示的内容不是500,
而是一些友好的提示性信息,比较合适。SpringMVC上下文中,已经装备好了异常解析器对象:ExceptionHandlerExceptionResolver,所有的其他装配的异常解析器都是ExceptionHandlerExceptionResolver的子类。

异常处理步骤:

1.在欢迎界面index.jsp
<a href="testExceptionHandler">test ExceptionHandler</a>
2.控制器中
@Controller
public class FileUploadConttroller {
	
	@RequestMapping("testExceptionHandler")
	public String testExceptionHandler() {
		System.out.println("testExceptionHandler");
		System.out.println(1/1);
		
		int arr[] = {1,2,3};
		System.out.println(arr[1]);
		
		Emp emp = new Emp();
		System.out.println(emp.getEmpName().length());
		
		return "success";
	}
3.编写异常处理器MyExceptionHandler,来处理指定异常类型
package com.wanbangee.exception;
import java.sql.SQLException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice//此注解用来专门处理后端异常的程序
public class MyExceptionHandler {

	//表示这个异常处理的方法能够处理 算术异常和sql异常
	@ExceptionHandler({ArithmeticException.class,SQLException.class})
	public String handlerArithmeticException() {
		System.out.println("后端出现了算术异常");
		return "error";
	}
	
	//表示这个异常处理的方法能够处理 数组下标越界异常
	@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
	public String handlerArrayIndexOutOfBoundsException() {
		System.out.println("后端出现了数组下标越界异常");
		return "error";
	}
	
	//表示这个异常处理的方法能够处理所有异常,没有出现上述单独标出的异常就会执行这个总异常
	//和所放前后顺序无关,就算这个总异常放在最前面,要是出现单独标出的异常如数组下标越界异常,也是先执行单独标出的数组下标越界异常
	@ExceptionHandler(Exception.class)
	public String handlerException() {
		System.out.println("后端出现了异常");
		return "error";
	}
}

4.编写视图解析器error.jsp
<h1>网站正在建设中!!!</h1>

总结

对于ExceptionHandler注解的使用要非常熟悉
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值