SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理

时间:2022-12-01 11:34:21

课程名称:企业项目实训II

设计题目:大学当图书商城

已知技术参数和设计要求:

1.问题描述(功能要求): 1.1 顾客端 1)注册登录:游客可浏览所有图书,只有注册用户才能添加购物车、下订单购买、评论; 2)图书分类浏览:图书分三个层级进行分类浏览; 3)动态搜索图书:可以按书名、作者、出版社及价格范围进行搜索,搜索的图书分页显示; 4)图书详情:可从图书列表中进一步看到图书的详细信息,包括书号、书名、作者、出版社、印次、内容简介等信息; 5)添加并管理购物车:登录用户可以选择图书加入到购物车,并可对购物车的图书进行增删改查的操作; 6)下订单:登录用户可对图书进行下订单,同时可查看订单当前的状态,订单状态包括待付款(模拟)、待发货(模拟)、待收货(模拟)、待评论; 7)模拟支付:所有注册用户都有一个余额钱包,可模拟充值,充值记录保存下来并可查看,利用余额钱包进行付款。或者使用支付宝的模拟支付接口进行支付; 8)商品评论:购买完图书后进行评价,给一个1-5等级及相应的评论内容。 1.2商家 1)商家注册登录:注册信息包括商家名称,商家地址、经营类型、注册资金、log图片等基本信息,刚注册的商家处于未审核状态,只有等待区域运营方审核后才能正常登录。区域运营方由管理员在后台直接添加; 2)商家管理自家商品:可对商品进行相应的增、删、改、查的操作; 3)商家管理自家店铺:商家可根据平台提供的模板,对店铺进行管理; 4)商家管理订单:商家可对顾客下的订单进行相应的操作,如查看订单、发货(模拟)、查看评论。

2. 运行环境要求: (1)客户端: Windows操作系统 浏览器 (2)服务器: windows操作系统 Tomcat 服务器 MySQL 数据库服务器

3. 技术要求: (1)需求分析规格说明书与用例规约 (2)系统数据库设计,时序图,类图 (3)系统采用SSM框架技术,完整编码(采用spring boot和vue) (4)为保证系统安全性,要求用到日志 (5)在必要地方使用事务技术 (6)在必要地方使用2个以上设计模式

目前的目录结构、后续还需要改进 SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理

1、搭建框架 (我花了一天的时间将整个项目的框架搭建起来、使用spring boot整合mybatis、thymeleaf和shiro) 使用spring boot是真的爽到上天、spring简直就是配置地狱。

  • 整合mybatis的目的是简化sql的编写管理
  • 整合thymeleaf的目的是数据的交互
  • 整合shiro的目的是简化权限认证以及授权....

使用到的maven依赖

    <dependencies>
        <!--整合shiro
            subject:用户
            security manager:管理所有的用户
            realm:连接数据库

        -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>


        <!--整合mybatis-->
        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>


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

        <!--  Mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>

        <!-- 导入页面依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>    -->

<!--        thymeleaf,都是基于3.x开发的-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>


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

<!--        简化set和get方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
            <scope>provided</scope>
        </dependency>

<!--        导入日志依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 日志文件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j</artifactId>
            <version>1.3.8.RELEASE</version>
        </dependency>

        <!-- 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>

    </dependencies>

2、接下来就是编写相应的代码(太简单了、几乎是无脑操作)

随便展示一个书籍的操作、从前台页面到后端代码的编写

先看效果图 SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理

前端页面代码


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>书籍列表</title>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

</head>
<body>


<div class="container">

    <!--导航栏部分-->
    <nav class="navbar navbar-inverse">
        <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <a rel="nofollow" class="navbar-brand" href="#">书籍商城</a>
            </div>

            <div class="collapse navbar-collapse" >
                <ul class="nav navbar-nav">
                    <li>
                        <a rel="nofollow" th:href="@{/customer/toCustomerIndex}">个人信息</a>
                    </li>
                    <li>
                        <a rel="nofollow" href="#">购买记录</a>
                    </li>
                    <li>
                        <a rel="nofollow" href="#">购物车</a>
                    </li>
                    <li>
                        <a rel="nofollow" th:href="@{/book/bookList}">书籍商城</a>
                    </li>

                </ul>


            </div>
            <!-- /.navbar-collapse -->
        </div>
        <!-- /.container-fluid -->
    </nav>
    <hr>

    <div class="row ">
        <form  th:action="@{/book/bookList}" style="float: left">

            <div class="col-md-2">
                <input type="text" name="queryBookName" class="form-control" placeholder="书籍名">
            </div>
            <div class="col-md-2" >
                <input type="text" name="queryBookAuthor" class="form-control" placeholder="作者名">
            </div>
            <div class="col-md-2" >
                <input type="text" name="queryBookAddress" class="form-control" placeholder="出版社">
            </div>

                <div class="col-md-4" >
                    <div class="col-row">
                        <div class="col-md-5">
                            <input type="text" name="minPrice" class="form-control" placeholder="最低价格">
                        </div>
                           <div class="col-md-1">
                               <span><strong>:</strong></span>
                           </div>
                        <div class="col-md-5">
                            <input type="text" name="maxPrice" class="form-control" placeholder="最高价格">
                        </div>
                    </div>

                </div>
            <input type="submit" value="查询" class="btn btn-primary">
        </form>

    </div>
<hr>
    <div class="row clearfix">
        <div class="col-md-12 column">
            <table class="table table-hover table-striped">
                <thead>
                <tr>
<!--                    <th>书籍编号</th>-->
                    <th>书籍名称</th>
                    <th>书籍作者</th>
                    <th>书籍价格</th>
                    <th>书籍出版社</th>
                    <th>操作</th>
                </tr>
                </thead>
                <!--查询书籍处理-->
                <tbody>
                <tr th:each="book:${bookList}">
<!--                    <td  th:text="${book.getBookId()}">编号</td>-->
                    <td th:text="${book.getBookName()}">书名</td>
                    <td th:text="${book.getBookAuthor()}">作者</td>
                    <td th:text="${book.getPrice()}"></td>
                    <td th:text="${book.getAddress()}">出版社</td>
                    <td><a rel="nofollow"  th:href="@{/book/todetail(bookId=${book.getBookId()})}" >详情</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a rel="nofollow"  th:href="@{/shop/addshop(bookId=${book.getBookId()})}" >加入购物车</a></td>


                </tr>
                </tbody>
            </table>
            <div class="col-md-3">
                <a rel="nofollow" class="btn btn-primary" th:href="@{/book/bookList}">返回查询首页</a>
            </div>
            <div class="col-md-5">
                <p ><strong>当前</strong> <span th:text="${pageInfo.pageNum}"></span><strong> 页,总 </strong><span th:text="${pageInfo.pages}"></span><strong> 页,共</strong> <span th:text="${pageInfo.total}"></span><strong> 条记录</strong></p>

            </div>

            <div class="col-md-4">
                <div class="row">
                    <div class="col-md-12">
                        <a rel="nofollow" class="btn btn-primary" th:href="@{/book/bookList}">首页</a>
                        <a rel="nofollow" class="btn btn-primary" th:href="@{/book/bookList(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1)}">上一页</a>
                        <a rel="nofollow" class="btn btn-primary" th:href="@{/book/bookList(pageNum=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages})}">下一页</a>
                        <a rel="nofollow" class="btn btn-primary" th:href="@{/book/bookList(pageNum=${pageInfo.pages})}">尾页</a>
                    </div>
                </div>
            </div>

        </div>
    </div>

</div>
</body>
</html>



编写的书籍实体类

(没有使用lombox、失去了Java的灵魂、不喜欢用)

package com.example.zheng.pojo;

public class Books {
    private String bookId;
    private String bookName;
    private String bookAuthor;
    private Double price;
    private String address;
    private String impression;
    private String introduce;

    //新增这两个字段的原因,将前台传入的价格范围封装、在编写sql语句的时候动态替换价格参数
    private Double minPrice;//最小价格
    private Double maxPrice;//最大价格

    public Books(String bookId, String bookName, String bookAuthor, Double price, String address, String impression, String introduce, Double minPrice, Double maxPrice) {
        this.bookId = bookId;
        this.bookName = bookName;
        this.bookAuthor = bookAuthor;
        this.price = price;
        this.address = address;
        this.impression = impression;
        this.introduce = introduce;
        this.minPrice = minPrice;
        this.maxPrice = maxPrice;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Double getMinPrice() {
        return minPrice;
    }

    public void setMinPrice(Double minPrice) {
        this.minPrice = minPrice;
    }

    public Double getMaxPrice() {
        return maxPrice;
    }

    public void setMaxPrice(Double maxPrice) {
        this.maxPrice = maxPrice;
    }

    public Books() { }


    public String getBookId() {
        return bookId;
    }

    public void setBookId(String bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public String getBookAuthor() {
        return bookAuthor;
    }

    public void setBookAuthor(String bookAuthor) {
        this.bookAuthor = bookAuthor;
    }



    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getImpression() {
        return impression;
    }

    public void setImpression(String impression) {
        this.impression = impression;
    }

    public String getIntroduce() {
        return introduce;
    }

    public void setIntroduce(String introduce) {
        this.introduce = introduce;
    }
}

controller层

(这里的传输数据可以使用其他的方式ajax等等、我这里是暂时走通前后端。后期会进一步改造)

package com.example.zheng.controller;


import com.example.zheng.pojo.Books;
import com.example.zheng.service.BooksService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Controller
public class BooksController {

    @Autowired
    BooksService booksService;


    @RequestMapping("/book/bookList")
    public String bookList(@RequestParam(defaultValue = "1",value = "pageNum") Integer pageNum,Double minPrice,Double maxPrice, String queryBookAuthor, String queryBookAddress, String queryBookName, Model model){
        PageHelper.startPage(pageNum,10);
        Books books =new Books();
        books.setBookAuthor(queryBookAuthor);
        books.setBookName(queryBookName);
        books.setAddress(queryBookAddress);
        books.setMinPrice(minPrice);
        books.setMaxPrice(maxPrice);

        try {
            List<Books> list = booksService.queryBookList(books);
            PageInfo<Books> pageInfo = new PageInfo<Books>(list);
            model.addAttribute("pageInfo",pageInfo);
            if(list == null){
                model.addAttribute("error","书籍列表为空");
            }else{
                model.addAttribute("bookList",list);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "allBook";

    }

    @RequestMapping("/book/todetail")
    public String bookDetail(String bookId,Model model){
        try {
            Books books = booksService.queryBookById(bookId);
            if(books == null){
                model.addAttribute("error","查询书籍详细信息失败");
            }else{
                model.addAttribute("books",books);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "bookDetail";


    }

}

service层

package com.example.zheng.service;

import com.example.zheng.pojo.Books;

import java.util.List;

public interface BooksService {
    /**
     * 查询图书
     */
    public List<Books> queryBookList(Books books);

    /**
     * 查询书籍的详细信息通过书籍编号
     */

    public Books queryBookById(String bookId);
}

实现类

package com.example.zheng.service.impl;

import com.example.zheng.mapper.BooksMapper;
import com.example.zheng.pojo.Books;
import com.example.zheng.service.BooksService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BooksServiceImpl implements BooksService {

    @Autowired
    BooksMapper booksMapper;

    //查询所有书籍
    @Override
    public List<Books> queryBookList(Books books) {
        return booksMapper.queryBookList(books);
    }

    //根据id查询具体书籍信息
    @Override
    public Books queryBookById(String bookId) {
        return booksMapper.queryBookById(bookId);
    }
}

Dao层

package com.example.zheng.mapper;

import com.example.zheng.pojo.Books;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper    //这个注解表示这个是mybatis的mapeper
@Repository
public interface BooksMapper {

    /**
     * 查询图书
     */
    public List<Books> queryBookList(Books books);



    /**
     * 查询书籍的详细信息通过书籍编号
     */

    public Books queryBookById(String id);



}

sql语句

<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.zheng.mapper.BooksMapper">

    <select  parameterType="com.example.zheng.pojo.Books" resultType="com.example.zheng.pojo.Books">
        select * from bookss
        <where>
             1=1
        <if test="bookName != null and bookName != ''">
            And bookName =#{bookName}
        </if>
        <if test="bookAuthor != null and bookAuthor != ''">
            And bookAuthor =#{bookAuthor}
        </if>
        <if test="address != null and address !=''">
            And address =#{address}
        </if>

        <if test="minPrice !=null and minPrice !=''">
            And price > #{minPrice}
            <if test="maxPrice !=null and maxPrice !=''">
                And price &lt; #{maxPrice}
            </if>
        </if>

        </where>
    </select>




    <select  resultType="com.example.zheng.pojo.Books">
        select * from bookss where bookId=#{bookId}
    </select>


</mapper>

差不多整个流程就是这样。大差不差。

在开发中遇到的问题以及解决的办法?

一、在整合框架的时候遇到的奇葩问题:

1、跳转到指定页面出现问题?必须要在properties中配置,这样的目的是,说白了是个视图解析器。和我以前写的不一样,这个也太简单了 2、必须在pom依赖中添加试图解析的依赖。 3、图片路径的问题,添加static则不显示图片,这个是相对路径,找不出来。就很离谱。解决方法:把static删除,然后重启idea。 4、@Controller。跳转到指定页面中要使用这个注解,另外一个出现问题

二、使用thymeleaf在进行传输数据的时候遇到的奇葩

这里需要注意的是,怎样获取当前书籍的主键、以及怎样将这个主键传递到controller层对应的方法中。将这个主键作为查询条件。 解决的方法:<a rel="nofollow" th:href="@{/book/todetail(bookId=${book.getBookId()})}" >详情</a> 这个方法是thymleaf特有的传输形式

1、改进书籍展示的信息、将书籍编号隐藏。只给顾客展示、书名、作者、出版社、价格

SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理 解决办法:1、首先是想到隐藏该列信息、但是没能成功。2、直接将该列信息代码删除、在点击详情需要根据改行的图书主键搜索数据库。由于当前的对象中,已经包含该图书的所有信息。可以直接拿来使用。当前行图书主键存在的意义不是很大。

三、实现在一定价格区间内搜索图书。

遇到的问题:编写的问题sql语句中的价格参数是变动的。不能将其写死在sql中。

解决办法:直接在前端页面设置两个输入框,用来接受数据。同时在实体类书籍中添加最大价格和最小价格两个属性、将前端的数据封装到实体类中,然后将数据直接传输到sql中。

遇到的问题:在新增两个属性后、查询不到数据、爆空指针异常。

经过分析发现、在实体类中书籍的价格应使用Double而不是double.idea没有给我报错、离谱。这种错误我也是醉了。我还真找了好一会。

遇到的问题:获得了参数、在写入sql中,<号不能使用。总是爆红、上网搜索、需要使用转移字符:&lt; SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理

四、遇到的问题? 怎样将查询到的用户权限放入shiro中、进行接下来的认证。

解决办法:将查询到的用户 已经查询到当前用户具有的权限

 //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        System.out.println("执行了授权");

        //拿到当前登录的这个对象
        Subject subject = SecurityUtils.getSubject();
        Customer currentCustomer = (Customer) subject.getPrincipal();//拿到对象
       //设置当前用户的权限
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
         //设置集合
        Collection<String> perStringCollection = new HashSet<String>();

        //已经查询到当前用户具有的权限
        Set<Roles> permissionSet =rolesService.queryRoles(currentCustomer.getUsercount());
        for(Roles perm : permissionSet){
            //将每一个当前用户的权限加入
            perStringCollection.add(perm.getAuthName());
        }
        info.addStringPermissions(perStringCollection);
        return info;


    }

五、根据登录用户、在查询购物车的时候、查询当前用户的购物车。在购买商品的时候加入到当前用户的购物车中

主要的代码是:

Customer parent = (Customer) SecurityUtils.getSubject().getPrincipal();

待编辑