本文介绍如何使用 MERN
技术栈开发一个先后端分离的电商项目,水平有限,不足之处,请指出,Github。javascript
安装好 mongodb 并启动, 新建一个后端项目目录, 目录结构以下 css
npm init
后,安装须要用到的库
npm i express mongoose multer validator jsonwebtoken dotenv cors bcrypt -S
html
图片上传 multer
, 验证表单数据 validator
, 配置环境变量 dotenv
, 跨域处理 cors
前端
新建 .env
文件,在里面配置数据库等参数java
DB_HOST=localhost
DB_PORT=27017
DB_NAME=cake-shop
JWT_KEY=my_jwt_key
PORT=9090
HOSTNAME=http://localhost
复制代码
接着在 models 目录下定义数据模型 product.js
表明产品,其余同理react
// product.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const productSchema = new Schema(
{
name: {
type: String,
required: true,
default: ''
},
description: {
type: String,
default: ''
},
price: {
type: Number,
required: true
},
stock: {
type: Number,
default: 0
},
imgList: {
type: Array,
default: ''
},
category: {
type: Array
},
top: {
type: Boolean,
default: false
},
rate: {
type: Number,
default: 5.0
},
publish: {
type: Boolean,
default: false
}
},
{
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
}
)
module.exports = mongoose.model('Product', productSchema)
复制代码
在 routes 目录下创建 product.js 用来定义产品相关的路由,创建 index.js
将 product.js
路由导出, 而后在 controllers 目录下创建 product.js 用来处理产品相关的路由, 而后在入口文件中使用便可ios
// routes/product.js
const express = require('express')
const router = express.Router()
const auth = require('../middleware/auth')
const controller = require('../controllers/product')
router.get('/', controller.getAllProducts)
router.get('/top', controller.getTopProducts)
router.get('/recommend', controller.getRecommendProducts)
router.get('/detail/:productId', controller.getProductDetail)
router.get('/:sort', controller.getProducts)
router.post('/', controller.addProduct)
router
.route('/:productId')
.put(auth, controller.updateProduct)
.delete(auth, controller.deleteProduct)
module.exports = router
复制代码
// index.js
const routes = require('./routes')
.....
app.use('/api/product', routes.product)
复制代码
在 controller 里面编写路由对应的逻辑代码,用 Postman 将代码逻辑跑通,用户注册时要对字段进行验证,以及须要对用户密码加密,用户登陆时要将 jsonwentoken
生成的token 返回给前端nginx
// controllers/user.js
// 注册
async function signUp(req, res, next) {
let { name, email, password } = req.body
if (!isVerifiedField(name)) {
return res.status(400).json({ success: false, message: '请输入字符长度大于4的用户名' })
}
if (!isVerifiedEmail(email)) {
return res.status(400).json({ success: false, message: '请输入正确的邮箱地址' })
}
if (!isVerifiedField(password)) {
return res.status(400).json({ success: false, message: '请设置长度不小于4个字符的密码' })
}
const oldUser = await User.findOne({ email }).exec() // 检验用户是否已存在
if (oldUser !== null) {
return res.status(409).json({ success: false, message: '用户已存在' })
}
password = await bcrypt.hash(password, 10) // 对用户密码加密
const newUser = new User({ name, email, password })
newUser
.save()
.then(result => {
return res.status(201).json({ success: true, result })
})
.catch(error => {
return res.status(500).json({ success: false, error })
})
}
// 登陆
async function login(req, res, next) {
const { name, email, password } = req.body
if (name) {
checkField({ name })
}
if (email) {
checkField({ email })
}
async function checkField(field) {
const user = await User.findOne(field).exec()
if (user === null) {
return res.status(404).json({ success: false, message: '用户不存在' })
}
const isMatch = await bcrypt.compare(password, user.password)
if (isMatch) {
const token = jwt.sign({ field, id: user._id }, process.env.JWT_KEY) // 生成token
return res.status(200).json({ success: true, message: '登陆成功', token }) // 返回token
} else {
return res.status(401).json({ success: false, message: '密码错误' })
}
}
}
复制代码
使用 create-react-app
创建项目,使用 yarn
安装须要的依赖包git
npx create-react-app you-project
github
yarn add antd react-router-dom axios
根据 create-react-app
的 User Guide 配置 CSS 预处理器等
创建项目目录结构
配置 axios
import axios from 'axios'
const token = localStorage.getItem('CAKE_SHOP_AUTH_TOKEN')
const Request = axios.create({
baseURL: 'http://localhost:9090/api',
timeout: 5000,
headers: {
authorization: token ? token : '' // 若是有token就在请求headers里面带上
}
})
export default Request
复制代码
在 pages 目录下创建管理员注册,登陆等页面,用户登陆后把后端返回的token放到 localStorage
里面,而后跳转到工做台首页
// pages/Login
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import { Layout, Form, Icon, Input, Button, Checkbox, message as Message } from 'antd'
import '../account.css'
import { ManagerContext } from '../../store/manager'
import ManagerService from '../../services/manager'
import { CAKE_SHOP_AUTH_TOKEN, CAKE_SHOP_USER_INFO } from '../../constant'
const { Item } = Form
const { Content, Footer } = Layout
class Login extends Component {
handleSubmit = e => {
e.preventDefault()
this.props.form.validateFields(async (err, values) => {
if (!err) {
const { name, password } = values
ManagerService.login(name, password) // 请求登陆接口
.then(res => {
const { history, login } = this.props // history 对象来自于 react-router-dom
const { message, token, manager } = res.data
Message.success(message)
localStorage.setItem(CAKE_SHOP_AUTH_TOKEN, `Bearer ${token}`)
localStorage.setItem(CAKE_SHOP_USER_INFO, JSON.stringify(manager))
login(manager)
history.push('/dashboard')
})
.catch(error => {
const { data } = error.response
Message.error(data.message)
})
}
})
}
render() {
const { getFieldDecorator } = this.props.form
return (
<ManagerContext.Consumer>
{login => (
<Layout className="account" login={login}>
<Content className="account__content">
<h1 className="account__title">店铺管理系统</h1>
<sub className="account__sub-title">登陆</sub>
<Form className="account__form" onSubmit={this.handleSubmit}>
<Item>
{getFieldDecorator('name', {
rules: [{ required: true, message: '请输入你的用户名!' }]
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username admin"
/>
)}
</Item>
<Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: '请输入你的密码!' }]
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password admin"
/>
)}
</Item>
<Item>
<Button type="primary" htmlType="submit" block>
登陆
</Button>
</Item>
<Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true
})(<Checkbox>记住我</Checkbox>)}
</Item>
</Form>
</Content>
<Footer className="account__footer">
<a
className="account__link"
href="https://xrr2016.github/io"
target="_blank"
rel="noopener noreferrer"
>
<Icon type="github" />
</a>
<a className="account__link" href="mailto:xiaoranran1993@outlook.com">
<Icon type="mail" />
</a>
</Footer>
</Layout>
)}
</ManagerContext.Consumer>
)
}
}
export default withRouter(Form.create()(Login))
复制代码
在 routes 目录下创建工做台里面的子路由页面,编写对应逻辑, 处理不一样的业务, 最后在入口文件中定义 react-router-dom
的路由
一样使用 create-react-app
创建项目,使用 yarn
安装须要的依赖包
npx create-react-app you-project
yarn add antd-mobile react-router-dom axios
创建项目目录结构
这里的逻辑跟管理后台主要的区别在于请求的数据接口不一样,以及页面的UI不一样,具体实现,UI交互等按我的而定。
功能开发完毕后,使用 yanr build
将前端以及管理后台项目打包,将代码传到服务器上,配置不一样的域名,使用 nginx
进行反向代理,防止刷新浏览器后404。
location / {
try_files $uri $uri/ /index.html;
}
复制代码