Spring Boot+Thymeleaf+MyBatis--推荐一个后端练手极佳的商城项目

发布时间 2023-10-13 00:00:36作者: 码dao成功

项目整体架构

    newbee-mall
        ├── src/main/java
            └── ltd.newbee.mall
                    ├── common // 存放相关的常量配置及枚举类
                    ├── config // 存放 web 配置类
                    ├── controller // 存放控制类,包括商城端和后台管理系统中的 controller 类
                            ├── admin // 存放后台管理系统中的 controller 类
                            ├── common // 存放通用的 controller 类
                            └── mall // 存放商城端的 controller 类
                    ├── dao // 存放数据层接口
                    ├── entity // 存放实体类
                    ├── interceptor // 存放拦截器
                    ├── service // 存放业务层方法
                    ├── util // 存放工具类
                    └── NewBeeMallApplication // Spring Boot 项目主类
        ├── src/main/resources
            ├── mapper // 存放 MyBatis 的通用 Mapper文件
            ├── static // 默认的静态资源文件目录
                    ├── admin // 存放后台管理系统端的静态资源文件目录
                    └── mall // 存放商城端的静态资源文件目录
            ├── templates
                    ├── admin // 存放后台管理系统端页面的模板引擎目录
                    └── mall // 存放商城端页面的模板引擎目录
            ├── application.properties // 项目配置文件
            ├── newbee_mall_schema.sql // 项目所需的 SQL 文件 
            └── upload.zip // 商品图片
        └── pom.xml // Maven 配置文件

运行项目

商城项目包括后台管理系统和商城系统,在启动成功后,两个系统都可以正常访问。

商城系统的访问地址如下,键入任何一个地址都能够访问到商城首页。

①BASE_URL

②BASE_URL + '/'

③BASE_URL + /index

④BASE_URL + /index.html

后台管理系统的访问地址如下,键入任何一个地址都能够进入到商城后台管理系统。

①BASE_URL + '/admin'

②BASE_URL + '/admin/login'

③BASE_URL + '/admin/index'

其中BASE_URL为主机名+端口号,比如笔者的本地地址就是localhost:28089。

  • 账号及密码
    项目启动成功了,但是由于权限问题无法完整地体验商城的所有功能。这就需要几个测试账号来解决。该项目在表中初始化了几条用户记录和管理员记录,数据分别存储在tbnewbeemalluser表和tbnewbeemalladmin_user表中,读者也可以自行添加和修改这些记录。

    商城系统用户的测试账号及密码如下所示。

    ①账号:13700002703 密码:123456

    ②账号:13711113333 密码:shisan

    ③账号:13811113333 密码:shisan

    后台管理系统管理员的测试账号及密码如下所示。

    ①账号:admin 密码:123456

    ②账号:newbee-admin1 密码:123456

    ③账号:newbee-admin2 密码:123456

    对于两张表中存储的密码字段都是经过MD5加密后的字符串。如果想要直接通过数据库来修改密码的话,需要将密码进行MD5转换,然后将加密后的字符串放到数据表的密码这一列中。

  • 登录和管理系统登录

    商城系统的很多功能是需要登陆才能够正常使用的,比如后台管理、下单流程。如果不是商城的用户或者管理员,肯定没有权限,大部分功能也无法操作。这一节就演示一下登陆过程。

    首先是商城端的登录页面,打开浏览器并输入登录页面地址:

    http://localhost:28089

    如下图所示,在登录页面依次输入正确的账号密码和验证码,并点击“立即登录”按钮完成商城端的登录。这样就可以完成后续的商品下单等操作了。

    然后是商城后台管理系统端的登录页面,打开浏览器并输入登录页面地址:

    http://localhost:28089/admin/login

    如下图所示,在登录页面依次输入正确的账号密码和验证码,并点击“登录”按钮就能够进入到后台管理系统中。

    进入后台管理系统后,就可以对商品模块、轮播图模块等进行操作。

项目优点

  1. 商城系统也是一个比较复杂的系统,牵涉的技术内容比较多,而且功能点和技术栈要求也比较高,从零搭建一个商城系统,这个过程不仅考验着开发人员的技术储备丰富度,更考验着技术使用的熟练度,同时对于开发人员的系统设计能力也有要求(系统如何切分,功能点如何设计,页面结构和交互如何优化等),这些技术栈的掌握程度和项目整体的统筹规划都在一定程度上代表着一个技术人员的能力。

    开发和统筹一个完整的大型商城系统往往要求技术人员了解很多不同的技术或者框架,比如常用的前端页面模板和基本的 Web 开发知识、后端开发技术框架(如 Spring Boot 、模板引擎、ORM 框架等)、服务器基础设施(如基础的 shell 命令,Nginx 、 MySQL 等常用软件的搭建和使用)都需进行全局考虑和选择。

  2. 产品流程完整
    横向来梳理一个商城系统的产品流程,从零到正式上线访问所涉及的项目环节如下,产品设计、原型设计、功能开发、功能测试、产品上线、后期维护等环节缺一不可,开发人员可能不太关注完整的流程,但是能够掌握整个流程的基本脉络对于日后的提升会有极大的帮助。

项目功能

  • 首页
    用户进入商城首页后,首先会看到管理员设置的分类信息和轮播图,之后是配置的热销商品等模块,这些数据都是在后台管理系统中配置好的运营数据,在商城端直接读取并且显示到页面即可。

  • 浏览
    接下来用户可以选择直接浏览商品信息,也可以根据分类或者关键字去搜索商品,这是用户的浏览行为。

  • 登录和注册
    在浏览时我们需要判断用户是否登录,这里就牵涉到商城用户的注册和登录功能了。如果不是商城的注册用户,很多行为是会被限制掉的,此时在商城浏览的用户可以选择填写信息并注册为我们的商城用户,之后就能够登录并且使用商城端的所有功能。

  • 选择商品
    登录用户可以在商城网站上任性壕气的浏览、挑选商品,此时就引出了商城的购物车功能,用户可以将商品放到购物车中,可以增加、删除、修改购物车中的商品以及商品数量,这些功能点已经在新蜂商城系统中实现。

  • 提交订单
    壕气的用户终于要下手了,在确定了需要购买的商品和数量后,就可以执行提交订单的操作,仅仅选择商品是不行的,我们还需要把用户的收货信息给记录下来。以上这些信息都处理完毕后,就到了订单环节,此时的用户点击结算按钮,我们的系统中也就对应的生成了一笔订单数据。

  • 订单流程
    用户在成功的提交订单后,就可以正式的进入到订单操作环节,此时可以选择三个操作:
    此时的订单尚未支付,选择去支付流程
    下单玩玩,不去管支付流程,继续购物或者退出系统
    不想买了或者商品拍错,选择取消订单
    以上就是订单模块的流程,包括订单列表功能、订单详情功能、订单取消功能、选择支付方式、支付功能。

  • 确认收货
    这是订单流程的最后一个环节,此环节需要商家来配合,用户在支付成功之后订单就进入到商家的管理系统中进行后续的处理,在操作了拣货和配送两个订单状态之后,用户就能够在订单详情页面点击确认收货,此时订单的正向流程就顺利完结了。
    因为还有取消订单和关闭订单等操作,所以我们把下单->支付->确认收货这个流程称为正向流程。

后端

界面如何设计?

项目亮点:

本章将会介绍在网页开发中常用的验证码功能,并具体讲解如何使用Spring Boot生成验证码并进行后续的验证操作。--验证码功能

Spring Boot整合easy-captcha生成验证码
生成验证码的方式和案例有很多,在新蜂商城项目中验证码的形式为传统输入式验证码,所选择的实现方案是easy-captcha工具包。

easy-captcha开源地址为:

github.com/whvcse/Easy…

它是一款国人开发的验证码工具,支持GIF、中文、算术等类型,可用于Java Web等项目,生成的验证码形式如下图所示。

接下来将会通过一个代码案例来讲解如何使用Spring Boot生成验证码并在网页中显示验证码。

添加easy-captcha依赖

Spring Boot整合easy-captcha的第一步就是增加依赖。首先需要将easy-captcha的依赖配置文件增加到pom.xml文件中,此时的pom.xml文件代码如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>ltd.newbee.mall</groupId>
	<artifactId>newbee-mall</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>newbee-mall</name>
	<description>captcha-demo</description>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- 验证码 -->
		<dependency>
			<groupId>com.github.whvcse</groupId>
			<artifactId>easy-captcha</artifactId>
			<version>1.6.2</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>alimaven</id>
			<name>aliyun maven</name>
			<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

如果是在本地开发的话,只需要等待 jar 包及相关依赖下载完成即可。

asy-captcha验证码格式

easy-captcha验证码工具支持GIF、中文、算术等类型,分别通过以下几个实例对象实现:SpecCaptcha、GifCaptcha、ChineseCaptcha、ChineseGifCaptcha和ArithmeticCaptcha。

他们依次为PNG类型的静态图片验证码、GIF类型的图片验证码、中文类型的图片验证码、中文GIF类型的图片验证码和算数类型的验证码。读者可以针对自身项目的情况选择合适的验证码生成方式。一般常用SpecCaptcha和GifCaptcha两种方式来生成验证码。

// 生成验证码对象
SpecCaptcha captcha = new SpecCaptcha(130, 48, 5);
// 设置验证码的字符类型
captcha.setCharType(Captcha.TYPE_ONLY_NUMBER);

只有SpecCaptcha和GifCaptcha设置才有效果。

  • easy-captcha字体设置
    easy-captcha默认提供了10种内置字体

  • easy-captcha验证码图片输出
    这里可以选择输出为文件流,这是比较常见的处理方式。当然,也有一些Web项目会使用base64编码的图片。这两种方式easy-captcha都支持。

  • 输出base64编码如下所示:

specCaptcha.toBase64();

// 如果不想要base64的头部data:image/png;base64,加一个空的参数即可
// specCaptcha.toBase64("");
输出到文件流如下所示:

// 输出到磁盘上
FileOutputStream outputStream = new FileOutputStream(new File("/home/project/captcha.png"));
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
specCaptcha.out(outputStream);

该段代码为生成一张图片并保存到磁盘目录中,这里可以使用easy-captcha工具自带的.out()方法输出。而在开发Web项目时,则会使用Response对象的输出流进行验证码的输出,接下来会结合代码进行详细讲解。

后端逻辑实现:生成并输出验证码

在controller包中新建KaptchaController类,就可以新建一个方法。在方法里使用GifCaptcha可以生成一个PNG类型的验证码对象,并以图片流的方式输出到前端以供显示,代码如下所示:

package ltd.newbee.mall.controller;

import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class KaptchaController {

    @GetMapping("/kaptcha")
    public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        httpServletResponse.setHeader("Cache-Control", "no-store");
        httpServletResponse.setHeader("Pragma", "no-cache");
        httpServletResponse.setDateHeader("Expires", 0);
        httpServletResponse.setContentType("image/gif");

        // 三个参数分别为宽、高、位数
        SpecCaptcha captcha = new SpecCaptcha(75, 30,4);

        // 设置类型为数字和字母混合
        captcha.setCharType(Captcha.TYPE_DEFAULT);

        // 设置字体
        captcha.setCharType(Captcha.FONT_9);

        // 验证码存入session
        httpServletRequest.getSession().setAttribute("verifyCode", captcha.text().toLowerCase());

        // 输出图片流
        captcha.out(httpServletResponse.getOutputStream());
    }
}

前端逻辑实现:在页面中展示验证码

在static目录中新建kaptcha.html页面,在该页面中显示验证码,代码如下所示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>验证码显示</title>
  </head>
  <body>
    <img src="/kaptcha" onclick="this.src='/kaptcha?d='+new Date()*1" />
  </body>
</html>

这里访问后端验证码路径/kaptcha,由于验证码是图片形式,所以将其显示在img标签中。然后定义onclick方法,在点击该img标签时可以动态切换显示一个新的验证码。点击时访问的路径为'/kaptcha?d='+new Date()*1,即原来的验证码路径后面带上一个时间戳参数 d。时间戳是会变化的,所以每次点击都会是一个与之前不同的请求。如果不这样处理的话,由于浏览器的缓存机制,在点击刷新验证码后可能不会重新发送请求,将导致在一段时间内一直显示同一张验证码图片。

在编码完成后,启动Spring Boot项目。在启动成功后打开浏览器并输入验证码显示的测试页面地址:

验证码的输入验证

在验证码的显示完成后,紧接着要做的就是对用户输入的验证码进行比对和验证。

一般的做法是在后端生成验证码后,先对当前生成的验证码内容进行保存,可以选择保存在session对象中,或者保存在缓存中,或者保存在数据库中。然后,返回验证码图片并显示到前端页面。用户在识别验证码后,在页面对应的输入框中填写验证码并向后端发送请求,后端在接到请求后会对用户输入的验证码进行验证。如果用户输入的验证码与之前保存的验证码不相等的话,则返回“验证码错误”的提示消息且不会进行后续的流程,只有验证成功才会继续后续的流程。

  • 后端逻辑实现

在KaptchaController类中新增verify()方法,代码如下所示:

@GetMapping("/verify")
@ResponseBody
public String verify(@RequestParam("code") String code, HttpSession session) {
  if (!StringUtils.hasLength(code)) {
    return "验证码不能为空";
  }
  String kaptchaCode = session.getAttribute("verifyCode") + "";
  if (!StringUtils.hasLength(kaptchaCode) || !code.toLowerCase().equals(kaptchaCode)) {
    return "验证码错误";
  }
  return "验证成功";
}

该方法所拦截处理的路径为/verify,请求参数为code,即用户输入的验证码。在进行基本的非空验证后,与之前保存在session中的verifyCode值进行比较,如果两个字符串不相等则返回“验证码错误”的提示,二者相同则返回“验证码成功”的提示。

  • 前端逻辑实现

在static目录中新建verify.html,该页面会显示验证码,同时也包含供用户输入验证码的输入框和提交按钮,代码如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>验证码测试</title>
</head>
<body>
<img src="/kaptcha" onclick="this.src='/kaptcha?d='+new Date()*1" />
<br>
<input type="text" maxlength="5" id="code" placeholder="请输入验证码" />
<button id="verify">验证</button>
<br>
<p id="verifyResult">
</p>
</body>
<!-- 下方地址为字节跳动提供的jQuery cdn地址 -->
<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
      // 验证按钮的点击事件
      $('#verify').click(function () {
        var code = $('#code').val();
        $.ajax({
          type: 'GET', // 方法类型
          url: '/verify?code=' + code,
          success: function (result) {
            // 将验证结果显示在p标签中
            $('#verifyResult').html(result);
          },
          error: function () {
            alert('请求失败');
          },
        });
      });
    });
  </script>
</html>

登录流程设计

用户登录状态

客户端(通常是浏览器)在连上Web服务器后,如果想获得Web服务器中的各种资源,就需要遵守一定的通讯格式。Web项目通常使用的是HTTP协议,它用于定义客户端与Web服务器通讯的格式。而HTTP协议是无状态的协议,也就是说,这个协议是无法记录用户访问状态的,其每次请求都是独立的、没有任何关联的。一个请求就只是一个请求。

以新蜂商城的后台管理系统为例,它拥有多张页面。在页面跳转过程中和通过接口在进行数据交互时,系统需要知道用户的状态,尤其是用户登录的状态,以便服务器验证用户状态是否正常。这样系统才能判断是否可以让当前用户使用某些功能或者获取某些数据。

这时,就需要在每个页面对用户的身份进行验证和确认。但现实情况却不能如此。一个网站不可能让用户在每个页面上都输入用户名和密码。这是一个违反操作逻辑的设计,也不会有用户使用有这种设计的系统。

因此,在设计登录流程时,需要让用户只进行一次登录操作即可。为了实现这一功能就需要一些辅助技术,用得最多的技术就是浏览器的Cookie。而在Java Web开发中,比较常见的是使用Session技术来实现。将用户登录的信息存放在Cookie或Session中,这样就可以通过读取在Cookie或Session中的用户登录信息,达到记录用户状态、验证用户状态的目的。

分页设计 即设计子模块

19.3.2 后端分页功能设计
分页功能在前端页面的执行是渲染数据和分页信息展示,在后端页面则需要按照前端传输而来的请求,将分页所需的数据正确地查询出来并返回给前端。两端的侧重点并不相同。比如,前端需要展示所有页码,而后端则只需要提供总页数即可,不需要对总页数进行其他操作。再比如前端需要根据用户的操作记录当前页码的参数,以便对页码信息进行调整和限制,而后端只需要接收前端传输过来的页码并进行相应判断和查询即可。

后端分页必不可少的两个参数:页码、条数(每页)。

在实现分页功能时,使用不同的数据库实现方式也不同。因为不同数据库实现分页功能的关键字有些差别,比如SQL Server的top关键字、Oracle的rownum关键字、MySQL的limit关键字。limit关键字如下所示:

//下面是mysql的实现语句:

select * from tb_xxxx limit 10,20
分页功能的最终实现就是通过页码和条数,确定数据库需要查询的数据。比如查询第1页且每页20条的数据,就是查询数据库中从第1到20条的数据;查询第4页且每页10条的数据就是查询数据库中第30到40条的数据。因此,对于后端代码的实现来说,页码和条数两个参数就显得特别重要,缺少这两个参数查询逻辑就不成立,分页数据也就无从查起。

此外,为了前端分页区的展示,还要将数据总量或者总页数返回给前端。数据总量是必不可少的,而总页数可以计算出来(即数据总量除以每页条数)。数据总量的获取方式:

select count(*) from tb_xxxx
之后将数据封装,并返回给前端即可。

19.4 分页功能的编码实现
这里实现的分页功能是一个通用的分页接口。笔者将分页数据封装到一个返回结果对象中,并通过JSON格式返回,目的是让读者理解分页功能的主要逻辑和简单的代码实现。

接下来将结合tb_user表进行简单的查询与分页功能的实现,即在前端请求对应的页数时,返回那一页的所有数据。