1. 项目概述:学员个人备忘系统的核心价值
去年帮学校实验室重构信息化系统时,发现学生们最迫切的需求不是复杂的课程管理,而是一个能随手记录学习要点的个人备忘工具。这个基于SpringBoot和Vue的学员个人备忘系统,正是为解决这个痛点而生。它不像传统笔记软件那样功能臃肿,而是针对学习场景做了深度优化——上课时快速记录重点、实验前查看操作步骤、复习时整理知识脉络,所有功能都围绕"高效记录+快速检索"展开。
技术选型上,后端采用SpringBoot 2.7 + MyBatis-Plus组合,前端使用Vue 3 + Element Plus。这套技术栈的优势在于:SpringBoot的自动配置让后端开发效率极高,Vue的响应式特性完美适配频繁更新的备忘内容。实测从零搭建到基本功能可用,两个开发者配合只需3天时间。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计解析
2.1 前后端分离架构实践
采用经典的前后端分离模式,通过RESTful API进行数据交互。这种架构让学生客户端(Web/App)和后端服务可以独立演进。特别设计了版本兼容方案:API路径中包含v1标识(如/api/v1/memos),为后续升级留出空间。
后端模块划分:
- memo-core:核心业务逻辑(备忘CRUD)
- memo-auth:JWT认证模块
- memo-storage:附件存储服务
- memo-scheduler:定时提醒服务
前端工程结构:
code复制/src
/api - 接口封装
/components - 公共组件
Editor.vue - 富文本编辑器
Reminder.vue - 提醒设置面板
/stores - Pinia状态管理
/views - 页面组件
2.2 数据库关键设计
使用MySQL 8.0作为主数据库,主要表结构设计如下:
sql复制CREATE TABLE `memo` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL COMMENT '所属用户',
`title` VARCHAR(100) NOT NULL COMMENT '标题',
`content` LONGTEXT COMMENT '内容(Markdown格式)',
`is_pinned` TINYINT DEFAULT 0 COMMENT '是否置顶',
`status` TINYINT DEFAULT 1 COMMENT '状态(1正常 0删除)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME ON UPDATE CURRENT_TIMESTAMP,
FULLTEXT INDEX `ft_content` (`title`, `content`) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `memo_tag` (
`memo_id` BIGINT NOT NULL,
`tag_name` VARCHAR(20) NOT NULL,
PRIMARY KEY (`memo_id`, `tag_name`)
) COMMENT='备忘标签关联表';
特别说明几个设计要点:
- 使用utf8mb4字符集支持emoji表情
- 通过ngram全文索引实现中文搜索
- 采用软删除设计保留操作痕迹
- 标签使用独立表实现多对多关系
3. 核心功能实现细节
3.1 富文本编辑器集成
对比了Quill、Tiptap等主流方案后,最终选用Toast UI Editor。选择依据:
- 完美支持Markdown双向转换
- 提供完整的API文档
- 体积仅300KB左右
在Vue中的集成示例:
javascript复制// 组件封装
<template>
<div class="editor-container">
<tui-editor
ref="editorRef"
:initialValue="content"
:options="editorOptions"
height="500px"
@change="onChange"
/>
</div>
</template>
<script setup>
import 'codemirror/lib/codemirror.css';
import '@toast-ui/editor/dist/toastui-editor.css';
import { Editor } from '@toast-ui/vue-editor';
const editorOptions = {
minHeight: '200px',
language: 'zh-CN',
hideModeSwitch: true,
toolbarItems: [
['heading', 'bold', 'italic', 'strike'],
['hr', 'quote'],
['ul', 'ol', 'task'],
['table', 'link'],
['code', 'codeblock']
]
};
</script>
踩坑提醒:直接使用v-model绑定会导致性能问题,建议通过ref手动获取内容
3.2 提醒功能实现
结合Spring的@Scheduled和WebSocket实现实时提醒:
java复制// 后端定时任务
@Scheduled(cron = "0 0/1 * * * ?")
public void checkReminders() {
List<Memo> memos = memoMapper.selectRemindersBefore(LocalDateTime.now());
memos.forEach(memo -> {
String message = String.format("备忘提醒: %s", memo.getTitle());
websocketHandler.sendMessage(memo.getUserId(), message);
memo.setReminderTime(null); // 清除已触发的提醒
memoMapper.updateById(memo);
});
}
前端通过StompJS接收WebSocket消息:
javascript复制import { Client } from '@stomp/stompjs';
const client = new Client({
brokerURL: 'ws://your-domain/ws-memo',
onConnect: () => {
client.subscribe('/user/queue/reminders', (message) => {
ElNotification({
title: '备忘提醒',
message: message.body,
duration: 0,
showClose: true
});
});
}
});
4. 性能优化实践
4.1 前端懒加载优化
对备忘录列表采用虚拟滚动技术,核心配置:
javascript复制<template>
<el-table
:data="memos"
style="width: 100%"
height="calc(100vh - 180px)"
row-key="id"
:row-height="72"
:virtual-scroller-options="{
bufferSize: 10,
loading: loading
}"
>
<!-- 列定义 -->
</el-table>
</template>
实测数据显示:
- 100条数据:渲染时间从1200ms → 200ms
- 500条数据:内存占用减少65%
4.2 后端缓存策略
采用多级缓存方案:
- 热点数据:Caffeine本地缓存(最大500条,过期时间5分钟)
- 高频查询:Redis集群缓存(过期时间30分钟)
- 持久层:MyBatis二级缓存
缓存一致性通过@CacheEvict注解保证:
java复制@PostMapping
@CacheEvict(value = "memos", key = "#memo.userId")
public Result createMemo(@RequestBody Memo memo) {
// 创建逻辑
}
5. 安全防护措施
5.1 XSS防御方案
前端使用DOMPurify过滤危险HTML:
javascript复制import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['p', 'strong', 'em', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: []
});
后端通过Jackson配置HTML转义:
java复制@Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder()
.featuresToEnable(JsonWriteFeature.ESCAPE_HTML_CHARS);
}
5.2 接口安全设计
- 采用JWT + 动态密钥方案
- 敏感接口增加频率限制(Guava RateLimiter)
- 密码传输使用RSA非对称加密
登录流程安全加固示例:
java复制public String login(LoginDTO dto) {
// 1. RSA解密密码
String rawPassword = RSAUtils.decrypt(dto.getPassword(), privateKey);
// 2. 验证码校验
if (!captchaService.verify(dto.getCaptchaKey(), dto.getCaptcha())) {
throw new BusinessException("验证码错误");
}
// 3. 密码错误次数检查
if (loginAttemptService.isBlocked(dto.getUsername())) {
throw new BusinessException("账户已锁定,请10分钟后再试");
}
// ... 后续认证逻辑
}
6. 部署与监控方案
6.1 Docker Compose部署
生产环境部署文件示例:
yaml复制version: '3.8'
services:
memo-db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PWD}
MYSQL_DATABASE: memo
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 3s
memo-backend:
image: memo-server:${TAG}
depends_on:
memo-db:
condition: service_healthy
environment:
SPRING_PROFILES_ACTIVE: prod
ports:
- "8080:8080"
deploy:
resources:
limits:
cpus: '1'
memory: 1G
memo-frontend:
image: memo-web:${TAG}
ports:
- "80:80"
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
volumes:
mysql-data:
6.2 Prometheus监控配置
SpringBoot启用Actuator端点:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: memo-system
关键监控指标:
- http_server_requests_seconds:接口响应时间
- jvm_memory_used:JVM内存使用
- system_cpu_usage:CPU负载
- hikaricp_connections_active:数据库连接池状态
7. 典型问题排查实录
7.1 跨域问题解决方案
开发环境常见跨域错误,推荐两种解决方式:
方案一:SpringBoot配置全局CORS
java复制@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:8081")
.allowedMethods("*")
.allowCredentials(true);
}
};
}
方案二:Nginx反向代理配置
nginx复制location /api/ {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
add_header 'Access-Control-Allow-Origin' '$http_origin' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
}
7.2 文件上传大小限制
遇到413 Request Entity Too Large错误时,需要修改配置:
SpringBoot端:
yaml复制spring:
servlet:
multipart:
max-file-size: 20MB
max-request-size: 30MB
Nginx端:
nginx复制client_max_body_size 30m;
8. 项目扩展方向
8.1 移动端适配方案
基于Uniapp的跨平台方案:
javascript复制// 条件编译处理平台差异
// #ifdef H5
import ToastUIEditor from '@toast-ui/vue-editor';
// #endif
// #ifdef APP-PLUS
const editor = uni.requireNativePlugin('NativeEditor');
// #endif
8.2 智能提醒功能
利用NLP技术增强提醒:
python复制# Python服务示例(可通过HTTP调用)
import hanlp
recognizer = hanlp.load(hanlp.pretrained.ner.MSRA_NER_BERT_BASE_ZH)
def extract_time(text):
doc = recognizer(text)
return [str(ent) for ent in doc.ents if ent.type == 'TIME']
实际使用中发现,对于"下周一交作业"这类相对时间表述,需要结合jieba分词和自定义规则库进行补充识别。
