简介:本资源是一套基于Basic4Android(B4A)实现安卓端用户登录与注册功能的完整开发实践方案,面向Android初学者、B4A入门开发者及需快速对接MySQL后端的移动应用学习者,解决本地App与远程数据库交互的核心问题。压缩包共53个文件,含29个编译类文件(classes)、8个Java底层支持文件、3个B4A源码文件(.bas)、3个布局与逻辑封装文件(.bal)、3个XML资源定义,以及APK安装包、B4A工程文件、DEX字节码等,整体1.84MB,结构清晰,覆盖从界面设计、JDBC连接配置、SQL增查操作到密码校验与异常处理的全流程。已有713人学习下载,提供可直接导入B4A IDE运行的工程,含注册/登录双模块源码、预置MySQL连接参数示例、基础安全防护(如输入验证与语句预处理提示),并附带AndroidManifest配置与资源目录规范,助读者快速理解B4A+MySQL典型业务集成模式。
1. B4A连接MySQL实现登录注册:为什么不用REST API而坚持直连数据库?
这不是一个“教你怎么用B4A发HTTP请求”的入门教程,而是一份从某高校课程设计答辩现场拿回来的、被导师当场追问了7个问题的完整方案——它解决的是学生在离线调试环境受限、服务器资源为零、又必须交出可运行安卓APK+后端验证逻辑的真实困境。很多同学一上来就套用主流方案:前端B4A调用PHP接口,PHP再连MySQL。但现实是:本地没Apache、没Nginx、没域名、没公网IP,连XAMPP都装不全;更关键的是,课程设计验收只要求“能跑通登录注册”,不要求高并发、不考核接口安全、不查WAF日志。这时候硬上REST架构,等于给自己加三重编译障碍:PHP环境配错、URL路径写死、JSON解析异常——而B4A原生支持jRDC2和OkHttpUtils2直连MySQL JDBC桥接器(通过中间服务端代理),配合极简Java Servlet封装,反而能在3小时内完成从零到APK安装验证的闭环。本方案面向的是需要交付可演示APK、无服务器运维能力、但必须体现“客户端-服务端-数据库”三层交互逻辑的毕设/课设场景,所有代码、配置、部署步骤均来自某实验室连续三届学生复用验证过的最小可行路径。
2. 总体架构设计:B4A客户端 + Java Servlet中间层 + MySQL数据库的三层职责划分
这个结构不是为了炫技,而是把“谁该干啥”切得足够清楚,避免学生在调试时陷入“到底是B4A写错了、还是SQL写错了、还是网络超时了”的黑匣子状态。三层之间只保留最窄的契约接口:B4A只发固定格式的POST请求(含action参数和JSON body),Servlet只做路由分发+参数校验+JDBC执行+JSON返回,MySQL只响应标准SQL。没有JWT令牌、没有Session管理、不处理密码加盐(课程设计允许明文传输,但方案中已预留SHA-256升级位),所有复杂度被压到Servlet层内部,B4A端保持纯UI逻辑。
2.1 客户端层(B4A):用OkHttpUtils2替代默认HttpJob,规避SSL证书报错
B4A默认的HttpJob在Android 9+上会因未配置android:usesCleartextTraffic="true"而静默失败,且不支持自定义Header。我们改用社区验证稳定的OkHttpUtils2库(v2.80+),它底层基于OkHttp3,对HTTP/HTTPS兼容性更好,错误提示也更明确:
' B4A客户端登录逻辑(Main Activity) Sub btnLogin_Click Dim job As HttpJob job.Initialize("login", Me) Dim m As Map m.Initialize m.Put("action", "login") m.Put("username", EditText1.Text.Trim) m.Put("password", EditText2.Text.Trim) job.PostString("http://192.168.1.100:8080/B4AMySQL/LoginServlet", _ JsonGenerator.ConvertMapToJson(m)) End Sub提示:
192.168.1.100是开发机IP,非localhost——Android模拟器无法解析localhost指向宿主机,必须用真实局域网IP。真机调试时,手机和电脑需在同一WiFi下,且防火墙放行8080端口。
2.2 中间层(Java Servlet):用jRDC2精简版实现单文件路由分发
我们不部署Tomcat全套生态,而是采用轻量级jRDC2(Java Remote Database Connector 2)的定制分支——它把数据库连接池、SQL映射、JSON序列化全部封装在一个RemoteDatabaseConnector.java里,无需XML配置,只需修改config.properties中的JDBC URL、用户名、密码。核心设计是:所有请求统一走/B4AMySQL/LoginServlet,通过action参数区分业务(login/register/checkUser),避免建多个Servlet类:
// LoginServlet.java 核心路由逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String action = request.getParameter("action"); JSONObject result = new JSONObject(); try { if ("login".equals(action)) { result = handleLogin(request); } else if ("register".equals(action)) { result = handleRegister(request); } else if ("checkUser".equals(action)) { result = handleCheckUser(request); } else { result.put("success", false).put("message", "未知操作"); } } catch (Exception e) { result.put("success", false).put("message", "系统错误:" + e.getMessage()); } response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(result.toString()); }handleLogin()方法内直接调用jRDC2的ExecuteQuery执行预编译SQL,返回List<Map<String, Object>>,再转成JSON。全程不暴露Connection对象,不手写ResultSet遍历——这是学生最容易翻车的点:手动关闭Connection导致连接池耗尽,或ResultSet未关闭引发内存泄漏。
2.3 数据库层(MySQL):建表语句与字符集强制统一为utf8mb4
课程设计常见血泪经验:中文用户名存进数据库变成乱码????,或注册时提示“Duplicate entry 'admin' for key 'username'"却查不到记录。根因是MySQL默认字符集不是utf8mb4,且B4A发送的JSON未声明UTF-8编码。解决方案是三步强约束:
- 创建数据库时指定字符集:
CREATE DATABASE b4a_login_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - 建用户表时字段显式声明:
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - 在Servlet的
doPost开头强制设置请求编码:request.setCharacterEncoding("UTF-8"); // 必须在getParameter前调用
这样从B4A输入框→HTTP Body→Servlet接收→MySQL存储,全程UTF-8链路打通。某同学曾因漏掉第1步,反复重装MySQL五次,最后发现只是建库时少写了CHARACTER SET。
3. 关键模块实现:登录验证、注册防重、用户名实时校验的三段式代码落地
这三个功能不是独立模块,而是共享同一套数据访问契约。我们用同一个Servlet处理,靠action参数切换,避免学生为每个功能建新Activity或新Servlet导致项目结构失控。
3.1 登录验证:用PreparedStatement防SQL注入,而非字符串拼接
学生最容易写的错误代码是:
' ❌ 危险!直接拼接SQL(B4A端伪代码) Dim sql As String = "SELECT * FROM users WHERE username='" & username & "' AND password='" & password & "'"这会导致' OR '1'='1注入。正确做法是:B4A只传原始参数,由Servlet端用PreparedStatement绑定变量:
// ✅ 安全的登录查询(LoginServlet.java) private JSONObject handleLogin(HttpServletRequest req) throws Exception { String username = req.getParameter("username"); String password = req.getParameter("password"); String sql = "SELECT id, username, email FROM users WHERE username = ? AND password = ?"; List<Map<String, Object>> rows = rdc.ExecuteQuery(sql, Arrays.asList(username, password), null); // rdc是jRDC2实例 JSONObject result = new JSONObject(); if (rows.size() > 0) { result.put("success", true) .put("message", "登录成功") .put("user", new JSONObject(rows.get(0))); } else { result.put("success", false).put("message", "用户名或密码错误"); } return result; }rdc.ExecuteQuery内部自动使用PreparedStatement,?占位符由JDBC驱动安全转义。学生只需记住:所有用户输入的值,必须作为executeQuery的第二个参数(List)传入,绝不在SQL字符串里拼接。
3.2 注册防重:用INSERT IGNORE + 影响行数判断,而非先查后插
传统“先SELECT再INSERT”在高并发下有竞态条件(两个请求同时查到用户名不存在,然后都插入成功)。课程设计虽无并发压力,但此写法暴露了学生对数据库事务理解的薄弱。我们改用MySQL原生INSERT IGNORE语法,利用唯一索引(username字段设为UNIQUE)自动拦截重复:
// ✅ 注册逻辑(LoginServlet.java) private JSONObject handleRegister(HttpServletRequest req) throws Exception { String username = req.getParameter("username"); String password = req.getParameter("password"); String email = req.getParameter("email"); String sql = "INSERT IGNORE INTO users (username, password, email) VALUES (?, ?, ?)"; int affected = rdc.ExecuteNonQuery(sql, Arrays.asList(username, password, email)); JSONObject result = new JSONObject(); if (affected == 1) { result.put("success", true).put("message", "注册成功"); } else if (affected == 0) { result.put("success", false).put("message", "用户名已存在"); } else { result.put("success", false).put("message", "注册失败,请重试"); } return result; }INSERT IGNORE执行后,getUpdateCount()返回1表示插入成功,0表示因唯一键冲突被忽略。比SELECT COUNT(*)少一次数据库往返,逻辑更原子。
3.3 用户名实时校验:B4A端用Timer实现防抖,避免频繁请求
注册页面常加“用户名是否可用”实时检测,但学生常写成EditText_TextChanged事件里每敲一个字就发请求,导致Servlet被刷爆。正确做法是加防抖(Debounce):用户停止输入500ms后再触发检查:
' B4A端实时校验(Registration Activity) Sub EditText1_TextChanged If Timer1.Enabled = False Then Timer1.Interval = 500 Timer1.Enabled = True End If End Sub Sub Timer1_Tick Timer1.Enabled = False If EditText1.Text.Length > 2 Then CheckUsernameAvailability(EditText1.Text) End If End Sub Sub CheckUsernameAvailability(username As String) Dim job As HttpJob job.Initialize("checkUser", Me) Dim m As Map m.Initialize m.Put("action", "checkUser") m.Put("username", username) job.PostString("http://192.168.1.100:8080/B4AMySQL/LoginServlet", _ JsonGenerator.ConvertMapToJson(m)) End SubServlet端handleCheckUser只需执行SELECT COUNT(*) FROM users WHERE username = ?,返回{"available": true}即可。防抖逻辑放在B4A端,减轻服务端压力,也符合移动端交互直觉。
4. 部署与调试避坑指南:5个让90%学生卡住的致命细节
这套方案看似简单,但实际部署时,某高校连续两届学生平均卡在以下环节超过8小时。以下是按发生频率排序的避坑清单,每条都附真实翻车场景和可立即执行的修复命令。
4.1 现象:B4A报错“java.net.ConnectException: failed to connect to /192.168.1.100 (port 8080) from /:: (port 40000) after 30000ms”
原因:Android 9+默认禁止明文HTTP流量,且未在AndroidManifest.xml中声明android:usesCleartextTraffic="true"。
解决:在B4A项目Manifest Editor中添加:
<application android:usesCleartextTraffic="true" />注意:不是在
<activity>标签内,而是在<application>根标签属性中。漏掉此行,所有HTTP请求必超时,且LogCat无明确提示。
4.2 现象:Servlet返回{"success":false,"message":"系统错误:null"},但MySQL日志显示连接成功
原因:jRDC2的config.properties中JDBC URL写成jdbc:mysql://localhost:3306/...,而Servlet运行在开发机上,localhost指向127.0.0.1(即Servlet自身),而非MySQL服务。
解决:将JDBC URL中的localhost改为开发机真实IP(如192.168.1.100):
# config.properties JdbcUrl=jdbc:mysql://192.168.1.100:3306/b4a_login_db?useUnicode=true&characterEncoding=utf8mb4某同学调试三天,最后发现MySQL根本没收到任何连接请求——因为
localhost在Tomcat进程里解析成了它自己。
4.3 现象:注册成功后,MySQL里密码字段存的是明文123456,但登录时输123456却提示“密码错误”
原因:B4A端发送的JSON中password字段名写错(如写成pwd),Servlet用req.getParameter("password")取到null,插入数据库的密码是NULL,而登录时又用?绑定null,导致WHERE password = NULL永远不成立(SQL中NULL = NULL为false)。
解决:在Servlet的doPost开头加日志打印所有参数:
Enumeration<String> paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { String name = paramNames.nextElement(); Log.d("PARAM", name + "=" + request.getParameter(name)); }用Android Studio Logcat过滤PARAM,确认B4A发送的字段名与Servlet读取的完全一致。
4.4 现象:真机安装APK后,登录按钮点击无反应,LogCat里没有网络相关日志
原因:Android 9+要求所有网络请求必须在主线程外执行,而学生把job.PostString写在了btnLogin_Click的主线程里,但未加Wait For(job.Done)等待,导致请求未发出就退出了。
解决:必须用Wait For等待异步完成,并在JobDone子程序中处理结果:
Sub btnLogin_Click Dim job As HttpJob job.Initialize("login", Me) ' ... 构造参数 ... job.PostString("http://...", json) Wait For(job.Done) If job.Success Then Dim res As String = job.GetString Log(res) ' 解析JSON并更新UI End If End Sub缺少
Wait For是B4A新手最高频错误,现象就是“点按钮像没点一样”。
4.5 现象:Servlet部署到Tomcat后,访问http://localhost:8080/B4AMySQL/LoginServlet返回404
原因:web.xml中Servlet映射路径写错,或B4A请求URL里的上下文路径(/B4AMySQL)与Tomcat部署的WAR包名不一致。
解决:确认WAR包名为B4AMySQL.war,且web.xml中<url-pattern>为/LoginServlet(不是/login或/servlet/LoginServlet):
<servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/LoginServlet</url-pattern> </servlet-mapping>然后B4A请求URL必须为http://IP:8080/B4AMySQL/LoginServlet——上下文路径/B4AMySQL由WAR包名决定,/LoginServlet由<url-pattern>决定,二者缺一不可。
5. 验收与答辩实战:如何用3张截图+1段日志说服导师“我真跑通了”
课程设计验收不是考你代码多漂亮,而是考你能否在5分钟内向导师证明:“这个APK真的连上了真实的MySQL,做了登录注册,且逻辑正确”。我带过三届学生,总结出最高效的答辩呈现法:不打开IDE,不讲原理,只展示三张关键截图+一段终端日志,全程用手机投屏。下面是我要求学生必须准备的交付物:
5.1 截图1:MySQL命令行确认用户已入库(证明注册成功)
在MySQL客户端执行:
USE b4a_login_db; SELECT id, username, password, created_at FROM users ORDER BY id DESC LIMIT 3;截图要清晰显示:
- 表头
id,username,password,created_at - 至少一条由B4A注册产生的记录(
username应为中文或含特殊字符,如测试用户123) password字段内容与B4A输入的明文一致(课程设计允许)
导师看到真实数据落库,立刻相信“后端连上了”。某同学曾用
SELECT * FROM users截图,但表里只有test测试数据,被问“这是你注册的吗?”,当场哑火。
5.2 截图2:B4A登录成功后Toast弹窗+Activity跳转(证明客户端逻辑)
截取手机屏幕,必须包含:
- 顶部状态栏(显示时间,证明是实时操作)
- 中间Toast提示“登录成功”(B4A用
ToastMessageShow("登录成功", True)) - 底部新Activity的标题栏(如“欢迎,张三”)
不要截空白Activity或LogCat。导师只认“看得见的反馈”。曾有学生截了10张LogCat,但没一张显示“登录成功”,被质疑“到底有没有成功”。
5.3 截图3:Tomcat控制台日志(证明请求抵达服务端)
在Tomcat启动窗口(Windows是cmd,Mac是终端),滚动到最新日志,截取类似:
INFO: [LoginServlet] Received login request for user: admin INFO: [LoginServlet] Query executed, found 1 row INFO: [LoginServlet] Response sent: {"success":true,"message":"登录成功",...}日志必须带时间戳和明确的
LoginServlet标识。某学生截了Server startup in XXX ms,被问“这只能证明Tomcat启动了,不能证明你的Servlet被调用了”。
5.4 终端日志:用tcpdump抓包证明数据流真实存在(答辩加分项)
如果导师较严格,可现场用开发机执行:
sudo tcpdump -i any -A port 8080 | grep -E "(username|password|login|register)"当B4A发起登录请求时,终端会实时打印HTTP POST的原始Body,如:
POST /B4AMySQL/LoginServlet HTTP/1.1 ... {"action":"login","username":"admin","password":"123456"}这招能瞬间击穿“你是不是在本地Mock数据”的质疑。某导师当场说:“看到这个,我就信你没作弊。”
从那以后我每次指导学生,都强制他们答辩前用这四样东西走一遍全流程:先在MySQL里删掉测试数据,再用B4A注册一个新用户,截图1;再用该用户登录,截图2;同时开着Tomcat控制台,截图3;最后用tcpdump抓一次包,存为文本。四样齐备,答辩时直接投屏,导师点头就过。希望帮到你。
本文还有配套的精品资源,点击获取