• 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两种语言实现:

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

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

  • 相关阅读:
    团队项目第一阶段冲刺站立会议(5月10日)
    团队项目第一阶段冲刺站立会议(5月9日)
    团队项目第一阶段冲刺站立会议(5月7日)
    课堂练习之找数字0-N中“1”出现的次数
    团队开发项目-----来用------项目风险分析
    《你的灯亮着吗》阅读笔记之第五篇与第六篇
    《你的灯亮着吗》阅读笔记之第三篇与第四篇
    《你的灯亮着吗》阅读笔记之第一篇与第二篇
    课堂练习之检测水军(拓展)
    课后作业之输入法评价
  • 原文地址:https://www.cnblogs.com/6b7b5fc3/p/14334081.html
Copyright © 2020-2023  润新知