Vue+Nuxt+SSR+Koa2+Element-ui全栈开发美团网----

1,345 阅读4分钟

技术栈

前端

  • 前端
    • 初始化项目: Nuxt.js
    • 组件库: ELement-ui
    • 路由控制/拦截: vue-router
    • 状态管理: Vuex
  • 服务端
    • 运行环境: Node.js
    • 后台开发框架: Koa2
    • 路由中间件: Koa-router
    • 发送邮件: Nodemailer
  • HTTP通讯
    • 接口请求/拦截: Axios
    • 登录验证: Passport
  • 数据库
    • MongoDB
    • 数据库操作: Mongoose
    • 缓存工具: Redis
  • 源码

page-index 首页

布局

  • 使用Element-ui 的el-header、el-main、el-footer来进行页面布局
  • 每个部分都使用el-row、el-col的24格栅格布局
  • 嵌套关系比较多,这里使用scss来设置页面的样式更直观

数据

  • 首页需要左上角的用户所在城市需要通过根据目标的IP地址或者经纬度来获取
    • 这里我们可以直接根据用户请求的IP地址查找相应的所在城市,具体实现请看我之前写的一篇博客,如何使用高德地图Web服务API来获取用户所在城市,链接如下
    • juejin.cn/post/684490… ,结果如下
  • 餐饮以及娱乐数据也可以通过高德地图API来获取,具体则是Web服务的搜索POI功能,传入美食、景点、SPA等等名称就可以从高德地图API那里得到返回过来的具体数据,有地址,人均消费,类型等等。
    • 其中这些数据都是先请求过来然后通过Vuex状态管理,actions的时机就在Vue生命周期mounted时请求,请求后得到的数据就保存在State里面了,这样就可以完成SSR渲染,有利于SEO搜索
    • 原理同上,结果如下

page-register 新用户注册页面

  • 布局:使用element-ui提供的form表单组件进行快速的构建基础骨架,使用vue中的v-model指令将数据和dom进行双向绑定准备数据处理。
  • 逻辑判断:
    • 当用户填写完用户名和邮箱,点击发送验证码的时候,通过Nodemailer发送验证码给邮箱地址。具体代码如下
  router.post('/verify', async (ctx, next) => {
  let username = ctx.request.body.username
  const saveExpire = await Store.hget(`nodemail:${username}`, 'expire')
  if (saveExpire && new Date().getTime() - saveExpire < 0) {
    ctx.body = {
      code: -1,
      msg: '验证过于频繁,1分钟内1次'
    }
    return false
  }

  let transporter = nodeMailer.createTransport({
    host: Email.smtp.host,
    port: 587,
    secure: false,
    auth: {
      user: Email.smtp.user,
      pass: Email.smtp.pass
    }
  })

  let ko = {
    code: Email.smtp.code(),
    expire: Email.smtp.expire(),
    email: ctx.request.body.email,
    user: ctx.request.body.username
  }

  let mailOptions = {
    from: `"认证邮件" <${Email.smtp.user}>`,
    to: ko.email,
    subject: '《高仿美团网全栈实战》注册码',
    html: `您在《高仿美团网全栈实战》中注册,您的邀请码是${ko.code}`
  }

  await transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      return console.log('error')
    } else {
      Store.hmset(`nodemail:${ ko.user }`, 'code', ko.code, 'expire', ko.expire, 'email', ko.email)
    }
  })
  ctx.body = {
    code: 0,
    msg: '验证码已发送, 可能会有延迟,有效期1分钟'
   }
})
  • 当用户填写完所有信息点击注册按钮的时候,会判断该用户是否填写完整以及用户是否被注册等等信息,全部无误后会把该用户的注册信息存放进数据库,密码会以MD5的加密方式保存。
  • 核心代码如下:
router.post('/signup', async (ctx) => {
  const {
    username,
    password,
    email,
    code
  } = ctx.request.body

  if (code) {
    const saveCode = await Store.hget(`nodemail:${username}`, 'code')
    const saveExpire = await Store.hget(`nodemail:${username}`, 'expire')

    if (code === saveCode) {
      console.log(code + saveCode)
      if (new Date().getTime() - saveExpire > 0) {
        ctx.body = {
          code: -1,
          msg: '验证码已过期,请重新尝试'
        }
        return false
      }
    } else {
      ctx.body = {
        code: -1,
        msg: '请填写正确的验证码'
      }
    }
  } else {
    ctx.body = {
      code: -1,
      msg: '请填写验证码'
    }
  }
  // 链接数据库查找该用户名是否被注册
  let user = await User.find({
    username
  })

  if (user.length) {
    ctx.body = {
      code: -1,
      msg: '用户名已被注册'
    }
    return false
  }
  // 把用户的信息存放进数据内
  let newuser = await User.create({
    username,
    password,
    email
  })
  if (newuser) {
    ctx.body = {
      code: 0,
      msg: '注册成功'
    }
  } else {
    ctx.body = {
      code: -1,
      msg: '注册失败'
    }
  }
})

page-login 登录页面

使用Passport验证登录

  • 新建一个Passport.js文件,里面设置好Passport的策略,这里使用local本地策略。
const passport = require('koa-passport')
const LocalStrategy = require('passport-local')
const UserModel = require('../../dbs/modules/users')

passport.use(
  new LocalStrategy(
    async function (username, password, done) {
      let where = {
        username
      }
      let result = await UserModel.findOne(where)
      if (result != null) {
        if (result.password === password) {
          return done(null, result)
        } else {
          return done(null, false, '密码错误')
        }
      } else {
        return done(null, false, '用户不存在')
      } 
    }
  )
)

passport.serializeUser(function (user, done) {
  done(null, user)
})

passport.deserializeUser(function (user, done) {
  done(null, user)
})

module.exports = passport
  • 在用户输入完用户名和密码,点击登录按钮的时候,设置好的路由请求就会调用Passport方法来判断用户是否账号密码正确
router.post('/signin', async (ctx, next) => {
  return Passport.authenticate('local', function (err, user, info, status) {
    if (err) {
      ctx.body = {
        code: -1,
        msg: err
      }
    } else {
      if (user) {
        ctx.body = {
          code: 0,
          msg: '登录成功',
          user
        }
        return ctx.login(user)
      } else {
        ctx.body = {
          code: 1,
          msg: info
        }
      }
    }
  })(ctx, next)
})
  • 这里注意,登录后会把用户信息存放在session里面,等下跳转到首页的时候把用户信息渲染到首页上。

page-changeCity 切换城市页面

获取全国的省份和城市数据

  • 这里可以参考我之前写的一篇博客,还是利用高德地图的Web服务API,链接在下面:
  • juejin.cn/post/684490…

使用V-for循环把数据渲染到Element-ui的模板上

  • 结果如下

  • 这里注意在省份选择那里,用户如果不选择某个省份的话,右边的城市框也应该是不可选择状态,这里用:disabled="!city.length"实现,用户选取了某个省份就返回这个省份的所有城市数据。
  • 用户选择了某个城市后,会跳转到首页同时把这个选择的城市保存在Vuex里面,这样首页的内容也会根据这个城市的名称或者adcode值搜索渲染出来。