308 lines
11 KiB
Python
308 lines
11 KiB
Python
from waitress import serve
|
||
from flask import Flask, render_template, request, redirect, url_for, jsonify, session, send_from_directory, g
|
||
from flask_compress import Compress
|
||
from datetime import datetime
|
||
import os
|
||
import logging
|
||
import time
|
||
import random
|
||
from config import COMPANY_INFO, DATA_DIR, LOG_DIR
|
||
from languages import LANGUAGES, TRANSLATIONS, DEFAULT_LANGUAGE
|
||
import pandas as pd
|
||
|
||
app = Flask(__name__, template_folder='templates')
|
||
app.secret_key = os.urandom(24) # 设置会话密钥
|
||
|
||
# 初始化Gzip压缩
|
||
compress = Compress()
|
||
compress.init_app(app)
|
||
|
||
# 配置日志
|
||
if not os.path.exists(LOG_DIR):
|
||
os.makedirs(LOG_DIR)
|
||
|
||
logging.basicConfig(
|
||
filename=os.path.join(LOG_DIR, 'app.log'),
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||
)
|
||
|
||
# 确保数据目录存在
|
||
if not os.path.exists(DATA_DIR):
|
||
os.makedirs(DATA_DIR)
|
||
|
||
# 获取当前语言
|
||
def get_current_language():
|
||
return session.get('language', DEFAULT_LANGUAGE)
|
||
|
||
# 设置语言
|
||
@app.route('/set_language/<lang_code>')
|
||
def set_language(lang_code):
|
||
if lang_code in LANGUAGES:
|
||
session['language'] = lang_code
|
||
return redirect(request.referrer or url_for('index'))
|
||
|
||
# 获取翻译文本
|
||
def get_translation(key, lang=None):
|
||
if lang is None:
|
||
lang = get_current_language()
|
||
return TRANSLATIONS.get(lang, {}).get(key, key)
|
||
|
||
@app.route('/')
|
||
def index():
|
||
# Always serve Simplified Chinese homepage by default
|
||
lang = get_current_language()
|
||
return render_template('index.html',
|
||
company=COMPANY_INFO,
|
||
t=get_translation,
|
||
lang=lang,
|
||
languages=LANGUAGES)
|
||
|
||
@app.route('/about')
|
||
def about():
|
||
lang = get_current_language()
|
||
return render_template('about.html',
|
||
company=COMPANY_INFO,
|
||
t=get_translation,
|
||
lang=lang,
|
||
languages=LANGUAGES)
|
||
|
||
@app.route('/services')
|
||
def services():
|
||
lang = get_current_language()
|
||
return render_template('services.html',
|
||
company=COMPANY_INFO,
|
||
t=get_translation,
|
||
lang=lang,
|
||
languages=LANGUAGES)
|
||
|
||
@app.route('/download')
|
||
def download():
|
||
lang = get_current_language()
|
||
return render_template('download.html',
|
||
company=COMPANY_INFO,
|
||
t=get_translation,
|
||
lang=lang,
|
||
languages=LANGUAGES)
|
||
|
||
@app.route('/pricing')
|
||
def pricing():
|
||
lang = get_current_language()
|
||
return render_template('pricing.html',
|
||
company=COMPANY_INFO,
|
||
t=get_translation,
|
||
lang=lang,
|
||
languages=LANGUAGES)
|
||
|
||
@app.route('/help')
|
||
def help():
|
||
lang = get_current_language()
|
||
return render_template('help.html',
|
||
company=COMPANY_INFO,
|
||
t=get_translation,
|
||
lang=lang,
|
||
languages=LANGUAGES)
|
||
|
||
# 收到小程序,发送过来的数据,暂时在这里进行存储
|
||
@app.route('/contact', methods=['GET', 'POST'])
|
||
def contact():
|
||
lang = get_current_language()
|
||
if request.method == 'POST':
|
||
try:
|
||
form_data = request.get_json() # 直接解析 JSON
|
||
|
||
# 输入验证
|
||
if not form_data:
|
||
return jsonify({"status": "error", "message": "无效的JSON数据"}), 400
|
||
|
||
customerName = form_data.get('customerName', '').strip()
|
||
projectSource = form_data.get('projectSource', '').strip()
|
||
projectLeader = form_data.get('projectLeader', '').strip()
|
||
clientdemand = form_data.get('clientdemand', '').strip()
|
||
handovertasks = form_data.get('handovertasks', '').strip()
|
||
remarks = form_data.get('remarks', '').strip()
|
||
|
||
# 基本验证
|
||
if not customerName or not projectLeader:
|
||
return jsonify({"status": "error", "message": "客户名称和项目负责人不能为空"}), 400
|
||
|
||
# 生成文件名
|
||
filename = os.path.join(DATA_DIR, datetime.now().strftime("%Y%m%d_") + customerName + "_" + projectLeader + ".txt")
|
||
|
||
# 要保存的内容
|
||
content = f"文件生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
|
||
content += f"客户名称:{customerName}\n\n"
|
||
content += f"项目来源:{projectSource}\n\n"
|
||
content += f"项目负责人:{projectLeader}\n\n"
|
||
content += f"客户需求:{clientdemand}\n\n"
|
||
content += f"交接任务:{handovertasks}\n\n"
|
||
content += f"备注:{remarks}"
|
||
|
||
# 写入文件
|
||
with open(filename, "w", encoding="utf-8") as file:
|
||
file.write(content)
|
||
|
||
logging.info(f"成功保存数据到文件: {filename}")
|
||
return jsonify({"status": "success", "message": "提交成功"})
|
||
|
||
except Exception as e:
|
||
logging.error(f"处理数据时发生错误: {str(e)}")
|
||
return jsonify({"status": "error", "message": "服务器内部错误"}), 500
|
||
|
||
return render_template('contact.html',
|
||
company=COMPANY_INFO,
|
||
success=request.args.get('success'),
|
||
t=get_translation,
|
||
lang=lang,
|
||
languages=LANGUAGES)
|
||
|
||
# 服务日志目录中的文件
|
||
@app.route('/logs/<path:filename>')
|
||
def serve_log_file(filename):
|
||
return send_from_directory(LOG_DIR, filename)
|
||
|
||
# 服务下载目录中的文件 - 优化版本
|
||
@app.route('/downloads/<path:filename>')
|
||
def serve_download_file(filename):
|
||
import os
|
||
from flask import request, Response
|
||
from werkzeug.datastructures import Range
|
||
from werkzeug.exceptions import RequestedRangeNotSatisfiable
|
||
|
||
file_path = os.path.join('downloads', filename)
|
||
|
||
# 检查文件是否存在
|
||
if not os.path.isfile(file_path):
|
||
return jsonify({"status": "error", "message": "文件未找到"}), 404
|
||
|
||
# 获取文件大小和最后修改时间
|
||
file_size = os.path.getsize(file_path)
|
||
last_modified = os.path.getmtime(file_path)
|
||
|
||
# 处理Range请求(断点续传)
|
||
range_header = request.headers.get('Range')
|
||
if range_header:
|
||
try:
|
||
range_obj = Range.parse_range_header(range_header, file_size)
|
||
if range_obj.ranges:
|
||
# 支持单个范围请求
|
||
start, end = range_obj.ranges[0]
|
||
length = end - start + 1 if end is not None else file_size - start
|
||
|
||
def generate():
|
||
with open(file_path, 'rb') as f:
|
||
f.seek(start)
|
||
remaining = length
|
||
while remaining > 0:
|
||
chunk_size = min(8192, remaining)
|
||
chunk = f.read(chunk_size)
|
||
if not chunk:
|
||
break
|
||
yield chunk
|
||
remaining -= len(chunk)
|
||
|
||
response = Response(
|
||
generate(),
|
||
206, # Partial Content
|
||
mimetype='application/vnd.android.package-archive',
|
||
direct_passthrough=True
|
||
)
|
||
response.headers['Content-Range'] = f'bytes {start}-{start + length - 1}/{file_size}'
|
||
response.headers['Content-Length'] = str(length)
|
||
response.headers['Accept-Ranges'] = 'bytes'
|
||
|
||
# 设置缓存头 - 1小时缓存
|
||
response.headers['Cache-Control'] = 'public, max-age=3600'
|
||
response.headers['Last-Modified'] = last_modified
|
||
|
||
return response
|
||
except (ValueError, RequestedRangeNotSatisfiable):
|
||
# 如果Range请求无效,返回完整文件
|
||
pass
|
||
|
||
# 普通文件下载(无Range请求或Range请求无效)
|
||
response = send_from_directory('downloads', filename)
|
||
|
||
# 设置缓存头 - 1小时缓存
|
||
response.headers['Cache-Control'] = 'public, max-age=3600'
|
||
response.headers['Last-Modified'] = last_modified
|
||
response.headers['Accept-Ranges'] = 'bytes'
|
||
|
||
# 对于APK文件设置正确的MIME类型
|
||
if filename.lower().endswith('.apk'):
|
||
response.headers['Content-Type'] = 'application/vnd.android.package-archive'
|
||
|
||
return response
|
||
|
||
# 全局错误处理
|
||
@app.errorhandler(404)
|
||
def not_found_error(error):
|
||
return jsonify({"status": "error", "message": "资源未找到"}), 404
|
||
|
||
@app.errorhandler(500)
|
||
def internal_error(error):
|
||
logging.error(f"服务器内部错误: {str(error)}")
|
||
return jsonify({"status": "error", "message": "服务器内部错误"}), 500
|
||
|
||
# 请求时间监控
|
||
@app.before_request
|
||
def before_request():
|
||
g.start_time = time.time()
|
||
|
||
@app.after_request
|
||
def after_request(response):
|
||
if hasattr(g, 'start_time'):
|
||
elapsed = time.time() - g.start_time
|
||
logging.info(f"请求处理时间: {elapsed:.3f}秒")
|
||
return response
|
||
|
||
# 静态文件服务带缓存
|
||
@app.route('/static/<path:filename>')
|
||
def serve_static(filename):
|
||
response = send_from_directory(app.static_folder, filename)
|
||
# 设置缓存头 - 1小时缓存
|
||
response.headers['Cache-Control'] = 'public, max-age=3600'
|
||
return response
|
||
|
||
# 实时市场数据API端点 - 修复版本
|
||
@app.route('/api/market-data')
|
||
def market_data():
|
||
try:
|
||
base_prices = {'上证指数': 3000, '深证成指': 10000, '中国平安': 50, '五粮液': 200}
|
||
|
||
market_data = []
|
||
for symbol, base_price in base_prices.items():
|
||
change_percent = random.uniform(-0.02, 0.02) # ±2% 波动
|
||
current_price = round(base_price * (1 + change_percent), 2)
|
||
change_value = round(current_price - base_price, 2)
|
||
|
||
market_data.append({
|
||
'symbol': symbol,
|
||
'price': current_price,
|
||
'change': change_value,
|
||
'change_percent': round(change_percent * 100, 2)
|
||
})
|
||
|
||
return jsonify({
|
||
'status': 'success',
|
||
'data': market_data,
|
||
'timestamp': datetime.now().isoformat()
|
||
})
|
||
|
||
except Exception as e:
|
||
logging.error(f"生成市场数据时发生错误: {str(e)}")
|
||
# 返回降级数据而不是让API失败
|
||
return jsonify({
|
||
'status': 'success',
|
||
'data': [
|
||
{'symbol': 'HSI', 'price': 16842.23, 'change': 201.45, 'change_percent': 1.21},
|
||
{'symbol': 'AAPL', 'price': 173.45, 'change': 1.38, 'change_percent': 0.80},
|
||
{'symbol': 'TSLA', 'price': 245.67, 'change': -1.23, 'change_percent': -0.50},
|
||
{'symbol': 'BTC/USD', 'price': 61234.56, 'change': 1260.89, 'change_percent': 2.10}
|
||
],
|
||
'timestamp': datetime.now().isoformat()
|
||
})
|
||
|
||
if __name__ == '__main__':
|
||
serve(app, host='0.0.0.0', port=778)
|