附上源码地址源码地址,点击"源码地址"前往github查看
一、创建SpringBoot项目
1)File->New->Project
2)创建Spring Initializr
以及选择JDK
版本
3)填写项目信息
4)选择web
依赖包---Spring Web Starter
或者Web
,具体根据页面显示的勾选
5)选择项目保存路径
6)创建项目成功,看一下目前的项目结构
二、设计数据库
1)创建数据库
-
使用终端进入
mysql
// "cd"到"mysql"目录下
$ cd /usr/local/mysql
// 进入"mysql"
$ mysql -u root -p
// 输入密码
Enter password:
// 密码验证成功,则进入"mysql"
mysql>
-
创建数据库
// 创建数据库"js_springboot_demo"
create database js_springboot_demo character set utf8;
// 查看数据库
show databases;
2)创建数据表
// 进入我们的数据库`js_springboot_demo`
use js_springboot_demo
// 创建数据表
create table user (id int(11) auto_increment primary key, title varchar(255) not null, name varchar(255) null, wx_number varchar(255) null);
// 查看表
show tables;
// 查看表的详细信息
describe user;
3)连接数据库
-
用
idea
打开项目,选择右侧database
-
选择
mysql
-
mysql
数据库信息填写
-
连接成功后,再次回到
database
,可以看到我们创建的数据库以及表、字段等信息
三、添加项目依赖库以及相关配置
1)添加依赖库
打开pom.xml
文件,依次添加下列依赖库:
-
添加
FreeMarker
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
-
添加数据库(
mysql
、jdbc
)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
-
添加Gson
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
-
添加
lombok
插件,用于生成getter
、setter
方法(需要先在idea设置中添加该插件再进行依赖库的引入)
a、添加lombok
插件
b、添加依赖库
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2)配置
打开application.properties
文件,添加如下配置:
#本地数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/js_springboot_demo
spring.datasource.username=root
spring.datasource.password=****
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
server.port=8080
server.connection-timeout=10s
server.tomcat.uri-encoding=UTF-8
#freemarker 配置信息
spring.freemarker.cache=true
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.suffix=.ftl
spring.freemarker.templateEncoding=UTF-8
#设定ftl文件路径
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.expose-spring-macro-helpers=false
#设定静态文件路径,js,css等
spring.resources.static-locations=classpath:/templates/,classpath:/static/
四、测试Java Class
与前端.ftl
页面的连接
1)view(即前端.ftl
页面)
-
在
templates
中创建一个名为user
的目录,且在该目录下创建一个名为index.ftl
文件
-
index.ftl
内容
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试</title>
</head>
<body>
<!-- ${name}为freemarker的模板 -->
<p>hello world!!! ${name} </p>
</body>
</html>
2)Controller(即java class
)
-
创建一个名为
HelloWorldController
的java class
-
实现
package com.springboot.helloworld.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import java.util.Map;
@RestController
public class HelloWorldController {
@GetMapping("/index")
public ModelAndView index(Map<String, Object> map){
map.put("name", "af");
return new ModelAndView("user/index", map);
}
}
-
页面展示
打开浏览器,访问http://localhost:8080/index
如上,可以通过controller
访问页面后,接下来我们进行本章重点内容:增删改查以及分页的介绍
五、实现增删改查功能
这里我们先介绍一下
java
的层级结构,主要分为以下四层:
dao层:主要跟数据库进行交互,称为持久层。一般是创建一个dao
接口,然后去定义接口的实现类。
entity层:数据库在项目中的类,称为实体层或者model层。主要就是去定义与数据库中的对象相对应的属性,包括getter
、setter
、toString
方法以及一些构造函数等。
service层:控制业务的,称为业务层。主要负责业务模块的逻辑应用设计,也是先设计接口,然后去定义接口的实现类。其实就是将业务逻辑独立出来,有利于复用。
controller层:管理业务以及页面跳转,称为控制层。主要获取前端数据,调用service
方法,转发到下一个页面等。
1)entity
-
创建实体包
右击helloworld
,选择New->Package
,命名为entity
-
创建实体类
右击entity
,选择New->Java Class
,命名为UserEntity
,并添加如表user
中的属性(数据表中字段常以_
分割,java class
中常以驼峰
命名,对应关系详解可以查看这篇文章
package com.springboot.helloworld.entity;
import lombok.Data;
//@Data: lombok,生成getter、setter方法
@Data
public class UserEntity {
// id
private int id;
// 标题
private String title;
// 姓名
private String name;
// 微信
private String wxNumber;
}
2)dao
-
同创建实体包一样,创建一个名为
dao
的包 -
在
dao
下创建一个名为UserDao
的接口
在
UserDao
接口中添加如下方法:
package com.springboot.helloworld.dao;
import com.springboot.helloworld.entity.UserEntity;
import java.util.List;
public interface UserDao {
/***
* 查询用户
* @return
*/
public List<UserEntity> selectUser(String keyword);
/**
* 插入数据
*/
public int insertUser(UserEntity user);
/**
* 删除数据
*/
public int deleteUser(int uid);
/**
* 更新数据
*/
public int updateUser(UserEntity user);
}
-
为
UserDao
接口添加实现类,名为UserDaoImpl
,并实现接口UserDao
的方法
package com.springboot.helloworld.dao.impl;
import com.springboot.helloworld.dao.UserDao;
import com.springboot.helloworld.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<UserEntity> selectUser(String keyword) {
String sql = "SELECT * FROM user WHERE CONCAT(id, title, name, wx_number) LIKE ?";
return jdbcTemplate.query(sql, new Object[]{"%" + keyword + "%"}, new BeanPropertyRowMapper<>(UserEntity.class));
}
@Override
public int insertUser(UserEntity user) {
String sql = "INSERT INTO user(title, name, wx_number) VALUES(?, ?, ?)";
return jdbcTemplate.update(sql, user.getTitle(), user.getName(), user.getWxNumber());
}
@Override
public int updateUser(UserEntity user) {
String sql = "UPDATE user SET title=?, name=?, wx_number=? WHERE id=?";
return jdbcTemplate.update(sql, user.getTitle(), user.getName(), user.getWxNumber(), user.getId());
}
@Override
public int deleteUser(int uid) {
String sql = "DELETE FROM user WHERE id=?";
return jdbcTemplate.update(sql, uid);
}
}
3)service
-
同创建实体包一样,创建一个名为
service
的包 -
在
service
下创建一个名为UserService
的接口
在UserService
接口中添加如下方法:
package com.springboot.helloworld.service;
import com.springboot.helloworld.entity.UserEntity;
import java.util.Map;
public interface UserService {
// 查询
public Map<String, Object> selectUser(String keyword);
// 插入
public String insertUser(UserEntity user);
// 删除
public String deleteUser(String uid);
// 更新
public String updateUser(UserEntity user);
}
-
为
UserService
接口添加实现类,名为UserServiceImpl
,并实现接口UserService
的方法
package com.springboot.helloworld.service.impl;
import com.google.gson.JsonObject;
import com.springboot.helloworld.dao.UserDao;
import com.springboot.helloworld.entity.UserEntity;
import com.springboot.helloworld.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public Map<String, Object> selectUser(String keyword) {
List<UserEntity> list = userDao.selectUser(keyword);
Map<String, Object> map = new HashMap<>();
if (list.size() == 0){
map.put("userList", list);
map.put("message", "没有查询到数据");
}else{
map.put("userList", list);
}
return map;
}
@Override
public String insertUser(UserEntity user) {
JsonObject result = new JsonObject();
if (user.getName().isEmpty()) {
result.addProperty("code", "-1");
result.addProperty("message", "标题不能为空");
}else{
int insertResult = userDao.insertUser(user);
if (insertResult >= 1){
result.addProperty("code", "200");
result.addProperty("message", "插入成功");
}else{
result.addProperty("code", "0");
result.addProperty("message", "插入失败");
}
}
return result.toString();
}
@Override
public String updateUser(UserEntity user) {
JsonObject result = new JsonObject();
int updateResult = userDao.updateUser(user);
if (updateResult >= 1){
result.addProperty("code", "200");
result.addProperty("message", "修改成功");
}else{
result.addProperty("code", "0");
result.addProperty("message", "修改失败");
}
return result.toString();
}
@Override
public String deleteUser(String uid) {
int deleteResult = userDao.deleteUser(Integer.parseInt(uid));
JsonObject result = new JsonObject();
if (deleteResult >= 1){
result.addProperty("code", "200");
result.addProperty("message", "删除成功");
}else{
result.addProperty("code", "0");
result.addProperty("message", "删除失败");
}
return result.toString();
}
}
4)web
在测试连接的部分我们已经创建了index.ftl
页面了,这里就不再重复创建了。
-
插入
要实现的效果:先在页面上添加一个插入
按钮,点击插入
弹出模态框
,输入对应信息,然后在页面上显示插入结果
代码:
/**
* html代码
**/
<button type="button" class="btn btn-primary" style="float: left;margin:20px 20px 0;" data-toggle="modal" data-target="#infoModel" data-id="insertCommit">插入</button>
<div class="modal fade" id="infoModel" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">New message</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="recipient-title" class="col-form-label">标题:</label>
<input type="text" class="form-control" id="recipient-title" name="title">
</div>
<div class="form-group">
<label for="recipient-name" class="col-form-label">名称:</label>
<input type="text" class="form-control" id="recipient-name" name="name">
</div>
<div class="form-group">
<label for="recipient-wxnum" class="col-form-label">微信号:</label>
<input type="text" class="form-control" id="recipient-wxnum" name="wxNumber">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="commitBtn" onclick="commitEvent(this)">提交</button>
</div>
</div>
</div>
</div>
<p id="requestResult">请求结果:</p>
/**
* jQuery代码
**/
//显示模态视图
$('#infoModel').on('show.bs.modal', function (event) {
// $("#commitBtn").attr("data-id", "insertEvent");
});
//模态视图提交内容
commitEvent = function (event) {
// console.log($(event).attr("data-id"));
insertEvent();
};
// 插入
insertEvent = function () {
//将form表单内容转为json串
var jsonStr = JSON.stringify($('form').serializeJSON());
console.log(jsonStr);
// 通过ajax请求接口
$.ajax({
type: "POST",
url: "/insertUser",
contentType: "application/json",//数据请求格式
dataType : 'json',//数据返回格式
data: jsonStr,
success: function(result){
console.log(result);
$('#requestResult').append('success===' + result);
$('#infoModel').modal('hide');
//插入成功后刷新页面
//window.location.reload();
},
error:function(error){
console.log(error);
$('#requestResult').append('error===' + error);
$('#infoModel').modal('hide');
}
});
}
-
查找全部
要实现的效果:用列表形式展示查询到的所有数据
代码:
/**
* html
**/
<#--数据展示-->
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col" style="width: 10%;"> ID </th>
<th scope="col" style="width: 30%;">标题</th>
<th scope="col" style="width: 20%;">姓名</th>
<th scope="col" style="width: 20%;">微信</th>
<th scope="col" style="width: 20%;">操作</th>
</tr>
</thead>
<tbody>
<#if userList?? && (userList?size > 0)>
<#list userList as row>
<tr>
<th scope="row">${row.id}</th>
<td>${row.title}</td>
<td>${row.name}</td>
<td>${row.wxNumber}</td>
<!-- 修改和删除的按钮先放着,事件会在后续代码中进行说明 -->
<td>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#infoModel" data-id="alterCommit" data-param='{"id":"${row.id}","title":"${row.title}","name":"${row.name}","wxNumber":"${row.wxNumber}"}'>修改</button>
<button class="btn btn-danger" onclick="deleteRow(${row.id})">删除</button>
</td>
</tr>
</#list>
<#else>
<p>${message}</p>
</#if>
</tbody>
</table>
</div>
-
更新
在查找全部的前端页面中,已经加入了修改和删除按钮,这里就只写相关事件了(注:修改同样是要弹出模态视图,跟插入共用一个,不同之处是修改的时候需要给form表单赋值,那么插入则需要将form表单的值清空
):
//显示模态视图
$('#infoModel').on('show.bs.modal', function (event) {
//获取当前点击按钮,用于判断是插入还是修改
var button = $(event.relatedTarget);
var btnId = button.data('id');
//用于填充表单
var modal = $(this);
if (btnId == "insertCommit"){
//插入
modal.find("#recipient-title").val("");
modal.find("#recipient-name").val("");
modal.find("#recipient-wxnum").val("");
} else if (btnId == "alterCommit"){
// 修改
var info = button.data("param");
console.log(info);
modal.find("#recipient-title").val(info.title);
modal.find("#recipient-name").val(info.name);
modal.find("#recipient-wxnum").val(info.wxNumber);
//传rowid用于修改数据
$("#commitBtn").attr("data-rowId", info.id);
}
//提交按钮加上id用于区分是插入提交还是修改提交
$("#commitBtn").attr("data-id", btnId);
});
//模态视图提交内容
commitEvent = function (event) {
var btnId = $(event).attr("data-id");
if (btnId == "insertCommit") {
insertEvent();
}else if (btnId == "alterCommit"){
var rowId = $(event).attr("data-rowId");
updateEvent(rowId);
}
};
// 修改
updateEvent = function (rowId) {
var object = $('form').serializeJSON();
var paramInfo = {
"title": object.title,
"name" : object.name,
"wxNumber" : object.wxNumber,
"id": rowId
}
var jsonStr = JSON.stringify(paramInfo);
console.log(jsonStr);
$.ajax({
type: "POST",
url: "/updateUser",
contentType: "application/json",
dataType: "json",
data: jsonStr,
success: function(result){
console.log(result);
var json = JSON.parse(result);
// 关闭模态框
$('#infoModel').modal('hide');
//刷新页面
window.location.reload();
},
error:function(result){
alert("error");
$('#infoModel').modal('hide');
}
});
}
-
关键字搜索
要实现的效果:在搜索框中输入内容,查找数据库中所有包含该内容的数据并展示
代码:
/**
* html
**/
<div class="input-group mb-3" style="width: 90%;margin: 20px 0 0 20px;float: left;">
<input type="text" class="form-control" id="search_id" placeholder="请输入关键字" aria-label="关键字" aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" type="submit" onclick="searchId()">搜索</button>
</div>
</div>
/**
* jQuery
**/
// 搜索
searchId = function () {
var keyword = $("#search_id").val();
window.location.href = "/index?keyword=" + keyword;
}
-
删除
// 删除
deleteRow = function (rowId) {
var param = {
"uid": rowId,
}
var jsonStr = JSON.stringify(param);
$.ajax({
type: "POST",
url: "/deleteUser",
contentType: "application/json",
dataType: "json",
data: jsonStr,
success: function(result){
console.log(result);
//刷新页面
window.location.reload();
},
error:function(result){
alert("error");
}
});
}
5)controller
在测试连接的部分我们已经创建了HelloWorldController
页面了,这里就不再重复创建了。
在开始
Controller
之前,有个知识点需要提一下。在这个项目中,我们使用@RequestBody
注解来使Spring
自动将http
中的body(json格式)
转换为java
内部的类。该项目中我们使用了Gson
,但由于Spring
内部默认使用JackSon
来进行转换,所以我们需要配置一下。
-
配置
Gson
创建:
创建一个名为GsonHttpMessageConverterConfiguration
的类:
配置:
package com.springboot.helloworld.config;
import com.google.gson.Gson;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
@Configuration
@ConditionalOnClass(Gson.class)
@ConditionalOnMissingClass("com.fasterxml.jackson.core.JsonGenerator")
@ConditionalOnBean(Gson.class)
public class GsonHttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean
public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
}
在HelloworldApplication
启动类中加入不适用JackSon
的代码:
@SpringBootApplication (
// 确保Jackson没有被使用
exclude = {
JacksonAutoConfiguration.class,
}
)
有了以上配置,我们就可以使用Gson
中的JsonObject
来接收前端传过来的json
串了。
-
插入
// 插入
@PostMapping(value = "/insertUser", consumes="application/json")
public String insert(@RequestBody JsonObject object){
UserEntity user = new Gson().fromJson(object, UserEntity.class);
return userService.insertUser(user);
}
-
查找/关键字搜索
由于需要将查询结果显示在index页面上,即访问http://localhost:8080/index展示查询结果,所以HelloWorldController
中查询就写在上面测试连接的方法中:
//默认keyword为空串,即查找全部
@GetMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "keyword", defaultValue = "") String keyword){
return new ModelAndView("user/index", userService.selectUser(keyword));
}
-
更新
// 修改
@PostMapping(value = "/updateUser", consumes = "application/json")
public String update(@RequestBody JsonObject object){
UserEntity user = new Gson().fromJson(object, UserEntity.class);
return userService.updateUser(user);
}
-
删除
// 删除
@PostMapping(value = "/deleteUser", consumes = "application/json")
public String delete(@RequestBody JsonObject object){
String userId = object.get("uid").toString();
return userService.deleteUser(userId);
}
到这里为止,我们的增删改查功能已经全部实现,接下来我们看分页的实现。
六、实现分页功能
主要实现分页从数据库中查询记录,包括加入关键字搜索的分页查询。
1) 添加PageEntity
实体类
在entity
包下添加分页的实体类,并实现构造方法计算总页数等数据。
PageEntity实体类的实现
package com.springboot.helloworld.entity;
import lombok.Data;
import java.util.List;
@Data
public class PageEntity {
/**
* 当前页码
*/
private int currentPage;
/**
* 每页显示条数
*/
private int pageSize;
/**
* 数据库查询到的记录总条数
*/
private int totalRecordSize;
/**
* 当前页的数据列表
*/
private List<UserEntity> currentRecordList;
/**
* 关键字
*/
private String keyword;
/**
* 总页数
*/
private int totalPage;
/**
* 页码开始索引
*/
private int startPageIndex;
/**
* 页码结束索引
*/
private int endPageIndex;
// 构造方法
public PageEntity(int currentPage, int pageSize, String keyword, int totalRecordSize, List<UserEntity> recordList){
this.currentPage = currentPage;
this.pageSize = pageSize;
this.keyword = keyword;
this.totalRecordSize = totalRecordSize;
this.currentRecordList = recordList;
/**
* 计算总页数
*/
totalPage = (totalRecordSize + pageSize - 1) / pageSize;
/**
* 计算beginPageIndex以及endPageIndex
* 如果是10页以内,则页数为1~10
* 大于10页,则显示10附近的10个页数,比如6~15、7~16......
*/
if (totalPage <= 10){
startPageIndex = 1;
endPageIndex = totalPage;
}else{
// 前4页+当前页+后5页
startPageIndex = currentPage - 4;
endPageIndex = currentPage + 5;
// 当前面页数不足4页时,显示前10个页面
if (startPageIndex < 1){
startPageIndex = 1;
endPageIndex = 10;
}
// 当后面页数不足5页时,显示后面10个页码
if (endPageIndex > totalPage){
startPageIndex = totalPage - 10 + 1;
endPageIndex = totalPage;
}
}
}
}
2) 在UserDao
中添加分页查询接口,并在UserDaoImpl
中实现该接口方法
UserDao
实现代码:
/**
* 分页查询
*/
public PageEntity selectPageUser(int start, int size, String keyword);
UserDaoImpl
实现代码:
@Override
public PageEntity selectPageUser(int start, int size, String keyword) {
// 查询记录列表
String sql = "SELECT * FROM user WHERE CONCAT(id, title, name, wx_number) LIKE ? LIMIT ?,?";
// 查询总记录数
String recordSql = "SELECT COUNT(id) FROM user WHERE CONCAT(id, title, name, wx_number) LIKE ?";
List<UserEntity> list = jdbcTemplate.query(sql, new Object[]{"%" + keyword + "%", (start-1)*size, size}, new BeanPropertyRowMapper<>(UserEntity.class));
int count = jdbcTemplate.queryForObject(recordSql, new Object[]{"%" + keyword + "%"}, Integer.class);
return new PageEntity(start, size, keyword, count, list);
}
3) 在UserService
中添加分页查询接口,并在UserServiceImpl
中实现该接口方法
UserService
实现代码:
// 分页
public Map<String, Object> selectPageUser(int start, int size, String keyword);
UserServiceImpl
实现代码:
@Override
public Map<String, Object> selectPageUser(int start, int size, String keyword) {
PageEntity pageEntity = userDao.selectPageUser(start, size, keyword);
List<UserEntity> list = pageEntity.getCurrentRecordList();
Map<String, Object> map = new HashMap<>();
map.put("userList", list);
map.put("currentPage", pageEntity.getCurrentPage());
map.put("totalPage", pageEntity.getTotalPage());
map.put("keyword", pageEntity.getKeyword());
map.put("pageUrl", "/index?");
if (list.size() == 0){
map.put("message", "暂时没有数据哦");
}
return map;
}
4) 在HelloWorldController
中,修改index
方法
@GetMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "keyword", defaultValue = "") String keyword, @RequestParam(value = "page", defaultValue = "1") Integer page){
return new ModelAndView("user/index", userService.selectPageUser(page, 5, keyword));
}
5) 在index.ftl
页面中加入分页
<#if userList?? && (userList?size > 0)>
<#--分页-->
<div class="col-md-12 column">
<ul class="pagination">
<#--lte less than or equal 小于等于-->
<#if currentPage lte 1>
<li class="disabled">
<a class="page-link" href="#"><<</a>
</li>
<#else>
<li>
<a class="page-link" href="${pageUrl}page=${currentPage - 1}&keyword=${keyword}"><<</a>
</li>
</#if>
<#list 1..totalPage as index>
<#if currentPage == index>
<li class="page-item active">
<a class="page-link" href="#">${index}</a>
</li>
<#else>
<li>
<a class="page-link" href="${pageUrl}page=${index}&keyword=${keyword}">${index}</a>
</li>
</#if>
</#list>
<#--gte greater than or equal 大于等于-->
<#if currentPage gte totalPage>
<li class="disabled">
<a class="page-link" href="#">>></a>
</li>
<#else>
<li>
<a class="page-link" href="${pageUrl}page=${currentPage + 1}&keyword=${keyword}">>></a>
</li>
</#if>
</ul>
</div>
</#if>