首页 文章 精选 留言 我的

精选列表

搜索[SpringBoot4],共10000篇文章
优秀的个人博客,低调大师

【WebAPI No.4】Swagger实现API文档功能

介绍: Swagger也称为Open API,Swagger从API文档中手动完成工作,并提供一系列用于生成,可视化和维护API文档的解决方案。简单的说就是一款让你更好的书写API文档的框架。 我们为什么选择swagger,现在的网站开发结果越来越注重前后端的分离,比如以前的webFrom到现在的mvc模式都是为了这个前后端的分离。就算再如何的分离实现,也是不可避免的要进行数据交互的,那么接口的重要性就提现出来了。他成了前端和后端交互的重要途径,API文档也因此成了前端开发人员与后端开发人员的重要纽带。所有我们有一个API文档框架岂不是美哉。 Swashbuckle有三个主要组件: Swashbuckle.AspNetCore.Swagger:Swagger对象模型和中间件,将SwaggerDocument对象公开为JSON端点。Swashbuckle.AspNetCore.SwaggerGen:一种Swagger生成器,可SwaggerDocument直接从路由,控制器和模型构建对象。它通常与Swagger端点中间件结合使用,以自动公开Swagger JSON。Swashbuckle.AspNetCore.SwaggerUI:Swagger UI工具的嵌入式版本。它将Swagger JSON解释为构建丰富的,可定制的Web API功能描述体验。它包括用于公共方法的内置测试线束。 开始使用: 首先我们要有一个WebAPI项目,假设我们已经创建好了,不懂WebAPI如何创建的请看这篇文章:创建简单的WebAPI 好了现在我们已经有了项目那我们就开始添加引用吧: Nuget命令行 :Install-Package Swashbuckle.AspNetCore 添加并配置Swagger中间件: 然后配置Startup.cs文件中的ConfigureServices方法,当然首先不要忘记引用一下using Swashbuckle.AspNetCore.Swagger命名空间,不然info类会报错。 public void ConfigureServices(IServiceCollection services) { services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1) .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver());//JSON首字母小写解决; services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info { Version = "v1", Title = " API 文档" }); }); } 然后在Configure方法中添加: //允许中间件为JSON端点服务生成的Siggg app.UseSwagger(); //使中间件能够服务于轻量级用户界面(HTML、JS、CSS等),并指定SWAGJER JSON端点 app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); // 要在应用程序的根处提供Swagger UI ,请将该RoutePrefix属性设置为空字符串 c.RoutePrefix = string.Empty; }); View Code 启动效果: 这样就可以启动了,配置好以上就是一个最简单的api文档示例了。首先你可以访问:http://localhost:<port>/swagger/v1/swagger.json这个网址看到端点生成的文档。 当然你想看到的是这个:http://localhost:<port>/swagger ,你输入这个网址也可以,当然我把他配置成了根节点,这样直接启动根节点就是该页面。主要是上面代码中的:c.RoutePrefix = string.Empty这句代码。 扩展一些显示: 我们可以扩展一些附加信息,比如作者,许可证,服务条款等。传递给该AddSwaggerGen方法的配置: //Swagger生成器添加到方法中的服务集合中 services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info { Version = "v1", Title = " API 文档", Description = "这是一个示例webapi文档", //服务条款 TermsOfService = "None", //作者信息 Contact = new Contact { Name = "ybf", Email = string.Empty, Url = "http://www.cnblogs.com/yanbigfeg/" }, //许可证 License = new License { Name = "许可证名字", Url = "http://www.cnblogs.com/yanbigfeg/" } }); }); View Code 显示结果 xml注释: 启用XML注释可为未记录的公共类型和成员提供调试信息。未记录的类型和成员由警告消息指示。配置Swagger以使用生成的XML文件。 在我们实现前先设置一下项目属性: 这样就可以在项目bin下生成xml文件了。下面进行代码配置启用,还是在Startup类中的ConfigureServices方法中,在我们刚才配置的下面在增加以下代码: // 设置SWAGER JSON和UI的注释路径。 var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); 使用反射主要是为了生成一个与项目文件名相同的xml文件。第二部然后是配置文件路径。这些配置好了以后。我们就可以把方法或者类的三道斜杠(“///”)的注释添加到动作。我们来看一下效果。 代码 界面: 注意哦,就是开启这个功能,项目会自动检测那些方法或者类没有注释,会给出警告。 取消警告小技巧: 当然我们也可以取消掉:我们在个人.csproj文件中使用分号分隔来取消警告信息: <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <DocumentationFile>bin\Debug\netcoreapp2.1\SwaggerTest.xml</DocumentationFile> <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies> <NoWarn>1701;1702;1705;1591</NoWarn> </PropertyGroup> View Code 这样就不会有警告提示了。 描述相应类型: 接口使用者最关心的就是接口的返回内容和相应类型啦。下面展示一下201和400一个简单例子: 我们需要在我们的方法上添加:[ProducesResponseType(201)][ProducesResponseType(400)] 然后添加相应的状态说明:<response code="201">返回value字符串</response><response code="400">如果id为空</response> 最终代码应该是这个样子: /// <summary> /// values带id参数的get /// </summary> /// <param name="id">id参数</param> /// <returns>返回字符串类型</returns> /// <response code="201">返回value字符串</response> /// <response code="400">如果id为空</response> // GET api/values/5 [HttpGet("{id}")] [ProducesResponseType(201)] [ProducesResponseType(400)] public ActionResult<string> Get(int id) { return "value"; } View Code 运行起来我们看一下结果: 本例源码下载:SwaggerTest.rar 传送门: WebApi系列文章介绍如何使用WebAPI: WebApi系列文章目录介绍 作者:YanBigFeg —— 颜秉锋 出处:http://www.cnblogs.com/yanbigfeg 本文版权归作者和博客园共有,欢迎转载,转载请标明出处。如果您觉得本篇博文对您有所收获,觉得小弟还算用心,请点击右下角的 [推荐],谢谢!

优秀的个人博客,低调大师

解答区块链4大常见问题

瑞士小镇,Zug(楚格),瑞士区块链研究中心 | Shutterstock 近来比特币火红,虽然很多人都读过区块链的报导,但也觉得离这个主题还很遥远。我们希望从亲和的角度,探索技术背后的原始情境、人的力量。 瑞士在忙什么? 瑞士最近正积极投入一项新任务--建立世界级的加密聚落实验室(Crypto Valley Labs),首推区块链中心,命名为Genesis Hub。什么是区块链?为什么它让世界的银行,瑞士,视为是“创世纪”般的发明? 比特币在10年内席卷全球货币市场,它使用的技术,区块链,更一时之间成为人人追捧的话题,美国、德国、日本、中国、爱沙尼亚、新加坡…超过20个国家,陆续将其列为重大政策之一,用于金融业、政府部门、企业管理… 如果只能用一句话说明,那么区块链就是消除人们对网络世界不信任的“解方”,就是人们在网络世界寻觅数十年,终于

优秀的个人博客,低调大师

《C#类设计手册》读书随笔(4)

Programmer To Programer 丛书 C# Class Design Handbook Richard Conway, Teun Duynstee等著 清华大学出版社 代码组织和元数据 共享程序集 全局程序集缓存 Global Assembly Cache (GAC) 程序集 可重用的最小单元 元数据 为公共语言运行时提供了在运行期间加载类型和调用方法所需的所有信息 metadata 是元编程的基础,在很多系统中也是根本的东西,了解这个东西可以比较深入的了解某个架构 查看元数据最简单方法使用ildasm.exe /adv或/advancel选项 .NET模块是一个可移植、可执行(PE)格式文件 只有数据的模块 SDK包含一个工具Assembly Linker(AL.exe) 利用该工具将这些非MISL模块链接到程序集中 将该文件编译成模块,必须使用C#.NET命令行编译器 不能使用Visual Studio .NET 因为它总是创建单、程序集 例如:csc /target:module csMod.cs csMod.netmodule *.netmodule 是.NET的模块 增加模块到程序集清单中: csc /addmodule: csMod.netmodule /addmodule:VBMod.netmodule MainMod.cs 程序部署部署单程序集应用程序比较简单,直接用xcopy部署。私有程序集部署应用程序也不复杂,将需要的程序集合放在同一个文件夹或者用应用程序配置文件通知CLR在什么位置查找程序集 部署共享程序集 添加到GAC 全局程序集缓存但Microsoft不推荐应用程序与GAC相互依赖,应尽量使用应用私有程序集部署方式 利用公有-私有密钥对可以为程序集创建强名称 编译期间编译器使用私钥,将相应的公钥写入程序集清单 sn (Strong Name) sn \k *.snkMSDN中查找"Keys crytography"查找相应内容publickey 较长的数字 public key token 公钥令牌 sn \t *.snk 将共享程序集安装到GAC中 gacutil -i *.dll 需要管理员权限 或者在控制面板的管理工具中找 .NET FrameWork Configuration 本文转自风前絮~~博客园博客,原文链接:http://www.cnblogs.com/windsails/archive/2004/09/05/39837.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android开发者指南(4) —— Application Fundamentals

正文 应用程序基础(Application Fundamentals) Android应用程序使用Java做为开发语言。aapt工具把编译后的Java代码连同其它应用程序需要的数据和资源文件一起打包到一个Android包文件中,这个文件使用.apk做为扩展名,它是分发应用程序并安装到移动设备的媒介,用户只需下载并安装此文件到他们的设备。单一.apk文件中的所有代码被认为是一个应用程序。 从很多方面来看,每个Android应用程序都存在于它自己的世界之中: * 默认情况下,每个应用程序均运行于它自己的Linux进程中。当应用程序中的任意代码开始执行时,Android启动一个进程,而当不再需要此进程而其它应用程序又需要系统资源时,则关闭这个进程。 *每个进程都运行于自己的Java虚拟机(VM)中。所以应用程序代码实际上与其它应用程序的代码是隔绝的。 *默认情况下,每个应用程序均被赋予一个唯一的Linux用户ID,并加以权限设置,使得应用程序的文件仅对这个用户、这个应用程序可见。当然,也有其它的方法使得这些文件同样能为别的应用程序所访问。 使两个应用程序共有同一个用户ID是可行的,这种情况下他们可以看到彼此的文件。从系统资源维护的角度来看,拥有同一个ID的应用程序也将在运行时使用同一个Linux进程,以及同一个虚拟机。 应用程序组件(Application Components) Android的核心功能之一就是一个应用程序可以使用其它应用程序的元素(如果那个应用程序允许的话)。比如说,如果你的应用程序需要一个图片卷动列表,而另一个应用程序已经开发了一个合用的而又允许别人使用的话,你可以直接调用那个卷动列表来完成工作,而不用自己再开发一个。你的应用程序并没有吸纳或链接其它应用程序的代码,它只是在有需求的时候启动了其它应用程序的那个功能部分。 为达到这个目的,系统必须在一个应用程序的一部分被需要时启动这个应用程序,并将那个部分的Java对象实例化。与在其它系统上的应用程序不同,Android应用程序没有为应用准备一个单独的程序入口(比如说,没有main()方法),而是为系统依照需求实例化提供了基本的组件。共有四种组件类型: Activities *Activity是为用户操作而展示的可视化用户界面。比如说,一个activity可以展示一个菜单项列表供用户选择,或者显示一些包含说明的照片。一个短消息应用程序可以包括一个用于显示做为发送对象的联系人的列表的activity,一个给选定的联系人写短信的activity以及翻阅以前的短信和改变设置的activity。尽管它们一起组成了一个内聚的用户界面,但其中每个activity都与其它的保持独立。每个都是以Activity类为基类的子类实现。 *一个应用程序可以只有一个activity,或者,如刚才提到的短信应用程序那样,包含很多个。每个activity的作用,以及其数目,自然取决于应用程序及其设计。一般情况下,总有一个应用程序被标记为用户在应用程序启动的时候第一个看到的。从一个activity转向另一个的方式是靠当前的activity启动下一个。 *每个activity都被给予一个默认的窗口以进行绘制。一般情况下,这个窗口是满屏的,但它也可以是一个小的位于其它窗口之上的浮动窗口。一个activity也可以使用超过一个的窗口──比如,在activity运行过程中弹出的一个供用户反应的小对话框,或是当用户选择了屏幕上特定项目后显示的必要信息。 *窗口显示的可视内容是由一系列视图构成的,这些视图均继承自View基类。每个视图均控制着窗口中一块特定的矩形空间。父级视图包含并组织它子视图的布局。叶节点视图(位于视图层次最底端)在它们控制的矩形中进行绘制,并对用户对其直接操作做出响应。所以,视图是activity与用户进行交互的界面。比如说,视图可以显示一个小图片,并在用户指点它的时候产生动作。Android有很多既定的视图供用户直接使用,包括按钮、文本域、卷轴、菜单项、复选框等等。 *视图层次是由Activity.setContentView()方法放入activity的窗口之中的。上下文视图是位于视图层次根位置的视图对象。(参见用户界面章节获取关于视图及层次的更多信息。) 服务(Services) *服务没有可视化的用户界面,而是在一段时间内在后台运行。比如说,一个服务可以在用户做其它事情的时候在后台播放背景音乐、从网络上获取一些数据或者计算一些东西并提供给需要这个运算结果的activity使用。每个服务都继承自Service基类。 *一个媒体播放器播放播放列表中的曲目是一个不错的例子。播放器应用程序可能有一个或多个activity来给用户选择歌曲并进行播放。然而,音乐播放这个任务本身不应该为任何activity所处理,因为用户期望在他们离开播放器应用程序而开始做别的事情时,音乐仍在继续播放。为达到这个目的,媒体播放器activity应该启用一个运行于后台的服务。而系统将在这个activity不再显示于屏幕之后,仍维持音乐播放服务的运行。 *你可以连接至(绑定)一个正在运行的服务(如果服务没有运行,则启动之)。连接之后,你可以通过那个服务暴露出来的接口与服务进行通讯。对于音乐服务来说,这个接口可以允许用户暂停、回退、停止以及重新开始播放。 *如同activity和其它组件一样,服务运行于应用程序进程的主线程内。所以它不会对其它组件或用户界面有任何干扰,它们一般会派生一个新线程来进行一些耗时任务(比如音乐回放)。参见下述进程和线程(Processes and Threads)。 广播接收器(Broadcast receivers) *广播接收器是一个专注于接收广播通知信息,并做出对应处理的组件。很多广播是源自于系统代码的──比如,通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以进行广播──比如说,通知其它应用程序一些数据下载完成并处于可用状态。 *应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。所有的接收器均继承自BroadcastReceiver基类。 *广播接收器没有用户界面。然而,它们可以启动一个activity来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。 内容提供者(Content providers) *内容提供者将一些特定的应用程序数据供给其它应用程序使用。数据可以存储于文件系统、SQLite数据库或其它方式。内容提供者继承于ContentProvider基类,为其它应用程序取用和存储它管理的数据实现了一套标准方法。然而,应用程序并不直接调用这些方法,而是使用一个ContentResolver对象,调用它的方法作为替代。ContentResolver可以与任意内容提供者进行会话,与其合作来对所有相关交互通讯进行管理。 *参阅独立的内容提供者Content Providers章节获得更多关于使用内容提供者的内容。 每当出现一个需要被特定组件处理的请求时,Android会确保那个组件的应用程序进程处于运行状态,或在必要的时候启动它。并确保那个相应组件的实例的存在,必要时会创建那个实例。 激活组件Activating components: intents 当接收到ContentResolver发出的请求后,内容提供者被激活。而其它三种组件──activity、服务和广播接收器被一种叫做intent的异步消息所激活。intent是一个保存着消息内容的Intent对象。对于activity和服务来说,它指明了请求的操作名称以及作为操作对象的数据的URI和其它一些信息。比如说,它可以承载对一个activity的请求,让它为用户显示一张图片,或者让用户编辑一些文本。而对于广播接收器而言,Intent对象指明了声明的行为。比如,它可以对所有感兴趣的对象声明照相按钮被按下。 对于每种组件来说,激活的方法是不同的: *通过传递一个Intent对象至Context.startActivity()或Activity.startActivityForResult()以载入(或指定新工作给)一个activity。相应的activity可以通过调用getIntent()方法来查看激活它的intent。Android通过调用activity的onNewIntent()方法来传递给它继发的intent。 一个activity经常启动了下一个。如果它期望它所启动的那个activity返回一个结果,它会以调用startActivityForResult()来取代startActivity()。比如说,如果它启动了另外一个activity以使用户挑选一张照片,它也许想知道哪张照片被选中了。结果将会被封装在一个Intent对象中,并传递给发出调用的activity的onActivityResult()方法。 *通过传递一个Intent对象至Context.startService()将启动一个服务(或给予正在运行的服务以一个新的指令)。Android调用服务的onStart()方法并将Intent对象传递给它。 与此类似,一个Intent可以被调用组件传递给Context.bindService()以获取一个正在运行的目标服务的连接。这个服务会经由onBind()方法的调用获取这个Intent对象(如果服务尚未启动,bindService()会先启动它)。比如说,一个activity可以连接至前述的音乐回放服务,并提供给用户一个可操作的(用户界面)以对回放进行控制。这个activity可以调用bindService()来建立连接,然后调用服务中定义的对象来影响回放。 后面一节:远程方法调用(Remote procedure calls)将更详细的阐明如何绑定至服务。 *应用程序可以凭借将Intent对象传递给Context.sendBroadcast(),Context.sendOrderedBroadcast(),以及Context.sendStickyBroadcast()和其它类似方法来产生一个广播。Android会调用所有对此广播有兴趣的广播接收器的onReceive()方法,将intent传递给它们。 欲了解更多intent消息的信息,请参阅独立章节Intent和Intent滤过器(Intents and Intent Filters)。 关闭组件(Shutting down components) 内容提供者仅在响应ContentResolver提出请求的时候激活。而一个广播接收器仅在响应广播信息的时候激活。所以,没有必要去显式的关闭这些组件。 而activity则不同,它提供了用户界面,并与用户进行会话。所以只要会话依然持续,哪怕对话过程暂时停顿,它都会一直保持激活状态。与此相似,服务也会在很长一段时间内保持运行。所以Android为关闭activity和服务提供了一系列的方法。 *可以通过调用它的finish()方法来关闭一个activity。一个activity可以通过调用另外一个activity(它用startActivityForResult()启动的)的finishActivity()方法来关闭它。 *服务可以通过调用它的stopSelf()方法来停止,或者调用Context.stopService()。 系统也会在组件不再被使用的时候或者Android需要为活动组件声明更多内存的时候关闭它。后面的组件的生命周期一节,将对这种可能及附属情况进行更详细的讨论。 manifest文件(The manifest file) 当Android启动一个应用程序组件之前,它必须知道那个组件是存在的。所以,应用程序会在一个manifest文件中声明它的组件,这个文件会被打包到Android包中。这个.apk文件还将涵括应用程序的代码、文件以及其它资源。 这个manifest文件以XML作为结构格式,而且对于所有应用程序,都叫做AndroidManifest.xml。为声明一个应用程序组件,它还会做很多额外工作,比如指明应用程序所需链接到的库的名称(除了默认的Android库之外)以及声明应用程序期望获得的各种权限。 但manifest文件的主要功能仍然是向Android声明应用程序的组件。举例说明,一个activity可以如下声明: <activity>元素的name属性指定了实现了这个activity的Activity的子类。icon和label属性指向了包含展示给用户的此activity的图标和标签的资源文件。 其它组件也以类似的方法声明──<service>元素用于声明服务,<receiver>元素用于声明广播接收器,而<provider>元素用于声明内容提供者。manifest文件中未进行声明的activity、服务以及内容提供者将不为系统所见,从而也就不会被运行。然而,广播接收器既可以在manifest文件中声明,也可以在代码中进行动态的创建,并以调用Context.registerReceiver()的方式注册至系统。 欲更多了解如何为你的应用程序构建manifest文件,请参阅AndroidManifest.xml文件一章。 Intent过滤器(Intent filters) Intent对象可以被显式的指定目标组件。如果进行了这种指定,Android会找到这个组件(依据manifest文件中的声明)并激活它。但如果Intent没有进行显式的指定,Android就必须为它找到对于intent来说最合适的组件。这个过程是通过比较Intent对象和所有可能对象的intent过滤器完成的。组件的intent过滤器会告知Android它所能处理的intent类型。如同其它相对于组件很重要的信息一样,这些是在manifest文件中进行声明的。这里是上面实例的一个扩展,其中加入了针对activity的两个intent过滤器声明: 示例中的第一个过滤器──action “android.intent.action.MAIN”和类别“android.intent.category.LAUNCHER”的组合──是通常具有的。它标明了这个activity将在应用程序加载器中显示,就是用户在设备上看到的可供加载的应用程序列表。换句话说,这个activity是应用程序的入口,是用户选择运行这个应用程序后所见到的第一个activity。 第二个过滤器声明了这个activity能被赋予一种特定类型的数据。 组件可以拥有任意数量的intent过滤器,每个都会声明一系列不同的能力。如果它没有包含任何过滤器,它将只能被显式声明了目标组件名称的intent激活。 对于在代码中创建并注册的广播接收器来说,intent过滤器将被直接以IntentFilter对象实例化。其它过滤器则在manifest文件中设置。 欲获得更多intent过滤器的信息,请参阅独立章节:Intent和Intent过滤器。 Activity和任务(Activities and Tasks) 如前所述,一个activity可以启动另外一个,甚至包括与它不处于同一应用程序之中的。举个例子说,假设你想让用户看到某个地方的街道地图。而已经存在一个具有此功能的activity了,那么你的activity所需要做的工作就是把请求信息放到一个Intent对象里面,并把它传递给startActivity()。于是地图浏览器就会显示那个地图。而当用户按下BACK键的时候,你的activity又会再一次的显示在屏幕上。 对于用户来说,这看起来就像是地图浏览器是你activity所在的应用程序中的一个组成部分,其实它是在另外一个应用程序中定义,并运行在那个应用程序的进程之中的。Android将这两个activity放在同一个任务中来维持一个完整的用户体验。简单的说,任务就是用户所体验到的“应用程序”。它是安排在一个堆栈中的一组相关的activity。堆栈中的根activity就是启动了这整个任务的那个──一般情况下,它就是用户在应用程序加载器中所选择的。而堆栈最上方的activity则是当前运行的──用户直接对其进行操作的。当一个activity启动另外一个的时候,新的activity就被压入堆栈,并成为当前运行的activity。而前一个activity仍保持在堆栈之中。当用户按下BACK键的时候,当前activity出栈,而前一个恢复为当前运行的activity。 堆栈中保存的其实是对象,所以如果发生了诸如需要多个地图浏览器的情况,就会使得一个任务中出现多个同一Activity子类的实例同时存在,堆栈会为每个实例单独开辟一个入口。堆栈中的Activity永远不会重排,只会压入或弹出。 任务其实就是activity的堆栈,而不是manifest文件中的一个类或者元素。所以你无法撇开activity而为一个任务设置一个值。而事实上整个任务使用的值是在根activity中设置的。比如说,下一节我们会谈及“任务的affinity”,从affinity中读出的值将会设置到任务的根activity之中。 任务中的所有activity是作为一个整体进行移动的。整个的任务(即activity堆栈)可以移到前台,或退至后台。举个例子说,比如当前任务在堆栈中存有四个activity──三个在当前activity之下。当用户按下HOME键的时候,回到了应用程序加载器,然后选择了一个新的应用程序(也就是一个新任务)。则当前任务遁入后台,而新任务的根activity显示出来。然后,过了一小会儿,用户再次回到了应用程序加载器而又选择了前一个应用程序(上一个任务)。于是那个任务,带着它堆栈中所有的四个activity,再一次的到了前台。当用户按下BACK键的时候,屏幕不会显示出用户刚才离开的activity(上一个任务的根activity)。取而代之,当前任务的堆栈中最上面的activity被弹出,而同一任务中的上一个activity显示了出来。 上述的种种即是activity和任务的默认行为模式。但是有一些方法可以改变所有这一切。activity和任务的联系、任务中activity的行为方式都被启动那个activity的Intent对象中设置的一系列标记和manifest文件中那个activity中的<activity>元素的系列属性之间的交互所控制。无论是请求发出者和回应者在这里都拥有话语权。 我们刚才所说的这些关键Intent标记如下: FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP FLAG_ACTIVITY_RESET_TASK_IF_NEEDED FLAG_ACTIVITY_SINGLE_TOP 而关键的<activity>属性是: taskAffinity launchMode allowTaskReparenting clearTaskOnLaunch alwaysRetainTaskState finishOnTaskLaunch 接下来的一节会描述这些标记以及属性的作用,它们是如何互相影响的,以及控制它们的使用时必须考虑到的因素。 任务共用性和新任务Affinities and new tasks 默认情况下,一个应用程序中的activity相互之间会有一种Affinity──也就是说,它们首选都归属于一个任务。然而,可以在<activity>元素中把每个activity的taskAffinity属性设置为一个独立的affinity。于是在不同的应用程序中定义的activity可以享有同一个affinity,或者在同一个应用程序中定义的activity有着不同的affinity。affinity在两种情况下生效:当加载activity的Intent对象包含了FLAG_ACTIVITY_NEW_TASK标记,或者当activity的allowTaskReparenting属性设置为“true”。 FLAG_ACTIVITY_NEW_TASK标记 如前所述,在默认情况下,一个新activity被另外一个调用了startActivity()方法的activity载入了任务之中。并压入了调用者所在的堆栈。然而,如果传递给startActivity()的Intent对象包含了FLAG_ACTIVITY_NEW_TASK标记,系统会为新activity安排另外一个任务。一般情况下,如同标记所暗示的那样,这会是一个新任务。然而,这并不是必然的。如果已经存在了一个与新activity有着同样affinity的任务,则activity会载入那个任务之中。如果没有,则启用新任务。 allowTaskReparenting属性 如果一个activity将allowTaskReparenting属性设置为“true”。它就可以从初始的任务中转移到与其拥有同一个affinity并转向前台的任务之中。比如说,一个旅行应用程序中包含的预报所选城市的天气情况的activity。它与这个应用程序中其它的activity拥有同样的affinity(默认的affinity)而且允许重定父级。你的另一个activity启动了天气预报,于是它就会与这个activity共处与同一任务之中。然而,当那个旅行应用程序再次回到前台的时候,这个天气预报activity就会被再次安排到原先的任务之中并显示出来。 如果在用户的角度看来,一个.apk文件中包含了多于一个的“应用程序”,你可能会想要为它们所辖的activity安排不一样的affinity。 加载模式(Launch modes) <activity>元素的launchMode属性可以设置四种不同的加载模式: "standard" (默认模式) "singleTop" "singleTask" "singleInstance" 这些模式之间的差异主要体现在四个方面: *哪个任务会把持对intent做出响应的activity。对“standard”和“singleTop”模式而言,是产生intent(并调用startActivity())的任务──除非Intent对象包含FLAG_ACTIVITY_NEW_TASK标记。而在这种情况下,如同上面Affinitie和新任务一节所述,会是另外一个任务。 相反,对“singleTask”和“singleInstance”模式而言,activity总是位于任务的根部。正是它们定义了一个任务,所以它们绝不会被载入到其它任务之中。 *activity是否可以存在多个实例。一个“standard”或“singleTop”的activity可以被多次初始化。它们可以归属于多个任务,而一个任务也可以拥有同一activity的多个实例。 相反,对“singleTask”和“singleInstance”的activity被限定于只能有一个实例。因为这些activity都是任务的起源,这种限制意味着在一个设备中同一时间只允许存在一个任务的实例。 *在实例所在的任务中是否会有别的activity。一个“singleInstance”模式的activity将会是它所在的任务中唯一的activity。如果它启动了别的activity,那个activity将会依据它自己的加载模式加载到其它的任务中去──如同在intent中设置了FLAG_ACTIVITY_NEW_TASK标记一样的效果。在其它方面,“singleInstance”模式的效果与“singleTask”是一样的。 剩下的三种模式允许一个任务中出现多个activity。“singleTask”模式的activity将是任务的根activity,但它可以启动别的activity并将它们置入所在的任务中。“standard”和“singleTop”activity则可以在堆栈的任意位置出现。 *是否要载入新的类实例以处理新的intent。对默认的"standard"模式来说,对于每个新intent都会创建一个新的实例以进行响应,每个实例仅处理一个intent。“singleTop”模式下,如果activity位于目的任务堆栈的最上面,则重用目前现存的activity来处理新的intent。如果它不是在堆栈顶部,则不会发生重用。而是创建一个新实例来处理新的intent并将其推入堆栈。 举例来说,假设一个任务的堆栈由根activityA和activity B、C和位于堆栈顶部的D组成,即堆栈A-B-C-D。一个针对D类型的activity的intent抵达的时候,如果D是默认的“standard”加载模式,则创建并加载一个新的类实例,于是堆栈变为A-B-C-D-D。然而,如果D的载入模式为“singleTop”,则现有的实例会对新intent进行处理(因为它位于堆栈顶部)而堆栈保持A-B-C-D的形态。 换言之,如果新抵达的intent是针对B类型的activity,则无论B的模式是“standard”还是“singleTop”,都会加载一个新的B的实例(因为B不位于堆栈的顶部),而堆栈的顺序变为A-B-C-D-B。 如前所述,“singleTask”或“singleInstance”模式的activity永远不会存在多于一个实例。所以实例将处理所有新的intent。一个“singleInstance”模式的activity永远保持在堆栈的顶部(因为它是那个堆栈中唯一的一个activity),所以它一直坚守在处理intent的岗位上。然而,对一个“singleTask”模式的activity来说,它上面可能有,也可能没有别的activity和它处于同一堆栈。在有的情况下,它就不在能够处理intent的位置上,则那个intent将被舍弃。(即便在intent被舍弃的情况下,它的抵达仍将使这个任务切换至前台,并一直保留) 当一个现存的activity被要求处理一个新的intent的时候,会调用onNewIntent()方法来将intent对象传递至activity。(启动activity的原始intent对象可以通过调用getIntent()方法获得。) 请注意,当一个新的activity实例被创建以处理新的intent的时候,用户总可以按下BACK键来回到前面的状态(回到前一个activity)。但当使用现存的activity来处理新intent的时候,用户是不能靠按下BACK键回到当这个新intent抵达之前的状态的。 想获得更多关于加载模式的内容,请参阅<activity>元素的描述。 清理堆栈(Clearing the stack) 如果用户离开一个任务很长一段时间,系统会清理该任务中除了根activity之外的所有activity。当用户再次回到这个任务的时候,除了只剩下初始化activity尚存之外,其余都跟用户上次离开它的时候一样。这样做的原因是:在一段时间之后,用户再次回到一个任务的时候,他们更期望放弃他们之前的所作所为,做些新的事情。 这些属于默认行为,另外,也存在一些activity的属性用以控制并改变这些行为: alwaysRetainTaskState属性 如果一个任务的根activity中此属性设置为“true”,则上述默认行为不会发生。任务将在很长的一段时间内保留它堆栈内的所有activity。 clearTaskOnLaunch属性 如果一个任务的根activity中此属性设置为“true”,则每当用户离开这个任务和返回它的时候,堆栈都会被清空至只留下rootactivity。换句话说,这是alwaysRetainTaskState的另一个极端。哪怕仅是过了一小会儿,用户回到任务时,也是见到它的初始状态。 finishOnTaskLaunch属性 这个属性与clearTaskOnLaunch属性相似,但它仅作用于单个的activity,而不是整个的task。而且它可以使任意activity都被清理,甚至根activity也不例外。当它设置为“true”的时候,此activity仅做为任务的一部分存在于当前回话中,一旦用户离开并再次回到这个任务,此activity将不复存在。 此外,还有别的方式从堆栈中移除一个activity。如果一个intent对象包含FLAG_ACTIVITY_CLEAR_TOP标记,而且目标任务的堆栈中已经存在了一个能够响应此intent的activity类型的实例。则这个实例之上的所有activity都将被清理以使它位于堆栈的顶部来对intent做出响应。如果此时指定的activity的加载模式为“standard”,则它本身也会从堆栈中移除,并加载一个新的实例来处理到来的intent。这是因为加载模式为“standard”的activity总会创建一个新实例来处理新的intent。 FLAG_ACTIVITY_CLEAR_TOP与FLAG_ACTIVITY_NEW_TASK经常合并使用。这时,这些标记提供了一种定位其它任务中现存的activity并将它们置于可以对intent做出响应的位置的方法。 启动任务(Starting tasks) 当一个activity被指定一个“android.intent.action.MAIN”做为动作,以及“android.intent.category.LAUNCHER”做为类别的intent过滤器之后(在前述intent过滤器一节中已经有了这个示例),它就被设置为一个任务的入口点。这样的过滤器设置会在应用程序加载器中为此activity显示一个图标和标签,以供用户加载任务或加载之后在任意时间回到这个任务。 第二个能力相当重要:用户必须可以离开一个任务,并在一段时间后返回它。出于这个考虑,加载模式被设定为“singleTask”和“singleInstance”的activity总是会初始化一个新任务,这样的activity仅能用于指定了一个MAIN和LAUNCHER过滤器的情况之下。我们来举例说明如果没指定过滤器的情况下会发生的事情:一个intent加载了一个“singleTask”的activity,初始化了一个新任务,用户在这个任务中花费了一些时间来完成工作。然后用户按下了HOME键。于是任务被要求转至后台并被主屏幕所掩盖。因为它并没有在应用程序加载器中显示图标,这将导致用户无法再返回它。 类似的困境也可由FLAG_ACTIVITY_NEW_TASK标记引起。如果此标记使一个activity启动了一个新任务继而用户按下了HOME键离开了它,则用户必须要有一些方法再次回到这个任务。一些实体(诸如通知管理器)总是在另外的任务中启动新activity,而不是做为它们自己的一部分,所以它们总是将FLAG_ACTIVITY_NEW_TASK标记包含在intent里面并传递给startActivity()。如果你写了一个能被外部实体使用这个标记调用的activity,你必须注意要给用户留一个返回这个被外部实体启动的任务的方法。 当你不想让用户再次返回一个activity的情况下,可以将<activity>元素的finishOnTaskLaunch设置为“true”。参见前述清理堆栈。 进程和线程(Processes and Threads) 当一个应用程序开始运行它的第一个组件时,Android会为它启动一个Linux进程,并在其中执行一个单一的线程。默认情况下,应用程序所有的组件均在这个进程的这个线程中运行。 然而,你也可以安排组件在其他进程中运行,而且可以为任意进程衍生出其它线程。 进程(Processes) 组件运行所在的进程由manifest文件所控制。组件元素——<activity>,<service>,<receiver>和<provider>——都有一个process属性来指定组件应当运行于哪个进程之内。这些属性可以设置为使每个组件运行于它自己的进程之内,或一些组件共享一个进程而其余的组件不这么做。它们也可以设置为令不同应用程序的组件在一个进程中运行——使应用程序的组成部分共享同一个Linux用户ID并赋以同样的权限。<application>元素也有一个process属性,以设定所有组件的默认值。 所有的组件实例都位于特定进程的主线程内,而对这些组件的系统调用也将由那个线程进行分发。一般不会为每个实例创建线程。因此,某些方法总是运行在进程的主线程内,这些方法包括诸如View.onKeyDown()这样报告用户动作以及后面组件生命周期一节所要讨论的生命周期通告的。这意味着组件在被系统调用的时候,不应该施行长时间的抑或阻塞的操作(诸如网络相关操作或是循环计算),因为这将阻塞同样位于这个进程的其它组件的运行。你应该如同下面线程一节所叙述的那样,为这些长时间操作衍生出一个单独的线程进行处理。 在可用内存不足而又有一个正在为用户进行服务的进程需要更多内存的时候,Android有时候可能会关闭一个进程。而在这个进程中运行着的应用程序也因此被销毁。当再次出现需要它们进行处理的工作的时候,会为这些组件重新创建进程。 在决定结束哪个进程的时候,Android会衡量它们对于用户的相对重要性。比如说,相对于一个仍有用户可见的activity的进程,它更有可能去关闭一个其activity已经不为用户所见的进程。也可以说,决定是否关闭一个进程主要依据在那个进程中运行的组件的状态。这些状态将在后续的一节组件生命周期中予以说明。 线程(Threads) 尽管你可以把你的应用程序限制于一个单独的进程中,有时,你仍然需要衍生出一个线程以处理后台任务。因为用户界面必须非常及时的对用户操作做出响应,所以,控管activity的线程不应用于处理一些诸如网络下载之类的耗时操作。所有不能在瞬间完成的任务都应安排到不同的线程中去。 线程在代码中是以标准JavaThread对象创建的。Android提供了很多便于管理线程的类:Looper用于在一个线程中运行一个消息循环,Handler用于处理消息,HandlerThread用于使用一个消息循环启用一个线程。 远程方法调用(Remote procedure calls) Android有一个轻量级的远程方法调用(RPC)机制:即在本地调用一个方法,但在远程(其它的进程中)进行处理,然后将结果返回调用者。这将方法调用及其附属的数据以系统可以理解的方式进行分离,并将其从本地进程和本地地址空间传送至远程过程和远程地址空间,并在那里重新装配并对调用做出反应。返回的结果将以相反的方向进行传递。Android提供了完成这些工作所需的所有的代码,以使你可以集中精力来实现RPC接口本身。 RPC接口可以只包括方法。即便没有返回值,所有方法仍以同步的方式执行(本地方法阻塞直至远程方法结束)。 简单的说,这套机制是这样工作的:一开始,你用简单的IDL(界面描绘语言)声明一个你想要实现的RPC接口。然后用aidl工具为这个声明生成一个Java接口定义,这个定义必须对本地和远程进程都可见。它包含两个内部类,如下图所示: 内部类中有管理实现了你用IDL声明的接口的远程方法调用所需要的所有代码。两个内部类均实现了IBinder接口。一个用于系统在本地内部使用,你些的代码可以忽略它;另外一个,我们称为Stub,扩展了Binder类。除了实现了IPC调用的内部代码之外,它还包括了你声明的RPC接口中的方法的声明。你应该如上图所示的那样写一个Stub的子类来实现这些方法。 一般情况下,远程过程是被一个服务所管理的(因为服务可以通知系统关于进程以及它连接到别的进程的信息)。它包含着aidl工具产生的接口文件和实现了RPC方法的Stub的子类。而客户端只需要包括aidl工具产生的接口文件。 下面将说明服务与其客户端之间的连接是如何建立的: *服务的客户端(位于本地)应该实现onServiceConnected()和onServiceDisconnected()方法。这样,当至远程服务的连接成功建立或者断开的时候,它们会收到通知。这样它们就可以调用bindService()来设置连接。 *而服务则应该实现onBind()方法以接受或拒绝连接。这取决于它收到的intent(intent将传递给bindService())。如果接受了连接,它会返回一个Stub的子类的实例。 *如果服务接受了连接,Android将会调用客户端的onServiceConnected()方法,并传递给它一个IBinder对象,它是由服务所管理的Stub的子类的代理。通过这个代理,客户端可以对远程服务进行调用。 本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/582304,如需转载请自行联系原作者

优秀的个人博客,低调大师

使用Identity Server 4建立Authorization Server (3)

预备知识:http://www.cnblogs.com/cgzl/p/7746496.html 第一部分:http://www.cnblogs.com/cgzl/p/7780559.html 第二部分:http://www.cnblogs.com/cgzl/p/7788636.html 上一部分简单的弄了个web api 并通过Client_Credentials和ResourceOwnerPassword两种方式获取token然后进行api请求. 这次讲一下Authentication 身份验证 (而Authorization是授权, 注意区分), 使用的是OpenIdConnect. 这次我们使用的是一个MVC客户端. 建立MVC客户端项目 在同一个解决方案建立一个名字叫MvcClient的asp.net core mvc 项目: 不要配置Authentication(身份验证), 应该是没有验证. 修改运行方式为控制台, 端口改为5002, 也就是修改launchSettings.json, 把IISExpress相关的去掉: { "profiles": { "MvcClient": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:5002/" } } } 在HomeController的About方法上面添加Authorize属性: [Authorize] public IActionResult About() { ViewData["Message"] = "Your application description page."; return View(); } 然后设置解决方案的启动项目为MvcClient和Authorization Server, 解决方案右键属性: 然后运行项目, 在http://localhost:5002里点击About菜单: 就会出现以下异常 (500): 我们现在要做的就是, 用户点击About之后, 页面重定向到Authorization Server, 用户填写完信息之后登陆到Authorization Server之后再重定向回到该网站(MvcClient). 这也意味着用户是在Authorization Server使用用户名和密码, 而MvcClient不保存用户的用户名和密码. 下面就开始配置 添加OpenId ConnectAuthentication 在Startup的ConfigureServices里面添加: public void ConfigureServices(IServiceCollection services) { services.AddMvc(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ClientId = "mvc_implicit"; options.SaveTokens = true; }); } JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); 这句话是指, 我们关闭了JWT的Claim 类型映射, 以便允许well-known claims. 这样做, 就保证它不会修改任何从Authorization Server返回的Claims. AddAuthentication()方法是像DI注册了该服务. 这里我们使用Cookie作为验证用户的首选方式: DefaultScheme = "Cookies". 而把DefaultChanllangeScheme设为"oidc"是因为, 当用户需要登陆的时候, 将使用的是OpenId Connect Scheme. 然后的AddCookie, 是表示添加了可以处理Cookie的处理器(handler). 最后AddOpenIdConnect是让上面的handler来执行OpenId Connect 协议. 其中的Authority是指信任的Identity Server ( Authorization Server). ClientId是Client的识别标志. 目前Authorization Server还没有配置这个Client, 一会我们再弄. Client名字也暗示了我们要使用的是implicit flow, 这个flow主要应用于客户端应用程序, 这里的客户端应用程序主要是指javascript应用程序. implicit flow是很简单的重定向flow, 它允许我们重定向到authorization server, 然后带着id token重定向回来, 这个 id token就是openid connect 用来识别用户是否已经登陆了. 同时也可以获得access token. 很明显, 我们不希望access token出现在那个重定向中. 这个一会再说. 一旦OpenId Connect协议完成, SignInScheme使用Cookie Handler来发布Cookie (中间件告诉我们已经重定向回到MvcClient了, 这时候有token了, 使用Cookie handler来处理). SaveTokens为true表示要把从Authorization Server的Reponse中返回的token们持久化在cookie中. 注意正式生产环境要使用https, 这里就不用了. 接下来在Startup的Configure方法配置中间件, 以确保每次请求都执行authentication: public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseAuthentication(); app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } 注意在管道配置的位置一定要在useMVC之前. 在Authorization Server添加Client 在Authorization Server的InMemoryConfiguration里面添加Client: public static IEnumerable<Client> Clients() { return new[] { new Client { ClientId = "socialnetwork", ClientSecrets = new [] { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AllowedScopes = new [] { "socialnetwork" } }, new Client { ClientId = "mvc_implicit", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.Implicit, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "socialnetwork" } } }; } ClientId要和MvcClient里面指定的名称一致. OAuth是使用Scopes来划分Api的, 而OpenId Connect则使用Scopes来限制信息, 例如使用offline access时的Profile信息, 还有用户的其他细节信息. 这里GrantType要改为Implicit. 使用Implicit flow时, 首先会重定向到Authorization Server, 然后登陆, 然后Identity Server需要知道是否可以重定向回到网站, 如果不指定重定向返回的地址的话, 我们的Session有可能就会被劫持. RedirectUris就是登陆成功之后重定向的网址, 这个网址(http://localhost:5002/signin-oidc)在MvcClient里, openid connect中间件使用这个地址就会知道如何处理从authorization server返回的response. 这个地址将会在openid connect 中间件设置合适的cookies, 以确保配置的正确性. 而PostLogoutRedirectUris是登出之后重定向的网址. 有可能发生的情况是, 你登出网站的时候, 会重定向到Authorization Server, 并允许从Authorization Server也进行登出动作. 最后还需要指定OpenId Connect使用的Scopes, 之前我们指定的socialnetwork是一个ApiResource. 而这里我们需要添加的是让我们能使用OpenId Connect的SCopes, 这里就要使用Identity Resources. Identity Server带了几个常量可以用来指定OpenId Connect预包装的Scopes. 上面的AllowedScopes设定的就是我们要用的scopes, 他们包括 openid Connect和用户的profile, 同时也包括我们之前写的api resource: "socialnetwork". 要注意区分, 这里有Api resources, 还有openId connect scopes(用来限定client可以访问哪些信息), 而为了使用这些openid connect scopes, 我们需要设置这些identity resoruces, 这和设置ApiResources差不多: public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; } 然后我们需要配置Authorization Server来允许使用这些Identity Resources, Statup的: public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer() //.AddDeveloperSigningCredential() .AddSigningCredential(new X509Certificate2(@"D:\Projects\test\socialnetwork.pfx", "Bx@steel")) .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources()) .AddTestUsers(InMemoryConfiguration.Users().ToList()) .AddInMemoryClients(InMemoryConfiguration.Clients()) .AddInMemoryApiResources(InMemoryConfiguration.ApiResources()); services.AddMvc(); } 设置完了, 运行一下, 点击About菜单就会重定向到Authorization Server: 注意看URL, 我们确实是在Authorization Server. 然后输入用户名密码(TestUser的), 会看见一个请求允许的画面: 可以看到网站请求了Profile信息和User Identity. 这个时候看上面菜单处, 可以发现用户已经成功登陆了Authorization Server: 所以这就允许我们做SSO(Single Sign-On) 单点登录了. 这时候其他使用这个Authorization Server的Client应用, 由于用户已经登陆到Authorization Server了, 只需要请求用户的许可来访问用户的数据就行了. 然后点击同意 Yes Allow, 就会重定向返回MvcClient网站的About页面: 在View中显示Claims 打开MvcClient的About.cshtml: <dl> @foreach (var claim in User.Claims) { <dt>@claim.Type</dt> <dd>@claim.Value</dd> } </dl> 显示所有用户的Claims. Claims就是从Authorization Server返回的Payload里面的数据. 运行进入About页面: 嗯当前用户有这些信息.... 想要从MvcClient调用WebApi 我们现在想从MvcClient调用WebApi的api/Values节点, 这就需要使用从Authorization Server返回的token. 但是由于我们使用的是implicit flow, 而使用implicit flow, 一切数据都是被发送到Client的. 这就是说, 为了让MvcClient知道用户已经成功登陆了, Authorization Server将会告诉Client(Chrome浏览器)重定向回到MvcClient网站, 并附带着数据. 这意味着token和其他安全信息将会在浏览器里面被传递. 也就是说从Authorization Server发送access token的时候, 如果有人监听的话就会看见这些数据, 使用ssl能有效阻止监听到数据. 当然肯定有办法解决这个问题, 例如使用其他flow. 但是有时候还是必须要使用implicit flow 获取到access token. 我们需要做的就是告诉Authorization Server可以使用implicit flow来获取token. 首先我们把token显示出来: @using Microsoft.AspNetCore.Authentication <div> <strong>id_token</strong> <span>@await ViewContext.HttpContext.GetTokenAsync("id_token")</span> </div> <div> <strong>access_token</strong> <span>@await ViewContext.HttpContext.GetTokenAsync("access_token")</span> </div> <dl> @foreach (var claim in User.Claims) { <dt>@claim.Type</dt> <dd>@claim.Value</dd> } </dl> id_token是openid connect指定的, 你需要从authorization server获得它, 用来验证你的身份, 知道你已经登陆了. id_token不是你用来访问api的. access_token是用来访问api的. 运行一下: 可以看到id_token有了, 而access_token没有, 这是因为我们还没有告诉Authorization Server在使用implicit flow时可以允许返回Access token. 修改Authorization Server的Client来允许返回Access Token new Client { ClientId = "mvc_implicit", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.Implicit, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "socialnetwork" }, AllowAccessTokensViaBrowser = true } 在某种情况下还是不建议这么做. 然后在运行一下: 还是没有access token. 这是因为我们需要重新登陆来获取access token. 我们首先需要登出. 实现Logout 登出 在MvcClient的homeController添加方法: public async Task Logout() { await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); } 这里需要确保同时登出本地应用(MvcClient)的Cookies和OpenId Connect(去Identity Server清除单点登录的Session). 运行, 在浏览器输入地址:http://localhost:5002/Home/Logout 然后就会跳转到Identity Server的Logout了的页面: 这写到, 点击here可以返回到mvcclient. 点击here, 回到mvcclient, 然后点击About, 重新登陆. 同意, 重定向回来: 还是没有access token..... 看看authorization server的控制台: 有个地方写到返回类型是id_token. 这表示我们要进行的是Authentication. 而我们想要的是既做Authentication又做Authorization. 也就是说我们既要id_token还要token本身. 这么做, 在MvcClient的CongifureServices: public void ConfigureServices(IServiceCollection services) { services.AddMvc(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ClientId = "mvc_implicit"; options.ResponseType = "id_token token"; options.SaveTokens = true; }); } 然后重新运行, 退出, 重登录: 这次终于看到access_token了.... 现在就可以使用access_token访问api了. 先写到这. 明后天继续.下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

优秀的个人博客,低调大师

疯狂ios讲义之使用CoreLocation定位(4

希望iOS设备进入某个区域发出通知,那么这种区域监测的功能也被称为临近警告。所谓临近警告的示意图如图9.6所示。 图9.6临近警告的示意图 用户设备不断地临近指定固定点,当与该固定点的距离小于指定范围时,系统可以触发相应的处理。用户设备离开指定固定点,当与该固定点的距离大于指定范围时,系统也可以触发相应的处理。 iOS的区域监测同样可以使用CLLocationManager来实现,监听设备是否进入/离开某个区域的步骤如下。 创建CLLocationManager对象,该对象负责获取定位相关信息,并为该对象设置一些必要的属性。对于区域监测而言,CLLocationManager对象需要设置monitoredRegions属性,该属性值用于设置该设备监听的多个区域。 为CLLocationManager指定delegate属性,该属性值必须是一个实现CLLocationManagerDelegate协议的对象。实现CLLocationManagerDelegate协议时可根据需要实现协议中特定的方法。 调用CLLocationManager的startMonitoringForRegion:方法进行区域监测。区域监测结束时,可调用stopMonitoringForRegion:方法结束区域监测。 当设备进入指定区域时,iOS系统将会自动激发CLLocationManager的delegate对象的locationManager:didEnterRegion:方法;当设备离开指定区域时,iOS系统将会自动激发CLLocationManager的delegate对象的locationManager:didExitRegion:方法,开发者可重写这两个方法对用户进行提醒。 iOS提供了CLRegion来定义被监测的区域,但实际编程中推荐使用CLCircularRegion(CLRegion的子类)创建圆形区域,创建CLCircularRegion对象时无非就是指定圆心、半径等信息,非常简单。下面示例会进行详细示范。 新建一个SingleView Application,该应用无须修改界面设计文件,直接修改视图控制器类的实现部分来监测设备是否进入、离开某个区域。该示例的视图控制器类的实现部分代码如下。 程序清单:codes/09/9.4/RegionMonitor/RegionMonitor/FKViewController.m @interface FKViewController ()<CLLocationManagerDelegate> @property (retain,nonatomic) CLLocationManager*locationManager; @end @implementation FKViewController - (void)viewDidLoad { [superviewDidLoad]; if([CLLocationManager locationServicesEnabled]) { self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; //定义一个CLLocationCoordinate2D作为区域的圆心 CLLocationCoordinate2D companyCenter; companyCenter.latitude = 23.126272; companyCenter.longitude = 113.395568; //使用CLCircularRegion创建一个圆形区域,半径为500米 CLRegion* fkit = [[CLCircularRegionalloc] initWithCenter:companyCenter radius:500 identifier:@"fkit"]; //开始监听fkit区域 [self.locationManagerstartMonitoringForRegion:fkit]; } else { //使用警告框提醒用户 [[[UIAlertView alloc] initWithTitle:@"提醒" message:@"您的设备不支持定位" delegate:self cancelButtonTitle:@"确定"otherButtonTitles: nil] show]; } } //进入指定区域以后将弹出提示框提示用户 -(void)locationManager:(CLLocationManager*)manager didEnterRegion:(CLRegion *)region { [[[UIAlertView alloc] initWithTitle:@"区域检测提示" message:@"您已经【进入】疯狂软件教育中心区域内!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } //离开指定区域以后将弹出提示框提示用户 -(void)locationManager:(CLLocationManager*)manager didExitRegion:(CLRegion *)region { [[[UIAlertView alloc] initWithTitle:@"区域检测提示" message:@"您已经【离开】疯狂软件教育中心区域!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } @end 正如上面程序中的粗体字代码所看到的,第1行粗体字代码创建了一个CLRegion对象作为被监测区域,接下来调用CLLocationManager的startMonitoringForRegion:方法监听该设备是否进入/离开指定区域。 当该设备进入或离开指定区域时,iOS系统都会自动激发CLLocationManager的delegate对象的相应方法,上面程序重写了这两个方法对用户进行提醒。 编译、运行该程序,如果将模拟器的位置设为{23.126272, 113.395568},此时设备将会进入“疯狂软件教育中心区域内”,系统将会显示如图9.7所示的提示。 编译、运行该程序,如果将模拟器的位置设为其他位置,使之离开{23.126272, 113.395568}超过500米,此时设备将会离开“疯狂软件教育中心区域内”,系统将会显示如图9.8所示的提示。 图9.7进入区域提示 图 9.8 离开区域提示 本文转自fkJava李刚51CTO博客,原文链接:http://blog.51cto.com/javaligang/1390256,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册