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); }); });