express
npm install -g express-generator
创建express项目
express --view=hjs myapp
数据表的设计
第一类是应用类,商城活动页面,门店,其他的小程序
id name application_string
第二类是页面层
id application / name / cover / is_able / page_string / update_time create_time / creator share_desc
第三类组件详情
id / page_id / defin_id / sort / component_string / name
用户
id / user_name / password
什么是mvc
model-view-controller 用于应用程序的分层开发
model 业务流程,处理过程对其他层来时是黑箱操作,业务功能的实现,模型接收视图请求的数据,对数据库操作,给controller层返回结果。
controller,模型和视图之上,能接收从接口传递的数据和参数,将校验和参数的处理,往view层数据输出。
创建controller文件夹
创建model文件夹
创建view文件
routes 获取
controller中间件的实现
extend
controller.js
// 在路由中拿到controller对象,中的方法
// controller mvc架构封装
// 中间件的相关逻辑
// 获取node核心模块
const fs = require('fs') // 文件操作方法
const path = require('path') // 处理路径
class Controller {
constructor(req,res,app) {
this.req = req
this.res = res
this.app = app
}
}
export.Controller = Controller // 对外暴露
// 接收传递进来的路径
// 返回:对象
const loadController = controllerPath => {
const ControllerMap = {}
const files = fs.readdirSync(controllerPath) // 数组列表
files.forEach(file => {
// 状态信息
const fileState = fs.statSync(path.join(controllerPath,file))
// 判断当前文件是目录还是文件,如果为true,为目录,false,是文件
if (fileState.isDirectory()) {
ControllerMap[file] = {
isDir:true,
...loadController(path.join(controllerPath,file))
}
} else {
// 是文件
const controllerName = file.substr(0,file.lastIndexOf('.')) // 获取文件名称
ControllerMap[controllerName] = {
isDir:false,
value:require(path.join(controllerPath,file))
}
}
})
return ControllerMap
}
const errMsg = message = ({code:1,message,data:null})
// 对象拦截器
const createProxy = (controllerMap,app) => {
return new Proxy(controllerMap), {
get(target,name) {
if (target[name]) {
if (!target[name].isDir) {
return new Proxy(target[name]) ,// 具体某个文件的内容 { get(target,name) {
return (req,res) => {
if(Object.getOwnPropertyNames(target,value.prototype).includes(name)){
new target.value(req,res,qpp)[name]()
} else {
res.send(errMsg('method not exist'))
}}}}
// 如果是文件目录
} else {
return createProxy(target[name],app)
}
// 名称不存在
} else {
return new Proxy({},{
get() {
return (req,res) => {
res.sed(errMsg('controller not exist')
}
}
})
}
}
}
}
}
const controllerMap = loadController(path.join(_dirname,'../controller'))
//_dirname:根目录
module.export = app => {
app.controller = createProxy(controllerMap ,app)
return(req,res,next) => {
next()
}
}
// 获取文件目录下的所有文件,读取文件目录信息,proxy处理,返回proxy对象。
// 通过.的方式获取方法
service中间件的实现
extend中写index.js
// 对外暴露
const controller = require('./controller ')
const service = require('./service ')
module.export = {
controller,
service
}
app.js
const {controller,service} = require('./extend')
// 添加中间件
app.use(controller)
app.use(service)
// 给app绑定两个属性,直接通过文件名访问下面的方法
config配置
config.env.dev.js
//
module.exports = {}
6.10
从8:00开始
npm install @bigbighu/altas@0.0.1
config.env.prod.js
config.js
const dev = require('./config.env.dev')
const prod = require('./config.env.prod')
const common = require('@bigbighu/altas')
const config = {
dev,
prod
}
// 添加common
for (let i in config) {
config[i]['common'] = common
}
module.export = config
index.js
const config = require('./config')
const env = config[process.env.NODE_ENV] || config.dev
module.exports = env
sequelize介绍
sequelize
ORM通过对对象和数据库关系的描述。
对数据库操作,调用api,不用管sql,数据库增删改查,调用正确的api
npm install sequelize@5.22.3
modules下面的indx
// 引入核心模块
const fs = require('fs')
// 数据模型定义
// 1. sequelize连接数据库
const config = require(__dirname + '/../config').db
// 数据库信息,
const {database,username,password} = config
//排除其他属性
const _ = require('lodash') // js工具库
const sequelize = new Sequelize(database,username,password,_.pick(config,['port','dialect','host'])) // 传入其他的信息
// 2. 读取moduel,添加到对象
cosnt db = {}
// 获取当前文件名
const basename = path.basename(__filename)
fs.readddirSync(_dirname)
.filter(file => {
return(file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js' )
})
.forEach(file => {
// 获取每一个对象
const model = sequelize['import'](path.join(__dirname,file))
db[model.name] = model // 模型的对象的集合
})
// 创建关联关系
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})
db.sequelize = sequelize
db.Sequelize = Sequelize
module.export = db
npm install lodash
pick, 选中的对象,1个是对象,2是选中的属性
config.env.dev.js
//
module.exports = {
// 数据库的配置
db: {
database:'cms',
host:'127.0.0.1',
port:3306,
username:'root',
password:'',
dialect:'mysql' // 数据库类型
}
}
安装mysql,通过navicat连接数据库
Sequelize中的模型
应用层模型的创建
application.js
module.exports = (sequelize,Datatypes) => {
// 模型的创建 ,表的名称,表的描述
const application = sequelize.define('application',{
id: {
type:Datatypes.INTEGER // 校验
allowNull:false // 不允许为null
primaryKey:true // 是否为主键
autoIncrement: true // 是否递增
},
name: {
type:Datatypes.STRING(128)
allowNull:false
},
application_string: {
type:Datatypes.STRING(256)
allowNull:true
},
// 模型的配置
{
freezeTableName:true //
timestamps:false // 创建事件
}
})
return application
}
page模型的创建
page.js
const dayjs = require('dayjs')
module.export = (sequelize,Datatypes) => {
const page = sequelize.define('page',{
// 列的相关描述
id: {
type:Datatypes.INTEGER // 校验
allowNull:false // 不允许为null
primaryKey:true // 是否为主键
autoIncrement: true // 是否递增
},
// page表和应用表关联
application_id: {
type:Datatypes.INTEGER
allowNull:false
},
name:{
type:Datatypes.STRING(128)
allowNull:false
},
cover: {
type:Datatypes.STRING(100)
allowNull:false
},
page_type: {
type:Datatypes.INTEGER
},
is_abled: {
type:Datatypes.INTEGER
},
page_string: {
type:Datatypes.STRING(256)
allowNull:false
// get和set监听
get() {
const page_string = this.getDataValue('page_string ')
return page_string ? JSON.parse(page_string)
}
},
create_time: {
type:Datatypes.DATE,
get() {
const create_time = this.getDataValue('create_time')
return create_time ? dayjs(create_time).format('YYYY-MM-DD HH:mm:ss'): ''
}
},
update_time: {
type:Datatypes.DATE,
get() {
const update_time = this.getDataValue('update_time')
return update_time ? dayjs(update_time).format('YYYY-MM-DD HH:mm:ss'): ''
}
},
creator: {
type:Datatypes.STRING(10)// 校验
allowNull:false
},
share_desc: {
type:Datatypes.STRING(100)// 校验
},
share_image: {
type:Datatypes.STRING(100)// 校验
}
},
{
freezeTableName:true //
timestamps:false // 创建事件
})
return page
}
npm install dayjs // 时间插件,对格式、时间计算
components.js
module.exports = (sequelize,Datatypes) => {
const components = sequelize.define('components',{
id: {
type:Datatypes.INTEGER // 校验
allowNull:false // 不允许为null
primaryKey:true // 是否为主键
autoIncrement: true // 是否递增
},
page_id: {
type:DataTypes.INTEGER,
allowNull:false
},
component_string: {
type:DataTypes.STRING(500)
allowNull:false
get() {
const str = this.getDataValue('component_string')
return str ? JSON.parse(str) : ''
}
},
sort: {
type:DataTyper.INTEGER,
allowNull:false
},
name:{
type:DataTyper.STRING(20),
allowNull:false
}
},
{
freezeTableName:true //
timestamps:false // 创建事件
})
return components;
}
user.js 用户模型
module.exports = (sequelize,Datatypes) => {
const user = sequelize.define('user',{
id: {
type:Datatypes.INTEGER // 校验
allowNull:false // 不允许为null
primaryKey:true // 是否为主键
autoIncrement: true // 是否递增
},
user_name {
type: Datatypes.STRING(20)
allowNull:false
},
password:{
type: Datatypes.STRING(100)
allowNull:false
}
},
{
freezeTableName:true //
timestamps:false // 创建事件
});
return user
}
cms数据表关联关系
一对一关系: a和b
belongsto
hasmany 一对多:
多对多
application
application.associate = models => {
application.hasMany(models.page,{
// 外键
foreignKey:'application_id'
})
}
page.js
page.associate = models => {
// 应用表page属于
page.belongsTo(models.application,{
foreighKey:'application_id'
})
// 当前页面,所有组件,通过page_id
page.hasMany(models.components,{
foreignKey:'page_id'
})
}
components.js
components.associate = models => {
components.belongsTo(models.page,{
foreignKey:'page_id'
})
}
应用,组件,页面表有关联关系。user没有
添加热更新:
修改了代码,添加console.log() 保存后不会重新启动
更改后,改写的代码能够立即生效
npm i nodemon@2.0.7 -S
start 加 " nodemon --exec node ./bin/www"
baseController封装
cms和h5两类控制器,校验的时候,校验实例的创建,都存放在公共的类中。
业务特征
base.js
// extend中的class
const Controller = require('../extend/controller').Controller
const Parameter = require('parameter')
// 生成Parameter 实例
const parameter = new Parameter({validateRoot:true})
class BaseController extends Controller {
response(data) {
return this.res.send(data)
}
// 接口正常响应
success(data = {}, code = 10000,message = 'success') {
return this.res.send({
code,
data,
message
})
}
error(data = null, code = -1,message = 'error') {
return this.res.send({
code,
data,
message
})
}
// 对前端传进来的参数初步的校验
validate(rules,data) {
try {
// 拿到Parameter
return parameter.validate(rules,data)
} catch (e) {
return e.message
}
}
getValidate (value) {
return value.map(v => {
return `${v.field} ${v.message}`
}).join(';')
}
}
module.exports = BaseController
npm i parameter
baseService
业务层面,继承base文件,定义状态,公共方法和属性
base.js
const Sequelize = require('sequelize')
const models = require('../models')
class BaseService {
static model = models
static Sequelize = Sequelize
static success(data,message,code) {
return {
code: code || 10000,
message: message || 'success', // 默认
data
}
}
static error (data = null, message,code = -1) {
return {
data,
message,
code
}
}
}
module.exports = BaseService
node启动前,调用中间件
const {controller,service} = require('./extend')
app.use(controller(app))
app.use(controller(service))
创建新增页面数据的接口
首先定义路由
routes 下的index
// 路由有2种,router.get/use
module.exports = (app,config) => {
router.use(`/${config.common.cmsApi}`,require('./cms')(app,config))
return router
}
cms.js
定义所有路由
const express = require('express')
const router = express.Router()
module.export = (app) => {
// 所有的路由
const { controller } = app
// 注册接口,cms定义在controller下,调用方法
router.post('/addPageJson', controller.cms.addPageJson)
// router文件定义接口路径
router.post('/updateCmsJson',controller.cms.updateCmsJson)
return router
//
}
controller下
cms.js
const BaseController = require('./base')
class CmsController extends BaseController {
async addPageJson () {
const {req} = this
const validate = this.validate({
name: {required:true,type:'string'}
}, req.body)
if (validate) {
return this.error(this.getValidate(validate))
}
// 校验通过,往数据库添加数据
const result = await this.app.service.cms.addPageJson(req.body)
return this.response(result) // 返回接口信息
}
}
module.exports = CmsController
service
cms.js
const _ = require('lodash') // 排除对象
const dayjs = require('dayjs')
class CMSService extends BaseService {
static async addPageJson(data) {
const { model } = this
// 分段插入,不能保证插入成功,因此创建一个事物,如果有报错,需要将整个事物回滚
const t = await model.sequelize.transaction()
// 新增页面数据
try {
const { name, cover,shareDesc,shareImage,componentList,creator = ""} = data
cosnt pageString = _.omit(data,['name','cover','componentList','shareDesc','shareImage'])
// 当前页面数据
const insert_page = await model.page.create({
application_id: 1,
name,
cover,
page_type: 0,
is_abled:0,
page_string:JSON.stringify(pageString),
create_time:dayjs.format('YYYY-MM-DD HH:mm:ss')
update_time:dayjs.format('YYYY-MM-DD HH:mm:ss')
creator,
shareDesc,
shareImage
})
// 组件数据
const componentData = componentList.map(data => {
const {sort} = data
return {
page_id:insert_page.id,
component_string:JSON.stringify(data.data)
name:data.name,
sort
}
})
// 批量插入
await model.components.bulkCreate(componentData)
await t.commit() // 事物的commit
return this.success({
id: insert_page.id
})
} catch(e) {
// 回滚
await t.rollback()
return this.error('接口调用失败')
}
}
}
module.exports = CMSService
npm i mysql2@2.2.5
cosnt BaseService = require('./base')
使用postman,模拟端口
const routes = require('./routes')
const config = require('./config')
app.use(routes(app,config))
controller编辑页面数据接口
// 2. controller下的cms.js
async updateCmsJson () {
const {req} = this
const data = {
id: req.body.id
}
const validate = this.validate({
id: {required:true,type:'number'}
})
if (validate) {
return this.error(this.getValidate(validate))
}
// 调用service,传递参数
await this.app.service.cms.updateCmsJson(req.body)
}
service下的cms.js
static async updateCmsJson(data) {
const { model } = this
const t = await model.sequelize.transaction()
const updateId = data.id
try {
const {name,cover,shareDesc,shareImage,componentList} = data
cosnt pageString = _.omit(data,['name','cover','componentList','shareDesc','shareImage'])
await model.page.update({
name,
cover,
page_string: JSON.stringify(pageString)
update_time:dayjs().formate('YYYY-MM-DD HH:mm:ss')
share_desc:shareDesc
share_image:shareImage
}, {
where: {
id:updateId // 找到表的数据进行更新
}
})
await model.components.destroy({
// 传入查询条件
where: {
page_id:updateId
}
}) // 删除对应的api用destroy
const componentData = componentList.map(data => {
const { sort,name } = data
const componentString = _.omit(data,['sort']) // 获取除了sort外的字段
return {
page_id: updateId,
component_string:JSON.stringify(componentString.data),
sort,
name
}
})
await model.components.bulkCreate(componentData)
await t.commit()
} catch(err) {
await t.rooback()
return this.error('调用接口失败')
}
}
获取详情页面接口
cms.js
// 1. 路由
router.get('/getPageJson',controller.cms.getPageJson)
controller 定义 getPageJson
async getPageJson () {
const {req} = this // 获取当前请求
// get请求,参数拼接在url地址上 获取到参数
const {id} = req.query // url地址取id
const result = await this.app.service.cms.getPageData({
id,type:'cms'
})
return this.success(result)
}
service
cms.js
static async getPageData(data) {
const { model } = this
const { id, type } = data
try {
// 对page表和组件表进行关联查询
// 获取某一条数据
const res = await model.page.findOne({
include:[
{
model:model.components
}
],
where: {
id // id查找页面,页面和组件进行关联,查询结果是页面下所有组件数据
},
order:[[sequelize.col('sort'),'asc']]
})
// 对数据进行处理
let result = {}
if (res) {
const {
name,
color,
bgColor,
cover,
components,
share_desc,
share_image,
page_string
} = res
// 数据组装
const pageData = {
id: res.id,
name,
color,
bgColor,
cover,
shareDesc:share_image,
shareImage:share_image,
backgroundColor:page_string.backgroundColor,
backgroundImage:page_string.backgroundImage
}
const componentList = []
components.forEach((component_string,name,id) => {
// 创建componentData
componentData = _.pick(component_string,['id','name'])
componentData.data = _.omit(component_string,['id','name'])
componentData.name = name
componentData.id = id
if (type == 'cms') {
componentList.push(componentData)
} else {
// h5页面,生效时间内显示,外不显示
if (component_string.validTime.length) {
if (dayjs(component_string.validTime[0]).isBetween(new Data.getTime(),component_string.validTime[1])) {
componentList.push(componentData)
//如果不存在,就显示
} else {
componentList.push(componentData)
}
}
}
})
result = {
...pageData,
componentList
}
return result
}
} catch (err) {
return this.error('接口调用失败')
}
}
h5 获取页面详情数据接口
router.get('/getH5PageJson',controller.cms.getH5PageJson)
controller
async getH5PageJson() {
const {req} = this // 获取当前请求
// get请求,参数拼接在url地址上 获取到参数
const {id} = req.query // url地址取id
const result = await this.app.service.cms.getPageData({
id,type:'h5'
})
return this.success(result)
}
用户管理列表的接口
router.get('/getPageList',controller.cms.getPageList)
controller
// 列表接口 需要1.分页功能,2.接收前端传来的form查询条件
async getPageList() {
const { req } = this
const data = {
pageNum:Number(req.query.pageNum),
pageSize:Number(req.query.pageSize),
name:req.query.name,
id:req.query.id,
creator:req.query.creator,
is_abled:req.query.isAbled,
startData:req.query.startData,
endData:req.query.endData
},
const validate = this.validate({
pageNum: {required:true,type:'number'},
pageSize:{required:true,type:'number'}
},data) {
if (validate ) {
return this.error(this.getValidate(validate))
}
const result = await this.app.service.cms.getPageList(data)
return this.success(result)
}
}
service
static async getPageList() {
const { pageNum,pageSize,name,id,creator,is_abled,startData,endData} = data
const {Op} = this.Sequelize
try{
const where = {}
if (name) {
where.name = {
// 模糊查询,前后模糊
[Op.like]: `%${name}%`
}
}
if (id) {
where.id = id
}
if (creator) {
where.creator = creator
}
if (is_abled) {
where.is_abled = is_abled
}
if (startData || endData) {
where.create_time = {
[Op.lt]:startData,
[Op.gt]:endData
}
}
const page = await this.model.page.findAndCountAll({
where,
offset: (pageNum - 1) * pageSize,//当前查询的数量
limit:pageSize,//查询的条数
order:[['updata_time','desc']]
}) // 接收查询条件,返回查询条数
return {
list:page.rows,
total:page.count,
pageNum,
pageSize
}
} catch(err) {
return null
}
}
活动页的删除接口
router.post('/deletePage',controller.cms.deletePage)
controller
async deletePage () {
const { req } = this
const { id } = req.body
const validate = this.validate({
id: {required:true,type:'Number'}
},{id})
if (validate) {
return this.error(this.getValidate(validata))
}
const result = await this.app.service.cms.deletePage(id)
return this.success(result)
}
service
static async deletePage(id) {
const {model} = this
try {
await model.components.destroy({
where: {
page_id: id
}
})
await model.page.destroy({
where: {
id
}
})
return {}
} catch(err) {
return null
}
}
文件上传接口
routes
router.post('/file/uplosd',contrller.cms.fileUpload)
controller
const ftp = require('../utils/ftp')
async fileUpload () {
// 获取当前文件,文件上传服务器,返回图片地址
const { req } = this
const from = formidable({multiples:true})
form.parse(req, (err,fields,files) => {
if (err) {
return
}
ftp(files.file.filepath,files.file.originalFilename)
return this.success(`http://43.129.13.3/${files.file.originalFilename}`)
})
}
安装插件,获取前端数据
npm i formidable@2.0.1
npm i ftp@0.3.10 连接ftp服务器
调用ftp,工具方法定义在utils
登录接口
router.post('/login',controller.cms.login)
controller
async login () {
const {req} = this
const {username,password} = req.body
const data = {
username,
password
}
const validate = this.validate({
username:{required:true,type:'string'},
password:{required:true,type:'string'},
},data)
if (validate) {
return this.error(this.getValidate(validate))
}
const result = await this.app.service.cms.login(data)
if (result) {
return this.success(result)
} else {
return this.error('账号密码错误')
}
}
service
const { createHmac } = require('crypto')
static async login () {
// 用户传递的密码, 通过加密方式和数据库
const { username, password } = data
const {model} = this
try {
const user = await model.user.findOne({
where: {
user_name:username
}
})
const secret = 'cmsSecret'
const hash = createHmac('sha256',secret)
.update(password)
.digest('hex')
if (hash === user.password) {
return {
token: hash,
username: user.user_name
}
}
return null
} catch(err) {
return null
}
}