SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理相关的知识,希望对你有一定的参考价值。
课程名称:企业项目实训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个以上设计模式
目前的目录结构、后续还需要改进
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、接下来就是编写相应的代码(太简单了、几乎是无脑操作)
随便展示一个书籍的操作、从前台页面到后端代码的编写
先看效果图
前端页面代码
<!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" id="bs-example-navbar-collapse-1">
<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> | <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 id="queryBookList" 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 < #maxPrice
</if>
</if>
</where>
</select>
<select id="queryBookById" 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、改进书籍展示的信息、将书籍编号隐藏。只给顾客展示、书名、作者、出版社、价格
解决办法:1、首先是想到隐藏该列信息、但是没能成功。2、直接将该列信息代码删除、在点击详情需要根据改行的图书主键搜索数据库。由于当前的对象中,已经包含该图书的所有信息。可以直接拿来使用。当前行图书主键存在的意义不是很大。
三、实现在一定价格区间内搜索图书。
遇到的问题:编写的问题sql语句中的价格参数是变动的。不能将其写死在sql中。
解决办法:直接在前端页面设置两个输入框,用来接受数据。同时在实体类书籍中添加最大价格和最小价格两个属性、将前端的数据封装到实体类中,然后将数据直接传输到sql中。
遇到的问题:在新增两个属性后、查询不到数据、爆空指针异常。
经过分析发现、在实体类中书籍的价格应使用Double而不是double.idea没有给我报错、离谱。这种错误我也是醉了。我还真找了好一会。
遇到的问题:获得了参数、在写入sql中,<号不能使用。总是爆红、上网搜索、需要使用转移字符:<
四、遇到的问题? 怎样将查询到的用户权限放入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();
待编辑
以上是关于SpringBoot+Bootstrap+Thymeleaf+Restful 实现图书商城管理的主要内容,如果未能解决你的问题,请参考以下文章
springboot-17-springboot的文件上传和下载
Mobilefirst 移动应用和 THyM 移动应用的建议
带有 Cordova 默认应用程序 XML 错误的 Eclipse THyM