Version 0.1.1 rise to flask app factory
This commit is contained in:
46
Html/apps/services/auth_service.py
Normal file
46
Html/apps/services/auth_service.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# myapp/services/auth_service.py
|
||||
import os, uuid, json
|
||||
from flask import current_app, session
|
||||
|
||||
def create_user_json(username: str):
|
||||
user_dir = current_app.config["USER_DATA_DIR"]
|
||||
os.makedirs(user_dir, exist_ok=True)
|
||||
path = os.path.join(user_dir, f"{username}.json")
|
||||
if not os.path.exists(path):
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump({"username": username}, f, ensure_ascii=False)
|
||||
|
||||
def register_user(username: str, password: str, *, teacher: bool=False):
|
||||
users_list = current_app.extensions["users_list"]
|
||||
# 你原逻辑似乎把 has_user 的判断反了:直觉上「不存在才允许新增」
|
||||
exists = users_list.has_user(username)
|
||||
if exists:
|
||||
return False, "用户已存在,请更换用户名"
|
||||
users_list.add_user(username, password, teacher=teacher)
|
||||
create_user_json(username)
|
||||
return True, "注册成功"
|
||||
|
||||
def login_user(username: str, password: str, *, require_teacher: bool=False):
|
||||
users_list = current_app.extensions["users_list"]
|
||||
pswd = users_list.get_user_pswd(username)
|
||||
if pswd is None or pswd != password:
|
||||
return False, "用户名或密码错误"
|
||||
|
||||
if require_teacher:
|
||||
is_teacher = users_list.get_user_is_teacher(username)
|
||||
if not is_teacher:
|
||||
return False, "用户名不存在或非教师账号"
|
||||
|
||||
# 设置会话 + 关联映射
|
||||
user_uuid = "user_" + str(uuid.uuid4())
|
||||
session["user_id"] = user_uuid
|
||||
|
||||
username2uuid = current_app.extensions["username2uuid"]
|
||||
uuid2username = current_app.extensions["uuid2username"]
|
||||
username2uuid[username] = user_uuid
|
||||
uuid2username[user_uuid] = username
|
||||
|
||||
return True, "登录成功"
|
||||
|
||||
def logout_user():
|
||||
session.pop("user_id", None)
|
||||
59
Html/apps/services/backboard_service.py
Normal file
59
Html/apps/services/backboard_service.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# myapp/services/backboard_service.py
|
||||
import os
|
||||
from flask import current_app
|
||||
from backboardManager import Backboard # 你已有的类
|
||||
|
||||
def _to_wsl_path_if_needed(path: str) -> str:
|
||||
cfg = current_app.config["VSCODE_WEB_PATH"]
|
||||
if cfg.get("is_wsl") and cfg.get("windows_path") and cfg.get("wsl_path"):
|
||||
return path.replace("\\", "/").replace(cfg["windows_path"], cfg["wsl_path"])
|
||||
return path
|
||||
|
||||
def realtime_response(config: dict, realtime_action: dict) -> None:
|
||||
"""
|
||||
业务:根据 VSCode 上报的动作,更新 Backboard(黑板)状态。
|
||||
"""
|
||||
# 依赖从 app.extensions 取,避免循环导入
|
||||
username2uuid = current_app.extensions["username2uuid"]
|
||||
backboard_manager = current_app.extensions["backboard_manager"]
|
||||
|
||||
user_id = config["user_id"]
|
||||
folder_path = config["path"] # 如需 WSL 转换可在生成 config 时处理
|
||||
useruuid = username2uuid[user_id]
|
||||
bb = backboard_manager.get_backboard(useruuid)
|
||||
assert isinstance(bb, Backboard)
|
||||
|
||||
# 统一记历史
|
||||
bb.add_history(realtime_action)
|
||||
|
||||
rtype = realtime_action.get("type")
|
||||
|
||||
if rtype == "workspaceFolders":
|
||||
bb.file_tree = realtime_action.get("fileTree")
|
||||
|
||||
elif rtype == "activeFile":
|
||||
file_path = realtime_action.get("filePath")
|
||||
assert isinstance(file_path, str)
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
bb.active_file_content = f.read()
|
||||
bb.active_file_path = file_path
|
||||
|
||||
elif rtype == "paste":
|
||||
file_path = realtime_action.get("filePath")
|
||||
assert isinstance(file_path, str)
|
||||
bb.pasted_file_path = file_path
|
||||
bb.pasted_content = realtime_action.get("content")
|
||||
bb.active_file_path = file_path
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
bb.active_file_content = f.read()
|
||||
|
||||
elif rtype == "fileEdit":
|
||||
file_path = realtime_action.get("filePath")
|
||||
assert isinstance(file_path, str)
|
||||
bb.active_file_path = file_path
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
bb.active_file_content = f.read()
|
||||
|
||||
# 如需调试输出,可在这里统一记录日志
|
||||
current_app.logger.debug("vscode config: %s", config)
|
||||
current_app.logger.debug("vscode action: %s", realtime_action)
|
||||
20
Html/apps/services/course_service.py
Normal file
20
Html/apps/services/course_service.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# myapp/services/course_service.py
|
||||
from flask import current_app
|
||||
|
||||
# 假设你已有这两个 loader
|
||||
from db.course import load_course_from_json
|
||||
|
||||
def load_course(course_id: str):
|
||||
return load_course_from_json(
|
||||
course_id, course_data_dir=current_app.config["COURSE_DATA_DIR"]
|
||||
)
|
||||
|
||||
def user_selected_course_briefs(user_obj):
|
||||
"""根据用户对象的选课列表,拼装课程简要信息数组"""
|
||||
course_list = current_app.extensions["course_list"]
|
||||
briefs = []
|
||||
for cid in getattr(user_obj, "select_course", []):
|
||||
course_obj = load_course(cid)
|
||||
brief = course_list.get_course_brief_info(cid, course_obj)
|
||||
briefs.append(brief)
|
||||
return briefs
|
||||
40
Html/apps/services/markdown_service.py
Normal file
40
Html/apps/services/markdown_service.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import os, shutil, markdown
|
||||
|
||||
def convert_markdown_to_html(md_file_path: str) -> str:
|
||||
"""读取 markdown 并转成 HTML 片段"""
|
||||
with open(md_file_path, 'r', encoding='utf-8') as f:
|
||||
md_content = f.read()
|
||||
return markdown.markdown(md_content)
|
||||
|
||||
def wrap_with_styles(html_content: str) -> str:
|
||||
"""加上 CSS 样式和外层 HTML"""
|
||||
return f"""
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
img {{
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}}
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
|
||||
</head>
|
||||
<body class="markdown-body">
|
||||
{html_content}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
def save_html(html_str: str, output_path: str) -> None:
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(html_str)
|
||||
|
||||
def copy_images(source_dir: str, target_dir: str) -> None:
|
||||
if not os.path.exists(source_dir):
|
||||
return
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
for filename in os.listdir(source_dir):
|
||||
src = os.path.join(source_dir, filename)
|
||||
if os.path.isfile(src):
|
||||
shutil.copy(src, target_dir)
|
||||
26
Html/apps/services/memory_service.py
Normal file
26
Html/apps/services/memory_service.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# myapp/services/memory_service.py
|
||||
import os, json
|
||||
from flask import current_app
|
||||
from db.user import load_user_from_json # 按你项目实际替换
|
||||
|
||||
def save_chapter_memory_by_uuid(
|
||||
user_uuid: str,
|
||||
course_id: str,
|
||||
lesson_id: str,
|
||||
subchapter_title: str,
|
||||
mem_list,
|
||||
score,
|
||||
is_rebuttal: bool,
|
||||
):
|
||||
uuid2username = current_app.extensions["uuid2username"]
|
||||
user_map = current_app.extensions["user_id2UserClass"] # 可复用缓存
|
||||
user_data_dir = current_app.config["USER_DATA_DIR"]
|
||||
|
||||
username = uuid2username[user_uuid]
|
||||
# 若内存里已有用户对象就直接用;否则从 JSON 载入
|
||||
user_obj = user_map.get(user_uuid) or load_user_from_json(username, user_data_dir=user_data_dir)
|
||||
user_map[user_uuid] = user_obj
|
||||
|
||||
user_obj.save_chapter_memory(
|
||||
course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal
|
||||
)
|
||||
10
Html/apps/services/my_function.py
Normal file
10
Html/apps/services/my_function.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# myapp/services/my_function.py
|
||||
from flask import current_app
|
||||
from .memory_service import save_chapter_memory_by_uuid
|
||||
|
||||
class MyFunction:
|
||||
def save_chapter_memory(self, user_uuid, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal):
|
||||
current_app.logger.debug("save_chapter_memory %s %s %s", user_uuid, course_id, lesson_id)
|
||||
save_chapter_memory_by_uuid(
|
||||
user_uuid, course_id, lesson_id, subchapter_title, mem_list, score, is_rebuttal
|
||||
)
|
||||
42
Html/apps/services/user_service.py
Normal file
42
Html/apps/services/user_service.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# myapp/services/user_service.py
|
||||
import os
|
||||
from typing import Tuple
|
||||
from flask import current_app, session
|
||||
|
||||
# 假设你已有这两个 loader
|
||||
from db.user import load_user_from_json
|
||||
|
||||
def _username_from_session() -> str:
|
||||
"""通过 session['user_id'] -> username"""
|
||||
user_uuid = session.get("user_id")
|
||||
if not user_uuid:
|
||||
return None
|
||||
uuid2username = current_app.extensions["uuid2username"]
|
||||
return uuid2username.get(user_uuid)
|
||||
|
||||
def get_or_load_current_user():
|
||||
"""
|
||||
返回当前用户对象(如 user_id2UserClass 中没有则从 JSON 载入并缓存)
|
||||
"""
|
||||
user_uuid = session.get("user_id")
|
||||
if not user_uuid:
|
||||
return None
|
||||
|
||||
username = _username_from_session()
|
||||
if not username:
|
||||
return None
|
||||
|
||||
user_map = current_app.extensions["user_id2UserClass"]
|
||||
if user_uuid not in user_map:
|
||||
user_map[user_uuid] = load_user_from_json(
|
||||
username, user_data_dir=current_app.config["USER_DATA_DIR"]
|
||||
)
|
||||
return user_map[user_uuid]
|
||||
|
||||
def add_course_for_current_user(course_id: str, course_data):
|
||||
user_obj = get_or_load_current_user()
|
||||
if user_obj is None:
|
||||
return False
|
||||
# 你的 UserClass 应该有 select_new_course 接口
|
||||
user_obj.select_new_course(course_id, course_data)
|
||||
return True
|
||||
Reference in New Issue
Block a user