导航
项目场景:
web\小程序。限制账户同时在线、登录。或者限制账户并发数
项目环境:asp.net core 3.1 API+redis+jwt +微信小程序
问题描述:
我们经常可以看到一些软件无法同时登录同一个账号,也就是同时只能有一个用户使用这个账号。或者另一种情形,限制用户并发数。这些都涉及到对在线用户登陆态的管理
解决思路:
- 后端在用户登录时记录下登录账号、时间、Token值,以Hash类型存入Redis。
- 登录成功后使用websocket监听客户端是否在线(主要为了解决用户非正常退出或关闭,而导致登陆态无法改变的情况)
- 用户登录时判断Redis数据库中该账号是否已存在登陆态数据,已存在则无法登录。
- 用户退出或是程序非正常关闭时,WS连接断开并同时清除redis中的用户登陆态数据
后端解决方案代码:
JWT实现鉴权中心,写成服务注入IOC容器中
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace canteen_order.Unity
{
public interface IJWTService{
string GetToken(string UserName);
}
public class JWTService : IJWTService
{
private readonly IConfiguration _configuration;
public JWTService(IConfiguration configuration){
_configuration = configuration;
}
public string GetToken(string UserName)
{
//payload信息
Claim[] claim = new[] { new Claim("WorkId",UserName), };
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
SigningCredentials credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
expires: DateTime.Now.AddMinutes(10),
signingCredentials:credentials,
audience:_configuration["Audience"],
issuer:_configuration["Issuer"],
claims:claim
) ;
string reutrnToken = new JwtSecurityTokenHandler().WriteToken(token);
return reutrnToken; //生成Token值
}
}
}
Redis写成服务注入IOC容器中
using ServiceStack.Redis;
namespace canteen_order.Services
{
public interface ICache
{
public IRedisClient redisClient { get; }
}
}
----------------------------------------------------------------------
using canteen_order.Services;
using ServiceStack.Redis;
namespace canteen_order.Unity
{
public class RedisHelper:ICache
{
public IRedisClient _redisClient = null;
public IRedisClient redisClient => _redisClient ?? new RedisClient("127.0.0.1", 6379);
}
}
使用signalR进行websocket通信
using canteen_order.Models;
using canteen_order.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace canteen_order.Hubs
{
public class MenuHub:Hub
{
public ICache _redisHelper;
public static ConcurrentDictionary<string, OnlineUser> OnlineClients { get; } = new ConcurrentDictionary<string, OnlineUser>();
private static readonly object SyncObj = new object();
public MenuHub(ICache cache)
{
_redisHelper = cache;
}
public override async Task OnConnectedAsync()
{
var http = Context.GetHttpContext();
var client = new OnlineUser()
{
WorkId= http.Request.Query["WorkId"],
};
lock (SyncObj)
{
OnlineClients[Context.ConnectionId] = client;
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
bool isRemoved=false;
OnlineUser client=null;
lock (SyncObj)
{
_redisHelper.redisClient.RemoveEntryFromHash("OnlineUser", OnlineClients[Context.ConnectionId].WorkId);
//WS连接断开后,清除登录态数据。
isRemoved = OnlineClients.TryRemove(Context.ConnectionId, out client);
}
}
}
}
}
登录控制器引用上述服务
public async Task<string> Login([FromBody] User user){
var use = await RepositoryWrap.User.Login(user);
if(use!=null){
if(_cache.redisClient.HashContainsEntry("OnlineUser",user.WorkId)){//判断该账户是否已登录
return "false";
}
string token = _jWTService.GetToken(user.WorkId);//重点
_cache.redisClient.SetEntryInHash("OnlineUser",user.WorkId,JsonConvert.SerializeObject(new OnlineUser { WorkId = user.WorkId, dateTime = DateTime.Now,Token = token, }));--重点
return token;//登录成功返回前端Token值
}
return "false";
}
前端解决方案代码
onLoad: function (options) {
var that=this
that.setData({WorkId:wx.getStorageSync('user')})
this.hubConnect = new Hub.HubConnection();
this.hubConnect.start("https://localhost:5001/menuhub", { WorkId:that.data.WorkId });
this.hubConnect.onOpen = res => {
console.log("成功开启连接")
}
this.hubConnect.on("system", res => {
wx.showModal({
title: '系统消息',
content: res,
})
})
this.hubConnect.on("receive", res => {
centendata.push({
content: res.msg,
time: new Date().toLocaleString(),
head_owner: res.avatar,
is_show_right: 0
});
this.setData({
centendata: centendata
})
})
},
小程序因为不支持JQ,所以引用了一个signal.JS的封装包。实现对HUB的操作,需要的可以进群联系我。
最终效果


Redis数据库中显示已存在登录态数据
总结
其实实现的方法还有很多譬如 session+redis,不过因为小程序不支持session,自定义请求头后测试发现还存在一些问题就弃用了这个方法,但是web端是可以适用的,因为浏览器支持session、cookie。
本次利用了redisd的Hash类型(键唯一)解决了限制账户同时在线的情况,稍加修改就可以变成限制用户并发数的情况,比如count<3.
本文只是实现了基础,其上还可以继续拓展。因篇幅有限,需要详细解答或者JS包的请进Q群联系:6406277
该项目使用ASP.NET Core 3.1 API、Redis和JWT,结合微信小程序,实现限制同一账号多用户同时在线和并发数控制。通过在用户登录时记录账号信息到Redis,并用websocket监听客户端状态,当账号已存在登录态数据时阻止新登录,退出时清除数据。前端使用signal.JS进行websocket通信。
1022

被折叠的 条评论
为什么被折叠?



