从零构建校园APP:Android+Spring Boot+MySQL全栈开发实战
2026/9/11 15:14:56 网站建设 项目流程

简介:本资源是一套面向Android开发初学者与高校计算机专业学生的校园服务类APP完整项目,解决传统Web校园服务在移动端访问不便的问题,适用于课程设计、毕业设计及移动应用实训场景。压缩包共1174个文件,含119个Java核心业务代码、191个编译后Class文件、61个XML布局与配置文件、87个Jar依赖库、216个PNG界面资源图及202个GIF动效素材,另有SQL建表脚本、APK安装包、Tomcat部署配置及论文文档等,整体31.32MB。已有2406人学习下载,资源附带完整操作录像,直观展示APP功能演示、MySQL数据库连接、Android Studio调试过程及前后端交互逻辑;论文部分涵盖需求分析、系统设计、关键技术实现与测试方案,代码结构清晰,模块划分明确(含用户管理、课表查询、通知公告、校园地图等),便于理解MVC架构与Android+Java+MySQL+B/S混合开发全流程。

1. 项目概述:一个校园APP的诞生记

最近几年,我身边不少朋友和学弟学妹都在问,想做一个能拿得出手的、功能完整的移动端项目,到底该从何下手?特别是对于计算机相关专业的学生来说,一个结合了移动开发、后端服务和数据库的“校园APP”,几乎是毕业设计或课程大作业的“标配”。今天,我就以“基于安卓Android的校园APP系统设计”这个经典命题为例,把我自己从零到一搭建一个完整可运行系统的全过程、踩过的坑、以及那些教科书里不会写的实操细节,毫无保留地分享出来。这个项目绝不仅仅是一个“源码+论文”的压缩包,它背后涉及的需求分析、技术选型、架构设计、编码实现和部署测试,是一套完整的工程化思维训练。无论你是想完成课业、备战面试,还是单纯想提升自己的全栈开发能力,这篇长文都能给你提供一条清晰的路径和一堆可以直接“抄作业”的代码与配置。

简单来说,我们要做的是一个运行在安卓手机上的APP,它需要连接一个自己搭建的后台服务器,服务器则使用MySQL数据库来存储和管理所有数据,比如用户信息、课程表、校园新闻、失物招领信息等等。最终,你会得到一个安装包(APK)、一套可运行的后台代码、一份数据库建表脚本,以及所有支撑你完成论文的设计文档和思路。整个过程,我会重点解释“为什么”要这么选、这么设计,而不仅仅是“怎么做”。

2. 核心需求与功能模块拆解

在动手写一行代码之前,我们必须想清楚这个APP到底要干什么。校园APP的范畴很大,我们不能贪多求全,而是要抓住几个核心、高频、能形成闭环的场景。基于我过往的经验和学生群体的实际反馈,我建议聚焦于以下四个核心模块,它们技术代表性足,业务逻辑清晰,且能很好地串联起安卓端、服务器和数据库。

2.1 用户中心:注册、登录与个人信息管理

这是所有系统的基石。我们需要实现手机号或学号注册、密码登录(后续可扩展短信验证码登录)。用户信息至少包括:学号、姓名、密码(加密存储)、头像、所属院系等。这个模块会涉及到安卓端的表单验证、网络请求(登录接口调用)、数据本地缓存(如使用SharedPreferences保存登录状态),以及服务器端的密码加密(如使用BCrypt)、会话管理(如使用Token)和数据库用户表的CRUD操作。

注意:密码绝对不能在网络中明文传输,也不能在数据库中明文存储。服务器端接收到密码后,应立即进行哈希加盐处理。这是一个安全底线,在论文中也需要着重强调。

2.2 校园资讯:新闻与通知的发布与浏览

模仿一个简单的校园新闻客户端。管理员(如学生会或教务处)可以在后台发布带标题、正文和图片的新闻或通知。普通学生用户可以在APP首页以列表形式浏览,点击进入详情页。这个模块会实践安卓的RecyclerView展示列表、图片加载框架(如Glide)的使用、以及服务器端富文本内容(或简单图文)的存储与接口设计。

2.3 课程表查询:学生核心功能

这是最能体现“校园”属性的功能。学生登录后,可以查看自己当前学期的课程表,以周视图的形式展示。数据来源可以是手动录入,更理想的是通过模拟接口从学校教务系统同步(毕业设计中常模拟此过程)。这需要设计课程表数据表,包含课程名、教师、上课地点、周次、星期几、节次等信息。安卓端需要实现一个自定义的课程表视图,涉及到自定义View或复杂的GridView布局。

2.4 失物招领:校园社区互动

创建一个轻量级的校园社区功能。用户可以发布失物信息或拾物信息,包含物品描述、图片、丢失/拾取地点、时间、联系方式等。其他用户可以浏览列表,并通过APP内联系或直接拨打电话联系发布者。这个模块综合了信息发布、列表展示、图片上传(到服务器或使用第三方图床)、简单的状态管理(如“已找到”、“待认领”)等功能。

确定了这四个模块,我们的技术方案选型就有了明确的靶心。接下来,我们就要为这个靶心选择合适的“弓箭”。

3. 技术栈选型与架构设计

技术选型没有绝对的好坏,只有是否适合当前场景。对于这样一个学生项目或个人练手项目,我们的核心原则是:主流、稳定、学习资料丰富、快速上手。基于这个原则,我为你梳理出一套经过验证的技术组合。

3.1 安卓客户端技术选型

  • 开发语言与IDE:毫无疑问,选择Kotlin+Android Studio。Kotlin已是安卓开发的官方首选语言,比Java更简洁安全,空指针问题处理得更好。Android Studio是谷歌官方的IDE,插件生态和调试工具最完善。
  • 网络请求:使用Retrofit2+OkHttp3+Gson这个黄金组合。Retrofit负责将HTTP API接口转化为Java/Kotlin接口,用起来像调用本地方法一样简单。OkHttp是强大的底层HTTP客户端。Gson用于JSON数据的序列化与反序列化。
  • 图片加载Glide。它链式调用API非常优雅,内存缓存和磁盘缓存策略高效,能自动处理图片生命周期,是当前最主流的图片加载库。
  • 本地数据存储:轻量级数据(如登录Token、用户设置)用SharedPreferences;需要存储复杂对象或列表时,可以考虑Room(SQLite的ORM封装),但鉴于我们项目数据主要来自网络,SharedPreferences在初期足够使用。
  • 架构模式:强烈推荐采用MVVM(Model-View-ViewModel)。它通过LiveDataFlow实现数据驱动UI,能有效避免Activity/Fragment过于臃肿,并且更利于单元测试。这是目前安卓开发的主流架构。

3.2 服务器端技术选型

  • 后端框架:选择Spring Boot。它是Java领域事实上的微服务标准框架,能极大简化Spring应用的初始搭建和开发过程。“约定大于配置”的理念让我们能快速构建出RESTful API。它的生态极其丰富,学习资源遍地都是。
  • 数据库ORM:使用MyBatis-Plus。它是对MyBatis的增强,在保留MyBatis灵活性的基础上,提供了强大的CRUD通用接口、分页插件、代码生成器等,能极大减少编写简单SQL的工作量,非常适合快速开发。
  • 数据库:如题所述,MySQL 8.0。它是世界上最流行的开源关系型数据库之一,安装、学习、运维资料都非常多。对于校园APP的数据规模,性能完全足够。

3.3 整体架构设计

一个清晰的架构图能让你的论文和代码都更有条理。我们的系统整体上是一个典型的前后端分离架构:

  1. 安卓客户端(前端):负责UI渲染、用户交互,通过HTTP/HTTPS协议调用后端API。
  2. Spring Boot后端服务:提供RESTful API,处理业务逻辑,充当安卓客户端和数据库之间的桥梁。
  3. MySQL数据库:持久化存储所有业务数据。

它们之间的数据流是这样的:用户在APP上操作 -> 安卓端通过Retrofit发送网络请求 -> 请求到达Spring Boot后端对应的Controller -> Controller调用Service处理业务逻辑 -> Service通过MyBatis-Plus与MySQL数据库交互 -> 数据原路返回,最终在APP界面上更新。

这个架构的优点是职责分离,安卓端和后端可以独立开发和部署,也便于后续扩展(例如,未来可以很容易地开发一个iOS版或小程序版,它们共用同一套后端API)。

4. 数据库设计与核心表结构

数据库设计是系统的“地基”,设计得好,后续开发事半功倍。根据我们的四个核心模块,我设计了以下五张核心表。这里我会给出关键的字段和说明,你可以直接用它来创建你的数据库。

表1:用户表 (sys_user)这是系统的核心表,存储所有用户信息。

CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `student_id` varchar(20) NOT NULL COMMENT '学号,唯一', `username` varchar(50) DEFAULT NULL COMMENT '姓名', `password` varchar(255) NOT NULL COMMENT '加密后的密码', `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `college` varchar(100) DEFAULT NULL COMMENT '学院', `major` varchar(100) DEFAULT NULL COMMENT '专业', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_student_id` (`student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

实操心得password字段长度一定要设得足够长(如255),因为BCrypt等加密算法生成的哈希串很长。使用utf8mb4字符集,可以支持存储Emoji表情。create_timeupdate_time是审计字段,建议每张表都加上,便于排查问题。

表2:新闻资讯表 (news_info)存储校园新闻和通知。

CREATE TABLE `news_info` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(200) NOT NULL COMMENT '标题', `content` text COMMENT '正文内容(可存HTML或Markdown)', `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图URL', `publisher_id` bigint DEFAULT NULL COMMENT '发布者ID(关联用户表)', `publisher_name` varchar(50) DEFAULT NULL COMMENT '发布者姓名(冗余存储,避免连表查询)', `view_count` int DEFAULT '0' COMMENT '浏览次数', `is_top` tinyint(1) DEFAULT '0' COMMENT '是否置顶,0否1是', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='新闻资讯表';

注意content字段类型为TEXT,可以存储大段文字。publisher_name是典型的“空间换时间”设计,在列表展示新闻时,我们通常需要显示发布者名字,如果每次都去连sys_user表查询,会影响性能。直接冗余存储在这里,查询效率更高。

表3:课程表 (course_schedule)存储学生的课程信息。

CREATE TABLE `course_schedule` ( `id` bigint NOT NULL AUTO_INCREMENT, `student_id` bigint NOT NULL COMMENT '学生ID(关联用户表)', `course_name` varchar(100) NOT NULL COMMENT '课程名称', `teacher` varchar(50) DEFAULT NULL COMMENT '任课教师', `location` varchar(100) DEFAULT NULL COMMENT '上课地点', `day_of_week` tinyint NOT NULL COMMENT '星期几(1-7)', `start_section` tinyint NOT NULL COMMENT '开始节次', `end_section` tinyint NOT NULL COMMENT '结束节次', `start_week` tinyint DEFAULT NULL COMMENT '起始周', `end_week` tinyint DEFAULT NULL COMMENT '结束周', `single_double` tinyint DEFAULT '0' COMMENT '单双周,0全部,1单周,2双周', `semester` varchar(20) DEFAULT NULL COMMENT '学期,如2023-2024-1', PRIMARY KEY (`id`), KEY `idx_student_semester` (`student_id`,`semester`) COMMENT '根据学生和学期查询课程表的索引' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表';

关键点解析:课程表设计是难点。这里采用“离散化”存储,即把一学期内每周重复的课程,拆解成一条条基于“星期几”和“节次”的记录。day_of_week表示周一至周日。start_sectionend_section表示从第几节上到第几节。start_weekend_weeksingle_double字段共同定义了这门课在哪几周有。这种设计虽然会使得同一门课可能产生多条记录(如果每周上多次),但查询和前端渲染时逻辑非常清晰直接。

表4:失物招领物品表 (lost_found_item)存储失物和招领信息。

CREATE TABLE `lost_found_item` ( `id` bigint NOT NULL AUTO_INCREMENT, `type` tinyint NOT NULL COMMENT '类型,1失物,2招领', `title` varchar(100) NOT NULL COMMENT '物品标题', `description` text COMMENT '详细描述', `image_urls` varchar(2000) DEFAULT NULL COMMENT '图片URL,多个用逗号分隔', `location` varchar(200) DEFAULT NULL COMMENT '丢失/拾取地点', `event_time` datetime DEFAULT NULL COMMENT '丢失/拾取时间', `contact` varchar(100) NOT NULL COMMENT '联系方式', `publisher_id` bigint NOT NULL COMMENT '发布者ID', `status` tinyint DEFAULT '0' COMMENT '状态,0待处理,1已找到/已归还,2已关闭', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_type_status` (`type`,`status`) COMMENT '按类型和状态查询的索引' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='失物招领表';

实操心得image_urls字段用于存储多张图片。这里采用简单粗暴的用逗号分隔多个URL字符串的方式。对于毕业设计级别的项目,这完全可行。如果追求更规范,可以拆分成单独的一张图片表。status字段用于管理物品状态,是一个简单的状态机。

有了清晰的表结构,我们就可以开始搭建开发环境,并让前后端跑起来了。

5. 后端Spring Boot服务搭建与核心API实现

后端是整个系统的大脑,我们先把它搭建起来。这里我假设你已经有基本的Java和Maven基础。

5.1 项目初始化与依赖配置

使用 Spring Initializr 网站或IDE内的Spring Initializr插件,快速生成项目。关键依赖选择:

  • Spring Web:用于构建Web API。
  • MyBatis Framework:数据库ORM框架。
  • MySQL Driver:MySQL数据库驱动。
  • Lombok:简化Java Bean代码的神器(强烈推荐)。

生成项目后,在pom.xml中手动加入MyBatis-Plus的依赖:

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>最新版本</version> <!-- 例如 3.5.3 --> </dependency>

5.2 核心配置详解

application.ymlapplication.properties中配置数据库连接和MyBatis-Plus:

server: port: 8080 # 服务端口 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_app_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: your_password mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,调试用 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名(如果要用) logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值 mapper-locations: classpath:mapper/*.xml # XML映射文件位置

注意serverTimezone=Asia/Shanghai非常重要,能避免数据库时区问题导致的日期时间错误。log-impl配置在开发阶段非常有用,可以实时看到MyBatis-Plus执行的SQL语句,方便调试。

5.3 用户登录API实现示例

我们以实现用户登录为例,展示一个完整的API从Controller到Service到Mapper的流程。首先,创建用户实体类User.java,使用Lombok简化代码:

@Data @TableName("sys_user") // 指定对应表名 public class User { @TableId(type = IdType.AUTO) // 主键自增 private Long id; private String studentId; private String username; private String password; // 存储的是加密后的密码 private String avatar; // ... 其他字段及getter/setter已被@Data注解包含 }

创建Mapper接口UserMapper.java,继承MyBatis-Plus的BaseMapper,即可获得通用的CRUD方法:

@Mapper public interface UserMapper extends BaseMapper<User> { // 可以在此定义自定义的SQL查询方法 // 例如:根据学号查询用户 User selectByStudentId(@Param("studentId") String studentId); }

对应的XML映射文件UserMapper.xml(如果使用注解方式可省略,但复杂SQL建议用XML):

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.yourpackage.mapper.UserMapper"> <select id="selectByStudentId" resultType="com.yourpackage.entity.User"> SELECT * FROM sys_user WHERE student_id = #{studentId} </select> </mapper>

创建Service层UserService.java

@Service public class UserService { @Autowired private UserMapper userMapper; public User login(String studentId, String rawPassword) { // 1. 根据学号查询用户 User user = userMapper.selectByStudentId(studentId); if (user == null) { throw new RuntimeException("用户不存在"); } // 2. 验证密码 (使用BCryptPasswordEncoder) BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); if (!encoder.matches(rawPassword, user.getPassword())) { throw new RuntimeException("密码错误"); } // 3. 登录成功,生成Token(这里简化,实际可用JWT) // String token = JwtUtil.generateToken(user.getId(), user.getStudentId()); // 返回用户信息(注意剔除密码字段) user.setPassword(null); return user; } }

创建Controller层UserController.java,定义RESTful API:

@RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; @PostMapping("/login") public Result login(@RequestBody LoginForm form) { // LoginForm是一个简单的DTO,包含studentId和password字段 try { User user = userService.login(form.getStudentId(), form.getPassword()); // 构造返回结果,包含用户信息和Token Map<String, Object> data = new HashMap<>(); data.put("user", user); data.put("token", "模拟的Token字符串"); // 实际应替换为JWT Token return Result.success("登录成功", data); } catch (RuntimeException e) { return Result.error(e.getMessage()); } } }

这里的Result是一个自定义的统一响应体类,包含codemsgdata字段,便于前端处理。

至此,一个完整的登录API就完成了。你可以使用Postman等工具测试POST http://localhost:8080/api/user/login这个接口。其他模块的API,如新闻查询、课程表获取、失物发布,其代码结构与此类似,都是“Controller接收请求 -> Service处理业务 -> Mapper操作数据库”的模式。

6. 安卓客户端开发与关键功能实现

后端API准备好后,我们开始在安卓端“搭积木”。我们采用MVVM架构,使用Jetpack组件,让代码更清晰、更健壮。

6.1 项目初始化与网络层配置

首先,在项目的build.gradle文件中添加必要的依赖:

dependencies { // ... 其他基础依赖 // 网络请求 implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:okhttp:4.11.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0' // 网络日志拦截器 // 图片加载 implementation 'com.github.bumptech.glide:glide:4.15.1' kapt 'com.github.bumptech.glide:compiler:4.15.1' // Kotlin注解处理器 // MVVM & Jetpack implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' implementation 'androidx.activity:activity-ktx:1.7.0' // 协程 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' }

然后,创建Retrofit的单例实例RetrofitClient.kt

object RetrofitClient { private const val BASE_URL = "http://你的电脑IP:8080/" // 注意:不能用localhost,要用本机在局域网中的IP private val okHttpClient = OkHttpClient.Builder() .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) // 打印请求日志 .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build() private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() fun <T> createService(serviceClass: Class<T>): T = retrofit.create(serviceClass) }

踩坑提醒:在安卓模拟器或真机上访问本地后端服务时,BASE_URL不能是http://localhost:8080http://127.0.0.1:8080,因为这在手机环境里指向的是手机自己。你必须将localhost替换为你开发电脑在局域网中的实际IP地址(如http://192.168.1.100:8080)。同时,确保电脑防火墙允许8080端口的入站连接。

6.2 实现用户登录功能(MVVM模式)

我们以登录功能为例,展示MVVM在安卓端的完整实践。

1. 定义数据模型和API接口创建User.kt数据类,对应后端的用户实体。 创建ApiService.kt接口,定义网络请求:

interface ApiService { @POST("api/user/login") suspend fun login(@Body loginForm: LoginForm): Result<LoginResponse> // Result是统一响应体,LoginResponse包含user和token }

2. 创建Repository(数据仓库)Repository负责协调多个数据源(网络、本地数据库等)。

class UserRepository(private val apiService: ApiService) { suspend fun login(studentId: String, password: String): Result<LoginResponse> { return try { val response = apiService.login(LoginForm(studentId, password)) if (response.isSuccess()) { // 假设Result有isSuccess方法 // 登录成功,可以在这里将token保存到SharedPreferences saveTokenToLocal(response.data?.token) } response } catch (e: Exception) { Result.error("网络请求失败: ${e.message}") } } private fun saveTokenToLocal(token: String?) { // 使用SharedPreferences保存token } }

3. 创建ViewModelViewModel持有UI相关的数据,并暴露给UI层观察。

class LoginViewModel(private val repository: UserRepository) : ViewModel() { // 使用LiveData来观察登录状态和结果 private val _loginResult = MutableLiveData<Result<LoginResponse>>() val loginResult: LiveData<Result<LoginResponse>> = _loginResult fun login(studentId: String, password: String) { viewModelScope.launch { _loginResult.value = Result.loading() // 发送加载状态 val result = repository.login(studentId, password) _loginResult.value = result // 发送最终结果 } } }

4. 在Activity/Fragment中观察ViewModel

class LoginActivity : AppCompatActivity() { private lateinit var viewModel: LoginViewModel private lateinit var binding: ActivityLoginBinding // ViewBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityLoginBinding.inflate(layoutInflater) setContentView(binding.root) // 初始化ViewModel val factory = ViewModelFactory(UserRepository(RetrofitClient.createService(ApiService::class.java))) viewModel = ViewModelProvider(this, factory)[LoginViewModel::class.java] binding.btnLogin.setOnClickListener { val studentId = binding.etStudentId.text.toString() val password = binding.etPassword.text.toString() if (validateInput(studentId, password)) { viewModel.login(studentId, password) } } // 观察登录结果 viewModel.loginResult.observe(this) { result -> when (result.status) { Status.LOADING -> showLoading() Status.SUCCESS -> { hideLoading() result.data?.let { // 登录成功,跳转到主页面 startActivity(Intent(this, MainActivity::class.java)) finish() } } Status.ERROR -> { hideLoading() Toast.makeText(this, result.message ?: "登录失败", Toast.LENGTH_SHORT).show() } } } } }

通过以上步骤,我们就完成了一个结构清晰、职责分离的登录功能。其他功能模块,如新闻列表、课程表展示,都可以遵循同样的MVVM模式进行开发。

6.3 课程表UI实现技巧

课程表UI是前端的一个小挑战。一个常见的实现方式是使用一个自定义的GridView或者直接使用Canvas绘制。这里提供一个使用RecyclerView+多类型ViewHolder的简化思路:

  1. 数据准备:将从后端获取的一周课程列表,转换成一个7(天)x N(最大节次,如12)的二维数组或列表。没有课的位置为null
  2. 布局设计:最左侧一列显示节次(1,2,3...),最上方一行显示星期(一,二,三...),中间区域是课程格子。
  3. Adapter实现RecyclerView.Adapter需要处理多种类型的ViewHolder:表头(星期)、表侧(节次)、课程格子。根据position判断当前项应该渲染成哪种类型。
  4. 课程格子视图:每个课程格子是一个CardView,里面显示课程名、地点。需要根据课程的start_sectionend_section来计算这个格子应该占据几行(span)。这可以通过GridLayoutManagersetSpanSizeLookup方法来实现。

这个实现有一定复杂度,但能很好地锻炼你对RecyclerView和自定义布局的理解。如果时间紧张,也可以考虑使用一些开源的课程表控件。

7. 项目集成、测试与常见问题排查

当后端API和安卓端主要页面都开发完毕后,就到了激动人心的联调测试阶段。这个阶段往往是问题集中爆发的时期。

7.1 后端服务部署与启动

  1. 确保你的MySQL服务已启动,并已创建好数据库campus_app_db和所有表结构。
  2. 在IDE中直接运行Spring Boot项目的main方法,或在项目根目录下执行mvn spring-boot:run命令。
  3. 看到控制台输出类似Started Application in 5.123 seconds (JVM running for 5.789)的日志,说明启动成功。
  4. 打开浏览器,访问http://localhost:8080(如果配置了简单的接口,如/hello),看是否有响应。更推荐使用Postman直接测试你的API接口,如POST http://localhost:8080/api/user/login

7.2 安卓真机调试与网络连接

  1. IP地址问题:这是最常见的坑。确保安卓代码中的BASE_URLIP地址正确,且手机和电脑在同一个局域网(连接同一个Wi-Fi)。
  2. 网络权限:在AndroidManifest.xml中务必添加网络权限:
    <uses-permission android:name="android.permission.INTERNET" />
  3. HTTP明文流量限制(Android 9+):如果你的后端使用HTTP(非HTTPS),在Android 9及以上版本,默认禁止明文流量。解决方法有两种:
    • (推荐)在res/xml下创建network_security_config.xml文件
      <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">你的电脑IP</domain> </domain-config> </network-security-config>
      然后在AndroidManifest.xml<application>标签内引用:
      android:networkSecurityConfig="@xml/network_security_config"
    • (临时)在AndroidManifest.xml<application>标签内直接设置
      android:usesCleartextTraffic="true"
      但这种方法安全性较低,仅用于开发测试。

7.3 常见问题与排查技巧实录

下面我将遇到过的典型问题整理成表,方便你快速对照排查:

问题现象可能原因排查步骤与解决方案
安卓APP点击登录无反应,或报“Network Error”1. 后端服务未启动。
2. IP地址或端口错误。
3. 手机和电脑不在同一网络。
4. 电脑防火墙阻止了连接。
1. 检查后端控制台是否启动成功。
2. 在电脑上用curl或浏览器访问http://电脑IP:8080/api/health(需自己写个健康检查接口)看是否通。
3. 手机浏览器访问同一个地址,看是否能打开。
4. 临时关闭电脑防火墙测试。
登录接口返回“404 Not Found”1. 请求URL路径错误。
2. 请求方法(GET/POST)不对。
1. 用Postman对比测试,确保URL、方法、请求体完全一致。
2. 检查后端@RequestMapping@PostMapping注解的路径。
登录接口返回“500 Internal Server Error”后端代码运行时异常,如空指针、数据库连接失败。1.查看后端控制台日志!这是最重要的排错信息源,异常堆栈会打印在这里。
2. 检查数据库连接配置、SQL语句是否正确。
能收到响应,但状态码是200,数据解析失败(如Gson解析错误)1. 后端返回的JSON格式与安卓端定义的ResultLoginResponse类结构不匹配。
2. 字段名或类型不一致。
1. 使用Retrofit的HttpLoggingInterceptor查看原始响应JSON。
2. 对比后端返回的JSON字符串和安卓端数据类的定义,确保字段名、嵌套结构完全一致。可以使用@SerializedName注解处理字段名映射。
课程表页面列表为空1. 后端查询课程表的API逻辑错误,没查到数据。
2. 安卓端请求参数(如学生ID、学期)传错了。
3. 安卓端RecyclerView的Adapter设置有问题。
1. 用Postman直接调用课程表API,看是否返回数据。
2. 在安卓端请求成功的回调里,打印或调试查看返回的数据对象。
3. 检查Adapter的getItemCount()是否正确,以及onBindViewHolder里是否设置了数据。
图片加载不出来(Glide)1. 图片URL是错的或为空。
2. URL是HTTP,而Android 9+限制了明文流量(见上文)。
3. 图片服务器问题。
1. 打印图片URL,在电脑浏览器中直接打开看能否访问。
2. 配置network_security_config.xml
3. 使用Glide的错误监听器.listener()来查看具体错误信息。
发布失物信息失败,特别是带图片1. 文件上传接口有问题。
2. 安卓端未正确处理文件选择和Multipart表单上传。
1. 先用Postman测试文件上传接口是否正常。
2. 安卓端使用MultipartBody.Part来包装文件,并与其他表单字段一起通过@Multipart注解上传。确保文件路径正确,且有读取存储的权限。

7.4 性能与优化小贴士

  • 图片优化:使用Glide,它自带了强大的缓存和图片压缩功能。对于用户上传的图片,后端最好也做一次压缩再存储或返回缩略图URL。
  • 列表分页:新闻列表、失物招领列表一定要做分页!后端使用MyBatis-Plus的Page对象,前端使用RecyclerView的滑动监听或Paging 3库来实现上拉加载更多。这是避免OOM和提升用户体验的关键。
  • 数据缓存:对于不常变的数据,如学院、专业列表,可以在安卓端首次加载后缓存到SharedPreferences或数据库中,减少不必要的网络请求。
  • 代码混淆与加固:项目完成后,在打包Release APK前,记得在build.gradle中启用代码混淆(minifyEnabled),并使用一些第三方加固平台对APK进行加固,防止反编译。

走到这一步,你的校园APP系统已经从一个想法变成了一个可以真机安装、实际运行的应用了。从数据库设计到API编写,再到安卓界面交互,你完整地走通了一个全栈项目的开发流程。这份经历和这个可运行、有论文、有源码的项目,无论是用于毕业答辩还是充实个人作品集,都极具分量。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询