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

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

核心功能模块

  1. 用户管理

    • 教师/学生角色划分
    • JWT认证授权
    • 个人信息管理
  2. 实验报告管理

    • 报告模板创建
    • 在线编辑保存
    • 批量导入导出
    • 版本控制
  3. 评阅系统

    • 在线批改
    • 评分规则配置
    • 成绩统计分析

关键代码示例

后端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>

项目亮点

  1. 微服务架构:采用微服务设计,各模块独立部署,提高系统可扩展性
  2. 性能优化
    • Redis缓存热点数据
    • 分布式文件存储
    • 延迟加载策略
  3. 安全性
    • XSS防护
    • SQL注入防护
    • CSRF防护
  4. 用户体验
    • 响应式设计
    • 断点续传
    • 自动保存

开发难点及解决方案

  1. 并发编辑冲突

    • 采用分布式锁
    • 实现操作合并策略
  2. 大文件处理

    • 分片上传
    • 断点续传
    • 异步处理
  3. 性能优化

    • 引入Redis缓存
    • 实现延迟加载
    • SQL优化

项目收获

  1. 深入理解全栈开发流程
  2. 掌握微服务架构设计
  3. 提升系统优化能力
  4. 增强项目管理经验

后续优化方向

  1. 引入容器化部署
  2. 完善监控系统
  3. 优化用户体验
  4. 扩展功能模块

总结

通过本项目的开发,不仅实现了一个功能完善的在线实验报告系统,更重要的是积累了宝贵的全栈开发经验。项目中遇到的各种技术难点的解决过程。

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>

四、安全考虑

  1. 密码安全

    • 使用BCrypt加密存储
    • 密码强度校验
    • 定期修改提醒
  2. Token安全

    • Token过期机制
    • 刷新Token设计
    • 单点登录控制
  3. 接口安全

    • 接口权限控制
    • 请求频率限制
    • 参数校验
  4. 数据安全

    • 敏感数据加密
    • XSS防护
    • SQL注入防护

五、优化建议

  1. 性能优化

    • 用户信息缓存
    • 接口响应压缩
    • 延迟加载策略
  2. 功能扩展

    • 第三方登录集成
    • 多因素认证
    • 用户行为分析
  3. 运维支持

    • 用户操作日志
    • 登录异常告警
    • 性能监控

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;
    }
}

五、安全考虑

  1. 内容安全

    • XSS过滤
    • 敏感词过滤
    • 图片安全检测
  2. 权限控制

    • 报告访问权限
    • 模板使用权限
    • 版本操作权限
  3. 数据安全

    • 定期备份
    • 加密存储
    • 防止信息泄露

六、扩展建议

  1. 智能辅助

    • AI辅助写作
    • 智能纠错
    • 相似度检测
  2. 协同功能

    • 多人协作编辑
    • 实时评论
    • 修改建议
  3. 统计分析

    • 报告质量分析
    • 完成情况统计
    • 使用情况追踪

http://www.kler.cn/a/535495.html

相关文章:

  • 旋转变压器工作及解调原理
  • [高等数学]曲率
  • 19C RAC在vmware虚拟机环境下的安装
  • 【算法篇】贪心算法
  • 设计模式——策略模式
  • Windows 中学习Docker环境准备3、在Ubuntu中安装Docker
  • Git仓库托管基本使用_01
  • MybatisPlus较全常用复杂查询引例(limit、orderby、groupby、having、like...)
  • C++:内存泄漏
  • MyBatis一条语句(PostgresSql)实现批量新增更新操作ON CONFLICT
  • 2024最新版Node.js详细安装教程(含npm配置淘宝最新镜像地址)
  • CTF SQL注入学习笔记
  • 第七天 开始学习ArkTS基础,理解声明式UI编程思想
  • vue3-响应式 shallowRef
  • 网络安全 | 零信任架构:重构安全防线的未来趋势
  • 【2025最新计算机毕业设计】基于SSM健身俱乐部管理系统【提供源码+答辩PPT+文档+项目部署】
  • 【Vitest】单元测试
  • 【STM32】蓝牙模块数据包解析
  • 【华为OD-E卷 - 108 最大矩阵和 100分(python、java、c++、js、c)】
  • crewai框架第三方API使用官方RAG工具(pdf,csv,json)
  • 高斯溅射和GIS融合之路- 将splat文件切片成3dtiles
  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-tasks.py
  • E4982A,keysight是德科技台式LCR表
  • 通义灵码在跨领域应用拓展之物联网篇
  • OSPF基础(1):工作过程、状态机、更新
  • Web 音视频(四)在浏览器中处理音频