SpringMVC框架(2/3)

第1章 响应数据和结果视图

1.1 返回值分类

1.1.1 字符串
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。 
//指定逻辑视图名,经过视图解析器解析为jsp物理路径:/WEB-INF/pages/success.jsp 
@RequestMapping("/testReturnString") 
	public String testReturnString() { System.out.println("AccountController的testReturnString 方法执行了。。。。"); 
	return "success"; } 

运行结果:
在这里插入图片描述

1.1.2 void

在之前的学习中,我们知道Servlet原始API可以作为控制器中方法的参数:
@RequestMapping("/testReturnVoid") public void testReturnVoid(HttpServletRequest request,HttpServletResponse response) throws Exception { } 在controller方法形参上可以定义request和response,使用request或response指定响应结果:
1、使用request转向页面,如下:

request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response); 

2、也可以通过response页面重定向:

 response.sendRedirect("testRetrunString")

3、也可以通过response指定响应结果,例如响应json数据:

response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8"); 
response.getWriter().write("json串");
1.1.3 ModelAndView

ModelAndView是SpringMVC为我们提供的一个对象,该对象也可以用作控制器方法的返回值。 该对象中有两个方法:

示例代码:

 /** * 返回ModeAndView * @return */ @RequestMapping("/testReturnModelAndView") 
 public ModelAndView testReturnModelAndView() { 
 	ModelAndView mv = new ModelAndView(); 
 	mv.addObject("username", "张三"); mv.setViewName("success");
return mv; } 

响应的jsp代码:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 
<!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>执行成功</title> 
</head> 
<body> 
	执行成功! ${requestScope.username} 
</body> 
</html> 

输出结果:
注意: 我们在页面上上获取使用的是requestScope.username取的,所以返回ModelAndView类型时,浏览器跳转只能是请求转发。

1.2 转发和重定向

1.2.1 forward转发

controller方法在提供了String类型的返回值之后,默认就是请求转发。我们也可以写成:

/** * 转发 * @return */ @RequestMapping("/testForward") 
public String testForward() { 
	System.out.println("AccountController的testForward 方法执行了。。。。");
	return "forward:/WEB-INF/pages/success.jsp"; }

需要注意的是,如果用了formward:则路径必须写成实际视图url,不能写逻辑视图。 它相当于“request.getRequestDispatcher(“url”).forward(request,response)”。使用请求转发,既可以转发到jsp,也可以转发到其他的控制器方法。

1.2.2 Redirect重定向

contrller方法提供了一个String类型返回值之后,它需要在返回值里使用:redirect:

 /** * 重定向 * @return */ 
 @RequestMapping("/testRedirect") 
 public String testRedirect() { 
 	System.out.println("AccountController的testRedirect 方法执行了。。。。"); 
 	return "redirect:testReturnModelAndView"; } 

它相当于“response.sendRedirect(url)”。需要注意的是,如果是重定向到jsp页面,则jsp页面不能写在WEB-INF目录中,否则无法找到。

1.3 ResponseBody响应json数据

1.3.1 使用说明

作用: 该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端

1.3.2 使用示例 需求: 使用@ResponseBody注解实现

将controller方法返回对象转换为json响应给客户端。 前置知识点: Springmvc默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包。
注意:2.7.0以下的版本用不了

jsp中的代码:

 <script type="text/javascript" 
 src="${pageContext.request.contextPath}/js/jquery.min.js">
 </script> 
 <script type="text/javascript"> 
 $(function(){ $("#testJson").click(function(){ $.ajax({ type:"post", 
 url:"${pageContext.request.contextPath}/testResponseJson", 
 contentType:"application/json;charset=utf-8", 
 data:'{"id":1,"name":"test","money":999.0}', dataType:"json", 
 success:function(data){ alert(data); } }); }); }) 
 </script> ```
 <!-- 测试异步请求 --> 

```java
 <input type="button" value="测试ajax请求json和响应json" id="testJson"/> 
 控制器中的代码: /** * 响应json数据的控制器 * * @Version 1.0 */ @Controller("jsonController") 
 public class JsonController { /** * 测试响应json数据 */ 
 @RequestMapping("/testResponseJson") 
 public @ResponseBody Account testResponseJson(@RequestBody Account account) { 
 System.out.println("异步请求:"+account); return account; } } 

运行结果:

第2章 SpringMVC实现文件上传

2.1 文件上传的回顾

2.1.1 文件上传的必要前提 A form表单的enctype取值必须是:multipart/form-data (默认值是:application/x-www-form-urlencoded) enctype:是表单请求正文的类型 B method属性取值必须是Post C 提供一个文件选择域
2.1.2 文件上传的原理分析 当form表单的enctype取值不是默认值后,request.getParameter()将失效。 enctype=”application/x-www-form-urlencoded”时,form表单的正文内容是: key=value&key=value&key=value 当form表单的enctype取值为Mutilpart/form-data时,请求正文内容就变成: 每一部分都是MIME类型描述的正文 -----------------------------7de1a433602ac 分界符 Content-Disposition: form-data; name=“userName” 协议头

aaa 协议的正文 -----------------------------7de1a433602ac Content-Disposition: form-data; name=“file”; filename=“C:\Users\zhy\Desktop\fileupload_demofile\b.txt” Content-Type: text/plain 协议的类型(MIME类型) bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -----------------------------7de1a433602ac–
2.1.3 借助第三方组件实现文件上传 使用Commons-fileupload组件实现文件上传,需要导入该组件相应的支撑jar包:Commons-fileupload和commons-io。commons-io 不属于文件上传组件的开发jar文件,但Commons-fileupload 组件从1.1 版本开始,它工作时需要commons-io包的支持。
2.2 springmvc传统方式的文件上传
2.2.1 说明 传统方式的文件上传,指的是我们上传的文件和访问的应用存在于同一台服务器上。 并且上传完成之后,浏览器可能跳转。
2.2.2 实现步骤
2.2.2.1 第一步:拷贝文件上传的jar包到工程的lib目录

2.2.2.2 第二步:编写jsp页面 名称:
图片:

2.2.2.3 第三步:编写控制器
 /** * 文件上传的的控制器 * @author 黑马程序员 * @Version 1.0 */ @Controller("fileUploadController") 
 public class FileUploadController { /** * 文件上传 */ @RequestMapping("/fileUpload") 
 public String testResponseJson(String picname,MultipartFile uploadFile,HttpServletRequest request) throws Exception{ //定义文件名 
 String fileName = ""; 
 //1.获取原始文件名 String uploadFileName = 
 uploadFile.getOriginalFilename(); 
 //2.截取文件扩展名 String extendName = uploadFileName.substring(uploadFileName.lastIndexOf(".")+1, 
 uploadFileName.length()); 
 //3.把文件加上随机数,防止文件重复 String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase(); 
 //4.判断是否输入了文件名 if(!StringUtils.isEmpty(picname)) { fileName = uuid+"_"+picname+"."+extendName; }else { fileName = uuid+"_"+uploadFileName; } System.out.println(fileName); 
 //2.获取文件路径

ServletContext context = request.getServletContext(); String basePath = context.getRealPath("/uploads"); //3.解决同一文件夹中文件过多问题 String datePath = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); 
//4.判断路径是否存在 File file = new File(basePath+"/"+datePath); if(!file.exists()) { file.mkdirs(); } 
//5.使用MulitpartFile接口中方法,把上传的文件写到指定位置 uploadFile.transferTo(new File(file,fileName)); return "success"; } }
2.2.2.4 第四步:配置文件解析器
<!-- 配置文件上传解析器 --> <bean id="multipartResolver" <!-- id的值是固定的--> class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设置上传文件的最大尺寸为5MB --> <property name="maxUploadSize"> <value>5242880</value> </property> </bean>

注意: 文件上传的解析器id是固定的,不能起别的名称,否则无法实现请求参数的绑定。(不光是文件,其他字段也将无法绑定)

2.3 springmvc跨服务器方式的文件上传

2.3.1 分服务器的目的

在实际开发中,我们会有很多处理不同功能的服务器。例如: 应用服务器:负责部署我们的应用 数据库服务器:运行我们的数据库 缓存和消息服务器:负责处理大并发访问的缓存和消息 文件服务器:负责存储用户上传文件的服务器。 (注意:此处说的不是服务器集群)
分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。

2.3.2 准备两个tomcat服务器,并创建一个用于存放图片的web工程

在文件服务器的tomcat配置中加入,允许读写操作。文件位置:
加入内容:

加入此行的含义是:接收文件的目标服务器可以支持写入操作。

2.3.3 拷贝jar包 在我们负责处理文件上传的项目中拷贝文件上传的必备jar包
2.3.4 编写控制器实现上传图片
/** * 响应json数据的控制器  @Version 1.0 */ @Controller("fileUploadController2") public class FileUploadController2 { public static final String FILESERVERURL = "http://localhost:9090/day06_spring_image/uploads/"; 
/** * 文件上传,保存文件到不同服务器 */ 
@RequestMapping("/fileUpload2") public String testResponseJson(String picname,MultipartFile uploadFile) throws Exception{ 
//定义文件名 String fileName = ""; 
//1.获取原始文件名 
String uploadFileName = uploadFile.getOriginalFilename(); 
//2.截取文件扩展名 
String extendName =uploadFileName.substring(uploadFileName.lastIndexOf(".")+1, uploadFileName.length()); 
//3.把文件加上随机数,防止文件重复 String uuid = UUID.randomUUID().toString().replace("-", "").toUpperCase(); 
//4.判断是否输入了文件名 
	if(!StringUtils.isEmpty(picname)) { fileName = uuid+"_"+picname+"."+extendName; }else { fileName = uuid+"_"+uploadFileName; } System.out.println(fileName); 
//5.创建sun公司提供的jersey包中的Client对象 
	Client client = Client.create();
 //6.指定上传文件的地址,该地址是web路径 
 WebResource resource = client.resource(FILESERVERURL+fileName); 
//7.实现上传 
	String result = resource.put(String.class,uploadFile.getBytes()); 		
	System.out.println(result); return "success"; } }
2.3.5 编写jsp页面
名称:
图片:
2.3.6 配置解析器
<!-- 配置文件上传解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 设置上传文件的最大尺寸为5MB -->
<property name="maxUploadSize"> 
<value>5242880
</value>
 </property> 
 </bean>

第3章 SpringMVC中的异常处理

3.1 异常处理的思路

系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。 系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理,如下图:
3.2 实现步骤
3.2.1 编写异常类和错误页面 /** * 自定义异常
Controller
客户端
Service
Dao
Springmvc
DispatcherServlet
请求
异常
HandlerExceptionResolver
异常处理器
异常
异常

* * @Version 1.0 */ 
public class CustomException extends Exception { 
	private String message; 
	public CustomException(String message) { 
		this.message = message; } 
	public String getMessage() { 
		return message; } }
jsp页面: 

```html
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!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>执行失败</title> </head> <body> 执行失败! ${message } </body> </html>

3.2.2 自定义异常处理器

/** * 自定义异常处理器 * @author  * @CompaMr顺  * @Version 1.0 */ public class CustomExceptionResolver implements HandlerExceptionResolver { @Override

	public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { ex.printStackTrace(); CustomException customException = null; //如果抛出的是系统自定义异常则直接转换 
		if(ex instanceof CustomException){ 
			customException = (CustomException)ex; }
		else{ //如果抛出的不是系统自定义异常则重新构造一个系统错误异常。 
			customException = new CustomException("系统错误,请与系统管理 员联系!"); } 
		ModelAndView modelAndView = new ModelAndView(); 
		modelAndView.addObject("message", customException.getMessage()); 
		modelAndView.setViewName("error"); return modelAndView; } 
	}
3.2.3 配置异常处理器
<!-- 配置自定义异常处理器 --> 
<bean id="handlerExceptionResolver" 
class="com.itheima.exception.CustomExceptionResolver"/>

3.2.4 运行结果:

第4章 SpringMVC中的拦截器

4.1 拦截器的作用

Spring MVC 的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。 用户可以自己定义一些拦截器来实现特定的功能。 谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但是也有区别,接下来我们就来说说他们的区别: 过滤器是servlet规范中的一部分,任何java web工程都可以使用。
拦截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用。
过滤器在url-pattern中配置了/*之后,可以对所有要访问的资源拦截。 拦截器它是只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js是不会进行拦截的。 它也是AOP思想的具体应用。 我们要想自定义拦截器, 要求必须实现:HandlerInterceptor接口。

4.2 自定义拦截器的步骤

4.2.1 第一步:编写一个普通类实现HandlerInterceptor接口
/** * 自定义拦截器 * @author Mr顺 * * @Version 1.0 */ 
public class HandlerInterceptorDemo1 implements HandlerInterceptor { @Override 
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 	
	System.out.println("preHandle拦截器拦截了"); 
	return true; } 
@Override 
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

ModelAndView modelAndView) throws Exception { System.out.println("postHandle方法执行了"); } 
@Override 
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
	System.out.println("afterCompletion方法执行了"); } }

4.2.2 第二步:配置拦截器

<!-- 配置拦截器 --> 
<mvc:interceptors> 
<mvc:interceptor> 
<mvc:mapping path="/**"/> 
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1">
</bean> 
</mvc:interceptor> 
</mvc:interceptors>
4.2.3 测试运行结果:

4.3 拦截器的细节

4.3.1 拦截器的放行

放行的含义是指,如果有下一个拦截器就执行下一个,如果该拦截器处于拦截器链的最后一个,则执行控制器中的方法。

4.3.2 拦截器中方法的说明
public interface HandlerInterceptor { /** * 如何调用: * 按拦截器定义顺序调用 * 何时调用: * 只要配置了都会调用 * 有什么用: * 如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去 * 进行处理,则返回true。 * 如果程序员决定不需要再调用其他的组件去处理请求,则返回false。 */ 
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; }
/** * 如何调用: * 按拦截器定义逆序调用 * 何时调用: * 在拦截器链内所有拦截器返成功调用 * 有什么用: * 在业务处理器处理完请求后,但是DispatcherServlet向客户端返回响应前被调用, * 在该方法中对用户请求request进行处理。 */ 
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } /** * 如何调用: * 按拦截器定义逆序调用 * 何时调用: * 只有preHandle返回true才调用 * 有什么用: * 在 DispatcherServlet 完全处理完请求后被调用, * 可以在该方法中进行一些资源清理的操作。 */ 
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } } 

思考: 如果有多个拦截器,这时拦截器1的preHandle方法返回true,但是拦截器2的preHandle方法返回false,而此时拦截器1的afterCompletion方法是否执行?
4.3.3 拦截器的作用路径 作用路径可以通过在配置文件中配置。

<!-- 配置拦截器的作用范围 --> 
<mvc:interceptors> 
<mvc:interceptor> 
<mvc:mapping path="/**" />
<!-- 用于指定对拦截的url --> 
<mvc:exclude-mapping path=""/><!-- 用于指定排除的url--> 
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1">
</bean> 
</mvc:interceptor> </mvc:interceptors>

4.3.4 多个拦截器的执行顺序 多个拦截器是按照配置的顺序决定的。
4.4 正常流程测试
4.4.1 配置文件:

<!-- 配置拦截器的作用范围 --> 
<mvc:interceptors> <mvc:interceptor>
<mvc:mapping path="/**" /><!-- 用于指定对拦截的url --> 
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> 
<bean id="handlerInterceptorDemo2" 	class="com.itheima.web.interceptor.HandlerInterceptorDemo2">
</bean> 
</mvc:interceptor> 
</mvc:interceptors>
4.4.2 拦截器1的代码:
 /** * 自定义拦截器 * 
@author Mr顺 * 
@Company http://www.ithiema.com * @Version 1.0 */ 
public class HandlerInterceptorDemo1 implements HandlerInterceptor { @Override 
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
		System.out.println("拦截器1:preHandle拦截器拦截了"); return true; } @Override 
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
		System.out.println("拦截器1:postHandle方法执行了"); } @Override 
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
		System.out.println("拦截器1:afterCompletion方法执行了"); } }
4.4.3 拦截器2的代码:
 /** * 自定义拦截器 * 
@author Mr顺 * 
 * @Version 1.0 */ 
public class HandlerInterceptorDemo2 implements HandlerInterceptor { @Override 
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
	System.out.println("拦截器2:preHandle拦截器拦截了"); return true; } @Override 
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
	System.out.println("拦截器2:postHandle方法执行了"); } 
@Override 
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
	System.out.println("拦截器2:afterCompletion方法执行了"); } }

4.4.4 运行结果:

4.5 中断流程测试

4.5.1 配置文件:
<!-- 配置拦截器的作用范围 --> 
<mvc:interceptors> 
<mvc:interceptor> 
<mvc:mapping path="/**" />
<!-- 用于指定对拦截的url --> 
<bean id="handlerInterceptorDemo1" class="com.itheima.web.interceptor.HandlerInterceptorDemo1">
</bean> 
</mvc:interceptor> 
<mvc:interceptor> 
<mvc:mapping path="/**" /> 
<bean id="handlerInterceptorDemo2" class="com.itheima.web.interceptor.HandlerInterceptorDemo2">
</bean> 
</mvc:interceptor> 
</mvc:interceptors>`
4.5.2 拦截器1的代码:
 /** * 自定义拦截器 * @author Mr顺 * 
@Version 1.0 */ 
 public class HandlerInterceptorDemo1 implements HandlerInterceptor { @Override 
 	public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception { 
 	System.out.println("拦截器1:preHandle拦截器拦截了"); return true; } @Override 
 	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
 	System.out.println("拦截器1:postHandle方法执行了"); }

@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
	System.out.println("拦截器1:afterCompletion方法执行了"); } }

4.5.3 拦截器2的代码:

 /** * 自定义拦截器 * @author Mr顺 * 
* @Version 1.0 */ 
* public class HandlerInterceptorDemo2 implements HandlerInterceptor { 
* @Override 
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
System.out.println("拦截器2:preHandle拦截器拦截了"); 
return false; } 
@Override 
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
System.out.println("拦截器2:postHandle方法执行了"); } 
@Override 
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
System.out.println("拦截器2:afterCompletion方法执行了"); } }

4.5.4 运行结果:

4.6 拦截器的简单案例(验证用户是否登录)

4.6.1 实现思路

1、有一个登录页面,需要写一个controller访问页面
2、登录页面有一提交表单的动作。需要在controller中处理。
-----2.1、判断用户名密码是否正确
-----2.2、如果正确 向session中写入用户信息
-----2.3、返回登录成功。
3、拦截用户请求,判断用户是否登录
-----3.1、如果用户已经登录。放行
-----3.2、如果用户未登录,跳转到登录页面

4.6.2 控制器代码
//登陆页面 
@RequestMapping("/login") 
public String login(Model model)throws Exception{ return "login"; } 
//登陆提交 //userid:用户账号,pwd:密码 @RequestMapping("/loginsubmit") 
public String loginsubmit(HttpSession session,String userid,String pwd)throws Exception{ 
//向session记录用户身份信息 
	session.setAttribute("activeUser", userid); return "redirect:/main.jsp"; }

//退出 
@RequestMapping("/logout") 
public String logout(HttpSession session)throws Exception{ 
//session过期 
	session.invalidate(); 
	return "redirect:index.jsp"; }
4.6.3 拦截器代码
 public class LoginInterceptor implements HandlerInterceptor{ @Override 
 Public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果是登录页面则放行 
 if(request.getRequestURI().indexOf("login.action")>=0){ return true; } HttpSession session = request.getSession(); 
 //如果用户已登录也放行 
 if(session.getAttribute("user")!=null){ return true; } 
 //用户没有登录挑战到登录页面 
 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); return false; } }

在这里插入图片描述

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr顺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值