技术栈
前端
- 前端
- 初始化项目: 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地址或者经纬度来获取
- 餐饮以及娱乐数据也可以通过高德地图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 切换城市页面
获取全国的省份和城市数据
使用V-for循环把数据渲染到Element-ui的模板上
- 这里注意在省份选择那里,用户如果不选择某个省份的话,右边的城市框也应该是不可选择状态,这里用
:disabled="!city.length"
实现,用户选取了某个省份就返回这个省份的所有城市数据。
- 用户选择了某个城市后,会跳转到首页同时把这个选择的城市保存在Vuex里面,这样首页的内容也会根据这个城市的名称或者adcode值搜索渲染出来。