7、一粒云二次封装与迁移 – 数据导出程序编写(服务端)

目录 未分类

1、数据发送程序创建

首先创建项目目录

mkdir yliyunDMS && cd yliyunDMS && npm ini

拉取库

npm i figlet chalk mysql express body-parser str-random minimist -d
const args = minimist(process.argv.slice(2));

const PORT = !args.p ? 1115 : args.p;
const TABLENAME = !args.tn ? 'yliyun' : args.tn;
const HOST = !args.h ? 'localhost' : args.h;
const USER = !args.u ? 'root' : args.u;
const PASSWD = !args.P ? '' : args.P;
const DFSDG = args.dg;
const DFSDP = args.dp;
const MYSQLPORT = !args.mP ? 3306 : args.mP;
const SIGN = !args.s ? strRandom(6,{letters: "qwertyuiopasdfghjklzxcvbnm1234567890"}) : args.s;
const HELP = args.help;

设置输入参数,可以使用xxx –p xxx等类型输入参数。

PORT 服务端暴露给客户端的端口,默认1115

TABLENAME 链接数据库的表名,默认 yliyun

HOST 主机地址,默认localhost

USER 数据库连接用户,默认root

PASSWD 数据库连接密码。默认为空

DFSDG DFS组名称,不能为空,指定一个FastDFS组即可,一般情况下只有一个group1

DFSDP 数据存储目录,不能为空,格式采用–dp M00=/data_01@M01=/data_02,DMS会自动将目录作为地址提供给接收端,接收端直接使用链接下载即可,这样的好处是只需要有数据库备份就可以进行数据导出,完全脱离网络文件系统和Nginx文件系统模块。

MYSQLPORT Mysql端口,默认3306

SIGN 通讯码,默认随机

HELP 帮助

if(HELP !== undefined || !DFSDG || !DFSDP){
    console.log("\n");
    console.log("yliYunDms --p " + chalk.yellow("<端口>") + " --tn " + chalk.yellow("<数据库表名>") + " --mP " + chalk.yellow("<数据库端口>") + " --h " + chalk.yellow("<主机地址>") + " --u " + chalk.yellow("<用户名>") + " --P " + chalk.yellow("<密码>") + " --s " + chalk.yellow("<客户端通讯密钥>") + " --dg" + chalk.yellow("<DFS组名>") + " --dp" + chalk.yellow("<DFS数据目录>") + " --help " + chalk.yellow("<帮助>"));
    console.log("\n");
    console.log("           --p     <端口>              服务端暴露给客户端的端口          默认 -> 1115");
    console.log("           --tn    <数据库表>          链接数据库的表名                  默认 -> yliyun");
    console.log("           --mp    <数据库端口>        链接数据库的端口                  默认 -> 3306");
    console.log("           --h     <主机地址>          数据库地址                        默认 -> localhost");
    console.log("           --u     <用户名>            数据库连接用户                    默认 -> root");
    console.log("           --P     <密码>              数据库连接密码                    默认 -> 空");
    console.log("           --s     <客户端通讯密钥>    客户端必须持有此令牌才允许通讯    默认 -> 启动时随机生成");
    console.log("           --dg    <DFS组名>           DFS组名称                         默认 -> 不能为空");
    console.log("           --dp    <DFS数据目录>       数据存储目录                      默认 -> 不能为空");
    console.log("           --help  <帮助>              使用帮助                          默认 -> 输入时才生效");
    console.log("\n");
    console.log("如果您无法获取到数据库的密码,请在my.cnf配置文件最后添加一行skip-grant-tables并重新启动Mysql服务");
    console.log("即可免密登录。");
    console.log("\n");
    console.log("如下是一条fastDFS特有的URl,可以按/拆分成为5部分,其中第一部分<group1>就是该文件系统中一个storage的组号");
    console.log("\n");
    console.log("            " + chalk.yellow("group1/M00/00/08/wKgB-2G8ZqeEJyHSAAAAAOEwDgU843.MP4"));
    console.log("\n");
    console.log("第二部分M00表示存放的基路径代号,这个代号与fastDfs storage配置文件中指定的storage路径存在顺序关系");
    console.log("例如我们按照下面这个案例,既可以得出 /data_01 = M00@/data_02 = M01");
    console.log("\n");
    console.log("            " + chalk.yellow("store_path_count=2"));
    console.log("            " + chalk.yellow("store_path0=/data_01"));
    console.log("            " + chalk.yellow("store_path0=/data_02"));
    console.log("\n");
    console.log("那么--dp参数填写为 --dp M00=/data_01@M01=/data_02  即可,这样yliYun DMS就会自动将目录作为地址提供给yliYun DMC访问");
    console.log("并将fastDfs的URl路由到正确的文件位置进行下载。");
    console.log("您一般只需要指定一个<group1>即可,因为fastDFS多<group>实行的是多副本策略");
    console.log("那么--dg参数填写为 --dp group1  即可,当然如果您指定了其它组名,请更改为对应的组名");
    console.log("\n");
    return false;
}

不管指定了什么参数,只要用户使用了–help参数就弹出帮助信息。

MacBook-Pro yliYunDMS % node app.js 


yliYunDms --p <端口> --tn <数据库表名> --mP <数据库端口> --h <主机地址> --u <用户名> --P <密码> --s <客户端通讯密钥> --dg<DFS组名> --dp<DFS数据目录> --help <帮助>


           --p     <端口>              服务端暴露给客户端的端口          默认 -> 1115
           --tn    <数据库表>          链接数据库的表名                  默认 -> yliyun
           --mp    <数据库端口>        链接数据库的端口                  默认 -> 3306
           --h     <主机地址>          数据库地址                        默认 -> localhost
           --u     <用户名>            数据库连接用户                    默认 -> root
           --P     <密码>              数据库连接密码                    默认 -> 空
           --s     <客户端通讯密钥>    客户端必须持有此令牌才允许通讯    默认 -> 启动时随机生成
           --dg    <DFS组名>           DFS组名称                         默认 -> 不能为空
           --dp    <DFS数据目录>       数据存储目录                      默认 -> 不能为空
           --help  <帮助>              使用帮助                          默认 -> 输入时才生效


如果您无法获取到数据库的密码,请在my.cnf配置文件最后添加一行skip-grant-tables并重新启动Mysql服务
即可免密登录。


如下是一条fastDFS特有的URl,可以按/拆分成为5部分,其中第一部分<group1>就是该文件系统中一个storage的组号


            group1/M00/00/08/wKgB-2G8ZqeEJyHSAAAAAOEwDgU843.MP4


第二部分M00表示存放的基路径代号,这个代号与fastDfs storage配置文件中指定的storage路径存在顺序关系
例如我们按照下面这个案例,既可以得出 /data_01 = M00@/data_02 = M01


            store_path_count=2
            store_path0=/data_01
            store_path0=/data_02


那么--dp参数填写为 --dp M00=/data_01@M01=/data_02  即可,这样yliYun DMS就会自动将目录作为地址提供给yliYun DMC访问
并将fastDfs的URl路由到正确的文件位置进行下载。
您一般只需要指定一个<group1>即可,因为fastDFS多<group>实行的是多副本策略
那么--dg参数填写为 --dp group1  即可,当然如果您指定了其它组名,请更改为对应的组名

接下去创建web服务器,展示UI,并对一些参数的正确性进行校验,映射文件系统静态路径。

const app = express();
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

const logo = figlet.textSync("        yliYun    DMS");
console.log("\n");
console.log(chalk.yellow(logo));
console.log("\n");
console.log("            配合yliYun DMC,将YliYun系统中的目录结构与文件");
console.log("                           完整的迁移至外部");
console.log("                             --help帮助");
console.log("\n");

const connection = mysql.createConnection({
    host     : HOST,
    user     : USER,
    port     : MYSQLPORT,
    password : PASSWD,
    database : TABLENAME
});
connection.connect();

console.log("\n");
console.log('#################################' + chalk.yellow.bold('数据库') + '################################');
console.log("         1.地址:                                    " + chalk.green(HOST));
console.log("         2.用户名:                                  " + chalk.green(USER));
console.log("         3.密码:                                  " + chalk.green(PASSWD));
console.log("         4.端口:                                    " + chalk.green(MYSQLPORT));
console.log("         5.连接表:                                  " + chalk.green(TABLENAME));

yms.dbQuery(connection,"show databases",(res) => {

    if(!res.status){
        console.log("         5.数据库:                                  " + chalk.red('[NO]'));
        console.log("\n");
        console.log("错误: 连接Mysql数据库失败,请在" + chalk.bold.red('my.cnf') + "配置文件");
        console.log("末尾添加" + chalk.bold.red('skip-grant-tables') + "并重启Mysql服务");
        return false;
    }

    if(res.status){
        console.log("         5.数据库:                                  " + chalk.green('[OK]'));
        yms.dbQuery(connection,"select count(*) from personal_file where folder=1",(res) => {

            if(!res.status){
                console.log("         6.目录记录:                                " + chalk.red('[NO]'));
                console.log("\n\n");
                console.log("错误: 没有获取到目录记录,请确认" + chalk.bold.red('personal_file'));
                console.log("表是否存在");
                return false;
            }
            
            if(res.status){
                console.log("         6.目录记录:                                " + chalk.green(res.data[0]["count(*)"]));
                yms.dbQuery(connection,"select count(*) from fs_file;",(res) => {

                    if(!res.status){
                        console.log("         7.文件记录:                                " + chalk.red('[NO]'));
                        console.log("\n\n");
                        console.log("错误: 没有获取到文件记录,请确认" + chalk.bold.red('fs_file'));
                        console.log("表是否存在");
                        return false;
                    }
                    
                    if(res.status){
                        console.log("         7.文件记录:                                " + chalk.green(res.data[0]["count(*)"]));

                        //console.log(chalk.black.bold.bgWhite(' 二.启动                                            -'));
                        console.log("\n");
                        console.log('##################################' + chalk.yellow.bold('服务') + '#################################');
                        const server = app.listen(PORT, () => {
                            let host = server.address().address;
                            let port = server.address().port;
                            console.log("         1.迁移服务:                                " + chalk.green('[OK]'));
                            console.log("         2.地址:                                    " + chalk.green(host + ":" + port));
                            console.log("         3.密钥:                                    " + chalk.green(SIGN));
                            console.log("\n");
                            console.log('##################################' + chalk.yellow.bold('存储') + '#################################');
                            let testDfsPaths = DFSDP.split('@');
                            testDfsPaths.map((item,i) => {
                                if(!item){
                                    console.log("\n");
                                    console.log("错误: --dp 参数无法解析,请按照格式填写");
                                    console.log("\n");
                                    process.exit();
                                }else{
                                    let dfs = item.split('=');
                                    if(!dfs[0] || !dfs[1]){
                                        console.log("\n");
                                        console.log("错误: --dp 参数无法解析,请按照格式填写");
                                        console.log("\n");
                                        process.exit();
                                    }
                                }
                            })
                            console.log("         组:                                        " + chalk.green(DFSDG));
                            let dfsPaths = DFSDP.split('@');
                            dfsPaths.map((item,i) => {
                                let dfs = item.split('=');
                                app.use("/" + DFSDG + "/" + dfs[0],express.static(dfs[1]));
                                console.log("         " + dfs[0] + ":                                       " + chalk.green(dfs[1]));
                            })
                            routes.initialization(app,connection,SIGN,HOST);
                        })
                    }
                });
            }
        });
    }

})

在头部导入路由程序和公共函数库,并进行创建

const routes = require('./routes')
const yms = require('./script');
mkdir routes script && touch routes/index.js && touch script/index.js

在公共函数库内创建数据查询方法和数字校验方法,并进行暴露。

var script = {};

script.dbQuery = (db,sqlStr,callback) => {
    db && db.query(sqlStr,(err, result) => {
        if(err){
            callback && callback({status: false, err: err})
        }
        if(result){
            callback && callback({status: true, data: result})
        }
    })
}

script.isRealNum = (val) => {
    if(val === "" || val ==null){
        return false;
    }
   if(!isNaN(val)){
        return true; 
    }else{ 
        return false; 
    } 
}

module.exports = script;

编辑routes/index.js,创建一个路由初始化方法,传入express对象和数据库对象还有通讯码,后续就可以将路由写在此方法中。

const script = require('../script');

var routes = {};

routes.initialization = (app,db,key) => {
        ......
})

2、公共数据

1、文件夹

创建一个/public/folder/number路由,方法为post,主要获取公共区域文件夹的数量,用来接收程序进行进度的展示和判断。

/**公共区域文件夹数量 */
app.post("/public/folder/number",(req,res) => {
    let sign = req.body.sign;
    if(sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    script.dbQuery(db,"SELECT count(*) from public_file where folder=1 and del_status=0;",(data) => {
        res.json(data);
    });
});

 

创建一个/public/folder/info路由,方法为post,主要获取公共区域文件夹中的parent_ids和file_name字段,依靠这2个字段就可以还原出文件夹的路径。

/**公共区域文件夹信息 */
app.post("/public/folder/info",(req,res) => {
   let sign = req.body.sign;
   if(sign !== key){
       res.json({status: false, message: "密钥不正确!"});
       return false;
   }
   script.dbQuery(db,"SELECT parent_ids,file_name from public_file where folder=1 and del_status=0;",(data) => {
        res.json(data);
   });
});

 

创建一个/public/folder/path路由,方法为post,主要解析parent_ids数据,用来将-3-4-这类路径转换为/测试/一级目录这样的真实路径。

/**公共区域文件夹路径反推 */
app.post("/public/folder/path",(req,res) => {
    let pathData = req.body.path;
    let sign = req.body.sign;
    if(!pathData || sign !== key){
         res.json({status: false, message: "密钥不正确!"});
         return false;
    }
        let publicPath = [];
        let fielIds = pathData.split("-");
        fielIds.shift();fielIds.pop();
        let sqlYield = undefined;
        function * sql(paths){
            if(paths.length === 0){
                res.json({status: true, data: publicPath});
            }
            if(paths.length !== 0){
                let data = paths.shift();
                script.dbQuery(db,"SELECT file_name from public_file where file_id=" + data + " and folder=1 and del_status=0;",(dbRes) => {
                    if(dbRes.status){
                        publicPath.push(dbRes.data[0]["file_name"]);
                        sqlYield = sql(paths);
                        sqlYield.next();
                    }
                    if(!dbRes.status){
                        res.json({status: false, message: "路径获取时发生错误!"});
                    }
                });
                publicPath
        }
        yield;
        sqlYield = sql(paths);
        sqlYield.next();
    }
    sqlYield = sql(fielIds);
    sqlYield.next();
});

2、文件

创建一个/public/files/number路由,方法为post,主要获取公共区域文件的数量,用来接收程序进行进度的展示和判断。

/**公共区域文件数量 */
app.post("/public/files/number",(req,res) => {
    let sign = req.body.sign;
    if(sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    script.dbQuery(db,"select count(*) from public_file where folder = 0 and del_status = 0;",(data) => {
        res.json(data);
    });
})

 

创建一个/public/files/info路由,方法为post,主要获取公共区域文件中的fs_file_id,parent_ids和file_name字段,依靠这2个字段就可以还原出文件的路径。

/**公共区域文件信息 */
app.post("/public/files/info",(req,res) => {
    let limit = req.body.limit;
    let offset = req.body.offset;
    let sign = req.body.sign;
    if(!script.isRealNum(limit) || !script.isRealNum(offset) || sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    script.dbQuery(db,"select fs_file_id,parent_ids,file_name from public_file where folder = 0 and del_status = 0 LIMIT " + limit + " OFFSET " + offset + ";",(data) => {
        res.json(data);
    });
});

创建一个/public/files/path路由,方法为post,主要解析parent_ids数据,用来将-3-4-这类路径转换为/测试/一级目录/xxxxx.doc这样的真实路径。

/**公共区域文件路径反推 */
app.post("/public/files/path",(req,res) => {
    let pathData = req.body.path;
    let sign = req.body.sign;
    if(!pathData || sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    let publicPath = [];
    let fielIds = pathData.split("-");
    fielIds.shift();fielIds.pop();
    let sqlYield = undefined;
    function * sql(paths){
        if(paths.length === 0){
            res.json({status: true, data: publicPath});
        }
        if(paths.length !== 0){
            let data = paths.shift();
            script.dbQuery(db,"SELECT file_name from public_file where file_id=" + data + " and folder=1 and del_status=0;",(dbRes) => {
                if(dbRes.status){
                    publicPath.push(dbRes.data[0]["file_name"]);
                    sqlYield = sql(paths);
                    sqlYield.next();
                }
                if(!dbRes.status){
                    res.json({status: false, message: "路径获取时发生错误!"});
                }
            });
            publicPath
        }
        yield;
        sqlYield = sql(paths);
        sqlYield.next();
    }
    sqlYield = sql(fielIds);
    sqlYield.next();
});

创建一个/public/files/dfs/path路由,方法为post,获取到文件id后前往fs_file表查询文件在文件系统中的路径。

/**公共区域文件文件系统路径获取 */
app.post("/public/files/dfs/path",(req,res) => {
    let id = req.body.id;
    let sign = req.body.sign;
    if(!script.isRealNum(id) || sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    script.dbQuery(db,"select file_name,file_md5 from fs_file where fs_file_id = " + id + ";",(data) => {
        res.json(data);
    });
});

 

 

2、个人数据

1、文件夹

创建一个/personal/folder/number路由,方法为post,主要获取个人区域文件夹的数量,用来接收程序进行进度的展示和判断。

 /**个人区域文件夹数量 */
app.post("/personal/folder/number",(req,res) => {
    let sign = req.body.sign;
    if(sign !== key){
       res.json({status: false, message: "密钥不正确!"});
       return false;
    }
    script.dbQuery(db,"SELECT count(*) from personal_file where folder=1 and del_status=0;",(data) => {
       res.json(data);
    });
});

 

创建一个/personal/folder/info路由,方法为post,主要获取公共区域文件夹中的parent_ids和file_name字段,依靠这2个字段就可以还原出文件夹的路径。

/**个人区域文件夹信息 */
app.post("/personal/folder/info",(req,res) => {
    let sign = req.body.sign;
    if(sign !== key){
         res.json({status: false, message: "密钥不正确!"});
         return false;
     }
     script.dbQuery(db,"SELECT parent_ids,file_name,creater_name from personal_file where folder=1 and del_status=0;",(data) => {
         res.json(data);
     });
});

 

创建一个/personal/folder/path路由,方法为post,主要解析parent_ids数据,用来将-3-4-这类路径转换为/测试/一级目录这样的真实路径。

/**个人区域文件夹路径反推 */
app.post("/personal/folder/path",(req,res) => {
    let pathData = req.body.path;
    let createrName = req.body.createrName;
    let sign = req.body.sign;
    if(!pathData || !createrName || sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    let publicPath = [createrName];
    let fielIds = pathData.split("-");
    fielIds.shift();fielIds.pop();
    let sqlYield = undefined;
    function * sql(paths){
        if(paths.length === 0){
            res.json({status: true, data: publicPath});
        }
        if(paths.length !== 0){
            let data = paths.shift();
            script.dbQuery(db,"SELECT file_name from personal_file where file_id=" + data + " and folder=1 and del_status=0;",(dbRes) => {
                if(dbRes.status){
                    publicPath.push(dbRes.data[0]["file_name"]);
                    sqlYield = sql(paths);
                    sqlYield.next();
                }
                if(!dbRes.status){
                    res.json({status: false, message: "路径获取时发生错误!"});
                }
            });
            publicPath
        }
        yield;
        sqlYield = sql(paths);
        sqlYield.next();
    }
    sqlYield = sql(fielIds);
    sqlYield.next();
});

 

2、文件

创建一个/personal/files/number路由,方法为post,获取个人区域文件的数量,用来接收程序进行进度的展示和判断

/**个人区域文件数量 */
app.post("/personal/files/number",(req,res) => {
    let sign = req.body.sign;
    if(sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    script.dbQuery(db,"select count(*) from personal_file where folder = 0 and del_status = 0;",(data) => {
        res.json(data);
    });
})

 

创建一个/personal/files/info路由,方法为post,主要获取个人区域文件中的fs_file_id,parent_ids和file_name字段,依靠这3个字段就可以还原出文件的路径。

/**个人区域文件信息 */
app.post("/personal/files/info",(req,res) => {
    let limit = req.body.limit;
    let offset = req.body.offset;
    let sign = req.body.sign;
    if(!script.isRealNum(limit) || !script.isRealNum(offset) || sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    script.dbQuery(db,"select fs_file_id,parent_ids,file_name from personal_file where folder = 0 and del_status = 0 LIMIT " + limit + " OFFSET " + offset + ";",(data) => {
        res.json(data);
    });
});

创建一个/personal/files/path路由,方法为post,主要解析parent_ids数据,用来将-3-4-这类路径转换为用户目录/测试/一级目录/xxxxx.doc这样的真实路径。

/**个人区域文件路径反推 */
app.post("/personal/files/path",(req,res) => {
    let pathData = req.body.path;
    let sign = req.body.sign;
    if(!pathData || sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    let publicPath = [];
    let fielIds = pathData.split("-");
    fielIds.shift();fielIds.pop();
    let sqlYield = undefined;
    function * sql(paths){
        if(paths.length === 0){
            res.json({status: true, data: publicPath});
        }
        if(paths.length !== 0){
            let data = paths.shift();
            script.dbQuery(db,"SELECT file_name from personal_file where file_id=" + data + " and folder=1 and del_status=0;",(dbRes) => {
                if(dbRes.status){
                    publicPath.push(dbRes.data[0]["file_name"]);
                    sqlYield = sql(paths);
                    sqlYield.next();
                }
                if(!dbRes.status){
                    res.json({status: false, message: "路径获取时发生错误!"});
                }
            });
            publicPath
        }
        yield;
        sqlYield = sql(paths);
        sqlYield.next();
    }
    sqlYield = sql(fielIds);
    sqlYield.next();
});

创建一个/personal/files/dfs/path路由,方法为post,获取到文件id后前往fs_file表查询文件在文件系统中的路径。

/**个人区域文件文件系统路径获取 */
app.post("/personal/files/dfs/path",(req,res) => {
    let id = req.body.id;
    if(!script.isRealNum(id) || sign !== key){
        res.json({status: false, message: "密钥不正确!"});
        return false;
    }
    script.dbQuery(db,"select file_name,file_md5 from fs_file where fs_file_id = " + id + ";",(data) => {
        res.json(data);
    });
});