最近的项目有个功能点: 商户端需要扫描用户端的二维码, 扫描结果要展示给商户和用户端.
商户端的提示比较好处理, 根据接口返回数据进行展示就可以, 稍微麻烦的是用户被扫的提示.
解决方案有两种:
1.用户端进行循环查询,每2秒进行一次接口查询,接口有数据时,根据数据展示;
2.用户端使用webSocket与服务器进行长连接,有返回数据时再进行提示.
两种方式都有实现,简单说明一下1方式的实现, 本篇着重介绍2方式的实现
- 轮询实现方式
- 商户扫码,判断二维码信息的合法性, 合法进行扣款;
- 扣款的同时在redis里存储一条记录, key是用户的标识, value是需要返回给用户的消息;
- 用户端轮询redis中的数据, 返回数据时提示用户扣款结果.
这种方式对服务器的资源占用较大(仅做说明)
- webSocket实现方式
- 用户进入二维码扫码界面,与服务器建立webSocket连接, 服务端用map存储, key: 用户标识 value: session;
- 商户扫码后, 在map中查找用户的session, 向用户发送消息;
- 前端接收到webSocket的信息后, 进行相应的展示.
nginx配置
小程序的webSocket是https协议, 我们项目使用的是nginx转发, 需要配置nginx, 贴一下nginx配置,
如果不配置nginx的upgrade协议, wss请求会报错:Error during WebSocket handshake: Unexpected response code: 400
location /webSocket/ {} 中的webSocket根据自己websocket的注解进行更改
- map $http_upgrade $connection_upgrade {
- default upgrade;
- '' close;
- }
- server {
- listen 443 ssl;
- server_name 你的域名;
- ssl on;
- ssl_certificate cert/你的ssl ;
- ssl_certificate_key cert/你的ssl key.key;
- ssl_session_timeout 5m;
- ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- ssl_prefer_server_ciphers on;
- location / {
- proxy_pass ;
- }
- location /webSocket/ {
- proxy_pass ;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
- }
- }
maven依赖
- <dependency>
- <groupId>javax</groupId>
- <artifactId>javaee-api</artifactId>
- <version>7.0</version>
- <scope>provided</scope>
- </dependency>
webSocket 文件
webSocket的路径中包含了用户标识{userId}和{carwashId}, 这个方法也是借鉴大神的,没有找到出处了, 就没注明啦
- import com.carwa;
- import javax.websocket.*;
- import javax.web;
- import javax.web;
- import java.io.IOException;
- import java.u;
- /**
- * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
- * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
- */
- @ServerEndpoint("/webSocket/{userId}/{carwashId}")
- public class WebSocketTest {
- //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
- private static int onlineCount = 0;
- //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
- private static ConcurrentHashMap<String, WebSocketTest> webSocketSet = new ConcurrentHashMap<String, WebSocketTest>();
- //与某个客户端的连接会话,需要通过它来给客户端发送数据
- private Session session;
- private String userIdCarwashId;
- /**
- * 连接建立成功调用的方法
- * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
- */
- @OnOpen
- public void onOpen(Session session, @PathParam("userId") Integer userId, @PathParam("carwashId") Integer carwashId){
- = session;
- = userId + "_" + carwashId;
- webSocke(userIdCarwashId, this); //加入set中
- addOnlineCount(); //在线数加1
- Sy("有新连接加入!当前在线人数为" + getOnlineCount());
- }
- /**
- * 连接关闭调用的方法
- */
- @OnClose
- public void onClose(){
- webSocke(); //从set中删除
- subOnlineCount(); //在线数减1
- Sy("有一连接关闭!当前在线人数为" + getOnlineCount());
- }
- /**
- * 收到客户端消息后调用的方法
- * @param message 客户端发送过来的消息
- * @param session 可选的参数
- */
- @OnMessage
- public void onMessage(String message, Session session) {
- // Sy("来自客户端的消息:" + message);
- // //群发消息
- // for(WebSocketTest item: webSocketSet){
- // try {
- // i(message);
- // } catch (IOException e) {
- // e.printStackTrace();
- // continue;
- // }
- // }
- }
- /**
- * 发生错误时调用
- * @param session
- * @param error
- */
- @OnError
- public void onError(Session session, Throwable error){
- Sy("发生错误");
- error.printStackTrace();
- }
- /**
- * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
- * @param message
- * @throws IOException
- */
- public static void sendMessage(String userIdCarwashId, String message) throws IOException{
- Sy("当前在线用户数量: " + webSocke());
- WebSocketTest userSocket = webSocke(userIdCarwashId);
- i(userSocket)) {
- u().sendText(message);
- }
- //.getAsyncRemote().sendText(message);
- }
- public static synchronized int getOnlineCount() {
- return onlineCount;
- }
- public static synchronized void addOnlineCount() {
- WebSocke;
- }
- public static synchronized void subOnlineCount() {
- WebSocke;
- }
- }
还需要在中增加包扫描
<context:component-scan base-package="com.carwa; />
调用发送消息的方法为
WebSocke(QRUserId + "_" + QRCarwashId, "0_扣费成功_剩余次数: " + restNumber);
小程序调试
在这里贴一下小程序的调试代码
web
- <!--pages/websocket/web-->
- <view class="page">
- <view class="page__hd">
- </view>
- <view class="page__bd">
- <button bindtap="connectWebsocket" type="primary">连接websocket</button>
- </view>
- </view>
web
- // pages/websocket/web
- Page({
- /**
- * 页面的初始数据
- */
- data: {
- },
- connectWebsocket: function () {
- wx.connectSocket({
- url: 'wss://你的域名/webSocket/17/2',
- data: {
- x: '1',
- y: '22'
- },
- header: {
- 'content-type': 'application/json'
- },
- method: "GET"
- })
- wx.onSocketOpen(function (res) {
- con('WebSocket连接已打开!')
- })
- wx.onSocketError(function (res) {
- con(res)
- con('WebSocket连接打开失败,请检查!')
- })
- wx.onSocketMessage(function (res) {
- con('收到服务器内容:' + res.data)
- })
- }
- })
商户扫码后的结果
到这里就大功告成了, 如有疑问可以留言