feat: 添加用户端链接状态查询接口及自动刷新逻辑
主要修改: 1. 在LinkController中新增获取用户链接状态的接口,支持通过linkId或codeNo查询。 2. 在LinkStatusService中实现用户链接状态查询逻辑,包含自动刷新和二维码更新功能。 3. 更新LinkTask实体,添加needRefresh、refreshTime、qrCreatedAt和qrExpireAt字段以支持新功能。 4. 在ScriptClient中新增检查空闲设备、选区、刷新、检查上号状态等操作的实现。 5. 更新SecurityConfig,允许用户端获取链接状态接口公开访问。 技术细节: - 新增UserLinkStatusResponse DTO以支持用户链接状态的返回格式。 - 通过脚本端接口实现链接状态的自动刷新和二维码信息更新。
This commit is contained in:
@@ -58,6 +58,102 @@ public class JwtService {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成链接code(用于用户端访问链接)
|
||||
*/
|
||||
public String generateLinkCode(Long linkId, String codeNo, int expireHours) {
|
||||
log.info("=== 开始生成链接code ===");
|
||||
log.info("生成参数: linkId={}, codeNo={}, expireHours={}", linkId, codeNo, expireHours);
|
||||
|
||||
Instant now = Instant.now();
|
||||
var builder = Jwts.builder()
|
||||
.setSubject("link_access")
|
||||
.setIssuedAt(Date.from(now))
|
||||
.setExpiration(Date.from(now.plus(expireHours, ChronoUnit.HOURS)))
|
||||
.claim("linkId", linkId)
|
||||
.claim("codeNo", codeNo)
|
||||
.claim("type", "link_access");
|
||||
|
||||
String code = builder.signWith(key, SignatureAlgorithm.HS256).compact();
|
||||
|
||||
log.info("=== 链接code生成成功 ===");
|
||||
log.info("code长度: {} 字符", code.length());
|
||||
log.info("过期时间: {} 小时后", expireHours);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析链接code获取链接信息
|
||||
*/
|
||||
public LinkCodeInfo parseLinkCode(String code) {
|
||||
log.debug("=== 开始解析链接code ===");
|
||||
log.debug("code长度: {} 字符", code.length());
|
||||
|
||||
try {
|
||||
io.jsonwebtoken.Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(code)
|
||||
.getBody();
|
||||
|
||||
// 检查是否是链接类型的token
|
||||
String type = claims.get("type", String.class);
|
||||
if (!"link_access".equals(type)) {
|
||||
throw new IllegalArgumentException("无效的链接code类型");
|
||||
}
|
||||
|
||||
Long linkId = claims.get("linkId", Long.class);
|
||||
String codeNo = claims.get("codeNo", String.class);
|
||||
|
||||
if (linkId == null || codeNo == null) {
|
||||
throw new IllegalArgumentException("链接code格式错误");
|
||||
}
|
||||
|
||||
LinkCodeInfo info = new LinkCodeInfo();
|
||||
info.setLinkId(linkId);
|
||||
info.setCodeNo(codeNo);
|
||||
info.setIssuedAt(claims.getIssuedAt());
|
||||
info.setExpireAt(claims.getExpiration());
|
||||
|
||||
log.debug("=== 链接code解析成功 ===");
|
||||
log.debug("linkId: {}, codeNo: {}", linkId, codeNo);
|
||||
|
||||
return info;
|
||||
} catch (Exception e) {
|
||||
log.warn("=== 链接code解析失败 ===");
|
||||
log.warn("code: {}", code);
|
||||
log.warn("错误详情: {}", e.getMessage());
|
||||
throw new IllegalArgumentException("无效的链接code", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 链接code信息
|
||||
*/
|
||||
public static class LinkCodeInfo {
|
||||
private Long linkId;
|
||||
private String codeNo;
|
||||
private Date issuedAt;
|
||||
private Date expireAt;
|
||||
|
||||
public Long getLinkId() { return linkId; }
|
||||
public void setLinkId(Long linkId) { this.linkId = linkId; }
|
||||
|
||||
public String getCodeNo() { return codeNo; }
|
||||
public void setCodeNo(String codeNo) { this.codeNo = codeNo; }
|
||||
|
||||
public Date getIssuedAt() { return issuedAt; }
|
||||
public void setIssuedAt(Date issuedAt) { this.issuedAt = issuedAt; }
|
||||
|
||||
public Date getExpireAt() { return expireAt; }
|
||||
public void setExpireAt(Date expireAt) { this.expireAt = expireAt; }
|
||||
|
||||
public boolean isExpired() {
|
||||
return expireAt != null && new Date().after(expireAt);
|
||||
}
|
||||
}
|
||||
|
||||
public io.jsonwebtoken.Claims parse(String token) {
|
||||
log.info("=== 开始解析JWT token ===");
|
||||
log.info("token长度: {} 字符", token.length());
|
||||
|
||||
@@ -41,7 +41,8 @@ public class SecurityConfig {
|
||||
.pathMatchers("/actuator/**").permitAll()
|
||||
.pathMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
|
||||
.pathMatchers(HttpMethod.GET, "/api/auth/me").permitAll()
|
||||
.pathMatchers("/api/link/**").authenticated() // 链接接口需要认证
|
||||
.pathMatchers(HttpMethod.GET, "/api/link/status").permitAll() // 用户端获取链接状态接口,公开访问
|
||||
.pathMatchers("/api/link/**").authenticated() // 其他链接接口需要认证
|
||||
.anyExchange().permitAll() // 其他接口后续再收紧
|
||||
)
|
||||
// 关键:将JWT过滤器集成到Security过滤链中,放在AUTHENTICATION位置
|
||||
@@ -59,6 +60,7 @@ public class SecurityConfig {
|
||||
log.info(" * /actuator/** -> 允许所有");
|
||||
log.info(" * POST /api/auth/login -> 允许所有");
|
||||
log.info(" * GET /api/auth/me -> 允许所有");
|
||||
log.info(" * GET /api/link/status -> 允许所有 (用户端公开接口)");
|
||||
log.info(" * /api/link/** -> 需要认证");
|
||||
log.info(" * 其他路径 -> 允许所有");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user