1
This commit is contained in:
@@ -54,6 +54,8 @@ public class ClientAccountController extends BaseController {
|
||||
private JwtRsaKeyService jwtRsaKeyService;
|
||||
@Autowired
|
||||
private SseHubService sseHubService;
|
||||
@Autowired
|
||||
private ClientDeviceMapper clientDeviceMapper;
|
||||
|
||||
/**
|
||||
* 查询账号列表
|
||||
@@ -137,9 +139,6 @@ public class ClientAccountController extends BaseController {
|
||||
if (!"0".equals(account.getStatus())) {
|
||||
return AjaxResult.error("账号已被停用");
|
||||
}
|
||||
if (account.getExpireTime() != null && account.getExpireTime().before(new Date())) {
|
||||
return AjaxResult.error("账号已过期");
|
||||
}
|
||||
String clientId = loginData.get("clientId");
|
||||
String accessToken = Jwts.builder()
|
||||
.setHeaderParam("kid", jwtRsaKeyService.getKeyId())
|
||||
@@ -151,7 +150,6 @@ public class ClientAccountController extends BaseController {
|
||||
.claim("clientId", clientId)
|
||||
.signWith(SignatureAlgorithm.RS256, jwtRsaKeyService.getPrivateKey())
|
||||
.compact();
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("accessToken", accessToken);
|
||||
result.put("permissions", account.getPermissions());
|
||||
@@ -181,6 +179,15 @@ public class ClientAccountController extends BaseController {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -203,36 +210,53 @@ public class ClientAccountController extends BaseController {
|
||||
|
||||
/**
|
||||
* 客户端账号注册
|
||||
* 新设备注册送3天VIP,同一设备ID重复注册不赠送
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public AjaxResult register(@RequestBody ClientAccount clientAccount) {
|
||||
if (StringUtils.isEmpty(clientAccount.getUsername()) || StringUtils.isEmpty(clientAccount.getPassword())) {
|
||||
return AjaxResult.error("用户名和密码不能为空");
|
||||
}
|
||||
if (clientAccount.getPassword().length() < 6) {
|
||||
return AjaxResult.error("密码长度不能少于6位");
|
||||
}
|
||||
if (clientAccountService.selectClientAccountByUsername(clientAccount.getUsername()) != null) {
|
||||
return AjaxResult.error("用户名已存在");
|
||||
}
|
||||
public AjaxResult register(@RequestBody Map<String, String> registerData) {
|
||||
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);
|
||||
clientAccount.setCreateBy("system");
|
||||
clientAccount.setStatus("0");
|
||||
clientAccount.setPermissions("{\"amazon\":true,\"rakuten\":true,\"zebra\":true}");
|
||||
clientAccount.setPassword(passwordEncoder.encode(clientAccount.getPassword()));
|
||||
if (clientAccount.getExpireTime() == null) {
|
||||
Date expireDate = new Date(System.currentTimeMillis() + 90L * 24 * 60 * 60 * 1000);
|
||||
clientAccount.setExpireTime(expireDate);
|
||||
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; // 立即过期,需要续费
|
||||
}
|
||||
|
||||
if (vipDays > 0) {
|
||||
Date expireDate = new Date(System.currentTimeMillis() + vipDays * 24L * 60 * 60 * 1000);
|
||||
clientAccount.setExpireTime(expireDate);
|
||||
} else {
|
||||
clientAccount.setExpireTime(new Date());
|
||||
}
|
||||
|
||||
int result = clientAccountService.insertClientAccount(clientAccount);
|
||||
if (result <= 0) {
|
||||
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();
|
||||
|
||||
@@ -256,5 +280,43 @@ public class ClientAccountController extends BaseController {
|
||||
return AjaxResult.success(account == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 续费账号
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('monitor:account:edit')")
|
||||
@Log(title = "账号续费", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/renew")
|
||||
public AjaxResult renew(@RequestBody Map<String, Object> data) {
|
||||
Long accountId = Long.valueOf(data.get("accountId").toString());
|
||||
Integer days = Integer.valueOf(data.get("days").toString());
|
||||
|
||||
ClientAccount account = clientAccountService.selectClientAccountById(accountId);
|
||||
if (account == null) {
|
||||
return AjaxResult.error("账号不存在");
|
||||
}
|
||||
|
||||
java.util.Calendar cal = java.util.Calendar.getInstance();
|
||||
if (account.getExpireTime() != null && account.getExpireTime().after(new Date())) {
|
||||
cal.setTime(account.getExpireTime());
|
||||
} else {
|
||||
cal.setTime(new Date());
|
||||
}
|
||||
cal.add(java.util.Calendar.DAY_OF_MONTH, days);
|
||||
Date newExpireTime = cal.getTime();
|
||||
|
||||
account.setExpireTime(newExpireTime);
|
||||
account.setUpdateBy(getUsername());
|
||||
clientAccountService.updateClientAccount(account);
|
||||
|
||||
// 通过SSE推送续费通知给该账号的所有在线设备
|
||||
try {
|
||||
sseHubService.sendEventToAllDevices(account.getUsername(), "VIP_RENEWED",
|
||||
"{\"expireTime\":\"" + newExpireTime + "\"}");
|
||||
} catch (Exception e) {
|
||||
// SSE推送失败不影响续费操作
|
||||
}
|
||||
|
||||
return AjaxResult.success("续费成功,新的过期时间:" + newExpireTime);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.ip.IpUtils;
|
||||
import com.ruoyi.system.domain.ClientDevice;
|
||||
import com.ruoyi.system.mapper.ClientDeviceMapper;
|
||||
import com.ruoyi.system.mapper.ClientAccountMapper;
|
||||
import com.ruoyi.system.domain.ClientAccount;
|
||||
import com.ruoyi.web.sse.SseHubService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -19,9 +21,28 @@ public class ClientDeviceController {
|
||||
@Autowired
|
||||
private ClientDeviceMapper clientDeviceMapper;
|
||||
@Autowired
|
||||
private ClientAccountMapper clientAccountMapper;
|
||||
@Autowired
|
||||
private SseHubService sseHubService;
|
||||
private static final int DEFAULT_LIMIT = 3;
|
||||
|
||||
/**
|
||||
* 获取账号的设备数量限制
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 设备数量限制,如果账号不存在或未配置则返回默认值
|
||||
*/
|
||||
private int getDeviceLimit(String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
return DEFAULT_LIMIT;
|
||||
}
|
||||
ClientAccount account = clientAccountMapper.selectClientAccountByUsername(username);
|
||||
if (account == null || account.getDeviceLimit() == null) {
|
||||
return DEFAULT_LIMIT;
|
||||
}
|
||||
return account.getDeviceLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设备配额与已使用数量
|
||||
*
|
||||
@@ -35,8 +56,9 @@ public class ClientDeviceController {
|
||||
for (ClientDevice d : all) {
|
||||
if (!"removed".equals(d.getStatus())) used++;
|
||||
}
|
||||
int limit = getDeviceLimit(username);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("limit", DEFAULT_LIMIT);
|
||||
map.put("limit", limit);
|
||||
map.put("used", used);
|
||||
return AjaxResult.success(map);
|
||||
}
|
||||
@@ -55,26 +77,26 @@ public class ClientDeviceController {
|
||||
return AjaxResult.success(active);
|
||||
}
|
||||
/**
|
||||
* 设备注册(幂等)
|
||||
*
|
||||
* 根据 deviceId 判断:
|
||||
* - 不存在:插入新记录(检查设备数量限制)
|
||||
* - 已存在:更新设备信息
|
||||
* 设备注册
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public AjaxResult register(@RequestBody ClientDevice device, HttpServletRequest request) {
|
||||
ClientDevice exists = clientDeviceMapper.selectByDeviceId(device.getDeviceId());
|
||||
String ip = IpUtils.getIpAddr(request);
|
||||
String deviceName = request.getHeader("X-Client-User") + "@" + ip + " (" + request.getHeader("X-Client-OS") + ")";
|
||||
// 从请求体读取用户名和操作系统,构建设备名称
|
||||
String username = device.getUsername();
|
||||
String os = device.getOs();
|
||||
String deviceName = username + "@" + ip + " (" + os + ")";
|
||||
if (exists == null) {
|
||||
// 检查设备数量限制
|
||||
int deviceLimit = getDeviceLimit(device.getUsername());
|
||||
List<ClientDevice> userDevices = clientDeviceMapper.selectByUsername(device.getUsername());
|
||||
int activeDeviceCount = 0;
|
||||
for (ClientDevice d : userDevices) {
|
||||
if (!"removed".equals(d.getStatus())) activeDeviceCount++;
|
||||
}
|
||||
if (activeDeviceCount >= DEFAULT_LIMIT) {
|
||||
return AjaxResult.error("设备数量已达上限(" + DEFAULT_LIMIT + "个),请先移除其他设备");
|
||||
if (activeDeviceCount >= deviceLimit) {
|
||||
return AjaxResult.error("设备数量已达上限(" + deviceLimit + "个),请先移除其他设备");
|
||||
}
|
||||
device.setIp(ip);
|
||||
device.setStatus("online");
|
||||
@@ -139,7 +161,6 @@ public class ClientDeviceController {
|
||||
public AjaxResult offline(@RequestBody Map<String, String> body) {
|
||||
String deviceId = body.get("deviceId");
|
||||
if (deviceId == null) return AjaxResult.error("deviceId不能为空");
|
||||
|
||||
ClientDevice device = clientDeviceMapper.selectByDeviceId(deviceId);
|
||||
if (device != null) {
|
||||
device.setStatus("offline");
|
||||
@@ -157,16 +178,20 @@ public class ClientDeviceController {
|
||||
public AjaxResult heartbeat(@RequestBody ClientDevice device, HttpServletRequest request) {
|
||||
ClientDevice exists = clientDeviceMapper.selectByDeviceId(device.getDeviceId());
|
||||
String ip = IpUtils.getIpAddr(request);
|
||||
String deviceName = request.getHeader("X-Client-User") + "@" + ip + " (" + request.getHeader("X-Client-OS") + ")";
|
||||
// 从请求体读取用户名和操作系统,构建设备名称
|
||||
String username = device.getUsername() ;
|
||||
String os = device.getOs();
|
||||
String deviceName = username + "@" + ip + " (" + os + ")";
|
||||
if (exists == null) {
|
||||
// 检查设备数量限制
|
||||
int deviceLimit = getDeviceLimit(device.getUsername());
|
||||
List<ClientDevice> userDevices = clientDeviceMapper.selectByUsername(device.getUsername());
|
||||
int activeDeviceCount = 0;
|
||||
for (ClientDevice d : userDevices) {
|
||||
if (!"removed".equals(d.getStatus())) activeDeviceCount++;
|
||||
}
|
||||
if (activeDeviceCount >= DEFAULT_LIMIT) {
|
||||
return AjaxResult.error("设备数量已达上限(" + DEFAULT_LIMIT + "个),请先移除其他设备");
|
||||
if (activeDeviceCount >= deviceLimit) {
|
||||
return AjaxResult.error("设备数量已达上限(" + deviceLimit + "个),请先移除其他设备");
|
||||
}
|
||||
device.setIp(ip);
|
||||
device.setStatus("online");
|
||||
|
||||
@@ -92,6 +92,28 @@ public class SseHubService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定账号的所有设备推送消息
|
||||
*/
|
||||
public void sendEventToAllDevices(String username, String type, String message) {
|
||||
if (username == null || username.isEmpty()) return;
|
||||
|
||||
// 遍历所有会话,找到匹配的username
|
||||
String prefix = username + ":";
|
||||
sessionEmitters.forEach((key, emitter) -> {
|
||||
if (key.startsWith(prefix)) {
|
||||
try {
|
||||
String data = message != null ? message : "{}";
|
||||
String eventData = "{\"type\":\"" + type + "\",\"message\":" + escapeJson(data) + "}";
|
||||
emitter.send(SseEmitter.event().data(eventData));
|
||||
} catch (IOException e) {
|
||||
sessionEmitters.remove(key);
|
||||
try { emitter.complete(); } catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备状态
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.web.task;
|
||||
|
||||
import com.ruoyi.system.service.IBanmaAccountService;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Component
|
||||
public class BanmaTokenRefreshTask {
|
||||
|
||||
@Resource
|
||||
private IBanmaAccountService banmaAccountService;
|
||||
|
||||
// 每两天凌晨3点
|
||||
@Scheduled(cron = "0 0 3 */2 * ?")
|
||||
public void refreshAllTokens() {
|
||||
banmaAccountService.refreshAllTokens();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.ruoyi.web.task;
|
||||
|
||||
import com.ruoyi.system.domain.ClientDevice;
|
||||
import com.ruoyi.system.mapper.ClientDeviceMapper;
|
||||
import com.ruoyi.web.sse.SseHubService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备心跳定时任务
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
public class DeviceHeartbeatTask {
|
||||
|
||||
@Autowired
|
||||
private ClientDeviceMapper clientDeviceMapper;
|
||||
|
||||
@Autowired
|
||||
private SseHubService sseHubService;
|
||||
|
||||
/**
|
||||
* 每30秒发送一次ping,保持SSE连接活跃
|
||||
*/
|
||||
@Scheduled(fixedRate = 30000)
|
||||
public void sendHeartbeatPing() {
|
||||
List<ClientDevice> onlineDevices = clientDeviceMapper.selectOnlineDevices();
|
||||
for (ClientDevice device : onlineDevices) {
|
||||
sseHubService.sendPing(device.getUsername(), device.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每2分钟清理一次过期设备
|
||||
*/
|
||||
@Scheduled(fixedRate = 120000)
|
||||
public void cleanExpiredDevices() {
|
||||
clientDeviceMapper.updateExpiredDevicesOffline();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ qiniu:
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
port: 8080
|
||||
port: 8085
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
|
||||
Reference in New Issue
Block a user