Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89810942cd |
@@ -38,6 +38,28 @@ jobs:
|
|||||||
--exclude='.gitea' \
|
--exclude='.gitea' \
|
||||||
"${CLONE_DIR}/" "${RELEASE_DIR}/"
|
"${CLONE_DIR}/" "${RELEASE_DIR}/"
|
||||||
|
|
||||||
|
echo "=== 2.5 Ensure .env secrets ==="
|
||||||
|
# .env 不随 git 走,线上凭据由服务器本地 shared/.env 提供。
|
||||||
|
# 若缺失则从 CI secrets 自动补齐;已存在的变量保留,避免覆盖线上配置/导致 session 失效。
|
||||||
|
mkdir -p "${DEPLOY_BASE}/shared"
|
||||||
|
SHARED_ENV="${DEPLOY_BASE}/shared/.env"
|
||||||
|
touch "${SHARED_ENV}"
|
||||||
|
ensure_env_var() {
|
||||||
|
key="$1"; val="$2"
|
||||||
|
if [ -z "$val" ]; then
|
||||||
|
echo " ! ${key} 未提供(CI secret 为空),跳过写入"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if ! grep -q "^${key}=" "${SHARED_ENV}"; then
|
||||||
|
echo "${key}=${val}" >> "${SHARED_ENV}"
|
||||||
|
echo " + 已写入 ${key}"
|
||||||
|
else
|
||||||
|
echo " = ${key} 已存在,保留原值"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
ensure_env_var "DB_PASSWORD" "${{ secrets.OPC_DB_PASSWORD }}"
|
||||||
|
ensure_env_var "SECRET_KEY" "${{ secrets.OPC_SECRET_KEY }}"
|
||||||
|
|
||||||
echo "=== 3. Link shared resources ==="
|
echo "=== 3. Link shared resources ==="
|
||||||
mkdir -p "${RELEASE_DIR}/data"
|
mkdir -p "${RELEASE_DIR}/data"
|
||||||
# .env from shared dir (not in git)
|
# .env from shared dir (not in git)
|
||||||
|
|||||||
@@ -39,12 +39,22 @@ UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|||||||
|
|
||||||
|
|
||||||
# ---------- 数据库连接 ----------
|
# ---------- 数据库连接 ----------
|
||||||
|
def _require_env(name):
|
||||||
|
"""强制读取必需环境变量,缺失即抛出清晰错误(禁止弱默认降级)"""
|
||||||
|
val = os.environ.get(name)
|
||||||
|
if not val:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"缺少必需的环境变量 {name},请在 .env 中显式配置(关键凭据禁止硬编码兜底)"
|
||||||
|
)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
def db():
|
def db():
|
||||||
return mysql.connector.connect(
|
return mysql.connector.connect(
|
||||||
host=os.environ.get("DB_HOST", "127.0.0.1"),
|
host=os.environ.get("DB_HOST", "127.0.0.1"),
|
||||||
port=int(os.environ.get("DB_PORT", "3306")),
|
port=int(os.environ.get("DB_PORT", "3306")),
|
||||||
user=os.environ.get("DB_USER", "opc"),
|
user=os.environ.get("DB_USER", "opc"),
|
||||||
password=os.environ.get("DB_PASSWORD", "opc123456"),
|
password=_require_env("DB_PASSWORD"),
|
||||||
database=os.environ.get("DB_NAME", "opc"),
|
database=os.environ.get("DB_NAME", "opc"),
|
||||||
charset="utf8mb4",
|
charset="utf8mb4",
|
||||||
collation="utf8mb4_unicode_ci",
|
collation="utf8mb4_unicode_ci",
|
||||||
@@ -55,10 +65,44 @@ def now():
|
|||||||
return datetime.utcnow().isoformat()
|
return datetime.utcnow().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_placeholders(sql):
|
||||||
|
"""将 ? 占位符转为 MySQL 的 %s,仅替换字符串字面量之外的 ?。
|
||||||
|
|
||||||
|
避免 `sql.replace('?', '%s')` 的全局替换缺陷:当 SQL 字面量或 LIKE 模式
|
||||||
|
中恰好含 `?` 时会误替换、导致参数错位。这里用状态机跳过单/双引号内的内容。
|
||||||
|
"""
|
||||||
|
out = []
|
||||||
|
in_single = in_double = False
|
||||||
|
i, n = 0, len(sql)
|
||||||
|
while i < n:
|
||||||
|
ch = sql[i]
|
||||||
|
if ch == "\\" and i + 1 < n: # 跳过转义序列
|
||||||
|
out.append(ch)
|
||||||
|
out.append(sql[i + 1])
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if ch == "'" and not in_double:
|
||||||
|
in_single = not in_single
|
||||||
|
elif ch == '"' and not in_single:
|
||||||
|
in_double = not in_double
|
||||||
|
out.append("%s" if (ch == "?" and not in_single and not in_double) else ch)
|
||||||
|
i += 1
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
def _exec(conn, sql, args=()):
|
def _exec(conn, sql, args=()):
|
||||||
"""执行 SQL,自动将 ? 转为 MySQL 的 %s"""
|
"""执行 SQL,将 ? 占位符安全地转为 MySQL 的 %s。
|
||||||
|
|
||||||
|
占位符数量与参数数量必须一致,否则立即报错,防止静默错位。
|
||||||
|
"""
|
||||||
|
converted = _convert_placeholders(sql)
|
||||||
|
n_ph = converted.count("%s")
|
||||||
|
if n_ph != len(args):
|
||||||
|
raise ValueError(
|
||||||
|
f"SQL 占位符数量({n_ph})与参数数量({len(args)})不匹配,请检查 SQL 与传参: {sql}"
|
||||||
|
)
|
||||||
cur = conn.cursor(dictionary=True)
|
cur = conn.cursor(dictionary=True)
|
||||||
cur.execute(sql.replace("?", "%s"), args)
|
cur.execute(converted, args)
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,14 @@ app = Flask(
|
|||||||
template_folder=str(ROOT / "templates"),
|
template_folder=str(ROOT / "templates"),
|
||||||
static_folder=str(ROOT / "static"),
|
static_folder=str(ROOT / "static"),
|
||||||
)
|
)
|
||||||
app.secret_key = os.environ.get("SECRET_KEY", "opc-dev-secret-2026")
|
# secret_key 必须从环境变量注入,禁止硬编码兜底——否则 session 可被伪造。
|
||||||
|
_secret_key = os.environ.get("SECRET_KEY")
|
||||||
|
if not _secret_key:
|
||||||
|
raise RuntimeError(
|
||||||
|
"缺少必需的环境变量 SECRET_KEY,请在 .env 中配置(建议: "
|
||||||
|
"python3 -c 'import secrets;print(secrets.token_hex(32))')"
|
||||||
|
)
|
||||||
|
app.secret_key = _secret_key
|
||||||
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
app.config["TEMPLATES_AUTO_RELOAD"] = True
|
||||||
app.register_blueprint(bp)
|
app.register_blueprint(bp)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user