增加添加默认设备头像

This commit is contained in:
2025-08-25 10:05:10 +08:00
parent 1038c1b0d4
commit 2cfb4f0b3e
23 changed files with 290 additions and 16 deletions

View File

@@ -1,6 +1,11 @@
FROM eclipse-temurin:21-jdk-jammy
MAINTAINER TongTongStudio <tongtongstudios@gmail.com>
RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak
RUN mkdir /data
RUN mkdir /data/uploads/
RUN mkdir /data/uploads/tablet/
RUN mkdir /data/uploads/tablet/avatar/
VOLUME /tmp
ADD target/*.jar app.jar
EXPOSE 8088

View File

@@ -101,6 +101,11 @@
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.2.RELEASE</version> <!-- 推荐 2.x 最新版本 -->
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>

View File

@@ -1,8 +1,12 @@
package com.onekeycall.videotablet;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@MapperScan({"com.onekeycall.videotablet.mapper",})
@SpringBootApplication
public class VideoTabletApplication {

View File

@@ -0,0 +1,7 @@
package com.onekeycall.videotablet.config;
public class FilePath {
public static final String TABLET_PATH = "tablet";
public static final String AVATAR_PATH = "avatar";
}

View File

@@ -0,0 +1,15 @@
package com.onekeycall.videotablet.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class FileService {
@Value("${file.upload.path}")
private String uploadPath;
// 使用uploadPath操作外部文件
public void processFile(String fileName) {
String fullPath = uploadPath + fileName;
// 文件操作逻辑...
}
}

View File

@@ -30,6 +30,7 @@ public class SecurityConfig {
.requestMatchers("/public/**").permitAll()
.requestMatchers("/sn/**").permitAll()
.requestMatchers("/user/**").permitAll()
.requestMatchers("/rtc/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
// .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/user/**").permitAll()

View File

@@ -2,12 +2,14 @@ package com.onekeycall.videotablet.config;
import com.onekeycall.videotablet.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//@Configuration
//public class WebConfig implements WebMvcConfigurer {
@Configuration
public class WebConfig implements WebMvcConfigurer {
// @Autowired
// private TokenInterceptor tokenInterceptor;
//
@@ -17,4 +19,4 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// .addPathPatterns("/user/**") // 保护用户相关端点
// .excludePathPatterns("/public/**"); // 开放登录注册
// }
//}
}

View File

@@ -150,7 +150,7 @@ public class BindSnController {
/**
* 获取平板sn绑定状态
*
* 标准的通过平板获取接口,需要 Device-Token Device-ID Device-Sig
* @param deviceToken
* @param deviceId
* @param deviceSig
@@ -158,7 +158,7 @@ public class BindSnController {
* @return
*/
// TODO: 2025/8/22 Device_Token在docker无法被接收到使用Device-Token代替
@PostMapping("/get_bind_statu")
@GetMapping("/get_bind_statu")
public Result getBindStatus(
@RequestHeader("Device-Token") String deviceToken, @RequestHeader("Device-ID") String deviceId,
@RequestHeader("Device-Sig") String deviceSig,

View File

@@ -1,10 +1,15 @@
package com.onekeycall.videotablet.controller;
import com.onekeycall.videotablet.entity.DeviceInfo;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.DeviceSnService;
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.*;
import java.util.List;
@RestController
@RequestMapping("/sn")
public class DevicesController {
@@ -13,5 +18,38 @@ public class DevicesController {
@Autowired
private DeviceSnService deviceSnService;
@GetMapping("/get_sn_list")
public Result register(@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId, @RequestParam(value = "sn", required = false) 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");
}
if (TextUtils.isEmpty(sn)) {
List<DeviceInfo> deviceInfos = deviceSnService.findByUserId(userId);
if (deviceInfos == null || deviceInfos.isEmpty()) {
return Result.notFound().message("sn not found");
} else {
return Result.ok().data("deviceInfos", deviceInfos);
}
}else {
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.notFound().message("sn not found");
}
if(!deviceInfo.getUserId().equals(userId)) {
return Result.error().message("sn not belong to user");
}
return Result.ok().data("deviceInfo", deviceInfo);
}
}
}

View File

@@ -1,17 +1,26 @@
package com.onekeycall.videotablet.controller;
import com.onekeycall.videotablet.config.FilePath;
import com.onekeycall.videotablet.entity.DeviceInfo;
import com.onekeycall.videotablet.entity.TabletDefaultSettings;
import com.onekeycall.videotablet.mapper.TabletDefaultSettingsMapper;
import com.onekeycall.videotablet.repository.TabletDefaultSettingsRepository;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.DeviceSnService;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.CXAESUtil;
import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.TextUtils;
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -21,12 +30,57 @@ import java.util.Map;
public class ManageSnController {
Logger logger = LoggerFactory.getLogger(ManageSnController.class);
@Value("${file.upload.path}")
private String uploadPath;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserService userService;
@Autowired
private DeviceSnService deviceSnService;
@Autowired
private TabletDefaultSettingsMapper tabletDefaultSettingsMapper;
@Autowired
private TabletDefaultSettingsRepository tabletDefaultSettingsRepository;
@PostMapping("/tablet/upload_avatar")
public Result uploadAvatar(@RequestParam("file") MultipartFile multipartFile) {
String avatarPath = uploadPath + File.separator + FilePath.TABLET_PATH + File.separator + FilePath.AVATAR_PATH + File.separator;
File fileDir = new File(avatarPath);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
String originalFilename = multipartFile.getOriginalFilename();
logger.info("originalFilename:" + originalFilename);
File file = new File(avatarPath + File.separator + originalFilename);
logger.info("file path = " + file.getAbsolutePath());
try {
multipartFile.transferTo(file);
} catch (IOException e) {
logger.error(e.getMessage());
return Result.error().message("upload avatarPath failed");
}
// 检查是否存在默认设置记录
TabletDefaultSettings tabletDefaultSettings = tabletDefaultSettingsMapper.getDefaultSettings();
if (tabletDefaultSettings == null) {
// 如果不存在,则创建新记录
tabletDefaultSettings = new TabletDefaultSettings();
tabletDefaultSettings.setDefaultAvatar(originalFilename);
tabletDefaultSettingsMapper.insertDefaultSettings(tabletDefaultSettings);
} else {
// 如果存在,则更新记录
tabletDefaultSettings.setDefaultAvatar(originalFilename);
tabletDefaultSettingsMapper.updateDefaultSettings(tabletDefaultSettings);
}
return Result.ok();
}
@PostMapping("/add_sn")
public Result addSn(

View File

@@ -6,6 +6,7 @@ import com.onekeycall.videotablet.service.DeviceSnService;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.tencent.trtc.TLSSigAPIv2;
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.*;
@@ -14,7 +15,7 @@ import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/public")
@RequestMapping("/rtc")
public class TencentTrtcController {
@Autowired
private JwtUtil jwtUtil;
@@ -38,12 +39,43 @@ public class TencentTrtcController {
return Result.error().message("Invalid token");
}
TLSSigAPIv2 tlsSigAPIv2 = new TLSSigAPIv2(1600100994, "ccc0d591fe50bd9c05df7e3182256eb43fd83d1127ae7dc01a3d256dd80f3ae2");
TLSSigAPIv2 tlsSigAPIv2 = new TLSSigAPIv2(1600103026, "78c08f86a7a1fa17bb76e1fdce824e983ce44fda132f470ad03dc868176a3afa");
String sig = tlsSigAPIv2.genUserSig(userId);
Map<String, Object> map = new HashMap<>();
map.put("sig", sig);
map.put("expire", Instant.now().getEpochSecond() + TLSSigAPIv2.EXPIRETIME);
return Result.ok().data(map);
}
@GetMapping("/get_tablet_trtc_sig")
public Result getTabletTrtcSig(
@RequestHeader("Device-Token") String deviceToken, @RequestHeader("Device-ID") String deviceId,
@RequestHeader("Device-Sig") String deviceSig,
@RequestParam(value = "sn") String sn
) {
if (!jwtUtil.validateDeviceToken(deviceToken, deviceId, sn)) {
return Result.error().message("Invalid token");
}
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.notFound().message("sn not found");
}
if (!deviceInfo.getBindSig().equals(deviceSig)) {
return Result.error().message("device sig not match");
}
if (TextUtils.isEmpty(deviceInfo.getBindPhone())) {
return Result.error().message("sn not bind");
}
TLSSigAPIv2 tlsSigAPIv2 = new TLSSigAPIv2(1600103026, "78c08f86a7a1fa17bb76e1fdce824e983ce44fda132f470ad03dc868176a3afa");
String sig = tlsSigAPIv2.genUserSig(sn);
Map<String, Object> map = new HashMap<>();
map.put("sig", sig);
map.put("expire", Instant.now().getEpochSecond() + TLSSigAPIv2.EXPIRETIME);
return Result.ok().data(map);
}
}

View File

@@ -0,0 +1,16 @@
package com.onekeycall.videotablet.entity;
import jakarta.persistence.*;
import lombok.Data;
@Data
@Entity
@Table(name = "tablet_default_settings")
public class Contacts {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id",unique = true, nullable = false)
private Long id;
}

View File

@@ -27,6 +27,9 @@ public class DeviceInfo {
@Column(name = "device_alias")
private String deviceAlias;
@Column(name = "tablet_avatar")
private String tabletAvatar;
@Convert(converter = AesAttributeConverter.class)
@Column(name = "user_id")
private String userId;

View File

@@ -0,0 +1,21 @@
package com.onekeycall.videotablet.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
@Entity
@Table(name = "tablet_default_settings")
public class TabletDefaultSettings {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id",unique = true, nullable = false)
private Long id;
@Column(name = "default_avatar", unique = true, nullable = false)
private String defaultAvatar;
}

View File

@@ -24,11 +24,10 @@ public class User implements UserDetails {
@Column(name = "id")
private Long id;
// 使用@Convert注解指定转换器
@Convert(converter = AesAttributeConverter.class)
@Column(name = "user_id", unique = true, nullable = false)
private String userId;
// 使用@Convert注解指定转换器
@Convert(converter = AesAttributeConverter.class)
@Column
private String nickname;

View File

@@ -0,0 +1,16 @@
package com.onekeycall.videotablet.mapper;
import com.onekeycall.videotablet.entity.TabletDefaultSettings;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface TabletDefaultSettingsMapper {
// 获取默认设置
TabletDefaultSettings getDefaultSettings();
// 更新默认设置
int updateDefaultSettings(TabletDefaultSettings tabletDefaultSettings);
// 新增默认设置
int insertDefaultSettings(TabletDefaultSettings tabletDefaultSettings);
}

View File

@@ -3,6 +3,9 @@ package com.onekeycall.videotablet.repository;
import com.onekeycall.videotablet.entity.DeviceInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface DeviceSnRepository extends JpaRepository<DeviceInfo, Long> {
DeviceInfo findBySn(String deviceId);
List<DeviceInfo> findByUserId(String userId);
DeviceInfo findBySn(String sn);
}

View File

@@ -0,0 +1,8 @@
package com.onekeycall.videotablet.repository;
import com.onekeycall.videotablet.entity.TabletDefaultSettings;
import org.springframework.data.jpa.repository.JpaRepository;
public interface TabletDefaultSettingsRepository extends JpaRepository<TabletDefaultSettings, Long> {
TabletDefaultSettings getTabletDefaultSettingsById(Long id);
}

View File

@@ -5,6 +5,8 @@ import com.onekeycall.videotablet.repository.DeviceSnRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeviceSnService {
private final DeviceSnRepository deviceSnRepository;
@@ -14,6 +16,10 @@ public class DeviceSnService {
this.deviceSnRepository = deviceSnRepository;
}
public List<DeviceInfo> findByUserId(String userId) {
return deviceSnRepository.findByUserId(userId);
}
public DeviceInfo findBySn(String sn) {
return deviceSnRepository.findBySn(sn);
}

View File

@@ -1,5 +1,6 @@
spring.application.name=VideoTablet
server.port=8088
file.upload.path=/data/uploads/
## mysql 数据连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@@ -41,7 +42,7 @@ logging.file.name=app.log
logging.file.path=/var/log/myapp
# 设置日志级别
logging.level.root=INFO////////////////////////////////////////////////////////////////////////////
logging.level.root=INFO
logging.level.com.example=DEBUG
@@ -51,4 +52,7 @@ logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger - %msg%n
# 日志文件切割默认10MB分割保留7天
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=30
logging.logback.rollingpolicy.max-history=30
mybatis.type-aliases-package=com.onekeycall.videotablet.entity
mybatis.mapperLocations=classpath:mapper/*.xml

View File

@@ -1,5 +1,6 @@
spring.application.name=VideoTablet
server.port=8088
file.upload.path=/data/uploads/
## mysql 数据连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@@ -41,7 +42,7 @@ logging.file.name=app.log
logging.file.path=/var/log/myapp
# 设置日志级别
logging.level.root=INFO////////////////////////////////////////////////////////////////////////////
logging.level.root=INFO
logging.level.com.example=DEBUG
@@ -51,4 +52,7 @@ logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger - %msg%n
# 日志文件切割默认10MB分割保留7天
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=30
logging.logback.rollingpolicy.max-history=30
mybatis.type-aliases-package=com.onekeycall.videotablet.entity
mybatis.mapperLocations=classpath:mapper/*.xml

View File

@@ -1,5 +1,6 @@
spring.application.name=VideoTablet
server.port=8088
file.upload.path=/data/uploads/
## mysql 数据连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
@@ -41,7 +42,7 @@ logging.file.name=app.log
logging.file.path=/var/log/myapp
# 设置日志级别
logging.level.root=INFO////////////////////////////////////////////////////////////////////////////
logging.level.root=INFO
logging.level.com.example=DEBUG
@@ -51,4 +52,7 @@ logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger - %msg%n
# 日志文件切割默认10MB分割保留7天
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=30
logging.logback.rollingpolicy.max-history=30
mybatis.type-aliases-package=com.onekeycall.videotablet.entity
mybatis.mapperLocations=classpath:mapper/*.xml

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.onekeycall.videotablet.mapper.TabletDefaultSettingsMapper">
<!-- 查询默认设置 -->
<select id="getDefaultSettings" resultType="com.onekeycall.videotablet.entity.TabletDefaultSettings">
SELECT id, default_avatar
FROM tablet_default_settings
WHERE id = 1
LIMIT 1
</select>
<!-- 更新默认设置 -->
<update id="updateDefaultSettings" parameterType="com.onekeycall.videotablet.entity.TabletDefaultSettings">
UPDATE tablet_default_settings
<set>
<if test="defaultAvatar != null">default_avatar = #{defaultAvatar}</if>
</set>
WHERE id = #{id}
</update>
<!-- 插入默认设置 -->
<insert id="insertDefaultSettings" parameterType="com.onekeycall.videotablet.entity.TabletDefaultSettings"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO tablet_default_settings (default_avatar)
VALUES (#{defaultAvatar})
</insert>
</mapper>