1. 项目概述:基于SpringBoot+Vue的读书笔记共享平台
这个全栈项目采用SpringBoot+Vue技术栈构建,主要面向高校学生群体实现读书笔记的在线共享与管理。作为典型的教学级应用,它完整覆盖了用户管理、笔记发布、互动评论、数据统计等核心功能模块,数据库选用MySQL进行持久化存储。我在实际开发中发现,这种技术组合特别适合作为计算机相关专业的毕业设计或课程设计选题——后端SpringBoot提供了稳健的REST API服务,前端Vue则实现了响应式的用户界面,两者通过axios进行数据交互,形成了清晰的前后端分离架构。
从技术选型角度看,SpringBoot 2.7.x + Vue 3.x的组合既保证了技术先进性,又具备丰富的学习资源。项目源码结构严格遵循Maven多模块规范,前端采用Vue CLI脚手架初始化,这种工程化实践能让学生真实体验企业级开发流程。特别值得注意的是,平台实现了笔记的Markdown编辑与实时预览功能,这需要前后端协同处理文本转换与XSS防护,是很好的技术实践案例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 后端SpringBoot实现要点
后端采用经典的MVC分层架构:
code复制com.example.notebook
├── config # 安全配置与Swagger配置
├── controller # 暴露REST接口
├── service # 业务逻辑层
├── dao # 数据访问层
├── entity # 数据库实体
└── util # 工具类包
关键依赖配置:
xml复制<!-- pom.xml核心依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.21.0</version>
</dependency>
</dependencies>
安全防护方面特别需要注意:
- 使用Spring Security配置RBAC权限模型
- 笔记内容入库前需经过HTML标签过滤
- 文件上传接口要限制文件类型和大小
- 启用CSRF防护并配置CORS规则
重要提示:处理Markdown内容时,一定要在服务端进行XSS过滤。实测发现CommonMark库虽然能转换Markdown为HTML,但不会自动过滤脚本标签,需要额外引入jsoup进行净化处理。
2.2 前端Vue技术方案
前端工程采用Vue 3组合式API开发,主要技术栈:
- 状态管理:Pinia替代Vuex
- UI组件:Element Plus
- 路由管理:Vue Router 4
- HTTP客户端:axios封装
核心页面结构:
bash复制src/
├── api/ # 接口请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── MarkdownEditor.vue # 带预览的编辑器
│ └── Pagination.vue # 分页组件
├── router/ # 路由配置
├── stores/ # Pinia状态管理
└── views/ # 页面组件
├── Home.vue # 首页
├── NoteDetail.vue # 笔记详情
└── UserCenter.vue # 个人中心
编辑器组件实现要点:
javascript复制// MarkdownEditor.vue
import { marked } from 'marked';
import DOMPurify from 'dompurify';
const renderMarkdown = (rawText) => {
return DOMPurify.sanitize(marked(rawText));
};
3. 数据库设计与优化
3.1 MySQL表结构设计
主要实体关系图:
sql复制CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`avatar` varchar(255) DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `note` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`title` varchar(100) NOT NULL,
`content` text NOT NULL,
`view_count` int DEFAULT '0',
`is_public` tinyint DEFAULT '1',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
FULLTEXT KEY `ft_content` (`title`,`content`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
索引优化建议:
- 用户表的username字段必须建立唯一索引
- 笔记表按user_id建立普通索引加速查询
- 对笔记标题和内容建立全文索引支持搜索
- 大文本字段单独存储时可考虑分表策略
3.2 性能调优实践
通过EXPLAIN分析发现的两个典型问题及解决方案:
问题1:笔记列表页关联查询用户信息时出现Using filesort
sql复制-- 原始查询
EXPLAIN SELECT n.*, u.username FROM note n JOIN user u ON n.user_id = u.id
ORDER BY n.created_at DESC LIMIT 10;
优化方案:
sql复制-- 优化后查询
ALTER TABLE note ADD INDEX idx_created (created_at);
EXPLAIN SELECT n.*, u.username FROM note n FORCE INDEX(idx_created)
JOIN user u ON n.user_id = u.id
ORDER BY n.created_at DESC LIMIT 10;
问题2:热门笔记统计查询响应慢
sql复制-- 原始方案:实时COUNT
SELECT COUNT(*) FROM note WHERE is_public = 1;
优化方案:
sql复制-- 使用Redis缓存计数
redis.incr("public_note_count");
// 配合定时任务同步到数据库
4. 典型功能实现细节
4.1 笔记发布流程
完整时序图:
- 前端调用/precheck接口验证标题是否重复
- 用户编辑Markdown内容并提交
- 前端将原始Markdown发送到/api/notes
- 后端进行XSS过滤后存储原始内容
- 同时生成净化后的HTML版本存入数据库
- 返回笔记ID给前端进行页面跳转
关键代码示例:
java复制// NoteController.java
@PostMapping("/notes")
public Result publishNote(@Valid @RequestBody NoteDTO dto) {
// 标题重复检查
if(noteService.existsByTitle(dto.getTitle())){
return Result.fail("标题已存在");
}
// 转换并净化Markdown
String cleanHtml = markdownService.convertToSafeHtml(dto.getContent());
// 保存笔记
Note note = new Note();
note.setTitle(dto.getTitle());
note.setRawContent(dto.getContent()); // 原始Markdown
note.setHtmlContent(cleanHtml); // 净化后的HTML
noteService.save(note);
return Result.success(note.getId());
}
4.2 权限控制方案
采用RBAC模型实现:
- 角色定义:游客、普通用户、管理员
- 权限粒度:笔记查看、笔记编辑、用户管理
Spring Security配置核心:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/notes/new").authenticated()
.antMatchers("/notes/**").permitAll()
.and()
.formLogin()
.loginPage("/login");
return http.build();
}
}
前端路由守卫示例:
javascript复制// router/index.js
router.beforeEach((to) => {
const authStore = useAuthStore();
if (to.meta.requiresAdmin && !authStore.isAdmin) {
return { path: '/403' };
}
});
5. 部署与运维实践
5.1 多环境配置方案
SpringBoot的profile配置:
yaml复制# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/notebook_dev
username: devuser
password: dev123
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/notebook
username: ${DB_USER}
password: ${DB_PASS}
前端环境变量处理:
javascript复制// .env.development
VITE_API_BASE=http://localhost:8080/api
// .env.production
VITE_API_BASE=/api
5.2 Docker容器化部署
后端Dockerfile示例:
dockerfile复制FROM openjdk:17-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
前端Dockerfile示例:
dockerfile复制FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
使用docker-compose编排:
yaml复制version: '3'
services:
backend:
build: ./backend
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
frontend:
build: ./frontend
ports:
- "80:80"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: notebook
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
6. 常见问题排查指南
6.1 跨域问题解决方案
典型错误:
code复制Access to XMLHttpRequest at 'http://localhost:8080/api/notes'
from origin 'http://localhost:3000' has been blocked by CORS policy
后端解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("*")
.allowCredentials(true);
}
}
前端代理方案(开发环境):
javascript复制// vite.config.js
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
6.2 性能优化记录
通过JProfiler分析发现的三个性能瓶颈及优化措施:
-
问题:频繁查询笔记作者信息
优化:在笔记列表接口中使用JOIN一次性获取 -
问题:Markdown转换消耗CPU
优化:引入缓存机制,对相同内容直接返回缓存 -
问题:N+1查询问题
原始代码:java复制List<Note> notes = noteMapper.selectList(); notes.forEach(n -> { User user = userMapper.selectById(n.getUserId()); // ... });优化方案:
java复制@Select("SELECT n.*, u.username FROM note n JOIN user u ON n.user_id = u.id") List<NoteVO> selectNotesWithUser();
7. 项目扩展方向
基于现有架构,可以考虑以下增强功能:
-
社交化功能扩展
- 添加关注/粉丝体系
- 实现笔记打赏机制
- 引入消息通知系统
-
技术深度优化
- 接入Elasticsearch实现全文检索
- 使用WebSocket实现实时评论
- 引入Redis缓存热点数据
-
运维监控增强
- 集成Prometheus监控指标
- 添加健康检查端点
- 实现日志集中收集
实际开发中发现,在笔记详情页添加阅读进度指示器能显著提升用户体验。这里分享我的实现方案:
vue复制<script setup>
const scrollHandler = () => {
const winScroll = document.documentElement.scrollTop;
const height = document.documentElement.scrollHeight -
document.documentElement.clientHeight;
const scrolled = (winScroll / height) * 100;
progress.value = Math.round(scrolled);
};
</script>
<template>
<div class="progress-bar" :style="{width: progress + '%'}"></div>
</template>
