{"action":"created","issue":{"url":"https://api.github.com/repos/leancloud/leanengine-node-sdk/issues/38","labels_url":"https://api.github.com/repos/leancloud/leanengine-node-sdk/issues/38/labels{/name}","comments_url":"https://api.github.com/repos/leancloud/leanengine-node-sdk/issues/38/comments","events_url":"https://api.github.com/repos/leancloud/leanengine-node-sdk/issues/38/events","html_url":"https://github.com/leancloud/leanengine-node-sdk/issues/38","id":120117988,"number":38,"title":"有关实现不依赖 domain 的用户状态管理的几种方案","user":{"login":"jysperm","id":1191561,"avatar_url":"https://avatars.githubusercontent.com/u/1191561?v=3","gravatar_id":"","url":"https://api.github.com/users/jysperm","html_url":"https://github.com/jysperm","followers_url":"https://api.github.com/users/jysperm/followers","following_url":"https://api.github.com/users/jysperm/following{/other_user}","gists_url":"https://api.github.com/users/jysperm/gists{/gist_id}","starred_url":"https://api.github.com/users/jysperm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jysperm/subscriptions","organizations_url":"https://api.github.com/users/jysperm/orgs","repos_url":"https://api.github.com/users/jysperm/repos","events_url":"https://api.github.com/users/jysperm/events{/privacy}","received_events_url":"https://api.github.com/users/jysperm/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":3,"created_at":"2015-12-03T08:34:40Z","updated_at":"2015-12-25T07:29:17Z","closed_at":null,"body":"## 每次操作都需要传 sessionToken 参数\r\n\r\n    var Topic = AV.Object.extend('Topic');\r\n\r\n    AV.Cloud.define('func', function(request, response) {\r\n      var query = new AV.Query(Topic);\r\n      query.find({\r\n        sessionToken: request.user._sessionToken,\r\n        success: function(results) {\r\n          response.success(results);\r\n        }\r\n      })\r\n    });\r\n\r\n实现简单、容易理解；但用户需要修改现有代码，很容易遗漏某些地方，需要在函数间传递 user.\r\n\r\n## 为每个请求提供一个 AV 实例\r\n\r\n    AV.Cloud.define('func', function(request, response) {\r\n      var Topic = request.AV.Object.extend('Topic');\r\n      var query = new AV.Query(Topic);\r\n      query.find({\r\n        success: function(results) {\r\n          response.success(results);\r\n        }\r\n      })\r\n    });\r\n\r\n可以通过改写 AV._request 来实现，用户需要修改一部分代码，也需要在函数间传递 AV."},"comment":{"url":"https://api.github.com/repos/leancloud/leanengine-node-sdk/issues/comments/167204370","html_url":"https://github.com/leancloud/leanengine-node-sdk/issues/38#issuecomment-167204370","issue_url":"https://api.github.com/repos/leancloud/leanengine-node-sdk/issues/38","id":167204370,"user":{"login":"jysperm","id":1191561,"avatar_url":"https://avatars.githubusercontent.com/u/1191561?v=3","gravatar_id":"","url":"https://api.github.com/users/jysperm","html_url":"https://github.com/jysperm","followers_url":"https://api.github.com/users/jysperm/followers","following_url":"https://api.github.com/users/jysperm/following{/other_user}","gists_url":"https://api.github.com/users/jysperm/gists{/gist_id}","starred_url":"https://api.github.com/users/jysperm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jysperm/subscriptions","organizations_url":"https://api.github.com/users/jysperm/orgs","repos_url":"https://api.github.com/users/jysperm/repos","events_url":"https://api.github.com/users/jysperm/events{/privacy}","received_events_url":"https://api.github.com/users/jysperm/received_events","type":"User","site_admin":false},"created_at":"2015-12-25T07:29:17Z","updated_at":"2015-12-25T07:29:17Z","body":"* 提供禁用用户会话的选项（例如 AVUser.disableCurrentUser(), Parse 是叫 disableUnsafeCurrentUser()），启用后 AVUser.current() 将不会返回数据，AVUser.logIn 等方法也不会向 `AVUser._currentUser` 写入数据。Node SDK 方面也不会在调用这些方法时向 domain 写入数据（可以考虑也不为请求创建 domain），亦不会在请求结束时从 domain 向客户端写回 Cookie. \r\n* 在 JavaScript SDK 所有会产生网络请求的方法的 options 参数里添加一个 sessionToken 参数，以替代来自 `AVUser._currentUser` 中的 sessionToken; 为了方便从 User 对象上获取 sessionToken, 还要加一个 `user.sessionToken()` 的函数（或者叫 `getSessionToken()`）返回 `user._sessionToken`.\r\n* 在 Node SDK 的 request 和 response 对象上提供读取和设置用户信息的方法：request.currentUser(), request.sessionToken(), response.saveCurrentUser().\r\n\r\n我来解决一下JavaScript SDK currentUser 在 Node 环境的问题，初步打算实现这么几个修改。\r\n\r\n- - - -\r\n\r\n具体的使用场景（假设用户已完全接受了新的机制），首先为了兼容原有代码，默认保持之前的行为，需要用户在程序启动时自行禁用 currentUser:\r\n\r\n```\r\nvar AV = require('leanengine');\r\nAV.User.disableCurrentUser();\r\n```\r\n\r\n在禁用 currentUser 之后，`AV.User.current()` 总是返回空数据（可以考虑打印一条警告），用户必须在路由中从 request 对象上获取当前用户的信息：\r\n\r\n```\r\n// 云函数\r\nAV.Cloud.define('profile', function(request, response) {\r\n  var user = request.currentUser()\r\n  response.success({\r\n    name: user.get('name')\r\n  });\r\n});\r\n\r\n// 普通 Express 路由\r\napp.get('/profile', function(request, response) {\r\n  var user = request.currentUser()\r\n  response.json({\r\n    name: user.get('name')\r\n  });\r\n});\r\n```\r\n\r\n禁用 currentUser 后，`AV.User.logIn` 也不会在内存中储存数据，只是会返回登录后的用户，用户需要自行保存 User 对象，例如实现一个自定义的登录接口：\r\n\r\n```\r\nAV.Cloud.define('login', function(request, response) {\r\n  AV.User.login(request.body.name, request.body.password).then(function(user) {\r\n    response.saveCurrentUser(user);\r\n    response.success();\r\n  });\r\n});\r\n```\r\n\r\n从上面的代码可以看出如果用户需要将当前用户的登录状态（sessionToken）写回 Cookie, 需要自行调用 `response.saveCurrentUser(user)`（这个函数名还需要斟酌一下，因为 saveCurrentUser 和 disableCurrentUser 放在一起很令人困惑），如果是在路由中访问 response 对象还算方便，但如果写回操作是其他函数进行的，则需要显式传递 response 对象：\r\n\r\n```\r\nAV.Cloud.define('login', function(request, response) {\r\n  AV.User.login(request.body.name, request.body.password).then(function(user) {\r\n    loginLogic(user, response);\r\n  });\r\n});\r\n\r\nfunction loginLogic(user, res) {\r\n  // Other logics\r\n  res.saveCurrentUser(user);\r\n  res.success();\r\n}\r\n```\r\n\r\n虽然显得比较繁琐，不过确实是无奈之举，不过从「保持抽象层级的一致性」的角度，写回 Cookie 的操作确实应该在路由（或所谓 Controller）中完成，相信有过 Web 后端编程经验的人还是可以理解这一点的。\r\n\r\n如果用户需要进行数据查改，则必须在 `AVQuery.find`、`AVObject.fetch` 之类的函数的 options 参数中加入一个 sessionToken, 这个 sessionToken 可以从 request 对象或 user 对象上得到。出于「保持抽象层级的一致性」的考虑，我们建议用户在函数间传递 user 对象而不是 request 对象：\r\n\r\n```\r\napp.get('/friends', function(request, response) {\r\n  getFriends(request.currentUser()).then(function(friends) {\r\n    response.json(friends);\r\n  });\r\n});\r\n\r\nfunction getFriends(user, otherOptions) {\r\n  user.fetch({\r\n    include: 'friends'\r\n  }, {\r\n    sessionToken: user.sessionToken\r\n  }).then(function() {\r\n    // Other logics\r\n    return user.get('friends');\r\n  });\r\n}\r\n```\r\n\r\n如果用户没有传递 sessionToken, 则不向服务器发送 sessionToken, 相当于匿名访问，很有可能出现没有权限的情况，这个可能需要在文档中特别地提醒。\r\n\r\n以上就是 `AV.User.disableCurrentUser()` 这个修改产生的影响，关于每个方法都需要传递 sessionToken 比较繁琐的这一点，之后可以考虑通过一些高层次的 API（但仍基于上述的基本 API）来进行优化，例如下面是我想到的一种方法，仅当抛砖引玉：\r\n\r\n```\r\napp.post('/updatePost', function(request, response) {\r\n  var query = new AV.Query(Post);\r\n  query.get(request.body.id).then(function(post) {\r\n    updatePostBody(post.asUser(request.currentUser())).then(function(post) {\r\n      response.json(post);\r\n    });\r\n  });\r\n});\r\n\r\nfunction updatePostBody(post) {\r\n  post.body = //...\r\n  return post.save(); // 因为在路由中已经调用了 post.asUser, 这里不必传 sessionToken.\r\n}\r\n```\r\n\r\n在 find 的时候也可以将查询到的所有对象都绑定到用户：\r\n\r\n```\r\nquery.find({asUser: request.currentUser()}).then(function(objects) {\r\n  // 在 objects 中的对象上进行查改操作不需要传递 sessionToken.\r\n});\r\n```"}}