【更新中】-bupt-web简易的联系人管理系统制作-Spring+Thymeleaf

4 篇文章 0 订阅
2 篇文章 0 订阅

在这里插入图片描述

Spring MVC基于注解器的开发模式

在Spring boot中,嵌入了Tomcat等Servlet容器,同时支持尽可能的自动配置,因此当创建的Spring boot项目添加了spring-boot-starter-web依赖时(用spring initializr创建时勾选了web),在启动后会对dispatchservlet进行自动配置。

控制器的意义

Controller实现类包含了对用户请求的处理逻辑,是用户请求和业务逻辑之间的“桥梁”。
Spring MVC支持基于注解的控制器,无需再配置文件中部署映射,不必扩展基类,也不需要实现特定的接口。极大简化了Web应用的开发。
常见的注解:

  1. @Controller,配置在类上,表明类是Spring MVC中的Controller,Spring MVC会自动扫描注解了此注解的类。
  2. @RequestMapping,可以配置在类和方法上,用来指定类或者方法是针对哪个URI的处理器。一旦声明时RequestMapping,无论时GET还是POST请求都可以被拦截下来
  3. @ResponseBody,表示将返回值放在response体内,而不是返回一个页面。
  4. @RestController,是一个组合注解,组合了@Controller和@ ResponseBody,当后端只返回数据时(如json),就可以使用此注解。
  5. @PathVariable,用来接收路径参数,此注解放置在参数前。
  6. @SessionAttribute:用在处理器的类上,用于在多个请求之间传递参数,类似于Session的Attribute,但不完全一样,一般来说@SessionAttribute设置的参数只用于暂时的传递,而不是长期的保存
  7. @ModelAttribute:
    1. 用于方法上时:通常用来在处理@RequestMapping之前,为请求绑定需要从后台查询的model;
    2. 用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数bean上;要绑定的值来源于:
      1. @SessionAttributes 启用的attribute 对象上;
      2. @ModelAttribute 用于方法上时指定的model对象;
      3. 上述两种情况都没有时,new一个需要绑定的bean对象,然后把request中按名称对应的方式把值绑定到bean中。

Spring工作流程

这里我只说下最最最常用的(题里用最多的)
controller:
在这里插入图片描述
总体来说,每个请求都会被Dispatcher Servlet获取(是的,一个单纯的Servlet工程其实很冗杂,而且单纯的Servlet工程效率并不高),之后会根据拦截到的url指派一些注解器服务。

一个Controller一般会以一个模型(Model)作为返回对象,所谓模型其实就可以理解为一个“对象”,比如一个变量啊,或者自定义的一个Java类啊都算是一个模型。模型返回给Dispatcher后一般就会交付给一个View进行处理,所谓View其实就可以理解成前端的东西,例如一个html文件。在这一步中,模型会被“渲染”,即通过前端接收用户自己的一些操作从而改写模型的一些数据,进而用户可以请求更多的服务。

一般情况下我们可以认为一个Spring工程时默认有一个Dispatcher Servlet的,各个注解器的拦截安排就算是Dispatcher Servlet的工作。

Thymeleaf

Thymeleaf :Java服务端的模板引擎,不新增标签,采用拓展属性(th:xx)去跟服务端进行数据交互,保留原始页面风格,使用浏览器直接打开,相当于打开原生页面,简洁漂亮、容易理解,完美支持HTML5,给前端人员也带来一定的便利。

Thymeleaf基础语法

  1. 在这里插入图片描述

  2. 在这里插入图片描述

  3. 在这里插入图片描述

  4. 在这里插入图片描述

  5. 在这里插入图片描述

  6. 在这里插入图片描述

  7. 在这里插入图片描述

基于Thymeleaf新建Spring项目

点击新建,选择Spring Initializer
在这里插入图片描述
等一下网络连接,之后包和Artifact随便起个名字就行
在这里插入图片描述
至少选择Spring Web和Thymeleaf

之后开始等待。。。系统会动态给你选择一个Maven版本并自动安装
这里需要注意一个技巧,即镜像的配置。因为IDEA毕竟不是国内的软件,Spring Boot也一样,所以下载很多东西其实都是在访问外网,有时候会不经意间卡死。所以,可以借助国内一些很棒的镜像做这件事,以更快配置好我们的工程环境。

  1. 小伙伴们可以以此为参考找一下该目录下的settings.xml
    在这里插入图片描述
  2. 打开settings.xml,在mirrors那一段标签内容里加上
	<mirror>
        <id>alimaven</id>
        <mirrorOf>central</mirrorOf>
        <name>aliyun maven</name>
        <url>https://maven.aliyun.com/repository/public/</url>
    </mirror>

在这里插入图片描述
这样配置的话最好配好后重启一下IDEA,之后的下载会顺畅很多。

编写工程

工程结构

根据题目要求以及关于Spring Boot基本知识,先暂时定一个工程框架出来:

那么同时我也决定将本工程的核心设置在对Controller和其Service的管理上。
总体思路如下:
在这里插入图片描述

登录服务

关于登录服务,基本的思路就是:

  1. 设置一个表单类,可以用来记录用户名、密码
  2. url访问login操作时会被捕获,并触发login相关的Service。在该Service中,会以该Java类作为Model交给前端处理
  3. 前端渲染的方式可以利用Tymeleaf将输入信息和类绑定

LoginController

提供的服务如上述。该控制器注解器核心任务是:

  1. 拦截一些GET请求,在用户未登录时返回登录界面以及一个可修改的Model;已登陆时直接重定向到联系人列表
  2. 拦截login.html内的form表单以POST提交的数据(Model),如果用户名和密码符合规范则予以通过,进入联系人列表;否则,设置错误信息,将当前Model重新交由login.html渲染
//引用的类和包略
@Controller
public class LoginController {
    //直接访问/login或直接进入8080端口
    @RequestMapping({"/","/login"})
    public String login(LoginInfo user, Model model, HttpServletRequest request){
        Object flag = request.getSession().getAttribute("login");
        //未登录,交给login.html渲染
        if(null == flag){
            //准备一个模型的数据,将模型的数据加载回login.html中
            flag=model.getAttribute("user");
            if(null == flag){
                model.addAttribute("user",user);
            }
            //返回login.html进行渲染
            return "login";
        }
        //已经登录,直接重定向到通讯录列表
        else
            return "redirect:/list";
    }

    //登录后的检测,通过login.html的 form action以post方式访问
    @PostMapping("/checklogin")
    public String checkLogin(LoginInfo user, Model model, HttpServletRequest request){
        //用户名密码正确,设置session中的login参数,并进入联系人表格界面
        if(user.getUsername().equals("admin") && user.getPassword().equals("123456")){
            user.setMessage("您已登录");
            request.getSession().setAttribute("login",1);
            return "redirect:/list";
        }
        //用户名或密码错误,需要重新返回登录界面,但是为了有提示信息,所以直接保留当前user,通过该结构内的message设置提示信息
        else{
            user.setMessage("用户名或密码错误!");
            user.setUsername("");
            user.setPassword("");
            return login(user,model,request);
        }
    }
    //直接从网址走到这访问,如果没登录要重定向回login,如果已经登录可以直接进入列表位
    @GetMapping("/checklogin")
    public String redirectLogin(HttpServletRequest request){
        if(null == request.getSession().getAttribute("login")){
            return "redirect:/login";
        }
        else{
            return "redirect:/list";
        }
    }
	//直接进入联系人列表的请求
    @GetMapping("/list")
    public String showMain(HttpServletRequest request){
        Object flag = request.getSession().getAttribute("login");
        if(null != flag){
            HttpSession session = request.getSession();
            //如果访问时还是一个空列表对象,就先为其分配一个空间
            if(null == session.getAttribute("table")){
                Table table = new Table();
                session.setAttribute("table",table);
            }
            return "list";
        }
        else
            return "redirect:/login";
    }
    //退出请求
    @RequestMapping("/exit")
    public String exit(HttpServletRequest request){
        request.getSession().removeAttribute("login");
        return "redirect:/login";
    }
}

LoginInfo登陆界面消息 Java类

这是一个基本的Java类,其中包含了登陆界面涉及到的用户名、密码。同时还设置了一个message变量,用于协助在login.html内显示登陆错误信息。

package myspring.struct;

import java.io.Serializable;

public class LoginInfo implements Serializable {
    private String username,password,message;

    public String getUsername(){
        return username;
    }
    public String getPassword(){
        return password;
    }
    public String getMessage(){
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setUsername(String username) {
        this.username = username;
    }
}

login.html

这就是登录界面前端,它将对传过来的模型进行渲染,即:用户在框内输入,点击提交的同时也会设置模型(LoginInfo)中的username、password,从而使得Controller可以针对模型进行一些用户校验

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--引用样式文件,位置随意-->
    <link rel="stylesheet" href="../static/login.css"/>
</head>

<body>
<!--应要求,在页面开头提示需要输入的用户名密码-->
<p>
    用户名->admin
    密码->123456
</p>
<!--form表单提交的用户名和密码会作为一个结构体LoginInfo提交,该结构体包含username和password-->
<!--传给login.html进行处理的模型是一个LoginInfo类型的变量user-->
    <form th:object="${user}" action="checklogin" method="post">
<!--field把input的内容绑定到一个变量中-->
        <table>
            <tr>
                <td><span>用户名</span></td>
                <td><input th:field="*{username}" th:text="${user.username}" type="text" id="username" required></td>
            </tr>
            <tr>
                <td><span>密 码</span></td>
                <td><input th:field="*{password}" th:text="${user.password}" type="password" id="password" pattern=".{6,}" maxlength="16" required></td>
            </tr>
        </table>
        <input type="submit" value="登录">
<!--登录错误提示信息,与user的message变量绑定,如果message是空就不会显示这个标签-->
        <span class="alert" th:if="${user.message}" th:text="${user.message}"></span>
    </form>
</body>
</html>

联系人列表操作与相关服务

关于列表服务,就是增删改查。这里关于Thymeleaf也有一个核心的应用就是其可迭代对象

changeController

package myspring.demo;

import myspring.struct.Linkman;
import myspring.struct.Table;
import myspring.struct.changeTable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

//访问添加页面、判断修改状态页面,在Session中创建和add.html传递数据的类linkman
@Controller
public class changeController {
    //通过导航栏直接访问
    @GetMapping({"add","/checkadd","/change","/checkchange","/del"})
    public String goBack(HttpServletRequest request){
        Object flag = request.getSession().getAttribute("login");
        if(null != flag){
            HttpSession session = request.getSession();
            if(null == session.getAttribute("table")){
                Table table = new Table();
                session.setAttribute("table",table);
            }
            return "redirect:/list";
        }
        else
            return "redirect:/login";
    }
    //点击"修改"按钮
    @PostMapping("/change")
    public String showChange(@ModelAttribute(value="row")Integer row, HttpServletRequest request, Model model){
        //修改会将当前列表行传过去
        Table t = (Table)request.getSession().getAttribute("table");
        //利用这一行列表实例化一个联系人对象
        Linkman linkman = t.getTable().elementAt(row);
        //将联系人对象传给change.html以便修改
        model.addAttribute("linkman",linkman);
        return "change";
    }
    //在change.html点击提交
    @PostMapping("/checkchange")
    public String checkChange(Linkman linkman, HttpServletRequest request){
        Table t = (Table)request.getSession().getAttribute("table");
        changeTable.changeElement(t,linkman);
        return "redirect:/list";
    }
}

linkman联系人 Java类

每个联系人包含姓名、电话、邮箱、住址、QQ等信息

package myspring.struct;

public class Linkman {
    private String name,tel,email,address,QQ;
    private String message;
    public Linkman(String name,String tel,String email,String address,String QQ){
        this.name = name;
        this.tel = tel;
        this.email = email;
        this.address = address;
        this.QQ = QQ;
    }
    public String getName(){ return name; }
    public String getTel() {
        return tel;
    }
    public String getEmail() {
        return email;
    }
    public String getAddress() {
        return address;
    }
    public String getQQ() {
        return QQ;
    }
    public String getMessage(){ return message; }
    public void setMessage(String message) { this.message = message; }
    public void setName(String name){ this.name = name; }
}

Table 联系人数组 Java类

主要是列表,因为在list.html中,我们使用了thymeleaf的可迭代对象,这就是根据联系人列表展现出来的

package myspring.struct;

import java.io.Serializable;
import java.util.Vector;

public class Table implements Serializable {
    private final Vector<Linkman> tableinfo;
    //对应初始化列表
    public Table(){
        tableinfo = new Vector<Linkman>();
        //可以选择对自己的列表联系人初始化一些联系人出来,也可以选择只是一个空表
//        tableinfo.add(new Linkman(
//                "怡宝的代言人连高波", "17333695199", "915799721@qq.com", "北京市海淀区北太平庄街道西土城路10号", "915799721"
//        ));
    }
    //返回当前的列表全内容
    public Vector<Linkman> getTable(){
        return tableinfo;
    }
}

list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>table</title>
    <link rel="stylesheet" href="../static/list.css"/>
    <script src="../static/list.js"></script>
</head>
<body>
    <div class="content">
        <table>
            <thead>
                <tr>
                    <td>姓名</td>
                    <td>电话</td>
                    <td>邮箱</td>
                    <td>住址</td>
                    <td>QQ</td>
                </tr>
            </thead>
            <tbody>
            <!--循环迭代对象Thymeleaf的th:each,通过传入的getTableInfo这个Vector可以将各个行展现出来-->
                <tr th:each="m:${session.table.getTable()}">
                    <td th:text="${m.getName()}"></td>
                    <td th:text="${m.getTel()}"></td>
                    <td th:text="${m.getEmail()}"></td>
                    <td th:text="${m.getAddress()}"></td>
                    <td th:text="${m.getQQ()}"></td>
                    <td style="border: 0;">
                        <button class="changebutton" onclick="change(this)">编 辑</button>
                        <button class="delbutton" onclick="del(this)">删 除</button>
                    </td>
                </tr>
            </tbody>
        </table>
        <div class="change_div">
            <button class="addbutton" onclick="add()">添 加</button>
            <button class="exit" onclick="exit()">退出系统</button>
        </div>
    </div>
</body>
</html>

add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"><!--自适应宽度和缩放-->
    <title>Add</title>
    <link rel="stylesheet" href="../static/add.css"/>
    <script src="../static/add.js"></script>
</head>
<body>
<div>
    <h1>新的联系人</h1>
    <!--form表单提交的联系人会作为一个结构体linkman提交,包含姓名 电话 邮箱 地址 QQ-->
    <form action="/checkadd" method="post" th:object="${linkman}">
        <table>
            <tr>
                <td><span>姓名</span></td>
                <td>
                    <input type="text" id="linker_Name" th:field="*{name}"
                           oninput="check('linker_Name')" pattern="[\u4e00-\u9fa5|a-zA-Z|\s*]{2,}"
                           placeholder="请输入姓名" required/>
                </td>
                <td><p id="pname">请输入姓名</p></td>
            </tr>
            <tr>
                <td><span>电话</span></td>
                <td>
                    <input type="text" id="linker_Phone" th:field="*{tel}" maxlength="11"
                           oninput="check('linker_Phone')" pattern="1\d{10}"
                           placeholder="请输入8位手机号" required/>
                </td>
                <td><p id="pphone">请输入电话</p></td>
            </tr>
            <tr>
                <td><span>邮箱</span></td>
                <td>
                    <input type="email" id="linker_Email" th:field="*{email}"
                           oninput="check('linker_Email')" pattern="(\w-*\.*)+@(\w-?)+\.+(com|cn)+"
                           placeholder="请输入邮箱" required/>
                </td>
                <td><p id="pemail">请输入邮箱</p></td>
            </tr>
            <tr>
                <td><span>住址</span></td>
                <td>
                    <input type="text" id="linker_Address" th:field="*{address}" oninput="check('linker_Address')"
                           autocomplete="off" maxlength="50" pattern=".+"
                           placeholder="请输入住址" required/>
                </td>
                <td><p id="paddress">请输入住址</p></td>
            </tr>
            <tr>
                <td><span>QQ</span></td>
                <td>
                    <input type="text" id="linker_QQ" th:field="*{QQ}" oninput="check('linker_QQ')"
                           maxlength="10" pattern="[0-9]{8,10}"
                           placeholder="请输入QQ号" required/>
                </td>
                <td><p id="pqq">请输入QQ</p></td>
            </tr>
        </table>
        <br>
        <input type="submit" id="sub" value="提交"
               style="margin: 0 auto;">
        <span class="alert" th:if="${linkman.message}" th:text="${linkman.message}"></span>
    </form>
</div>
</body>
</html>

change.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"><!--自适应宽度和缩放-->
    <title>change</title>
    <link rel="stylesheet" href="../static/add.css"/>
    <script src="../static/add.js">
    </script>
</head>
<body>
<div>
    <h1>修改联系人</h1>
    <form action="/checkchange" method="post" th:object="${linkman}">
        <table>
            <tr>
                <td><span>姓名</span></td>
                <td>
                    <input type="text" id="linker_Name" th:field="*{name}"
                           oninput="check('linker_Name')" pattern="[\u4e00-\u9fa5|a-zA-Z|\s*]{2,}"
                           placeholder="请输入姓名" required readonly/>
                </td>
                <td><p id="pname">姓名</p></td>
            </tr>
            <tr>
                <td><span>电话</span></td>
                <td>
                    <input type="text" id="linker_Phone" th:field="*{tel}" maxlength="11"
                           oninput="check('linker_Phone')" pattern="1\d{10}"
                           placeholder="请输入8位手机号" required/>
                </td>
                <td><p id="pphone">请输入电话</p></td>
            </tr>
            <tr>
                <td><span>邮箱</span></td>
                <td>
                    <input type="email" id="linker_Email" th:field="*{email}"
                           oninput="check('linker_Email')" pattern="(\w-*\.*)+@(\w-?)+\.+(com|cn)+"
                           placeholder="请输入邮箱" required/>
                </td>
                <td><p id="pemail">请输入邮箱</p></td>
            </tr>
            <tr>
                <td><span>住址</span></td>
                <td>
                    <input type="text" id="linker_Address" th:field="*{address}" oninput="check('linker_Address')"
                           autocomplete="off" maxlength="50" pattern=".+"
                           placeholder="请输入住址" required/>
                </td>
                <td><p id="paddress">请输入住址</p></td>
            </tr>
            <tr>
                <td><span>QQ</span></td>
                <td>
                    <input type="text" id="linker_QQ" th:field="*{QQ}" oninput="check('linker_QQ')"
                           maxlength="10" pattern="[0-9]{8,10}"
                           placeholder="请输入QQ号" required/>
                </td>
                <td><p id="pqq">请输入QQ</p></td>
            </tr>
        </table>
        <br>
        <input type="submit" id="sub" value="提交修改"
               style="margin: 0 auto;">
    </form>
</div>
</body>
</html>

最终效果

成功/错误的登录

添加联系人

编辑联系人

附:关于外部引用的js文件

CSS的样式大家可以根据自己喜好来,js的逻辑见下:

add.js

这个时add.html和change.html通用的,因为逻辑时一模一样的

function check(id)
{
    var elem = document.getElementById(id);     //获取需要检查的标签元素
    var content = elem.value;                   //获取该标签内容
    var temp = null;
    var pattern = elem.pattern;                 //获取该标签已经设置好的正则匹配规则
    var regex = new RegExp('^' + pattern + '$'); //将pattern加上头尾标识
    var match = regex.exec(content);              //进行正则匹配

    if (id == 'linker_Name')
        temp = document.getElementById('pname');
    else if (id == 'linker_Phone')
        temp = document.getElementById('pphone');
    else if (id == 'linker_Email')
        temp = document.getElementById('pemail');
    else if (id == 'linker_Address')
        temp = document.getElementById('paddress');
    else if (id == 'linker_QQ')
        temp = document.getElementById('pqq');
    //内容变为空
    if ("" == content && temp!=null)
    {
        temp.innerHTML = "?";
        temp.style.color = "#FFA500";
    }
    //匹配成功
    else if (null != match && temp!=null)
    {
        temp.innerHTML = "√";
        temp.style.color = "#00FF00";
    }
    //匹配失败
    else if (null == match && temp!=null)
    {
        temp.innerHTML = "×";
        temp.style.color = "#FF0000";
    }
}

list.js

点击删除、编辑时如何获取当前迭代对象的行呢?在这里可以利用JavaScript

function add() {
    var temp = document.createElement("form")
    temp.action = "/add"
    temp.method = "post"
    temp.style.display = "none"
    document.body.appendChild(temp)
    temp.submit()
}
function exit(){
    var temp = document.createElement("form")
    temp.action = "/exit"
    temp.method = "post"
    temp.style.display = "none"
    document.body.appendChild(temp)
    temp.submit()
}
function change(elem) {
    var row = elem.parentNode.parentNode.rowIndex - 1
    var temp = document.createElement("form")
    temp.action = "/change"
    temp.method = "post"
    temp.style.display = "none"
    //由于form类型的元素没办法设置value值,所以随便创建一个其他类型的元素记录,将row值加载body后面
    var current = document.createElement("textarea")
    current.name = "row"
    current.value = row.toString()
    temp.appendChild(current)
    document.body.appendChild(temp)
    temp.submit()
}

function del(elem) {
    //第一次parentNode得到td标签的位置,第二次parentNode得到tr标签的位置
    var row = elem.parentNode.parentNode.rowIndex -1
    var tr = elem.parentNode.parentNode
    var tbody = tr.parentNode
    var temp = document.createElement("form")
    temp.action = "/del"
    temp.method = "post"
    temp.style.display = "none"
    var opt = document.createElement("textarea")
    opt.name = "row"
    opt.value = row.toString()
    temp.appendChild(opt)
    document.body.appendChild(temp)
    //找到tr标签后删掉
    tbody.removeChild(tr)
    temp.submit()
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值