.net core WebApi使用Redis教程(一)Redis代替Session实现登录
小白浏览:5622023-12-26 14:32:24本文累计收益:0我也要赚钱

本文基于.net core webapi进行讲解,使用CSRedis库操作Redis,实现项目的登录模块。登录方式使用手机号、短信验证码进行登录。登录过程包含获取短信验证码、判断用户输入手机号及短信验证码是否正确及刷新登录有效期3个环节。

一、获取短信验证码

用户登录第一步先输入手机号码,并获取验证码,后台生成验证码后存储到Redis中,并设置有效期,验证码用字符串格式存储在Redis中,为确保多用户同时发送验证码时验证码唯一,将使用手机号作为Redis的Key。发送手机验证码接口相关代码实现如下:

1、接口Controllers代码

namespace RedisTest.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    [TypeFilter(typeof(CustomAuthorizeFilter))]
    public class LoginController : ControllerBase
    {
        private readonly IRedisDomainService redisService;

        public LoginController(IRedisDomainService redisService)
        {
            this.redisService = redisService;
        }
        /// <summary>
        /// 发送手机验证码
        /// </summary>
        /// <param name="phone"></param>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        public ResponseContent SendCode(string phone)
        {
            //HttpContext.Session.GetString("key");
            return redisService.SendCode(phone);
        }
    }
}

2、短信验证码发送服务代码

using Common.CacheManager;
using Common.Commons;
using Common.Enum;
using Domain.Entities;
using Domain.Repository;
using Microsoft.AspNetCore.Http;
using Common.Const;
using Common.Extensions;
using System.Security.Policy;

namespace Domain.Service
{
    public class RedisDomainService : IRedisDomainService
    {
        private readonly IRedisDomainRepostory redisRepostory;
        private readonly ICacheService cacheService;
        public RedisDomainService(IRedisDomainRepostory redisRepostory, ICacheService cacheService)
        {
            this.redisRepostory = redisRepostory;
            this.cacheService = cacheService;
        }

        /// <summary>
        /// 发送短信验证码
        /// </summary>
        /// <param name="phone"></param>
        /// <returns></returns>
        public ResponseContent SendCode(string phone)
        {
            ResponseContent response = new ResponseContent();
            //1.校验手机号
            if (!RegexUtils.IsMobile(phone))
            {
                //2.如果不符合,返回错误信息
                return response.Error(ResponseType.ParameterVerificationError);
            }
            //3.符合生成验证码
            Random random = new Random();
            string code = random.Next(100000, 999999).ToString();
            //4.保存验证码到Redis
            cacheService.Add(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL);
            //5.发送验证码(模拟发送)
            Console.WriteLine("短信验证码已经发送,验证码:" + code);
            //返回ok
            return response.Ok(code);
        }
    }
}
二、判断用户输入手机号码是否正确

用户输入手机号及短信验证码,后端代码将于Redis中存储的短信验证码进行对比,如果一致在进行判断数据库中是否存在用户信息,不存在将进行用户创建,最后将用户信息存储到Redis中,供其他模块使用,存储用户信息将使用Hash类型进行存储,随机生成Guid作为Key值,并将Guid作为Token返回给前端,前端在访问其他接口时需要在Header的Authorization中携带token。关键代码如下:

1、接口Controllers代码

using Common.Commons;
using Domain.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

namespace RedisTest.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    [TypeFilter(typeof(CustomAuthorizeFilter))]
    public class LoginController : ControllerBase
    {
        private readonly IRedisDomainService redisService;

        public LoginController(IRedisDomainService redisService)
        {
            this.redisService = redisService;
        }
        /// 登录
        /// </summary>
        /// <param name="phone"></param>
        /// <param name="code"></param>
        /// <returns></returns>
        [HttpPost]
        [AllowAnonymous]
        public async Task<ResponseContent> Login(string phone, string code)
        {
            return await redisService.Login(phone, code);
        }
    }
}

2、用户登录验证服务代码

using Common.CacheManager;
using Common.Commons;
using Common.Enum;
using Domain.Entities;
using Domain.Repository;
using Microsoft.AspNetCore.Http;
using Common.Const;
using Common.Extensions;
using System.Security.Policy;

namespace Domain.Service
{
    public class RedisDomainService : IRedisDomainService
    {
        private readonly IRedisDomainRepostory redisRepostory;
        private readonly ICacheService cacheService;
        public RedisDomainService(IRedisDomainRepostory redisRepostory, ICacheService cacheService)
        {
            this.redisRepostory = redisRepostory;
            this.cacheService = cacheService;
        }

        /// <summary>
        /// 登录
        /// </summary>
        /// <param name="phone"></param>
        /// <param name="code"></param>
        /// <returns></returns>
        public async Task<ResponseContent> Login(string phone, string code)
        {
            ResponseContent response = new ResponseContent();
            try
            {
                //1.校验手机号
                if (!RegexUtils.IsMobile(phone))
                {
                    //2.如果不符合,返回错误信息
                    return response.Error(ResponseType.ParameterVerificationError);
                }
                //2.校验验证码
                //string cacheCode = _session.GetString("code");
                string cacheCode = cacheService.Get(RedisConstants.LOGIN_CODE_KEY + phone);
                if (cacheCode == null || !cacheCode.Equals(code))
                {
                    //3.不一致,报错
                    return response.Error(ResponseType.PINError);
                }
                //4.一致,根据手机号查询用户
                User user = await redisRepostory.FindOneByMobile(phone);
                //5.判断用户是否存在
                if (user == null)
                {
                    //6.不存在,创建新用户并保存
                    Random random = new Random();
                    user = await redisRepostory.Add(phone, "测试用户" + random.Next(100000, 999999).ToString());
                }
                //7.保证用户信息到session中
                //_session.SetString("userMobile", user.Mobile);
                //7.保证用户信息到Redis中
                //7.1随机生成token,作为登录令牌
                string token = Guid.NewGuid().ToString();
                //7.2将User对象转为Dictionary存储
                Dictionary<string, string> userDic = ObjectExtension.ModelToDic(user);
                //7.3保存数据到Resdis
                string tokenKey = RedisConstants.LOGIN_TOKEN_KEY + token;
                cacheService.AddHash(tokenKey, userDic);
                //7.4设置token有效期
                cacheService.Expire(tokenKey, RedisConstants.LOGIN_TOKEN_TTL);

                //8.返回token
                return response.Ok(new { token = token });
            }
            catch (Exception e) {
                return response.Error(404,e.Message);
            }
        }
    }
}
三、刷新登录有效期

以上实现了登录功能,但是用户名信息存储在Redis中,到达过期时间就会被自动删除。解决此问题的方法在需要登录才能访问的接口中,使用IAuthorizationFilter过滤器,过滤器中根据前台传的Token获取Redis中的用户信息,如果获取成功,则刷新过期时间。

1、IAuthorizationFilter实现代码如下:

using Common.CacheManager;
using Common.Const;
using Common.Dto;
using Common.Enum;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Common.Commons
{
    /// <summary>
    /// 自定义身份验证过滤器,实现登录及权限的验证
    /// </summary>
    public class CustomAuthorizeFilter : Attribute, IAuthorizationFilter
    {
        private readonly ICacheService cacheService;

        public CustomAuthorizeFilter(ICacheService cacheService)
        {
            this.cacheService = cacheService;
        }

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            //当调用的接口是加了[AllowAnonymous]特性的,绕过权限验证的
            if (context.ActionDescriptor.EndpointMetadata.Any(item => item is IAllowAnonymous))
            {
                return;
            }
            //未登录处理
            if (string.IsNullOrEmpty(context.HttpContext.Request.Headers["Authorization"]))
            {
                context.Result = new JsonResult(new ResponseContent().Error(ResponseType.LoginExpiration));
                return;
            }
            //1.获取token
            string token = context.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
            //2.基于Token获取Redis中的用户
            string tokenKey = RedisConstants.LOGIN_TOKEN_KEY + token;
            UserDto user = cacheService.GetHash<UserDto>(tokenKey);
            //3.判断用户是否存在
            if (user == null) {
                //4.不存在,拦截,返回401状态码
                context.Result = new JsonResult(new ResponseContent().Error(ResponseType.LoginError));
                return;
            }
            //5.刷新token有效期
            cacheService.Expire(tokenKey, RedisConstants.LOGIN_TOKEN_TTL);
            //6.放行
            return;
        }
    }
}

2、接口Controllers代码

using Common.Commons;
using Domain.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

namespace RedisTest.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    [TypeFilter(typeof(CustomAuthorizeFilter))]
    public class LoginController : ControllerBase
    {
        private readonly IRedisDomainService redisService;

        public LoginController(IRedisDomainService redisService)
        {
            this.redisService = redisService;
        }
        
        [HttpPost]
        public async Task<ResponseContent> GetUserInfo(string phone)
        {
            ResponseContent response = new ResponseContent();
            return response.Ok(new { Message = "获取用户信息成功" });
        }
        [HttpPost]
        [AllowAnonymous]
        public async Task<ResponseContent> AllowAnonymousTest()
        {
            ResponseContent response = new ResponseContent();
            return response.Ok(new { Message = "访问成功" });
        }
    }
}

以上代码保证了,只要用户不断访问需要登录的接口,Redis中存储的用户信息就不会过期。

作者初学.net core开发,有不对的地方欢迎评论区批评交流,后续将不断更新本教程。

四、附Redis操作工具类代码。
using CSRedis;
using Newtonsoft.Json;
using System.Reflection;

namespace Common.CacheManager
{
    public class RedisCacheService : ICacheService
    {
        public RedisCacheService()
        {
            var csredis = new CSRedisClient("127.0.0.1,Password=123456,SyncTimeout=15000");
            RedisHelper.Initialization(csredis);
        }

        /// <summary>
        /// 验证缓存项是否存在
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public bool Exists(string key)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            return RedisHelper.Exists(key);
        }
        /// <summary>
        /// List头部插入一个元素
        /// </summary>
        /// <param name="key"></param>
        /// <param name="val"></param>
        public void LPush(string key, string val)
        {
            RedisHelper.LPush(key, val);
        }

        /// <summary>
        /// List尾部插入一个元素
        /// </summary>
        /// <param name="key"></param>
        /// <param name="val"></param>
        public void RPush(string key, string val)
        {
            RedisHelper.RPush(key, val);
        }

        /// <summary>
        /// 从List尾部删除一个元素
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T ListDequeue<T>(string key) where T : class
        {
            string value = RedisHelper.RPop(key);
            if (string.IsNullOrEmpty(value))
                return null;
            return JsonConvert.DeserializeObject<T>(value);
        }

        /// <summary>
        /// 从List尾部删除一个元素
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public object ListDequeue(string key)
        {
            string value = RedisHelper.RPop(key);
            if (string.IsNullOrEmpty(value))
                return null;
            return value;
        }

        /// <summary>
        /// 移除list中的数据,keepIndex为保留的位置到最后一个元素如list 元素为1.2.3.....100
        /// 需要移除前3个数,keepindex应该为4
        /// </summary>
        /// <param name="key"></param>
        /// <param name="keepIndex"></param>
        public void ListRemove(string key, int keepIndex)
        {
            RedisHelper.LTrim(key, keepIndex, -1);
        }

        /// <summary>
        /// 添加一个String类型元素
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireSeconds"></param>
        /// <param name="isSliding"></param>
        /// <returns></returns>
        public bool Add(string key, string value, int expireSeconds = -1, bool isSliding = false)
        {
            return RedisHelper.Set(key, value, expireSeconds);
        }

        /// <summary>
        /// 添加一个元素
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireSeconds"></param>
        /// <param name="isSliding"></param>
        /// <returns></returns>
        public bool AddObject(string key, object value, int expireSeconds = -1, bool isSliding = false)
        {
            return RedisHelper.Set(key, value, expireSeconds);
        }
        /// <summary>
        /// 添加一个Hash类型元素
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expireSeconds"></param>
        /// <param name="isSliding"></param>
        /// <returns></returns>
        public void AddHash(string key, Dictionary<string, string> value)
        {
            foreach (var item in value)
            {
                RedisHelper.HSet(key, item.Key, item.Value);
            }
        }

        public T GetHash<T>(string key) where T : class
        {
            //获取所有键值
            RedisScan<(string field, string value)> m_pair = RedisHelper.HScan(key, 0);
            (string field, string value)[] items = m_pair.Items;
            //构造新实例
            T t = Activator.CreateInstance<T>(); 
            try
            {
                //获得新实例类型
                var Types = t.GetType();
                //获得遍历类型的属性字段 
                foreach (PropertyInfo sp in Types.GetProperties()) 
                {
                    //遍历Hash值
                    for (int i = 0; i < items.Length; ++i)
                    {
                        //判断属性名是否相同
                        if (items[i].field == sp.Name)
                        {
                            string propertyType = sp.PropertyType.ToString();
                            //获得s对象属性的值复制给d对象的属性  
                            if (propertyType.Equals("System.Guid"))
                                sp.SetValue(t, Guid.Parse(items[i].value), null);
                            else
                                sp.SetValue(t, items[i].value, null);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return t;
        }

        public bool Expire(string key, int expireSeconds = -1)
        {
            return RedisHelper.Expire(key, expireSeconds);
        }

        /// <summary>
        /// 删除缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public bool Remove(string key)
        {
            RedisHelper.Del(key);
            return true;
        }

        /// <summary>
        /// 批量删除缓存
        /// </summary>
        /// <param name="key">缓存Key集合</param>
        /// <returns></returns>
        public void RemoveAll(IEnumerable<string> keys)
        {
            RedisHelper.Del(keys.ToArray());
        }

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public T Get<T>(string key) where T : class
        {
            return RedisHelper.Get<T>(key);
        }

        /// <summary>
        /// 获取缓存
        /// </summary>
        /// <param name="key">缓存Key</param>
        /// <returns></returns>
        public string Get(string key)
        {
            return RedisHelper.Get(key);
        }

        public void Dispose()
        {
        }
    }
}

 

本文附件(20240105155142RedisTest项目代码下载.rar)价格:0
评论列表
发表评论
+ 关注