First Commit
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
require('dotenv').config();
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 15, // 增加併發處理能力
|
||||
queueLimit: 0,
|
||||
multipleStatements: true,
|
||||
timezone: '+08:00' // 強制時區一致
|
||||
});
|
||||
|
||||
// 啟動測試
|
||||
pool.getConnection()
|
||||
.then(conn => {
|
||||
console.log('✅ MariaDB Connected Successfully.');
|
||||
conn.release();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('❌ MariaDB Connection Failed:', err.message);
|
||||
});
|
||||
|
||||
module.exports = pool;
|
||||
@@ -0,0 +1,49 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
const schedule = require('node-schedule');
|
||||
const db = require('../config/db');
|
||||
const logger = require('../utils/logger'); // 必須引入!
|
||||
|
||||
class MailService {
|
||||
constructor() {
|
||||
this.transporter = nodemailer.createTransport({
|
||||
host: "smtp.gmail.com",
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PASS }
|
||||
});
|
||||
}
|
||||
|
||||
async initAutoMail() {
|
||||
// 每 30 秒執行一次
|
||||
schedule.scheduleJob('*/30 * * * * *', async () => {
|
||||
try {
|
||||
// 增加一個簡單的連線檢查,避免資料庫掛掉時噴錯
|
||||
const [rows] = await db.query('SELECT * FROM zen_mail_list WHERE mail_status = 0 LIMIT 10');
|
||||
|
||||
if (rows.length > 0) {
|
||||
logger.info(`AutoMail: Processing ${rows.length} messages.`);
|
||||
}
|
||||
|
||||
for (let row of rows) {
|
||||
try {
|
||||
await this.transporter.sendMail({
|
||||
from: process.env.MAIL_USER,
|
||||
to: row.recipient,
|
||||
subject: row.mail_subject,
|
||||
html: row.content
|
||||
});
|
||||
await db.query('UPDATE zen_mail_list SET mail_status = 1, sent_at = NOW() WHERE id = ?', [row.id]);
|
||||
} catch (mailErr) {
|
||||
logger.error(`Mail ID ${row.id} failed: ${mailErr.message}`);
|
||||
await db.query('UPDATE zen_mail_list SET mail_status = 9, error_log = ? WHERE id = ?', [mailErr.message, row.id]);
|
||||
}
|
||||
}
|
||||
} catch (dbErr) {
|
||||
// 這裡現在可以正確使用 logger 了
|
||||
logger.error(`AutoMail Job DB Failure: ${dbErr.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new MailService();
|
||||
@@ -0,0 +1,516 @@
|
||||
/**
|
||||
* ORM Service for MariaDB
|
||||
* 功能:處理 amis 請求參數、解析核心 ORM 邏輯、呼叫 MariaDB Stored Procedures
|
||||
* Author: Senior Engineer Refactor
|
||||
*/
|
||||
|
||||
const db = require('../config/db');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
class OrmService {
|
||||
/**
|
||||
* 執行 Stored Procedure 並回傳 amis 指定格式
|
||||
* @param {string} spName - SP 名稱
|
||||
* @param {Array} params - 參數陣列
|
||||
* @param {number|string} dsNo - 回傳格式代號 (2, 3, 8, 20)
|
||||
*/
|
||||
async executeSp(spName, params, dsNo) {
|
||||
if (spName === "orm_api") {
|
||||
spName = "orm_api_v2"
|
||||
}
|
||||
|
||||
// 1. 強制確保 params 是陣列,避免 null.map 或 undefined.map 導致當機
|
||||
const safeParams = Array.isArray(params) ? params : [];
|
||||
|
||||
// MariaDB SP 呼叫語法: CALL sp_name(?, ?, ...)
|
||||
const placeholders = safeParams.map(() => '?').join(',');
|
||||
const sql = (safeParams.length > 0 || spName != "zen_null")
|
||||
? `CALL ${spName}(${placeholders})`
|
||||
: `CALL ${spName}()`; // 無參數時補上括號
|
||||
|
||||
// debug & hard code
|
||||
/*if (spName === "zen_null") {
|
||||
sql = `CALL ${spName}()`;
|
||||
}*/
|
||||
// 配合 amis 特殊執行 js 需透過呼叫 api, 當 dsNo=11 時,就直接 return
|
||||
if (dsNo === 11 || spName === "zen_null") {
|
||||
/*return {
|
||||
status: 0,
|
||||
code: 0,
|
||||
msg: 'ok',
|
||||
data: ''
|
||||
};*/
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- 除錯 Log 啟動 ---
|
||||
const startTime = Date.now();
|
||||
// 將參數轉為字串方便顯示,若是長字串(JSON)則截斷或完整呈現
|
||||
const paramsDebug = JSON.stringify(params);
|
||||
logger.info(`[SQL EXECUTE] Query: ${sql} | Params: ${paramsDebug}`);
|
||||
// --------------------
|
||||
|
||||
try {
|
||||
const [resultSets] = await db.query(sql, params);
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
// 取得結果集與狀態 (MariaDB 回傳格式處理)
|
||||
const status = resultSets[0] && resultSets[0][0] ? resultSets[0][0] : { code: 0, msg: 'Success', recno: 0 };
|
||||
const dataSet = resultSets[1] || [];
|
||||
|
||||
logger.info(`[SQL SUCCESS] SP: ${spName} | Time: ${duration}ms | Status: ${status.code}`);
|
||||
|
||||
// 根據 dsNo 格式化回傳內容給前端 amis
|
||||
const dsNoInt = parseInt(dsNo);
|
||||
switch (dsNoInt) {
|
||||
//新增 0,8,9 都是為了配合 amis 的格式需求, 0 是載入程式執行, 配合 genCode, 9 是為了顯示 json
|
||||
case 0: // CRUD 列表格式
|
||||
return {
|
||||
status: status.code,
|
||||
code: status.code,
|
||||
msg: status.msg,
|
||||
data: dataSet[0] ? (typeof dataSet[0].jsoncode === 'string' ? JSON.parse(dataSet[0].jsoncode) : dataSet[0].jsoncode) : {}
|
||||
};
|
||||
|
||||
case 1: // 原始結果集
|
||||
return resultSets;
|
||||
|
||||
case 3: // CRUD 列表格式
|
||||
return {
|
||||
status: status.code === 0 ? 0 : status.code,
|
||||
code: status.code === 0 ? 0 : status.code,
|
||||
msg: status.msg,
|
||||
data: {
|
||||
items: dataSet,
|
||||
total: status.recno || dataSet.length
|
||||
}
|
||||
};
|
||||
|
||||
case 5: // Get 使用, 固定回傳 xValue
|
||||
return {
|
||||
status: status.code,
|
||||
code: status.code,
|
||||
msg: status.msg,
|
||||
data: {
|
||||
"xValue": dataSet[0] ? dataSet[0].xValue : null
|
||||
}
|
||||
};
|
||||
|
||||
case 6: // 動態 options 回傳
|
||||
//const field6 = returnField || 'options';
|
||||
const field6 = 'options';
|
||||
return {
|
||||
status: status.code,
|
||||
code: status.code,
|
||||
msg: status.msg,
|
||||
data: {
|
||||
[field6]: dataSet[0] ? (typeof dataSet[0].jsoncode === 'string' ? JSON.parse(dataSet[0].jsoncode) : dataSet[0].jsoncode) : []
|
||||
}
|
||||
};
|
||||
|
||||
case 7: // Query Report 使用
|
||||
return {
|
||||
status: status.code,
|
||||
code: status.code,
|
||||
msg: status.msg,
|
||||
data: {
|
||||
"count": status.recno,
|
||||
"items": dataSet[0] ? (typeof dataSet[0].query === 'string' ? JSON.parse(dataSet[0].query) : dataSet[0].query) : []
|
||||
}
|
||||
};
|
||||
|
||||
case 8: // Options 選項格式 (Select/Checkboxes)
|
||||
return {
|
||||
status: 0,
|
||||
code: 0,
|
||||
msg: status.msg,
|
||||
data: { options: dataSet }
|
||||
};
|
||||
|
||||
case 9: // 動態 config 回傳
|
||||
//const field9 = returnField || 'config';
|
||||
const field9 = 'config';
|
||||
return {
|
||||
status: status.code,
|
||||
code: status.code,
|
||||
msg: status.msg,
|
||||
data: {
|
||||
[field9]: dataSet[0] ? (typeof dataSet[0].jsoncode === 'string' ? JSON.parse(dataSet[0].jsoncode) : dataSet[0].jsoncode) : {}
|
||||
}
|
||||
};
|
||||
|
||||
case 20: // 單筆資料格式 (Form Edit)
|
||||
return {
|
||||
status: 0,
|
||||
code: 0,
|
||||
msg: status.msg,
|
||||
data: dataSet[0] || {}
|
||||
};
|
||||
|
||||
case 2: // 標準格式
|
||||
default:
|
||||
return {
|
||||
status: 0,
|
||||
code: 0,
|
||||
msg: status.msg,
|
||||
recno: status.recno,
|
||||
data: dataSet
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`[SQL ERROR] SP: ${spName} | Error: ${err.message} | Params: ${JSON.stringify(params)}`);
|
||||
return { status: 500, msg: `DB Error: ${err.message}`, data: null };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析核心 ORM 邏輯 (最佳化原本 index.js 內的字串切割部分)
|
||||
* 將 ^ 分隔符號轉換為 MariaDB 易於處理的 JSON 格式
|
||||
*/
|
||||
// 修改為接收 req 物件,以便抓取 IP
|
||||
prepareOrmPayload(req) {
|
||||
const body = req.body;
|
||||
let spName = req.params.name;
|
||||
|
||||
// --- 情況 A: 固定格式的 orm_api 或 checklogin ---
|
||||
if (spName === 'orm_api' || spName === 'orm_api_v2') {
|
||||
logger.info(`[MODE] Fixed Mapping for ${spName}`);
|
||||
spName = 'orm_api_v2';
|
||||
|
||||
// 呼叫 orm_kernel 取得處理後的 orm_data
|
||||
let ret_orm_data = this.orm_kernel(req);
|
||||
logger.info(`[ret_orm_data] ${ret_orm_data}`);
|
||||
|
||||
// 回傳 5 個參數給 CALL orm_api(?,?,?,?,?)
|
||||
return [
|
||||
body.token || '',
|
||||
body._$_tableName || '',
|
||||
body._$_pk || '',
|
||||
body._$_action || '',
|
||||
ret_orm_data // 這就是第 5 個參數 body.paras
|
||||
];
|
||||
}
|
||||
|
||||
// For Excel Import(TBD)
|
||||
if (spName === 'sys_excel_import') {
|
||||
const importDbname = req.body.db_name || dbname;
|
||||
const orm_table = req.body.table_name;
|
||||
const orm_pk = req.body.pk;
|
||||
const orm_action = 'C';
|
||||
const orm_data = JSON.stringify(req.body);
|
||||
|
||||
spName = 'orm_api_v2';
|
||||
|
||||
return [
|
||||
body.token || '',
|
||||
orm_table,
|
||||
orm_pk,
|
||||
orm_action,
|
||||
orm_data // 這就是第 5 個參數 body.paras
|
||||
];
|
||||
}
|
||||
|
||||
// --- 情況 B: 呼叫任意 SP (動態參數模式) ---
|
||||
// 排除所有系統保留字(以 _$_ 開頭或特定名稱),剩下的全部按順序傳入 SP
|
||||
// --- 1. 處理第一個固定參數: token ---
|
||||
const paraKeys = Object.keys(body).filter(key => {
|
||||
return key !== 'token' && !key.startsWith('_$_');
|
||||
});
|
||||
|
||||
// 字母排序確保順序一致 (para01 -> para02)
|
||||
paraKeys.sort();
|
||||
const orderedParas = paraKeys.map(key => body[key]);
|
||||
|
||||
// --- 2. 動態偵測是否有 token 參數 ---
|
||||
let finalParams = [];
|
||||
const hasToken = Object.prototype.hasOwnProperty.call(body, 'token');
|
||||
|
||||
if (hasToken) {
|
||||
// [模式 A] 有傳入 token:[token, para01, para02...]
|
||||
finalParams = [body.token || '', ...orderedParas];
|
||||
logger.info(`[PARAM_PREPARE] SP: ${spName} | Detected Token -> [token, ${paraKeys.join(', ')}]`);
|
||||
} else {
|
||||
// [模式 B] 沒有傳入 token:[para01, para02...]
|
||||
finalParams = [...orderedParas];
|
||||
logger.info(`[PARAM_PREPARE] SP: ${spName} | No Token Field -> [${paraKeys.join(', ')}]`);
|
||||
}
|
||||
|
||||
// 3. 統一進行空參數檢查 (防護機制)
|
||||
if (finalParams.length === 0) {
|
||||
finalParams = [];
|
||||
logger.warn(`[PARAM_CHECK] No parameters found for ${spName}. Array is empty.`);
|
||||
}
|
||||
|
||||
// 以下程式是因為 mariadb sp 的參數不支援 default, 為了防錯加入的 20250808
|
||||
// get_treedata 應傳入2個參數
|
||||
if (spName === 'get_treedata' || spName === 'get_assignData') {
|
||||
finalParams = [...orderedParas, "''"];
|
||||
}
|
||||
// get_treedata 應傳入3個參數
|
||||
//if (spName === 'zen_showsource') {
|
||||
// finalParams = [...orderedParas, "''"];
|
||||
//}
|
||||
|
||||
return finalParams;
|
||||
}
|
||||
|
||||
// null 回傳空白
|
||||
isnull(str) {
|
||||
// 檢查是否為 null, undefined 或空字串
|
||||
if (str === null || str === undefined || str === '') {
|
||||
return '';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
//先用 ~ 代替單引號, 在 orm_api 再 replace, 否則無法傳參
|
||||
quotstr2(str) {
|
||||
return '~' + str + '~';
|
||||
}
|
||||
|
||||
|
||||
//// copy from ms-sql version code 2026/01/04
|
||||
/**
|
||||
* 核心字串與物件處理邏輯
|
||||
* 本程式目前是完全照搬, 用 Gemini 優化都會有問題(轉換邏輯不全), 日後在想辦法優化
|
||||
*/
|
||||
orm_kernel(req) {
|
||||
let orm_pageNo = req.body.page || '1';
|
||||
let orm_perPage = req.body.perPage || '';
|
||||
let orm_keywords = req.body.keywords || '';
|
||||
let orm_keyfield = req.body._field_ || '';
|
||||
let orm_keyid = req.body._id_ || '';
|
||||
let orm_where = '';
|
||||
//
|
||||
let orm_table = req.body._$_tableName;
|
||||
let orm_pk = req.body._$_pk;
|
||||
let orm_action = req.body._$_action;
|
||||
let orm_data = '';
|
||||
//
|
||||
let i = 0;
|
||||
//
|
||||
let frmQ1 = '';
|
||||
let frmQ2 = '';
|
||||
let frmQ3 = '';
|
||||
let ret_sql= '';
|
||||
|
||||
//debug
|
||||
// console.log(req.body);
|
||||
// logger.info(`[PARAMS-orm_data] ${req.body}`);
|
||||
|
||||
/* orm 改用此種方式, 但 token 該如何處理 ??
|
||||
新機制: 加入 token, 為了限制查詢的標準功能
|
||||
*/
|
||||
//orm_table = req.body._$_tableName;
|
||||
if (req.body.token) {
|
||||
orm_table = req.body.token + '^' + req.body._$_tableName;
|
||||
} else {
|
||||
orm_table = req.body._$_tableName;
|
||||
}
|
||||
|
||||
orm_pk = req.body._$_pk;
|
||||
orm_action = req.body._$_action;
|
||||
|
||||
//debug
|
||||
//logger.info(`[debug-_query_filter] ${req.body._$_query_filter}`);
|
||||
|
||||
if (req.body._$_query_filter) {
|
||||
//換頁次
|
||||
if (req.body.page) {
|
||||
orm_pageNo = req.body.page;
|
||||
}
|
||||
if (req.body.perPage) {
|
||||
orm_perPage = req.body.perPage;
|
||||
}
|
||||
if (req.body.keywords) {
|
||||
orm_keywords = req.body.keywords;
|
||||
}
|
||||
// _id_ 是網址傳遞參數用, 當作 _query_filter 的第 9/10 個參數(where 的一部分)
|
||||
if (req.body._field_) {
|
||||
orm_keyfield = req.body._field_;
|
||||
}
|
||||
if (req.body._id_) {
|
||||
orm_keyid = req.body._id_;
|
||||
}
|
||||
|
||||
//debug
|
||||
logger.info(`[debug-_frmQueryFlds_] ${req.body._$_frmQueryFlds_}`);
|
||||
logger.info(`[debug-_frmQueryData_] ${req.body._$_frmQueryData_}`);
|
||||
logger.info(`[debug-_frmQueryType_] ${req.body._$_frmQueryType_}`);
|
||||
|
||||
/***********************************
|
||||
擴充 where 支援 Form Header 的多欄位查詢條件, 稍微複雜的表單, 應該都使用多欄位查詢, 而非單一 Query
|
||||
前端 post 進來時, 增加 3個 _$_開頭的變數
|
||||
req.body._$_frmQueryFlds_ exp: bildate^personid^productid
|
||||
req.body._$_frmQueryData_ exp: 20220101,20221231^A01^P001
|
||||
req.body._$_frmQueryType_ exp: C^C^C --> C=字串 要前後單引號 I=數字,不加單引號 C 和 I 後面可以加 command, 如 Clike 表示 %%, I>= 表示 >= 該值
|
||||
Michael 2022/4/23
|
||||
************************************/
|
||||
if (req.body._$_frmQueryFlds_) {
|
||||
frmQ1 = req.body._$_frmQueryFlds_;
|
||||
frmQ2 = req.body._$_frmQueryData_;
|
||||
frmQ3 = req.body._$_frmQueryType_;
|
||||
|
||||
// debug
|
||||
// console.log(frmQ2);
|
||||
|
||||
if (frmQ1 != '') {
|
||||
let qryArr1 = frmQ1.split("^");
|
||||
let qryArr2 = frmQ2.split("^");
|
||||
let qryArr3 = frmQ3.split("^");
|
||||
|
||||
for (i = 0; i < qryArr1.length; i++) {
|
||||
let v_filter = qryArr3[i].substring(1);
|
||||
if (qryArr3[i].substr(0, 1) == 'C') {
|
||||
if (qryArr2[i] != '' && qryArr2[i] != '0') {
|
||||
//if (qryArr2[i] != '') {
|
||||
//Date Range (between)
|
||||
let qryArr4 = qryArr2[i].split(",");
|
||||
if (qryArr4.length > 1) {
|
||||
orm_where = orm_where + qryArr1[i] + ' >= ' + this.quotstr2(qryArr4[0]) + ' and ' + qryArr1[i] + ' <= ' + this.quotstr2(qryArr4[1]) + (i == qryArr1.length - 1 ? '' : ' and ');
|
||||
}
|
||||
else if (v_filter == 'like') {
|
||||
orm_where = orm_where + qryArr1[i] + ' like ' + this.quotstr2('%' + qryArr2[i] + '%') + (i == qryArr1.length - 1 ? '' : ' and ');
|
||||
} else {
|
||||
orm_where = orm_where + qryArr1[i] + ' = ' + this.quotstr2(qryArr2[i]) + (i == qryArr1.length - 1 ? '' : ' and ');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (qryArr2[i] != '') {
|
||||
if (v_filter == '') {
|
||||
orm_where = orm_where + qryArr1[i] + '=' + qryArr2[i] + (i == qryArr1.length - 1 ? '' : ' and ');
|
||||
} else {
|
||||
orm_where = orm_where + qryArr1[i] + v_filter + qryArr2[i] + (i == qryArr1.length - 1 ? '' : ' and ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//去掉後5碼的 and
|
||||
if (orm_where.substr(orm_where.length - 5, 5) == ' and ') {
|
||||
orm_where = orm_where.substr(0, orm_where.length - 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
/****************************************************/
|
||||
|
||||
orm_data = req.body._$_query_filter;
|
||||
const myArr = orm_data.split("^");
|
||||
//特別處理一下 orm_keywords, 1st=* 2st=myArr[7] 3st=非空的orm_keywords
|
||||
if (orm_keywords == '*') {
|
||||
orm_keywords = '';
|
||||
} else if (this.isnull(myArr[7]) != '') {
|
||||
//防錯(在特殊情況會誤傳, 在 ${ls:query_id} 機制)
|
||||
if (myArr[7] == '{$ls:token}') {
|
||||
orm_keywords = '';
|
||||
} else {
|
||||
orm_keywords = myArr[7];
|
||||
}
|
||||
}
|
||||
|
||||
// where keyword
|
||||
if (orm_where == '' || !orm_where) {
|
||||
orm_where = '';
|
||||
}
|
||||
|
||||
if (this.isnull(myArr[4]) != '' && orm_where == '') {
|
||||
orm_where = myArr[4];
|
||||
}
|
||||
|
||||
if (orm_perPage == '' || !orm_perPage) {
|
||||
orm_perPage = myArr[1];
|
||||
if (!orm_perPage) { orm_perPage = 10; }
|
||||
}
|
||||
|
||||
// 表單互 call 時, 傳遞參數也會借用此機制, 所以多加一個空值判斷 2023/1/28 Michael
|
||||
if (this.isnull(myArr[8]) != '' && this.isnull(myArr[9]) != '' && orm_keyfield == '' && orm_keyid == '') {
|
||||
orm_keyfield = myArr[8];
|
||||
orm_keyid = myArr[9];
|
||||
}
|
||||
|
||||
// debug to see the content
|
||||
// logger.info(`[PARAMS-orm_data] ${orm_data}`);
|
||||
|
||||
//前端傳入 sample: 1^10^functiontag^*^^^functiontag,tablename,fieldname^abc01^functiontag^abc01
|
||||
orm_data = orm_pageNo + '^' + orm_perPage + '^' + myArr[2] + '^' + myArr[3] + '^' + orm_where + '^' + this.isnull(myArr[5]) + '^' + this.isnull(myArr[6]) + '^' + orm_keywords + '^' + orm_keyfield + '^' + orm_keyid;
|
||||
} else //if (spName == 'orm_api') {
|
||||
{
|
||||
//專門處理 details 的 Object 問題(amis 的 joinValues Issue)
|
||||
/*
|
||||
if (req.body.details) {
|
||||
for (let i = 0; i < req.body.details.length; i++) {
|
||||
// 先 hard code productid, 確認無誤後, 再想辦法改為通用 20230828 Michael
|
||||
if (typeof req.body.details[i].productid === 'object' && req.body.details[i].productid.productid !== null) {
|
||||
req.body.details[i].productid = req.body.details[i].productid.productid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orm_api_v2 主要是改為用 parseJSON 分析傳入的 json
|
||||
主要是 amis 的 details 或是 picker 要多選取回時, json 的內容要在特殊處理
|
||||
改為 parseJSON 因為會寫入暫存, 相關比較好控制, 但還是要仔細測試過, 目前仍在測試中 20240127
|
||||
*/
|
||||
|
||||
// 先 hard code productid, 確認無誤後, 再想辦法改為通用 20230828 Michael (下列程式碼待測試中)
|
||||
let obj_id = {};
|
||||
let obj_id2 = '';
|
||||
let obj_id3 = {};
|
||||
//console.log(obj_id);
|
||||
//quickEdit donot execute
|
||||
console.log(Object.keys(req.body));
|
||||
console.log(req.body.hasOwnProperty("config"));
|
||||
if (Object.keys(req.body)[1] === 'rowsDiff' || Object.keys(req.body)[3] === 'rowsOrigin') {
|
||||
// break
|
||||
console.log('quickEdit');
|
||||
//} else if (Object.keys(req.body)[0] === 'config' || Object.keys(req.body)[6] === 'config') {
|
||||
} else if (req.body.hasOwnProperty("config") || req.body.hasOwnProperty("options")) {
|
||||
// break
|
||||
console.log('config-children or options');
|
||||
} else if (Object.keys(req.body)[0] === '0' || req.body._$_tableName === 'zen_fields') {
|
||||
// break
|
||||
console.log('zen_import_fields orm write error');
|
||||
} else {
|
||||
for (let i = 0; i < Object.keys(req.body).length; i++) {
|
||||
//console.log( Object.keys(req.body)[i]);
|
||||
|
||||
if (Object.keys(req.body)[i] == 'details') {
|
||||
//console.log( Object.keys(req.body.details));
|
||||
for (let j = 0; j < Object.keys(req.body.details).length; j++) {
|
||||
// 先 hard code productid, 確認無誤後, 再想辦法改為通用 20230828 Michael
|
||||
obj_id = req.body.details[Object.keys(req.body.details)[j]];
|
||||
//console.log(obj_id);
|
||||
for (let k = 0; k < Object.keys(obj_id).length; k++) {
|
||||
obj_id3 = obj_id[Object.keys(obj_id)[k]];
|
||||
if (typeof obj_id3 === 'object') {
|
||||
obj_id2 = Object.keys(obj_id3)[0];
|
||||
//console.log(obj_id2);
|
||||
if (obj_id2 !== null) {
|
||||
//console.log(obj_id2);
|
||||
req.body.details[j][obj_id2] = req.body.details[j][obj_id2][obj_id2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj_id = req.body[Object.keys(req.body)[i]];
|
||||
|
||||
if (typeof obj_id === 'object') {
|
||||
obj_id2 = Object.keys(obj_id)[0];
|
||||
//console.log(obj_id2);
|
||||
if (obj_id2 !== null) {
|
||||
//req.body[Object.keys(req.body)[i]] = req.body[Object.keys(req.body)[i]][obj_id2];
|
||||
req.body[obj_id2] = req.body[obj_id2][obj_id2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
orm_data = JSON.stringify(req.body);
|
||||
}
|
||||
//
|
||||
return orm_data;
|
||||
}
|
||||
////
|
||||
}
|
||||
|
||||
module.exports = new OrmService();
|
||||
@@ -0,0 +1,32 @@
|
||||
const multer = require('multer');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const { dbname, typeid } = req.params;
|
||||
const uploadPath = path.join(__dirname, `../../public/upload/${dbname}/${typeid}`);
|
||||
fs.mkdirSync(uploadPath, { recursive: true });
|
||||
cb(null, uploadPath);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const dateStr = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
cb(null, `${dateStr}-${file.originalname}`);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage,
|
||||
limits: { fileSize: parseInt(process.env.MAX_FILE_SIZE) || 50 * 1024 * 1024 },
|
||||
fileFilter: (req, file, cb) => {
|
||||
// 禁止上傳危險副檔名
|
||||
const forbiddenExtensions = ['.exe', '.sh', '.php', '.js', '.bat'];
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
if (forbiddenExtensions.includes(ext)) {
|
||||
return cb(new Error('File type not allowed'), false);
|
||||
}
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = upload;
|
||||
@@ -0,0 +1,44 @@
|
||||
const winston = require('winston');
|
||||
require('winston-daily-rotate-file');
|
||||
const path = require('path');
|
||||
|
||||
const logFormat = winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||
winston.format.printf(info => `${info.timestamp} [${info.level.toUpperCase()}]: ${info.message}`)
|
||||
);
|
||||
|
||||
// 設定檔案滾動 (每天產生一個新檔,保留 14 天)
|
||||
const dailyRotateFileTransport = new winston.transports.DailyRotateFile({
|
||||
filename: 'logs/application-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
format: logFormat
|
||||
});
|
||||
|
||||
const errorFileTransport = new winston.transports.DailyRotateFile({
|
||||
level: 'error',
|
||||
filename: 'logs/error-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '30d',
|
||||
format: logFormat
|
||||
});
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
transports: [
|
||||
dailyRotateFileTransport,
|
||||
errorFileTransport,
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
logFormat
|
||||
)
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
module.exports = logger;
|
||||
Reference in New Issue
Block a user