实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)

如上图,我的百度网盘已登录设备列表,有一个手机,2个windows客户端。手机设备有型号、最后登录时间、IP等。windows客户端信息有最后登录时间、操作系统类型、IP地址等。这些具体是如何实现的?下面分别给出android APP中采集手机信息,VUE3中采集电脑信息的实现思路和关键代码。


一、思路
 

通常是通过 Session 管理+Token机制+数据库存储 组合实现的。以下是可能的实现方式:

1. Session 或 Token 机制

  • 服务器为每个客户端生成唯一的 sessiontoken(JWT、OAuth等)。
  • 这些 token 会存储在数据库或缓存系统中(如 Redis)。

2. 记录登录状态

  • 当用户成功登录后,服务器会在数据库中记录 当前设备信息(如 IP、User-Agent、设备 ID)。
  • 还可以给每个 session 生成一个唯一 ID,并记录 设备唯一标识(如浏览器指纹、手机唯一 ID)。

3. 检查并限制登录数量

  • 登录时检查:当用户登录时,服务器会查询数据库或缓存,看当前账号已登录的设备数量是否已达到上限(如 3 个)。
  • 超限处理
    • 方案1:阻止新登录:如果已达上限,则拒绝新的登录请求,并提示“已达到最大设备数量”。
    • 方案2:踢掉旧设备:可以让用户选择踢掉最早登录的设备(删除最早的 session/token)。
    • 方案3:手动管理:提供用户后台,让用户手动管理登录设备,选择登出某个设备。

4. 定期清理过期/无效的 Session

  • 服务器可以设置 session 过期时间(如 7 天无操作自动登出)。
  • 或者在用户 主动登出 时,删除对应的 session/token。

超出最大登录数量时,百度网盘的实现方案是方案1:拒绝新的登录请求,并提示“已达到最大设备数量”。


二、上代码
 

1. Android APP 采集设备信息(Java/Kotlin)

Android 端可以使用 Build 类、TelephonyManagerWifiManager 采集设备信息,例如 设备型号、系统版本、IP 地址、MAC 地址 等。

示例代码 (Kotlin)

import android.content.Context
import android.net.wifi.WifiManager
import android.os.Build
import android.telephony.TelephonyManager
import java.net.NetworkInterface
import java.net.SocketException
import java.util.*

fun getDeviceInfo(context: Context): Map<String, String> {
    val deviceInfo = mutableMapOf<String, String>()

    // 设备型号
    deviceInfo["deviceModel"] = Build.MODEL
    // 设备品牌
    deviceInfo["deviceBrand"] = Build.BRAND
    // Android 版本
    deviceInfo["androidVersion"] = Build.VERSION.RELEASE
    // 设备唯一 ID
    deviceInfo["deviceId"] = Build.SERIAL

    // 获取 IP 地址(WIFI 或移动网络)
    val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
    val wifiInfo = wifiManager.connectionInfo
    val ip = android.text.format.Formatter.formatIpAddress(wifiInfo.ipAddress)
    deviceInfo["ipAddress"] = ip

    // 获取 MAC 地址
    deviceInfo["macAddress"] = getMacAddress()

    return deviceInfo
}

// 获取 MAC 地址
fun getMacAddress(): String {
    try {
        val interfaces = Collections.list(NetworkInterface.getNetworkInterfaces())
        for (networkInterface in interfaces) {
            if (!networkInterface.name.equals("wlan0", ignoreCase = true)) continue
            val macBytes = networkInterface.hardwareAddress ?: return ""
            return macBytes.joinToString(":") { "%02X".format(it) }
        }
    } catch (ex: SocketException) {
        ex.printStackTrace()
    }
    return "02:00:00:00:00:00" // 默认 MAC 地址
}

输出示例

2. Vue3 获取电脑信息

在 Vue3 Web 端,可以获取 IP、浏览器类型、操作系统等,但无法获取 MAC 地址(受浏览器安全限制)。需要配合后端来获取客户端 IP。

示例代码

<script setup>
import { onMounted, ref } from "vue";

const deviceInfo = ref({
  userAgent: "",
  platform: "",
  ipAddress: "",
});

// 获取本地设备信息
const getDeviceInfo = async () => {
  deviceInfo.value.userAgent = navigator.userAgent; // 浏览器信息
  deviceInfo.value.platform = navigator.platform; // 操作系统

  // 获取公网 IP(需要后端支持)
  try {
    const response = await fetch("https://api.ipify.org?format=json");
    const data = await response.json();
    deviceInfo.value.ipAddress = data.ip;
  } catch (error) {
    console.error("IP 获取失败", error);
  }
};

onMounted(() => {
  getDeviceInfo();
});
</script>

<template>
  <div>
    <h3>设备信息</h3>
    <p>操作系统: {{ deviceInfo.platform }}</p>
    <p>浏览器信息: {{ deviceInfo.userAgent }}</p>
    <p>IP 地址: {{ deviceInfo.ipAddress }}</p>
  </div>
</template>

输出示例

3. 将 session 存入 Redis的示例代码

首先,在 Spring Boot 项目的 pom.xml 中引入 Redis 相关依赖:

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

application.yml 中配置 Redis 连接信息:

spring:
  redis:
    host: localhost
    port: 6379
    password: ""
    timeout: 5000
  session:
    store-type: redis
    timeout: 86400  # 1天(可根据需求调整)

在 Java 代码中,我们使用 RedisTemplate 来存储 session,每次用户登录时:

  • 检查是否超出设备数量(例如最多 3 个)
  • 如果超出,踢掉最早的设备
  • 存入 Redis 并设置过期时间
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class SessionService {

    private final RedisTemplate<String, Object> redisTemplate;
    private static final int MAX_SESSIONS = 3;  // 限制最大设备登录数

    public SessionService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 记录新的登录 session
     * @param userId 用户 ID
     * @param sessionId 新的 session ID
     * @param deviceInfo 设备信息(IP、设备类型)
     */
    public void addSession(String userId, String sessionId, String deviceInfo) {
        String key = "user_sessions:" + userId;

        // 获取当前已存储的 session 列表
        List<Object> sessions = redisTemplate.opsForList().range(key, 0, -1);

        if (sessions != null && sessions.size() >= MAX_SESSIONS) {
            // 超过最大限制,移除最旧的 session(列表最左侧的)
            redisTemplate.opsForList().leftPop(key);
        }

        // 添加新的 session
        redisTemplate.opsForList().rightPush(key, sessionId + ":" + deviceInfo);
        
        // 设置 session 过期时间(7 天)
        redisTemplate.expire(key, 7, TimeUnit.DAYS);
    }

    /**
     * 获取用户已登录的设备列表
     */
    public List<Object> getSessions(String userId) {
        String key = "user_sessions:" + userId;
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 删除某个 session(用于手动登出)
     */
    public void removeSession(String userId, String sessionId) {
        String key = "user_sessions:" + userId;
        redisTemplate.opsForList().remove(key, 1, sessionId);
    }
}

测试 API

创建一个 RestController 让前端可以调用:

import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/session")
public class SessionController {

    private final SessionService sessionService;

    public SessionController(SessionService sessionService) {
        this.sessionService = sessionService;
    }

    // 添加新的登录 session
    @PostMapping("/add")
    public String addSession(@RequestParam String userId, @RequestParam String sessionId, @RequestParam String deviceInfo) {
        sessionService.addSession(userId, sessionId, deviceInfo);
        return "Session added successfully!";
    }

    // 获取用户的 session 列表
    @GetMapping("/list")
    public List<Object> getSessions(@RequestParam String userId) {
        return sessionService.getSessions(userId);
    }

    // 移除指定 session(登出)
    @PostMapping("/remove")
    public String removeSession(@RequestParam String userId, @RequestParam String sessionId) {
        sessionService.removeSession(userId, sessionId);
        return "Session removed!";
    }
}

测试示例

启动 Spring Boot 服务器后,可以用 Postmancurl 测试:

① 添加新 session
 

curl -X POST "http://localhost:8080/session/add?userId=123&sessionId=abcd123&deviceInfo=Windows_192.168.1.10"

返回

Session added successfully!

② 获取已登录设备

curl -X GET "http://localhost:8080/session/list?userId=123"

返回

③ 手动登出某个设备

curl -X POST "http://localhost:8080/session/remove?userId=123&sessionId=session1"

返回:Session removed!

总结

功能实现方式
存储 sessionRedis List 数据结构 (opsForList())
限制最多 3 个设备超过 3 个时 leftPop() 删除最旧 session
查询设备range(0, -1) 获取当前 session
登出设备remove() 删除指定 session
Session 过期expire(7, TimeUnit.DAYS) 设置 7 天过期

这样就可以限制用户最多只能在3台设备上登录,并支持手动踢出设备。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/968526.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何获取,CPU,GPU,硬盘,网卡,内存等硬件性能监控与各项温度传感器

首先需要下载 OpenHardwareMonitorServer 这是一个基于OpenHardwareMonitor 的 Web 服务器。可以让任何语言都可以获取硬件信息和值&#xff0c;OpenHardwareMonitorServer 是没有UI界面的因此它可以当成控制台程序使用。 该程序可用参数如下 参数&#xff1a;需要管理员权限…

解锁大语言模型潜能:KITE 提示词框架全解析

大语言模型的应用日益广泛。然而&#xff0c;如何确保这些模型生成的内容在AI原生应用中符合预期&#xff0c;仍是一个需要不断探索的问题。以下内容来自于《AI 原生应用开发&#xff1a;提示工程原理与实战》一书&#xff08;京东图书&#xff1a;https://item.jd.com/1013604…

C++STL容器之map的使用及复现

map 1. 关联式容器 vector、list、deque、forward_list(C11) 等STL容器&#xff0c;其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身&#xff0c;这样的容器被统称为序列式容器。而 map、set 是一种关联式容器&#xff0c;关联式容器也是用来存储数据的&#xf…

网络工程师 (30)以太网技术

一、起源与发展 以太网技术起源于20世纪70年代&#xff0c;最初由Xerox公司的帕洛阿尔托研究中心&#xff08;PARC&#xff09;开发。最初的以太网采用同轴电缆作为传输介质&#xff0c;数据传输速率为2.94Mbps&#xff08;后发展为10Mbps&#xff09;&#xff0c;主要用于解决…

30天开发操作系统 第 20 天 -- API

前言 大家早上好&#xff0c;今天我们继续努力哦。 昨天我们已经实现了应用程序的运行, 今天我们来实现由应用程序对操作系统功能的调用(即API, 也叫系统调用)。 为什么这样的功能称为“系统调用”(system call)呢&#xff1f;因为它是由应用程序来调用(操作)系统中的功能来完…

Java面试题及答案整理( 2023年 6 月最新版,持续更新)

秋招金九银十快到了&#xff0c;发现网上很多Java面试题都没有答案&#xff0c;所以花了很长时间搜集整理出来了这套Java面试题大全~ 这套互联网 Java 工程师面试题包括了&#xff1a;MyBatis、ZK、Dubbo、EL、Redis、MySQL、并发编程、Java面试、Spring、微服务、Linux、Spri…

查询语句来提取 detail 字段中包含 xxx 的 URL 里的 commodity/ 后面的数字串

您可以使用以下 SQL 查询语句来提取 detail 字段中包含 oss.kxlist.com 的 URL 里的 commodity/ 后面的数字串&#xff1a; <p><img style"max-width:100%;" src"https://oss.kxlist.com//8a989a0c55e4a7900155e7fd7971000b/commodity/20170925/20170…

管式超滤膜分离技术都可以应用到哪些行业?

管式超滤膜分离技术由于其高效、稳定和适应性强的特点&#xff0c;在多个行业都有广泛的应用&#xff1a; 1. 生物制药与医药行业 纯化与浓缩&#xff1a;在生物药品的下游处理阶段&#xff0c;管式超滤膜被用来纯化抗体、疫苗、蛋白质等生物大分子&#xff0c;通过精确筛选分子…

基于opencv的 24色卡IQA评测算法源码-可完全替代Imatest

1.概要 利用24色卡可以很快的分析到曝光误差&#xff0c;白平衡误差&#xff0c;噪声&#xff0c;色差&#xff0c;饱和度&#xff0c;gamma值。IQA或tuning工程一般用Imatest来手动计算&#xff0c;不便于产测部署&#xff0c;现利用opencv实现了imatest的全部功能&#xff0c…

【matlab优化算法-17期】基于DBO算法的微电网多目标优化调度

基于蜣螂DBO算法的微电网多目标优化调度 一、前言 微电网作为智能电网的重要组成部分&#xff0c;其优化调度对于降低能耗、减少环境污染具有重要意义。本文介绍了一个基于Dung Beetle Optimizer&#xff08;DBO&#xff09;算法的微电网多目标优化调度项目&#xff0c;旨在通…

【多模态大模型】系列2:Transformer Encoder-Decoder——BLIP、CoCa、BEITv3

目录 1 BLIP2 CoCa3 BEITv3 1 BLIP BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation BLIP是 ALBEF 原班人马做的&#xff0c;基本可以看做吸收了 VLMo 思想的 ALBEF。训练的 loss 和技巧都与 ALBEF一致&#xff…

算法——搜索算法:原理、类型与实战应用

搜索算法&#xff1a;开启高效信息检索的钥匙 在信息爆炸的时代&#xff0c;搜索算法无疑是计算机科学领域中熠熠生辉的存在&#xff0c;它就像一把神奇的钥匙&#xff0c;为我们打开了高效信息检索的大门。无论是在日常生活中&#xff0c;还是在专业的工作场景里&#xff0c;…

在vmd中如何渲染透明水分子

1.设置背景为白色 依次点击Graphics>>Colors... 2. 改变渲染模式 依次点击Display>>rendermode>>GLSL 3. 渲染水分子 选中水分子&#xff0c;显色方式改为ColorID, 编号10的颜色&#xff1b; 选择材质为GlassBubble; 绘图方式为QuickSurf. 若水盒子显示效…

【Cocos TypeScript 零基础 15.1】

目录 见缝插针UI脚本针脚本球脚本心得_旋转心得_更改父节点心得_缓动动画成品展示图 见缝插针 本人只是看了老师的大纲,中途不明白不会的时候再去看的视频 所以代码可能与老师代码有出入 SIKI_学院_点击跳转 UI脚本 import { _decorator, Camera, color, Component, directo…

Go+Wails+Vue 开发:添加停止按钮功能的实现

在本教程中&#xff0c;我将展示如何在一个使用 Wails 框架&#xff08;后端 Go&#xff09;和 Vue.js&#xff08;前端&#xff09;的彩票模拟器项目中添加一个“停止”按钮。由于现有的教程内容较为单一&#xff0c;我将通过具体的实现步骤进行详细说明。 项目初始化 首先&a…

微服务保护---Sentinel

1. 初始Sentinel 1.1. 雪崩问题及解决方案 1.1.1. 雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。 如图&#xff0c;如果服务提供者I发生了故障&#xff0c;当前的应用的部分业务因为依赖于服务I&#xff0c;因此也会…

win32汇编环境,窗口程序使用跟踪条(滑块)控件示例一

;运行效果 ;win32汇编环境,窗口程序使用跟踪条(滑块)控件示例一 ;生成2条横的跟踪条,分别设置不同的数值范围,设置不同的进度副度的例子 ;直接抄进RadAsm可编译运行。重要部分加备注。 ;下面为asm文件 ;>>>>>>>>>>>>>>>>>…

pnpm的使用

pnpm的使用 1.安装和使用2.统一包管理工具下载依赖 1.安装和使用 pnpm:performant npm &#xff0c;意味“高性能的npm”。 pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”。 pnpm安装指令: npm i -g p…

音视频协议

1. 多媒体信息 1.1 多媒体信息的两个主要特点&#xff1a; 信息量很大 标准语音&#xff1a;64Kbits(8KHz采样&#xff0c;8位编码)高质量音频&#xff1a;3Mbps(100KHz采样&#xff0c;12位编码) 在传输多媒体数据时&#xff0c;对时延和时延抖动均有较高要求 1.2 处理时延…

DeepSeek应用——与word的配套使用

目录 一、效果展示 二、配置方法 三、使用方法 四、注意事项 1、永久化使用 2、宏被禁用 3、office的生成失败 记录自己学习应用DeepSeek的过程...... 这个是与WPS配套使用的过程&#xff0c;office的与这个类似&#xff1a; 一、效果展示 二、配置方法 1、在最上方的…