博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
轻松的节点身份验证:将所有帐户链接在一起
阅读量:2510 次
发布时间:2019-05-11

本文共 31146 字,大约阅读时间需要 103 分钟。

This will be the final article in our . We will be using all of the previous articles together.

这将是我们的的最后一篇文章。 我们将一起使用所有先前的文章。

Edit 11/18/2017: Updated to reflect Facebook API changes.

编辑11/18/2017:已更新以反映Facebook API的更改。

This article is part of our Easy Node Authentication series.

本文是我们的轻松节点身份验证系列的一部分。

关联帐户 (Linking Accounts Together)

This article will combine all the different Node Passport Strategies so that a user will be able to have one account and link all their social networks together.

本文将结合所有不同的Node Passport策略,以便用户将能够拥有一个帐户并将其所有社交网络链接在一起。

There are many changes that need to take place from the previous articles to accomplish this. Here are the main cases we have to account for when moving from authenticating with only 1 account versus multiple accounts.

要实现此目的,需要对先前的文章进行许多更改。 这是从仅使用1个帐户进行验证而不是使用多个帐户进行身份验证时必须考虑的主要情况。

解决方案 (Scenarios to Account For)

  • Linking Accounts: Check if a user already exists in the database. If they do, then add a social account to their user profile

    关联帐户 :检查数据库中是否已存在用户。 如果他们这样做,则将社交帐户添加到他们的用户个人资料中
  • Account Creation: If a user does not exist in the database, then create a user profile

    创建帐户 :如果数据库中不存在用户,则创建一个用户配置文件
  • Unlinking: Unlinking social accounts

    取消关联 :取消关联社交帐户
  • Reconnecting: If a user unlinked a social account, but wants to reconnect it

    重新连结:如果使用者取消连结社交帐户,但想重新建立连结

We'll be going through each of these scenarios and updating our previous code to account for them.

我们将研究每种情况,并更新我们以前的代码以解决这些情况。

我们将要编码的 (What We'll Be Coding)

We'll be working with the Local Strategy and the Facebook Strategy to demonstrate linking accounts. The tactics used for the Facebook Strategy will carry over to Twitter and Google.

我们将与“本地策略”和“ Facebook策略”一起展示链接帐户。 Facebook策略所采用的策略将延续到Twitter和Google。

In order to add linking accounts to our application, we will need to:

为了将链接帐户添加到我们的应用程序,我们需要:

  • Update our Strategies

    更新我们的策略
  • Add new routes

    新增路线
  • Update our views for linking and unlinking

    更新我们的视图以进行链接和取消链接

用户模型说明 (User Model Explanation)

When looking at the way we set up our user model, we deliberately set up all the user accounts to be set up within their own object. This ensures that we can link and unlink different accounts as our user sees fit. Notice that the social accounts will use token and id while our local account will use email and password.

在研究建立用户模型的方式时,我们特意设置了所有要在其自己的对象内设置的用户帐户。 这样可以确保我们可以在用户认为合适的情况下链接和取消链接不同的帐户。 请注意, 社交帐户将使用tokenid而我们的本地帐户将使用emailpassword

// app/models/user.js...var userSchema = mongoose.Schema({    local            : {        email        : String,        password     : String,    },    facebook         : {        id           : String,        token        : String,        email        : String,        name         : String    },    twitter          : {        id           : String,        token        : String,        displayName  : String,        username     : String    },    google           : {        id           : String,        token        : String,        email        : String,        name         : String    }});...

We have also added in email, name, displayName, and username for some accounts just to show that we can pull that information from the respective social connection.

我们还为某些帐户添加了电子邮件,名称,displayName和用户名,只是为了表明我们可以从相应的社交关系中提取该信息。

Once a user has linked all their accounts together, they will have one user account in our database, with all of these fields full.

一旦用户将所有帐户链接在一起,他们将在我们的数据库中拥有一个用户帐户,所有这些字段均已填写。

验证与授权 (Authenticating vs Authorizing)

When we originally made these Strategies, we would use passport.authenticate. This is what we should be using upon first authentication of our user. But what do we do if they are already logged in? They will be logged in and their user stored in session when we want to link them to their current account.

最初制定这些策略时,我们将使用passport.authenticate 。 这是我们在对用户进行首次身份验证时应该使用的。 但是,如果他们已经登录怎么办? 当我们想要将它们链接到当前帐户时,它们将被登录并将其用户存储在会话中。

Luckily, Passport provides a way to "connect" a users account. They provide passport.authorize for users that are already authenticated. To read more on the usage, visit the .

幸运的是,Passport提供了一种“连接”用户帐户的方法。 他们为已认证的用户提供passport.authorize 。 要了解有关用法的更多信息,请访问 。

We will update our routes to handle the authorization first, and then we'll update our Passport Strategies to handle the authorization.

我们将先更新路线以处理授权,然后再更新Passport Strategies来处理授权。

路线 (Routes)

Let's create our routes first so that we can see how we link everything together. In the past articles, we created our routes for authentication. Let's create a second set of routes for authorization. Once we've done that, we'll change our Strategy to accommodate the new scenarios.

让我们先创建路线,以便可以看到如何将所有内容链接在一起。 在过去的文章中,我们创建了用于身份验证的路由。 让我们为授权创建第二组路由。 完成此操作后,我们将更改策略以适应新的情况。

Our old routes will be commented to make a cleaner file.

我们将对旧路线进行评论,以使文件更整洁。

// app/routes.jsmodule.exports = function(app, passport) {// normal routes ===============================================================    // show the home page (will also have our login links)    // PROFILE SECTION =========================    // LOGOUT ==============================// =============================================================================// AUTHENTICATE (FIRST LOGIN) ==================================================// =============================================================================    // locally --------------------------------        // LOGIN ===============================        // show the login form        // process the login form        // SIGNUP =================================        // show the signup form        // process the signup form    // facebook -------------------------------        // send to facebook to do the authentication        app.get('/auth/facebook', passport.authenticate('facebook', { scope : 'email' }));        // handle the callback after facebook has authenticated the user        app.get('/auth/facebook/callback',            passport.authenticate('facebook', {                successRedirect : '/profile',                failureRedirect : '/'            }));    // twitter --------------------------------        // send to twitter to do the authentication        // handle the callback after twitter has authenticated the user    // google ---------------------------------        // send to google to do the authentication        // the callback after google has authenticated the user// =============================================================================// AUTHORIZE (ALREADY LOGGED IN / CONNECTING OTHER SOCIAL ACCOUNT) =============// =============================================================================    // locally --------------------------------        app.get('/connect/local', function(req, res) {            res.render('connect-local.ejs', { message: req.flash('loginMessage') });        });        app.post('/connect/local', passport.authenticate('local-signup', {            successRedirect : '/profile', // redirect to the secure profile section            failureRedirect : '/connect/local', // redirect back to the signup page if there is an error            failureFlash : true // allow flash messages        }));    // facebook -------------------------------        // send to facebook to do the authentication        app.get('/connect/facebook', passport.authorize('facebook', {           scope : ['public_profile', 'email']         }));        // handle the callback after facebook has authorized the user        app.get('/connect/facebook/callback',            passport.authorize('facebook', {                successRedirect : '/profile',                failureRedirect : '/'            }));    // twitter --------------------------------        // send to twitter to do the authentication        app.get('/connect/twitter', passport.authorize('twitter', { scope : 'email' }));        // handle the callback after twitter has authorized the user        app.get('/connect/twitter/callback',            passport.authorize('twitter', {                successRedirect : '/profile',                failureRedirect : '/'            }));    // google ---------------------------------        // send to google to do the authentication        app.get('/connect/google', passport.authorize('google', { scope : ['profile', 'email'] }));        // the callback after google has authorized the user        app.get('/connect/google/callback',            passport.authorize('google', {                successRedirect : '/profile',                failureRedirect : '/'            }));};// route middleware to ensure user is logged infunction isLoggedIn(req, res, next) {    if (req.isAuthenticated())        return next();    res.redirect('/');}

As you can see, we have all the authentication routes and the routes to show our index and profile pages. Now we have added authorization routes which will look incredibly similar to our authentication routes.

如您所见,我们拥有所有身份验证路由以及显示索引和配置文件页面的路由。 现在,我们添加了授权路由 ,它们看起来与我们的身份验证路由非常相似。

With our newly created routes, let's update the Strategy so that our authorization routes are utilized.

使用我们新创建的路由,让我们更新策略,以便利用我们的授权路由。

更新策略 (Updating Strategies)

We will just update the Facebook and Local Strategies to get a feel for how we can accommodate all our different scenarios.

我们将仅更新Facebook和本地策略,以了解如何适应所有不同情况。

When using the passport.authorize route, our user that is stored in session (since they are already logged in) will be passed to the Strategy. We will make sure we change our code to account for that.

在使用passport.authorize路线时,存储在会话中的用户(因为他们已经登录)将被传递到该策略。 我们将确保更改代码以解决此问题。

We're going to show the old Strategy and then the new Strategy. Read the comments to get a full understanding of the changes.

我们将展示旧策略,然后展示新策略。 阅读评论以全面了解更改。

旧Facebook策略 (Old Facebook Strategy)

// config/passport.js...    // =========================================================================    // FACEBOOK ================================================================    // =========================================================================    passport.use(new FacebookStrategy({        // pull in our app id and secret from our auth.js file        clientID        : configAuth.facebookAuth.clientID,        clientSecret    : configAuth.facebookAuth.clientSecret,        callbackURL     : configAuth.facebookAuth.callbackURL    },    // facebook will send back the token and profile    function(token, refreshToken, profile, done) {        // asynchronous        process.nextTick(function() {            // find the user in the database based on their facebook id            User.findOne({ 'facebook.id' : profile.id }, function(err, user) {                // if there is an error, stop everything and return that                // ie an error connecting to the database                if (err)                    return done(err);                // if the user is found, then log them in                if (user) {                    return done(null, user); // user found, return that user                } else {                    // if there is no user found with that facebook id, create them                    var newUser            = new User();                    // set all of the facebook information in our user model                    newUser.facebook.id    = profile.id; // set the users facebook id                                       newUser.facebook.token = token; // we will save the token that facebook provides to the user                                        newUser.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName; // look at the passport user profile to see how names are returned                    newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first                    // save our user to the database                    newUser.save(function(err) {                        if (err)                            throw err;                        // if successful, return the new user                        return done(null, newUser);                    });                }            });        });    }));...

Now we want the ability to authorize a user.

现在,我们希望能够授权用户。

新的Facebook策略 (New Facebook Strategy)

...    // =========================================================================    // FACEBOOK ================================================================    // =========================================================================    passport.use(new FacebookStrategy({        // pull in our app id and secret from our auth.js file        clientID        : configAuth.facebookAuth.clientID,        clientSecret    : configAuth.facebookAuth.clientSecret,        callbackURL     : configAuth.facebookAuth.callbackURL,        passReqToCallback : true // allows us to pass in the req from our route (lets us check if a user is logged in or not)    },    // facebook will send back the token and profile    function(req, token, refreshToken, profile, done) {        // asynchronous        process.nextTick(function() {            // check if the user is already logged in            if (!req.user) {                // find the user in the database based on their facebook id                User.findOne({ 'facebook.id' : profile.id }, function(err, user) {                    // if there is an error, stop everything and return that                    // ie an error connecting to the database                    if (err)                        return done(err);                    // if the user is found, then log them in                    if (user) {                        return done(null, user); // user found, return that user                    } else {                        // if there is no user found with that facebook id, create them                        var newUser            = new User();                        // set all of the facebook information in our user model                        newUser.facebook.id    = profile.id; // set the users facebook id                                           newUser.facebook.token = token; // we will save the token that facebook provides to the user                                            newUser.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName; // look at the passport user profile to see how names are returned                        newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first                        // save our user to the database                        newUser.save(function(err) {                            if (err)                                throw err;                            // if successful, return the new user                            return done(null, newUser);                        });                    }                });            } else {                // user already exists and is logged in, we have to link accounts                var user            = req.user; // pull the user out of the session                // update the current users facebook credentials                user.facebook.id    = profile.id;                user.facebook.token = token;                user.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName;                user.facebook.email = profile.emails[0].value;                // save the user                user.save(function(err) {                    if (err)                        throw err;                    return done(null, user);                });            }        });    }));...

Now we have accounted for linking an account if a user is already logged in. We still have the same functionality from before, now we just check if the user is logged in before we take action.

现在, 如果用户已经登录,我们已经考虑了链接帐户 。 我们仍然具有以前的功能,现在我们只是在执行操作之前检查用户是否已登录。

关联账户 (Linking Accounts)

Using this new code in our Strategy, we will create a new user if they are not already logged in, or we will add our Facebook credentials to our user if they are currently logged in and stored in session.

使用我们策略中的新代码,如果他们尚未登录,我们将创建一个新用户,或者如果他们当前已登录并存储在会话中,我们将向我们的用户添加Facebook凭据。

Other Strategies: The code for the Facebook Strategy will be the same for Twitter and Google. Just apply that code to both of those to get this working. We will also provide the full code so you can look at and reference.

其他策略 :Twitter和Google的Facebook策略代码相同。 只需将代码同时应用于这两个代码即可。 我们还将提供完整的代码,以便您查看和参考。

Now that we have the routes that will pass our user to our new Facebook Strategy, let's make sure our UI lets our user use the newly created routes.

现在我们有了将用户引导到新的Facebook策略路由 ,让我们确保我们的UI允许用户使用新创建的路由。

We will update our index.ejs and our profile.ejs to show all the login buttons on the home page, and all the accounts and link buttons on the profile page. Here is the full code for both with the important parts highlighted.

我们将更新index.ejsprofile.ejs以在主页上显示所有登录按钮,并在配置文件页面上显示所有帐户和链接按钮。 这是这两个代码的完整代码,并突出显示了重要部分。

主页index.ejs (Home Page index.ejs)

Node Authentication

Node Authentication

Login or Register with:

Local Login
Local Signup
Facebook
Twitter
Google+

资料页面profile.ejs (Profile Page profile.ejs)

Node Authentication

Local

<% if (user.local.email) { %>

id: <%= user._id %>

email: <%= user.local.email %>
password: <%= user.local.password %>

Unlink <% } else { %>
Connect Local <% } %>

Facebook

<% if (user.facebook.token) { %>

id: <%= user.facebook.id %>

token: <%= user.facebook.token %>
email: <%= user.facebook.email %>
name: <%= user.facebook.name %>

Unlink <% } else { %>
Connect Facebook <% } %>

Twitter

<% if (user.twitter.token) { %>

id: <%= user.twitter.id %>

token: <%= user.twitter.token %>
display name: <%= user.twitter.displayName %>
username: <%= user.twitter.username %>

Unlink <% } else { %>
Connect Twitter <% } %>

Google+

<% if (user.google.token) { %>

id: <%= user.google.id %>

token: <%= user.google.token %>
email: <%= user.google.email %>
name: <%= user.google.name %>

Unlink <% } else { %>
Connect Google <% } %>

Now we will have the links to each of our login methods. Then after they have logged in with one, the profile will check which accounts are already linked and which are not.

现在,我们将具有指向每个登录方法的链接。 然后,他们用一个帐户登录后,配置文件将检查哪些帐户已链接,哪些未链接。

If an account is not yet linked, it will show the Connect Button. If an account is already linked, then it our view will show the account information and the unlink button.

如果尚未链接帐户,则会显示“ 连接按钮” 。 如果一个帐户已经被链接,那么我们的视图将显示该帐户信息和“取消链接”按钮。

Remember that our user is passed to our profile view from the routes.js file.

请记住,我们的用户已从routes.js文件传递到我们的个人资料视图。

连接本地 (Connecting Local)

Our social accounts can easily be configured this way. The only problem currently is if a user wanted to connect a local account. The problem comes in because they will need to see a signup page to add their email and password.

我们的社交帐户可以通过这种方式轻松配置。 当前唯一的问题是用户是否想连接本地帐户。 之所以出现问题,是因为他们需要查看注册页面来添加其电子邮件密码

We have already created a route to handle showing our new connection form (in our routes.js file: (app.get('connect/local'))). All we need to do is create the view that the route brings up.

我们已经创建了一个路由来处理以显示新的连接形式(在routes.js文件中: (app.get('connect/local')) )。 我们要做的就是创建路由显示的视图。

Create a file in your views folder: views/connect-local.ejs.

在您的views文件夹中创建一个文件: views/connect-local.ejs

Node Authentication

Add Local Account

<% if (message.length > 0) { %>
<%= message %>
<% } %>

Go back to profile

This will be look incredibly similar to our signup.ejs form. That's because it really is. We pretty much just changed out the verbiage and the action URL for the form.

这看起来非常类似于我们的signup.ejs表单。 那是因为确实如此。 我们几乎只是更改了表格的字词和action网址。

Now when someone tries to connect a local account, they will be directed to this form, and then when submitted, they will be directed to our Local Strategy. That links the accounts!

现在,当有人尝试连接本地帐户时,他们将被定向到此表单,然后在提交后将被定向到我们的本地策略。 链接帐户!

关联帐户有效! (Linking Accounts Works!)

With just those routes and the update to our Passport Strategies, our application can now link accounts together! Take a look at a user in our database that has all their accounts linked using :

仅需这些路线和对Passport Strategies的更新,我们的应用程序现在就可以将帐户链接在一起! 看一下我们数据库中某个用户的所有帐户都使用 链接的

取消帐户关联 (Unlinking Accounts)

Linking accounts was easy. What about unlinking? Let's say a user no longer wants their Facebook account linked.

关联帐户很容易。 取消链接呢? 假设用户不再希望链接其Facebook帐户。

For our purposes, when a user wants to unlink an account, we will remove their token only. We will keep their id in the database just in case they realize their mistake of leaving and want to come back to join our application.

就我们的目的而言,当用户想要取消帐户关联时,我们将仅删除其token 。 我们将把他们的id保存在数据库中,以防万一他们意识到自己离开的错误并想回来加入我们的应用程序。

We can do this all in our routes file. You are welcome to create a controller and do all this logic there. Then you would just call the controller from the routes. For simplicity's sake, we'll throw that code directly into our routes.

我们可以在我们的路线文件中完成所有操作。 欢迎您创建一个控制器并在那里执行所有这些逻辑。 然后,您只需从路由中调用控制器即可。 为简单起见,我们将代码直接放入我们的路由中。

Let's add our unlinking routes after our newly created authorization routes.

让我们在新创建的授权路由之后添加取消链接 路由

// app/routes.js...// normal routes// authentication routes// authorization routes// =============================================================================// UNLINK ACCOUNTS =============================================================// =============================================================================// used to unlink accounts. for social accounts, just remove the token// for local account, remove email and password// user account will stay active in case they want to reconnect in the future    // local -----------------------------------    app.get('/unlink/local', function(req, res) {        var user            = req.user;        user.local.email    = undefined;        user.local.password = undefined;        user.save(function(err) {            res.redirect('/profile');        });    });    // facebook -------------------------------    app.get('/unlink/facebook', function(req, res) {        var user            = req.user;        user.facebook.token = undefined;        user.save(function(err) {            res.redirect('/profile');        });    });    // twitter --------------------------------    app.get('/unlink/twitter', function(req, res) {        var user           = req.user;        user.twitter.token = undefined;        user.save(function(err) {           res.redirect('/profile');        });    });    // google ---------------------------------    app.get('/unlink/google', function(req, res) {        var user          = req.user;        user.google.token = undefined;        user.save(function(err) {           res.redirect('/profile');        });    });...

In these routes, we just pull a user's information out of the request (session) and then remove the correct information. Since we already had created our links to these routes in profile.ejs, they will now work since we have created the routes finally.

在这些路由中,我们只是将用户信息从请求(会话)中拉出,然后删除正确的信息。 由于我们已经在profile.ejs创建了到这些路由的链接,因此自从我们最终创建了路由以来,它们现在将起作用。

Now you can link an account and unlink an account.

现在,您可以链接帐户和取消链接帐户。

When trying to unlink, we will have to do a little more configuration for that to work. Since the
id is already stored in the database, we will have to plan for that scenario when a user links account that was already previously linked.

尝试取消链接时,我们将需要做一些更多的配置才能工作。 由于id已存储在数据库中,因此当用户链接先前已链接的帐户时,我们将必须针对这种情况进行计划。

取消链接后重新链接帐户 (Relinking Account After Unlinked)

After a user is unlinked, their id still lives in the database. Therefore, when a user logs in or relinks an account, we have to check if their id exists in the database.

取消链接用户后,其id仍保留在数据库中。 因此,当用户登录或重新链接帐户时,我们必须检查其id存在于数据库中。

We will handle this in our Strategy. Let's add to our Facebook Strategy.

我们将在我们的战略中进行处理。 让我们添加到我们的Facebook策略

// config/passport.js...    // =========================================================================    // FACEBOOK ================================================================    // =========================================================================    passport.use(new FacebookStrategy({        // pull in our app id and secret from our auth.js file        clientID        : configAuth.facebookAuth.clientID,        clientSecret    : configAuth.facebookAuth.clientSecret,        callbackURL     : configAuth.facebookAuth.callbackURL,        passReqToCallback : true // allows us to pass in the req from our route (lets us check if a user is logged in or not)    },    // facebook will send back the token and profile    function(req, token, refreshToken, profile, done) {        // asynchronous        process.nextTick(function() {            // check if the user is already logged in            if (!req.user) {                // find the user in the database based on their facebook id                User.findOne({ 'facebook.id' : profile.id }, function(err, user) {                    // if there is an error, stop everything and return that                    // ie an error connecting to the database                    if (err)                        return done(err);                    // if the user is found, then log them in                    if (user) {                        // if there is a user id already but no token (user was linked at one point and then removed)                        // just add our token and profile information                        if (!user.facebook.token) {                            user.facebook.token = token;                            user.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName;                            user.facebook.email = profile.emails[0].value;                            user.save(function(err) {                                if (err)                                    throw err;                                return done(null, user);                            });                        }                        return done(null, user); // user found, return that user                    } else {                        // if there is no user found with that facebook id, create them                        var newUser            = new User();                        // set all of the facebook information in our user model                        newUser.facebook.id    = profile.id; // set the users facebook id                                           newUser.facebook.token = token; // we will save the token that facebook provides to the user                                            newUser.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName; // look at the passport user profile to see how names are returned                        newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first                        // save our user to the database                        newUser.save(function(err) {                            if (err)                                throw err;                            // if successful, return the new user                            return done(null, newUser);                        });                    }                });            } else {                // user already exists and is logged in, we have to link accounts                var user            = req.user; // pull the user out of the session                // update the current users facebook credentials                user.facebook.id    = profile.id;                user.facebook.token = token;                user.facebook.name  = profile.name.givenName + ' ' + profile.name.familyName;                user.facebook.email = profile.emails[0].value;                // save the user                user.save(function(err) {                    if (err)                        throw err;                    return done(null, user);                });            }        });    }));...

Now just add that same code across the board to all of our Strategies and we have a an application that can register a user, link accounts, unlink accounts, and relink accounts!

现在,只需将相同的代码全面添加到我们的所有策略中,我们就有一个应用程序可以注册用户链接帐户取消链接帐户重新链接帐户

完整代码 (Full Code)

For those interested in seeing the entire code all together, make sure you check out the . Also, here are direct links to the two most important files:

对于那些有兴趣一起查看完整代码的人,请确保您签出了 。 另外,这是两个最重要文件的直接链接:

结论 (Conclusion)

Hopefully we covered most of the cases that you'll run into when authenticating and authorizing users. Make sure to take a look at the full code and the demo to make sure that everything is working properly. If you see anything that raises questions, just let me know and be sure to go look at the for clarification!

希望我们涵盖了您在验证和授权用户时遇到的大多数情况。 确保查看完整的代码和演示,以确保一切正常。 如果您发现任何引发问题的信息,请告诉我,并确保仔细阅读以进行澄清!

Thanks for sticking with us throughout this entire series. We hope you enjoyed it. We'll be expanding on authentication further in the future by doing a Node and Angular authentication tutorial. Until then, happy authenticating!

感谢您在整个系列中都坚持与我们合作。 希望您喜欢。 将来,我们将通过编写Node and Angular认证教程来进一步扩展认证。 在此之前,请进行身份验证!

This article is part of our Easy Node Authentication series.

本文是我们的轻松节点身份验证系列的一部分。

翻译自:

转载地址:http://qjywd.baihongyu.com/

你可能感兴趣的文章
B站 React教程笔记day2(3)React-Redux
查看>>
找了一个api管理工具
查看>>
Part 2 - Fundamentals(4-10)
查看>>
使用Postmark测试后端存储性能
查看>>
NSTextView 文字链接的定制化
查看>>
第五天站立会议内容
查看>>
(转))iOS App上架AppStore 会遇到的坑
查看>>
做好产品
查看>>
项目管理经验
查看>>
JMeter响应数据出现乱码的处理-三种解决方式
查看>>
No qualifying bean of type available问题修复
查看>>
spfile
查看>>
Team Foundation Service更新:改善了导航和项目状态速查功能
查看>>
Cookie/Session机制具体解释
查看>>
ATMEGA16 IOport相关汇总
查看>>
JAVA基础-多线程
查看>>
面试题5:字符串替换空格
查看>>
[Codevs] 线段树练习5
查看>>
Amazon
查看>>
component-based scene model
查看>>