增加websocket
This commit is contained in:
11
pom.xml
11
pom.xml
@@ -59,6 +59,17 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- WebSocket & STOMP 支持 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- WebRTC 信令传输依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-messaging</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
|||||||
54
sql/video_tablet_db.sql
Normal file
54
sql/video_tablet_db.sql
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
Navicat Premium Data Transfer
|
||||||
|
|
||||||
|
Source Server : local_mariadb
|
||||||
|
Source Server Type : MariaDB
|
||||||
|
Source Server Version : 110702 (11.7.2-MariaDB-ubu2404)
|
||||||
|
Source Host : localhost:3305
|
||||||
|
Source Schema : video_tablet_db
|
||||||
|
|
||||||
|
Target Server Type : MariaDB
|
||||||
|
Target Server Version : 110702 (11.7.2-MariaDB-ubu2404)
|
||||||
|
File Encoding : 65001
|
||||||
|
|
||||||
|
Date: 07/08/2025 11:47:20
|
||||||
|
*/
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for devices_sn
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `devices_sn`;
|
||||||
|
CREATE TABLE `devices_sn` (
|
||||||
|
`sn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '设备唯一标识',
|
||||||
|
`device_model` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '设备型号',
|
||||||
|
`device_alias` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定用户给设备的备注',
|
||||||
|
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定的用户id',
|
||||||
|
`bind_phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '绑定用户的手机',
|
||||||
|
`add_time` datetime NOT NULL COMMENT '添加时间',
|
||||||
|
`activation_time` datetime NULL DEFAULT NULL COMMENT '激活时间',
|
||||||
|
PRIMARY KEY (`sn`) USING BTREE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
-- ----------------------------
|
||||||
|
-- Table structure for ordinary_users
|
||||||
|
-- ----------------------------
|
||||||
|
DROP TABLE IF EXISTS `ordinary_users`;
|
||||||
|
CREATE TABLE `ordinary_users` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||||
|
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
|
||||||
|
`device_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
|
||||||
|
`create_time` datetime NULL DEFAULT NULL,
|
||||||
|
`last_login_time` datetime(6) NOT NULL,
|
||||||
|
`update_time` datetime(6) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `UKdu5v5sr43g5bfnji4vb8hg5s3`(`phone`) USING BTREE,
|
||||||
|
UNIQUE INDEX `UK2ty1xmrrgtn89xt7kyxx6ta7h`(`nickname`) USING BTREE
|
||||||
|
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
@@ -25,6 +25,8 @@ public class SecurityConfig {
|
|||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http.csrf(csrf -> csrf.disable())
|
http.csrf(csrf -> csrf.disable())
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/ws/**").permitAll()
|
||||||
|
.requestMatchers("/api/ws/**", "/topic/**").permitAll()
|
||||||
.requestMatchers("/public/**").permitAll()
|
.requestMatchers("/public/**").permitAll()
|
||||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||||
// .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
|
// .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.onekeycall.videotablet.config;
|
||||||
|
|
||||||
|
import com.onekeycall.videotablet.handler.CustomWebSocketHandler;
|
||||||
|
import com.onekeycall.videotablet.interceptor.AuthHandshakeInterceptor;
|
||||||
|
import com.onekeycall.videotablet.utils.JwtUtil;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||||
|
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSocket
|
||||||
|
public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
@Autowired
|
||||||
|
private JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
private final CustomWebSocketHandler webSocketHandler;
|
||||||
|
|
||||||
|
public WebSocketConfig(CustomWebSocketHandler webSocketHandler) {
|
||||||
|
this.webSocketHandler = webSocketHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||||
|
registry.addHandler(webSocketHandler, "/ws") // 客户端连接端点
|
||||||
|
.setAllowedOrigins("*") // 允许跨域
|
||||||
|
.addInterceptors(new AuthHandshakeInterceptor(jwtUtil)); // 握手拦截器(如JWT校验)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.onekeycall.videotablet.controller;
|
||||||
|
|
||||||
|
import com.onekeycall.videotablet.entity.DeviceInfo;
|
||||||
|
import com.onekeycall.videotablet.entity.User;
|
||||||
|
import com.onekeycall.videotablet.result.Result;
|
||||||
|
import com.onekeycall.videotablet.service.DeviceSnService;
|
||||||
|
import com.onekeycall.videotablet.service.UserService;
|
||||||
|
import com.onekeycall.videotablet.utils.JwtUtil;
|
||||||
|
import com.onekeycall.videotablet.utils.TextUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/public")
|
||||||
|
public class BindSnController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtUtil jwtUtil;
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
@Autowired
|
||||||
|
private DeviceSnService deviceSnService;
|
||||||
|
|
||||||
|
@PostMapping("/bind_sn")
|
||||||
|
public Result bindSn(
|
||||||
|
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
|
||||||
|
@RequestParam(value = "user_id") String userId, @RequestParam(value = "sn") String sn) {
|
||||||
|
// 1. 校验 Authorization 头
|
||||||
|
if (!authHeader.startsWith("Bearer ")) {
|
||||||
|
return Result.error().message("Invalid Authorization header");
|
||||||
|
}
|
||||||
|
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
|
||||||
|
|
||||||
|
// 2. 校验 Token
|
||||||
|
if (!jwtUtil.validateAccessToken(userId, token, deviceId)) {
|
||||||
|
return Result.error().message("Invalid token");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userService.getUserByUserId(userId);
|
||||||
|
String userPhone = user.getPhone();
|
||||||
|
|
||||||
|
// 3. 校验 sn 是否存在
|
||||||
|
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
|
||||||
|
if (deviceInfo == null) {
|
||||||
|
return Result.error().message("sn not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(deviceInfo.getBindPhone())) {
|
||||||
|
return Result.error().message("sn already bind");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,15 +5,11 @@ import com.onekeycall.videotablet.entity.User;
|
|||||||
import com.onekeycall.videotablet.result.Result;
|
import com.onekeycall.videotablet.result.Result;
|
||||||
import com.onekeycall.videotablet.service.UserService;
|
import com.onekeycall.videotablet.service.UserService;
|
||||||
import com.onekeycall.videotablet.utils.JwtUtil;
|
import com.onekeycall.videotablet.utils.JwtUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -37,15 +33,34 @@ public class LoginController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<?> registerUser(@RequestBody RegisterRequest registerRequest) {
|
public Result registerUser(@RequestHeader("Device-ID") String deviceId,
|
||||||
|
@RequestParam(value = "user_id") String userId, @RequestParam String password) {
|
||||||
try {
|
try {
|
||||||
userService.registerUser(registerRequest.getUsername(), registerRequest.getPassword());
|
userService.registerUser(userId, password);
|
||||||
return new ResponseEntity<>("User registered successfully", HttpStatus.CREATED);
|
return Result.ok().message("User registered successfully");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
return Result.error().message(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Result login(
|
||||||
|
@RequestHeader("Device-ID") String deviceId,
|
||||||
|
@RequestParam(value = "user_id") String userId, @RequestParam String password) {
|
||||||
|
// 1. 创建认证令牌
|
||||||
|
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(userId, password);
|
||||||
|
|
||||||
|
// 2. 使用 AuthenticationManager 进行认证(核心步骤)
|
||||||
|
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
||||||
|
|
||||||
|
// 3. 认证成功后生成 JWT
|
||||||
|
User userDetails = (User) authentication.getPrincipal();
|
||||||
|
TokenPair tokenPair = jwtUtil.generateTokenPair(userDetails.getUserId(), deviceId);
|
||||||
|
|
||||||
|
// 4. 返回 Token
|
||||||
|
return Result.ok().data(Collections.singletonMap("token", tokenPair.toMap()));
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/phone_login")
|
@PostMapping("/phone_login")
|
||||||
public Result phoneLogin(
|
public Result phoneLogin(
|
||||||
@RequestHeader("Device-ID") String deviceId,
|
@RequestHeader("Device-ID") String deviceId,
|
||||||
@@ -74,48 +89,6 @@ public class LoginController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
|
||||||
public ResponseEntity<?> login(
|
|
||||||
@RequestHeader("Device-ID") String deviceId,
|
|
||||||
@RequestParam(value = "user_id") String userId, @RequestParam String password) {
|
|
||||||
// 1. 创建认证令牌
|
|
||||||
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(userId, password);
|
|
||||||
|
|
||||||
// 2. 使用 AuthenticationManager 进行认证(核心步骤)
|
|
||||||
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
|
||||||
|
|
||||||
// 3. 认证成功后生成 JWT
|
|
||||||
User userDetails = (User) authentication.getPrincipal();
|
|
||||||
TokenPair tokenPair = jwtUtil.generateTokenPair(userDetails.getUserId(), deviceId);
|
|
||||||
|
|
||||||
// 4. 返回 Token
|
|
||||||
return ResponseEntity.ok(Collections.singletonMap("token", tokenPair.toMap()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册请求参数类
|
|
||||||
public static class RegisterRequest {
|
|
||||||
private String username;
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
// Getters and Setters
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username) {
|
|
||||||
this.username = username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassword(String password) {
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/phone_register")
|
@PostMapping("/phone_register")
|
||||||
public Result registerByPhone(
|
public Result registerByPhone(
|
||||||
@RequestParam String phone, @RequestParam String code,
|
@RequestParam String phone, @RequestParam String code,
|
||||||
@@ -185,6 +158,9 @@ public class LoginController {
|
|||||||
return Result.error().message("verify key is expired");
|
return Result.error().message("verify key is expired");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// @PostMapping("/device_login")
|
||||||
|
// public Result loginByDeviceSn(){
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.onekeycall.videotablet.controller;
|
||||||
|
|
||||||
|
import com.onekeycall.videotablet.handler.CustomWebSocketHandler;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/ws")
|
||||||
|
public class WebSocketController {
|
||||||
|
|
||||||
|
private final CustomWebSocketHandler webSocketHandler;
|
||||||
|
|
||||||
|
public WebSocketController(CustomWebSocketHandler webSocketHandler) {
|
||||||
|
this.webSocketHandler = webSocketHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/broadcast")
|
||||||
|
public ResponseEntity<String> broadcast(@RequestBody String message) {
|
||||||
|
webSocketHandler.broadcast(message);
|
||||||
|
return ResponseEntity.ok("广播发送成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ public class DeviceInfo {
|
|||||||
private String sn;
|
private String sn;
|
||||||
|
|
||||||
@Column(name = "device_model", nullable = false)
|
@Column(name = "device_model", nullable = false)
|
||||||
private String device_model;
|
private String deviceModel;
|
||||||
|
|
||||||
@Column(name = "device_alias")
|
@Column(name = "device_alias")
|
||||||
private String deviceAlias;
|
private String deviceAlias;
|
||||||
@@ -23,12 +23,12 @@ public class DeviceInfo {
|
|||||||
private String userId;
|
private String userId;
|
||||||
|
|
||||||
@Column(name = "bind_phone")
|
@Column(name = "bind_phone")
|
||||||
private String bind_phone;
|
private String bindPhone;
|
||||||
|
|
||||||
@Column(name = "add_time", nullable = false)
|
@Column(name = "add_time", nullable = false)
|
||||||
private Date add_time;
|
private Date addTime;
|
||||||
|
|
||||||
@Column(name = "activation_time")
|
@Column(name = "activation_time")
|
||||||
private Date activation_time;
|
private Date activationTime;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.onekeycall.videotablet.handler;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.socket.CloseStatus;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class CustomWebSocketHandler extends TextWebSocketHandler {
|
||||||
|
|
||||||
|
// 存储活跃会话(线程安全)
|
||||||
|
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(WebSocketSession session) {
|
||||||
|
String sessionId = session.getId();
|
||||||
|
sessions.put(sessionId, session);
|
||||||
|
log.info("✅ 连接建立: ID={}, 当前连接数: {}", sessionId, sessions.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||||
|
String payload = message.getPayload();
|
||||||
|
log.info("📩 收到消息: {}", payload);
|
||||||
|
|
||||||
|
// 示例:回复客户端
|
||||||
|
String reply = "服务器已接收: " + payload;
|
||||||
|
try {
|
||||||
|
session.sendMessage(new TextMessage(reply));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("回复失败: ID={}", session.getId(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:广播给所有客户端
|
||||||
|
// broadcast("用户" + sessionId + "说: " + payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||||
|
String sessionId = session.getId();
|
||||||
|
sessions.remove(sessionId);
|
||||||
|
log.info("❌ 连接关闭: ID={}, 原因: {}", sessionId, status.getReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 广播消息给所有客户端
|
||||||
|
public void broadcast(String message) {
|
||||||
|
sessions.values().stream()
|
||||||
|
.filter(WebSocketSession::isOpen)
|
||||||
|
.forEach(session -> {
|
||||||
|
try {
|
||||||
|
session.sendMessage(new TextMessage(message));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("广播失败: ID={}", session.getId(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.onekeycall.videotablet.handler;
|
|||||||
import com.onekeycall.videotablet.result.Result;
|
import com.onekeycall.videotablet.result.Result;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||||
@@ -98,4 +99,13 @@ public class GlobalExceptionHandler {
|
|||||||
// });
|
// });
|
||||||
// return errors;
|
// return errors;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@ResponseBody
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
@ExceptionHandler(InvalidTokenException.class)
|
||||||
|
public Result handleInvalidTokenException(InvalidTokenException e) {
|
||||||
|
// 控制台打印异常
|
||||||
|
e.printStackTrace();
|
||||||
|
return Result.error().message(e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.onekeycall.videotablet.handler;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class SignalingHandler extends TextWebSocketHandler {
|
||||||
|
private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||||
|
// 使用 Jackson 解析
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
try {
|
||||||
|
JsonNode json = mapper.readTree(message.getPayload());
|
||||||
|
String targetId = json.get("targetId").asText();// 目标用户ID
|
||||||
|
if (sessions.containsKey(targetId)) {
|
||||||
|
sessions.get(targetId).sendMessage(message); // 转发信令
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(WebSocketSession session) {
|
||||||
|
String deviceId = session.getHandshakeHeaders().getFirst("device-id");
|
||||||
|
sessions.put(deviceId, session); // 保存会话
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.onekeycall.videotablet.interceptor;
|
||||||
|
|
||||||
|
import com.onekeycall.videotablet.utils.JwtUtil;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.web.socket.WebSocketHandler;
|
||||||
|
import org.springframework.web.socket.server.HandshakeInterceptor;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AuthHandshakeInterceptor implements HandshakeInterceptor {
|
||||||
|
|
||||||
|
private final JwtUtil jwtUtil;
|
||||||
|
|
||||||
|
public AuthHandshakeInterceptor(JwtUtil jwtUtil) {
|
||||||
|
this.jwtUtil = jwtUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||||
|
String userId = request.getHeaders().getFirst("user_id");
|
||||||
|
String authHeader = request.getHeaders().getFirst("Authorization");
|
||||||
|
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
|
||||||
|
String deviceId = request.getHeaders().getFirst("Device-ID");
|
||||||
|
return jwtUtil.validateAccessToken(userId, token, deviceId); // 自定义校验逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.onekeycall.videotablet.repository;
|
||||||
|
|
||||||
|
import com.onekeycall.videotablet.entity.DeviceInfo;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface DeviceSnRepository extends JpaRepository<DeviceInfo, Long> {
|
||||||
|
DeviceInfo findBySn(String deviceId);
|
||||||
|
}
|
||||||
@@ -11,4 +11,5 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
|||||||
boolean existsByUserId(String userId);
|
boolean existsByUserId(String userId);
|
||||||
boolean existsPasswordByUserId(String userId);
|
boolean existsPasswordByUserId(String userId);
|
||||||
Optional<User> findUserByPhone(String phone);
|
Optional<User> findUserByPhone(String phone);
|
||||||
|
Optional<User> findUserByUserId(String userId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.onekeycall.videotablet.service;
|
||||||
|
|
||||||
|
import com.onekeycall.videotablet.entity.DeviceInfo;
|
||||||
|
import com.onekeycall.videotablet.repository.DeviceSnRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class DeviceSnService {
|
||||||
|
private final DeviceSnRepository deviceSnRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public DeviceSnService(DeviceSnRepository deviceSnRepository) {
|
||||||
|
this.deviceSnRepository = deviceSnRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceInfo findBySn(String sn) {
|
||||||
|
return deviceSnRepository.findBySn(sn);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,4 +109,8 @@ public class UserService implements UserDetailsService {
|
|||||||
return Result.ok().message("change password success");
|
return Result.ok().message("change password success");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public User getUserByUserId(String userId) {
|
||||||
|
return userRepository.findUserByUserId(userId).orElseThrow(() -> new RuntimeException("User not found with userId: " + userId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.onekeycall.videotablet.utils;
|
||||||
|
|
||||||
|
public class PushUtils {
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user