3 后台分类管理
3.1概述
到这里就开始讲解功能开发了。 开发整站的顺序,通常来说还是按照依赖性来进行,前端需要的数据,都要先通过后台的功能维护在数据库中,才可以拿到。
所以,先进行后台功能的开发,然后再是前台功能的开发。
首先使用经典的 springboot 模式进行由浅入深地开发出第一个分类管理模块。
为了便于大家理解和消化这部分知识,分类管理模块的开发采用循序渐进的方式
1. 首先下载一个只有分类管理的可运行项目,先跑起来看看效果
2. 再以查询为例子,从零开始,按部就班地做出这么一个查询的功能来
3. 接着分页功能单独拿出来讲解
4. 最后讲解分类管理中其他的,增加,删除,编辑和修改功能关于 Redis, ES (ElasticSearch) 和 Shiro 的支持,会放在后面专门来做。
这样做的好处是什么呢? 先做不支持 Redis, ES 和 Shiro 的功能,然后再在这个基础上进行改造,这样就可以很清楚的明白二者的区别。 比起一来就什么都上,脉络更清晰,更容易掌握,理解和消化。
3.2 可运行项目
可运行项目
3.3 静态资源
参照3.2
图片资源
css
js
3.4 HTML包含关系
3.4.1 HTML包含技术
Html 本身不是能够直接包含其他的 Html 文件的,但是一个项目里又有很多可以共用的部分。 那么就会采用 Thymeleaf 的包含技术把这些共用部分拼接起来。
而Thymeleaf 支持纯 html ,这样即能使用 单纯的html ,又可以享受到包含的好处了。
3.4.2 分类查询对应的HTML文件
分类查询对应的HTML文件是listCategory.html
listCategory.html 用到了4个公共包含文件
1. <%@include file="../include/admin/adminHeader.html"%>
2. <%@include file="../include/admin/adminNavigator.html"%>
3. <%@include file="../include/admin/adminPage.html"%>
4. <%@include file="../include/admin/adminFooter.html"%>
3.4.3 adminHeader.html
每个后台页面都在一开始使用了adminHeader.html
1. <template th:fragment="html(title)" >
表示本页面可以通过 adminHeader::html 方式包含。 而且包含的时候可以传递 title 参数 进来
2. 用到的一系列的 js 和 css 文件
<script src="js/jquery/2.0.0/jquery.min.js"></script>
<link href="css/bootstrap/3.3.6/bootstrap.min.css" rel="stylesheet">
<script src="js/bootstrap/3.3.6/bootstrap.min.js"></script>
<script src="js/vue/2.5.16/vue.min.js"></script>
<script src="js/axios/0.17.1/axios.min.js"></script>
<script src="js/moment/2.22.2/moment.js"></script> <!– vue.js 格式化日期用的 –>
<link href="css/back/style.css" rel="stylesheet">
3. 各种自定义函数,这些函数都会在后台管理页面上用到
//判断是否为空
function checkEmpty(value,text){
if(null==value || value.length==0){
alert(text+ "不能为空");
return false;
}
return true;
}
4. 把传递进来的 title 参数显示在 title 元素里
<title th:text="${title}" ></title>
3.4.4 adminNavigator.html
导航条–超链接
<
div
class
=
"navitagorDiv"
th:fragment
=
"html"
>
<
nav
class
=
"navbar navbar-default navbar-fixed-top navbar-inverse"
>
<
img
style
=
"margin-left:10px;margin-right:0px"
class
=
"pull-left"
src
=
"img/site/tmallbuy.png"
height
=
"45px"
>
<
a
class
=
"navbar-brand"
href
=
"#nowhere"
>天猫后台</
a
>
<
a
class
=
"navbar-brand"
href
=
"admin_category_list"
>分类管理</
a
>
<
a
class
=
"navbar-brand"
href
=
"admin_user_list"
>用户管理</
a
>
<
a
class
=
"navbar-brand"
href
=
"admin_order_list"
>订单管理</
a
>
</
nav
>
</
div
>
3.4.5 adminPage.html
这是分页HTML。
分页功能不仅仅有前端效果,还需要结合服务端传递过来的数据综合才能起作用。
3.4.6 adminFooter.html
页脚部分,目前是留白。 大家掌握了之后,可以写自己的名字@myname, 年份,版本信息什么的。
3.5 查询
新建项目步骤
1.新建项目 菜单 -> File -> New -> Other -> Maven -> Maven Project
2.简单项目格式 勾选上 Create a simple project (skip archetype selection)
3.项目参数如图所示
4.项目创建好了
pom.xml
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.how2java.tmall</groupId><artifactId>tmall_springboot</artifactId><version>0.0.1-SNAPSHOT</version><name>tmall_springboot</name><description>tmall_springboot</description><packaging>war</packaging><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.9.RELEASE</version></parent><dependencies><!-- springboot web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- springboot tomcat 支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope></dependency><!-- 热部署 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional> </dependency> <!-- jpa--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency> <!-- redis --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> <!-- springboot test --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency> <!-- thymeleaf --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency> <!-- elastic search --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><!-- 用了 elasticsearch 就要加这么一个,不然要com.sun.jna.Native 错误 --><dependency><groupId>com.sun.jna</groupId><artifactId>jna</artifactId><version>3.0.9</version></dependency> <!-- thymeleaf legacyhtml5 模式支持 --> <dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.22</version> </dependency> <!-- 测试支持 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency> <!-- tomcat的支持.--><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId><version>8.5.23</version></dependency> <!-- mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.21</version></dependency><!-- junit --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version> 4.12</version></dependency> <!-- commons-lang --><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency> <!-- shiro --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency> <!-- hsqldb --><dependency><groupId>org.hsqldb</groupId><artifactId>hsqldb</artifactId></dependency> </dependencies><properties><java.version>1.8</java.version></properties><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
Category.java
首先新建包,项目右键->New-> Package -> 然后输入com.how2java.tmall.pojo
然后创建类 Category.java
@Entity :表示这是一个实体类
@Table(name = "category") :表示对应的表名是category
@JsonIgnoreProperties({ "handler","hibernateLazyInitializer" }) :
做前后端分离,而前后端数据交互用的是 json 格式。 那么 Category 对象就会被转换为 json 数据。 而本项目使用 jpa 来做实体类的持久化,jpa 默认会使用 hibernate, 在 jpa 工作过程中,就会创造代理类来继承 Category ,并添加 handler 和 hibernateLazyInitializer 这两个无须 json 化的属性,所以这里需要用 JsonIgnoreProperties 把这两个属性忽略掉。
Category.javapackage com.wyl.baymax.tmall.pojo;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;import com.fasterxml.jackson.annotation.JsonIgnoreProperties;@Entity
@Table(name = "category")
@JsonIgnoreProperties({ "handler","hibernateLazyInitializer" })
public class Category {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "id") int id;String name;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
CategoryDAO.java
CategoryDAO 类继承了 JpaRepository,就提供了CRUD和分页 的各种常见功能。 这就是采用 JPA 方便的地方~
package com.wyl.baymax.tmall.dao;import org.springframework.data.jpa.repository.JpaRepository;import com.wyl.baymax.tmall.pojo.Category;public interface CategoryDAO extends JpaRepository<Category, Integer>{}
CategoryService.java
@Service :标记这个类是 Service类
@Autowired CategoryDAO categoryDAO; :自动装配 上个步骤的 CategoryDAO 对象
public List<Category> list() {
Sort sort = new Sort(Sort.Direction.DESC, "id");
return categoryDAO.findAll(sort);
}
首先创建一个 Sort 对象,表示通过 id 倒排序, 然后通过 categoryDAO进行查询
注:这里抛弃了 CategoryService 接口 加上 CategoryService 实现类的这种累赘的写法,而是直接使用 CategoryService 作为实现类来做
package com.wyl.baymax.tmall.service;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;import com.wyl.baymax.tmall.dao.CategoryDAO;
import com.wyl.baymax.tmall.pojo.Category;@Service
public class CategoryService {@Autowired CategoryDAO categoryDAO;public List<Category> list(){Sort sort = new Sort(Sort.Direction.DESC,"id");return categoryDAO.findAll(sort);}
}
AdminPageController.java
后台管理页面跳转专用控制器。
因为是做前后端分离,所以数据是通过 RESTFUL接口来取的,而在业务上,除了 RESTFUL 服务要提供,还要提供页面跳转服务,所以所有的后台页面跳转都放在 AdminPageController 这个控制器里。 而RSTFUL 专门放在 Category 对应的控制器 CategoryController.java 里面。 这样代码更清晰~
@Controller :表示这是一个控制器
@GetMapping(value="/admin")
public String admin(){
return "redirect:admin_category_list";
}
访问地址 admin,客户端就会跳转到 admin_category_list去
@GetMapping(value="/admin_category_list")
public String listCategory(){
return "admin/listCategory";
}
访问地址 admin_category_list 就会访问 admin/listCategory.html 文件
package com.wyl.baymax.tmall.web;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;@Controller
public class AdminPageController {@GetMapping(value="/admin")public String admin(){return "redirect:admin_category_list";}@GetMapping(value="/admin_category_list")public String listCategory(){return "admin/listCategory";}
}
CategoryController.java
这个就是专门用来提供 RESTFUL 服务器控制器了
@RestController : 表示这是一个控制器,并且对每个方法的返回值都会直接转换为 json 数据格式。
@Autowired CategoryService categoryService; :自动装配 CategoryService
@GetMapping("/categories")
public List<Category> list() throws Exception {
return categoryService.list();
}
对于categories 访问,会获取所有的 Category对象集合,并返回这个集合。 因为是声明为 @RestController, 所以这个集合,又会被自动转换为 JSON数组抛给浏览器
package com.wyl.baymax.tmall.web;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import com.wyl.baymax.tmall.pojo.Category;
import com.wyl.baymax.tmall.service.CategoryService;@RestController // 表示这是一个控制器,并且对每个方法的返回值都会直接转换为 json 数据格式
public class CategoryController {@AutowiredCategoryService categoryService;// 自动装配 CategoryService/** 对于categories 访问,会获取所有的 Category对象集合,并返回这个集合。 因为是声明为 @RestController,* 所以这个集合,又会被自动转换为 JSON数组抛给浏览器*/@GetMapping("/categories")public List<Category> list() throws Exception {return categoryService.list();}
}
Application.java
启动类
package com.wyl.baymax.tmall;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/** 启动类*/
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
CORSConfiguration.java
配置类,用于允许所有的请求都跨域。
因为是二次请求,第一次是获取 html 页面, 第二次通过 html 页面上的 js 代码异步获取数据,一旦部署到服务器就容易面临跨域请求问题,所以允许所有访问都跨域,就不会出现通过 ajax 获取数据获取不到的问题了。
package com.wyl.baymax.tmall.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configuration
public class CORSConfiguration extends WebMvcConfigurerAdapter{@Overridepublic void addCorsMappings(CorsRegistry registry){//所有请求都允许跨域registry.addMapping("/**").allowedHeaders("*").allowedMethods("*").allowedOrigins("*");}
}
GloabalExceptionHandler.java
异常处理,主要是在处理删除父类信息的时候,因为外键约束的存在,而导致违反约束。
package com.wyl.baymax.tmall.exception;import javax.servlet.http.HttpServletRequest;import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;/** 异常处理,主要是在处理删除父类信息的时候,因为外键约束的存在,而导致违反约束*/
@RestController
@ControllerAdvice
public class GloabalExceptionHandler {@ExceptionHandler(value = Exception.class)public String defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {e.printStackTrace();Class constraintViolationException = Class.forName("org.hibernate.exception.ConstraintViolationException");if (null != e.getCause() && constraintViolationException == e.getCause().getClass()) {return "违反了约束,多半是外键约束";}return e.getMessage();}
}
TestTmall 测试数据
刚开始用的时候,数据库里是没有数据的。 这里使用 简单的 jdbc 代码插入10条数据。
注: 既然是Springboot 教程,为什么不用 jpa 创建测试数据,而要用JDBC创建? 因为增加功能要到下个知识点才讲啊。。。
package com.wyl.baymax.tmall.test;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;public class TestTmall {public static void main(String[] args) {// 准备测试数据try {// 加载驱动Class.forName("com.mysql.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}try (Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/tmall_springboot?useUnicode=true&characterEncoding=utf8", "root", "admin");Statement s = c.createStatement();) {for (int i = 1; i <= 10; i++) {String sqlFormat = "insert into category values (null, '测试分类%d')";String sql = String.format(sqlFormat, i);s.execute(sql);}System.out.println("已经成功创建10条分类测试数据");} catch (SQLException e) {e.printStackTrace();}}
}
application.properties
springboot 配置文件
下面是配置文件的内容:
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tmall_springboot?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto = none
分别是数据库访问地址,账号密码,驱动以及表结构自动生成策略(none)。
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
使用 thymeleaf 作为视图,这个是springboot 官方推荐视图,它的好处是可以是纯 html 。
其中LEGACYHTML5表示经典html5模式,即允许非严格的html出现,元素少点什么也可以编译通过, 这个比较符合大家的编写习惯,太过严格的html,写起来累。
cache=false 表示不要缓存,以免在开发过程中因为停留在缓存而给开发人员带来困扰
server.context-path=/tmall_springboot
上下文地址为 tmall_springboot, 所以访问的时候,都要加上这个
http://127.0.0.1:8080/tmall_springboot/admin
spring.http.multipart.maxFileSize=100Mb
spring.http.multipart.maxRequestSize=100Mb
设置上传文件大小,默认只有1 m
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
jpa对实体类的默认字段会把驼峰命名的属性,转换为字段名的时候自动加上下划线。 这个配置的作用就是去掉下划线
比如属性名称是 createDate, jpa 默认转换为字段名 create_Date。 有了这个配置之后,就会转换为同名字段 createDate
spring.jpa.show-sql=true
显示 hibernate 执行的sql语句。 这个在上线之后,应该是关掉的,因为大量的 控制台输出会严重影响系统性能。 但是呢,因为本项目会和 redis 和 es 整合,打印 sql 语句的目的是为了观察 缓存是否起效果
#database
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tmall_springboot?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto = none#thymeleaf
spring.thymeleaf.mode=LEGACYHTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false#context
server.context-path=/tmall_springboot#设置上传文件大小,默认只有1 m
spring.http.multipart.maxFileSize=100Mb
spring.http.multipart.maxRequestSize=100Mbspring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl#显示 hibernate运行的 sql 语句
spring.jpa.show-sql=true
静态资源
接下来是各种静态资源,诸如jquery, bootstrap, css, 图片,公用 html 等,内容稍杂,就不挨个列出来了。
这些静态资源打包在webapp.rar 里,被包含的文件打包在 include.rar里,放在右上角供下载,下载后解压即可,解压之后应该看到多出来如图所示的几个目录
listCategory.html
接着在 templates 下面新建 admin目录,然后新建 listCategory.html 文件。
listCategory.html 看着复杂,其实没那么复杂,它其实就了两件事: 获取数据 和 展示数据
思路
1. 首先浏览器上访问路径 /admin
2. 这个路径被 AdminPageController 的admin方法匹配,然后客户端跳转到 admin_category_list
3. admin_category_list 被 AdminPageController 的 listCategory 方法匹配,服务端跳转到 admin/listCategory.html
4. listCategory.html 这个html页面通过http协议传输到浏览器端
5. 浏览器根据html 上的js代码,异步调用 categories 这个地址。 CategoryController 获取捕捉到这个请求,到数据库里查出所有的分类数据,并转换为 json数组返回给浏览器。
6. 浏览器根据这个json数组,通过 vue 的v-for 方式把其遍历到 多个 tr 元素上,用户就看到了表格里的多条数据了。
3.6 Eclipse方式
3.7 分页
3.8 增加
3.9 删除
3.10 编辑
3.11 修改