Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

128 linhas
3.6 KiB

  1. const Base = require('../base');
  2. const svgCaptcha = require('svg-captcha');
  3. module.exports = class extends Base {
  4. // 登录页面
  5. async loginAction() {
  6. // 已登录状态访问登录页,自动退出
  7. if (this.cookie('admin_token')) {
  8. this.cookie('admin_token', null);
  9. }
  10. this.assign('siteConfig', {});
  11. this.assign('now_year', new Date().getFullYear());
  12. return this.display();
  13. }
  14. // 图形验证码
  15. async captchaAction() {
  16. const captcha = svgCaptcha.create({
  17. size: 4,
  18. ignoreChars: '0oO1lIi',
  19. noise: 3,
  20. color: true,
  21. background: '#f0f2f5',
  22. width: 120,
  23. height: 40
  24. });
  25. // 存入session
  26. await this.session('captcha', captcha.text.toLowerCase());
  27. this.ctx.type = 'image/svg+xml';
  28. this.ctx.body = captcha.data;
  29. }
  30. // 登录接口
  31. async doLoginAction() {
  32. const { username, password, remember, captcha } = this.post();
  33. if (!username || !password) {
  34. return this.fail('请输入用户名和密码');
  35. }
  36. // 验证码校验
  37. if (!captcha) {
  38. return this.fail('请输入验证码');
  39. }
  40. const sessionCaptcha = await this.session('captcha');
  41. // 用完即清
  42. await this.session('captcha', null);
  43. if (!sessionCaptcha || captcha.toLowerCase() !== sessionCaptcha) {
  44. return this.fail('验证码错误');
  45. }
  46. // 查找用户(不限status,锁定判断在后面)
  47. const user = await this.model('admin_user')
  48. .where({ username, is_deleted: 0 })
  49. .find();
  50. if (think.isEmpty(user)) {
  51. return this.fail('用户名或密码错误');
  52. }
  53. // 账号停用
  54. if (user.status !== 1) {
  55. return this.fail('账号已被禁用,请联系管理员');
  56. }
  57. // 锁定判断:当日错误>=5次
  58. const today = new Date().toISOString().slice(0, 10);
  59. const failDate = user.login_fail_date ? new Date(user.login_fail_date).toISOString().slice(0, 10) : null;
  60. const failCount = (failDate === today) ? (user.login_fail_count || 0) : 0;
  61. if (failCount >= 5) {
  62. return this.fail('密码错误次数过多,账号已锁定,请联系管理员或次日自动解锁');
  63. }
  64. // 密码验证
  65. if (user.password !== think.md5(password)) {
  66. const newCount = failCount + 1;
  67. await this.model('admin_user').where({ id: user.id }).update({
  68. login_fail_count: newCount,
  69. login_fail_date: today
  70. });
  71. const remain = 5 - newCount;
  72. if (remain <= 0) {
  73. return this.fail('密码错误次数过多,账号已锁定,请联系管理员或次日自动解锁');
  74. }
  75. return this.fail(`密码错误,还剩${remain}次机会`);
  76. }
  77. // 登录成功,清除错误计数,更新登录信息
  78. await this.model('admin_user').where({ id: user.id }).update({
  79. login_fail_count: 0,
  80. login_fail_date: null,
  81. last_login_time: think.datetime(),
  82. last_login_ip: this.ctx.ip || ''
  83. });
  84. // 生成JWT Token
  85. const token = Base.generateToken({
  86. id: user.id,
  87. username: user.username,
  88. role_id: user.role_id
  89. });
  90. const cookieOpts = {
  91. httpOnly: true,
  92. path: '/'
  93. };
  94. if (remember) {
  95. cookieOpts.maxAge = Base.JWT_EXPIRES_IN * 1000;
  96. }
  97. this.cookie('admin_token', token, cookieOpts);
  98. this.adminUser = { id: user.id, username: user.username };
  99. await this.log('login', '系统', `${user.username} 登录系统`);
  100. return this.success({ token });
  101. }
  102. // 退出登录
  103. async logoutAction() {
  104. if (this.adminUser) {
  105. await this.log('logout', '系统', `${this.adminUser.username} 退出系统`);
  106. }
  107. this.cookie('admin_token', null);
  108. return this.redirect('/admin/login.html');
  109. }
  110. };