RESTFul风格

六、RESTFUL风格

6.1 RESTFul简介

REST:Representational State Transfer,表现层资源状态转移。

这里介绍几个专业术语:

  • 资源:资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计得非常抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个 URl 来标识。 URI 既是资源的名称,也是资源在 Web 上的地址。对某个资源感兴趣的客户端应用,可以贡酬寸资源的 URI 与其进行交互。

  • 资源的表述:资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端一服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求一响应方向的表述通常使用不同的格式。

  • 状态转移:在客户端和服务器端之间转移( transfer )代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。

6.2 RESTFul的实现

前面写了那么多东西,相信很多人应该反而被绕晕了吧。没关系,我们要开始讲人话,贴代码了。

先简单提一下之前说过的一个点吧,HTTP协议中,有四个表示操作方式的动词:GET、POST、PUT、DELETE。
这四个动词分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不适用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性。

给个表格吧,看一眼你就懂了。

操作传统方式REST风格
addUseruser ——> post请求方式
deleteUser?id=1user/1 ——> delete请求方式
updateUseruser ——> put请求方式
getUserById?id=1user/1 ——> get请求方式

下面,就让我们使用RESTFul模拟下用户资源的增加和查找吧。先写一个
UserController.java类:

package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class UserController {

    @RequestMapping("/restful")
    public String toResuful(){
        return "restful";
    }

    /*
    *   使用RESTFul模拟用户资源的增删改查
    *   /user       GET请求       查询所有用户信息
    *   /user/1     GET请求       根据用户id查询用户信息
    *   /user       POST请求      添加用户信息
    *   /user/1     DELETE请求    删除用户信息
    *   /user       PUT请求       修改用户信息
    */

    @GetMapping("/user")
    public String getAllUser(){
        System.out.println("查询所有用户信息");
        return "success";
    }

    @GetMapping("/user/{id}")
    public String getUserById(){
        System.out.println("根据用户id查询用户信息");
        return "success";
    }

    @PostMapping("/user")
    public String addUser(String username, String password){
        System.out.println("添加用户信息:"+username+", "+password);
        return "success";
    }
}

然后写一下存放我们各种超链接的网页:
restful.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>REST风格</title>
</head>
<body>
    <a th:href="@{/user}">查询所有用户信息</a><br>
    <a th:href="@{/user/1}">根据id查询用户信息</a><br>
    <form th:action="@{/user}" method="post">
        用户名:<input type="text" name="username">&nbsp;&nbsp;码:<input type="password" name="password">
        <input type="submit" value="添加用户信息">
    </form>
</body>
</html>

重启服务器后:
来到我们的restful.html页面,分别点击上面两个超链接并输入用户名和密码后点击添加用户信息。
在这里插入图片描述
此时控制台显示如下:
在这里插入图片描述

6.3 HiddenHttpMethodFilter

由于浏览器只支持发送get和post方式的请求,SpringMVC便给我们提供了一个HiddenHttpMethodFilter帮助我们将POST请求转换为DELETE或PUT请求。

HiddenHttpMethodFilter处理put和delete请求的条件有以下:
1、当前请求的请求方式必须为post
2、当前请求必须传输请求参数 _method

演示代码如下:
先在web.xml中配置我们的HiddenHttpMethodFilter。(最好配置在CharacterEncodingFilter后边,否则会有中文乱码的问题)

    <!--配置HiddenHttpMethodFilter-->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

然后在Controller下添加方法:

    @PutMapping("/user")
    public String updateUser(String username, String password){
        System.out.println("修改用户信息:"+username+", "+password);
        return "success";
    }

    @DeleteMapping("/user/{id}")
    public String deleteUserById(){
        System.out.println("删除用户信息");
        return "success";
    }

最后在restful.html中添加超链接:

    <form th:action="@{/user}" method="post">
        <!--
            要想使用put和delete必须要有_method请求参数
            因为这个是用户不需要知道的,所以我们将它隐藏即可
        -->
        <input type="hidden" name="_method" value="put">
        用户名:<input type="text" name="username">&nbsp;&nbsp;码:<input type="password" name="password">
        <input type="submit" value="修改用户信息">
    </form>
    <br>

    <!--一般删除不会这么写哈,这里只是为了演示-->
    <form th:action="@{/user/1}" method="post">
        <input type="hidden" name="_method" value="delete">
        <input type="submit" value="删除用户信息">
    </form>
    <br>

6.4 完整的代码案例

首先,我们肯定先配置我们的依赖,也就是修改我们的pom.xml文件(这里只是学习RESTFul,因此并没有引入数据库的依赖)
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mvc_demo1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--SpringMVC-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.9</version>
        </dependency>

        <!--日志-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>test</scope>
        </dependency>

        <!--ServletAPI-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!--Spring5和Thymeleaf整合包-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>
    </dependencies>
</project>

然后就写我们整个项目的配置文件web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置编码过滤器,解决中文乱码问题-->
    <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>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--配置HiddenHttpMethodFilter-->
    <filter>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!--配置SpringMVC的前端控制器,对浏览器发送的请求进行统一处理-->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--配置SpringMVC配置文件的位置和名称-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <!--将前端控制器DispatcherServlet的初始化时间提前到服务器启动时-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <!--
            使用/可以拦截 /login、 .html、 .js 、.css方式的请求
            但是不能拦截 .jsp的请求,如果要把.jsp文件也拦下来,那么就得用/*
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

然后因为我们使用的是SpringMVC的框架,所以我们还得创建SpringMVC的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--扫描组件-->
    <context:component-scan base-package="com.example"/>

    <!--配置Thymeleaf视图解析器(针对h5文件,抄就完了)-->
    <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <property name="order" value="1"/>
        <property name="characterEncoding" value="UTF-8"/>
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                        <!--视图前缀-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--视图后缀-->
                        <property name="suffix" value=".html"/>
                        <property name="templateMode" value="HTML5"/>
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>

    <!--
        处理静态资源,如html、js、css、jpg
        若只设置该标签,则只访问静态资源,其他请求无法访问
        此时必须设置<mvc:annotation-driven/>解决问题
    -->
    <mvc:default-servlet-handler/>

    <!--开启mvc注解驱动-->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <!--处理响应中文乱码问题-->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"/>
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html</value>
                        <value>application/json</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
</beans>

之后呢,我们就开始写我们的po层的代码,比如创建一个员工类
Employee.java:(一般是写一个有参构造加一个无参构造加所有的get和set方法再重写一个toString方法)

package com.example.po;

public class Employee {
    private Integer id;     //唯一标识
    private String name;    //姓名
    private String email;   //邮箱
    private Integer gender; //性别

    public Employee() {
    }

    public Employee(Integer id, String name, String email, Integer gender) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.gender = gender;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                '}';
    }
}

然后就写我们的dao层:(一般的写法其实是写一个Dao接口,再写一个DaoImpl实现类,再写一个service类来调用),但是这里相当于把这三个合并成一个直接拿来用。
EmployeeDao.java

package com.example.dao;

import com.example.po.Employee;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class EmployeeDao {
    /*模拟数据库*/
    private static Map<Integer, Employee> employees = null;

    static {
        employees = new HashMap<Integer, Employee>();

        /*假设数据库中现有以下六条数据*/
        employees.put(1001,new Employee(1001, "林一", "123@qq.com", 0));
        employees.put(1002,new Employee(1002, "黄二", "456@qq.com", 1));
        employees.put(1003,new Employee(1003, "张三", "789@qq.com", 0));
        employees.put(1004,new Employee(1004, "李四", "321@qq.com", 1));
        employees.put(1005,new Employee(1005, "王五", "654@qq.com", 0));
        employees.put(1006,new Employee(1006, "刘六", "987@qq.com", 1));
    }

    /*模拟数据库的自增id*/
    private static Integer initId = 1007;

    /*增加一个Employee对象*/
    public void save(Employee employee){
        if (employee.getId() == null){
            employee.setId(initId++);
        }
        employees.put(employee.getId(),employee);
    }

    /*返回所有的Employees对象*/
    public Collection<Employee> getAll(){
        return employees.values();
    }

    /*通过id返回Employee对象*/
    public Employee get(Integer id){
        return employees.get(id);
    }

    /*通过id删除Employee对象*/
    public void delete(Integer id){
        employees.remove(id);
    }
}

然后我们根据下面的功能清单,写我们的控制器方法和超链接。
在这里插入图片描述

6.4.1 查询功能

先演示一个查询的功能。
EmployeeController.java代码如下:

package com.example.controller;

import com.example.dao.EmployeeDao;
import com.example.po.Employee;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import java.util.Collection;

@Controller
public class EmployeeController {
    @Resource
    private EmployeeDao employeeDao;

    @GetMapping("/")
    public String toIndex(){
        return "index";
    }

    @GetMapping("/employee")
    public ModelAndView getAllEmployee(ModelAndView modelAndView){
        Collection<Employee> employees = employeeDao.getAll();
        //保存数据到request域对象
        modelAndView.addObject("employees", employees);
        //设置跳转的视图为employees.html
        modelAndView.setViewName("employees");
        return modelAndView;
    }

    @GetMapping("/employee/{id}")
    public ModelAndView getEmployeeById(ModelAndView modelAndView, @PathVariable Integer id){
        Employee employee = employeeDao.get(id);
        /*保存数据到request对象*/
        modelAndView.addObject("employees", employee);
        /*设置跳转的视图为employees.html*/
        modelAndView.setViewName("employees");
        return modelAndView;
    }
}

然后写我们的首页:
index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>首页</h1>
    <a th:href="@{/employee}">查询所有员工信息</a><br>
    <a th:href="@{/employee/1001}">查询id为1001的员工信息</a><br>
</body>
</html>

最后我们陈列(显示数据)的页面:
employees.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>员工信息列表</title>
    <style type="text/css">
      table{
        border: 1px solid black;
        width: 50%;
        /*表格对于整体居中*/
        margin: 0 auto;
        /*表格中的文字居中*/
        text-align: center;
      }

      td,th{
        border: 1px solid black;
      }
    </style>
</head>
<body>
    <table>
      <tr>
        <!--设置让他独占五列-->
        <th colspan="5">员工信息列表</th>
      </tr>
      <tr>
        <th>工号</th>
        <th>姓名</th>
        <th>邮箱</th>
        <th>性别</th>
        <th>选项</th>
      </tr>
      <!--
          这里的用法类似于for(Employee item : employees)
          th:each表示的是取到的集合中的每一个对象
          : ${employees} 表示遍历request域中的employees
      -->
      <tr th:each="item : ${employees}">
        <td th:text="${item.id}"></td>
        <td th:text="${item.name}"></td>
        <td th:text="${item.email}"></td>
        <td th:text="${item.gender}"></td>
        <td>
          <a href="">删除</a>
          <a href="">修改</a>
        </td>
      </tr>
    </table>
</body>
</html>

启动服务器:
在这里插入图片描述
点击查询所有员工信息显示:
在这里插入图片描述
点击查询id为1001的用户信息显示:
在这里插入图片描述

6.4.2 删除功能

然后我们说一下删除功能(直接贴代码):
首先我们要引入我们的jQuery
在这里插入图片描述
然后,我们在控制器中添加方法:

    @DeleteMapping("/employee/{id}")
    public ModelAndView removeById(ModelAndView modelAndView, @PathVariable("id") Integer id){
        /*删除*/
        employeeDao.delete(id);
        modelAndView.setViewName("redirect:/employee");
        return modelAndView;
    }

再修改我们的employee.html页面如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>员工信息列表</title>
    <style type="text/css">
      table{
        border: 1px solid black;
        width: 50%;
        /*表格对于整体居中*/
        margin: 0 auto;
        /*表格中的文字居中*/
        text-align: center;
      }

      td,th{
        border: 1px solid black;
      }
    </style>
</head>
<body>
    <table>
      <tr>
        <!--设置让他独占五列-->
        <th colspan="5">员工信息列表</th>
      </tr>
      <tr>
        <th>工号</th>
        <th>姓名</th>
        <th>邮箱</th>
        <th>性别</th>
        <th>选项</th>
      </tr>
      <!--
          这里的用法类似于for(Employee item : employees)
          th:each表示的是取到的集合中的每一个对象
          : ${employees} 表示遍历request域中的employees
      -->
      <tr th:each="item : ${employees}">
        <td th:text="${item.id}"></td>
        <td th:text="${item.name}"></td>
        <td th:text="${item.email}"></td>
        <td th:text="${item.gender}"></td>
        <td>
          <a class="deleteOption" th:href="@{'/employee/'+${item.id}}">删除</a>
          <a id="updateOption" th:href="@{/employee}">修改</a>
        </td>
      </tr>
    </table>

    <!--删除表单-->
    <form id="deleteForm" method="post">
      <input type="hidden" name="_method" value="delete">
    </form>

    <script type="text/javascript" th:src="@{/}+'/static/js/jquery-3.6.0.js'"></script>
    <script type="text/javascript">
        $(function (){
            $(".deleteOption").click(function (event){
                /*获取a标签的href*/
                $("#deleteForm").attr("action",$(this).attr("href"));
                /*提交form表单*/
                $("#deleteForm").submit();
                //取消超链接的默认行为
                event.preventDefault();
            })
        });
    </script>
</body>
</html>

6.4.3 添加功能

接着说一下添加功能,代码如下:
现在index中添加一个超链接跳转到页面添加员工信息。
index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>首页</h1>
    <a th:href="@{/employee}">查询所有员工信息</a><br>
    <a th:href="@{/employee/1001}">查询id为1001的员工信息</a><br>
    <a th:href="@{/toAdd}">添加员工信息</a>
</body>
</html>

然后写我么你的添加员工信息的页面:
employee_add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>添加员工信息</title>
</head>
<body>
    <form th:action="@{/employee}" method="post">
        姓名:<input type="text" name="name"><br>
        邮箱:<input type="text" name="email"><br>
        性别:<input type="radio" name="gender" value="0"><input type="radio" name="gender" value="1"><br>
        <input type="submit" value="提交"><br>
    </form>
</body>
</html>

再在Controller中补两个方法:

    @GetMapping("/toAdd")
    public String toAdd(){
        return "employee_add";
    }

    @PostMapping("/employee")
    public ModelAndView addEmployee(Employee employee,ModelAndView modelAndView){
        employeeDao.save(employee);
        modelAndView.setViewName("redirect:/employee");
        return modelAndView;
    }

重启服务器:
在这里插入图片描述
输入信息后点击提交
在这里插入图片描述
结果:
在这里插入图片描述

6.4.4 修改功能

先修改我们之前的index页面(去掉查询个人的功能):
index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1>首页</h1>
    <a th:href="@{/employee}">查询所有员工信息</a><br>
    <a th:href="@{/toAdd}">添加员工信息</a>
</body>
</html>

然后写一个修改的页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>修改员工信息</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
  <input type="hidden" name="_method" value="put">
  <input type="hidden" name="id" th:value="${employee.id}">
  姓名:<input type="text" name="name" th:value="${employee.name}"><br>
  邮箱:<input type="text" name="email" th:value="${employee.email}"><br>
  性别:<input type="radio" name="gender" value="0" th:field="${employee.gender}"><input type="radio" name="gender" value="1" th:field="${employee.gender}"><br>
  <input type="submit" value="修改"><br>
</form>
</body>
</html>

最后写两个控制器方法:(第一个getEmployeeById之前写过,需要更改)

    @GetMapping("/employee/{id}")
    public ModelAndView getEmployeeById(ModelAndView modelAndView, @PathVariable("id") Integer id){
        Employee employee = employeeDao.get(id);
        /*保存数据到request对象*/
        modelAndView.addObject("employee", employee);
        /*设置跳转的视图为employees.html*/
        modelAndView.setViewName("employee_update");
        return modelAndView;
    }

    @PutMapping("/employee")
    public ModelAndView updateEmployee(ModelAndView modelAndView, Employee employee){
        employeeDao.save(employee);
        modelAndView.setViewName("redirect:/employee");
        return modelAndView;
    }

然后重启服务器:
演示就不演示了,直接看结果把。这里把王五改成了哈哈。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值