fix: create schema before postgres import
This commit is contained in:
@@ -190,56 +190,52 @@ jobs:
|
|||||||
|
|
||||||
- name: Health Check
|
- name: Health Check
|
||||||
if: success()
|
if: success()
|
||||||
run: |
|
uses: appleboy/ssh-action@v1.0.0
|
||||||
echo "🔍 执行健康检查..."
|
with:
|
||||||
|
host: ${{ secrets.SERVER_HOST || '172.16.74.149' }}
|
||||||
|
username: ${{ secrets.SERVER_USER || 'atai' }}
|
||||||
|
password: ${{ secrets.SERVER_PASSWORD || 'wengewudi666808' }}
|
||||||
|
port: ${{ secrets.SERVER_PORT || '22' }}
|
||||||
|
command_timeout: 10m
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
echo "🔍 执行健康检查..."
|
||||||
|
sleep 20
|
||||||
|
|
||||||
# 等待服务完全启动(延长等待时间)
|
for i in {1..10}; do
|
||||||
sleep 20
|
|
||||||
|
|
||||||
# 健康检查(增加重试次数和诊断信息)
|
|
||||||
for i in {1..10}; do
|
|
||||||
echo ""
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo "尝试 $i/10: 检查服务 ${{ env.HEALTH_CHECK_URL }}"
|
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
|
|
||||||
# 详细的curl诊断
|
|
||||||
HTTP_CODE=$(curl -v -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 10 ${{ env.HEALTH_CHECK_URL }} 2>&1)
|
|
||||||
echo "响应: $HTTP_CODE"
|
|
||||||
|
|
||||||
if echo "$HTTP_CODE" | grep -q "200\|301\|302"; then
|
|
||||||
echo "✅ 服务健康检查通过!HTTP状态码正常"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🎉 部署成功!服务已正常运行"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
exit 0
|
echo "尝试 ${i}/10: 检查服务 ${{ env.HEALTH_CHECK_URL }}"
|
||||||
fi
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
# 如果失败,显示更多诊断信息
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 --max-time 10 ${{ env.HEALTH_CHECK_URL }} || true)
|
||||||
if [ $i -eq 5 ]; then
|
echo "响应: ${HTTP_CODE}"
|
||||||
echo ""
|
|
||||||
echo "⚠️ 第5次尝试失败,执行深度诊断..."
|
if printf "%s" "$HTTP_CODE" | grep -qE "200|301|302"; then
|
||||||
echo ""
|
echo "✅ 服务健康检查通过!HTTP状态码正常"
|
||||||
echo "🔍 检查容器运行状态:"
|
echo ""
|
||||||
ssh -o StrictHostKeyChecking=no ${{ secrets.SERVER_USER || 'atai' }}@${{ secrets.SERVER_HOST || '172.16.74.149' }} "cd /home/atai/kt-financial-system && sudo docker-compose ps" || true
|
echo "🎉 部署成功!服务已正常运行"
|
||||||
echo ""
|
exit 0
|
||||||
echo "📝 最新容器日志:"
|
fi
|
||||||
ssh -o StrictHostKeyChecking=no ${{ secrets.SERVER_USER || 'atai' }}@${{ secrets.SERVER_HOST || '172.16.74.149' }} "cd /home/atai/kt-financial-system && sudo docker-compose logs --tail=50" || true
|
|
||||||
fi
|
if [ "$i" -eq 5 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ 第5次尝试失败,执行深度诊断..."
|
||||||
|
echo ""
|
||||||
|
echo "🔍 检查容器运行状态:"
|
||||||
|
cd /home/atai/kt-financial-system
|
||||||
|
sudo docker-compose ps || true
|
||||||
|
echo ""
|
||||||
|
echo "📝 最新容器日志:"
|
||||||
|
sudo docker-compose logs --tail=50 || true
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $i -lt 10 ]; then
|
|
||||||
echo "⏳ 等待6秒后重试..."
|
|
||||||
sleep 6
|
sleep 6
|
||||||
fi
|
done
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "❌ 服务健康检查失败:无法在多次重试后获得 200/301/302 响应"
|
||||||
echo "❌ 健康检查失败:10次尝试均未成功"
|
exit 1
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
echo ""
|
|
||||||
echo "🔍 最终诊断信息:"
|
|
||||||
ssh -o StrictHostKeyChecking=no ${{ secrets.SERVER_USER || 'atai' }}@${{ secrets.SERVER_HOST || '172.16.74.149' }} "cd /home/atai/kt-financial-system && sudo docker-compose ps && echo '---' && sudo docker-compose logs --tail=100" || true
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Send notification on success
|
- name: Send notification on success
|
||||||
if: success()
|
if: success()
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ function normalizeDate(rawValue, monthTracker, baseYear) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetFinanceTables(client) {
|
async function resetFinanceTables(client) {
|
||||||
|
await ensureSchema(client);
|
||||||
await client.query(`
|
await client.query(`
|
||||||
TRUNCATE TABLE
|
TRUNCATE TABLE
|
||||||
finance_transactions,
|
finance_transactions,
|
||||||
@@ -258,6 +259,165 @@ async function ensureCurrency(client, cache, code, name = code, symbol = code) {
|
|||||||
cache.currencies.add(code);
|
cache.currencies.add(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureSchema(client) {
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS finance_currencies (
|
||||||
|
code TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
symbol TEXT NOT NULL,
|
||||||
|
is_base BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS finance_exchange_rates (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
from_currency TEXT NOT NULL REFERENCES finance_currencies(code),
|
||||||
|
to_currency TEXT NOT NULL REFERENCES finance_currencies(code),
|
||||||
|
rate NUMERIC NOT NULL,
|
||||||
|
date DATE NOT NULL,
|
||||||
|
source TEXT DEFAULT 'manual'
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS finance_accounts (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
currency TEXT NOT NULL REFERENCES finance_currencies(code),
|
||||||
|
type TEXT DEFAULT 'cash',
|
||||||
|
icon TEXT,
|
||||||
|
color TEXT,
|
||||||
|
user_id INTEGER DEFAULT 1,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS finance_categories (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
icon TEXT,
|
||||||
|
color TEXT,
|
||||||
|
user_id INTEGER,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS finance_transactions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
amount NUMERIC NOT NULL,
|
||||||
|
currency TEXT NOT NULL REFERENCES finance_currencies(code),
|
||||||
|
exchange_rate_to_base NUMERIC NOT NULL,
|
||||||
|
amount_in_base NUMERIC NOT NULL,
|
||||||
|
category_id INTEGER REFERENCES finance_categories(id),
|
||||||
|
account_id INTEGER REFERENCES finance_accounts(id),
|
||||||
|
transaction_date DATE NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
project TEXT,
|
||||||
|
memo TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
status TEXT NOT NULL DEFAULT 'approved',
|
||||||
|
status_updated_at TIMESTAMP WITH TIME ZONE,
|
||||||
|
reimbursement_batch TEXT,
|
||||||
|
review_notes TEXT,
|
||||||
|
submitted_by TEXT,
|
||||||
|
approved_by TEXT,
|
||||||
|
approved_at TIMESTAMP WITH TIME ZONE,
|
||||||
|
is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
deleted_at TIMESTAMP WITH TIME ZONE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS finance_media_messages (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
chat_id BIGINT NOT NULL,
|
||||||
|
message_id BIGINT NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
username TEXT,
|
||||||
|
display_name TEXT,
|
||||||
|
file_type TEXT NOT NULL,
|
||||||
|
file_id TEXT NOT NULL,
|
||||||
|
file_unique_id TEXT,
|
||||||
|
caption TEXT,
|
||||||
|
file_name TEXT,
|
||||||
|
file_path TEXT NOT NULL,
|
||||||
|
file_size INTEGER,
|
||||||
|
mime_type TEXT,
|
||||||
|
duration INTEGER,
|
||||||
|
width INTEGER,
|
||||||
|
height INTEGER,
|
||||||
|
forwarded_to INTEGER,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
UNIQUE(chat_id, message_id)
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS telegram_notification_configs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
bot_token TEXT NOT NULL,
|
||||||
|
chat_id TEXT NOT NULL,
|
||||||
|
notification_types TEXT NOT NULL,
|
||||||
|
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
priority TEXT DEFAULT 'normal',
|
||||||
|
rate_limit_seconds INTEGER DEFAULT 0,
|
||||||
|
batch_enabled BOOLEAN DEFAULT FALSE,
|
||||||
|
batch_interval_minutes INTEGER DEFAULT 60,
|
||||||
|
retry_enabled BOOLEAN DEFAULT TRUE,
|
||||||
|
retry_max_attempts INTEGER DEFAULT 3,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS telegram_notification_history (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
config_id INTEGER NOT NULL REFERENCES telegram_notification_configs(id),
|
||||||
|
notification_type TEXT NOT NULL,
|
||||||
|
content_hash TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
retry_count INTEGER DEFAULT 0,
|
||||||
|
sent_at TIMESTAMP WITH TIME ZONE,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_finance_media_messages_created_at
|
||||||
|
ON finance_media_messages (created_at DESC);
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_finance_media_messages_user_id
|
||||||
|
ON finance_media_messages (user_id);
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_telegram_notification_configs_enabled
|
||||||
|
ON telegram_notification_configs (is_enabled);
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_config
|
||||||
|
ON telegram_notification_history (config_id, created_at DESC);
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_hash
|
||||||
|
ON telegram_notification_history (content_hash, created_at DESC);
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_telegram_notification_history_status
|
||||||
|
ON telegram_notification_history (status, retry_count);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureExchangeRate(
|
async function ensureExchangeRate(
|
||||||
client,
|
client,
|
||||||
cache,
|
cache,
|
||||||
@@ -463,6 +623,17 @@ async function importNewFormat(client, header, rows, cache) {
|
|||||||
throw new Error('CSV 表头缺少必需字段,无法导入新版格式数据');
|
throw new Error('CSV 表头缺少必需字段,无法导入新版格式数据');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await ensureCurrency(client, cache, 'CNY', '人民币', '¥');
|
||||||
|
await ensureExchangeRate(
|
||||||
|
client,
|
||||||
|
cache,
|
||||||
|
'CNY',
|
||||||
|
'CNY',
|
||||||
|
1,
|
||||||
|
'1970-01-01',
|
||||||
|
'system',
|
||||||
|
);
|
||||||
|
|
||||||
const transactions = [];
|
const transactions = [];
|
||||||
|
|
||||||
for (const columns of rows) {
|
for (const columns of rows) {
|
||||||
@@ -487,13 +658,7 @@ async function importNewFormat(client, header, rows, cache) {
|
|||||||
const accountIcon = resolveCurrencyIcon(currency);
|
const accountIcon = resolveCurrencyIcon(currency);
|
||||||
const accountColor = resolveCurrencyColor(currency);
|
const accountColor = resolveCurrencyColor(currency);
|
||||||
|
|
||||||
await ensureCurrency(
|
await ensureCurrency(client, cache, currency, currencyName, currencySymbol);
|
||||||
client,
|
|
||||||
cache,
|
|
||||||
currency,
|
|
||||||
currencyName,
|
|
||||||
currencySymbol,
|
|
||||||
);
|
|
||||||
await ensureExchangeRate(client, cache, currency, 'CNY', 1, date);
|
await ensureExchangeRate(client, cache, currency, 'CNY', 1, date);
|
||||||
|
|
||||||
const accountName = columns[indexMap.account]?.trim() || '默认账户';
|
const accountName = columns[indexMap.account]?.trim() || '默认账户';
|
||||||
|
|||||||
Reference in New Issue
Block a user