当前位置: 首页 > news >正文

PaperMind学术阅读平台搭建(一)

一、整体架构说明

我们在实现这个功能的时候也去参考了许多的相关登录功能实现,但是考虑到我们这个是一个课程项目,而不是真正的需要高并发等等复杂场景,为了方便实现,因此选择了较为简单的轻量方案来实现。

后端:FastAPI 中间件拦截 + Cookie-Session 机制
存储:暂时JSON 文件存储用户数据,无数据库依赖,这里只是我们当前开发阶段暂未设计数据库,等后续设计好数据库后会加入数据库依赖
密码:SHA-256 哈希,这里也是基于我们上学期密码学与编程,也了解到存储密码不应该明文存储,而是应该使用哈希加密等等技术手段存储密文值
前端:原生 HTML/CSS/JS,登录页与主页分离

这里考虑到因为是作为课程项目,我们暂时不需要 JWT、Redis Session、OAuth 这些重型方案,我也去了解了相关的技术,可能后面几个星期再优化时会加入这些技术方案,目前先轻量实现,但是轻量实现该有的安全登录认证等等都有

二、后端认证实现

2.1 用户服务层

用户数据存储在一个 JSON 文件中,结构很简单:

{
"admin": "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3",
"zhangsan": "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"
}

键是用户名,值是密码的 SHA-256 哈希。核心代码:

class AuthService:
def __init__(self, settings: Settings) -> None:
self.settings = settings
self._lock = threading.Lock()
self.users_file: Path = settings.auth_users_file
if not self.users_file.exists():
self._write_users({})
if not self.user_exists(settings.auth_username):
self.register(settings.auth_username, settings.auth_password)

def register(self, username: str, password: str) -> None:
username = username.strip()
if not username:
raise ValueError("用户名不能为空。")
if len(username) < 3 or len(username) > 24:
raise ValueError("用户名长度需在 3~24 之间。")
if len(password) < 6:
raise ValueError("密码长度至少 6 位。")

with self._lock:
users = self._read_users()
if username in users:
raise ValueError("用户名已存在。")
users[username] = self._hash_password(password)
self._write_users(users)

def verify_user(self, username: str, password: str) -> bool:
users = self._read_users()
digest = users.get(username.strip())
if digest is None:
return False
return digest == self._hash_password(password)

@staticmethod
def _hash_password(password: str) -> str:
return hashlib.sha256(password.encode("utf-8")).hexdigest()

个人理解:

这里有几个值得注意的设计决策:

1. 线程锁:注册操作涉及“读-判断-写”三步,如果不加锁,并发注册可能导致同名用户被重复创建。虽然课程项目并发量不高,但养成这个习惯很重要。
2. SHA-256 :生产环境应该用 bcrypt/scrypt/argon2 这类慢哈希算法来抵抗暴力破解。SHA-256 速度太快,攻击者可以高速试错。但对于课程项目,SHA-256 足以体现“密码绝不能明文存储”的底线意识。
3. JSON 文件存储:没有引入数据库,降低了部署复杂度。缺点是不适合高并发场景,但对于课程项目和演示系统,这种轻量方案反而更容易讲清楚。
4. 默认管理员自动创建:系统启动时会自动生成默认管理员账号,这种设计能显著降低首次部署成本。

2.2 登录、注册与 Cookie-Session

后端登录逻辑写在 `app/main.py` 中,核心流程如下:

1. 前端提交用户名和密码到 `/api/v1/auth/login`
2. 后端验证账号密码
3. 验证通过后生成随机 `session_id`
4. 将 `session_id -> username` 存到 `app.state.sessions`
5. 通过 `response.set_cookie()` 把 Session 写入浏览器

核心代码如下:

@app.post("/api/v1/auth/login")
def login(payload: LoginPayload):
username = payload.username.strip()
password = payload.password
if not username or not password:
raise HTTPException(status_code=400, detail="用户名和密码不能为空。")

if not auth_service.verify_user(username, password) and not auth_service.user_exists(username):
try:
auth_service.register(username, password)
except ValueError:
pass

session_id = token_urlsafe(32)
app.state.sessions[session_id] = username
response = JSONResponse({"message": "登录成功。", "username": username})
response.set_cookie(
key=session_cookie_name,
value=session_id,
httponly=True,
samesite="lax",
max_age=60 * 60 * 12,
)
return response

注册接口则相对直接:

@app.post("/api/v1/auth/register")
def register(payload: RegisterPayload):
if payload.password != payload.confirm_password:
raise HTTPException(status_code=400, detail="两次密码输入不一致。")
try:
auth_service.register(payload.username, payload.password)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return {"message": "注册成功,请登录。", "username": payload.username.strip()}

个人理解:

这里我觉得最值得讲的是:我们没有选 JWT,而是选了 Cookie-Session。

原因很简单:这个项目是一个典型的浏览器访问型系统,前后端都由我们自己控制,不需要跨端鉴权,也没有复杂的第三方 API 调用场景。此时 Cookie-Session 反而更顺手:

浏览器会自动携带 Cookie,前端代码更简洁
Session 可以由服务端统一失效,控制力更强
不需要额外处理 JWT 刷新、续签、黑名单这些问题

当然,这种方式也有局限:当前 Session 数据存放在内存里,服务一重启所有登录状态都会消失。如果以后系统要上线,我会优先把 Session 放到 Redis 中。

if not auth_service.verify_user(username, password) and not auth_service.user_exists(username):
try:
auth_service.register(username, password)

这意味着:如果用户不存在,登录接口会尝试自动注册。

这在严格意义上不属于标准登录逻辑,但从课程演示体验来看,它降低了首次使用门槛。用户第一次输入账号密码时,不一定非要先点“注册”。

我的理解是:这是一种“演示友好型设计”,适合项目答辩或教学场景;但如果是正式系统,我会把登录和注册职责彻底分离,避免语义混乱。

2.3 中间件拦截:未登录禁止访问解析系统

为了防止未登录用户直接访问解析接口,我们实现了一个认证中间件:

@app.middleware("http")
async def auth_middleware(request: Request, call_next):
path = request.url.path
if _is_public_path(path):
return await call_next(request)
if _get_session_user(request):
return await call_next(request)
if path.startswith("/api/"):
return JSONResponse(status_code=401, content={"detail": "请先登录。"})
return RedirectResponse(url="/login")

它做了三件事:

公共路径放行,比如 `/login`、`/health`、登录接口、注册接口
已登录用户放行
未登录用户访问 API 时返回 `401`
未登录用户访问页面时重定向到 `/login`

个人理解:

这一段设计非常“实用主义”。它没有把权限系统做复杂,而是先把“登录前不能用核心功能”这个基本约束落实下来。

特别值得注意的一点是:页面请求和接口请求做了差异化处理。

页面请求:重定向到登录页,用户体验更自然
API 请求:直接返回 401 JSON,方便前端准确识别错误

这其实体现了一个很重要的 Web 开发思维:同样是未认证,不同请求类型的响应策略应该不同。

三、前端登录页设计

3.1 原生三件套的选型考量

本项目采用原生 HTML、CSS、JavaScript 实现前端,主要基于以下考量:

  1. 部署便捷性:无需前端构建流程,静态页面可直接通过 FastAPI 返回。
  2. 结构透明性:代码逻辑直观,便于教学场景下的理解与评审。
  3. 基础能力强化:自主实现布局、动画与交互逻辑,更能体现设计深度。

设计哲学:技术选型应服务于场景目标。在课程项目中,原生开发能更纯粹地展示底层能力,避免框架带来的抽象层干扰。


3.2 视觉语言构建

登录页以“轻柔治愈+学术感”为核心风格,关键设计元素:

  • 色彩:蓝粉渐变背景,营造舒缓氛围。
  • 材质:半透明毛玻璃面板,增强现代感。
  • 布局:左表单(功能) + 右吉祥物(情感),平衡实用与趣味。
  • 动效:云朵角色眼球跟随鼠标,提升交互沉浸感。

设计价值:通过情感化设计降低工具冰冷感,形成产品记忆点。例如,云朵吉祥物将“学术解析”转化为具象化的温暖形象。


3.3 登录/注册同页切换

技术实现

function openTab(type) { const isLogin = type === "login"; // 切换 Tab 状态 tabLogin.classList.toggle("active", isLogin); tabRegister.classList.toggle("active", !isLogin); // 控制表单显隐与动效 loginForm.classList.toggle("active", isLogin); registerForm.classList.toggle("active", !isLogin); }
.form { opacity: 0; transform: translateX(14px); transition: opacity 0.25s, transform 0.25s; } .form.active { opacity: 1; transform: translateX(0); }

设计优势

  • 操作连贯性:避免页面跳转中断用户注意力。
  • 精致度提升:微动效暗示状态变迁,增强界面响应感。

3.4 表单交互优化

核心策略

  1. 异步提交:拦截默认行为,通过fetch发送请求。
  2. 即时反馈
    • 错误提示固定于表单底部(非弹窗)。
    • 密码一致性前端校验:
      if (regPassword.value !== regConfirm.value) { setHint("两次密码不一致"); return; }
  3. 防重机制:提交时禁用按钮。

体验逻辑:反馈贴近操作点,形成用户预期路径。例如固定提示区的位置,帮助用户快速定位信息。


3.5 情感化细节设计

云朵吉祥物通过 CSS 状态机表达情绪:

  • state-idle:悬浮待机
  • state-user:输入时微倾
  • state-pass:密码输入时眯眼(安全暗示)
  • state-submit:提交时跳动(进度反馈)

眼球追踪算法

function moveEyes(clientX, clientY) { const maxOffset = 3.5; eyes.forEach((eye) => { const pupil = eye.querySelector(".pupil"); const rect = eye.getBoundingClientRect(); const cx = rect.left + rect.width / 2; // 眼球中心 x const cy = rect.top + rect.height / 2; // 眼球中心 y const dx = clientX - cx; const dy = clientY - cy; const dist = Math.max(Math.hypot(dx, dy), 1); const ox = (dx / dist) * maxOffset; // x 轴偏移量 const oy = (dy / dist) * maxOffset; // y 轴偏移量 pupil.style.transform = `translate(calc(-50% + ${ox}px), calc(-50% + ${oy}px))`; }); }

情感价值:拟人化交互建立产品亲和力,尤其在展示场景中成为差异化记忆符号。


四、主页设计延续性

主页继承登录页设计语言,并强化功能分层:

  1. 布局:卡片化分区(上传区、任务状态区、结果预览区)。
  2. 信息流:按用户操作链路排列模块(提交→进度→结果)。
  3. 视觉统一:毛玻璃导航栏、蓝粉主色调、圆角元素。

设计准则:界面应引导用户视线流动。例如任务状态侧栏的固定位置,确保进度反馈始终可见。


http://www.jsqmd.com/news/639698/

相关文章:

  • SO3控制器在无人机轨迹跟踪中的核心算法解析
  • NAS部署MarkItDown
  • 2026江苏万高电机代理商哪家好?选无锡迈腾机电享正品保障 - 速递信息
  • HarmonyOS6 三方库插件实战:RcRate 评分组件核心架构与类型系统设计
  • 私域直播双端盈利 盲盒V6MAX源码系统小程序 商用盲盒app源码程序 海外定制开发 - 壹软科技
  • 笑不活了!AI时代打工人的超能力进化指南:从“Ctrl+C/V”到“动嘴皮子”
  • 2026年洛阳江浙菜宴请完全指南:诱江南官方联系方式+行业深度横评+避坑清单 - 精选优质企业推荐榜
  • 给嵌入式新手的U-Boot启动流程拆解:从SRAM到SDRAM,代码到底怎么跑的?
  • 健康160全自动挂号工具:3步实现专家号源秒杀
  • FastAPI 进阶:教你 APIRouter 模块化与 Pydantic 实战
  • 逆向归纳法实战:从海盗分金到子博弈精炼Nash均衡
  • 【深度解析】苏州工业机器人培训:核心内容与就业指南 - 速递信息
  • Qwen3字幕系统应用场景:清音刻墨助力法律庭审录音自动生成笔录时间轴
  • 2026年4月工业内窥镜手持式与防爆型推荐——哪家可定制特殊工况检测设备? - 品牌推荐大师1
  • python编程语法基础笔记(4.13)(网络编程)
  • Local SDXL-Turbo实战教程:用‘cyberpunk style, 4k, realistic’生成高清海报
  • 百度网盘免会员下载加速终极指南:三步实现满速下载
  • Shadcn-Vue终极指南:3个技巧打造专业级Vue组件库应用
  • 20N50 -ASEMI大电流场景的性能新标杆20N50
  • 2026年汽车电瓶栓、汽车连接器、保险盒口碑推荐榜单:浙江大欧电子车规级配套选型指南 - 海棠依旧大
  • FireRedASR-AED-L在MATLAB环境下的调用与性能分析
  • 2026活动小程序开发公司怎么选?麦冬科技提供定制方案(附带联系方式) - 品牌2025
  • 为什么你的文本文件总显示乱码?EncodingChecker 编码检测工具深度解析
  • 如何用Unlock Music Electron轻松解密加密音乐文件:终极完整指南
  • 基于单片机的智能家居门铃系统设计
  • 弦音墨影快速上手指南:3步启动视频理解系统,支持自然语言提问
  • 2026年4月药用级羟乙基纤维素与壳聚糖的供应选择解析 - 品牌推荐大师1
  • **发散创新:基于FFmpeg的视频编码优化实践与实战代码解析**在现代多媒
  • 2026社媒获客公司推荐:助力企业精准触达目标客户 - 品牌排行榜
  • 盘点2026年唐家、金湾、横琴别墅全屋定制公司,选哪家比较靠谱 - 工业品牌热点