UNPKG

open-rest-helper-rest

Version:

open-rest 的 helper 插件,用来实现 CRUD 的标准操作

353 lines (310 loc) 10.5 kB
const _ = require("lodash"); const mysql = require("mysql"); const NUMBER_TYPES = ["INTEGER", "FLOAT"]; // 处理关联包含 // 返回 // [Model1, Model2] // 或者 undefined const modelInclude = (params, includes) => { if (!includes) return undefined; if (!_.isString(params.includes)) return undefined; const ret = _.filter(params.includes.split(","), x => includes[x]); if (ret.length === 0) return undefined; // 这里之所以要用 _.clone 是为了防止修改了原始了配置信息,从而导致不同请求之间的干扰 return _.map(ret, x => _.clone(includes[x])); }; // 处理分页参数 // 返回 { // limit: xxx, // offset: xxx // } const defaultPageParams = { maxResults: 10, maxStartIndex: 10000, maxResultsLimit: 1000 }; const pageParams = (pagination, params) => { const _pagination = pagination || defaultPageParams; const startIndex = Math.max(+params.startIndex || 0, 0); const maxResults = Math.max(+params.maxResults || +_pagination.maxResults, 0); return { offset: Math.min(startIndex, _pagination.maxStartIndex), limit: Math.min(maxResults, _pagination.maxResultsLimit) }; }; // 处理排序参数 const sort = (params, conf) => { const value = params.sort; if (!conf) return undefined; if (!(value || conf.default)) return undefined; if (!value) return [[conf.default, conf.defaultDirection || "ASC"]]; const isDesc = value[0] === "-"; const direction = isDesc ? "DESC" : "ASC"; const order = isDesc ? value.substring(1) : value; // 如果请求的排序方式不允许,则返回null if (!conf.allow || !_.includes(conf.allow, order)) return undefined; return [[order, direction]]; }; // findOptFilter 的处理 const findOptFilter = (params, name, where, col = name) => { let value; if (!params) return; if (!_.isObject(params)) return; // 处理 where 的等于 if (_.isString(params[name])) { value = params[name].trim(); // 特殊处理null值 if (value === ".null.") value = null; if (!where[col]) where[col] = {}; where[col].$eq = value; } if (_.isNumber(params[name])) { if (!where[col]) where[col] = {}; where[col].$eq = params[name]; } // 处理where in if (_.isString(params[`${name}s`])) { if (!where[col]) where[col] = {}; where[col].$in = params[`${name}s`].trim().split(","); } // 处理where not in if (_.isString(params[`${name}s!`])) { if (!where[col]) where[col] = {}; where[col].$not = params[`${name}s!`].trim().split(","); } // 处理不等于的判断 if (_.isString(params[`${name}!`])) { value = params[`${name}!`].trim(); // 特殊处理null值 if (value === ".null.") value = null; if (!where[col]) where[col] = {}; where[col].$ne = value; } // 处理like if (_.isString(params[`${name}_like`])) { value = params[`${name}_like`].trim().replace(/\*/g, "%"); if (!where[col]) where[col] = {}; where[col].$like = value; } // 处理likes [like or] if (_.isString(params[`${name}_likes`])) { const likes = params[`${name}_likes`].trim().split(","); if (!where[col]) where[col] = {}; where[col].$or = likes.map(x => { value = x.trim().replace(/\*/g, "%"); return { $like: value }; }); } // 处理notLike if (_.isString(params[`${name}_notLike`])) { value = params[`${name}_notLike`].trim().replace(/\*/g, "%"); if (!where[col]) where[col] = {}; where[col].$notLike = value; } // 处理大于,小于, 大于等于,小于等于的判断 _.each(["gt", "gte", "lt", "lte"], x => { const c = `${name}_${x}`; if (!_.isString(params[c]) && !_.isNumber(params[c])) return; value = _.isString(params[c]) ? params[c].trim() : params[c]; if (!where[col]) where[col] = {}; where[col][`$${x}`] = value; }); }; // searchOpt 的处理,处理参数参数里的q, 实现简易搜索功能 /** # [ # 这下面有三个子数组,代表该model有三个字段参与搜索 [ # 这个数组长度为2,代表此次有2个搜索关键词 # 这个字符串用 OR 切开有三部分,代表该字段定义的search.match 有三部分 '((`user`.`name` LIKE \'a\') OR (`user`.`name` LIKE \'%,a\') OR (`user`.`name` LIKE \'a,%\') OR (`user`.`name` LIKE \'%,a,%\'))' '((`user`.`name` LIKE \'b\') OR (`user`.`name` LIKE \'%,b\') OR (`user`.`name` LIKE \'b,%\') OR (`user`.`name` LIKE \'%,b,%\'))' ] [ '((`user`.`email` LIKE \'%a%\'))' '((`user`.`email` LIKE \'%b%\'))' ] [ '((`user`.`id` = \'a\'))' '((`user`.`id` = \'b\'))' ] ] */ const searchOpt = (Model, searchStr, qstr, as) => { if (!qstr) return undefined; if (!_.isString(qstr)) return undefined; const q = qstr.trim() ? _.split(qstr.trim(), " ", 5) : null; if (!q) return undefined; const searchs = searchStr ? _.split(searchStr, ",") : null; const $ors = []; if (!Model.searchCols) return undefined; _.each(Model.searchCols, (conf, col) => { // 如果设置了搜索的字段,并且当前字读不在设置的搜索字段内,则直接返回 // 相当于跳过这个设置 const _col = as ? `${as}.${col}` : col; // 如果是include里的search,必须指定searchs // 这么做是为了避免用户不知情的一些筛选过滤 if (!searchs && as) return; if (searchs && searchs.length && !_.includes(searchs, _col)) return; $ors.push( _.map(q, x => { const arr = _.map(conf.match, match => { const v = match.replace("{1}", x); return [ `(\`${as || Model.name}\`.\`${col}\``, conf.op, `${mysql.escape(v)})` ].join(" "); }); return `(${arr.join(" OR ")})`; }) ); }); return $ors; }; // 合并多个词语的搜索条件 // 将单个或多个 searchOpt 返回的数组正确的合并成 where 子句, 字符串类型的 // 这个函数的目的是为了正确的使每个关键词之间的关系是 AND 的关系 // 单个关键词在不同的搜索字段之间是 OR 的关系 const mergeSearchOrs = orss => { const ands = []; _.each(orss, _orss => { _.each(_orss, ors => { _.each(ors, (_or, index) => { if (!ands[index]) ands[index] = []; ands[index].push(_or); }); }); }); const andsStr = _.map(ands, x => `(${x.join(" OR ")})`); return `(${andsStr.join(" AND ")})`; }; // 返回列表查询的条件 const findAllOpts = (Model, params, isAll) => { const where = {}; const searchOrs = []; const includes = modelInclude(params, Model.includes); _.each(Model.filterAttrs || _.keys(Model.rawAttributes), name => { findOptFilter(params, name, where); }); if (Model.rawAttributes.isDelete && !params.showDelete) { where.isDelete = "no"; } // 将搜索条件添加到主条件上 searchOrs.push(searchOpt(Model, params._searchs, params.q)); // 处理关联资源的过滤条件 // 以及关联资源允许返回的字段 if (includes) { _.each(includes, x => { const includeWhere = {}; const filterAttrs = x.model.filterAttrs || _.keys(x.model.rawAttributes); _.each(filterAttrs, name => { findOptFilter(params[x.as], name, includeWhere, name); }); if (x.model.rawAttributes.isDelete && !params.showDelete) { includeWhere.$or = [{ isDelete: "no" }]; if (x.required === false) includeWhere.$or.push({ id: null }); } // 将搜索条件添加到 include 的 where 条件上 searchOrs.push(searchOpt(x.model, params._searchs, params.q, x.as)); if (_.size(includeWhere)) x.where = includeWhere; // 以及关联资源允许返回的字段 if (x.model.allowIncludeCols) x.attributes = x.model.allowIncludeCols; }); } // 将 searchOrs 赋到 where 上 const _searchOrs = _.filter(_.compact(searchOrs), x => x.length); if (_searchOrs.length) where.$or = [[mergeSearchOrs(_searchOrs), [""]]]; const ret = { include: includes, order: sort(params, Model.sort) }; if (_.size(where)) ret.where = where; // 处理需要返回的字段 (() => { if (!params.attrs) return; if (!_.isString(params.attrs)) return; const attrs = []; _.each(params.attrs.split(","), x => { if (!Model.rawAttributes[x]) return; attrs.push(x); }); if (!attrs.length) return; ret.attributes = attrs; })(); if (!isAll) _.extend(ret, pageParams(Model.pagination, params)); return ret; }; /** * 忽略list中的某些属性 * 因为有些属性对于某些接口需要隐藏 * 比如 medias/:media/campaigns 中项目的 mediaIds 就不能显示出来 * 否则媒体就能知道该项目还投放了那些媒体 */ const itemAttrFilter = allowAttrs => x => { const ret = {}; for (const attr of allowAttrs) ret[attr] = x[attr]; return ret; }; const listAttrFilter = (ls, allowAttrs) => { if (!allowAttrs) return ls; return _.map(ls, itemAttrFilter(allowAttrs)); }; /** * 把 callback 的写法,作用到 promise 上 * promise.then(->callback(null)).catch(callback) * 目的是为了让callback的写法可以快速对接到 promise 上 */ const callback = (promise, cb) => promise .then(result => { cb.call(null, null, result); }) .catch(cb); const pickParams = (req, cols, Model) => { const attr = {}; const { params, isAdmin } = req; const { rawAttributes, onlyAdminCols } = Model; _.each(cols, x => { if (!_.has(params, x)) return; if (!_.has(rawAttributes, x)) return; const C = rawAttributes[x]; // 当设置了只有管理员才可以修改的字段,并且当前用户不是管理员 // 则去掉那些只有管理员才能修改的字段 if (onlyAdminCols && isAdmin !== true && _.includes(onlyAdminCols, x)) { return; } let value = params[x]; // 如果是数字类型的则数字化 if (_.includes(NUMBER_TYPES, C.type.key)) { if (value != null) value = +value; } // 如果字段允许为空,且默认值为 null 则在等于空字符串的时候赋值为 null if ( (value === "" || value === null || value === undefined) && _.has(C, "defaultValue") ) { value = C.allowNull === true ? null : C.defaultValue; } attr[x] = value; }); return attr; }; module.exports = { sort, callback, searchOpt, pickParams, pageParams, findAllOpts, modelInclude, findOptFilter, mergeSearchOrs, itemAttrFilter, listAttrFilter };