Spaces:
Sleeping
Sleeping
Add application file
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env +22 -0
- Dockerfile +23 -0
- __pycache__/database.cpython-314.pyc +0 -0
- __pycache__/main.cpython-314.pyc +0 -0
- config/__pycache__/default_companies.cpython-314.pyc +0 -0
- config/__pycache__/member_levels.cpython-314.pyc +0 -0
- config/__pycache__/templates.cpython-314.pyc +0 -0
- config/certification_types.py +35 -0
- config/default_companies.py +22 -0
- config/member_levels.py +77 -0
- config/templates.py +239 -0
- create_tables.py +23 -0
- database.py +111 -0
- main.py +181 -0
- middleware/trace.py +29 -0
- models/__pycache__/category.cpython-314.pyc +0 -0
- models/__pycache__/company.cpython-314.pyc +0 -0
- models/__pycache__/company_r2_config.cpython-314.pyc +0 -0
- models/__pycache__/contact.cpython-314.pyc +0 -0
- models/__pycache__/customer_relationship.cpython-314.pyc +0 -0
- models/__pycache__/media.cpython-314.pyc +0 -0
- models/__pycache__/member_level.cpython-314.pyc +0 -0
- models/__pycache__/membership.cpython-314.pyc +0 -0
- models/__pycache__/operation_log.cpython-314.pyc +0 -0
- models/__pycache__/product.cpython-314.pyc +0 -0
- models/__pycache__/product_certification.cpython-314.pyc +0 -0
- models/__pycache__/product_media.cpython-314.pyc +0 -0
- models/__pycache__/product_packaging.cpython-314.pyc +0 -0
- models/__pycache__/template.cpython-314.pyc +0 -0
- models/__pycache__/user.cpython-314.pyc +0 -0
- models/category.py +19 -0
- models/company.py +24 -0
- models/company_r2_config.py +17 -0
- models/contact.py +28 -0
- models/customer_relationship.py +18 -0
- models/media.py +87 -0
- models/member_level.py +26 -0
- models/membership.py +14 -0
- models/operation_log.py +17 -0
- models/product.py +46 -0
- models/product_certification.py +17 -0
- models/product_media.py +20 -0
- models/product_packaging.py +18 -0
- models/site_visit.py +30 -0
- models/template.py +20 -0
- models/user.py +15 -0
- requirements.txt +27 -0
- routers/__pycache__/auth.cpython-314.pyc +0 -0
- routers/__pycache__/categories.cpython-314.pyc +0 -0
- routers/__pycache__/company.cpython-314.pyc +0 -0
.env
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 数据库连接信息
|
| 2 |
+
DATABASE_URL="mysql+pymysql://root:mysql00@localhost:3306/xxxx"
|
| 3 |
+
#DATABASE_URL="mysql+pymysql://root:root%40000@101.126.146.60:33066/xxxx"
|
| 4 |
+
|
| 5 |
+
# 用于JWT签名的密钥
|
| 6 |
+
SECRET_KEY="your-secret-key-here"
|
| 7 |
+
|
| 8 |
+
# 算法
|
| 9 |
+
ALGORITHM="HS256"
|
| 10 |
+
|
| 11 |
+
# 访问令牌过期时间(分钟)
|
| 12 |
+
ACCESS_TOKEN_EXPIRE_MINUTES=600
|
| 13 |
+
|
| 14 |
+
# Cloudflare R2 配置
|
| 15 |
+
R2_ACCOUNT_ID="efcdfff6eb9a02a52320c089f89bcd39"
|
| 16 |
+
R2_ACCESS_KEY_ID="5a43e2f19b371745dc68f8a3e939fe40"
|
| 17 |
+
R2_SECRET_ACCESS_KEY="cf6421cc77954373a34b65925d34c9234e49679d8d31bc46a1404862af5c308e"
|
| 18 |
+
R2_BUCKET_NAME="yomaton"
|
| 19 |
+
R2_PUBLIC_URL="https://img.yomaton.com"
|
| 20 |
+
|
| 21 |
+
# 是否启用 R2 上传(true/false)
|
| 22 |
+
R2_ENABLED=true
|
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 基础镜像:python 3.11-slim(你的项目唯一稳定版本)
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# 创建用户(和示例保持一致)
|
| 5 |
+
RUN useradd -m -u 1000 user
|
| 6 |
+
USER user
|
| 7 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 8 |
+
|
| 9 |
+
# 工作目录
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
# 复制依赖清单
|
| 13 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 14 |
+
|
| 15 |
+
# 安装依赖 + 额外缺失包(pillow, email-validator, pydantic[email])
|
| 16 |
+
RUN pip install --no-cache-dir pillow email-validator pydantic[email] -i https://mirrors.aliyun.com/pypi/simple/ \
|
| 17 |
+
&& pip install --no-cache-dir --upgrade -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
| 18 |
+
|
| 19 |
+
# 复制全部项目代码
|
| 20 |
+
COPY --chown=user . /app
|
| 21 |
+
|
| 22 |
+
# 启动命令(你的命令:main.py runserver 8000)
|
| 23 |
+
CMD ["python", "main.py", "runserver", "0.0.0.0:8000"]
|
__pycache__/database.cpython-314.pyc
ADDED
|
Binary file (4.28 kB). View file
|
|
|
__pycache__/main.cpython-314.pyc
ADDED
|
Binary file (10.1 kB). View file
|
|
|
config/__pycache__/default_companies.cpython-314.pyc
ADDED
|
Binary file (564 Bytes). View file
|
|
|
config/__pycache__/member_levels.cpython-314.pyc
ADDED
|
Binary file (1.75 kB). View file
|
|
|
config/__pycache__/templates.cpython-314.pyc
ADDED
|
Binary file (7.06 kB). View file
|
|
|
config/certification_types.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 商品认证类型配置文件
|
| 2 |
+
# 包含主流外贸产品常用的认证类型
|
| 3 |
+
|
| 4 |
+
DEFAULT_CERTIFICATION_TYPES = [
|
| 5 |
+
{
|
| 6 |
+
"value": "CE",
|
| 7 |
+
"label": "CE",
|
| 8 |
+
"description": "欧盟强制性安全认证"
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"value": "FDA",
|
| 12 |
+
"label": "FDA",
|
| 13 |
+
"description": "美国食品药品监督管理局认证"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"value": "RoHS",
|
| 17 |
+
"label": "RoHS",
|
| 18 |
+
"description": "欧盟环保认证"
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"value": "FCC",
|
| 22 |
+
"label": "FCC",
|
| 23 |
+
"description": "美国联邦通信委员会认证"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"value": "ISO",
|
| 27 |
+
"label": "ISO",
|
| 28 |
+
"description": "国际标准化组织认证"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"value": "OTHER",
|
| 32 |
+
"label": "OTHER",
|
| 33 |
+
"description": "其他认证类型"
|
| 34 |
+
}
|
| 35 |
+
]
|
config/default_companies.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 默认会员公司数据
|
| 2 |
+
DEFAULT_COMPANIES = [
|
| 3 |
+
{
|
| 4 |
+
"company_code": "ymt0",
|
| 5 |
+
"name": "云贸通测试公司",
|
| 6 |
+
"description": "云贸通智能科技测试公司",
|
| 7 |
+
"phone": "13800138000",
|
| 8 |
+
"email": "test@ymt.com",
|
| 9 |
+
"address": "北京市朝阳区"
|
| 10 |
+
}
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
# 默认会员账号数据
|
| 14 |
+
DEFAULT_USERS = [
|
| 15 |
+
{
|
| 16 |
+
"username": "ymt0",
|
| 17 |
+
"email": "ymt0@ymt.com",
|
| 18 |
+
"password": "ymt123456", # 原始密码
|
| 19 |
+
"is_superadmin": False,
|
| 20 |
+
"company_code": "ymt0"
|
| 21 |
+
}
|
| 22 |
+
]
|
config/member_levels.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 会员等级配置文件
|
| 2 |
+
# 包含不同会员等级的权益配置
|
| 3 |
+
|
| 4 |
+
DEFAULT_MEMBER_LEVELS = [
|
| 5 |
+
{
|
| 6 |
+
"level": 0,
|
| 7 |
+
"name": "普通会员",
|
| 8 |
+
"description": "普通会员",
|
| 9 |
+
"annual_fee": 5980,
|
| 10 |
+
"discount_price": 5980,
|
| 11 |
+
"deployment_nodes": 1,
|
| 12 |
+
"email_marketing_limit": 10000,
|
| 13 |
+
"product_sku_limit": 100,
|
| 14 |
+
"sku_image_limit": 10,
|
| 15 |
+
"image_size_limit": 5,
|
| 16 |
+
"sku_video_limit": 1,
|
| 17 |
+
"image_storage_limit": 10,
|
| 18 |
+
"template_access": False,
|
| 19 |
+
"geo_seo_access": False,
|
| 20 |
+
"social_automation_access": False,
|
| 21 |
+
"custom_development_access": False
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"level": 1,
|
| 25 |
+
"name": "金卡会员",
|
| 26 |
+
"description": "金卡会员",
|
| 27 |
+
"annual_fee": 9680,
|
| 28 |
+
"discount_price": 9680,
|
| 29 |
+
"deployment_nodes": 3,
|
| 30 |
+
"email_marketing_limit": 30000,
|
| 31 |
+
"product_sku_limit": 1000,
|
| 32 |
+
"sku_image_limit": 10,
|
| 33 |
+
"image_size_limit": 5,
|
| 34 |
+
"sku_video_limit": 1,
|
| 35 |
+
"image_storage_limit": 10,
|
| 36 |
+
"template_access":True,
|
| 37 |
+
"geo_seo_access": True,
|
| 38 |
+
"social_automation_access": False,
|
| 39 |
+
"custom_development_access": False
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"level": 2,
|
| 43 |
+
"name": "钻石会员",
|
| 44 |
+
"description": "钻石会员",
|
| 45 |
+
"annual_fee": 16800,
|
| 46 |
+
"discount_price": 16800,
|
| 47 |
+
"deployment_nodes": 6,
|
| 48 |
+
"email_marketing_limit": 30000,
|
| 49 |
+
"product_sku_limit": 10000,
|
| 50 |
+
"sku_image_limit": 10,
|
| 51 |
+
"image_size_limit": 5,
|
| 52 |
+
"sku_video_limit": 1,
|
| 53 |
+
"image_storage_limit": 10,
|
| 54 |
+
"template_access": True,
|
| 55 |
+
"geo_seo_access": True,
|
| 56 |
+
"social_automation_access": True,
|
| 57 |
+
"custom_development_access": False
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"level": 3,
|
| 61 |
+
"name": "皇冠会员",
|
| 62 |
+
"description": "定制化会员",
|
| 63 |
+
"annual_fee": 26800,
|
| 64 |
+
"discount_price": 26800,
|
| 65 |
+
"deployment_nodes": 6,
|
| 66 |
+
"email_marketing_limit": 50000,
|
| 67 |
+
"product_sku_limit": 10000,
|
| 68 |
+
"sku_image_limit": 10,
|
| 69 |
+
"image_size_limit": 5,
|
| 70 |
+
"sku_video_limit": 1,
|
| 71 |
+
"image_storage_limit": 10,
|
| 72 |
+
"template_access": True,
|
| 73 |
+
"geo_seo_access": True,
|
| 74 |
+
"social_automation_access": True,
|
| 75 |
+
"custom_development_access": True
|
| 76 |
+
}
|
| 77 |
+
]
|
config/templates.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 模板配置文件
|
| 2 |
+
# 包含20+主流外贸独立站模板风格
|
| 3 |
+
|
| 4 |
+
DEFAULT_TEMPLATES = [
|
| 5 |
+
{
|
| 6 |
+
"key": "Home",
|
| 7 |
+
"name": "通用外贸",
|
| 8 |
+
"description": "适合大多数外贸行业的通用模板",
|
| 9 |
+
"color": "#3498db",
|
| 10 |
+
"features": ["响应式设计", "多语言支持", "产品展示"],
|
| 11 |
+
"applicable_scene": "通用外贸行业",
|
| 12 |
+
"regions": ["全球"]
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"key": "Electronics",
|
| 16 |
+
"name": "电子产品",
|
| 17 |
+
"description": "专为电子产品行业设计的模板",
|
| 18 |
+
"color": "#27ae60",
|
| 19 |
+
"features": ["产品对比", "技术规格", "客户评价"],
|
| 20 |
+
"applicable_scene": "电子产品、数码设备",
|
| 21 |
+
"regions": ["欧美", "全球"]
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"key": "Fashion",
|
| 25 |
+
"name": "时尚服装",
|
| 26 |
+
"description": "专为时尚服装行业设计的模板",
|
| 27 |
+
"color": "#e74c3c",
|
| 28 |
+
"features": ["图片轮播", "搭配推荐", "季节趋势"],
|
| 29 |
+
"applicable_scene": "服装、配饰、时尚产品",
|
| 30 |
+
"regions": ["欧美", "全球"]
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"key": "Furniture",
|
| 34 |
+
"name": "家居家具",
|
| 35 |
+
"description": "专为家居家具行业设计的模板",
|
| 36 |
+
"color": "#f39c12",
|
| 37 |
+
"features": ["3D展示", "空间搭配", "材质说明"],
|
| 38 |
+
"applicable_scene": "家具、家居用品、装饰品",
|
| 39 |
+
"regions": ["全球"]
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"key": "Robotics",
|
| 43 |
+
"name": "机器人",
|
| 44 |
+
"description": "专为机器人行业设计的模板",
|
| 45 |
+
"color": "#9b59b6",
|
| 46 |
+
"features": ["技术参数", "应用场景", "视频展示"],
|
| 47 |
+
"applicable_scene": "工业机器人、服务机器人、智能设备",
|
| 48 |
+
"regions": ["欧美", "全球"]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"key": "Medical",
|
| 52 |
+
"name": "医疗器械",
|
| 53 |
+
"description": "专为医疗器械行业设计的模板",
|
| 54 |
+
"color": "#1abc9c",
|
| 55 |
+
"features": ["产品认证", "技术文档", "案例展示"],
|
| 56 |
+
"applicable_scene": "医疗设备、健康产品、医疗器械",
|
| 57 |
+
"regions": ["欧美", "全球"]
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"key": "Automotive",
|
| 61 |
+
"name": "汽车配件",
|
| 62 |
+
"description": "专为汽车配件行业设计的模板",
|
| 63 |
+
"color": "#e67e22",
|
| 64 |
+
"features": ["适配车型", "安装指南", "质量认证"],
|
| 65 |
+
"applicable_scene": "汽车零部件、汽车用品、改装配件",
|
| 66 |
+
"regions": ["欧美", "全球"]
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"key": "Beauty",
|
| 70 |
+
"name": "美容美妆",
|
| 71 |
+
"description": "专为美容美妆行业设计的模板",
|
| 72 |
+
"color": "#f1948a",
|
| 73 |
+
"features": ["产品功效", "使用方法", "用户评价"],
|
| 74 |
+
"applicable_scene": "化妆品、护肤品、美容工具",
|
| 75 |
+
"regions": ["全球"]
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"key": "Sports",
|
| 79 |
+
"name": "运动户外",
|
| 80 |
+
"description": "专为运动户外行业设计的模板",
|
| 81 |
+
"color": "#e84118",
|
| 82 |
+
"features": ["产品参数", "使用场景", "用户体验"],
|
| 83 |
+
"applicable_scene": "运动器材、户外装备、健身用品",
|
| 84 |
+
"regions": ["全球"]
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"key": "Toys",
|
| 88 |
+
"name": "玩具礼品",
|
| 89 |
+
"description": "专为玩具礼品行业设计的模板",
|
| 90 |
+
"color": "#e1b12c",
|
| 91 |
+
"features": ["年龄适用", "安全认证", "创意展示"],
|
| 92 |
+
"applicable_scene": "儿童玩具、礼品、益智产品",
|
| 93 |
+
"regions": ["全球"]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"key": "Jewelry",
|
| 97 |
+
"name": "珠宝首饰",
|
| 98 |
+
"description": "专为珠宝首饰行业设计的模板",
|
| 99 |
+
"color": "#8e44ad",
|
| 100 |
+
"features": ["材质说明", "工艺展示", "证书认证"],
|
| 101 |
+
"applicable_scene": "珠宝、首饰、配饰",
|
| 102 |
+
"regions": ["全球"]
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"key": "Food",
|
| 106 |
+
"name": "食品饮料",
|
| 107 |
+
"description": "专为食品饮料行业设计的模板",
|
| 108 |
+
"color": "#c0392b",
|
| 109 |
+
"features": ["营养成分", "生产工艺", "品尝体验"],
|
| 110 |
+
"applicable_scene": "食品、饮料、保健品",
|
| 111 |
+
"regions": ["全球"]
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"key": "Pet",
|
| 115 |
+
"name": "宠物用品",
|
| 116 |
+
"description": "专为宠物用品行业设计的模板",
|
| 117 |
+
"color": "#16a085",
|
| 118 |
+
"features": ["适用宠物", "产品功能", "用户反馈"],
|
| 119 |
+
"applicable_scene": "宠物食品、宠物用品、宠物玩具",
|
| 120 |
+
"regions": ["欧美", "全球"]
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"key": "Office",
|
| 124 |
+
"name": "办公文具",
|
| 125 |
+
"description": "专为办公文具行业设计的模板",
|
| 126 |
+
"color": "#34495e",
|
| 127 |
+
"features": ["产品规格", "适用场景", "批量采购"],
|
| 128 |
+
"applicable_scene": "办公用品、文具、办公设备",
|
| 129 |
+
"regions": ["全球"]
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"key": "Building",
|
| 133 |
+
"name": "建筑建材",
|
| 134 |
+
"description": "专为建筑建材行业设计的模板",
|
| 135 |
+
"color": "#7f8c8d",
|
| 136 |
+
"features": ["材料规格", "施工指南", "质量认证"],
|
| 137 |
+
"applicable_scene": "建筑材料、装饰材料、五金工具",
|
| 138 |
+
"regions": ["全球"]
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"key": "Packaging",
|
| 142 |
+
"name": "包装印刷",
|
| 143 |
+
"description": "专为包装印刷行业设计的模板",
|
| 144 |
+
"color": "#2c3e50",
|
| 145 |
+
"features": ["定制服务", "印刷工艺", "样品展示"],
|
| 146 |
+
"applicable_scene": "包装材料、印刷制品、定制包装",
|
| 147 |
+
"regions": ["全球"]
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"key": "Energy",
|
| 151 |
+
"name": "能源环保",
|
| 152 |
+
"description": "专为能源环保行业设计的模板",
|
| 153 |
+
"color": "#22a7f0",
|
| 154 |
+
"features": ["节能效果", "环保认证", "技术参数"],
|
| 155 |
+
"applicable_scene": "太阳能产品、节能设备、环保材料",
|
| 156 |
+
"regions": ["欧美", "全球"]
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"key": "Textile",
|
| 160 |
+
"name": "纺织面料",
|
| 161 |
+
"description": "专为纺织面料行业设计的模板",
|
| 162 |
+
"color": "#ec407a",
|
| 163 |
+
"features": ["面料材质", "规格参数", "应用领域"],
|
| 164 |
+
"applicable_scene": "纺织品、面料、服装辅料",
|
| 165 |
+
"regions": ["全球"]
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"key": "Hardware",
|
| 169 |
+
"name": "五金工具",
|
| 170 |
+
"description": "专为五金工具行业设计的模板",
|
| 171 |
+
"color": "#7d5fff",
|
| 172 |
+
"features": ["工具规格", "使用方法", "质量保证"],
|
| 173 |
+
"applicable_scene": "五金工具、电动工具、手动工具",
|
| 174 |
+
"regions": ["全球"]
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"key": "Chemicals",
|
| 178 |
+
"name": "化工产品",
|
| 179 |
+
"description": "专为化工产品行业设计的模板",
|
| 180 |
+
"color": "#00b894",
|
| 181 |
+
"features": ["产品规格", "安全说明", "应用领域"],
|
| 182 |
+
"applicable_scene": "化工原料、化学制品、工业化学品",
|
| 183 |
+
"regions": ["全球"]
|
| 184 |
+
},
|
| 185 |
+
{
|
| 186 |
+
"key": "Agriculture",
|
| 187 |
+
"name": "农业产品",
|
| 188 |
+
"description": "专为农业产品行业设计的模板",
|
| 189 |
+
"color": "#6ab04c",
|
| 190 |
+
"features": ["产品规格", "种植技术", "质量认证"],
|
| 191 |
+
"applicable_scene": "农产品、农业设备、农业用品",
|
| 192 |
+
"regions": ["全球"]
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"key": "SoutheastAsia",
|
| 196 |
+
"name": "东南亚风格",
|
| 197 |
+
"description": "专为东南亚市场设计的模板,融入当地文化元素",
|
| 198 |
+
"color": "#ff6b6b",
|
| 199 |
+
"features": ["热带风格", "本地化语言", "移动优先"],
|
| 200 |
+
"applicable_scene": "面向东南亚市场的各类产品",
|
| 201 |
+
"regions": ["东南亚"]
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"key": "MiddleEast",
|
| 205 |
+
"name": "中东风格",
|
| 206 |
+
"description": "专为中东市场设计的模板,考虑当地文化和宗教因素",
|
| 207 |
+
"color": "#feca57",
|
| 208 |
+
"features": ["符合当地文化", "多语言支持", "尊贵感设计"],
|
| 209 |
+
"applicable_scene": "面向中东市场的各类产品",
|
| 210 |
+
"regions": ["中东"]
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"key": "European",
|
| 214 |
+
"name": "欧洲风格",
|
| 215 |
+
"description": "专为欧洲市场设计的模板,体现欧洲美学和品味",
|
| 216 |
+
"color": "#54a0ff",
|
| 217 |
+
"features": ["简约优雅", "高质量视觉", "环保理念"],
|
| 218 |
+
"applicable_scene": "面向欧洲市场的各类产品",
|
| 219 |
+
"regions": ["欧洲"]
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"key": "American",
|
| 223 |
+
"name": "美国风格",
|
| 224 |
+
"description": "专为美国市场设计的模板,体现美国消费者偏好",
|
| 225 |
+
"color": "#48dbfb",
|
| 226 |
+
"features": ["大胆设计", "清晰信息", "快速加载"],
|
| 227 |
+
"applicable_scene": "面向美国市场的各类产品",
|
| 228 |
+
"regions": ["美洲"]
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"key": "EastAsia",
|
| 232 |
+
"name": "东亚风格",
|
| 233 |
+
"description": "专为东亚市场设计的模板,融入东亚文化元素",
|
| 234 |
+
"color": "#ff9ff3",
|
| 235 |
+
"features": ["精致设计", "细腻体验", "社交分享"],
|
| 236 |
+
"applicable_scene": "面向东亚市场的各类产品",
|
| 237 |
+
"regions": ["东亚"]
|
| 238 |
+
}
|
| 239 |
+
]
|
create_tables.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from database import engine, Base
|
| 2 |
+
from models.product import Product
|
| 3 |
+
from models.company import Company
|
| 4 |
+
from models.user import User
|
| 5 |
+
from models.category import Category
|
| 6 |
+
from models.membership import Membership
|
| 7 |
+
from models.operation_log import OperationLog
|
| 8 |
+
from models.contact import Contact
|
| 9 |
+
from models.template import Template
|
| 10 |
+
from models.member_level import MemberLevel
|
| 11 |
+
from models.customer_relationship import CustomerRelationship
|
| 12 |
+
|
| 13 |
+
print("=" * 80)
|
| 14 |
+
print("开始创建数据库表...")
|
| 15 |
+
print("=" * 80)
|
| 16 |
+
|
| 17 |
+
# 创建所有表
|
| 18 |
+
Base.metadata.create_all(bind=engine)
|
| 19 |
+
|
| 20 |
+
print()
|
| 21 |
+
print("=" * 80)
|
| 22 |
+
print("所有表创建完成!")
|
| 23 |
+
print("=" * 80)
|
database.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import create_engine
|
| 2 |
+
from sqlalchemy.ext.declarative import declarative_base
|
| 3 |
+
from sqlalchemy.orm import sessionmaker
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
import os
|
| 6 |
+
import pymysql
|
| 7 |
+
|
| 8 |
+
# 加载环境变量
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
# 获取数据库连接 URL
|
| 12 |
+
DATABASE_URL = os.getenv("DATABASE_URL")
|
| 13 |
+
|
| 14 |
+
# 处理MySQL数据库自动创建
|
| 15 |
+
if DATABASE_URL.startswith("mysql"):
|
| 16 |
+
# 解析数据库连接信息
|
| 17 |
+
import re
|
| 18 |
+
match = re.match(r"mysql\+pymysql://([^:]+):([^@]+)@([^:]+):(\d+)/(.+)", DATABASE_URL)
|
| 19 |
+
if match:
|
| 20 |
+
user, password, host, port, db_name = match.groups()
|
| 21 |
+
port = int(port)
|
| 22 |
+
|
| 23 |
+
# 先连接到MySQL服务器
|
| 24 |
+
try:
|
| 25 |
+
# 连接到MySQL服务器(不指定数据库)
|
| 26 |
+
conn = pymysql.connect(
|
| 27 |
+
host=host,
|
| 28 |
+
port=port,
|
| 29 |
+
user=user,
|
| 30 |
+
password=password,
|
| 31 |
+
charset='utf8mb4'
|
| 32 |
+
)
|
| 33 |
+
cursor = conn.cursor()
|
| 34 |
+
|
| 35 |
+
# 检查数据库是否存在
|
| 36 |
+
cursor.execute(f"SHOW DATABASES LIKE '{db_name}'")
|
| 37 |
+
result = cursor.fetchone()
|
| 38 |
+
|
| 39 |
+
# 如果数据库不存在,创建它
|
| 40 |
+
if not result:
|
| 41 |
+
cursor.execute(f"CREATE DATABASE {db_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
|
| 42 |
+
print(f"数据库 {db_name} 已创建")
|
| 43 |
+
|
| 44 |
+
cursor.close()
|
| 45 |
+
conn.close()
|
| 46 |
+
except Exception as e:
|
| 47 |
+
print(f"创建数据库时出错: {e}")
|
| 48 |
+
|
| 49 |
+
# 创建数据库引擎
|
| 50 |
+
# 对于SQLite,需要添加check_same_thread参数
|
| 51 |
+
if DATABASE_URL.startswith("sqlite"):
|
| 52 |
+
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
| 53 |
+
else:
|
| 54 |
+
# 对于MySQL,添加charset参数确保utf8编码
|
| 55 |
+
if DATABASE_URL.startswith("mysql"):
|
| 56 |
+
engine = create_engine(DATABASE_URL + "?charset=utf8mb4")
|
| 57 |
+
else:
|
| 58 |
+
engine = create_engine(DATABASE_URL)
|
| 59 |
+
|
| 60 |
+
# 创建会话工厂
|
| 61 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 62 |
+
|
| 63 |
+
# 创建基类
|
| 64 |
+
Base = declarative_base()
|
| 65 |
+
|
| 66 |
+
# 检查表结构并自动更新
|
| 67 |
+
def check_and_update_tables():
|
| 68 |
+
"""检查表结构是否与models一致,不一致则自动更新"""
|
| 69 |
+
try:
|
| 70 |
+
# 导入所有模型,确保它们被注册到 Base
|
| 71 |
+
from models.product import Product
|
| 72 |
+
from models.company import Company
|
| 73 |
+
from models.user import User
|
| 74 |
+
from models.category import Category
|
| 75 |
+
from models.membership import Membership
|
| 76 |
+
from models.operation_log import OperationLog
|
| 77 |
+
from models.contact import Contact
|
| 78 |
+
from models.template import Template
|
| 79 |
+
from models.member_level import MemberLevel
|
| 80 |
+
from models.customer_relationship import CustomerRelationship
|
| 81 |
+
from models.product_certification import ProductCertification
|
| 82 |
+
from models.product_media import ProductMedia
|
| 83 |
+
from models.product_packaging import ProductPackaging
|
| 84 |
+
from models.company_r2_config import CompanyR2Config
|
| 85 |
+
from models.media import Media, MediaTag, MediaTagMapping, MediaDirectory
|
| 86 |
+
|
| 87 |
+
# 检查数据库连接
|
| 88 |
+
with engine.connect() as conn:
|
| 89 |
+
print("数据库连接成功")
|
| 90 |
+
|
| 91 |
+
# 自动创建不存在的表
|
| 92 |
+
Base.metadata.create_all(bind=engine)
|
| 93 |
+
print("表结构检查完成,不存在的表已创建")
|
| 94 |
+
|
| 95 |
+
# 注意:SQLAlchemy的create_all()只会创建不存在的表,不会更新已存在表的结构
|
| 96 |
+
# 对于表结构的更新,建议使用Alembic迁移工具
|
| 97 |
+
# print("提示:已存在表的结构更新需要使用Alembic迁移工具")
|
| 98 |
+
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f"检查表结构时出错: {e}")
|
| 101 |
+
|
| 102 |
+
# 调用检查表结构的函数
|
| 103 |
+
check_and_update_tables()
|
| 104 |
+
|
| 105 |
+
# 依赖项,用于获取数据库会话
|
| 106 |
+
def get_db():
|
| 107 |
+
db = SessionLocal()
|
| 108 |
+
try:
|
| 109 |
+
yield db
|
| 110 |
+
finally:
|
| 111 |
+
db.close()
|
main.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
from fastapi.responses import FileResponse
|
| 5 |
+
from routers import auth, company, categories, products, product_certifications, product_media, product_packaging, membership, operation_log, contact, template, customer_relationship, member_level, r2_config, media, statistics
|
| 6 |
+
from database import engine, Base, get_db
|
| 7 |
+
from sqlalchemy.orm import Session
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
from datetime import datetime, timedelta
|
| 13 |
+
|
| 14 |
+
# 导入中间件
|
| 15 |
+
from middleware.trace import TraceMiddleware
|
| 16 |
+
|
| 17 |
+
# 导入日志模块
|
| 18 |
+
from utils.logger import logger
|
| 19 |
+
|
| 20 |
+
# 加载 .env 文件
|
| 21 |
+
load_dotenv()
|
| 22 |
+
|
| 23 |
+
# 导入模板配置
|
| 24 |
+
from config.templates import DEFAULT_TEMPLATES
|
| 25 |
+
# 导入会员等级配置
|
| 26 |
+
from config.member_levels import DEFAULT_MEMBER_LEVELS
|
| 27 |
+
# 导入默认公司和用户配置
|
| 28 |
+
from config.default_companies import DEFAULT_COMPANIES, DEFAULT_USERS
|
| 29 |
+
|
| 30 |
+
# 导入所有模型,确保它们被注册到Base
|
| 31 |
+
from models.user import User
|
| 32 |
+
from models.company import Company
|
| 33 |
+
from models.category import Category
|
| 34 |
+
from models.product import Product
|
| 35 |
+
from models.membership import Membership
|
| 36 |
+
from models.operation_log import OperationLog
|
| 37 |
+
from models.contact import Contact
|
| 38 |
+
from models.template import Template
|
| 39 |
+
from models.customer_relationship import CustomerRelationship
|
| 40 |
+
from models.member_level import MemberLevel
|
| 41 |
+
from models.company_r2_config import CompanyR2Config
|
| 42 |
+
from models.media import Media, MediaTag, MediaTagMapping, MediaDirectory
|
| 43 |
+
from models.site_visit import SiteVisit
|
| 44 |
+
from utils.auth import get_password_hash
|
| 45 |
+
|
| 46 |
+
# 数据库表创建已在database.py中处理
|
| 47 |
+
|
| 48 |
+
app = FastAPI(
|
| 49 |
+
title="外贸独立站 API",
|
| 50 |
+
description="用于外贸独立站的后端 API",
|
| 51 |
+
version="1.0.0"
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
# 配置 CORS
|
| 55 |
+
app.add_middleware(
|
| 56 |
+
CORSMiddleware,
|
| 57 |
+
allow_origins=["*"], # 在生产环境中应该设置具体的前端域名
|
| 58 |
+
allow_credentials=True,
|
| 59 |
+
allow_methods=["*"],
|
| 60 |
+
allow_headers=["*"],
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
# 添加 TraceMiddleware 中间件
|
| 64 |
+
app.add_middleware(TraceMiddleware)
|
| 65 |
+
|
| 66 |
+
# 挂载静态文件目录(用于访问上传的图片)
|
| 67 |
+
BASE_DIR = Path(__file__).parent
|
| 68 |
+
IMAGES_DIR = BASE_DIR / "images"
|
| 69 |
+
if IMAGES_DIR.exists():
|
| 70 |
+
app.mount("/images", StaticFiles(directory=str(IMAGES_DIR)), name="images")
|
| 71 |
+
|
| 72 |
+
# 注册路由
|
| 73 |
+
# 注意:更具体的路由(如 /{product_number}/media)必须在较不具体的路由(如 /{product_number})之前注册
|
| 74 |
+
app.include_router(auth.router, prefix="/api/auth", tags=["认证"])
|
| 75 |
+
app.include_router(product_media.router, prefix="/api/products", tags=["产品媒体"])
|
| 76 |
+
app.include_router(product_certifications.router, prefix="/api/products", tags=["产品认证"])
|
| 77 |
+
app.include_router(product_packaging.router, prefix="/api/products", tags=["产品包装"])
|
| 78 |
+
app.include_router(products.router, prefix="/api/products", tags=["产品"])
|
| 79 |
+
app.include_router(company.router, prefix="/api/company", tags=["公司信息"])
|
| 80 |
+
app.include_router(categories.router, prefix="/api/categories", tags=["类目"])
|
| 81 |
+
app.include_router(membership.router, prefix="/api/membership", tags=["会员充值"])
|
| 82 |
+
app.include_router(operation_log.router, prefix="/api/operation-logs", tags=["操作日志"])
|
| 83 |
+
app.include_router(contact.router, prefix="/api/contact", tags=["联系信息管理"])
|
| 84 |
+
app.include_router(template.router, prefix="/api/templates", tags=["模板管理"])
|
| 85 |
+
app.include_router(customer_relationship.router, prefix="/api/customer-relationships", tags=["客户关系管理"])
|
| 86 |
+
app.include_router(member_level.router, prefix="/api/member-levels", tags=["会员等级管理"])
|
| 87 |
+
app.include_router(r2_config.router, prefix="/api/r2-config", tags=["R2 配置管理"])
|
| 88 |
+
app.include_router(media.router, prefix="/api/media", tags=["媒体素材管理"])
|
| 89 |
+
app.include_router(statistics.router, prefix="/api/statistics", tags=["统计指标"])
|
| 90 |
+
|
| 91 |
+
# 初始化超级管理员用户和默认类目
|
| 92 |
+
@app.on_event("startup")
|
| 93 |
+
def startup_event():
|
| 94 |
+
try:
|
| 95 |
+
db = next(get_db())
|
| 96 |
+
# 检查是否已有超级管理员
|
| 97 |
+
superadmin = db.query(User).filter(User.is_superadmin == True).first()
|
| 98 |
+
if not superadmin:
|
| 99 |
+
# 创建超级管理员
|
| 100 |
+
superadmin = User(
|
| 101 |
+
username="admin",
|
| 102 |
+
email="admin@mail.yomaton.com",
|
| 103 |
+
password=get_password_hash("admin00"),
|
| 104 |
+
is_superadmin=True,
|
| 105 |
+
company_code="0000"
|
| 106 |
+
)
|
| 107 |
+
db.add(superadmin)
|
| 108 |
+
db.commit()
|
| 109 |
+
logger.info("超级管理员已创建: username=admin, password=admin00")
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
# 创建默认模板
|
| 113 |
+
for template_data in DEFAULT_TEMPLATES:
|
| 114 |
+
# 转换features和regions列表为JSON字符串
|
| 115 |
+
template_data["features"] = json.dumps(template_data["features"], ensure_ascii=False)
|
| 116 |
+
template_data["regions"] = json.dumps(template_data["regions"], ensure_ascii=False)
|
| 117 |
+
|
| 118 |
+
existing_template = db.query(Template).filter(Template.key == template_data["key"]).first()
|
| 119 |
+
if not existing_template:
|
| 120 |
+
new_template = Template(**template_data)
|
| 121 |
+
db.add(new_template)
|
| 122 |
+
db.commit()
|
| 123 |
+
logger.info("默认模板已创建")
|
| 124 |
+
|
| 125 |
+
# 创建默认会员等级
|
| 126 |
+
for level_data in DEFAULT_MEMBER_LEVELS:
|
| 127 |
+
existing_level = db.query(MemberLevel).filter(MemberLevel.level == level_data["level"]).first()
|
| 128 |
+
if not existing_level:
|
| 129 |
+
new_level = MemberLevel(**level_data)
|
| 130 |
+
db.add(new_level)
|
| 131 |
+
db.commit()
|
| 132 |
+
logger.info("默认会员等级已创建")
|
| 133 |
+
|
| 134 |
+
# 创建默认公司
|
| 135 |
+
for company_data in DEFAULT_COMPANIES:
|
| 136 |
+
existing_company = db.query(Company).filter(Company.company_code == company_data["company_code"]).first()
|
| 137 |
+
if not existing_company:
|
| 138 |
+
new_company = Company(**company_data)
|
| 139 |
+
db.add(new_company)
|
| 140 |
+
db.commit()
|
| 141 |
+
logger.info("默认公司已创建")
|
| 142 |
+
|
| 143 |
+
# 创建默认用户和会员信息
|
| 144 |
+
for user_data in DEFAULT_USERS:
|
| 145 |
+
existing_user = db.query(User).filter(User.username == user_data["username"]).first()
|
| 146 |
+
if not existing_user:
|
| 147 |
+
user_data_copy = user_data.copy()
|
| 148 |
+
user_data_copy["password"] = get_password_hash(user_data_copy["password"])
|
| 149 |
+
new_user = User(**user_data_copy)
|
| 150 |
+
db.add(new_user)
|
| 151 |
+
db.flush()
|
| 152 |
+
|
| 153 |
+
# 为用户创建默认会员信息
|
| 154 |
+
existing_membership = db.query(Membership).filter(Membership.company_code == user_data["company_code"]).first()
|
| 155 |
+
if not existing_membership:
|
| 156 |
+
new_membership = Membership(
|
| 157 |
+
company_code = user_data["company_code"],
|
| 158 |
+
level = 0, # 默认普通会员
|
| 159 |
+
start_date = datetime.now(),
|
| 160 |
+
end_date = datetime.now() + timedelta(days=365)
|
| 161 |
+
)
|
| 162 |
+
db.add(new_membership)
|
| 163 |
+
db.commit()
|
| 164 |
+
logger.info("默认用户和会员信息已创建")
|
| 165 |
+
except Exception as e:
|
| 166 |
+
logger.error(f"初始化数据时出错: {e}")
|
| 167 |
+
|
| 168 |
+
@app.get("/")
|
| 169 |
+
def read_root():
|
| 170 |
+
logger.info("访问根路径")
|
| 171 |
+
return {"message": "欢迎使用外贸独立站 API"}
|
| 172 |
+
|
| 173 |
+
@app.get("/health")
|
| 174 |
+
def health_check():
|
| 175 |
+
logger.info("健康检查")
|
| 176 |
+
return {"status": "healthy"}
|
| 177 |
+
|
| 178 |
+
if __name__ == "__main__":
|
| 179 |
+
import uvicorn
|
| 180 |
+
logger.info("启动应用服务器")
|
| 181 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
middleware/trace.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import Request, Response
|
| 2 |
+
from starlette.middleware.base import BaseHTTPMiddleware
|
| 3 |
+
import uuid
|
| 4 |
+
from contextvars import ContextVar
|
| 5 |
+
|
| 6 |
+
# 创建一个上下文变量来存储 trace_id
|
| 7 |
+
trace_id_var = ContextVar('trace_id', default=None)
|
| 8 |
+
|
| 9 |
+
class TraceMiddleware(BaseHTTPMiddleware):
|
| 10 |
+
async def dispatch(self, request: Request, call_next) -> Response:
|
| 11 |
+
# 生成唯一的 trace_id
|
| 12 |
+
trace_id = str(uuid.uuid4())
|
| 13 |
+
|
| 14 |
+
# 设置上下文变量
|
| 15 |
+
token = trace_id_var.set(trace_id)
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
# 处理请求
|
| 19 |
+
response = await call_next(request)
|
| 20 |
+
# 将 trace_id 添加到响应头中
|
| 21 |
+
response.headers['X-Trace-ID'] = trace_id
|
| 22 |
+
return response
|
| 23 |
+
finally:
|
| 24 |
+
# 清理上下文变量
|
| 25 |
+
trace_id_var.reset(token)
|
| 26 |
+
|
| 27 |
+
def get_trace_id():
|
| 28 |
+
"""获取当前请求的 trace_id"""
|
| 29 |
+
return trace_id_var.get()
|
models/__pycache__/category.cpython-314.pyc
ADDED
|
Binary file (1.77 kB). View file
|
|
|
models/__pycache__/company.cpython-314.pyc
ADDED
|
Binary file (2.3 kB). View file
|
|
|
models/__pycache__/company_r2_config.cpython-314.pyc
ADDED
|
Binary file (1.78 kB). View file
|
|
|
models/__pycache__/contact.cpython-314.pyc
ADDED
|
Binary file (2.74 kB). View file
|
|
|
models/__pycache__/customer_relationship.cpython-314.pyc
ADDED
|
Binary file (1.8 kB). View file
|
|
|
models/__pycache__/media.cpython-314.pyc
ADDED
|
Binary file (6.36 kB). View file
|
|
|
models/__pycache__/member_level.cpython-314.pyc
ADDED
|
Binary file (2.59 kB). View file
|
|
|
models/__pycache__/membership.cpython-314.pyc
ADDED
|
Binary file (1.42 kB). View file
|
|
|
models/__pycache__/operation_log.cpython-314.pyc
ADDED
|
Binary file (1.7 kB). View file
|
|
|
models/__pycache__/product.cpython-314.pyc
ADDED
|
Binary file (3.43 kB). View file
|
|
|
models/__pycache__/product_certification.cpython-314.pyc
ADDED
|
Binary file (1.94 kB). View file
|
|
|
models/__pycache__/product_media.cpython-314.pyc
ADDED
|
Binary file (2.14 kB). View file
|
|
|
models/__pycache__/product_packaging.cpython-314.pyc
ADDED
|
Binary file (1.75 kB). View file
|
|
|
models/__pycache__/template.cpython-314.pyc
ADDED
|
Binary file (1.95 kB). View file
|
|
|
models/__pycache__/user.cpython-314.pyc
ADDED
|
Binary file (1.57 kB). View file
|
|
|
models/category.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, DateTime, text
|
| 2 |
+
from sqlalchemy.orm import relationship
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class Category(Base):
|
| 6 |
+
__tablename__ = "categories"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 9 |
+
parent_code = Column(String(100), nullable=False, server_default="", index=True, comment='父分类编码,空为顶级分类')
|
| 10 |
+
code = Column(String(100), nullable=False, server_default="", index=True, comment='分类编码,级联编码格式')
|
| 11 |
+
name = Column(String(100), nullable=False, server_default="", index=True, comment='分类名称')
|
| 12 |
+
sort_order = Column(Integer, nullable=False, server_default="0", comment='排序顺序')
|
| 13 |
+
status = Column(Integer, nullable=False, server_default="1", comment='状态:0-禁用,1-启用')
|
| 14 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码,用于数据隔离')
|
| 15 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 16 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
| 17 |
+
|
| 18 |
+
# 关系
|
| 19 |
+
products = relationship("Product", back_populates="category")
|
models/company.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class Company(Base):
|
| 6 |
+
__tablename__ = "companies"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
company_code = Column(String(50), nullable=False, server_default="", unique=True, index=True, comment='公司编码')
|
| 10 |
+
name = Column(String(100), nullable=False, server_default="", comment='公司名称')
|
| 11 |
+
logo_url = Column(String(255), nullable=False, server_default="", comment='公司logo')
|
| 12 |
+
description = Column(Text, nullable=False, default="", comment='公司描述')
|
| 13 |
+
address = Column(Text, nullable=False, default="", comment='公司地址')
|
| 14 |
+
phone = Column(String(50), nullable=False, server_default="", comment='联系电话')
|
| 15 |
+
email = Column(String(100), nullable=False, server_default="", comment='联系邮箱')
|
| 16 |
+
website = Column(String(255), nullable=False, server_default="", comment='公司网站')
|
| 17 |
+
business_license = Column(String(255), nullable=False, server_default="", comment='营业执照')
|
| 18 |
+
established_date = Column(DateTime, nullable=True, comment='成立日期')
|
| 19 |
+
employees_count = Column(Integer, nullable=False, server_default="0", comment='员工数量')
|
| 20 |
+
business_scope = Column(Text, nullable=False, default="", comment='经营范围')
|
| 21 |
+
level = Column(Integer, nullable=False, server_default="0", comment='会员等级:0-普通会员,1-金卡,2-钻石,3-皇冠')
|
| 22 |
+
template = Column(String(50), nullable=False, server_default="Home", comment='模板选择')
|
| 23 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 24 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/company_r2_config.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, DateTime
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class CompanyR2Config(Base):
|
| 6 |
+
__tablename__ = "company_r2_configs"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 9 |
+
company_code = Column(String(50), nullable=False, unique=True, index=True, comment='公司编码,唯一')
|
| 10 |
+
r2_account_id = Column(String(200), nullable=False, server_default="", comment='R2 账户 ID')
|
| 11 |
+
r2_access_key_id = Column(String(200), nullable=False, server_default="", comment='R2 访问密钥 ID')
|
| 12 |
+
r2_secret_access_key = Column(String(200), nullable=False, server_default="", comment='R2 密钥')
|
| 13 |
+
r2_bucket_name = Column(String(200), nullable=False, server_default="", comment='R2 桶名称')
|
| 14 |
+
r2_public_url = Column(String(500), nullable=False, server_default="", comment='R2 公共访问 URL')
|
| 15 |
+
r2_enabled = Column(Integer, nullable=False, server_default="1", comment='是否启用 R2:0-禁用,1-启用')
|
| 16 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 17 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/contact.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class Contact(Base):
|
| 6 |
+
__tablename__ = "contacts"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
company_code = Column(String(50), ForeignKey("companies.company_code"), nullable=False, server_default="", index=True, comment='公司编码')
|
| 10 |
+
contact_name = Column(String(100), nullable=False, server_default="", comment='联系人姓名')
|
| 11 |
+
phone = Column(String(50), nullable=False, server_default="", comment='联系电话')
|
| 12 |
+
email = Column(String(100), nullable=False, server_default="", comment='电子邮箱')
|
| 13 |
+
position = Column(String(100), nullable=False, server_default="", comment='职位')
|
| 14 |
+
department = Column(String(100), nullable=False, server_default="", comment='部门')
|
| 15 |
+
facebook = Column(String(255), nullable=False, server_default="", comment='Facebook账号')
|
| 16 |
+
facebook_api_key = Column(String(255), nullable=False, server_default="", comment='Facebook API Key')
|
| 17 |
+
facebook_app_secret = Column(String(255), nullable=False, server_default="", comment='Facebook App Secret')
|
| 18 |
+
tiktok = Column(String(255), nullable=False, server_default="", comment='TikTok账号')
|
| 19 |
+
tiktok_api_key = Column(String(255), nullable=False, server_default="", comment='TikTok API Key')
|
| 20 |
+
tiktok_app_secret = Column(String(255), nullable=False, server_default="", comment='TikTok App Secret')
|
| 21 |
+
whatsapp = Column(String(50), nullable=False, server_default="", comment='WhatsApp账号')
|
| 22 |
+
whatsapp_api_key = Column(String(255), nullable=False, server_default="", comment='WhatsApp API Key')
|
| 23 |
+
whatsapp_app_secret = Column(String(255), nullable=False, server_default="", comment='WhatsApp App Secret')
|
| 24 |
+
wechat = Column(String(100), nullable=False, server_default="", comment='微信账号')
|
| 25 |
+
linkedin = Column(String(255), nullable=False, server_default="", comment='LinkedIn账号')
|
| 26 |
+
note = Column(Text, nullable=False, default="", comment='备注')
|
| 27 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 28 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/customer_relationship.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class CustomerRelationship(Base):
|
| 6 |
+
__tablename__ = "customer_relationships"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码')
|
| 10 |
+
customer_name = Column(String(100), nullable=False, server_default="", comment='客户名称')
|
| 11 |
+
contact_person = Column(String(100), nullable=False, server_default="", comment='联系人')
|
| 12 |
+
phone = Column(String(50), nullable=False, server_default="", comment='电话')
|
| 13 |
+
email = Column(String(100), nullable=False, server_default="", comment='邮箱')
|
| 14 |
+
social_media = Column(Text, nullable=False, default="", comment='社交平台账号,JSON格式')
|
| 15 |
+
address = Column(Text, nullable=False, default="", comment='地址')
|
| 16 |
+
notes = Column(Text, nullable=False, default="", comment='备注')
|
| 17 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 18 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/media.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON, Enum, Float, Boolean
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
import enum
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class MediaStatus(enum.Enum):
|
| 8 |
+
PENDING = "pending"
|
| 9 |
+
UPLOADING = "uploading"
|
| 10 |
+
SUCCESS = "success"
|
| 11 |
+
FAILED = "failed"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class MediaTag(Base):
|
| 15 |
+
"""素材标签表"""
|
| 16 |
+
__tablename__ = "media_tags"
|
| 17 |
+
|
| 18 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 19 |
+
tag_name = Column(String(50), nullable=False, unique=True, index=True, comment='标签名称')
|
| 20 |
+
tag_color = Column(String(20), nullable=False, server_default="#3b82f6", comment='标签颜色(hex)')
|
| 21 |
+
sort_order = Column(Integer, nullable=False, server_default="0", comment='排序顺序')
|
| 22 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码')
|
| 23 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 24 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class MediaTagMapping(Base):
|
| 28 |
+
"""素材-标签关联表(多对多)"""
|
| 29 |
+
__tablename__ = "media_tag_mappings"
|
| 30 |
+
|
| 31 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 32 |
+
media_id = Column(String(32), ForeignKey('media.media_id'), nullable=False, index=True, comment='素材ID')
|
| 33 |
+
tag_id = Column(Integer, ForeignKey('media_tags.id'), nullable=False, index=True, comment='标签ID')
|
| 34 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class Media(Base):
|
| 38 |
+
__tablename__ = "media"
|
| 39 |
+
|
| 40 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 41 |
+
media_id = Column(String(32), nullable=False, unique=True, index=True, comment='素材唯一ID(32位UUID)')
|
| 42 |
+
file_name = Column(String(500), nullable=False, comment='原始文件名')
|
| 43 |
+
r2_key = Column(String(500), nullable=False, server_default="", comment='R2原图对象键(MD5命名)')
|
| 44 |
+
r2_url = Column(String(500), nullable=False, server_default="", comment='R2原图URL')
|
| 45 |
+
thumbnail_r2_key = Column(String(500), nullable=False, server_default="", comment='R2缩略图对象键')
|
| 46 |
+
thumbnail_r2_url = Column(String(500), nullable=False, server_default="", comment='R2缩略图URL')
|
| 47 |
+
media_type = Column(String(20), nullable=False, server_default="image", comment='媒体类型:image, video')
|
| 48 |
+
mime_type = Column(String(100), nullable=False, server_default="", comment='MIME类型')
|
| 49 |
+
file_size = Column(Integer, nullable=False, server_default="0", comment='文件大小(字节)')
|
| 50 |
+
width = Column(Integer, nullable=True, comment='图片宽度')
|
| 51 |
+
height = Column(Integer, nullable=True, comment='图片高度')
|
| 52 |
+
duration = Column(Float, nullable=True, comment='视频时长(秒)')
|
| 53 |
+
directory_id = Column(Integer, ForeignKey('media_directories.id'), nullable=True, comment='目录ID')
|
| 54 |
+
status = Column(Enum(MediaStatus), nullable=False, server_default="pending", comment='上传状态')
|
| 55 |
+
error_message = Column(Text, nullable=True, comment='错误信息')
|
| 56 |
+
batch_id = Column(String(32), nullable=True, index=True, comment='批次ID(批量上传时使用)')
|
| 57 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码')
|
| 58 |
+
is_deleted = Column(Boolean, nullable=False, server_default="0", index=True, comment='是否已删除:0-未删除,1-已删除')
|
| 59 |
+
deleted_at = Column(DateTime(timezone=True), nullable=True, comment='删除时间')
|
| 60 |
+
|
| 61 |
+
# 关联关系
|
| 62 |
+
tags = Column(JSON, nullable=True, comment='标签ID列表(JSON数组)')
|
| 63 |
+
|
| 64 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 65 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class MediaDirectory(Base):
|
| 70 |
+
__tablename__ = "media_directories"
|
| 71 |
+
|
| 72 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 73 |
+
name = Column(String(100), nullable=False, comment='目录名称')
|
| 74 |
+
parent_id = Column(Integer, ForeignKey('media_directories.id'), nullable=True, comment='父目录ID')
|
| 75 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码')
|
| 76 |
+
path = Column(String(500), nullable=False, server_default="", comment='完整路径')
|
| 77 |
+
level = Column(Integer, nullable=False, server_default="0", comment='目录层级')
|
| 78 |
+
sort_order = Column(Integer, nullable=False, server_default="0", comment='排序顺序')
|
| 79 |
+
cover_r2_key = Column(String(500), nullable=True, comment='目录封面图R2键')
|
| 80 |
+
cover_r2_url = Column(String(500), nullable=True, comment='目录封面图R2URL')
|
| 81 |
+
description = Column(Text, nullable=True, comment='目录描述')
|
| 82 |
+
is_public = Column(Boolean, nullable=False, server_default="0", comment='是否公开')
|
| 83 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 84 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
| 85 |
+
|
| 86 |
+
# 关联关系
|
| 87 |
+
media_count = Column(Integer, nullable=False, server_default="0", comment='素材数量(缓存字段)')
|
models/member_level.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, Float
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class MemberLevel(Base):
|
| 6 |
+
__tablename__ = "member_levels"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
level = Column(Integer, nullable=False, server_default="0", unique=True, index=True, comment='会员等级')
|
| 10 |
+
name = Column(String(50), nullable=False, server_default="", comment='等级名称')
|
| 11 |
+
description = Column(Text, nullable=False, default="", comment='等级描述')
|
| 12 |
+
annual_fee = Column(Float, nullable=False, server_default="0", comment='会员每年权益服务费')
|
| 13 |
+
discount_price = Column(Float, nullable=False, server_default="0", comment='当前优惠价')
|
| 14 |
+
deployment_nodes = Column(Integer, nullable=False, server_default="1", comment='独立站部署全球节点数')
|
| 15 |
+
email_marketing_limit = Column(Integer, nullable=False, server_default="10000", comment='每个月邮件营销推送数量')
|
| 16 |
+
product_sku_limit = Column(Integer, nullable=False, server_default="100", comment='产品SKU数')
|
| 17 |
+
sku_image_limit = Column(Integer, nullable=False, server_default="10", comment='每个SKU可加图片数量')
|
| 18 |
+
image_size_limit = Column(Integer, nullable=False, server_default="5", comment='图片大小限制(MB)')
|
| 19 |
+
sku_video_limit = Column(Integer, nullable=False, server_default="1", comment='每个SKU可加视频数量')
|
| 20 |
+
image_storage_limit = Column(Integer, nullable=False, server_default="5", comment='图片空间大小(G)')
|
| 21 |
+
template_access = Column(Boolean, nullable=False, server_default="0", comment='模板选择权限')
|
| 22 |
+
geo_seo_access = Column(Boolean, nullable=False, server_default="0", comment='GEO,SEO关键词营销')
|
| 23 |
+
social_automation_access = Column(Boolean, nullable=False, server_default="0", comment='社交平台自动化营销')
|
| 24 |
+
custom_development_access = Column(Boolean, nullable=False, server_default="0", comment='定制化需求开发')
|
| 25 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 26 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/membership.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
|
| 2 |
+
from sqlalchemy.sql import func, text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class Membership(Base):
|
| 6 |
+
__tablename__ = "membership"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码')
|
| 10 |
+
level = Column(Integer, nullable=False, server_default="0", comment='会员等级')
|
| 11 |
+
start_date = Column(DateTime, nullable=True, comment='有效期开始')
|
| 12 |
+
end_date = Column(DateTime, nullable=True, comment='有效期结束')
|
| 13 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 14 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/operation_log.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class OperationLog(Base):
|
| 6 |
+
__tablename__ = "operation_logs"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
user_id = Column(Integer, nullable=False, server_default="0", comment='操作人ID')
|
| 10 |
+
username = Column(String(50), nullable=False, server_default="", comment='操作人用户名')
|
| 11 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='操作所属公司编码')
|
| 12 |
+
module = Column(String(50), nullable=False, server_default="", comment='操作模块(如:user, company, product等)')
|
| 13 |
+
operation_type = Column(String(20), nullable=False, server_default="", comment='操作类型:add, edit, delete')
|
| 14 |
+
operation_detail = Column(Text, nullable=False, default="", comment='操作详情')
|
| 15 |
+
before_data = Column(Text, nullable=False, default="", comment='修改前的数据')
|
| 16 |
+
after_data = Column(Text, nullable=False, default="", comment='修改后的数据')
|
| 17 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='操作时间')
|
models/product.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Float, Text, DateTime, JSON, ForeignKey
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from sqlalchemy.orm import relationship
|
| 4 |
+
from database import Base
|
| 5 |
+
|
| 6 |
+
class Product(Base):
|
| 7 |
+
__tablename__ = "products"
|
| 8 |
+
|
| 9 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 10 |
+
category_id = Column(Integer, ForeignKey("categories.id"), nullable=False, server_default="0", index=True, comment='分类 ID')
|
| 11 |
+
product_number = Column(String(11), nullable=False, unique=True, index=True, comment='产品货号,11 位 UUID')
|
| 12 |
+
model = Column(String(50), nullable=False, server_default="", comment='产品型号,业务输入')
|
| 13 |
+
name = Column(String(200), nullable=False, server_default="", index=True, comment='产品名称')
|
| 14 |
+
brand = Column(String(100), nullable=False, server_default="", comment='品牌名称')
|
| 15 |
+
price = Column(Float, nullable=False, server_default="0", comment='价格')
|
| 16 |
+
original_price = Column(Float, nullable=False, server_default="0", comment='原价')
|
| 17 |
+
currency = Column(String(10), nullable=False, server_default="USD", comment='货币符号')
|
| 18 |
+
stock = Column(Integer, nullable=False, server_default="0", comment='库存')
|
| 19 |
+
min_order = Column(Integer, nullable=False, server_default="1", comment='最小起订量')
|
| 20 |
+
unit = Column(String(20), nullable=False, server_default="pcs", comment='计量单位')
|
| 21 |
+
status = Column(Integer, nullable=False, server_default="1", comment='状态:0-下架,1-上架')
|
| 22 |
+
is_new = Column(Integer, nullable=False, server_default="0", comment='是否新品:0-否,1-是')
|
| 23 |
+
is_hot = Column(Integer, nullable=False, server_default="0", comment='是否热销:0-否,1-是')
|
| 24 |
+
is_best_seller = Column(Integer, nullable=False, server_default="0", comment='是否畅销:0-否,1-是')
|
| 25 |
+
sort_order = Column(Integer, nullable=False, server_default="0", comment='排序顺序')
|
| 26 |
+
|
| 27 |
+
# 商品详情字段
|
| 28 |
+
description = Column(Text, nullable=False, comment='功能描述')
|
| 29 |
+
usage = Column(Text, nullable=False, comment='适用范围')
|
| 30 |
+
material = Column(String(200), nullable=False, server_default="", comment='材质')
|
| 31 |
+
parameters = Column(Text, nullable=False, comment='其他参数说明')
|
| 32 |
+
|
| 33 |
+
# SEO 字段
|
| 34 |
+
seo_title_tag = Column(String(200), nullable=False, server_default="", comment='SEO 页面标题')
|
| 35 |
+
seo_meta_keywords = Column(String(500), nullable=False, server_default="", comment='SEO 元关键词')
|
| 36 |
+
|
| 37 |
+
# 产地字段
|
| 38 |
+
country_of_origin = Column(String(100), nullable=False, server_default="", comment='原产国')
|
| 39 |
+
made_in_label = Column(String(100), nullable=False, server_default="", comment='Made in 标签')
|
| 40 |
+
|
| 41 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码,用于数据隔离')
|
| 42 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 43 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
| 44 |
+
|
| 45 |
+
# 关系
|
| 46 |
+
category = relationship("Category", back_populates="products")
|
models/product_certification.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class ProductCertification(Base):
|
| 6 |
+
__tablename__ = "product_certifications"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 9 |
+
product_id = Column(Integer, ForeignKey('products.id'), nullable=True, index=True, comment='产品 ID(外键)')
|
| 10 |
+
product_number = Column(String(11), nullable=False, server_default="", index=True, comment='产品货号')
|
| 11 |
+
certification_type = Column(String(50), nullable=False, server_default="", index=True, comment='认证类型:CE, FDA, RoHS, FCC, ISO, OTHER')
|
| 12 |
+
r2_url = Column(String(500), nullable=False, server_default="", comment='认证文件 URL(R2 URL)')
|
| 13 |
+
thumbnail_r2_url = Column(String(500), nullable=False, server_default="", comment='认证缩略图路径(R2 URL)')
|
| 14 |
+
media_id = Column(String(32), nullable=False, server_default="", comment='素材ID')
|
| 15 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码,用于数据隔离')
|
| 16 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 17 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/product_media.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class ProductMedia(Base):
|
| 6 |
+
__tablename__ = "product_media"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 9 |
+
product_id = Column(Integer, ForeignKey('products.id'), nullable=True, index=True, comment='产品 ID(外键)')
|
| 10 |
+
product_number = Column(String(11), nullable=False, server_default="", index=True, comment='产品货号')
|
| 11 |
+
media_type = Column(String(20), nullable=False, server_default="image", index=True, comment='媒体类型:image, video')
|
| 12 |
+
r2_url = Column(String(500), nullable=False, server_default="", comment='媒体 URL(R2 原图 URL)')
|
| 13 |
+
thumbnail_r2_url = Column(String(500), nullable=False, server_default="", comment='缩略图 URL(R2 缩略图 URL)')
|
| 14 |
+
media_id = Column(String(32), nullable=False, server_default="", comment='素材ID')
|
| 15 |
+
media_alt = Column(String(200), nullable=False, server_default="", comment='ALT 标签/描述')
|
| 16 |
+
is_main = Column(Integer, nullable=False, server_default="0", comment='是否主图:0-否,1-是')
|
| 17 |
+
sort_order = Column(Integer, nullable=False, server_default="0", comment='排序顺序')
|
| 18 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码,用于数据隔离')
|
| 19 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 20 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/product_packaging.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey
|
| 2 |
+
from sqlalchemy.sql import text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class ProductPackaging(Base):
|
| 6 |
+
__tablename__ = "product_packaging"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 9 |
+
product_number = Column(String(11), nullable=False, server_default="", index=True, comment='产品货号')
|
| 10 |
+
model = Column(String(50), nullable=False, server_default="", comment='型号')
|
| 11 |
+
length = Column(Float, nullable=False, server_default="0", comment='长度 (cm)')
|
| 12 |
+
width = Column(Float, nullable=False, server_default="0", comment='宽度 (cm)')
|
| 13 |
+
height = Column(Float, nullable=False, server_default="0", comment='高度 (cm)')
|
| 14 |
+
volume = Column(Float, nullable=False, server_default="0", comment='体积 (cm³)')
|
| 15 |
+
weight = Column(Float, nullable=False, server_default="0", comment='重量 (g)')
|
| 16 |
+
company_code = Column(String(50), nullable=False, server_default="", index=True, comment='公司编码,用于数据隔离')
|
| 17 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 18 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/site_visit.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, DateTime, Index, text
|
| 2 |
+
from sqlalchemy.sql import func
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class SiteVisit(Base):
|
| 7 |
+
__tablename__ = "site_visits"
|
| 8 |
+
|
| 9 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键 ID')
|
| 10 |
+
company_code = Column(String(50), nullable=False, index=True, comment='公司编码')
|
| 11 |
+
ip_address = Column(String(45), nullable=False, index=True, comment='访问IP地址')
|
| 12 |
+
visitor_id = Column(String(100), nullable=True, index=True, comment='访客ID')
|
| 13 |
+
|
| 14 |
+
# 地理位置信息
|
| 15 |
+
country = Column(String(50), nullable=True, comment='国家')
|
| 16 |
+
region = Column(String(50), nullable=True, comment='地区/省份')
|
| 17 |
+
city = Column(String(50), nullable=True, comment='城市')
|
| 18 |
+
latitude = Column(String(20), nullable=True, comment='纬度')
|
| 19 |
+
longitude = Column(String(20), nullable=True, comment='经度')
|
| 20 |
+
|
| 21 |
+
# 访问信息
|
| 22 |
+
user_agent = Column(String(500), nullable=True, comment='用户代理')
|
| 23 |
+
referer = Column(String(500), nullable=True, comment='来源页面')
|
| 24 |
+
page_url = Column(String(500), nullable=True, comment='访问页面')
|
| 25 |
+
visit_date = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='访问时间')
|
| 26 |
+
|
| 27 |
+
__table_args__ = (
|
| 28 |
+
Index('idx_company_visit_date', 'company_code', 'visit_date'),
|
| 29 |
+
Index('idx_company_country', 'company_code', 'country'),
|
| 30 |
+
)
|
models/template.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Text, DateTime
|
| 2 |
+
from sqlalchemy.sql import func, text
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class Template(Base):
|
| 6 |
+
__tablename__ = "templates"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
key = Column(String(50), nullable=False, server_default="", unique=True, index=True, comment='模板标识')
|
| 10 |
+
name = Column(String(100), nullable=False, server_default="", comment='模板名称')
|
| 11 |
+
description = Column(Text, nullable=False, default="", comment='模板描述')
|
| 12 |
+
color = Column(String(20), nullable=False, server_default="#000000", comment='模板主色调')
|
| 13 |
+
features = Column(Text, nullable=False, default="", comment='模板特性,JSON格式')
|
| 14 |
+
applicable_scene = Column(Text, nullable=False, default="", comment='适用场景')
|
| 15 |
+
regions = Column(Text, nullable=False, default="", comment='适用区域,JSON格式')
|
| 16 |
+
select_count = Column(Integer, nullable=False, server_default="0", comment='被选择总次数')
|
| 17 |
+
use_count = Column(Integer, nullable=False, server_default="0", comment='当前使用次数')
|
| 18 |
+
status = Column(Integer, nullable=False, server_default="0", comment='状态:0-不可用,1-可用')
|
| 19 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 20 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
models/user.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, DateTime, text
|
| 2 |
+
from sqlalchemy.orm import relationship
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class User(Base):
|
| 6 |
+
__tablename__ = "users"
|
| 7 |
+
|
| 8 |
+
id = Column(Integer, primary_key=True, index=True, comment='主键ID')
|
| 9 |
+
username = Column(String(50), unique=True, nullable=False, server_default="", index=True, comment='用户名')
|
| 10 |
+
password = Column(String(255), nullable=False, server_default="", comment='密码')
|
| 11 |
+
email = Column(String(100), unique=True, nullable=False, server_default="", index=True, comment='邮箱')
|
| 12 |
+
is_superadmin = Column(Boolean, nullable=False, server_default="0", comment='是否为超级管理员')
|
| 13 |
+
company_code = Column(String(50), nullable=False, server_default="", comment='公司编码')
|
| 14 |
+
created_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP'), comment='创建时间')
|
| 15 |
+
updated_at = Column(DateTime(timezone=True), nullable=False, server_default=text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间')
|
requirements.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FastAPI 及其依赖
|
| 2 |
+
fastapi==0.104.1
|
| 3 |
+
uvicorn[standard]==0.24.0.post1
|
| 4 |
+
pydantic==2.5.2
|
| 5 |
+
pydantic-settings==2.1.0
|
| 6 |
+
|
| 7 |
+
# 数据库相关
|
| 8 |
+
sqlalchemy==2.0.23
|
| 9 |
+
pymysql==1.1.0
|
| 10 |
+
|
| 11 |
+
# 安全相关
|
| 12 |
+
python-jose[cryptography]==3.3.0
|
| 13 |
+
passlib[bcrypt]==1.7.4
|
| 14 |
+
python-multipart==0.0.6
|
| 15 |
+
|
| 16 |
+
# 环境变量管理
|
| 17 |
+
python-dotenv==1.0.0
|
| 18 |
+
|
| 19 |
+
# 其他
|
| 20 |
+
python-magic==0.4.27
|
| 21 |
+
boto3==1.34.0
|
| 22 |
+
requests==2.31.0
|
| 23 |
+
|
| 24 |
+
# 开发工具
|
| 25 |
+
black==23.12.1
|
| 26 |
+
flake8==6.1.0
|
| 27 |
+
isort==5.12.0
|
routers/__pycache__/auth.cpython-314.pyc
ADDED
|
Binary file (12.3 kB). View file
|
|
|
routers/__pycache__/categories.cpython-314.pyc
ADDED
|
Binary file (15.6 kB). View file
|
|
|
routers/__pycache__/company.cpython-314.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|