diff --git a/pom.xml b/pom.xml
index 7abf7f3..531abb2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,6 +59,17 @@
org.springframework.boot
spring-boot-starter-jdbc
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+
+
+ org.springframework
+ spring-messaging
+
org.springdoc
springdoc-openapi-starter-webmvc-ui
diff --git a/sql/video_tablet_db.sql b/sql/video_tablet_db.sql
new file mode 100644
index 0000000..f912d13
--- /dev/null
+++ b/sql/video_tablet_db.sql
@@ -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;
diff --git a/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java b/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java
index db14cbb..c1c438a 100644
--- a/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java
+++ b/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java
@@ -25,6 +25,8 @@ public class SecurityConfig {
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
+ .requestMatchers("/ws/**").permitAll()
+ .requestMatchers("/api/ws/**", "/topic/**").permitAll()
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
// .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
diff --git a/src/main/java/com/onekeycall/videotablet/config/WebSocketConfig.java b/src/main/java/com/onekeycall/videotablet/config/WebSocketConfig.java
new file mode 100644
index 0000000..a508813
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/config/WebSocketConfig.java
@@ -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校验)
+ }
+}
diff --git a/src/main/java/com/onekeycall/videotablet/controller/BindSnController.java b/src/main/java/com/onekeycall/videotablet/controller/BindSnController.java
new file mode 100644
index 0000000..33cf7fd
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/controller/BindSnController.java
@@ -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();
+ }
+}
diff --git a/src/main/java/com/onekeycall/videotablet/controller/LoginController.java b/src/main/java/com/onekeycall/videotablet/controller/LoginController.java
index 27fea31..dd9bc29 100644
--- a/src/main/java/com/onekeycall/videotablet/controller/LoginController.java
+++ b/src/main/java/com/onekeycall/videotablet/controller/LoginController.java
@@ -5,15 +5,11 @@ import com.onekeycall.videotablet.entity.User;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.JwtUtil;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
-import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@@ -37,15 +33,34 @@ public class LoginController {
}
@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 {
- userService.registerUser(registerRequest.getUsername(), registerRequest.getPassword());
- return new ResponseEntity<>("User registered successfully", HttpStatus.CREATED);
+ userService.registerUser(userId, password);
+ return Result.ok().message("User registered successfully");
} 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")
public Result phoneLogin(
@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")
public Result registerByPhone(
@RequestParam String phone, @RequestParam String code,
@@ -185,6 +158,9 @@ public class LoginController {
return Result.error().message("verify key is expired");
}
}
-
+// @PostMapping("/device_login")
+// public Result loginByDeviceSn(){
+//
+// }
}
\ No newline at end of file
diff --git a/src/main/java/com/onekeycall/videotablet/controller/WebSocketController.java b/src/main/java/com/onekeycall/videotablet/controller/WebSocketController.java
new file mode 100644
index 0000000..912c168
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/controller/WebSocketController.java
@@ -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 broadcast(@RequestBody String message) {
+ webSocketHandler.broadcast(message);
+ return ResponseEntity.ok("广播发送成功");
+ }
+}
diff --git a/src/main/java/com/onekeycall/videotablet/entity/DeviceInfo.java b/src/main/java/com/onekeycall/videotablet/entity/DeviceInfo.java
index 9c4e412..f2fe8b0 100644
--- a/src/main/java/com/onekeycall/videotablet/entity/DeviceInfo.java
+++ b/src/main/java/com/onekeycall/videotablet/entity/DeviceInfo.java
@@ -14,7 +14,7 @@ public class DeviceInfo {
private String sn;
@Column(name = "device_model", nullable = false)
- private String device_model;
+ private String deviceModel;
@Column(name = "device_alias")
private String deviceAlias;
@@ -23,12 +23,12 @@ public class DeviceInfo {
private String userId;
@Column(name = "bind_phone")
- private String bind_phone;
+ private String bindPhone;
@Column(name = "add_time", nullable = false)
- private Date add_time;
+ private Date addTime;
@Column(name = "activation_time")
- private Date activation_time;
+ private Date activationTime;
}
diff --git a/src/main/java/com/onekeycall/videotablet/handler/CustomWebSocketHandler.java b/src/main/java/com/onekeycall/videotablet/handler/CustomWebSocketHandler.java
new file mode 100644
index 0000000..d99e40a
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/handler/CustomWebSocketHandler.java
@@ -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 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);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/onekeycall/videotablet/handler/GlobalExceptionHandler.java b/src/main/java/com/onekeycall/videotablet/handler/GlobalExceptionHandler.java
index 2572cd2..a84fa7f 100644
--- a/src/main/java/com/onekeycall/videotablet/handler/GlobalExceptionHandler.java
+++ b/src/main/java/com/onekeycall/videotablet/handler/GlobalExceptionHandler.java
@@ -3,6 +3,7 @@ package com.onekeycall.videotablet.handler;
import com.onekeycall.videotablet.result.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
@@ -98,4 +99,13 @@ public class GlobalExceptionHandler {
// });
// return errors;
// }
+
+ @ResponseBody
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ @ExceptionHandler(InvalidTokenException.class)
+ public Result handleInvalidTokenException(InvalidTokenException e) {
+ // 控制台打印异常
+ e.printStackTrace();
+ return Result.error().message(e.getMessage());
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/onekeycall/videotablet/handler/SignalingHandler.java b/src/main/java/com/onekeycall/videotablet/handler/SignalingHandler.java
new file mode 100644
index 0000000..ace7de2
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/handler/SignalingHandler.java
@@ -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 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); // 保存会话
+ }
+}
diff --git a/src/main/java/com/onekeycall/videotablet/interceptor/AuthHandshakeInterceptor.java b/src/main/java/com/onekeycall/videotablet/interceptor/AuthHandshakeInterceptor.java
new file mode 100644
index 0000000..898a1a1
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/interceptor/AuthHandshakeInterceptor.java
@@ -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 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) {
+
+ }
+}
diff --git a/src/main/java/com/onekeycall/videotablet/repository/DeviceSnRepository.java b/src/main/java/com/onekeycall/videotablet/repository/DeviceSnRepository.java
new file mode 100644
index 0000000..957cbab
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/repository/DeviceSnRepository.java
@@ -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 findBySn(String deviceId);
+}
diff --git a/src/main/java/com/onekeycall/videotablet/repository/UserRepository.java b/src/main/java/com/onekeycall/videotablet/repository/UserRepository.java
index 9cb1dd2..49dccd6 100644
--- a/src/main/java/com/onekeycall/videotablet/repository/UserRepository.java
+++ b/src/main/java/com/onekeycall/videotablet/repository/UserRepository.java
@@ -11,4 +11,5 @@ public interface UserRepository extends JpaRepository {
boolean existsByUserId(String userId);
boolean existsPasswordByUserId(String userId);
Optional findUserByPhone(String phone);
+ Optional findUserByUserId(String userId);
}
\ No newline at end of file
diff --git a/src/main/java/com/onekeycall/videotablet/service/DeviceSnService.java b/src/main/java/com/onekeycall/videotablet/service/DeviceSnService.java
new file mode 100644
index 0000000..54efaad
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/service/DeviceSnService.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/onekeycall/videotablet/service/UserService.java b/src/main/java/com/onekeycall/videotablet/service/UserService.java
index ae36159..fb32df5 100644
--- a/src/main/java/com/onekeycall/videotablet/service/UserService.java
+++ b/src/main/java/com/onekeycall/videotablet/service/UserService.java
@@ -109,4 +109,8 @@ public class UserService implements UserDetailsService {
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));
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/onekeycall/videotablet/utils/PushUtils.java b/src/main/java/com/onekeycall/videotablet/utils/PushUtils.java
new file mode 100644
index 0000000..0b25cba
--- /dev/null
+++ b/src/main/java/com/onekeycall/videotablet/utils/PushUtils.java
@@ -0,0 +1,4 @@
+package com.onekeycall.videotablet.utils;
+
+public class PushUtils {
+}