第九节:JWT简介和以JS+WebApi为例基于JWT的安全校验

一. 简介

1. 背景

  传统的基于Session的校验存在诸多问题,比如:Session过期、服务器开销过大、不能分布式部署、不适合前后端分离的项目。 传统的基于Token的校验需要存储Key-Value信息,存在Session或数据库中都有弊端,如果按照一定规律采用对称加密算法生成token,虽然能解决上面问题,但是一旦对称加密算法泄露,很容被反编译;所以在此基础上继续升级,利用userId生成Token,只要保存好秘钥即可,从而引出JWT。

2. 什么是JWT

  Json web token 简称:JWT, 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

下面就是一段JWT字符串(后面详细分析)

1 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiJ9.Qjw1epD5P6p4Yy2yju3-fkq28PddznqRj3ESfALQy_U

3. JWT的优点

(1). JWT是无状态的,不需要服务器端保存会话信息,减轻服务器端的读取压力(存储在客户端上),同时易于扩展、易于分布式部署。

(2). JWT可以跨语言支持。

(3). 便于传输,jwt的构成很简单,字节占用空间少,所以是非常便于传输的。

(4). 自身构成有payload部分,可以存储一下业务逻辑相关的非敏感信息。

  特别声明:JWT最大的优势是无状态的,相对传统的Session验证能减轻服务器端的存储压力,安全性更高,但也不是绝对的,比如针对同一个接口,JWT字符串被截取后,且在有效期内,在不篡改JWT字符串的情况下,也是可以模拟请求进行访问的。(随着下面的内容深入体会JWT的核心)

二. JWT深度剖析

1. JWT的长相

  下面的一段字符串就是JWT加密后的显示格式,我们仔细看,中间通过两个 “点” 将这段字符串分割成三部分了。

eyJ0eXAxIjoiMTIzNCIsImFsZzIiOiJhZG1pbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTU1MjI4Njc0Ni44Nzc0MDE4fQ.pEgdmFAy73walFonEm2zbxg46Oth3dlT02HR9iVzXa8

  上面一段很长的字符串到底是怎么来的呢?就需要了解JWT的构成原理。

2. JWT的构成

 JWT由三部分组成,如下图,分别是:Header头部、Payload负载、Signature签名。

  

(1). 头部(Header)

  通常包括两部分,类型(如 “typ”:“JWT”)和加密算法(如“alg”:"HS256"),当然你也可以添加其它自定义的一些参数,然后对这个对象机型base64编码,生成一段字符串,

如“eyJ0eXAxIjoiMTIzNCIsImFsZzIiOiJhZG1pbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0”,我们可以对其进行反编码一下,看一下其庐山真面目。

 注:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

(2). 负载(Payload)

  通常用来存放一些业务需要但不敏感的信息,比如:用户编号(userId)、用户账号(userAccount)、权限等等,该部分也有一些默认的声明,如下图,很多不常用。

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

其中最常用的就是exp过期时间,要和1970年1月1日那个点进行比对,用法如下,下面表示生成jwt字符串后20分钟过期。
 

最后对该部分组装成的对象进行base64编码,如:“eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTU1MjI4Njc0Ni44Nzc0MDE4fQ”,我们可以对其进行反编码看一下庐山真面目,如下图:

 

注:该部分也是可以解码的,所以不要存储敏感信息。

(3). 签名(Signature)

  这个部分需要base64加密后的headerbase64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

伪代码如下:

1 var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
2 var signature = HMACSHA256(encodedString, 'sercret密钥')

  说明:密钥存在服务器端,不要泄露,在不知道密钥的情况下,是不能进行解密的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

  特别说明:即使payload中的信息被篡改,服务器端通过signature就可以判断出来是非法请求,即校验不能通过。

3. 代码尝鲜

  需要通过Nuget装JWT包,新版本的jwt建议.Net 版本4.6起。

复制代码

 1         [HttpGet]2         public string JiaM()3         {4             //设置过期时间(可以不设置,下面表示签名后 20分钟过期)5             double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;6             var payload = new Dictionary<string, object>7             {8                 { "UserId", 123 },9                 { "UserName", "admin" },
10                 {"exp",exp }   //该参数也可以不写
11             };
12             var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";//不要泄露 
13             IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
14 
15             //注意这个是额外的参数,默认参数是 typ 和alg
16             var headers = new Dictionary<string, object>
17             {
18                 { "typ1", "1234" },
19                 { "alg2", "admin" }
20             };
21 
22             IJsonSerializer serializer = new JsonNetSerializer();
23             IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
24             IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
25             var token = encoder.Encode(headers, payload, secret);
26             return token;
27         }
28 
29        [HttpGet]
30         public string JieM(string token)
31         {
32             var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
33             try
34             {
35                 IJsonSerializer serializer = new JsonNetSerializer();
36                 IDateTimeProvider provider = new UtcDateTimeProvider();
37                 IJwtValidator validator = new JwtValidator(serializer, provider);
38                 IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
39                 IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
40                 var json = decoder.Decode(token, secret, true);
41                 return json;
42             }    
43             catch (TokenExpiredException)
44             {
45                 //过期了自动进入这里
46                 return "Token has expired";
47             }
48             catch (SignatureVerificationException)
49             {
50                 //校验未通过自动进入这里
51                 return "Token has invalid signature";
52             }
53             catch (Exception)
54             {
55                 //其它错误,自动进入到这里
56                 return "other error";
57             }

复制代码

上述代码方便通过PostMan进行快速测试,注意解密的方法中的三个catch,token过期,会自动进入到TokenExpiredException异常中,token验证不通过,会自动进入SignatureVerificationException中。

 

三. JWT的使用流程

   整体流程,大致如下图:

 

1. 客户端(前端或App端)通过一个Http请求把用户名和密码传到登录接口,建议采用Https的模式,避免信息被嗅探。

2. 服务器端校验登录的接口验证用户名和密码通过后,把一些业务逻辑需要的信息如:userId、userAccount放到Payload中,进而生成一个xxx.yyy.zzz形式的JWT字符串返回给客户端。

3. 客户端获取到JWT的字符串,可以存放到LocalStorage中,注意退出的登录的时候删除该值。

4. 登录成功,每次请求其它接口的时候都在表头带着该jwt字符串,建议放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)  或者自己命名比如:“auth”,进行该字符串的传递。

5. 服务器端要写一个过滤器,在该过滤器中进行校验jwt的有效性(签名是否正确、是否过期),验证通过进行接口的业务逻辑,验证不通过,返回给客户端。

这里要解决两个问题?

(1). 在WebApi的过滤器中,如果校验通过了,如何将解密后的值传递到action中。(解密两次就有点坑了)

(2). 在WebApi的过滤器中,如果校验不通过,如何返回给客户端,然后客户端针对这种情况,又该如何接受呢。

(实战中揭晓)。

 

四. 项目实战

一. 整体目标:

  通过一个登陆接口和一个获取信息的接口模拟JWT的整套验证逻辑。

二. 详细步骤

1. 封装JWT加密和解密的方法。

 需要通过Nuget安装JWT的程序集,JWT的最新版本建议使用.Net 4.6 起。

 JWTHelp

2. 模拟登陆接口

 在登录接口中,模拟数据库校验,即账号和密码为admin和12345,即校验通过,然后把账号和userId(实际应该到数据库中查),这里也可以设置一下过期时间,比如20分钟,一同存放到PayLoad中,然后生成JWT字符串,返回给客户端。

复制代码

        /// <summary>/// 模拟登陆/// </summary>/// <param name="userAccount"></param>/// <param name="pwd"></param>/// <returns></returns>[HttpGet]public string Login1(string userAccount, string pwd){try{//这里模拟数据操作,只要是admin和123456就验证通过if (userAccount == "admin" && pwd == "123456"){//1. 进行业务处理(这里模拟获取userId)string userId = "0806";//过期时间(可以不设置,下面表示签名后 20分钟过期)double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;//进行组装var payload = new Dictionary<string, object>{{"userId", userId },{"userAccount", userAccount },{"exp",exp }};//2. 进行JWT签名var token = JWTHelp.JWTJiaM(payload);var result = new { result = "ok", token = token };return JsonConvert.SerializeObject(result);}else{var result = new { result = "error", token = "" };return JsonConvert.SerializeObject(result);}}catch (Exception){var result = new { result = "error", token = "" };return JsonConvert.SerializeObject(result);}}

复制代码

3. 客户端调用登录接口

 这里只是单纯为了测试,使用的get请求,实际项目中建议post请求,且配置Https,请求成功后,把jwt字符串存放到localStorage中。

复制代码

 1            //1.登录2             $('#j_jwtLogin').on('click', function () {3                 $.get("/api/Seventh/Login1", { userAccount: "admin", pwd: "123456" }, function (data) {4                     var jsonData = JSON.parse(data);5                     if (jsonData.result == "ok") {6                         console.log(jsonData.token);7                         //存放到本地缓存中8                         window.localStorage.setItem("token", jsonData.token);9                         alert("登录成功,ticket=" + jsonData.token);
10                     } else {
11                         alert("登录失败");
12                     }
13                 });
14             });

复制代码

运行结果:

4. 服务器端过滤器

  代码中分享了两种获取header中信息的方式,获取到“auth”后,进行校验,校验不通过的话,通过状态码401返回给客户端,校验通过的话,则使用 actionContext.RequestContext.RouteData.Values.Add("auth", result); 进行解密值的存储,方便后续action的直接获取。

复制代码

 1  /// <summary>2     /// 验证JWT算法的过滤器3     /// </summary>4     public class JWTCheck : AuthorizeAttribute5     {6         public override void OnAuthorization(HttpActionContext actionContext)7         {8             //获取表头Header中值的几种方式9             //方式一:
10             //{
11             //    var authHeader2 = from t in actionContext.Request.Headers
12             //                      where t.Key == "auth"
13             //                      select t.Value.FirstOrDefault();
14             //    var token2 = authHeader2.FirstOrDefault();
15             //}
16 
17             //方式二:
18             IEnumerable<string> auths;
19             if (!actionContext.Request.Headers.TryGetValues("auth", out auths))
20             {
21                 //HttpContext.Current.Response.Write("报文头中的auth为空");
22                 //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获),注意:这句话并不能阶段该过滤器,还会继续往下走,要借助if-else        
23                 actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("报文头中的auth为空"));
24             }
25             else
26             {
27                 var token = auths.FirstOrDefault();
28                 if (token != null)
29                 {
30                     if (!string.IsNullOrEmpty(token))
31                     {
32                         var result = JWTHelp.JWTJieM(token);
33                         if (result == "expired")
34                         {
35                             //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)        
36                             actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("expired"));
37                         }
38                         else if (result == "invalid")
39                         {
40                             //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)   
41                             actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("invalid"));
42                         }
43                         else if (result == "error")
44                         {
45                             //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)   
46                             actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("error"));
47                         }
48                         else
49                         {
50                             //表示校验通过,用于向控制器中传值
51                             actionContext.RequestContext.RouteData.Values.Add("auth", result);
52                         }
53                     }
54                 }
55                 else
56                 {
57                     //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)   
58                     actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("token 空"));
59                 }
60             }
61 
62         }
63     }

复制代码

5.服务器端获取信息的的方法

   将上说过滤器以特性的形式作用在该方法中,然后通过 RequestContext.RouteData.Values["auth"] 获取到解密后的值,进而进行其它业务处理。

复制代码

 1         /// <summary>2         /// 加密后的获取信息3         /// </summary>4         /// <returns></returns>5         [JWTCheck]6         [HttpGet]7         public string GetInfor()8         {9             var userData = JsonConvert.DeserializeObject<userData>(RequestContext.RouteData.Values["auth"].ToString()); ;
10             if (userData == null)
11             {
12                 var result = new { Message = "error", data = "" };
13                 return JsonConvert.SerializeObject(result);
14             }
15             else
16             {
17                 var data = new { userId = userData.userId, userAccount = userData.userAccount };
18                 var result = new { Message = "ok", data =data };
19                 return JsonConvert.SerializeObject(result);
20             }
21         }

复制代码

6. 客户端调用获取信息的方法 

  前端获取到localStorage中token值,采用自定义header的方式以“auth”进行传递调用服务器端的方法,由于服务器的验证token不正确的时候,是以状态码的形式返回,所以这里要采用error方法,通过xhr.status==401进行判断,凡是进入到这个401中,均是token验证没有通过,具体是什么原因,可以通过xhr.responseText获取详细的值进行判断。

复制代码

 1           //2.获取信息2             $('#j_jwtGetInfor').on('click', function () {3                 //从本地缓存中读取token值4                 var token = window.localStorage.getItem("token");5                 $.ajax({6                     url: "/api/Seventh/GetInfor",7                     type: "Get",8                     data: {},9                     datatype: "json",
10                     //设置header的方式1
11                     headers: { "auth": token},
12                     //设置header的方式2
13                     //beforeSend: function (xhr) {
14                     //    xhr.setRequestHeader("auth", token)
15                     //},           
16                     success: function (data) {
17                         console.log(data);
18                         var jsonData = JSON.parse(data);
19                         if (jsonData.Message == "ok") {
20                             var myData = jsonData.data;
21                             console.log("获取成功");
22                             console.log(myData.userId);
23                             console.log(myData.userAccount);
24                         } else {
25                             console.log("获取失败");
26                         }              
27                     },
28                     //当安全校验未通过的时候进入这里
29                     error: function (xhr) {
30                         if (xhr.status == 401) {
31                             console.log(xhr.responseText);
32                             var jsonData = JSON.parse(xhr.responseText);
33                             console.log("授权失败,原因为:" + jsonData.Message);
34                         }
35                     }
36                 });
37             });

复制代码

运行结果:

 

 

其他的如token过期只需要改一下电脑时间即可以测试,token不正确改一下获取到的jwt字符串可以测试,这里不再进行 了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438337.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

赢在CSDN —— 我们一起向前

赢在CSDN&#xff0c;一起向前&#xff01; ⭐初遇CSDN ⭐我也成为了一名创作博主 ⭐如何在CSDN赚到一桶金 ⭐CSDN带给自己的成长 ⭐对CSDN的期待 ⭐个人创作的规划 ⭐初遇CSDN 不知不觉来到CSDN已经有1379天的时间啦&#xff0c;时间过得好快呀&#xff01;记得刚踏进CS…

volatile关键字的作用-适用场景

volatile在Java并发编程中常用于保持内存可见性和防止指令重排序。内存可见性&#xff08;Memory Visibility&#xff09;&#xff1a;所有线程都能看到共享内存的最新状态&#xff1b;防止指令重排&#xff1a;在基于偏序关系的Happens-Before内存模型中&#xff0c;指令重排技…

MySQL你掌握了多少?这些SQL题你能作对几道?

MySQL是工作中常用数据库&#xff0c;必须掌握&#xff0c;但小伙伴们又掌握了多少呢&#xff0c;今天一起来测试一下吧~ 力扣SQL ⭐组合两个表 ⭐第二高的薪水 ⭐超过经理收入的员工 ⭐查找重复的电子邮箱 ⭐从不订购的客户 ⭐大的国家 ⭐删除重复的电子邮箱 ⭐有趣的…

mybatis配置文件加注释报错怎么办?改一笔就能帮你解决

今天配置mybatis写了一个小demo&#xff0c;本以为顺顺利利的就完成了&#xff0c;没想到就报了如下错误。直接emo了。 java.lang.ExceptionInInitializerErrorat com.lm.learn.dao.UserTest.getUsers(UserTest.java:14)at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ…

第十二节:深究内核模式锁的使用场景(自动事件锁、手动事件锁、信号量、互斥锁、读写锁、动态锁)

一. 整体介绍 温馨提示&#xff1a;内核模式锁&#xff0c;在不到万不得已的情况下&#xff0c;不要使用它&#xff0c;因为代价太大了&#xff0c;有很多种替代方案。 内核模式锁包括&#xff1a; ①&#xff1a;事件锁 ②&#xff1a;信号量 ③&#xff1a;互斥锁 ④&#xf…

玩转Mybatis —— 一个小demo,带你快速入门Mybatis

目录 &#x1f91e;Mybatis官网介绍 &#x1f91e;Mybatis安装 &#x1f91e;Mybatis核心配置文件 &#x1f91e;构建 SqlSessionFactory &#x1f91e;获取 SqlSession &#x1f91e;通过 XML 定义已映射的 SQL 语句 &#x1f91e;作用域&#xff08;Scope&#xff09;…

MySQL掌握的怎么样?听说这几道MySQL面试题没有几个人能答得出来

MySQL可谓是程序员必备技能&#xff0c;怎么检测自己掌握了多少呢&#xff0c;一起来测试一下吧&#xff01;一共12个关卡&#xff0c;看看你能闯到第几关&#xff1f; 目录 什么是左外链接&#xff0c;什么是右外链接&#xff1f; 分页关键字是什么&#xff1f; Select 语句…

IIS/ASP.NET 管道

ASP.NET MVC 是建立在 ASP.NET 平台上基于 MVC 模式的 Web 应用框架&#xff0c;深刻理解 ASP.NET MVC 的前提是对 ASP.NET 管道式设计具有深刻的认识。由于 ASP.NET Web 应用大都寄宿于 IIS 上&#xff0c;将两者结合起来了解在 IIS 和 ASP.NET 管道中是如何流动的。 IIS5.x与…

【JAVA知识点每日一练】 —— 运算符的那些事

都说基础不牢地动山摇&#xff0c;还真是那么回事&#xff01;别小看这运算符&#xff0c;每个语法中运算符扮演者举足轻重的角色&#xff0c;稍微没用对&#xff0c;那可就犯了大错误&#xff01; 接下来跟随小梦&#xff0c;迈着轻松且愉快的步伐一起看看运算符的那些事吧~ 运…

ASP.NET Core管道深度剖析[共4篇]

在《管道是如何处理HTTP请求的&#xff1f;》中&#xff0c;我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍&#xff0c;接下来我们需要了解的是这样一个管道是如何被构建起来的。这样一个管道由一个服务器和一个HttpApplication构成&#xff0c…

SQL为什么动不动就百行以K记?

发明SQL的初衷之一显然是为了降低人们实施数据查询计算的难度。SQL中用了不少类英语的词汇和语法&#xff0c;这是希望非技术人员也能掌握。确实&#xff0c;简单的SQL可以当作英语阅读&#xff0c;即使没有程序设计经验的人也能运用。 然而&#xff0c;面对稍稍复杂的查询计算…

深入理解happens-before和as-if-serial语义

本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。下面可以和小编来一起学习下 概述 本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。 指令序列的重排序 我们在编写代码…

开源SPL重新定义OLAP Server

OLAP&#xff08;Online Analytical Processing&#xff09;是指在线联机分析&#xff0c;基于数据查询计算并实时获得返回结果。日常业务中的报表、数据查询、多维分析等一切需要即时返回结果的数据查询任务都属于OLAP的范畴。对应的&#xff0c;行业内也有相应产品来满足这类…

平层、错层、跃层、复式、loft的区别是什么?

平层正如字面上的理解&#xff0c;是所有功能厅都在同一个水平面上。平时我们所见的户型&#xff0c;都是平层。错层室内各功能用房在不同的平面上&#xff0c;用2-4个台阶进行隔断。跃层是两层的住宅&#xff0c;在室内会设计一条楼梯连接上下两层&#xff0c;功能区会分开。复…

C#集合类型总结和性能分析

C#集合类型概述 集合是.NET FCL(Framework Class Library)中很重要的一部分。所有的集合类都继承自IEnumerable。集合类总体可分为一下几类&#xff1a;关联/非关联型集合&#xff0c;顺序/随机访问集合&#xff0c;顺序/无序集合&#xff0c;泛型/非泛型集合&#xff0c;线程…

Spring AOP(通知、连接点、切点、切面)

一、AOP术语 通知&#xff08;Advice&#xff09;   切面的工作被称为通知。通知定义了切面是什么以及何时使用。除了描述切面要完成的工作&#xff0c;通知还解决了何时执行这个工作的问题。 5种通知类型&#xff1a;前置通知&#xff08;Before&#xff09;&#xff1a;在…

C#中几种常用的集合的用法

集合:将一推数据类型相同的数据放入到一个容器内&#xff0c;该容器就是数组&#xff1a;内存中开辟的一连串空间。 非泛型集合 ArrayList集合&#xff1a; ArrayList是基于数组实现的&#xff0c;是一个动态数组&#xff0c;其容量能自动 增长 ArrayList的命名空间System.…

C#使用Redis的基本操作

一&#xff0c;引入dll 1.ServiceStack.Common.dll 2.ServiceStack.Interfaces.dll 3.ServiceStack.Redis.dll 4.ServiceStack.Text.dll 二&#xff0c;修改配置文件 在你的配置文件中加入如下的代码&#xff1a; <appSettings><add key"RedisPath" value…

Navicat将mysql表结构导成oracle表结构

1&#xff0c;选中对应的表右键逆向表到模型 2.点击右上角文件转换模型为 3.模型选择物理&#xff0c;数据库oracle&#xff0c;选择对应的版本 4.新弹出的模型点击右上角文件&#xff0c;导出sql 5.选择路径导出sql

程序员们的三高:高并发、高性能、高可用

你们知道淘宝&#xff0c;京东这些购物商场吗&#xff1f;他们到了双11&#xff0c;双12为什么能支持全国14亿人口同时购物下单呢&#xff0c;因为他们的程序做到了高并发、高性能、高可用。那么你对程序员的三高了解多少呢&#xff1f; 高并发 一. 高并发 高并发是现在互联…