new file: app.py new file: app/ali_oss.py new file: app/app.py new file: app/app_common.py new file: app/config.py new file: app/coze.py new file: app/generate_api.py new file: app/html_crypto.py new file: app/main.py new file: app/web_source/admin.html new file: app/web_source/brand.html new file: app/web_source/home.html new file: app/web_source/index.html new file: app/web_source/login.html new file: app_common.py new file: blueprints/__init__.py new file: blueprints/__pycache__/__init__.cpython-311.pyc new file: blueprints/__pycache__/__init__.cpython-39.pyc new file: blueprints/__pycache__/admin.cpython-311.pyc new file: blueprints/__pycache__/admin.cpython-39.pyc new file: blueprints/__pycache__/auth.cpython-311.pyc new file: blueprints/__pycache__/auth.cpython-39.pyc new file: blueprints/__pycache__/brand.cpython-311.pyc new file: blueprints/__pycache__/brand.cpython-39.pyc new file: blueprints/__pycache__/image.cpython-311.pyc new file: blueprints/__pycache__/image.cpython-39.pyc new file: blueprints/__pycache__/main.cpython-311.pyc new file: blueprints/__pycache__/main.cpython-39.pyc new file: blueprints/admin.py new file: blueprints/auth.py new file: blueprints/brand.py new file: blueprints/image.py new file: blueprints/main.py new file: brand_spider/__pycache__/main.cpython-39.pyc new file: brand_spider/__pycache__/web_dec.cpython-39.pyc new file: brand_spider/main.py new file: brand_spider/web_dec.py new file: config.py new file: coze.py new file: generate_api.py new file: html_crypto.py new file: main.py new file: static/bg.jpg new file: "static/\345\223\201\347\211\214\346\226\207\346\241\243\346\240\274\345\274\217_\346\250\241\346\235\277.xlsx" new file: "static/\346\250\241\346\235\2772-\344\273\245\346\226\207\344\273\266\345\244\271\346\226\271\345\274\217\344\270\212\344\274\240.zip" new file: tool/.device_id new file: tool/__pycache__/devices.cpython-311.pyc new file: tool/__pycache__/devices.cpython-39.pyc new file: tool/devices.py
262 lines
11 KiB
Python
262 lines
11 KiB
Python
"""
|
||
公共模块:数据库连接、初始化、会话校验、装饰器、模板渲染
|
||
供各蓝图复用
|
||
"""
|
||
import os
|
||
import secrets
|
||
from datetime import timedelta
|
||
from functools import wraps
|
||
|
||
import pymysql
|
||
from flask import request, redirect, url_for, session, jsonify, render_template, render_template_string
|
||
|
||
from config import mysql_host, mysql_user, mysql_password, mysql_database
|
||
|
||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
STATIC_DIR = os.path.join(BASE_DIR, 'static')
|
||
|
||
|
||
def get_db():
|
||
return pymysql.connect(
|
||
host=mysql_host,
|
||
user=mysql_user,
|
||
password=mysql_password,
|
||
database=mysql_database,
|
||
charset='utf8mb4',
|
||
cursorclass=pymysql.cursors.DictCursor
|
||
)
|
||
|
||
|
||
def _render_html(template_name: str, **context):
|
||
"""读取 HTML 模板:若为加密文件则先解密,再渲染。未加密或解密失败时按明文渲染。"""
|
||
path = os.path.join(BASE_DIR, "web_source", template_name)
|
||
if not os.path.isfile(path):
|
||
return render_template(template_name, **context)
|
||
with open(path, "rb") as f:
|
||
raw = f.read()
|
||
try:
|
||
from html_crypto import decrypt
|
||
content = decrypt(raw).decode("utf-8")
|
||
except Exception:
|
||
content = raw.decode("utf-8", errors="replace")
|
||
return render_template_string(content, **context)
|
||
|
||
|
||
def _is_session_user_valid():
|
||
"""校验 session 中的 user_id 是否在数据库中仍存在;不存在则清除 session 并返回 False"""
|
||
uid = session.get('user_id')
|
||
if not uid:
|
||
return False
|
||
try:
|
||
conn = get_db()
|
||
with conn.cursor() as cur:
|
||
cur.execute("SELECT id FROM users WHERE id = %s", (uid,))
|
||
row = cur.fetchone()
|
||
conn.close()
|
||
if not row:
|
||
session.clear()
|
||
return False
|
||
return True
|
||
except Exception:
|
||
session.clear()
|
||
return False
|
||
|
||
|
||
def _get_current_admin_role():
|
||
"""获取当前登录用户的管理角色:super_admin / admin / None(非管理员)"""
|
||
try:
|
||
conn = get_db()
|
||
with conn.cursor() as cur:
|
||
cur.execute(
|
||
"SELECT id, username, is_admin, role, created_by_id FROM users WHERE id = %s",
|
||
(session['user_id'],)
|
||
)
|
||
row = cur.fetchone()
|
||
conn.close()
|
||
if not row or not row.get('is_admin'):
|
||
return None, None
|
||
return row.get('role') or ('super_admin' if row.get('created_by_id') is None else 'admin'), row
|
||
except Exception:
|
||
return None, None
|
||
|
||
|
||
def login_required(f):
|
||
@wraps(f)
|
||
def decorated(*args, **kwargs):
|
||
if not session.get('user_id') or not _is_session_user_valid():
|
||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||
return jsonify({'success': False, 'error': '未登录'}), 401
|
||
return redirect(url_for('auth.login'))
|
||
return f(*args, **kwargs)
|
||
return decorated
|
||
|
||
|
||
def admin_required(f):
|
||
@wraps(f)
|
||
def decorated(*args, **kwargs):
|
||
if not session.get('user_id'):
|
||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||
return jsonify({'success': False, 'error': '未登录'}), 401
|
||
return redirect(url_for('auth.login'))
|
||
try:
|
||
conn = get_db()
|
||
with conn.cursor() as cur:
|
||
cur.execute("SELECT is_admin, role FROM users WHERE id = %s", (session['user_id'],))
|
||
row = cur.fetchone()
|
||
conn.close()
|
||
if not row or not row.get('is_admin'):
|
||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||
return jsonify({'success': False, 'error': '需要管理员权限'}), 403
|
||
return redirect(url_for('main.home'))
|
||
except Exception as e:
|
||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||
return jsonify({'success': False, 'error': str(e)}), 500
|
||
return redirect(url_for('main.home'))
|
||
return f(*args, **kwargs)
|
||
return decorated
|
||
|
||
|
||
def init_db():
|
||
"""初始化数据库表,若不存在则创建"""
|
||
from werkzeug.security import generate_password_hash
|
||
conn = pymysql.connect(
|
||
host=mysql_host,
|
||
user=mysql_user,
|
||
password=mysql_password,
|
||
charset='utf8mb4'
|
||
)
|
||
try:
|
||
with conn.cursor() as cur:
|
||
cur.execute(f"CREATE DATABASE IF NOT EXISTS `{mysql_database}` DEFAULT CHARSET utf8mb4")
|
||
cur.execute(f"USE `{mysql_database}`")
|
||
cur.execute("""
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
username VARCHAR(64) NOT NULL UNIQUE,
|
||
password_hash VARCHAR(256) NOT NULL,
|
||
is_admin TINYINT(1) DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
""")
|
||
cur.execute("""
|
||
CREATE TABLE IF NOT EXISTS image_history (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
user_id INT NOT NULL,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
panel_type VARCHAR(64) DEFAULT '',
|
||
original_urls JSON,
|
||
params JSON,
|
||
result_urls JSON,
|
||
long_image_url VARCHAR(1024) DEFAULT NULL,
|
||
INDEX idx_user_created (user_id, created_at DESC)
|
||
)
|
||
""")
|
||
try:
|
||
cur.execute("ALTER TABLE image_history ADD COLUMN long_image_url VARCHAR(1024) DEFAULT NULL")
|
||
except Exception:
|
||
pass
|
||
try:
|
||
cur.execute("ALTER TABLE users ADD COLUMN machine VARCHAR(64) DEFAULT NULL")
|
||
except Exception:
|
||
pass
|
||
try:
|
||
cur.execute("ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'normal'")
|
||
except Exception:
|
||
pass
|
||
try:
|
||
cur.execute("ALTER TABLE users ADD COLUMN created_by_id INT NULL")
|
||
except Exception:
|
||
pass
|
||
cur.execute("""
|
||
CREATE TABLE IF NOT EXISTS brand_crawl_tasks (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
user_id INT NOT NULL,
|
||
file_paths JSON NOT NULL,
|
||
status VARCHAR(20) DEFAULT 'pending',
|
||
task_type TINYINT DEFAULT 1 COMMENT '1=立即执行,2=添加任务执行',
|
||
result_paths JSON NULL,
|
||
error_message TEXT NULL,
|
||
progress_current INT DEFAULT 0,
|
||
progress_total INT DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
INDEX idx_user_status (user_id, status)
|
||
)
|
||
""")
|
||
try:
|
||
cur.execute("ALTER TABLE brand_crawl_tasks ADD COLUMN progress_current INT DEFAULT 0")
|
||
except Exception:
|
||
pass
|
||
try:
|
||
cur.execute("ALTER TABLE brand_crawl_tasks ADD COLUMN progress_total INT DEFAULT 0")
|
||
except Exception:
|
||
pass
|
||
try:
|
||
cur.execute("ALTER TABLE brand_crawl_tasks ADD COLUMN `desc` VARCHAR(500) NULL COMMENT '上传文件名描述'")
|
||
except Exception:
|
||
pass
|
||
try:
|
||
cur.execute("ALTER TABLE brand_crawl_tasks ADD COLUMN strategy VARCHAR(20) DEFAULT 'Terms' COMMENT '品牌匹配方式: Terms/Simple'")
|
||
except Exception:
|
||
pass
|
||
try:
|
||
cur.execute("ALTER TABLE brand_crawl_tasks ADD COLUMN task_type TINYINT DEFAULT 1 COMMENT '1=立即执行,2=添加任务执行'")
|
||
except Exception:
|
||
pass
|
||
cur.execute("""
|
||
CREATE TABLE IF NOT EXISTS columns (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
name VARCHAR(128) NOT NULL COMMENT '栏目名',
|
||
column_key VARCHAR(64) NOT NULL COMMENT '栏目标识',
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE KEY uk_column_key (column_key)
|
||
)
|
||
""")
|
||
cur.execute("""
|
||
CREATE TABLE IF NOT EXISTS user_column_permission (
|
||
user_id INT NOT NULL,
|
||
column_id INT NOT NULL,
|
||
PRIMARY KEY (user_id, column_id),
|
||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||
FOREIGN KEY (column_id) REFERENCES columns(id) ON DELETE CASCADE
|
||
)
|
||
""")
|
||
try:
|
||
cur.execute("UPDATE users SET role = 'normal' WHERE (role IS NULL OR role = '') AND (is_admin = 0 OR is_admin IS NULL)")
|
||
cur.execute("SELECT MIN(id) AS mid FROM users WHERE is_admin = 1")
|
||
row = cur.fetchone()
|
||
if row and row.get('mid'):
|
||
mid = row['mid']
|
||
cur.execute("UPDATE users SET role = 'super_admin' WHERE id = %s", (mid,))
|
||
cur.execute("UPDATE users SET role = 'admin' WHERE is_admin = 1 AND id != %s", (mid,))
|
||
cur.execute("UPDATE users SET created_by_id = %s WHERE role = 'admin' AND (created_by_id IS NULL)", (mid,))
|
||
except Exception:
|
||
pass
|
||
conn.commit()
|
||
finally:
|
||
conn.close()
|
||
_create_initial_admin()
|
||
|
||
|
||
def _create_initial_admin():
|
||
"""若没有任何管理员,则创建默认超级管理员(首次启动时,仅一个)"""
|
||
from werkzeug.security import generate_password_hash
|
||
try:
|
||
conn = get_db()
|
||
with conn.cursor() as cur:
|
||
cur.execute("SELECT id FROM users WHERE role = 'super_admin' LIMIT 1")
|
||
if cur.fetchone():
|
||
conn.close()
|
||
return
|
||
admin_user = os.environ.get('ADMIN_USER', 'admin')
|
||
admin_pwd = os.environ.get('ADMIN_PASSWORD', 'admin123')
|
||
pwd_hash = generate_password_hash(admin_pwd, method='pbkdf2:sha256')
|
||
cur.execute(
|
||
"INSERT INTO users (username, password_hash, is_admin, role) VALUES (%s, %s, 1, 'super_admin')",
|
||
(admin_user, pwd_hash)
|
||
)
|
||
conn.commit()
|
||
conn.close()
|
||
except Exception:
|
||
pass
|