feat(client): 实现自定义开屏图片功能

- 在 ClientAccount 实体中新增 splashImage 字段用于存储开屏图片URL
- 在 ClientAccountController 中添加上传、获取和删除开屏图片的接口
- 集成七牛云存储实现图片上传功能,支持图片格式和大小校验
- 使用 Redis 缓存开屏图片URL,提升访问性能
- 在客户端登录成功后异步加载并保存开屏图片配置
- 新增 splashApi 模块封装开屏图片相关HTTP请求- 在主进程中实现开屏图片配置的持久化存储和读取
- 在设置页面中增加开屏图片管理界面,支持上传、预览和删除操作
- 修改 splash.html 支持动态加载自定义开屏图片
- 调整 CSP 策略允许加载本地和HTTPS图片资源
This commit is contained in:
2025-11-08 10:23:45 +08:00
parent 7c7009ffed
commit c2e1617a99
13 changed files with 374 additions and 19 deletions

View File

@@ -1,9 +1,6 @@
package com.ruoyi.web.controller.monitor;
import java.util.Date;
import java.util.List;
import java.util.Map;
import com.ruoyi.common.annotation.Anonymous;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -33,6 +30,17 @@ import com.ruoyi.system.mapper.ClientDeviceMapper;
import com.ruoyi.system.domain.ClientDevice;
import com.ruoyi.system.mapper.ClientAccountDeviceMapper;
import com.ruoyi.system.domain.ClientAccountDevice;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.config.Qiniu;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import com.qiniu.http.Response;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import java.io.InputStream;
import java.util.Date;
/**
@@ -47,11 +55,9 @@ public class ClientAccountController extends BaseController {
@Autowired
private IClientAccountService clientAccountService;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 访问令牌3天
private final long JWT_EXPIRATION = 3L * 24 * 60 * 60 * 1000;
@Autowired
private JwtRsaKeyService jwtRsaKeyService;
@Autowired
@@ -60,6 +66,16 @@ public class ClientAccountController extends BaseController {
private ClientDeviceMapper clientDeviceMapper;
@Autowired
private ClientAccountDeviceMapper accountDeviceMapper;
@Autowired
private RedisCache redisCache;
@Autowired
private Qiniu qiniu;
@Autowired
private UploadManager uploadManager;
@Autowired
private Auth auth;
private static final String SPLASH_IMAGE_CACHE_KEY = "splash_image:";
private AjaxResult checkDeviceLimit(Long accountId, String deviceId, int deviceLimit) {
int activeDeviceCount = accountDeviceMapper.countActiveDevicesByAccountId(accountId);
@@ -159,6 +175,11 @@ public class ClientAccountController extends BaseController {
// 检查设备限制
AjaxResult limitCheck = checkDeviceLimit(account.getId(), clientId, account.getDeviceLimit());
if (limitCheck != null) return limitCheck;
// 更新开屏图片缓存到 Redis
if (StringUtils.isNotEmpty(account.getSplashImage())) {
redisCache.setCacheObject(SPLASH_IMAGE_CACHE_KEY + username, account.getSplashImage());
}
String token = Jwts.builder()
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
@@ -191,7 +212,6 @@ public class ClientAccountController extends BaseController {
.getBody();
String username = (String) claims.get("sub");
String clientId = (String) claims.get("clientId");
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
if (account == null || !"0".equals(account.getStatus())) {
return AjaxResult.error("token无效");
@@ -331,4 +351,66 @@ public class ClientAccountController extends BaseController {
return AjaxResult.success(Map.of("expireTime", newExpireTime));
}
/**
* 上传开屏图片
*/
@PostMapping("/splash-image/upload")
public AjaxResult uploadSplashImage(@RequestParam("file") MultipartFile file, @RequestParam("username") String username) {
try {
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
if (account == null) return AjaxResult.error("账号不存在");
if (!file.getContentType().startsWith("image/")) return AjaxResult.error("只支持图片文件");
if (file.getSize() > 5 * 1024 * 1024) return AjaxResult.error("图片大小不能超过5MB");
String fileName = "splash/" + DateUtil.format(new Date(), "yyyy/MM/") + IdUtil.simpleUUID() + "." + FileUtil.extName(file.getOriginalFilename());
try (InputStream is = file.getInputStream()) {
Response res = uploadManager.put(is, fileName, auth.uploadToken(qiniu.getBucket()), null, "");
if (!res.isOK()) return AjaxResult.error("上传失败");
}
String url = qiniu.getResourcesUrl() + fileName;
account.setSplashImage(url);
clientAccountService.updateClientAccount(account);
redisCache.setCacheObject(SPLASH_IMAGE_CACHE_KEY + username, url);
return AjaxResult.success().put("url", url).put("fileName", fileName);
} catch (Exception e) {
return AjaxResult.error("上传失败");
}
}
/**
* 获取开屏图片
*/
@GetMapping({"/splash-image", "/splash-image/by-username"})
public AjaxResult getSplashImage(@RequestParam("username") String username) {
String url = redisCache.getCacheObject(SPLASH_IMAGE_CACHE_KEY + username);
if (StringUtils.isEmpty(url)) {
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
if (account != null && StringUtils.isNotEmpty(account.getSplashImage())) {
url = account.getSplashImage();
redisCache.setCacheObject(SPLASH_IMAGE_CACHE_KEY + username, url);
}
}
return AjaxResult.success(Map.of("splashImage", url != null ? url : "", "url", url != null ? url : ""));
}
/**
* 删除开屏图片
*/
@PostMapping("/splash-image/delete")
public AjaxResult deleteSplashImage(@RequestParam("username") String username) {
try {
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
if (account == null) return AjaxResult.error("账号不存在");
account.setSplashImage(null);
clientAccountService.updateClientAccount(account);
redisCache.deleteObject(SPLASH_IMAGE_CACHE_KEY + username);
return AjaxResult.success("开屏图片已删除");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("删除失败: " + e.getMessage());
}
}
}