From 22f9bccb09580b6ade4f36b178a58630dc1cabdc Mon Sep 17 00:00:00 2001 From: haoslm <6083297@qq.com> Date: Tue, 21 Jul 2026 15:37:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8F=91=E5=B8=83=E6=AD=A3?= =?UTF-8?q?=E5=BC=8F=E7=89=88=E7=9A=84=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 21 +++++++ .env.prod | 19 ++++++ lib/db.py | 32 +++++----- lib/report_generator.py | 45 +++++++++----- requirements.txt | 3 + run_prod.py | 25 ++++++++ 启动.bat | 132 ++++++++++++++++++++++++++++++++++++++++ 服务管理.bat | 123 +++++++++++++++++++++++++++++++++++++ 8 files changed, 371 insertions(+), 29 deletions(-) create mode 100644 .env.example create mode 100644 .env.prod create mode 100644 run_prod.py create mode 100644 启动.bat create mode 100644 服务管理.bat diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a040f01 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +# ======================================== +# 订单日报系统 - 环境变量配置示例 +# ======================================== +# 使用方法:复制本文件为 .env,然后修改相应配置 +# 注意:如果使用 .env 文件,需要安装 python-dotenv + +# 服务监听地址 +DEPLOY_RUN_HOST=0.0.0.0 + +# 服务监听端口 +DEPLOY_RUN_PORT=5000 + +# 数据库配置 +DB_HOST=haoslm2.xicp.net +DB_PORT=10216 +DB_USER=root +DB_PASSWORD=your_password +DB_NAME=your_database + +# 日志级别(DEBUG/INFO/WARNING/ERROR) +LOG_LEVEL=INFO diff --git a/.env.prod b/.env.prod new file mode 100644 index 0000000..91a53d2 --- /dev/null +++ b/.env.prod @@ -0,0 +1,19 @@ +# ======================================== +# 订单日报系统 - 正式环境配置 +# ======================================== + +# 服务监听地址 +DEPLOY_RUN_HOST=0.0.0.0 + +# 服务监听端口 +DEPLOY_RUN_PORT=5000 + +# 数据库配置(正式环境) +DB_HOST=192.168.10.250 +DB_PORT=9030 +DB_USER=root +DB_PASSWORD=DsideaL147258369 +DB_NAME=yltcharge + +# 日志级别(DEBUG/INFO/WARNING/ERROR) +LOG_LEVEL=INFO diff --git a/lib/db.py b/lib/db.py index 3098812..1da197d 100644 --- a/lib/db.py +++ b/lib/db.py @@ -1,24 +1,28 @@ """ 数据库连接配置 """ +import os import pymysql from contextlib import contextmanager from lib.logger import log_info, log_error, log_warning -# 数据库配置 -DB_CONFIG = { - 'host': 'haoslm2.xicp.net', - 'port': 10216, - 'user': 'root', - 'password': 'DsideaL147258369', - 'database': 'yltcharge', - 'charset': 'utf8mb4', - 'cursorclass': pymysql.cursors.DictCursor, - 'autocommit': True, - 'connect_timeout': 30, # 连接超时30秒 - 'read_timeout': 60, # 读取超时60秒 - 'write_timeout': 60 # 写入超时60秒 -} +def get_db_config(): + """从环境变量或默认值获取数据库配置""" + return { + 'host': os.environ.get('DB_HOST', 'haoslm2.xicp.net'), + 'port': int(os.environ.get('DB_PORT', 10216)), + 'user': os.environ.get('DB_USER', 'root'), + 'password': os.environ.get('DB_PASSWORD', 'DsideaL147258369'), + 'database': os.environ.get('DB_NAME', 'yltcharge'), + 'charset': 'utf8mb4', + 'cursorclass': pymysql.cursors.DictCursor, + 'autocommit': True, + 'connect_timeout': 30, + 'read_timeout': 60, + 'write_timeout': 60 + } + +DB_CONFIG = get_db_config() @contextmanager diff --git a/lib/report_generator.py b/lib/report_generator.py index 850f00a..9df88a3 100644 --- a/lib/report_generator.py +++ b/lib/report_generator.py @@ -869,31 +869,46 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results, charge_degree_col = col_idx break - # 第一列显示标题 + # 总列数 + total_cols = len(selected_fields) + 2 + if has_custom_service_fee: + total_cols += 1 + + # 标题文字 title_text = f'当月累计充电量(共{total_days}天,截止{report_date})' if missing_dates and len(missing_dates) > 0: - # 最多显示5个缺失日期,超过的用"...共N天"表示 if len(missing_dates) <= 5: - missing_str = '、'.join([d[5:] for d in missing_dates]) # 只显示月-日 + missing_str = '、'.join([d[5:] for d in missing_dates]) else: missing_str = '、'.join([d[5:] for d in missing_dates[:5]]) + f'...共{len(missing_dates)}天' title_text += f'\n(缺:{missing_str})' - ws.cell(row=monthly_row, column=1).value = title_text - ws.cell(row=monthly_row, column=1).font = monthly_font - ws.cell(row=monthly_row, column=1).fill = monthly_fill - ws.cell(row=monthly_row, column=1).alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) - ws.cell(row=monthly_row, column=1).border = thin_border + # 标题跨列:从第1列到 charge_degree_col 的前一列 + # 如果没有找到充电电量列,就跨到倒数第二列 + title_end_col = charge_degree_col - 1 if charge_degree_col else total_cols - 1 + if title_end_col < 1: + title_end_col = 1 - # 其他列也填充背景色 - total_cols = len(selected_fields) + 2 - if has_custom_service_fee: - total_cols += 1 + # 合并标题单元格 + if title_end_col > 1: + ws.merge_cells(start_row=monthly_row, start_column=1, + end_row=monthly_row, end_column=title_end_col) + + # 设置标题单元格 + title_cell = ws.cell(row=monthly_row, column=1) + title_cell.value = title_text + title_cell.font = monthly_font + title_cell.fill = monthly_fill + title_cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) + title_cell.border = thin_border + + # 其他列填充背景色和边框 for col_idx in range(2, total_cols): cell = ws.cell(row=monthly_row, column=col_idx) cell.fill = monthly_fill cell.border = thin_border + # 数值放在 charge_degree 列 if charge_degree_col: cell = ws.cell(row=monthly_row, column=charge_degree_col) cell.value = total_degree @@ -901,11 +916,11 @@ def generate_excel(orders, selected_fields, sum_fields, sum_results, cell.alignment = Alignment(horizontal='right', vertical='center') cell.number_format = '0.000' - # 如果有缺失日期,行高加大一点 + # 行高 if missing_dates and len(missing_dates) > 0: - ws.row_dimensions[monthly_row].height = 40 + ws.row_dimensions[monthly_row].height = 45 else: - ws.row_dimensions[monthly_row].height = 25 + ws.row_dimensions[monthly_row].height = 28 except Exception as e: log_error(f"[日报生成] 写入月度总计时出错: {e}", 'report') diff --git a/requirements.txt b/requirements.txt index bc934b9..df03e9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,5 +18,8 @@ APScheduler==3.10.4 python-dateutil==2.8.2 pytz==2023.3 +# 生产服务器(Windows推荐) +waitress==3.0.0 + # 开发工具(可选) python-dotenv==1.0.0 \ No newline at end of file diff --git a/run_prod.py b/run_prod.py new file mode 100644 index 0000000..d7af952 --- /dev/null +++ b/run_prod.py @@ -0,0 +1,25 @@ +""" +生产环境启动脚本 +使用 waitress 作为 WSGI 服务器 +""" +import os +from waitress import serve +from app import app + +if __name__ == '__main__': + port = int(os.environ.get('DEPLOY_RUN_PORT', 5000)) + host = os.environ.get('DEPLOY_RUN_HOST', '0.0.0.0') + + print(f'订单日报系统 - 生产模式') + print(f'监听地址: {host}:{port}') + print('按 Ctrl+C 停止服务') + print('-' * 50) + + serve( + app, + host=host, + port=port, + threads=8, + connection_limit=200, + channel_timeout=300, + ) diff --git a/启动.bat b/启动.bat new file mode 100644 index 0000000..fd1e04a --- /dev/null +++ b/启动.bat @@ -0,0 +1,132 @@ +@echo off +chcp 65001 >nul +title 订单日报系统 - 生产模式 + +cd /d "%~dp0" + +echo ======================================== +echo 订单日报系统 - 生产模式启动 +echo ======================================== +echo. + +REM ============================================ +REM 第一步:读取 .env 配置文件(如果存在) +REM ============================================ +echo [1/5] 读取配置... + +if exist ".env" ( + echo [信息] 找到 .env 文件,正在加载配置... + for /f "usebackq tokens=1,2 delims==" %%a in (".env") do ( + if not "%%a"=="" if not "%%~a"=="#" ( + set "%%a=%%b" + ) + ) + echo [信息] 配置加载完成 +) else if exist ".env.prod" ( + echo [信息] 找到 .env.prod 文件,正在加载配置... + for /f "usebackq tokens=1,2 delims==" %%a in (".env.prod") do ( + if not "%%a"=="" if not "%%~a"=="#" ( + set "%%a=%%b" + ) + ) + echo [信息] 配置加载完成 +) else ( + echo [信息] 未找到 .env 文件,使用默认配置 +) + +REM ============================================ +REM 第二步:检查 Python 是否安装 +REM ============================================ +echo. +echo [2/5] 检查 Python 环境... + +where python >nul 2>&1 +if errorlevel 1 ( + echo [错误] 未检测到 Python! + echo. + echo 请先安装 Python 3.10 或更高版本: + echo 下载地址: https://www.python.org/downloads/ + echo 安装时请勾选 "Add Python to PATH" + echo. + pause + exit /b 1 +) + +for /f "delims=" %%v in ('python --version 2^>^&1') do set PY_VERSION=%%v +echo [信息] Python 已安装: %PY_VERSION% + +REM ============================================ +REM 第三步:创建虚拟环境(如果不存在) +REM ============================================ +echo. +echo [3/5] 检查虚拟环境... + +if not exist ".venv\Scripts\python.exe" ( + echo [信息] 虚拟环境不存在,正在创建... + python -m venv .venv + if errorlevel 1 ( + echo [错误] 虚拟环境创建失败! + pause + exit /b 1 + ) + echo [信息] 虚拟环境创建成功 +) else ( + echo [信息] 虚拟环境已存在 +) + +set PYTHON=.venv\Scripts\python.exe +set PIP=.venv\Scripts\pip.exe + +REM ============================================ +REM 第四步:安装依赖(只在缺少时安装) +REM ============================================ +echo. +echo [4/5] 检查依赖... + +REM 检查核心依赖是否已安装 +%PYTHON% -c "import flask" 2>nul +if errorlevel 1 ( + set NEED_INSTALL=1 +) else ( + set NEED_INSTALL=0 +) + +if "%NEED_INSTALL%"=="1" ( + echo [信息] 正在安装依赖,请稍候... + %PIP% install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple + if errorlevel 1 ( + echo [警告] 清华源安装失败,尝试官方源... + %PIP% install -r requirements.txt + if errorlevel 1 ( + echo [错误] 依赖安装失败! + pause + exit /b 1 + ) + ) + echo [信息] 依赖安装完成 +) else ( + echo [信息] 依赖已就绪 +) + +REM ============================================ +REM 第五步:启动服务 +REM ============================================ +echo. +echo [5/5] 启动服务... +echo. +echo [信息] 服务启动后,访问 http://localhost:5000 +echo [信息] 按 Ctrl+C 停止服务 +echo. +echo ======================================== +echo. + +%PYTHON% run_prod.py + +if errorlevel 1 ( + echo. + echo ======================================== + echo [错误] 服务异常退出,错误代码: %errorlevel% + echo ======================================== + echo. + pause +) diff --git a/服务管理.bat b/服务管理.bat new file mode 100644 index 0000000..82b5a1f --- /dev/null +++ b/服务管理.bat @@ -0,0 +1,123 @@ +@echo off +chcp 65001 >nul +title 订单日报系统 - Windows服务管理 + +cd /d "%~dp0" + +REM ============================================ +REM 使用 NSSM 将程序注册为 Windows 服务 +REM 需要先下载 nssm.exe 放到当前目录 +REM 下载地址: https://nssm.cc/download +REM ============================================ + +set SERVICE_NAME=DailyReportSystem +set SERVICE_DESC=订单日报系统服务 +set NSSM_EXE=nssm.exe + +if not exist "%NSSM_EXE%" ( + echo [错误] 未找到 %NSSM_EXE% + echo. + echo 请先下载 NSSM: + echo 1. 访问 https://nssm.cc/download + echo 2. 下载 Win64 版本 + echo 3. 解压后将 nssm.exe 放到当前目录 + echo. + pause + exit /b 1 +) + +REM 检查虚拟环境 +if exist ".venv\Scripts\python.exe" ( + set PYTHON_PATH=%cd%\.venv\Scripts\python.exe +) else ( + for /f "delims=" %%i in ('where python') do ( + set PYTHON_PATH=%%i + goto :found_python + ) + :found_python +) + +echo ======================================== +echo 订单日报系统 - Windows服务管理 +echo ======================================== +echo. +echo 服务名称: %SERVICE_NAME% +echo Python路径: %PYTHON_PATH% +echo 工作目录: %cd% +echo. +echo 请选择操作: +echo 1. 安装服务 +echo 2. 启动服务 +echo 3. 停止服务 +echo 4. 重启服务 +echo 5. 删除服务 +echo 6. 查看服务状态 +echo 0. 退出 +echo. +set /p choice=请输入选项: + +if "%choice%"=="1" goto install +if "%choice%"=="2" goto start +if "%choice%"=="3" goto stop +if "%choice%"=="4" goto restart +if "%choice%"=="5" goto remove +if "%choice%"=="6" goto status +if "%choice%"=="0" goto end + +:install +echo. +echo [信息] 正在安装服务... +%NSSM_EXE% install %SERVICE_NAME% "%PYTHON_PATH%" "%cd%\run_prod.py" +%NSSM_EXE% set %SERVICE_NAME% AppDirectory "%cd%" +%NSSM_EXE% set %SERVICE_NAME% DisplayName "%SERVICE_DESC%" +%NSSM_EXE% set %SERVICE_NAME% Description "订单日报系统 - 自动生成充电订单日报报表" +%NSSM_EXE% set %SERVICE_NAME% Start SERVICE_AUTO_START +%NSSM_EXE% set %SERVICE_NAME% AppStdout "%cd%\logs\service_stdout.log" +%NSSM_EXE% set %SERVICE_NAME% AppStderr "%cd%\logs\service_stderr.log" +%NSSM_EXE% set %SERVICE_NAME% AppRotateFiles 1 +%NSSM_EXE% set %SERVICE_NAME% AppRotateOnline 1 +%NSSM_EXE% set %SERVICE_NAME% AppRotateBytes 10485760 +echo [信息] 服务安装完成! +goto end + +:start +echo. +echo [信息] 正在启动服务... +net start %SERVICE_NAME% +goto end + +:stop +echo. +echo [信息] 正在停止服务... +net stop %SERVICE_NAME% +goto end + +:restart +echo. +echo [信息] 正在重启服务... +net stop %SERVICE_NAME% +timeout /t 3 /nobreak >nul +net start %SERVICE_NAME% +goto end + +:remove +echo. +echo [警告] 即将删除服务 %SERVICE_NAME% +set /p confirm=确认删除吗?(y/n): +if /i not "%confirm%"=="y" goto end +echo [信息] 正在停止服务... +net stop %SERVICE_NAME% 2>nul +echo [信息] 正在删除服务... +%NSSM_EXE% remove %SERVICE_NAME% confirm +echo [信息] 服务已删除! +goto end + +:status +echo. +echo [信息] 服务状态: +sc query %SERVICE_NAME% +goto end + +:end +echo. +pause