oauth身份验证方式

用户组列表和信息(UGLI)应用程序开始很好地成形。 现在,您可以通过在“ 具有响应式Web设计的MEAN和UGLI CRUD ”中设置的CRUD屏幕显示您创建的本地内容。 您还可以使用在“ MEAN遇见Meetup.com和微数据 ”中开发的服务来合并来自外部站点的内容。

与公众共享会议信息是该项目的重要组成部分。 但是,作为用户组组长,我也希望将某些活动限制为该组的注册成员。 例如,通过关闭匿名访问并要求登录,我可能会选择在评论我们的演示文稿时保留一些礼貌。 因此,在本期中,您将使用Meetup.com的OAuth服务为UGLI应用提供登录功能。 请参阅下载以获取示例代码。

创建一个新的用户帐号

通过单击Sign up按钮(如图1所示),您的应用程序用户可以创建一个新帐户,该帐户存储在本地MongoDB中。 此功能内置于您的应用中-无需其他编程。

图1. UGLI注册页面
关于本系列

MEAN(MongoDB,Express,AngularJS,Node.js)堆栈是长期流行的LAMP堆栈的现代挑战者,用于使用开源软件构建专业网站。 MEAN代表了体系结构和思维模型的重大转变-从关系数据库到NoSQL,从服务器端的Model-View-Controller到客户端的单页应用程序。 在本系列中,将学习MEAN堆栈的技术如何相互补充,以及如何使用该堆栈来创建二十一世纪的现代全堆栈JavaScript Web应用程序。

从开发的角度来看,这种默认行为无疑是最简单的解决方案,但是就用户体验而言,它仍有一些不足之处。 您的用户组成员已经在Meetup.com上拥有一个帐户,用于RSVP即将举行的会议。 要求他们创建和维护一组重复的凭据不仅烦人,而且公然违反了“不要自己重复”(DRY)原则。

认证与授权

每当您登录网站时,都会发生两处完全不同但相关的事情:

  • 通过提供用户名和密码(或指纹,或视网膜扫描,或...),您就可以对自己进行身份验证 -通过共享秘密密码短语或唯一标识符来证明您的身份。 未经身份验证,任何人都可以登录到您的银行网站并取款或登录到您的社交媒体帐户,然后开始以您的名义发表煽动性声明。
  • 通过身份验证后,系统会找出您有权执行的操作。 您的银行网站授权您从帐户中进行存款和取款,但很可能不允许您调整利率或冲销银行费用。 要执行这些操作,您必须联系客户服务代表,该客户的帐户有权执行管理任务。

幸运的是,通过使用的MEAN堆栈,您可以设置使用OAuth和Passport的分布式身份验证和授权解决方案。 简单来说,您的用户可以使用用于登录Meetup.com的相同凭据登录(认证)到UGLI应用程序。 但是,如果没有用户的许可,这是无法完成的。 他们必须允许(授权)UGLI应用使用其Meetup.com凭据。

即使通过OAuth对UGLI应用进行了授权,用户凭据也不会与授权的应用共享。 您不会在UGLI中本地存储一组重复的用户名和密码。 希望获得授权的应用程序(UGLI)将用户重定向到OAuth提供程序(Meetup.com),他们在其中提供其凭据(用户名和密码)。 用户成功认证后,访问令牌将返回给授权的应用程序。

这种方案减少了应用程序中的大量代码和逻辑。 您不再需要担心在服务器上存储加密密码-这是OAuth提供程序要解决的问题。 同样,您不再必须编写算法来强制使用强密码或处理忘记的密码或强制用户定期更改其密码。

因此,OAuth为您的用户提供了更少的密码来记忆,并且为您提供了更少的编写代码。 如果这不是双赢方案的教科书定义,我不知道这是什么。

介绍OAuth和Passport

“您不再需要编写算法来强制使用强密码,也不必处理忘记的密码,也不必强制用户定期更改密码。 ”

OAuth是用于分布式身份验证和授权的开放标准。 它由Twitter和业务合作伙伴Ma.gnolia于2006年开发,旨在促进创建桌面小部件,这些小部件显示来自已验证服务的信息。 从那时起,OAuth被数百个主要网站采用,从Google到Facebook再到Twitter,GitHub,LinkedIn等。 (请参阅著名的OAuth服务提供商列表 。)

Passport是为Node.js编写的OAuth库。 具体来说,它是旨在与Express应用程序无缝集成的中间件。 有140多个Passport插件(称为策略 )可供使用,这些插件是为每个OAuth提供程序量身定制的。

如果您在文本编辑器中打开UGLI应用程序的package.json文件(如清单1所示),则可以看到针对四种主要服务(Facebook,Twitter,LinkedIn和Google)的Passport策略,以及用于直接存储凭据的本地策略。在MongoDB中。

清单1. package.json中的护照策略
"dependencies": {"passport": "~0.2.0","passport-local": "~1.0.0","passport-facebook": "~1.0.2","passport-twitter": "~1.0.2","passport-linkedin": "~0.1.3","passport-google-oauth": "~0.1.5"
}

使用现有示例作为指南,您将添加第六种策略,以将Meetup.com纳入UGLI的OAuth提供程序。

安装Meetup.com Passport策略

学到更多。 开发更多。 连接更多。

新的developerWorks Premium会员计划通过Safari图书在线提供对强大的开发工具和资源的无障碍访问,其中包括500个顶级技术标题(数十个专门用于Web开发人员),主要开发人员活动的超低折扣,最近O'Reilly的视频重播会议等。 立即注册 。

如果您访问使用Meetup API进行身份验证 ,您将看到Meetup.com提供OAuth服务。 在网上快速搜索meetup.com passport.js strategy生成指向您正在寻找的库的链接: passport-meetup

输入npm install passport-meetup --save meetup npm install passport-meetup --save将库下载到node_modules并更新package.json中的依赖项块。

那是容易的部分。 制定好策略后,下一步就是将其合并到注册和登录页面中。

将Meetup.com链接添加到注册和登录页面

在文本编辑器中打开public / modules / users / views / signup.client.view.html。 在文件的顶部,您可以看到指向各种OAuth提供程序的链接(如清单2所示)。

清单2. public / modules / users / views / signup.client.view.html
<h3 class="col-md-12 text-center">Sign up using your social accounts</h3>
<div class="col-md-12 text-center"><a href="/auth/facebook" class="undecorated-link"><img src="/modules/users/img/buttons/facebook.png"></a><a href="/auth/twitter" class="undecorated-link"><img src="/modules/users/img/buttons/twitter.png"></a><a href="/auth/google" class="undecorated-link"><img src="/modules/users/img/buttons/google.png"></a><a href="/auth/linkedin" class="undecorated-link"><img src="/modules/users/img/buttons/linkedin.png"></a>
</div>

将现有链接替换为指向您尚未创建的/ auth / meetup路由的链接,并显示尚未下载的Meetup.com图标(如清单3所示)。

清单3.链接到auth / meetup
<h3 class="col-md-12 text-center">Sign up using your Meetup.com account</h3>
<div class="col-md-12 text-center"><a href="/auth/meetup" class="undecorated-link"><img src="/modules/users/img/buttons/meetup.png"></a>
</div>

访问“ Meetup图标”页面,并将128x128像素的图像保存到存储其他社交媒体图标的public / modules / users / img / buttons /。

现在已经禁用了注册页面,请在文本编辑器中打开public / modules / users / views / signin.client.view.html,并以与注册页面相同的方式对其进行调整(如清单1所示)。 4)。

清单4. public / modules / users / views / signin.client.view.html
<h3 class="col-md-12 text-center">Sign in using your Meetup.com account</h3>
<div class="col-md-12 text-center"><a href="/auth/meetup" class="undecorated-link"><img src="/modules/users/img/buttons/meetup.png"></a></div>

如果一切都按计划进行,那么新的注册页面将如图2所示。当然,如果没有适当的路线,则单击链接将出现404页面未找到错误。 接下来,您将解决此问题。

图2.新的UGLI注册页面

设置服务器端的身份验证/会议路由

下一步是创建服务器端的auth / meetup路由。 回想一下,所有服务器端逻辑都存储在app目录中; 客户端逻辑存储在公用文件夹中。

在文本编辑器中打开app / routes / users.server.routes.js。 找到Facebook的代码块并复制/粘贴,然后用meetup替换facebook (如清单5所示)。

清单5. app / routes / users.server.routes.js
// Setting the facebook oauth routes
app.route('/auth/facebook').get(passport.authenticate('facebook', {scope: ['email']
}));
app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));// Setting the meetup oauth routes
app.route('/auth/meetup').get(passport.authenticate('meetup', {scope: ['email']
}));
app.route('/auth/meetup/callback').get(users.oauthCallback('meetup'));

还记得上一节中在注册和登录页面上创建的指向auth / meupup的超链接吗? 当用户通过单击链接向服务器发送HTTP GET请求时,将触发第一条路由(auth / meetup)。 Passport将尝试通过使用passport-meetup策略来验证用户身份。 登录尝试的结果(成功或否则)将异步发送到第二个auth / meetup / callback路由。

如果现在单击注册页面上的“会议集会”链接,则会收到500服务器错误而不是404错误。这不完全是一种改进,但至少是一种进步。 下一步:配置聚会策略。

配置聚会策略

您可以在同名的config / strategies目录中找到所有Passport策略。 将facebook.js复制到meetup.js,然后在文本编辑器中打开meetup.js。

与上一节中的操作一样,您将遍历此文件并将所有facebook实例替换为meetup 。 但这不仅仅是简单的查找/替换操作。 您还需要进行一些小的配置更改。

首先,将文件顶部所需的库从Facebook策略更改为Meetup策略(如清单6所示)。

清单6. config / strategies / meetup.js
/*** Module dependencies.*/
var passport = require('passport'),url = require('url'),MeetupStrategy = require('passport-meetup').Strategy,config = require('../config'),users = require('../../app/controllers/users');

接下来,您需要自定义传递给新策略的选项块。 这些价值因策略而异。 清单7显示了Facebook策略选项,这些选项不适用于Meetup。

清单7.不适用于Meetup的Facebook选项
module.exports = function() {// Use facebook strategypassport.use(new FacebookStrategy({clientID: config.facebook.clientID,clientSecret: config.facebook.clientSecret,callbackURL: config.facebook.callbackURL,passReqToCallback: true},

值得庆幸的是,您npm install passport-meetup模块附带了示例代码。 在文本编辑器中打开node_modules / passport-meetup / examples / login / app.js。 查找passport.use function call (如清单8所示)。

清单8. node_modules / passport-meetup / examples / login / app.js
passport.use(new MeetupStrategy({consumerKey: MEETUP_KEY,consumerSecret: MEETUP_SECRET,callbackURL: "http://127.0.0.1:3000/auth/meetup/callback"},

将此代码段复制到metup.js,覆盖Facebook代码。 接下来,将冒号右侧的值更改为清单9中所示的值。

清单9.会用的Meetup选项
passport.use(new MeetupStrategy({consumerKey: config.meetup.consumerKey,consumerSecret: config.meetup.consumerSecret,callbackURL: config.meetup.callbackURL,},
OAuth访问令牌和刷新令牌

访问令牌就像电影票:您从票房获得的一纸小纸钞,证明您是为入场费付费的人。 当您进入剧院时,您将闲聊交给一位员工,该员工告诉您您有权观看哪部电影,并且该员工将闲聊撕成两半以确保仅使用一次。

同样,OAuth访问令牌是具有短生存时间(TTL)的一次性令牌。 下次用户通过Meetup链接登录UGLI时,他或她不需要提供用户名和密码。 而是,Passport将刷新令牌发送到Meetup。 除非用户从Meetup撤消了对UGLI的访问权限,否则Meetup会以短TTL的新访问令牌响应。

刷新令牌应该在您的系统中保留很长时间-本质上,只要存在用户帐户,就可以保留。 相比之下,访问令牌是短暂的瞬态对象,每次用户向远程OAuth提供程序进行身份验证时都会重新生成。

在下一部分中,您将从Meetup.com获取consumerKeyconsumerSecret ,并将它们保存在config.js文件中。 但是,您需要先对当前文件进行一些其他更改,然后再进行操作。

紧随new MeetupStrategy构造函数之后的函数是事件处理程序,该事件处理程序从Meetup.com接收响应。 您对响应的三个关键部分感兴趣:访问令牌,刷新令牌和用户配置文件。 (有关令牌的详细信息,请参见OAuth访问令牌和刷新令牌侧栏。)

访问令牌和刷新令牌是您不变地传递给Passport的字符串。 尽管它们对于OAuth操作的成功至关重要,但他们很无聊。 (清单10包含了两者的示例。)

用户个人资料更有趣。 这是OAuth提供程序返回的JSON对象,其中包含有关成功通过身份验证的用户的信息。 具体细节因OAuth提供者而异。 清单10显示了Meetup返回的用户个人资料的示例。

清单10.从Meetup.com OAuth提供者返回的用户个人资料
{ provider: 'meetup',id: 13848777,displayName: 'Scott Davis',_raw: '{ "results": [{"status": "active","link": "http:\\\/\\\/www.meetup.com\\\/members\\\/13848777","photo": {"photo_link": "http:\\\/\\\/photos1.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/member_11849906.jpeg","thumb_link": "http:\\\/\\\/photos3.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/thumb_11849906.jpeg","photo_id": 11849906},"country": "us","state": "CO",        "city": "Denver","id": 13848777,"joined": 1295844957000,"bio": "Scott Davis is the founder of ThirstyHead.com, a training and
consulting company that specializes in leading-edge technology solutions like
HTML 5, NoSQL, Groovy, and Grails.","name": "Scott Davis","other_services": {"twitter": {"identifier": "@scottdavis99"}}}]  }',_json: { results: [ [Object] ],meta: { link: 'https://api.meetup.com/2/members',total_count: 1,url: 'https://api.meetup.com/2/members?order=name&member_id=13848777&offset=0
&format=json&page=800',title: 'Meetup Members v2',updated: 1392763702000,description: 'API method for accessing members of Meetup Groups',method: 'Members',},accessToken: 'c7b5577bb80aab55439785cd86abcdef',refreshToken: '2af98db68950235a1e2519a734abcdef' }
}

如您所见,Meetup返回用户详细信息,例如姓名,居住地,加入日期,个人资料照片,链接的社交媒体帐户等。

完成Meetup策略的自定义操作的最后一件事是将Meetup配置文件字段映射回app / models / user.server.model.js中定义的User Mongoose对象。 在config / strategies / meetup.js中编辑其余的Facebook块,如清单11所示。

清单11.将OAuth用户配置文件映射到User对象
// Create the user OAuth profile
var providerUserProfile = {firstName: '',lastName: '',displayName: profile.displayName,email: '',username: profile.id,provider: profile.provider,providerIdentifierField: 'id',providerData: providerData
};

如果您在Meetup配置文件JSON中看到要添加到User对象的字段,那么这是进行更改的最佳时机。 不要忘记在public / modules / users / views中将新字段添加到HTML表单中。

完成的config / strategies / meetup.js应该类似于清单12。

清单12.完整的config / strategies / meetup.js
'use strict';/*** Module dependencies.*/
var passport = require('passport'),url = require('url'),MeetupStrategy = require('passport-meetup').Strategy,config = require('../config'),users = require('../../app/controllers/users');module.exports = function() {// Use meetup strategypassport.use(new MeetupStrategy({consumerKey: config.meetup.clientID,consumerSecret: config.meetup.clientSecret,callbackURL: config.meetup.callbackURL,},function(req, accessToken, refreshToken, profile, done) {// Set the provider data and include tokensvar providerData = profile._json;providerData.accessToken = accessToken;providerData.refreshToken = refreshToken;// Create the user OAuth profilevar providerUserProfile = {firstName: '',lastName: '',displayName: profile.displayName,email: '',username: profile.id,provider: profile.provider,providerIdentifierField: 'id',providerData: providerData};// Save the user OAuth profileusers.saveOAuthUserProfile(req, providerUserProfile, done);}));
};

在测试此代码之前,您还需要做一件事:从Meetup获取consumerKeyconsumerSecret

获取Meetup的consumerKeyconsumerSecret

到目前为止,我已经用了整整篇文章来讨论对用户进行身份验证。 但是,在用户可以使用OAuth登录UGLI之前,您(开发人员)必须提供证明您的组织符合要求的证据。 您可以通过向用户提供公钥( consumerKey )来实现。 您的应用程序还需要知道其私钥( consumerSecret )是什么。

如果您以前曾使用过公钥基础结构 (PKI),那么您将知道隐藏私钥并确保其安全至关重要。 如果其他人发现了您的私钥,他们可以伪装成您的组织。 相反,如果您不与用户共享您的公钥,那么他们将无法证明您是谁。

如果您是Meetup.com上用户组的组织者,则可以从Meetup的“ 您的OAuth消费者”页面中生成consumerKeyconsumerSecret (请参见图3)。 我将HTML5 Denver User Group用作消费者名称,将http://www.meetup.com/HTML5-Denver-Users-Group/用于应用程序网站,并将http://localhost:3000/auth/meetup/callback用于重定向URI。 将UGLI应用程序投入生产后,我将把应用程序网站更改为http://html5denver.com ,并将重定向URI更改为http://html5denver.com/auth/meetup/callback

图3.生成HTML5 Denver的consumerKeyconsumerSecret

如果您没有在Meetup.com上运行用户组,则您的帐户无权代表该组生成OAuth密钥。 但是,您仍然可以为其他社交媒体帐户之一生成令牌,并相应地调整本文中的步骤。 请参阅实施通过Twitter登录以为您的Twitter帐户生成应用程序密钥,或访问访问令牌以使用您的Facebook帐户。 在your social media website oauth keys上进行快速网络搜索应该会得到逐步说明。

将组织OAuth密钥添加到您的应用程序

获得两个密钥(公共密钥和私有密钥)之后,您将通过环境变量将它们添加到您的应用程序中-就像在“ 浏览MEAN应用程序 ”中更改PORT

回想一下,您可以设置变量,这些变量根据您所运行的模式( developmentproductiontest 。 特定于环境的值存储在config / env中。 在文本编辑器中打开config / env / development.js。 复制/粘贴Facebook块,并针对Meetup进行相应调整(如清单13所示)。 可以肯定的属性名称匹配这里您在使用的属性名称passport.use在配置/策略/ meetup.js函数调用。

清单13. config / env / development.js
'use strict';module.exports = {db: 'mongodb://localhost/test-dev',app: {title: 'HTML5 Denver'},meetup: {consumerKey: process.env.MEETUP_KEY || 'APP_ID',consumerSecret: process.env.MEETUP_SECRET || 'APP_SECRET',callbackURL: 'http://localhost:3000/auth/meetup/callback'},    facebook: {clientID: process.env.FACEBOOK_ID || 'APP_ID',clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',callbackURL: 'http://localhost:3000/auth/facebook/callback'},twitter: {clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',callbackURL: 'http://localhost:3000/auth/twitter/callback'},google: {clientID: process.env.GOOGLE_ID || 'APP_ID',clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',callbackURL: 'http://localhost:3000/auth/google/callback'},linkedin: {clientID: process.env.LINKEDIN_ID || 'APP_ID',clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',callbackURL: 'http://localhost:3000/auth/linkedin/callback'}
};

您可以使用上一部分中检索到的consumerKeyconsumerSecret硬编码值替换APP_IDAPP_SECRET 。 但是更安全的解决方案是通过环境变量将这些值提供给UGLI应用程序。 要使用组织的consumerKeyconsumerSecret启动应用程序,请输入:

MEETUP_KEY=l75fkklhurkack36eelfhhfhjc MEETUP_SECRET=abcdeg316jd3ni43f21u1abcde NODE_ENV=development grunt

上线之前,请不要忘记对config / env / production.js进行类似的调整。 而且,如果您之前创建了用户帐户,请确保将其从MongoDB的html5-denver-dev数据库中删除,以便您可以重新完成新帐户的创建过程。

结论

Perl编程语言的创建者拉里·沃尔(Larry Wall)曾有句著名的话:“简单的事情应该很容易,而困难的事情应该可以。” 我希望这一观点能很好地总结您将OAuth和Passport连接起来以使用Meetup.com满足您的分布式身份验证和授权需求的经验。

在下一期Mastering MEAN文章中 ,我将带您了解MEAN堆栈中内置的测试基础结构。 您将了解用于服务器端测试的Mocha,用于客户端测试的Jasmine以及用于在多个浏览器上运行测试的Karma。 在此之前,请尽情掌握MEAN。


翻译自: https://www.ibm.com/developerworks/opensource/library/wa-mean5/index.html

oauth身份验证方式

oauth身份验证方式_使用OAuth和Passport管理身份验证相关推荐

  1. php mysql表单验证登录_使用PHP和MySql简单身份验证 1

    内容来源于<PHP和MySql Web开发> 简单的访问控制: 首先创建一个简单的登陆界面 直接上完整代码 $name = $_POST['name']; $password = $_PO ...

  2. python身份证验证系统_利用Python制作全国身份证号验证及查询系统!就问你吊不吊!...

    大家好哇,又是一个愉快的周末,今天本鸟给大家分享1个有趣的实战项目,用python制作"全国身份证号验证及查询系统",成品界面如下图: 本系统可以实现身份证号真伪验证,年龄.性别及 ...

  3. thinkphp 微信服务器验证代码_基于ThinkPHP5微信后台管理平台

    DIY分享秀 2019-10-08 22:28:57 ThinkAdmin V5 是一个基于 ThinkPHP 5.1 开发的后台管理系统. 我们致力于二次开发底层框架,提供完整的组件及API,基于此 ...

  4. mysql事务的管理方式_浅谈MySQL事务管理(基础)

    本篇文章给大家带来的内容是浅谈MySQL事务管理(基础),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助.事务处理用来维护数据库等完整性,保证mysql操作要么成功,要么失败(myisa ...

  5. 怎样更改SQL Server 2008的身份验证方式

    大家都知道sql server 有两种登录验证方式,即sql server验证方式和windows验证方式,但是sql server默认的是windows登录验证方式,我们如何启用sql server ...

  6. 域的非验证方式还原与验证方式还原的区别

    救急:一台服务器要重装系统,怎么份域用户信息 在Windows2000中,备份与恢复Active Directory是一项非常重要的工作.在NT中,所有有关用户和企业配置方面的信息都存储在注册表中,因 ...

  7. 连接mysql服务验证失败_数据库连接失败的原因分析及解决办法

    如何来解决三个最常见的连接错误. 第一个错误"SQL Server 不存在或访问被拒绝"通常是最复杂的,错误发生的原因比较多,需要检查的方面也比较多.一般说来,有以下几种可能性: ...

  8. 【学生管理系统】用户登录三种验证方式—图片验证、短信验证、邮件验证

    目录 一.页面需求展示 二.验证方式-按钮组件 三.手机短信验证 四.邮件验证 五.图片验证邮件验证

  9. java oauth sso 源码_基于Spring Security Oauth2的SSO单点登录+JWT权限控制实践

    概 述 在前文<基于Spring Security和 JWT的权限系统设计>之中已经讨论过基于 Spring Security和 JWT的权限系统用法和实践,本文则进一步实践一下基于 Sp ...

最新文章

  1. 在windows中手动安装第三方模块
  2. C#多线程开发-任务并行库
  3. 东北大哥在线反套路hhhhhh | 今日最佳
  4. java集合根据值排序_Java 8:对集合中的值进行排序
  5. 针对新手的Java EE7和Maven项目–第6部分
  6. linux 建立交叉编译环境变量,ARM-Linux-gcc-4.4.3交叉编译环境终于搭建
  7. ASP.NET服务器控件查询
  8. MySQL(my.ini)配置文件详解
  9. IPwe区块链智能池利用AI来处理专利分析
  10. VML实例-可控箭头
  11. 【202209秋招软开银行面试C++】
  12. NSGA-II中“支配”的概念
  13. 【转】我公务员工作七年后的肺腑之言
  14. python配置(二)——机器学习环境
  15. 2019年-2020年计划
  16. oracle dialog运行,win32窗口创建 之 CreateDialog和DialogBox
  17. flutter TextField 输入框组件
  18. 黑马程序员——ios笔试题——黑马 IOS 技术博客
  19. JavaFx的ScrollPane的面板以及滚动条的css的自定义化
  20. 如何设置 angular packageManager

热门文章

  1. nodemanger
  2. 【Linux】基本的指令(一)
  3. Crowdsourcing众包
  4. 优思学院|六西格玛管理的核心理念是什么?
  5. 创建test.html页面,无法生成HTML报告 · Issue #487 · AirtestProject/AirtestIDE · GitHub
  6. 各大家装企业中台化策略与布局
  7. [博学谷学习记录]超强总结,用心分享|第16节 集合续-----笔记篇
  8. 大数据系统计算的概念全面解析
  9. 中国共享汽车行业产销规模及应用趋势研究报告2021-2027年
  10. 总结(算不上总结的总结)