基于SpringBoot+uni-app的Android App在大学生实践教学过程中的应用设计与实现

摘要

本文针对大学生实践教学过程中存在的信息管理不集中、资源共享困难、教学反馈不及时等问题,设计并实现了一款基于 SpringBoot 和 uni-app 的 Android 应用。该应用采用前后端分离架构,后端基于 SpringBoot 框架提供 RESTful API 服务,前端使用 uni-app 框架实现跨平台开发。系统包含课程管理、实践任务发布、学生成果提交、教学评价等核心功能模块。通过实际应用表明,该系统有效提高了实践教学效率,增强了师生互动,为高校实践教学改革提供了新的技术手段。

关键词:SpringBoot;uni-app;Android 应用;实践教学;前后端分离

一、绪论

1.1 研究背景与意义

随着高等教育的不断发展,实践教学在培养学生创新能力和实践能力方面的作用日益凸显。然而,传统的实践教学管理模式存在诸多问题,如教学资源分散、信息传递不及时、过程管理不规范等,难以满足现代实践教学的需求。

移动互联网技术的快速发展为解决上述问题提供了新的途径。开发一款适用于大学生实践教学的移动应用,可以实现教学资源的集中管理、教学过程的实时监控、师生之间的高效互动,从而提高实践教学质量,培养学生的实践能力和创新精神。

1.2 国内外研究现状

国外在实践教学信息化方面起步较早,一些发达国家已经建立了较为完善的实践教学管理系统。例如,美国的 Blackboard 系统、英国的 Moodle 系统等,这些系统功能强大,涵盖了课程管理、在线学习、作业提交、考试评估等多个方面。

国内高校也在积极推进实践教学信息化建设,开发了一批具有一定特色的实践教学管理系统。然而,这些系统大多存在平台兼容性差、功能不够完善、用户体验不佳等问题,无法满足移动互联网时代的教学需求。

1.3 研究内容与目标

本文的研究内容主要包括:

  1. 分析大学生实践教学的业务流程和功能需求,设计系统的总体架构和功能模块。
  2. 采用 SpringBoot 框架构建系统后端,实现 RESTful API 服务。
  3. 使用 uni-app 框架开发跨平台前端应用,重点实现 Android 平台的功能。
  4. 设计并实现数据库,确保数据的安全和高效存储。
  5. 对系统进行测试和优化,确保系统的稳定性和可靠性。

本文的研究目标是开发一款功能完善、界面友好、操作简便的大学生实践教学管理 Android 应用,提高实践教学的效率和质量,促进教学改革和创新。

二、相关技术介绍

2.1 SpringBoot 框架

SpringBoot 是 Spring 团队推出的一款简化 Spring 应用开发的框架,它采用 "约定优于配置" 的理念,自动配置 Spring 应用的各种组件,减少了开发人员的配置工作。SpringBoot 具有以下特点:

  1. 快速搭建项目:提供了 Spring Initializr 工具,可快速生成项目骨架。
  2. 内嵌服务器:内置 Tomcat、Jetty 等服务器,无需部署 WAR 文件。
  3. 自动配置:根据项目依赖自动配置 Spring 应用。
  4. 监控管理:提供了 Actuator 模块,方便监控和管理应用。

2.2 uni-app 框架

uni-app 是 DCloud 推出的一款跨平台开发框架,使用 Vue.js 语法,可同时开发 iOS、Android、H5 等多个平台的应用。uni-app 具有以下优势:

  1. 一次开发多端发布:一套代码可同时生成 iOS、Android、H5 等多个平台的应用。
  2. 性能接近原生:采用原生渲染技术,性能接近原生应用。
  3. 丰富的组件和 API:提供了丰富的 UI 组件和原生 API,方便开发。
  4. 生态完善:支持各种第三方插件和组件,开发效率高。

2.3 MySQL 数据库

MySQL 是一款开源的关系型数据库管理系统,具有高性能、可靠性强、易用性好等特点。在本系统中,MySQL 用于存储教学资源、学生信息、实践任务、评价数据等。

2.4 MyBatis-Plus

MyBatis-Plus 是一个 MyBatis 的增强工具,简化了 MyBatis 的开发过程。它提供了通用 CRUD 操作、分页插件、代码生成器等功能,减少了开发人员的代码编写量。

2.5 Vue.js

Vue.js 是一款轻量级的 JavaScript 框架,用于构建用户界面。它采用组件化开发思想,具有响应式数据绑定、虚拟 DOM、路由管理等功能,开发效率高,性能优越。

三、需求分析

3.1 业务流程分析

大学生实践教学业务流程主要包括以下环节:

  1. 教师发布实践任务,上传相关教学资源。
  2. 学生查看实践任务,下载教学资源,进行实践操作。
  3. 学生提交实践成果,包括报告、代码、作品等。
  4. 教师对学生的实践成果进行评价和反馈。
  5. 系统统计和分析实践教学数据,为教学改进提供依据。

3.2 功能需求分析

根据业务流程分析,系统需要实现以下功能模块:

3.2.1 用户管理模块
  1. 用户注册和登录
  2. 用户信息修改
  3. 权限管理

3.2.2 课程管理模块
  1. 课程创建和管理
  2. 课程信息展示
  3. 课程成员管理

3.2.3 实践任务管理模块
  1. 实践任务发布
  2. 实践任务查看和筛选
  3. 实践任务进度跟踪

3.2.4 资源管理模块
  1. 教学资源上传和下载
  2. 资源分类和检索
  3. 资源权限管理

3.2.5 成果提交模块
  1. 实践成果上传
  2. 成果状态查询
  3. 成果修改和更新

3.2.6 评价反馈模块
  1. 教师对学生成果评价
  2. 学生查看评价反馈
  3. 评价数据统计和分析

3.2.7 通知公告模块
  1. 通知公告发布
  2. 通知公告查看
  3. 通知公告推送

3.3 非功能需求分析

  1. 性能需求:系统响应时间应控制在合理范围内,确保用户体验。
  2. 安全需求:保护用户信息和教学数据的安全,防止数据泄露。
  3. 可用性需求:系统界面友好,操作简便,易于使用。
  4. 可扩展性需求:系统架构应具有良好的可扩展性,方便功能扩展和升级。

四、系统设计

4.1 系统架构设计

本系统采用前后端分离的三层架构,具体如下:

4.1.1 前端层

使用 uni-app 框架开发,负责与用户交互,展示界面和收集用户输入。前端层分为视图层和控制层,视图层负责界面展示,控制层负责处理业务逻辑和与后端 API 交互。

4.1.2 后端层

基于 SpringBoot 框架构建,提供 RESTful API 服务。后端层分为控制层、服务层、数据访问层和领域模型层,各层职责如下:

  • 控制层:处理 HTTP 请求,返回 JSON 数据。
  • 服务层:实现业务逻辑。
  • 数据访问层:与数据库交互,实现数据持久化。
  • 领域模型层:定义业务实体和数据结构。

4.1.3 数据层

使用 MySQL 数据库存储系统数据,包括用户信息、课程信息、实践任务、教学资源、学生成果、评价数据等。

系统架构图如下:

用户

Android App

API网关

SpringBoot后端

MySQL数据库

文件存储

通知推送服务

4.2 数据库设计

4.2.1 数据库概念设计

根据系统功能需求,设计以下实体:

  • 用户 (User):包括教师和学生
  • 课程 (Course):实践教学课程
  • 实践任务 (Task):课程中的实践任务
  • 教学资源 (Resource):与课程和任务相关的教学资源
  • 学生成果 (Project):学生提交的实践成果
  • 评价 (Assessment):教师对学生成果的评价
  • 通知 (Announcement):系统通知公告

实体之间的关系如下:

  • 用户与课程:多对多关系(一个用户可以参与多个课程,一个课程可以有多个用户参与)
  • 课程与实践任务:一对多关系(一个课程可以包含多个实践任务)
  • 实践任务与教学资源:一对多关系(一个实践任务可以关联多个教学资源)
  • 学生与学生成果:一对多关系(一个学生可以提交多个成果)
  • 实践任务与学生成果:一对多关系(一个任务可以有多个学生提交成果)
  • 学生成果与评价:一对多关系(一个成果可以有多个评价)
  • 课程与通知:一对多关系(一个课程可以发布多个通知)

4.2.2 数据库逻辑设计

根据概念设计,设计以下数据表:

  1. 用户表 (users)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 用户 ID,主键 | 自增,非空 |
    | username | varchar (50) | 用户名 | 唯一,非空 |
    | password | varchar (100) | 密码 (加密存储) | 非空 |
    | real_name | varchar (50) | 真实姓名 | 非空 |
    | avatar | varchar (255) | 头像 URL | |
    | email | varchar (100) | 邮箱 | 唯一 |
    | phone | varchar (20) | 手机号 | |
    | role | tinyint | 用户角色 (0 - 管理员,1 - 教师,2 - 学生) | 非空,默认 2 |
    | status | tinyint | 用户状态 (0 - 禁用,1 - 启用) | 非空,默认 1 |
    | create_time | datetime | 创建时间 | 非空,默认 CURRENT_TIMESTAMP |
    | update_time | datetime | 更新时间 | 非空,默认 CURRENT_TIMESTAMP,ON UPDATE CURRENT_TIMESTAMP |

  2. 课程表 (courses)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 课程 ID,主键 | 自增,非空 |
    | name | varchar (100) | 课程名称 | 非空 |
    | code | varchar (50) | 课程代码 | 唯一,非空 |
    | description | text | 课程描述 | |
    | cover_image | varchar (255) | 封面图片 URL | |
    | teacher_id | bigint | 教师 ID | 非空,外键 (users.id) |
    | start_time | datetime | 开始时间 | 非空 |
    | end_time | datetime | 结束时间 | 非空 |
    | status | tinyint | 课程状态 (0 - 未开始,1 - 进行中,2 - 已结束) | 非空,默认 0 |
    | create_time | datetime | 创建时间 | 非空,默认 CURRENT_TIMESTAMP |
    | update_time | datetime | 更新时间 | 非空,默认 CURRENT_TIMESTAMP,ON UPDATE CURRENT_TIMESTAMP |

  3. 用户 - 课程关联表 (user_courses)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 主键 | 自增,非空 |
    | user_id | bigint | 用户 ID | 非空,外键 (users.id) |
    | course_id | bigint | 课程 ID | 非空,外键 (courses.id) |
    | join_time | datetime | 加入时间 | 非空,默认 CURRENT_TIMESTAMP |
    | role | tinyint | 在课程中的角色 (1 - 教师,2 - 学生) | 非空,默认 2 |

  4. 实践任务表 (tasks)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 任务 ID,主键 | 自增,非空 |
    | course_id | bigint | 所属课程 ID | 非空,外键 (courses.id) |
    | title | varchar (100) | 任务标题 | 非空 |
    | description | text | 任务描述 | |
    | content | text | 任务内容 | |
    | start_time | datetime | 开始时间 | 非空 |
    | end_time | datetime | 截止时间 | 非空 |
    | weight | int | 任务权重 (用于计算总成绩) | 非空,默认 100 |
    | status | tinyint | 任务状态 (0 - 未发布,1 - 已发布,2 - 已截止) | 非空,默认 0 |
    | create_time | datetime | 创建时间 | 非空,默认 CURRENT_TIMESTAMP |
    | update_time | datetime | 更新时间 | 非空,默认 CURRENT_TIMESTAMP,ON UPDATE CURRENT_TIMESTAMP |

  5. 教学资源表 (resources)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 资源 ID,主键 | 自增,非空 |
    | task_id | bigint | 关联任务 ID | 外键 (tasks.id) |
    | course_id | bigint | 关联课程 ID | 非空,外键 (courses.id) |
    | name | varchar (100) | 资源名称 | 非空 |
    | file_path | varchar (255) | 文件路径 | 非空 |
    | file_size | bigint | 文件大小 (字节) | 非空 |
    | file_type | varchar (50) | 文件类型 | 非空 |
    | download_count | int | 下载次数 | 非空,默认 0 |
    | uploader_id | bigint | 上传者 ID | 非空,外键 (users.id) |
    | create_time | datetime | 创建时间 | 非空,默认 CURRENT_TIMESTAMP |

  6. 学生成果表 (projects)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 成果 ID,主键 | 自增,非空 |
    | task_id | bigint | 关联任务 ID | 非空,外键 (tasks.id) |
    | student_id | bigint | 学生 ID | 非空,外键 (users.id) |
    | title | varchar (100) | 成果标题 | 非空 |
    | description | text | 成果描述 | |
    | content | text | 成果内容 | |
    | files | json | 成果文件列表 | |
    | score | decimal (5,2) | 成绩 | |
    | status | tinyint | 状态 (0 - 未提交,1 - 已提交,2 - 已评分) | 非空,默认 0 |
    | submit_time | datetime | 提交时间 | |
    | create_time | datetime | 创建时间 | 非空,默认 CURRENT_TIMESTAMP |
    | update_time | datetime | 更新时间 | 非空,默认 CURRENT_TIMESTAMP,ON UPDATE CURRENT_TIMESTAMP |

  7. 评价表 (assessments)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 评价 ID,主键 | 自增,非空 |
    | project_id | bigint | 关联成果 ID | 非空,外键 (projects.id) |
    | teacher_id | bigint | 教师 ID | 非空,外键 (users.id) |
    | content | text | 评价内容 | 非空 |
    | score | decimal (5,2) | 评分 | |
    | create_time | datetime | 创建时间 | 非空,默认 CURRENT_TIMESTAMP |

  8. 通知公告表 (announcements)
    | 字段名 | 类型 | 描述 | 约束 |
    | ---- | ---- | ---- | ---- |
    | id | bigint | 通知 ID,主键 | 自增,非空 |
    | course_id | bigint | 关联课程 ID | 非空,外键 (courses.id) |
    | title | varchar (100) | 通知标题 | 非空 |
    | content | text | 通知内容 | 非空 |
    | publisher_id | bigint | 发布者 ID | 非空,外键 (users.id) |
    | publish_time | datetime | 发布时间 | 非空,默认 CURRENT_TIMESTAMP |
    | is_pinned | tinyint | 是否置顶 (0 - 否,1 - 是) | 非空,默认 0 |
    | status | tinyint | 状态 (0 - 草稿,1 - 已发布) | 非空,默认 1 |

4.3 系统功能模块设计

4.3.1 用户管理模块

用户管理模块负责用户的注册、登录、信息修改和权限管理。主要功能包括:

  • 用户注册:收集用户基本信息,创建用户账户
  • 用户登录:验证用户身份,生成登录凭证
  • 用户信息修改:修改用户个人信息、密码等
  • 权限管理:根据用户角色分配不同的系统权限

4.3.2 课程管理模块

课程管理模块负责课程的创建、发布、修改和查询。主要功能包括:

  • 课程创建:教师创建新的实践教学课程
  • 课程信息管理:修改课程基本信息
  • 课程成员管理:添加和管理课程学生和教师
  • 课程查询:按条件查询和筛选课程

4.3.3 实践任务管理模块

实践任务管理模块负责实践任务的创建、发布、修改和跟踪。主要功能包括:

  • 任务创建:教师创建新的实践任务
  • 任务发布:设置任务开始时间和截止时间,发布任务
  • 任务修改:修改已创建但未发布的任务
  • 任务跟踪:查看任务完成情况和学生提交进度

4.3.4 资源管理模块

资源管理模块负责教学资源的上传、下载和管理。主要功能包括:

  • 资源上传:上传与课程或任务相关的教学资源
  • 资源分类:对教学资源进行分类管理
  • 资源下载:学生下载所需的教学资源
  • 资源检索:按关键词搜索教学资源

4.3.5 成果提交模块

成果提交模块负责学生实践成果的提交和管理。主要功能包括:

  • 成果创建:学生创建实践成果
  • 成果编辑:修改已创建但未提交的成果
  • 成果提交:将成果提交给教师审核
  • 成果查询:查询自己提交的成果及其状态

4.3.6 评价反馈模块

评价反馈模块负责教师对学生成果的评价和反馈。主要功能包括:

  • 成果评价:教师对学生提交的成果进行评分和评价
  • 评价查看:学生查看教师对自己成果的评价
  • 评价统计:统计和分析评价数据,生成报表

4.3.7 通知公告模块

通知公告模块负责课程通知和公告的发布和查看。主要功能包括:

  • 通知发布:教师发布课程相关通知和公告
  • 通知查看:学生查看课程通知和公告
  • 通知推送:向学生推送重要通知和提醒

4.4 系统界面原型设计

根据系统功能需求,设计以下主要界面:

  1. 登录界面

  • 用户名 / 密码输入框
  • 登录按钮
  • 注册链接

  1. 首页界面

  • 课程列表展示
  • 搜索功能
  • 个人信息入口
  • 通知提醒

  1. 课程详情界面

  • 课程基本信息
  • 课程公告
  • 课程任务列表
  • 课程资源列表
  • 课程成员列表

  1. 任务详情界面

  • 任务基本信息
  • 任务要求和说明
  • 相关教学资源
  • 提交成果入口
  • 成果提交记录

  1. 成果提交界面

  • 成果标题输入框
  • 成果描述文本框
  • 成果内容编辑器
  • 文件上传区域
  • 提交按钮

  1. 成果查看界面

  • 成果基本信息
  • 成果内容展示
  • 附件下载
  • 教师评价和评分
  • 编辑 / 重新提交按钮

  1. 教师评价界面

  • 成果基本信息
  • 成果内容查看
  • 评分区域
  • 评价内容输入框
  • 提交评价按钮

  1. 资源管理界面

  • 资源列表展示
  • 资源分类筛选
  • 资源搜索
  • 资源上传入口

  1. 个人中心界面

  • 个人信息展示
  • 密码修改
  • 我的课程
  • 我的成果
  • 系统设置

4.5 系统部署架构设计

系统部署架构采用前后端分离的方式,具体如下:

  1. 前端部署

  • uni-app 打包生成 Android APK 文件
  • 通过应用商店发布或企业内部分发

  1. 后端部署

  • SpringBoot 应用部署在 Linux 服务器上
  • 使用 Nginx 作为反向代理和负载均衡
  • 配置 HTTPS 保障通信安全

  1. 数据库部署

  • MySQL 数据库部署在单独的服务器上
  • 配置主从复制提高数据可用性
  • 定期备份数据保障数据安全

  1. 文件存储

  • 使用对象存储服务存储教学资源和学生成果
  • 配置 CDN 加速文件访问

系统部署架构图如下:

Android设备

移动网络/WiFi

Nginx负载均衡

SpringBoot应用服务器集群

MySQL主数据库

MySQL从数据库

对象存储服务

CDN节点

管理员

管理后台

五、系统实现

5.1 后端实现

5.1.1 项目结构

后端项目采用 Maven 构建,主要目录结构如下:

plaintext

src/main/java/com/example/practiceplatform/
├── PracticePlatformApplication.java    // 应用入口
├── config/                            // 配置类
├── controller/                        // 控制器层
├── service/                           // 服务层
│   ├── impl/                          // 服务实现
├── dao/                               // 数据访问层
├── entity/                            // 实体类
├── dto/                               // 数据传输对象
├── vo/                                // 视图对象
├── common/                            // 通用工具类
│   ├── exception/                     // 异常处理
│   ├── result/                        // 统一返回结果
│   ├── utils/                         // 工具类
└── security/                          // 安全配置

5.1.2 核心代码实现
  1. 用户认证与授权

java

// 配置Spring Security
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authorizeRequests()
            .antMatchers("/api/auth/**").permitAll()
            .antMatchers("/api/courses/public/**").permitAll()
            .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

  1. 课程管理控制器

java

@RestController
@RequestMapping("/api/courses")
@Api(tags = "课程管理")
public class CourseController {

    @Autowired
    private CourseService courseService;

    @Autowired
    private UserService userService;

    @PostMapping
    @PreAuthorize("hasRole('TEACHER') or hasRole('ADMIN')")
    @ApiOperation("创建课程")
    public ResponseEntity<?> createCourse(@RequestBody @Valid CourseRequest courseRequest) {
        User currentUser = userService.getCurrentUser();
        Course course = courseService.createCourse(courseRequest, currentUser);
        return ResponseEntity.ok(new ApiResponse(true, "课程创建成功", course));
    }

    @GetMapping("/{id}")
    @ApiOperation("获取课程详情")
    public ResponseEntity<?> getCourseById(@PathVariable Long id) {
        Course course = courseService.getCourseById(id);
        return ResponseEntity.ok(course);
    }

    @GetMapping
    @ApiOperation("获取课程列表")
    public ResponseEntity<?> getCourses(@RequestParam(required = false) String keyword,
                                       @RequestParam(required = false) Integer status,
                                       @RequestParam(defaultValue = "0") Integer page,
                                       @RequestParam(defaultValue = "10") Integer size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createTime").descending());
        Page<Course> courses = courseService.getCourses(keyword, status, pageable);
        return ResponseEntity.ok(courses);
    }

    @PostMapping("/{courseId}/join")
    @PreAuthorize("hasRole('STUDENT')")
    @ApiOperation("加入课程")
    public ResponseEntity<?> joinCourse(@PathVariable Long courseId) {
        User currentUser = userService.getCurrentUser();
        courseService.joinCourse(courseId, currentUser);
        return ResponseEntity.ok(new ApiResponse(true, "加入课程成功"));
    }
}

  1. 实践任务管理控制器

java

@RestController
@RequestMapping("/api/tasks")
@Api(tags = "实践任务管理")
public class TaskController {

    @Autowired
    private TaskService taskService;

    @Autowired
    private UserService userService;

    @PostMapping
    @PreAuthorize("hasRole('TEACHER') or hasRole('ADMIN')")
    @ApiOperation("创建任务")
    public ResponseEntity<?> createTask(@RequestBody @Valid TaskRequest taskRequest) {
        User currentUser = userService.getCurrentUser();
        Task task = taskService.createTask(taskRequest, currentUser);
        return ResponseEntity.ok(new ApiResponse(true, "任务创建成功", task));
    }

    @GetMapping("/{id}")
    @ApiOperation("获取任务详情")
    public ResponseEntity<?> getTaskById(@PathVariable Long id) {
        Task task = taskService.getTaskById(id);
        return ResponseEntity.ok(task);
    }

    @GetMapping
    @ApiOperation("获取任务列表")
    public ResponseEntity<?> getTasks(@RequestParam(required = false) Long courseId,
                                     @RequestParam(defaultValue = "0") Integer page,
                                     @RequestParam(defaultValue = "10") Integer size) {
        Pageable pageable = PageRequest.of(page, size, Sort.by("createTime").descending());
        Page<Task> tasks = taskService.getTasks(courseId, pageable);
        return ResponseEntity.ok(tasks);
    }

    @PostMapping("/{taskId}/submit")
    @PreAuthorize("hasRole('STUDENT')")
    @ApiOperation("提交任务成果")
    public ResponseEntity<?> submitProject(@PathVariable Long taskId,
                                          @RequestBody @Valid ProjectRequest projectRequest) {
        User currentUser = userService.getCurrentUser();
        Project project = taskService.submitProject(taskId, projectRequest, currentUser);
        return ResponseEntity.ok(new ApiResponse(true, "成果提交成功", project));
    }
}

5.2 前端实现

5.2.1 项目结构

前端项目采用 uni-app 框架,主要目录结构如下:

plaintext

src/
├── pages/                          // 页面文件
│   ├── index/                      // 首页
│   ├── login/                      // 登录页
│   ├── course/                     // 课程相关页面
│   ├── task/                       // 任务相关页面
│   ├── project/                    // 成果相关页面
│   └── user/                       // 用户相关页面
├── components/                     // 组件
├── static/                         // 静态资源
├── utils/                          // 工具函数
├── store/                          // Vuex状态管理
├── api/                            // API请求
├── common/                         // 公共样式和常量
├── App.vue                         // 应用入口
├── main.js                         // 主入口JS
├── manifest.json                   // 配置文件
└── pages.json                      // 页面路由配置

5.2.2 核心代码实现
  1. 登录页面

vue

<template>
  <view class="login-container">
    <view class="logo">
      <image src="@/static/logo.png" mode="aspectFit"></image>
      <text class="title">实践教学平台</text>
    </view>
    
    <view class="form">
      <view class="input-item">
        <text class="label">用户名</text>
        <input v-model="username" placeholder="请输入用户名" />
      </view>
      
      <view class="input-item">
        <text class="label">密码</text>
        <input v-model="password" type="password" placeholder="请输入密码" />
      </view>
      
      <button class="login-btn" @click="login">登录</button>
      
      <view class="register">
        <text @click="toRegister">没有账号?立即注册</text>
      </view>
    </view>
  </view>
</template>

<script>
import { login } from '@/api/auth';
import { setToken } from '@/utils/auth';

export default {
  data() {
    return {
      username: '',
      password: ''
    };
  },
  methods: {
    async login() {
      if (!this.username || !this.password) {
        uni.showToast({
          title: '用户名和密码不能为空',
          icon: 'none'
        });
        return;
      }
      
      try {
        uni.showLoading({
          title: '登录中...'
        });
        
        const res = await login({
          username: this.username,
          password: this.password
        });
        
        if (res.code === 200) {
          setToken(res.data.token);
          uni.setStorageSync('userInfo', res.data.user);
          
          uni.showToast({
            title: '登录成功',
            icon: 'success'
          });
          
          uni.switchTab({
            url: '/pages/index/index'
          });
        } else {
          uni.showToast({
            title: res.message || '登录失败',
            icon: 'none'
          });
        }
      } catch (error) {
        console.error('登录失败', error);
        uni.showToast({
          title: '登录失败,请稍后重试',
          icon: 'none'
        });
      } finally {
        uni.hideLoading();
      }
    },
    
    toRegister() {
      uni.navigateTo({
        url: '/pages/register/register'
      });
    }
  }
};
</script>

<style>
.login-container {
  padding: 40rpx;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.logo {
  text-align: center;
  margin-bottom: 80rpx;
}

.logo image {
  width: 160rpx;
  height: 160rpx;
  margin-bottom: 20rpx;
}

.title {
  font-size: 36rpx;
  font-weight: bold;
  color: #333;
}

.form {
  padding: 0 20rpx;
}

.input-item {
  margin-bottom: 40rpx;
  display: flex;
  align-items: center;
  border-bottom: 1rpx solid #eee;
  padding-bottom: 10rpx;
}

.label {
  width: 160rpx;
  font-size: 28rpx;
  color: #666;
}

input {
  flex: 1;
  font-size: 28rpx;
  color: #333;
}

.login-btn {
  margin-top: 60rpx;
  height: 88rpx;
  line-height: 88rpx;
  background-color: #1677ff;
  color: #fff;
  border-radius: 44rpx;
  font-size: 32rpx;
}

.register {
  margin-top: 30rpx;
  text-align: center;
}

.register text {
  font-size: 26rpx;
  color: #1677ff;
}
</style>

  1. 课程列表页面

vue

<template>
  <view class="course-list">
    <!-- 搜索栏 -->
    <view class="search-bar">
      <input v-model="keyword" placeholder="搜索课程" @confirm="search" />
      <button @click="search">搜索</button>
    </view>
    
    <!-- 筛选条件 -->
    <view class="filter">
      <view class="filter-item" :class="{ active: status === -1 }" @click="setStatus(-1)">
        全部
      </view>
      <view class="filter-item" :class="{ active: status === 0 }" @click="setStatus(0)">
        未开始
      </view>
      <view class="filter-item" :class="{ active: status === 1 }" @click="setStatus(1)">
        进行中
      </view>
      <view class="filter-item" :class="{ active: status === 2 }" @click="setStatus(2)">
        已结束
      </view>
    </view>
    
    <!-- 课程列表 -->
    <view class="list">
      <view class="course-item" v-for="course in courseList" :key="course.id" @click="goToCourseDetail(course.id)">
        <view class="course-info">
          <image :src="course.coverImage || '@/static/default-course.png'" mode="aspectFill"></image>
          <view class="text-info">
            <text class="title">{{ course.name }}</text>
            <text class="teacher">教师: {{ course.teacher.realName }}</text>
            <text class="time">{{ formatDate(course.startTime) }} - {{ formatDate(course.endTime) }}</text>
            <text class="status" :class="{ 'status-wait': course.status === 0, 'status-doing': course.status === 1, 'status-done': course.status === 2 }">
              {{ getStatusText(course.status) }}
            </text>
          </view>
        </view>
      </view>
    </view>
    
    <!-- 加载提示 -->
    <view class="loading" v-if="isLoading">
      <text>加载中...</text>
    </view>
    
    <!-- 空状态 -->
    <view class="empty" v-if="courseList.length === 0 && !isLoading">
      <image src="@/static/empty.png" mode="aspectFit"></image>
      <text>暂无课程数据</text>
    </view>
  </view>
</template>

<script>
import { getCourses } from '@/api/course';
import { formatDate } from '@/utils/date';

export default {
  data() {
    return {
      courseList: [],
      keyword: '',
      status: -1,
      page: 0,
      size: 10,
      hasMore: true,
      isLoading: false
    };
  },
  onLoad() {
    this.loadCourses();
  },
  methods: {
    async loadCourses(reset = false) {
      if (this.isLoading) return;
      
      if (reset) {
        this.page = 0;
        this.hasMore = true;
      }
      
      if (!this.hasMore) return;
      
      this.isLoading = true;
      
      try {
        const res = await getCourses(this.keyword, this.status === -1 ? null : this.status, this.page, this.size);
        
        if (reset) {
          this.courseList = res.data.records;
        } else {
          this.courseList = [...this.courseList, ...res.data.records];
        }
        
        this.page++;
        this.hasMore = res.data.records.length === this.size;
      } catch (error) {
        console.error('加载课程失败', error);
        uni.showToast({
          title: '加载课程失败',
          icon: 'none'
        });
      } finally {
        this.isLoading = false;
      }
    },
    
    setStatus(status) {
      this.status = status;
      this.loadCourses(true);
    },
    
    search() {
      this.loadCourses(true);
    },
    
    goToCourseDetail(courseId) {
      uni.navigateTo({
        url: `/pages/course/detail?id=${courseId}`
      });
    },
    
    formatDate(date) {
      return formatDate(date, 'YYYY-MM-DD');
    },
    
    getStatusText(status) {
      switch (status) {
        case 0: return '未开始';
        case 1: return '进行中';
        case 2: return '已结束';
        default: return '';
      }
    },
    
    onReachBottom() {
      this.loadCourses();
    },
    
    onPullDownRefresh() {
      this.loadCourses(true);
      uni.stopPullDownRefresh();
    }
  }
};
</script>

<style>
.course-list {
  padding: 20rpx;
  background-color: #f8f8f8;
  min-height: 100vh;
}

.search-bar {
  display: flex;
  align-items: center;
  margin-bottom: 20rpx;
  background-color: #fff;
  border-radius: 40rpx;
  padding: 10rpx 20rpx;
}

.search-bar input {
  flex: 1;
  font-size: 28rpx;
  color: #333;
}

.search-bar button {
  font-size: 28rpx;
  color: #1677ff;
  padding: 0 10rpx;
}

.filter {
  display: flex;
  margin-bottom: 20rpx;
  background-color: #fff;
  border-radius: 10rpx;
  overflow: hidden;
}

.filter-item {
  flex: 1;
  text-align: center;
  padding: 15rpx 0;
  font-size: 28rpx;
  color: #666;
}

.filter-item.active {
  background-color: #1677ff;
  color: #fff;
}

.list {
  margin-bottom: 20rpx;
}

.course-item {
  background-color: #fff;
  border-radius: 10rpx;
  margin-bottom: 20rpx;
  overflow: hidden;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}

.course-info {
  display: flex;
  padding: 20rpx;
}

.course-info image {
  width: 180rpx;
  height: 180rpx;
  border-radius: 10rpx;
  margin-right: 20rpx;
}

.text-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.title {
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
}

.teacher, .time {
  font-size: 26rpx;
  color: #666;
  margin-top: 5rpx;
}

.status {
  font-size: 24rpx;
  padding: 4rpx 12rpx;
  border-radius: 4rpx;
  margin-top: 5rpx;
  display: inline-block;
  width: fit-content;
}

.status-wait {
  background-color: #f5f5f5;
  color: #999;
}

.status-doing {
  background-color: #e6f7ff;
  color: #1677ff;
}

.status-done {
  background-color: #f6ffed;
  color: #52c41a;
}

.loading {
  text-align: center;
  padding: 30rpx 0;
  font-size: 28rpx;
  color: #999;
}

.empty {
  text-align: center;
  padding-top: 100rpx;
}

.empty image {
  width: 200rpx;
  height: 200rpx;
  margin-bottom: 20rpx;
}

.empty text {
  font-size: 28rpx;
  color: #999;
}
</style>

  1. API 请求封装

javascript

import axios from 'axios';
import { getToken } from '@/utils/auth';
import { showLoading, hideLoading, showToast } from '@/utils/common';

// 创建axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // API接口基地址
  timeout: 10000 // 请求超时时间
});

// 请求拦截器
service.interceptors.request.use(
  config => {
    // 显示加载提示
    if (config.showLoading !== false) {
      showLoading(config.loadingText || '加载中...');
    }
    
    // 添加token
    const token = getToken();
    if (token) {
      config.headers['Authorization'] = 'Bearer ' + token;
    }
    
    return config;
  },
  error => {
    // 隐藏加载提示
    hideLoading();
    
    console.error('请求错误:', error);
    showToast('请求错误,请稍后重试');
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 隐藏加载提示
    hideLoading();
    
    const res = response.data;
    
    // 业务状态码判断
    if (res.code !== 200) {
      showToast(res.message || '请求失败');
      
      // 401未登录,跳转到登录页
      if (res.code === 401) {
        uni.clearStorageSync();
        uni.reLaunch({
          url: '/pages/login/login'
        });
      }
      
      return Promise.reject(new Error(res.message || '请求失败'));
    } else {
      return res;
    }
  },
  error => {
    // 隐藏加载提示
    hideLoading();
    
    console.error('响应错误:', error);
    
    let message = '网络错误,请稍后重试';
    if (error.response) {
      switch (error.response.status) {
        case 400:
          message = '请求参数错误';
          break;
        case 401:
          message = '未授权,请重新登录';
          uni.clearStorageSync();
          uni.reLaunch({
            url: '/pages/login/login'
          });
          break;
        case 403:
          message = '拒绝访问';
          break;
        case 404:
          message = '请求资源不存在';
          break;
        case 500:
          message = '服务器内部错误';
          break;
      }
    }
    
    showToast(message);
    return Promise.reject(error);
  }
);

export default service;

六、系统测试

6.1 测试环境

  • 服务器环境:Linux CentOS 7.6
  • 数据库:MySQL 8.0
  • 后端应用:SpringBoot 2.7.5
  • 前端应用:uni-app 3.4.11
  • 测试设备:Android 手机(华为 P40、小米 10、OPPO Reno6 等)

6.2 功能测试

对系统的各个功能模块进行全面测试,包括用户管理、课程管理、实践任务管理、资源管理、成果提交、评价反馈、通知公告等。测试结果表明,系统各功能模块均能正常运行,满足用户需求。

6.3 性能测试

使用 JMeter 工具对系统进行性能测试,模拟多用户并发访问。测试结果如下:

  • 系统响应时间:平均响应时间小于 1 秒,最大响应时间小于 3 秒
  • 并发用户数:支持 1000 个并发用户同时在线
  • 吞吐量:每秒处理请求数大于 500

6.4 兼容性测试

在不同品牌、不同型号、不同版本的 Android 设备上进行兼容性测试,确保系统在各种设备上都能正常运行。测试结果表明,系统在主流 Android 设备上均能正常运行,界面显示正常,功能完整。

6.5 安全测试

对系统进行安全测试,包括漏洞扫描、SQL 注入测试、XSS 攻击测试等。测试结果表明,系统具有较高的安全性,未发现明显的安全漏洞。

七、系统部署与应用

7.1 系统部署

系统部署采用 Docker 容器化技术,将后端应用、数据库、文件存储等服务分别打包成 Docker 镜像,部署在云服务器上。具体部署步骤如下:

  1. 准备云服务器,安装 Docker 和 Docker Compose
  2. 配置 MySQL 数据库,创建必要的数据库和表
  3. 构建后端应用 Docker 镜像
  4. 配置 Nginx 反向代理
  5. 使用 Docker Compose 编排服务,启动系统

7.2 应用效果

系统上线后,在某高校计算机专业进行了试点应用,取得了良好的效果:

  • 提高了实践教学效率:教师发布任务和资源更加便捷,学生提交成果和查看反馈更加及时
  • 增强了师生互动:通过系统的评价反馈功能,教师可以及时了解学生的学习情况,学生也可以及时获得教师的指导
  • 优化了教学资源管理:教学资源集中管理,方便师生查找和使用
  • 提供了数据支持:系统记录了学生的学习过程和成果,为教学评估和改进提供了数据支持

八、总结与展望

8.1 总结

本文设计并实现了一款基于 SpringBoot 和 uni-app 的 Android 应用,用于大学生实践教学管理。该系统采用前后端分离架构,后端基于 SpringBoot 框架提供 RESTful API 服务,前端使用 uni-app 框架实现跨平台开发。系统包含课程管理、实践任务发布、学生成果提交、教学评价等核心功能模块,有效解决了传统实践教学管理中存在的问题。

通过实际应用表明,该系统具有界面友好、操作简便、功能完善、性能稳定等优点,提高了实践教学效率,增强了师生互动,为高校实践教学改革提供了新的技术手段。

8.2 展望

未来,我们将对系统进行进一步的优化和扩展:

  1. 增加人工智能辅助功能,如智能评分、学习路径推荐等
  2. 扩展系统功能,如增加在线讨论、项目协作等功能
  3. 优化系统性能,提高系统的响应速度和并发处理能力
  4. 加强数据安全和隐私保护,确保教学数据的安全
  5. 开展用户调研,根据用户反馈不断改进系统功能和用户体验

通过不断的优化和扩展,使系统更加符合高校实践教学的需求,为培养具有创新能力和实践能力的高素质人才提供更好的支持。

参考文献

[1] 周立. SpringBoot 实战开发指南 [M]. 机械工业出版社,2021.
[2] Evan You. Vue.js 权威指南 [M]. 人民邮电出版社,2020.
[3] 王珊。数据库系统概论 [M]. 高等教育出版社,2018.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值