fix timezone bug

This commit is contained in:
beabigegg
2025-09-21 11:37:39 +08:00
parent a408ce402d
commit 2a0b29402f
22 changed files with 1050 additions and 519 deletions

View File

@@ -2,6 +2,7 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, send_file, current_app, jsonify, abort
from flask_login import login_required, current_user
from datetime import datetime, timedelta
from utils.timezone import taiwan_now, format_taiwan_time
from models import TempSpec, db, Upload, SpecHistory
from utils import editor_or_admin_required, add_history_log, admin_required, send_email, process_recipients
from ldap_utils import get_ldap_group_members
@@ -27,7 +28,7 @@ def _generate_next_spec_code():
產生下一個暫時規範編號。
規則: PE + 民國年(3碼) + 月份(2碼) + 流水號(2碼)
"""
now = datetime.now()
now = taiwan_now()
roc_year = now.year - 1911
prefix = f"PE{roc_year}{now.strftime('%m')}"
@@ -54,7 +55,7 @@ def create_temp_spec():
if request.method == 'POST':
spec_code = _generate_next_spec_code()
form_data = request.form
now = datetime.now()
now = taiwan_now()
# 1. 在資料庫中建立紀錄
spec = TempSpec(
@@ -140,20 +141,25 @@ def edit_spec(spec_id):
doc_url = get_file_uri(doc_filename)
callback_url = url_for('temp_spec.onlyoffice_callback', spec_id=spec_id, _external=True)
# 2. 如果是在開發環境,將 URL 中的 localhost 替換為 Docker 可存取的地址
# 2. 修正容器間通訊的 URL使用正確的容器名稱
if '127.0.0.1' in doc_url or 'localhost' in doc_url:
# 同時修正 doc_url 和 callback_url
doc_url = doc_url.replace('127.0.0.1', 'host.docker.internal').replace('localhost', 'host.docker.internal')
callback_url = callback_url.replace('127.0.0.1', 'host.docker.internal').replace('localhost', 'host.docker.internal')
# 在 Docker Compose 環境中OnlyOffice 應該透過 nginx 存取 Flask 應用
doc_url = doc_url.replace('127.0.0.1:12013', 'panjit-tempspec-nginx:80').replace('localhost:12013', 'panjit-tempspec-nginx:80')
doc_url = doc_url.replace('127.0.0.1', 'panjit-tempspec-nginx').replace('localhost', 'panjit-tempspec-nginx')
callback_url = callback_url.replace('127.0.0.1:12013', 'panjit-tempspec-nginx:80').replace('localhost:12013', 'panjit-tempspec-nginx:80')
callback_url = callback_url.replace('127.0.0.1', 'panjit-tempspec-nginx').replace('localhost', 'panjit-tempspec-nginx')
# --- END: 修正文件下載與回呼的 URL ---
oo_secret = current_app.config['ONLYOFFICE_JWT_SECRET']
# 生成唯一的文件密鑰,包含更新時間戳
file_key = f"{spec.id}_{int(os.path.getmtime(doc_physical_path))}"
payload = {
"document": {
"fileType": "docx",
"key": f"{spec.id}_{int(os.path.getmtime(doc_physical_path))}",
"key": file_key,
"title": doc_filename,
"url": doc_url # <-- 使用修正後的 doc_url
},
@@ -161,7 +167,14 @@ def edit_spec(spec_id):
"editorConfig": {
"callbackUrl": callback_url, # <-- 使用修正後的回呼 URL
"user": { "id": str(current_user.id), "name": current_user.username },
"customization": { "autosave": True, "forcesave": True }
"customization": {
"autosave": True,
"forcesave": True,
"chat": False,
"comments": True,
"help": False
},
"mode": "edit"
}
}
@@ -181,22 +194,78 @@ def edit_spec(spec_id):
@temp_spec_bp.route('/onlyoffice-callback/<int:spec_id>', methods=['POST'])
def onlyoffice_callback(spec_id):
data = request.json
if data.get('status') == 2:
status = data.get('status')
# 記錄所有回調狀態以便調試
current_app.logger.info(f"OnlyOffice callback for spec {spec_id}: status={status}, data={data}")
# OnlyOffice 狀態說明:
# 0 - 文件未找到
# 1 - 文件編輯中
# 2 - 文件準備保存
# 3 - 文件保存中
# 4 - 文件已關閉,無變更
# 6 - 文件編輯中,但已強制保存
# 7 - 發生錯誤
if status in [2, 6]: # 文件需要保存或已強制保存
try:
response = requests.get(data['url'], timeout=10)
if 'url' not in data:
current_app.logger.error(f"OnlyOffice callback missing URL for spec {spec_id}")
return jsonify({"error": 1, "message": "Missing document URL"})
# 驗證 JWT Token (如果有)
token = data.get('token')
if token:
try:
oo_secret = current_app.config['ONLYOFFICE_JWT_SECRET']
jwt.decode(token, oo_secret, algorithms=['HS256'])
except jwt.InvalidTokenError:
current_app.logger.error(f"Invalid JWT token in OnlyOffice callback for spec {spec_id}")
return jsonify({"error": 1, "message": "Invalid token"})
# 修正 OnlyOffice 回調中的 URL 以供容器間通信使用
download_url = data['url']
# 將外部訪問 URL 轉換為容器間通信 URL
download_url = download_url.replace('localhost:12015', 'panjit-tempspec-onlyoffice:80')
download_url = download_url.replace('127.0.0.1:12015', 'panjit-tempspec-onlyoffice:80')
current_app.logger.info(f"Downloading updated document from: {data['url']} -> {download_url}")
response = requests.get(download_url, timeout=30)
response.raise_for_status()
# 保存文件
spec = TempSpec.query.get_or_404(spec_id)
doc_filename = f"{spec.spec_code}.docx"
file_path = os.path.join(current_app.static_folder, 'generated', doc_filename)
# 確保目錄存在
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'wb') as f:
f.write(response.content)
current_app.logger.info(f"Successfully saved updated document for spec {spec_id} to {file_path}")
# 更新資料庫中的修改時間
spec.updated_at = taiwan_now()
db.session.commit()
except requests.RequestException as e:
current_app.logger.error(f"Failed to download document from OnlyOffice for spec {spec_id}: {e}")
return jsonify({"error": 1, "message": f"Download failed: {str(e)}"})
except Exception as e:
current_app.logger.error(f"ONLYOFFICE callback error for spec {spec_id}: {e}")
current_app.logger.error(f"OnlyOffice callback error for spec {spec_id}: {e}")
return jsonify({"error": 1, "message": str(e)})
elif status == 1:
current_app.logger.info(f"Document {spec_id} is being edited")
elif status == 4:
current_app.logger.info(f"Document {spec_id} closed without changes")
elif status == 7:
current_app.logger.error(f"OnlyOffice error for document {spec_id}")
return jsonify({"error": 1, "message": "OnlyOffice reported an error"})
return jsonify({"error": 0})
# --- 其他既有路由 ---
@@ -251,7 +320,7 @@ def activate_spec(spec_id):
flash('您必須上傳一個檔案。', 'danger')
return redirect(url_for('temp_spec.activate_spec', spec_id=spec.id))
filename = secure_filename(f"{spec.spec_code}_signed_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf")
filename = secure_filename(f"{spec.spec_code}_signed_{taiwan_now().strftime('%Y%m%d%H%M%S')}.pdf")
upload_folder = os.path.join(BASE_DIR, current_app.config['UPLOAD_FOLDER'])
os.makedirs(upload_folder, exist_ok=True)
file_path = os.path.join(upload_folder, filename)
@@ -260,7 +329,7 @@ def activate_spec(spec_id):
new_upload = Upload(
temp_spec_id=spec.id,
filename=filename,
upload_time=datetime.now()
upload_time=taiwan_now()
)
db.session.add(new_upload)
@@ -313,7 +382,7 @@ def terminate_spec(spec_id):
spec.status = 'terminated'
spec.termination_reason = reason
spec.end_date = datetime.today().date()
spec.end_date = taiwan_now().date()
add_history_log(spec.id, '終止', f"原因: {reason}")
# --- Start of Dynamic Email Notification ---
@@ -382,7 +451,12 @@ def download_signed_pdf(spec_id):
@editor_or_admin_required
def extend_spec(spec_id):
spec = TempSpec.query.get_or_404(spec_id)
# 檢查展延次數限制最多展延2次
if spec.extension_count >= 2:
flash('此暫時規範已達展延次數上限2次無法再次展延。總效期已達90天上限。', 'danger')
return redirect(url_for('temp_spec.spec_list'))
if request.method == 'POST':
new_end_date_str = request.form.get('new_end_date')
uploaded_file = request.files.get('new_file')
@@ -399,7 +473,7 @@ def extend_spec(spec_id):
spec.extension_count += 1
spec.status = 'active'
filename = secure_filename(f"{spec.spec_code}_extension_{spec.extension_count}_{datetime.now().strftime('%Y%m%d')}.pdf")
filename = secure_filename(f"{spec.spec_code}_extension_{spec.extension_count}_{taiwan_now().strftime('%Y%m%d')}.pdf")
upload_folder = os.path.join(BASE_DIR, current_app.config['UPLOAD_FOLDER'])
os.makedirs(upload_folder, exist_ok=True)
file_path = os.path.join(upload_folder, filename)
@@ -408,7 +482,7 @@ def extend_spec(spec_id):
new_upload = Upload(
temp_spec_id=spec.id,
filename=filename,
upload_time=datetime.now()
upload_time=taiwan_now()
)
db.session.add(new_upload)