写在前头
  我来了!这篇文章将会从数据库连接开始,完成整个后端以及前后端用Ajax关联的代码讲解,实现局域网内的《魔塔》项目。

数据库使用

MySQL数据库

  相信大家都听过MySQL数据库,如果你没有数据库这方面的基础,不妨花半个小时速成一下:视频链接。了解了数据库的基本知识之后,我们来分析项目中表的结构:
{% tabs database01 %}

  • user图像
  • id:自增长主键,记录用户的唯一id
  • username:用户名,唯一
  • password:用户密码
  • player:用户玩家角色数据,json格式保存的text文本
  • data:地图、npc等数据,json格式保存的text文本
  • 与user表结构一致,可直接复制
  • 只保存一份初始数据,游戏重玩时加载数据
  • 不能改动数据

{% endtabs %}

  按照上面的结构创建我们项目的数据库和表,你可以使用命令行创建,也可以使用图像化界面来创建。

数据库连接:JDBC

  创建好数据库和表之后,我们需要确保我们能来在后端通过Java代码来增删改查数据表中的数据,这里需要一些关于JDBC的知识,如果你没有这方面的基础知识且时间充足,不妨再花个把小时看下这些视频:JDBC;如果你已经了解过,或时间紧迫,你可以直接试着使用JDBCTemplate

数据库连接池:Druid

  “ 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。 ”

  Druid数据库连接池由阿里巴巴开发,我们可以通过简单的代码和配置可以获取到数据源DataSource,将其提供给JDBC来连接数据库,获取和修改数据库中的数据。

{% tabs druid %}

//类: src/main/java/Utils/JDBCUtils.java
//作用:根据druid.properties获取DataSource
package Utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCutils {
    public static DataSource ds;
    //类加载时启动静态初始器获取DataSource
    static {

        try {
            InputStream is = JDBCutils.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties pro = new Properties();
            pro.load(is);
            ds = DruidDataSourceFactory.createDataSource(pro);
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    
    //关键部分,获取了DataSource
    public static DataSource getDataSource() {
        return ds;
    }

}
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/your database name?serverTimezone=Asia/Shanghai
username=your username
password=your password

initialSzie=5
maxActive=10
maxWait=3000

{% endtabs %}

JDBCTemplate概述

{% note quote,   JDBCTemplate是Spring框架对JDBC的封装,目的是使JDBC更加易于使用;它处理了资源的建立和释放,帮助我们避免一些常见的错误,比如忘了总要关闭连接;它运行核心的JDBC工作流,如PreparedStatement的建立和执行,而我们只需要提供SQL语句和提取结果。 %}

JDBCTemplate使用

  有了数据源DataSource之后,我们可以使用JDBCTemplate来操作数据库,首先创建一个JdbcTemplate对象:

private JdbcTemplate template = new JdbcTemplate(JDBCutils.getDataSource());

  在JdbcTemplate中执行SQL语句的方法大致分为3类:

  • execute:可以执行所有SQL语句,一般用于执行DDL语句,无返回值。
  • update:用于执行INSERT、UPDATE、DELETE等DML语句,返回受影响记录条数。
  • queryXxx:用于DQL数据查询语句,返回值与方法有关。

  接下来我们看一个完整示例:
{% tabs Test %}

//路径:src/main/java/test/Test.java

private JdbcTemplate template = new JdbcTemplate(JDBCutils.getDataSource());

//查找id为1的用户名,?为占位符
String sql = "select username from user where id = ?";
Map<String, Object> map = template.queryForMap(sql, 1);
System.out.println(map.get("username").toString());

//修改id为1的用户名,?为占位符
String sql1 = "update user set username = ? where id = ?";
int update = template.update(sql, 1);
System.out.println(update);
//路径: src/main/java/Utils/JDBCUtils.java
//作用:根据druid.properties获取DataSource
package Utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

public class JDBCutils {
    public static DataSource ds;
    //类加载时启动静态初始器获取DataSource
    static {

        try {
            InputStream is = JDBCutils.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties pro = new Properties();
            pro.load(is);
            ds = DruidDataSourceFactory.createDataSource(pro);
        }catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    
    //关键部分,获取了DataSource
    public static DataSource getDataSource() {
        return ds;
    }

}
//路径: src/main/java/druid.properties
//上面的路径不要弄错,不然jdbcUtils类找不到该文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/your database name?serverTimezone=Asia/Shanghai
username=your username
password=your password

initialSzie=5
maxActive=10
maxWait=3000

{% endtabs %}

后端接收前端请求

  现在我们能用Java代码把数据存到数据库了,但我们还需要有从前端获取数据的方法,这里我们使用Servlet+Ajax来实现;
{% tabs 后端 %}

  我们在js文件中使用jQuery的Ajax能向后端发送数据,值得注意的的是你需要先引入jquery.js;
示例:

function fun(){
  $.ajax({
    type: "post", //请求的方式  
    url: "../testServlet", //接收请求的servlet  
    data: {  
        "id": 1  //请求参数  
    },    
    async:true, // 是否异步   
    dataType: 'text', // 返回对象类型  
    success: function(data) {  
         console.log(data);  
    }  
})

除此之外,还有两种方法:

function fun(){
  $.ajaxSettings.async = true/false;//是否异步
  
  $.post("../testServlet",{ id:1 },function (data) { alert(data); },"text")
  
  //$.get同上
}

  Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
  使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,动态创建网页。
   在servlet3.0以后,我们可以不用再在web.xml里面配置servlet,只需要在类前加上@WebServlet注解就可以设置servlet类属性了。
  IDEA为我们提供了servlet类的模板,你在new类时选择下方的Create new servlet 即可创建一个servlet模板,你还可以在设置中修改这个模板。

示例:servlet接收前端请求,返回“hello,Wordld”字符串

package servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/testServlet")
public class TestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String id = request.getParameter("id");
        System.out.println(id);
        response.getWriter().write("Hello,World");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

{% endtabs %}

动态功能的分析与实现

动态功能列表

  在实现了数据库的连接和前后端数据交换之后,我们列举一下网页需要哪些动态功能:

  • 注册:新用户的注册,将最主要的用户名,密码存入数据库中,id自增长,游戏数据默认空即可,注册成功后跳转至主页;
  • 登录:用户登录,用户名和密码正确则跳转至主页;
  • 主页:加载页面时检测用户是否注册或登录成功;
  • 存/读档:将用户数据 保存到数据库中/从数据库中读取。
  • 退出登录

用户注册

这个部分主要有三个需求:

用户名是否已存在

  将用户名作为请求参数发送到后端,查询数据库中是否该关键字是否存在;

输入的验证码是否正确

  验证码图片是由后端 CheckCodeDemo类随机生成的,每次生成验证码后,要将验证码字符串存入Seesion域中,图片的src为:

check_img.src = "../checkCodeDemo?"+date;

  将输入的验证码作为请求参数发送到后端,后端从seesion域读取正确的验证码对比,返回结果;

将用户名和对应密码存入数据库

  将用户名和密码作为请求参数发送到后端,后端将数据存入数据库,同时将自增长得到的id存入session域中,跳转至首页;

注册模块代码

{% tabs register %}

package servlet;


import org.springframework.jdbc.core.JdbcTemplate;
import Utils.JDBCutils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 *      功能: 检测注册时填写的用户名是否已存在
 */
@WebServlet("/userNameExistServlet")
public class UserNameExistServlet extends HttpServlet {
    private JdbcTemplate template = new JdbcTemplate(JDBCutils.getDataSource());
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String sql = "select * from user where username = ?";
        String username = request.getParameter("username");
        List<Map<String, Object>> list = template.queryForList(sql, username);
        if(list.size()!=0) response.getWriter().write("true");
        else response.getWriter().write("false");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
    <link rel="stylesheet" href="css/register.css">
    <script src="js/lib/jquery-3.0.0.min.js"></script>
    <script src="js/register.js"></script>
</head>
<body>

<div class="rg_layout">
    <div class="rg_left">
        <p>新用户注册</p>
        <p>USER REGISTER</p>

    </div>

    <div class="rg_center">
        <div class="rg_form">
            <!--定义表单 form-->
            <form action="javascript:void(0)" id="form" method="post">
                <table>
                    <tr>
                        <td class="td_left">
                            <sapn class="error">*</sapn>
                            <label for="username">用户名</label>
                        </td>
                        <td class="td_right">
                            <input type="text" name="username" id="username" placeholder="请输入用户名">
                            <span id="s_username" class="error"></span>
                        </td>

                    </tr>

                    <tr>
                        <td class="td_left">
                            <sapn class="error">*</sapn>
                            <label for="password">密码</label>
                        </td>
                        <td class="td_right">
                            <input type="password" name="password" id="password" placeholder="请输入密码">
                            <span id="s_password" class="error"></span>
                        </td>
                    </tr>

                    <tr>
                        <td class="td_left"><label for="email">Email</label></td>
                        <td class="td_right"><input type="email" name="email" id="email" placeholder="请输入邮箱"></td>
                    </tr>

                    <tr>
                        <td class="td_left"><label for="name">姓名</label></td>
                        <td class="td_right"><input type="text" name="name" id="name" placeholder="请输入姓名"></td>
                    </tr>

                    <tr>
                        <td class="td_left"><label for="tel">手机号</label></td>
                        <td class="td_right"><input type="text" name="tel" id="tel" placeholder="请输入手机号"></td>
                    </tr>

                    <tr>
                        <td class="td_left"><label>性别</label></td>
                        <td class="td_right">
                            <input type="radio" name="gender" value="male"> 男
                            <input type="radio" name="gender" value="female"> 女
                        </td>
                    </tr>

                    <tr>
                        <td class="td_left"><label for="birthday">出生日期</label></td>
                        <td class="td_right"><input type="date" name="birthday" id="birthday" placeholder="请输入出生日期">
                        </td>
                    </tr>

                    <tr>
                        <td class="td_left"><label for="checkcode">验证码</label></td>
                        <td class="td_right"><input type="text" name="checkcode" id="checkcode" placeholder="请输入验证码">
                            <img id="img_check" src="../checkCodeDemo">
                        </td>
                    </tr>


                    <tr>
                        <td colspan="2"  id="td_sub"><input type="submit" id="btn_sub" value="注册"><span id="loading"style="margin-left: 5px"></span></td>
                    </tr>
                </table>

            </form>


        </div>

    </div>

    <div class="rg_right">
        <p>已有账号?<a href="login.html">立即登录</a></p>
    </div>


</div>


</body>
</html>
window.onload = function(){
    document.getElementById("form").onsubmit = function(){
        var flag = checkUsername() && checkPassword() && checkCode();
        if(flag == false) return flag;
        var username = $("#username").val();
        var password = $("#password").val();
        $.ajaxSettings.async = false;
        $.post("../registerServlet",{username:username,password:password},function () {
            alert("注册成功!");
            window.location.href="index.html";
        })
        $.ajaxSettings.async = true;
        return flag;
    }

    document.getElementById("username").onblur = checkUsername;
    document.getElementById("password").onblur = checkPassword;
    var check_img = document.getElementById("img_check");
    check_img.onclick = function () {
        var date = new Date().getTime();
        check_img.src = "../checkCodeDemo?"+date;
    }
}

function clickcode() {
    var check_img = document.getElementById("img_check");
    var date = new Date().getTime();
    check_img.src = "../checkCodeDemo?"+date;
}

function checkUsername(){
    var username = document.getElementById("username").value;
    var reg_username = /^\w{6,12}$/;
    var flag = reg_username.test(username);
    var s_username = document.getElementById("s_username");
    if(flag){
        s_username.innerHTML = "<img height='20' width='20' src='image/loading.gif'>";
        $.post("../userNameExistServlet",{username:username},function (data) {
            if(data=="true"){
                s_username.innerHTML = "用户名已存在";
            }
            else{
                s_username.innerHTML = "<img height='25' width='35' src='image/gou.png'>";
            }
        },"text");
    }else{
        s_username.innerHTML = "用户名6-12位";
    }
    return flag;
}

function checkPassword(){
    var password = document.getElementById("password").value;
    var reg_password = /^\w{6,12}$/;
    var flag = reg_password.test(password);
    var s_password = document.getElementById("s_password");
    if(flag){
        s_password.innerHTML = "<img height='25' width='35' src='image/gou.png'>"
    }else{
        s_password.innerHTML = "密码6-12位";
    }
    return flag;
}

function checkCode() {
    var flag = true;
    var code = $("#checkcode").val();
    $.ajaxSettings.async = false;
    $("#loading").html("<img height='20' width='20' src='image/loading.gif'>");
    $.post("../verifyCodeServlet",{code:code},function (data) {
        if(data == "false"){
            alert("验证码错误!");
            clickcode();
            flag = false;
        }
    },"text")
    $("#loading").html("");
    $.ajaxSettings.async = true;
    return flag;
}
package servlet;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;


/**
*       功能: 生成验证码图片,将验证码数据存入seesion域中,然后返回验证码图片给前端!
*
* */

@WebServlet("/checkCodeDemo")
public class CheckCodeDemo extends HttpServlet {
    private String Code; //验证码信息
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Code = "";
        int width = 140;
        int height = 60;

        //画一幅140*60的空白图
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        Graphics G = image.getGraphics();
        G.setColor(Color.pink);
        G.fillRect(0,0,width,height);
        G.setColor(Color.BLUE);
        G.drawRect(0,0,width-1,height-1);

        //在下面字符串中随机选取4位作为验证码,制成验证码图片
        String str = "ABCDEFGHYJKLMNOPQRSTUVWXYZabcdefghyjklmnoqrstuvwxyz0123456789";
        Random ran = new Random();
        G.setFont(new Font("宋体",Font.BOLD,40));
        for(int i=1;i<=4;i++){
            int index = ran.nextInt(str.length());
            Code += str.charAt(index)+"";
            G.drawString(str.charAt(index)+"", width/5*i, height/2);
        }

        //将验证码信息存入seesion域中
        HttpSession session = request.getSession();
        session.setAttribute("code",Code);

        G.setColor(Color.DARK_GRAY);

        //设置干扰线,防止机器识别!!
        Graphics2D G2d = (Graphics2D)G;
        G2d.setStroke(new BasicStroke(2.0f));
        for(int i=0;i<6;i++){
            int x1 = ran.nextInt(width);
            int x2 = ran.nextInt(width);
            int y1 = ran.nextInt(height);
            int y2 = ran.nextInt(height);
            G2d.drawLine(x1,y1,x2,y2);
        }

        //返回图片给前端
        response.setContentType("image/gif");
        ImageIO.write(image,"jpg",response.getOutputStream());

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request,response);
    }
}

package servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;


/**
 *      功能: 验证验证码是否正确模块
 */
@WebServlet("/verifyCodeServlet")
public class VerifyCodeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        Object code = session.getAttribute("code");
        String code_register = request.getParameter("code");
        String code_answer = code.toString().toLowerCase();
        code_register = code_register.toLowerCase();
        System.out.println(code_register + "-------" + code_answer);
        if(code_register.equals(code_answer))
            response.getWriter().write("true");
        else
            response.getWriter().write("false");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

package servlet;


import org.springframework.jdbc.core.JdbcTemplate;
import Utils.JDBCutils;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;
import java.util.Map;


/**
 *      功能: 根据前端请求参数来进行注册操作
 */
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
    private JdbcTemplate template = new JdbcTemplate(JDBCutils.getDataSource());
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        //插入数据
        String sql = "insert into user(username, password) values (?,?)";
        int update = template.update(sql, username, password);
        sql = "select id from user where username = ?";
        List<Map<String, Object>> list = template.queryForList(sql, username);
        HttpSession session = request.getSession();
        //跳转到首页前设置seesion中id信息
        session.setAttribute("id", list.get(0).get("id"));
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

{% endtabs %}

登录

  修改一下注册模块的代码即可。

主页

  页面加载完成时,发送请求至后端,后端返回seesion域中的id,若id为0,则用户为游客,否则根据用户id查找用户名,并显示在页面上。

存/读档

  在js中,我们可以用以下代码将对象转换成json格式的字符串:

var playerString = JSON.stringify(player);

将这类字符串作为请求参数发送到后端,以text文本的类型存入数据库;

退出登录

  退出登录只需发送请求给后端,后端将session域中的id值置零即可;

启动Tomcat,调试验收

  其实这一步在完成每一个小功能时就应该进行,例如验证码,登录等等,检查是否符合功能需求,在浏览器按F12打开控制台能更好的调试检测错误;如果你启动Tomcat的设备在局域网中,那么局域网内的另一个设备也可以通过一样的网址访问你的《魔塔》(比如同一个热点下,同一个wifi下)

思考与讨论

{% span logo green h4, 1. web项目没有写main函数,程序入口在哪里?%}
  在Tomcat服务器中由对应的main函数,服务器启动后程序一直运行着,下面这个是Tomcat的结构图:
Tomcat内部结构图
  *“一个Connecter将在某个指定的端口上侦听客户请求,接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理Engine(Container中的一部分),从Engine出获得响应并返回客户。”***
  总的来说,Tocmat里的Connector会趴在8080端口,我要登录了,发个请求给8080端口,被connector听到了,它
分配线程**给Container处理,然后再返回得到的结果;
{% span logo cyan h4, 2. web项目中没有创建过servlet的实例对象,怎么用到servlet的?%}
  这个问题的答案其实在上一个问题也稍微提到过了,servlet类的对象交给Tomcat容器创建了,这是IOC(控制反转)的思想,我们把类定义好,创建对象的权利从我们手上交给了容器,这样能实现一定程度的解耦合,虽然在我们这个小项目里没怎么体现出来。

写在最后

  这一篇的内容实在太多啦,我也没有办法全部细讲,挑了一些例子讲了以下,不是很明白的小伙伴可以先看一下B站上的基础知识视频:javaweb,这个项目已经是挺久之前写的啦,有一些地方连我都有些遗忘了,所以哪怕学过了的及时复习一下也是有必要滴😀
  下一篇文章讲部署到服务器上,不难实现但是步骤繁琐,大家也可以先了解一下Linux命令行(我用的是Ubuntu系统),那我们下次再见!

Q.E.D.


记录 • 分享 • 日常