SpringMVC

SpringMVC

一、
   springMVC作用在表现层,对请求参数进行处理,最会返回给浏览器响应结果,它通过一套注解让一个简单的java类成为处理请求的控制器,	   			而无需实现任何接口。同时支持RESTfull编程风格的请求。
   M:model 模型  javabean
   V:view  视图  jsp  
   C:controller  控制器  Servlet
   
springmvc与struts2区别
       共同点:
        它们都是表现层框架,都是基于 MVC 模型编写的。
        它们的底层都离不开原始 ServletAPI。
        它们处理请求的机制都是一个核心控制器。
        区别:
        Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter 
        Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所
        以 Spring MVC 会稍微比 Struts2 快些。(mvc是单例,s2是多例多了创建对象的时间所以慢)
        Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便
        (JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注
        解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
        Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提
        升,尤其是 struts2 的表单标签,远没有 html 执行效率高。

开启springmvc的环境搭建:
	   1.导入依赖
       2.在web.xml中写servlet映射
       3.在resources中写springmvc.xml
       4.配置服务器
    代码:
      1.在控制类上写@Controller注解
      2.在springmvc.xml中写扫描注解标签
            <context:component-scan base-package="cn.itcast"></context:component-scan>
      3.在需要执行的方法上写@RequestMapping注解【这个对应的也需要在springmvc.xml中】
      4.需要在web.xml中给servlet添加一个初始化参数把springmvc配置加载进去 
       		       	<init-param>
                      <param-name>contextConfigLocation</param-name>
                      <param-value>classpath:springmvc.xml</param-value>
                    </init-param>
                    <load-on-startup>1</load-on-startup>
     5.在执行的方法里面写return返回值(这个返回值就是对应的要跳转到的jsp页面的名称)
     6.用视图解析器才能完成跳转,需要在springmvc.xml中配置
             <!--视图解析器对象-->
         <bean id="internalResourceViewResolver" 			           						      		 						class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
             <property name="prefix" value="/WEB-INF/pages"></property>
             <property name="suffix" value=".jsp"></property>
         </bean>
@RequestMapping注解
      1. RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
      2. RequestMapping注解可以作用在方法和类上
            1. 作用在类上:第一级的访问目录
            2. 作用在方法上:第二级的访问目录
            3. 细节:路径可以不编写 / 表示应用的根目录开始
            4. 细节:${ pageContext.request.contextPath }也可以省略不写,但是路径上不能写 /
      3. RequestMapping的属性
            1. path 指定请求路径的url
            2. value value属性和path属性是一样的
            3. mthod 指定该方法的请求方式 method={RequestMethod.POST}
            4. params 指定限制请求参数的条件 params={"username"}代表要请求这个方法就必须带一个username的参数或					   username=heihei.
            5. headers 发送的请求中必须包含的请求头
            
请求参数的绑定
    1. 请求参数的绑定说明
        1. 绑定机制
            1. 表单提交的数据都是k=v格式的 username=haha&password=123
            2. SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
            3. 要求:提交表单的name和参数的名称是相同的
        2. 支持的数据类型
            1. 基本数据类型和字符串类型
            2. 实体类型(JavaBean) 
            3. 集合数据类型(List、map集合等)
    2. 基本数据类型和字符串类型
        1. 提交表单的name和参数的名称是相同的
        2. 区分大小写
    3. 实体类型(JavaBean的封装)
        1. 提交表单的name和JavaBean中的属性名称需要一致
        2. 如果一个JavaBean类中包含其他的引用类型,那么表单的name属性需要编写成:对象.属性 例如:address.name
	4. 给集合属性数据封装(集合封装)
		1. JSP页面编写方式:list[0].uname属性
		2. map['one'].uname
	5. 请求参数中文乱码的解决(解决从客户端传来中文字符时封装中文乱码)
        1. 在web.xml中配置Spring提供的过滤器类
            <!-- 配置过滤器,解决中文乱码的问题 -->
            <filter>
                <filter-name>characterEncodingFilter</filter-name> 
                <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter- class> 
                <!-- 指定字符集 -->
                <init-param>
                        <param-name>encoding</param-name>
                        <param-value>UTF-8</param-value>
                </init-param>
            </filter> 
            <filter-mapping> 
                <filter-name>characterEncodingFilter</filter-name>
                <url-pattern>/*</url-pattern> 
            </filter-mapping> 
	6. 自定义类型转换器
		1. 表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明Spring框架内部会默认进行			 型转换。
		2. 如果想自定义数据类型转换,可以实现Converter的接口(比如日期类等等格式不符合规定)
				1. 自定义类型转换器
                     
                        public class StringToDateConverter implements Converter<String, Date>{        
                            public Date convert(String source) { 
                                if(source == null) { 
                                    throw new RuntimeException("参数不能为空");
                                }
                                try {
                                        DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); // 解析字符串
                                        Date date = df.parse(source); 
                                        return date;
                                	} catch (Exception e) { 
                                		throw new RuntimeException("类型转换错误");
                                	}
                        		} 
                        }
				2. 注册自定义类型转换器,在springmvc.xml配置文件中编写配置
				<!-- 注册自定义类型转换器 --> 
                  <bean id="conversionService" 						 						   	        									class="org.springframework.context.support.ConversionServiceFactoryBean">   
                    <property name="converters"> 
                        <set>
                            <bean class="cn.itcast.utils.StringToDateConverter"/> 
                        </set> 
                    </property> 
                  </bean>
                  <!-- 开启Spring对MVC注解的支持 --> 
                  <mvc:annotation-driven conversion-service="conversionService"/>
	7. 在控制器中使用原生的ServletAPI对象
		1. 只需要在控制器的方法参数定义HttpServletRequest和HttpServletResponse对象
		
常用的注解	
	1. RequestParam注解(解决浏览器提交属性名与形参不对应的问题)
        1. 作用:把请求中的指定名称的参数传递给控制器中的形参赋值,
        2. 属性
            1. value:请求参数中的名称
            2. required:请求参数中是否必须提供此参数,默认值是true,必须提供
        3. 代码如下
        	@RequestMapping("/testRequestParam")
    		public String testRequestParam(@RequestParam(name = "name") String username){
                System.out.println("执行了....");
                System.out.println(username);
                return "success";
   			 }
   2. RequestBody注解
        1. 作用:用于获取请求体的内容(注意:get方法不可以,所以传表单)可以将返回数据转为json格式
        2. 属性
            1. required:是否必须有请求体,默认值是true
        3. 代码如下
        	 @RequestMapping("/testRequestBody")
            public String testRequestBody(@RequestBody String body){
                System.out.println("执行了....");
                System.out.println(body);
                return "success";
   			 }
   3. PathVariable注解
        1. 作用:拥有绑定url中的占位符的值。例如:url中有/delete/{id},{id}就是占位符
        2. 属性
            1. value:指定url中的占位符名称
		3. Restful风格的URL
            1. 请求路径一样可以根据不同的请求方式去执行后台的不同方法(规定不同的请求方式get post去找对应接收的getpost方法)
            2. restful风格的URL优点
                1. 结构清晰
                2. 符合标准
                3. 易于理解
				4. 扩展方便
		4. 代码如下
            @RequestMapping("/testPathVariable/{sid}")
            public String testPathVariable(@PathVariable(name = "sid") String id){
                System.out.println("执行了....");
                System.out.println(id);
                return "success";
            }

   4. RequestHeader注解
        1. 作用:获取指定请求头的值
        2. 属性
            1. value:请求头的名称
        3. 代码如下
            @RequestMapping(path="/hello")
            public String sayHello(@RequestHeader(value="Accept") String header) { 
                System.out.println(header);
                return "success";
            }
   5. CookieValue注解
        1. 作用:用于获取指定cookie的名称的值
        2. 属性
			1. value:cookie的名称
		3. 代码
			@RequestMapping(path="/hello") 
			public String sayHello(@CookieValue(value="JSESSIONID") String cookieValue) { 			 						System.out.println(cookieValue);
            	return "success";
               }
    6. ModelAttribute注解
        1. 作用
            1. 出现在方法上:表示当前方法会在控制器方法执行前线执行。
            2. 出现在参数上:获取指定的数据给参数赋值。
		2. 应用场景
            1. 当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。
		3. 具体的代码
			1. 修饰的方法有返回值
				作用在方法,先执行 
                @ModelAttribute 
                public User showUser(String uname){
                    System.out.println("showUser执行了");
                    //通过用户查询数据库(模拟)
                    User user = new User();
                    user.setUname(uname);
                    user.setAge(20);
                    user.setDate(new Date());
                    return user;
                }
                @RequestMapping("/testModelAttribute")
                public String testModelAttribute(User user){
                    System.out.println("testModelAttribute执行了....");
                    System.out.println(user);
                    return "success";
                }
            2. 修饰的方法没有返回值
             作用在方法,先执行
             @ModelAttribute
             public void showUser(String name,Map<String, User> map) { 
             System.out.println("showUser执行了..."); // 模拟从数据库中查询对象 
                 User user = new User(); 
                 user.setName("哈哈"); 
                 user.setPassword("123"); 
                 user.setMoney(100d);
                 map.put("abc", user);
             }
             修改用户的方法
             @RequestMapping(path="/updateUser") 
             public String updateUser(@ModelAttribute(value="abc") User user) { 
                 System.out.println(user);
                 return "success";
             }
        4. SessionAttributes注解
            1. 作用:用于多次执行控制器方法间的参数共享
            2. 属性
                1. value:指定存入属性的名称
			3. 代码如下
				   /**
             * SessionAttributes的注解
             * @return
             */
            @RequestMapping("/testSessionAttributes")
            public String testSessionAttributes(Model model){
                System.out.println("testSessionAttributes执行了....");
                //底层hui存储到request域中
                model.addAttribute("msg","meimei");

                return "success";
            }

            /**
             * 获取session中的值
             * @param modelMap
             * @return
             */
            @RequestMapping("/getSessionAttributes")
            public String getSessionAttributes(ModelMap modelMap){
                System.out.println("getSessionAttributes执行了....");
                //底层hui存储到request域中
                String msg = (String)modelMap.get("msg");
                System.out.println(msg);
                return "success";
            }

            /**
             * 删除session中的值
             * @param status
             * @return
             */
            @RequestMapping("/delSessionAttributes")
            public String delSessionAttributes(SessionStatus status){
                System.out.println("delSessionAttributes执行了....");
                status.setComplete();
                return "success";
            }	
二、响应数据和结果视图
	1. 返回值分类
		1. 返回字符串
			1. Controller方法返回字符串可以指定逻辑视图的名称,根据视图解析器为物理视图的地址。
			@RequestMapping("/testString")
            public String testString(Model model){
                System.out.println("testString执行了");
                //模拟从数据库添加信息
                User user = new User();
                user.setUsername("马海龙");
                user.setPassword("1234");
                user.setAge(20);
                //存起来
                model.addAttribute("user",user);
                return "success";
            }
		2. 返回值是void
            1. 如果控制器的方法返回值编写成void,执行程序报404的异常,默认查找JSP页面没有找到。
                1. 默认会跳转到@RequestMapping(value="/initUpdate") initUpdate的页面。
                2. 可以使用请求转发或者重定向跳转到指定的页面
                   /**
                 * 返回值是Void的测试
                 * 请求转发试一次请求,不用去编写项目的名称
                 * 重定向:response.sendRedirect(request.getContextPath()+"/WEB-INF/pages/xxx.jsp")
                 */
                @RequestMapping("/testVoid")
                public void testVoid(HttpServletResponse response, HttpServletRequest request) throws 						ServletException, IOException {
                    System.out.println("testVoid执行了");
                    //request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
                    //重定向
                    //response.sendRedirect(request.getContextPath()+"/index.jsp");
                    //直接会进行响应
                    //设置中文乱码
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("text/html;charset=UTF-8");
                    response.getWriter().print("你好");
                }
        3. 返回值是ModelAndView对象
            1. ModelAndView对象是Spring提供的一个对象,可以用来调整具体的JSP视图
            2. 具体的代码如下
            	 @RequestMapping("/testModelAndView")
                 public ModelAndView testModelAndView(){
                    System.out.println("testModelAndView执行");
                    ModelAndView mv = new ModelAndView();
                    //模拟从数据库添加信息
                    User user = new User();
                    user.setUsername("马海龙");
                    user.setPassword("1234");
                    user.setAge(20);
                    //把user对象存储到mv对象中,也会把user对象存入到request对象中
                    mv.addObject("user",user);
                    //跳转到哪个页面
                    mv.setViewName("success");
                    return mv;
                }
  2. SpringMVC框架提供的转发和重定向
      1. forward请求转发
          1. controller方法返回String类型,想进行请求转发也可以编写成     
      2. redirect重定向
		  1. controller方法返回String类型,想进行重定向也可以编写成
        @RequestMapping("/testForwardOrRedirect")
        public String testForwardOrRedirect(){
            System.out.println("testForwardOrRedirect执行了");

            //请求转发
            //return "forward:/WEB-INF/pages/success.jsp";
            //重定向
            return "redirect:/index.jsp";
        }
        
  3. ResponseBody响应json数据
  		1. DispatcherServlet会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而
    	  不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置
            1. mvc:resources标签配置不过滤
                1. location元素表示webapp目录下的包下的所有文件
                2. mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b
                <!-- 设置静态资源不过滤 -->
                <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> 
                <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 -->
                <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
        2. 使用@RequestBody获取请求体数据
        	$(function(){ // 绑定点击事件 
        	$("#btn").click(function(){ 
        			$.ajax({
                    	url:"user/testJson",
                        contentType:"application/json;charset=UTF-8", 																data:'{"addressName":"aa","addressNum":100}',
                        dataType:"json", type:"post",
                        success:function(data){ 
                        			alert(data); 
                        			alert(data.addressName);
                                    } 
                          }); 
                       }); 
                  });
                  
           @RequestMapping("/testJson") 
           public void testJson(@RequestBody String body) {
               System.out.println(body);
           }
        3. 使用@RequestBody注解把json的字符串转换成JavaBean的对象
        4. 使用@ResponseBody注解把JavaBean对象转换成json字符串,直接响应
        5. json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包
        
SpringMVC实现文件上传
	1. 文件上传的回顾(web基础的)
		1. 导入文件上传的jar包	
		<dependency> 
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version> 
        </dependency> 
        <dependency> 
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version> 
        </dependency>
        2. 编写文件上传的JSP页面
            <form action="user/fileupload" method="post" enctype="multipart/form-data"> 
                选择文件:<input type="file" name="upload"/><br/> 
                <input type="submit" value="上传文件"/> 
            </form>
        3. 编写文件上传的Controller控制器
                @RequestMapping("/fileupload1")
                public String fileupload1(HttpServletRequest request) throws Exception {
                    System.out.println("文件上传");
                    //使用fileupload组件完成文件上传
                    //上传位置
                    String path = request.getSession().getServletContext().getRealPath("/uploads/");
                    //判断,该路径是否存在
                    File file = new File(path);
                    if (!file.exists()){
                        //创建文件夹
                        file.mkdirs();
                    }
                    //解析request对象,获取上传的文件项
                    DiskFileItemFactory factory = new DiskFileItemFactory();
                    ServletFileUpload upload = new ServletFileUpload(factory);
                    //解析request
                    List<FileItem> items = upload.parseRequest(request);
                    //遍历
                    for (FileItem item:items) {
                        //进行判断,当前item对象是否是上传文件项
                        if (item.isFormField()){
                            //普通表单项
                        }else{
                            //说明是个上传文件项
                            //获取到上传文件名称
                            String filename = item.getName();
                            //完成上传文件
                            item.write(new File(path,filename));
                            //删除临时文件(大于10kb会有临时文件)
                            item.delete();
                        }
                    }
                    return "success";
                }
     2. SpringMVC传统方式文件上传
        1. SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的
        name属性名称相同。
        2. 代码如下
        	@RequestMapping("/fileupload2")
            public String fileupload2(HttpServletRequest request, MultipartFile upload) throws Exception {
                System.out.println("spring文件上传");
                //使用fileupload组件完成文件上传
                //上传位置
                String path = request.getSession().getServletContext().getRealPath("/uploads/");
                //判断,该路径是否存在
                File file = new File(path);
                if (!file.exists()){
                    //创建文件夹
                    file.mkdirs();
                }
                //说明是个上传文件项
                //获取到上传文件名称
                String filename = upload.getOriginalFilename();
                //把文件名设置唯一值,uuid
                String uuid = UUID.randomUUID().toString().replace("-", "");
                filename = uuid + "-" + filename;
                //完成上传文件
                upload.transferTo(new File(path,filename));
                return "success";
            }
        3. 配置文件解析器对象
        	  <bean id="multipartResolver" 			    												 							class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        		<property name="maxUploadSize" value="10485760"></property>
    		</bean>
    3. SpringMVC跨服务器方式文件上传
        1. 搭建图片服务器
            1. 根据文档配置tomcat9的服务器,现在是2个服务器
            2. 导入资料中day02_springmvc5_02image项目,作为图片服务器使用
        2. 实现SpringMVC跨服务器方式文件上传
            1. 导入开发需要的jar包   	
            	<dependency> 
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId> 
                    <version>1.18.1</version> 
                </dependency>
                <dependency> 
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-client</artifactId>
                    <version>1.18.1</version>
                </dependency>
             2. 编写文件上传的JSP页面
                 <form action="user/fileupload3" method="post" enctype="multipart/form-data">
                     选择文件:<input type="file" name="upload"/><br/>
                     <input type="submit" value="上传文件"/>
                 </form>
             3.编写控制器
                @RequestMapping("/fileupload3")
                public String fileupload3( MultipartFile upload) throws Exception {
                    System.out.println("spring跨服务器文件上传");

                    //定义上传文件服务器路径
                    String path = "http://localhost:9090/uploads/";//图片服务器地址需要创建uploads文件夹
                    //说明是个上传文件项
                    //获取到上传文件名称
                    String filename = upload.getOriginalFilename();
                    //把文件名设置唯一值,uuid
                    String uuid = UUID.randomUUID().toString().replace("-", "");
                    filename = uuid + "-" + filename;
                    //完成上传文件

                    //创建客户端对象
                    Client client = Client.create();
                    //和图片服务器进行连接
                    WebResource webResource = client.resource(path + filename);
                    //上传文件到服务器
                    webResource.put(upload.getBytes());
                    return "success";
                 }
                 
 SpringMVC的异常处理
 	1. 异常处理思路
		1. Controller调用service,service调用dao,异常都是向上抛出的最终有,DispatcherServlet找异常处理器进行异常的处理
		   要配置异常处理器在xml文件中。
	2. SpringMVC的异常处理
		1. 编写自定义异常类(做提示信息的)
           public class SysException extends Exception {
                //存储提示信息的
                private  String message;
                public SysException(String message) {
                    this.message = message;
                }

                @Override
                public String getMessage() {
                    return message;
                }

                public void setMessage(String message) {
                    this.message = message;
                }

              }
         2. 自定义异常处理器
         	public class SysExceptionResolver implements HandlerExceptionResolver {
                @Override
                public ModelAndView resolveException(HttpServletRequest httpServletRequest, 													HttpServletResponse httpServletResponse, Object o, Exception ex) {
                    //获取到异常对象
                    SysException e = null;
                    if (ex instanceof SysException){
                        e = (SysException)ex;
                    }else{
                        e = new SysException("系统正在维护...");
                    }
                    //创建ModelAndView对象
                    ModelAndView mv = new ModelAndView();
                    mv.addObject("errorMsg",e.getMessage());
                    mv.setViewName("error");
                    return mv;
                }
            }
        3.配置异常处理器
        	<bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"/>
 
 SpringMVC框架中的拦截器
 	1.拦截器的概述
        1. SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。(Filter)
        2. 可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链
        中的拦截器会按着定义的顺序执行。
        3. 拦截器和过滤器的功能比较类似,有区别
   			1. 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
            2. 拦截器是SpringMVC框架独有的。
            3. 过滤器配置了/*,可以拦截任何资源。
            4. 拦截器只会对控制器中的方法进行拦截。(controller)
 		4. 拦截器也是AOP思想的一种实现方式
		5. 想要自定义拦截器,需要实现HandlerInterceptor接口。
    2. 自定义拦截器步骤
		1. 创建类,实现HandlerInterceptor接口,重写需要的方法
            public class MyInterceptor1 implements HandlerInterceptor {
                /**
                 *预处理,controller方法执行之前执行
                 * return true放行,执行下一个拦截器,如果没有执行controller方法
                 * return false不放行,直接跳到某个页面
                 */
                @Override
                public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object 																			handler) throws Exception {
                    System.out.println("MyInterceptor1执行了....前");
                    //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
                    return true;
                }
                /*
                 * 后处理方法,controller方法执行后,success.jsp执行前
                 */
                @Override
                public void postHandle(HttpServletRequest request, HttpServletResponse response, Object 												handler, ModelAndView modelAndView) throws Exception {
                    System.out.println("MyInterceptor1执行了....后");
                    //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
                }
                /**
                 *success.jsp页面执行后,该方法会执行
                 */
                @Override
                public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object 														handler, Exception ex) throws Exception {
                    System.out.println("MyInterceptor1执行了....最后");
                }
            }
		2. 在springmvc.xml中配置拦截器类
			<!--配置拦截器-->
            <mvc:interceptors>
                <mvc:interceptor>
                    <!--你要拦截具体的方法-->
                    <mvc:mapping path="/user/*"/>
                    <!--你不拦截的方法
                    <mvc:exclude-mapping path=""/>-->
                    <!--配置拦截器对象-->
                    <bean class="cn.itcast.interceptor.MyInterceptor1"></bean>
                </mvc:interceptor>
            </mvc:interceptors>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值