Java全栈项目-在线实验报告系统开发实践
项目简介
在线实验报告系统是一个基于Spring Boot + Vue.js的全栈Web应用,旨在为高校师生提供一个便捷的实验报告提交和管理平台。本系统实现了实验报告在线编写、提交、批改等核心功能。
技术栈
后端技术
- Spring Boot 2.7.x
- Spring Security
- MyBatis Plus
- MySQL 8.0
- Redis
- JWT
前端技术
- Vue 3
- Element Plus
- Axios
- Vite
- Pinia
核心功能模块
-
用户管理
- 教师/学生角色划分
- JWT认证授权
- 个人信息管理
-
实验报告管理
- 报告模板创建
- 在线编辑保存
- 批量导入导出
- 版本控制
-
评阅系统
- 在线批改
- 评分规则配置
- 成绩统计分析
关键代码示例
后端JWT认证配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
前端报告编辑组件
<template>
<div class="report-editor">
<el-form :model="reportForm" label-width="80px">
<el-form-item label="实验标题">
<el-input v-model="reportForm.title"></el-input>
</el-form-item>
<el-form-item label="实验内容">
<rich-text-editor v-model="reportForm.content"/>
</el-form-item>
</el-form>
</div>
</template>
项目亮点
- 微服务架构:采用微服务设计,各模块独立部署,提高系统可扩展性
- 性能优化:
- Redis缓存热点数据
- 分布式文件存储
- 延迟加载策略
- 安全性:
- XSS防护
- SQL注入防护
- CSRF防护
- 用户体验:
- 响应式设计
- 断点续传
- 自动保存
开发难点及解决方案
-
并发编辑冲突
- 采用分布式锁
- 实现操作合并策略
-
大文件处理
- 分片上传
- 断点续传
- 异步处理
-
性能优化
- 引入Redis缓存
- 实现延迟加载
- SQL优化
项目收获
- 深入理解全栈开发流程
- 掌握微服务架构设计
- 提升系统优化能力
- 增强项目管理经验
后续优化方向
- 引入容器化部署
- 完善监控系统
- 优化用户体验
- 扩展功能模块
总结
通过本项目的开发,不仅实现了一个功能完善的在线实验报告系统,更重要的是积累了宝贵的全栈开发经验。项目中遇到的各种技术难点的解决过程。
Java全栈项目-用户管理模块详解
一、数据库设计
用户表设计
CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`real_name` varchar(50) COMMENT '真实姓名',
`email` varchar(100) COMMENT '邮箱',
`phone` varchar(20) COMMENT '手机号',
`avatar` varchar(200) COMMENT '头像URL',
`role_id` bigint NOT NULL COMMENT '角色ID',
`department_id` bigint COMMENT '院系ID',
`status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
角色表设计
CREATE TABLE `sys_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_code` varchar(50) NOT NULL COMMENT '角色编码',
`description` varchar(200) COMMENT '角色描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、核心代码实现
1. JWT工具类
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("username", userDetails.getUsername());
claims.put("roles", userDetails.getAuthorities());
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
2. 用户Service接口
public interface UserService {
UserDTO register(RegisterRequest request);
UserDTO updateUserInfo(UserUpdateRequest request);
UserDTO getUserInfo(Long userId);
Page<UserDTO> getUserList(UserQueryRequest request);
void updatePassword(PasswordUpdateRequest request);
void updateStatus(Long userId, Integer status);
}
3. 认证Controller
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthService authService;
@PostMapping("/login")
public ResponseResult<LoginVO> login(@RequestBody @Valid LoginRequest request) {
return ResponseResult.success(authService.login(request));
}
@PostMapping("/register")
public ResponseResult<UserDTO> register(@RequestBody @Valid RegisterRequest request) {
return ResponseResult.success(authService.register(request));
}
@PostMapping("/logout")
@RequiresAuthentication
public ResponseResult<Void> logout() {
authService.logout();
return ResponseResult.success();
}
}
4. 权限拦截器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtils jwtUtils;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (StringUtils.hasText(token) && jwtUtils.validateToken(token)) {
UserDetails userDetails = getUserDetails(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
三、前端实现
1. 用户状态管理(Pinia)
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: null,
roles: []
}),
actions: {
async login(loginForm: LoginForm) {
try {
const res = await loginApi(loginForm)
this.token = res.data.token
this.userInfo = res.data.userInfo
this.roles = res.data.roles
} catch (error) {
// 错误处理
}
},
logout() {
this.token = null
this.userInfo = null
this.roles = []
}
},
persist: {
enabled: true,
strategies: [
{
key: 'user',
storage: localStorage
}
]
}
})
2. 个人信息组件
<template>
<el-card class="user-info">
<template #header>
<div class="card-header">
<span>个人信息</span>
<el-button type="primary" @click="handleEdit">编辑</el-button>
</div>
</template>
<el-form :model="userForm" label-width="100px">
<el-form-item label="头像">
<el-upload
class="avatar-uploader"
:action="uploadUrl"
:show-file-list="false"
:on-success="handleAvatarSuccess">
<img v-if="userForm.avatar" :src="userForm.avatar" class="avatar">
<el-icon v-else><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="用户名">
<span>{{ userForm.username }}</span>
</el-form-item>
<el-form-item label="真实姓名">
<el-input v-model="userForm.realName" :disabled="!isEdit"/>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="userForm.email" :disabled="!isEdit"/>
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="userForm.phone" :disabled="!isEdit"/>
</el-form-item>
</el-form>
</el-card>
</template>
四、安全考虑
-
密码安全
- 使用BCrypt加密存储
- 密码强度校验
- 定期修改提醒
-
Token安全
- Token过期机制
- 刷新Token设计
- 单点登录控制
-
接口安全
- 接口权限控制
- 请求频率限制
- 参数校验
-
数据安全
- 敏感数据加密
- XSS防护
- SQL注入防护
五、优化建议
-
性能优化
- 用户信息缓存
- 接口响应压缩
- 延迟加载策略
-
功能扩展
- 第三方登录集成
- 多因素认证
- 用户行为分析
-
运维支持
- 用户操作日志
- 登录异常告警
- 性能监控
Java全栈项目-实验报告管理模块详解
一、数据库设计
1. 实验报告模板表
CREATE TABLE `report_template` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '模板标题',
`content` text COMMENT '模板内容',
`structure` json COMMENT '模板结构',
`creator_id` bigint NOT NULL COMMENT '创建者ID',
`course_id` bigint COMMENT '课程ID',
`status` tinyint DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 实验报告表
CREATE TABLE `report` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '报告标题',
`content` text COMMENT '报告内容',
`template_id` bigint COMMENT '模板ID',
`student_id` bigint NOT NULL COMMENT '学生ID',
`teacher_id` bigint COMMENT '教师ID',
`status` tinyint DEFAULT 0 COMMENT '状态:0-草稿,1-已提交,2-已批改',
`score` decimal(5,2) COMMENT '得分',
`comment` text COMMENT '评语',
`version` int DEFAULT 1 COMMENT '版本号',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 报告版本历史表
CREATE TABLE `report_version` (
`id` bigint NOT NULL AUTO_INCREMENT,
`report_id` bigint NOT NULL COMMENT '报告ID',
`content` text NOT NULL COMMENT '报告内容',
`version` int NOT NULL COMMENT '版本号',
`operator_id` bigint NOT NULL COMMENT '操作者ID',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_report_version` (`report_id`, `version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、核心代码实现
1. 报告模板Service
@Service
public class ReportTemplateServiceImpl implements ReportTemplateService {
@Autowired
private ReportTemplateMapper templateMapper;
@Override
@Transactional
public TemplateDTO createTemplate(TemplateCreateRequest request) {
ReportTemplate template = new ReportTemplate();
BeanUtils.copyProperties(request, template);
// 处理模板结构
template.setStructure(JsonUtils.toJson(request.getStructureList()));
templateMapper.insert(template);
return convertToDTO(template);
}
@Override
public Page<TemplateDTO> getTemplateList(TemplateQueryRequest request) {
LambdaQueryWrapper<ReportTemplate> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(request.getCourseId() != null, ReportTemplate::getCourseId, request.getCourseId())
.eq(ReportTemplate::getStatus, 1)
.orderByDesc(ReportTemplate::getCreateTime);
return templateMapper.selectPage(request.getPage(), wrapper)
.convert(this::convertToDTO);
}
}
2. 实验报告Service
@Service
public class ReportServiceImpl implements ReportService {
@Autowired
private ReportMapper reportMapper;
@Autowired
private ReportVersionMapper versionMapper;
@Override
@Transactional
public ReportDTO saveReport(ReportSaveRequest request) {
Report report = getById(request.getId());
if (report == null) {
report = new Report();
report.setStudentId(getCurrentUserId());
}
// 版本控制
if (!Objects.equals(report.getVersion(), request.getVersion())) {
throw new BusinessException("报告已被修改,请刷新后重试");
}
// 保存历史版本
saveVersion(report);
// 更新报告
BeanUtils.copyProperties(request, report);
report.setVersion(report.getVersion() + 1);
saveOrUpdate(report);
return convertToDTO(report);
}
@Override
public void exportReports(List<Long> reportIds, HttpServletResponse response) {
List<Report> reports = listByIds(reportIds);
ExcelWriter writer = ExcelUtil.getWriter();
// 设置Excel表头
writer.addHeaderAlias("title", "报告标题");
writer.addHeaderAlias("content", "报告内容");
writer.addHeaderAlias("score", "得分");
writer.write(reports, true);
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=reports.xls");
writer.flush(response.getOutputStream());
}
}
3. 版本控制Service
@Service
public class ReportVersionServiceImpl implements ReportVersionService {
@Autowired
private ReportVersionMapper versionMapper;
@Override
public void saveVersion(Report report) {
ReportVersion version = new ReportVersion();
version.setReportId(report.getId());
version.setContent(report.getContent());
version.setVersion(report.getVersion());
version.setOperatorId(getCurrentUserId());
versionMapper.insert(version);
}
@Override
public List<VersionDTO> getVersionHistory(Long reportId) {
LambdaQueryWrapper<ReportVersion> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ReportVersion::getReportId, reportId)
.orderByDesc(ReportVersion::getVersion);
return versionMapper.selectList(wrapper)
.stream()
.map(this::convertToDTO)
.collect(Collectors.toList());
}
}
三、前端实现
1. 富文本编辑器组件
<template>
<div class="editor-container">
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
mode="default"
style="border-bottom: 1px solid #ccc"
/>
<Editor
:defaultConfig="editorConfig"
mode="default"
v-model="editorContent"
@onCreated="handleCreated"
@onChange="handleChange"
/>
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef, onBeforeUnmount } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
const editorRef = shallowRef()
const editorContent = ref('')
// 编辑器配置
const editorConfig = {
placeholder: '请输入内容...',
autoFocus: false,
MENU_CONF: {
uploadImage: {
server: '/api/file/upload',
fieldName: 'file'
}
}
}
// 自动保存
let autoSaveTimer: number
const autoSave = () => {
autoSaveTimer = window.setInterval(() => {
if (editorContent.value) {
saveReport()
}
}, 60000) // 每分钟自动保存
}
onBeforeUnmount(() => {
clearInterval(autoSaveTimer)
})
</script>
2. 报告模板管理
<template>
<div class="template-manager">
<el-table :data="templateList" border>
<el-table-column prop="title" label="模板标题"/>
<el-table-column prop="courseName" label="所属课程"/>
<el-table-column prop="createTime" label="创建时间"/>
<el-table-column label="操作">
<template #default="scope">
<el-button type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 模板编辑对话框 -->
<el-dialog v-model="dialogVisible" title="编辑模板">
<template-form
:template-data="currentTemplate"
@submit="handleSubmit"
/>
</el-dialog>
</div>
</template>
3. 版本历史查看
<template>
<div class="version-history">
<el-timeline>
<el-timeline-item
v-for="version in versionList"
:key="version.id"
:timestamp="version.createTime"
placement="top"
>
<el-card>
<h4>版本 {{ version.version }}</h4>
<p>操作人:{{ version.operatorName }}</p>
<el-button type="text" @click="handlePreview(version)">
查看内容
</el-button>
<el-button type="text" @click="handleRestore(version)">
恢复此版本
</el-button>
</el-card>
</el-timeline-item>
</el-timeline>
<!-- 版本预览对话框 -->
<el-dialog v-model="previewVisible" title="版本预览">
<div v-html="previewContent"></div>
</el-dialog>
</div>
</template>
四、功能优化
1. 性能优化
- 使用Redis缓存热门模板
- 报告内容分片加载
- 图片懒加载
- 编辑器防抖处理
@Service
public class ReportCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String TEMPLATE_CACHE_KEY = "report:template:";
public void cacheTemplate(ReportTemplate template) {
String key = TEMPLATE_CACHE_KEY + template.getId();
redisTemplate.opsForValue().set(key, template, 1, TimeUnit.DAYS);
}
public ReportTemplate getTemplateFromCache(Long templateId) {
String key = TEMPLATE_CACHE_KEY + templateId;
return (ReportTemplate) redisTemplate.opsForValue().get(key);
}
}
2. 导入导出优化
- 异步处理大批量导出
- 分批处理导入数据
- 进度反馈
@Service
public class AsyncExportService {
@Async
public void exportReportsAsync(List<Long> reportIds, String userId) {
try {
// 创建导出任务
ExportTask task = createExportTask(userId);
// 分批处理
List<List<Long>> batches = Lists.partition(reportIds, 100);
int total = reportIds.size();
int processed = 0;
for (List<Long> batch : batches) {
exportBatch(batch);
processed += batch.size();
updateProgress(task.getId(), (processed * 100) / total);
}
// 生成下载链接
String downloadUrl = generateDownloadUrl(task.getId());
notifyUser(userId, downloadUrl);
} catch (Exception e) {
log.error("导出失败", e);
notifyError(userId);
}
}
}
3. 版本控制优化
- 差异化存储
- 定期清理历史版本
- 关键节点版本保护
@Service
public class VersionDiffService {
public String generateDiff(String oldContent, String newContent) {
// 使用diff-match-patch算法计算差异
DiffMatchPatch dmp = new DiffMatchPatch();
LinkedList<Diff> diffs = dmp.diff_main(oldContent, newContent);
dmp.diff_cleanupSemantic(diffs);
return JsonUtils.toJson(diffs);
}
public String restoreVersion(String baseContent, List<String> diffs) {
// 根据差异记录恢复指定版本内容
DiffMatchPatch dmp = new DiffMatchPatch();
String content = baseContent;
for (String diff : diffs) {
LinkedList<Diff> patches = JsonUtils.parseObject(diff, LinkedList.class);
content = dmp.patch_apply(patches, content);
}
return content;
}
}
五、安全考虑
-
内容安全
- XSS过滤
- 敏感词过滤
- 图片安全检测
-
权限控制
- 报告访问权限
- 模板使用权限
- 版本操作权限
-
数据安全
- 定期备份
- 加密存储
- 防止信息泄露
六、扩展建议
-
智能辅助
- AI辅助写作
- 智能纠错
- 相似度检测
-
协同功能
- 多人协作编辑
- 实时评论
- 修改建议
-
统计分析
- 报告质量分析
- 完成情况统计
- 使用情况追踪