Android后端登录开发(穿透)

1.引言

使用Android studio+idea构建一个简单的登录页面,使用mysql数据库存储数据,springboot框架构建后端。

2. 构建android前端

使用线性布局创建两个输入框和一个登入按钮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="50dp"
android:paddingRight="50dp">

<TextView
android:id="@+id/tv_Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="登录"
android:textSize="36sp" />

<EditText
android:id="@+id/et_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="用户名"
android:inputType="text" />

<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="密码"
android:inputType="textPassword" />

<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:textSize="20sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

3.idea后端

1730288115532.png

按照图示创建一个spring项目,不添加任何框架支持。

在pom.xml中添加mybatis,JDBC,JSON依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>

<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- JSON -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
</dependency>

按照如下创建好文件目录:

1730288610032.png

User对象只有username和password两个属性,生成他的get、set方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.loginddns.bean;
public class User {

private String username;
private String password;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

UserMapper与数据库构成映射,由mybatis自动映射:

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.loginddns.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {

@Select("SELECT count(*) FROM users WHERE username=#{username} AND password=#{password}")
int login(String username, String password);

}

UserController控制层完成对接口的映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.loginddns.controller;

import com.example.loginddns.dao.UserMapper;
import org.json.JSONObject;
import org.json.JSONException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api") //一级接口
public class UserController {

private JSONObject objectTrue = new JSONObject();
private JSONObject objectFalse = new JSONObject();

@Autowired
UserMapper userMapper;

public UserController() throws JSONException {
objectTrue.put("result", true);
objectFalse.put("result", false);
}

@ResponseBody
@RequestMapping("/login") //二级接口
public String login(String username, String password){
if(userMapper.login(username, password) == 0){
//不存在符合的用户
return objectFalse.toString();
} else {
return objectTrue.toString();
}
}
}

application.properties开放端口号,数据库链接配置:

1
2
3
4
5
6
7
8
9
10
11
spring.application.name=LoginDDNS
spring.datasource.url=jdbc:mysql://<数据库端口号>/<库名>?serverTimezone=UTC&characterEncoding=utf8
spring.datasource.username=<你的数据库账号>
spring.datasource.password=<你的数据库密码>
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true

server.port=10003

点击运行按钮,成功运行后显示当前tomcat版本等等信息:

1730289014395.png

4. 内网穿透

上述项目运行后,打开浏览器输入以下链接测试连通性:

1
http://localhost:10003/api/login?username=admin&password=password

1730290704809.png

这里使用花生壳内网穿透,6块钱买断制,进去注册好填写本机ipv4地址和后端项目的端口号,将他们创建在项目映射中,开启内网穿透访问外网地址,将上述的本机地址替换为花生壳提供的域名即可在其他环境访问本机端口开放的端口号。

1730290964421.png

5.开放android联网权限

在AndroidManifest.xml中添加以下语句:

1
<uses-permission android:name="android.permission.INTERNET" />

编写一个HttpUtils对网络发送来的json格式数据进行解析的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.example.loginddns;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class HttpUtils {

public HttpUtils(){

}

public static String getJsonContent(String url_path) {
try{
URL url = new URL(url_path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(3000);
connection.setRequestMethod("GET");
connection.setDoInput(true);
int code = connection.getResponseCode();
if(code == 200){
return changeInputStream(connection.getInputStream());
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "";
}

private static String changeInputStream(InputStream inputStream) {
String jsonString = "";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
int len = 0;
byte[] data = new byte[1024];
try{
while ((len = inputStream.read(data)) != -1) {
outputStream.write(data, 0, len);
}
jsonString = new String(outputStream.toByteArray());
return jsonString;
} catch (IOException e) {
e.printStackTrace();
}
return "";
}
}

将layout页面组件事件绑定到mainActivity中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.example.loginddns;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import org.json.JSONException;
import org.json.JSONObject;


public class MainActivity extends AppCompatActivity {

//控件
private TextView et_username;
private TextView et_password;
private Button btn_login;

//全局变量
private boolean password_currect = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView(); //初始化控件
initEvent(); //初始化事件
}

public void initView(){
et_username = this.findViewById(R.id.et_username);
et_password = this.findViewById(R.id.et_password);
btn_login = this.findViewById(R.id.btn_login);
}

public void initEvent(){
//给登录按钮添加点击事件(登录)
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取用户名和密码
String username = et_username.getText().toString();
String password = et_password.getText().toString();
//调用API验证用户名密码是否正确
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String url = "<花生壳提供的域名>/api/login?username=" +username + "&password=" + password;
String result = HttpUtils.getJsonContent(url);
try {
JSONObject jsonObject = new JSONObject(result);
if(jsonObject.getBoolean("result") == true) {
password_currect = true;
} else {
password_currect = false;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//登录事件
if(password_currect) {
Toast.makeText(MainActivity.this, "登录成功!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "密码错误!", Toast.LENGTH_SHORT).show();
}
}
});
}
}
1730291340307.jpg

可以看见页面正常访问到后端并且获取到了数据