引入依赖

<!-- Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- WebSocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- JSON -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

<!-- ZXing for QR Code generation -->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.5.3</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.5.3</version>
</dependency>

<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

配置

spring:
  redis:
    host: localhost
    port: 6379
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
  session:
    store-type: redis

生成二维码

/**
 * 二维码生成工具类
 */
public class QRCodeUtil {
    
    private static final int WIDTH = 300;
    private static final int HEIGHT = 300;
    private static final String FORMAT = "png";
    
    /**
     * 生成二维码字节数组
     * @param content 二维码内容
     * @return 二维码图片字节数组
     */
    public static byte[] generateQRCode(String content) throws WriterException, IOException {
        Map<EncodeHintType, Object> hints = new HashMap<>();
        // 设置字符编码
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        // 设置容错级别
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        // 设置边距
        hints.put(EncodeHintType.MARGIN, 1);
        
        BitMatrix bitMatrix = new MultiFormatWriter().encode(
                content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints);
        
        BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
        
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, FORMAT, outputStream);
        
        return outputStream.toByteArray();
    }
    
    /**
     * 生成二维码Base64字符串
     */
    public static String generateQRCodeBase64(String content) throws WriterException, IOException {
        byte[] bytes = generateQRCode(content);
        return "data:image/png;base64," + java.util.Base64.getEncoder().encodeToString(bytes);
    }
}

定义常量和实体类

/**
 * 常量类
 */
public class Constants {
    // Redis 中二维码状态的前缀
    public static final String QR_CODE_PREFIX = "qr:code:";
    // 二维码过期时间(秒)
    public static final long QR_CODE_EXPIRE = 5 * 60;
}

/**
 * 二维码状态枚举
 */
public enum QRCodeStatus {
    WAITING("waiting", "等待扫描"),
    SCANNED("scanned", "已扫描"),
    CONFIRMED("confirmed", "已确认"),
    CANCELLED("cancelled", "已取消"),
    EXPIRED("expired", "已过期"),
    ERROR("error", "错误");
    
    private String code;
    private String message;
    
    QRCodeStatus(String code, String message) {
        this.code = code;
        this.message = message;
    }
    
    // getter 方法
    public String getCode() {
        return code;
    }
    
    public String getMessage() {
        return message;
    }
}

/**
 * WebSocket消息实体
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class WebSocketMessage {
    private String uuid;
    private QRCodeStatus status;
    private String message;
    private Object data;
}

/**
 * 用户信息实体
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
    private Long userId;
    private String username;
    private String nickname;
    private String avatar;
}

配置WebSocket

/**
 * WebSocket配置
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 启用简单消息代理,前缀为/topic的消息会被代理转发到订阅了相应主题的客户端
        config.enableSimpleBroker("/topic");
        // 客户端发送消息的前缀
        config.setApplicationDestinationPrefixes("/app");
    }
    
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册STOMP端点,客户端通过此端点连接到WebSocket服务器
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }
    
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }
}

二维码服务实现

/**
 * 二维码服务
 */
@Service
public class QRCodeService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 创建新的二维码
     * @return 二维码UUID
     */
    public String createQRCode() {
        String uuid = java.util.UUID.randomUUID().toString();
        String redisKey = Constants.QR_CODE_PREFIX + uuid;

        // 存储二维码状态到Redis
        redisTemplate.opsForHash().put(redisKey, "status", QRCodeStatus.WAITING.getCode());
        redisTemplate.expire(redisKey, Constants.QR_CODE_EXPIRE, TimeUnit.SECONDS);
        return uuid;
    }

    /**
     * 更新二维码状态
     * @param uuid 二维码UUID
     * @param status 新状态
     */
    public void updateQRCodeStatus(String uuid, QRCodeStatus status) {
        String redisKey = Constants.QR_CODE_PREFIX + uuid;
        redisTemplate.opsForHash().put(redisKey, "status", status.getCode());
        redisTemplate.expire(redisKey, getRemainingTime(uuid), TimeUnit.SECONDS);
    }

    /**
     * 更新二维码状态并关联用户信息
     * @param uuid 二维码UUID
     * @param status 新状态
     * @param userInfo 用户信息
     */
    public void updateQRCodeStatusWithUser(String uuid, QRCodeStatus status, UserInfo userInfo) {
        String redisKey = Constants.QR_CODE_PREFIX + uuid;
        // 使用Hash结构存储状态和用户信息
        redisTemplate.opsForHash().put(redisKey, "status", status.getCode());
        redisTemplate.opsForHash().put(redisKey, "userInfo", userInfo);
        redisTemplate.expire(redisKey, getRemainingTime(uuid), TimeUnit.SECONDS);
    }

    /**
     * 获取二维码状态
     * @param uuid 二维码UUID
     * @return 状态
     */
    public QRCodeStatus getQRCodeStatus(String uuid) {
        String redisKey = Constants.QR_CODE_PREFIX + uuid;
        Object status = redisTemplate.opsForHash().get(redisKey, "status");

        if (status == null) {
            return QRCodeStatus.EXPIRED;
        }

        for (QRCodeStatus qrCodeStatus : QRCodeStatus.values()) {
            if (qrCodeStatus.getCode().equals(status.toString())) {
                return qrCodeStatus;
            }
        }

        return QRCodeStatus.ERROR;
    }

    /**
     * 获取二维码关联的用户信息
     * @param uuid 二维码UUID
     * @return 用户信息
     */
    public UserInfo getUserInfo(String uuid) {
        String redisKey = Constants.QR_CODE_PREFIX + uuid;
        return (UserInfo) redisTemplate.opsForHash().get(redisKey, "userInfo");
    }

    /**
     * 获取Redis键的剩余时间
     * @param uuid 二维码UUID
     * @return 剩余时间(秒)
     */
    private Long getRemainingTime(String uuid) {
        String redisKey = Constants.QR_CODE_PREFIX + uuid;
        return redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
    }

    /**
     * 使二维码过期
     * @param uuid 二维码UUID
     */
    public void expireQRCode(String uuid) {
        String redisKey = Constants.QR_CODE_PREFIX + uuid;
        redisTemplate.delete(redisKey);
    }
}

后端控制器实现

/**
 * 登录控制器
 */
@RestController
@RequestMapping("/api/login")
public class LoginController {

    @Autowired
    private QRCodeService qrCodeService;

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    /**
     * 生成二维码
     */
    @GetMapping("/qrCode")
    public Map<String, Object> generateQRCode() throws Exception {
        String uuid = qrCodeService.createQRCode();
        String qrCodeBase64 = QRCodeUtil.generateQRCodeBase64(uuid);

        Map<String, Object> result = new HashMap<>();
        result.put("uuid", uuid);
        result.put("qrCode", qrCodeBase64);
        result.put("expireTime", Constants.QR_CODE_EXPIRE);

        return result;
    }

    /**
     * 处理扫码请求
     */
    @PostMapping("/scan")
    public Map<String, Object> scanQRCode(@RequestBody Map<String, String> request) {

        String uuid = request.get("uuid");
        Long userId = Long.parseLong(request.get("userId"));
        Map<String, Object> result = new HashMap<>();

        // 检查二维码是否存在且有效
        QRCodeStatus status = qrCodeService.getQRCodeStatus(uuid);
        if (status != QRCodeStatus.WAITING) {
            result.put("success", false);
            result.put("message", "二维码无效或已过期");
            return result;
        }

        // 获取用户信息(这里应该从数据库查询,简化示例)
        UserInfo userInfo = new UserInfo(userId, "一安", "一安未来", "https://picsum.photos/200/200");

        // 更新二维码状态为已扫描
        qrCodeService.updateQRCodeStatusWithUser(uuid, QRCodeStatus.SCANNED, userInfo);

        // 通过WebSocket通知前端二维码已被扫描
        WebSocketMessage message = new WebSocketMessage(
                uuid,
                QRCodeStatus.SCANNED,
                "二维码已被扫描,请确认登录",
                userInfo
        );
        messagingTemplate.convertAndSend("/topic/qr/" + uuid, message);

        result.put("success", true);
        result.put("message", "扫码成功,请在PC端确认登录");
        return result;
    }

    /**
     * 处理授权请求
     */
    @PostMapping("/authorize")
    public Map<String, Object> authorize(@RequestBody Map<String, String> request) {


        String uuid = request.get("uuid");
        Boolean confirm = Boolean.parseBoolean(request.get("confirm"));
        Map<String, Object> result = new HashMap<>();

        // 检查二维码是否存在且已扫描
        QRCodeStatus status = qrCodeService.getQRCodeStatus(uuid);
        if (status != QRCodeStatus.SCANNED) {
            result.put("success", false);
            result.put("message", "二维码状态无效");
            return result;
        }

        if (confirm) {
            // 获取用户信息
            UserInfo userInfo = qrCodeService.getUserInfo(uuid);

            // 更新二维码状态为已确认
            qrCodeService.updateQRCodeStatusWithUser(uuid, QRCodeStatus.CONFIRMED, userInfo);

            // 通过WebSocket通知前端登录成功
            WebSocketMessage message = new WebSocketMessage(
                    uuid,
                    QRCodeStatus.CONFIRMED,
                    "登录成功",
                    userInfo
            );
            messagingTemplate.convertAndSend("/topic/qr/" + uuid, message);

            result.put("success", true);
            result.put("message", "授权成功");
            result.put("userInfo", userInfo);
        } else {
            // 更新二维码状态为已取消
            qrCodeService.updateQRCodeStatus(uuid, QRCodeStatus.ERROR);

            // 通过WebSocket通知前端登录已取消
            WebSocketMessage message = new WebSocketMessage(
                    uuid,
                    QRCodeStatus.ERROR,
                    "用户取消登录",
                    null
            );
            messagingTemplate.convertAndSend("/topic/qr/" + uuid, message);

            result.put("success", false);
            result.put("message", "用户取消登录");
        }

        return result;
    }

    /**
     * 检查二维码状态(轮询方式)
     */
    @GetMapping("/checkStatus")
    public Map<String, Object> checkStatus(@RequestParam String uuid) {
        Map<String, Object> result = new HashMap<>();

        QRCodeStatus status = qrCodeService.getQRCodeStatus(uuid);
        result.put("status", status.getCode());
        result.put("message", status.getMessage());

        if (status == QRCodeStatus.SCANNED) {
            // 获取用户信息
            UserInfo userInfo = qrCodeService.getUserInfo(uuid);
            result.put("userInfo", userInfo);
        }

        return result;
    }
}

WebSocket消息处理器

/**
 * WebSocket消息处理器
 */
@Controller
public class WebSocketController {
    
    /**
     * 处理客户端订阅二维码状态的消息
     */
    @MessageMapping("/subscribeQr")
    @SendTo("/topic/qr/{uuid}")
    public WebSocketMessage subscribeQr(@PathVariable String uuid) {
        // 这里可以根据需要返回初始状态
        return new WebSocketMessage(
                uuid, 
                QRCodeStatus.WAITING, 
                "等待扫描", 
                null
        );
    }
}

二维码登录系统技术方案

流程概述

1. 二维码生成阶段

UserWebFrontWebBackRedis打开登录页面请求生成二维码ID生成唯一二维码ID存储二维码状态(等待扫描)返回二维码图片建立WebSocket连接UserWebFrontWebBackRedis
  1. 扫描确认阶段
UserMobileAppWebBackWebFrontRedis扫描二维码发送扫描请求(二维码ID)更新状态为已扫描WebSocket推送状态变更更新UI显示已扫描显示账号选择界面选择账号并确认UserMobileAppWebBackWebFrontRedis
  1. 登录完成阶段
UserMobileAppWebBackWebFrontRedis确认登录请求验证二维码状态生成用户令牌更新状态为已确认(含用户信息)WebSocket推送登录成功完成登录流程显示用户信息显示登录成功UserMobileAppWebBackWebFrontRedis

数据结构设计

// 二维码状态枚举
enum QRCodeStatus {
    PENDING,      // 等待扫描
    SCANNED,      // 已扫描
    CONFIRMED,    // 已确认
    EXPIRED,      // 已过期
    REJECTED      // 已拒绝
}

// 二维码数据结构
public class QRCodeData {
    private String qrCodeId;          // 二维码ID
    private QRCodeStatus status;      // 当前状态
    private Long createTime;          // 创建时间
    private Long scanTime;           // 扫描时间
    private Long confirmTime;        // 确认时间
    private String userId;           // 用户ID(确认后)
    private String deviceInfo;       // 设备信息
    private String token;            // 登录令牌
    private String ipAddress;        // 扫描IP
}
@RestController
@RequestMapping("/api/qrcode")
public class QRCodeController {
    
    @PostMapping("/generate")
    public ResponseResult<QRCodeResponse> generateQRCode() {
        // 1. 生成唯一二维码ID
        String qrCodeId = UUID.randomUUID().toString();
        
        // 2. 创建二维码数据
        QRCodeData qrCodeData = new QRCodeData();
        qrCodeData.setQrCodeId(qrCodeId);
        qrCodeData.setStatus(QRCodeStatus.PENDING);
        qrCodeData.setCreateTime(System.currentTimeMillis());
        qrCodeData.setExpireTime(System.currentTimeMillis() + 5 * 60 * 1000); // 5分钟过期
        
        // 3. 存储到Redis
        redisTemplate.opsForValue().set(
            "qrcode:" + qrCodeId, 
            qrCodeData, 
            5, TimeUnit.MINUTES
        );
        
        // 4. 生成二维码图片
        String qrContent = generateQRContent(qrCodeId);
        byte[] qrImage = generateQRImage(qrContent);
        
        return ResponseResult.success(new QRCodeResponse(qrCodeId, qrImage));
    }
    
    @PostMapping("/scan")
    public ResponseResult<Void> scanQRCode(@RequestBody ScanRequest request) {
        // 1. 验证二维码存在且未过期
        QRCodeData qrCodeData = getQRCodeData(request.getQrCodeId());
        if (qrCodeData == null || qrCodeData.isExpired()) {
            return ResponseResult.error("二维码已过期");
        }
        
        // 2. 更新状态为已扫描
        qrCodeData.setStatus(QRCodeStatus.SCANNED);
        qrCodeData.setScanTime(System.currentTimeMillis());
        qrCodeData.setDeviceInfo(request.getDeviceInfo());
        qrCodeData.setIpAddress(request.getIpAddress());
        
        // 3. 保存到Redis
        redisTemplate.opsForValue().set(
            "qrcode:" + qrCodeId, 
            qrCodeData
        );
        
        // 4. 通过WebSocket通知Web端
        notifyWebSocket(qrCodeId, "SCANNED");
        
        return ResponseResult.success();
    }
    
    @PostMapping("/confirm")
    public ResponseResult<LoginResult> confirmLogin(@RequestBody ConfirmRequest request) {
        // 1. 验证二维码状态
        QRCodeData qrCodeData = getQRCodeData(request.getQrCodeId());
        if (qrCodeData.getStatus() != QRCodeStatus.SCANNED) {
            return ResponseResult.error("二维码状态无效");
        }
        
        // 2. 验证用户身份
        User user = authenticateUser(request.getToken());
        
        // 3. 生成登录令牌
        String loginToken = generateLoginToken(user);
        
        // 4. 更新二维码状态
        qrCodeData.setStatus(QRCodeStatus.CONFIRMED);
        qrCodeData.setConfirmTime(System.currentTimeMillis());
        qrCodeData.setUserId(user.getId());
        qrCodeData.setToken(loginToken);
        
        // 5. 保存到Redis
        redisTemplate.opsForValue().set(
            "qrcode:" + qrCodeId, 
            qrCodeData
        );
        
        // 6. 通过WebSocket通知Web端
        notifyWebSocket(qrCodeId, "CONFIRMED", user);
        
        return ResponseResult.success(new LoginResult(loginToken));
    }
}

WebSocket处理器

@Component
@ServerEndpoint("/ws/qrcode/{qrCodeId}")
public class QRCodeWebSocketHandler {
    
    private static Map<String, Session> sessions = new ConcurrentHashMap<>();
    
    @OnOpen
    public void onOpen(Session session, @PathParam("qrCodeId") String qrCodeId) {
        sessions.put(qrCodeId, session);
    }
    
    @OnClose
    public void onClose(@PathParam("qrCodeId") String qrCodeId) {
        sessions.remove(qrCodeId);
    }
    
    public void notifyStatusChange(String qrCodeId, String status, Object data) {
        Session session = sessions.get(qrCodeId);
        if (session != null && session.isOpen()) {
            try {
                Map<String, Object> message = new HashMap<>();
                message.put("event", "QRCODE_STATUS_CHANGE");
                message.put("qrCodeId", qrCodeId);
                message.put("status", status);
                message.put("data", data);
                message.put("timestamp", System.currentTimeMillis());
                
                session.getBasicRemote().sendText(
                    objectMapper.writeValueAsString(message)
                );
            } catch (Exception e) {
                log.error("WebSocket发送失败", e);
            }
        }
    }
}

安全增强实现

@Component
public class SecurityEnhancer {
    
    // 防重放攻击
    public boolean verifyReplayAttack(String signature, String timestamp, String nonce) {
        // 1. 检查时间戳是否在有效期内(5分钟内)
        long currentTime = System.currentTimeMillis();
        long requestTime = Long.parseLong(timestamp);
        if (Math.abs(currentTime - requestTime) > 5 * 60 * 1000) {
            return false;
        }
        
        // 2. 检查nonce是否已使用过
        if (redisTemplate.hasKey("nonce:" + nonce)) {
            return false;
        }
        redisTemplate.opsForValue().set("nonce:" + nonce, "1", 5, TimeUnit.MINUTES);
        
        // 3. 验证签名
        String expectedSignature = calculateSignature(timestamp, nonce);
        return signature.equals(expectedSignature);
    }
    
    // IP限制
    public boolean checkIpLimit(String ipAddress) {
        String key = "ip_limit:" + ipAddress;
        Long count = redisTemplate.opsForValue().increment(key, 1);
        
        if (count == 1) {
            redisTemplate.expire(key, 1, TimeUnit.HOURS);
        }
        
        return count <= 100; // 每小时最多100次请求
    }
    
    // 数据加密
    public String encryptData(String data) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            byte[] encrypted = cipher.doFinal(data.getBytes());
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("加密失败", e);
        }
    }
}

多设备支持实现

@Service
public class DeviceManagementService {
    
    // 设备管理
    public void recordDeviceLogin(String userId, DeviceInfo deviceInfo) {
        DeviceRecord record = new DeviceRecord();
        record.setUserId(userId);
        record.setDeviceId(deviceInfo.getDeviceId());
        record.setDeviceType(deviceInfo.getDeviceType());
        record.setLoginTime(System.currentTimeMillis());
        record.setLocation(deviceInfo.getLocation());
        record.setIpAddress(deviceInfo.getIpAddress());
        
        // 保存设备记录
        deviceRecordRepository.save(record);
        
        // 检查异地登录
        checkUnusualLocation(userId, deviceInfo.getLocation());
    }
    
    // 异地登录检测
    private void checkUnusualLocation(String userId, String newLocation) {
        List<DeviceRecord> recentLogins = deviceRecordRepository
            .findRecentLogins(userId, 30); // 最近30天
        
        if (recentLogins.isEmpty()) {
            return;
        }
        
        String commonLocation = findCommonLocation(recentLogins);
        if (!isSameRegion(commonLocation, newLocation)) {
            // 发送异地登录提醒
            sendUnusualLoginAlert(userId, newLocation);
        }
    }
    
    // 单点登录
    public void handleSingleSignOn(String userId, String newToken) {
        // 1. 使旧token失效
        invalidateOldTokens(userId);
        
        // 2. 保存新token
        saveNewToken(userId, newToken);
        
        // 3. 通知其他设备
        notifyOtherDevices(userId, "NEW_LOGIN_DETECTED");
    }
}

前端实现
Web端二维码处理

class QRCodeLogin {
    constructor() {
        this.qrCodeId = null;
        this.ws = null;
        this.pollingInterval = null;
    }
    
    // 初始化二维码
    async initQRCode() {
        try {
            const response = await fetch('/api/qrcode/generate');
            const data = await response.json();
            
            this.qrCodeId = data.qrCodeId;
            this.displayQRCode(data.qrImage);
            this.connectWebSocket();
            
            // 启动状态轮询(备用方案)
            this.startStatusPolling();
        } catch (error) {
            console.error('生成二维码失败:', error);
        }
    }
    
    // WebSocket连接
    connectWebSocket() {
        this.ws = new WebSocket(`ws://${location.host}/ws/qrcode/${this.qrCodeId}`);
        
        this.ws.onmessage = (event) => {
            const message = JSON.parse(event.data);
            this.handleStatusChange(message);
        };
        
        this.ws.onclose = () => {
            console.log('WebSocket连接关闭,切换到轮询模式');
            this.switchToPolling();
        };
    }
    
    // 处理状态变更
    handleStatusChange(message) {
        switch (message.status) {
            case 'SCANNED':
                this.showScannedStatus();
                break;
            case 'CONFIRMED':
                this.completeLogin(message.data);
                break;
            case 'EXPIRED':
                this.showExpiredStatus();
                this.refreshQRCode();
                break;
        }
    }
}

配置示例
application.yml

# Redis配置
spring:
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    timeout: 3000ms
    database: 0

# 二维码配置
qrcode:
  expire-time: 300 # 5分钟
  polling-interval: 2000 # 轮询间隔2秒
  max-scan-attempts: 3 # 最大扫描尝试次数
  
# 安全配置
security:
  encryption:
    algorithm: AES
    key-size: 256
    iv-size: 16
  rate-limit:
    ip-max-requests: 100
    ip-window-hours: 1
    
# WebSocket配置
websocket:
  enabled: true
  endpoint: /ws
  allowed-origins: "*"
  message-size-limit: 65536

监控与日志

@Aspect
@Component
@Slf4j
public class QRCodeLogAspect {
    
    @Around("@annotation(QRCodeOperation)")
    public Object logQRCodeOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        String operation = getOperationName(joinPoint);
        String qrCodeId = getQRCodeId(joinPoint.getArgs());
        
        log.info("QRCode操作开始: operation={}, qrCodeId={}", operation, qrCodeId);
        
        long startTime = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long duration = System.currentTimeMillis() - startTime;
            
            log.info("QRCode操作成功: operation={}, qrCodeId={}, duration={}ms", 
                operation, qrCodeId, duration);
            
            // 记录监控指标
            recordMetrics(operation, duration, "success");
            
            return result;
        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            
            log.error("QRCode操作失败: operation={}, qrCodeId={}, duration={}ms, error={}", 
                operation, qrCodeId, duration, e.getMessage());
            
            // 记录监控指标
            recordMetrics(operation, duration, "error");
            
            throw e;
        }
    }
}

注意事项
安全性考虑

二维码ID必须是不可预测的随机值

所有敏感数据传输必须加密

实施严格的访问控制和权限验证

定期更新加密密钥

性能优化

Redis使用连接池

WebSocket连接保持活跃

二维码图片使用缓存

异步处理非关键操作

容错处理

WebSocket断开时自动切换轮询

Redis故障时的降级方案

网络异常的重试机制

状态不一致的恢复策略

用户体验

二维码过期自动刷新

实时状态反馈

多设备登录提示

操作确认机制

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐