Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89810942cd |
@@ -38,6 +38,28 @@ jobs:
|
||||
--exclude='.gitea' \
|
||||
"${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 ==="
|
||||
mkdir -p "${RELEASE_DIR}/data"
|
||||
# .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():
|
||||
return mysql.connector.connect(
|
||||
host=os.environ.get("DB_HOST", "127.0.0.1"),
|
||||
port=int(os.environ.get("DB_PORT", "3306")),
|
||||
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"),
|
||||
charset="utf8mb4",
|
||||
collation="utf8mb4_unicode_ci",
|
||||
@@ -55,10 +65,44 @@ def now():
|
||||
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=()):
|
||||
"""执行 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.execute(sql.replace("?", "%s"), args)
|
||||
cur.execute(converted, args)
|
||||
return cur
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,14 @@ app = Flask(
|
||||
template_folder=str(ROOT / "templates"),
|
||||
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.register_blueprint(bp)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user