基于.Net Framework 4.0 Web API开发(4):ASP.NET Web APIs 基于令牌TOKEN验证的实现

概述: 

  ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作。但是在使用API的时候总会遇到跨域请求的问题, 特别各种APP万花齐放的今天,对API使用者身份角色验证是不能避免的(完全开发的API不需要对使用者身份角色进行管控,可以绕过),这篇文章就来谈谈基于令牌TOKEN身份验证的实现。

问题:

   对于Web API的选择性的开放,使用者无论使用AJAX,还是HttpClient对接,总要对使用者的身份角色进行验证,然而使用API总有跨域使用情况的存在,这样就导致所有基于cookie验证方式都不再适用于API的验证。

原因:

  比如,基于form表单验证的基础是登录验证成功后,用户的信息存在缓存或数据库或cookie,无论哪种方式存储用户信息,都不能绕过对cookie的使用,所以form表单验证方法对于禁用cookie的浏览器都不能正常使用,结论就是不能使用cookie 的环境就不能使用基本的form表单验证方式。因此WEB API 由于跨域的使用,导致cookie不能正常工作,所以不能再使用基于表单验证的方式来实现。

基于令牌TOKEN验证方法的实现:

方法一:

     1. 实现对缓存TOKEN的管理,以防IIS服务器的宕机,可以对TOKEN进行持久化存储处理,每次IIS重启重新初始化已经登录成功TOKEN缓存。实现如下:

复制代码
  1 public class UserTokenManager2     {3         private static readonly IUserTokenRepository _tokenRep;4         private const string TOKENNAME = "PASSPORT.TOKEN";5 6         static UserTokenManager()7         {8             _tokenRep = ContainerManager.Resolve<IUserTokenRepository>();9         }10         /// <summary>11         /// 初始化缓存12         /// </summary>13         private static List<UserToken> InitCache()14         {15             if (HttpRuntime.Cache[TOKENNAME] == null)16             {17                 var tokens = _tokenRep.GetAll();18                 // cache 的过期时间, 令牌过期时间 *219                 HttpRuntime.Cache.Insert(TOKENNAME, tokens, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromDays(7 * 2));20             }21             var ts = (List<UserToken>)HttpRuntime.Cache[TOKENNAME];22             return ts;23         }24 25 26         public static int GetUId(string token)27         {28             var tokens = InitCache();29             var result = 0;30             if (tokens.Count > 0)31             {32                 var id = tokens.Where(c => c.Token == token).Select(c => c.UId).FirstOrDefault();33                 if (id != null)34                     result = id.Value;35             }36             return result;37         }38 39 40         public static string GetPermission(string token)41         {42             var tokens = InitCache();43             if (tokens.Count == 0)44                 return "NoAuthorize";45             else46                 return tokens.Where(c => c.Token == token).Select(c => c.Permission).FirstOrDefault();47         }48 49         public static string GetUserType(string token)50         {51             var tokens = InitCache();52             if (tokens.Count == 0)53                 return "";54             else55                 return tokens.Where(c => c.Token == token).Select(c => c.UserType).FirstOrDefault();56         }57 58         /// <summary>59         /// 判断令牌是否存在60         /// </summary>61         /// <param name="token"></param>62         /// <returns></returns>63         public static bool IsExistToken(string token)64         {65             var tokens = InitCache();66             if (tokens.Count == 0) return false;67             else68             {69                 var t = tokens.Where(c => c.Token == token).FirstOrDefault();70                 if (t == null)71                     return false;72                 else if (t.Timeout < DateTime.Now)73                 {74                     RemoveToken(t);75                     return false;76                 }77                 else78                 {79                     // 小于8小时 更新过期时间80                     if ((t.Timeout - DateTime.Now).TotalMinutes < 1 * 60 - 1)81                     {82                         t.Timeout = DateTime.Now.AddHours(8);83                         UpdateToken(t);84                     }85                     return true;86                 }87 88             }89         }90 91         /// <summary>92         /// 添加令牌, 没有则添加,有则更新93         /// </summary>94         /// <param name="token"></param>95         public static void AddToken(UserToken token)96         {97             var tokens = InitCache();98             // 不存在  怎增加99             if (!IsExistToken(token.Token))
100             {
101                 token.ID = 0;
102                 tokens.Add(token);
103                 // 插入数据库
104                 _tokenRep.Add(token);
105             }
106             else  // 有则更新
107             {
108                 UpdateToken(token);
109             }
110         }
111 
112         public static bool UpdateToken(UserToken token)
113         {
114             var tokens = InitCache();
115             if (tokens.Count == 0) return false;
116             else
117             {
118                 var t = tokens.Where(c => c.Token == token.Token).FirstOrDefault();
119                 if (t == null)
120                     return false;
121                 t.Timeout = token.Timeout;
122                 // 更新数据库
123                 var tt = _tokenRep.FindByToken(token.Token);
124                 if (tt != null)
125                 {
126                     tt.UserType = token.UserType;
127                     tt.UId = token.UId;
128                     tt.Permission = token.Permission;
129                     tt.Timeout = token.Timeout;
130                     _tokenRep.Update(tt);
131                 }
132                 return true;
133             }
134         }
135         /// <summary>
136         /// 移除指定令牌
137         /// </summary>
138         /// <param name="token"></param>
139         /// <returns></returns>
140         public static void RemoveToken(UserToken token)
141         {
142             var tokens = InitCache();
143             if (tokens.Count == 0) return;
144             tokens.Remove(token);
145             _tokenRep.Remove(token);
146         }
147 
148         public static void RemoveToken(string token)
149         {
150             var tokens = InitCache();
151             if (tokens.Count == 0) return;
152 
153             var ts = tokens.Where(c => c.Token == token).ToList();
154             foreach (var t in ts)
155             {
156                 tokens.Remove(t);
157                 var tt = _tokenRep.FindByToken(t.Token);
158                 if (tt != null)
159                     _tokenRep.Remove(tt);
160             }
161         }
162 
163 
164         public static void RemoveToken(int uid)
165         {
166             var tokens = InitCache();
167             if (tokens.Count == 0) return;
168 
169             var ts = tokens.Where(c => c.UId == uid).ToList();
170             foreach (var t in ts)
171             {
172                 tokens.Remove(t);
173                 var tt = _tokenRep.FindByToken(t.Token);
174                 if (tt != null)
175                     _tokenRep.Remove(tt);
176             }
177         }
178     }
复制代码

     2. 新建ApiAuthorizeAttribute类,继承AuthorizeAttribute,重写方法IsAuthorized,这样基于TOKEN验证方式就完成了。实现如下:

复制代码
 1   public class ApiAuthorizeAttribute : AuthorizeAttribute2     {3         protected override bool IsAuthorized(HttpActionContext actionContext)4         {5             // 验证token6             //var token = actionContext.Request.Headers.Authorization;7             var ts = actionContext.Request.Headers.Where(c => c.Key.ToLower() == "token").FirstOrDefault().Value;8             if (ts != null && ts.Count() > 0)9             {
10                 var token = ts.First<string>();
11                 // 验证token
12                 if (!UserTokenManager.IsExistToken(token))
13                 {
14                     return false;
15                 }
16                 return true;
17             }
18 
19             if (actionContext.Request.Method == HttpMethod.Options)
20                 return true;
21             return false;
22         }
23     }
复制代码

     3. 登录实现

复制代码
  1     /// <summary>2     /// 账户3     /// </summary>4     public class AccountController : ApiController5     {6         /// <summary>7         /// 登录8         /// </summary>9         /// <param name="user">登录人员信息: 账号,密码 ,是否记住密码</param>10         /// <returns></returns>11         [HttpPost]12         [AllowAnonymous]13         public ResultData Login([FromBody]LoginUser user)14         {15             string mobile = user.Mobile;16             string password = user.Password;17             bool IsRememberMe = user.IsRememberMe;18 19             if (string.IsNullOrEmpty(mobile) || string.IsNullOrEmpty(password))20                 return new ResultData(((int)LoginResultEnum.UserNameOrPasswordError), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameOrPasswordError));21 22             User u=null;23             IMembershipService membershipSvc = ContainerManager.Container.Resolve<IMembershipService>();24             LoginResultEnum loginResult = membershipSvc.Login(mobile, password, out u);25             if (loginResult == LoginResultEnum.Success)26             {27                 //SetAuthenticationTicket(u, IsRememberMe);28 29                 // token   处理30                 UserTokenManager.RemoveToken(u.ID);31                 // 生成新Token32                 var token = Utility.MD5Encrypt(string.Format("{0}{1}", Guid.NewGuid().ToString("D"), DateTime.Now.Ticks));33                 // token过期时间34                 int timeout = 8;35                 if (!int.TryParse(ConfigurationManager.AppSettings["TokenTimeout"], out timeout))36                     timeout = 8;37                 // 创建新token38                 var ut = new UserToken()39                 {40                     Token = token,41                     Timeout = DateTime.Now.AddHours(timeout),42                     UId = u.ID,43                     UserType = (u.IsSaler.HasValue && u.IsSaler.Value) ? "Saler" : "Vip"44                 };45 46                 UserTokenManager.AddToken(ut);47 48 49                 // 登录log50                 var logRep = ContainerManager.Container.Resolve<ISysLogRepository>();51                 var log = new Log()52                 {53                     Action = "Login",54                     Detail = "会员登录:" + u.Mobile + "|" + u.Name,55                     CreateDate = DateTime.Now,56                     CreatorLoginName = u.Mobile,57                     IpAddress = GetClientIp(this.Request)58                 };59 60                 logRep.Add(log);61 62                 var data = new63                 {64                     id = u.ID,65                     issaler = u.IsSaler.HasValue ? u.IsSaler.Value : false,66                     mobile = u.Mobile,67                     token = token68                 };69                 var result = new ResultData(data);70                 result.desc = "登录成功";71                 return result;72             }73 74             if (loginResult == LoginResultEnum.UserNameUnExists)75             {76                 return new ResultData(((int)LoginResultEnum.UserNameUnExists), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameUnExists));77             }78             if (loginResult == LoginResultEnum.VerifyCodeError)79             {80                 return new ResultData(((int)LoginResultEnum.VerifyCodeError), EnumExtension.GetEnumDescription(LoginResultEnum.VerifyCodeError));81             }82             if (loginResult == LoginResultEnum.UserNameOrPasswordError)83             {84                 return new ResultData(((int)LoginResultEnum.UserNameOrPasswordError), EnumExtension.GetEnumDescription(LoginResultEnum.UserNameOrPasswordError));85             }86             return new ResultData(ResultType.UnknowError, "登录失败,原因未知");87         }88         /// <summary>89         /// 退出当前账号90         /// </summary>91         /// <returns></returns>92         [HttpPost]93         public ResultData SignOut()94         {95             // 登录log96             var logRep = ContainerManager.Resolve<ISysLogRepository>();97             var log = new Log()98             {99                 Action = "SignOut",
100                 Detail = "会员退出:" + RISContext.Current.CurrentUserInfo.UserName,
101                 CreateDate = DateTime.Now,
102                 CreatorLoginName = RISContext.Current.CurrentUserInfo.UserName,
103                 IpAddress = GetClientIp(this.Request)
104             };
105             logRep.Add(log);
106             //System.Web.Security.FormsAuthentication.SignOut();
107             UserTokenManager.RemoveToken(this.Token);
108             return new ResultData(ResultType.Success, "退出成功");
109         }
110     }
复制代码

    4. 测试API

         这样就可以配合.NET原有的 AllowAnonymousAttribute 属性使用, 使用方法如下:
         不需要验证身份的 类或者Action 添加  [AllowAnonymous]属性,否则添加[ApiAuthorize]

复制代码
 1     /// <summary>2     /// 测试3     /// </summary>4     [ApiAuthorize]5     public class TestController : BaseApiController6     {7         /// <summary>8         /// 测试权限19         /// </summary>
10         [HttpGet]
11         public string TestAuthorize1()
12         {
13             return "TestAuthorize1";
14         }
15         /// <summary>
16         /// 测试权限2
17         /// </summary>
18         [AllowAnonymous]
19         [HttpGet]
20         public string TestAuthorize2()
21         {
22             return "TestAuthorize2";
23         }
24     }
复制代码

 

测试一:

复制代码
 1 //TestAuthorize2 function TestAuthorize1() {3     $.ajax({4         type: "get",5         url: host + "/mobileapi/test/TestAuthorize1",6         dataType: "text",7         data: {},8         beforeSend: function (request) {9             request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
10         },
11         success: function (data) {
12             alert(data);
13         },
14         error: function (x, y, z) {
15             alert("报错无语");
16         }
17     });
18 }
复制代码

     结果如下:

 

测试二:

复制代码
 1 //TestAuthorize2 function TestAuthorize2() {3     $.ajax({4         type: "get",5         url: host + "/mobileapi/test/TestAuthorize2",6         dataType: "text",7         data: {},8         beforeSend: function (request) {9             request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
10         },
11         success: function (data) {
12             alert(data);
13         },
14         error: function (x, y, z) {
15             alert("报错无语");
16         }
17     });
18 }
复制代码

    结果如下:

 

测试三:

复制代码
 1 //TestAuthorize2 function TestAuthorize1() {3     $.ajax({4         type: "get",5         url: host + "/mobileapi/test/TestAuthorize1",6         dataType: "text",7         data: {},8         beforeSend: function (request) {9             //request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
10         },
11         success: function (data) {
12             alert(data);
13         },
14         error: function (x, y, z) {
15             alert("报错无语");
16         }
17     });
18 }
复制代码

     结果如下:

 

测试四:

复制代码
 1 //TestAuthorize2 function TestAuthorize2() {3     $.ajax({4         type: "get",5         url: host + "/mobileapi/test/TestAuthorize2",6         dataType: "text",7         data: {},8         beforeSend: function (request) {9             //request.setRequestHeader("token", $("#token").val());  // 请求发起前在头部附加token
10         },
11         success: function (data) {
12             alert(data);
13         },
14         error: function (x, y, z) {
15             alert("报错无语");
16         }
17     });
18 }
复制代码

    结果如下:


方法二:

   此方法缺点就是每次请求都需要附带token请求参数,这对于有强迫症的程序猿来说是一种折磨,不细说,实现代码如下,有需要的自己研究研究:

复制代码
 1     /// <summary>2     /// 用户令牌验证3     /// </summary>4     public class TokenAuthorizeAttribute : ActionFilterAttribute5     {6         private const string UserToken = "token";7         public override void OnActionExecuting(HttpActionContext actionContext)8         {9             // 匿名访问验证
10             var anonymousAction = actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>();
11             if (!anonymousAction.Any())
12             {
13                 // 验证token
14                 var token = TokenVerification(actionContext);
15             }
16             base.OnActionExecuting(actionContext);
17         }
18 
19         /// <summary>
20         /// 身份令牌验证
21         /// </summary>
22         /// <param name="actionContext"></param>
23         protected virtual string TokenVerification(HttpActionContext actionContext)
24         {
25             string msg = "";
26             // 获取token
27             var token = GetToken(actionContext, out msg);
28             if (!string.IsNullOrEmpty(msg))
29                 actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.OK, new NoAuthData() { code = "401", msg = msg });
30             // 判断token是否有效
31             if (!UserTokenManager.IsExistToken(token))
32             {
33                 actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.OK, new NoAuthData() { code = "401", msg = "Token已失效,请重新登录!" });
34                 //actionContext.Response = actionContext.Request.CreateResponse<NoAuthData>(System.Net.HttpStatusCode.Unauthorized, new NoAuthData() { code = "401", msg = "Token已失效,请重新登录!" });
35                 // actionContext.Response = actionContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, "Token已失效,请重新登录!");
36             }
37 
38             return token;
39         }
40 
41         private string GetToken(HttpActionContext actionContext, out string msg)
42         {
43             Dictionary<string, object> actionArguments = actionContext.ActionArguments;
44             HttpMethod type = actionContext.Request.Method;
45             msg = "";
46             var token = "";
47             if (type == HttpMethod.Post)
48             {
49                 if (actionArguments.ContainsKey(UserToken))
50                 {
51                     if (actionArguments[UserToken] != null)
52                         token = actionArguments[UserToken].ToString();
53                 }
54                 else
55                 {
56                     foreach (var value in actionArguments.Values)
57                     {
58                         if (value != null && value.GetType().GetProperty(UserToken) != null)
59                             token = value.GetType().GetProperty(UserToken).GetValue(value, null).ToString();
60                     }
61                 }
62 
63                 if (string.IsNullOrEmpty(token))
64                     msg = "登录超时,请重新登录!";
65             }
66             else if (type == HttpMethod.Get)
67             {
68                 if (!actionArguments.ContainsKey(UserToken))
69                     msg = "还未登录";
70                 // throw new HttpException(401, "还未登录");
71 
72                 if (actionArguments[UserToken] != null)
73                     token = actionArguments[UserToken].ToString();
74                 else
75                     msg = "登录超时,请重新登录!";
76             }
77             else
78             {
79                 throw new HttpException(404, "暂未开放除POST,GET之外的访问方式!");
80             }
81             return token;
82         }
83     }
84 
85     public class NoAuthData
86     {
87         public string code { get; set; }
88         public string msg { get; set; }
89     }
复制代码


此篇到此结束,欢迎大家讨论!

 

  

 

那些曾以为念念不忘的事情就在我们念念不忘的过程中,被我们遗忘了。

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

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

相关文章

匈牙利命名法为何被淘汰_为何甲烷的习惯命名法用甲烷而不是一烷?

其实在有机化合物中文翻译早期&#xff0c;是有过用数字命名的阶段的&#xff0c;先上图。部分有机化合物在不同时期的汉译名对照那么后来为何未采用数字&#xff0c;而使用天干。与商务印书馆和郑贞文个人其实有很大关系。那么有机物中文命名的演进是怎样的呢&#xff1f;下面…

Windows下Mysql Cluster集群启动脚本与启动服务添加方法

3.2.8 集群启动脚本及启动服务注册 从上面的启动过程我们可以看出&#xff0c;Mysql集群启动命令较复杂&#xff0c;容易造成cmd窗口因误操作关闭而导致的数据库停止&#xff0c;在此我介绍一下如何将这些命令的操作变成windos的服务项&#xff0c;当windos启动时&#xff0…

iOS 本地化应用程序汇总 国际化

最近要做一个应用要实现本地化&#xff0c;因为使用的是xcode4&#xff0c;应用程序本地化的问题跟以前的版本还是有些不同&#xff0c;在网上找了些资料对于xcode4以上的版本资料还是相对较少&#xff0c;有些最后要通过手动创建文件&#xff0c;这样操作实在是太麻烦&#xf…

图卷积神经网络_深度层次化图卷积神经网络

来源&#xff1a;IJCAI 2019论文地址&#xff1a;https://arxiv.org/abs/1902.06667代码地址&#xff1a;https://github.com/CRIPAC-DIG/H-GCNIntroduction1、问题定义&#xff1a;什么是半监督的节点分类&#xff1f;图1 半监督节点分类如图1所示&#xff0c;在标记数据量很少…

老师能提供什么帮助

老师能提供什么帮助&#xff1f; 这篇本来想上周写的&#xff0c;原计划在感恩节推送&#xff0c;可惜太忙&#xff0c;计划赶不上变化。 时隔十年&#xff0c;我又要给计算机系的同学们上课了&#xff0c;心里还是有些小激动的。我下学期要开一门大课叫程序设计与数据结构,涵盖…

12个Icon图标资源网站

1.除了Icon以外&#xff0c;还有很多不错的UI设计素材。 地址&#xff1a;http://worldui.com/2.除了免费Icon资源下载以外&#xff0c;还提供Icon定制的付费服务。地址&#xff1a;http://dryicons.com/3.很喜欢这个icon资源站的展示方式。地址&#xff1a;http://www.icotrip…

linux中解决SSH连接慢问题

2019独角兽企业重金招聘Python工程师标准>>> 现在连接linux服务器一般都是使用SSH远程连接的方式。最近新装了一台服务器&#xff0c;发现telnet时速度很快&#xff0c;ping时一切也正常&#xff0c;但SSH连接的时候却很慢。经过网上资料查询&#xff0c;大致是有以…

阿里云深圳数据中心正式开放

阿里云深圳数据中心正式开放 ​ 8月29日&#xff0c;阿里云深圳数据中心正式开放运营&#xff0c;这是继杭州、青岛、北京、香港之后&#xff0c;我们在全球开放的第五个数据中心。 深圳数据中心主要辐射以深圳、广州为中心的华南区域&#xff0c;以满足这一区域内的商贸企业、…

c语言函数声明定义参数命名,C语言函数声明与定义

C语言函数声明与定义教程在C语言函数声明与定义语法type funcName(paramType1 param1, paramType2 param2){// 执行语句...return val}参数参数描述type函数的返回值类型&#xff0c;如果没有任何返回值&#xff0c;则写 void&#xff0c;不可以死省略不写。funcName函数名。pa…

SNF开发平台WinForm之五-高级查询使用说明-SNF快速开发平台3.3-Spring.Net.Framework

5.1运行效果&#xff1a; 5.2开发实现&#xff1a; 1、按上面效果来说&#xff0c;先来看一下在程序当中如果调用。第一步在页面拖拽一个按钮为“高级查询”&#xff0c;事件上写下如下代码&#xff1a; 如果是单表查询的话&#xff0c;只需要传GridView就行&#xff0c;如果是…

颈椎病防治指南

2019独角兽企业重金招聘Python工程师标准>>> 长期从事财会、写作、打字、办公室等职业的工作人员&#xff0c;由于长期低头伏案工作&#xff0c;使颈椎长时间处于屈曲位或某些特定体位&#xff0c;不仅使颈椎间盘内的压力增高&#xff0c;而且也使颈部肌肉长期处于非…

智能手机计步算法c语言实现,【转载】智能手机计步器算法的实现

现在的智能手机嵌入了一些微小的传感器&#xff0c;比如重力传感器、光传感器、声音传感器等。如何有效地利用这些传感器来开发一些应用&#xff0c;是一个值得深入研究的课题。比如开发医疗健康的应用、运动量监视器等。本文采用htc Touch Pro智能手机的重力传感器来开发一款监…

Arduino教程资料汇总(8月22日悄悄跟新了一下)

http://www.geek-workshop.com/thread-985-1-1.html 本帖最后由 迷你强 于 2013-8-31 12:36 编辑 F-101 arduino基础套件使用资料 Arduino入门教程--课前准备--Arduino驱动安装及1.0 IDE菜单介绍Arduino入门教程--第一课--板载Led闪烁实验Arduino入门教程--第二课--第一次面包板…

HTML5/CSS3系列教程:HTML5 区域(Sectioning)的重要性

日期&#xff1a;2013-2-4 来源&#xff1a;GBin1.com 不管你以前在web页面布局中如何称呼它们 - “区域”还是“块”&#xff0c;我们一直都在布局中将页面分成可视的不同区域。但真正的问题在于我们并没有使用任何正确的工具来实现。一般情况下我们使用典型的网格来划分页头…

CoreAnimation —— CAReplicatorLayer(拷贝图层)

2019独角兽企业重金招聘Python工程师标准>>> CAReplicatorLayer是一个layer容器&#xff0c;会对其中的subLayer进行一些差异处理&#xff08;它的子layer都可以拷贝&#xff09; 属性&#xff1a; //拷贝的次数 property NSInteger instanceCount; //是否开启景深效…

android 接收短信代码,短信接收功能实现的代码

其中包含了widget必备的要素以及对应文件分别为&#xff1a;appwidgetprovider--------------------------SmsWidget.javawidget的config--------------------------SmsWidgetConfig.javawidget引发的app-------------------------SmsAider.javaappwidgetproviderinfo---------…

Entity Framework With Oracle

虽然EF6都快要出来了&#xff0c;但是对于Oracle数据库&#xff0c;仍然只能用DB first和Model First来编程&#xff0c;不能用Code First真是一个很大的遗憾啊。 好了&#xff0c;废话少说&#xff0c;我们来看看EF中是如何用DB first和Model First来对Oracle编程的。 首先我们…

(三)Maven仓库介绍与本地仓库配置

1.Maven本地仓库/远程仓库的基本介绍 示意图&#xff1a; 本地仓库是指存在于我们本机的仓库&#xff0c;在我们加入依赖时候&#xff0c;首先会跑到我们的本地仓库去找&#xff0c;如果找不到则会跑到远程仓库中去找。对于依赖的包大家可以从这个地址进行搜索&#xff1a;http…

android分辨率比例成像,像素不是唯一 决定成像效果你必知的真相

像素并不是唯一如今不少人在选购一部手机时&#xff0c;非常重视手机摄像头的像素大小&#xff0c;因为一部高像素的手机可以为不少喜爱拍照的人省去买单反的费用&#xff0c;而且携带起来也非常方便。不过&#xff0c;手机并不能与专业的单反相机相比&#xff0c;成像效果并不…

Android底部导航栏实现(一)之BottomNavigationBar

BottomNavigationBar这个控件的使用之前已经写过&#xff0c;这里不再赘述&#xff0c;详情请参考BottomNavigationBar的使用。 下面直接上代码&#xff1a; 初始化及相关设置&#xff1a; mBottomNavigationBar (BottomNavigationBar) view.findViewById(R.id.bottom_navigat…