代码拉取完成,页面将自动刷新
npm init -y
cnpm i koa koa-router koa-bodyparser -S
cnpm i nodemon -D
package.json
下增加开发启动脚本"scripts": {
"dev": "nodemon app.js"
},
VSCode
编辑器下调试,首先把nodemon
安装在全局VSCode
编辑器的调试小虫子,添加Node.js nodemon
等几个调试配置,生成的.vscode/launch.json
文件内容如下:{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "nodemon",
"runtimeExecutable": "nodemon",
"program": "${workspaceFolder}/app.js",
"restart": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
},
{
"type": "node",
"request": "launch",
"name": "启动程序",
"program": "${workspaceFolder}\\app.js"
},
{
"type": "node",
"request": "launch",
"name": "当前文件",
"program": "${file}"
}
]
}
windows
环境下修改NODE_ENV
的值,安装 cross-env
: cnpm i cross-env -D
require-directory
: cnpm i require-directory -S
app/router/home.js
const Router = require('koa-router')
const router = new Router()
router.get('/', async(ctx, next) => {
ctx.body = `<h1>我是首页</h1>`
})
router.get('/siteinfo', async(ctx, next) => {
ctx.body = {
siteName: '站点名称',
siteUrl: 'http://www.everydaykaixin.com',
title: 'xxxxx',
description: '站点描述',
footer: 'Copyright @ 2020 www.wxboyan.com. 无锡博言传动机械有限公司版权所有 苏ICP备16021376'
}
})
// module.exports = { router } // 这种导出路由的方式也支持
module.exports = router
const Koa = require('koa')
const Router = require('koa-router')
const requireDirectory = require('require-directory')
const app = new Koa()
requireDirectory(module, './app/router', { visit: whenModuleLoaded })
function whenModuleLoaded(obj) {
if(obj instanceof Router) {
app.use(obj.routes()).use(obj.allowedMethods())
} else {
let objKeys = Object.keys(obj)
if(!objKeys.length) return
for(let key of objKeys) {
const router = obj[key]
if(router instanceof Router) { // 找到模块导出的是Router,就注册为路由,默认只为模块注册一个路由,其它导出忽略
app.use(router.routes()).use(router.allowedMethods())
break
}
}
}
}
app.listen(8888, () => {
console.log(`服务运行于端口8888`)
})
HttpException
,该类继承自Error
类,以便throw
, 并定义一些继承自HttpException
的子类class HttpException extends Error {
constructor(msg = '服务器异常', errorCode = 10000, code = 400) {
super()
this.errorCode = errorCode
this.code = code
this.msg = msg
}
}
class ParameterException extends HttpException {
constructor(msg, errorCode) {
super()
this.code = 400
this.msg = msg || '参数错误'
this.errorCode = errorCode || 10000
}
}
class Success extends HttpException {
constructor(msg, errorCode) {
super()
this.code = 201
this.msg = msg || 'ok'
this.errorCode = errorCode || 0
}
}
class NotFound extends HttpException{
constructor(msg, errorCode) {
super()
this.msg = msg || '资源未找到'
this.errorCode = errorCode || 10000
this.code = 404
}
}
class AuthFailed extends HttpException {
constructor(msg, errorCode) {
super()
this.msg = msg || '授权失败'
this.errorCode = errorCode || 10004
this.code = 401
}
}
module.exports = {
HttpException,
ParameterException,
Success,
NotFound,
AuthFailed
}
exception.js
const { HttpException } = require('../core/http-exception')
const catchError = async (ctx, next) => {
try {
await next()
} catch (error) {
if(error instanceof HttpException) { // 处理自己定义的异常
ctx.body = {
msg: error.msg,
errorCode: error.errorCode,
request: `${ctx.method} ${ctx.request.path}`
}
ctx.status = error.code
return
}
ctx.body = {
msg: '抱歉,服务器发生了异常',
errorCode: 999,
request: `${ctx.method} ${ctx.request.path}`
}
ctx.status = 500
}
}
module.exports = catchError
lin-validator
的依赖包yarn add validator lodash
MySQL
数据库sequelize
yarn add sequelize
mysql
数据库,安装mysql2
数据库驱动:yarn add mysql2
const Sequelize = require('sequelize')
const { database } = require('../config/config')
const { dbName, host, port, user, password } = database
const sequelize = new Sequelize(dbName, user, password, {
host,
port,
dialect: 'mysql',
// dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */
logging: true,
dialectOptions: {
dateStrings: true, // mysql获取时间时会自动作时区转换,禁止mysql的转换
typeCast(field, next) { // 覆盖了sequelize的转换,看代码,目前只影响date和GEOMETRY,所以加上DATETIME类型
if(field.type === 'DATETIME') {
return field.string()
}
return next()
}
},
timezone: '+08:00',
define: {
timestamps: true, // 会自动生成 createAt和updateAt
freezeTableName: true // 不将表格名称复数化
}
})
sequelize.sync()
// Note: using `force: true` will drop the table if it already exists
// sequelize.sync({force: true})
sequelize.authenticate().then(() => {
console.log('数据库连接成功.')
}).catch(err => {
console.error('数据库连接失败:', err)
})
module.exports = { sequelize }
const { Sequelize, Model } = require('sequelize')
const { sequelize } = require('../../core/db')
class User extends Model {}
User.init({
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
userName: {
type: Sequelize.STRING(30),
unique: true
},
password: Sequelize.STRING(50),
loginCount: {
type: Sequelize.INTEGER,
defaultValue: 0
}
}, {
sequelize,
tableName: 'User',
paranoid: true // 会自动增加 deleteAt字段,实行软删除
})
module.exports = {
User
}
app/validators/userValidator.js
const { LinValidator, Rule } = require('../../core/lin-validator-v2')
class RegisterValidator extends LinValidator {
constructor() {
super()
// 注册时id不用提供
// this.id = [
// new Rule('isInt', 'id必须是正整数', {min: 1})
// ]
this.userName = [
new Rule('isLength', '用户名必须是2-32个字符', {
min: 2,
max: 32
})
]
this.password = [
new Rule('isLength', '密码至少6个字符,最多32个字符', {
min: 6,
max: 32
}),
new Rule('matches', '密码不符合规范', '^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]')
]
this.confirmPassword = this.password
}
validatePassword(vals) { // 自定义的验证器必须以validate开头
const psw1 = vals.body.password
const psw2 = vals.body.confirmPassword
if (psw1 !== psw2) {
throw new Error('密码和确认密码必须相同')
}
}
}
module.exports = {
RegisterValidator
}
const Router = require('koa-router')
const { success } = require('../../lib/helper')
const { RegisterValidator } = require('../../validators/userValidator')
const { User } = require('../../models/User')
const router = new Router({prefix: '/admin/user'})
router.post('/register', async(ctx) => {
const v = await new RegisterValidator().validate(ctx)
const user = {
userName: v.get('body.userName'),
password: v.get('body.password')
}
await User.create(user)
success('注册成功')
})
// module.exports = { router } // 这种导出路由的方式也支持
module.exports = router
koa-static
中间件: yarn add koa-static
app.js
配置上中间件const path = require('path')
const static = require('koa-static')
app.use(static(path.join(__dirname,'./static')))
/**
* 获取产品分页数据
*/
router.post('/pageproduct/:id?', async(ctx) => {
const v = await new ProductQueryValidator().validate(ctx)
const pageIndex = v.get('body.pageIndex')
const pageSize = v.get('body.pageSize')
const cateId = v.get('path.id')
let where = null
if(cateId) { // 如果有传入分类id, 表明是查询该分类下的分页数据,默认是所有产品的分页数据
where = {
ProCateId: cateId * 1
}
}
const offset = (pageIndex - 1) * pageSize
const products = await Product.findAndCountAll({
where: where,
attributes: { exclude: ['createdAt','updatedAt'] },
include: [
{ model: ProCate, required: true, attributes: { exclude: ['content','createdAt','updatedAt'] } }
],
order: [
['orderNum', 'DESC']
],
offset: offset,
limit: pageSize
})
const httpOrigin = ctx.origin
products.rows.forEach(item => item.pic = httpOrigin + item.pic)
ctx.body = new SuccessResult(products)
})
上面的查询要注意在定义模型时 定义好关联关系
ProCate.hasMany(Product, {
constraints: false
})
Product.belongsTo(ProCate, { // 会给Product表自动生成 ProCateId (模型名ProCate + id 并且是驼峰形式所以是 ProCateId)
constraints: false
})
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。