Android+Java Web+MySQL實現登錄注冊


1 前言&概述

這篇文章是基於此處文章的更新,更新了一些技術棧,更加貼近實際需要,以及修復了若干的錯誤。

這是一個前端Android+后端Java/Kotlin通過Servelt進行后台數據庫(MySQL)交互的詳細步驟以及源碼實現,技術棧:

  • Android基礎
  • 原生JDBC+原生Servlet
  • Tomcat+MySQLDocker

當然現在的很多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安裝包:

在這里插入圖片描述

macOS下提供了dmg安裝包:

在這里插入圖片描述

可以戳這里下載。

Linux下一般來說MySQL安裝有如下方式:

  • 軟件包安裝(apt/apt-getyumdnfpacman等)
  • 下載壓縮包安裝
  • 源碼編譯安裝
  • 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

在這里插入圖片描述

安裝:

sudo yum install mysql-community-server

在這里插入圖片描述

3.2.2.2 啟動服務並查看初始化密碼

啟動服務:

systemctl start mysqld

查看臨時密碼:

sudo grep 'temporary password' /var/log/mysqld.log

在這里插入圖片描述

輸入臨時密碼登錄:

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安裝不難,直接從官網下載即可:

在這里插入圖片描述

解壓:

tar -zxvf apache-tomcat-10.0.0.tar.gz

進入bin目錄運行startup.sh

cd apache-tomcat-10.0.0/bin
./startup.sh

本地訪問localhost:8080

在這里插入圖片描述

這樣就算成功了。對於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,如果需要GradleKotlin的話自行勾選即可:

在這里插入圖片描述

2020.3版本的IDEA相比起以前,更加人性化的添加了選擇庫的功能,默認是選中了Servlet,需要其他庫的話自行選擇即可。

另外一個要注意的是JavaEE已經更名為JakartaEE,因此版本這里可以選擇JakartaEE

在這里插入圖片描述

填上對應包名並選擇位置:

在這里插入圖片描述

創建完成后,這里筆者遇到了一個錯誤,找不到對應的Servlet包:

在這里插入圖片描述

在設置中選擇更新中心倉庫即可:

在這里插入圖片描述

創建后的目錄如圖所示:

在這里插入圖片描述

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

  • MySQL
  • Jackson
  • Lombok

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>

這樣第一步就完成了。

5.2 結構

項目結構如下:

  • 持久層操作:Dao
  • 實體類:User
  • 響應體:ResponseBody
  • Servlet層:SignIn/SignUp/Test
  • 工具類:DBUtils
  • 啟動類:不需要,因為在Web服務器中運行

先創建好文件以及目錄:

在這里插入圖片描述

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();

一般來說:

  • selectexecuteQuery()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 Servlet

  • SingIn類用於處理登錄,調用JDBC查看數據庫是否有對應的用戶
  • SignUp類用於處理注冊,把User添加到數據庫中
  • Test為測試Servlet,返回固定字符串

先上SignIn.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/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

在這里插入圖片描述

設置Tomcat根目錄:

在這里插入圖片描述

接着在Deployment選擇+后,選擇第二個帶exploded的(當然第一個也不是不可以,不過第一個一般是發布到遠程版本,是以WAR形式的,而第二個是直接將所有文件以當前目錄形式復制到webapps下,並且在調試模式下支持熱部署):

在這里插入圖片描述

另外可以把這個路徑修改為一個比較簡單的路徑,方便操作:

在這里插入圖片描述

調試(運行不能進行熱部署):

在這里插入圖片描述

訪問localhost:8080/demoIDEA應該會自動打開)會出現如下頁面:

在這里插入圖片描述

訪問路徑下的test會出現:

在這里插入圖片描述

這樣后端就處理完成了,下面處理Android端。

6 Android

6.1 新建項目

在這里插入圖片描述

在這里插入圖片描述

6.2 依賴/權限

依賴如下:

implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.1'

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

buildFeatures{
    viewBinding = true
}

在這里插入圖片描述

viewBinding就是視圖綁定功能,以前是通過findViewById獲取對應的組件,后面就有了Butter Knife,到現在Butter Knife過期了,推薦使用view binding

在這里插入圖片描述

另外在AndroidManifest.xml中加入網絡權限:

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

還需要添加HTTP的支持,因為這是一個示例Demo就不上HTTPS了,但是目前Android的版本默認不支持,因此需要在<application>添加:

android:usesCleartextTraffic="true"

在這里插入圖片描述

6.3 項目結構

四個文件:

  • MainActivity:核心Activity
  • NetworkSettings:請求URL,常量
  • NetworkThread:網絡請求線程
  • ResponseBody:請求體

在這里插入圖片描述

6.4 ResponseBody

package com.example.androiddemo;

public class ResponseBody {
    private int code;
    private Object data;

    public int getCode() {
        return code;
    }

    public Object getData() {
        return data;
    }
}

響應體,一個返回碼字段+一個數據字段。

6.5 NetworkSettings

package 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

在這里插入圖片描述

6.6 NetworkThread

package 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<String> {
    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>接口,表示返回的是String類型的數據,主線程可通過get()阻塞獲取返回值。

6.7 MainActivity

package 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<String> 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<String> 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官方的文檔中就是:

在這里插入圖片描述

6.8 資源文件

兩個:

  • activity_main.xml
  • strings.xml

分別如下,不細說了:

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

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

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

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

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

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

    <Button
        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" />
</androidx.constraintlayout.widget.ConstraintLayout>
<resources>
    <string name="app_name">AndroidDemo</string>
    <string name="name">用戶名</string>
    <string name="password">密碼</string>
    <string name="signUp">注冊</string>
    <string name="signIn">登錄</string>
</resources>

7 測試

7.1 本地測試

首先運行Java Web端,應該會自動打開如下界面:

在這里插入圖片描述

附加test后:

在這里插入圖片描述

運行Android端,先輸入一個不存在的用戶名或密碼,提示登錄失敗,再進行注冊,然后登錄成功:

在這里插入圖片描述

同時查看后端數據庫如下:

在這里插入圖片描述

7.2 部署測試

首先確保本地數據庫的用戶名與密碼與服務器的用戶名與密碼一致。同時存在對應的表以及庫

部署Java Web端之前先在pom.xml中加入一個<finalName>

在這里插入圖片描述

在右側的工具欄先選擇clean,再選擇編譯,最后選擇打包:

在這里插入圖片描述

之所以這樣做是因為如果更新了文件,打包不會把文件更新再打包進去,因此需要先清除原來的字節碼文件,再編譯最后打包。

完成后會出現一個demo.war位於target下:

在這里插入圖片描述

scp(或其他工具)上傳到服務器,並移動到Tomcatwebapps(為了方便說明以下假設服務器的IP8.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的文件夾:

在這里插入圖片描述

訪問8.8.8.8/demo看到本地測試的頁面就可以了。接着修改Android端的NetworkSettings中的HOST8.8.8.8,如果沒問題的話就能正常訪問了:

在這里插入圖片描述

服務器數據庫:

在這里插入圖片描述

8 注意事項

注意事項比較瑣碎而且有點多,因此另開了一篇博客,戳這里

如果還有其他問題歡迎留言。

9 源碼

提供了Java+Kotlin兩種語言實現:

如果覺得文章好看,歡迎點贊。

同時歡迎關注微信公眾號:氷泠之路。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM