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

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

一、获取短信验证码

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

1、接口Controllers代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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、短信验证码发送服务代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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、用户登录验证服务代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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操作工具类代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
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
评论列表
发表评论
+ 关注