Browse Source

指标库 no.1 up

laiguoran 7 years ago
parent
commit
df62172f1b

+ 192 - 0
app/base/base_service.js

@@ -0,0 +1,192 @@
+'use strict';
+
+/**
+ * 业务逻辑基类
+ *
+ * @author CaiAoLin
+ * @date 2017/10/11
+ * @version
+ */
+
+const Service = require('egg').Service;
+// sql拼装器
+const SqlBuilder = require('../lib/sql_builder');
+class BaseService extends Service {
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局context
+     * @return {void}
+     */
+    constructor(ctx) {
+        super(ctx);
+        this.db = this.app.mysql;
+        this.cache = this.app.redis;
+        this.transaction = null;
+        this.sqlBuilder = null;
+    }
+
+    /**
+     * 设置表名
+     *
+     * @param {String} table - 表名
+     * @return {void}
+     */
+    set tableName(table) {
+        this._table = this.app.config.tablePrefix + table;
+    }
+
+    /**
+     * 获取表名
+     *
+     * @return {String} - 返回表名
+     */
+    get tableName() {
+        return this._table;
+    }
+
+    /**
+     * 初始化sqlBuilder
+     *
+     * @return {void}
+     */
+    initSqlBuilder() {
+        if (this.sqlBuilder === null) {
+            this.sqlBuilder = new SqlBuilder();
+        }
+    }
+
+    /**
+     * 根据id查找数据
+     *
+     * @param {Number} id - 数据库中的id
+     * @return {Object} - 返回单条数据
+     */
+    async getDataById(id) {
+        return await this.db.get(this.tableName, { id });
+    }
+
+    /**
+     * 根据条件查找单条数据
+     *
+     * @param {Object} condition - 筛选条件
+     * @return {Object} - 返回单条数据
+     */
+    async getDataByCondition(condition) {
+        return await this.db.get(this.tableName, condition);
+    }
+
+    /**
+     * 根据条件查找单条数据
+     *
+     * @param {Object} condition - 筛选条件
+     * @return {Array} - 返回数据
+     */
+    async getAllDataByCondition(condition) {
+        return await this.db.select(this.tableName, condition);
+    }
+
+    /**
+     * 根据id删除数据
+     *
+     * @param {Number} id - 数据库中的id
+     * @return {boolean} - 返回是否删除成功
+     */
+    async deleteById(id) {
+        const result = await this.db.delete(this.tableName, { id });
+        return result.affectedRows > 0;
+    }
+
+    /**
+     * 获取总数
+     *
+     * @param {Object} option - 过滤条件(参考select方法中的condition)
+     * @return {Number} - 返回查找的总数
+     */
+    async count(option = {}) {
+        const result = await this.db.count(this.tableName, option);
+        return result;
+    }
+
+    /**
+     * 更新数据
+     *
+     * @param {Object} data - 需要更新的数据
+     * @param {Object} condition - 更新的条件筛选
+     * @return {Boolean} - 返回更新结果
+     */
+    async update(data, condition) {
+        if (Object.keys(data).length <= 0 || Object.keys(condition).length <= 0) {
+            return false;
+        }
+        const sqlParam = [];
+        const param = [];
+        for (const key in data) {
+            param.push(key + ' = ?');
+            sqlParam.push(data[key]);
+        }
+        const paramString = param.join(',');
+
+        const where = [];
+        for (const key in condition) {
+            where.push(key + ' = ?');
+            sqlParam.push(condition[key]);
+        }
+        const whereString = where.join(' AND ');
+
+        const sql = 'UPDATE ' + this.tableName + ' SET ' + paramString + ' WHERE ' + whereString;
+        const result = await this.db.query(sql, sqlParam);
+        return result.affectedRows > 0;
+    }
+
+    /**
+     * 获取分页数据(SqlBuilder版本)
+     *
+     * @return {Array} - 返回分页数据
+     */
+    async getListWithBuilder() {
+
+        // 由于egg-mysql不提供like、不等于操作,所以需要自己组sql
+        if (this.sqlBuilder === null) {
+            return [];
+        }
+
+        // 分页相关
+        this.sqlBuilder.limit = this.app.config.pageSize;
+        this.sqlBuilder.offset = this.app.config.pageSize * (this.ctx.page - 1);
+
+        // 数据筛选
+        if (this.ctx.sort !== undefined && this.ctx.sort.length > 0) {
+            this.sqlBuilder.orderBy = [this.ctx.sort];
+        }
+
+        const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+        const list = await this.db.query(sql, sqlParam);
+
+        return list;
+    }
+
+    /**
+     * 获取分页数据
+     *
+     * @param {Object} condition - 搜索条件
+     * @return {Array} - 返回分页数据
+     */
+    async getList(condition) {
+        const page = this.ctx.page;
+
+        // 分页设置
+        condition.limit = condition.limit === undefined ? this.app.config.pageSize : condition.limit;
+        condition.offset = condition.offset === undefined ? this.app.config.pageSize * (page - 1) : condition.offset;
+
+        if (this.ctx.sort !== undefined && this.ctx.sort.length > 0) {
+            condition.orders = [this.ctx.sort];
+        }
+        const list = await this.db.select(this.tableName, condition);
+
+        return list;
+    }
+
+
+}
+module.exports = BaseService;

+ 26 - 0
app/const/lib.js

@@ -0,0 +1,26 @@
+'use strict';
+
+/**
+ * 指标源相关常量
+ *
+ * @author EllisRan.
+ * @date 2018/4/20
+ * @version
+ */
+
+//指标源状态
+const status = {
+    pend: 1,
+    enter: 2
+};
+const statusString = [];
+statusString[status.pend] = '待处理';
+statusString[status.enter] = '已入库';
+
+const quotaLibDir = 'app/public/quotalib/';
+
+module.exports = {
+    status,
+    statusString,
+    quotaLibDir
+}

+ 67 - 4
app/controller/lib_controller.js

@@ -7,12 +7,14 @@
  * @data 2018/4/19
  * @version
  */
-
+const fs = require('fs');
+const path = require('path');
+const awaitWriteStream = require('await-stream-ready').write;
+const sendToWormhole = require('stream-wormhole');
+const libConst = require('../const/lib');
 module.exports = app => {
     class LibController extends app.BaseController {
 
-
-
         /**
          * 指标库页面
          *
@@ -20,7 +22,68 @@ module.exports = app => {
          * @return {void}
          */
         async index (ctx) {
-            await this.layout('lib/index.ejs', {}, '');
+            let status = ctx.query.status;
+            const libList = await ctx.service.quotaLib.getList(status);
+
+            //获取指标源状态数量
+            const libStatus = await ctx.service.quotaLib.getStatusNum();
+            const renderData = {
+                status,
+                libList,
+                libConst,
+                libStatus
+            };
+            await this.layout('lib/index.ejs', renderData, 'lib/modal.ejs');
+        }
+
+        /**
+         * 上传指标源
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async upload(ctx) {
+            const parts = ctx.multipart({ autoFields: true });
+            let addData = {};
+            let stream;
+            while ((stream = await parts()) != null) {
+                const filename = stream.filename.toLowerCase();
+                if(filename === ''){
+                    throw '请上传文件';
+                }
+                const fileInfo = path.parse(filename);
+                let create_time = Date.parse( new Date())/1000;
+                let filepath = libConst.quotaLibDir + create_time + fileInfo.ext;
+
+                addData = {
+                    name: fileInfo.name,
+                    path: filepath,
+                    create_time: create_time
+                };
+                const target = path.join(filepath);
+                const writeStream = fs.createWriteStream(target);
+                try {
+                    await awaitWriteStream(stream.pipe(writeStream));
+                } catch (err) {
+                    await sendToWormhole(stream);
+                    throw err;
+                }
+            }
+            if(addData !== {}){
+
+                try {
+                    //插入数据到数据库
+                    const result = await ctx.service.quotaLib.add(addData);
+                    if (!result) {
+                        throw '新增指标源数据失败';
+                    }
+                    ctx.redirect('/lib');
+                } catch (err) {
+                    throw err;
+                }
+            }else{
+                throw '请上传文件';
+            }
         }
     }
 

+ 246 - 0
app/lib/sql_builder.js

@@ -0,0 +1,246 @@
+'use strict';
+
+/**
+ * sql语句构建器
+ *
+ * @author CaiAoLin
+ * @date 2017/10/11
+ * @version
+ */
+class SqlBuilder {
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        this.resetCondition();
+    }
+
+    /**
+     * 设置andWhere数据
+     *
+     * @param {String} field - where中的字段名称
+     * @param {Object} data - where中的value部分
+     * @return {void}
+     */
+    setAndWhere(field, data) {
+        if (Object.keys(data).length <= 0) {
+            return;
+        }
+        this.andWhere.push({ field, data });
+    }
+
+    /**
+     * 更新字段设置
+     *
+     * @param {String} field - set的字段
+     * @param {Object} data - set中的字段
+     * @return {void}
+     */
+    setUpdateData(field, data) {
+        if (Object.keys(data).length <= 0) {
+            return;
+        }
+        this.setData.push({ field, data });
+    }
+
+    /**
+     * 重置条件
+     *
+     * @return {void}
+     */
+    resetCondition() {
+        this.andWhere = [];
+        this.orWhere = [];
+        this.columns = [];
+        this.limit = -1;
+        this.offset = -1;
+        this.orderBy = [];
+        this.setData = [];
+
+        this.sql = '';
+        this.sqlParam = [];
+        this.type = 'select';
+    }
+
+    /**
+     * 类型设置
+     *
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    _typeBuild(tableName) {
+        switch (this.type) {
+            case 'select':
+                this.sql = this.columns.length === 0 ? 'SELECT * FROM ??' : 'SELECT ?? FROM ??';
+                this.sqlParam = this.columns.length === 0 ? [tableName] : [this.columns, tableName];
+                break;
+            case 'update':
+                this.sql = 'UPDATE ?? SET ';
+                this.sqlParam = [tableName];
+                break;
+            case 'delete':
+                this.sql = 'DELETE FROM ??';
+                this.sqlParam = [tableName];
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * 设置update数据
+     *
+     * @return {void}
+     */
+    _setDataBuild() {
+        if (this.setData.length <= 0) {
+            return;
+        }
+        const setDataArr = [];
+        for (const set of this.setData) {
+            if (set.data.literal) {
+                const values = set.data.value instanceof Array ? set.data.value : [set.data.value];
+                setDataArr.push(' ?? = ' + set.data.literal + '(' + values.join(',') + ')');
+                this.sqlParam.push(set.field);
+            } else {
+                const tmp = set.data.selfOperate !== undefined ?
+                    ' ?? = IF(IsNull(??), 0, ??) ' + set.data.selfOperate + ' ' + set.data.value : ' ?? = ' + set.data.value;
+                setDataArr.push(tmp);
+                // 如果是自身操作则压多一次字段进数组
+                if (set.data.selfOperate !== undefined) {
+                    this.sqlParam.push(set.field);
+                    this.sqlParam.push(set.field);
+                }
+                this.sqlParam.push(set.field);
+            }
+        }
+
+        const setString = setDataArr.join(',');
+        this.sql += setString;
+    }
+
+    /**
+     * andWhere设置
+     *
+     * @return {void}
+     */
+    _andWhereBuild() {
+        if (this.andWhere.length <= 0) {
+            return;
+        }
+
+        const whereArr = [];
+        for (const where of this.andWhere) {
+            if (where.data.operate === 'in') {
+                // in操作
+                const valueLength = where.data.value instanceof Array ? where.data.value.length : 1;
+                // 生成参数集
+                const inArr = [];
+                for (let i = 0; i < valueLength; i++) {
+                    inArr.push('?');
+                }
+                const inData = inArr.join(',');
+                whereArr.push(' ?? IN (' + inData + ')');
+                this.sqlParam.push(where.field);
+                // 赋值参数
+                this.sqlParam.push.apply(this.sqlParam, where.data.value);
+            } else {
+                // 普通操作
+                whereArr.push(' ?? ' + where.data.operate + ' ' + where.data.value);
+                this.sqlParam.push(where.field);
+            }
+
+        }
+        const whereString = whereArr.join(' AND ');
+        this.sql += this.sql.indexOf('WHERE') > 0 ? whereString : ' WHERE ' + whereString;
+    }
+
+    /**
+     * orWhere设置
+     *
+     * @return {void}
+     */
+    _orWhereBuild() {
+        if (this.orWhere.length <= 0) {
+            return;
+        }
+
+        const whereArr = [];
+        for (const where in this.orWhere) {
+            whereArr.push(' ?? ' + where.data.operate + ' ' + where.data.value);
+            this.sqlParam.push(where.field);
+        }
+        const whereString = whereArr.join(' OR ');
+        // 如果前面已经有设置过WHERE则不需要再重复添加
+        this.sql += this.sql.indexOf('WHERE') > 0 ? whereString : ' WHERE ' + whereString;
+    }
+
+    /**
+     * limit设置
+     *
+     * @return {void}
+     */
+    _limitBuild() {
+        if (typeof this.limit !== 'number' || this.limit <= 0) {
+            return;
+        }
+
+        this.offset = parseInt(this.offset);
+        this.offset = isNaN(this.offset) || this.offset < 0 ? 0 : this.offset;
+        const limitString = this.offset >= 0 ? this.offset + ',' + this.limit : this.limit;
+        this.sql += ' LIMIT ' + limitString;
+    }
+
+    /**
+     * orderby 设置
+     *
+     * @return {void}
+     */
+    _orderByBuild() {
+        if (this.orderBy.length <= 0) {
+            return;
+        }
+
+        const orderArr = [];
+        for (const index in this.orderBy) {
+            orderArr.push(' ?? ' + this.orderBy[index][1]);
+            this.sqlParam.push(this.orderBy[index][0]);
+        }
+        const orderByString = orderArr.join(',');
+        this.sql += ' ORDER BY ' + orderByString;
+    }
+
+    /**
+     * 构建sql
+     *
+     * @param {String} tableName - 表名
+     * @param {String} type - 类型
+     * @return {Array} - 返回数组,第一个元素为sql语句,第二个元素为sql中问号部分的param
+     */
+    build(tableName, type = 'select') {
+        this.type = type;
+        this._typeBuild(tableName);
+        if (this.sql === '') {
+            throw '类型错误';
+        }
+
+        this._setDataBuild();
+        this._andWhereBuild();
+        this._orWhereBuild();
+
+        this._limitBuild();
+        this._orderByBuild();
+
+        const sql = this.sql;
+        const sqlParam = this.sqlParam;
+        // 重置数据
+        this.resetCondition();
+
+        return [sql, sqlParam];
+    }
+
+}
+module.exports = SqlBuilder;

File diff suppressed because it is too large
+ 1578 - 0
app/public/js/jquery/jquery.validate.js


+ 84 - 0
app/service/quotaLib.js

@@ -0,0 +1,84 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author EllisRan.
+ * @date 2018/4/19
+ * @version
+ */
+const libConst = require('../const/lib');
+module.exports = app => {
+
+    class QuotaLib extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'quota_lib';
+        }
+
+        /**
+         * 获取指标源列表
+         *
+         * @param {Object} status - 指标源状态
+         * @return {Array} - 返回列表数据
+         */
+        async getList(status = 0) {
+
+            this.initSqlBuilder();
+
+            if(status !== 0) {
+                this.sqlBuilder.setAndWhere('status', {
+                    value: status,
+                    operate: '=',
+                });
+            }
+            this.sqlBuilder.orderBy = [['id', 'desc']];
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取指标源各状态数量
+         *
+         * @return {Array} - 返回数量列表数据
+         */
+        async getStatusNum() {
+            const pend = await this.db.count(this.tableName, { status: libConst.status.pend });
+            const enter = await this.db.count(this.tableName, { status: libConst.status.enter });
+            const data = {
+                pend,
+                enter
+            };
+            return data;
+        }
+
+        /**
+         * 新增指标源
+         *
+         * @param {Object} postData - 文件上传过来的数据
+         * @return {Boolean} - 返回新增结果
+         */
+        async add(postData) {
+            const insertData = {
+                filename: postData.name,
+                status: libConst.status.pend,
+                filepath: postData.path,
+                create_time: postData.create_time,
+                enter_time: ''
+            };
+            const operate = await this.db.insert(this.tableName, insertData);
+            return operate;
+        }
+
+    }
+
+    return QuotaLib;
+}

+ 20 - 13
app/view/lib/index.ejs

@@ -5,12 +5,22 @@
                 <div class="btn-group">
                     <ul class="nav nav-pills m-0">
                         <li class="nav-item">
-                            <a class="nav-link" href="#">全部</a>
+                            <a class="nav-link <% if(status === undefined) { %>active<% } %>" href="/lib">全部</a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="#">待处理 <span class="badge badge-secondary">4</span></a>
+                            <% if(parseInt(status) === libConst.status.pend) { %>
+                            <a class="nav-link active" href="/lib?status=<%= libConst.status.pend %>"><%= libConst.statusString[libConst.status.pend] %> <span class="badge badge-light"><%= libStatus.pend %></span></a>
+                            <% } else { %>
+                            <a class="nav-link" href="/lib?status=<%= libConst.status.pend %>"><%= libConst.statusString[libConst.status.pend] %> <span class="badge badge-secondary"><%= libStatus.pend %></span></a>
+                            <% } %>
+                        </li>
+                        <li class="nav-item">
+                            <% if(parseInt(status) === libConst.status.enter) { %>
+                            <a class="nav-link active" href="/lib?status=<%= libConst.status.enter %>"><%= libConst.statusString[libConst.status.enter] %> <span class="badge badge-light"><%= libStatus.enter %></span></a>
+                            <% } else { %>
+                            <a class="nav-link" href="/lib?status=<%= libConst.status.enter %>"><%= libConst.statusString[libConst.status.enter] %> <span class="badge badge-secondary"><%= libStatus.enter %></span></a>
+                            <% } %>
                         </li>
-                        <li class="nav-item"><a class="nav-link active" href="#">已入库 <span class="badge badge-light">4</span></a></li>
                     </ul>
                 </div>
             </div>
@@ -29,18 +39,15 @@
                         <th class="">导入时间</th>
                         <th class=""></th>
                     </tr>
-                    <!--待处理-->
-                    <tr>
-                        <td><a href="my-Library-detail.html">XXX标段 2018-03-14 17:12:23</a></td>
-                        <td>2018-03-14 17:36:10 </td>
-                        <td><a href="my-Library-detail.html" class="btn btn-sm btn-outline-primary">填写参数</a> </td>
-                    </tr>
-                    <!--已入库-->
+                    <% if(libList.length > 0) { %>
+                    <% libList.forEach(function(lib) { %>
                     <tr>
-                        <td><a href="my-Library-detail.html">YYY标段 2018-03-14 17:12:23</a></td>
-                        <td>2018-03-14 17:36:10 </td>
-                        <td>2018-03-14 17:36:10 入库</td>
+                        <td><a href="/lib/detail/<%= lib.id %>"><%= lib.filename %></a></td>
+                        <td><%= lib.create_time > 0 ? moment(lib.create_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '-' %> </td>
+                        <td><% if(lib.status === 1) { %><a href="/lib/detail/<%= lib.id %>" class="btn btn-sm btn-outline-primary">填写参数</a> <% } else { %><%= lib.enter_time > 0 ? moment(lib.enter_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '-' %> 入库<% } %></td>
                     </tr>
+                    <% }) %>
+                    <% } %>
                 </table>
             </div>
         </div>

+ 31 - 0
app/view/lib/modal.ejs

@@ -0,0 +1,31 @@
+<!-- 导入指标源 -->
+<div id="upload" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">导入指标源</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+            </div>
+            <form method="POST" action="/lib/upload?_csrf=<%= ctx.csrf %>" enctype="multipart/form-data" onsubmit="return checkfileupload();">
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label for="exampleFormControlFile1">上传指标源文件</label>
+                        <div class="form-control"><input class="form-control-file" id="exampleFormControlFile1" name="file" type="file"></div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button class="btn btn-primary" type="submit">确认导入</button>
+                    <button class="btn btn-secondary" type="button" data-dismiss="modal" aria-hidden="true">关闭</button>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+<script>
+    function checkfileupload() {
+        if($('#exampleFormControlFile1').val() == ''){
+            alert('请选择上传文件');
+            return false;
+        }
+    }
+</script>

+ 46 - 0
config/config.qa.js

@@ -0,0 +1,46 @@
+'use strict';
+/**
+ * 开发环境相关配置
+ *
+ * @author CaiAoLin
+ * @date 2017/8/29
+ * @version
+ */
+
+module.exports = appInfo => {
+    const config = {};
+    // 数据库配置
+    config.mysql = {
+        client: {
+            // host
+            host: '192.168.1.139',
+            // 端口号
+            port: '3306',
+            // 用户名
+            user: 'root',
+            // 密码
+            password: 'root',
+            // 数据库名
+            database: 'index_sys',
+        },
+        // 是否加载到 app 上,默认开启
+        app: true,
+        // 是否加载到 agent 上,默认关闭
+        agent: false,
+    };
+    // 表名前缀
+    config.tablePrefix = 'is_';
+
+    // redis设置
+    config.redis = {
+        client: {
+            host: '192.168.1.139',
+            port: '6379',
+            password: 'test',
+            db: '0',
+        },
+        agent: true,
+    };
+
+    return config;
+};

+ 29 - 0
test/app/lib/sql_builder.test.js

@@ -0,0 +1,29 @@
+/**
+ * sql拼接器单元测试
+ *
+ * @author CaiAoLin
+ * @date 2017/10/20
+ * @version
+ */
+
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+const SqlBuilder = require('../../../app/lib/sql_builder');
+
+describe('test/app/lib/sql_builder.test.js', () => {
+    it('set normal sql', function* () {
+        const sqlBuilder = new SqlBuilder();
+        sqlBuilder.columns = ['id'];
+        sqlBuilder.limit = 10;
+        sqlBuilder.offset = 1;
+        sqlBuilder.orderBy = [['id', 'DESC']];
+
+        const [sql, param] = sqlBuilder.build('table');
+        const finalSql = app.mysql.format(sql, param);
+        // 最后组合后应该获得的数据
+        const matchSql = 'SELECT `id` FROM `table` LIMIT 1,10 ORDER BY  `id` DESC';
+        assert(finalSql === matchSql);
+    });
+
+});

+ 25 - 0
test/app/service/lib.test.js

@@ -0,0 +1,25 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author EllisRan.
+ * @date 2018/4/20
+ * @version
+ */
+const { app, assert } = require('egg-mock/bootstrap');
+const libConst = require('../../../app/const/lib');
+
+describe('test/app/service/lib.test.js', () => {
+    it('add sql', function* () {
+        const ctx = app.mockContext();
+        const data = {
+            name: 'test',
+            path: 'app/public/quotalib/1524216653.sql',
+            create_time: '1524216653'
+        };
+        const libInfo = yield ctx.service.quotaLib.add(data);
+        assert(libInfo === true);
+    });
+
+});