geqintan commited on
Commit
5d3d013
·
1 Parent(s): 4967c44
ai_edu.db CHANGED
Binary files a/ai_edu.db and b/ai_edu.db differ
 
app.py CHANGED
@@ -1,15 +1,30 @@
1
- # conda activate rzwl && uvicorn app:app --host 0.0.0.0 --port 7860 --reload
2
- from fastapi import FastAPI, APIRouter, HTTPException, status
3
  from fastapi.middleware.cors import CORSMiddleware
4
- from pydantic import BaseModel
5
  import uuid
6
  import sqlite3 # 导入 sqlite3 模块
7
- from typing import Optional
 
 
 
 
 
 
8
 
9
- app = FastAPI()
10
 
11
  DATABASE_URL = "ai_edu.db"
12
 
 
 
 
 
 
 
 
 
 
13
  def init_db():
14
  with sqlite3.connect(DATABASE_URL) as conn:
15
  cursor = conn.cursor()
@@ -51,6 +66,16 @@ def init_db():
51
  level TEXT
52
  )
53
  """)
 
 
 
 
 
 
 
 
 
 
54
  conn.commit()
55
 
56
  # 在应用启动时初始化数据库
@@ -88,12 +113,19 @@ class EnrollmentIndividualRequest(BaseModel):
88
  position: str
89
  email: str
90
 
 
 
 
 
 
91
  class Course(BaseModel):
 
92
  title: str
93
  description: Optional[str] = None
94
- price: float
95
- duration_hours: Optional[int] = None
96
  level: Optional[str] = None
 
97
 
98
  @api_router.post("/auth/register")
99
  async def register_user(request: RegisterRequest):
@@ -101,9 +133,10 @@ async def register_user(request: RegisterRequest):
101
  try:
102
  with sqlite3.connect(DATABASE_URL) as conn:
103
  cursor = conn.cursor()
 
104
  cursor.execute(
105
  "INSERT INTO users (user_id, username, password) VALUES (?, ?, ?)",
106
- (user_id, request.username, request.password)
107
  )
108
  conn.commit()
109
  return {
@@ -131,24 +164,30 @@ async def login_user(request: LoginRequest):
131
  with sqlite3.connect(DATABASE_URL) as conn:
132
  cursor = conn.cursor()
133
  cursor.execute(
134
- "SELECT user_id, username FROM users WHERE username = ? AND password = ?",
135
- (request.username, request.password)
136
  )
137
  user = cursor.fetchone()
138
  if user:
139
- user_id, username = user
140
- return {
141
- "code": 200,
142
- "message": "登录成功",
143
- "data": {
144
- "user_id": user_id,
145
- "username": username
 
 
 
 
 
 
 
 
146
  }
147
- }
148
  else:
149
- # 用户名或密码错误时,返回自定义的错误结构
150
  return {
151
- "code": 401, # 业务错误码
152
  "message": "用户名或密码错误",
153
  "data": None
154
  }
@@ -161,6 +200,90 @@ async def login_user(request: LoginRequest):
161
  "data": None
162
  }
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  @api_router.post("/enrollment/individual")
165
  async def enroll_individual(request: EnrollmentIndividualRequest):
166
  enrollment_id = str(uuid.uuid4())
@@ -232,31 +355,62 @@ async def get_individual_orders():
232
  detail=f"获取个人报名订单失败: {str(e)}"
233
  )
234
 
235
- @api_router.post("/course/add")
236
- async def add_course(course: Course):
 
237
  try:
238
  with sqlite3.connect(DATABASE_URL) as conn:
239
  cursor = conn.cursor()
240
- cursor.execute(
241
- "INSERT INTO courses (title, description, price, duration_hours, level) VALUES (?, ?, ?, ?, ?)",
242
- (course.title, course.description, course.price, course.duration_hours, course.level)
243
- )
244
- conn.commit()
245
- course_id = cursor.lastrowid
246
- return {
247
- "code": 200,
248
- "message": "课程添加成功",
249
- "data": {
250
- "course_id": course_id,
251
- **course.dict()
252
- }
253
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  except Exception as e:
255
  raise HTTPException(
256
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
257
- detail=f"添加课程失败: {str(e)}"
258
  )
259
 
 
260
  @api_router.get("/course/details/{course_id}")
261
  async def get_course_details(course_id: int):
262
  try:
@@ -266,10 +420,13 @@ async def get_course_details(course_id: int):
266
  cursor.execute("SELECT * FROM courses WHERE course_id = ?", (course_id,))
267
  course = cursor.fetchone()
268
  if course:
 
 
 
269
  return {
270
  "code": 200,
271
  "message": "获取课程详情成功",
272
- "data": dict(course)
273
  }
274
  else:
275
  raise HTTPException(
@@ -309,6 +466,32 @@ async def delete_individual_enrollment(enrollment_id: str):
309
  detail=f"删除报名数据失败: {str(e)}"
310
  )
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  @app.get("/")
313
  def greet_json():
314
  return {"Hello": "World!"}
 
1
+ # conda activate rzwl && uvicorn app:app --host 0.0.0.0 --port 7861 --reload
2
+ from fastapi import FastAPI, APIRouter, HTTPException, status, File, UploadFile # 导入 File 和 UploadFile
3
  from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel, Field
5
  import uuid
6
  import sqlite3 # 导入 sqlite3 模块
7
+ from typing import Optional, List, Dict # 导入类型提示
8
+ import json # 导入 json 模块
9
+ import os # 导入 os 模块
10
+ import urllib.parse # 导入 urllib.parse 用于 URL 编码
11
+ from fastapi.staticfiles import StaticFiles # 导入 StaticFiles
12
+ from passlib.context import CryptContext # 导入 CryptContext
13
+ import httpx # 导入 httpx 用于异步 HTTP 请求
14
 
15
+ app = FastAPI(max_upload_size=10 * 1024 * 1024) # 设置最大上传大小为 10MB
16
 
17
  DATABASE_URL = "ai_edu.db"
18
 
19
+ # 用于密码哈希的 CryptContext
20
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
21
+
22
+ def verify_password(plain_password, hashed_password):
23
+ return pwd_context.verify(plain_password, hashed_password)
24
+
25
+ def get_password_hash(password):
26
+ return pwd_context.hash(password)
27
+
28
  def init_db():
29
  with sqlite3.connect(DATABASE_URL) as conn:
30
  cursor = conn.cursor()
 
66
  level TEXT
67
  )
68
  """)
69
+ # Add page_info column if it doesn't exist
70
+ cursor.execute("""
71
+ PRAGMA table_info(courses);
72
+ """)
73
+ columns = cursor.fetchall()
74
+ column_names = [col[1] for col in columns]
75
+ if 'page_info' not in column_names:
76
+ cursor.execute("""
77
+ ALTER TABLE courses ADD COLUMN page_info TEXT;
78
+ """)
79
  conn.commit()
80
 
81
  # 在应用启动时初始化数据库
 
113
  position: str
114
  email: str
115
 
116
+ # WeChat Configuration (PLACEHOLDERS - REPLACE WITH ACTUAL VALUES OR ENVIRONMENT VARIABLES)
117
+ WECHAT_APP_ID = os.getenv("WECHAT_APP_ID", "YOUR_WECHAT_APP_ID")
118
+ WECHAT_APP_SECRET = os.getenv("WECHAT_APP_SECRET", "YOUR_WECHAT_APP_SECRET") # Add App Secret
119
+ WECHAT_REDIRECT_URI = os.getenv("WECHAT_REDIRECT_URI", "http://localhost:7861/api/auth/wechat/callback") # This should be your frontend callback URL
120
+
121
  class Course(BaseModel):
122
+ course_id: Optional[int] = None
123
  title: str
124
  description: Optional[str] = None
125
+ price: float = 0
126
+ duration_hours: Optional[int] = 0
127
  level: Optional[str] = None
128
+ page_info: Optional[List[Dict]] = None # 改为接收JSON对象而非字符串
129
 
130
  @api_router.post("/auth/register")
131
  async def register_user(request: RegisterRequest):
 
133
  try:
134
  with sqlite3.connect(DATABASE_URL) as conn:
135
  cursor = conn.cursor()
136
+ hashed_password = get_password_hash(request.password)
137
  cursor.execute(
138
  "INSERT INTO users (user_id, username, password) VALUES (?, ?, ?)",
139
+ (user_id, request.username, hashed_password)
140
  )
141
  conn.commit()
142
  return {
 
164
  with sqlite3.connect(DATABASE_URL) as conn:
165
  cursor = conn.cursor()
166
  cursor.execute(
167
+ "SELECT user_id, username, password FROM users WHERE username = ?",
168
+ (request.username,)
169
  )
170
  user = cursor.fetchone()
171
  if user:
172
+ user_id, username, hashed_password = user
173
+ if verify_password(request.password, hashed_password):
174
+ return {
175
+ "code": 200,
176
+ "message": "登录成功",
177
+ "data": {
178
+ "user_id": user_id,
179
+ "username": username
180
+ }
181
+ }
182
+ else:
183
+ return {
184
+ "code": 401,
185
+ "message": "用户名或密码错误",
186
+ "data": None
187
  }
 
188
  else:
 
189
  return {
190
+ "code": 401,
191
  "message": "用户名或密码错误",
192
  "data": None
193
  }
 
200
  "data": None
201
  }
202
 
203
+ @api_router.get("/auth/wechat/qrcode")
204
+ async def get_wechat_qrcode():
205
+ """
206
+ 获取微信登录二维码URL。
207
+ """
208
+ # Generate a random state to prevent CSRF attacks
209
+ state = str(uuid.uuid4())
210
+ # In a real application, you would store this state in a session or database
211
+ # to verify it upon callback.
212
+
213
+ # URL-encode the redirect_uri
214
+ encoded_redirect_uri = urllib.parse.quote_plus(WECHAT_REDIRECT_URI)
215
+
216
+ # Construct the WeChat QR code login URL
217
+ qrcode_url = (
218
+ f"https://open.weixin.qq.com/connect/qrconnect?"
219
+ f"appid={WECHAT_APP_ID}&"
220
+ f"redirect_uri={encoded_redirect_uri}&"
221
+ f"response_type=code&"
222
+ f"scope=snsapi_login&"
223
+ f"state={state}#wechat_redirect"
224
+ )
225
+
226
+ return {
227
+ "code": 200,
228
+ "message": "获取微信二维码URL成功",
229
+ "data": {
230
+ "qrcode_url": qrcode_url,
231
+ "state": state # Return state to frontend for verification
232
+ }
233
+ }
234
+
235
+ @api_router.get("/auth/wechat/callback")
236
+ async def wechat_callback(code: str, state: str):
237
+ """
238
+ 微信登录回调接口,用于接收微信授权码并获取access_token。
239
+ """
240
+ # In a real application, you would verify the 'state' parameter
241
+ # against the one stored in the user's session to prevent CSRF attacks.
242
+ # For this example, we'll just print it.
243
+ print(f"Received WeChat callback with code: {code} and state: {state}")
244
+
245
+ # Exchange code for access_token
246
+ token_url = (
247
+ f"https://api.weixin.qq.com/sns/oauth2/access_token?"
248
+ f"appid={WECHAT_APP_ID}&"
249
+ f"secret={WECHAT_APP_SECRET}&"
250
+ f"code={code}&"
251
+ f"grant_type=authorization_code"
252
+ )
253
+
254
+ async with httpx.AsyncClient() as client:
255
+ response = await client.get(token_url)
256
+ token_data = response.json()
257
+
258
+ if "errcode" in token_data:
259
+ raise HTTPException(
260
+ status_code=status.HTTP_400_BAD_REQUEST,
261
+ detail=f"微信授权失败: {token_data.get('errmsg', '未知错误')}"
262
+ )
263
+
264
+ # Successfully got access_token and other info
265
+ access_token = token_data.get("access_token")
266
+ openid = token_data.get("openid")
267
+ unionid = token_data.get("unionid") # May not be present if scope is only snsapi_base
268
+
269
+ # In a real application, you would now use the access_token and openid/unionid
270
+ # to log in the user, create a new user, or fetch more user info.
271
+ # For example, you might fetch user info:
272
+ # userinfo_url = f"https://api.weixin.qq.com/sns/userinfo?access_token={access_token}&openid={openid}&lang=zh_CN"
273
+ # user_response = await client.get(userinfo_url)
274
+ # user_info = user_response.json()
275
+
276
+ return {
277
+ "code": 200,
278
+ "message": "微信登录回调成功",
279
+ "data": {
280
+ "access_token": access_token,
281
+ "openid": openid,
282
+ "unionid": unionid,
283
+ # "user_info": user_info # Uncomment if fetching user info
284
+ }
285
+ }
286
+
287
  @api_router.post("/enrollment/individual")
288
  async def enroll_individual(request: EnrollmentIndividualRequest):
289
  enrollment_id = str(uuid.uuid4())
 
355
  detail=f"获取个人报名订单失败: {str(e)}"
356
  )
357
 
358
+ @api_router.post("/course/save")
359
+ async def save_course(course: Course):
360
+ print('\n\n\nsave_course')
361
  try:
362
  with sqlite3.connect(DATABASE_URL) as conn:
363
  cursor = conn.cursor()
364
+ print('\n\n\n\n')
365
+ print(course)
366
+ # Convert page_info to JSON string before saving
367
+ page_info_json = json.dumps(course.page_info) if course.page_info is not None else None
368
+
369
+ if course.course_id is not None:
370
+ # 更新现有课程
371
+ cursor.execute(
372
+ "UPDATE courses SET title = ?, description = ?, price = ?, duration_hours = ?, level = ?, page_info = ? WHERE course_id = ?",
373
+ (course.title, course.description, course.price, course.duration_hours, course.level, page_info_json, course.course_id)
374
+ )
375
+ conn.commit()
376
+ if cursor.rowcount == 0:
377
+ raise HTTPException(
378
+ status_code=status.HTTP_404_NOT_FOUND,
379
+ detail="未找到该课程进行更新"
380
+ )
381
+ return {
382
+ "code": 200,
383
+ "message": "课程更新成功",
384
+ "data": {
385
+ "course_id": course.course_id,
386
+ **course.dict(exclude_unset=True)
387
+ }
388
+ }
389
+ else:
390
+ # 添加新课程
391
+ cursor.execute(
392
+ "INSERT INTO courses (title, description, price, duration_hours, level, page_info) VALUES (?, ?, ?, ?, ?, ?)",
393
+ (course.title, course.description, course.price, course.duration_hours, course.level, page_info_json)
394
+ )
395
+ conn.commit()
396
+ course_id = cursor.lastrowid
397
+ return {
398
+ "code": 200,
399
+ "message": "课程添加成功",
400
+ "data": {
401
+ "course_id": course_id,
402
+ **course.dict(exclude_unset=True)
403
+ }
404
+ }
405
+ except HTTPException as e:
406
+ raise e
407
  except Exception as e:
408
  raise HTTPException(
409
  status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
410
+ detail=f"保存课程失败: {str(e)}"
411
  )
412
 
413
+
414
  @api_router.get("/course/details/{course_id}")
415
  async def get_course_details(course_id: int):
416
  try:
 
420
  cursor.execute("SELECT * FROM courses WHERE course_id = ?", (course_id,))
421
  course = cursor.fetchone()
422
  if course:
423
+ course_dict = dict(course)
424
+ if course_dict['page_info']:
425
+ course_dict['page_info'] = json.loads(course_dict['page_info'])
426
  return {
427
  "code": 200,
428
  "message": "获取课程详情成功",
429
+ "data": course_dict
430
  }
431
  else:
432
  raise HTTPException(
 
466
  detail=f"删除报名数据失败: {str(e)}"
467
  )
468
 
469
+ @api_router.post("/upload/images")
470
+ async def upload_image(image: UploadFile = File(...)): # 将参数名从 file 改为 image,并恢复 File(...)
471
+ os.makedirs("upload/images", exist_ok=True) # 自动创建目录
472
+ file_location = f"upload/images/{image.filename}" # 使用 image.filename
473
+ try:
474
+ with open(file_location, "wb+") as buffer: # 改为 wb+
475
+ content = await image.read() # 异步读取
476
+ buffer.write(content)
477
+ return {
478
+ "code": 200,
479
+ "message": "图片上传成功", # 增加 message 字段
480
+ "data": {
481
+ "filename": image.filename, # 使用 image.filename
482
+ "url": f"/upload/images/{image.filename}" # 使用 image.filename
483
+ }
484
+ }
485
+ except Exception as e:
486
+ print(f"图片上传失败: {str(e)}") # 输出详细错误
487
+ raise HTTPException(
488
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, # 恢复 status.HTTP_500_INTERNAL_SERVER_ERROR
489
+ detail=f"图片上传失败: {str(e)}"
490
+ )
491
+
492
+ # 挂载静态文件目录
493
+ app.mount("/upload", StaticFiles(directory="upload"), name="upload")
494
+
495
  @app.get("/")
496
  def greet_json():
497
  return {"Hello": "World!"}
memory-bank/activeContext.md CHANGED
@@ -8,6 +8,7 @@
8
  - 优化了登录接口的错误响应,使其返回自定义的业务错误码和信息。
9
  - **新增了课程管理功能,包括课程表的创建和课程详情查询接口 (`/api/course/details/{course_id}`)。**
10
  - **新增了课程添加接口 (`/api/course/add`) 用于测试和数据初始化。**
 
11
 
12
  ## 最近的更改
13
  - `app.py`:
@@ -20,6 +21,13 @@
20
  - 添加 `/api/auth/login` 接口,实现用户登录验证和错误响应。
21
  - 添加 `CORSMiddleware` 处理跨域请求。
22
  - **修复了 `Course` Pydantic 模型中的类型提示错误,将 `str | None` 更改为 `Optional[str]`,以兼容 Python 3.9 环境。**
 
 
 
 
 
 
 
23
 
24
  ## 下一步计划
25
  - **安全性增强**:
 
8
  - 优化了登录接口的错误响应,使其返回自定义的业务错误码和信息。
9
  - **新增了课程管理功能,包括课程表的创建和课程详情查询接口 (`/api/course/details/{course_id}`)。**
10
  - **新增了课程添加接口 (`/api/course/add`) 用于测试和数据初始化。**
11
+ - **创建了 `upload/images` 文件夹,用于存储上传的图片文件。**
12
 
13
  ## 最近的更改
14
  - `app.py`:
 
21
  - 添加 `/api/auth/login` 接口,实现用户登录验证和错误响应。
22
  - 添加 `CORSMiddleware` 处理跨域请求。
23
  - **修复了 `Course` Pydantic 模型中的类型提示错误,将 `str | None` 更改为 `Optional[str]`,以兼容 Python 3.9 环境。**
24
+ - **在 `init_db()` 中为 `courses` 表添加了 `page_info` 字段(TEXT 类型,用于存储 JSON)。**
25
+ - **更新了 `Course` Pydantic 模型,包含 `page_info` 字段。**
26
+ - **修改了 `add_course` 接口,以处理 `page_info` 字段的 JSON 序列化存储。**
27
+ - **修改了 `get_course_details` 接口,以处理 `page_info` 字段的 JSON 反序列化。**
28
+ - **文件系统**:
29
+ - **创建了 `upload/images` 目录。**
30
+ - **配置了 FastAPI 以提供静态文件服务,`upload` 目录可通过 `/upload` 路径访问。**
31
 
32
  ## 下一步计划
33
  - **安全性增强**:
memory-bank/progress.md CHANGED
@@ -16,6 +16,13 @@
16
  - 登录失败返回自定义错误码和信息。
17
  - 优化了错误捕获和日志输出。
18
  - **类型提示兼容性修复**: 修复了 `app.py` 中 `Course` 模型因 Python 版本导致的类型提示错误。
 
 
 
 
 
 
 
19
 
20
  ## 待完成的工作
21
  - **安全性增强**:
 
16
  - 登录失败返回自定义错误码和信息。
17
  - 优化了错误捕获和日志输出。
18
  - **类型提示兼容性修复**: 修复了 `app.py` 中 `Course` 模型因 Python 版本导致的类型提示错误。
19
+ - **课程表字段扩展**: 在 `courses` 表中增加了 `page_info` 字段(TEXT 类型,用于存储 JSON)。
20
+ - **课程接口功能增强**:
21
+ - `Course` Pydantic 模型已更新,包含 `page_info` 字段。
22
+ - `add_course` 接口现在支持 `page_info` 字段的 JSON 序列化存储。
23
+ - `get_course_details` 接口现在支持 `page_info` 字段的 JSON 反序列化。
24
+ - **文件系统**:
25
+ - **创建了 `upload/images` 目录。**
26
 
27
  ## 待完成的工作
28
  - **安全性增强**:
memory-bank/systemPatterns.md CHANGED
@@ -5,6 +5,7 @@
5
  - **数据库**: SQLite
6
  - **API 路由**: 使用 `APIRouter` 进行模块化管理,所有 API 路径前缀为 `/api`。
7
  - **CORS**: 使用 `CORSMiddleware` 处理跨域请求。
 
8
 
9
  ## 关键技术决策
10
  - **用户 ID 生成**: 采用 `uuid.uuid4()` 生成全局唯一的用户 ID。
@@ -19,10 +20,10 @@
19
  - `app.py`: 包含 FastAPI 应用实例、CORS 配置、数据库初始化逻辑、API 路由定义。
20
  - `api_router`: 负责定义 `/api/auth/register`, `/api/auth/login`, `/api/course/add`, `/api/course/details/{course_id}` 等接口。
21
  - `sqlite3`: 用于与 `ai_edu.db` 数据库文件进行交互。
22
- - `pydantic.BaseModel`: 用于定义请求体的模型(`RegisterRequest`, `LoginRequest`)。
23
 
24
  ## 关键实现路径
25
  - **用户注册**: 接收 `username` 和 `password` -> 生成 `user_id` (UUID) -> 插入 `users` 表 -> 返回成功响应或错误。
26
  - **用户登录**: 接收 `username` 和 `password` -> 查询 `users` 表验证凭据 -> 返回成功响应(包含 `user_id` 和 `username`)或错误。
27
- - **课程添加**: 接收课程详情 (`title`, `description`, `price`, `duration_hours`, `level`) -> 插入 `courses` 表 -> 返回成功响应。
28
- - **课程详情查询**: 接收 `course_id` -> 查询 `courses` 表 -> 返回课程详情或 404 错误。
 
5
  - **数据库**: SQLite
6
  - **API 路由**: 使用 `APIRouter` 进行模块化管理,所有 API 路径前缀为 `/api`。
7
  - **CORS**: 使用 `CORSMiddleware` 处理跨域请求。
8
+ - **文件存储**: `upload/images` 目录用于存储上传的图片文件,并通过 `/upload` 路径提供静态文件服务。
9
 
10
  ## 关键技术决策
11
  - **用户 ID 生成**: 采用 `uuid.uuid4()` 生成全局唯一的用户 ID。
 
20
  - `app.py`: 包含 FastAPI 应用实例、CORS 配置、数据库初始化逻辑、API 路由定义。
21
  - `api_router`: 负责定义 `/api/auth/register`, `/api/auth/login`, `/api/course/add`, `/api/course/details/{course_id}` 等接口。
22
  - `sqlite3`: 用于与 `ai_edu.db` 数据库文件进行交互。
23
+ - `pydantic.BaseModel`: 用于定义请求体的模型(`RegisterRequest`, `LoginRequest`, `Course`)。
24
 
25
  ## 关键实现路径
26
  - **用户注册**: 接收 `username` 和 `password` -> 生成 `user_id` (UUID) -> 插入 `users` 表 -> 返回成功响应或错误。
27
  - **用户登录**: 接收 `username` 和 `password` -> 查询 `users` 表验证凭据 -> 返回成功响应(包含 `user_id` 和 `username`)或错误。
28
+ - **课程添加**: 接收课程详情 (`title`, `description`, `price`, `duration_hours`, `level`, `page_info`) -> 插入 `courses` 表(`page_info` 存储为 JSON 字符串) -> 返回成功响应。
29
+ - **课程详情查询**: 接收 `course_id` -> 查询 `courses` 表 -> 返回课程详情(`page_info` 反序列化为 JSON 对象)或 404 错误。
memory-bank/techContext.md CHANGED
@@ -7,6 +7,7 @@
7
  - **Pydantic**: 数据验证和设置管理库,用于定义请求体模型
8
  - **SQLite3**: Python 内置的轻量级数据库
9
  - **uuid**: Python 标准库,用于生成通用唯一标识符
 
10
 
11
  ## 开发环境设置
12
  - **Conda**: 用于管理 Python 虚拟环境 (`conda activate rzwl`)
 
7
  - **Pydantic**: 数据验证和设置管理库,用于定义请求体模型
8
  - **SQLite3**: Python 内置的轻量级数据库
9
  - **uuid**: Python 标准库,用于生成通用唯一标识符
10
+ - **FastAPI.staticfiles**: 用于提供静态文件服务
11
 
12
  ## 开发环境设置
13
  - **Conda**: 用于管理 Python 虚拟环境 (`conda activate rzwl`)
page_info.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "type": "image",
4
+ "url": "/upload/images/banner.jpg"
5
+ },
6
+ {
7
+ "pictures": [
8
+ {
9
+ "url": "/upload/images/pic1.jpg"
10
+ },
11
+ {
12
+ "url": "/upload/images/pic2.jpg"
13
+ },
14
+ {
15
+ "url": "/upload/images/pic3.jpg"
16
+ }
17
+ ]
18
+ }
19
+ ]
requirements.txt CHANGED
@@ -1,2 +1,7 @@
1
  fastapi
2
- uvicorn[standard]
 
 
 
 
 
 
1
  fastapi
2
+ uvicorn
3
+ pydantic
4
+ python-multipart
5
+ passlib
6
+ bcrypt
7
+ httpx
方案.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI-EDU 后端 API 方案
2
+
3
+ ## 1. 项目概述
4
+
5
+ **项目名称**: AI-EDU 后端 API
6
+
7
+ **项目目标**: 使用 FastAPI 实现 AI-EDU H5 应用的后端 API,提供用户认证(注册、登录)、课程管理等功能,并使用 SQLite 数据库进行数据存储。
8
+
9
+ **核心需求**:
10
+ - 实现用户注册接口 (`/api/auth/register`)
11
+ - 实现用户登录接口 (`/api/auth/login`)
12
+ - 实现课程添加接口 (`/api/course/add`)
13
+ - 实现课程详情查询接口 (`/api/course/details/{course_id}`)
14
+ - 使用 SQLite 数据库存储用户和课程数据
15
+ - 接口响应遵循统一的数据结构(code, message, data)
16
+ - 处理跨域请求 (CORS)
17
+ - 提供静态文件服务,用于存储上传的图片文件
18
+
19
+ **预期成果**: 一个稳定、可扩展的 FastAPI 后端服务,能够支持 AI-EDU H5 应用的基本用户认证和课程管理功能。
20
+
21
+ ## 2. 产品背景
22
+
23
+ **项目存在的原因**: 为 AI-EDU H5 前端应用提供必要的后端支持,特别是用户认证、课程管理和数据管理功能。
24
+
25
+ **解决的问题**:
26
+ - 用户注册和登录:允许用户创建账户并登录到应用。
27
+ - 数据存储:持久化用户和课程数据,为后续功能提供基础。
28
+ - API 接口:提供前端与后端交互的标准接口。
29
+ - 静态资源服务:支持图片等静态文件的上传和访问。
30
+
31
+ **工作方式**: 前端通过 HTTP 请求调用后端 API,后端处理业务逻辑并与 SQLite 数据库交互,然后返回结构化的 JSON 响应。
32
+
33
+ **用户体验目标**:
34
+ - 注册和登录流程顺畅,响应及时。
35
+ - 错误信息清晰,便于用户理解和操作。
36
+ - 课程信息展示准确,加载迅速。
37
+
38
+ ## 3. 系统架构与技术栈
39
+
40
+ **系统架构**:
41
+ - **后端框架**: FastAPI
42
+ - **数据库**: SQLite
43
+ - **ASGI 服务器**: Uvicorn
44
+ - **API 路由**: 使用 `APIRouter` 进行模块化管理,所有 API 路径前缀为 `/api`。
45
+ - **CORS**: 使用 `CORSMiddleware` 处理跨域请求。
46
+ - **文件存储**: `upload/images` 目录用于存储上传的图片文件,并通过 `/upload` 路径提供静态文件服务。
47
+
48
+ **关键技术决策**:
49
+ - **用户 ID 生成**: 采用 `uuid.uuid4()` 生成全局唯一的用户 ID。
50
+ - **密码存储**: 当前直接存储明文密码(**待改进:未来应使用哈希加密存储**)。
51
+ - **错误处理**: 统一的 `code`, `message`, `data` 响应格式。
52
+ - **数据库初始化**: 在 FastAPI 应用启动时通过 `@app.on_event("startup")` 钩子函数初始化 SQLite 数据库和相关表。
53
+ - **课程 `page_info` 存储**: 存储为 JSON 字符串,查询时反序列化。
54
+
55
+ **组件关系**:
56
+ - `app.py`: FastAPI 应用实例、CORS 配置、数据库初始化、API 路由定义。
57
+ - `api_router`: 定义用户认证和课程管理接口。
58
+ - `sqlite3`: 与 `ai_edu.db` 数据库文件交互。
59
+ - `pydantic.BaseModel`: 定义请求体模型(`RegisterRequest`, `LoginRequest`, `Course`)。
60
+ - `FastAPI.staticfiles`: 提供静态文件服务。
61
+
62
+ **使用技术**:
63
+ - Python
64
+ - FastAPI
65
+ - Uvicorn
66
+ - Pydantic
67
+ - SQLite3
68
+ - uuid
69
+ - FastAPI.staticfiles
70
+
71
+ **开发环境设置**:
72
+ - Conda: 用于管理 Python 虚拟环境 (`conda activate rzwl`)
73
+ - 依赖安装: `pip install -r requirements.txt` (包含 `fastapi`, `uvicorn[standard]`)
74
+
75
+ **运行命令**:
76
+ `conda activate rzwl && uvicorn app:app --host 0.0.0.0 --port 7860 --reload`
77
+
78
+ **技术约束**:
79
+ - SQLite 数据库适用于小型项目或开发环境,不适合高并发、大规模生产环境。
80
+ - 密码当前未加密存储,存在安全风险,未来需要引入密码哈希库(如 `passlib`)。
81
+
82
+ ## 4. 当前进展与下一步计划
83
+
84
+ **已完成的工作**:
85
+ - FastAPI 应用启动和 CORS 配置。
86
+ - 用户注册接口 (`/api/auth/register`):实现了用户 ID 生成、数据存储到 SQLite、用户名重复处理。
87
+ - 用户登录接口 (`/api/auth/login`):实现了用户验证、自定义错误响应。
88
+ - 课程管理功能:
89
+ - 实现了 `courses` 表的创建。
90
+ - 实现了 `/api/course/add` 接口,用于添加课程,支持 `page_info` 字段的 JSON 序列化存储。
91
+ - 实现了 `/api/course/details/{course_id}` 接口,用于获取课程详情,支持 `page_info` 字段的 JSON 反序列化。
92
+ - `Course` Pydantic 模型类型提示兼容性修复。
93
+ - 创建了 `upload/images` 目录,并配置了 FastAPI 提供静态文件服务。
94
+
95
+ **待完成的工作**:
96
+ - **安全性增强**:
97
+ - **密码哈希**: 引入密码哈希(如 `passlib`)来加密存储用户密码,提高安全性。
98
+ - **会话管理/JWT**: 实现用户登录后的会话管理或 JWT 认证机制。
99
+ - **更多业务接口**: 根据 AI-EDU H5 的需求,逐步实现其他后端 API。
100
+ - **数据库抽象**: 考虑引入 ORM(如 SQLAlchemy)来更好地管理数据库操作。
101
+
102
+ **当前状态**:
103
+ - 基本的用户认证和课程管理功能已实现并可运行。
104
+ - 数据库集成已完成。
105
+ - 接口响应格式已统一。
106
+
107
+ **已知问题**:
108
+ - 密码明文存储,存在安全风���。
109
+
110
+ **项目决策演变**:
111
+ - 从简单的接口逐步扩展到完整的用户认证和课程管理流程。
112
+ - 从模拟 `user_id` 到使用 `uuid` 确保唯一性。
113
+ - 从默认错误到自定义业务错误响应。
114
+ - 从未处理 CORS 到添加 `CORSMiddleware`。
115
+ - 增加了课程管理功能和静态文件服务。