实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)
如上图,我的百度网盘已登录设备列表,有一个手机,2个windows客户端。手机设备有型号、最后登录时间、IP等。windows客户端信息有最后登录时间、操作系统类型、IP地址等。这些具体是如何实现的?下面分别给出android APP中采集手机信息,VUE3中采集电脑信息的实现思路和关键代码。
一、思路
通常是通过 Session 管理+Token机制+数据库存储 组合实现的。以下是可能的实现方式:
1. Session 或 Token 机制
- 服务器为每个客户端生成唯一的 session 或 token(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
类、TelephonyManager
、WifiManager
采集设备信息,例如 设备型号、系统版本、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// 设备唯一 IDdeviceInfo["deviceId"] = Build.SERIAL// 获取 IP 地址(WIFI 或移动网络)val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManagerval wifiInfo = wifiManager.connectionInfoval 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)) continueval 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: localhostport: 6379password: ""timeout: 5000session:store-type: redistimeout: 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);}// 添加新的 sessionredisTemplate.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 服务器后,可以用 Postman 或 curl
测试:
① 添加新 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!
总结
功能 | 实现方式 |
---|---|
存储 session | Redis List 数据结构 (opsForList() ) |
限制最多 3 个设备 | 超过 3 个时 leftPop() 删除最旧 session |
查询设备 | range(0, -1) 获取当前 session |
登出设备 | remove() 删除指定 session |
Session 过期 | expire(7, TimeUnit.DAYS) 设置 7 天过期 |
这样就可以限制用户最多只能在3台设备上登录,并支持手动踢出设备。
相关文章:

实现限制同一个账号最多只能在3个客户端(有电脑、手机等)登录(附关键源码)
如上图,我的百度网盘已登录设备列表,有一个手机,2个windows客户端。手机设备有型号、最后登录时间、IP等。windows客户端信息有最后登录时间、操作系统类型、IP地址等。这些具体是如何实现的?下面分别给出android APP中采集手机信…...
Python入门全攻略(四)
函数 初识函数 函数:封装具有某种功能的代码块。 函数定义 使用def关键字来定义函数 # 定义函数 def 函数名(): 代码块 # 调用函数 函数名() 函数参数 def 函数名(形参) 代码块 # 调用 函数名(实参) 位置参数 按参数顺序传参 def func(a, b): print(a b)…...

Ubuntu 22.04 - OpenLDAP安装使用(服务器+LAM+客户端)
csdn你…怎么不自动保存了很崩溃啊啊啊啊,我记得发现没保存之后我又改了一遍然后保存了,怎么现在又没了啊啊啊啊,过了这么久我都不记得了啊啊啊啊啊 参考 在 Ubuntu 22.04|20.04|18.04 上安装 OpenLDAP 和 phpLDAPadmin 在 Ubuntu 22.04|20…...

Linux ARM64 将内核虚拟地址转化为物理地址
文章目录 前言一、通用方案1.1 kern_addr_valid1.2 __pa 二、ARM64架构2.1 AT S1E1R2.2 is_kernel_addr_vaild2.3 va2pa_helper 三、demo演示参考资料 前言 本文介绍一种通用的将内核虚拟地址转化为物理地址的方案以及一种适用于ARM64 将内核虚拟地址转化为物理地址的方案&…...

使用 Visual Studio Code (VS Code) 开发 Python 图形界面程序
安装Python、VS Code Documentation for Visual Studio Code Python Releases for Windows | Python.org 更新pip >python.exe -m pip install --upgrade pip Requirement already satisfied: pip in c:\users\xxx\appdata\local\programs\python\python312\lib\site-pa…...
图像处理篇---基本OpenMV图像处理
文章目录 前言1. 灰度化(Grayscale)2. 二值化(Thresholding)3. 掩膜(Mask)4. 腐蚀(Erosion)5. 膨胀(Dilation)6. 缩放(Scaling)7. 旋转…...
一文讲清springboot所有注解
Spring Boot 注释是提供有关 Spring 应用程序信息的元数据。 基于 Spring 构建,涵盖其所有功能, Spring Boot 因其生产就绪环境而迅速成为开发人员的最爱,它允许开发人员直接专注于逻辑,而无需配置和设置的麻烦。 Spring Boot 是一…...
pytest测试专题 - 1.1 运行pytest
<< 返回目录 1 pytest学习笔记 - 1.1 运行pytest 1.1 运行pyest 在命令行执行pytest --help usage: pytest [options] [file_or_dir] [file_or_dir] [...] ... ...1.1.1 pytest不携带参数 pytest不带参数时,会扫描当前目录下的所有目录、子目录中符合测试用…...

Java多线程——线程池的使用
线程饥饿死锁 在单线程的Executor中,如果任务A将任务B提交给同一个Executor,并且等待任务B的结果,就会引发死锁线程池中所有正在执行任务的线程由于等待其他仍处于工作队列中的任务而阻塞 执行时间较长的任务 执行时间较长的任务不仅会造成…...

NO.15十六届蓝桥杯备战|while循环|六道练习(C++)
while循环 while语法形式 while 语句的语法结构和 if 语句⾮常相似,但不同的是 while 是⽤来实现循环的, if 是⽆法实现循环的。 下⾯是 while 循环的语法形式: //形式1 while ( 表达式 )语句; //形式2 //如果循环体想包含更多的语句&a…...
DeepSeek 从入门到精通学习指南,2025清华大学《DeepSeek从入门到精通》正式发布104页pdf版超全解析
DeepSeek 是一款强大的 AI 搜索引擎,广泛应用于企业级数据检索和分析。无论您是初学者还是有经验的用户,掌握 DeepSeek 的使用都能为您的工作带来极大的便利。本文将从入门到精通,详细介绍如何学习和使用 DeepSeek。 链接: https://pan.baid…...

2025年SEO自动优化工具
随着2025年互联网的快速发展,越来越多的企业和个人意识到,拥有一个排名靠前的网站对于吸引客户、增加流量、提高转化率至关重要。而要想让自己的网站脱颖而出,获得更多曝光,最重要的一项工作就是进行SEO优化。传统的SEO优化方式通…...

KEPServerEX 的接口类型与连接方式的详细说明
目录 一、KEPServerEX 核心架构 二、KEPServerEX 支持的接口类型 三、KEPServerEX 支持的连接类型 1. 通用工业协议 2. 品牌专属协议 3. 行业专用协议 4. 数据库与文件接口 四、配置示例 1. 接口配置(以OPC UA为例) 2. 连接配置(以…...

AGI时代的认知重塑:人类文明的范式转移与思维革命
文章目录 引言:站在文明转型的临界点一、认知危机:当机器开始理解世界1.1 AGI的本质突破:从模式识别到世界建模1.2 人类认知的脆弱性暴露二、认知革命:重构思维的四个维度2.1 元认知升级:从直觉思维到二阶观察2.2 混合智能:人机认知回路的构建2.3 认知安全:防御机器思维…...

OmniManip:以目标为中心的交互基元作为空间约束实现通用机器人操作
25年1月来自北大、北大-智元实验室和智元机器人公司的论文“OmniManip: Towards General Robotic Manipulation via Object-Centric Interaction Primitives as Spatial Constraints”。 开发能够在非结构化环境中进行操作的通用机器人系统是一项重大挑战。虽然视觉-语言模型 …...

论文第二次阅读笔记
摘要学习 存在问题:目前流行的图神经网络仅通过欧几里得几何及其相关的向量空间操作来建模数据,存在局限性 我们通过提出一种数学上有根据的图卷积网络(GCN)的推广,将其扩展到常曲率空间(或其乘积空间),从而填补了这一空白。 一是引入一种统一的形式主义,可以在所有常…...

【Android开发AI实战】选择目标跟踪基于opencv实现——运动跟踪
文章目录 【Android 开发 AI 实战】选择目标跟踪基于 opencv 实现 —— 运动跟踪一、引言二、Android 开发与 AI 的融合趋势三、OpenCV 简介四、运动跟踪原理(一)光流法(二)卡尔曼滤波(三)粒子滤波 五、基于…...

系统漏洞扫描服务:安全风险识别与防护指南
系统安全的关键在于漏洞扫描服务,此服务能迅速发现潜在的安全风险。借助专业的扫描工具和技术,它确保系统稳定运作。以下将简要介绍这一服务的主要特点。 扫描原理 系统漏洞扫描服务依赖两种主要手段:一是通过漏洞数据库进行匹配࿰…...

2.Excel:滨海市重点中学的物理统考考试情况❗(15)
目录 NO12 1.数据透视表 2. 3.sum函数 4.sumifs客观/主观平均分 5.sumifs得分率 6.数字格式修改 NO3/4/5 sumifs某一组数据相加,某一范围,某一范围的具体点向下拖拉,锁定列;向左右,锁定行F4&#x…...
使用 React 16+Webpack 和 pdfjs-dist 或 react-pdf 实现 PDF 文件显示、定位和高亮
写在前面 在本文中,我们将探讨如何使用 React 16Webpack 和 pdfjs-dist 或 react-pdf 库来实现 PDF 文件的显示、定位和高亮功能。这些库提供了强大的工具和 API,使得在 Web 应用中处理 PDF 文件变得更加容易。 项目设置 首先,我们需要创建…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...