专栏首页一Li小麦持久化储存(一)

持久化储存(一)

十多年前,高一的体育老师说过一句很每个时刻都会有所回味的话:

年轻靠爆发力,老了以后靠持久力。

之前的示例项目具有最明显的是:没有一个很好的持久化储存数据的途径。

本文介绍的是fs储存,mysql和sequelize。

fs 储存

现在就来完整实践一个fs-db操作库。需求如下:

  • 读取
  • 写入
// 根据属性获取数据
const getDataByProp = (prop) => {
    return fs.readFile(file, (err, data) => {
        if (!err) {
            data = JSON.parse(data);
            return data[prop];
        } else {
            console.log('读取失败!')
        }
    })
}

// 设置属性值
const setDataByProp = (prop, value) => {
    fs.readFile(file, (err, data) => {
        if (!err) {
            data = JSON.parse(data);
            data[prop] = value;
            data = JSON.stringify(data);

            fs.writeFile(file, data, (_err) => {
                if (!_err) {
                    console.log('写入成功!')
                } else {
                    console.log('写入失败')
                }
            })
        } else {
            console.log('读取失败')
        }
    })
}

读取和写入都比较简单。再实现一个命令行版的,允许命令行查询。

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

rl.on("line", 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

用docker安装非常方便(Mac):

https://yeasy.gitbooks.io/docker_practice/install/mac.html

https://yeasy.gitbooks.io/docker_practice/container/run.html

基本原生操作

sql语句对前端来说,看这个就足够了:

https://www.runoob.com/sql/sql-tutorial.html

连接

安装mysql2,它提供了一套相当不错的ES7写法。

// 原生使用mysql
setTimeout(async () => {
    const mysql = require('mysql2/promise');
    const cfg = {
        host: 'localhost',
        user: 'root',
        password: '12345678',
        database: 'djtao'
    }
    const connection = await mysql.createConnection(cfg);

});
新建表
// sql语句
    const CREATE_SQL = `CREATE TABLE IF NOT EXISTS test (
    id INT NOT NULL AUTO_INCREMENT,
    message VARCHAR(45) NULL,
    PRIMARY KEY (id))`;  

    // 新建表
    let ret = await connection.execute(CREATE_SQL);
    console.log('create',ret)

打印出来了。发现新建了一个表

增加行

继续输入

// 插入表
        const INSERT_SQL = `INSERT INTO test(message) VALUES(?)`;
    ret = await connection.execute(INSERT_SQL,['hello']);
    console.log('insert',ret);

新增了一条数据。id作为主键,是自增的。因此不需要理他。

查询行

继续输入

// 查询表
    const SELECT_SQL = `SELECT * FROM test`;
    const [rows,fileds] = await connection.execute(SELECT_SQL);
    console.log('select',rows);
修改行
const UPDATE_SQL=`UPDATE test SET  message='hi' WHERE id=1`
    await connection.execute(UPDATE_SQL)

把id为1的message改为了hi

删除行
// 删除行
    const DEL_SQL=`DELETE FROM test WHERE message='hello'`;
    await connection.execute(DEL_SQL);

所有hello的都被删除了。

SQL中间件:Sequelize

Sequelize是一款基于Nodejs功能强大的异步ORM框架。说白了就是对sql语句的封装。 同时支持PostgreSQL, MySQL, SQLite and MSSQL多种数据库,很适合作为Nodejs后端数据库的存储接口,为快速开发Nodejs应用奠定扎实、安全的基础。 既然Nodejs的强项在于异步,没有理由不找一个强大的支持异步的数据库框架,与之配合。

http://docs.sequelizejs.com/

// sequelize.js
(async ()=>{
    const Sequelize=require('sequelize');

    // 建立连接
    const sequelize=new Sequelize('djtao',`root`,`12345678`,{
        host:'localhost',
        dialect:'mysql', //方言
        operatorsAliases:false //操作符别名,不允许
    });

    // 6定义模型
    const Fruit =sequelize.define('Fruit',{
        name:{type:Sequelize.STRING(20),allowNull:false},
        price:{type:Sequelize.FLOAT,allowNull:false},
        stock:{type:Sequelize.INTEGER,defaultValue:0}
    });


    // 同步数据库
    let ret=await Fruit.sync();
    console.log('sync',ret)
})()

发现生成了一个数据表:

包括模型定义的三个字段,还有id和其它2个时间戳。

// 如果不想请求 
const Fruit = sequelize.define("Fruit", {}, {
  timestamps: false
});

定义模型后,就不用建表了。

增加

插入数据呢?一行代码搞定:

await Fruit.create({name:'苹果',price:3.5});

就像操作对象一样操作数据库。

简单查询

如果你要查询全部:

ret = await Fruit.findAll()
    console.log('findAll',JSON.stringify(ret))

如果你想查询价格0-2.5的商品:

const Op=Sequelize.Op;
ret =await Fruit.FindAll({
  where:{price:{[Op.lt]:0,[Op.gt]:2.5}}
})
修改
await Fruit.update({price:'8.5'},{where:{name:'苹果'}})
删除
// 方式1
Fruit.findOne({ where: { id: 1 } }).then(r => r.destroy());
// 方式2
Fruit.destroy({ where: { id: 1 } }).then(r => console.log(r));

删除数据库字段:通常不会操作已有的数据库。如果需要真的删除,则需要强制同步:

Fruit.sync({force: true})
校验
price: {
    validate: {
            isFloat: { msg: "价格字段请输入数字" },
            min: { args: [0], msg: "价格字段必须大于0" } }
        }, 
    stock: {
            validate: {isNumeric: { msg: "库存字段请输入数字" }
        } 
 }
// 添加类级别方法
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)}`);
    });

电商系统数据库设计

以下是一个标准电商系统的ER图(实体关系与类模型),它反映出一对一或一对多映射关系

在这张图里,用户处于中心地位:一个以用户为中心的订单,最基本的要素包括六大类:

  • 用户表(users)字段包括地址,名字等。
  • 商品(products):标题,价格,图片,描述,用户
  • 购物车(carts):哪个用户的购物车(外键)
  • 订单(orders):哪个用户下的单(外键)
  • 购物车单个明细(cartItems):关联有什么商品(外键),属于哪个购物车,商品数量数量
  • 用户的订单明细(ohterIstems)哪个订单(外键),有什么商品,数量。

models模块

项目更目录下新建一个models模块,存放6个js文件对应六张表。建表不需要考虑外键。

// users.js
const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const User = sequelize.define('user', {
    id : {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    name: Sequelize.STRING,
    email: Sequelize.STRING
});

module.exports = User;

// products.js
const Sequelize = require('sequelize');
const sequelize = require('../util/database');
const Product = sequelize.define('product', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    title: {
        type: Sequelize.STRING,
        allowNull: false
    },
    price: {
        type: Sequelize.DOUBLE,
        allowNull: false
    },
    imageUrl: {
        type: Sequelize.STRING,
        allowNull: false
    },
    description: {
        type: Sequelize.STRING,
        allowNull: false
    }
});

module.exports = Product;

// cart.js
const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const Cart = sequelize.define('cart', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        primaryKey: true,
        allowNull: false
    }
});

module.exports = Cart;

// order.js
const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const Order = sequelize.define('order', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    }
});

module.exports = Order;


// cart-item.js
const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const CartItem = sequelize.define('cartItem', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    quantity: Sequelize.INTEGER
});

module.exports = CartItem;

// order-item.js
const Sequelize = require('sequelize');
const sequelize = require('../util/database');

const OrderItem = sequelize.define('orderItem', {
    id: {
        type: Sequelize.INTEGER,
        autoIncrement: true,
        allowNull: false,
        primaryKey: true
    },
    quantity: Sequelize.INTEGER
});

module.exports = OrderItem;

通用模块(utils)

数据库配置

把数据库配置独立出来:

const Sequelize = require('sequelize');
const env = require('dotenv')

env.config();
const sequelize = new Sequelize(
    process.env.DB_Database, 
    process.env.DB_USER, 
    process.env.DB_PWD, {
    dialect: 'mysql',
    host: 'localhost',
    operatorsAliases: false
});

module.exports = sequelize;

初始化(init.js)

数据建立需要初始化,那么可以写一个init函数:

以下展示了数据表初始化的过程.

连接
setTimeout(async ()=>{
    // 定义打印函数
    const log=(text,data)=>{
        console.log(`=======${text}========`);
        console.log(JSON.stringify(data,null,'\t'));
        console.log(`======================`);
    };

    const sequelize=require('./util/database');

    // 定义1对N对模型关系
    const Products=require('./models/products');
    const Users=require('./models/users');
    const Cart=require('./models/cart');
    const CartItem=require('./models/cart-item');
    const OrderItem=require('./models/order-item');
    const Order=require('./models/order');

    // 同步
    await sequelize.sync({false:true});
})
一对多:
// 产品表属于用户表
    Products.belongsTo(Users,{
        constraints:true,
        onDelete:'CASCADE'//阻止删除
    });
    Users.hasMany(Products);

    // 首要是创建用户,pk就是primarykey,对应的就是id
    let user = await Users.findByPk(1);
    if(!user){
        user=await Users.create({
            name:'djao',
            email:'dangjingtao@163.com'
        })
    }

    //添加商品
    let product=await user.createProduct({
        title:'iphone x',
        price:999,
        imageUrl:'iphonex.jpg',
        description:'爱疯叉商品描述'
    });

    log('Product',product);

打印结果如下

多对多
// 产品表属于用户表
    Products.belongsTo(Users, {
        constraints: true,
        onDelete: 'CASCADE'//阻止删除
    });
    Users.hasMany(Products);

    // 首要是创建用户,pk就是primarykey,对应的就是id
    let user = await Users.findByPk(1);
    if (!user) {
        user = await Users.create({
            name: 'djao',
            email: 'dangjingtao@163.com'
        })
    }

    //添加商品
    let product = await user.createProduct({
        title: 'iphone x',
        price: 999,
        imageUrl: 'iphonex.jpg',
        description: '爱疯叉商品描述'
    });

那么对应对关系(外键)就建立起来了。

以上。

本文分享自微信公众号 - 一Li小麦(gh_c88159ec1309),作者:一li小麦

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 基于koa定制属于自己的企业级框架

    笔者前公司用的是think.js作为后端框架,初次使用,深感业务场景的傻瓜式。它就是一个基于koa二次开发。一个显著的特点就是可以在对应文件夹下直接书写接口。比...

    一粒小麦
  • 那些年初级前后端一起撕过的逼

    一个项目一开始总是出于还不错愿景,但做着做着,就越来越乱了。万丈高楼平地起,有些基础的问题解决好,后面改需求就不会那么痛苦了。

    一粒小麦
  • nodejs微信公众号开发

    网上关于node开发公众号的资料相当缺乏,本文旨在以node的视角对公众号开发做一个阐述。

    一粒小麦
  • 边做算法边学go语言之LeetCode1160:拼写单词

    题目位置:https://leetcode-cn.com/problems/find-words-that-can-be-formed-by-character...

    机智的程序员小熊
  • 设计模式在游戏开发中的应用之命令模式

    设计模式在一些大型的软件系统中非常常用,用来处理复杂的结构和逻辑。游戏其实也是一个软件系统,也会有庞大的系统,复杂的逻辑关系,对设计模式的合理使用可以帮助我们更...

    用户1428723
  • Porting Winforms Applications to Mono and xacc.ide

    Guide: Porting Winforms Applications,内文提到如何将NClass移植到 Linux Mono 上的过程。 整个移植的过程相当...

    张善友
  • leetcode: 68. Text Justification [✗]

    JNingWei
  • 当creator遇上protobufjs|激情

    我们上一篇讲解了通过修改源码的方案,让protobufjs能正常运行在jsb环境上。这个方案适合将protobufjs源码直接放到项目中,而我们使用npm来管理...

    张晓衡
  • 1016. 部分A+B (15)

    正整数A的“DA(为1位整数)部分”定义为由A中所有DA组成的新整数PA。例如:给定A = 3862767,DA = 6,则A的“6部分”PA是66,因为A中有...

    AI那点小事
  • 大数据时代别说社交媒体没用,只是你没用对!

    6月16日消息,在大数据营销大行其道的背景下,国内领先的跨境整合数字营销服务专家深诺互动(SinoInteractive)相关负责人有些不同的看法,他们认为海外...

    灯塔大数据

扫码关注云+社区

领取腾讯云代金券