hsailorj commited on
Commit
551658a
·
1 Parent(s): 1f72f4b

Add application file

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +22 -0
  2. Dockerfile +23 -0
  3. __pycache__/database.cpython-314.pyc +0 -0
  4. __pycache__/main.cpython-314.pyc +0 -0
  5. config/__pycache__/default_companies.cpython-314.pyc +0 -0
  6. config/__pycache__/member_levels.cpython-314.pyc +0 -0
  7. config/__pycache__/templates.cpython-314.pyc +0 -0
  8. config/certification_types.py +35 -0
  9. config/default_companies.py +22 -0
  10. config/member_levels.py +77 -0
  11. config/templates.py +239 -0
  12. create_tables.py +23 -0
  13. database.py +111 -0
  14. main.py +181 -0
  15. middleware/trace.py +29 -0
  16. models/__pycache__/category.cpython-314.pyc +0 -0
  17. models/__pycache__/company.cpython-314.pyc +0 -0
  18. models/__pycache__/company_r2_config.cpython-314.pyc +0 -0
  19. models/__pycache__/contact.cpython-314.pyc +0 -0
  20. models/__pycache__/customer_relationship.cpython-314.pyc +0 -0
  21. models/__pycache__/media.cpython-314.pyc +0 -0
  22. models/__pycache__/member_level.cpython-314.pyc +0 -0
  23. models/__pycache__/membership.cpython-314.pyc +0 -0
  24. models/__pycache__/operation_log.cpython-314.pyc +0 -0
  25. models/__pycache__/product.cpython-314.pyc +0 -0
  26. models/__pycache__/product_certification.cpython-314.pyc +0 -0
  27. models/__pycache__/product_media.cpython-314.pyc +0 -0
  28. models/__pycache__/product_packaging.cpython-314.pyc +0 -0
  29. models/__pycache__/template.cpython-314.pyc +0 -0
  30. models/__pycache__/user.cpython-314.pyc +0 -0
  31. models/category.py +19 -0
  32. models/company.py +24 -0
  33. models/company_r2_config.py +17 -0
  34. models/contact.py +28 -0
  35. models/customer_relationship.py +18 -0
  36. models/media.py +87 -0
  37. models/member_level.py +26 -0
  38. models/membership.py +14 -0
  39. models/operation_log.py +17 -0
  40. models/product.py +46 -0
  41. models/product_certification.py +17 -0
  42. models/product_media.py +20 -0
  43. models/product_packaging.py +18 -0
  44. models/site_visit.py +30 -0
  45. models/template.py +20 -0
  46. models/user.py +15 -0
  47. requirements.txt +27 -0
  48. routers/__pycache__/auth.cpython-314.pyc +0 -0
  49. routers/__pycache__/categories.cpython-314.pyc +0 -0
  50. 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