diff --git a/.gitignore b/.gitignore index 1b6c35e..61f4dd7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ .vscode-upload.json package-lock.json **/apidoc/dist/* - +**/__bak__/* # compiled output /dist /tmp diff --git a/package.json b/package.json index e92e490..ec86268 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "ts" ], "dependencies": { + "debug": "^4.1.1", "koa": "^2.8.1", "koa-body": "^4.1.1", "koa-favicon": "^2.0.1", @@ -31,16 +32,21 @@ "koa-router": "^7.4.0", "koa-static-server": "^1.4.0", "koa-views": "^6.2.1", + "log4js": "^5.3.0", + "mysql": "^2.17.1", "pug": "^2.0.4" }, "devDependencies": { + "@types/debug": "^4.1.5", "@types/jest": "^24.0.19", "@types/koa": "2.0.50", "@types/koa-compose": "3.2.4", "@types/koa-logger": "^3.1.1", "@types/koa-router": "^7.0.42", "@types/koa-views": "^2.0.3", + "@types/log4js": "^2.3.5", "@types/mocha": "^5.2.7", + "@types/mysql": "^2.15.8", "@types/node": "^12.7.5", "@types/shelljs": "^0.8.5", "@types/supertest": "^2.0.8", diff --git a/shell/start-local.cmd b/shell/start-local.cmd new file mode 100644 index 0000000..c52af83 --- /dev/null +++ b/shell/start-local.cmd @@ -0,0 +1,4 @@ +set PORT=8080 +:: 数据库密码 +set MARIADBPWD=xxxx +npm run dev diff --git a/shell/test.cmd b/shell/test.cmd new file mode 100644 index 0000000..df27c20 --- /dev/null +++ b/shell/test.cmd @@ -0,0 +1,4 @@ +set PORT=8080 +:: 数据库密码 +set MARIADBPWD=xxxx +npm run test diff --git a/src/app/config/dev-server.config.ts b/src/app/config/dev-server.config.ts new file mode 100644 index 0000000..16612c8 --- /dev/null +++ b/src/app/config/dev-server.config.ts @@ -0,0 +1,11 @@ +import { MARIADBPWD } from './variate'; + +// 服务器开发版 +export default { + mariadb: { + host: 'xxx.xxx.xxx.xxx', + user: 'root', + password: MARIADBPWD, + database: 'koa-ts-full-template-db' + } +}; diff --git a/src/app/config/dev.config.ts b/src/app/config/dev.config.ts new file mode 100644 index 0000000..4a3852e --- /dev/null +++ b/src/app/config/dev.config.ts @@ -0,0 +1,11 @@ +import { MARIADBPWD } from './variate'; + +// 本地开发版 +export default { + mariadb: { + host: 'localhost', + user: 'root', + password: MARIADBPWD, + database: 'koa-ts-full-template-db' + } +}; diff --git a/src/app/config/prod.config.ts b/src/app/config/prod.config.ts new file mode 100644 index 0000000..8f366b8 --- /dev/null +++ b/src/app/config/prod.config.ts @@ -0,0 +1,10 @@ +import { MARIADBPWD } from './variate'; +// 正式版 +export default { + mariadb: { + host: 'xxx.xxx.xxx.xxx', + user: 'root', + password: MARIADBPWD, + database: 'koa-ts-full-template-db' + } +}; diff --git a/src/app/config/system.config.ts b/src/app/config/system.config.ts new file mode 100644 index 0000000..6f5f6b7 --- /dev/null +++ b/src/app/config/system.config.ts @@ -0,0 +1,35 @@ +import devConfig from './dev.config'; +import devServer from './dev-server.config'; +import prodConfig from './prod.config'; +import Debug from 'debug'; +const debug = Debug('dev-server'); + +class GlobalConfig { + config = { + mariadb: devConfig.mariadb, + isDev: /(^dev.*|^test.*)/.test(process.env.npm_lifecycle_event) + }; + + constructor() { + this.getConfig(); + } + + getConfig() { + switch (process.env.npm_lifecycle_event) { + case 'dev': this.config = Object.assign(this.config, devConfig); + break; + case 'devServer': this.config = Object.assign(this.config, devServer); + break; + case 'prod': this.config = Object.assign(this.config, prodConfig); + break; + default: this.config = Object.assign(this.config, devConfig); + } + return this.config; + } +} + +const globalConfig = new GlobalConfig(); +debug(globalConfig.config); + +const GLOBAL_CONFIG = globalConfig.config; +export default GLOBAL_CONFIG; diff --git a/src/app/config/variate.ts b/src/app/config/variate.ts new file mode 100644 index 0000000..0c3e9d5 --- /dev/null +++ b/src/app/config/variate.ts @@ -0,0 +1,4 @@ +/** + * 整个项目的入口变量 + */ +export const MARIADBPWD: string = process.env.MARIADBPWD; // 数据库密码 diff --git a/src/app/core/bll/v1/user_bll.ts b/src/app/core/bll/v1/user_bll.ts index d254f58..1098bcc 100644 --- a/src/app/core/bll/v1/user_bll.ts +++ b/src/app/core/bll/v1/user_bll.ts @@ -5,14 +5,14 @@ import { UserResModel, UserDbModel } from '@model/v1'; * bll 层主要对 dal 层进行调用,并且格式化数据,并被 controller 层调用 */ -export async function getUserList(dbModel?: UserDbModel, search?: string, page_no?: number, page_size?: number) { - const userList: Array = await userDal.getUserList(dbModel, search, page_no, page_size); +export async function getUserList(dbModel?: UserDbModel, search?: string, pageNo?: number, pageSize?: number) { + const userList: Array = await userDal.getUserList(dbModel, search, pageNo, pageSize); return formatDbList(userList); } export async function getByName(userName) { - const user: UserDbModel = await userDal.getByName(userName); - return formatDb(user); + const users: Array = await userDal.getByName(userName); + return formatDb(users[0]); } export function formatDb(db: UserDbModel): UserResModel { diff --git a/src/app/core/dal/common/dalHelper.ts b/src/app/core/dal/common/dalHelper.ts new file mode 100644 index 0000000..fe5a05d --- /dev/null +++ b/src/app/core/dal/common/dalHelper.ts @@ -0,0 +1,178 @@ +/** + * [ + * { + * sql: 'sql1', + * params: ['params1'] + * } + * { + * sql: 'sql2', + * params: ['params2','params3'] + * } + * ] + */ +export function joinAnd(sqlWhereArr) { + if (!Array.isArray(sqlWhereArr)) { + return { + sql: '', + params: [] + }; + } + sqlWhereArr = sqlWhereArr.filter(item => item && item !== '' && item.sql && item.sql !== ''); + if (sqlWhereArr.length <= 0) { + return { + sql: '', + params: [] + }; + } + const firstSqlWhere = sqlWhereArr.shift(); + let sqlWhere = firstSqlWhere.sql; + const params = firstSqlWhere.params && Array.isArray(firstSqlWhere.params) ? firstSqlWhere.params : []; + sqlWhereArr.forEach(item => { + if (item.sql && item.sql !== '') { + sqlWhere += ` and ${item.sql} `; + item.params && Array.isArray(item.params) ? params.push(...item.params) : []; + } + }); + return { + sql: sqlWhere, + params + }; +} + +export function andByModel(obj, tbName?: string) { + tbName = tbName ? `${tbName}.` : ''; + if (Object.prototype.toString.call(obj) !== '[object Object]') { + return { + sql: '', + params: [] + }; + } + let sqlWhere = ''; + const params = []; + Object.keys(obj).forEach(item => { + if (obj[item] || obj[item] === '' || obj[item] === 0) { + if (sqlWhere) { + sqlWhere += ` and ${tbName}${item}=? `; + } else { + sqlWhere = ` ${tbName}${item}=? `; + } + params.push(obj[item]); + } + }); + return { + sql: sqlWhere, + params + }; +} + +export function insertByModel(obj) { + if (Object.prototype.toString.call(obj) !== '[object Object]' && Object.prototype.toString.call(obj) !== '[object Array]') { + return { + sql: '', + params: [] + }; + } + const insertKey = []; + const placeholders = []; + const params = []; + // obj为对象时 生成插入一条数据的sql + if (Object.prototype.toString.call(obj) === '[object Object]') { + Object.keys(obj).forEach(item => { + if (obj[item] === '') { + insertKey.push(item); + placeholders.push('?'); + params.push(null); + } else if (obj[item] || obj[item] === 0) { + insertKey.push(item); + placeholders.push('?'); + params.push(obj[item]); + } + }); + return { + sql: ` (${insertKey.join(',')}) values (${placeholders.join(',')}) `, + params + }; + } + // 当obj为数组对象时 生成插入多条数据的sql + if (Object.prototype.toString.call(obj) === '[object Array]') { + let temp = '('; + for (let i = 0; i < obj.length; i++) { + const objItem = obj[i]; + if (i === 0) { + Object.keys(objItem).forEach((item, index) => { + insertKey.push(item); + if (index === Object.keys(objItem).length - 1) { + temp += '?)'; + } else { + temp += '?,'; + } + }); + } + Object.keys(objItem).forEach(item => { + if ( + objItem[item] === '' + || objItem[item] === undefined + || objItem[item] === null + ) { + params.push(null); + } else { + params.push(objItem[item]); + } + }); + placeholders.push(temp); + } + return { + sql: ` (${insertKey.join(',')}) values ${placeholders.join(',')} `, + params + }; + } +} + +export function updateByModel(obj) { + if (Object.prototype.toString.call(obj) !== '[object Object]') { + return { + sql: '', + params: [] + }; + } + const updateSql = []; + const params = []; + Object.keys(obj).forEach(item => { + if (obj[item] === '') { + updateSql.push(` ${item}=?`); + params.push(null); + } else if (obj[item] || obj[item] === 0) { + updateSql.push(` ${item}=?`); + params.push(obj[item]); + } + }); + if (updateSql.length === 0) { + throw new Error('update error'); + } + return { + sql: updateSql.join(','), + params + }; +} + +export function orderByFields(sort) { + if (Object.prototype.toString.call(sort) !== '[object Array]') { + return { + sql: '', + params: [] + }; + } + const orderBySql = []; + const params = []; + sort.forEach(item => { + if (item.field) { + const order = item.order ? item.order : 'ASC'; + orderBySql.push(` ${item.field} ${order}`); + } + }); + const sql = orderBySql.length > 0 ? `order by ${orderBySql.join(',')}` : ''; + return { + sql, + params + }; +} diff --git a/src/app/core/dal/common/dbHelper.ts b/src/app/core/dal/common/dbHelper.ts new file mode 100644 index 0000000..08564fe --- /dev/null +++ b/src/app/core/dal/common/dbHelper.ts @@ -0,0 +1,82 @@ +import * as mysql from 'mysql'; +import dbConnectionString from './mariadb.conf'; +import { normal } from '../../../lib/logger'; + +const logger = normal(); +let pool = null; + +function handleError(err) { + logger.error(err.stack || err); + connect(); +} + +function connect() { + pool = mysql.createPool(Object.assign(dbConnectionString, { connectionLimit: 10 })); + pool.on('error', handleError); + pool.on('acquire', function () { + // console.log('Connection %d acquired', connection.threadId); + }); + + pool.on('enqueue', function () { + logger.info('Waiting for available connection slot'); + }); +} + +connect(); + +// execute the sql. +function exeScript(sqlType, sql, params) { + return new Promise((resolve, reject) => { + pool.query(sql, params, function (error, results) { + if (error) { + logger.error(`${sqlType}:${error}`); + reject(error); + } + resolve(results); + }); + }); +} + +async function callExeScript(sqlType, sql, params) { + let result; + try { + result = await exeScript(sqlType, sql, params); + } catch (err) { + logger.error(err); + throw new Error(err); + } + return result; +} + +async function query(sql, params) { + const result = await callExeScript('query', sql, params); + if (result.length >= 1) { + return result; + } + // 这里等于空时,不直接返回error的数据结构,因为等于空时不一定代表错误,因此把为空的错误逻辑交给调用者进行处理 + return ''; +} + +async function insert(sql, params) { + const result = await callExeScript('insert', sql, params); + return result; +} + +async function update(sql, params) { + const result = await callExeScript('update', sql, params); + return result; +} + +// 这里delete估计是关键,所以采用delete1 +async function delete1(sql, params) { + const result = await callExeScript('delete', sql, params); + return result; +} + + +export default { + query, + insert, + update, + delete: delete1 +}; diff --git a/src/app/core/dal/common/index.ts b/src/app/core/dal/common/index.ts new file mode 100644 index 0000000..51a70d8 --- /dev/null +++ b/src/app/core/dal/common/index.ts @@ -0,0 +1,11 @@ +import * as dalHelper from './dalHelper'; +import dbHelper from './dbHelper'; +import mariadbConf from './mariadb.conf'; +import * as sqlHelper from './sqlHelper'; + +export { + dalHelper, + dbHelper, + mariadbConf, + sqlHelper +}; diff --git a/src/app/core/dal/common/mariadb.conf.ts b/src/app/core/dal/common/mariadb.conf.ts new file mode 100644 index 0000000..01b90dc --- /dev/null +++ b/src/app/core/dal/common/mariadb.conf.ts @@ -0,0 +1,9 @@ +import GLOBAL_CONFIG from '../../../config/system.config'; + +export default { + host: GLOBAL_CONFIG.mariadb.host, + user: GLOBAL_CONFIG.mariadb.user, + password: GLOBAL_CONFIG.mariadb.password, + database: GLOBAL_CONFIG.mariadb.database, + dateStrings: true +}; diff --git a/src/app/core/dal/common/sqlHelper.ts b/src/app/core/dal/common/sqlHelper.ts new file mode 100644 index 0000000..b3122ed --- /dev/null +++ b/src/app/core/dal/common/sqlHelper.ts @@ -0,0 +1,7 @@ +export function sqlPaging(sqlStr, pageNo, pageSize) { + if (!pageNo || !pageSize) { + return sqlStr; + } + sqlStr += ` limit ${pageSize * (pageNo - 1)},${pageSize}`; + return sqlStr; +} diff --git a/src/app/core/dal/v1/user_dal.ts b/src/app/core/dal/v1/user_dal.ts index 6de4fcc..499cc69 100644 --- a/src/app/core/dal/v1/user_dal.ts +++ b/src/app/core/dal/v1/user_dal.ts @@ -1,53 +1,51 @@ +import { dbHelper, sqlHelper, dalHelper } from '../common'; import { UserDbModel } from '@model/v1'; -/** - * dal 层主要实现对数据库操作,主要是 sql 语句 - */ +export async function getUserList(dbModel?: UserDbModel, + search?: string, pageNo?: number, pageSize?: number): Promise> { + const sqlWhere = getCommonWhere(dbModel, search); + const whereSql = sqlWhere.whereSql; + const whereParams = sqlWhere.whereParams; -const USERLIST: Array = [ - { - user_name: 'user1', - age: 18, - hobby: 'Guitar' - }, - { - user_name: 'user2', - age: 19, - hobby: 'Piano' - }, - { - user_name: 'user3', - age: 20, - hobby: 'Piano' - }, - { - user_name: 'user4', - age: 18, - hobby: 'Piano' - } -]; + const sql = `SELECT * FROM user ${whereSql}`; + const sqlWithPaging = sqlHelper.sqlPaging(sql, pageNo, pageSize); + const sqlParams = [...whereParams]; + return dbHelper.query(sqlWithPaging, sqlParams); +} -function isNullorUndefined(value: string) { - return value === undefined || value === null; +export async function getByName(userName): Promise> { + const sql = 'SELECT * FROM user where user_name=?'; + const sqlParams = [userName]; + return dbHelper.query(sql, sqlParams); } -export async function getUserList(dbModel?: UserDbModel, - search?: string, page_no?: number, page_size?: number): Promise> { - // 'select * from user' - return USERLIST.filter((item: UserDbModel) => { - let flat = true; - for (const key in dbModel) { - if (!isNullorUndefined(dbModel[key]) && item[key] !== dbModel[key]) { - flat = false; - break; - } - } - return flat; - }); +function createSearchWhereSql(search: string, tbName?: string) { + tbName = tbName ? `${tbName}.` : ''; + // 关键词查询 where + let searchWhereSql; + if (search && search !== '') { + searchWhereSql = `(${tbName}user_name like '%${search}%' + or ${tbName}hobby like '%${search}%')`; + } + return searchWhereSql; } -export async function getByName(userName): Promise { - // `select * from user where user_name = 'xxx'` - const user = USERLIST.find(item => item.user_name === userName); - return user; +function getCommonWhere(dbModel: UserDbModel, search: string) { + // 关键词查询 where + const searchWhereSql = createSearchWhereSql(search); + const searchWhere = { + sql: searchWhereSql, + params: [] + }; + // 根据参数精确查询 where + const modelWhere = dalHelper.andByModel(dbModel); + + const sqlWhere = dalHelper.joinAnd([searchWhere, modelWhere]); + const whereSql = sqlWhere.sql ? `where ${sqlWhere.sql}` : ' '; + const whereParams = sqlWhere.params && Array.isArray(sqlWhere.params) ? sqlWhere.params : []; + + return { + whereSql, + whereParams + }; } diff --git a/src/app/lib/common.ts b/src/app/lib/common.ts new file mode 100644 index 0000000..b126d20 --- /dev/null +++ b/src/app/lib/common.ts @@ -0,0 +1,50 @@ +/** + * 检查参数是否错误或者为空 + */ +export function checkParamsIsNullOrError(params: Array): boolean { + for (const param of params) { + if (param === '' || param === undefined || param === null || param === []) { + return true; + } + if (Array.prototype.isPrototypeOf(param) && param.length === 0) { return true; } + if (Object.prototype.isPrototypeOf(param) && Object.keys(param).length === 0) { return true; } + } + return false; +} + + +export function strToJson(str: string) { + let jsonData = []; + if (str === null || str === undefined || str === 'null' || str === '') { + return jsonData; + } + try { + jsonData = JSON.parse(str); + } catch (e) { + jsonData = []; + } + return jsonData; +} + +export function JsonToStr(json: any) { + try { + return JSON.stringify(json); + } catch (e) { + return ''; + } +} + +export function strToNumber(str: string): number { + const strNum = parseInt(str, 10); + if (isNaN(strNum)) { + return null; + } + return strNum; +} + +export function toBoolean(str: string): boolean { + if (str === 'false' || str === '0' || str === '-0' || str === 'NaN') { + return false; + } + return Boolean(str); +} diff --git a/src/app/lib/index.ts b/src/app/lib/index.ts new file mode 100644 index 0000000..482fd56 --- /dev/null +++ b/src/app/lib/index.ts @@ -0,0 +1,23 @@ +import * as common from './common'; +import * as ctxHelper from './ctx-helper'; +import * as dateHelper from './date-helper'; +// import * as dfsHelper from './dfs-helper'; +import * as error from './error'; +import * as fsHelper from './fs-helper'; +import * as logger from './logger'; +import * as reportHelper from './report-helper'; +import * as v01Auth from './v01-auth'; +import * as dhCrypto from './dhCrypto'; + +export { + common, + ctxHelper, + dateHelper, + // dfsHelper, + error, + fsHelper, + logger, + reportHelper, + v01Auth, + dhCrypto +}; diff --git a/src/app/lib/logger.ts b/src/app/lib/logger.ts new file mode 100644 index 0000000..e0221a6 --- /dev/null +++ b/src/app/lib/logger.ts @@ -0,0 +1,25 @@ +import * as log4js from 'log4js'; +import { Logger } from 'log4js'; + +/** + * ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < MARK < OFF + */ +log4js.configure({ + appenders: { + console: { + type: 'console' + } + }, + categories: { + default: { appenders: ['console'], level: 'info' }, // 只输出到控制台 + } +}); + +export function normal(category?: string): Logger { + if (!category) { + // get filename + const error = (new Error()).stack.toString().split('\n')[2] || ''; + category = (error.match(/[\\/(]([-\w.]+.\w+):\d+:\d+\)$/) || [])[1] || 'unknow'; + } + return log4js.getLogger(category); +} diff --git a/src/app/sql/sql_20191111.sql b/src/app/sql/sql_20191111.sql new file mode 100644 index 0000000..40c5e0c --- /dev/null +++ b/src/app/sql/sql_20191111.sql @@ -0,0 +1,17 @@ +create database `koa-ts-full-template-db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + +use `koa-ts-full-template-db`; + +CREATE TABLE `user` ( + `user_id` int(11) NOT NULL AUTO_INCREMENT, + `user_name` varchar(20) DEFAULT NULL, + `age` int(11) DEFAULT NULL, + `hobby` varchar(20) DEFAULT NULL, + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; + + +insert into `user` (`user_name`,`age`,`hobby`) values ('user1',18,'Guitar'); +insert into `user` (`user_name`,`age`,`hobby`) values ('user2',19,'Piano'); +insert into `user` (`user_name`,`age`,`hobby`) values ('user3',20,'Piano'); +insert into `user` (`user_name`,`age`,`hobby`) values ('user4',18,'Piano');