feat(client): 实现用户数据隔离与设备绑定优化- 添加用户会话ID构建逻辑,确保数据按用户隔离- 优化设备绑定流程,支持设备状态更新和绑定时间同步- 实现用户缓存清理功能,仅清除当前用户的数据- 增强客户端账号删除逻辑,级联删除相关数据

- 调整设备在线查询逻辑,确保只返回活跃绑定的设备
- 优化试用期逻辑,精确计算过期时间和类型- 添加账号管理弹窗和相关状态注入
-修复跟卖精灵按钮加载状态显示问题
- 增强文件上传区域UI,显示选中文件名
- 调整分页组件样式,优化界面展示效果- 优化反馈日志存储路径逻辑,默认使用用户目录
- 移除冗余代码和无用导入,提升代码整洁度
This commit is contained in:
2025-10-24 13:43:46 +08:00
parent e2a438c84e
commit 3a76aaa3c0
50 changed files with 860 additions and 590 deletions

View File

@@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.util.TimeZone;
/**
* 启动程序
@@ -15,6 +16,7 @@ public class RuoYiApplication
{
public static void main(String[] args)
{
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
// System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoYiApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 若依启动成功 ლ(´ڡ`ლ)゙ \n" +

View File

@@ -37,7 +37,7 @@ public class ClientFeedbackController extends BaseController {
@Autowired
private IClientFeedbackService feedbackService;
@Value("${feedback.log.path:C:/ProgramData/erp-logs/feedback/}")
@Value("${feedback.log.path:}")
private String feedbackLogPath;
/**
@@ -258,20 +258,21 @@ public class ClientFeedbackController extends BaseController {
* 保存日志文件
*/
private String saveLogFile(String username, String deviceId, MultipartFile file) throws IOException {
// 确保目录存在
Path uploadPath = Paths.get(feedbackLogPath);
String logPath = feedbackLogPath;
if (logPath == null || logPath.trim().isEmpty()) {
logPath = System.getProperty("user.home") + File.separator + "erp-feedback";
}
Path uploadPath = Paths.get(logPath).toAbsolutePath();
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
// 生成文件名: username_deviceId_timestamp.log
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String fileName = String.format("%s_%s_%s.log", username, deviceId, timestamp);
Path filePath = uploadPath.resolve(fileName);
// 保存文件
file.transferTo(filePath.toFile());
return filePath.toString();
}
}

View File

@@ -10,7 +10,6 @@ import com.ruoyi.system.mapper.ClientAccountDeviceMapper;
import com.ruoyi.system.domain.ClientAccountDevice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Date;
@@ -22,7 +21,6 @@ public class ClientDeviceController {
@Autowired private ClientAccountMapper accountMapper;
@Autowired private ClientAccountDeviceMapper accountDeviceMapper;
@Autowired private SseHubService sseHubService;
private ClientAccount getAccount(String username) {
return accountMapper.selectClientAccountByUsername(username);
}
@@ -58,18 +56,18 @@ public class ClientDeviceController {
String deviceId = data.get("deviceId");
ClientAccount account = getAccount(username);
if (account == null) return AjaxResult.error("账号不存在");
int limit = getDeviceLimit(account);
if (exceedDeviceLimit(account.getId(), deviceId, limit)) {
return AjaxResult.error("设备数量已达上限(" + limit + "个)");
}
ClientDevice device = deviceMapper.selectByDeviceId(deviceId);
Date now = new Date();
ClientDevice device = deviceMapper.selectByDeviceId(deviceId);
if (device == null) {
device = new ClientDevice();
device.setDeviceId(deviceId);
device.setTrialExpireTime(new Date(System.currentTimeMillis() + 3 * 24L * 60 * 60 * 1000));
device.setStatus("online");
device.setTrialExpireTime(new Date(System.currentTimeMillis() + 7 * 24L * 60 * 60 * 1000));
deviceMapper.insert(device);
}
device.setName(username + "@" + data.get("computerName") + " (" + data.get("os") + ")");
@@ -84,12 +82,13 @@ public class ClientDeviceController {
binding = new ClientAccountDevice();
binding.setAccountId(account.getId());
binding.setDeviceId(deviceId);
binding.setBindTime(now);
binding.setStatus("active");
accountDeviceMapper.insert(binding);
} else if ("removed".equals(binding.getStatus())) {
accountDeviceMapper.updateStatus(account.getId(), deviceId, "active");
binding.setStatus("active");
accountDeviceMapper.update(binding);
}
binding.setBindTime(now);
return AjaxResult.success();
}

View File

@@ -5,6 +5,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.system.domain.ClientAccount;
import com.ruoyi.system.mapper.ClientAccountMapper;
import com.ruoyi.system.mapper.BanmaAccountMapper;
import com.ruoyi.system.mapper.ClientFeedbackMapper;
import com.ruoyi.system.mapper.ClientMonitorMapper;
import com.ruoyi.system.mapper.ClientAccountDeviceMapper;
import com.ruoyi.system.mapper.RefreshTokenMapper;
import com.ruoyi.web.service.IClientAccountService;
/**
@@ -17,6 +22,16 @@ public class ClientAccountServiceImpl implements IClientAccountService
{
@Autowired
private ClientAccountMapper clientAccountMapper;
@Autowired
private BanmaAccountMapper banmaAccountMapper;
@Autowired
private ClientFeedbackMapper clientFeedbackMapper;
@Autowired
private ClientMonitorMapper clientMonitorMapper;
@Autowired
private ClientAccountDeviceMapper clientAccountDeviceMapper;
@Autowired
private RefreshTokenMapper refreshTokenMapper;
/**
* 查询客户端账号
@@ -65,10 +80,31 @@ public class ClientAccountServiceImpl implements IClientAccountService
/**
* 批量删除客户端账号
* 级联删除所有关联数据:斑马账号、反馈、错误报告、设备绑定、刷新令牌
*/
@Override
public int deleteClientAccountByIds(Long[] ids)
{
for (Long id : ids) {
// 查询账号信息获取username
ClientAccount account = clientAccountMapper.selectClientAccountById(id);
if (account != null) {
String username = account.getUsername();
// 根据username删除关联数据
if (username != null) {
banmaAccountMapper.deleteByClientUsername(username);
clientFeedbackMapper.deleteFeedbackByUsername(username);
clientMonitorMapper.deleteErrorReportByUsername(username);
}
// 根据accountId删除关联数据
clientAccountDeviceMapper.deleteByAccountId(id);
refreshTokenMapper.deleteByAccountId(id);
}
}
// 最后删除客户端账号本身
return clientAccountMapper.deleteClientAccountByIds(ids);
}

View File

@@ -3,6 +3,7 @@ package com.ruoyi.web.sse;
import com.ruoyi.system.domain.ClientDevice;
import com.ruoyi.system.mapper.ClientDeviceMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -18,6 +19,14 @@ public class SseHubService {
@Autowired
private ClientDeviceMapper clientDeviceMapper;
@Scheduled(fixedRate = 30000)
public void heartbeat() {
sessionEmitters.forEach((key, emitter) -> {
String clientId = key.split(":")[1];
sendPing(key.split(":")[0], clientId);
});
}
public String buildSessionKey(String username, String clientId) {
return (username == null ? "" : username) + ":" + (clientId == null ? "" : clientId);