引言
在智能柜仪器管理系统中,用户完成人脸识别后,需要让Android客户端和服务端前端页面实时同步显示该用户可领取的仪器列表,传统的轮询方式存在实时性差、资源消耗高的问题。本文将详细介绍如何基于WebSocket协议,结合Spring Boot、Vue.js和Android(Kotlin)技术栈,实现人脸识别后仪器领取信息的跨端实时同步方案。
一、需求与架构分析
1. 核心需求
用户人脸识别成功后:
- Android客户端(SmartCabinet)在刷卡页面展示可领取仪器列表;
- 服务端Vue前端(SmartCabinetManageVue)实时展示该用户可领取仪器信息。
2. 现有系统架构
| 端侧 | 技术栈 | 核心组件 |
|---|---|---|
| 客户端 | Kotlin | FaceRecognitionActivity(人脸识别)、CardScanActivity(刷卡验证)、ExternalApiService(服务端通信) |
| 服务端 | Java + Spring Boot | RESTful API、UserQueryVO(用户信息)、UsersCardsAuthVO(卡片权限) |
| 前端 | Vue.js | 智能柜管理界面(无实时监控能力) |
二、核心方案设计
1. 技术选型
针对跨端实时通信需求,选择WebSocket作为核心通信协议,各端技术适配如下:
| 组件 | 技术选型 | 说明 |
|---|---|---|
| 服务端WebSocket | Spring Boot WebSocket + STOMP | 提供标准化的WebSocket服务和消息代理能力 |
| Android客户端 | OkHttp WebSocket | 轻量、稳定的Android端WebSocket实现 |
| Vue前端 | StompJS + SockJS | 兼容STOMP协议,适配浏览器WebSocket兼容性问题 |
2. 核心流程
- 客户端人脸识别成功后,通过WebSocket向服务端发送用户信息;
- 服务端接收消息后,通过STOMP协议广播至所有连接的前端客户端;
- 前端接收消息,实时更新页面展示用户可领取仪器列表。
三、服务端实现(Spring Boot)
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. WebSocket配置
配置STOMP代理和端点,支持跨域和SockJS降级:
package cn.harry.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic"); // 消息广播前缀
config.setApplicationDestinationPrefixes("/app"); // 客户端发送消息前缀
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS(); // 启用SockJS
}
}
3. 消息模型与控制器
定义通用WebSocket消息结构,实现消息接收与广播:
// 消息模型
package cn.harry.modular.smartcabinet.api.dto;
import lombok.Data;
@Data
public class WebSocketMessage<T> {
private String eventType; // 事件类型
private T data; // 业务数据
private Long timestamp; // 时间戳
}
// WebSocket控制器
package cn.harry.modular.smartcabinet.controller;
import cn.harry.modular.smartcabinet.api.dto.WebSocketMessage;
import cn.harry.modular.smartcabinet.api.vo.UserQueryVO;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class WebSocketController {
@MessageMapping("/faceRecognitionSuccess") // 客户端消息接收地址
@SendTo("/topic/faceRecognition") // 前端订阅地址
public WebSocketMessage<UserQueryVO> handleFaceRecognitionSuccess(WebSocketMessage<UserQueryVO> message) {
return message; // 广播接收到的用户信息
}
}
四、Android客户端实现(Kotlin)
1. 引入依赖
// app/build.gradle.kts
implementation("com.squareup.okhttp3:okhttp:4.12.0")
2. WebSocket工具类
封装OkHttp WebSocket连接、消息发送与断开逻辑:
package cn.harry.cabinet.websocket
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import java.util.concurrent.TimeUnit
class WebSocketClient(private val url: String) {
private val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build()
private var webSocket: WebSocket? = null
fun connect(listener: WebSocketListener) {
val request = Request.Builder()
.url(url)
.build()
webSocket = client.newWebSocket(request, listener)
}
fun send(message: String) {
webSocket?.send(message)
}
fun disconnect() {
webSocket?.close(1000, "Normal closure")
}
}
3. 人脸识别成功后发送消息
在人脸识别成功回调中,构建并发送WebSocket消息:
private fun onAuthSuccess(user: GetUserResponse) {
// 原有业务逻辑...
// 发送WebSocket消息
sendFaceRecognitionSuccessMessage(user)
}
private fun sendFaceRecognitionSuccessMessage(user: GetUserResponse) {
lifecycleScope.launch {
try {
val wsUrl = withContext(Dispatchers.IO) {
configRepository.getByTypeAndKey(
AppConfigKeys.TYPE_SERVER,
AppConfigKeys.KEY_WEBSOCKET_URL
)?.value ?: "ws://localhost:8080/ws"
}
val wsClient = WebSocketClient(wsUrl)
wsClient.connect(object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
val message = mapOf(
"eventType" to "FACE_RECOGNITION_SUCCESS",
"data" to user,
"timestamp" to System.currentTimeMillis()
)
webSocket.send(Json.encodeToString(message))
webSocket.close(1000, "Message sent")
}
})
} catch (e: Exception) {
Log.e(TAG, "发送WebSocket消息失败", e)
}
}
}
五、Vue前端实现
1. 安装依赖
pnpm add stompjs sockjs-client
2. WebSocket工具封装
// src/utils/websocket.js
import SockJS from 'sockjs-client'
import Stomp from 'stompjs'
class WebSocketService {
constructor() {
this.stompClient = null
this.callbacks = {}
}
connect(url = '/ws') {
return new Promise((resolve, reject) => {
try {
const socket = new SockJS(url)
this.stompClient = Stomp.over(socket)
this.stompClient.connect({}, () => {
this.stompClient.subscribe('/topic/faceRecognition', (message) => {
const data = JSON.parse(message.body)
this.handleMessage(data)
})
resolve()
}, (error) => {
reject(error)
})
} catch (error) {
reject(error)
}
})
}
on(eventType, callback) {
if (!this.callbacks[eventType]) {
this.callbacks[eventType] = []
}
this.callbacks[eventType].push(callback)
}
handleMessage(message) {
const { eventType } = message
if (this.callbacks[eventType]) {
this.callbacks[eventType].forEach(callback => callback(message))
}
}
}
export default new WebSocketService()
3. 全局初始化连接
// src/main.ts
import WebSocketService from './utils/websocket'
WebSocketService.connect()
window.addEventListener('beforeunload', () => {
WebSocketService.disconnect()
})
4. 实时监控页面开发
<template>
<div class="real-time-monitor">
<h3>实时监控</h3>
<div v-if="currentUser" class="user-info">
<h4>当前识别用户</h4>
<p>姓名:{{ currentUser.name }}</p>
<p>工号:{{ currentUser.employeeId }}</p>
<p>部门:{{ currentUser.dept }}</p>
</div>
<div v-if="currentUser?.cardsAuth && currentUser.cardsAuth.length > 0" class="cards-auth">
<h4>可领取仪器</h4>
<el-table :data="currentUser.cardsAuth" style="width: 100%">
<el-table-column prop="typeName" label="仪器类型" />
<el-table-column prop="maxNum" label="最大领取数量" />
<el-table-column prop="receivedNum" label="已领取数量" />
<el-table-column label="可领取数量" :formatter="(row) => row.maxNum - row.receivedNum" />
</el-table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import WebSocketService from '@/utils/websocket'
const currentUser = ref(null)
const handleFaceRecognitionSuccess = (message) => {
currentUser.value = message.data
setTimeout(() => {
currentUser.value = null
}, 5000) // 5秒后清除展示
}
onMounted(() => {
WebSocketService.on('FACE_RECOGNITION_SUCCESS', handleFaceRecognitionSuccess)
})
</script>
六、方案评估与扩展
1. 方案优势
- 实时性:基于WebSocket长连接,消息秒级同步,优于轮询;
- 低耦合:客户端、前端通过服务端广播通信,无直接依赖;
- 易扩展:可快速添加刷卡成功、仪器归还等实时事件。
2. 优化方向
- 连接可靠性:添加WebSocket断线重连、心跳检测机制;
- 消息可靠性:增加消息确认机制,避免消息丢失;
- 权限控制:限制前端实时监控页面的访问权限,仅对管理员开放;
- 性能优化:服务端添加连接数限制,避免高并发下的性能问题。
七、总结
本文基于WebSocket协议,结合Spring Boot、Vue和Android技术栈,实现了智能柜人脸识别后仪器领取信息的跨端实时同步。该方案不仅满足了核心业务需求,还具备良好的可扩展性和维护性,可为类似的跨端实时通信场景提供参考。