2021-01-15:用户登录,保存30天的免登,只允许两个设备登录,如果有第三个设备登录,踢掉第一个。改密码的时候,所有设备需要下线。这个逻辑怎么实现呢?

福哥答案2021-01-15:
一、我想到的答案。
为什么不能用设备id?
接口调用,如果是网页,设备id是无法获取的。另外,设备id可以作假。

为什么不能用ip区分?
有些网络(某些校园网),对外就是一个ip。

用token区分设备有什么缺点?
同一台设备登录多次,会被当成多个不同的设备。实在想不到好的办法,故逻辑实现采用token区分设备。

逻辑实现如下:
方案1:用redis里的list,key存【用户id】,value存【登录token】列表。用redis里的string,key存【登录token】,value存【登录时间】。30天免登,string中value值是否是30天以前,如果是30天以前,可以删掉string中的这个token,list中对应的token也需要删除。踢掉第1个,当用户数超过两个,踢掉list的第1个token,string中的token也需要删除。改密码,删除某个用户下的list以及string里的对应token。
方案2:用redis里的list,key存【用户id】,value存【登录token+时间】列表。

二、知乎答案:
使用 Redis 存储用户 ,登录的设备实现,利用 ZSET。
每个用户一个 ZSET(假设就是以用户 id 作为 ZSET 的 KEY),里面的 KEY 为设备 id,value 为登录时间戳。
1.当用户登录时,使用 lua 脚本(防止并发导致登录设备多于 2 个)检查设备:
ZSCORE 判断设备是否存在以及登录时间
如果存在:
ZADD就更新SCORE为当前时间戳
如果不存在:
ZCARD 用户id 获取当前用户有多少设备
如果设备数量 >= 2 则移除除了最后一个的其他设备(可以通过ZRANGE获取最后一个KEY,ZSCORE获取其分数之后ZREMRANGEBYSCORE删除小于这个分数的所有KEY)
ZADD设置设备id为KEY,当前时间戳为SCORE
设置ZSET过期时间为30天,减少30天内没有设备登录时检查登录态的判断消耗

2.当用户请求需要登录态后的 API,检查登录态时:
ZSCORE 判断设备是否存在以及登录时间
如果设备存在存在并且登录时间与当前时间间隔小于30天,则有效。
否则,登录态失效,需要重新登录

3.修改密码,所有设备下线:
删除这个ZSET

三、享学答案:
1.要能够识别不同的设备,比如移动设备的imei。
2.服务端需要有存储账号与设备的记录,以及客户端的ip及端口。
对于是否登出的检测有主动和被动两种方式。
3.服务端主动调用客户端登出。
3.1客户端可以开放清楚客户端cookie等登录信息的接口。
3.2.服务端在登录时存入设备与用户的记录,并且判断是否超出限制,如果超出限制则找出最早登录的客户端ip端口调用清楚客户端cookie的接口。
3.4.修改密码同理,调用所有已登录客户端的清楚cookie接口。
4.客户端主动检测登录是否已失效。
4.1服务端需要有检测登录有效性的接口。
4.2客户端在接到请求时主动调用服务器接口检测,根据响应判断当前登录状态,如果已失效则主动退出当前账号。
4.3登录超出限制或者修改密码时,服务端删除账号与设备的记录,在客户端调用登录有效性检测接口时查不到对应设备的记录则返回已失效。


2020-01-15:用户登录,保存30天的免登,只允许两个设备登录,如果有第三个设…如何回答呢?
2020-01-15:用户登录,保存30天的免登,只允许两个设备登录,如果有第三个设备登录,踢掉第一个。改密码的时候,所有设备需要下线。这个逻辑怎么实现呢?
评论

<think>好的,用户需要我帮忙写一个C#类来管理用户软件的使用期限,要求30后需要短期和长期密码才能使用。首先,我需要明确这个类需要哪些功能。 首先,使用期限管理,可能需要记录首次运行的时间,然后计算已经过去的数。这里应该用DateTime来记录首次启动时间,并且需要持久化存储,比如保存到文件或注册表,这样下次启动时还能读取。如果第一次运行,就保存当前时间。 然后,30后需要验证两个密码。可能需要两个属性或字段来存储短期和长期密码。短期密码可能是有时效性的,比如每或每小时变化,而长期密码是固定的。但用户的需求中短期和长期密码都是必须的,所以可能短期密码用户设置的,长期密码也是,或者系统生成的。需要进一步确认,但根据问题描述,可能用户需要设置这两个密码30后检查。 接下来,类需要提供检查是否过期的方法,即是否超过了30。还需要验证密码的方法,当过期后,用户必须输入两个密码才能继续使用。 另外,需要考虑数据的持久化,所以这个类需要能在启动时加载保存的首次运行时间,以及保存密码。可能需要用文件读写,比如使用JSON序列化保存到本地文件。 还可能要考虑防篡,比如加密保存的数据,否则用户可能修系统时间或删除保存的文件来绕过验证。但用户没有特别提到安全性,所以可能先实现基本功能,后续再加强安全措施。 现在开始设计类结构: 类名可能叫LicenseManager,包含以下成员: - 私有字段:DateTime首次运行时间 - 属性:短期密码和长期密码的字符串 - 方法:CheckIfExpired() 返回是否过期 - 方法:ValidatePasswords(string shortPwd, string longPwd) 验证两个密码是否正确 - 方法:SaveData() 和 LoadData() 用于持久化 首次运行时,如果没有保存的数据,就记录当前时间,并生成或让用户设置初始密码。但根据问题描述,用户可能需要设置这两个密码,所以可能需要构造函数或初始化方法允许传入这两个密码,或者从外部设置。 但问题中用户可能希望这个类自己管理密码,比如短期密码是动态生成的,或者长期密码是固定的。需要进一步分析。可能用户希望这两个密码是在30后要求输入,所以可能在过期后,用户必须提供正确的密码才能继续。因此,密码可能需要存储在某个地方,比如在安装时设置,或者由管理员设置。但这里可能简化,让类在初始化时设置默认密码,或者允许通过方法设置。 或者,问题中的短期和长期密码可能是预先设定的,比如软件开发商提供,用户需要输入正确的才能继续使用。所以LicenseManager类需要存储这两个正确的密码,在验证时比较用户输入的是否匹配。 综上,可能的实现步骤: 1. 类LicenseManager,在构造时检查是否有保存的数据(如保存的文件),如果有则加载首次运行时间和两个密码;如果没有,则记录当前时间为首次运行时间,并生成或设置默认密码保存到文件。 2. CheckExpiration方法计算是否超过30。 3. 当过期时,用户必须调用ValidatePasswords方法,传入两个密码,如果都正确,则允许使用。 但问题中可能需要过期后每次启动都需要验证密码,所以类可能需要一个状态,比如IsExpired,而CheckIfExpired方法返回这个状态。 现在考虑持久化存储。使用JSON序列化保存数据到一个文件,比如license.json。数据包括首次运行时间、短期密码、长期密码。或者,使用加密的方式保存,但先简单实现。 可能的代码结构: public class LicenseData { public DateTime FirstRunDate { get; set; } public string ShortTermPassword { get; set; } public string LongTermPassword { get; set; } } public class LicenseManager { private LicenseData _data; private string _filePath = "license.dat"; public LicenseManager() { LoadData(); if (_data == null) { _data = new LicenseData { FirstRunDate = DateTime.Now, ShortTermPassword = "defaultShort", LongTermPassword = "defaultLong" }; SaveData(); } } public bool IsExpired() { TimeSpan days = DateTime.Now - _data.FirstRunDate; return days.TotalDays > 30; } public bool ValidatePasswords(string shortPwd, string longPwd) { return shortPwd == _data.ShortTermPassword && longPwd == _data.LongTermPassword; } private void LoadData() { if (File.Exists(_filePath)) { string json = File.ReadAllText(_filePath); _data = JsonConvert.DeserializeObject<LicenseData>(json); } } private void SaveData() { string json = JsonConvert.SerializeObject(_data); File.WriteAllText(_filePath, json); } } 这样,当首次运行时,会创建文件,记录首次运行时间,并设置默认密码。之后每次启动检查是否超过30。如果过期,调用ValidatePasswords验证用户输入的密码是否正确。 但用户可能需要自己设置密码,而不是使用默认的。所以可能需要提供方法来修密码,或者初始化时传入密码。但根据问题描述,用户可能需要预先设定这两个密码,所以可以在构造函数中允许传入,或者从外部配置读取。比如修LicenseManager的构造函数,接受两个密码参数: public LicenseManager(string shortPwd, string longPwd) { LoadData(); if (_data == null) { _data = new LicenseData { FirstRunDate = DateTime.Now, ShortTermPassword = shortPwd, LongTermPassword = longPwd }; SaveData(); } } 但这样在首次创建时需要传入密码,可能更适合。或者,允许在未过期时修密码,但需要权限。这部分可能需要更多逻辑,但根据问题描述,可能用户只需要一个基本结构,所以暂时按默认密码处理,用户可以在代码中修。 但问题中提到“30后需要有短期密码和长期密码,才能使用”,所以可能在30后,每次启动软件都需要验证这两个密码。因此,使用这个LicenseManager的代码应该在启动时检查IsExpired,如果为true,则要求用户输入两个密码,调用ValidatePasswords,如果返回true,则继续,否则退出。 此外,需要考虑系统时间被用户的情况。例如,用户将系统时间调回去,绕过30限制。为了防止这种情况,可能需要记录每次运行的时间,或者使用网络时间,但这会增加复杂度。根据问题描述,可能不需要处理这种情况,所以暂不实现。 现在,针对用户的问题,给出一个完整的类,并解释各部分的功能。可能还需要注意,保存的数据可能需要加密,否则用户可以直接修文件中的密码或首次运行时间。例如,使用ProtectedData来加密保存的文件内容,但这需要更多的代码。 不过,根据用户的要求,可能先提供一个基本实现,然后在解释中提到安全性的问题,作为后续进的点。 综上,给出的代码应该包括LicenseManager类,处理首次运行时间的记录,过期检查,密码验证,以及数据的持久化。</think><< 以下是实现软件使用期限管理的C#类,包含短期和长期密码验证功能: ```csharp using System; using System.IO; using Newtonsoft.Json; public class LicenseManager { private const string DataFilePath = "license.dat"; private LicenseData _licenseData; public LicenseManager() { LoadLicenseData(); if (_licenseData == null) { InitializeNewLicense(); } } private void InitializeNewLicense() { _licenseData = new LicenseData { FirstRunDate = DateTime.Now, ShortTermPassword = GenerateDefaultPassword("short"), LongTermPassword = GenerateDefaultPassword("long") }; SaveLicenseData(); } private string GenerateDefaultPassword(string type) { return type switch { "short" => Guid.NewGuid().ToString().Substring(0, 8), "long" => Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16), _ => throw new ArgumentException("无效的密码类型") }; } public bool IsLicenseExpired() { return GetRemainingDays() <= 0; } public int GetRemainingDays() { TimeSpan elapsed = DateTime.Now - _licenseData.FirstRunDate; return 30 - (int)elapsed.TotalDays; } public bool ValidatePasswords(string shortPwd, string longPwd) { return shortPwd == _licenseData.ShortTermPassword && longPwd == _licenseData.LongTermPassword; } private void LoadLicenseData() { if (File.Exists(DataFilePath)) { string json = File.ReadAllText(DataFilePath); _licenseData = JsonConvert.DeserializeObject<LicenseData>(json); } } private void SaveLicenseData() { string json = JsonConvert.SerializeObject(_licenseData); File.WriteAllText(DataFilePath, json); } // 用于存储许可证数据的内部类 private class LicenseData { public DateTime FirstRunDate { get; set; } public string ShortTermPassword { get; set; } public string LongTermPassword { get; set; } } } ``` 实现说明: 1. 数据持久化:使用JSON序列化存储首次运行时间和生成的默认密码 2. 密码生成:使用GUID生成8位短期密码和16位长期密码 3. 数计算:精确计算剩余数(按24小时制) 4. 验证机制:独立验证两个密码的匹配性 5. 首次运行初始化:自动创建新的许可证记录 使用方法: ```csharp var manager = new LicenseManager(); if (manager.IsLicenseExpired()) { // 弹出密码输入界面 if (!manager.ValidatePasswords(inputShortPwd, inputLongPwd)) { // 密码验证失败处理 Environment.Exit(0); } } else { // 显示剩余数:manager.GetRemainingDays() } ``` 安全进建议: 1. 使用加密存储代替明文JSON 2. 添加防止系统时间篡的校验 3. 实现密码重置机制 4. 增加密码尝试次数限制 >>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

福大大架构师每日一题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值