First Commit

This commit is contained in:
DATAEXPRESS\4734
2026-01-06 18:24:42 +08:00
commit 65f033adbc
37 changed files with 3010 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# --- Server 系統設定 ---
PORT=5060
NODE_ENV=development
# --- MariaDB 資料庫連線設定 ---
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD="1qazCDE#"
DB_NAME=eis_demo
# --- Mail 寄送設定 (以 Gmail 為例) ---
# 注意:若使用 Gmail,請務必開啟「兩步驟驗證」並申請「應用程式密碼」
MAIL_USER=kylinsys@gmail.com
MAIL_PASS=owgj hajw iuum txjm
# --- 檔案上傳設定 ---
UPLOAD_PATH=./public/upload
# 限制上傳大小 (100MB)
MAX_FILE_SIZE=104857600
# SSL 設定 (只有在 Node.js 直接處理 HTTPS 時需要)
ENABLE_HTTPS=true
SSL_KEY_PATH=./ssl/private.key
SSL_CRT_PATH=./ssl/certificate.crt
HTTPS_PORT=8033
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
Rem ************************************************
Rem 執行 Node.js 的 mysql Server 端程式
Rem ************************************************
cd C:\api\mariadb-v2
node server.js
pause
+1914
View File
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
{
"name": "mariadb-v2",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"helmet": "^8.1.0",
"morgan": "^1.10.1",
"multer": "^2.0.2",
"mysql2": "^3.16.0",
"node-schedule": "^2.1.1",
"nodemailer": "^7.0.12",
"winston": "^3.19.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"nodemon": "^3.1.11"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+41
View File
@@ -0,0 +1,41 @@
/my-api-v2
├── .env # 環境變數 (DB 帳密, Mail 帳密)
├── server.js # 主程式 (Entry Point)
├── src/
│ ├── config/
│ │ └── db.js # MariaDB 連線池設定
│ ├── services/
│ │ ├── ormService.js # 核心 ORM 與 SP 邏輯
│ │ ├── uploadService.js # 檔案上傳邏輯
│ │ └── mailService.js # 郵件與排程邏輯
│ └── utils/
│ └── helper.js # 通用工具函式 (quotstr, date format)
└── public/upload # 檔案上傳目錄
>> https://gemini.google.com/app/57091040afbd48e1
# 1. 初始化 npm 專案 (若尚未執行)
npm init -y
# 2. 安裝核心 Web 框架與安全套件
npm install express cors helmet dotenv
# 3. 安裝資料庫驅動 (MariaDB/MySQL)
npm install mysql2
# 4. 安裝檔案處理與上傳套件
npm install multer
# 5. 安裝郵件與排程套件
npm install nodemailer node-schedule
# 6. 安裝進階 Log 管理系統
npm install morgan winston winston-daily-rotate-file
# 7. (選用) 安裝開發環境自動重啟工具
npm install --save-dev nodemon
+98
View File
@@ -0,0 +1,98 @@
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const fs = require('fs');
const http = require('http');
const https = require('https');
const morgan = require('morgan');
const logger = require('./src/utils/logger');
require('dotenv').config();
const ormService = require('./src/services/ormService');
const mailService = require('./src/services/mailService');
const upload = require('./src/services/uploadService');
const app = express();
// 整合 Morgan 到 Winston
app.use(morgan('combined', {
stream: { write: message => logger.info(message.trim()) }
}));
// --- Middleware ---
const corsOptions = {
origin: [
'https://app.gex.com.tw', // 您的正式環境前端
'http://localhost:3000' // 本地開發測試用
],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
allowedHeaders: ['Content-Type', 'Authorization', 'token'], // 確保 token 能被傳送
credentials: true, // 如果需要帶 Cookie
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
app.use(helmet());
app.use(express.json({ limit: '100mb' }));
app.use(express.urlencoded({ extended: true, limit: '100mb' }));
//app.use('/static', express.static('public'));
app.use(express.static(__dirname + '/public')); //Serves resources from public folder
// 啟動郵件排程
mailService.initAutoMail().then(() => console.log('?? Mail Scheduler Started.'));
// --- Routes (保持不變) ---
// 支援 v1 (舊版) 與 v3 (新版)
const ormRoutes = ['/xapi/v1/:dbname/:name/:dsNo', '/xapi/v2/:dbname/:name/:dsNo', '/xapi/v3/:dbname/:name/:dsNo'];
app.post(ormRoutes, async (req, res, next) => {
try {
const params = ormService.prepareOrmPayload(req);
// 增加一個 log 觀察是哪個路徑進來的
logger.info(`Incoming Request: ${req.url} -> SP: ${req.params.name}`);
// logger.info(`Calling SP: ${req.params.name} by User: ${req.body.token || 'Guest'}`);
const result = await ormService.executeSp(req.params.name, params, req.params.dsNo);
res.json(result);
} catch (err) {
logger.error(`API Error on ${req.params.name}: ${err.message}`);
next(err);
}
});
app.post('/upload/:dbname/:typeid', upload.single('file'), (req, res) => {
if (!req.file) return res.status(400).json({ status: 400, msg: 'No file' });
const fileUrl = `/static/upload/${req.params.dbname}/${req.params.typeid}/${req.file.filename}`;
res.json({ status: 0, data: { value: fileUrl, url: fileUrl }, msg: 'success' });
});
// 全域錯誤處理
app.use((err, req, res, next) => {
res.status(err.status || 500).json({ status: err.status || 500, msg: err.message });
});
// --- Server 啟動邏輯 ---
const HTTP_PORT = process.env.PORT || 5050;
// 啟動標準 HTTP
http.createServer(app).listen(HTTP_PORT, () => {
console.log(`?? HTTP Server running on http://localhost:${HTTP_PORT}`);
});
// 根據 .env 決定是否啟動 HTTPS
if (process.env.ENABLE_HTTPS === 'true') {
try {
const options = {
key: fs.readFileSync(process.env.SSL_KEY_PATH),
cert: fs.readFileSync(process.env.SSL_CRT_PATH)
};
const HTTPS_PORT = process.env.HTTPS_PORT || 5443;
https.createServer(options, app).listen(HTTPS_PORT, () => {
console.log(`?? HTTPS Server running on https://localhost:${HTTPS_PORT}`);
});
} catch (error) {
console.error('? Failed to start HTTPS server (Check SSL paths):', error.message);
}
}
+27
View File
@@ -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;
+49
View File
@@ -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();
+516
View File
@@ -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();
+32
View File
@@ -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;
+44
View File
@@ -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;
+25
View File
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
+32
View File
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFgTCCBGmgAwIBAgIQOXJEOvkit1HX02wQ3TE1lTANBgkqhkiG9w0BAQwFADB7
MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE
AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTE5MDMxMjAwMDAwMFoXDTI4
MTIzMTIzNTk1OVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgSmVyc2V5
MRQwEgYDVQQHEwtKZXJzZXkgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBO
ZXR3b3JrMS4wLAYDVQQDEyVVU0VSVHJ1c3QgUlNBIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAgBJlFzYOw9sI
s9CsVw127c0n00ytUINh4qogTQktZAnczomfzD2p7PbPwdzx07HWezcoEStH2jnG
vDoZtF+mvX2do2NCtnbyqTsrkfjib9DsFiCQCT7i6HTJGLSR1GJk23+jBvGIGGqQ
Ijy8/hPwhxR79uQfjtTkUcYRZ0YIUcuGFFQ/vDP+fmyc/xadGL1RjjWmp2bIcmfb
IWax1Jt4A8BQOujM8Ny8nkz+rwWWNR9XWrf/zvk9tyy29lTdyOcSOk2uTIq3XJq0
tyA9yn8iNK5+O2hmAUTnAU5GU5szYPeUvlM3kHND8zLDU+/bqv50TmnHa4xgk97E
xwzf4TKuzJM7UXiVZ4vuPVb+DNBpDxsP8yUmazNt925H+nND5X4OpWaxKXwyhGNV
icQNwZNUMBkTrNN9N6frXTpsNVzbQdcS2qlJC9/YgIoJk2KOtWbPJYjNhLixP6Q5
D9kCnusSTJV882sFqV4Wg8y4Z+LoE53MW4LTTLPtW//e5XOsIzstAL81VXQJSdhJ
WBp/kjbmUZIO8yZ9HE0XvMnsQybQv0FfQKlERPSZ51eHnlAfV1SoPv10Yy+xUGUJ
5lhCLkMaTLTwJUdZ+gQek9QmRkpQgbLevni3/GcV4clXhB4PY9bpYrrWX1Uu6lzG
KAgEJTm4Diup8kyXHAc/DVL17e8vgg8CAwEAAaOB8jCB7zAfBgNVHSMEGDAWgBSg
EQojPpbxB+zirynvgqV/0DCktDAdBgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rID
ZsswDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAG
BgRVHSAAMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29t
L0FBQUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggr
BgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBDAUA
A4IBAQAYh1HcdCE9nIrgJ7cz0C7M7PDmy14R3iJvm3WOnnL+5Nb+qh+cli3vA0p+
rvSNb3I8QzvAP+u431yqqcau8vzY7qN7Q/aGNnwU4M309z/+3ri0ivCRlv79Q2R+
/czSAaF9ffgZGclCKxO/WIu6pKJmBHaIkU4MiRTOok3JMrO66BQavHHxW/BBC5gA
CiIDEOUMsfnNkjcZ7Tvx5Dq2+UUTJnWvu6rvP3t3O9LEApE9GQDTF1w52z97GA1F
zZOFli9d31kWTz9RvdVFGD/tSo7oBmF0Ixa1DVBzJ0RHfxBdiSprhTEUxOipakyA
vGp4z7h/jnZymQyd/teRCBaho1+V
-----END CERTIFICATE-----
+37
View File
@@ -0,0 +1,37 @@
-----BEGIN CERTIFICATE-----
MIIGizCCBPOgAwIBAgIRAMpp0hTVnTNA8nHekzJofCkwDQYJKoZIhvcNAQELBQAw
YDELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDE3MDUGA1UE
AxMuU2VjdGlnbyBQdWJsaWMgU2VydmVyIEF1dGhlbnRpY2F0aW9uIENBIERWIFIz
NjAeFw0yNTA3MTUwMDAwMDBaFw0yNjA3MTUyMzU5NTlaMBkxFzAVBgNVBAMTDmFw
aS5nZXguY29tLnR3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsofB
Hrxs0VV2pATNZeawwqC5tvuRlhURnLcm9EC9YtiSeeKjukg4kZpr5+hKiNh6lsvW
2smkLl37j+gTa0d7DMpUWNmEtmSUmMx0EvnWsDECIynW4PuZitjR/4VmeS9bxMI7
hsa+kQGnzfjvvCkofhwWLYCw0h4en1tGAQq4TlGcP4wZeGfyjfbdpCRyd5wHNiE9
F1Lh0S0URC+PLltOyQX2FaJLxUKnOrKLl5V7hQvgIDDPCAcySdWFcXAMmJpKeRHb
L3ThdWBxPH4OcYs9GJsVAWcO+cV0f++sBTozt+TgnfLFU5oGONqNB349tUts6Zu0
yg/KCGZTPHZvFXXjOQIDAQABo4IDBTCCAwEwHwYDVR0jBBgwFoAUaMASFhgOr872
h6YyV6NGUV3LBycwHQYDVR0OBBYEFFK96TipyR8phtAVR1EiYOtdDfmBMA4GA1Ud
DwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
BgEFBQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEW
F2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEE
eDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29Q
dWJsaWNTZXJ2ZXJBdXRoZW50aWNhdGlvbkNBRFZSMzYuY3J0MCMGCCsGAQUFBzAB
hhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAtBgNVHREEJjAkgg5hcGkuZ2V4LmNv
bS50d4ISd3d3LmFwaS5nZXguY29tLnR3MIIBfwYKKwYBBAHWeQIEAgSCAW8EggFr
AWkAdwDYCVU7lE96/8gWGW+UT4WrsPj8XodVJg8V0S5yu0VLFAAAAZgMbNSkAAAE
AwBIMEYCIQDGR0Nuw7OsH2o+TRAH7GwNPhkIe7lZWRs0ccnLIjgKcAIhANrIAcCy
TkXkE4wHYKgrXJSKVI5jJMjMKul4gdcLIpWDAHYArKswcGzr7IQx9BPS9JFfER5C
JEOx8qaMTzwrO6ceAsMAAAGYDGzUagAABAMARzBFAiB5nz6YPWn41YXMMArlvxPH
m6o6CEK7Ai7HrYNU5HIZigIhAMJ4KTxfg+kR6MWqP83f9P15Qyw16iJm3hByObEM
kV/yAHYA1219ENGn9XfCx+lf1wC/+YLJM1pl4dCzAXMXwMjFaXcAAAGYDGzUQwAA
BAMARzBFAiBo3c0jrE1ri4XLwNX0pjiksWCBJt0zRlNVyhnfU1UhOQIhAOHt4Vnd
mjno7yn7zUPOY95JrGXOYyCmA97qimNjAUL9MA0GCSqGSIb3DQEBCwUAA4IBgQCM
w8XCex+oGGatwFLFzctyUSGZ6i/19T4Bk1CzBvscLsn1xe0eVvT8bkaBwuzFJQN0
35q5FEXqIGlhsXIZYEeRAYe1xIHe8M03vWL8T1fPzqbCevSsZCOOljWOiaxeNAx9
2AUlV/iORzfW1Pirj8XRUrvF15EQ8Fg0DPdOb8K8FfOkgEAubn6omTSimP0gTzF1
KvMT87CqGuhIkr847/H+Hux7wpio4jy32QxMVIxVjUCP+Vp3QBPIjrErlfF9Qn8m
S8OCgQn3CcZPCB9lsONRRhDEjkvGhUXdZWED7A1XlgGl9odavdwr5cMadujmE/Do
XjlNod4V0C6sxH9JBfS/cV5scTzJZHuQs38h34FndVVQFgB9x2T1BVeDxrNeARJz
lvnNYQ6jQyitkSmy2R1F2Eflml8wB3pS2xWfuGAPPfvqdEk6Hl+H0xzU+GzLq14A
jZQt9wi59a8K47dcG/U0lT7nn5HdDbpj1xp7p6PJUPEsjCGsQ4BhKvUS8MyauUg=
-----END CERTIFICATE-----
+39
View File
@@ -0,0 +1,39 @@
-----BEGIN CERTIFICATE-----
MIIG1TCCBL2gAwIBAgIQbFWr29AHksedBwzYEZ7WvzANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMjAw
MTMwMDAwMDAwWhcNMzAwMTI5MjM1OTU5WjBLMQswCQYDVQQGEwJBVDEQMA4GA1UE
ChMHWmVyb1NTTDEqMCgGA1UEAxMhWmVyb1NTTCBSU0EgRG9tYWluIFNlY3VyZSBT
aXRlIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAhmlzfqO1Mdgj
4W3dpBPTVBX1AuvcAyG1fl0dUnw/MeueCWzRWTheZ35LVo91kLI3DDVaZKW+TBAs
JBjEbYmMwcWSTWYCg5334SF0+ctDAsFxsX+rTDh9kSrG/4mp6OShubLaEIUJiZo4
t873TuSd0Wj5DWt3DtpAG8T35l/v+xrN8ub8PSSoX5Vkgw+jWf4KQtNvUFLDq8mF
WhUnPL6jHAADXpvs4lTNYwOtx9yQtbpxwSt7QJY1+ICrmRJB6BuKRt/jfDJF9Jsc
RQVlHIxQdKAJl7oaVnXgDkqtk2qddd3kCDXd74gv813G91z7CjsGyJ93oJIlNS3U
gFbD6V54JMgZ3rSmotYbz98oZxX7MKbtCm1aJ/q+hTv2YK1yMxrnfcieKmOYBbFD
hnW5O6RMA703dBK92j6XRN2EttLkQuujZgy+jXRKtaWMIlkNkWJmOiHmErQngHvt
iNkIcjJumq1ddFX4iaTI40a6zgvIBtxFeDs2RfcaH73er7ctNUUqgQT5rFgJhMmF
x76rQgB5OZUkodb5k2ex7P+Gu4J86bS15094UuYcV09hVeknmTh5Ex9CBKipLS2W
2wKBakf+aVYnNCU6S0nASqt2xrZpGC1v7v6DhuepyyJtn3qSV2PoBiU5Sql+aARp
wUibQMGm44gjyNDqDlVp+ShLQlUH9x8CAwEAAaOCAXUwggFxMB8GA1UdIwQYMBaA
FFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBTI2XhootkZaNU9ct5fCj7c
tYaGpjAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUE
FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwIgYDVR0gBBswGTANBgsrBgEEAbIxAQIC
TjAIBgZngQwBAgEwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1
c3QuY29tL1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYG
CCsGAQUFBwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3Qu
Y29tL1VTRVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRw
Oi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQAVDwoIzQDV
ercT0eYqZjBNJ8VNWwVFlQOtZERqn5iWnEVaLZZdzxlbvz2Fx0ExUNuUEgYkIVM4
YocKkCQ7hO5noicoq/DrEYH5IuNcuW1I8JJZ9DLuB1fYvIHlZ2JG46iNbVKA3ygA
Ez86RvDQlt2C494qqPVItRjrz9YlJEGT0DrttyApq0YLFDzf+Z1pkMhh7c+7fXeJ
qmIhfJpduKc8HEQkYQQShen426S3H0JrIAbKcBCiyYFuOhfyvuwVCFDfFvrjADjd
4jX1uQXd161IyFRbm89s2Oj5oU1wDYz5sx+hoCuh6lSs+/uPuWomIq3y1GDFNafW
+LsHBU16lQo5Q2yh25laQsKRgyPmMpHJ98edm6y2sHUabASmRHxvGiuwwE25aDU0
2SAeepyImJ2CzB80YG7WxlynHqNhpE7xfC7PzQlLgmfEHdU+tHFeQazRQnrFkW2W
kqRGIq7cKRnyypvjPMkjeiV9lRdAM9fSJvsB3svUuu1coIG1xxI1yegoGM4r5QP4
RGIVvYaiI76C0djoSbQ/dkIUUXQuB8AL5jyH34g3BZaaXyvpmnV4ilppMXVAnAYG
ON51WhJ6W0xNdNJwzYASZYH+tmCWI+N60Gv2NNMGHwMZ7e9bXgzUCZH5FaBFDGR5
S9VWqHB73Q+OyIVvIbKYcSc2w/aSuFKGSA==
-----END CERTIFICATE-----
+37
View File
@@ -0,0 +1,37 @@
-----BEGIN CERTIFICATE-----
MIIGbDCCBFSgAwIBAgIRAP+xCJvA5ssP/6EmqA7p960wDQYJKoZIhvcNAQEMBQAw
SzELMAkGA1UEBhMCQVQxEDAOBgNVBAoTB1plcm9TU0wxKjAoBgNVBAMTIVplcm9T
U0wgUlNBIERvbWFpbiBTZWN1cmUgU2l0ZSBDQTAeFw0yMzA0MzAwMDAwMDBaFw0y
MzA3MjkyMzU5NTlaMBkxFzAVBgNVBAMTDmFwaS5nZXguY29tLnR3MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqrhI+MLi+rsUSGlPHIrPnJ4+SGnntnQn
6W1xbd5go2FOiU1Z0d8gfIo4acH9qO6sFSL8MwjREkeG2szHSoOONx2dDll+bioq
c5QrB0PsdRUgEX+xlvHgIbJjm4xiSgGVhlDAoK9fc9E+UMQa7uRIy01X8zjqYoNo
rgGlF6Nf4ffpedK48A9oflIiN1VsgsQLRR3yJlG7qaFR78qJd8aH9U+feZ9H/fIW
F9iOAEI0+Zpz6nRzc0zqinap4/+Gas+MSRcGkptX6Znw+NwVKMdPkSTmDSyXswU1
5Ns4wjCF2EqnAbMbuQqbOkV8c3AqwL/pZnadNVacW5s6NwRJpX16EwIDAQABo4IC
ezCCAncwHwYDVR0jBBgwFoAUyNl4aKLZGWjVPXLeXwo+3LWGhqYwHQYDVR0OBBYE
FAuVtNojbIzcY48XCw9/6Pmy03KaMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8E
AjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBJBgNVHSAEQjBAMDQG
CysGAQQBsjEBAgJOMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20v
Q1BTMAgGBmeBDAECATCBiAYIKwYBBQUHAQEEfDB6MEsGCCsGAQUFBzAChj9odHRw
Oi8vemVyb3NzbC5jcnQuc2VjdGlnby5jb20vWmVyb1NTTFJTQURvbWFpblNlY3Vy
ZVNpdGVDQS5jcnQwKwYIKwYBBQUHMAGGH2h0dHA6Ly96ZXJvc3NsLm9jc3Auc2Vj
dGlnby5jb20wggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEAdwCt9776fP8QyIudPZwe
PhhqtGcpXc+xDCTKhYY069yCigAAAYfRcwfDAAAEAwBIMEYCIQDocC5ZkoSr0GWX
GuLPEosM2pbIkYLyQz3EmEHNQUwdQQIhAOr+1q3jIRN1IxG8LMKwkB8Y71Oeisos
IiyaRmBD4NADAHYAejKMVNi3LbYg6jjgUh7phBZwMhOFTTvSK8E6V6NS61IAAAGH
0XMIHAAABAMARzBFAiEAssoYv0oEVSZraxpPjdyg29pNLREQcodN68Fl2Gy/+t8C
IBH8Z0ohdsXlfBzqI0PaylUK3+QmR7Gl67lkz/9QxlYyMBkGA1UdEQQSMBCCDmFw
aS5nZXguY29tLnR3MA0GCSqGSIb3DQEBDAUAA4ICAQByseVToYNy2DQgKfhA4uom
PDYbfYjfQT5xPlIk2JiXfeB34JxjVtO77hw4gfMlVi+TdzBj7tKzuz0V+pL4MhiW
hsHUwPtNZGUSdFAdLGlEE1x6s+Js38+nXTSiG+SDXGyOwQeoXDJtckOX03N1/CKa
/FM2T2HbyWziiW6erzHekkRrzOEXgQRt/b3pA1EYhj5bhG+bLmEDmfkeot0y2BvP
rBhkgcwpPqut7y0Jhfdo5KgBbWGYPjjWUQOzwldQMjovRnA0m77A5OpDZwOwfpbE
KhCK8sj5OQFKAC7SHLPmLkCRhW55OsSlRt+3o8szO/MfO51otR8kbtkbbYJcLRFq
6g7lVXGiVYEl63P+L/QqIZli8YUk0XijDIf9CZlbS/ZB41PKxJT3z/XrOrrkmaF+
QZFDOya4CBIeBdBAJcVkwf+rV1JvmYxxKSu0VHRiPSugwqT9TokOWUFUlJKapFu9
w3/JOlfeLgucH8M4tcwnmTyBQAkSSr8gSLgyVOLI+78z8YiLBSpnUYJ1tE4iw7WB
fjSN0V6wCO9iijbeMoZ56qO8bcbL+iRCSSKfjJFL77OegV6sZGqcrZ04zXD/7rAp
u6AytbEfxvrKE6xC3jJzUdVO7wstOBUcGaliVQ8bcFthU3XRYT/SI1CgsKCsbHQD
BNXcZTV0CZQEul/CRxyBqA==
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAqrhI+MLi+rsUSGlPHIrPnJ4+SGnntnQn6W1xbd5go2FOiU1Z
0d8gfIo4acH9qO6sFSL8MwjREkeG2szHSoOONx2dDll+bioqc5QrB0PsdRUgEX+x
lvHgIbJjm4xiSgGVhlDAoK9fc9E+UMQa7uRIy01X8zjqYoNorgGlF6Nf4ffpedK4
8A9oflIiN1VsgsQLRR3yJlG7qaFR78qJd8aH9U+feZ9H/fIWF9iOAEI0+Zpz6nRz
c0zqinap4/+Gas+MSRcGkptX6Znw+NwVKMdPkSTmDSyXswU15Ns4wjCF2EqnAbMb
uQqbOkV8c3AqwL/pZnadNVacW5s6NwRJpX16EwIDAQABAoIBAHqR9ypLWgWkfaqD
mV4nt2l+H7+D8e4INYOdpRu51m5W9bkUhRFufneMEXa05fb87SX7yVCm2tyh6At9
jPRNwbSGmXVtAZ38vh/fWIlN1h41vaFyqcFzBXWMC4HpZ8Q0oPMfVT3Iy+Jub6wz
MBcmZbE5ZZTl3zo9mNGdsrdm//dPBUvXFHPljRYJRgK8ZMD0NK+eWn7ynzYPtHBu
U/djjCUqoUGVokXPymdCc/FfD9FdLJo7d9b3udShrELCZA3nrk+COmgGFgoEiz7n
QDAG0MNMtRL3BXrpDkxoB3ZIQpe1eI996axRffdAPMOOGrNNLb/90IwTI4ZzaCgx
TWnF/pkCgYEA7pclED+YDBXz8f6D2UkA9P4UFv3c+JIdTHZ+vvyisTMJdEbgHgfy
NvzwYm+nHvbestb0sFV3X6y7LA5WetxeX0f7iCUSwo1/GmgO9u6QA5b1h758BZhh
auhhHq5GRwi4M3Ddzo5KzmBFCvt4jW8EM8Ahv+fujhNPGCaFc1LSIMcCgYEAty1S
hUgTdwECbmBLQi9Wg9DGPSBooOYhlJ9kll3I5oydLHb5LxxfSfLi17g4JXRVr/aJ
uUFzf0aLUgYiGH+H2I2cH2qAGeCw3P/gFXHUntUqBKb9yEb3rngahVViV+zWTPQY
gPSbscDC51jtzbkFBx4RF65v6Kr24T2xrrQaqFUCgYEAwsMK9FqVSbhZ7H8xLhxT
9am6yJHu4dKUrChGiq08OZ5J52s75Afkh1KvAAvYxoQLgr3J/G+oBKfCGtKRcbWW
K/1crORmjf5ndSzgml5fnS6Lu4WZRBAB5f1P2nW01KOfig5mYgjxs8oOUkq5O+sV
6RC7/CXeLIhClppLcbp34V0CgYEAqYJodexADwVBIbE8k6/IyMyfVpuyFws/khB6
feel7Su8mxQx/hWsXHtGzkestYRVczObOXNHFUXL0YjZboA9zvkku7HLf6b9JFYp
IM8o6h7q9kkuWnLJ64s7R52/FwyVN7s/Hb6+gZyXAI276n9IFtENEjQbXV0wfzmf
PsNuU80CgYAIrPJMTa3jTQyrq7Rl5KNQLvn5ciUmi2yM0sH6zDnYv0KDd/rbl2LB
KgrYI/UAzBySzmls/5Oxw0/2hyW+x7HQGSwgganUQ8o9f8t5lXwU+v6609adnHkN
GJV+CShfoDI9oIAIT/KehwC3V1tGcqkl2qYG3bWyAdfvuRpMviCrJw==
-----END RSA PRIVATE KEY-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCyh8EevGzRVXak
BM1l5rDCoLm2+5GWFRGctyb0QL1i2JJ54qO6SDiRmmvn6EqI2HqWy9bayaQuXfuP
6BNrR3sMylRY2YS2ZJSYzHQS+dawMQIjKdbg+5mK2NH/hWZ5L1vEwjuGxr6RAafN
+O+8KSh+HBYtgLDSHh6fW0YBCrhOUZw/jBl4Z/KN9t2kJHJ3nAc2IT0XUuHRLRRE
L48uW07JBfYVokvFQqc6souXlXuFC+AgMM8IBzJJ1YVxcAyYmkp5EdsvdOF1YHE8
fg5xiz0YmxUBZw75xXR/76wFOjO35OCd8sVTmgY42o0Hfj21S2zpm7TKD8oIZlM8
dm8VdeM5AgMBAAECggEAYOLHbVS4gUouHoj0pu5w0s7Cuoj/00EP08qZe0h+/tul
cjwKupdjC7GQCcTIXkeND7UDnqx7SyZ51PkPQNMD9kb/S1CQcyNWeS3LeFjZp7iQ
/mBAWsxv4SgVnuPYXr/Sv47hGx1UYgFR/bew8p4reXTonD0QuJbb64NvFDWrG+TJ
vG6lLuJ6UIjD/b7Xrm+SewmTCU4vo/ItQATmuMisDmqYbQsqfjyZO6FXl7kGCkbD
g9zSEiq9PzX9kf+fQpHBYSNi8vkxvkSzjPgX/kNG7rHPM0r66S1hKi1KhuFgiEdq
5YUrnFme0eGeg3aOyQrNDGCNjcoXELJvksw5DdAh2QKBgQDjN0Qpsy4wYJdvQj0T
G3/Yuo0R8LCHfj6ba+WQn1sLKLFywHwfiFlJXXBAKLVK919414E0CHlhN14SMTp6
DRHbFEqfk06OCfxi7Q41UJEJey7phbB//4Ak24NBba20Pk2qhycyWiuae9HTK2LW
fATxsUD3zQtcJv/m6U5s4YZbpwKBgQDJJZaKT/l5Bmj4x5qanjTZbZmfwBWVjBWE
6DlcptDlYjyJuhhe30YM6kwh79Qgsb5T42jHW+HzJbRwgFV2469yssEvnZ32xa2M
MHWzR57wL3KpwUldeCyP843ELPaVqUxyEcj12RM+d6EAa4nuohu5qRcxmyScrOqs
1nnZgXomHwKBgDFz/en1jcAJxev5RsG54oGRWNLmyAU1fPrm6PgpNu+e08BeFKXo
T0agh6XvNkYywRzUCtMAZSq7mkXrYQbfgVtldzh0H0za74u4fLct6eNeZD9/UYMK
YSGkhVTgQkZQqSbMc8OJzKO6/1guwTxdkdXo9nL4tdhAxMHFVcRs8YlRAoGAMiqO
vhYuGRIRMiLOG0vj0Gp2SxJkD7UtVMV6MPJl3R/AC3V/SIIU/Xmd3UTsOe86kb+A
zV/MHZFEzF4lco5cnpWYeuCrmfU5UGJxqHHvYOXkihPr0UT8kDZlIA06vp7Hsxde
cHpgQ08gt3MapoqFsatLXpLb8btqmAqxvx58sW8CgYBqrO1Uw3h0TBDiRhr3rUi0
AH08GHIDqGFdMDLM6FJuiFX+MEH6NdLIW+zZTLkO9JFGKXsjBpoSewZt09di+E2b
97xAnc5qvzlx4JNBahWjghp6kRZrIC0mFVQ+7VWvlFyiH8q+E6BRqxGpz5A3xdI7
DMGXTmNW+7LfmQZ6aNErsw==
-----END PRIVATE KEY-----