- 技术选型
- 项目结构
1. 项目创建
在 CMD 窗口执行以下命令创建项目 share
$ npm install -g koa-generator
$ koa2 -e share
$ cd share && npm install
$ SET DEBUG=koa* & npm start share
然后就可以使用浏览器访问了 http://127.0.0.1:3000
2. 目录结构
执行以上命令后生成的项目目录如下
myapp
│ app.js
│ package-lock.json
│ package.json
│
├─bin
│ www
│
├─node_modules
│
├─public
│ ├─images
│ ├─javascripts
│ └─stylesheets
│ style.css
│
├─routes
│ index.js
│ users.js
│
└─views
error.ejs
index.ejs
对项目目录作如下优化
- 添加 config 目录,放置系统配置文件,也可以把 数据库、缓存、邮件、支付等工具类文件放于此处,当然也可以放到 utils 目录下,看个人爱好
- 添加 controllers 目录,放置控制器文件
- 添加 middlewares 目录,放置中间件文件
- 创建 models 目录,放置数据库 ORM 文件
- 创建 utils 目录,放置工具类文件
安装如下所需模块(可根据具体需求进行选择)
$ npm install koa2-cors --save
$ npm install koa-jwt --save
$ npm install jsonwebtoken --save
$ npm install md5-node --save
$ npm install mongoose --save
$ npm install ioredis --save
$ npm install nodemailer --save
$ npm install alipay-sdk --save
优化后的项目目录如下
myapp
│ app.js
│ package-lock.json
│ package.json
│
├─bin
│ www
│
├─node_modules
│
├─config
│ alipay.js
│ index.js
│ license.txt
│ mail.js
│ mongo.js
│ private-key.pem
│ public-key.pem
│ redis.js
│ store.js
│
├─controllers
├─middlewares
├─models
│ goods.js
│ order.js
│ user.js
│
├─public
│ ├─images
│ │
│ ├─javascripts
│ │ embed.js
│ │ index.html
│ │ pay.js
│ │ qrcode.min.js
│ │
│ └─stylesheets
│ embed.css
│ layer.css
│ pay.css
│ style.css
│
├─routes
│ goods.js
│ index.js
│ order.js
│ user.js
│
├─utils
│ util.js
│
└─views
error.ejs
index.ejs
pay.ejs
3. 样本代码
app.js 入口文件
const Koa = require('koa')
const app = new Koa()
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const json = require('koa-json')
const logger = require('koa-logger')
const views = require('koa-views')
app.keys = require('./config').keys
// error handler
onerror(app)
// cors
const cors = require('koa2-cors')
app.use(cors())
// middlewares
app.use(bodyparser({
enableTypes: ['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
extension: 'ejs'
}))
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// error
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.statusCode || err.status || 500
ctx.body = err.message
ctx.app.emit('error', err, ctx)
}
})
// jwt
const jwt = require('koa-jwt')
app.use(jwt({
secret: app.keys.join()
}).unless({
path: [
/^\/$/,
/^\/sell$/,
/^\/buy$/,
/^\/pay$/,
/^\/user\/captcha$/,
/^\/user\/register$/,
/^\/user\/login$/,
/^\/user\/password$/
]
}))
// routes
const fs = require('fs')
fs.readdirSync(__dirname + '/routes').forEach(route => {
let api = require(`./routes/${route}`)
app.use(api.routes(), api.allowedMethods())
})
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
})
// mongodb
const mongo = require('./config/mongo')
mongo.connect()
// redis
const Redis = require('./config/redis')
app.context.$redis = new Redis()
module.exports = app
routes/goods.js 商品路由文件
const router = require('koa-router')()
const Goods = require('../models/goods')
const md5 = require('md5-node')
router.prefix('/goods')
/* 添加商品 */
router.post('/', async (ctx, next) => {
let token = ctx.request.header.authorization
let user = await ctx.$redis.get(token)
if (user && user._id) {
let data = ctx.request.body
data.code = md5(Date.now())
data.user = user.email
data.date = Date.now()
data.state = 0
let goods = await Goods.create(data)
if (goods && goods._id) {
return ctx.body = {
code: 0,
msg: 'success',
data: null
}
}
}
ctx.body = {
code: 1,
msg: 'failure',
data: null
}
})
/* 删除商品 */
router.delete('/:_id', async (ctx, next) => {
let token = ctx.request.header.authorization
let user = await ctx.$redis.get(token)
if (user && user._id) {
let _id = ctx.params._id
let result = await Goods.deleteOne({
_id: _id,
user: user.email
})
if (result && result.ok > 0 && result.deleteCount > 0) {
return ctx.body = {
code: 0,
msg: 'success',
data: null
}
}
}
ctx.body = {
code: 1,
msg: 'failure',
data: null
}
})
/* 修改商品 */
router.put('/:_id', async (ctx, next) => {
let token = ctx.request.header.authorization
let user = await ctx.$redis.get(token)
if (user && user._id) {
let _id = ctx.params._id
let data = ctx.request.body
let result = await User.updateOne({
_id: _id,
user: user.email
}, {
$set: data
}, {
runValidators: true
})
if (result && result.ok > 0 && result.nModified > 0) {
return ctx.body = {
code: 0,
msg: 'success',
data: null
}
}
}
ctx.body = {
code: 1,
msg: 'failure',
data: null
}
})
router.get('/', async (ctx, next) => {
let token = ctx.request.header.authorization
let user = await ctx.$redis.get(token)
if (user && user._id) {
let skip = parseInt(ctx.query.skip || 0)
let limit = parseInt(ctx.query.limit || 50)
let goods = await Goods.find({
user: user.email
}).skip(skip).limit(limit)
if (goods && goods.length > 0) {
return ctx.body = {
code: 0,
msg: 'success',
data: goods
}
}
}
ctx.body = {
code: 1,
msg: 'failure',
data: null
}
})
router.get('/:_id', async (ctx, next) => {
let token = ctx.request.header.authorization
let user = await ctx.$redis.get(token)
if (user && user._id) {
let _id = ctx.params._id
let goods = await Goods.findOne({
_id: _id,
user: user.email
})
if (goods && goods._id) {
return ctx.body = {
code: 0,
msg: 'success',
data: goods
}
}
}
ctx.body = {
code: 1,
msg: 'failure',
data: null
}
})
module.exports = router
models/goods.js 商品模型文件
const mongoose = require('mongoose')
const GoodsSchema = mongoose.Schema({
code: {
type: String,
required: [true, 'code cannot be null']
}, // 商品编号
name: {
type: String,
required: [true, 'name cannot be null']
}, // 名称
description: { type: String }, // 描述
type: {
type: String,
enum: ['one', 'many'],
default: 'one'
}, // 类型
content: {
type: [String],
required: [true, 'content cannot be null']
}, // 内容
price: {
type: Number,
required: [true, 'price cannot be null']
}, // 价格
count: {
type: Number,
default: 0
}, // 数量
date: {
type: Date,
default: Date.now
}, // 日期
state: {
type: Number,
default: 0
}, // 状态
user: { type: String, required: [true, 'user cannot be null'] } // 用户
})
const Goods = mongoose.model('Goods', GoodsSchema)
module.exports = Goods
config/index.js 系统配置文件
const fs = require('fs')
const config = {
license: fs.readFileSync(__dirname + '/license.txt'),
keys: ['I Love U'],
mongo: {
debug: true,
autoReconnect: true,
url: 'mongodb://localhost:27017/myapp'
},
mail: {
host: 'smtp.126.com',
port: 465,
secure: true,
secureConnection: true,
auth: {
user: '[email protected]',
pass: 'xxxxxx'
},
from: 'myapp'
},
alipay: {
appId: 'xxxxxx', // 必填
privateKey: fs.readFileSync(__dirname + '/private-key.pem', 'ascii'), // 必填
encryptKey: 'xxxxxx', // 必填
signType: 'RSA2', // 选填 默认
alipayPublicKey: fs.readFileSync(__dirname + '/public-key.pem', 'ascii'), // 选填
gateway: 'https://openapi.alipay.com/gateway.do', // 选填 默认
timeout: 5000, // 选填 默认
camelcase: true, // 选填 默认
notifyUrl: 'xxxxxx',
qrCodeTimeoutExpress: '5m'
}
}
module.exports = config
config/mongo.js 操作 MongoDB
// config/mongo.js
const config = require('./index').mongo
const mongoose = require('mongoose').set('debug', config.debug)
const url = config.url
const options = {
// autoReconnect: config.autoReconnect,
useNewUrlParser: true,
useUnifiedTopology: true
}
module.exports = {
connect: () => {
mongoose.connect(url, options)
let db = mongoose.connection
db.on('error', () => {
console.log('mongodb: error')
})
db.once('open', () => {
console.log('mongodb: open success')
})
}
}
config/redis.js 操作 Redis
const IORedis = require('ioredis')
class Redis extends Object {
constructor() {
super()
this.redis = new IORedis()
}
async get(cid) {
let data = await this.redis.get(`CACHE:${cid}`)
return JSON.parse(data)
}
async set(cid, cache, maxAge = 60 * 60 * 1000) {
try {
// Use redis set EX to automatically drop expired caches
await this.redis.set(`CACHE:${cid}`, JSON.stringify(cache), 'EX', maxAge / 1000)
} catch (e) {
console.log(e)
}
return cid
}
async destroy(cid) {
return await this.redis.del(`CACHE:${cid}`)
}
}
module.exports = Redis
config/mail.js 操作邮件
const config = require('./index').mail
const nodemailer = require('nodemailer')
const transporter = nodemailer.createTransport({
host: config.host,
port: config.port,
secure: config.secure,
secureConnection: config.secureConnection,
auth: {
user: config.auth.user,
pass: config.auth.pass
}
})
const test = (email) => {
let mail = {
from: config.from,
to: email,
subject: 'trace',
text: 'trace success'
}
return send(mail)
}
const send = (mail) => {
return new Promise((resolve, reject) => {
mail.from = config.from
transporter.sendMail(mail, (error, info) => {
if (error) {
return reject(error)
} else {
return resolve(info)
}
})
})
}
module.exports = {
test: test,
send: send
}
config/alipay.js 支付宝当面付
const config = require('./index').alipay
const AlipaySdk = require('alipay-sdk').default
const alipaySdk = new AlipaySdk({
appId: config.appId,
privateKey: config.privateKey,
encryptKey: config.encryptKey,
signType: config.signType,
alipayPublicKey: config.alipayPublicKey,
gateway: config.gateway,
timeout: config.timeout,
camelcase: config.camelcase
})
const pay = (outTradeNo, goods, count = 1) => {
return alipaySdk.exec('alipay.trade.precreate', {
notifyUrl: config.notifyUrl,
// app_auth_token: '',
bizContent: {
outTradeNo: outTradeNo,
// sellerId: '',
totalAmount: goods.price * count,
// discountableAmount: 0.01,
subject: goods.name,
body: goods.description,
storeId: goods.user,
qrCodeTimeoutExpress: config.qrCodeTimeoutExpress
}
})
}
const cancel = (outTradeNo, tradeNo) => {
return alipaySdk.exec('alipay.trade.cancel', {
bizContent: {
outTradeNo: outTradeNo,
tradeNo: tradeNo
}
})
}
const close = (outTradeNo, tradeNo, operatorId) => {
return alipaySdk.exec('alipay.trade.close', {
notifyUrl: '',
bizContent: {
tradeNo: tradeNo,
outTradeNo: outTradeNo,
operatorId: operatorId
}
})
}
const query = (outTradeNo, tradeNo, queryOptions) => {
return alipaySdk.exec('alipay.trade.query', {
bizContent: {
outTradeNo: outTradeNo,
tradeNo: tradeNo,
queryOptions: queryOptions
}
})
}
module.exports = {
pay: pay,
cancel: cancel,
close: close,
query: query
}
4. 小结
以上就是 node.js + koa + mongodb 项目的基本创建过程和项目核心代码了,并且使用了一些中间件来完成项目中所必须的一些操作,剩下的主要就是根据需求进行业务逻辑的开发。
koa2-cors // 解决跨域问题
koa-jwt // 进行 JWT 验证
jsonwebtoken // 进行 JWT 验证
md5-node // MD5 加密
mongoose // 操作 MongoDB 的 ORM 库
ioredis // 操作 Redis
nodemailer // 发送邮件
alipay-sdk // 支付宝官方 SDK