Spring Web MVC入门

Spring Web MVC概念

Spring Web MVC 是基于 Servlet API 构建的原始Web 框架 ,从一开始就包含在 Spring 框架中。它的正式名称"Spring Web MVC"来自其源模块的名称(Spring-webmvc),但它通常被称为"Spring

MVC".​

Spring Web MVC 是一个 Web 框架


MVC 定义

MVC 是 Model View Controller 的缩写,它是软件工程中的一种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分

• View(视图) 指在应用程序中专门用来与浏览器进行交互,展示数据的资源.​
• Model(模型) 是应用程序的主体部分,用来处理程序中数据逻辑的部分. ​
**• Controller(控制器)**可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型.


Spring MVC

MVC 是一种架构设计模式, 也是一种思想, 而 Spring MVC 是对 MVC 思想的具体实现. 除此之外,
Spring MVC还是一个Web框架.​

总结来说,Spring MVC 是一个实现了 MVC 模式的 Web 框架. ​

所以, Spring MVC主要关注有两个点:​
1. MVC​(思想)
2. Web框架​

其实, Spring MVC 我们在前面已经用过了, 在创建 Spring Boot 项目时,我们勾选的 Spring Web 框架其实就是Spring MVC 框架.

Spring Boot 只是实现Spring MVC 的其中一种方式而已.​
Spring Boot 可以添加很多依赖, 借助这些依赖实现不同的功能. Spring Boot 通过添加Spring Web MVC框架, 来实现web功能. ​

不过Spring在实现MVC时, 也结合自身项目的特点, 做了一些改变, 相对而言, 下面这个图或许更加合适一些.​


学习Spring MVC​

既然是 Web 框架, 那么当用户在浏览器中输入了url 之后,我们的 Spring MVC 项目就可以感知到用户的请求, 并给予响应. ​

咱们学习Spring MVC, 重点也就是学习**如何通过浏览器和用户程序进行交互.**​

主要分以下三个方面:

1. 建立连接: 将用户(浏览器)和 Java 程序连接起来,也就是访问一个地址能够调用到我们的

Spring 程序。​
2. 请求: 用户请求的时候会带一些参数,在程序中要想办法获取到参数, 所以请求这块主要是 获取参数的功能. ​
**3. 响应:**执行了业务逻辑之后,要把程序执行的结果返回给用户, 也就是响应. ​


项目准备​

Spring MVC 项目创建和 Spring Boot 创建项目相同,在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项目.【上一篇博客已经详细介绍】

建立连接​

java 复制代码
package org.example.spring_mac_test;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Test {
    @RequestMapping("/hello")
    public String Hello() {
        return "Hello Spring MAC Test";
    }
}

@RequestMapping 注解介绍 ​

@RequestMapping 是Spring Web MVC应⽤程序中最常被⽤到的注解之⼀,它是⽤来注册接⼝的 路由映射的.

路由映射:当⽤⼾访问⼀个URL时,将⽤⼾的请求对应到程序中某个类的某个⽅法的过程就叫路由映射.


@RestController注解的介绍

@RestController 是 Spring MVC 框架中的一个注解,它是一个复合注解 ,等价于在类上同时使用 @Controller 和 @ResponseBody。在 Spring 应用中,@Controller 用于标识一个类为 Spring MVC 控制器,而 @ResponseBody 用于将控制器方法的返回值直接作为 HTTP 响应体返回给客户端,而不是将其解析为视图名称。因此,@RestController 注解的类中的所有方法 都会将返回值 直接转换为 HTTP 响应体,适合构建 RESTful API。


@Controller注解的介绍

@Controller:它是 Spring MVC 框架中用于定义控制器的原始注解。使用 @Controller 注解的类主要用于处理 HTTP 请求并返回视图。也就是说,控制器方法的返回值通常是一个视图名称,Spring 会根据这个视图名称来查找并渲染相应的视图(如 JSP、Thymeleaf 模板等)


@RequestMapping使用

@RequestMapping既可修饰 ,也可以修饰**⽅法**,当修饰类和⽅法时,访问的地址是类路径+⽅ 法路径.

代码举例:

java 复制代码
package org.example.spring_mac_test;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
public class Test {
    @RequestMapping("/hello")
    public String Hello() {
        return "Hello Spring MAC Test";
    }
}

访问结果:

如上图报了一个404 的错误,404错误即客户端输入的url在后端找到这个路径,后端无法给予响应

如上图所示,当路径变为127.0.0.1:8080/test/hello,即能正确访问到后端的代码。是由于我们在之前的代码的基础上给类加了一个**@RequestMapping("/test")。**所以要先访问类的路径,再访问方法的路径。


@RequestParam注解的介绍

java 复制代码
 /**
     * 将前端传回来得参数改名,使其更符合项目的需求
     * @param Username
     * @return
     */
    @RequestMapping("r6")
    public String r6(@RequestParam("name") String Username){
        return "接受到参数name = "+Username;
    }

这个注解的作用就是修改前端传给后端的参数名字,并在后端代码中使用修改之后的名字。


@RequestBody注解介绍

java 复制代码
    /**
     * 使用json进行接受
     * @param student
     * @return
     */
    @RequestMapping("/r10")
    public String r10(@RequestBody Student student){
        return "接受参数,student:"+student;
    }

在 Web 开发里,客户端向服务器发送请求时,请求体里可能包含 JSON、XML 等格式的数据。@RequestBody 注解能够把这些请求体里的数据绑定到控制器方法的参数上,之后 Spring 会自动依据请求体的内容类型(如 application/json),选用合适的消息转换器把请求体数据转换为 Java 对象。


@PathVariable注解介绍

java 复制代码
/**
     * 获取URL中的参数@PathVariable
     * 从路径中获取参数
     */
    @RequestMapping("/article/{articlelId}")
    public String r11(@PathVariable("articlelId") Integer articlelId){
        return "接受参数,articlelId:"+articlelId;
    }

在 RESTful 风格的 Web 服务中,URL 往往包含了一些动态的参数。@PathVariable 注解可以让开发者轻松地从 URL 路径中提取这些参数,并将它们传递给控制器方法的参数。


@RequestPart注解介绍

java 复制代码
    /**
     * 上传文件@RequestPart
     */
    @RequestMapping("/r13")
    public String r13(MultipartFile file){
        String originalFilename = file.getOriginalFilename();
        return "接收到文件,文件名称"+originalFilename;
    }

@RequestPart 是 Spring 框架中用于处理 multipart/form-data 请求的注解,通常在处理文件上传 或者包含二进制数据的表单提交时使用。


@RequestHeader注解的介绍

java 复制代码
​
    @RequestMapping("/getHeader2")
    public String getHeader2(@RequestHeader("user-Agent") String userAgent){
        return "从header中获取信息,userAgent:"+userAgent;
    }

​

@RequestHeader 是 Spring 框架里用于处理 HTTP 请求头 信息的注解,主要功能是从 HTTP 请求头里获取指定的字段值 ,并将其绑定到控制器方法的参数上。


@ResponseBody注解的介绍

java 复制代码
@ResponseBody    // 早起这个注解是返回数据的
    @RequestMapping("/indexData")
    public String indexData() {
        return "返回数据";
    }

@ResponseBody 是 Spring 框架中的一个重要注解,主要用于将控制器方法的返回值 直接作为 HTTP 响应体返回给客户端,而不是将其解析为视图名称。


@RequestMapping是GET还是POST请求?

1.post

一个测试前端代码:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <form action="/request/hello" method="post">
    <input type="submit" value="发送请求">
  </form>
</body>
</html>

如上述前端代码所示,这个from表单的请求是post

如上述两幅图所示,分别是用浏览器访问这个前端页面,并点击发送请求即跳转此页面。并用fiddler进行抓包,此结果也是post请求;

2.GET

如上图所示,我们用postman进行请求的构造,构造一个GET请求,也能顺利返回。

所以**@RequestMapping既是GET又是POST请求。**


传参介绍

1)传一个参数

代码举例:

java 复制代码
 @RequestMapping("/r1")
    public String r1(String name) {
        return "Hello " + name;
    }

结果展示:

如上图所示,在postman中构造的请求也能顺利得到之后路径后端的响应。


2)参数为包装类型的参数

代码展示:

java 复制代码
    /**
     * 参数是包装型参数
     */
    @RequestMapping("/r2")
    public String r2(Integer age) {
        return "云倩怡的年龄为:"+age;
    }

<1>参数不为空

结果展示:

当参数不为空的时候是能够通过url路径以及参数,顺利得得到后端的响应

<2>参数为空

结果展示:

当在url里面不传参数的时候,后端的响应就会默认返回一个null.


3)参数类型为非包装类型

代码展示:

java 复制代码
 /**
     * 参数类型为非包装类型
     */
    @RequestMapping("/r3")
    public String r3(int age) {
        return "云倩怡的年龄为:"+age;
    }

<1>参数不为空

结果展示:

如上图所示,响应正常

<2>参数为空

结果展示:

如上图所示,报了一个500的错误。


4)传多个参数

代码展示:

java 复制代码
    /**
     * 传多个参数
     */
    @RequestMapping("/r4")
    public String r4(String name ,Integer age) {
        return name+"的年龄为:"+age;
    }

结果展示:

如上图所示,当postman后端 发出一个请求之后,后端 返回一个预期的响应前端

当我们要传多个参数的时候,且多个方法要传多个参数的时候,就会冗余,让代码看起来非常的复杂,所以我们把这个参数打包成一个对象,往这个方法里面传这个对象。

5)传一个对象

代码展示:

java 复制代码
package org.example.spring_mac_test;

public class Student {
    private String name;
    private Integer age;
    private Integer id;

    // 写一个无参的构造方法
    public  Student() {

    }


    // get和set方法

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

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


    // 重写tostring方法

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                '}';
    }
}
java 复制代码
    /**
     * 参数为一个对象
     */
    @RequestMapping("/r5")
    public String r5(Student student) {
        return "student:"+student;
    }

结果展示:

如上图所示,postman中请求的参数要和Student对象中的参数名称一一对应,不然在postman向后端发起请求的时候,后端无法识别前端的请求。如下图所示:

如上图所示,name这个参数没有从前端的请求这里获取信息,所以直接报了一个null;这就回到我们之前所学的包装类型和基础类型在获取不到前端传回来的参数时,是会传回null还是报404的错误呢?这里又再一次得得到了验证。


6)给参数改名

代码展示:

java 复制代码
 /**
     * 将前端传回来得参数改名,使其更符合项目的需求
     * @param Username
     * @return
     */
    @RequestMapping("r6")
    public String r6(@RequestParam("name") String Username){
        return "接受到参数name = "+Username;
    }

结果展示:

如上述代码所示,在代码块中使用的参数是Username ,而前端传的参数的name ,但是这里我们通过一个注解**@RequestParam** 来进行对前端参数进行更名。在方法的内部使用的是更改过后的名字。而且这个参数是必传的 ,如果前端不向后端传这个数据就会报400的错误,如下图所示:

为了解决这个问题,我们就要把参数改成非必传的。如下所示


7)将参数改成非必传

代码展示:

java 复制代码
 /**
     * 只想重命名,不想必传
     */
    @RequestMapping("r7")
    public String r7(@RequestParam(value = "name",required = false) String Username){
        return "接受到参数name = "+Username;
    }

结果展示:

如上图所示,当我们在后端代码中做出一点改动,依旧是使用**@RequestParam(value = "name",required = false)**这个注解进行改动;


8)传递一个数组

代码展示:

java 复制代码
    /**
     * 怎么传一个数组
     */
    @RequestMapping("r8")
    public String r8(String[] array){
        return "接受参数,array = "+ Arrays.toString(array);
    }

结果展示:


9)传递一个集合

代码展示:

java 复制代码
    /**
     * 如果要传一个集合呢?
     * 接受的其实是一个数组,把我接收到参数赋值给这个list
     * @return
     */
    @RequestMapping("r9")
    public String r9(@RequestParam(required = false) List<String> list){
        return "接受参数,list = "+ list;
    }

结果展示:


10)传递JSON数据

代码展示:

java 复制代码
    /**
     * 使用json进行接受
     * @param student
     * @return
     */
    @RequestMapping("/r10")
    public String r10(@RequestBody Student student){
        return "接受参数,student:"+student;
    }

注:代码中用到Student对象即为前面所创建的Student对象

结果展示:

如上图所是,我们在postman中传一个JSON形式的数据的时候,在代码这边是使用**@RequestBody**这个注解帮助接受这个数据的。

提问:JSON字符串和JSON对象的区别


11)获取URL中参数@PathVariable

代码展示:

java 复制代码
    /**
     * 获取URL中的参数@PathVariable
     * 从路径中获取参数
     */
    @RequestMapping("/article/{articleId}")
    public String r11(@PathVariable("articleId") Integer articleId){
        return "接受参数,articleId:"+articleId;
    }

结果展示:


12)上传⽂件**@RequestPart**

代码展示:

上传文件:

java 复制代码
    /**
     * 上传文件@RequestPart
     */
    @RequestMapping("/r13")
    public String r13(MultipartFile file){
        String originalFilename = file.getOriginalFilename();
        return "接收到文件,文件名称"+originalFilename;
    }

对上传的文件进行重命名:

java 复制代码
/**
     * 上传文件@RequestPart
     * 对参数进行重命名
     */
    @RequestMapping("/r14")
    public String r14(@RequestPart("file") MultipartFile PDFfile){
        String originalFilename = PDFfile.getOriginalFilename();
        return "接收到文件,文件名称"+originalFilename;
    }

结果展示:

上传文件:

对上传的文件进行重命名:


获取Cookie/Session

1)什么是Cookie

<1>定义: Cookie 是服务器发送到用户浏览器并保存在本地 的一小段数据。这些数据会在浏览器下次访问同一服务器 时被发送回服务器,用于识别用户身份、记录用户偏好或跟踪用户会话
<2>工作原理: 当用户访问网站时,服务器可以通过 HTTP 响应头中的Set-Cookie字段向浏览器发送 Cookie。例如,服务器返回Set-Cookie: username=John; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/,浏览器会将这个 Cookie 保存下来。之后,浏览器每次向该服务器发送请求时,会在请求头中包含这个 Cookie,如Cookie: username=John,服务器就能根据 Cookie 中的信息来识别用户。
<3>特点: Cookie 存储在客户端 ,可设置过期时间,过期后自动删除;有大小限制,一般每个 Cookie 不超过 4KB,且每个域名下的 Cookie 数量也有限制;可以设置HttpOnly属性,防止客户端 JavaScript 访问,提高安全性;通过设置Secure属性,确保 Cookie 仅在 HTTPS 连接下发送,增强数据传输的安全性。
**<4>应用场景:**用于记住用户登录状态,实现自动登录;记录用户的浏览偏好,如字体大小、主题颜色等;跟踪用户的浏览行为,用于分析用户习惯和进行广告投放。

2)什么是Session

<1>定义 :Session 是服务器 端用于跟踪用户会话的机制。服务器为每个用户创建一个唯一的会话对象,存储与该用户相关的数据
<2>工作原理: 当用户首次访问网站时,服务器会创建一个 Session 对象 ,并为其分配一个唯一的 Session ID 。这个 Session ID 通常通过 Cookie 发送给浏览器,存储在客户端。后续浏览器每次请求时,都会带上这个 Session ID,服务器根据 Session ID 找到对应的 Session 对象 ,获取和修改与用户相关的数据。
<3>特点: Session 数据存储在服务器端 ,相对安全,用户无法直接修改;没有数据大小限制,可存储复杂数据结构;有一定的生命周期,默认情况下,当用户在一段时间内没有活动(如 30 分钟),Session 会自动过期,也可手动设置过期时间;依赖 Cookie(通常用于传递 Session ID) ,但也可通过 URL 重写等方式在不依赖 Cookie 的情况下使用。
**<4>应用场景:**在电商网站中,用于存储用户购物车信息;在多页面表单提交场景中,保存用户在各个页面填写的数据;在需要用户登录的系统中,存储用户的登录信息和权限信息,用于页面访问权限控制。

Session的本质就是⼀个"哈希表",存储了⼀些键值对结构.Key就是SessionID,Value就是⽤⼾信息(⽤ ⼾信息可以根据需求灵活设计).

SessionId 是由服务器⽣成的⼀个**"唯⼀性字符串"**,从Session机制的⻆度来看,这个唯⼀性字符串称 为"SessionId".但是站在整个登录流程中看待,也可以把这个唯⼀性字符串称为"token".

  1. 当⽤⼾登陆的时候,服务器在Session中新增⼀个新记录,并把sessionId 返回给客⼾端.(通过 HTTP响应中的Set-Cookie字段返回).

  2. 客⼾端后续再给服务器发送请求的时候,需要在请求中带上sessionId .(通过HTTP请求中的Cookie字段带上).

  3. 服务器收到请求之后,根据请求中的sessionIdSession信息 中获取到对应的⽤⼾信息,再进⾏后 续操作.找不到则重新创建Session,并把SessionID返回.

Session默认是保存在内存中的.如果重启服务器则Session数据就会丢失.

3)Cookie和Session的区别[重点]

<1>Cookie是客户端 保存用户信息的一种机制。Session是服务器端保存用户信息的一种机制。

<2>Cookie和Session之间主要是通过SessionId关联起来的,SessionId是Cookie和Session之间的桥梁。

<3>Cookie和Session经常会在一起配合使用,但是不是必须配合

  • 完全可以用Cookie来保存一些数据在客户端。这些数据不一定是用户身份信息,也不一定是SessionId。

  • Session中的sessionId也不需要非得通过Cookie/Set-Cookie传递,比如通过URL传递。

4)获取Cookie

<1>传统方式

代码展示:

java 复制代码
@RequestMapping("/getC")
    public String getCookie(HttpServletRequest request, HttpServletResponse response){
        Cookie[] cookies = request.getCookies();
        if(cookies != null){
            Arrays.stream(cookies).forEach(cookie -> {
                System.out.println(cookie.getName()+":"+cookie.getValue());
            });
        }
        return "获取cookie";
    }

结果展示:

<2>注解方式

代码展示:

java 复制代码
  @RequestMapping("/getC2")
    public String getCookie2(@CookieValue("bite") String bite){
        return "从cookie中获取值 bite:"+bite;
    }

结果展示:

1.在浏览器中发起请求

2.在postman中发起请求


5)获取session

设置session:

代码展示:

java 复制代码
    /**
     * 如何设置session
     */
    @RequestMapping("setSess")
    public String setSess(HttpServletRequest request){
        // 从cookie中获取sessionID,根据session获取session对象,如果没有获取到就会创建一个session对象
        HttpSession session = request.getSession();
        session.setAttribute("name","zhangsan");
        return "设置session成功!";

    }

结果展示:

注:必须要先设置session之后才能去获取session,不然获取的就是一个null值。

<1>传统方式

代码示例:

java 复制代码
    @RequestMapping("getSess")
    public String getSess(HttpServletRequest request){
        // 从cookie中获取sessionID,根据session获取session对象
        HttpSession session = request.getSession();
        String name = (String) session.getAttribute("name");
        return "从session中获取name:"+name;
    }

结果展示:

<2>简洁方式

代码示例:

java 复制代码
@RequestMapping("getSess2")
    public String getSess2(HttpSession session){
        String name = (String) session.getAttribute("name");
        return "从session中获取name:"+name;
    }

结果展示:

<3>进一步封装的方式

代码展示:

java 复制代码
/**
     * 又进行了进一步的封装
     * @param name
     * @return
     */
    @RequestMapping("getSess3")
    public String getSess3(@SessionAttribute("name") String name){
        return "从session中获取name:"+name;
    }

结果展示:

如上述三种方式都能获取到sessionname的信息。


header的获取

1)传统方式

代码展示:

java 复制代码
    @RequestMapping("/getHeader")
    public String getHeader(HttpServletRequest request){
        String userAgent = request.getHeader("User-Agent");
        return "从header中获取信息,userAgent:"+userAgent;
    }

结果展示:

2)简洁方式

代码展示:

java 复制代码
    @RequestMapping("/getHeader2")
    public String getHeader2(@RequestHeader("user-Agent") String userAgent){
        return "从header中获取信息,userAgent:"+userAgent;
    }

结果展示:


响应

在我们前⾯的代码例⼦中,都已经设置了响应数据,Http响应结果可以是数据 ,也可以是静态⻚⾯ ,也可以针对响应设置状态码 ,Header信息等.

1)返回静态页面

代码展示:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index页面</title>
</head>
<body>
  Hello,Spring MVC,我是Index⻚⾯.
</body>
</html>
java 复制代码
@RequestMapping("/res")
@Controller    // 早期这个注解是返回视图
public class ResponseController {
    @RequestMapping("/index")
    public String index() {
        return "/index.html";   // /一定要加
    }

结果展示:

注:我们这里的注解由原来的**@RestController** 变为**@Controller**,那如果我们依旧选择之前的注释@RestController,看一个能不能显示这个前端页面。结果展示:

如上图所示,⻚⾯未正确返回,http响应把 "/index.html" 当做了http响应正⽂的数据。

问:@RestController 变为**@Controller**这两个注解之间到底有什么区别呢?

1)@Controller: 它是 Spring MVC 框架中用于定义控制器的原始注解。使用 @Controller 注解的类主要用于处理 HTTP 请求并返回视图。也就是说,控制器方法的返回值通常是一个视图名称,Spring 会根据这个视图名称来查找并渲染相应的视图(如 JSP、Thymeleaf 模板等)。

2)@RestController: 这是一个组合注解,它相当于 @Controller 和 @ResponseBody 注解的结合。使用 @RestController 注解的类,其所有方法的返回值都会被直接作为 HTTP 响应体返回给客户端,而不会经过视图解析器解析为视图。这使得它非常适合用于构建 RESTful Web 服务,返回 JSON、XML 等数据格式。


2)返回数据@ResponseBody

代码展示:

java 复制代码
    @ResponseBody    // 早起这个注解是返回数据的
    @RequestMapping("/indexData")
    public String indexData() {
        return "返回数据";
    }

结果展示:

3)返回HTML代码⽚段

代码展示:

java 复制代码
   /**
     * 返回html代码片段
     * @return
     */
    @ResponseBody
    @RequestMapping("/indexData2")
    public String indexData2() {
        return "<h1>我是中国人</h1>";
    }

结果展示:

4)返回JSON

代码展示:

java 复制代码
    /**
     * 返回JSON
     */
    @ResponseBody
    @RequestMapping("/getMap")
    public HashMap<String,String> getMap(){
        HashMap<String,String> map = new HashMap<>();
        map.put("k1","v1");
        map.put("k2","v2");
        map.put("k3","v3");
        map.put("k4","v4");
        return map;
    }

结果展示:

5)设置状态码

代码展示:

java 复制代码
   /**
     * 返回状态码
     */
    @ResponseBody
    @RequestMapping("/setStatus")
    public String setStatus(HttpServletResponse response) {
        response.setStatus(418);
        return "设置状态码";
    }

结果展示:


相关推荐
我自纵横20236 分钟前
JavaScript 中常见的鼠标事件及应用
前端·javascript·css·html·计算机外设·ecmascript
li_Michael_li7 分钟前
Vue 3 模板引用(Template Refs)详解与实战示例
前端·javascript·vue.js
excel10 分钟前
webpack 核心编译器 十五 节
前端
excel15 分钟前
webpack 核心编译器 十六 节
前端
lazy★boy1 小时前
DDD与MVC扩展能力对比
mvc·ddd
雪落满地香2 小时前
css:圆角边框渐变色
前端·css
风无雨4 小时前
react antd 项目报错Warning: Each child in a list should have a unique “key“prop
前端·react.js·前端框架
人无远虑必有近忧!4 小时前
video标签播放mp4格式视频只有声音没有图像的问题
前端·video
VX_CXsjNo19 小时前
免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制
java·spring boot·spring·游戏·eclipse·android studio·android-studio
安分小尧9 小时前
React 文件上传新玩法:Aliyun OSS 加持的智能上传组件
前端·react.js·前端框架