first commit

This commit is contained in:
renjianbo
2026-04-02 17:39:09 +08:00
parent 18cb2e6614
commit f19668ca3a
66 changed files with 475 additions and 59 deletions

107
.env.example Normal file
View File

@@ -0,0 +1,107 @@
# ========================================
# Flask提示词大师应用环境变量配置示例
# ========================================
# 复制此文件为 .env 并根据实际情况修改配置
# cp env.example .env
# ========================================
# Flask基础配置
# ========================================
# Flask应用密钥必需
SECRET_KEY=your-secret-key-here
# 应用环境development/production/testing
FLASK_ENV=development
# 调试模式(开发环境开启,生产环境关闭)
DEBUG=True
# ========================================
# 数据库配置
# ========================================
# 主数据库连接URL必需
# MySQL示例: mysql+pymysql://username:password@host:port/database_name?charset=utf8mb4
DATABASE_URL=mysql+pymysql://username:password@localhost:3306/database_name?charset=utf8mb4
# 腾讯云数据库连接URL可选
# TENCENT_DATABASE_URL=mysql+pymysql://username:password@tencent-host:port/database_name?charset=utf8mb4
# ========================================
# OpenAI兼容API配置
# ========================================
# API基础URL必需
LLM_API_URL=https://api.deepseek.com/v1
# API密钥必需
LLM_API_KEY=sk-your-api-key-here
# ========================================
# 微信小程序配置
# ========================================
# 小程序AppID必需
WX_APPID=your-wx-appid-here
# 小程序Secret必需
WX_SECRET=your-wx-secret-here
# ========================================
# 跨域配置
# ========================================
# 允许跨域的域名,多个用逗号分隔
# 开发环境: http://localhost:3000,http://127.0.0.1:3000
# 生产环境: https://yourdomain.com,https://www.yourdomain.com
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
# ========================================
# 日志配置
# ========================================
# 日志级别DEBUG/INFO/WARNING/ERROR/CRITICAL
LOG_LEVEL=INFO
# 日志文件路径
LOG_FILE=logs/app.log
# ========================================
# 缓存配置
# ========================================
# 缓存类型simple/redis/memcached
CACHE_TYPE=simple
# 缓存默认超时时间(秒)
CACHE_DEFAULT_TIMEOUT=300
# Redis缓存URL当CACHE_TYPE=redis时使用
# REDIS_URL=redis://localhost:6379/0
# ========================================
# 会话配置
# ========================================
# 会话生命周期(小时)
SESSION_LIFETIME_HOURS=24
# ========================================
# 文件上传配置
# ========================================
# 最大文件上传大小(字节)
MAX_CONTENT_LENGTH=16777216
# 文件上传目录
UPLOAD_FOLDER=uploads
# ========================================
# 安全配置
# ========================================
# 是否启用CSRF保护
WTF_CSRF_ENABLED=True
# CSRF令牌超时时间
WTF_CSRF_TIME_LIMIT=3600
# ========================================
# 性能配置
# ========================================
# 数据库连接池大小
DB_POOL_SIZE=20
# 数据库连接池最大溢出连接数
DB_MAX_OVERFLOW=30

10
.env.test Normal file
View File

@@ -0,0 +1,10 @@
# 测试环境变量文件
FLASK_ENV=development
SECRET_KEY=test-secret-key-for-development
DATABASE_URL=sqlite:///test.db
LLM_API_URL=https://api.deepseek.com/v1
LLM_API_KEY=sk-test-api-key-for-development
WX_APPID=test-wx-appid
WX_SECRET=test-wx-secret
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
LOG_LEVEL=DEBUG

44
.gitignore vendored
View File

@@ -5,3 +5,47 @@ env/
ENV/
myenv/
# 环境变量文件
.env
.env.local
.env.*.local
# 日志文件
logs/
*.log
# 缓存文件
__pycache__/
*.py[cod]
*$py.class
# 数据库文件
*.db
*.sqlite3
# 上传文件
uploads/
# 备份文件
*.bak
*.backup
# IDE配置文件
.vscode/
.idea/
*.swp
*.swo
# 操作系统文件
.DS_Store
Thumbs.db
# 测试覆盖率
.coverage
htmlcov/
# 构建输出
dist/
build/
*.egg-info/

View File

@@ -6,50 +6,68 @@
# class Config:
# SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
# # MySQL数据库配置
# SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'mysql+pymysql://root:123456@localhost:3306/pro_db?charset=utf8mb4'
# SQLALCHEMY_TRACK_MODIFICATIONS = False
# # OpenAI兼容API配置
# LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
# LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-fdf7cc1c73504e628ec0119b7e11b8cc'
# # 微信小程序配置
# WX_APPID = os.environ.get('WX_APPID') or 'wx2c65877d37fc29bf' # 替换为你的小程序 appid
# WX_SECRET = os.environ.get('WX_SECRET') or '89aa97dda3c1347c6ae3d6ab4627f1f4' # 替换为你的小程序 secret
# # 添加跨域支持
# CORS_ORIGINS = ['*'] # 生产环境建议设置具体域名
# 此文件已弃用,请使用 config/ 目录下的配置系统
# 为了向后兼容,此文件重定向到新的配置系统
import os
import warnings
from dotenv import load_dotenv
# 在配置类定义前加载环境变量
load_dotenv()
# 警告:此文件已弃用,请使用 config/ 目录下的配置系统
warnings.warn("config.py 已弃用,请使用新的配置系统 (config/ 目录)", DeprecationWarning)
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
# ---------------------- 原有本地MySQL数据库配置 ----------------------
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'mysql+pymysql://root:123456@localhost:3306/pro_db?charset=utf8mb4'
SQLALCHEMY_TRACK_MODIFICATIONS = False # 关闭SQLAlchemy的修改跟踪减少性能消耗
# ---------------------- 新增:腾讯云数据库配置 ----------------------
# 腾讯云数据库连接URI格式mysql+pymysql://用户名:密码@数据库地址:端口/数据库名?charset=utf8mb4
# 注意:需先在腾讯云控制台创建目标数据库(如命名为 pro_db_tencent需替换为你的实际库名
TENCENT_SQLALCHEMY_DATABASE_URI = os.environ.get('TENCENT_DATABASE_URL') or \
'mysql+pymysql://root:!Rjb12191@gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936/pro_db?charset=utf8mb4'
# 腾讯云数据库同样关闭修改跟踪(与本地配置保持一致)
SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
raise ValueError("SECRET_KEY 环境变量未设置,请设置环境变量或使用新的配置系统")
# ---------------------- 数据库配置 ----------------------
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
if not SQLALCHEMY_DATABASE_URI:
raise ValueError("DATABASE_URL 环境变量未设置,请设置环境变量或使用新的配置系统")
SQLALCHEMY_TRACK_MODIFICATIONS = False
# ---------------------- 腾讯云数据库配置 ----------------------
TENCENT_SQLALCHEMY_DATABASE_URI = os.environ.get('TENCENT_DATABASE_URL')
TENCENT_SQLALCHEMY_TRACK_MODIFICATIONS = False
# ---------------------- 其他原有配置(保持不变) ----------------------
# OpenAI兼容API配置
# ---------------------- OpenAI兼容API配置 ----------------------
LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-fdf7cc1c73504e628ec0119b7e11b8cc'
# 微信小程序配置
WX_APPID = os.environ.get('WX_APPID') or 'wx2c65877d37fc29bf' # 替换为你的小程序 appid
WX_SECRET = os.environ.get('WX_SECRET') or '89aa97dda3c1347c6ae3d6ab4627f1f4' # 替换为你的小程序 secret
# 添加跨域支持
CORS_ORIGINS = ['*'] # 生产环境建议设置具体域名(如 ['https://your-domain.com']
LLM_API_KEY = os.environ.get('LLM_API_KEY')
if not LLM_API_KEY:
raise ValueError("LLM_API_KEY 环境变量未设置,请设置环境变量或使用新的配置系统")
# ---------------------- 微信小程序配置 ----------------------
WX_APPID = os.environ.get('WX_APPID')
WX_SECRET = os.environ.get('WX_SECRET')
if not WX_APPID or not WX_SECRET:
raise ValueError("WX_APPID 和 WX_SECRET 环境变量未设置,请设置环境变量或使用新的配置系统")
# ---------------------- 跨域配置 ----------------------
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
@staticmethod
def init_app(app):
"""初始化应用配置(为兼容性保留)"""
# 创建必要的目录
os.makedirs('logs', exist_ok=True)
os.makedirs('uploads', exist_ok=True)

View File

@@ -23,14 +23,14 @@ class Config:
# OpenAI兼容API配置
LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-your-api-key-here'
LLM_API_KEY = os.environ.get('LLM_API_KEY')
# 微信小程序配置
WX_APPID = os.environ.get('WX_APPID') or 'wx-your-appid-here'
WX_SECRET = os.environ.get('WX_SECRET') or 'your-wx-secret-here'
# 跨域配置
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')
WX_APPID = os.environ.get('WX_APPID')
WX_SECRET = os.environ.get('WX_SECRET')
# 跨域配置 - 默认值在具体环境配置中设置
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '').split(',')
# 日志配置
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')

View File

@@ -12,6 +12,22 @@ class DevelopmentConfig(Config):
super().__init__()
if not self.SQLALCHEMY_DATABASE_URI:
self.SQLALCHEMY_DATABASE_URI = 'sqlite:///dev.db'
# 开发环境API配置如果未设置使用示例值并给出警告
if not self.LLM_API_KEY:
self.LLM_API_KEY = 'sk-dev-example-api-key'
import warnings
warnings.warn("LLM_API_KEY not set, using development example key")
if not self.WX_APPID:
self.WX_APPID = 'wx-dev-example-appid'
if not self.WX_SECRET:
self.WX_SECRET = 'wx-dev-example-secret'
# 开发环境CORS配置如果没有设置使用开发默认值
if not self.CORS_ORIGINS or self.CORS_ORIGINS == ['']:
self.CORS_ORIGINS = ['http://localhost:3000', 'http://127.0.0.1:3000', '*']
# 开发环境日志配置
LOG_LEVEL = 'DEBUG'

View File

@@ -7,7 +7,27 @@ class ProductionConfig(Config):
"""
DEBUG = False
TESTING = False
def __init__(self):
super().__init__()
# 生产环境必须设置的关键配置检查
required_configs = [
('SECRET_KEY', self.SECRET_KEY),
('LLM_API_KEY', self.LLM_API_KEY),
('WX_APPID', self.WX_APPID),
('WX_SECRET', self.WX_SECRET),
('SQLALCHEMY_DATABASE_URI', self.SQLALCHEMY_DATABASE_URI)
]
for name, value in required_configs:
if not value:
raise ValueError(f"生产环境必须设置{name}环境变量")
# 生产环境CORS配置检查在父类中已经设置
if not self.CORS_ORIGINS or self.CORS_ORIGINS == ['']:
raise ValueError("生产环境必须设置CORS_ORIGINS环境变量指定允许的域名")
# 生产环境日志配置
LOG_LEVEL = 'WARNING'
LOG_FILE = 'logs/production.log'
@@ -31,8 +51,8 @@ class ProductionConfig(Config):
# 生产环境跨域配置(需要设置具体的域名)
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '').split(',')
if not CORS_ORIGINS or CORS_ORIGINS == ['']:
# 如果没有设置,使用默认值而不是抛出异常
CORS_ORIGINS = ['https://yourdomain.com']
# 生产环境必须设置CORS域名
raise ValueError("生产环境必须设置CORS_ORIGINS环境变量指定允许的域名")
# 生产环境性能配置
SQLALCHEMY_ENGINE_OPTIONS = {

View File

@@ -9,4 +9,5 @@ waitress>=3.0.0
redis>=5.0.1
pywin32>=306
requests>=2.28.0
psutil>=5.9.0
psutil>=5.9.0
bcrypt>=4.0.0

View File

@@ -37,7 +37,7 @@ def init_admin(app):
login_manager.login_message = '请先登录'
# 创建Admin实例
admin = Admin(app, name='提示词大师后台管理', template_mode='bootstrap4')
admin = Admin(app, name='提示词大师后台管理')
# 注册视图
admin.add_view(UserAdminView(User, db.session, name='用户管理', endpoint='admin_user'))

View File

@@ -1,23 +1,36 @@
import os
import warnings
from dotenv import load_dotenv
# 在配置类定义前加载环境变量
load_dotenv()
# 警告:此文件已弃用,请使用 config/ 目录下的配置系统
warnings.warn("src/flask_prompt_master/config.py 已弃用,请使用新的配置系统 (config/ 目录)", DeprecationWarning)
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-key'
# MySQL数据库配置 - 腾讯云数据库
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:!Rjb12191@gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com:24936/pro_db?charset=utf8mb4'
SECRET_KEY = os.environ.get('SECRET_KEY')
if not SECRET_KEY:
raise ValueError("SECRET_KEY 环境变量未设置")
# MySQL数据库配置 - 从环境变量读取
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
if not SQLALCHEMY_DATABASE_URI:
raise ValueError("DATABASE_URL 环境变量未设置")
SQLALCHEMY_TRACK_MODIFICATIONS = False
# OpenAI兼容API配置
LLM_API_URL = os.environ.get('LLM_API_URL') or 'https://api.deepseek.com/v1'
LLM_API_KEY = os.environ.get('LLM_API_KEY') or 'sk-fdf7cc1c73504e628ec0119b7e11b8cc'
LLM_API_KEY = os.environ.get('LLM_API_KEY')
if not LLM_API_KEY:
raise ValueError("LLM_API_KEY 环境变量未设置")
# 微信小程序配置
WX_APPID = os.environ.get('WX_APPID') or 'wx2c65877d37fc29bf' # 替换为你的小程序 appid
WX_SECRET = os.environ.get('WX_SECRET') or '89aa97dda3c1347c6ae3d6ab4627f1f4' # 替换为你的小程序 secret
# 添加跨域支持
CORS_ORIGINS = ['*'] # 生产环境建议设置具体域名
WX_APPID = os.environ.get('WX_APPID')
WX_SECRET = os.environ.get('WX_SECRET')
if not WX_APPID or not WX_SECRET:
raise ValueError("WX_APPID 和 WX_SECRET 环境变量未设置")
# 跨域配置
CORS_ORIGINS = os.environ.get('CORS_ORIGINS', '*').split(',')

View File

@@ -5,6 +5,7 @@
import hashlib
import os
import re
import bcrypt
from datetime import datetime, timedelta
from src.flask_prompt_master import db
from src.flask_prompt_master.models.models import User
@@ -14,13 +15,20 @@ class AuthService:
@staticmethod
def generate_salt():
"""生成随机盐值"""
return os.urandom(16).hex()
"""生成随机盐值为兼容性保留新用户使用bcrypt"""
return 'bcrypt' # 新用户使用bcrypt标识为'bcrypt'
@staticmethod
def hash_password(password, salt):
"""使用盐值加密密码"""
return hashlib.md5((password + salt).encode('utf-8')).hexdigest()
"""加密密码新用户使用bcrypt旧用户保持MD5"""
if salt == 'bcrypt':
# 使用bcrypt哈希密码
password_bytes = password.encode('utf-8')
hashed = bcrypt.hashpw(password_bytes, bcrypt.gensalt())
return hashed.decode('utf-8')
else:
# 旧MD5哈希方式向后兼容
return hashlib.md5((password + salt).encode('utf-8')).hexdigest()
@staticmethod
def validate_password(password):
@@ -28,6 +36,21 @@ class AuthService:
if len(password) < 6:
return False, "密码长度至少6位"
return True, "密码格式正确"
@staticmethod
def check_password(password, hashed_password, salt):
"""验证密码支持bcrypt和MD5"""
if salt == 'bcrypt':
# 使用bcrypt验证
try:
password_bytes = password.encode('utf-8')
hashed_bytes = hashed_password.encode('utf-8')
return bcrypt.checkpw(password_bytes, hashed_bytes)
except Exception:
return False
else:
# 使用MD5验证
return hashlib.md5((password + salt).encode('utf-8')).hexdigest() == hashed_password
@staticmethod
def validate_email(email):
@@ -135,8 +158,7 @@ class AuthService:
return {'success': False, 'message': '账户已被禁用'}
# 验证密码
hashed_password = AuthService.hash_password(login_pwd, user.login_salt)
if hashed_password != user.login_pwd:
if not AuthService.check_password(login_pwd, user.login_pwd, user.login_salt):
return {'success': False, 'message': '用户名或密码错误'}
# 更新最后登录时间
@@ -245,8 +267,7 @@ class AuthService:
return {'success': False, 'message': '用户不存在'}
# 验证旧密码
old_hashed = AuthService.hash_password(old_password, user.login_salt)
if old_hashed != user.login_pwd:
if not AuthService.check_password(old_password, user.login_pwd, user.login_salt):
return {'success': False, 'message': '原密码错误'}
# 验证新密码

166
test_config.py Normal file
View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试新的配置系统
"""
import os
import sys
from dotenv import load_dotenv
# 加载测试环境变量
env_file = '.env.test'
if os.path.exists(env_file):
load_dotenv(env_file)
print(f"[OK] 已加载环境变量文件: {env_file}")
else:
print(f"[WARN] 环境变量文件不存在: {env_file}")
print("正在检查默认环境变量...")
load_dotenv()
def test_config_system():
"""测试配置系统"""
print("\n[TEST] 测试配置系统...")
try:
# 导入新的配置系统
from config import get_config
# 获取配置
config_class = get_config()
print(f"[OK] 获取配置类: {config_class.__name__}")
# 检查环境变量
env = os.environ.get('FLASK_ENV', 'development')
print(f"[OK] 当前环境: {env}")
# 创建配置实例
config = config_class()
print(f"[OK] 配置实例创建成功")
# 检查关键配置
print(f"[OK] SECRET_KEY: {'已设置' if config.SECRET_KEY else '未设置'}")
print(f"[OK] DATABASE_URL: {'已设置' if config.SQLALCHEMY_DATABASE_URI else '未设置'}")
print(f"[OK] LLM_API_KEY: {'已设置' if config.LLM_API_KEY else '未设置'}")
return True
except Exception as e:
print(f"[ERROR] 配置系统测试失败: {str(e)}")
import traceback
traceback.print_exc()
return False
def test_old_config_compatibility():
"""测试旧配置文件的兼容性"""
print("\n[TEST] 测试旧配置文件兼容性...")
try:
# 测试旧的config.py
import warnings
# 捕获警告
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# 导入旧的config.py会触发警告
from config import Config as OldConfig
if w:
print(f"[OK] 检测到弃用警告: {w[0].message}")
else:
print("[WARN] 未检测到弃用警告(可能有问题)")
# 尝试创建实例
try:
old_config = OldConfig()
print(f"[OK] 旧配置类可实例化")
return True
except ValueError as e:
print(f"[OK] 旧配置类按要求抛出错误(正常): {e}")
return True
except Exception as e:
print(f"[ERROR] 旧配置兼容性测试失败: {str(e)}")
return False
def test_app_factory():
"""测试应用工厂"""
print("\n[TEST] 测试应用工厂...")
try:
from src.flask_prompt_master import create_app
# 创建应用(应该使用新的配置系统)
app = create_app()
print(f"[OK] 应用创建成功")
# 检查应用配置
print(f"[OK] 应用调试模式: {app.config.get('DEBUG', '未设置')}")
print(f"[OK] 数据库URI: {app.config.get('SQLALCHEMY_DATABASE_URI', '未设置')[:50]}...")
return True
except Exception as e:
print(f"[ERROR] 应用工厂测试失败: {str(e)}")
import traceback
traceback.print_exc()
return False
def main():
"""主测试函数"""
print("=" * 60)
print("aitsc 项目配置系统测试")
print("=" * 60)
# 显示当前工作目录
print(f"工作目录: {os.getcwd()}")
# 检查环境变量文件
env_files = ['.env', '.env.test', 'env.example']
for env_file in env_files:
if os.path.exists(env_file):
print(f"[OK] 找到环境变量文件: {env_file}")
else:
print(f"[WARN] 未找到环境变量文件: {env_file}")
# 运行测试
tests = [
("配置系统", test_config_system),
("旧配置兼容性", test_old_config_compatibility),
("应用工厂", test_app_factory),
]
results = []
for test_name, test_func in tests:
success = test_func()
results.append((test_name, success))
# 汇总结果
print("\n" + "=" * 60)
print("测试结果汇总")
print("=" * 60)
all_passed = True
for test_name, success in results:
status = "[OK] 通过" if success else "[ERROR] 失败"
print(f"{test_name}: {status}")
if not success:
all_passed = False
print("\n" + "=" * 60)
if all_passed:
print("[SUCCESS] 所有测试通过!项目可以正常运行。")
print("\n下一步操作:")
print("1. 复制 .env.example 为 .env")
print("2. 在 .env 中填写实际的环境变量值")
print("3. 删除或备份旧的 .env 文件(包含硬编码密码)")
print("4. 运行 python run_dev.py 启动应用")
else:
print("[WARN] 部分测试失败,需要进一步检查。")
return all_passed
if __name__ == '__main__':
success = main()
sys.exit(0 if success else 1)