doomiwork 4.2.3 → 4.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +91 -92
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +91 -92
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -233,24 +233,25 @@ function requireAppsetting () {
|
|
|
233
233
|
if (hasRequiredAppsetting) return appsetting.exports;
|
|
234
234
|
hasRequiredAppsetting = 1;
|
|
235
235
|
(function (module, exports) {
|
|
236
|
-
const path
|
|
237
|
-
const fs
|
|
238
|
-
class appsetting{
|
|
236
|
+
const path = require$$1;
|
|
237
|
+
const fs = require$$0;
|
|
238
|
+
class appsetting {
|
|
239
239
|
static #instance;
|
|
240
240
|
|
|
241
|
-
constructor(){
|
|
241
|
+
constructor(baseFolder = process.cwd()) {
|
|
242
242
|
if (appsetting.#instance) throw new Error('Use getCurrentApp() instead of new appsetting()');
|
|
243
243
|
const config = process.env.CONFIGFILE || 'configuration.json';
|
|
244
|
-
const configfile = path.join(
|
|
244
|
+
const configfile = path.join(baseFolder, config);
|
|
245
245
|
if (!fs.existsSync(configfile)) throw new Error('missing app configuration file');
|
|
246
246
|
this.settingjson = require(configfile);
|
|
247
|
+
this.folder = baseFolder;
|
|
247
248
|
}
|
|
248
249
|
/**
|
|
249
250
|
* 单例模式
|
|
250
251
|
* @param {*} config
|
|
251
252
|
*/
|
|
252
|
-
static getCurrentApp(){
|
|
253
|
-
if (!appsetting.#instance) appsetting.#instance = new appsetting();
|
|
253
|
+
static getCurrentApp(baseFolder = process.cwd()) {
|
|
254
|
+
if (!appsetting.#instance) appsetting.#instance = new appsetting(baseFolder);
|
|
254
255
|
return appsetting.#instance;
|
|
255
256
|
}
|
|
256
257
|
/**
|
|
@@ -258,18 +259,18 @@ function requireAppsetting () {
|
|
|
258
259
|
* @param {*} settingKey
|
|
259
260
|
* @param {*} defaultValue
|
|
260
261
|
*/
|
|
261
|
-
getSetting
|
|
262
|
+
getSetting(settingKey, defaultValue) {
|
|
262
263
|
if (!this.settingjson || !this.settingjson.appsetting) return defaultValue;
|
|
263
|
-
|
|
264
|
-
|
|
264
|
+
const item = this.settingjson.appsetting[settingKey];
|
|
265
|
+
return item ?? defaultValue;
|
|
265
266
|
}
|
|
266
|
-
getSection(sectionName){
|
|
267
|
+
getSection(sectionName) {
|
|
267
268
|
return this.settingjson[sectionName];
|
|
268
269
|
}
|
|
269
270
|
/*
|
|
270
271
|
* 获取配置文件中的数据库连接串
|
|
271
272
|
*/
|
|
272
|
-
getConnection
|
|
273
|
+
getConnection(connName) {
|
|
273
274
|
return this.settingjson.connections[connName];
|
|
274
275
|
};
|
|
275
276
|
}
|
|
@@ -292,47 +293,44 @@ function requireDoomiwork () {
|
|
|
292
293
|
*/
|
|
293
294
|
const { loadController } = requireRouterconfig();
|
|
294
295
|
const DoomiApiResult = requireDoomiapiresult();
|
|
295
|
-
const config = requireAppsetting()
|
|
296
|
+
const config = requireAppsetting();
|
|
296
297
|
const fs = require$$0;
|
|
297
298
|
const path = require$$1;
|
|
298
299
|
class Doomiwork {
|
|
300
|
+
/**
|
|
301
|
+
* 启动框架
|
|
302
|
+
* @param {*} application app对象
|
|
303
|
+
* @param {*} option 启动的参数
|
|
304
|
+
* @param {*} injector
|
|
305
|
+
*/
|
|
299
306
|
static startWork(application,option={}) {
|
|
300
|
-
|
|
307
|
+
/// 默认的配置文件所在目录就是程序运行的根目录
|
|
308
|
+
const ApplicationConfiguration = config.getCurrentApp(option.configfolder ?? process.cwd());
|
|
309
|
+
application.config = ApplicationConfiguration;
|
|
301
310
|
///启动redis进行缓存
|
|
302
311
|
////为每个controller注入同一个redis实例
|
|
303
|
-
if (
|
|
304
|
-
application.redis = option.redis;// redisHelper.getInstance();
|
|
305
|
-
}
|
|
306
|
-
////记录用户行为的日志记录器
|
|
307
|
-
// const logHandler = config.getSetting("logaction", null)
|
|
308
|
-
// if (logHandler){
|
|
309
|
-
// let actionLogger = path.join(startupRoot, logHandler);
|
|
310
|
-
// if (actionLogger && fs.existsSync(actionLogger)) {
|
|
311
|
-
// const logInstance = require(actionLogger);
|
|
312
|
-
// application.actionLogger = new logInstance();
|
|
313
|
-
// }
|
|
314
|
-
// }
|
|
312
|
+
if (ApplicationConfiguration.getSetting("redis", false) === true && option.redis) application.redis = option.redis;// redisHelper.getInstance();
|
|
315
313
|
///用户操作日志记录
|
|
316
|
-
if(
|
|
314
|
+
if (ApplicationConfiguration.getSetting("logaction", false)===true && option.logaction) application.actionLogger = option.logaction;
|
|
317
315
|
///日志记录器
|
|
318
|
-
if (
|
|
316
|
+
if (ApplicationConfiguration.getSetting("logger", false) === true && option.logger) application.logger = option.logger;// redisHelper.getInstance();
|
|
319
317
|
//application.logger = logHelper.getInstance().getLogger("framework");
|
|
320
|
-
/**全局所有请求拦截器
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
318
|
+
/**全局所有请求拦截器 默认将整个请求头都注入到req.user 中*/
|
|
319
|
+
if (option.interceptorForUser !==false){
|
|
320
|
+
application.use('/', (req, res, next) => {
|
|
321
|
+
req.user = req.headers;
|
|
322
|
+
return next();
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
325
|
/**
|
|
326
326
|
* 注册监控路由
|
|
327
327
|
*/
|
|
328
328
|
if (!(option.monitor===false)) this.registerMonitorRouter(application);
|
|
329
329
|
/** 加载所有的控制器 */
|
|
330
330
|
if (!option.routes){
|
|
331
|
-
const roterfile = path.join(process.cwd(), 'routerconfig.json');
|
|
331
|
+
const roterfile = path.join(option.configfolder ?? process.cwd(), 'routerconfig.json');
|
|
332
332
|
///将配置中的所有需要加载的controller文件都加载出来
|
|
333
|
-
if (fs.existsSync(roterfile))
|
|
334
|
-
option.routes = require(roterfile);
|
|
335
|
-
}
|
|
333
|
+
if (fs.existsSync(roterfile))option.routes = require(roterfile);
|
|
336
334
|
}
|
|
337
335
|
loadController(application, option.routes);
|
|
338
336
|
Doomiwork.app = application;
|
|
@@ -920,13 +918,15 @@ function requireDataconfig () {
|
|
|
920
918
|
const path = require$$1;
|
|
921
919
|
class dataConfig
|
|
922
920
|
{
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
921
|
+
static #instance;
|
|
922
|
+
constructor(folder = process.cwd()){
|
|
923
|
+
if (dataConfig.#instance) throw new Error('Use getCurrent() instead of new dataConfig()');
|
|
924
|
+
let configfile = path.join(folder,'dataconfig.json');
|
|
925
|
+
let comcfgfile = path.join(folder,'simplecommon.json');
|
|
926
926
|
////检查dataconfig.json文件是否存在,优先加载dataconfig.json
|
|
927
927
|
if (!fs.existsSync(configfile)){
|
|
928
928
|
///如果json文件不存在,则加载后缀为js的文件
|
|
929
|
-
configfile = path.join(
|
|
929
|
+
configfile = path.join(folder,'dataconfig.js');
|
|
930
930
|
if (!fs.existsSync(configfile))
|
|
931
931
|
this.cache = {}; //throw new Error('missing app configuration file')
|
|
932
932
|
else
|
|
@@ -937,13 +937,13 @@ function requireDataconfig () {
|
|
|
937
937
|
if (fs.existsSync(comcfgfile)){
|
|
938
938
|
this.commoncache = require(comcfgfile);
|
|
939
939
|
}else {
|
|
940
|
-
comcfgfile =
|
|
940
|
+
comcfgfile = path.join(folder,'simplecommon.js');
|
|
941
941
|
if (fs.existsSync(comcfgfile))
|
|
942
942
|
this.commoncache = require(comcfgfile);
|
|
943
943
|
else
|
|
944
944
|
this.commoncache = {};
|
|
945
945
|
}
|
|
946
|
-
|
|
946
|
+
this.folder = folder;
|
|
947
947
|
}
|
|
948
948
|
/*
|
|
949
949
|
*根据名称获取到配置中的对应节
|
|
@@ -956,9 +956,9 @@ function requireDataconfig () {
|
|
|
956
956
|
* 单例模式
|
|
957
957
|
* @param {*} config
|
|
958
958
|
*/
|
|
959
|
-
static getCurrent(){
|
|
960
|
-
if (!dataConfig
|
|
961
|
-
return dataConfig
|
|
959
|
+
static getCurrent(folder = process.cwd()){
|
|
960
|
+
if (!dataConfig.#instance) dataConfig.#instance = new dataConfig(folder);
|
|
961
|
+
return dataConfig.#instance;
|
|
962
962
|
}
|
|
963
963
|
|
|
964
964
|
getDataConfig(itemName) {
|
|
@@ -980,7 +980,7 @@ function requireRequestparser () {
|
|
|
980
980
|
if (hasRequiredRequestparser) return requestparser;
|
|
981
981
|
hasRequiredRequestparser = 1;
|
|
982
982
|
const { parseKeyValue, validatorParamsType } = requireKeywordparse();
|
|
983
|
-
const dataconfig = requireDataconfig()
|
|
983
|
+
const dataconfig = requireDataconfig();
|
|
984
984
|
const mysql = require$$0$3;
|
|
985
985
|
const ORDER_STRING = ['asc', 'desc'];
|
|
986
986
|
const FORBID_SQL_KEYWORD = /;|(--)|(\bWHERE\b)|(\bCASE\b)|(\bWHEN\b)|(\bSLEEP\b)|(\bSHOW\b)|(\bCOUNT\(\b)|(\bCREATE\b)|(\bCALL\b)|(\bBY\b)|(\bORDER\b)|(\bJOIN\b)|(\bUNION\b)|(\bFROM\b)|(\bSELECT\b)|(\bDROP\b)|(\bTRUNCATE\b)|(\bDELETE\b)|(\bUPDATE\b)|(\bINSERT\b)|(\bEXEC\b)|(\bEXECUTE\b)/gi;
|
|
@@ -1161,9 +1161,9 @@ function requireRequestparser () {
|
|
|
1161
1161
|
* 列表请求上下文中获取需要的信息
|
|
1162
1162
|
* 如Page ,PageSize , Sort 等等
|
|
1163
1163
|
*/
|
|
1164
|
-
const getListInfo = (req,
|
|
1165
|
-
if (!
|
|
1166
|
-
const dataConfig = dataconfig.getConfig(
|
|
1164
|
+
const getListInfo = (req, { key, folder, configtype = 0, dao }) => {
|
|
1165
|
+
if (!key) return null;
|
|
1166
|
+
const dataConfig = dataconfig.getCurrent(folder ?? process.cwd()).getConfig(key, configtype);
|
|
1167
1167
|
if (!dataConfig?.list) return null;
|
|
1168
1168
|
///确认是否是需要导出Excel
|
|
1169
1169
|
const export2Excel = (req.query.exportexcel + "").toLowerCase() === "true";
|
|
@@ -1220,9 +1220,9 @@ function requireRequestparser () {
|
|
|
1220
1220
|
* 详细页面请求上下文中获取需要的信息
|
|
1221
1221
|
* 如Page ,PageSize , Sort 等等
|
|
1222
1222
|
*/
|
|
1223
|
-
const getDetailInfo = (req,
|
|
1224
|
-
if (
|
|
1225
|
-
const dataConfig = dataconfig.getConfig(
|
|
1223
|
+
const getDetailInfo = (req,{key,folder}) => {
|
|
1224
|
+
if (key) {
|
|
1225
|
+
const dataConfig = dataconfig.getCurrent(folder ?? process.cwd()).getConfig(key);
|
|
1226
1226
|
if (dataConfig && dataConfig.detail) {
|
|
1227
1227
|
req.dataConfig = dataConfig;
|
|
1228
1228
|
const { primary, field: fields, primaryIsAutoIncrease } = dataConfig.detail;
|
|
@@ -1972,20 +1972,19 @@ function requireController () {
|
|
|
1972
1972
|
/*
|
|
1973
1973
|
* 获取查询列表记录的方法
|
|
1974
1974
|
*/
|
|
1975
|
-
async getListData(req,
|
|
1976
|
-
if (this.logger) this.logger.trace("准备获取dataconfig文件中对应的 %s list数据",
|
|
1977
|
-
const listinfo = getListInfo(req,
|
|
1978
|
-
if (!listinfo) return DoomiApiResult.fail(-10, `缺失${
|
|
1979
|
-
|
|
1975
|
+
async getListData(req, key, configtype = 0) {
|
|
1976
|
+
if (this.logger) this.logger.trace("准备获取dataconfig文件中对应的 %s list数据", key);
|
|
1977
|
+
const listinfo = getListInfo(req, { key, folder: this.app.config.folder, configtype, dao:this._daoModel}); //, ignorefilter==1
|
|
1978
|
+
if (!listinfo) return DoomiApiResult.fail(-10, `缺失${key}对应的查询语句`);
|
|
1980
1979
|
////直接操作数据库之前,可由子类再次Handler
|
|
1981
|
-
|
|
1980
|
+
const beforeData = await this.beforeAccessDB(req, listinfo.sql, listinfo.params, "list", this);
|
|
1982
1981
|
////如果返回Null,则表示不加载数据了
|
|
1983
|
-
if (beforeData.canceled
|
|
1982
|
+
if (beforeData.canceled) return DoomiApiResult.fail(1, '操作取消');
|
|
1984
1983
|
////操作数据库
|
|
1985
|
-
|
|
1984
|
+
const result = await this._daoModel.loadData(beforeData.sql, beforeData.sqlParams);
|
|
1986
1985
|
if(!result.successed) {
|
|
1987
1986
|
////失败也需要记录
|
|
1988
|
-
if (req.query.exportexcel==='true') this.logUserAction(req,this._daoModel.getBusiness(),5,-1);
|
|
1987
|
+
if (String(req.query.exportexcel).toLowerCase() === 'true') this.logUserAction(req,this._daoModel.getBusiness(),5,-1);
|
|
1989
1988
|
return result;
|
|
1990
1989
|
}
|
|
1991
1990
|
///从数据库中获取到了原始数据
|
|
@@ -2000,8 +1999,8 @@ function requireController () {
|
|
|
2000
1999
|
}
|
|
2001
2000
|
extra.total = await this.getListRecordCount(result);
|
|
2002
2001
|
///处理导出Excel
|
|
2003
|
-
if (req.query.exportexcel==='true') {
|
|
2004
|
-
const excelResult = await this.export2Excel(extra.rows, req.query.excelkey ||
|
|
2002
|
+
if (String(req.query.exportexcel).toLowerCase() === 'true') {
|
|
2003
|
+
const excelResult = await this.export2Excel(extra.rows, req.query.excelkey || key);
|
|
2005
2004
|
this.logUserAction(req, this._daoModel.getBusiness(), 5, 0);
|
|
2006
2005
|
return excelResult;
|
|
2007
2006
|
}
|
|
@@ -2014,19 +2013,19 @@ function requireController () {
|
|
|
2014
2013
|
* @param {*} data
|
|
2015
2014
|
*/
|
|
2016
2015
|
async getListRecordCount(result){
|
|
2017
|
-
if (result.rows && result.rows.length == 2 && result.rows[1].length) return result.rows[1][0].total;
|
|
2016
|
+
if (result && result.rows && result.rows.length == 2 && result.rows[1].length) return result.rows[1][0].total;
|
|
2018
2017
|
return 0;
|
|
2019
2018
|
}
|
|
2020
2019
|
/*
|
|
2021
2020
|
* 获取详细记录的方法
|
|
2022
2021
|
*/
|
|
2023
|
-
async getDataById(req,
|
|
2024
|
-
if (this.logger) this.logger.trace("准备获取dataconfig文件中对应的 %s 详细数据",
|
|
2025
|
-
const detailinfo = getDetailInfo(req,
|
|
2026
|
-
if (!detailinfo) return DoomiApiResult.fail(-10, `缺失${
|
|
2027
|
-
const beforeData = await this.beforeAccessDB(req, sqlCommand || this._daoModel.getByIdSql(),
|
|
2022
|
+
async getDataById(req, key, sqlCommand,dataUid) {
|
|
2023
|
+
if (this.logger) this.logger.trace("准备获取dataconfig文件中对应的 %s 详细数据", key);
|
|
2024
|
+
const detailinfo = getDetailInfo(req, { key,folder:this.app.config.folder });
|
|
2025
|
+
if (!detailinfo) return DoomiApiResult.fail(-10, `缺失${key}对应的详情配置`);
|
|
2026
|
+
const beforeData = await this.beforeAccessDB(req, sqlCommand || this._daoModel.getByIdSql(), dataUid ||req.params.id, "detail", this);
|
|
2028
2027
|
////如果返回Null,则表示不加载数据了
|
|
2029
|
-
if (beforeData.canceled
|
|
2028
|
+
if (beforeData.canceled) return DoomiApiResult.fail(1, '操作已取消');
|
|
2030
2029
|
/////处理SQL中的一些定义符
|
|
2031
2030
|
const parseSql = parseTagInSql(req,beforeData.sql);
|
|
2032
2031
|
////操作数据库
|
|
@@ -2065,23 +2064,23 @@ function requireController () {
|
|
|
2065
2064
|
/*
|
|
2066
2065
|
* 新增实体记录的方法
|
|
2067
2066
|
*/
|
|
2068
|
-
async create(req,
|
|
2069
|
-
if (this.logger) this.logger.trace("准备根据dataconfig文件中对应的 %s 配置插入数据",
|
|
2070
|
-
|
|
2067
|
+
async create(req, key, sqlCommand) {
|
|
2068
|
+
if (this.logger) this.logger.trace("准备根据dataconfig文件中对应的 %s 配置插入数据", key);
|
|
2069
|
+
const mapping = String(req.query.Mapping??true)===String(true); //|| 'true'==='true' ;//commonHelper.ifNull(req.query.Mapping, true);
|
|
2071
2070
|
let detailinfo, resultItem = { successed: true, data: req.body };
|
|
2072
2071
|
///是否需要将data中的key-value值到配置文件中进行转换
|
|
2073
|
-
if (
|
|
2074
|
-
detailinfo = getDetailInfo(req,
|
|
2075
|
-
if (!detailinfo) return DoomiApiResult.fail(-10, `缺失${
|
|
2072
|
+
if (mapping) {
|
|
2073
|
+
detailinfo = getDetailInfo(req, { key,folder:this.app.config.folder });
|
|
2074
|
+
if (!detailinfo) return DoomiApiResult.fail(-10, `缺失${key}对应的详情配置`);
|
|
2076
2075
|
resultItem = View2Data(req, detailinfo.fields, "create");
|
|
2077
2076
|
///如果转换的验证过程中出现错误,则返回
|
|
2078
2077
|
if (!resultItem.successed) return resultItem;
|
|
2079
2078
|
}
|
|
2080
|
-
|
|
2079
|
+
const beforeData = await this.beforeAccessDB(req, sqlCommand || this._daoModel.insertSql(), resultItem.data, "create", this);
|
|
2081
2080
|
////如果返回Null,则表示不加载数据了
|
|
2082
2081
|
if (beforeData.canceled) return DoomiApiResult.fail(1, '操作取消');
|
|
2083
2082
|
if (this.logger) this.logger.trace("插入数据准备 : ",beforeData);
|
|
2084
|
-
|
|
2083
|
+
const result = await this._daoModel.create(beforeData.sql, beforeData.sqlParams, req);
|
|
2085
2084
|
if(!result.successed) {
|
|
2086
2085
|
this.logUserAction(req,this._daoModel.getBusiness(),1,-1);
|
|
2087
2086
|
return result;
|
|
@@ -2111,26 +2110,26 @@ function requireController () {
|
|
|
2111
2110
|
/*
|
|
2112
2111
|
* 修改实体记录的方法
|
|
2113
2112
|
*/
|
|
2114
|
-
async update(req,
|
|
2115
|
-
if (this.logger)
|
|
2116
|
-
|
|
2117
|
-
|
|
2113
|
+
async update(req, key, sqlCommand, id) {
|
|
2114
|
+
if (this.logger) this.logger.trace("准备根据dataconfig文件中对应的 %s 配置修改数据", key);
|
|
2115
|
+
id = id ?? req.params.id;
|
|
2116
|
+
const mapping = String(req.query.Mapping ?? true)===String(true);// commonHelper.ifNull(req.query.Mapping, true);
|
|
2118
2117
|
let detailinfo, resultItem = { successed: true, data: req.body };
|
|
2119
2118
|
///是否需要将data中的key-value值到配置文件中进行转换
|
|
2120
|
-
if (
|
|
2121
|
-
detailinfo = getDetailInfo(req,
|
|
2122
|
-
if (!detailinfo) return DoomiApiResult.fail(-10, `缺失${
|
|
2119
|
+
if (mapping) {
|
|
2120
|
+
detailinfo = getDetailInfo(req, {key,folder:this.app.config.folder });
|
|
2121
|
+
if (!detailinfo) return DoomiApiResult.fail(-10, `缺失${key}对应的详情配置`);
|
|
2123
2122
|
///从Request中获取到用户提交的数据
|
|
2124
2123
|
resultItem = View2Data(req, detailinfo.fields, "update");
|
|
2125
2124
|
}
|
|
2126
2125
|
///如果不需要转换,则直接使用前端提交上来的值
|
|
2127
|
-
|
|
2126
|
+
const beforeData = await this.beforeAccessDB(req, sqlCommand || this._daoModel.updateSql(), resultItem.data, "update", this);
|
|
2128
2127
|
////如果返回Null,则表示不加载数据了
|
|
2129
2128
|
if (beforeData.canceled) return DoomiApiResult.fail(1, '操作取消');
|
|
2130
2129
|
if (this.logger) this.logger.trace("修改数据准备 : ",beforeData);
|
|
2131
2130
|
/////处理SQL中的一些定义符
|
|
2132
|
-
|
|
2133
|
-
|
|
2131
|
+
const parseSql = parseTagInSql(req,beforeData.sql);
|
|
2132
|
+
const result = await this._daoModel.update(parseSql, beforeData.sqlParams, id);
|
|
2134
2133
|
if(!result.successed) {
|
|
2135
2134
|
this.logUserAction(req,this._daoModel.getBusiness(),3,-1);
|
|
2136
2135
|
return result;
|
|
@@ -2158,13 +2157,13 @@ function requireController () {
|
|
|
2158
2157
|
*/
|
|
2159
2158
|
async delete(req, id, sqlCommand) {
|
|
2160
2159
|
if (this.logger) this.logger.trace("准备删除数据",id);
|
|
2161
|
-
|
|
2160
|
+
const beforeData = await this.beforeAccessDB(req, sqlCommand || this._daoModel.deleteSql(id), id, "delete", this);
|
|
2162
2161
|
////如果返回Null,则表示不加载数据了
|
|
2163
2162
|
if (beforeData.canceled) return DoomiApiResult.fail(1, '操作取消');
|
|
2164
2163
|
/////处理SQL中的一些定义符
|
|
2165
|
-
|
|
2164
|
+
const parseSql = parseTagInSql(req,beforeData.sql);
|
|
2166
2165
|
////操作数据库
|
|
2167
|
-
|
|
2166
|
+
const result = await this._daoModel.delete(parseSql, beforeData.sqlParams,req.user?.id);
|
|
2168
2167
|
if(!result.successed) {
|
|
2169
2168
|
this.logUserAction(req,this._daoModel.getBusiness(),2,-1);
|
|
2170
2169
|
return result;
|