# 手机扫码上传文件并实时刷新列表(Springboot+WebSocket)

# 流程

  1. 前端将编写好的上传文件的页面地址生成二维码
  2. 用户手机扫码,访问那个页面,进行文件上传。(测试时手机和电脑连一个Wifi哈)
  3. 上传文件成功后,上传文件接口肯定有返回数据,我们携带返回数据调用后台websocket接口
  4. 后台websocket接口会去推送内容至前端
  5. 至此功能完成

如果你的需求仅仅是后端推送到前端,前端请求的频率远没有后端推送的频率高, 那么你可以选用 SSE ( Server-sent Events )使用 HTTP 协议,进行替换WebSocket。
简单说两句SSE:
SSE 是单向通道,只能服务器向客户端发送消息,如果客户端需要向服务器发送消息,则需要一个新的 HTTP 请求。
详细说明见这里 (opens new window)

# 代码实现

一共两个html页面,注意替换其中的 IP

先看一下目录结构: 图片

# 前端页面

# showCodePage.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>生产二维码,等待后台websockect推送</title>
</head>
<body>
<h1>二维码在这里展示</h1>
<div id="tag1"></div>
<h1 id="tag2">手机扫码上传文件成功后(文件名+id)会实时由后端推送到这里显示!</h1>
</body>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery.qrcode/1.0/jquery.qrcode.min.js" type="text/javascript"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/utf8/3.0.0/utf8.js"></script>
<script type="text/javascript">
    function init() {
        //Article-content 为显示二维码的div id
        $("#tag1").qrcode({
            render : "canvas",    //设置渲染方式,有table和canvas,使用canvas方式渲染性能相对来说比较好
            text: "http://192.168.0.109:8081/index",    //扫描二维码后显示的内容,可以直接填一个网址,扫描二维码后自动跳向该链接
            width : "200",               //二维码的宽度
            height : "200",              //二维码的高度
            background : "#ffffff",       //二维码的后景色
            foreground : "#000000",        //二维码的前景色
        });
    }
    init();
</script>
<script type="text/javascript">
    var websocket = null;
    if ('WebSocket' in window) {
        websocket = new WebSocket('ws://192.168.0.109:8081/webSocket');
    } else {
        alert('该浏览器不支持websocket!');
    }

    websocket.onopen = function (event) {
        console.log('建立连接');
    }

    websocket.onclose = function (event) {
        console.log('连接关闭');
    }

    websocket.onmessage = function (event) {
        console.log('收到消息:' + event.data)
        document.getElementById("tag2").innerHTML = event.data;
    }

    websocket.onerror = function () {
        alert('websocket通信发生错误!');
    }

    window.onbeforeunload = function () {
        websocket.close();
    }
</script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>文件上传的页面</title>
    <!-- cdn引入ElementUI样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<div id="app">
    <el-upload
            class="upload-demo"
            drag
            action="http://192.168.0.109:8998/file/uploadFile"
            multiple
            :on-success="uploadSuccess">
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        <div class="el-upload__tip" slot="tip">文件上传接口,用的是file项目接口,启动一下即可</div>
    </el-upload>
</div>
</body>
<!--cdn引入ElementUI组件必须先引入Vue-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- cdn引入ElementUI组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="text/javascript">
    const vm = new Vue({ // 配置对象 options
        // 配置选项(option)
        el: '#app',  // element: 指定用vue来管理页面中的哪个标签区域
        data: {
            msg: ''
        },
        methods: {
            uploadSuccess(response, file, fileList) {
                var url = 'http://192.168.0.109:8081/WebSocketMsg/' + response[0].name +'----'+ response[0].id;
                // 成功后调用websocket接口 通知前端刷新数据
                axios
                        .get(url, {
                            params: {}
                        })
                        .then(function (response) {
                            console.log("调用通知前端接口成功!!!");
                        })
                        .catch(function (error) {
                            console.log("调用通知前端接口失败!!!");
                        });
            }
        }
    })
</script>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 后端接口

文件上传代码不在我的这个调试项目,自己去写一个文件上传接口使用哈!

文件上传代码不在我的这个调试项目,自己去写一个文件上传接口使用哈!

文件上传代码不在我的这个调试项目,自己去写一个文件上传接口使用哈!

下面给出上图中的代码

# pom

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.kusch</groupId>
    <artifactId>ares-springboot-websocket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ares-springboot-websocket</name>
    <description>ares-springboot-websocket</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

# application.properties

server.port=8081
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
1
2
3
4

# controller

@Controller
public class WebSocketController {
    @Autowired
    private WebSocketInfo WebSocket;

    @RequestMapping("/")
    public String showCodePage() {
        //WebSocket
        return "showCodePage";
    }

    @RequestMapping("/index")
    public String index() {
        //WebSocket
        return "index";
    }

    //WebSocket通知页面测试
    @RequestMapping("/WebSocketMsg/{info}")
    @ResponseBody
    public void product(@PathVariable(name = "info") String info) {
        //WebSocket
        WebSocket.sendMessage("本次websocket推送前端的内容为:" + info);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# WebSocketInfo

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CopyOnWriteArraySet;

@Component
@ServerEndpoint("/webSocket")
@Slf4j
public class WebSocketInfo {

    private Session session;

    private static CopyOnWriteArraySet<WebSocketInfo> webSocketSet = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);
        log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size());
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
    }

    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端发来的消息:{}", message);
    }

    public void sendMessage(String message) {
        for (WebSocketInfo webSocket : webSocketSet) {
            log.info("【websocket消息】广播消息, message={}", message);
            try {
                webSocket.session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# WebSocketStompConfig

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@EnableWebSocket
@Configuration
public class WebSocketStompConfig {
    //这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket  ,如果你使用外置的tomcat就不需要该配置文件
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14