package com.inspect.nvr.service; import com.alibaba.fastjson.JSONObject; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; import com.inspect.nvr.domain.Infrared.NvrInfo; import com.inspect.nvr.jna.lincseek.IRNetSDK; import com.inspect.nvr.jna.lincseek.IRNetSDKStruct; import com.inspect.nvr.jna.lincseek.IRNetSDKStruct.IRNETHANDLE; import com.inspect.nvr.utils.redis.RedisService; import com.sun.jna.Pointer; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; @Slf4j @Service public class LincseekLoginService { private static final String ERROR_LOGOUT_KEY = "lincseek:error"; @Resource private RedisService redisService; @Resource private IRNetSDK irNetSDK; // 使用 Caffeine 缓存,10分钟未访问自动移除并登出 private final Cache sessionCache = Caffeine.newBuilder() // 10分钟未被get()就过期 .expireAfterAccess(10, TimeUnit.MINUTES) .removalListener((String ip, IRNETHANDLE handle, RemovalCause cause) -> { if (handle != null && (cause == RemovalCause.EXPIRED || cause == RemovalCause.SIZE)) { log.info("[朗驰]会话超时自动登出,ip: {},handler: {}", ip, handle); doLogout(ip, handle); } }).build(); /** * 登录(Caffeine.get 保证同一 IP 的并发请求只执行一次登录) */ public IRNETHANDLE login(NvrInfo nvrInfo) { String ip = nvrInfo.getNvrIp(); Integer port = nvrInfo.getServerPort(); if (port == null) { throw new IllegalArgumentException("serverPort 不能为空"); } return sessionCache.get(ip, key -> { IRNetSDKStruct.CHANNEL_CLIENTINFO info = IRNetSDKStruct.CHANNEL_CLIENTINFO.createWithUrl( "video server", nvrInfo.getAccount(), nvrInfo.getPassword(), (byte) nvrInfo.getChannelNumber().intValue(), ip, null, null, 0, null, null); IRNETHANDLE handle = irNetSDK.IRNET_ClientStart(ip, info, port.shortValue(), 0); if (IRNETHANDLE.INVALID_HANDLE_VALUE.equals(handle)) { throw new RuntimeException("[朗驰]登录失败, ip: " + ip); } log.info("[朗驰]登录成功,ip:{},handler:{}", ip, handle); return handle; }); } /** * 记录注销失败信息到 Redis */ private void recordLogoutError(String ip, Integer userID, int errorCode) { JSONObject json = new JSONObject(); json.put("ip", ip); json.put("userID", userID); json.put("errorCode", errorCode); json.put("time", System.currentTimeMillis()); redisService.redisTemplate.opsForZSet().add(ERROR_LOGOUT_KEY, json.toJSONString(), System.currentTimeMillis()); } /** * 登出具体实现 */ public void doLogout(String ip, IRNETHANDLE handle) { if (handle != null) { // 调用登出SDK boolean isLogout = irNetSDK.IRNET_ClientStop(handle); if (isLogout) { log.info("[朗驰]登出成功,ip: {},handle: {}", ip, handle); } else { log.error("[朗驰]登出失败,ip: {},handle: {}", ip, handle); // 登出失败日志记录到Redis中 recordLogoutError(ip, (int) Pointer.nativeValue(handle), -1); } } } /** * 登出 */ public synchronized void logout(String ip) { IRNETHANDLE handle = sessionCache.getIfPresent(ip); if (handle != null) { sessionCache.invalidate(ip); } } /** * 登出所有用户 */ public void logoutAll() { // 获取所有缓存的IP和userID sessionCache.asMap().forEach((ip, handle) -> { doLogout(ip, handle); }); // 清空整个缓存 sessionCache.invalidateAll(); log.info("[朗驰]所有用户已登出"); } /** * 检查是否已登录(同时刷新过期时间) */ public boolean isLoggedIn(String ip) { return sessionCache.getIfPresent(ip) != null; } }