我们首先来实现用户的注册功能。修改 default.scheme.js ,添加如下代码:

var validator = require('validator');
var crypto = require('crypto');

module.exports = {
  "(GET|POST) /signup": {
    "request": {
      "session": checkNotLogin
    }
  },
  "POST /signup": {
    "request": {
      "body": checkSignupBody
    }
  }
};

function md5 (str) {
  return crypto.createHash('md5').update(str).digest('hex');
}

function checkNotLogin() {
  if (this.session && this.session.user) {
    this.flash = {error: '已登录!'};
    this.redirect('back');
    return false;
  }
  return true;
}

function checkLogin() {
  if (!this.session || !this.session.user) {
    this.flash = {error: '未登录!'};
    this.redirect('/signin');
    return false;
  }
  return true;
}

function checkSignupBody() {
  var body = this.request.body;
  var flash;
  if (!body || !body.name) {
    flash = {error: '请填写用户名!'};
  }
  else if (!body.email || !validator.isEmail(body.email)) {
    flash = {error: '请填写正确邮箱地址!'};
  }
  else if (!body.password) {
    flash = {error: '请填写密码!'};
  }
  else if (body.password !== body.re_password) {
    flash = {error: '两次密码不匹配!'};
  }
  else if (!body.gender || !~['男', '女'].indexOf(body.gender)) {
    flash = {error: '请选择性别!'};
  }
  else if (body.signature && body.signature.length > 50) {
    flash = {error: '个性签名不能超过50字!'};
  }
  if (flash) {
    this.flash = flash;
    this.redirect('back');
    return false;
  }
  body.name = validator.trim(body.name);
  body.email = validator.trim(body.email);
  body.password = md5(validator.trim(body.password));
  return true;
}

这里我们定义了两个函数 checkNotLogin 和 checkLogin 用于检查用户的登录状态,并规定只有非登录状态才能访问 signup 页或者提交注册信息。当用户提交注册信息后,koa-scheme 会对请求体做严格的验证,验证失败则直接返回,验证通过则对请求体做过滤和格式化(将密码 md5 加密),然后请求才会传递到下一个中间件。

修改 signup.js ,添加如下代码:

var Models = require('../lib/core');
var $User = Models.$User;

exports.get = function* () {
  yield this.render('signup');
};

exports.post = function* () {
  var data = this.request.body;

  var userExist = yield $User.getUserByName(data.name);
  if (userExist) {
    this.flash = {error: '用户名已存在!'};
    return this.redirect('/');
  }

  yield $User.addUser(data);

  this.session.user = {
    name: data.name,
    email: data.email
  };

  this.flash = {success: '注册成功!'};
  this.redirect('/');
};

以上代码的意思是:当用户访问 /signup 时,渲染 signup.ejs 并返回页面;当用户提交注册信息时,首先检查用户名是否存在,存在则返回 '用户名已存在!' 的错误提示,不存在则添加该用户,并将该用户的信息保存到 session 中,返回 '注册成功!' 的提示。接下来我们完成模版文件和基本的 css 代码。

header.ejs

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>N-club</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.12.0/semantic.min.css">
    <link rel="stylesheet" href="/css/style.css">
    <script src="/js/jquery-1.11.2.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.12.0/semantic.min.js"></script>
  </head>
  <body>
    <div class="ui fixed menu navbar">
      <div class="container">
        <a href="/" class="item"><%= $app.name %></a>
        <div class="item"><%= $app.description %></div>
        <div class="right menu">
          <% if ($this.session.user) { %>
            <a href="/logout" class="item">登出</a>
          <% } else { %>
            <a href="/signup" class="item">注册</a>
            <a href="/signin" class="item">登录</a>
          <% } %>
        </div>
      </div>
    </div>


    <% if ($this.flash && $this.flash.success) { %>
      <div class="flash">
        <div class="ui green message"><i class="close icon"></i><%= $this.flash.success %></div>
      </div>
    <% } %>
    <% if ($this.flash && $this.flash.error) { %>
      <div class="flash">
        <div class="ui red message"><i class="close icon"></i><%= $this.flash.error %></div>
      </div>
    <% } %>

    <script type="text/javascript">
      $('.message .close').on('click', function() {
        $(this).closest('.message').fadeOut();
      });
    </script>

footer.ejs

  </body>
</html>

signup.ejs

<% include header %>

<div class="container">
  <div class="ui form segment" style="width:400px;margin:40px auto">
    <form method="post">
      <div class="two fields">
        <div class="field required">
          <label>用户名</label>
          <input placeholder="用户名" type="text" name="name">
        </div>
        <div class="field required">
          <label>性别</label>
          <div class="ui selection dropdown">
            <div class="default text">选择性别</div>
            <i class="dropdown icon"></i>
            <input type="hidden" name="gender">
            <div class="menu">
              <div class="item" data-value="男">男</div>
              <div class="item" data-value="女">女</div>
            </div>
          </div>
        </div>
      </div>
      <div class="two fields">
        <div class="field required">
          <label>密码</label>
          <input placeholder="密码" type="password" name="password">
        </div>
        <div class="field required">
          <label>重复密码</label>
          <input placeholder="重复密码" type="password" name="re_password">
        </div>
      </div>
      <div class="field required">
        <label>邮箱</label>
        <input placeholder="邮箱" type="email" name="email">
      </div>
      <div class="field">
        <label>个人签名</label>
        <input type="text" placeholder="限制在50字以内" name="signature">
      </div>
      <input type="submit" class="ui button fluid" value="注册">
    </form>
  </div>
</div>

<script type="text/javascript">
  $('.ui.dropdown').dropdown();
</script>

<% include footer %>

style.css

body{background-color:#f9f9f7;padding-top:100px}
.container{width:100%;max-width:1100px;margin:0 auto}
.navbar{height:60px;line-height:60px;background-color:rgba(255,255,255,.9) !important}
.flash{max-width:1100px;margin:0 auto 20px}
.summary{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.topic{margin-top:10px;color:rgba(0,0,0,.4);font-size:.9rem}
.content{font-size:16px;line-height:24px}
.userCard{height:48px;margin-bottom:10px}
.userCard img{width:48px!important;height:48px;float:left;margin-right:15px}
.userCard .info{line-height:48px}
.list img{width:40px}
.ui.pagination{margin-top:10px !important}
.ui.comments{max-width:100%;font-size:16px}
.ui.menu .item:before{width:0}

至此我们就完成了用户注册的功能,启动 mongodb 并运行 npm start 试试吧。