回顧
- http協(xié)議
- websocket
- socket.io
課堂目標
- 掌握node.js中實現(xiàn)持久化的多種方法
- 掌握mysql下載、安裝和配置
- 掌握node.js中原生mysql驅動模塊的應用
- 掌握node.js中的ORM模塊Sequelize的應用
- 掌握Sequelize的應用案例
node.js中實現(xiàn)持久化的多種方法
- 文件系統(tǒng) fs
- 數(shù)據(jù)庫
關系型數(shù)據(jù)庫-mysql
文檔型數(shù)據(jù)庫-mongodb
鍵值對數(shù)據(jù)庫-redis
文件系統(tǒng)數(shù)據(jù)庫
// fsdb.js
// 實現(xiàn)一個文件系統(tǒng)讀寫數(shù)據(jù)庫
const fs = require("fs");
function get(key) {
fs.readFile("./db.json", (err, data) => {
const json = JSON.parse(data);
console.log(json[key]);
});
}
function set(key, value) {
fs.readFile("./db.json", (err, data) => {
// 可能是空文件,則設置為空對象
const json = data ? JSON.parse(data) : {};
json[key] = value; // 設置值
// 重新寫入文件
fs.writeFile("./db.json", JSON.stringify(json), err => {
if (err) {
console.log(err);
}
console.log("寫入成功!");
});
});
}
// 命令行接口部分
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on("line", function (input) {
const [op, key, value] = input.split(" ");
if (op === 'get') {
get(key)
} else if (op === 'set') {
set(key, value)
} else if (op === 'quit') {
rl.close();
} else {
console.log('沒有該操作');
}
});
rl.on("close", function () {
console.log("程序結束");
process.exit(0);
});
MySQL安裝、配置
菜鳥教程 http://www.runoob.com/mysql/mysql-tutorial.html
node.js原生驅動
- 安裝mysql模塊: npm i mysql --save
- mysql模塊基本使用
// mysql.js
const mysql = require("mysql");
// 連接配置
const cfg = {
host: "localhost",
user: "root",
password: "example", // 修改為你的密碼
database: "kaikeba" // 請確保數(shù)據(jù)庫存在
};
// 創(chuàng)建連接對象
const conn = mysql.createConnection(cfg);
// 連接
conn.connect(err => {
if (err) {
throw err;
} else {
console.log("連接成功!");
}
});
// 查詢 conn.query()
// 創(chuàng)建表
const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test (
id INT NOT NULL AUTO_INCREMENT,
message VARCHAR(45) NULL,
PRIMARY KEY (id))`;
const INSERT_SQL = `INSERT INTO test(message) VALUES(?)`;
const SELECT_SQL = `SELECT * FROM test`;
conn.query(CREATE_SQL, err => {
if (err) {
throw err;
}
// 插入數(shù)據(jù)
conn.query(INSERT_SQL, "hello,world", (err, result) => {
if (err) {
throw err;
}
console.log(result);
conn.query(SELECT_SQL, (err, results) => {
console.log(results);
conn.end(); // 若query語句有嵌套,則end需在此執(zhí)行
})
});
});
ES2017寫法
// mysql2.js
(async () => {
// get the client
const mysql = require('mysql2/promise');
// 連接配置
const cfg = {
host: "localhost",
user: "root",
password: "example", // 修改為你的密碼
database: "kaikeba" // 請確保數(shù)據(jù)庫存在
};
// create the connection
const connection = await mysql.createConnection(cfg);
// 查詢 conn.query()
// 創(chuàng)建表
const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test (
id INT NOT NULL AUTO_INCREMENT,
message VARCHAR(45) NULL,
PRIMARY KEY (id))`;
const INSERT_SQL = `INSERT INTO test(message) VALUES(?)`;
const SELECT_SQL = `SELECT * FROM test`;
// query database
let ret = await connection.execute(CREATE_SQL);
console.log('create:', ret)
ret = await connection.execute(INSERT_SQL, ['abc']);
console.log('insert:', ret)
const [rows, fields] = await connection.execute(SELECT_SQL);
console.log('select:', rows)
})()
Node.js ORM - Sequelize
- 概述:基于Promise的ORM(Object Relation Mapping),是一種數(shù)據(jù)庫中間件 支持多種數(shù)據(jù)庫、事務、關聯(lián)等
中間件是介于應用系統(tǒng)和系統(tǒng)軟件之間的一類軟件,它使用系統(tǒng)軟件所提供的基礎服務(功
能),銜接網(wǎng)絡上應用系統(tǒng)的各個部分或不同的應用,能夠達到資源共享、功能共享的目
的。目前,它并沒有很嚴格的定義,但是普遍接受IDC的定義:中間件是一種獨立的系統(tǒng)軟
件服務程序,分布式應用軟件借助這種軟件在不同的技術之間共享資源,中間件位于客戶機
服務器的操作系統(tǒng)之上,管理計算資源和網(wǎng)絡通信。從這個意義上可以用一個等式來表示中
間件:中間件=平臺+通信,這也就限定了只有用于分布式系統(tǒng)中才能叫中間件,同時也把它
與支撐軟件和實用軟件區(qū)分開來。
- 安裝: npm i sequelize mysql2 -S
- 基本使用:
(async () => {
const Sequelize = require("sequelize");
// 建立連接
const sequelize = new Sequelize("kaikeba", "root", "example", {
host: "localhost",
dialect: "mysql",
operatorsAliases: false // 仍可通過傳入 operators map 至 operatorsAliases 的方式來使用字符串運算符,但會返回棄用警告
});
// 定義模型
const Fruit = sequelize.define("Fruit", {
name: { type: Sequelize.STRING(20), allowNull: false },
price: { type: Sequelize.FLOAT, allowNull: false },
stock: { type: Sequelize.INTEGER, defaultValue: 0 }
});
// 同步數(shù)據(jù)庫,force: true則會刪除已存在表
let ret = await Fruit.sync()
console.log('sync', ret)
ret = await Fruit.create({
name: "香蕉",
price: 3.5
})
console.log('create', ret)
ret = await Fruit.findAll()
await Fruit.update(
{ price: 4 },
{ where: { name: '香蕉' } }
)
console.log('findAll', JSON.stringify(ret))
const Op = Sequelize.Op;
ret = await Fruit.findAll({
// where: { price: { [Op.lt]:4 }, stock: { [Op.gte]: 100 } }
where: { price: { [Op.lt]: 4, [Op.gt]: 2 } }
})
console.log('findAll', JSON.stringify(ret, '', '\t'))
})()
- 強制同步:創(chuàng)建表之前先刪除已存在的表
Fruit.sync({force: true})
- 避免自動生成時間戳字段
const Fruit = sequelize.define("Fruit", {}, {
timestamps: false
});
- 指定表名: freezeTableName: true 或 tableName:'xxx'
設置前者則以modelName作為表名;設置后者則按其值作為表名。蛇形命名 underscored: true,默認駝峰命名
- UUID-主鍵
id: {
type: Sequelize.DataTypes.UUID,
defaultValue: Sequelize.DataTypes.UUIDV1,
primaryKey: true
},
- Getters & Setters:可用于定義偽屬性或映射到數(shù)據(jù)庫字段的保護屬性
// 定義為屬性的一部分
name: {
type: Sequelize.STRING,
allowNull: false,
get() {
const fname = this.getDataValue("name");
const price = this.getDataValue("price");
const stock = this.getDataValue("stock");
return `${fname}(價格:¥${price} 庫存:${stock}kg)`;
}
}
// 定義為模型選項
// options中
{
getterMethods: {
amount(){
return this.getDataValue("stock") + "kg";
}
},
setterMethods: {
amount(val){
const idx = val.indexOf('kg');
const v = val.slice(0, idx);
this.setDataValue('stock', v);
}
}
}
// 通過模型實例觸發(fā)setterMethods
Fruit.findAll().then(fruits => {
console.log(JSON.stringify(fruits));
// 修改amount,觸發(fā)setterMethods
fruits[0].amount = '150kg';
fruits[0].save();
});
校驗:可以通過校驗功能驗證模型字段格式、內容,校驗會在 create 、 update 和 save 時自動運行
price: {
validate: {
isFloat: { msg: "價格字段請輸入數(shù)字" },
min: { args: [0], msg: "價格字段必須大于0" }
}
},
stock: {
validate: {
isNumeric: { msg: "庫存字段請輸入數(shù)字" }
}
}
- 模型擴展:可添加模型實例方法或類方法擴展模型
// 添加類級別方法
Fruit.classify = function (name) {
const tropicFruits = ['香蕉', '芒果', '椰子']; // 熱帶水果
return tropicFruits.includes(name) ? '熱帶水果' : '其他水果';
};
// 添加實例級別方法
Fruit.prototype.totalPrice = function (count) {
return (this.price * count).toFixed(2);
};
// 使用類方法
['香蕉', '草莓'].forEach(f => console.log(f + '是' + Fruit.classify(f)));
// 使用實例方法
Fruit.findAll().then(fruits => {
const [f1] = fruits;
console.log(`買5kg${f1.name}需要¥${f1.totalPrice(5)}`);
});
- 數(shù)據(jù)查詢
// 通過id查詢(不支持了)
Fruit.findById(1).then(fruit => {
// fruit是一個Fruit實例,若沒有則為null
console.log(fruit.get());
});
// 通過屬性查詢
Fruit.findOne({ where: { name: "香蕉" } }).then(fruit => {
// fruit是首個匹配項,若沒有則為null
console.log(fruit.get());
});
// 指定查詢字段
Fruit.findOne({ attributes: ['name'] }).then(fruit => {
// fruit是首個匹配項,若沒有則為null
console.log(fruit.get());
});
// 獲取數(shù)據(jù)和總條數(shù)
Fruit.findAndCountAll().then(result => {
console.log(result.count);
console.log(result.rows.length);
});
// 查詢操作符
const Op = Sequelize.Op;
Fruit.findAll({
// where: { price: { [Op.lt]:4 }, stock: { [Op.gte]: 100 } }
where: { price: { [Op.lt]: 4, [Op.gt]: 2 } }
}).then(fruits => {
console.log(fruits.length);
});
// 或語句
Fruit.findAll({
// where: { [Op.or]:[{price: { [Op.lt]:4 }}, {stock: { [Op.gte]: 100 }}]
}
where: { price: { [Op.or]: [{ [Op.gt]: 3 }, { [Op.lt]: 2 }] } }
}).then(fruits => {
console.log(fruits[0].get());
});
// 分頁
Fruit.findAll({
offset: 0,
limit: 2,
})
// 排序
Fruit.findAll({
order: [['price', 'DESC']],
})
// 聚合
Fruit.max("price").then(max => {
console.log("max", max);
});
Fruit.sum("price").then(sum => {
console.log("sum", sum);
});
- 更新
Fruit.findById(1).then(fruit => {
// 方式1
fruit.price = 4;
fruit.save().then(() => console.log('update!!!!'));
});
// 方式2
Fruit.update({ price: 4 }, { where: { id: 1 } }).then(r => {
console.log(r);
console.log('update!!!!')
})
- 刪除
// 方式1
Fruit.findOne({ where: { id: 1 } }).then(r => r.destroy());
// 方式2
Fruit.destroy({ where: { id: 1 } }).then(r => console.log(r));
實體關系圖和與域模型 ERD

model.jpg
- 初始化數(shù)據(jù)庫
// 初始化數(shù)據(jù)庫
const sequelize = require('./util/database');
const Product = require('./models/product');
const User = require('./models/user');
const Cart = require('./models/cart');
const CartItem = require('./models/cart-item');
const Order = require('./models/order');
const OrderItem = require('./models/order-item');
Product.belongsTo(User, {
constraints: true,
onDelete: 'CASCADE'
});
User.hasMany(Product);
User.hasOne(Cart);
Cart.belongsTo(User);
Cart.belongsToMany(Product, {
through: CartItem
});
Product.belongsToMany(Cart, {
through: CartItem
});
Order.belongsTo(User);
User.hasMany(Order);
Order.belongsToMany(Product, {
through: OrderItem
});
Product.belongsToMany(Order, {
through: OrderItem
});
- 同步數(shù)據(jù)
// 同步數(shù)據(jù)
sequelize.sync().then(
async result => {
let user = await User.findByPk(1)
if (!user) {
user = await User.create({
name: 'Sourav',
email: 'sourav.dey9@gmail.com'
})
await user.createCart();
}
app.listen(3000, () => console.log("Listening to port 3000"));
})
- 中間件鑒權
app.use(async (ctx, next) => {
const user = await User.findByPk(1)
ctx.user = user;
await next();
});
- 功能實現(xiàn)
const router = require('koa-router')()
/**
* 查詢產(chǎn)品
*/
router.get('/admin/products', async (ctx, next) => {
// const products = await ctx.user.getProducts()
const products = await Product.findAll()
ctx.body = { prods: products }
})
/**
* 創(chuàng)建產(chǎn)品
*/
router.post('/admin/product', async ctx => {
const body = ctx.request.body
const res = await ctx.user.createProduct(body)
ctx.body = { success: true }
})
/**
* 刪除產(chǎn)品
*/
router.delete('/admin/product/:id', async (ctx, next) => {
const id = ctx.params.id
const res = await Product.destroy({
where: {
id
}
})
ctx.body = { success: true }
})
/**
* 查詢購物車
*/
router.get('/cart', async ctx => {
const cart = await ctx.user.getCart()
const products = await cart.getProducts()
ctx.body = { products }
})
/**
* 添加購物車
*/
router.post('/cart', async ctx => {
const { body } = ctx.request
const prodId = body.id
let newQty = 1
const cart = await ctx.user.getCart()
const products = await cart.getProducts({
where: {
id: prodId
}
})
let product
if (products.length > 0) {
product = products[0]
}
if (product) {
const oldQty = product.cartItem.quantity
newQty = oldQty + 1
} else {
product = await Product.findByPk(prodId)
}
await cart.addProduct(product, {
through: {
quantity: newQty
}
})
ctx.body = { success: true }
})
/**
* 添加訂單
*/
router.post('/orders', async ctx => {
const cart = await ctx.user.getCart()
const products = await cart.getProducts()
const order = await ctx.user.createOrder()
const result = await order.addProduct(
products.map(p => {
p.orderItem = {
quantity: p.cartItem.quantity
}
return p
})
)
await cart.setProducts(null)
ctx.body = { success: true }
})
/**
* 刪除購物車
*/
router.delete('/cartItem/:id', async ctx => {
const id = ctx.params.id
const cart = await ctx.user.getCart()
const products = await cart.getProducts({
where: { id }
})
const product = products[0]
await product.cartItem.destroy()
ctx.body = { success: true }
})
/**
* 查詢訂單
*/
router.get('/orders', async ctx => {
const orders = await ctx.user.getOrders({
include: ['products'], order:
[['id', 'DESC']]
})
ctx.body = { orders }
})
app.use(router.routes())
Restful服務
實踐指南 http://www.ruanyifeng.com/blog/2014/05/restful_api.html
原理 http://www.ruanyifeng.com/blog/2011/09/restful.html
TODO List范例 https://github.com/BayliSade/TodoList
關于新版本的警告問題https://segmentfault.com/a/1190000011583806
資料來源:開課吧