feat(electron-vue-template):重构认证与设备管理模块

- 统一token存取逻辑,封装getToken/setToken/removeToken方法
-优化设备ID获取逻辑,调整API路径
- 完善设备管理接口类型定义,增强类型安全
- 调整SSE连接逻辑,使用统一配置管理- 重构HTTP客户端,集中管理后端服务配置
- 更新认证相关API接口,完善请求/响应类型
- 优化设备列表展示逻辑,移除冗余字段
- 调整图片代理路径,统一API前缀
- 完善用户反馈列表展示功能,增强交互体验
- 移除冗余的错误处理逻辑,简化代码结构
This commit is contained in:
2025-10-16 10:37:00 +08:00
parent 6f04658265
commit 132299c4b7
37 changed files with 2193 additions and 682 deletions

View File

@@ -130,9 +130,8 @@ public class ClientAccountController extends BaseController {
public AjaxResult login(@RequestBody Map<String, String> loginData) {
String username = loginData.get("username");
String password = loginData.get("password");
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return AjaxResult.error("用户名和密码不能为空");
}
String clientId = loginData.get("clientId");
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
if (account == null || !passwordEncoder.matches(password, account.getPassword())) {
return AjaxResult.error("用户名或密码错误");
@@ -142,24 +141,32 @@ public class ClientAccountController extends BaseController {
}
// 检查设备数量限制
String clientId = loginData.get("clientId");
int deviceLimit = account.getDeviceLimit();
List<ClientDevice> userDevices = clientDeviceMapper.selectByUsername(username);
int userDevice = userDevices.size();
boolean exists = userDevices.stream()
.anyMatch(d -> clientId.equals(d.getDeviceId()));
if(exists)userDevice--;
int userDevice = userDevices.size();
boolean exists = userDevices.stream().anyMatch(d -> clientId.equals(d.getDeviceId()));
if (exists) userDevice--;
if (userDevice >= deviceLimit) {
return AjaxResult.error("设备数量已达上限(" + deviceLimit + "个),请先移除其他设备");
}
String accessToken = Jwts.builder().setHeaderParam("kid", jwtRsaKeyService.getKeyId()).setSubject(username).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)).claim("accountId", account.getId()).claim("username", username).claim("clientId", clientId).signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey()).compact();
Map<String, Object> result = new HashMap<>();
result.put("accessToken", accessToken);
result.put("permissions", account.getPermissions());
result.put("accountName", account.getAccountName());
result.put("expireTime", account.getExpireTime());
return AjaxResult.success("登录成功", result);
String token = Jwts.builder()
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION))
.claim("accountId", account.getId())
.claim("username", username)
.claim("clientId", clientId)
.signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey())
.compact();
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("permissions", account.getPermissions());
data.put("accountName", account.getAccountName());
data.put("expireTime", account.getExpireTime());
return AjaxResult.success(data);
}
@@ -169,29 +176,23 @@ public class ClientAccountController extends BaseController {
@PostMapping("/verify")
public AjaxResult verifyToken(@RequestBody Map<String, String> data) {
String token = data.get("token");
if (StringUtils.isEmpty(token)) {
return AjaxResult.error("token不能为空");
}
Map<String, Object> claims = Jwts.parser().setSigningKey(jwtRsaKeyService.getPublicKey()).parseClaimsJws(token).getBody();
Map<String, Object> claims = Jwts.parser()
.setSigningKey(jwtRsaKeyService.getPublicKey())
.parseClaimsJws(token)
.getBody();
String username = (String) claims.get("sub");
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
if (account == null || !"0".equals(account.getStatus())) {
return AjaxResult.error("token无效");
}
Map<String, Object> result = new HashMap<>();
result.put("username", username);
result.put("permissions", account.getPermissions());
result.put("accountName", account.getAccountName());
result.put("expireTime", account.getExpireTime());
// 计算VIP状态
if (account.getExpireTime() != null) {
boolean isExpired = account.getExpireTime().before(new Date());
result.put("isVip", !isExpired);
} else {
result.put("isVip", false);
}
return AjaxResult.success("验证成功", result);
return AjaxResult.success(result);
}
/**
@@ -223,6 +224,7 @@ public class ClientAccountController extends BaseController {
String username = registerData.get("username");
String password = registerData.get("password");
String deviceId = registerData.get("deviceId");
ClientAccount clientAccount = new ClientAccount();
clientAccount.setUsername(username);
clientAccount.setAccountName(username);
@@ -231,22 +233,12 @@ public class ClientAccountController extends BaseController {
clientAccount.setPermissions("{\"amazon\":true,\"rakuten\":true,\"zebra\":true}");
clientAccount.setPassword(passwordEncoder.encode(password));
// 检查设备ID是否已注册过赠送VIP逻辑
boolean isNewDevice = true;
if (!StringUtils.isEmpty(deviceId)) {
ClientDevice existingDevice = clientDeviceMapper.selectByDeviceId(deviceId);
isNewDevice = (existingDevice == null);
}
int vipDays;
if (isNewDevice) {
vipDays = 3;
} else {
vipDays = 0; // 立即过期,需要续费
}
// 新设备赠送3天VIP
ClientDevice existingDevice = clientDeviceMapper.selectByDeviceId(deviceId);
int vipDays = (existingDevice == null) ? 3 : 0;
if (vipDays > 0) {
Date expireDate = new Date(System.currentTimeMillis() + vipDays * 24L * 60 * 60 * 1000);
clientAccount.setExpireTime(expireDate);
clientAccount.setExpireTime(new Date(System.currentTimeMillis() + vipDays * 24L * 60 * 60 * 1000));
} else {
clientAccount.setExpireTime(new Date());
}
@@ -256,14 +248,22 @@ public class ClientAccountController extends BaseController {
return AjaxResult.error("注册失败");
}
String accessToken = Jwts.builder().setHeaderParam("kid", jwtRsaKeyService.getKeyId()).setSubject(clientAccount.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)).claim("accountId", clientAccount.getId()).claim("clientId", deviceId).signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey()).compact();
String token = Jwts.builder()
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
.setSubject(clientAccount.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION))
.claim("accountId", clientAccount.getId())
.claim("clientId", deviceId)
.signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey())
.compact();
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("accessToken", accessToken);
dataMap.put("permissions", clientAccount.getPermissions());
dataMap.put("accountName", clientAccount.getAccountName());
dataMap.put("expireTime", clientAccount.getExpireTime());
return AjaxResult.success("注册成功", dataMap);
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("permissions", clientAccount.getPermissions());
data.put("accountName", clientAccount.getAccountName());
data.put("expireTime", clientAccount.getExpireTime());
return AjaxResult.success(data);
}
/**
@@ -271,9 +271,6 @@ public class ClientAccountController extends BaseController {
*/
@GetMapping("/check-username")
public AjaxResult checkUsername(@RequestParam("username") String username) {
if (StringUtils.isEmpty(username)) {
return AjaxResult.error("用户名不能为空");
}
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
return AjaxResult.success(account == null);
}
@@ -306,14 +303,13 @@ public class ClientAccountController extends BaseController {
account.setUpdateBy(getUsername());
clientAccountService.updateClientAccount(account);
// 通过SSE推送续费通知给该账号的所有在线设备
try {
sseHubService.sendEventToAllDevices(account.getUsername(), "VIP_RENEWED", "{\"expireTime\":\"" + newExpireTime + "\"}");
} catch (Exception e) {
// SSE推送失败不影响续费操作
}
// 推送续费通知
sseHubService.sendEventToAllDevices(account.getUsername(), "VIP_RENEWED",
"{\"expireTime\":\"" + newExpireTime + "\"}");
return AjaxResult.success("续费成功,新的过期时间:" + newExpireTime);
Map<String, Object> result = new HashMap<>();
result.put("expireTime", newExpireTime);
return AjaxResult.success(result);
}
}

View File

@@ -0,0 +1,278 @@
package com.ruoyi.web.controller.monitor;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.ClientFeedback;
import com.ruoyi.web.service.IClientFeedbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 客户端反馈控制器
*
* @author ruoyi
*/
@RestController
@RequestMapping("/monitor/feedback")
public class ClientFeedbackController extends BaseController {
@Autowired
private IClientFeedbackService feedbackService;
@Value("${feedback.log.path:C:/ProgramData/erp-logs/feedback/}")
private String feedbackLogPath;
/**
* 提交用户反馈(客户端调用)
*/
@Anonymous
@PostMapping("/submit")
public AjaxResult submitFeedback(
@RequestParam("username") String username,
@RequestParam("deviceId") String deviceId,
@RequestParam("feedbackContent") String feedbackContent,
@RequestParam(value = "logDate", required = false) String logDate,
@RequestParam(value = "logFile", required = false) MultipartFile logFile) {
try {
ClientFeedback feedback = new ClientFeedback();
feedback.setUsername(username);
feedback.setDeviceId(deviceId);
feedback.setFeedbackContent(feedbackContent);
feedback.setStatus("pending");
// 处理日志文件上传
if (logFile != null && !logFile.isEmpty()) {
String logFilePath = saveLogFile(username, deviceId, logFile);
feedback.setLogFilePath(logFilePath);
// 解析日志日期
if (logDate != null && !logDate.isEmpty()) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
feedback.setLogDate(sdf.parse(logDate));
} catch (Exception e) {
logger.warn("解析日志日期失败: {}", logDate, e);
}
}
}
feedbackService.insertFeedback(feedback);
return AjaxResult.success("success");
} catch (Exception e) {
logger.error("提交反馈失败", e);
return AjaxResult.error("反馈提交失败: " + e.getMessage());
}
}
/**
* 查询反馈列表(管理端调用)
*/
@PreAuthorize("@ss.hasPermi('monitor:feedback:list')")
@GetMapping("/list")
public TableDataInfo list(ClientFeedback feedback) {
startPage();
List<ClientFeedback> list = feedbackService.selectFeedbackList(feedback);
return getDataTable(list);
}
/**
* 获取反馈详情
*/
@PreAuthorize("@ss.hasPermi('monitor:feedback:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return AjaxResult.success(feedbackService.selectFeedbackById(id));
}
/**
* 获取反馈附带的日志内容(用于在线查看)
*/
@PreAuthorize("@ss.hasPermi('monitor:feedback:query')")
@GetMapping("/log/content/{id}")
public AjaxResult getLogContent(@PathVariable("id") Long id) {
try {
ClientFeedback feedback = feedbackService.selectFeedbackById(id);
if (feedback == null) {
return AjaxResult.error("反馈记录不存在");
}
String logFilePath = feedback.getLogFilePath();
if (logFilePath == null || logFilePath.isEmpty()) {
return AjaxResult.error("该反馈未附带日志文件");
}
File logFile = new File(logFilePath);
if (!logFile.exists()) {
return AjaxResult.error("日志文件不存在");
}
// 读取日志文件内容
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
}
Map<String, Object> result = new HashMap<>();
result.put("content", content.toString());
result.put("fileName", logFile.getName());
result.put("fileSize", logFile.length());
return AjaxResult.success(result);
} catch (Exception e) {
logger.error("读取日志文件失败", e);
return AjaxResult.error("读取失败: " + e.getMessage());
}
}
/**
* 下载反馈附带的日志文件
*/
@PreAuthorize("@ss.hasPermi('monitor:feedback:query')")
@GetMapping("/log/download/{id}")
public void downloadLog(@PathVariable("id") Long id, HttpServletResponse response) {
try {
ClientFeedback feedback = feedbackService.selectFeedbackById(id);
if (feedback == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("反馈记录不存在");
return;
}
String logFilePath = feedback.getLogFilePath();
if (logFilePath == null || logFilePath.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("该反馈未附带日志文件");
return;
}
File logFile = new File(logFilePath);
if (!logFile.exists()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().write("日志文件不存在");
return;
}
// 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=" + logFile.getName());
response.setContentLengthLong(logFile.length());
// 读取文件并写入响应
try (FileInputStream fis = new FileInputStream(logFile);
OutputStream os = response.getOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
}
} catch (Exception e) {
logger.error("下载日志文件失败", e);
try {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.getWriter().write("下载失败: " + e.getMessage());
} catch (IOException ignored) {
}
}
}
/**
* 更新反馈状态
*/
@PreAuthorize("@ss.hasPermi('monitor:feedback:edit')")
@Log(title = "客户端反馈", businessType = BusinessType.UPDATE)
@PutMapping("/status/{id}")
public AjaxResult updateStatus(@PathVariable("id") Long id, @RequestBody Map<String, String> params) {
String status = params.get("status");
String remark = params.get("remark");
ClientFeedback feedback = new ClientFeedback();
feedback.setId(id);
feedback.setStatus(status);
feedback.setRemark(remark);
int result = feedbackService.updateFeedback(feedback);
return toAjax(result);
}
/**
* 删除反馈
*/
@PreAuthorize("@ss.hasPermi('monitor:feedback:remove')")
@Log(title = "客户端反馈", businessType = BusinessType.DELETE)
@DeleteMapping("/{id}")
public AjaxResult remove(@PathVariable("id") Long id) {
// 删除前先删除日志文件
ClientFeedback feedback = feedbackService.selectFeedbackById(id);
if (feedback != null && feedback.getLogFilePath() != null) {
try {
File logFile = new File(feedback.getLogFilePath());
if (logFile.exists()) {
logFile.delete();
}
} catch (Exception e) {
logger.warn("删除日志文件失败: {}", feedback.getLogFilePath(), e);
}
}
return toAjax(feedbackService.deleteFeedbackById(id));
}
/**
* 获取反馈统计信息
*/
@PreAuthorize("@ss.hasPermi('monitor:feedback:list')")
@GetMapping("/statistics")
public AjaxResult getStatistics() {
Map<String, Object> stats = new HashMap<>();
stats.put("pendingCount", feedbackService.countPendingFeedback());
stats.put("todayCount", feedbackService.countTodayFeedback());
return AjaxResult.success(stats);
}
/**
* 保存日志文件
*/
private String saveLogFile(String username, String deviceId, MultipartFile file) throws IOException {
// 确保目录存在
Path uploadPath = Paths.get(feedbackLogPath);
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

@@ -98,12 +98,8 @@ public class ClientMonitorController extends BaseController {
*/
@PostMapping("/api/auth")
public AjaxResult clientAuth(@RequestBody Map<String, Object> authData) {
try {
String authKey = (String) authData.get("authKey");
return AjaxResult.success("认证成功", clientMonitorService.authenticateClient(authKey, authData));
} catch (Exception e) {
return AjaxResult.error("认证失败:" + e.getMessage());
}
String authKey = (String) authData.get("authKey");
return AjaxResult.success(clientMonitorService.authenticateClient(authKey, authData));
}
@@ -112,25 +108,14 @@ public class ClientMonitorController extends BaseController {
*/
@PostMapping("/api/error")
public AjaxResult clientError(@RequestBody Map<String, Object> errorData) {
try {
clientMonitorService.recordErrorReport(errorData);
return AjaxResult.success();
} catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
clientMonitorService.recordErrorReport(errorData);
return AjaxResult.success();
}
/**
* 客户端数据上报API
*/
@PostMapping("/api/data")
public AjaxResult clientDataReport(@RequestBody Map<String, Object> dataReport) {
try {
clientMonitorService.recordDataReport(dataReport);
return AjaxResult.success();
} catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
clientMonitorService.recordDataReport(dataReport);
return AjaxResult.success();
}
/**
* 获取特定客户端的详细信息
@@ -155,12 +140,8 @@ public class ClientMonitorController extends BaseController {
@PreAuthorize("@ss.hasPermi('monitor:client:export')")
@PostMapping("/cleanup")
public AjaxResult cleanupExpiredData() {
try {
clientMonitorService.cleanExpiredData();
return AjaxResult.success("过期数据清理完成");
} catch (Exception e) {
return AjaxResult.error("清理过期数据失败: " + e.getMessage());
}
clientMonitorService.cleanExpiredData();
return AjaxResult.success();
}

View File

@@ -34,28 +34,17 @@ public class VersionController extends BaseController {
*/
@GetMapping("/check")
public AjaxResult checkVersion(@RequestParam String currentVersion) {
try {
// 从Redis获取最新版本信息
String latestVersion = redisTemplate.opsForValue().get(VERSION_REDIS_KEY);
// 比较版本号
boolean needUpdate = compareVersions(currentVersion, latestVersion) < 0;
Map<String, Object> result = new HashMap<>();
result.put("currentVersion", currentVersion);
result.put("latestVersion", latestVersion);
result.put("needUpdate", needUpdate);
// 从Redis获取下载链接
String asarUrl = redisTemplate.opsForValue().get(ASAR_URL_REDIS_KEY);
String jarUrl = redisTemplate.opsForValue().get(JAR_URL_REDIS_KEY);
result.put("asarUrl", asarUrl);
result.put("jarUrl", jarUrl);
// 兼容旧版本保留downloadUrl字段指向asar
result.put("downloadUrl", asarUrl);
return AjaxResult.success(result);
} catch (Exception e) {
return AjaxResult.error("版本检查失败: " + e.getMessage());
}
String latestVersion = redisTemplate.opsForValue().get(VERSION_REDIS_KEY);
boolean needUpdate = compareVersions(currentVersion, latestVersion) < 0;
Map<String, Object> data = new HashMap<>();
data.put("currentVersion", currentVersion);
data.put("latestVersion", latestVersion);
data.put("needUpdate", needUpdate);
data.put("asarUrl", redisTemplate.opsForValue().get(ASAR_URL_REDIS_KEY));
data.put("jarUrl", redisTemplate.opsForValue().get(JAR_URL_REDIS_KEY));
return AjaxResult.success(data);
}
/**
* 获取当前版本信息
@@ -63,24 +52,18 @@ public class VersionController extends BaseController {
@PreAuthorize("@ss.hasPermi('system:version:query')")
@GetMapping("/info")
public AjaxResult getVersionInfo() {
try {
String currentVersion = redisTemplate.opsForValue().get(VERSION_REDIS_KEY);
if (StringUtils.isEmpty(currentVersion)) {
currentVersion = "2.0.0";
}
String asarUrl = redisTemplate.opsForValue().get(ASAR_URL_REDIS_KEY);
String jarUrl = redisTemplate.opsForValue().get(JAR_URL_REDIS_KEY);
Map<String, Object> result = new HashMap<>();
result.put("currentVersion", currentVersion);
result.put("asarUrl", asarUrl);
result.put("jarUrl", jarUrl);
result.put("updateTime", System.currentTimeMillis());
return AjaxResult.success(result);
} catch (Exception e) {
return AjaxResult.error("获取版本信息失败: " + e.getMessage());
String currentVersion = redisTemplate.opsForValue().get(VERSION_REDIS_KEY);
if (StringUtils.isEmpty(currentVersion)) {
currentVersion = "2.0.0";
}
Map<String, Object> data = new HashMap<>();
data.put("currentVersion", currentVersion);
data.put("asarUrl", redisTemplate.opsForValue().get(ASAR_URL_REDIS_KEY));
data.put("jarUrl", redisTemplate.opsForValue().get(JAR_URL_REDIS_KEY));
data.put("updateTime", System.currentTimeMillis());
return AjaxResult.success(data);
}
/**
@@ -92,23 +75,20 @@ public class VersionController extends BaseController {
public AjaxResult updateVersionInfo(@RequestParam("version") String version,
@RequestParam(value = "asarUrl", required = false) String asarUrl,
@RequestParam(value = "jarUrl", required = false) String jarUrl) {
try {
redisTemplate.opsForValue().set(VERSION_REDIS_KEY, version);
if (StringUtils.isNotEmpty(asarUrl)) {
redisTemplate.opsForValue().set(ASAR_URL_REDIS_KEY, asarUrl);
}
if (StringUtils.isNotEmpty(jarUrl)) {
redisTemplate.opsForValue().set(JAR_URL_REDIS_KEY, jarUrl);
}
Map<String, Object> result = new HashMap<>();
result.put("version", version);
result.put("asarUrl", asarUrl);
result.put("jarUrl", jarUrl);
result.put("updateTime", System.currentTimeMillis());
return AjaxResult.success("版本信息更新成功", result);
} catch (Exception e) {
return AjaxResult.error("版本信息更新失败: " + e.getMessage());
redisTemplate.opsForValue().set(VERSION_REDIS_KEY, version);
if (StringUtils.isNotEmpty(asarUrl)) {
redisTemplate.opsForValue().set(ASAR_URL_REDIS_KEY, asarUrl);
}
if (StringUtils.isNotEmpty(jarUrl)) {
redisTemplate.opsForValue().set(JAR_URL_REDIS_KEY, jarUrl);
}
Map<String, Object> data = new HashMap<>();
data.put("version", version);
data.put("asarUrl", asarUrl);
data.put("jarUrl", jarUrl);
data.put("updateTime", System.currentTimeMillis());
return AjaxResult.success(data);
}
/**

View File

@@ -0,0 +1,76 @@
package com.ruoyi.web.service;
import com.ruoyi.system.domain.ClientFeedback;
import java.util.List;
/**
* 客户端反馈服务接口
*
* @author ruoyi
*/
public interface IClientFeedbackService
{
/**
* 查询反馈列表
*
* @param feedback 反馈信息
* @return 反馈集合
*/
List<ClientFeedback> selectFeedbackList(ClientFeedback feedback);
/**
* 根据ID查询反馈
*
* @param id 反馈ID
* @return 反馈信息
*/
ClientFeedback selectFeedbackById(Long id);
/**
* 新增反馈
*
* @param feedback 反馈信息
* @return 结果
*/
int insertFeedback(ClientFeedback feedback);
/**
* 更新反馈
*
* @param feedback 反馈信息
* @return 结果
*/
int updateFeedback(ClientFeedback feedback);
/**
* 删除反馈
*
* @param id 反馈ID
* @return 结果
*/
int deleteFeedbackById(Long id);
/**
* 统计待处理反馈数量
*
* @return 数量
*/
int countPendingFeedback();
/**
* 统计今日反馈数量
*
* @return 数量
*/
int countTodayFeedback();
/**
* 更新反馈状态
*
* @param id 反馈ID
* @param status 状态
* @return 结果
*/
int updateFeedbackStatus(Long id, String status);
}

View File

@@ -0,0 +1,117 @@
package com.ruoyi.web.service.impl;
import com.ruoyi.system.domain.ClientFeedback;
import com.ruoyi.system.mapper.ClientFeedbackMapper;
import com.ruoyi.web.service.IClientFeedbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 客户端反馈服务实现
*
* @author ruoyi
*/
@Service
public class ClientFeedbackServiceImpl implements IClientFeedbackService
{
@Autowired
private ClientFeedbackMapper clientFeedbackMapper;
/**
* 查询反馈列表
*
* @param feedback 反馈信息
* @return 反馈集合
*/
@Override
public List<ClientFeedback> selectFeedbackList(ClientFeedback feedback)
{
return clientFeedbackMapper.selectFeedbackList(feedback);
}
/**
* 根据ID查询反馈
*
* @param id 反馈ID
* @return 反馈信息
*/
@Override
public ClientFeedback selectFeedbackById(Long id)
{
return clientFeedbackMapper.selectFeedbackById(id);
}
/**
* 新增反馈
*
* @param feedback 反馈信息
* @return 结果
*/
@Override
public int insertFeedback(ClientFeedback feedback)
{
return clientFeedbackMapper.insertFeedback(feedback);
}
/**
* 更新反馈
*
* @param feedback 反馈信息
* @return 结果
*/
@Override
public int updateFeedback(ClientFeedback feedback)
{
return clientFeedbackMapper.updateFeedback(feedback);
}
/**
* 删除反馈
*
* @param id 反馈ID
* @return 结果
*/
@Override
public int deleteFeedbackById(Long id)
{
return clientFeedbackMapper.deleteFeedbackById(id);
}
/**
* 统计待处理反馈数量
*
* @return 数量
*/
@Override
public int countPendingFeedback()
{
return clientFeedbackMapper.countPendingFeedback();
}
/**
* 统计今日反馈数量
*
* @return 数量
*/
@Override
public int countTodayFeedback()
{
return clientFeedbackMapper.countTodayFeedback();
}
/**
* 更新反馈状态
*
* @param id 反馈ID
* @param status 状态
* @return 结果
*/
@Override
public int updateFeedbackStatus(Long id, String status)
{
return clientFeedbackMapper.updateFeedbackStatus(id, status);
}
}

View File

@@ -65,17 +65,6 @@ public class SseHubService {
String key = buildSessionKey(username, clientId);
SseEmitter emitter = sessionEmitters.get(key);
if (emitter == null) {
try {
ClientDevice device = clientDeviceMapper.selectByDeviceId(clientId);
// 只有当设备状态不是removed时才更新为offline
if (device != null && !"removed".equals(device.getStatus())) {
device.setStatus("offline");
device.setLastActiveAt(new Date());
clientDeviceMapper.updateByDeviceId(device);
}
} catch (Exception ignored) {
// 静默处理,不影响心跳主流程
}
return;
}
@@ -84,7 +73,7 @@ public class SseHubService {
} catch (IOException e) {
sessionEmitters.remove(key);
try { emitter.complete(); } catch (Exception ignored) {}
// 发送失败更新为离线
// 发送失败说明连接已断开,更新为离线
updateDeviceStatus(clientId, "offline");
}
}