ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android+XAMPP+MySQL家校互动平台:从数据库到接口的完整实现

Android+XAMPP+MySQL家校互动平台:从数据库到接口的完整实现 简介这是一份面向Android开发学习者、毕业设计或课程设计学生以及需要搭建家校互动类项目的开发者的完整项目资料。资源围绕Android客户端与XAMPP环境下的MySQL数据库展开采用CS架构实现家校互动平台包含项目全套源码和完整设计文档源码已经过测试校正可百分百成功运行适合作为相关项目设计的参考蓝本。压缩包约151.66MB以源代码文件和文档为主涵盖Android端界面与逻辑、服务端配置以及数据库脚本等模块便于按目录结构快速定位并直接导入开发环境学习。目前已有293人学习下载口碑可见。透过该项目可掌握Android与MySQL交互的完整流程、XAMPP本地服务器部署方法以及家校通知、互动反馈等功能模块的实现思路对理解实际CS架构项目的分层设计与调试非常有帮助。1. 基于AndroidXAMPPMySQL的家校互动平台不是App是三段工程的完整闭环先说一个反直觉的结论这类标题看起来是一个“App 项目”但真正决定成败的并不是 Android 界面而是 MySQL 里的表结构、PHP 接口的返回格式以及 Android 端与 XAMPP 服务端之间的联调。家校互动平台这个方向本质上是典型的 CS 架构——Android 作为 C 端负责展示和交互跑在 XAMPP 里的 ApachePHPMySQL 组成 S 端负责数据和业务逻辑。适合拿来做毕业设计或课程设计的人也很适合刚学完 Android 基础、想完整走一遍“移动端后端数据库”链路的学生。它的业务场景很清晰教师发通知、布置作业、录入成绩、记录考勤家长查公告、看成绩、给孩子请假、给老师留言。你一旦把这条链路打通等于把 Android 开发里最常用的网络请求、列表展示、本地缓存以及后端里最基础的接口设计和表设计全部过了一遍。下面我会按“先定表再写接口最后接 Android”的顺序把这套方案从头拆到能跑起来的程度。2. MySQL 库表设计与接口约定先把家校互动平台的“地基”钉死不管最终交付包里放的是源码还是文档真正上架到 XAMPP 环境里跑的只有三类东西数据库脚本、PHP 接口文件、Android 工程。而这三者里数据库脚本是改动成本最高的一层。因为后期一旦发现表结构不对PHP 要改Android 端的字段映射也要跟着改牵一发动全身。2.1 角色、班级与用户用一张 users 表兜住四种身份家校互动平台里有四类人系统管理员、教师、家长、学生。最常见的做法不是建四张用户表而是建一张 users 表用 user_type 字段区分身份。原因很简单登录逻辑只需要查一张表接口里根据 user_type 做分支权限控制不用搞四套登录表和四套 token 机制。CREATE DATABASE IF NOT EXISTS family_school DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE family_school; CREATE TABLE users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(32) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT password_hash 加密后的密文, real_name VARCHAR(32) NOT NULL COMMENT 真实姓名, user_type TINYINT NOT NULL DEFAULT 2 COMMENT 0管理员 1教师 2家长 3学生, class_id INT NOT NULL DEFAULT 0 COMMENT 班级ID管理员为0, phone VARCHAR(20) DEFAULT COMMENT 手机号, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT用户表;这里的几个设计点值得说明。user_type 用 TINYINT 而不用字符串是为了后续在 PHP 里用 (int) 强转比较时不容易翻车也为 index 省空间。password 字段长度留 255是因为后面要用 password_hash() 生成密文这个函数默认输出长度是 60 个字符左右但各家 PHP 版本和加密算法的演化会留更多余地索性直接给 255。class_id 对家长和学生都有用家长登录后要按孩子的班级查公告学生本身也在班级里所以这条外键语义是“当前用户属于哪个班级”。管理员这类身份不需要绑定班级所以在写入时给 class_id 一个默认值 0。这里也顺带回答了热搜里“mysql设置默认值为0”这种问题字段默认值不只在建表时能体现在后续 INSERT 语句里如果不传这个字段数据库会自动填 0。2.2 六张核心业务表公告、作业、成绩、考勤、请假、留言家校互动的业务核心可以浓缩成六张表。设计它们的时候我建议你带着一个问题Android 端列表页需要哪些字段接口只要查一张表就能拿全数据的情况最理想尽量避免 JOIN 三张表以上——不是 MySQL 不行而是课程设计文档里讲不清楚答辩时也容易被追问到“你这里查出来的数据对不对”这种问题上。CREATE TABLE notice ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, class_id INT NOT NULL DEFAULT 0 COMMENT 0表示全校公告, title VARCHAR(100) NOT NULL, content TEXT NOT NULL, teacher_id INT NOT NULL COMMENT 发布人ID, publish_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_class_time (class_id, publish_time) ) ENGINEInnoDB COMMENT通知公告表; CREATE TABLE homework ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, class_id INT NOT NULL, subject VARCHAR(32) NOT NULL COMMENT 学科, content TEXT NOT NULL, deadline DATETIME NOT NULL, teacher_id INT NOT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_class (class_id, create_time) ) ENGINEInnoDB COMMENT作业表; CREATE TABLE score ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL COMMENT 学生ID, subject VARCHAR(32) NOT NULL, exam_name VARCHAR(64) NOT NULL COMMENT 考试名称, score DECIMAL(5,1) NOT NULL, teacher_id INT NOT NULL, record_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_student (student_id, exam_name) ) ENGINEInnoDB COMMENT成绩表; CREATE TABLE attendance ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, course_date DATE NOT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 2迟到 3缺勤 4请假, remark VARCHAR(255) DEFAULT , recorder_id INT NOT NULL COMMENT 登记人通常是班主任, UNIQUE KEY uk_student_date (student_id, course_date) ) ENGINEInnoDB COMMENT考勤表; CREATE TABLE leave_request ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, reason VARCHAR(255) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审批 1通过 2驳回, teacher_id INT DEFAULT NULL COMMENT 审批教师, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT请假表; CREATE TABLE message ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, from_user_id INT NOT NULL, to_user_id INT NOT NULL, content VARCHAR(500) NOT NULL, is_read TINYINT NOT NULL DEFAULT 0 COMMENT 0未读 1已读, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_to_user (to_user_id, is_read) ) ENGINEInnoDB COMMENT一对一留言表;这里有两个容易忽略的约束。attendance 表上的 UNIQUE KEY (student_id, course_date)保证一个学生一天只能有一条考勤记录这在业务上是对的也避免重复 INSERT 产生脏数据。message 表的 to_user_id 和 is_read 联合索引是为了支撑“未读消息数”这个高频查询——家长登录后首页往往要显示“你有 3 条新留言”这个索引能保证数据量上来后查询不慢。2.3 统一 JSON 返回结构让 PHP 接口和 Android 客户端避免“各写各的”在写第一个 PHP 接口之前先定契约。Android 端不管请求哪个接口拿到的 JSON 永远是同一个外壳{ code: 0, msg: ok, data: [] }code 为 0 表示成功非 0 表示失败msg 给用户看或给开发调试用data 里面放对象或数组。这个结构可能看起来简单但它能避免后面最痛苦的一件事每个接口返回格式都不一样Android 端解析器要写十几套 Gson Bean。定了统一外壳之后Android 端只需要一个 BaseResponse 泛型类配合 Retrofit 的 GsonConverter所有接口共用一套解析逻辑。对应到 MySQL 端表字段名尽量就按这个原则来直接用 snake_case比如 publish_time、real_name。Android 端用 Gson 解析时如果不做映射默认会把 JSON 里的 real_name 映射成 realName 字段。为了让 Gson 少做转换安卓端在定义数据类时也直接用 SerializedName 注解声明成下划线名两端都能少踩坑。这里最重要的是把“先有表后有接口再有页面”这个顺序固化下来。很多人在 Android 端写了半天布局才发现服务端根本没这个接口或者字段对不上。后端的表就是整条链路的 SchemaAndroid 端所有页面能显示什么是由这张表决定的不是由布局决定的。3. XAMPP 环境下部署 PHP 服务端从 htdocs 目录到能连上的接口数据库脚本写好之后下一步就是让接口活着跑起来。这个过程里XAMPP 是你最省事的“后悔药”它把 Apache、PHP、MySQL 三个东西打包成一个绿色服务双击启动不用分别配置三个服务的环境变量。对于课程设计或毕业设计的交付场景来说XAMPP 的另一个好处是评委老师拿到你的项目包后只要装了 XAMPP把 htdocs 目录复制过去导入 SQL就能复现整个环境。3.1 安装与自检为什么选 XAMPP 而不是手装 ApachePHPMySQL很多教程会让你分别装 Apache、PHP、MySQL再手动改配置文件。但对这类项目来说手装三个服务的成本远大于收益。XAMPP 把默认端口都定好了Apache 是 80MySQL 是 3306phpMyAdmin 是一个现成的 Web 图形界面。你只需要在 XAMPP Control Panel 里把 Apache 和 MySQL 两个服务点成绿色然后在浏览器里访问 http://127.0.0.1/能看到 XAMPP 的默认欢迎页就说明环境起来了。一个细节安装路径不要带空格和中文比如 D:\xampp 就比 D:\Program Files\xampp 省事。PHP 的 include 路径和 Apache 的文档根目录在带空格的路径下偶尔会出现玄学问题尤其是后面你要在代码里 require 其他文件时路径拼接容易出幺蛾子。3.2 接口清单与目录结构一个请求从 Android 到 MySQL 的完整路径服务端的目录结构按功能拆分不搞扁平化的一堆 PHP 文件放在根目录。常见做法是在 htdocs 下建一个家校园项目目录里面分 includes 和 api 两块htdocs/family_school/ includes/ db.php # PDO 数据库连接 auth.php # token 校验函数 api/ login.php notice_list.php notice_detail.php homework_list.php score_list.php leave_apply.php message_send.php message_list.php接口清单先列出来后续写代码时照着表格逐一实现。这样做的目的是让你在写 Android 端时一眼能看到每个页面该调哪个接口、传什么参数、拿什么数据。接口文件名用途主要入参返回 datalogin.php登录username, passwordtoken, user_type, real_namenotice_list.php公告列表token公告数组notice_detail.php公告详情token, id单条公告对象homework_list.php作业列表token作业数组score_list.php成绩列表token, student_id成绩数组leave_apply.php提交请假token, reason, start_time, end_timeleave_idmessage_list.php留言列表token, other_user_id消息数组请求从 Android 端出发经过 Retrofit 拼 URL到达 Apache 监听端口Apache 把请求交给 PHP 解释器PHP 执行文件里的 SQL 语句通过 PDO 连到 MySQLMySQL 返回结果集PHP 把结果包成统一 JSON 返给 Android。这条链路里的每一个环节都有对应的日志和错误点后面排查问题就是按这个顺序一层层往里找。3.3 用 PDO 代替 mysqli连接串、UTF-8 与 SQL 注入三个问题一次解决老旧的 PHP 教程还在用 mysqli_connect 和拼字符串的方式执行 SQL这在本地课设没问题但一旦评委追问安全性和“为什么这么写”你很难答得漂亮。换成 PDO 之后连接代码稳定、预处理防注入、字符集问题还能在连接串里直接解决。?php // includes/db.php header(Content-Type: application/json; charsetutf-8); $host 127.0.0.1; $port 3306; $dbname family_school; $username root; $password ; // XAMPP 默认 root 无密码如果改过请同步修改 try { $pdo new PDO( mysql:host$host;port$port;dbname$dbname;charsetutf8mb4, $username, $password, [ PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES false, ] ); } catch (PDOException $e) { echo json_encode([ code 500, msg 数据库连接失败 . $e-getMessage(), data null ]); exit; }第 14 行的连接串里直接写了 charsetutf8mb4这比建表时指定字符集更进一步——它保证 PHP 和 MySQL 之间的通信链路也是 UTF-8。第 18 行的 PDO::ATTR_EMULATE_PREPARES 设为 false是要求 MySQL 服务端真正做预处理而不是让 PDO 在本地模拟。这个参数对防 SQL 注入很关键也能让 SQL 执行计划被 MySQL 缓存重复查询时性能更好。每个接口文件的开头都这样写?php require_once __DIR__ . /../includes/db.php; require_once __DIR__ . /../includes/auth.php;用DIR拼路径而不是相对路径是避免不同入口文件 require 时基准目录不一致。这个细节在 XAMPP 环境下尤其重要因为 Apache 的当前工作目录不一定是 htdocs漏掉这个会让你的接口 500 都不知道去哪查。3.4 登录、token 与权限隔离CS 架构下怎么判定“你是谁”传统网页端靠 PHP Session 保持登录态但 Android 客户端不是浏览器Session 的处理既别扭又难调试。更贴近 CS 架构的做法是登录成功后服务端生成一个随机 token把 token 存在一张 token 表里客户端后续每个请求都在 HTTP Header 里带这个 token。服务端接口先查 token 表再拿对应用户信息处理业务。?php // api/login.php require_once __DIR__ . /../includes/db.php; $input json_decode(file_get_contents(php://input), true); if (!isset($input[username], $input[password])) { echo json_encode([code 400, msg 用户名和密码不能为空, data null]); exit; } $stmt $pdo-prepare(SELECT * FROM users WHERE username :username LIMIT 1); $stmt-execute([:username $input[username]]); $user $stmt-fetch(); if ($user password_verify($input[password], $user[password])) { $token bin2hex(random_bytes(32)); $expireTime date(Y-m-d H:i:s, time() 7 * 86400); $stmt $pdo-prepare( INSERT INTO user_tokens (user_id, token, expire_at) VALUES (:user_id, :token, :expire_at) ); $stmt-execute([ :user_id $user[id], :token $token, :expire_at $expireTime, ]); echo json_encode([ code 0, msg ok, data [ token $token, user_type (int)$user[user_type], real_name $user[real_name], ], ]); } else { echo json_encode([code 401, msg 用户名或密码错误, data null]); }第 15 行用 password_verify 来校验密码意味着建种子数据时不能直接 INSERT 明文密码而要用 password_hash(123456, PASSWORD_DEFAULT) 生成密文再入库。这是新手非常容易翻车的地方明明数据库里能看到密码就是登录不上。token 用 random_bytes 生成而不是直接用 md5(时间戳)是因为 md5 的输入可预测生成的 token 容易被枚举。有了 token 表后再写一个统一的鉴权函数放到 auth.php 里?php // includes/auth.php function check_token(PDO $pdo) { $headers getallheaders(); $token $headers[token] ?? $headers[Token] ?? ; if ($token ) { echo json_encode([code 401, msg 未登录, data null]); exit; } $stmt $pdo-prepare( SELECT u.* FROM user_tokens t INNER JOIN users u ON u.id t.user_id WHERE t.token :token AND t.expire_at NOW() LIMIT 1 ); $stmt-execute([:token $token]); $user $stmt-fetch(); if (!$user) { echo json_encode([code 401, msg 登录已过期, data null]); exit; } return $user; }到这里CS 架构的“S 端”已经具备了骨架连接层、鉴权层、接口层都有了。接下来你写每个业务接口只需要在开头 require 这两个文件然后调用 check_token($pdo) 拿到用户再写业务 SQL 即可。4. Android 客户端对接登录后按角色渲染不同的首页Android 端是用户直接看到的部分也是课程设计答辩时评委盯着看的部分。与其把所有功能塞进一个 Activity不如把一个“主界面”搭清楚登录页 一个带底部导航的主页首页里按登录用户的角色显示不同菜单。这一章先解决工程依赖再把网络层和登录态打通。4.1 工程骨架与依赖选择Retrofit 2 Gson 是课程设计里最稳的组合Android 端的网络请求有三种常见选择HttpURLConnection、Volley、Retrofit 2。我建议选 Retrofit 2 Gson原因是它把“接口定义、请求执行、JSON 解析”三层都给你封装齐了代码量最少答辩时解释起来也清晰。它的核心思路是用一个 Java 接口描述 HTTP APIRetrofit 根据接口上的注解自动生成实现类。先在 app/build.gradle 里加上依赖dependencies { implementation androidx.appcompat:appcompat:1.7.0 implementation com.google.android.material:material:1.12.0 implementation androidx.recyclerview:recyclerview:1.3.2 implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation com.squareup.okhttp3:logging-interceptor:4.12.0 }最后一行 logging-interceptor 不是必选的但强烈建议加上。它可以把每个请求的 URL、Header、响应 JSON 打到 Logcat 里联调时哪个字段对不上一眼就能看到服务端到底返回了什么。这是排查联调问题的黑匣子不用它你就只能靠瞎猜。4.2 从登录接口到 SharedPreferences把 token 存好后面所有列表都要用登录页拿到 token 后要把 token、user_type、real_name 存在本地。常见做法是用 SharedPreferences不需要引入其他存储框架。SharedPreferences 是同步读写数据量小适合存这些登录态参数。// LoginActivity.java 核心逻辑 public class LoginActivity extends AppCompatActivity { private ApiService api; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); api RetrofitClient.get(); findViewById(R.id.btnLogin).setOnClickListener(v - { String username ((EditText) findViewById(R.id.etUsername)).getText().toString(); String password ((EditText) findViewById(R.id.etPassword)).getText().toString(); LoginRequest request new LoginRequest(username, password); api.login(request).enqueue(new CallbackBaseResponseLoginData() { Override public void onResponse(CallBaseResponseLoginData call, ResponseBaseResponseLoginData response) { BaseResponseLoginData body response.body(); if (body ! null body.code 0) { SharedPreferences sp getSharedPreferences(user, MODE_PRIVATE); sp.edit() .putString(token, body.data.token) .putInt(user_type, body.data.userType) .putString(real_name, body.data.realName) .apply(); Intent intent new Intent(LoginActivity.this, MainActivity.class); startActivity(intent); finish(); } else { Toast.makeText(LoginActivity.this, body ! null ? body.msg : 登录失败, Toast.LENGTH_SHORT).show(); } } Override public void onFailure(CallBaseResponseLoginData call, Throwable t) { Toast.makeText(LoginActivity.this, 网络异常 t.getMessage(), Toast.LENGTH_SHORT).show(); } }); }); } }这里有一个必须注意的点enqueue 是异步回调回调线程是 Android 主线程还是子线程取决于 Retrofit 的实现默认回调执行在 Android 主线程所以可以直接在回调里更新 SharedPreferences 和启动 Activity不用额外 runOnUiThread。如果你哪天换成 execute() 同步调用就必须自己切线程否则会崩溃。RetrofitClient 这类工具类负责单例创建public class RetrofitClient { // 模拟器访问宿主机固定用 10.0.3.2真机调试改成电脑局域网 IP private static final String BASE_URL http://10.0.3.2/; private static volatile ApiService instance; public static ApiService get() { if (instance null) { synchronized (RetrofitClient.class) { if (instance null) { instance new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .client(new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor() .setLevel(HttpLoggingInterceptor.Level.BODY)) .build()) .build() .create(ApiService.class); } } } return instance; } }Retrofit 的 baseUrl 必须以 / 结尾这是 Retrofit 的硬性要求否则请求拼出来的 URL 会丢路径。模拟器访问宿主机时10.0.3.2 是 Android 模拟器给宿主机预留的地址换成真机调试时要改成电脑在局域网里的 IP比如 192.168.1.107。4.3 首页列表与进度条RecyclerView 每次刷新都要处理 loading 态登录之后的主页面常见的交互结构是一个 Layout顶部是用户信息问候下面是四个功能入口卡片。功能入口根据 user_type 动态隐藏。比如 user_type 是家长只显示“学校公告、成绩查询、请假申请、在线留言”是教师则显示“发布通知、作业管理、考勤登记、成绩录入”。列表页要处理的数据加载态至少有三态加载中、加载成功、加载失败。这个进度条实现很简单但很容易被忽略——因为接口快的时候 loading 一闪而过你根本注意不到。可一旦网络慢或者服务端没启动RecyclerView 就会白屏用户不知道是在加载还是加载失败了。常见做法是在布局里用 FrameLayout 把一个 ProgressBar 叠在 RecyclerView 上面FrameLayout android:layout_widthmatch_parent android:layout_heightmatch_parent androidx.recyclerview.widget.RecyclerView android:idid/rvNotice android:layout_widthmatch_parent android:layout_heightmatch_parent / ProgressBar android:idid/progressBar android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter / /FrameLayout请求发起时让 progressBar 可见请求成功或失败后让它消失。这个 ProgressBar 是 Android 自带控件不需要第三方的加载动画库就能满足课程设计里“有进度条”的硬性要求。加载成功后用 adapter.setData 更新列表失败时用 Toast 提示并把 RecyclerView 置为空布局或保留上次数据。4.4 模拟器与真机的 BaseUrl 切换10.0.3.2 和局域网 IP 的坑这大概是所有联调问题里翻车率最高的一个。模拟器里 localhost 指的是模拟器自己不是你的电脑你在模拟器的浏览器里访问 http://localhost 也是打不开 XAMPP 的。正确地址是 http://10.0.3.2。如果换了真机10.0.3.2 也不通得用电脑的真实局域网 IP。查看电脑 IP在 Windows 上执行 ipconfig在 macOS 上执行 ifconfig。拿到之后先在电脑浏览器里访问 http://192.168.x.x/确认 XAMPP 欢迎页能出来再填到 RetrofitClient 的 BASE_URL 里。还要确认 Windows 防火墙允许 80 端口入站否则手机请求会被防火墙默默丢掉浏览器却一切正常。这属于非常典型的“电脑上通手机上不通”的坑。Android 9 之后系统默认禁止明文 HTTP 流量这一步不处理后面接口永远连不上第五章里我会专门讲。5. 家校平台部署避坑从 Error 2002 到中文乱码的 6 个高频问题这一章的价值在于让你少走弯路。这些坑不是某一个项目的特例而是 XAMPP MySQL Android 联调里几乎人人都会撞上的问题。每条按现象、原因、解决三个层次来写碰到直接对照处理。5.1 MySQL root 密码与 Error 2002 (HY000)跳不过去的第一道坎现象打开 phpMyAdmin 能进但 Android 端或 PHP 脚本报错提示 SQLSTATE[HY000] [2002] Cant connect to local MySQL server through socket或者 Access denied for user rootlocalhost。原因XAMPP 里 MySQL 默认 root 用户是没有密码的但很多人看教程改了密码或者装过其他 MySQL 版本导致 PHP 连接时用的还是默认空密码自然被拒绝。还有一种是 MySQL 服务没启动XAMPP Control Panel 里 MySQL 那一行不是绿色也会出现 2002。socket 报错和 TCP 连不上在表面上很相似但原因完全不同一个是服务没监听一个是认证失败。解决先看 XAMPP 面板确认 MySQL 是绿色运行状态。然后打开命令行进入 XAMPP 的 mysql/bin 目录执行mysql -u root -p能进去就说明服务正常问题出在 PHP 代码里的密码。如果 root 设过密码就把 db.php 里的 $password 改成实际密码。如果忘了密码用 mysql -u root --skip-password 进入后执行ALTER USER rootlocalhost IDENTIFIED BY 新密码;MySQL 的 update 语法也能改权限表但 ALTER USER 是更标准的做法8.0 版本里别再用老旧的 UPDATE mysql.user 写法。5.2 模拟器连不上10.0.3.2 与 192.168.x.x 的差异现象模拟器里 App 圈子转半天Logcat 报 Connection refused 或者 SocketTimeoutException。浏览器访问 http://localhost 是好的。原因Android 模拟器内部是一个独立的 Linux 虚拟机它的 localhost 指模拟器自己。开发机的 XAMPP 服务对它来说在另一台“机器”上需要走模拟器网关 10.0.3.2。Connection refused 说明请求已发出但没人监听这通常是地址不对或端口不对SocketTimeoutException 通常是防火墙拦截或 IP 不通。解决把 RetrofitClient 里的 BASE_URL 改成 http://10.0.3.2/确认电脑端浏览器能访问 http://10.0.3.2/。如果换真机改成局域网 IP。要注意改了 BASE_URL 必须重新 build不能只点 Run——由于 Retrofit 的 baseUrl 是构建时传进去的不重新编译不会生效。5.3 Android 9 明文流量限制接口 200 了但数据不回来现象接口在浏览器里能访问curl 能通Android 端却报错提示 cleartext HTTP traffic to 10.0.3.2 not permitted。原因Android 9API 28开始系统默认禁止明文 HTTP 请求。你的接口是 http:// 不是 https://所以被系统拦截。这不是服务端问题是 Android 的安全策略变化。很多新手卡在这一关好几天怎么查都查不到服务端有问题因为服务端确实没问题。解决在 AndroidManifest.xml 的 application 节点上加上 usesCleartextTrafficapplication android:usesCleartextTraffictrue ...这个属性是给整个 App 放开明文流量限制的开关。对课程设计项目来说这样足够但要清楚正式上线应用时不应开全局明文流量而是用网络安全配置只对特定域名放开。立完属性重新 Run 一次问题就没了。5.4 中文乱码建库、连接串、HTTP 头三层都要 utf8mb4现象MySQL 里查询正常Android 端显示公告标题是问号或一串乱码。或者反过来App 提交中文数据库里变成问号。原因乱码从来不是单点问题。建库时如果不是 utf8mb4中文存进去就是乱码PHP 连接串没指定 charset读写取时可能转了 latin1最后 PHP 返回 JSON 时没设置 Content-Type 的 charsetUTF-8Android 端会把字节流按系统默认方式解码也会乱码。三层里任何一层掉了链子展示层一定出错。解决建库 SQL 里显式写 DEFAULT CHARACTER SET utf8mb4db.php 连接串里写 charsetutf8mb4PHP 文件开头写 header(Content-Type: application/json; charsetutf-8)。做完这三步新建的数据一定不会乱码。如果库里已经有乱码数据需要把表转成 utf8mb4 再配合更新已经损坏的字符很难逆转直接删除重建种子数据更省事。5.5 Apache 端口被占用80/443 起不来时改哪里现象XAMPP 面板里 Apache 一直不是绿色点 Start 后几秒又变红提示 Port 80 in use。原因本地装了 IIS、Nginx、其他版本的 Apache或者腾讯会议、某些杀毒软件占用了 80 端口。Windows 上很多软件会偷偷占用 80这是最常见的冲突。解决如果只是临时跑项目把占用 80 的软件关掉。如果不想关就改 Apache 端口。打开 xampp/apache/conf/httpd.conf找到 Listen 80 改成 Listen 8080再找到 ServerName localhost:80 改一致。改完后访问路径变成 http://127.0.0.1:8080/RetrofitClient 的 BASE_URL 也要加上 :8080。这个端口改动看似很小但最容易漏的是 Retrofit 端忘记同步改。5.6 导入 SQL 失败phpMyAdmin 限制与 mysql 命令行两条路现象在 phpMyAdmin 导入 family_school.sql提示上传文件超过限制或者导入到一半报语法错误。原因phpMyAdmin 默认有 upload_max_filesize 和 post_max_size 限制XAMPP 默认值通常只有 2M。课设的 SQL 脚本虽然一般不会超过这个值但如果包含大量 INSERT 种子数据很容易超限。另一种情况是 SQL 文件里包含了 CREATE DATABASE 语句而当前用户在 phpMyAdmin 里没有对应权限导致 Executed ALTER TABLE 期间报错。解决优先用命令行导入不经过 phpMyAdmin 的大小限制。在 XAMPP 的 mysql/bin 目录下执行mysql -u root -p family_school D:/family_school.sql如果 SQL 文件里已经写了 CREATE DATABASE 和 USE family_school命令里的 family_school 可以不写。命令行导入还能看到具体的报错行号排查起来比 phpMyAdmin 弹一条笼统错误直观得多。6. 交付前最后一小时用 curl 和一条 SQL 说清系统健康度到这一步整个平台基本跑通。最后这段时间我建议不要急着多写功能而是做一次“服务端体检”——用 curl 模拟 Android 客户端的请求再用 SQL 直接查数据库确认链路每个环节都是通的。6.1 用 curl 模拟 Android 客户端的调用过程curl 是排查接口问题最趁手的工具它不需要打开 Android Studio也不需要等待模拟器启动直接在电脑终端里就能看到接口返回的原始 JSON。先把 POST 登录接口测通curl -X POST http://127.0.0.1/family_school/api/login.php \ -H Content-Type: application/json \ -d {username:parent01,password:123456}正常返回的结果应该包含 token 字段。拿到 token 后把它复制到下一个请求的 Header 里测列表接口curl -X GET http://127.0.0.1/family_school/api/notice_list.php \ -H token: 上一步返回的token字符串这里能看到返回的 JSON 里 code 是不是 0、data 数组里有没有内容。如果 code 是 401说明 token 没传对或被服务端判为过期如果 code 是 500把 PHP 的 display_errors 打开看具体报错。6.2 检查数据是否真正落库一条 SQL 看穿所有接口接口通了不代表数据正确。用 MySQL 命令行或 phpMyAdmin 执行以下 SQL校验插入的数据是否和接口返回一致USE family_school; SELECT u.id, u.username, u.real_name, u.user_type, t.token, t.expire_at FROM users u LEFT JOIN user_tokens t ON t.user_id u.id WHERE u.username parent01; SELECT id, title, publish_time FROM notice ORDER BY publish_time DESC LIMIT 5; SELECT COUNT(*) AS unread_count FROM message WHERE to_user_id 2 AND is_read 0;第一条 SQL 检查登录链路如果查不到 token 记录说明登录接口根本没执行到 INSERT。第二条 SQL 检查公告写入如果接口返回有数据但表里是空的问题在接口查询条件的 class_id 过滤写错了。第三条 SQL 是给“未读消息数”这个功能用的接口里如果用了这条 SQL数据对得上就说明联合索引设计得合理。6.3 我的交付习惯先跑通再写文档这几年来我经手过的 Android 项目有一个共同规律凡是先写文档后调通的最后文档里写的界面截图和实际操作都不一样凡是先跑通再补文档的反而能把细节写清楚。原因是只有你真的踩过 Error 2002、改过 httpd.conf、看过明文 HTTP 的报错才写得出“可能出现的问题”那一节。我每次交付这类 C/S 架构的项目都会留出最后一小时按“curl 登录 → curl 列表 → 查库”这个顺序做一遍体检然后再截图放进文档确保文档里的每个画面都是实拍的。这个方向最值得投入的地方不是某一个页面的动画效果而是把“数据库表 → 服务端接口 → Android 端展示”这条链路练得滚瓜烂熟。你只要完整走通一遍后面再做任何系统的移动端都只是换业务表而已。希望这份整理能帮你把项目从“能跑”推到“能讲清楚”答辩或复盘时心里有底。本文还有配套的精品资源点击获取
返回列表