mysql pom.xml 5.8_Android+Java Web+MySQL实现登录注册

本文介绍了如何使用Android作为前端,后端采用Java原生Servlet与MySQL数据库交互,实现登录注册功能。涉及的技术栈包括Android、JDBC、Servlet、Tomcat、MySQL以及Docker。通过详细步骤和源码展示了如何设置环境、搭建服务器、创建数据库、编写后端处理逻辑以及Android客户端的网络请求。适合初学者理解基础的Web开发流程。
摘要由CSDN通过智能技术生成

1 前言&概述

这篇文章是基于此处文章的更新,更新了一些技术栈,更加贴近实际需要,以及修复了若干的错误。

这是一个前端Android+后端Java/Kotlin通过Servelt进行后台数据库(MySQL)交互的详细步骤以及源码实现,技术栈:Android基础

原生JDBC+原生Servlet

Tomcat+MySQL(Docker)

当然现在的很多Java后端开发都使用了Spring Boot而不是原生的Servlet,所以使用Spring Boot实现的可以笔者的另一篇文章。

尽管基于Spring Boot实现非常的简便,但是使用原生的Servlet更能理解底层的原理。另外本篇文章是偏基础向的教程,很多步骤都会比较详细而且附上了图,好了废话不说,正文开始。

2 环境Android Studio 4.1.2

IntelliJ IDEA 2020.3

MySQL 8.0.23

Tomcat 10.0

Docker 20.10.1

服务器CentOS 8.1.1911

3 环境准备

3.1 IDE准备

官网安装Android Studio+IDEA,这部分就省略了。

3.2 MySQL

3.2.1 安装概述

这里的MySQL若无特殊说明指的是MySQL Community。

首先,在Windows下,MySQL提供了exe安装包:

6689a680eff1cc24f259be9639014b1f.png

macOS下提供了dmg安装包:

07fb888a2e579fa9534e3809f0cee801.png

可以戳这里下载。

Linux下一般来说MySQL安装有如下方式:软件包安装(apt/apt-get、yum、dnf、pacman等)

下载压缩包安装

源码编译安装

Docker安装

其中相对省事的安装方式为Docker安装以及软件包安装,其次是压缩包方式安装,特别不建议源码安装(当然如果喜欢挑战的话可以参考笔者的一篇编译安装8.0.19以及编译安装8.0.20)。

3.2.2 安装开始

这里笔者本地测试选择的是使用Docker安装,步骤可以查看这里。

另外对于服务器,也可以使用Docker安装,如果使用软件包安装的话,这里以笔者的CentOS8为例,其他系统的参考如下:

3.2.2.1 下载并安装

添加仓库:sudo yum install https://repo.mysql.com/mysql80-community-release-el8-1.noarch.rpm

禁用默认MySQL模块(CentOS8中会包含一个默认的MySQL模块,不禁用的话没办法使用上面添加的仓库安装):sudo yum module disable mysql

5240b109b0e9877a3088263f68d8f2c1.png

安装:sudo yum install mysql-community-server

e1f831fc67d7cfcc5b2d22c3bcf764ee.png

3.2.2.2 启动服务并查看初始化密码

启动服务:systemctl start mysqld

查看临时密码:sudo grep 'temporary password' /var/log/mysqld.log

2d7bcd5aa0cb51c72c2ba3c4597c0d08.png

输入临时密码登录:mysql -u root -p

修改密码:alter user 'root'@'localhost' identified by 'PASSWORD'

3.2.2.3 创建外部访问用户

不建议在Java中直接访问root用户,一般是新建一个对应权限的用户并进行访问,这里就为了方便就省略了。

3.3 Tomcat

3.3.1 本地Tomcat

Tomcat安装不难,直接从官网下载即可:

3d6b8e2910f373497345ba097c279768.png

解压:tar -zxvf apache-tomcat-10.0.0.tar.gz

进入bin目录运行startup.sh:cd apache-tomcat-10.0.0/bin

./startup.sh

本地访问localhost:8080:

feea2b8cfb99a3f4d6c8dcf5c5390ef8.png

这样就算成功了。对于Windows的读者,可以戳这里下载,解压步骤类似,解压后运行startup.bat即可访问localhost:8080。

3.3.2 服务器Tomcat

服务器的话可以直接使用wget安装:wget https://downloads.apache.org/tomcat/tomcat-10/v10.0.0/bin/apache-tomcat-10.0.0.tar.gz

但是这样速度很慢,建议下载到本地再使用scp上传:scp apache-tomcat-10.0.0.tar.gz username@xxx.xxx.xxx.xxx:/

一样按照上面的方法解压后运行startup.sh,访问公网IP:8080即可观察是否成功。

4 建库建表

4.1 用户表

这里使用到的MySQL脚本如下:CREATE DATABASE userinfo;

USE userinfo;

CREATE TABLE user

(

id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,

name CHAR(30) NULL,

password CHAR(30) NULL

)

4.2 导入mysql -u root -p < user.sql

5 后端部分

因为是比较基础向的教程,所以先从创建项目开始吧。

5.1 创建项目+导库

选择对应Java Enterprise,默认是选中了其中的Web application,构建工具默认Maven,测试工具JUnit,如果需要Gradle或Kotlin的话自行勾选即可:

2036f56c2f90fc821b0c6d2ddba7b551.png

2020.3版本的IDEA相比起以前,更加人性化的添加了选择库的功能,默认是选中了Servlet,需要其他库的话自行选择即可。

另外一个要注意的是JavaEE已经更名为JakartaEE,因此版本这里可以选择JakartaEE:

cbab9f51e60206c4e872f7938e6cfa28.png

填上对应包名并选择位置:

a6f67f7c2a738c362509ef16bd460970.png

创建完成后,这里笔者遇到了一个错误,找不到对应的Servlet包:

8e08901a5de5edc44c07cc773560c927.png

在设置中选择更新中心仓库即可:

4edd797354021c6f222ddd7cf77a4ae4.png

创建后的目录如图所示:

1d0f3bdea77f9914f45bb27dedc15d39.png

接着添加依赖,用到的依赖包括:MySQL

Jackson

Lombok

添加到pom.xml中即可(注意版本,MySQL不同版本可以查看这里):

mysql

mysql-connector-java

8.0.23

org.projectlombok

lombok

1.18.16

provided

com.fasterxml.jackson.core

jackson-databind

2.12.1

这样第一步就完成了。

5.2 结构

项目结构如下:持久层操作:Dao

实体类:User

响应体:ResponseBody

Servlet层:SignIn/SignUp/Test

工具类:DBUtils

启动类:不需要,因为在Web服务器中运行

先创建好文件以及目录:

cd7dc7af4984bcb3101f9673cdd91584.png

5.3 DBUtils

原生JDBC获取连接工具类:package com.example.javawebdemo.utils;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

public class DBUtils {

private static Connection connection = null;

public static Connection getConnection() {

try {

Class.forName("com.mysql.cj.jdbc.Driver");

final String url = "jdbc:mysql://127.0.0.1:3306/userinfo";

final String username = "root";

final String password = "123456";

connection = DriverManager.getConnection(url, username, password);

} catch (Exception e) {

e.printStackTrace();

return null;

}

return connection;

}

public static void closeConnection() {

if (connection != null) {

try {

connection.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

}

重点在这四行:Class.forName("com.mysql.cj.jdbc.Driver");

String url = "jdbc:mysql://127.0.0.1:3306/userinfo";

String username = "root";

String password = "123456";

根据个人需要修改,注意MySQL8注册驱动与旧版的区别,旧版的是:Class.forName("com.mysql.jdbc.Driver");

5.4 User

三字段+@Getter:package com.example.javawebdemo.entity;

import lombok.Getter;

@Getter

public class User {

private final String name;

private final String password;

public User(String name, String password) {

this.name = name;

this.password = password;

}

}

5.5 Dao

数据库操作层:package com.example.javawebdemo.dao;

import com.example.javawebdemo.entity.User;

import com.example.javawebdemo.utils.DBUtils;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

public class Dao {

public boolean select(User user) {

final Connection connection = DBUtils.getConnection();

final String sql = "select * from user where name = ? and password = ?";

try {

final PreparedStatement preparedStatement = connection.prepareStatement(sql);

preparedStatement.setString(1, user.getName());

preparedStatement.setString(2, user.getPassword());

ResultSet resultSet = preparedStatement.executeQuery();

return resultSet.next();

} catch (SQLException e) {

e.printStackTrace();

return false;

} finally {

DBUtils.closeConnection();

}

}

public boolean insert(User user) {

final Connection connection = DBUtils.getConnection();

final String sql = "insert into user(name,password) values(?,?)";

try {

final PreparedStatement preparedStatement = connection.prepareStatement(sql);

preparedStatement.setString(1, user.getName());

preparedStatement.setString(2, user.getPassword());

preparedStatement.executeUpdate();

return preparedStatement.getUpdateCount() != 0;

} catch (SQLException e) {

e.printStackTrace();

return false;

} finally {

DBUtils.closeConnection();

}

}

}

两个操作:查询:存在该用户返回true,否则false

插入:添加用户

注意插入操作中使用executeUpdate()进行插入,同时使用getUpdateCount() != 0判断插入的结果,而不能直接使用return preparedStatement.execute();

一般来说:select:executeQuery(),executeQuery()返回ResultSet,表示结果集,保存了select语句的执行结果,配合next()使用

delete/insert/update:使用executeUpdate(),executeUpdate()返回的是一个整数,表示受影响的行数,即delete/insert/update修改的行数,对于drop/create操作返回0

create/drop:使用execute(),execute()的返回值是这样的,如果第一个结果是ResultSet对象,则返回true,如果第一个结果是更新计数或者没有结果则返回false

所以在这个例子中return preparedStatement.execute();

肯定返回false,不能直接判断是否插入成功。

5.6 响应体

添加一个响应体类方便设置返回码以及数据:package com.example.javawebdemo.response;

import lombok.Getter;

import lombok.Setter;

@Setter

@Getter

public class ResponseBody{

private Object data;

private int code;

}

5.7 ServletSingIn类用于处理登录,调用JDBC查看数据库是否有对应的用户

SignUp类用于处理注册,把User添加到数据库中

Test为测试Servlet,返回固定字符串

先上SignIn.javapackage com.example.javawebdemo.servlet;

import com.example.javawebdemo.dao.Dao;

import com.example.javawebdemo.entity.User;

import com.example.javawebdemo.response.ResponseBody;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.annotation.WebServlet;

import jakarta.servlet.http.HttpServlet;

import jakarta.servlet.http.HttpServletRequest;

import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/sign/in")

public class SignIn extends HttpServlet {

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {

req.setCharacterEncoding("utf-8");

resp.setCharacterEncoding("utf-8");

resp.setContentType("application/json;charset=utf-8");

String name = req.getParameter("name");

String password = req.getParameter("password");

Dao dao = new Dao();

User user = new User(name,password);

ObjectMapper mapper = new ObjectMapper();

ResponseBody body = new ResponseBody();

if (dao.select(user)) {

body.setCode(200);

body.setData("success");

} else {

body.setCode(404);

body.setData("failed");

}

mapper.writeValue(resp.getWriter(), body);

}

}

注意点:@WebServlet:定义Servlet(不加这个注解也是可以的但是需要在web.xml中手工定义Servlet),默认的属性为value,表示Servlet路径

编码:HttpServletRequest/HttpServletResponse均设置UTF8(虽然在这个例子中并不是必要的因为没有中文字符)

获取参数:request.getParameter,从请求中获取参数,传入的参数是键值

写响应体:利用Jackson,将response.getWriter以及响应体传入,接着交给mapper.writeValue进行写响应体

下面是SignUp.java,大部分代码类似:package com.example.javawebdemo.servlet;

import com.example.javawebdemo.dao.Dao;

import com.example.javawebdemo.entity.User;

import com.example.javawebdemo.response.ResponseBody;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.annotation.WebServlet;

import jakarta.servlet.http.HttpServlet;

import jakarta.servlet.http.HttpServletRequest;

import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/sign/up")

public class SignUp extends HttpServlet {

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {

req.setCharacterEncoding("utf-8");

resp.setCharacterEncoding("utf-8");

resp.setContentType("application/json;charset=utf-8");

String name = req.getParameter("name");

String password = req.getParameter("password");

Dao dao = new Dao();

User user = new User(name,password);

ResponseBody body = new ResponseBody();

ObjectMapper mapper = new ObjectMapper();

if (dao.insert(user)) {

body.setCode(200);

body.setData("success");

} else {

body.setCode(500);

body.setData("failed");

}

mapper.writeValue(resp.getWriter(), body);

}

}

测试Servlet:package com.example.javawebdemo.servlet;

import jakarta.servlet.annotation.WebServlet;

import jakarta.servlet.http.HttpServlet;

import jakarta.servlet.http.HttpServletRequest;

import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/test")

public class Test extends HttpServlet {

@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

resp.getWriter().print("Hello, Java Web");

}

}

5.8 运行

需要借助Tomcat运行,选择运行配置中的Tomcat Server:

a9d02abd2436bc40359f6e9fd745a9e4.png

设置Tomcat根目录:

4c0cc5ada9294c0bc1c3d2fa34ad3b92.png

接着在Deployment选择+后,选择第二个带exploded的(当然第一个也不是不可以,不过第一个一般是发布到远程版本,是以WAR形式的,而第二个是直接将所有文件以当前目录形式复制到webapps下,并且在调试模式下支持热部署):

9ce410bbf2ee754df1af4da5ee30285a.png

另外可以把这个路径修改为一个比较简单的路径,方便操作:

ac3b35228f41fa5ecf08e2d317baa43f.png

调试(运行不能进行热部署):

fbea82bdf519de075538e8a70c23b350.png

访问localhost:8080/demo(IDEA应该会自动打开)会出现如下页面:

1694d03cb767aa5cb35638be83d499d6.png

访问路径下的test会出现:

c13c8c4e2cbdf8f90d3bafeba743ba54.png

这样后端就处理完成了,下面处理Android端。

6 Android端

6.1 新建项目

e87aa99044a9a2be75093df6ff632bd2.png

bfbf41d365127520a54eb0a02d65a348.png

6.2 依赖/权限

依赖如下:implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.1'

在build.gradle中加上即可,另外,再加上:buildFeatures{

viewBinding = true

}

3b0a3959fd6670e410f8fbe8b9f3fbd1.png

viewBinding就是视图绑定功能,以前是通过findViewById获取对应的组件,后面就有了Butter Knife,到现在Butter Knife过期了,推荐使用view binding。

8532d3ccbe24037804aa0863b3824d6a.png

另外在AndroidManifest.xml中加入网络权限:

还需要添加HTTP的支持,因为这是一个示例Demo就不上HTTPS了,但是目前Android的版本默认不支持,因此需要在添加:android:usesCleartextTraffic="true"

7d3a8ef3beb187b218b70058b815211b.png

6.3 项目结构

四个文件:MainActivity:核心Activity

NetworkSettings:请求URL,常量

NetworkThread:网络请求线程

ResponseBody:请求体

d975647931c95e46e0c24cbff275a958.png

6.4 ResponseBodypackage com.example.androiddemo;

public class ResponseBody {

private int code;

private Object data;

public int getCode() {

return code;

}

public Object getData() {

return data;

}

}

响应体,一个返回码字段+一个数据字段。

6.5 NetworkSettingspackage com.example.androiddemo;

public class NetworkSettings {

private static final String HOST = "192.168.43.35";

private static final String PORT = "8080";

public static final String SIGN_IN = "http://"+ HOST +":"+PORT + "/demo/sign/in";

public static final String SIGN_UP = "http://"+ HOST +":"+PORT + "/demo/sign/up";

}

请求URL常量,HOST请修改为自己的内网IP,注意不能使用localhost/127.0.0.1。

可以使用ip addr/ifconfig/ipconfig等查看自己的内网IP:

1693392160f32a7c5e2416b72102f4ea.png

6.6 NetworkThreadpackage com.example.androiddemo;

import java.io.IOException;

import java.net.HttpURLConnection;

import java.net.URL;

import java.net.URLEncoder;

import java.nio.charset.StandardCharsets;

import java.util.concurrent.Callable;

public class NetworkThread implements Callable {

private final String name;

private final String password;

private final String url;

public NetworkThread(String name, String password, String url) {

this.name = name;

this.password = password;

this.url = url;

}

@Override

public String call(){

try {

//开启连接

HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();

//拼接数据

String data = "name="+ URLEncoder.encode(name, StandardCharsets.UTF_8.toString())+"&password="+URLEncoder.encode(password,StandardCharsets.UTF_8.toString());

//设置请求方法

connection.setRequestMethod("POST");

//允许输入输出

connection.setDoInput(true);

connection.setDoOutput(true);

//写数据(也就是发送数据)

connection.getOutputStream().write(data.getBytes(StandardCharsets.UTF_8));

byte [] bytes = new byte[1024];

//获取返回的数据

int len = connection.getInputStream().read(bytes);

return new String(bytes,0,len,StandardCharsets.UTF_8);

} catch (IOException e) {

e.printStackTrace();

return "";

}

}

}

发送网络请求的线程类,由于是异步操作的线程,实现了Callable接口,表示返回的是String类型的数据,主线程可通过get()阻塞获取返回值。

6.7 MainActivitypackage com.example.androiddemo;

import android.os.Bundle;

import android.view.View;

import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.androiddemo.databinding.ActivityMainBinding;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.concurrent.FutureTask;

public class MainActivity extends AppCompatActivity {

private ActivityMainBinding binding;

private final ObjectMapper mapper = new ObjectMapper();

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());

setContentView(binding.getRoot());

}

public void signIn(View view){

String name = binding.editTextName.getText().toString();

String password = binding.editTextPassword.getText().toString();

FutureTask signInTask = new FutureTask<>(new NetworkThread(name,password,NetworkSettings.SIGN_IN));

Thread thread = new Thread(signInTask);

thread.start();

try{

//get获取线程返回值,通过ObjectMapper反序列化为ResponseBody

ResponseBody body = mapper.readValue(signInTask.get(),ResponseBody.class);

//根据返回码确定提示信息

Toast.makeText(getApplicationContext(),body.getCode() == 200 ? "登录成功" : "登录失败",Toast.LENGTH_SHORT).show();

}catch (Exception e){

e.printStackTrace();

}

}

public void signUp(View view){

String name = binding.editTextName.getText().toString();

String password = binding.editTextPassword.getText().toString();

FutureTask signUpTask = new FutureTask<>(new NetworkThread(name,password,NetworkSettings.SIGN_UP));

Thread thread = new Thread(signUpTask);

thread.start();

try{

ResponseBody body = mapper.readValue(signUpTask.get(),ResponseBody.class);

Toast.makeText(getApplicationContext(),body.getCode() == 200 ? "注册成功" : "注册失败",Toast.LENGTH_SHORT).show();

}catch (Exception e){

e.printStackTrace();

}

}

}

说一下viewBinding,在onCreate中:super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());

setContentView(binding.getRoot());

通过ActivityMainBinding的静态方法获取binding,注意ActivityMainBinding这个类的类名不是固定的,比如Android官方的文档中就是:

baf565b48f2fe759449b333759442ac6.png

6.8 资源文件

两个:activity_main.xml

strings.xml

分别如下,不细说了:<?xml version="1.0" encoding="utf-8"?>

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">

android:id="@+id/textViewName"

android:layout_width="45dp"

android:layout_height="38dp"

android:layout_marginStart="24dp"

android:layout_marginTop="92dp"

android:text="@string/name"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintTop_toTopOf="parent" />

android:id="@+id/editTextName"

android:layout_width="300dp"

android:layout_height="40dp"

android:layout_marginStart="64dp"

android:layout_marginTop="84dp"

android:autofillHints=""

android:inputType="text"

app:layout_constraintLeft_toLeftOf="@id/textViewName"

app:layout_constraintTop_toTopOf="parent"

tools:ignore="LabelFor" />

android:id="@+id/textViewPassword"

android:layout_width="45dp"

android:layout_height="36dp"

android:layout_marginStart="24dp"

android:layout_marginTop="72dp"

android:text="@string/password"

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintTop_toTopOf="@id/textViewName" />

android:id="@+id/editTextPassword"

android:layout_width="300dp"

android:layout_height="40dp"

android:layout_marginStart="64dp"

android:layout_marginTop="72dp"

android:autofillHints=""

android:inputType="textPassword"

app:layout_constraintLeft_toLeftOf="@id/textViewPassword"

app:layout_constraintTop_toTopOf="@id/editTextName"

tools:ignore="LabelFor" />

android:id="@+id/buttonSignUp"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginStart="56dp"

android:layout_marginTop="32dp"

android:onClick="signUp"

android:text="@string/signUp"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toBottomOf="@+id/textViewPassword"

tools:ignore="ButtonStyle" />

android:id="@+id/buttonSignIn"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginTop="36dp"

android:layout_marginEnd="52dp"

android:onClick="signIn"

android:text="@string/signIn"

app:layout_constraintEnd_toEndOf="parent"

app:layout_constraintTop_toBottomOf="@+id/editTextPassword"

tools:ignore="ButtonStyle" />

AndroidDemo

用户名

密码

注册

登录

7 测试

7.1 本地测试

首先运行Java Web端,应该会自动打开如下界面:

26e43fd0651593c059c82fbfafc4e4ff.png

附加test后:

2fa1a21691c1da0efb2fa1870c198071.png

运行Android端,先输入一个不存在的用户名或密码,提示登录失败,再进行注册,然后登录成功:

c9c2d4c3010f69806e01020d0cc7bb80.gif

同时查看后端数据库如下:

a2d7ae9d07d69a78afffb515046a0a6d.png

7.2 部署测试

首先确保本地数据库的用户名与密码与服务器的用户名与密码一致。同时存在对应的表以及库

部署Java Web端之前先在pom.xml中加入一个:

a6862bdb6f5662c55d32d2a9e6407002.png

在右侧的工具栏先选择clean,再选择编译,最后选择打包:

4fc73af1ca9163305bb777f7e54d26d5.png

之所以这样做是因为如果更新了文件,打包不会把文件更新再打包进去,因此需要先清除原来的字节码文件,再编译最后打包。

完成后会出现一个demo.war位于target下:

05c61b2d5078cc5d3685866517b3c615.png

scp(或其他工具)上传到服务器,并移动到Tomcat的webapps(为了方便说明以下假设服务器的IP为8.8.8.8):scp demo.war 8.8.8.8/xxx

# 通过ssh连接服务器后

cp demo.war /usr/local/tomcat/webapps

启动Tomcat:cd /usr/local/tomcat/bin

./startup.sh

启动后就可以看见在webapps下多了一个demo的文件夹:

d11a121b8319a81f350ea566c27572db.png

访问8.8.8.8/demo看到本地测试的页面就可以了。接着修改Android端的NetworkSettings中的HOST为8.8.8.8,如果没问题的话就能正常访问了:

cf368f1fa8fa98e82929a2eece4a8be8.gif

服务器数据库:

d2eda1eda3e80bc176462d232e14f9e9.png

8 注意事项

注意事项比较琐碎而且有点多,因此另开了一篇博客,戳这里。

如果还有其他问题欢迎留言。

9 源码

提供了Java+Kotlin两种语言实现:

如果觉得文章好看,欢迎点赞。

同时欢迎关注微信公众号:氷泠之路。

42e49f96ab169116f477613fd5d41eae.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值