前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[Element-UI表格组件全行可点击交...]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
ReactJS
...构建可复用的动画库或组件。这篇文会手把手带你揭秘如何用React这个工具,捣鼓出一套既能炫又能复用的动画库或者组件,还会送上一些实打实的例子,让你能对照着学,包你一看就懂! 二、为什么需要可复用的动画库或组件? 首先,我们需要明确为什么需要可复用的动画库或组件。随着项目的不断扩大,我们会遇到越来越多复杂的动画效果需求。要是每次我们都要从头开始编写全新的动画效果代码,那铁定会让我们的工作量蹭蹭上涨,而且这样很容易就造成代码到处都是重复的部分,维护起来也会变得相当棘手。所以,如果我们能捣鼓出一套可重复使用的动画库或者组件,那就能超级省时省力,让我们的开发工作轻松不少。而且这样一来,代码的稳定性和一致性也能妥妥地得到保障,就像给程序加了保险一样。 三、如何利用React构建可复用的动画库或组件? 接下来,我们将介绍如何利用React构建可复用的动画库或组件。具体来说,我们需要遵循以下几个步骤: 步骤一:定义动画效果 首先,我们需要定义动画效果。这一步真的超级重要,要知道,动画效果做得好不好,那可是直接关系到整个APP用起来爽不爽的关键因素!比如,我们可以设计一个超酷的淡入动画效果,想象一下这样的情景:当你轻轻一点按钮,页面上的某个元素就像被施展了魔法一样,慢慢地、优雅地从隐形状态显现出来,给你带来意想不到的视觉惊喜。 步骤二:封装动画效果 接下来,我们需要封装动画效果。在做这个操作的时候,我们可以把动画效果单独打包成一个自定义的小部件或者函数,这样一来,你在其他任何需要的地方都能随时调用它,就像从工具箱里取出小工具一样方便。比如说,我们能创建一个名叫FadeIn的组件,这个小家伙呢,会接收一个props参数,这个参数的作用可大了,就是用来告诉我们它要让哪个元素逐渐显现出来,实现淡入效果。 步骤三:使用动画效果 最后,我们需要在应用程序中使用动画效果。在这个过程中,我们可以直接调用封装好的动画效果,而不必再次编写动画效果的代码。比如说,当你点击一个按钮的时候,我们可以在那个按钮的“事件响应小助手”里头,呼唤出一个叫FadeIn的小工具,让它帮忙让某个元素像魔术般慢慢显现出来,实现淡入的效果。 四、实战演示 现在,让我们来看一下如何利用React构建一个可复用的淡入动画库或组件。首先,我们需要定义动画效果。想在React项目里实现一个淡入动画效果?这里有个小窍门。首先,我们可以巧妙地利用React那个叫做useState的小工具来掌控状态的变化。然后呢,再搭配CSS动画的魔法,就能轻松把淡入效果玩转起来,让元素如同晨雾般自然显现。以下是代码示例: javascript import React, { useState } from 'react'; import './FadeIn.css'; const FadeIn = ({ children }) => { const [show, setShow] = useState(false); return ( {children} ); }; export default FadeIn; 在上述代码中,我们首先导入了useState钩子和相关的CSS文件。接下来,我们捣鼓出了一个名叫FadeIn的组件,这个小家伙有个特性,它可以接受一个叫children的属性,这个属性呢,就是用来告诉我们它要帮哪些内容慢慢变得可见,也就是淡入进来。在咱这组件里面,我们用了一个叫做useState的小玩意儿来捯饬"show"这个状态。简单来说,就是如果"show"这小家伙的值是true,那我们就把内容亮出来给大家瞅瞅;否则的话,就把它藏起来,不让大家看到。此外,我们还添加了一个CSS类名fade-in和hidden,用于控制淡入和隐藏的效果。 接下来,我们需要在应用程序中使用动画效果。以下是一个简单的示例,我们在点击按钮时,调用FadeIn组件来淡入某个元素: javascript import React, { useState } from 'react'; import FadeIn from './FadeIn'; function App() { const [showMessage, setShowMessage] = useState(false); const handleClick = () => { setShowMessage(true); }; return ( Click me {showMessage && {message} } ); } export default App; 在上述代码中,我们首先导入了FadeIn组件和useState钩子。然后,我们定义了一个App组件,这个组件包含一个按钮和一个FadeIn组件。当按钮被点击时,我们调用setShowMessage方法来改变showMessage的状态,从而触发FadeIn组件的淡入效果。
2023-03-14 20:38:59
105
草原牧歌-t
转载文章
...面有一个Button组件(其实例名称是bt)和一个lebel组件(其实例名称是txt)。当一个用户点击Button,客户端连接到服务器;然后客户端运行服务器端的函数来返回一个字符串的值。当服务器端回应了,客户端的回应函数在label上显示字符传。客户端通过改变Button的label来断开连接。当diaconnect的按钮被点击,客户端断开连接,并且清空label。 ONE.创建用户界面 1.开启Flash CS3,然后选择新建>flash文件(ActionScript 3.0)。 2.选择窗口>组件,然后选择User Interface>Button。在属性栏里面为按钮取名bt。 3.添加一个Label组件,移动它到按钮上面,取名为txt。 保存文件为test.fla。 TWO.建立as文件。 输入以下代码: package { import flash.display.MovieClip; import flash.events.MouseEvent; import flash.events.NetStatusEvent; import flash.net.NetConnection; import flash.net.Responder; public class Main extends MovieClip { public var nc:NetConnection; public var myRespond:Responder; public function Main():void { txt.text=""; bt.label="请点击链接"; myRespond=new Responder(success,failed); bt.addEventListener(MouseEvent.CLICK,clickHandler); } private function clickHandler(e:MouseEvent) { if (bt.label=="请点击链接") { bt.label="请点击断开"; nc=new NetConnection(); nc.connect("rtmp://localhost/viniFMS"); nc.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); nc.call("sayServermsg",myRespond,"Hi"); } else { txt.text=""; bt.label="请点击链接"; nc.close(); } } private function statusHandler(e:NetStatusEvent) { if (e.info.code=="NetConnection.Connect.Success") { trace("ok"); } } private function success(result:Object) { trace("成功:"+result.toString()); txt.text=result.toString(); } private function failed(result:Object) { trace("失败:"+result.toString()); } } } 将as文件保存为Main.as 在test.fla的属性那的文档类输入Main。保存。 Three:建立通讯文件(.asc) 1.选择文件>新建>actionscript通信文件。 输入以下代码: application.onConnect=function(client){ application.acceptConnection(client); client.sayServermsg=function(msg){ return msg+",欢迎你来到FMS的世界 !"; } } 将文件保存到fms的application的文件夹下的viniFMS文件夹下,文件名为:main.asc. 确保FMS的服务已经打开,80端口没有被php等占用。 然后运行flash,点击按钮。就会有结果出现了。如下图所示。 再点击按钮。关闭连接。再点就是打开。如此循环。客户端会得到服务器端返回的 数据。 一个客户端用actionscript编码来连接到服务器,处理事件,和做其它工作。通过flash CS3你可以使用actionscript 3.0,2.0或1.0,但是actionscript3.0提供更多特性。要想使用flex,你必须使用actionscript 3.0. Actionscript3.0显著的不同于actionscript 2.0。这个向导假设你是在正在编写actionscript 3.0的类,这些类是一些外部的.as文件,有符合你的开发环境的目录结构的包的名称 转载于:https://blog.51cto.com/vini123/681426 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_33895475/article/details/91647859。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-10 18:10:29
66
转载
Kylin
Kylin的报表设计经验分享 一、引言 Kylin是一个基于Hadoop的数据仓库工具,其主要目标是提供一个快速查询分析海量数据的方式。本文将分享我在使用Kylin进行报表设计过程中的一些经验和技巧。 二、Kylin的优势 首先,让我们来了解一下Kylin的优点。Kylin在对付大数据的时候,可真是展现出了超凡的实力,为啥呢?因为它用了一种叫“多维立方体”的独门数据结构。这就像是给数据装上了一辆超级跑车,让数据访问速度嗖嗖地往上窜,效果显著到不行!另外,Kylin还特别贴心地提供了超级灵活的查询语句支持,让你能够按照自己的小心愿,随心所欲地定制SQL查询语句,这样一来,就能轻松捞到更加精确无比的结果啦! 三、如何开始 开始使用Kylin的第一步就是创建一个项目。在Kylin的网页界面里头,瞅准那个醒目的“新建项目”按钮,给它轻轻一点,接着就可以麻溜地输入你项目的响亮大名和其他一些必要的细节信息啦。接着,你需要配置你的Hadoop集群信息,包括HDFS地址、JobTracker地址等。最后,点击"提交"按钮,Kylin就会开始创建你的项目。 java // 创建一个新的Kylin项目 ClientService client = ClientService.getInstance(); ProjectMeta meta = new ProjectMeta(); meta.setName("my_project"); meta.setHiveUrl("hdfs://localhost:9000"); meta.setHiveUser("hive"); meta.setHivePasswd("hive"); client.createProject(meta); 四、数据模型设计 在Kylin中,我们通常需要对我们的数据进行建模,以便于后续的查询操作。Kylin提供了两种数据模型:维度模型和事实模型。维度模型,你把它想象成一个大大的资料夹,里面装着实体的各种详细信息,像是什么时间发生的、在哪个地点、属于哪种产品类型等等;而事实模型呢,就更像是个记账本,专门用来记录实体的各种行为表现,像卖了多少货、交易额有多少这些具体的数字信息。 java // 创建一个新的维度模型 DimensionModelDesc modelDesc = new DimensionModelDesc(); modelDesc.setName("my_dim_model"); modelDesc.setColumns(Arrays.asList(new ColumnDesc("dim_date", "date"), new ColumnDesc("dim_location", "string"))); client.createDimModel(modelDesc); // 创建一个新的事实模型 FactModelDesc factModelDesc = new FactModelDesc(); factModelDesc.setName("my_fact_model"); factModelDesc.setColumns(Arrays.asList(new ColumnDesc("fact_sales", "bigint"))); factModelDesc.setDimensions(Arrays.asList("my_dim_model")); client.createFactModel(factModelDesc); 五、报表设计与查询 接下来,我们可以开始设计我们的报表了。在Kylin这个工具里头,我们能够像平常一样用标准的SQL查询语句去查数据,然后把查出来的结果,随心所欲地转换成各种格式保存,比如说CSV啦、Excel表格什么的,超级方便。 java // 查询指定日期的销售数据 String sql = "SELECT dim_date, SUM(fact_sales) FROM my_fact_model GROUP BY dim_date"; CubeInstance cube = CubeManager.getInstance().getCube("my_cube"); List rows = cube.cubeQuery(sql); for (Row row : rows) { System.out.println(row.getString(0) + ": " + row.getLong(1)); } 六、总结 总的来说,Kylin是一个非常强大的数据分析工具,它可以帮助我们轻松地处理大量的数据,并且提供了丰富的查询功能,使得我们能够更方便地获取所需的信息。如果你也在寻找一种高效的数据分析解决方案,那么我强烈推荐你试试Kylin。
2023-05-03 20:55:52
111
冬日暖阳-t
转载文章
...解到前端框架对于数据交互和动态渲染的重要性。近年来,随着前端技术的快速发展,Angular已升级至了Angular(v2+),它采用全新的架构设计,性能更优、模块化程度更高。例如,在Angular最新版本中,HttpClient模块替代了原有的$http服务,提供了更现代化的HTTP请求处理方式,同时引入了RxJS库,增强了异步编程能力。 另外,针对分页组件的开发,Bootstrap等UI框架提供了现成且易于集成的分页组件,开发者可以通过指令或服务的方式与Angular结合使用,简化开发流程,提高用户体验。而在Angular Material等官方支持的组件库中,也有专门针对分页设计的mat-paginator组件,可实现更为丰富且灵活的分页效果,并能轻松与数据源绑定,进行实时数据更新。 此外,现代前端应用越来越注重SEO优化及服务器端渲染(SSR)。Angular Universal项目允许开发者在服务器端预渲染应用,从而提升网页加载速度和搜索引擎可见性,这对于电商类网站的商品评价列表展示场景尤其重要。 总之,虽然文章关注的是AngularJS 1.7中的具体实践,但放眼当前的技术趋势,不断学习和掌握新版Angular框架及其生态系统中的最新工具和技术,将有助于开发者更好地应对复杂多变的前端需求,高效构建出实用高效的商品评价系统和其他丰富的Web应用程序。
2023-10-12 14:36:16
72
转载
转载文章
...多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted。选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。 以上是官网对于选项的概念,简单的说,选项是一组由Vue定义好的对象,你可以将你的代码写在指定的选项中,从而获得一些 “特异功能” 。 注:由于选项是Vue规定好的,因此在使用中我们不能更改其名称,也不可以重复定义 常用选项 1. data选项 必须是一个函数,将组件需要使用的变量定义在此函数的返回值对象中,定义的变量将会获得一个“特异功能” ---- 响应式 <template><div><!-- 在这里使用插值表达式将name渲染到页面 -->{ { name } }</div></template><script>export default {// data选项data(){return{// name是响应式的name:"Jay",} },}</script> 上面例子中的name就是一个响应式数据,在值发生改变时,视图(页面)上的name也会发生变化,那我们便可以通过操作name的变化去使视图发生变化,而不用进行繁琐的DOM操作,这也体现着Vue框架的 数据驱动 这一核心思想。 为什么数据要定义在data函数的返回值中,而不是定义在一个对象中? 将数据定义在函数返回值中,可以确保每产生一个组件实例,都会调用一次函数,并返回一个新的对象,开辟一块新的空间。 如果将数据定义在对象中,可能会出现类似于浅拷贝中出现的问题,即多个组件实例指向同一块空间,一个组件实例修改数据,则全部数据发生变化。 2. methods选项 此选项是一个对象,其中存放着该组件要使用的函数,比如事件的回调函数… <template><div><!-- 添加点击事件,事件回调函数在methods中定义 --><button @click="add">点击加一</button> <p>{ { count } }</p></div></template><script>export default {data(){return{count:0,} },// 在methods中定义函数(方法)methods:{add(){// 在函数中要使用data中的变量,需加thisthis.count++},} }</script> 通过点击事件改变count的值,从而使页面上的值随之变化,再次体现 数据驱动 的核心思想 3. computed 计算属性 计算属性,对象形式,顾名思义,在计算属性中保存着一系列需要经过运算得出的属性 <template><p>路程:{ { distance } } km</p><p>速度:{ { speed } } km/h</p><!-- 使用计算属性,与变量的使用相同 --><p>花费的时间:{ { time } } h</p></template><script>export default {data() {return {distance: 1000,speed: 50,} },computed: {// 定义计算属性,类似于函数的定义,返回值就是该计算属性的值time() {return this.distance / this.speed} }}</script> 计算属性内部所依赖的数据发生变化时, 计算属性本身就会自动重新计算返回一个新的计算值并缓存起来。 计算属性内部所依赖的数据没有发生变化, 计算属性会直接返回上一次缓存的值。 因此上面例子中的distance(路程)与speed(速度)无论如何变化,time都会计算出正确的值。 4. directives 选项, 定义自定义指令( 局部指令 ) 在上节,我们学习了一些Vue内置指令,功能十分强大,那么我们可以自己定义一些指令吗? 当然可以!我们可以在directives选项中创建自定义指令。 <template><!-- 使用自定义指令 --><div v-myshow="1"></div><div v-myshow="0"></div></template><script>export default {// 在directives中定义一个自定义指令,来模仿v-show的功能directives: {//el:添加自定义指令的元素;binding:指令携带的参数myshow(el, binding) {if (binding.value) {el.style.display = "block";} else {el.style.display = "none";} }} }</script><style scoped>div {width: 100px;height: 100px;background-color: red;margin: 10px;}</style> 像以上这种,在组件中定义的指令是局部指令,只能在本组件中使用,全局指令需要在main.js文件中定义,全局指令在任何.vue文件中都可使用。 注意: 当局部指令和全局指令冲突时, 局部指令优先生效. var app = createApp(App)//定义全局指令 app.directive("myshow", (el, binding) => {if (binding.value) {el.style.display = "block";} else {el.style.display = "none";} })// 全局指令可在任何组件使用 5. components组件选项(注册局部组件) 在一个组件中我们可能会使用到其他组件,在将组件引入后,需要在components中进行注册,才能使用。 <template><!-- 使用组件 --><Test /></template><script>// 引入组件import Test from './Test.vue'export default {// 注册组件components: {Test},}</script> 局部组件只能在当前组件内部使用,需要在任何组件中使用,需要在main.js文件中注册为全局组件 // 引入组件import Test from './Test.vue'// 注册全局组件,可在所有.vue文件中使用app.component('Test',Test); 6. 其他 filters 选项, 定义过滤器,vue2中使用,Vue3中已经弃用 mounted 等生命周期函数选项,我们在下节进行详细讲解… 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_57714647/article/details/130878069。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-12-25 22:28:14
65
转载
Python
... 示例2:增加用户交互 python import math def calculate_volume(): radius = float(input("请输入半球的半径:")) volume = (2/3) math.pi (radius 3) print(f"半球的体积约为:{volume:.2f}") calculate_volume() 在这个版本中,我们增加了用户交互功能,允许用户输入半球的半径,然后程序会输出对应的体积。这儿用的是 input() 函数来抓取大伙儿的输入,然后用 print() 函数把结果弄得漂漂亮亮的,保留俩小数点,看着就顺眼。 示例3:面向对象编程 python import math class Hemisphere: def __init__(self, radius): self.radius = radius def volume(self): return (2/3) math.pi (self.radius 3) 创建半球实例 hemisphere = Hemisphere(5) print(f"半球的体积为:{hemisphere.volume():.2f}") 这个版本采用了面向对象的方法,定义了一个名为 Hemisphere 的类,该类包含一个构造函数和一个方法 volume() 来计算体积。通过这种方式,我们可以更方便地管理和操作半球的相关属性和行为。 4. 总结与反思 通过上述三个不同的示例,我们可以看到,即使是同一个问题,也可以用多种方式来解决。从最基本的函数调用,到让用户动起来的交互设计,再到酷炫的面向对象编程,每种方式都有它的独门绝技。这事儿让我明白,在编程这个圈子里,其实没有什么绝对的对错之分,最重要的是得找到最适合自己眼下情况和需要的方法。 同时,这次探索也让我深刻体会到数学与编程之间的紧密联系。很多时候,我们面对的问题不仅仅是技术上的挑战,更是对数学知识的理解和应用。希望能给你带来点灵感,不管是学Python还是别的啥,保持好奇心和爱折腾的精神可太重要了! 好了,这就是今天的内容。如果你有任何想法或疑问,欢迎随时留言讨论。让我们一起继续学习,享受编程带来的乐趣吧! --- 这篇文章旨在通过具体案例展示如何利用Python解决实际问题,同时穿插了一些个人思考和感受,希望能够符合你对于“口语化”、“情感化”的要求。希望对你有所帮助!
2024-11-19 15:38:42
113
凌波微步
Saiku
...用户名和密码,潇洒地点击登录按钮后,就仿佛拿到了打开Saiku世界大门的钥匙,接下来,你将踏上一段充满惊喜的数据探索旅程。 2. 主界面布局 登录成功后,你会看到Saiku的主界面,这里就像一个数据分析师的工作台,精心划分了多个功能区域。 - 菜单栏(1):位于页面顶部,如同烹饪中的调料架,包含了文件管理、新建报表、保存、加载等多种基本操作选项,帮助你在数据世界中导航自如。 - 工作区(2):占据页面中央的核心位置,这是你施展分析技巧的主要舞台,可以在此创建新的查询,查看并编辑现有的多维数据集,就像在画布上绘制一幅幅数据图像。 - 维度/度量区(3):位于工作区左侧,就好比你的工具箱,里面装满了各种维度(如时间、地点等分类标签)和度量(如销售额、客户数等数值指标),你可以拖拽它们至中间的查询设计面板,构建出复杂的数据视图。 - 结果展示区(4):当你完成查询设计并执行后,结果显示在右侧区域,像是一块实时更新的数据仪表盘,可能是一个表格、一张图表或者一个自定义的透视表,直观地呈现你的分析成果。 - 过滤器面板(5):有时候,你需要对全局数据进行精细化筛选,这时就可以借助过滤器面板,就如同戴上一副透视眼镜,只看你想看的那一部分数据。 3. 深度探究功能 Saiku还提供了丰富的交互式探索功能,例如,你可以在结果展示区直接对数据进行排序、筛选、钻取等操作,系统会立即响应并动态更新视图,这种即时反馈的体验犹如与数据进行一场即兴对话。 另外,Saiku支持用户自定义公式、设置计算成员以及保存个性化视图,这些高级功能仿佛为你配备了一套强大的数据处理装备,助你在浩瀚的数据海洋中挖掘出更有价值的信息。 总结来说,Saiku的界面设计以用户体验为核心,通过清晰明了的功能分区和直观易用的操作方式,让每一位用户都能轻松驾驭复杂的业务数据,享受数据驱动决策带来的乐趣与便利。这可不只是个普通工具,它更像是一个舞台,让你能和数据一起跳起探戈。每当你点击、拖拽或选择时,就像是在未知世界的版图上又踩下了一小步,离它的秘密更近一步,对它的理解也更深一层。
2023-10-04 11:41:45
102
初心未变
Kibana
...gt;“索引模式”,点击“创建索引模式”,输入索引名称logs,Kibana会自动检测字段类型并建立映射关系。 - 探索数据: 进入“发现”视图,选择我们刚才创建的logs索引模式,Kibana会展示出所有日志记录。在这里,你可以实时搜索、筛选以及初步分析数据。 3. 初步构建可视化组件 - 创建可视化图表: 进入“可视化”界面,点击“新建”,开始创建你的第一个可视化图表。例如,我们可以创建一个柱状图来展示不同HTTP方法的请求次数: a. 选择“柱状图”可视化类型。 b. 在“buckets”区域添加一个“terms”分桶,字段选择method。 c. 在“metrics”区域添加一个“计数”指标,计算每个方法的请求总数。 保存这个可视化图表,命名为“HTTP方法请求统计”。 4. 构建仪表板 - 创建仪表板: 进入“仪表板”界面,点击“新建”,创建一个新的空白仪表板。 - 添加可视化组件: 点击右上角的“添加可视化”按钮,选择我们在第3步创建的“HTTP方法请求统计”图表,将其添加至仪表板中。 - 扩展仪表板: 不止于此,我们可以继续创建其他可视化组件,比如折线图显示随着时间推移的响应时间变化,热力图展示不同路径和状态码的分布情况等,并逐一将它们添加到此仪表板上。 5. 自定义与交互性调整 Kibana的真正魅力在于其丰富的自定义能力和交互性设计。比如,你完全可以给每张图表单独设定过滤器规则,这样一来,整个仪表板上的数据就能像变魔术一样联动更新,超级炫酷。另外,你还能借助那个时间筛选器,轻轻松松地洞察到特定时间段内数据走势的变化,就像看一部数据演变的电影一样直观易懂。 在整个创建过程中,你可能会遇到疑惑、困惑,甚至挫折,但请记住,这就是探索和学习的魅力所在。随着对Kibana的理解逐渐加深,你会发现它不仅是一个工具,更是你洞察数据、讲述数据故事的强大伙伴。尽情发挥你的创造力,让数据活起来,赋予其生动的故事性和价值性。 总结来说,创建Kibana可视化仪表板的过程就像绘制一幅数据画卷,从准备画布(导入数据)开始,逐步添置元素(创建可视化组件),最后精心布局(构建仪表板),期间不断尝试、调整和完善,最终成就一份令人满意的可视化作品。在这个探索的过程中,你要像个充满好奇的小探险家一样,时刻保持对未知的热情,脑袋瓜子灵活运转,积极思考各种可能性。同时,也要有敢于动手实践的勇气,大胆尝试,别怕失败。这样下去,你肯定能在浩瀚的数据海洋中挖到那些藏得深深的宝藏,收获满满的惊喜。
2023-08-20 14:56:06
336
岁月静好
转载文章
...务逻辑的流程图,软件设计ER模板,工作流,各种云平台的系统部署架构图包括阿里云、AWS云、腾讯云、Oracle、Asure云、IBM云平台等。 使用 用户通过浏览器访问网址:https://www.freedgo.com 点击在线制图,进入图形设计工具页面即可在线制图. 选择制图不同类型的图形,请点击页面下面 + 更多图形,选择相应的制图类型。如下图: 可以绘制哪些图表UML UML统一建模语言(英语:Unified Modeling Language,缩写 UML),是一种开放的方法,用于说明、可视化、构建和编写一个正在开发的、面向对象的、软件密集系统的制品的开放方法。UML展现了一系列最佳工程实践,这些最佳实践在对大规模,复杂系统进行建模方面,特别是在软件架构层次已经被验证有效。 在UML系统开发中有三个主要的模型: 功能模型:从用户的角度展示系统的功能,包括用例图。 对象模型:采用对象,属性,操作,关联等概念展示系统的结构和基础,包括类别图、对象图。 动态模型:展现系统的内部行为。包括序列图,活动图,状态图。 通过Freedgo Desgin 可以绘制各类UML图表,包括 UML 用例图 UML 类图 UML 时序图 UML 活动图 UML 泳道图 点击页面下面 + 更多图形,选择 商务/(业务建模) -> UML, 可以设计各类UML图表, 参见下图: 数据库ER模型 ER模型是在数据库设计中常用的数据建模工具,通常是用来描述实体的信息及实体与实体之前的关系。 在Freedgo Design提供了对ER模型的支持: 通过图标库 选择ER模型绘制数据库ER模型 通过菜单 调整图形 -> 插入 -> SQL... 导入sql DDL脚本创建数据库ER模型 BPMN模型设计 BPMN是业务流程建模与标记,是用于构建业务流程图的一种建模语言标准。 可以通过图标库 选择BPMN绘制BPMN模型 Archimate设计 Archimate是一种整合多种架构的一种可视化业务分析模型语言,属于架构描述语言(ADL),它从业务、应用和技术三个层次(Layer),物件、行为和主体三个方面(Aspect)和产品、组织、流程、资讯、资料、应用、技术领域(Domain)来进行描述。 可以通过图标库 选择BPMN绘制BPMN模型 EPC设计 EPC是用于说明业务流程工作流,是进行业务工程设计的 SAP R/3 建模概念的重要组件。 可以通过图标库 选择EPC绘制EPC模型 流程图 流程图是流经一个系统的信息流、观点流或部件流的图形代表。在企业中,流程图主要用来说明某一过程。这种过程既可以是生产线上的工艺流程,也可以是完成一项任务必需的管理过程。 流程图是揭示和掌握封闭系统运动状况的有效方式。作为诊断工具,它能够辅助决策制定,让管理者清楚地知道,问题可能出在什么地方,从而确定出可供选择的行动方案。 流程图有时也称作输入-输出图。该图直观地描述一个工作过程的具体步骤。流程图对准确了解事情是如何进行的,以及决定应如何改进过程极有帮助。这一方法可以用于整个企业,以便直观地跟踪和图解企业的运作方式。 流程图使用一些标准符号代表某些类型的动作,如决策用菱形框表示,具体活动用方框表示。但比这些符号规定更重要的,是必须清楚地描述工作过程的顺序。流程图也可用于设计改进工作过程,具体做法是先画出事情应该怎么做,再将其与实际情况进行比较。 可以通过图标库 选择流程图绘制 UX设计 Freedgo Design提供一系列UX设计的制作,可以实现IOS,安卓,以及一系列页面设计的效果制图,下面简单说明:IOS android material Bootstrap 手机应用 网站应用 平面图 Freedgo Design可以绘制平面图包括建筑平面表,房屋平面表,房屋效果图设计,在图例中提供了家庭、办公、厨房、卫生间等等图例,具体可以登录在线制图网站,查看 图例 网络架构图 Freedgo Design 可以绘制各种网络拓扑图,和机架图。 云架构 Freedgo Design 提供了各类云架构的系统架构图、系统部署图,包括AWS架构,阿里云架构、腾讯云架构、IBM、ORACLE、Azure和Google云等等。AWS 阿里云架构 腾讯云架构 IBM架构 ORACLE架构 Azure架构 GOOGLE架构 工程 Freedgo Design 提供在线基本电气图设计、在线电气逻辑图设计、在线电路原理图设计、在线接线图设计 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39605997/article/details/109976987。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-03 21:03:06
105
转载
转载文章
...和异步消息处理的关键组件。在Android应用程序中,它与MessageQueue(消息队列)和Looper协同工作,允许开发者在一个线程中发送消息到另一个线程,并在目标线程的MessageQueue中排队。当Looper在指定线程中循环遍历MessageQueue时,会根据消息的时间戳调用相应Handler对象的handleMessage()方法来处理这些消息,从而实现不同线程间的交互和UI更新等操作。 Binder , Binder是Android操作系统提供的一种进程间通信(IPC, Inter-Process Communication)机制,是一种高效的、基于C/S架构的跨进程通信方式。在Android Framework层,Binder作为Android核心服务与应用程序之间的桥梁,实现了系统服务与应用之间以及应用之间的数据交换和方法调用。通过内存映射技术和引用计数管理,Binder能够高效地实现一次数据拷贝,同时确保了进程间通信的安全性。在文章中提到,Binder拥有定向制导功能,可以通过查找特定的Binder实体服务,实现跨进程或线程间的唤起与交互。 MessageQueue , MessageQueue在Android中是一个消息队列,负责存储待处理的消息(封装为Message对象)。每个启动了Looper的线程都会关联一个MessageQueue,其内部采用先进先出(FIFO)的原则对消息进行排序。主线程中的MessageQueue接收来自各种源(如触摸事件、UI刷新请求、Handler发送的消息等)的消息,并由该线程的Looper不断循环检查和处理这些消息。当MessageQueue中有新的Message到来时,Looper会将消息取出并传递给对应的Handler进行处理,这样就实现了异步消息处理机制,保证了Android应用的流畅运行及各组件间的正确同步。
2023-11-15 10:35:50
217
转载
Hadoop
...我们可以在NiFi的UI界面中创建新的流程节点,并将它们连接起来。例如,我们可以使用“GetFile”节点来读取HDFS中的数据,使用“TransformJSON”节点来处理数据,使用“PutFile”节点来将处理后的数据保存到其他位置。 三、Apache Beam简介 Apache Beam是一个开源的统一编程模型,它可以用于构建批处理和实时数据处理应用程序。这个东西的好处在于,你可以在各种不同的数据平台上跑同一套代码,这样一来,开发者们就能把更多的精力放在数据处理的核心逻辑上,而不是纠结于那些底层的繁琐细节啦。 四、Hadoop与Apache Beam集成 为了使Hadoop与Apache Beam进行集成,我们需要使用Apache Beam SDK,并将其添加到Hadoop集群中。具体步骤如下: 1. 安装Apache Beam SDK 我们可以从Apache Beam的官方网站下载最新的稳定版本,并按照官方提供的指导手册进行安装。在安装这玩意儿的时候,我们得先调好几个基础配置,就好比Beam的通讯端口、验证登录的方式这些小细节。 2. 将Apache Beam SDK添加到Hadoop集群中 为了让Apache Beam能够访问Hadoop集群中的数据,我们需要配置Beam的环境变量。首先,我们需要确定Hadoop集群的位置,然后在Beam的环境中添加以下参数: javascript export HADOOP_CONF_DIR=/path/to/hadoop/conf export HADOOP_HOME=/path/to/hadoop 3. 编写数据处理代码 接下来,我们可以编写数据处理代码,并使用Apache Beam SDK来运行它。以下是使用Apache Beam SDK处理HDFS中的数据的一个简单示例: java public class HadoopWordCount { public static void main(String[] args) throws Exception { Pipeline p = Pipeline.create(); String input = "gs://dataflow-samples/shakespeare/kinglear.txt"; TextIO.Read read = TextIO.read().from(input); PCollection words = p | read; PCollection> wordCounts = words.apply( MapElements.into(TypeDescriptors.KVs(TypeDescriptors.strings(), TypeDescriptors.longs())) .via((String element) -> KV.of(element, 1)) ); wordCounts.apply(Write.to("gs://my-bucket/output")); p.run(); } } 在这个示例中,我们首先创建了一个名为“p”的Pipeline对象,并指定要处理的数据源。然后,我们使用“TextIO.Read”方法从数据源中读取数据,并将其转换为PCollection类型。接下来,我们要用一个叫“KV.of”的小技巧,把每一条数据都变个身,变成一个个键值对。这个键呢,就是咱们平常说的单词,而对应的值呢,就是一个简简单单的1。就像是给每个单词贴上了一个标记“已出现,记1次”。最后,我们将处理后的数据保存到Google Cloud Storage中的指定位置。 五、结论 总的来说,Hadoop与Apache NiFi和Apache Beam的集成都是非常容易的。只需要按照上述步骤进行操作,并编写相应的数据处理代码即可。而且,你知道吗,Apache NiFi和Apache Beam都超级贴心地提供了灵活度爆棚的API接口,这就意味着我们完全可以按照自己的小心思,随心所欲定制咱们的数据处理流程,就像DIY一样自由自在!相信过不了多久,Hadoop和ETL工具的牵手合作将会在大数据处理圈儿掀起一股强劲风潮,成为大伙儿公认的关键趋势。
2023-06-17 13:12:22
581
繁华落尽-t
转载文章
...基础知识,就能够配合UI设计师进行项目布局开发了。 如果你是应届生,想去找实习的工作,学完这个部分,初级前端开发工程师,前端实习生等岗位,你就可以去面试了,入职后待遇能达到4K-5K。 这一步骤共分为三部分内容:HTML、CSS和页面制作工具。HTML是前端开发入门首先要学会的东西,有了它,你才可以布局页面结构。CSS是页面美化和精细化的核心技术。想要更好的完成页面的开发,更好的与UI部门合作,这些页面制作工具是必须掌握的。 第二阶段:页面布局实战 有了第一步的基础知识,你就可以实战各种页面布局了。学会后,更加夯实初级Web前端工程师水平,能够完成各种PC端与移动端网页布局与样式设计实现了。 应届生找工作会更加有底气,入职后待遇能达到6K-7K。 这一步骤共分为两部分内容:布局技术,布局规范与方案。 想要轻松的完成各种PC端和手机端的布局,这些重要的布局技术必须掌握。另外一些布局规范与布局方案,是完成浏览器兼容和各种设备适配的法宝。 第三阶段:前端开发内功 第四阶段:PC端全栈项目开发 有了JavaScript、HTML、CSS知识,再加上这个步骤的技能点学习,你就能够完成一个PC端的前后端整体项目开发了。 可以从事网站开发工程师,以及Web前端开发工程师的工作了。薪资能达到11K-13K。 这一步骤共分为四部分内容:首先学会常用的前端工具库,掌握前端工程化和模块化,然后系统学习后端,或者叫服务端开发工具 Node.js,最后你就能独立完成一个网站或者管理系统的开发了。 第五阶段,前端高级框架技术。 这个步骤是从事前端工作必须掌握的重要内容,尤其是Vue、React,已经是公司开发企业项目的首选框架。 学会这个部分,你就是一名高级Web前端工程师了,可以胜任公司的C端和B端的所有项目,薪资待遇能达到14K-18K。那这些框架都需要学习掌握什么呢? Vue框架,需要掌握Vue3和它的生态技术。掌握了Vue3的选项式API,Vue2的项目也信手拈来。Vue3生态的每个技术都包含了很多内容,都需要你掌握它并熟练应用。像Vue3的组合式API、Vite2+SFC、VueRouter4、Vuex4、Pinia2、TypeScript基础、TS+Vue3,其他的技术栈。学会这些,你就可以基于这些技术开发Vue3的C端和B端项目了。 React框架,同样需要掌握React18和它的生态技术。每个生态也都包含很多内容,像Umi技术栈、其他技术栈。React技术备受大厂青睐,一般情况下,React岗位薪资也会比Vue高些。那除了这两个主要框架还需要什么呢? Angular框架,企业用的比较少些了,基本上都是老项目的维护了。 数据可视化,可以选学,如果项目里有这块需求,可以仔细研究一下。 第六阶段,混合应用开发技术。 所谓混合开发,就是将HTML5基于浏览器的应用,嵌入到基于Android和iOS手机APP里,或者嵌入到基于Node和Chromium的桌面APP里。因为兼具了WebApp和NativeApp的双重优点,混合应用开发技术得到了广泛的应用。 学会这个部分,就拥有了多端开发能力,能够胜任跨平台跨设备的架构工作。通过Vue和React基础加持,薪资待遇能达到19K-22K。 常见的混合开发如手机端的微信公众号、微信小程序、桌面端的Electron技术和PWA技术等。 第七步,原生应用开发技术。 所谓原生应用开发,就是应用前端的技术,脱离浏览器,进行原生的手机APP的开发。 掌握这部分内容,可以达到大前端高级开发工程师水平,可以主导移动端多元产品项目实现,能够跨平台开发提出可建设性解决方案。薪资待遇能达到 23K-30K。 比如,Facebook的基于React技术的ReactNative原生APP的开发,谷歌的基于Dart技术的Flutter原生APP的开发,以及华为的基于JS技术的HarmonyOS鸿蒙原生APP的开发。 第八步,大前端架构。 这是本学习路线图最后一个步骤了,同时也到达了一个至高点。 掌握这个部分,即可拥有大前端架构师水平,主要进行前端项目架构和项目把控。能够解决网站出现的突发状况,能够改进网站性能到极致。拥有大型网站、大量高并发访问量等开发经验。这个岗位的薪资能达到30K以上的水平。 前端架构师,包含很多内容,要求有广度也要有深度,这里给出了重要的五部分内容,包括开发工具及服务器技术、前端性能、微前端架构、低代码与组件库开发以及前端安全技术。 小白起点的前端路线图,我们都走了一遍,你可能会问,这些知识我们我该如何学习呢?你可以靠查文档、看视频,也可以找个师父带你。上面给大家推荐的视频都是核心的技术点视频以及项目练手视频,更多更细节的技术点请大家关注IT千锋教育搜索你需要的课程。 本篇文章为转载内容。原文链接:https://blog.csdn.net/longz_org_cn/article/details/127673811。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-07 21:33:13
269
转载
转载文章
...a平台下用来与数据库交互的一套标准API(应用程序接口),它允许Java应用程序连接到各种类型的关系型数据库,并执行SQL语句、处理结果集等数据库操作。在自学编程的过程中,学习JDBC是为了理解如何使用Java代码实现对数据库的基本增删改查功能,它是后续学习更高级ORM框架如Mybatis的基础。 Spring框架 , Spring是一个开源的企业级Java应用程序框架,它以其轻量级、非侵入式和基于依赖注入的设计原则而广受欢迎。Spring框架提供了众多模块,包括Spring Core(核心容器)、Spring MVC(模型-视图-控制器模式实现,用于WEB开发)、Spring JDBC(对JDBC进行了封装,简化了数据库操作)等。在文章中提到的SpringMVC是Spring框架的重要组成部分,它有助于开发者构建高性能、松耦合的Web应用程序,通过整合SpringMVC与其他组件如Spring和Mybatis,可以构建出功能完善的管理系统。
2023-07-02 23:59:06
60
转载
转载文章
...、更便于管理和控制的组件的过程。它通过树状图或列表形式展现,从项目的最高层级目标开始,逐层向下细分直至最底层的具体可执行任务。在本文中,作者通过举例说明不同的任务划分方式对应的工作分解结构,探讨了其对项目沟通成本、开发效率以及团队协作的影响。 责任矩阵(Responsibility Assignment Matrix, RAM) , 在项目管理实践中,责任矩阵是一种直观展示各个工作任务与团队成员之间关系的工具,通常以表格形式存在,明确每个工作任务由谁负责(Responsible)、由谁最终承担责任(Accountable)、需要咨询谁的意见(Consulted)以及需要通知哪些相关人员(Informed),简称为RACI模型。文章中提到的责任矩阵有助于确定每个人员在完成工作分解结构中各工作细目时的角色定位,从而降低沟通成本和提高项目执行效率。 模块化设计 , 在软件工程和系统设计领域中,模块化设计是一种将复杂系统划分为一系列相互独立且功能相对集中的模块的方法。这些模块间通过清晰定义的接口进行交互,使得每个模块都能够单独开发、测试、维护和复用。文中,作者提倡采用模块化设计来优化任务分解,强调在任务划分过程中应遵循“输入什么、做什么事、输出什么”的原则,确保每个模块接口设计得当,以便于团队成员高效协作,减少重复劳动,并降低因理解误差和沟通不畅导致的成本增加。
2023-07-29 21:22:45
111
转载
转载文章
...um、PyAutoGUI等开源工具在UI自动化测试方面的应用越来越广泛。其中,PyAutoGUI作为一款基于Python的图形用户界面自动化库,不仅能够模拟鼠标和键盘操作,还支持跨平台使用,对于Windows、Mac OS X及Linux系统均能提供一致的操作接口。 与此同时,针对更复杂的交互场景如游戏或三维设计软件,一些高级模拟技术如Robot Framework、Appium也开始受到广泛关注。这些框架不仅能模拟基本的键盘鼠标输入,还能处理更精细的触屏手势操作,并能适应各种移动设备和桌面环境,极大提高了自动化测试的覆盖率和效率。 另外,在安全性方面,研究人员正不断探索如何防止恶意软件通过模拟合法用户的键盘和鼠标操作进行攻击。例如,某些安全软件已开始采用行为分析和机器学习算法来识别并阻止非人类产生的异常输入模式,确保只有真实的用户交互才能触发敏感操作。 总之,Python win32api提供的键盘鼠标模拟功能为自动化测试与脚本编写打开了新世界的大门,而结合最新的自动化测试技术和安全防护手段,我们不仅可以更高效地实现UI自动化,还能在保障用户体验的同时,有效抵御潜在的安全威胁。未来,随着相关技术的持续发展和完善,这一领域的应用场景将更加丰富多元。
2023-06-07 19:00:58
54
转载
转载文章
...含其他内容,例如数据表格、搜索表单或相关的logo图片。 我们可以使用该元素来写整个页面的标题部分: The most important heading on this page 同一个页面中,每一个内容区块都可以有自己的元素,例如: The most important heading on this page 在HTML5中,我们可以不使用div,而用更加语义化的footer来写: copyright sitemap contact to top 在同一个页面中可以使用多个元素,即可以用作页面整体的页脚,也可以作为一个内容区块的结尾,例如,我们可以将直接写在或是中: Section content appears here. Footer information for section. Article content appears here. Footer information for article. nav -- 作用 -- 导航栏 nav nav元素是一个可以用来作为页面导航的链接组;其中的导航元素链接到其他页面或当前页面的其他部分。并不是所有的链接组都要被放进元素;例如,在页脚中通常会有一组链接,包括服务条款、首页、版权声明等;这时使用元素是最恰当的,而不需要元素。 一直以来,我们都习惯用如下这种方式来定义导航条: Home About Blog 下面是W3C给出的一个代码示例: The Wiki Center Of Exampland Home Current Events ...more... Demos in Exampland Written by A. N. Other. Public demonstrations Demolitions ...more... Public demonstrations ...more... Demolitions ...more... ...more... Edit | Delete | Rename © copyright 1998 Exampland Emperor 关键自li,em,dl,ul,ol,footer,header,nav,aside,article section 版块 用于划分页面上的不同区域,或者划分文章里不同的节 header 页面头部或者版块(section)头部 footer 页面底部或者(section)底部 nav 导航 (包含链接 ... html5新特性-header,nav,footer,aside,article,section等各元素的详解 Html5新增了27个元素,废弃了16个元素,根据现有的标准规范,把HTML5的元素按优先级定义为结构性属性.级块性元素.行内语义性元素和交互性元素四大类. 下面是对各标签的详解,section.he ... h5中的结构元素header、nav、article、aside、section、footer介绍 结构元素不具有任何样式,只是使页面元素的的语义更加明确. header元素 header元素是一种具有引导和导航作用的的结构元素,该元素可以包含所有通常放在页面头部的内容.header元素通常用来放置 ... html5,html5教程 html5,html5教程 1.向后兼容 HTML5是这样被定义的:能向后兼容目前UA处理内容的方式.为了让语言更简单,一些老的元素和Attribute被舍弃.比如一些纯粹用于展现的元素(译注:即非语 ... 一步HTML5教程学会体系 HTML5是HTML最新的版本,万维网联盟. HTML5是下一代的HTML标准,HTML5是为了在移动设备上支持多媒体. 新特性: 绘画的canvas元素,用于媒介回放的video和audio元素,对 ... IT兄弟连 HTML5教程 了解HTML5的主流应用1 在很多人眼里,HTML5与互联网营销密切相关,但其实从开发者的角度而言,它是一种网页标准,定义了浏览器语言的编写规范.伴随HTML5标准尘埃落定,浏览器对HTML5特性的逐步支持,再加上国内对HTML ... 【转帖】39个让你受益的HTML5教程 39个让你受益的HTML5教程 闲话少说,本文作者为大家收集了网上学习HTML5的资源,期望它们可以帮助大家更好地学习HTML5. 好人啊! 不过,作者原来说的4 ... 【特别推荐】Web 开发人员必备的经典 HTML5 教程 对于我来说,Web 前端开发是最酷的职业之一,因为你可以用新的技术发挥,创造出一些惊人的东西.唯一的问题是,你需要跟上这个领域的发展脚步,因此,你必须不断的学习,不断的前进.本文将分享能够帮助您快速掌 ... HTML5教程之本地存储SessionStorage SessionStorage: 将数据保存在session对象中,所谓session是指用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间会话,也就是用户浏览这个网站所花费的时间就是sess ... 随机推荐 【转】MySQL索引背后的数据结构及算法原理 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BT ... IIS7 / IIS7.5 URL 重写 HTTP 重定向到 HTTPS(转) 转自: http://www.cnblogs.com/yipu/p/3880518.html 1.购买SSL证书,参考:http://www.cnblogs.com/yipu/p/3722135. ... OpenGL的glViewPort窗口设置函数实现分屏 之前实现过全景图片查看(OpenGL的几何变换3之内观察全景图),那么我们需要进行分屏该如何实现呢?如下图: 没错就是以前提过的glViewPort函数,废话不多说了,我直接上代码: //从这里开始进 ... hdu 4764 Stone (巴什博弈,披着狼皮的羊,小样,以为换了身皮就不认识啦) 今天(2013/9/28)长春站,最后一场网络赛! 3~5分钟后有队伍率先发现伪装了的签到题(博弈) 思路: 与取石头的巴什博弈对比 题目要求第一个人取数字在[1,k]间的某数x,后手取x加[1,k] ... android报表图形引擎(AChartEngine)demo解析与源码 AchartEngine支持多种图表样式,本文介绍两种:线状表和柱状表. AchartEngine有两种启动的方式:一种是通过ChartFactory.getView()方式来直接获取到view ... CSS长度单位及区别 em ex px pt in 1. css相对长度单位 Ø em 元素的字体高度 Ø ex 字体x的高度 Ø px ... es6的箭头函数 1.使用语法 : 参数 => 函数语句; 分为以下几种形式 : (1) ()=>语句 ( )=> statement 这是一种简写方法省略了花括号和return 相当于 ()=&g ... pdfplumber库解析pdf格式 参考地址:https://github.com/jsvine/pdfplumber 简单的pdf转换文本: import pdfplumber with pdfplumber.open(path) a ... KMP替代算法——字符串Hash 很久以前写的... 今天来谈谈一种用来替代KMP算法的奇葩算法--字符串Hash 例题:给你两个字符串p和s,求出p在s中出现的次数.(字符串长度小于等于1000000) 字符串的Hash 根据字面意 ... SSM_CRUD新手练习(5)测试mapper 上一篇我们使用逆向工程生成了所需要的bean.dao和对应的mapper.xml文件,并且修改好了我们需要的数据库查询方法. 现在我们来测试一下DAO层,在test包下新建一个MapperTest.j ... 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_35666639/article/details/118169985。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-16 11:42:34
252
转载
转载文章
...t扩展一个firstElement属性: var 反编译后的java代码如下: public static final Object getFirstElement(@NotNull List $this$firstElement) { 内部类 kotlin的内部类与java的内部类有点不同java的内部类可以直接访问外部类的成员,kotlin的内部类不能直接访问外部类的成员,必须用inner标记之后才能访问外部类的成员 没有使用inner标记的内部类 class A{ 反编译后的java代码 public 用inner标记的内部类 class A{ 反编译后的java代码 public 从上面可以看出,没有使用inner标记的内部类最后生成的是静态内部类,而使用inner标记的生成的是非静态内部类 匿名内部类 匿名内部类主要是针对那些获取抽象类或者接口对象而来的。最常见的匿名内部类View点击事件: //java,匿名内部类的写法 上面这个是java匿名内部类的写法,kotlin没有new关键字,那么kotlin的匿名内部类该怎么写呢? object : View.OnClickListener{ 方法的参数是一个匿名内部类,先写object:,然后写你的参数类型View.OnClickListener{} kotlin还有一个写法lambda 表达式,非常之方便: print( 数据类 在Java中没有专门的数据类,常常是通过JavaBean来作为数据类,但在Kotlin中提供了专门的数据类。 Java public 从上面的例子中可以看到,如果要使用数据类,需要手动写相应的setter/getter方法(尽管IDE也可以批量生成),但是从代码阅读的角度来说,在属性较多的情况下,诸多的seeter/getter方法还是不利于代码的阅读和维护。 Kotlin 在Kotlin中,可以通过关键字data来生成数据类: data 即在class关键字之前添加data关键字即可。编译器会根据主构造函数中的参数生成相应的数据类。自动生成setter/getter、toString、hashCode等方法 要声明一个数据类,需要满足: 主构造函数中至少有一个参数 主构造函数中所有参数需要标记为val或var 数据类不能是抽象、开发、密封和内部的 枚举类 枚举类是一种特殊的类,kotlin可以通过enum class关键字定义枚举类。 枚举类可以实现0~N个接口; 枚举类默认继承于kotlin.Enum类(其他类最终父类都是Any),因此kotlin枚举类不能继承类; 非抽象枚举类不能用open修饰符修饰,因此非抽象枚举类不能派生子类; 抽象枚举类不能使用abstract关键字修饰enum class,抽象方法和抽象属性需要使用; 枚举类构造器只能使用private修饰符修饰,若不指定,则默认为private; 枚举类所有实例在第一行显式列出,每个实例之间用逗号隔开,整个声明以分号结尾; 枚举类是特殊的类,也可以定义属性、方法、构造器; 枚举类应该设置成不可变类,即属性值不允许改变,这样更安全; 枚举属性设置成只读属性后,最好在构造器中为枚举类指定初始值,如果在声明时为枚举指定初始值,会导致所有枚举值(或者说枚举对象)的该属性都一样。 定义枚举类 / 定义一个枚举类 / 枚举类实现接口 枚举值分别实现接口的抽象成员 enum 枚举类统一实现接口的抽象成员 enum 分别实现抽象枚举类抽象成员 enum 委托 委托模式 是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承。 Java中委托: interface Printer { Kotlin: interface Printer { by表示 p 将会在 PrintImpl 中内部存储, 并且编译器将自动生成转发给 p 的所有 Printer 的方法。 委托属性 有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括: 延迟属性(lazy properties): 其值只在首次访问时计算; 可观察属性(observable properties): 监听器会收到有关此属性变更的通知; 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。 为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性 。 委托属性的语法是: var : 在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。 标准委托: Kotlin 标准库为几种有用的委托提供了工厂方法。 延迟属性 Lazy lazy() 接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。例如: val lazyValue: String 可观察属性 Observable Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值: class User { 如果想拦截赋的新值,并根据你是不是想要这个值来决定是否给属性赋新值,可以使用 vetoable() 取代 observable(),接收的参数和 observable 一样,不过处理程序 返回值是 Boolean 来决定是否采用新值,即在属性被赋新值生效之前 会调用传递给 vetoable 的处理程序。例如: class User { 把属性存在map 中 一个常见的用例是在一个映射(map)里存储属性的值。这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。 例如: class User(map: Map 在上例中,委托属性会从构造函数传入的map中取值(通过字符串键——属性的名称),如果遇到声明的属性名在map 中找不到对应的key 名,或者key 对应的value 值的类型与声明的属性的类型不一致,会抛出异常。 内联函数 当一个函数被声明为inline时,它的函数体是内联的,也就是说,函数体会被直接替换到函数被调用地方 inline函数(内联函数)从概念上讲是编译器使用函数实现的真实代码来替换每一次的函数调用,带来的最直接的好处就是节省了函数调用的开销,而缺点就是增加了所生成字节码的尺寸。基于此,在代码量不是很大的情况下,我们是否有必要将所有的函数定义为内联?让我们分两种情况进行说明: 将普通函数定义为内联:众所周知,JVM内部已经实现了内联优化,它会在任何可以通过内联来提升性能的地方将函数调用内联化,并且相对于手动将普通函数定义为内联,通过JVM内联优化所生成的字节码,每个函数的实现只会出现一次,这样在保证减少运行时开销的同时,也没有增加字节码的尺寸;所以我们可以得出结论,对于普通函数,我们没有必要将其声明为内联函数,而是交给JVM自行优化。 将带有lambda参数的函数定义为内联:是的,这种情况下确实可以提高性能;但在使用的过程中,我们会发现它是有诸多限制的,让我们从下面的例子开始展开说明: inline 假如我们这样调用doSomething: fun main(args: Array<String>) { 上面的调用会被编译成: fun main(args: Array<String>) { 从上面编译的结果可以看出,无论doSomething函数还是action参数都被内联了,很棒,那让我们换一种调用方式: fun main(args: Array<String>) { 上面的调用会被编译成: fun main(args: Array<String>) { doSomething函数被内联,而action参数没有被内联,这是因为以函数型变量的形式传递给doSomething的lambda在函数的调用点是不可用的,只有等到doSomething被内联后,该lambda才可以正常使用。 通过上面的例子,我们对lambda表达式何时被内联做一下简单的总结: 当lambda表达式以参数的形式直接传递给内联函数,那么lambda表达式的代码会被直接替换到最终生成的代码中。 当lambda表达式在某个地方被保存起来,然后以变量形式传递给内联函数,那么此时的lambda表达式的代码将不会被内联。 上面对lambda的内联时机进行了讨论,消化片刻后让我们再看最后一个例子: inline 上面的例子是否有问题?是的,编译器会抛出“Illegal usage of inline-parameter”的错误,这是因为Kotlin规定内联函数中的lambda参数只能被直接调用或者传递给另外一个内联函数,除此之外不能作为他用;那我们如果确实想要将某一个lambda传递给一个非内联函数怎么办?我们只需将上述代码这样改造即可: inline 很简单,在不需要内联的lambda参数前加上noinline修饰符就可以了。 以上便是我对内联函数的全部理解,通过掌握该特性的运行机制,相信大家可以做到在正确的时机使用该特性,而非滥用或因恐惧弃而不用。 Kotlin下单例模式 饿汉式实现 //Java实现 懒汉式 //Java实现 上述代码中,我们可以发现在Kotlin实现中,我们让其主构造函数私有化并自定义了其属性访问器,其余内容大同小异。 如果有小伙伴不清楚Kotlin构造函数的使用方式。请点击 - - - 构造函数 不清楚Kotlin的属性与访问器,请点击 - - -属性和字段 线程安全的懒汉式 //Java实现 大家都知道在使用懒汉式会出现线程安全的问题,需要使用使用同步锁,在Kotlin中,如果你需要将方法声明为同步,需要添加@Synchronized注解。 双重校验锁式 //Java实现 哇!小伙伴们惊喜不,感不感动啊。我们居然几行代码就实现了多行的Java代码。其中我们运用到了Kotlin的延迟属性 Lazy。 Lazy内部实现 public 观察上述代码,因为我们传入的mode = LazyThreadSafetyMode.SYNCHRONIZED, 那么会直接走 SynchronizedLazyImpl,我们继续观察SynchronizedLazyImpl。 Lazy接口 SynchronizedLazyImpl实现了Lazy接口,Lazy具体接口如下: public 继续查看SynchronizedLazyImpl,具体实现如下: SynchronizedLazyImpl内部实现 private 通过上述代码,我们发现 SynchronizedLazyImpl 覆盖了Lazy接口的value属性,并且重新了其属性访问器。其具体逻辑与Java的双重检验是类似的。 到里这里其实大家还是肯定有疑问,我这里只是实例化了SynchronizedLazyImpl对象,并没有进行值的获取,它是怎么拿到高阶函数的返回值呢?。这里又涉及到了委托属性。 委托属性语法是:val/var : by 。在 by 后面的表达式是该 委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性)。 而Lazy.kt文件中,声明了Lazy接口的getValue扩展函数。故在最终赋值的时候会调用该方法。 internal.InlineOnly 静态内部类式 //Java实现 静态内部类的实现方式,也没有什么好说的。Kotlin与Java实现基本雷同。 补充 在该篇文章结束后,有很多小伙伴咨询,如何在Kotlin版的Double Check,给单例添加一个属性,这里我给大家提供了一个实现的方式。(不好意思,最近才抽出时间来解决这个问题) class SingletonDemo private constructor( 其中关于?:操作符,如果 ?: 左侧表达式非空,就返回其左侧表达式,否则返回右侧表达式。请注意,当且仅当左侧为空时,才会对右侧表达式求值。 Kotlin 智能类型转换 对于子父类之间的类型转换 先看这样一段 Java 代码 public 尽管在 main 函数中,对 person 这个对象进行了类型判断,但是在使用的时候还是需要强制转换成 Student 类型,这样是不是很不智能? 同样的情况在 Kotlin 中就变得简单多了 fun main(args: Array<String>) { 在 Kotlin 中,只要对类型进行了判断,就可以直接通过父类的对象去调用子类的函数了 安全的类型转换 还是上面的那个例子,如果我们没有进行类型判断,并且直接进行强转,会怎么样呢? public static void main(String[] args) { 结果就只能是 Exception in thread "main" java.lang.ClassCastException 那么在 Kotlin 中是不是会有更好的解决方法呢? val person: Person = Person() 在转换操作符后面添加一个 ?,就不会把程序 crash 掉了,当转化失败的时候,就会返回一个 null 在空类型中的智能转换 需要提前了解 Kotlin 类型安全的相关知识(Kotlin 中的类型安全(对空指针的优化处理)) String? = aString 在定义的时候定义成了有可能为 null,按照之前的写法,我们需要这样写 String? = 但是已经进行了是否为 String 类型的判断,所以就一定 不是 空类型了,也就可以直接输出它的长度了 T.()->Unit 、 ()->Unit 在做kotlin开发中,经常看到一些系统函数里,用函数作为参数 public .()-Unit与()->Unit的区别是我们调用时,在代码块里面写this,的时候,两个this代表的含义不一样,T.()->Unit里的this代表的是自身实例,而()->Unit里,this代表的是外部类的实例。 推荐阅读 对 Kotlin 与 Java 编程语言的思考 使用 Kotlin 做开发一个月后的感想 扫一扫 关注我的公众号如果你想要跟大家分享你的文章,欢迎投稿~ 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39611037/article/details/109984124。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-23 23:56:14
470
转载
转载文章
...加入体验阵营。 请 点击访问此网站 了解支持本次体验的全部设备,以及如何在这些设备上安装 Android P Beta。想要为 Android P Beta 开发应用,请 点击访问此网站。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 以机器学习为核心,打造 “更加智能的” 智能手机 Android P 让智能手机可以学习用户、适应用户,从而使 “智能” 更为智能。现在您的应用可以借助本地硬件中的机器学习成果,来触达更多受众,并为他们提供前所未有的体验。 · 动态电量管理 (Adaptive Battery) 无论用户们使用的是何种手机,电量一直都是他们最为关心问题。在 Android P 中,我们和 DeepMind 合作推出了一个全新功能,即动态电量管理 (Adaptive Battery),来优化各个应用的电量使用。 动态电量管理通过机器学习来管理用户们最关心的那些应用所能占用的系统资源。各个应用会被划分到四个不同的群组里,这些群组对系统资源调用有不同的限制,我们称之为 “应用待机群组 (App Standby buckets)”。随着用户的使用,应用会在这四个群组里切换,那些不在 “活跃 (active)” 组里的应用在包括任务 (jobs)、警报、网络以及高优先级的 Firebase Cloud Messages 等资源调用上会受到相应的限制。 如果您的应用已经针对 Doze, App Standby 和后台运行限制做过优化,那么它就应该已经能和动态电量管理完美配合。我们建议您在四个应用待机群组中都对自己的 app 进行测试,请阅读相关文档了解详情。 · App Actions 当用户想要做一个操作的时候,App Actions 会推荐能帮助他们完成这个操作的 app,而且这个推荐的功能会覆盖整个操作系统中的重要交互环节,比如启动器 (Launcher)、智能文本选择、Google Play、Google Search 应用,以及 Assistant。 App Actions 通过机器学习来分析用户最近的行为或使用场景,从而筛选出需要推荐的应用。由于这些推荐与用户当前想要做的事情高度关联,所以这套机制非常利于拓展新用户以及促活现有用户。 只需将您应用中的各个功能定义为语义意图 (semantic intent),便可以充分享受 App Actions 带来的好处。App Actions 中的意图和我们早些时候在 Google Assistant 上推出的语音对谈式动作 (Conversational Action) 是使用同一套通用意图分类,这个分类支持语音控制的音箱、智能屏幕、车载系统、电视、耳机等设备。由于不需要额外的 API 接口,所以只要用户的 Android 平台版本支持,App Actions 就可以正常使用了。 App Actions 很快就会面向开发者发布,如果您希望收到这方面的通知,请点击这里找到相关链接参与订阅。 · Slices 和 App Actions 一同到来的新功能还有 Slices,这个功能可以让您的应用以模块化、富交互的形式插入到多个使用场景中,比如 Google Search 和 Assistant。Slices 支持的交互包括 actions、开关、滑动条、滑动内容等等。 Slices 是让内容与用户联系的极佳方式,所以我们希望它可以在更多的场景中出现。除了在 Android P 上对这个功能进行了平台级别的整合外, Slices 的 API 和模板也加入到了 Android Jetpack 里。Android Jetpack 是我们全新打造的一套创建优秀应用的工具和库,通过 Android Jetpack,您制作的 Slices 能在 Kitkat (API 等级 19) 及更高版本上使用 —— 这覆盖了 95% 的已激活 Android 设备。我们也会定期更新 Slices 的模板来支持更多类型的场景和交互 (比如文本输入)。 请查阅上手指南以了解如何制作 Slices,使用 SliceViewer 工具查看您做好的 Slices。接下来,我们计划进一步拓展其使用场景,包括在其他 app 中展现您的 Slices。 · 通知智能回复 (Smart reply in notifications) 机器智能可以为用户体验带来非常积极的进化,Gmail 和 Inbox 里的智能回复功能已经成功地证明了这一点。在 Android P 中,通知消息也加入了智能回复功能,而且我们准备了 API 让您可以为用户带来更度身的使用感受。用来帮助您更轻松地在通知中生成回复的 ML Kit 很快就会到来,请 点击访问此网站 了解详情。 · 文本识别 (Text Classifier) 在 Android P 中,我们将识别文本的机器学习模型进行了扩展,使得它可以识别出诸如日期或航班号这样的信息,并通过 TextClassifier API 来让开发者使用到这些改进。我们还更新了 Linkify API 来利用文本识别的结果生成链接,并为用户提供了更多点击后的选项,从而让他们得以更快地进行下一步操作。当然,开发者也可以在给文本识别出来的信息添加链接时拥有更多的选项。智能 Linkify 在识别精准度以及速度上都有明显的提升。 这个模型现在正在通过 Google Play 进行更新,所以您的应用使用现有的 API 就可以享受到本次更新所带来的变化。在安装更新完的模型后,设备即可直接在本地识别文本里的各种信息,而且这些识别出来的信息只保存在您的手机上而不会通过网络流传出去。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 简洁 (Simplicity) 在 Android P,我们格外强调简洁,并据此改进 Android 的 UI 从而帮助用户们更流畅、更高效地完成操作。对开发者来说,简洁的系统则会帮助用户更容易查找、使用和管理您的应用。 · 全新系统导航 (New system navigation) 我们为 Android P 设计了全新的系统导航,只需使用下图中这个在所有界面中都能看到的小按钮,即可更轻松地访问手机主屏、概览页以及 Assistant。新导航系统也使多任务切换及发现关联应用变得更加简单。在概览页,用户可以拥有更大的视野来查看他们之前中断的操作,这自然也会让他们更容易找到并回到之前的应用中。概览页也提供了搜索、预测推荐应用以及上文提到的 App Actions,而且只需再多划一次即可进入所有应用的列表。 · 文字放大镜 (Text Magnifier) 在 Android P 中,我们加入了新的放大镜工具 (Magnifier widget),使选择文本和调整光标位置变得更加轻松。默认情况下,所有继承自 TextView 的类都会自动支持放大镜,但您也可以使用放大镜 API 将它添加到任何自定义的视图上,从而打造更多样化的体验。 · 后台限制 (Background restrictions) 用户可以更加简单地找到并管理那些在后台消耗电量的应用。通过 Android Vitals 积累下来的成果,Android 可以识别那些过度消耗电量的行为,如滥用唤醒锁定等。在 Android P 中,电池设置页面直接列出了这些过度消耗电量的应用,用户只需一次点击就可以限制它们在后台的活动。 一旦应用被限制,那么它的后台任务、警报、服务以及网络访问都会受限。想要避免被限制的话,请留意 Play Console 中的Android Vitals 控制面板,帮助您了解如何提高性能表现以及优化电量消耗。 后台限制能有效保护系统资源不被恶意消耗,从而确保开发者的应用在不同制造商的不同设备上也能拥有一个基础的合理的运行环境。虽然制造商可以在限制列表上额外添加限制的应用,但它们也必须在电池设置页面为用户开放这些限制的控制权。 我们添加了一个标准 API 来帮助应用知晓自己是否被限制,以及一个 ADB 命令来帮助开发者手动限制应用,从而进行测试。具体请参阅相关文档。接下来我们计划在 Play Console 的 Android Vitals 控制面板里添加一个统计数据,以展示应用受到限制的情况。 · 使用动态处理增强音频 (Enhanced audio with Dynamics Processing) Android P 在音频框架里加入了动态处理效果 (Dynamic Processing Effect) 来帮助开发者改善声音品质。通过动态处理,您可以分离出特定频率的声音,降低过大的音量,或者增强那些过小的音量。举例来说,即便说话者离麦克风较远,而且身处嘈杂或者被刺耳的各种环境音包围的地方,您的应用依然可以有效分离并增强他/她的细语。 动态处理 API 提供了多声场、多频段的动态处理效果,包括一个预均衡器、一个多频段压缩器,一个后均衡器以及一个串联的音量限制器。这样您就可以根据用户的喜好或者环境的变化来控制 Android 设备输出的声音。频段数量以及各个声场的开关都完全可控,大多数参数都支持实时控制,如增益、信号的压缩/释放 (attack/release) 时长,阈值等等。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 安全 (Security) · 用户识别提示 (Biometric prompt) Android P 为市面上涌现出来的各种用户识别机制在系统层面提供了统一的使用体验,应用们不再需要自行提供用户识别操作界面,而只需要使用统一的 BiometricPrompt API 即可。这套全新的 API 替代了 DP1 版本中的 FingerprintDialog API,且支持包括指纹识别 (包括屏幕下指纹识别)、面部识别以及虹膜识别,而且所有系统支持的用户识别需求都包含在一个 USE_BIOMETRIC 权限里。FingerprintManager 以及对应的 USE_FINGERPRINT 权限已经被废弃,请开发者尽快转用 BiometricPrompt。 · 受保护的确认操作 (Protected Confirmation) Android P 新增了受保护的确认操作 (Android Protected Confirmation),这个功能使用可信执行环境 (Trusted Execution Environment, TEE) 来确保一个显示出来的提示文本被真实用户确认。只有在用户确认之后,TEE 才会放行这个文本并可由应用去验证。 · 对私有密钥的增强保护 (Stronger protection for private keys) 我们添加了一个新的 KeyStore 类型,StrongBox。并提供对应的 API 来支持那些提供了防入侵硬件措施的设备,比如独立的 CPU,内存以及安全存储。您可以在 KeyGenParameterSpec 里决定您的密钥是否该交给 StrongBox 安全芯片来保存。 Android P Beta 为用户带来新版本的 Android 需要 Google、芯片供应商以及设备制造商和运营商的共同努力。这个过程中充满了技术挑战,并非一日之功 —— 为了让这个过程更加顺畅,去年我们启动了 Project Treble,并将其包含在 Android Oreo 中。我们与合作伙伴们一直在努力开发这个项目,也已经看到 Treble 所能带来的机遇。 我们宣布,以下 6 家顶级合作伙伴将和我们一起把 Android P Beta 带给全世界的用户,这些设备包括:索尼 Xperia XZ2, 小米 Mi Mix 2S, 诺基亚 7 Plus, Oppo R15 Pro, Vivo X21UD 和 X21, 以及 Essential PH‑1。此外,再加上 Pixel 2, Pixel 2 XL, Pixel 和 Pixel XL,我们希望来自世界各地的早期体验者以及开发者们都能通过这些设备体验到 Android P Beta。 您可查看今天推送的文章查阅支持 beta 体验的合作伙伴和 Pixel 设备清单,并能看到每款设备的详细配置说明。如果您使用 Pixel 设备,现在就可以加入 Android Beta program,然后自动获得最新的 Android P Beta。 马上开始在您喜欢的设备上体验 Android P Beta 吧,欢迎您向我们反馈意见和建议!并请继续关注 Project Treble 的最新动态。 确保 app 兼容 随着越来越多的用户开始体验 Android P Beta,是时候开始测试您 app 的兼容性,以尽早解决在测试中发现的问题并尽快发布更新。请查看迁移手册了解操作步骤以及 Android P 的时间推进表。 请从 Google Play 下载您的应用,并在运行 Android P Beta 的设备或模拟器上测试用户流程。确保您的应用体验良好,并正确处理 Android P 的行为变更。尤其注意动态电量管理、Wi-Fi 权限变化、后台调用摄像头以及传感器的限制、针对应用数据的 SELinux 政策、默认启用 TLS 的变化,以及 Build.SERIAL 限制。 · 公开 API 的兼容性 (Compatibility through public APIs) 针对非 SDK 接口的测试十分重要。正如我们之前所强调的,在 Android P 中,我们将逐渐收紧一些非 SDK 接口的使用,这也要求广大的开发者们,包括 Google 内部的应用团队,使用公开 API。 如果您的应用正在使用私有 Android API 或者库,您需要改为使用 Android SDK 或 NDK 公开的 API。我们在 DP1 里已经对使用私有接口的开发者发出了警告信息,从 Android P Beta 开始,调用非 SDK 接口将会报错 (部分被豁免的私有 API 除外) —— 也就是说您的应用将会遭遇异常,而不再只是警告了。 为了帮助您定位非 SDK API 的使用情况,我们在 StrictMode 里加入了两个新的方法。您可以使用 detectNonSdkApiUsage() 在应用通过反射或 JNI 调用非 SDK API 的时候收到警报,您还可以使用 permitNonSdkApiUsage() 来阻止 StrictMode 针对这些调用报错。这些方法都可助您了解应用调用非 SDK API 的情况,但请注意,即便调用的 API 暂时得到了豁免,最保险的做法依然是尽快放弃对它们的使用。 如果您确实遇到了公开 API 无法满足需求的情况,请立刻告知我们。更多详细内容请查看相关文档。 · 凹口屏测试 (Test with display cutout) 针对凹口屏测试您的应用也十分重要。现在您可以在运行 Android P Beta 的合作伙伴机型上测试,确保您的应用在凹口屏上表现良好。同时,您也可以在 Android P 设备的开发者选项里打开对凹口屏的模拟,对您的应用做相应测试。 体验 Android P 在准备好开发条件后,请深入了解 Android P 并学习可以在您的应用中使用到的全新功能和 API。为了帮助您更轻松地探索和使用新 API,请查阅 API 变化报告 (API 27->DP2, DP1->DP2) 以及 Android P API 文档。访问开发者预览版网站了解详情。 下载/更新 Android P 开发者预览版 SDK 和工具包至 Android Studio 3.1,或使用最新版本的 Android Studio 3.2。如果您手边没有 Android P Beta 设备 (或查看今天推送的次条文章),请使用 Android P 模拟器来运行和测试您的应用。 您的反馈一直都至关重要,我们欢迎您畅所欲言。如果您在开发或测试过程中遇到了问题,请在文章下方留言给我们。再次感谢大家一路以来的支持。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_34258782/article/details/87952581。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-10 18:19:36
338
转载
转载文章
...避免因ActiveX组件导致的各种安全问题。 为解决B/S打印中的痛点,我工作室开发了本报表服务器,完美地解决了在浏览器端不用ActiveX而获得与C/S系统一样的打印能力。 本报表系统不需要在浏览器安装任何插件,只需通过JavaScript即可实现报表精确打印以及打印过程免人工介入。 ------------- 二、特点: 1、高兼容:不需要在浏览器端和服务端安装任何插件,在浏览器插件被各大浏览器纷纷禁用的今天,无插件设计兼容绝大多数浏览器; 2、免安装:软件即拷即用,不安装,不污染操作系统,让操作系统历久弥新; 3、可视化:可视化的模板设计器,通过拖拽即可完成模板设计; 4、高精度:实现精确到毫米的打印精度,对于一些格式复杂,要求精确打印的场合,可以很容易达到毫米级精度; 5、易套打:可视化的模板设计器,在模板中加入一个票据格式的底图,可以很方便地实现套打,对于实现发票、快递面单、支票等打印毫无压力; 6、功能强:从简单报表、主从报表到嵌套报表甚至交叉报表,均能轻松应对。还有一维二维条形码,甚至,还有逆天的脚本功能,只有想不到,没有做不到; 7、自动化: 打印过程中全部自动化,无需象生成PDF、Word、Excel那样还需要人工再点打印; 8、易部署:打印模板既可以部署在客户端(与 cfprint.exe 程序放在同一目录下),也支持部署在服务端随报表数据一起传到客户端; 9、目标活:支持在数据文件中或模板中指定要输出的打印机,发票用针打、报表用激光打、小票用小票机,专机专打; 三、使用前提条件: 1、IE6以上版本、Chrome(谷歌浏览器)4.0以上版本、Firefox 4.0以上版本、Opera 11以上版本、Safari 5.0.2以上版本、iOS 4.2以上版本 或使用Chrome内核、Firefox内核的浏览器均可直接使用本打印系统; 2、在进行打印前,需要先设计好打印模板(模板设计器请见第五节); 3、打印数据必须Json的格式发送给打印服务器,并且数据必须满足指定的格式(见下文); 四、数据格式说明: 下面以一个跨境电商快递面单数据为例解释一下数据各项的含义; { "template": "waybill.fr3", /打印模板文件名。除了指定模板文件以外,还支持把模板嵌入到数据文件中,以实现在服务器端灵活使用打印模板,格式如下:/ /"template": "base64:QTBBRTNEQTE3MkFFQjIzNEFERD<后面省略>" / "ver": 4, /数据模板文件版本/ "Copies": 3, /打印份数,支持指定打印份数/ "Duplex": 1, /是否双面打印,0:默认,不双面,1:垂直,2:水平,3:单面打印(simplex)/ "Printer": "priPrinter", /指定打印机,本系统支持在数据文件中指定打印机,也支持在打印模板中指定打印机/ "PageNumbers": "", /要打印的页码范围,同打印机的打印设置里的格式相同,例如:"1,2,3"表示打印前3页, “2-5”:表示打印第2到5页,“1,2,4-8”表示打印第1、2、4到8页/ "Preview": 1, /是否预览,跟主界面上选择“预览”效果相同,取值为0:不预览,1:预览/ "Tables":[ /数据表数组/ { "Name": "Table1", /表名/ "Cols": [ /字段定义/ { "type": "str", /字段类型,可选值:String,Str,Integer,Int,Smallint,Float,Long, Blob,/ /对于图片、PDF等使用Blob类型,并把值进行Base64编码,并加前缀:/ / "base64/pdf:" 字段值是PDF; "base64/jpg:" 字段值是jpg; "base64/png:" 字段值是png; "base64/gif:" 字段值是gif; / "size": 255, /字段长度/ "name": "HAWB", /字段名称,必须与打印模板中的打印项名称相同/ "required": false /字段是否必填/ }, { "type": "int", "size": 0, "name": "NO", "required": false }, { "type": "float", "size": 0, "name": "报关公司面单号", "required": false }, { "type": "integer", "size": 0, "name": "公司内部单号", "required": false }, { "type": "str", "size": 255, "name": "发件人", "required": false }, { "type": "str", "size": 255, "name": "发件人地址", "required": false }, { "type": "str", "size": 255, "name": "发件人电话", "required": false }, { "type": "str", "size": 255, "name": "发货国家", "required": false }, { "type": "str", "size": 255, "name": "收件人", "required": false }, { "type": "str", "size": 255, "name": "收件人地址", "required": false }, { "type": "str", "size": 255, "name": "收件人电话", "required": false }, { "type": "str", "size": 255, "name": "收货人证件号码", "required": false }, { "type": "str", "size": 255, "name": "收货省份", "required": false }, { "type": "float", "size": 0, "name": "总计费重量", "required": false }, { "type": "int", "size": 0, "name": "总件数", "required": false }, { "type": "float", "size": 0, "name": "申报总价(CNY)", "required": false }, { "type": "float", "size": 0, "name": "申报总价(JPY)", "required": false }, { "type": "int", "size": 0, "name": "件数1", "required": false }, { "type": "str", "size": 255, "name": "品名1", "required": false }, { "type": "float", "size": 0, "name": "单价1(JPY)", "required": false }, { "type": "str", "size": 255, "name": "单位1", "required": false }, { "type": "float", "size": 0, "name": "申报总价1(CNY)", "required": false }, { "type": "float", "size": 0, "name": "申报总价1(JPY)", "required": false }, { "type": "int", "size": 0, "name": "件数2", "required": false }, { "type": "str", "size": 255, "name": "品名2", "required": false }, { "type": "float", "size": 0, "name": "单价2(JPY)", "required": false }, { "type": "str", "size": 255, "name": "单位2", "required": false }, { "type": "float", "size": 0, "name": "申报总价2(CNY)", "required": false }, { "type": "float", "size": 0, "name": "申报总价2(JPY)", "required": false }, { "type": "AutoInc", "size": 0, "name": "ID", "required": false }, { "type": "blob", "size": 0, "name": "附件", "required": false } ], "Data": [ /数据行定义,每一行含义见上面的字段定义/ { "HAWB": "860014010055", "NO": 1, "报关公司面单号": 200303900791, "公司内部单号": 730293, "发件人": "NAKAGAWA SUMIRE 2", "发件人地址": " 991-199-113,Kameido,Koto-ku,Tokyo", "发件人电话": "03-3999-3999", "发货国家": "日本", "收件人": "张三丰", "收件人地址": "上海市闵行区虹梅南路1660弄蔷薇八村99号9999室", "收件人电话": "182-1234-8888", "收货人证件号码": null, "收货省份": null, "总计费重量": 3.2, "总件数": 13, "申报总价(CNY)": null, "申报总价(JPY)": null, "件数1": 10, "品名1": "纸尿片", "单价1(JPY)": null, "单位1": null, "申报总价1(CNY)": null, "申报总价1(JPY)": null, "件数2": null, "品名2": null, "单价2(JPY)": null, "单位2": null, "申报总价2(CNY)": null, "申报总价2(JPY)": null, "ID": 1, "附件": "base64/pdf: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IDI3OCAyNzgKMjc4IDI3OCAyNzggMjc4IDI3OCAyNzggMjc4IDI3OCAyNzggMjc4IDI3OCAyNzggMjc4IDI3OCAyNzggMjc4CjI3OCA1NTYgNTU2IDMzMyA1NTYgNTU2IDU1NiA1NTYgMjc4IDY2NyAyNzggMjc4IDI3OCAyNzggMjc4IDY2NwoyNzggNjY3IDI3OCAyNzggMjc4IDI3OCAyNzggNjY3IDI3OCA2NjcgMjc4IDY2NyAyNzggNjY3IDI3OCAyNzgKMjc4IDY2NyAyNzggNjY3IDU1MiAyNzggMjc4IDI3OCAyNzggNTU2IDI3OCA1NTYgMjc4IDI3OCAyNzggNjY3CjI3OCA2NjcgMjc4IDI3OCAyNzggNjY3IDI3OCA2NjcgMjc4IDY2NyAyNzggNjY3IDI3OCA2NjcgMjc4IDI3OF0KL0VuY29kaW5nL1dpbkFuc2lFbmNvZGluZwovRm9udERlc2NyaXB0b3IgMTEgMCBSCj4+CmVuZG9iagoxMSAwIG9iago8PAovVHlwZS9Gb250RGVzY3JpcHRvcgovRm9udE5hbWUgL0FyaWFsTVQKL0ZsYWdzIDMyCi9Gb250QkJveFstNjY1IC0zMjUgMjAwMCAxMDA2XQovU3RlbVYgOTUKL0l0YWxpY0FuZ2xlIDAKL0NhcEhlaWdodCA5MDUKL0FzY2VudCA5MDUKL0Rlc2NlbnQgLTIxMgo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZS9QYWdlcwovQ291bnQgMQovS2lkc1s0IDAgUl0KPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUvQ2F0YWxvZwovUGFnZXMgMyAwIFIKL1BhZ2VMYXlvdXQvU2luZ2xlUGFnZQovVmlld2VyUHJlZmVyZW5jZXMgMTIgMCBSCj4+CmVuZG9iagoxMiAwIG9iago8PAovVHlwZS9WaWV3ZXJQcmVmZXJlbmNlcwo+PgplbmRvYmoKeHJlZgowIDEzCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxNiAwMDAwMCBuDQowMDAwMDA0MjEzIDAwMDAwIG4NCjAwMDAwMDQxNTggMDAwMDAgbg0KMDAwMDAwMDcxNiAwMDAwMCBuDQowMDAwMDAwMjQxIDAwMDAwIG4NCjAwMDAwMDA4NzIgMDAwMDAgbg0KMDAwMDAwMjkyNyAwMDAwMCBuDQowMDAwMDAxMjQ1IDAwMDAwIG4NCjAwMDAwMDEwNTUgMDAwMDAgbg0KMDAwMDAwMTU2MiAwMDAwMCBuDQowMDAwMDAzOTg5IDAwMDAwIG4NCjAwMDAwMDQzMTAgMDAwMDAgbg0KdHJhaWxlcgo8PAovU2l6ZSAxMwovSW5mbyAxIDAgUgovUm9vdCAyIDAgUgovSURbPDVBMkU0QzkzOTdENEU0RDE3NkIwOTBDRUU3OTMxMzRGPjw1QTJFNEM5Mzk3RDRFNEQxNzZCMDkwQ0VFNzkzMTM0Rj5dCj4+CnN0YXJ0eHJlZgo0MzU2CiUlRU9GCg==", }, { "HAWB": "860014010035", "NO": 2, "报关公司面单号": 200303900789, "公司内部单号": 730291, "发件人": "NAKAGAWA SUMIRE", "发件人地址": " 991-199-113,Kameido,Koto-ku,Tokyo", "发件人电话": "03-3999-3999", "发货国家": "日本", "收件人": "张无忌", "收件人地址": "上海市闵行区虹梅南路1660弄蔷薇八村88号8888室", "收件人电话": "182-1234-8888", "收货人证件号码": null, "收货省份": null, "总计费重量": 3.2, "总件数": 13, "申报总价(CNY)": null, "申报总价(JPY)": null, "件数1": 10, "品名1": "纸尿片", "单价1(JPY)": null, "单位1": null, "申报总价1(CNY)": null, "申报总价1(JPY)": null, "件数2": null, "品名2": null, "单价2(JPY)": null, "单位2": null, "申报总价2(CNY)": null, "申报总价2(JPY)": null, "ID": 2, "附件":"base64/gif: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" } ] }, { "Name": "Table2", "Cols": [ { "type": "int", "size": 0, "name": "NO", "required": false }, { "type": "float", "size": 0, "name": "订单编号", "required": false }, { "type": "integer", "size": 0, "name": "下单日期", "required": false }, { "type": "str", "size": 255, "name": "下单平台", "required": false } ], "Data": [ { "NO": 1, "订单编号": 200303900791, "下单日期": "2017-01-20", "下单平台": "天猫" }, { "NO": 2, "订单编号": 200303900792, "下单日期": "2017-01-20", "下单平台": "京东" } ] } ] } 五、调用示例: <!-- ★★★ 模式1 ★★★ --> <!DOCTYPE html> <head> <meta charset="utf-8" /> <title>康虎云报表系统测试</title> </head> <body> <div style="width: 100%;text-align:center;"> <h2>康虎云报表系统</h2> <h3>打印测试(模式1)</h3> <div> <input type="button" id="btnPrint" value="打印" onClick="doSend(_reportData);" /> </div> </div> <div id="output"></div> </body> <script type="text/javascript"> //定义数据脚本 var _reportData = '{"template":"waybill.fr3","Cols":[{"type":"str","size":255,"name":"HAWB","required":false},<这里省略1000字> ]}'; //在浏览器控制台输出调试信息 console.log("reportData = " + _reportData); </script> <script language="javascript" type="text/javascript" src="cfprint.min.js"></script> <script language="javascript" type="text/javascript" src="cfprint_ext.js"></script> <script language="javascript" type="text/javascript"> /下面四个参数必须放在myreport.js脚本后面,以覆盖myreport.js中的默认值/ var _delay_send = 1000; //发送打印服务器前延时时长,-1则表示不自动打印 var _delay_close = 1000; //打印完成后关闭窗口的延时时长, -1则表示不关闭 var cfprint_addr = "127.0.0.1"; //打印服务器监听地址 var cfprint_port = 54321; //打印服务器监听端口 </script> </html> <!-- ★★★ 模式2 ★★★ --> <?php //如果有php运行环境,只需把该文件扩展名改成 .php,然后上传到web目录即可在真实服务器上测试 header("Access-Control-Allow-Origin: "); ?> <!DOCTYPE html> <head> <meta charset="utf-8" /> <title>康虎云报表系统测试</title> <style type="text/css"> output {font-size: 12px; background-color:F0FFF0;} </style> </head> <body> <div style="width: 100%;text-align:center;"> <h2>康虎云报表系统(Ver 1.3.0)</h2> <h3>打印测试(模式2)</h3> <div style="line-height: 1.5;"> <div style="width: 70%; text-align: left;"> <b>一、首先按下列步骤设置:</b><br/> 1、运行打印服务器;<br/> 2、按“停止”按钮停止服务;<br/> 3、打开“设置”区;<br/> 4、在“常用参数-->服务模式”中,选择“模式2”;<br/> 5、按“启动”按钮启动服务。 </div> <div style="width: 70%; text-align: left;"> <b>二、按本页的“打印”按钮开始打印。</b><br/> </div><br/> <input type="button" id="btnPrint" value="打印" /><br/><br/> <div style="width: 70%; text-align: left; font-size: 12px;"> 由于JavaScript在不同域名下访问会出现由来已久的跨域问题,所以正式部署到服务器使用时,要解决跨域问题。<br/> 对于IE8以上版本浏览器,只需增加一个reponse头:Access-Control-Allow-Origin即可,而对于php、jsp、asp/aspx等动态语言而言,增加一个response头是非常简单的事,例如:<br/> <b>在php:</b><br/><span style="color: red;"> <?php <br/> header("Access-Control-Allow-Origin: ");<br/> ?><br/> </span> <b>在jsp:</b><br/><span style="color: red;"> <% <br/> response.setHeader("Access-Control-Allow-Origin", ""); <br/> %><br/> </span> <b>在asp.net中:</b><br/><span style="color: red;"> Response.AppendHeader("Access-Control-Allow-Origin", ""); </span>,<br/>其他语言里,大家请自行搜索“ajax跨域”。而对于IE8以下的浏览器,大家可以自行搜索“IE6+Ajax+跨域”寻找解决办法吧,也可以联系我们帮助。 </div> </div> </div> <div id="output"></div> </body> <!-- 引入模式2所需的javascript支持库 --> <script type="text/javascript" src="cfprint_mode2.min.js" charset="UTF-8"></script> <!-- 构造报表数据 --> <script type="text/javascript"> var _reportData = '{"template":"waybill.fr3","ver":3, "Tables":[ {"Name":"Table1", "Cols":[{"type":"str","size":255,"name":"HAWB","required":false},{"type":"int","size":0,"name":"NO","required":false},{"type":"float","size":0,"name":"报关公司面单号","required":false},{"type":"integer","size":0,"name":"公司内部单号","required":false},{"type":"str","size":255,"name":"发件人","required":false},{"type":"str","size":255,"name":"发件人地址","required":false},{"type":"str","size":255,"name":"发件人电话","required":false},{"type":"str","size":255,"name":"发货国家","required":false},{"type":"str","size":255,"name":"收件人","required":false},{"type":"str","size":255,"name":"收件人地址","required":false},{"type":"str","size":255,"name":"收件人电话","required":false},{"type":"str","size":255,"name":"收货人证件号码","required":false},{"type":"str","size":255,"name":"收货省份","required":false},{"type":"float","size":0,"name":"总计费重量","required":false},{"type":"int","size":0,"name":"总件数","required":false},{"type":"float","size":0,"name":"申报总价(CNY)","required":false},{"type":"float","size":0,"name":"申报总价(JPY)","required":false},{"type":"int","size":0,"name":"件数1","required":false},{"type":"str","size":255,"name":"品名1","required":false},{"type":"float","size":0,"name":"单价1(JPY)","required":false},{"type":"str","size":255,"name":"单位1","required":false},{"type":"float","size":0,"name":"申报总价1(CNY)","required":false},{"type":"float","size":0,"name":"申报总价1(JPY)","required":false},{"type":"int","size":0,"name":"件数2","required":false},{"type":"str","size":255,"name":"品名2","required":false},{"type":"float","size":0,"name":"单价2(JPY)","required":false},{"type":"str","size":255,"name":"单位2","required":false},{"type":"float","size":0,"name":"申报总价2(CNY)","required":false},{"type":"float","size":0,"name":"申报总价2(JPY)","required":false},{"type":"int","size":0,"name":"件数3","required":false},{"type":"str","size":255,"name":"品名3","required":false},{"type":"float","size":0,"name":"单价3(JPY)","required":false},{"type":"str","size":255,"name":"单位3","required":false},{"type":"float","size":0,"name":"申报总价3(CNY)","required":false},{"type":"float","size":0,"name":"申报总价3(JPY)","required":false},{"type":"int","size":0,"name":"件数4","required":false},{"type":"str","size":255,"name":"品名4","required":false},{"type":"float","size":0,"name":"单价4(JPY)","required":false},{"type":"str","size":255,"name":"单位4","required":false},{"type":"float","size":0,"name":"申报总价4(CNY)","required":false},{"type":"float","size":0,"name":"申报总价4(JPY)","required":false},{"type":"int","size":0,"name":"件数5","required":false},{"type":"str","size":255,"name":"品名5","required":false},{"type":"float","size":0,"name":"单价5(JPY)","required":false},{"type":"str","size":255,"name":"单位5","required":false},{"type":"float","size":0,"name":"申报总价5(CNY)","required":false},{"type":"float","size":0,"name":"申报总价5(JPY)","required":false},{"type":"str","size":255,"name":"参考号","required":false},{"type":"AutoInc","size":0,"name":"ID","required":false}],"Data":[{"公司内部单号":730293,"发货国家":"日本","单价1(JPY)":null,"申报总价2(JPY)":null,"单价4(JPY)":null,"申报总价2(CNY)":null,"申报总价5(JPY)":null,"报关公司面单号":200303900791,"申报总价5(CNY)":null,"收货人证件号码":null,"申报总价1(JPY)":null,"单价3(JPY)":null,"申报总价1(CNY)":null,"申报总价4(JPY)":null,"申报总价4(CNY)":null,"收件人电话":"182-1758-9999","收件人地址":"上海市闵行区虹梅南路1660弄蔷薇八村139号502室","HAWB":"860014010055","发件人电话":"03-3684-9999","发件人地址":" 1-1-13,Kameido,Koto-ku,Tokyo","NO":3,"ID":3,"单价2(JPY)":null,"申报总价3(JPY)":null,"单价5(JPY)":null,"申报总价3(CNY)":null,"收货省份":null,"申报总价(JPY)":null,"申报总价(CNY)":null,"总计费重量":3.20,"收件人":"张三丰2","总件数":13,"品名5":null,"品名4":null,"品名3":null,"品名2":null,"品名1":"纸尿片","参考号":null,"发件人":"NAKAGAWA SUMIRE 2","单位5":null,"单位4":null,"单位3":null,"单位2":null,"单位1":null,"件数5":null,"件数4":null,"件数3":3,"件数2":null,"件数1":10},{"公司内部单号":730291,"发货国家":"日本","单价1(JPY)":null,"申报总价2(JPY)":null,"单价4(JPY)":null,"申报总价2(CNY)":null,"申报总价5(JPY)":null,"报关公司面单号":200303900789,"申报总价5(CNY)":null,"收货人证件号码":null,"申报总价1(JPY)":null,"单价3(JPY)":null,"申报总价1(CNY)":null,"申报总价4(JPY)":null,"申报总价4(CNY)":null,"收件人电话":"182-1758-9999","收件人地址":"上海市闵行区虹梅南路1660弄蔷薇八村139号502室","HAWB":"860014010035","发件人电话":"03-3684-9999","发件人地址":" 1-1-13,Kameido,Koto-ku,Tokyo","NO":1,"ID":1,"单价2(JPY)":null,"申报总价3(JPY)":null,"单价5(JPY)":null,"申报总价3(CNY)":null,"收货省份":null,"申报总价(JPY)":null,"申报总价(CNY)":null,"总计费重量":3.20,"收件人":"张三丰","总件数":13,"品名5":null,"品名4":null,"品名3":null,"品名2":null,"品名1":"纸尿片","参考号":null,"发件人":"NAKAGAWA SUMIRE","单位5":null,"单位4":null,"单位3":null,"单位2":null,"单位1":null,"件数5":null,"件数4":null,"件数3":3,"件数2":null,"件数1":10},{"公司内部单号":730292,"发货国家":"日本","单价1(JPY)":null,"申报总价2(JPY)":null,"单价4(JPY)":null,"申报总价2(CNY)":null,"申报总价5(JPY)":null,"报关公司面单号":200303900790,"申报总价5(CNY)":null,"收货人证件号码":null,"申报总价1(JPY)":null,"单价3(JPY)":null,"申报总价1(CNY)":null,"申报总价4(JPY)":null,"申报总价4(CNY)":null,"收件人电话":"182-1758-9999","收件人地址":"上海市闵行区虹梅南路1660弄蔷薇八村139号502室","HAWB":"860014010045","发件人电话":"03-3684-9999","发件人地址":" 1-1-13,Kameido,Koto-ku,Tokyo","NO":2,"ID":2,"单价2(JPY)":null,"申报总价3(JPY)":null,"单价5(JPY)":null,"申报总价3(CNY)":null,"收货省份":null,"申报总价(JPY)":null,"申报总价(CNY)":null,"总计费重量":3.20,"收件人":"张无忌","总件数":13,"品名5":null,"品名4":null,"品名3":null,"品名2":null,"品名1":"纸尿片","参考号":null,"发件人":"NAKAGAWA SUMIRE 1","单位5":null,"单位4":null,"单位3":null,"单位2":null,"单位1":null,"件数5":null,"件数4":null,"件数3":3,"件数2":null,"件数1":10}]}]}'; if(window.console) console.log("reportData = " + _reportData); </script> <!-- 设置服务器参数 --> <script language="javascript" type="text/javascript"> var cfprint_addr = "127.0.0.1"; //打印服务器监听地址 var cfprint_port = 54321; //打印服务器监听端口 var _url = "http://"+cfprint_addr+":"+cfprint_port; </script> <!-- 编写回调函数用以处理服务器返回的数据 --> <script type="text/javascript"> / 参数: readyState: XMLHttpRequest的状态 httpStatus: 服务端返回的http状态 responseText: 服务端返回的内容 / var callbackSuccess = function(readyState, httpStatus, responseText){ if (httpStatus === 200) { //{"result": 1, "message": "打印完成"} var response = CFPrint.parseJSON(responseText); alert(response.message+", 状态码["+response.result+"]"); }else{ alert('打印失败,HTTP状态代码是:'+httpStatus); } } / 参数: message: 错误信息 / var callbackFailed = function(message){ alert('发送打印任务出错: ' + message); } </script> <!-- 调用发送打印请求功能 --> <script type="text/javascript"> (function(){ document.getElementById("btnPrint").onclick = function() { CFPrint.outputid = "output"; //指定调试信息输出div的id CFPrint.SendRequest(_url, _reportData, callbackSuccess, callbackFailed); //发送打印请求 }; })(); </script> </html> 六、模板设计器(重要!重要!!,好多朋友都找不到设计器入口) 在主界面上,双击右下角的“设计”两个字,即可打开模板设计工具箱,在工具箱有三个按钮和一个大文本框。三个按钮的作用分别是: 设计:以大文本框中的json数据为数据源,打开模板设计器窗口; 预览:以大文本框中的json数据为数据源,预览当前所用模板的打印效果; 打印:以大文本框中的json数据为数据源,向打印机输出当前所用模板生成的报表; 以后将会有详细的模板设计教程发布,如果您遇到紧急的难题,请向作者咨询。 本篇文章为转载内容。原文链接:https://blog.csdn.net/chensongmol/article/details/76087600。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-01 18:34:12
234
转载
转载文章
...tity has built-in support for Microsoft, Facebook, and Twitter accounts as well. Table 15-1 summarizes this chapter. 本章将完成对ASP.NET Identity的描述,向你展示它所提供的一些高级特性。我将演示,你可以扩展ASP.NET Identity的数据库架构,其办法是在用户类上定义一些自定义属性。也会演示如何使用数据库迁移,这样可以运用自定义属性,而不必删除ASP.NET Identity数据库中的数据。还会解释ASP.NET Identity如何支持声明(Claims)概念,并演示如何将它们灵活地用来对动作方法进行授权访问。最后向你展示ASP.NET Identity很容易通过第三方部件来认证用户,以此结束本章以及本书。将要演示的是使用Google账号认证,但ASP.NET Identity对于Microsoft、Facebook以及Twitter账号,都有内建的支持。表15-1是本章概要。 Table 15-1. Chapter Summary 表15-1. 本章概要 Problem 问题 Solution 解决方案 Listing 清单号 Store additional information about users. 存储用户的附加信息 Define custom user properties. 定义自定义用户属性 1–3, 8–11 Update the database schema without deleting user data. 更新数据库架构而不删除用户数据 Perform a database migration. 执行数据库迁移 4–7 Perform fine-grained authorization. 执行细粒度授权 Use claims. 使用声明(Claims) 12–14 Add claims about a user. 添加用户的声明(Claims) Use the ClaimsIdentity.AddClaims method. 使用ClaimsIdentity.AddClaims方法 15–19 Authorize access based on claim values. 基于声明(Claims)值授权访问 Create a custom authorization filter attribute. 创建一个自定义的授权过滤器注解属性 20–21 Authenticate through a third party. 通过第三方认证 Install the NuGet package for the authentication provider, redirect requests to that provider, and specify a callback URL that creates the user account. 安装认证提供器的NuGet包,将请求重定向到该提供器,并指定一个创建用户账号的回调URL。 22–25 15.1 Preparing the Example Project 15.1 准备示例项目 In this chapter, I am going to continue working on the Users project I created in Chapter 13 and enhanced in Chapter 14. No changes to the application are required, but start the application and make sure that there are users in the database. Figure 15-1 shows the state of my database, which contains the users Admin, Alice, Bob, and Joe from the previous chapter. To check the users, start the application and request the /Admin/Index URL and authenticate as the Admin user. 本章打算继续使用第13章创建并在第14章增强的Users项目。对应用程序无需做什么改变,但需要启动应用程序,并确保数据库中有一些用户。图15-1显示了数据库的状态,它含有上一章的用户Admin、Alice、Bob以及Joe。为了检查用户,请启动应用程序,请求/Admin/Index URL,并以Admin用户进行认证。 Figure 15-1. The initial users in the Identity database 图15-1. Identity数据库中的最初用户 I also need some roles for this chapter. I used the RoleAdmin controller to create roles called Users and Employees and assigned the users to those roles, as described in Table 15-2. 本章还需要一些角色。我用RoleAdmin控制器创建了角色Users和Employees,并为这些角色指定了一些用户,如表15-2所示。 Table 15-2. The Types of Web Forms Code Nuggets 表15-2. 角色及成员(作者将此表的标题写错了——译者注) Role 角色 Members 成员 Users Alice, Joe Employees Alice, Bob Figure 15-2 shows the required role configuration displayed by the RoleAdmin controller. 图15-2显示了由RoleAdmin控制器所显示出来的必要的角色配置。 Figure 15-2. Configuring the roles required for this chapter 图15-2. 配置本章所需的角色 15.2 Adding Custom User Properties 15.2 添加自定义用户属性 When I created the AppUser class to represent users in Chapter 13, I noted that the base class defined a basic set of properties to describe the user, such as e-mail address and telephone number. Most applications need to store more information about users, including persistent application preferences and details such as addresses—in short, any data that is useful to running the application and that should last between sessions. In ASP.NET Membership, this was handled through the user profile system, but ASP.NET Identity takes a different approach. 我在第13章创建AppUser类来表示用户时曾做过说明,基类定义了一组描述用户的基本属性,如E-mail地址、电话号码等。大多数应用程序还需要存储用户的更多信息,包括持久化应用程序爱好以及地址等细节——简言之,需要存储对运行应用程序有用并且在各次会话之间应当保持的任何数据。在ASP.NET Membership中,这是通过用户资料(User Profile)系统来处理的,但ASP.NET Identity采取了一种不同的办法。 Because the ASP.NET Identity system uses Entity Framework to store its data by default, defining additional user information is just a matter of adding properties to the user class and letting the Code First feature create the database schema required to store them. Table 15-3 puts custom user properties in context. 因为ASP.NET Identity默认是使用Entity Framework来存储其数据的,定义附加的用户信息只不过是给用户类添加属性的事情,然后让Code First特性去创建需要存储它们的数据库架构即可。表15-3描述了自定义用户属性的情形。 Table 15-3. Putting Cusotm User Properties in Context 表15-3. 自定义用户属性的情形 Question 问题 Answer 回答 What is it? 什么是自定义用户属性? Custom user properties allow you to store additional information about your users, including their preferences and settings. 自定义用户属性让你能够存储附加的用户信息,包括他们的爱好和设置。 Why should I care? 为何要关心它? A persistent store of settings means that the user doesn’t have to provide the same information each time they log in to the application. 设置的持久化存储意味着,用户不必每次登录到应用程序时都提供同样的信息。 How is it used by the MVC framework? 在MVC框架中如何使用它? This feature isn’t used directly by the MVC framework, but it is available for use in action methods. 此特性不是由MVC框架直接使用的,但它在动作方法中使用是有效的。 15.2.1 Defining Custom Properties 15.2.1 定义自定义属性 Listing 15-1 shows how I added a simple property to the AppUser class to represent the city in which the user lives. 清单15-1演示了如何给AppUser类添加一个简单的属性,用以表示用户生活的城市。 Listing 15-1. Adding a Property in the AppUser.cs File 清单15-1. 在AppUser.cs文件中添加属性 using System;using Microsoft.AspNet.Identity.EntityFramework;namespace Users.Models { public enum Cities {LONDON, PARIS, CHICAGO}public class AppUser : IdentityUser {public Cities City { get; set; } }} I have defined an enumeration called Cities that defines values for some large cities and added a property called City to the AppUser class. To allow the user to view and edit their City property, I added actions to the Home controller, as shown in Listing 15-2. 这里定义了一个枚举,名称为Cities,它定义了一些大城市的值,另外给AppUser类添加了一个名称为City的属性。为了让用户能够查看和编辑City属性,给Home控制器添加了几个动作方法,如清单15-2所示。 Listing 15-2. Adding Support for Custom User Properties in the HomeController.cs File 清单15-2. 在HomeController.cs文件中添加对自定义属性的支持 using System.Web.Mvc;using System.Collections.Generic;using System.Web;using System.Security.Principal;using System.Threading.Tasks;using Users.Infrastructure;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.Owin;using Users.Models;namespace Users.Controllers {public class HomeController : Controller {[Authorize]public ActionResult Index() {return View(GetData("Index"));}[Authorize(Roles = "Users")]public ActionResult OtherAction() {return View("Index", GetData("OtherAction"));}private Dictionary<string, object> GetData(string actionName) {Dictionary<string, object> dict= new Dictionary<string, object>();dict.Add("Action", actionName);dict.Add("User", HttpContext.User.Identity.Name);dict.Add("Authenticated", HttpContext.User.Identity.IsAuthenticated);dict.Add("Auth Type", HttpContext.User.Identity.AuthenticationType);dict.Add("In Users Role", HttpContext.User.IsInRole("Users"));return dict;} [Authorize]public ActionResult UserProps() {return View(CurrentUser);}[Authorize][HttpPost]public async Task<ActionResult> UserProps(Cities city) {AppUser user = CurrentUser;user.City = city;await UserManager.UpdateAsync(user);return View(user);}private AppUser CurrentUser {get {return UserManager.FindByName(HttpContext.User.Identity.Name);} }private AppUserManager UserManager {get {return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();} }} } I added a CurrentUser property that uses the AppUserManager class to retrieve an AppUser instance to represent the current user. I pass the AppUser object as the view model object in the GET version of the UserProps action method, and the POST method uses it to update the value of the new City property. Listing 15-3 shows the UserProps.cshtml view, which displays the City property value and contains a form to change it. 我添加了一个CurrentUser属性,它使用AppUserManager类接收了表示当前用户的AppUser实例。在GET版本的UserProps动作方法中,传递了这个AppUser对象作为视图模型。而在POST版的方法中用它更新了City属性的值。清单15-3显示了UserProps.cshtml视图,它显示了City属性的值,并包含一个修改它的表单。 Listing 15-3. The Contents of the UserProps.cshtml File in the Views/Home Folder 清单15-3. Views/Home文件夹中UserProps.cshtml文件的内容 @using Users.Models@model AppUser@{ ViewBag.Title = "UserProps";}<div class="panel panel-primary"><div class="panel-heading">Custom User Properties</div><table class="table table-striped"><tr><th>City</th><td>@Model.City</td></tr></table></div> @using (Html.BeginForm()) {<div class="form-group"><label>City</label>@Html.DropDownListFor(x => x.City, new SelectList(Enum.GetNames(typeof(Cities))))</div><button class="btn btn-primary" type="submit">Save</button>} Caution Don’t start the application when you have created the view. In the sections that follow, I demonstrate how to preserve the contents of the database, and if you start the application now, the ASP.NET Identity users will be deleted. 警告:创建了视图之后不要启动应用程序。在以下小节中,将演示如何保留数据库的内容,如果现在启动应用程序,将会删除ASP.NET Identity的用户。 15.2.2 Preparing for Database Migration 15.2.2 准备数据库迁移 The default behavior for the Entity Framework Code First feature is to drop the tables in the database and re-create them whenever classes that drive the schema have changed. You saw this in Chapter 14 when I added support for roles: When the application was started, the database was reset, and the user accounts were lost. Entity Framework Code First特性的默认行为是,一旦修改了派生数据库架构的类,便会删除数据库中的数据表,并重新创建它们。在第14章可以看到这种情况,在我添加角色支持时:当重启应用程序后,数据库被重置,用户账号也丢失。 Don’t start the application yet, but if you were to do so, you would see a similar effect. Deleting data during development is usually not a problem, but doing so in a production setting is usually disastrous because it deletes all of the real user accounts and causes a panic while the backups are restored. In this section, I am going to demonstrate how to use the database migration feature, which updates a Code First schema in a less brutal manner and preserves the existing data it contains. 不要启动应用程序,但如果你这么做了,会看到类似的效果。在开发期间删除数据没什么问题,但如果在产品设置中这么做了,通常是灾难性的,因为它会删除所有真实的用户账号,而备份恢复是很痛苦的事。在本小节中,我打算演示如何使用数据库迁移特性,它能以比较温和的方式更新Code First的架构,并保留架构中的已有数据。 The first step is to issue the following command in the Visual Studio Package Manager Console: 第一个步骤是在Visual Studio的“Package Manager Console(包管理器控制台)”中发布以下命令: Enable-Migrations –EnableAutomaticMigrations This enables the database migration support and creates a Migrations folder in the Solution Explorer that contains a Configuration.cs class file, the contents of which are shown in Listing 15-4. 它启用了数据库的迁移支持,并在“Solution Explorer(解决方案资源管理器)”创建一个Migrations文件夹,其中含有一个Configuration.cs类文件,内容如清单15-4所示。 Listing 15-4. The Contents of the Configuration.cs File 清单15-4. Configuration.cs文件的内容 namespace Users.Migrations {using System;using System.Data.Entity;using System.Data.Entity.Migrations;using System.Linq;internal sealed class Configuration: DbMigrationsConfiguration<Users.Infrastructure.AppIdentityDbContext> {public Configuration() {AutomaticMigrationsEnabled = true;ContextKey = "Users.Infrastructure.AppIdentityDbContext";}protected override void Seed(Users.Infrastructure.AppIdentityDbContext context) {// This method will be called after migrating to the latest version.// 此方法将在迁移到最新版本时调用// You can use the DbSet<T>.AddOrUpdate() helper extension method// to avoid creating duplicate seed data. E.g.// 例如,你可以使用DbSet<T>.AddOrUpdate()辅助器方法来避免创建重复的种子数据//// context.People.AddOrUpdate(// p => p.FullName,// new Person { FullName = "Andrew Peters" },// new Person { FullName = "Brice Lambson" },// new Person { FullName = "Rowan Miller" }// );//} }} Tip You might be wondering why you are entering a database migration command into the console used to manage NuGet packages. The answer is that the Package Manager Console is really PowerShell, which is a general-purpose tool that is mislabeled by Visual Studio. You can use the console to issue a wide range of helpful commands. See http://go.microsoft.com/fwlink/?LinkID=108518 for details. 提示:你可能会觉得奇怪,为什么要在管理NuGet包的控制台中输入数据库迁移的命令?答案是“Package Manager Console(包管理控制台)”是真正的PowerShell,这是Visual studio冒用的一个通用工具。你可以使用此控制台发送大量的有用命令,详见http://go.microsoft.com/fwlink/?LinkID=108518。 The class will be used to migrate existing content in the database to the new schema, and the Seed method will be called to provide an opportunity to update the existing database records. In Listing 15-5, you can see how I have used the Seed method to set a default value for the new City property I added to the AppUser class. (I have also updated the class file to reflect my usual coding style.) 这个类将用于把数据库中的现有内容迁移到新的数据库架构,Seed方法的调用为更新现有数据库记录提供了机会。在清单15-5中可以看到,我如何用Seed方法为新的City属性设置默认值,City是添加到AppUser类中自定义属性。(为了体现我一贯的编码风格,我对这个类文件也进行了更新。) Listing 15-5. Managing Existing Content in the Configuration.cs File 清单15-5. 在Configuration.cs文件中管理已有内容 using System.Data.Entity.Migrations;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.EntityFramework;using Users.Infrastructure;using Users.Models;namespace Users.Migrations {internal sealed class Configuration: DbMigrationsConfiguration<AppIdentityDbContext> {public Configuration() {AutomaticMigrationsEnabled = true;ContextKey = "Users.Infrastructure.AppIdentityDbContext";}protected override void Seed(AppIdentityDbContext context) {AppUserManager userMgr = new AppUserManager(new UserStore<AppUser>(context));AppRoleManager roleMgr = new AppRoleManager(new RoleStore<AppRole>(context)); string roleName = "Administrators";string userName = "Admin";string password = "MySecret";string email = "admin@example.com";if (!roleMgr.RoleExists(roleName)) {roleMgr.Create(new AppRole(roleName));}AppUser user = userMgr.FindByName(userName);if (user == null) {userMgr.Create(new AppUser { UserName = userName, Email = email },password);user = userMgr.FindByName(userName);}if (!userMgr.IsInRole(user.Id, roleName)) {userMgr.AddToRole(user.Id, roleName);}foreach (AppUser dbUser in userMgr.Users) {dbUser.City = Cities.PARIS;}context.SaveChanges();} }} You will notice that much of the code that I added to the Seed method is taken from the IdentityDbInit class, which I used to seed the database with an administration user in Chapter 14. This is because the new Configuration class added to support database migrations will replace the seeding function of the IdentityDbInit class, which I’ll update shortly. Aside from ensuring that there is an admin user, the statements in the Seed method that are important are the ones that set the initial value for the City property I added to the AppUser class, as follows: 你可能会注意到,添加到Seed方法中的许多代码取自于IdentityDbInit类,在第14章中我用这个类将管理用户植入了数据库。这是因为这个新添加的、用以支持数据库迁移的Configuration类,将代替IdentityDbInit类的种植功能,我很快便会更新这个类。除了要确保有admin用户之外,在Seed方法中的重要语句是那些为AppUser类的City属性设置初值的语句,如下所示: ...foreach (AppUser dbUser in userMgr.Users) { dbUser.City = Cities.PARIS;}context.SaveChanges();... You don’t have to set a default value for new properties—I just wanted to demonstrate that the Seed method in the Configuration class can be used to update the existing user records in the database. 你不一定要为新属性设置默认值——这里只是想演示Configuration类中的Seed方法,可以用它更新数据库中的已有用户记录。 Caution Be careful when setting values for properties in the Seed method for real projects because the values will be applied every time you change the schema, overriding any values that the user has set since the last schema update was performed. I set the value of the City property just to demonstrate that it can be done. 警告:在用于真实项目的Seed方法中为属性设置值时要小心,因为你每一次修改架构时,都会运用这些值,这会将自执行上一次架构更新之后,用户设置的任何数据覆盖掉。这里设置City属性的值只是为了演示它能够这么做。 Changing the Database Context Class 修改数据库上下文类 The reason that I added the seeding code to the Configuration class is that I need to change the IdentityDbInit class. At present, the IdentityDbInit class is derived from the descriptively named DropCreateDatabaseIfModelChanges<AppIdentityDbContext> class, which, as you might imagine, drops the entire database when the Code First classes change. Listing 15-6 shows the changes I made to the IdentityDbInit class to prevent it from affecting the database. 在Configuration类中添加种植代码的原因是我需要修改IdentityDbInit类。此时,IdentityDbInit类派生于描述性命名的DropCreateDatabaseIfModelChanges<AppIdentityDbContext> 类,和你相像的一样,它会在Code First类改变时删除整个数据库。清单15-6显示了我对IdentityDbInit类所做的修改,以防止它影响数据库。 Listing 15-6. Preventing Database Schema Changes in the AppIdentityDbContext.cs File 清单15-6. 在AppIdentityDbContext.cs文件是阻止数据库架构变化 using System.Data.Entity;using Microsoft.AspNet.Identity.EntityFramework;using Users.Models;using Microsoft.AspNet.Identity; namespace Users.Infrastructure {public class AppIdentityDbContext : IdentityDbContext<AppUser> {public AppIdentityDbContext() : base("IdentityDb") { }static AppIdentityDbContext() {Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());}public static AppIdentityDbContext Create() {return new AppIdentityDbContext();} } public class IdentityDbInit : NullDatabaseInitializer<AppIdentityDbContext> {} } I have removed the methods defined by the class and changed its base to NullDatabaseInitializer<AppIdentityDbContext> , which prevents the schema from being altered. 我删除了这个类中所定义的方法,并将它的基类改为NullDatabaseInitializer<AppIdentityDbContext> ,它可以防止架构修改。 15.2.3 Performing the Migration 15.2.3 执行迁移 All that remains is to generate and apply the migration. First, run the following command in the Package Manager Console: 剩下的事情只是生成并运用迁移了。首先,在“Package Manager Console(包管理器控制台)”中执行以下命令: Add-Migration CityProperty This creates a new migration called CityProperty (I like my migration names to reflect the changes I made). A class new file will be added to the Migrations folder, and its name reflects the time at which the command was run and the name of the migration. My file is called 201402262244036_CityProperty.cs, for example. The contents of this file contain the details of how Entity Framework will change the database during the migration, as shown in Listing 15-7. 这创建了一个名称为CityProperty的新迁移(我比较喜欢让迁移的名称反映出我所做的修改)。这会在文件夹中添加一个新的类文件,而且其命名会反映出该命令执行的时间以及迁移名称,例如,我的这个文件名称为201402262244036_CityProperty.cs。该文件的内容含有迁移期间Entity Framework修改数据库的细节,如清单15-7所示。 Listing 15-7. The Contents of the 201402262244036_CityProperty.cs File 清单15-7. 201402262244036_CityProperty.cs文件的内容 namespace Users.Migrations {using System;using System.Data.Entity.Migrations; public partial class Init : DbMigration {public override void Up() {AddColumn("dbo.AspNetUsers", "City", c => c.Int(nullable: false));}public override void Down() {DropColumn("dbo.AspNetUsers", "City");} }} The Up method describes the changes that have to be made to the schema when the database is upgraded, which in this case means adding a City column to the AspNetUsers table, which is the one that is used to store user records in the ASP.NET Identity database. Up方法描述了在数据库升级时,需要对架构所做的修改,在这个例子中,意味着要在AspNetUsers数据表中添加City数据列,该数据表是ASP.NET Identity数据库用来存储用户记录的。 The final step is to perform the migration. Without starting the application, run the following command in the Package Manager Console: 最后一步是执行迁移。无需启动应用程序,只需在“Package Manager Console(包管理器控制台)”中运行以下命令即可: Update-Database –TargetMigration CityProperty The database schema will be modified, and the code in the Configuration.Seed method will be executed. The existing user accounts will have been preserved and enhanced with a City property (which I set to Paris in the Seed method). 这会修改数据库架构,并执行Configuration.Seed方法中的代码。已有用户账号会被保留,且增强了City属性(我在Seed方法中已将其设置为“Paris”)。 15.2.4 Testing the Migration 15.2.4 测试迁移 To test the effect of the migration, start the application, navigate to the /Home/UserProps URL, and authenticate as one of the Identity users (for example, as Alice with the password MySecret). Once authenticated, you will see the current value of the City property for the user and have the opportunity to change it, as shown in Figure 15-3. 为了测试迁移的效果,启动应用程序,导航到/Home/UserProps URL,并以Identity中的用户(例如Alice,口令MySecret)进行认证。一旦已被认证,便会看到该用户City属性的当前值,并可以对其进行修改,如图15-3所示。 Figure 15-3. Displaying and changing a custom user property 图15-3. 显示和个性自定义用户属性 15.2.5 Defining an Additional Property 15.2.5 定义附加属性 Now that database migrations are set up, I am going to define a further property just to demonstrate how subsequent changes are handled and to show a more useful (and less dangerous) example of using the Configuration.Seed method. Listing 15-8 shows how I added a Country property to the AppUser class. 现在,已经建立了数据库迁移,我打算再定义一个属性,这恰恰演示了如何处理持续不断的修改,也为了演示Configuration.Seed方法更有用(至少无害)的示例。清单15-8显示了我在AppUser类上添加了一个Country属性。 Listing 15-8. Adding Another Property in the AppUserModels.cs File 清单15-8. 在AppUserModels.cs文件中添加另一个属性 using System;using Microsoft.AspNet.Identity.EntityFramework; namespace Users.Models {public enum Cities {LONDON, PARIS, CHICAGO} public enum Countries {NONE, UK, FRANCE, USA}public class AppUser : IdentityUser {public Cities City { get; set; }public Countries Country { get; set; }public void SetCountryFromCity(Cities city) {switch (city) {case Cities.LONDON:Country = Countries.UK;break;case Cities.PARIS:Country = Countries.FRANCE;break;case Cities.CHICAGO:Country = Countries.USA;break;default:Country = Countries.NONE;break;} }} } I have added an enumeration to define the country names and a helper method that selects a country value based on the City property. Listing 15-9 shows the change I made to the Configuration class so that the Seed method sets the Country property based on the City, but only if the value of Country is NONE (which it will be for all users when the database is migrated because the Entity Framework sets enumeration columns to the first value). 我已经添加了一个枚举,它定义了国家名称。还添加了一个辅助器方法,它可以根据City属性选择一个国家。清单15-9显示了对Configuration类所做的修改,以使Seed方法根据City设置Country属性,但只当Country为NONE时才进行设置(在迁移数据库时,所有用户都是NONE,因为Entity Framework会将枚举列设置为枚举的第一个值)。 Listing 15-9. Modifying the Database Seed in the Configuration.cs File 清单15-9. 在Configuration.cs文件中修改数据库种子 using System.Data.Entity.Migrations;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.EntityFramework;using Users.Infrastructure;using Users.Models; namespace Users.Migrations {internal sealed class Configuration: DbMigrationsConfiguration<AppIdentityDbContext> {public Configuration() {AutomaticMigrationsEnabled = true;ContextKey = "Users.Infrastructure.AppIdentityDbContext";}protected override void Seed(AppIdentityDbContext context) {AppUserManager userMgr = new AppUserManager(new UserStore<AppUser>(context));AppRoleManager roleMgr = new AppRoleManager(new RoleStore<AppRole>(context)); string roleName = "Administrators";string userName = "Admin";string password = "MySecret";string email = "admin@example.com";if (!roleMgr.RoleExists(roleName)) {roleMgr.Create(new AppRole(roleName));}AppUser user = userMgr.FindByName(userName);if (user == null) {userMgr.Create(new AppUser { UserName = userName, Email = email },password);user = userMgr.FindByName(userName);}if (!userMgr.IsInRole(user.Id, roleName)) {userMgr.AddToRole(user.Id, roleName);} foreach (AppUser dbUser in userMgr.Users) {if (dbUser.Country == Countries.NONE) {dbUser.SetCountryFromCity(dbUser.City);} }context.SaveChanges();} }} This kind of seeding is more useful in a real project because it will set a value for the Country property only if one has not already been set—subsequent migrations won’t be affected, and user selections won’t be lost. 这种种植在实际项目中会更有用,因为它只会在Country属性未设置时,才会设置Country属性的值——后继的迁移不会受到影响,因此不会失去用户的选择。 1. Adding Application Support 1. 添加应用程序支持 There is no point defining additional user properties if they are not available in the application, so Listing 15-10 shows the change I made to the Views/Home/UserProps.cshtml file to display the value of the Country property. 应用程序中如果没有定义附加属性的地方,则附加属性就无法使用了,因此,清单15-10显示了我对Views/Home/UserProps.cshtml文件的修改,以显示Country属性的值。 Listing 15-10. Displaying an Additional Property in the UserProps.cshtml File 清单15-10. 在UserProps.cshtml文件中显示附加属性 @using Users.Models@model AppUser@{ ViewBag.Title = "UserProps";} <div class="panel panel-primary"><div class="panel-heading">Custom User Properties</div><table class="table table-striped"><tr><th>City</th><td>@Model.City</td></tr> <tr><th>Country</th><td>@Model.Country</td></tr></table></div>@using (Html.BeginForm()) {<div class="form-group"><label>City</label>@Html.DropDownListFor(x => x.City, new SelectList(Enum.GetNames(typeof(Cities))))</div><button class="btn btn-primary" type="submit">Save</button>} Listing 15-11 shows the corresponding change I made to the Home controller to update the Country property when the City value changes. 为了在City值变化时能够更新Country属性,清单15-11显示了我对Home控制器所做的相应修改。 Listing 15-11. Setting Custom Properties in the HomeController.cs File 清单15-11. 在HomeController.cs文件中设置自定义属性 using System.Web.Mvc;using System.Collections.Generic;using System.Web;using System.Security.Principal;using System.Threading.Tasks;using Users.Infrastructure;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.Owin;using Users.Models; namespace Users.Controllers {public class HomeController : Controller {// ...other action methods omitted for brevity...// ...出于简化,这里忽略了其他动作方法... [Authorize]public ActionResult UserProps() {return View(CurrentUser);}[Authorize][HttpPost]public async Task<ActionResult> UserProps(Cities city) {AppUser user = CurrentUser;user.City = city;user.SetCountryFromCity(city);await UserManager.UpdateAsync(user);return View(user);}// ...properties omitted for brevity...// ...出于简化,这里忽略了一些属性...} } 2. Performing the Migration 2. 准备迁移 All that remains is to create and apply a new migration. Enter the following command into the Package Manager Console: 剩下的事情就是创建和运用新的迁移了。在“Package Manager Console(包管理器控制台)”中输入以下命令: Add-Migration CountryProperty This will generate another file in the Migrations folder that contains the instruction to add the Country column. To apply the migration, execute the following command: 这将在Migrations文件夹中生成另一个文件,它含有添加Country数据表列的指令。为了运用迁移,可执行以下命令: Update-Database –TargetMigration CountryProperty The migration will be performed, and the value of the Country property will be set based on the value of the existing City property for each user. You can check the new user property by starting the application and authenticating and navigating to the /Home/UserProps URL, as shown in Figure 15-4. 这将执行迁移,Country属性的值将根据每个用户当前的City属性进行设置。通过启动应用程序,认证并导航到/Home/UserProps URL,便可以查看新的用户属性,如图15-4所示。 Figure 15-4. Creating an additional user property 图15-4. 创建附加用户属性 Tip Although I am focused on the process of upgrading the database, you can also migrate back to a previous version by specifying an earlier migration. Use the –Force argument make changes that cause data loss, such as removing a column. 提示:虽然我们关注了升级数据库的过程,但你也可以回退到以前的版本,只需指定一个早期的迁移即可。使用-Force参数进行修改,会引起数据丢失,例如删除数据表列。 15.3 Working with Claims 15.3 使用声明(Claims) In older user-management systems, such as ASP.NET Membership, the application was assumed to be the authoritative source of all information about the user, essentially treating the application as a closed world and trusting the data that is contained within it. 在旧的用户管理系统中,例如ASP.NET Membership,应用程序被假设成是用户所有信息的权威来源,本质上将应用程序视为是一个封闭的世界,并且只信任其中所包含的数据。 This is such an ingrained approach to software development that it can be hard to recognize that’s what is happening, but you saw an example of the closed-world technique in Chapter 14 when I authenticated users against the credentials stored in the database and granted access based on the roles associated with those credentials. I did the same thing again in this chapter when I added properties to the user class. Every piece of information that I needed to manage user authentication and authorization came from within my application—and that is a perfectly satisfactory approach for many web applications, which is why I demonstrated these techniques in such depth. 这是软件开发的一种根深蒂固的方法,使人很难认识到这到底意味着什么,第14章你已看到了这种封闭世界技术的例子,根据存储在数据库中的凭据来认证用户,并根据与凭据关联在一起的角色来授权访问。本章前述在用户类上添加属性,也做了同样的事情。我管理用户认证与授权所需的每一个数据片段都来自于我的应用程序——而且这是许多Web应用程序都相当满意的一种方法,这也是我如此深入地演示这些技术的原因。 ASP.NET Identity also supports an alternative approach for dealing with users, which works well when the MVC framework application isn’t the sole source of information about users and which can be used to authorize users in more flexible and fluid ways than traditional roles allow. ASP.NET Identity还支持另一种处理用户的办法,当MVC框架的应用程序不是有关用户的唯一信息源时,这种办法会工作得很好,而且能够比传统的角色授权更为灵活且流畅的方式进行授权。 This alternative approach uses claims, and in this section I’ll describe how ASP.NET Identity supports claims-based authorization. Table 15-4 puts claims in context. 这种可选的办法使用了“Claims(声明)”,因此在本小节中,我将描述ASP.NET Identity如何支持“Claims-Based Authorization(基于声明的授权)”。表15-4描述了声明(Claims)的情形。 提示:“Claim”在英文字典中不完全是“声明”的意思,根据本文的描述,感觉把它说成“声明”也不一定合适,所以在之后的译文中基本都写成中英文并用的形式,即“声明(Claims)”。根据表15-4中的声明(Claims)的定义:声明(Claims)是关于用户的一些信息片段。一个用户的信息片段当然有很多,每一个信息片段就是一项声明(Claim),用户的所有信息片段合起来就是该用户的声明(Claims)。请读者注意该单词的单复数形式——译者注 Table 15-4. Putting Claims in Context 表15-4. 声明(Claims)的情形 Question 问题 Answer 答案 What is it? 什么是声明(Claims)? Claims are pieces of information about users that you can use to make authorization decisions. Claims can be obtained from external systems as well as from the local Identity database. 声明(Claims)是关于用户的一些信息片段,可以用它们做出授权决定。声明(Claims)可以从外部系统获取,也可以从本地的Identity数据库获取。 Why should I care? 为何要关心它? Claims can be used to flexibly authorize access to action methods. Unlike conventional roles, claims allow access to be driven by the information that describes the user. 声明(Claims)可以用来对动作方法进行灵活的授权访问。与传统的角色不同,声明(Claims)让访问能够由描述用户的信息进行驱动。 How is it used by the MVC framework? 如何在MVC框架中使用它? This feature isn’t used directly by the MVC framework, but it is integrated into the standard authorization features, such as the Authorize attribute. 这不是直接由MVC框架使用的特性,但它集成到了标准的授权特性之中,例如Authorize注解属性。 Tip you don’t have to use claims in your applications, and as Chapter 14 showed, ASP.NET Identity is perfectly happy providing an application with the authentication and authorization services without any need to understand claims at all. 提示:你在应用程序中不一定要使用声明(Claims),正如第14章所展示的那样,ASP.NET Identity能够为应用程序提供充分的认证与授权服务,而根本不需要理解声明(Claims)。 15.3.1 Understanding Claims 15.3.1 理解声明(Claims) A claim is a piece of information about the user, along with some information about where the information came from. The easiest way to unpack claims is through some practical demonstrations, without which any discussion becomes too abstract to be truly useful. To get started, I added a Claims controller to the example project, the definition of which you can see in Listing 15-12. 一项声明(Claim)是关于用户的一个信息片段(请注意这个英文单词的单复数形式——译者注),并伴有该片段出自何处的某种信息。揭开声明(Claims)含义最容易的方式是做一些实际演示,任何讨论都会过于抽象根本没有真正的用处。为此,我在示例项目中添加了一个Claims控制器,其定义如清单15-12所示。 Listing 15-12. The Contents of the ClaimsController.cs File 清单15-12. ClaimsController.cs文件的内容 using System.Security.Claims;using System.Web;using System.Web.Mvc; namespace Users.Controllers {public class ClaimsController : Controller {[Authorize]public ActionResult Index() {ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity;if (ident == null) {return View("Error", new string[] { "No claims available" });} else {return View(ident.Claims);} }} } Tip You may feel a little lost as I define the code for this example. Don’t worry about the details for the moment—just stick with it until you see the output from the action method and view that I define. More than anything else, that will help put claims into perspective. 提示:你或许会对我为此例定义的代码感到有点失望。此刻对此细节不必着急——只要稍事忍耐,当看到该动作方法和视图的输出便会明白。尤为重要的是,这有助于洞察声明(Claims)。 You can get the claims associated with a user in different ways. One approach is to use the Claims property defined by the user class, but in this example, I have used the HttpContext.User.Identity property to demonstrate the way that ASP.NET Identity is integrated with the rest of the ASP.NET platform. As I explained in Chapter 13, the HttpContext.User.Identity property returns an implementation of the IIdentity interface, which is a ClaimsIdentity object when working using ASP.NET Identity. The ClaimsIdentity class is defined in the System.Security.Claims namespace, and Table 15-5 shows the members it defines that are relevant to this chapter. 可以通过不同的方式获得与用户相关联的声明(Claims)。方法之一就是使用由用户类定义的Claims属性,但在这个例子中,我使用了HttpContext.User.Identity属性,目的是演示ASP.NET Identity与ASP.NET平台集成的方式(请注意这句话所表示的含义:用户类的Claims属性属于ASP.NET Identity,而HttpContext.User.Identity属性则属于ASP.NET平台。由此可见,ASP.NET Identity已经融合到了ASP.NET平台之中——译者注)。正如第13章所解释的那样,HttpContext.User.Identity属性返回IIdentity的接口实现,当使用ASP.NET Identity时,该实现是一个ClaimsIdentity对象。ClaimsIdentity类是在System.Security.Claims命名空间中定义的,表15-5显示了它所定义的与本章有关的成员。 Table 15-5. The Members Defined by the ClaimsIdentity Class 表15-5. ClaimsIdentity类所定义的成员 Name 名称 Description 描述 Claims Returns an enumeration of Claim objects representing the claims for the user. 返回表示用户声明(Claims)的Claim对象枚举 AddClaim(claim) Adds a claim to the user identity. 给用户添加一个声明(Claim) AddClaims(claims) Adds an enumeration of Claim objects to the user identity. 给用户添加Claim对象的枚举。 HasClaim(predicate) Returns true if the user identity contains a claim that matches the specified predicate. See the “Applying Claims” section for an example predicate. 如果用户含有与指定谓词匹配的声明(Claim)时,返回true。参见“运用声明(Claims)”中的示例谓词 RemoveClaim(claim) Removes a claim from the user identity. 删除用户的声明(Claim)。 Other members are available, but the ones in the table are those that are used most often in web applications, for reason that will become obvious as I demonstrate how claims fit into the wider ASP.NET platform. 还有一些可用的其它成员,但表中的这些是在Web应用程序中最常用的,随着我演示如何将声明(Claims)融入更宽泛的ASP.NET平台,它们为什么最常用就很显然了。 In Listing 15-12, I cast the IIdentity implementation to the ClaimsIdentity type and pass the enumeration of Claim objects returned by the ClaimsIdentity.Claims property to the View method. A Claim object represents a single piece of data about the user, and the Claim class defines the properties shown in Table 15-6. 在清单15-12中,我将IIdentity实现转换成了ClaimsIdentity类型,并且给View方法传递了ClaimsIdentity.Claims属性所返回的Claim对象的枚举。Claim对象所示表示的是关于用户的一个单一的数据片段,Claim类定义的属性如表15-6所示。 Table 15-6. The Properties Defined by the Claim Class 表15-6. Claim类定义的属性 Name 名称 Description 描述 Issuer Returns the name of the system that provided the claim 返回提供声明(Claim)的系统名称 Subject Returns the ClaimsIdentity object for the user who the claim refers to 返回声明(Claim)所指用户的ClaimsIdentity对象 Type Returns the type of information that the claim represents 返回声明(Claim)所表示的信息类型 Value Returns the piece of information that the claim represents 返回声明(Claim)所表示的信息片段 Listing 15-13 shows the contents of the Index.cshtml file that I created in the Views/Claims folder and that is rendered by the Index action of the Claims controller. The view adds a row to a table for each claim about the user. 清单15-13显示了我在Views/Claims文件夹中创建的Index.cshtml文件的内容,它由Claims控制器中的Index动作方法进行渲染。该视图为用户的每项声明(Claim)添加了一个表格行。 Listing 15-13. The Contents of the Index.cshtml File in the Views/Claims Folder 清单15-13. Views/Claims文件夹中Index.cshtml文件的内容 @using System.Security.Claims@using Users.Infrastructure@model IEnumerable<Claim>@{ ViewBag.Title = "Claims"; }<div class="panel panel-primary"><div class="panel-heading">Claims</div><table class="table table-striped"><tr><th>Subject</th><th>Issuer</th><th>Type</th><th>Value</th></tr>@foreach (Claim claim in Model.OrderBy(x => x.Type)) {<tr><td>@claim.Subject.Name</td><td>@claim.Issuer</td><td>@Html.ClaimType(claim.Type)</td><td>@claim.Value</td></tr>}</table></div> The value of the Claim.Type property is a URI for a Microsoft schema, which isn’t especially useful. The popular schemas are used as the values for fields in the System.Security.Claims.ClaimTypes class, so to make the output from the Index.cshtml view easier to read, I added an HTML helper to the IdentityHelpers.cs file, as shown in Listing 15-14. It is this helper that I use in the Index.cshtml file to format the value of the Claim.Type property. Claim.Type属性的值是一个微软模式(Microsoft Schema)的URI(统一资源标识符),这是特别有用的。System.Security.Claims.ClaimTypes类中字段的值使用的是流行模式(Popular Schema),因此为了使Index.cshtml视图的输出更易于阅读,我在IdentityHelpers.cs文件中添加了一个HTML辅助器,如清单15-14所示。Index.cshtml文件正是使用这个辅助器格式化了Claim.Type属性的值。 Listing 15-14. Adding a Helper to the IdentityHelpers.cs File 清单15-14. 在IdentityHelpers.cs文件中添加辅助器 using System.Web;using System.Web.Mvc;using Microsoft.AspNet.Identity.Owin;using System;using System.Linq;using System.Reflection;using System.Security.Claims;namespace Users.Infrastructure {public static class IdentityHelpers {public static MvcHtmlString GetUserName(this HtmlHelper html, string id) {AppUserManager mgr= HttpContext.Current.GetOwinContext().GetUserManager<AppUserManager>();return new MvcHtmlString(mgr.FindByIdAsync(id).Result.UserName);} public static MvcHtmlString ClaimType(this HtmlHelper html, string claimType) {FieldInfo[] fields = typeof(ClaimTypes).GetFields();foreach (FieldInfo field in fields) {if (field.GetValue(null).ToString() == claimType) {return new MvcHtmlString(field.Name);} }return new MvcHtmlString(string.Format("{0}",claimType.Split('/', '.').Last()));} }} Note The helper method isn’t at all efficient because it reflects on the fields of the ClaimType class for each claim that is displayed, but it is sufficient for my purposes in this chapter. You won’t often need to display the claim type in real applications. 注:该辅助器并非十分有效,因为它只是针对每个要显示的声明(Claim)映射出ClaimType类的字段,但对我要的目的已经足够了。在实际项目中不会经常需要显示声明(Claim)的类型。 To see why I have created a controller that uses claims without really explaining what they are, start the application, authenticate as the user Alice (with the password MySecret), and request the /Claims/Index URL. Figure 15-5 shows the content that is generated. 为了弄明白我为何要先创建一个使用声明(Claims)的控制器,而没有真正解释声明(Claims)是什么的原因,可以启动应用程序,以用户Alice进行认证(其口令是MySecret),并请求/Claims/Index URL。图15-5显示了生成的内容。 Figure 15-5. The output from the Index action of the Claims controller 图15-5. Claims控制器中Index动作的输出 It can be hard to make out the detail in the figure, so I have reproduced the content in Table 15-7. 这可能还难以认识到此图的细节,为此我在表15-7中重列了其内容。 Table 15-7. The Data Shown in Figure 15-5 表15-7. 图15-5中显示的数据 Subject(科目) Issuer(发行者) Type(类型) Value(值) Alice LOCAL AUTHORITY SecurityStamp Unique ID Alice LOCAL AUTHORITY IdentityProvider ASP.NET Identity Alice LOCAL AUTHORITY Role Employees Alice LOCAL AUTHORITY Role Users Alice LOCAL AUTHORITY Name Alice Alice LOCAL AUTHORITY NameIdentifier Alice’s user ID The table shows the most important aspect of claims, which is that I have already been using them when I implemented the traditional authentication and authorization features in Chapter 14. You can see that some of the claims relate to user identity (the Name claim is Alice, and the NameIdentifier claim is Alice’s unique user ID in my ASP.NET Identity database). 此表展示了声明(Claims)最重要的方面,这些是我在第14章中实现传统的认证和授权特性时,一直在使用的信息。可以看出,有些声明(Claims)与用户标识有关(Name声明是Alice,NameIdentifier声明是Alice在ASP.NET Identity数据库中的唯一用户ID号)。 Other claims show membership of roles—there are two Role claims in the table, reflecting the fact that Alice is assigned to both the Users and Employees roles. There is also a claim about how Alice has been authenticated: The IdentityProvider is set to ASP.NET Identity. 其他声明(Claims)显示了角色成员——表中有两个Role声明(Claim),体现出Alice被赋予了Users和Employees两个角色这一事实。还有一个是Alice已被认证的声明(Claim):IdentityProvider被设置到了ASP.NET Identity。 The difference when this information is expressed as a set of claims is that you can determine where the data came from. The Issuer property for all the claims shown in the table is set to LOCAL AUTHORITY, which indicates that the user’s identity has been established by the application. 当这种信息被表示成一组声明(Claims)时的差别是,你能够确定这些数据是从哪里来的。表中所显示的所有声明的Issuer属性(发布者)都被设置到了LOACL AUTHORITY(本地授权),这说明该用户的标识是由应用程序建立的。 So, now that you have seen some example claims, I can more easily describe what a claim is. A claim is any piece of information about a user that is available to the application, including the user’s identity and role memberships. And, as you have seen, the information I have been defining about my users in earlier chapters is automatically made available as claims by ASP.NET Identity. 因此,现在你已经看到了一些声明(Claims)示例,我可以更容易地描述声明(Claim)是什么了。一项声明(Claim)是可用于应用程序中的有关用户的一个信息片段,包括用户的标识以及角色成员等。而且,正如你所看到的,我在前几章定义的关于用户的信息,被ASP.NET Identity自动地作为声明(Claims)了。 15.3.2 Creating and Using Claims 15.3.2 创建和使用声明(Claims) Claims are interesting for two reasons. The first reason is that an application can obtain claims from multiple sources, rather than just relying on a local database for information about the user. You will see a real example of this when I show you how to authenticate users through a third-party system in the “Using Third-Party Authentication” section, but for the moment I am going to add a class to the example project that simulates a system that provides claims information. Listing 15-15 shows the contents of the LocationClaimsProvider.cs file that I added to the Infrastructure folder. 声明(Claims)比较有意思的原因有两个。第一个原因是应用程序可以从多个来源获取声明(Claims),而不是只能依靠本地数据库关于用户的信息。你将会看到一个实际的示例,在“使用第三方认证”小节中,将演示如何通过第三方系统来认证用户。不过,此刻我只打算在示例项目中添加一个类,用以模拟一个提供声明(Claims)信息的系统。清单15-15显示了我添加到Infrastructure文件夹中LocationClaimsProvider.cs文件的内容。 Listing 15-15. The Contents of the LocationClaimsProvider.cs File 清单15-15. LocationClaimsProvider.cs文件的内容 using System.Collections.Generic;using System.Security.Claims; namespace Users.Infrastructure {public static class LocationClaimsProvider {public static IEnumerable<Claim> GetClaims(ClaimsIdentity user) {List<Claim> claims = new List<Claim>();if (user.Name.ToLower() == "alice") {claims.Add(CreateClaim(ClaimTypes.PostalCode, "DC 20500"));claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "DC"));} else {claims.Add(CreateClaim(ClaimTypes.PostalCode, "NY 10036"));claims.Add(CreateClaim(ClaimTypes.StateOrProvince, "NY"));}return claims;}private static Claim CreateClaim(string type, string value) {return new Claim(type, value, ClaimValueTypes.String, "RemoteClaims");} }} The GetClaims method takes a ClaimsIdentity argument and uses the Name property to create claims about the user’s ZIP code and state. This class allows me to simulate a system such as a central HR database, which would be the authoritative source of location information about staff, for example. GetClaims方法以ClaimsIdentity为参数,并使用Name属性创建了关于用户ZIP码(邮政编码)和州府的声明(Claims)。上述这个类使我能够模拟一个诸如中心化的HR数据库(人力资源数据库)之类的系统,它可能会成为全体职员的地点信息的权威数据源。 Claims are associated with the user’s identity during the authentication process, and Listing 15-16 shows the changes I made to the Login action method of the Account controller to call the LocationClaimsProvider class. 在认证过程期间,声明(Claims)是与用户标识关联在一起的,清单15-16显示了我对Account控制器中Login动作方法所做的修改,以便调用LocationClaimsProvider类。 Listing 15-16. Associating Claims with a User in the AccountController.cs File 清单15-16. AccountController.cs文件中用户用声明的关联 ...[HttpPost][AllowAnonymous][ValidateAntiForgeryToken]public async Task<ActionResult> Login(LoginModel details, string returnUrl) {if (ModelState.IsValid) {AppUser user = await UserManager.FindAsync(details.Name,details.Password);if (user == null) {ModelState.AddModelError("", "Invalid name or password.");} else {ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(LocationClaimsProvider.GetClaims(ident));AuthManager.SignOut();AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);return Redirect(returnUrl);} }ViewBag.returnUrl = returnUrl;return View(details);}... You can see the effect of the location claims by starting the application, authenticating as a user, and requesting the /Claim/Index URL. Figure 15-6 shows the claims for Alice. You may have to sign out and sign back in again to see the change. 为了看看这个地点声明(Claims)的效果,可以启动应用程序,以一个用户进行认证,并请求/Claim/Index URL。图15-6显示了Alice的声明(Claims)。你可能需要退出,然后再次登录才会看到发生的变化。 Figure 15-6. Defining additional claims for users 图15-6. 定义用户的附加声明 Obtaining claims from multiple locations means that the application doesn’t have to duplicate data that is held elsewhere and allows integration of data from external parties. The Claim.Issuer property tells you where a claim originated from, which helps you judge how accurate the data is likely to be and how much weight you should give the data in your application. Location data obtained from a central HR database is likely to be more accurate and trustworthy than data obtained from an external mailing list provider, for example. 从多个地点获取声明(Claims)意味着应用程序不必复制其他地方保持的数据,并且能够与外部的数据集成。Claim.Issuer属性(图15-6中的Issuer数据列——译者注)能够告诉你一个声明(Claim)的发源地,这有助于让你判断数据的精确程度,也有助于让你决定这类数据在应用程序中的权重。例如,从中心化的HR数据库获取的地点数据可能要比外部邮件列表提供器获取的数据更为精确和可信。 1. Applying Claims 1. 运用声明(Claims) The second reason that claims are interesting is that you can use them to manage user access to your application more flexibly than with standard roles. The problem with roles is that they are static, and once a user has been assigned to a role, the user remains a member until explicitly removed. This is, for example, how long-term employees of big corporations end up with incredible access to internal systems: They are assigned the roles they require for each new job they get, but the old roles are rarely removed. (The unexpectedly broad systems access sometimes becomes apparent during the investigation into how someone was able to ship the contents of the warehouse to their home address—true story.) 声明(Claims)有意思的第二个原因是,你可以用它们来管理用户对应用程序的访问,这要比标准的角色管理更为灵活。角色的问题在于它们是静态的,而且一旦用户已经被赋予了一个角色,该用户便是一个成员,直到明确地删除为止。例如,这意味着大公司的长期雇员,对内部系统的访问会十分惊人:他们每次在获得新工作时,都会赋予所需的角色,但旧角色很少被删除。(在调查某人为何能够将仓库里的东西发往他的家庭地址过程中发现,有时会出现异常宽泛的系统访问——真实的故事) Claims can be used to authorize users based directly on the information that is known about them, which ensures that the authorization changes when the data changes. The simplest way to do this is to generate Role claims based on user data that are then used by controllers to restrict access to action methods. Listing 15-17 shows the contents of the ClaimsRoles.cs file that I added to the Infrastructure. 声明(Claims)可以直接根据用户已知的信息对用户进行授权,这能够保证当数据发生变化时,授权也随之而变。此事最简单的做法是根据用户数据来生成Role声明(Claim),然后由控制器用来限制对动作方法的访问。清单15-17显示了我添加到Infrastructure中的ClaimsRoles.cs文件的内容。 Listing 15-17. The Contents of the ClaimsRoles.cs File 清单15-17. ClaimsRoles.cs文件的内容 using System.Collections.Generic;using System.Security.Claims; namespace Users.Infrastructure {public class ClaimsRoles {public static IEnumerable<Claim> CreateRolesFromClaims(ClaimsIdentity user) {List<Claim> claims = new List<Claim>();if (user.HasClaim(x => x.Type == ClaimTypes.StateOrProvince&& x.Issuer == "RemoteClaims" && x.Value == "DC")&& user.HasClaim(x => x.Type == ClaimTypes.Role&& x.Value == "Employees")) {claims.Add(new Claim(ClaimTypes.Role, "DCStaff"));}return claims;} }} The gnarly looking CreateRolesFromClaims method uses lambda expressions to determine whether the user has a StateOrProvince claim from the RemoteClaims issuer with a value of DC and a Role claim with a value of Employees. If the user has both claims, then a Role claim is returned for the DCStaff role. Listing 15-18 shows how I call the CreateRolesFromClaims method from the Login action in the Account controller. CreateRolesFromClaims是一个粗糙的考察方法,它使用了Lambda表达式,以检查用户是否具有StateOrProvince声明(Claim),该声明来自于RemoteClaims发行者(Issuer),值为DC。也检查用户是否具有Role声明(Claim),其值为Employees。如果用户这两个声明都有,那么便返回一个DCStaff角色的Role声明。清单15-18显示了如何在Account控制器中的Login动作中调用CreateRolesFromClaims方法。 Listing 15-18. Generating Roles Based on Claims in the AccountController.cs File 清单15-18. 在AccountController.cs中根据声明生成角色 ...[HttpPost][AllowAnonymous][ValidateAntiForgeryToken]public async Task<ActionResult> Login(LoginModel details, string returnUrl) {if (ModelState.IsValid) {AppUser user = await UserManager.FindAsync(details.Name,details.Password);if (user == null) {ModelState.AddModelError("", "Invalid name or password.");} else {ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);ident.AddClaims(LocationClaimsProvider.GetClaims(ident)); ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident));AuthManager.SignOut();AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);return Redirect(returnUrl);} }ViewBag.returnUrl = returnUrl;return View(details);}... I can then restrict access to an action method based on membership of the DCStaff role. Listing 15-19 shows a new action method I added to the Claims controller to which I have applied the Authorize attribute. 然后我可以根据DCStaff角色的成员,来限制对一个动作方法的访问。清单15-19显示了在Claims控制器中添加的一个新的动作方法,在该方法上已经运用了Authorize注解属性。 Listing 15-19. Adding a New Action Method to the ClaimsController.cs File 清单15-19. 在ClaimsController.cs文件中添加一个新的动作方法 using System.Security.Claims;using System.Web;using System.Web.Mvc;namespace Users.Controllers {public class ClaimsController : Controller {[Authorize]public ActionResult Index() {ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity;if (ident == null) {return View("Error", new string[] { "No claims available" });} else {return View(ident.Claims);} } [Authorize(Roles="DCStaff")]public string OtherAction() {return "This is the protected action";} }} Users will be able to access OtherAction only if their claims grant them membership to the DCStaff role. Membership of this role is generated dynamically, so a change to the user’s employment status or location information will change their authorization level. 只要用户的声明(Claims)承认他们是DCStaff角色的成员,那么他们便能访问OtherAction动作。该角色的成员是动态生成的,因此,若是用户的雇用状态或地点信息发生变化,也会改变他们的授权等级。 提示:请读者从这个例子中吸取其中的思想精髓。对于读物的理解程度,仁者见仁,智者见智,能领悟多少,全凭各人,译者感觉这里的思想有无数的可能。举例说明:(1)可以根据用户的身份进行授权,比如学生在校时是“学生”,毕业后便是“校友”;(2)可以根据用户所处的部门进行授权,人事部用户属于人事团队,销售部用户属于销售团队,各团队有其自己的应用;(3)下一小节的示例是根据用户的地点授权。简言之:一方面用户的各种声明(Claim)都可以用来进行授权;另一方面用户的声明(Claim)又是可以自定义的。于是可能的运用就无法估计了。总之一句话,这种基于声明的授权(Claims-Based Authorization)有无限可能!要是没有我这里的提示,是否所有读者在此处都会有所体会?——译者注 15.3.3 Authorizing Access Using Claims 15.3.3 使用声明(Claims)授权访问 The previous example is an effective demonstration of how claims can be used to keep authorizations fresh and accurate, but it is a little indirect because I generate roles based on claims data and then enforce my authorization policy based on the membership of that role. A more direct and flexible approach is to enforce authorization directly by creating a custom authorization filter attribute. Listing 15-20 shows the contents of the ClaimsAccessAttribute.cs file, which I added to the Infrastructure folder and used to create such a filter. 前面的示例有效地演示了如何用声明(Claims)来保持新鲜和准确的授权,但有点不太直接,因为我要根据声明(Claims)数据来生成了角色,然后强制我的授权策略基于角色成员。一个更直接且灵活的办法是直接强制授权,其做法是创建一个自定义的授权过滤器注解属性。清单15-20演示了ClaimsAccessAttribute.cs文件的内容,我将它添加在Infrastructure文件夹中,并用它创建了这种过滤器。 Listing 15-20. The Contents of the ClaimsAccessAttribute.cs File 清单15-20. ClaimsAccessAttribute.cs文件的内容 using System.Security.Claims;using System.Web;using System.Web.Mvc; namespace Users.Infrastructure {public class ClaimsAccessAttribute : AuthorizeAttribute {public string Issuer { get; set; }public string ClaimType { get; set; }public string Value { get; set; }protected override bool AuthorizeCore(HttpContextBase context) {return context.User.Identity.IsAuthenticated&& context.User.Identity is ClaimsIdentity&& ((ClaimsIdentity)context.User.Identity).HasClaim(x =>x.Issuer == Issuer && x.Type == ClaimType && x.Value == Value);} }} The attribute I have defined is derived from the AuthorizeAttribute class, which makes it easy to create custom authorization policies in MVC framework applications by overriding the AuthorizeCore method. My implementation grants access if the user is authenticated, the IIdentity implementation is an instance of ClaimsIdentity, and the user has a claim with the issuer, type, and value matching the class properties. Listing 15-21 shows how I applied the attribute to the Claims controller to authorize access to the OtherAction method based on one of the location claims created by the LocationClaimsProvider class. 我所定义的这个注解属性派生于AuthorizeAttribute类,通过重写AuthorizeCore方法,很容易在MVC框架应用程序中创建自定义的授权策略。在这个实现中,若用户是已认证的、其IIdentity实现是一个ClaimsIdentity实例,而且该用户有一个带有issuer、type以及value的声明(Claim),它们与这个类的属性是匹配的,则该用户便是允许访问的。清单15-21显示了如何将这个注解属性运用于Claims控制器,以便根据LocationClaimsProvider类创建的地点声明(Claim),对OtherAction方法进行授权访问。 Listing 15-21. Performing Authorization on Claims in the ClaimsController.cs File 清单15-21. 在ClaimsController.cs文件中执行基于声明的授权 using System.Security.Claims;using System.Web;using System.Web.Mvc;using Users.Infrastructure;namespace Users.Controllers {public class ClaimsController : Controller {[Authorize]public ActionResult Index() {ClaimsIdentity ident = HttpContext.User.Identity as ClaimsIdentity;if (ident == null) {return View("Error", new string[] { "No claims available" });} else {return View(ident.Claims);} } [ClaimsAccess(Issuer="RemoteClaims", ClaimType=ClaimTypes.PostalCode,Value="DC 20500")]public string OtherAction() {return "This is the protected action";} }} My authorization filter ensures that only users whose location claims specify a ZIP code of DC 20500 can invoke the OtherAction method. 这个授权过滤器能够确保只有地点声明(Claim)的邮编为DC 20500的用户才能请求OtherAction方法。 15.4 Using Third-Party Authentication 15.4 使用第三方认证 One of the benefits of a claims-based system such as ASP.NET Identity is that any of the claims can come from an external system, even those that identify the user to the application. This means that other systems can authenticate users on behalf of the application, and ASP.NET Identity builds on this idea to make it simple and easy to add support for authenticating users through third parties such as Microsoft, Google, Facebook, and Twitter. 基于声明的系统,如ASP.NET Identity,的好处之一是任何声明都可以来自于外部系统,即使是将用户标识到应用程序的那些声明。这意味着其他系统可以代表应用程序来认证用户,而ASP.NET Identity就建立在这样的思想之上,使之能够简单而方便地添加第三方认证用户的支持,如微软、Google、Facebook、Twitter等。 There are some substantial benefits of using third-party authentication: Many users will already have an account, users can elect to use two-factor authentication, and you don’t have to manage user credentials in the application. In the sections that follow, I’ll show you how to set up and use third-party authentication for Google users, which Table 15-8 puts into context. 使用第三方认证有一些实际的好处:许多用户已经有了账号、用户可以选择使用双因子认证、你不必在应用程序中管理用户凭据等等。在以下小节中,我将演示如何为Google用户建立并使用第三方认证,表15-8描述了事情的情形。 Table 15-8. Putting Third-Party Authentication in Context 表15-8. 第三方认证情形 Question 问题 Answer 回答 What is it? 什么是第三方认证? Authenticating with third parties lets you take advantage of the popularity of companies such as Google and Facebook. 第三方认证使你能够利用流行公司,如Google和Facebook,的优势。 Why should I care? 为何要关心它? Users don’t like having to remember passwords for many different sites. Using a provider with large-scale adoption can make your application more appealing to users of the provider’s services. 用户不喜欢记住许多不同网站的口令。使用大范围适应的提供器可使你的应用程序更吸引有提供器服务的用户。 How is it used by the MVC framework? 如何在MVC框架中使用它? This feature isn’t used directly by the MVC framework. 这不是一个直接由MVC框架使用的特性。 Note The reason I have chosen to demonstrate Google authentication is that it is the only option that doesn’t require me to register my application with the authentication service. You can get details of the registration processes required at http://bit.ly/1cqLTrE. 提示:我选择演示Google认证的原因是,它是唯一不需要在其认证服务中注册我应用程序的公司。有关认证服务注册过程的细节,请参阅http://bit.ly/1cqLTrE。 15.4.1 Enabling Google Authentication 15.4.1 启用Google认证 ASP.NET Identity comes with built-in support for authenticating users through their Microsoft, Google, Facebook, and Twitter accounts as well more general support for any authentication service that supports OAuth. The first step is to add the NuGet package that includes the Google-specific additions for ASP.NET Identity. Enter the following command into the Package Manager Console: ASP.NET Identity带有通过Microsoft、Google、Facebook以及Twitter账号认证用户的内建支持,并且对于支持OAuth的认证服务具有更普遍的支持。第一个步骤是添加NuGet包,包中含有用于ASP.NET Identity的Google专用附件。请在“Package Manager Console(包管理器控制台)”中输入以下命令: Install-Package Microsoft.Owin.Security.Google -version 2.0.2 There are NuGet packages for each of the services that ASP.NET Identity supports, as described in Table 15-9. 对于ASP.NET Identity支持的每一种服务都有相应的NuGet包,如表15-9所示。 Table 15-9. The NuGet Authenticaton Packages 表15-9. NuGet认证包 Name 名称 Description 描述 Microsoft.Owin.Security.Google Authenticates users with Google accounts 用Google账号认证用户 Microsoft.Owin.Security.Facebook Authenticates users with Facebook accounts 用Facebook账号认证用户 Microsoft.Owin.Security.Twitter Authenticates users with Twitter accounts 用Twitter账号认证用户 Microsoft.Owin.Security.MicrosoftAccount Authenticates users with Microsoft accounts 用Microsoft账号认证用户 Microsoft.Owin.Security.OAuth Authenticates users against any OAuth 2.0 service 根据任一OAuth 2.0服务认证用户 Once the package is installed, I enable support for the authentication service in the OWIN startup class, which is defined in the App_Start/IdentityConfig.cs file in the example project. Listing 15-22 shows the change that I have made. 一旦安装了这个包,便可以在OWIN启动类中启用此项认证服务的支持,启动类的定义在示例项目的App_Start/IdentityConfig.cs文件中。清单15-22显示了所做的修改。 Listing 15-22. Enabling Google Authentication in the IdentityConfig.cs File 清单15-22. 在IdentityConfig.cs文件中启用Google认证 using Microsoft.AspNet.Identity;using Microsoft.Owin;using Microsoft.Owin.Security.Cookies;using Owin;using Users.Infrastructure;using Microsoft.Owin.Security.Google;namespace Users {public class IdentityConfig {public void Configuration(IAppBuilder app) {app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions {AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,LoginPath = new PathString("/Account/Login"),}); app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);app.UseGoogleAuthentication();} }} Each of the packages that I listed in Table 15-9 contains an extension method that enables the corresponding service. The extension method for the Google service is called UseGoogleAuthentication, and it is called on the IAppBuilder implementation that is passed to the Configuration method. 表15-9所列的每个包都含有启用相应服务的扩展方法。用于Google服务的扩展方法名称为UseGoogleAuthentication,它通过传递给Configuration方法的IAppBuilder实现进行调用。 Next I added a button to the Views/Account/Login.cshtml file, which allows users to log in via Google. You can see the change in Listing 15-23. 下一步骤是在Views/Account/Login.cshtml文件中添加一个按钮,让用户能够通过Google进行登录。所做的修改如清单15-23所示。 Listing 15-23. Adding a Google Login Button to the Login.cshtml File 清单15-23. 在Login.cshtml文件中添加Google登录按钮 @model Users.Models.LoginModel@{ ViewBag.Title = "Login";}<h2>Log In</h2> @Html.ValidationSummary()@using (Html.BeginForm()) {@Html.AntiForgeryToken();<input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" /><div class="form-group"><label>Name</label>@Html.TextBoxFor(x => x.Name, new { @class = "form-control" })</div><div class="form-group"><label>Password</label>@Html.PasswordFor(x => x.Password, new { @class = "form-control" })</div><button class="btn btn-primary" type="submit">Log In</button>}@using (Html.BeginForm("GoogleLogin", "Account")) {<input type="hidden" name="returnUrl" value="@ViewBag.returnUrl" /><button class="btn btn-primary" type="submit">Log In via Google</button>} The new button submits a form that targets the GoogleLogin action on the Account controller. You can see this method—and the other changes I made the controller—in Listing 15-24. 新按钮递交一个表单,目标是Account控制器中的GoogleLogin动作。可从清单15-24中看到该方法,以及在控制器中所做的其他修改。 Listing 15-24. Adding Support for Google Authentication to the AccountController.cs File 清单15-24. 在AccountController.cs文件中添加Google认证支持 using System.Threading.Tasks;using System.Web.Mvc;using Users.Models;using Microsoft.Owin.Security;using System.Security.Claims;using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.Owin;using Users.Infrastructure;using System.Web; namespace Users.Controllers {[Authorize]public class AccountController : Controller {[AllowAnonymous]public ActionResult Login(string returnUrl) {if (HttpContext.User.Identity.IsAuthenticated) {return View("Error", new string[] { "Access Denied" });}ViewBag.returnUrl = returnUrl;return View();}[HttpPost][AllowAnonymous][ValidateAntiForgeryToken]public async Task<ActionResult> Login(LoginModel details, string returnUrl) {if (ModelState.IsValid) {AppUser user = await UserManager.FindAsync(details.Name,details.Password);if (user == null) {ModelState.AddModelError("", "Invalid name or password.");} else {ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie); ident.AddClaims(LocationClaimsProvider.GetClaims(ident));ident.AddClaims(ClaimsRoles.CreateRolesFromClaims(ident)); AuthManager.SignOut();AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false}, ident);return Redirect(returnUrl);} }ViewBag.returnUrl = returnUrl;return View(details);} [HttpPost][AllowAnonymous]public ActionResult GoogleLogin(string returnUrl) {var properties = new AuthenticationProperties {RedirectUri = Url.Action("GoogleLoginCallback",new { returnUrl = returnUrl})};HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");return new HttpUnauthorizedResult();}[AllowAnonymous]public async Task<ActionResult> GoogleLoginCallback(string returnUrl) {ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync();AppUser user = await UserManager.FindAsync(loginInfo.Login);if (user == null) {user = new AppUser {Email = loginInfo.Email,UserName = loginInfo.DefaultUserName,City = Cities.LONDON, Country = Countries.UK};IdentityResult result = await UserManager.CreateAsync(user);if (!result.Succeeded) {return View("Error", result.Errors);} else {result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);if (!result.Succeeded) {return View("Error", result.Errors);} }}ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);ident.AddClaims(loginInfo.ExternalIdentity.Claims);AuthManager.SignIn(new AuthenticationProperties {IsPersistent = false }, ident);return Redirect(returnUrl ?? "/");}[Authorize]public ActionResult Logout() {AuthManager.SignOut();return RedirectToAction("Index", "Home");}private IAuthenticationManager AuthManager {get {return HttpContext.GetOwinContext().Authentication;} }private AppUserManager UserManager {get {return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();} }} } The GoogleLogin method creates an instance of the AuthenticationProperties class and sets the RedirectUri property to a URL that targets the GoogleLoginCallback action in the same controller. The next part is a magic phrase that causes ASP.NET Identity to respond to an unauthorized error by redirecting the user to the Google authentication page, rather than the one defined by the application: GoogleLogin方法创建了AuthenticationProperties类的一个实例,并为RedirectUri属性设置了一个URL,其目标为同一控制器中的GoogleLoginCallback动作。下一个部分是一个神奇阶段,通过将用户重定向到Google认证页面,而不是应用程序所定义的认证页面,让ASP.NET Identity对未授权的错误进行响应: ...HttpContext.GetOwinContext().Authentication.Challenge(properties, "Google");return new HttpUnauthorizedResult();... This means that when the user clicks the Log In via Google button, their browser is redirected to the Google authentication service and then redirected back to the GoogleLoginCallback action method once they are authenticated. 这意味着,当用户通过点击Google按钮进行登录时,浏览器被重定向到Google的认证服务,一旦在那里认证之后,便被重定向回GoogleLoginCallback动作方法。 I get details of the external login by calling the GetExternalLoginInfoAsync of the IAuthenticationManager implementation, like this: 我通过调用IAuthenticationManager实现的GetExternalLoginInfoAsync方法,我获得了外部登录的细节,如下所示: ...ExternalLoginInfo loginInfo = await AuthManager.GetExternalLoginInfoAsync();... The ExternalLoginInfo class defines the properties shown in Table 15-10. ExternalLoginInfo类定义的属性如表15-10所示: Table 15-10. The Properties Defined by the ExternalLoginInfo Class 表15-10. ExternalLoginInfo类所定义的属性 Name 名称 Description 描述 DefaultUserName Returns the username 返回用户名 Email Returns the e-mail address 返回E-mail地址 ExternalIdentity Returns a ClaimsIdentity that identities the user 返回标识该用户的ClaimsIdentity Login Returns a UserLoginInfo that describes the external login 返回描述外部登录的UserLoginInfo I use the FindAsync method defined by the user manager class to locate the user based on the value of the ExternalLoginInfo.Login property, which returns an AppUser object if the user has been authenticated with the application before: 我使用了由用户管理器类所定义的FindAsync方法,以便根据ExternalLoginInfo.Login属性的值对用户进行定位,如果用户之前在应用程序中已经认证,该属性会返回一个AppUser对象: ...AppUser user = await UserManager.FindAsync(loginInfo.Login);... If the FindAsync method doesn’t return an AppUser object, then I know that this is the first time that this user has logged into the application, so I create a new AppUser object, populate it with values, and save it to the database. I also save details of how the user logged in so that I can find them next time: 如果FindAsync方法返回的不是AppUser对象,那么我便知道这是用户首次登录应用程序,于是便创建了一个新的AppUser对象,填充该对象的值,并将其保存到数据库。我还保存了用户如何登录的细节,以便下次能够找到他们: ...result = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);... All that remains is to generate an identity the user, copy the claims provided by Google, and create an authentication cookie so that the application knows the user has been authenticated: 剩下的事情只是生成该用户的标识了,拷贝Google提供的声明(Claims),并创建一个认证Cookie,以使应用程序知道此用户已认证: ...ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user,DefaultAuthenticationTypes.ApplicationCookie);ident.AddClaims(loginInfo.ExternalIdentity.Claims);AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);... 15.4.2 Testing Google Authentication 15.4.2 测试Google认证 There is one further change that I need to make before I can test Google authentication: I need to change the account verification I set up in Chapter 13 because it prevents accounts from being created with e-mail addresses that are not within the example.com domain. Listing 15-25 shows how I removed the verification from the AppUserManager class. 在测试Google认证之前还需要一处修改:需要修改第13章所建立的账号验证,因为它不允许example.com域之外的E-mail地址创建账号。清单15-25显示了如何在AppUserManager类中删除这种验证。 Listing 15-25. Disabling Account Validation in the AppUserManager.cs File 清单15-25. 在AppUserManager.cs文件中取消账号验证 using Microsoft.AspNet.Identity;using Microsoft.AspNet.Identity.EntityFramework;using Microsoft.AspNet.Identity.Owin;using Microsoft.Owin;using Users.Models; namespace Users.Infrastructure {public class AppUserManager : UserManager<AppUser> {public AppUserManager(IUserStore<AppUser> store): base(store) {}public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options,IOwinContext context) {AppIdentityDbContext db = context.Get<AppIdentityDbContext>();AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db)); manager.PasswordValidator = new CustomPasswordValidator {RequiredLength = 6,RequireNonLetterOrDigit = false,RequireDigit = false,RequireLowercase = true,RequireUppercase = true}; //manager.UserValidator = new CustomUserValidator(manager) {// AllowOnlyAlphanumericUserNames = true,// RequireUniqueEmail = true//};return manager;} }} Tip you can use validation for externally authenticated accounts, but I am just going to disable the feature for simplicity. 提示:也可以使用外部已认证账号的验证,但这里出于简化,取消了这一特性。 To test authentication, start the application, click the Log In via Google button, and provide the credentials for a valid Google account. When you have completed the authentication process, your browser will be redirected back to the application. If you navigate to the /Claims/Index URL, you will be able to see how claims from the Google system have been added to the user’s identity, as shown in Figure 15-7. 为了测试认证,启动应用程序,通过点击“Log In via Google(通过Google登录)”按钮,并提供有效的Google账号凭据。当你完成了认证过程时,浏览器将被重定向回应用程序。如果导航到/Claims/Index URL,便能够看到来自Google系统的声明(Claims),已被添加到用户的标识中了,如图15-7所示。 Figure 15-7. Claims from Google 图15-7. 来自Google的声明(Claims) 15.5 Summary 15.5 小结 In this chapter, I showed you some of the advanced features that ASP.NET Identity supports. I demonstrated the use of custom user properties and how to use database migrations to preserve data when you upgrade the schema to support them. I explained how claims work and how they can be used to create more flexible ways of authorizing users. I finished the chapter by showing you how to authenticate users via Google, which builds on the ideas behind the use of claims. 本章向你演示了ASP.NET Identity所支持的一些高级特性。演示了自定义用户属性的使用,还演示了在升级数据架构时,如何使用数据库迁移保护数据。我解释了声明(Claims)的工作机制,以及如何将它们用于创建更灵活的用户授权方式。最后演示了如何通过Google进行认证结束了本章,这是建立在使用声明(Claims)的思想基础之上的。 本篇文章为转载内容。原文链接:https://blog.csdn.net/gz19871113/article/details/108591802。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-28 08:49:21
283
转载
转载文章
...毕业后在香港理工大学设计学院从事游戏引擎及相关技术的研发,职至项目主任。除发表学术文章外,也曾合著《DirectX9游戏编程实务》。2008年往上海育碧担任引擎工程师开发《美食从天而降(Cloudy with a Chance of Meatballs)》Xbox360/PS3/Wii/PC,2009年起于麻辣马开发《爱丽丝:疯狂回归(Alice: Madness Returns)》Xbox360/PS3/PC,2011年加入腾讯互动娱乐引擎技术中心担任专家工程师,所研发的技术已用于《斗战神》、《天涯明月刀》、《众神争霸》等项目中。 推荐序1 最初拿到《Game Engine Architecture》一书的英文版,是编辑侠少邮寄给我的打印版。他建议我接下翻译此书的合同。当时我正在杭州带领一个团队开发3D游戏引擎,我和我的同事都对这本书的内容颇有兴趣,两大本打印的英文书立刻在同事间传开。可惜那段时间个人精力顾及不来,把近千页的英文读物精读而后翻译成中文对个人的业余时间是个极大的挑战,不能担此翻译任务颇为遗憾。 不久以后听说Milo Yip(叶劲峰)已开始着手翻译,甚为欣喜。翻译此巨著,他一定是比我更合适的人选。我和Milo虽未曾蒙面,但神交已久。在网络上读过一些他的成长经历,和我颇为相似,心有戚戚。他对游戏3D实时渲染技术研究精深为我所不及,我们曾通过Google Talk讨论过许多技术问题,他都有独到的见解。翻译工作开始后,Milo是香港人,英文技术术语在香港的中文译法和大陆的有许多不同。但此书由大陆出版社出版,考虑到面对的读者主要是大陆程序员,Milo希望能更符合大陆程序员的用词习惯,所以在翻译一开始就通过Google Docs创建了协作页面,邀请大家共同探讨书中技术名词的中译名。从中我们可以一窥他作为译者的慎重。 三年之后,有幸在出版之前就拿到了完整的译本。这是一本用LaTeX精心排版的800页的电子书,我只花了一周时间,几乎是一口气读完。流畅的阅读享受,绝对不仅仅是因为原著精彩的内容,精美的版面和翔实的译注也加了不少分。 在阅读本书的过程中,我不只一次地获得共鸣。例如在第5章的内存管理系统的介绍中,作者介绍的几种游戏特有的内存管理方法我都曾在项目中用过,而这是第一次有书籍专门将这些方法详尽记录;又如第11章动画系统的介绍,我们也同样在3D引擎开发过程中改进原有动画片段混合方法的经历。虽然书中介绍的每个技术点,都可能可以在某篇论文,某本其他的书的章节,某篇网络blog上见过,但之前却无一本书可以把这些东西放在一起相互参照。对于从事游戏引擎开发的程序员来说,了解各种引擎在处理每个具体问题时的方案是相当重要的。而每种方案又各有利弊,即使不做引擎开发工作而是在某一特定游戏引擎上做游戏开发,从中也可以理解引擎的局限性以及可能的改进方法。尤其是第14章介绍的对游戏性相关系统的设计,各个开发人员几乎都是凭经验设计,很少见有书籍对这些做总结。对于基于渲染引擎做开发的游戏程序员,这是必须面对的工作,这一章会有很大的借鉴意义。 本书作者是业内资深的游戏引擎开发人,他所参于的《神秘海域》和《最后生还者》都是我的个人最爱。在玩游戏的过程中,作为游戏程序员的天性,自然会不断地猜想各个技术点是如何实现的,背后需要怎样的工具支持。能在书中一一得到印证是件特别开心的事情。作者反复强调代码实践的重要性,在书中遍布着C++代码。我不认为这些代码有直接取来使用的价值,但它们极大地帮助了读者理解书中的技术点。书中列出的顽皮狗工作室用lisp方言作为游戏配置脚本的范例也给我很大的启发,有了这些具体的代码示例以及作者本身的一线工程师背景,也让我确信书中那些关于主机游戏开发相关等,我所没有接触过的内容都也绝非泛泛而谈。 国内的游戏开发社区的壮大,主要是随最近十年的MMO风潮而生。而就在大型网络游戏在中国有些畸形发展,让这类游戏偏离电子游戏游戏性的趋势时,我们有幸迎来了为移动设备开发游戏的大潮。游戏开发的重心重新回到游戏性本身。我们更需要去借鉴单机游戏是如何为玩家带来更纯粹的游戏体验,我相信书中记录的各种技术点会变的更有帮助。 资深游戏开发及创业者 云风 @简悦云风 推荐序2 在我认识的许多游戏业开发同仁中,只有少数香港同胞,Milo Yip(叶劲峰)却正是这样一位给我印象非常深刻的优秀香港游戏开发者。我俩认识,是在Milo加入腾讯互动娱乐研发部引擎技术中心后,说来到现在也只是两年多时间。其间,他为人的谦逊务实,对待技术问题的严谨求真态度,对算法设计和性能优化的娴熟技术,都为人所称道。Milo一丝不苟的工作风格,甚至表现在对待技术文档排版这类事情上(Milo常执著地用LaTeX将技术文档排到完美),我想这一定是他在香港读大学、硕士及在香港理工大学的多媒体创新中心从事研究员,一贯沿袭至今的好作风。 我很高兴腾讯游戏有实力吸引到这样优秀的技术专家;即使在其已从上海迁回香港家中,依然选择到深圳腾讯互动娱乐总部工作。叶兄从此工作日每天早晚过关,来往香港和深圳两地,虽有舟车劳顿,但是兼顾了对家庭的照顾和在游戏引擎方面的专业研究,希望这样的状况是令他满意的。 认识叶兄当时,我便知道他在进行Jason Gregory所著《游戏引擎架构》一书的中译工作。因为自己从前也有业余翻译游戏开发有关书籍的经历,所以我能理解其中的辛苦和责任重大,对叶兄也更多一分钦佩。我以为,本书以及本书的中文读者最大的幸运便是,遇到叶兄这位对游戏有着如同对家对国般强烈责任感,犹如“游戏科学工作者”般的专业译者! 现在(2013年年末)无疑是游戏史上对独立游戏制作者最友好的年代。开发设备方便获得(相对过往仅由主机厂商授权才能获得专利开发设备,现在有一台智能手机和一台个人电脑就可以开发)、技术工具友好、调试过程简单方便,且互联网上有丰富的例程和开源代码参考,也有网上社区便于交流。很多爱好者能够很快地制作出可运行的游戏原型,其中一些也能发布到应用商店。 但是不全面掌握各方面知识,尤其是游戏引擎架构知识,往往只能停留在勉强修改、凑合重用别人提供的资源的应用程度上,难以做极限的性能改进,更妄谈革命式的架构创新。这样的程度是很难在成千上万的游戏中脱颖而出的。我们所认可的真正的游戏大作,必定是在某方面大幅超越用户期待的产品。为了打造这样的产品,游戏内容创作者(策划、美术等)需要“戴着镣铐跳舞”(在当前的机能下争取更多的创作自由度),而引擎架构合理的游戏可以经得起──也值得进行──反复优化,最终可以提供更多的自由度,这是大作出现的技术前提。 书的作者、译者、出版社的编者,加上读者,大家是因书而结缘的有缘人。因叶兄这本《游戏引擎架构》译著而在线上线下相识的读者们,你们是不是因“了解游戏引擎架构,从而制作/优化好游戏”这样的理想而结了缘呢? 亲爱的读者,愿你的游戏有一天因谜题巧妙绝伦、趣味超凡、虚拟世界气势磅礴、视觉效果逼真精美等专业因素取得业界褒奖,并得到玩家真诚的赞美。希望届时曾读叶兄这本《游戏引擎架构》译作的你,也可以回馈社会,回馈游戏开发的学习社区,帮助新人。希望你也可以建立微信公众号、博客等,或翻译游戏开发书籍,造福外语不好的读者,所以如果你的外语(英语、日语、韩语之于游戏行业比较重要)水平仍需精进,现在也可以同步加油了! 腾讯《天天爱消除》游戏团队Leader 沙鹰 @也是沙鹰 译序 数千年以来,艺术家们通过文学、绘画、雕塑、建筑、音乐、舞蹈、戏剧等传统艺术形式充实人类的精神层面。自20世纪中叶,计算机的普及派生出另一种艺术形式──电子游戏。游戏结合了上述传统艺术以及近代科技派生的其他艺术(如摄影、电影、动画),并且完全脱离了艺术欣赏这种单向传递的方式──游戏必然是互动的,“玩家”并不是“读者”、“观众”或“听众”,而是进入游戏世界、感知并对世界做出反应的参与者。 基于游戏的互动本质,游戏的制作通常比其他大众艺术复杂。商业游戏的制作通常需要各种人才的参与,而他们则需要依赖各种工具及科技。游戏引擎便是专门为游戏而设计的工具及科技集成。之所以称为引擎,如同交通工具中的引擎,提供了最核心的技术部分。因为复杂,研发成本高,人们不希望制作每款游戏(或车款)时都重新设计引擎,重用性是游戏引擎的一个重要设计目标。 然而,各游戏本身的性质以及平台的差异,使研发完全通用的游戏引擎变得极困难,甚至不可能。市面上出售的游戏引擎,有一些虽然已经达到很高的技术水平,但在商业应用中,很多时候还是需要因应个别游戏项目对引擎改造、整合、扩展及优化。因此,即使能使用市面上最好的商用引擎或自研引擎,我们仍需要理解当中的架构、各种机制和技术,并且分析及解决在制作中遇到的问题。这些也是译者曾任于上海两家工作室时的主要工作范畴。 选择翻译此著作,主要原因是在阅读中得到共鸣,并且能知悉一些知名游戏作品实际上所采用的方案。有感坊间大部分游戏开发书籍并不是由业内人士执笔,内容只足够应付一些最简单的游戏开发,欠缺宏观比较各种方案,技术与当今实际情况也有很大差距。而一些Gems类丛书虽然偶有好文章,但受形式所限欠缺系统性、全面性。难得本书原作者身为世界一流游戏工作室的资深游戏开发者(注1),在繁重的游戏开发工作外,还在大学教授游戏开发课程以至编写本著作。此外,从与内地同事的交流中,了解到许多从业者不愿意阅读外文书籍。为了普及知识及反馈业界社会,希望能尽绵力。 或许有些人以为本著作是针对单机/游戏机游戏的,并不适合国内以网游为主的环境。但译者认为这是一种误解,许多游戏本身所涉及的技术是具通用性的。例如游戏性相关的游戏性系统、场景管理、人工智能、物理模拟等部分,许多时候也会同时用于网游的前台和后台。现时,一些动作为主、非MMO的国内端游甚至会直接在后台运行传统意义上的游戏引擎。至于前台相关的技术,单机和端游的区别更少。此外,随着近年移动终端的兴起,其硬件性能已超越传统掌上游戏机,开发手游所需的技术与传统掌上游戏机并无太大差异。还可预料,现时单机/游戏机的一些较高级的架构及技术,将在不远的未来着陆移动终端平台。 译者认为,本书涵括游戏开发技术的方方面面,同时适合入门及经验丰富的游戏程序员。书名中的架构二字,并不单是给出一个系统结构图,而是描述每个子系统的需求、相关技术及与其他子系统的关系。对译者本人而言,本书的第11章(动画系统)及第14章(运行时游戏性基础系统)是本书特別精彩之处,含有许多少见于其他书籍的内容。而第10章(渲染引擎)由于是游戏引擎中的一个极大的部分,有限的篇幅可能未能覆盖广度及深度,推荐读者参考[1](注2),人工智能方面也需参考其他专著。 本译作采用LaTeX排版(注3),以Inkscape编译矢量图片。为了令阅读更流畅,内文中的网址都统一改以脚注标示。另外,由于现时游戏开发相关的文献以英文为主,而且游戏开发涉及的知识面很广,本译作尽量以括号形式保留英文术语。为了方便读者查找内容,在附录中增设中英文双向索引(索引条目与原著的不同)。 本人在香港成长学习及工作,至2008年才赴内地游戏工作室工作,不黯内地的中文写作及用字习惯,翻译中曾遇到不少困难。有幸得到出版社人员以及良师益友的帮助,才能完成本译作。特别感谢周筠老师支持本作的提案,并耐心地给予协助及鼓励。编辑张春雨老师和卢鸫翔老师,以及好友余晟给予了大量翻译上的知识及指导。也感谢游戏业界专家云风、大宝和Dave给予了许多宝贵意见。此书的翻译及排版工作比预期更花时间,感谢妻子及儿女们的体谅。此次翻译工作历时三年半,因工作及家庭事宜导致严重延误,唯有在翻译及排版工作上更尽心尽力,希望求得等待此译作的读者们谅解。无论是批评或建议,诚希阁下通过电邮miloyip@gmail.com、新浪微博、豆瓣等渠道不吝赐教。 叶劲峰(Milo Yip) 2013年10月 原作者是顽皮狗(Naughty Dog)《神秘海域(Uncharted)》系列的通才程序员、《最后生还者(The Last of Us)》的首席程序员,之前还曾在EA和Midway工作。 中括号表示引用附录中的参考文献。一些参考条目加入了其中译本的信息。 具体是使用CTEX套装,它是在MiKTeX的基础上增加中文的支持。 前言 最早的电子游戏完全由硬件构成,但微处理器(microprocessor)的高速发展完全改变了游戏的面貌。现在的游戏是在多用途的PC和专门的电子游戏主机(video game console)上玩的,凭借软件带来绝妙的游戏体验。从最初的游戏诞生至今已有半个世纪,但很多人仍然认为游戏是一个未成熟的产业。即使游戏可能是个年轻的产业,若仔细观察,也会发现它正在高速发展。 现时游戏已成为一个上百亿美元的产业,覆盖不同年龄、性别的广泛受众。 千变万化的游戏,可以分为从纸牌游戏到大型多人在线游戏(massively multiplayer online game,MMOG)等多个种类(category)和“类型(genre)”(注1),也可以运行在任何装有微芯片(microchip)的设备上 。你现在可以在PC、手机及多种特别为游戏而设计的手持/电视游戏主机上玩游戏。家用电视游戏通常代表最尖端的游戏科技,又由于它们是周期性地推出新版本,因此有游戏机“世代”(generation)的说法。最新一代(注2)的游戏机包括微软的Xbox 360和索尼的PlayStation 3,但一定不可忽视长盛不衰的PC,以及最近非常流行的任天堂Wii。 最近,剧增的下载式休闲游戏,使这个多样化的商业游戏世界变得更复杂。虽然如此,大型游戏仍然是一门大生意。今天的游戏平台非常复杂,有难以置信的运算能力,这使软件的复杂度得以进一步提升。所有这些先进的软件都需要由人创造出来,这导致团队人数增加,开发成本上涨。随着产业变得成熟,开发团队要寻求更好、更高效的方式去制作产品,可复用软件(reusable software)和中间件(middleware)便应运而生,以补偿软件复杂度的提升。 由于有这么多风格迥异的游戏及多种游戏平台,因此不可能存在单一理想的软件方案。然而,业界已经发展出一些模式 ,也有大量的潜在方案可供选择。现今的问题是如何找到一个合适的方案去迎合某个项目的需要。再进一步,开发团队必须考虑项目的方方面面,以及如何把各方面集成。对于一个崭新的游戏设计,鲜有可能找到一个完美搭配游戏设计各方面的软件包。 现时业界内的老手,入行时都是“开荒牛”。我们这代人很少是计算机科学专业出身(Matt的专业是航空工程、Jason的专业是系统设计工程),但现时很多学院已设有游戏开发的课程和学位。时至今日,为了获取有用的游戏开发信息,学生和开发者必须找到好的途径。对于高端的图形技术,从研究到实践都有大量高质量的信息。可是,这些信息经常不能直接应用到游戏的生产环境,或者没有一个生产级质量的实现。对于图形以外的游戏开发技术,市面上有一些所谓的入门书籍,没提及参考文献就描述很多内容细节,像自己发明的一样。这种做法根本没有用处,甚至经常带有不准确的内容。另一方面,市场上有一些高端的专门领域书籍,例如物理、碰撞、人工智能等。可是,这类书或者啰嗦到让你难以忍受,或者高深到让部分读者无法理解,又或者内容过于零散而难于融会贯通。有一些甚至会直接和某项技术挂钩,软硬件一旦改动,其内容就会迅速过时。 此外,互联网也是收集相关知识的绝佳工具。可是,除非你确实知道要找些什么,否则断链、不准确的资料、质量差的内容也会成为学习障碍。 好在,我们有Jason Gregory,他是一位拥有在顽皮狗(Naughty Dog)工作经验的业界老手,而顽皮狗是全球高度瞩目的游戏工作室之一。Jason在南加州大学教授游戏编程课程时,找不到概括游戏架构的教科书。值得庆幸的是,他承担了这个任务,填补了这个空白。 Jason把应用到实际发行游戏的生产级别知识,以及整个游戏开发的大局编集于本书。他凭经验,不仅融汇了游戏开发的概念和技巧,还用实际的代码示例及实现例子去说明怎样贯通知识来制作游戏。本书的引用及参考文献可以让读者更深入探索游戏开发过程的各方面。虽然例子经常是基于某些技术的,但是概念和技巧是用来实际创作游戏的,它们可以超越个别引擎或API的束缚。 本书是一本我们入行做游戏时想要的书。我们认为本书能让入门者增长知识,也能为有经验者开拓更大的视野。 Jeff Lander(注3) Matthew Whiting(注4) 译注:Genre一词在文学中为体裁。电影和游戏里通常译作类型。不同的游戏类型可见1.2节。 译注:按一般说法,2005年至今属于第7个游戏机世代。这3款游戏机的发行年份为Xbox 360(2005)、PlayStation 3(2006)、Wii(2006)。有关游戏机世代可参考维基百科。 译注:Jeff Lander现时为Darwin 3D公司的首席技术总监、Game Tech公司创始人,曾为艺电首席程序员、Luxoflux公司游戏性及动画技术程序员。 译注:Matthew Whiting现时为Wholesale Algorithms公司程序员,曾为Luxoflux公司首席软件工程师、Insomniac Games公司程序员。 序言 欢迎来到《游戏引擎架构》世界。本书旨在全面探讨典型商业游戏引擎的主要组件。游戏编程是一个庞大的主题,有许多内容需要讨论。不过相信你会发现,我们讨论的深度将足以使你充分理解本书所涵盖的工程理论及常用实践的方方面面。话虽如此,令人着迷的漫长游戏编程之旅其实才刚刚启程。与此相关的每项技术都包含丰富内容,本书将为你打下基础,并引领你进入更广阔的学习空间。 本书焦点在于游戏引擎的技术及架构。我们会探讨商业游戏引擎中,各个子系统的相关理论,以及实现这些理论所需要的典型数据结构、算法和软件接口。游戏引擎与游戏的界限颇为模糊。我们将把注意力集中在引擎本身,包括多个低阶基础系统(low-level foundation system)、渲染引擎(rendering engine)、碰撞系统(collision system)、物理模拟(physics simulation)、人物动画(character animation),及一个我称为游戏性基础层(gameplay foundation layer)的深入讨论。此层包括游戏对象模型(game object model)、世界编辑器(world editor)、事件系统(event system)及脚本系统(scripting system)。我们也将会接触游戏性编程(gameplay programming)的多个方面,包括玩家机制(player mechanics)、摄像机(camera)及人工智能(artificial intelligence,AI)。然而,这类讨论会被限制在游戏性系统和引擎接口范围。 本书可以作为大学中等级游戏程序设计中两到三门课程的教材。当然,本书也适合软件工程师、业余爱好者、自学的游戏程序员,以及游戏行业从业人员。通过阅读本书,资历较浅的游戏程序员可以巩固他们所学的游戏数学、引擎架构及游戏科技方面的知识。专注某一领域的资深程序员也能从本书更为全面的介绍中获益。 为了更好地学习本书内容,你需要掌握基本的面向对象编程概念并至少拥有一些C++编程经验。尽管游戏行业已经开始尝试使用一些新的、令人兴奋的编程语言,然而工业级的3D游戏引擎仍然是用C或C++编写的,任何认真的游戏程序员都应该掌握C++。我们将在第3章重温一些面向对象编程的基本原则,毫无疑问,你还会从本书学到一些C++的小技巧,不过C++的基础最好还是通过阅读[39]、[31]及[32]来获得。如果你对C++已经有点生疏,建议你在阅读本书的同时,最好能重温这几本或者类似书籍。如果你完全没有C++经验,在看本书之前,可以考虑先阅读[39]的前几章,或者尝试学习一些C++的在线教程。 学习编程技能最好的方法就是写代码。在阅读本书时,强烈建议你选择一些特别感兴趣的主题付诸实践。举例来说,如果你觉得人物动画很有趣,那么可以首先安装OGRE,并测试一下它的蒙皮动画示范。接着还可以尝试用OGRE实现本书谈及的一些动画混合技巧。下一步你可能会打算用游戏手柄控制人物在平面上行走。等你能玩转一些简单的东西了,就应该以此为基础,继续前进!之后可以转移到另一个游戏技术范畴,周而复始。这些项目是什么并不重要,重要的是你在实践游戏编程的艺术,而不是纸上谈兵。 游戏科技是一个活生生、会呼吸的家伙 ,永远不可能将之束缚于书本之上 。因此,附加的资源、勘误、更新、示例代码、项目构思等已经发到本书的网站。 目录 推荐序1 iii推荐序2 v译序 vii序言 xvii前言 xix致谢 xxi第一部分 基础 1第1章 导论 31.1 典型游戏团队的结构 41.2 游戏是什么 71.3 游戏引擎是什么 101.4 不同游戏类型中的引擎差异 111.5 游戏引擎概观 221.6 运行时引擎架构 271.7 工具及资产管道 46第2章 专业工具 532.1 版本控制 532.2 微软Visual Studio 612.3 剖析工具 782.4 内存泄漏和损坏检测 792.5 其他工具 80第3章 游戏软件工程基础 833.1 重温C++及最佳实践 833.2 C/C++的数据、代码及内存 903.3 捕捉及处理错误 118第4章 游戏所需的三维数学 1254.1 在二维中解决三维问题 1254.2 点和矢量 1254.3 矩阵 1394.4 四元数 1564.5 比较各种旋转表达方式 1644.6 其他数学对象 1684.7 硬件加速的SIMD运算 1734.8 产生随机数 180第二部分 低阶引擎系统 183第5章 游戏支持系统 1855.1 子系统的启动和终止 1855.2 内存管理 1935.3 容器 2085.4 字符串 2255.5 引擎配置 234第6章 资源及文件系统 2416.1 文件系统 2416.2 资源管理器 251第7章 游戏循环及实时模拟 2777.1 渲染循环 2777.2 游戏循环 2787.3 游戏循环的架构风格 2807.4 抽象时间线 2837.5 测量及处理时间 2857.6 多处理器的游戏循环 2967.7 网络多人游戏循环 304第8章 人体学接口设备(HID) 3098.1 各种人体学接口设备 3098.2 人体学接口设备的接口技术 3118.3 输入类型 3128.4 输出类型 3168.5 游戏引擎的人体学接口设备系统 3188.6 人体学接口设备使用实践 332第9章 调试及开发工具 3339.1 日志及跟踪 3339.2 调试用的绘图功能 3379.3 游戏内置菜单 3449.4 游戏内置主控台 3479.5 调试用摄像机和游戏暂停 3489.6 作弊 3489.7 屏幕截图及录像 3499.8 游戏内置性能剖析 3499.9 游戏内置的内存统计和泄漏检测 356第三部分 图形及动画 359第10章 渲染引擎 36110.1 采用深度缓冲的三角形光栅化基础 36110.2 渲染管道 40410.3 高级光照及全局光照 42610.4 视觉效果和覆盖层 43810.5 延伸阅读 446第11章 动画系统 44711.1 角色动画的类型 44711.2 骨骼 45211.3 姿势 45411.4 动画片段 45911.5 蒙皮及生成矩阵调色板 47111.6 动画混合 47611.7 后期处理 49311.8 压缩技术 49611.9 动画系统架构 50111.10 动画管道 50211.11 动作状态机 51511.12 动画控制器 535第12章 碰撞及刚体动力学 53712.1 你想在游戏中加入物理吗 53712.2 碰撞/物理中间件 54212.3 碰撞检测系统 54412.4 刚体动力学 56912.5 整合物理引擎至游戏 60112.6 展望:高级物理功能 616第四部分 游戏性 617第13章 游戏性系统简介 61913.1 剖析游戏世界 61913.2 实现动态元素:游戏对象 62313.3 数据驱动游戏引擎 62613.4 游戏世界编辑器 627第14章 运行时游戏性基础系统 63714.1 游戏性基础系统的组件 63714.2 各种运行时对象模型架构 64014.3 世界组块的数据格式 65714.4 游戏世界的加载和串流 66314.5 对象引用与世界查询 67014.6 实时更新游戏对象 67614.7 事件与消息泵 69014.8 脚本 70714.9 高层次的游戏流程 726第五部分 总结 727第15章 还有更多内容吗 72915.1 一些未谈及的引擎系统 72915.2 游戏性系统 730参考文献 733中文索引 737英文索引 755 参考文献 Tomas Akenine-Moller, Eric Haines, and Naty Hoffman. Real-Time Rendering (3rd Edition). Wellesley, MA: A K Peters, 2008. 中译本:《实时计算机图形学(第2版)》,普建涛译,北京大学出版社,2004. Andrei Alexandrescu. Modern C++ Design: Generic Programming and Design Patterns Applied. Resding, MA: Addison-Wesley, 2001. 中译本:《C++设计新思维:泛型编程与设计模式之应用》,侯捷/於春景译,华中科技大学出版社,2003. Grenville Armitage, Mark Claypool and Philip Branch. Networking and Online Games: Understanding and Engineering Multiplayer Internet Games. New York, NY: John Wiley and Sons, 2006. James Arvo (editor). Graphics Gems II. San Diego, CA: Academic Press, 1991. Grady Booch, Robert A. Maksimchuk, Michael W. Engel, Bobbi J. Young, Jim Conallen, and Kelli A. Houston. Object-Oriented Analysis and Design with Applications (3rd Edition). Reading, MA: Addison-Wesley, 2007. 中译本:《面向对象分析与设计(第3版)》,王海鹏/潘加宇译,电子工业出版社,2012. Mark DeLoura (editor). Game Programming Gems. Hingham, MA: Charles River Media, 2000. 中译本:《游戏编程精粹 1》, 王淑礼译,人民邮电出版社,2004. Mark DeLoura (editor). Game Programming Gems 2. Hingham, MA: Charles River Media, 2001. 中译本:《游戏编程精粹 2》,袁国忠译,人民邮电出版社,2003. Philip Dutré, Kavita Bala and Philippe Bekaert. Advanced Global Illumination (2nd Edition). Wellesley, MA: A K Peters, 2006. David H. Eberly. 3D Game Engine Design: A Practical Approach to Real-Time Computer Graphics. San Francisco, CA: Morgan Kaufmann, 2001. 国内英文版:《3D游戏引擎设计:实时计算机图形学的应用方法(第2版)》,人民邮电出版社,2009. David H. Eberly. 3D Game Engine Architecture: Engineering Real-Time Applications with Wild Magic. San Francisco, CA: Morgan Kaufmann, 2005. David H. Eberly. Game Physics. San Francisco, CA: Morgan Kaufmann, 2003. Christer Ericson. Real-Time Collision Detection. San Francisco, CA: Morgan Kaufmann, 2005. 中译本:《实时碰撞检测算法技术》,刘天慧译,清华大学出版社,2010. Randima Fernando (editor). GPU Gems: Programming Techniques, Tips and Tricks for Real-Time Graphics. Reading, MA: Addison-Wesley, 2004. 中译本:《GPU精粹:实时图形编程的技术、技巧和技艺》,姚勇译,人民邮电出版社,2006. James D. Foley, Andries van Dam, Steven K. Feiner, and John F. Hughes. Computer Graphics: Principles and Practice in C (2nd Edition). Reading, MA: Addison-Wesley, 1995. 中译本:《计算机图形学原理及实践──C语言描述》,唐泽圣/董士海/李华/吴恩华/汪国平译,机械工业出版社,2004. Grant R. Fowles and George L. Cassiday. Analytical Mechanics (7th Edition). Pacific Grove, CA: Brooks Cole, 2005. John David Funge. AI for Games and Animation: A Cognitive Modeling Approach. Wellesley, MA: A K Peters, 1999. Erich Gamma, Richard Helm, Ralph Johnson, and John M. Vlissiddes. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1994. 中译本:《设计模式:可复用面向对象软件的基础》,李英军/马晓星/蔡敏/刘建中译,机械工业出版社,2005. Andrew S. Glassner (editor). Graphics Gems I. San Francisco, CA: Morgan Kaufmann, 1990. Paul S. Heckbert (editor). Graphics Gems IV. San Diego, CA: Academic Press, 1994. Maurice Herlihy, Nir Shavit. The Art of Multiprocessor Programming. San Francisco, CA: Morgan Kaufmann, 2008. 中译本:《多处理器编程的艺术》,金海/胡侃译,机械工业出版社,2009. Roberto Ierusalimschy, Luiz Henrique de Figueiredo and Waldemar Celes. Lua 5.1 Reference Manual. Lua.org, 2006. Roberto Ierusalimschy. Programming in Lua, 2nd Edition. Lua.org, 2006. 中译本:《Lua程序设计(第2版)》,周惟迪译,电子工业出版社,2008. Isaac Victor Kerlow. The Art of 3-D Computer Animation and Imaging (2nd Edition). New York, NY: John Wiley and Sons, 2000. David Kirk (editor). Graphics Gems III. San Francisco, CA: Morgan Kaufmann, 1994. Danny Kodicek. Mathematics and Physics for Game Programmers. Hingham, MA: Charles River Media, 2005. Raph Koster. A Theory of Fun for Game Design. Phoenix, AZ: Paraglyph, 2004. 中译本:《快乐之道:游戏设计的黄金法则》,姜文斌等译,百家出版社,2005. John Lakos. Large-Scale C++ Software Design. Reading, MA: Addison-Wesley, 1995. 中译本:《大规模C++程序设计》,李师贤/明仲/曾新红/刘显明译,中国电力出版社,2003. Eric Lengyel. Mathematics for 3D Game Programming and Computer Graphics (2nd Edition). Hingham, MA: Charles River Media, 2003. Tuoc V. Luong, James S. H. Lok, David J. Taylor and Kevin Driscoll. Internationalization: Developing Software for Global Markets. New York, NY: John Wiley & Sons, 1995. Steve Maguire. Writing Solid Code: Microsoft's Techniques for Developing Bug Free C Programs. Bellevue, WA: Microsoft Press, 1993. 国内英文版:《编程精粹:编写高质量C语言代码》,人民邮电出版社,2009. Scott Meyers. Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition). Reading, MA: Addison-Wesley, 2005. 中译本:《Effective C++:改善程序与设计的55个具体做法(第3版)》,侯捷译,电子工业出版社,2011. Scott Meyers. More Effective C++: 35 New Ways to Improve Your Programs and Designs. Reading, MA: Addison-Wesley, 1996. 中译本:《More Effective C++:35个改善编程与设计的有效方法(中文版)》,侯捷译,电子工业出版社,2011. Scott Meyers. Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library. Reading, MA: Addison-Wesley, 2001. 中译本:《Effective STL:50条有效使用STL的经验》,潘爱民/陈铭/邹开红译,电子工业出版社,2013. Ian Millington. Game Physics Engine Development. San Francisco, CA: Morgan Kaufmann, 2007. Hubert Nguyen (editor). GPU Gems 3. Reading, MA: Addison-Wesley, 2007. 中译本:《GPU精粹3》,杨柏林/陈根浪/王聪译,清华大学出版社,2010. Alan W. Paeth (editor). Graphics Gems V. San Francisco, CA: Morgan Kaufmann, 1995. C. Michael Pilato, Ben Collins-Sussman, and Brian W. Fitzpatrick. Version Control with Subversion (2nd Edition). Sebastopol , CA: O'Reilly Media, 2008. (常被称作“The Subversion Book”,线上版本.) 国内英文版:《使用Subversion进行版本控制》,开明出版社,2009. Matt Pharr (editor). GPU Gems 2: Programming Techniques for High-Performance Graphics and General-Purpose Computation. Reading, MA: Addison-Wesley, 2005. 中译本:《GPU精粹2:高性能图形芯片和通用计算编程技巧》,龚敏敏译,清华大学出版社,2007. Bjarne Stroustrup. The C++ Programming Language, Special Edition (3rd Edition). Reading, MA: Addison-Wesley, 2000. 中译本《C++程序设计语言(特别版)》,裘宗燕译,机械工业出版社,2010. Dante Treglia (editor). Game Programming Gems 3. Hingham, MA: Charles River Media, 2002. 中译本:《游戏编程精粹3》,张磊译,人民邮电出版社,2003. Gino van den Bergen. Collision Detection in Interactive 3D Environments. San Francisco, CA: Morgan Kaufmann, 2003. Alan Watt. 3D Computer Graphics (3rd Edition). Reading, MA: Addison Wesley, 1999. James Whitehead II, Bryan McLemore and Matthew Orlando. World of Warcraft Programming: A Guide and Reference for Creating WoW Addons. New York, NY: John Wiley & Sons, 2008. 中译本:《魔兽世界编程宝典:World of Warcraft Addons完全参考手册》,杨柏林/张卫星/王聪译,清华大学出版社,2010. Richard Williams. The Animator's Survival Kit. London, England: Faber & Faber, 2002. 中译本:《原动画基础教程:动画人的生存手册》,邓晓娥译,中国青年出版社,2006. 勘误 第1次印册(2014年2月) P.xviii: 译注中 Wholesale Algoithms -> Wholesale Algorithms P.10: 最后一段第一行 微软的媒体播放器 -> 微软的Windows Media Player (多谢读者OpenGPU来函指正) P.15: 1.4.3节第三点 按妞 -> 按钮 (多谢读者一个小小凡人来函指正) P.40: 正文最后一行 按扭 -> 按钮 P.50: 1.7.8节第二节第一行 同是 -> 同时 (多谢读者czfdd来函指正) P.98: 代码 writeExampleStruct(Example& ex, Stream& ex) 中 Stream& ex -> Stream& stream (多谢读者Snow来函指正) P.106: 第一段中有六处 BBS -> BSS,最后一段代码的注释也有同样错误 (多谢读者trout来函指正) P.119: 译注中 软体工程 -> 软件工程 (多谢读者Snow来函指正) P.214: 正文第一段有两处 虚内存 -> 虚拟内存 (多谢读者Snow来函指正) P.216: 脚注24应标明为译注 (多谢读者Snow来函指正) P.221: 第一段代码的第二个断言应为 ASSERT(link.m_pPrev != NULL); (多谢读者Snow来函指正) P.230: 5.4.4.1节 第二段 软体 -> 软件 P.286: 脚注4应标明为译注 (多谢读者Snow来函指正) P.322: 第二段 按扭事件字 -> 按钮事件 P.349: 9.8节第二段第二行两处 部析器 -> 剖析器 (多谢读者Snow来函指正) P.738-572: 双数页页眉 参考文献 -> 中文索引 P.755-772: 双数页页眉 参考文献 -> 英文索引 P.755: kd tree项应归入K而不是Symbols 以上的错误已于第2次印册中修正。 第2次印册及之前 P.11: 第四行 细致程度 -> 层次细节 (这是level-of-detail/LOD的内地通译,多谢读者OpenGPU来函指正) P.12: 正文第一段及图1.2标题 使命之唤 -> 使命召唤 (多谢读者OpenGPU来函指正) P.12: 正文第一段 战栗时空 -> 半条命 (多谢读者OpenGPU来函指正) P.16: 第一点 表面下散射 -> 次表面散射 (多谢读者OpenGPU来函指正) P.17: 1.4.4节第五行 次文化 -> 亚文化 (此译法在内地更常用。多谢读者OpenGPU来函提示) P.22: 战栗时空 -> 半条命 P.24: 战栗时空2 -> 半条命2 P.34: 1.6.8.2节第一行 提呈 -> 提交 (这术语在本书其他地方都写作提交。多谢读者OpenGPU来函提示) P.35: 第七行 提呈 -> 提交 (这术语在本书其他地方都写作提交。多谢读者OpenGPU来函提示) P.50: 战栗时空2 -> 半条命2 P.365: 第四段第二行: 细致程度 -> 层次细节 P.441: 10.4.3.2节第三行 细致程度 -> 层次细节 P.494: sinusiod -> sinusoid (多谢读者OpenGPU来函指正) P.511: 11.10.4节第一行 谈入 -> 淡入 (多谢读者Snow来函指正) P.541: 战栗时空2 -> 半条命2 P.627: 战栗时空2 -> 半条命2 P.654: 第二行 建康值 -> 血量 (原来是改正错别字,但译者发现应改作前后统一使用的“血量”。多谢读者Snow来函指正) P.692: 第二行 内部分式 -> 内部方式 (多谢读者Snow来函指正) P.696: 14.7.6节第四行 不设实际 -> 不切实际 (多谢读者Snow来函指正) 以上的错误已于第3次印册中修正。 其他意见 P.220: 正文第一段 m_root.m_pElement 和 P.218 第一段代码中的 m_pElem 不统一。原文有此问题,但因为它们是不同的struct,暂不列作错误。 (多谢读者Snow来函提示) P.331: 8.5.8节第二段中 “反覆”较常见的写法为“反复”,但前者也是正确的,暂不列作错误。 (多谢读者Snow来函提示) P.390: 10.1.3.3节静态光照第二段中“取而代之,我们会使用一张光照纹理贴到所有受光源影响范围内的物体上。这样做能令动态物体经过光源时得到正确的光照。” 后面的一句与前句好像难以一起理解。译者认为,作者应该是指,使用同一静态光源去为静态物件生成光照纹理,以及用于动态对象的光照,能使两者的效果维持一致性。译者会考虑对译文作出改善或加入译注解译。(多谢读者店残来函查询) P.689: 第五行 并行处理世代 -> 并行处理时代 是对era较准确的翻译。 (多谢读者Snow来函提示) 本篇文章为转载内容。原文链接:https://blog.csdn.net/mypongo/article/details/38388381。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-12 23:04:05
327
转载
转载文章
...on and requires separate license. Other Sankey diagram Chord, Chord directed, and Chord non-ribbon diagrams Pack Treemap Tree Sunburst Partition Force-directed tree Radar & Polar Word cloud Venn diagram Efficiency built-in Canvas rendering amCharts 5 uses browser’s Canvas API which in most cases is way faster than SVG. Less moving parts in the DOM tree, faster rendering. Layering Common element groups are isolated into separate independent canvases, so that heavily updated sections do not trigger expensive repaints in places that do not change. Fast data processing Data processing in amCharts 5 is designed to be as efficient as possible. Incremental updates, lack of repetitive aggregations, and lightweight data object use makes data processing fast and very memory-efficient. Faster dashboards amCharts 5 is capable of running scores of charts on the same page, without crippling the browser, due to its lightweight approach to data parsing and rendering. Tiny binaries We made amCharts 5 really small – the core functionality compiles to a file of only around 400KB. Each niche functionality is separated into files, so you load only what you really need. Users will surely appreciate faster load times. Better tree-shaking We also designed amCharts 5 to be extremely tree-shakable. If you are using Webpack or similar packager, only code that is really needed will be included into your final application. The most advanced chart package Classics with some new twists XY charts are now so powerful and flexible, you can plot any data on them. Number, date, duration, or category axes are supported, in all directions. Pie charts are now fully nestable, with support for custom start and end angles, to create half circles. New geo maps Our maps use GeoJSON format. Being open and widely accepted standard it opens up a lot of possibilities and sources for ready-made and custom maps. Furthermore, maps are now very flexible, with multi-series support, configurable down to the nut and bolt. (Maps is and add-on to amCharts 5: Charts which requires separate license) More about amCharts 5: Maps Pictorials Create multi-layer, multi-series pictorial charts. Any SVG path can be used as a shape for your chart. Sankey Diagrams Stunning flow diagrams, in horizontal and vertical. With draggable, fully configurable nodes. Enhanced radar charts With stacked column, bands, axes, and other dramatic enhancements, radar charts are now way more useful. Treemaps Completely zoomable, multi-level, highly configurable. Heatmaps Automatically build heat-maps, with custom axes, color ranges, and awesome new interactive Heat Legend. Create heatmaps using colors, or point size or both. Universal and flexible heat rules allow attaching any value in data to any property or properties on any element. Chord diagrams Visualize your 2-way relational data in a neat circular Chord diagrams. We do have different variations of the classic diagram: Chord, Chord directed, and Chord non-ribbon. True funnel charts amCharts 5 offers true Funnel charts the way they were meant to be. Slice’s area size represents the value, so each step’s influence on overall volume reduction is more prominent than with basic funnels. Trapezoid form can also be configured to further emphasize reduction. Complete it with other visual elements, like fully configurable slice lines, multiple series support, togglable legends, and many many more options. Customizable beauty built-in Powerful theme engine amCharts 5 comes with a bunch of beautiful themes as well as a super flexible theme engine, which you can use. We devised a CSS-like rule-based theme targeting system in themes. Using, creating, customizing themes or standalone rules has never been so easy. The new system allows applying defaults to elements based on their type, features, or position in a virtual element tree. Fresh new look Default looks designed to look fresh, like something out of tomorrow. Carefully selected color schemes and default settings were specifically chosen to make the charts stand out. Silk-smooth animations Every setting – colors, positions, sizes, opacity, and many more – is animatable to ensure smooth transitions. No choppy, stepped animations – everything is fluid, including zoom and toggling of series and other items. Flexibility Element templates Most elements are created using templates: a collection of default settings, events, and adapters. Changing template automatically propagates changes to actual elements, making it easy to do batch updates. Everything’s configurable Numerous configuration options allow inventive uses, bordering on new chart types. Angles, colors, positions, radii, a-n-y-t-h-i-n-g can be set to bend classic and new chart types exactly the way you need them. Element states Easily change how an element looks like under different circumstances, e.g. on some interaction, hover, click, or related to data (eg. column look if a value is down). The engine will automatically apply the required properties as needed, animating between old and new values smoothly. Create and apply custom states via the API. Multi-type multi-axis support Add any number of axes of any type. Create overlaid comparison of different time scales. Use any mix of dimensional values: numbers, dates, categories, or duration. Adapters “Adapters” functionality allows plugging in custom code to dynamically override just about any setting or data value. Text formatting All text labels – tooltips, axis labels, titles, etc. – now support rich text formatting options, like changing colors, font weight, or applying just about any styling option from the CSS arsenal. In addition to formatting support, labels can now contain in-line placeholders for real data, with the ability to apply custom formatting to values. Accessibility & Interactivity Accessibility Accessibility was riding shotgun when amCharts 5 was being developed. All interactive elements are TAB-selectable, with customizable roles, order, and screen-reader texts. Everything that can be moved by touch or mouse, can be moved by keyboard. Everything that can be clicked or toggled, can be interacted with with keyboard, too. Touch support Charts have been designed to work with touch devices out-of-the-box. They will work not only on phones or tablets, but also touch capable computers. Under the hood Built with TypeScript Supports strong type and error checking in TypeScript applications. Enjoy code completion, error checking and dynamic help popups in major IDEs. Full support for TypeScript and ES6 modules. 100% for JavaScript Can be fully used in any vanilla JavaScript application. amCharts 5 does not use or rely on globals, external frameworks or 3rd party libraries. Universal rendering engine Can be used to build dynamic, interactive Canvas-based interfaces and applications. Add various elements to the screen, make them interactive, with a few lines of code. Make them clickable, draggable, hoverable, using built-in interactivity functionality. Our universal layout engine will place, size and arrange elements according to set rules. 本篇文章为转载内容。原文链接:https://blog.csdn.net/john_dwh/article/details/127460821。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-17 18:18:34
351
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
sudo su - user
- 切换到指定用户(需有sudo权限)。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"