SpringMVC快速上手笔记!!!

SpringMVC

1. Springmvc入门案例

1.1 SpringMVC入门程序开发总结

  • 一次性工作
    • 创建工程,设置服务器,加载工程
    • 导入坐标
    • 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
    • SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
  • 多次工作
    • 定义处理请求的控制器类
    • 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)

1.2 入门案例工作流程分析

  • 启动服务器初始化过程
    • 服务器启动,执行ServletContainersInitConfig类,初始化web容器
    • 执行createServletApplicationContext方法,创建了WebApplicationContext对象
    • 加载SpringMvcConfig
    • 执行@ComponentScan加载对应的bean
    • 加载UserController,每个@RequestMapping的名称对应一个具体的方法
    • 执行getServletMappings方法,定义所有的请求都通过SpringMVC
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig{}
@Controller
public class UserController{
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("user save");
        return "{'info':'springmvc'}"
    }
}
  • 单次请求过程
    • 发送请求localhost/save
    • web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
    • 解析请求路径/save
    • 由/save匹配执行对应的方法save()
    • 执行save()
    • 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方

1.3 Controller加载控制与业务bean加载控制

  • SpringMVC相关bean(表现层bean)

  • Spring控制的bean

    • 业务bean(Service)
    • 功能bean(DataSource等)
  • SpringMVC相关bean加载控制

    • SpringMVC加载的bean对应的包均在com.itheima.controller包内
  • Spring相关bean加载控制

    • 方式一:Spring加载的bean设定扫描范围为com.itheim,排除掉controller包内的bean
    • 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包,dao包等
    • 方式三:不区分Spring和SpringMVC环境,加载到同一个环境中
  • 名称:@ComponentScan

  • 类型:类注解

  • 范例:

@Configuration
@ComponentScan(value="com.itheima",excludeFilters=@ComponentScan.Filter(type=FilterType.ANNOTATION,classes=Controller.class))
public class SpringConfig{}
  • 属性
    • excludeFilters:排除扫描路径中的bean,需要指定类别(type)与具体项(classes)
      • includeFilters:加载指定的bean,需要指定类别type与具体项(classes)
  • bean加载格式
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }
    
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
    
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

  • 简化开发
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{SpringConfig.class};
    }
}

1.4 PostMan简介

  • PostMan是一款功能强大的网页调试与发送网页Http请求的Chrome插件
  • 作用:常用于进行接口测试

2 请求与响应

2.1 设置请求映射路径

设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀

@Controller
@RequestMapping("/user")//请求映射路径。路径前缀
public class UserController{
    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("user save");
        return "{'info':'user save'}"
    }
    @RequestMapping("/delete")
    @ResponseBody
    public String delete(Integer id){
        System.out.println("user delete");
        return "{'info':'user save'}"
    }
}

2.2请求方式

  • Get请求:http://localhost/commonParam?name=itcast&age=15

相同名称可以映射,或者用@RequestParam

@RequestMapping("/commonParam")
@ResponseBody
public string commonParam(String name int age){
    System.out.println("普通参数传递 name ==>"+name);
    System.out.println("普通参数传递 age ==>"+age);
    return "{'module':'common param'}"
}
//输出为:普通参数传递 name ==>itcast
//		 普通参数传递 name ==>15

不同名称的映射

@RequestMapping("/commonParam")
@ResponseBody
public string commonParam(@RequestParam("name") String name1 int age){
    System.out.println("普通参数传递 name ==>"+name1);
    System.out.println("普通参数传递 age ==>"+age);
    return "{'module':'common param'}"
}
//输出为:普通参数传递 name ==>itcast
//		 普通参数传递 name ==>15
  • Post请求:

请求与Get类似,在Body里的x-www-form-urlencoded里面添加参数

为web容器添加过滤器并指定字符集,Spring-web包中提供了专用的字符过滤器

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{SpringConfig.class};
    }
    //中文乱码处理
	@0verride
	protected Filter[]getservletFilters(){
        CharacterEncodingFilter filter = new characterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
}

2.3 请求参数

  • 普通参数:url地址传参,地址参数名与形参形参变量名相同,定义形参即可接收参数
@RequestMapping("/commonParam")
@ResponseBody
public string commonParam(String name int age){
    System.out.println("普通参数传递 name ==>"+name);
    System.out.println("普通参数传递 age ==>"+age);
    return "{'module':'common param'}"
}
  • POJO参数:请求参数名和形参对象属性名相同,定义POJO类型形参即可接收参数
@RequestMapping("/pojoParam")
@ResponseBody
public string pojoParam(User user){
    System.out.println("pojo参数传递 user ==>"+user);
    return "{'module':'pojo param'}"
}
  • 嵌套POJO参数:POJO对象中包含对象
public class User{
    private String name;
    private int age;
    private Address address;
}
public class Addresss{
    private String province;
    private String city
}
@RequestMapping("/listpojoParam")
@ResponseBody
public string listpojoParam(List<User> list){    
    System.out.println("listpojo参数传递 list ==>"+list);    
    return "{'module':'listpojo param'}"
}
  • 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型形参即可接收参数
@RequestMapping("/arrayParam")
@ResponseBody
public string arrayParam(String[] likes){
    System.out.println("数组参数传递 likes ==>"+Arrays.toString(likes));
    return "{'module':'array param'}"
}
  • 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
@RequestMapping("/listParam")
@ResponseBody
public string listParam(@RequestParm List<String> likes){
    System.out.println("集合参数传递 likes ==>"+likes;
    return "{'module':'list param'}"
}
  • Json数据

    • 添加坐标
    • 设置发送的json的数据(请求body中添加json)
    • 开启SpringMVC多项辅助功能

    @EnableWebMVC:开启json转化成对象的功能

    @Configuration
    @ComponentScan("com.itheima.controller")
    @EnableWebMVC
    public class SpringMvcConfig{}
    
    • 设置接受json数据

    @RequestBody:将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次

    @RequestMapping("/listParamForJson")
    @ResponseBody
    public String listParamForJson(@RequestBody List<String> likes){
        System.out.println("list common(json)参数传递 list ==> "+likes);
        return "{'module':'list common for json param'}"
    }
    public String pojoParamForJson(@RequestBody User user){
        System.out.println("pojo(json)参数传递 user ==> "+user);
        return "{'module':'pojo for json param'}"
    }
    public String listpojoParamForJson(@RequestBody List<User> list){
        System.out.println("list pojo(json)参数传递 list ==> "+list);
        return "{'module':'list pojo for json param'}"
    }
    
  • 日期类型数据:2088-08-18 2088/08/18 08/18/2088

    • @DateTimeFormat:设定日期时间型数据格式

    • pattern:日期时间格式字符串

    • Converter接口:类型转换器1.String->Integer 2.String->Date

    • @EnableWebMVC功能之一:根据类型匹配对应的类型转换器

根据不同的日期格式设置不同的接收方式

@RequestMapping("/dateParam")
@ResponseBody
public String dataParam(Date date,@DateTimeFormat(parttern="yyyy-MM-dd") Date date1, @DateTimeFormat(parttern="yyyy-MM-dd HH:mm:ss") date2){
    System.out.println("参数传递 date ==> "+date);//2088/08/18
    System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1)//2088-08-18
    System.out.println("参数传递 date2(yyyy-MM-dd HH:mm:ss) ==> "+date2)//2088-08-18 08:08:08
    return "{'module':'date param'}"
}

@RequestBody与@RequestParam区别

1.区别

  • @RequestParam用于接收ur地址传参,表单传参【application/x-www-form-urencoded】
  • @RequestBody用于接受json数据【application/json】

2.应用

  • 后期开发,发送json格式数据为主,@RequestBody应用较广
  • 如果发送非json格式数据,选用@RequestParam接收请求参数

2.4响应

@ResponseBody:设置当前控制器返回值作为响应体

  • 响应页面
@RequestMapping("/toPage")
public String toPage(){
    return "page.jsp";
}
  • 响应文本数据
@RequestMapping("/toText")
@ResponseBody
public String toText(){
    return "response text";
}
  • 响应json数据(对象转json)
@RequestMapping("/toJsonPojo")
@ResponseBody
public User toJsonPojo(){
    User user = new User();
    user.setName("赵云");
    user.setAge(41);
    return user;
}
  • 响应json数据(对象集合转json数组)
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
    User user1 = new User();
    user.setName("赵云");
    user.setAge(41);
    User user2 = new User();
    user.setName("马超");
    user.setAge(40);
    List<User> userList = new ArrayList<User>();
    userList.add(user1);
    userList.add(user2);
    return userList;
}

3 Rest风格(资源访问的形式)

3.1简介

  • REST,表现形式状态转换
    • 传统风格
      • http://localhost/user/getById?id=1
      • http://localhost/user/saveUser’
    • REST
      • http://localhost/user/1
      • http://localhost/user/
  • 优点:
    • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
    • 书写简化
  • 按照REST风格访问资源时使用行为动作区分对资源进行何种操作
    • http://localhost/users 查询全部用户信息 GET(查询)
    • http://localhost/users/1 查询指定用户信息 GET(查询)
    • http://localhost/users 添加用户信息 POST(新增保存)
    • http://localhost/users 修改用户信息 PUT(修改/更新)
    • http://localhost/user/1 删除用户信息 DELETE(删除)

描述模块通常使用复数,表示此类资源如users

  • 根据REST风格对资源进行访问成为RESTful

3.2 入门案例

  • 设定http请丢动作(动词):method=RequestMethod.DELETE
  • 设定请求参数(路径变量):value="/users/{id}) @PathVariable Integer id

method:请求方式的设定

@PathVariable

@Controller
@RequestMapping("/user")//请求映射路径。路径前缀
public class UserController{
    @RequestMapping(value="/save",method=RequestMethod.POST)
    @ResponseBody
    public String save(){
        System.out.println("user save");
        return "{'info':'user save'}"
    }
    @RequestMapping(value="/users/{id}",method=RequestMethod.DELETE)//路径变量
    @ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println("user delete");
        return "{'info':'user save'}"
    }
    @RequestMapping(value="/users",method=RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user){
        System.out.println("user update..."+user);
        return "{'info':'user update '}"
    }
    @RequestMapping(value="/users/{id}",method=RequestMethod.GET)//路径变量
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'info':'user getById'}"
    }
    @RequestMapping(value="/users",method=RequestMethod.GET)
    @ResponseBody
    public String getAll(){
        System.out.println("user getAll..."+id);
        return "{'info':'user getAll'}"
    }
}

@RequestBody @RequestParam @PathVariable

  • 区别
    • @RequestParam用于接收url地址传参或表单传参
    • @RequestBody用于接收json数据
    • @PathVariablei用于接收路径参数,使用参数名称描述路径参数
  • 应用
    • 后期开发,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
    • 如果非json格式数据,选用@RequestParam接收请求参数
    • 采用RESTful开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于请求id值

3.3 快速开发

///@Controller
//@ResponseBody
@RestController//替代由@Controller和@ResponseBody的组成
@RequestMapping("/user")//请求映射路径。路径前缀
public class UserController{
    //@RequestMapping(method=RequestMethod.POST)
    @PostMapping//代替上面@RequestMapping
    public String save(){
        System.out.println("user save");
        return "{'info':'user save'}"
    }
    //@RequestMapping(value="/{id}",method=RequestMethod.DELETE)//路径变量
    @DeleteMapping(value="/{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("user delete");
        return "{'info':'user save'}"
    }
    //@RequestMapping(method=RequestMethod.PUT)
    @PutMapping
    public String update(@RequestBody User user){
        System.out.println("user update..."+user);
        return "{'info':'user update '}"
    }
    //@RequestMapping(value="/{id}",method=RequestMethod.GET)//路径变量
    @GetMapping(value="/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'info':'user getById'}"
    }
    //@RequestMapping(method=RequestMethod.GET)
    @GetMapping()
    public String getAll(){
        System.out.println("user getAll..."+id);
        return "{'info':'user getAll'}"
    }
}

3.4 放行(访问page css js等)

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 当访问/pages/????时候,走/pages目录下的内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}

4 SSM整合

4.1表现层数据封装

  • 前端接收数据格式——创建结果模型类

    • 封装数据到data属性中
    • 封装操作到code属性中
    • 封装特殊消息到message(msg)属性中(20040查询失败 20041查询成功 0结尾失败)
    public class Result{
        private Object data;
        private Integer code;
        private String msg;
    }
    public class Code{
        public static final Integer SAVE_OK = 20011;
        public static final Integer DELETE_OK = 20021;
        public static final Integer UPDATE_OK = 20031;
        public static final Integer GET_OK = 20041;
    	
        public static final Integer SAVE_ERR =20010;
        public static final Integer DELETE_ERR =20020;
        public static final Integer UPDATE_ERR = 20030,
        public static final Integer GET_ERR = 20048
    }
    
    @RequestMapping('/books')
    public class BookController{
        @Autowired
        private BookService bookService;
        @GetMapping("/{id}")
        public Result getById(@PathVariable Integer id){
            Book book = bookService.getById(id);
            Integer code = book != null ? Code.GET_ok : Code.GET_ERR;
            String msg = book != null ? "" : "数据查询失败,请重试";
            return new Result(code,book,msg);
        }
    
    

4.2异常处理器

  • 出现的位置和诱因

    • 框架内部抛出异常:使用不合规
    • 数据层抛出异常:外部服务器故障导致(服务器访问超时)
    • 业务层抛出异常:业务逻辑书写错误(遍历业务书写操作,导致索引异常)
    • 表现层抛出异常:数据收集、校验等规则(不匹配的数据类型间导致)
    • 工具类抛出异常:因工具类书写不严谨不够健壮导致(必要释放的连接长期未释放)
  • 异常均放到表现层处理

  • 异常使用AOP来处理

  • 异常处理器

    • 集中的统一的处理项目中的异常

    @ExceptionHandler:拦截异常

    @RestControllerAdvice  
    public class ProjectExceptionAdvice{
        @ExceptionHandler(Exception.class)
        public Result doException(Exception ex){
            return new Result(666,null);
        }
    }
    

4.3 异常处理方案

  • 项目异常分类

    • 业务异常
      • 规范的用户行为产生的异常
      • 不规范的用户行为操作产生的异常
    • 系统异常
      • 项目运行过程中可预计且无法避免的异常
    • 其他异常
      • 编程人员未预期到的异常
  • 项目异常处理方案

    • 业务异常
      • 发送对应消息传递给用户,提醒规范操作
    • 系统异常
      • 发送固定消息传递给用户,安抚用户
      • 发特定消息给运维人员,提醒维护
      • 记录日常
    • 其他异常
      • 发送固定消息传递给用户,安抚用户
      • 发特定消息给编程人员,提醒维护(纳入预期范围内 )
      • 记录日志
  • 自定义项目系统级异常

public class SystemException extends RuntimeException {
    private Integer code;
    public SystemException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    public SystemException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
}

  • 自定义业务级异常
public class BusinessException extends RuntimeException {
    private Integer code;
    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    public BusinessException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
}
  • 自定义异常编码
public class Code{
    public static final Integer SYSTEM_UNKNOW_ERROR = 50001;
    public static final Integer SYSTEM_TIMEOUT_ERROR = 50002;
    
    public static final Integer PROJECT_VALIDATION_ERROR = 60001;
    public static final Integer PROJECT_BUSINESS_ERROR = 60002;
}
  • 触发自定义异常
@Service
public class BookServiceImpl implements BookService{
    @Autowired
    private BookDao bookDao;
    public Book getById(Integer id){
        if(id<0){
            throw new BusinessException(Code.PROJECT_BUSINESS_ERROR,"请勿进行非法操作");
        }
        return bookDao.getById(id);
    }
}
  • 拦截并处理异常
@RestControllerAdvice
public class ProjectExceptionAdvice {
    @ExceptionHandler(BusinessException.class)
    public Result doBusinessException(BusinessException ex) {
        return new Result(ex.getCode(), null, ex.getMessage());
    }
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException ex) {
        // 记录日志(错误堆栈)
        // 发送邮件给开发人员
        // 发送短信给运维人员
        return new Result(ex.getCode(), null, ex.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex) {
        // 记录日志(错误堆栈)
        // 发送邮件给开发人员
        // 发送短信给运维人员
        return new Result(Code.SYSTEM_UNKNOW_ERROR, null, "系统繁忙,请联系管理员!");
    }
}

5 拦截器

  • 拦截器是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行

  • 作用

    • 在指定的方法调用前后执行预先设定的代码
    • 阻止原始方法的执行
  • 拦截器与过滤器的区别

    • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
    • 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强

5.1 入门案例

  • 声明拦截器的bean,并实现HandlerInterceptor接口(扫描加载bean)
@Component
public class ProjectInterceptor 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...");
    }
}
  • 定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法(注意:扫描加载配置)
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport{
    @Override
    public void addInterception(InterceptorRegistry registry){
        ...
    }
}
  • 添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport{
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Override
    public void addInterception(InterceptorRegistry registry){
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
    }
}
  • 使用标准接口简化开发(侵入性较强)
@Configuration
@EnableWebMvc
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
    }
}
  • preHandle一定执行!如果其返回值是true则继续执行下面的controller postHandle afterComletion,如果是false则结束

5.2 拦截器参数

  • 前置处理
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return true;
}
//参数:request:请求对象;response:响应对象;handler:被调用的处理器对象,本质是一个方法对象,对反射技术中的method对象进行再包装
//返回值:false:被拦截的处理器将不执行
  • 后置处理
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
}
//参数:modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
  • 完成后处理
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
}
//参数:ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理

5.3 多拦截器执行顺序

  • 当配置多个拦截器时,形成拦截器链
  • 拦截器链的运行顺序参照拦截器添加顺序为准
  • 当拦截器出现对原始处理器的拦截,后面的拦截器均终止运行
  • 当拦截器运行终端,仅运行配置在前面的拦截器的afterCompletion操作
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值