Spring Boot项目学习之通用权限管理项目02
接着上一篇,这里完成最基本的页面分页展示的功能。这也是权限管理系统的最基本功能的组成。
1. 创建业务层
创建service包,在该包下创建UrmsUserService.java 接口类及其实现类。
UrmsUserService.java
public interface UserService {
/**
* 新增用户(完整信息)
* @param urmsUser
* @return
*/
int insert(UrmsUser urmsUser);
/**
* 新增用户(不完整信息)
* @param urmsUser
* @return
*/
int insertSelective(UrmsUser urmsUser);
/**
* 根据ID修改用户信息(完全修改)
* @param urmsUser
* @return
*/
int updateByPrimaryKey(UrmsUser urmsUser);
/**
* 根据ID修改用户信息(部分修改)
* @param urmsUser
* @return
*/
int updateByPrimaryKeySelective(UrmsUser urmsUser);
/**
* 根据ID删除用户信息
* @param urmsUserId
* @return
*/
int deleteByPrimaryKey(Integer urmsUserId);
/**
* 根据ID查询用户
* @param urmsUserId
* @return
*/
UrmsUser get(Integer urmsUserId);
}
UrmsUserServiceImpl.java
@Service("urmsUserService")
public class UrmsUserServiceImpl implements UrmsUserService {
@Autowired
private UrmsUserMapper urmsUserMapper;
public int insert(UrmsUser urmsUser){
return urmsUserMapper.insert(urmsUser);
}
public int insertSelective(UrmsUser urmsUser){
return urmsUserMapper.insertSelective(urmsUser);
}
public int updateByPrimaryKey(UrmsUser urmsUser){
return urmsUserMapper.updateByPrimaryKey(urmsUser);
}
public int updateByPrimaryKeySelective(UrmsUser urmsUser){
return urmsUserMapper.updateByPrimaryKeySelective(urmsUser);
}
public int deleteByPrimaryKey(Integer urmsUserId){
return urmsUserMapper.deleteByPrimaryKey(urmsUserId);
}
public UrmsUser get(Integer urmsUserId){
return urmsUserMapper.selectByPrimaryKey(urmsUserId);
}
}
2.创建控制层
在controller包下创建UrmsUserController.java类。
UrmsUserController.java
@Controller
@RequestMapping("/user")
public class UrmsUserController {
@Autowired
private UrmsUserService urmsUserService;
/**
* 通过ID查询哦那个户信息
* @param urmsUserId
* @return
*/
@GetMapping("/search")
@ResponseBody
public R search(Integer urmsUserId){
Map<String, Object> res = new HashMap<>();
UrmsUser urmsUser = urmsUserService.get(urmsUserId);
res.put("user", urmsUser);
return R.ok(res);
}
}
3.进行测试
接下来进行简要的测试,这里使用Junit单元测试,所以添加测试框架依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
然后在测试启动类UrmsApplicationTests.java中添加测试方法。这里有一点需要注意。需要在该类上添加必要的注解。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UrmsApplicationTests {
}
SpringRunner.class:让 junit 与 Spring 环境进行整合
SpringBootTest(classes={webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT}) 的作用
- 1.当前类为 Spring Boot 的测试类
- 2.加载 Spring Boot 启动类。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UrmsApplicationTests {
@Autowired
private UrmsUserService urmsUserService;
@Test
public void get(){
Integer urmsUserId = 1;
UrmsUser urmsUser = urmsUserService.get(urmsUserId);
Assert.assertEquals(urmsUserId, urmsUser.getUserId());
Assert.assertEquals("admin", urmsUser.getUsername());
}
}
启动测试上述断言,出现如下界面则就测试通过。
测试带有事务的方法,并且测试成功后,避免对数据库的数据有影响。此时我们需要测试方法运行完毕后回滚事务。
@Test
@Transactional
@Rollback(true)
public void insert(){
UrmsUser user = new UrmsUser();
user.setUsername("test");
user.setRealname("测试用户");
user.setPassword("abc123");
user.setPhone("123456789");
user.setEmail("test001@email.cn");
user.setSex((byte) 0);
Integer userId = urmsUserService.insert(user);
Assert.assertNotNull(userId);
System.out.println("返回ID为:" + user.getUserId());
}
在测试的时候,如果不想测试某个方法或者想跳过某个方法可以使用注解@Ignore跳过该方法。
一般自测或者试验一些简单的功能都是使用单元测试,所以需要有一些规范需要遵循,这样对整个项目后续的维护更加友好。
- 【建议】应该在 /src/test 包下创建测试类,不应该在其他包中创建,而且创建的包应该和 /src/java 包保持一致。
- 【建议】最好不要在 main 方法内编写测试内容。
- 【强制】测试类和测试方法的访问权限必须是 public。不然运行时会报错。
- 【建议】测试类类名命名规范:测试类类名=被测试类类名+“Test”。
- 【建议】测试方法名命名规范:测试方法名=被测试方法名 或 测试方法名=被测试方法名+ “Test”
- 【建议】测试方法中不建议用人肉测试方法,即打印日志的方式来人眼观察是否结果正确,正确方式应该使用 Assert 等测试类专用方法。
4.用户管理之分页管理
一个页面需要引入很多静态资源,对于一些公共的静态资源,如果每个页面都引入的话,那么会使得页面布局十分臃肿;最好的做法是将这些公共的资源单独放在一个文件中,然后在需要的地方引入这个文件即可。
创建include.html
<head th:fragment="header">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<meta name="keywords" content="">
<meta name="description" content="">
<link href="../static/css/bootstrap.min.css?v=3.3.7"
th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link href="../static/css/font-awesome.css?v=4.4.0"
th:href="@{/css/font-awesome.css}" rel="stylesheet">
<link href="../static/css/plugins/bootstrap-table/bootstrap-table.min.css"
th:href="@{/css/plugins/bootstrap-table/bootstrap-table.min.css}"
rel="stylesheet">
<link href="../static/css/plugins/zTree/zTreeStyle/zTreeStyle.css"
th:href="@{/css/plugins/zTree/zTreeStyle/zTreeStyle.css}"
rel="stylesheet">
<link href="../static/css/plugins/jqTreeGrid/jquery.treegrid.css"
th:href="@{/css/plugins/jqTreeGrid/jquery.treegrid.css}"
rel="stylesheet">
<!--summernote css -->
<link href="../static/css/plugins/summernote/summernote-0.8.8.css"
th:href="@{/css/plugins/summernote/summernote-0.8.8.css}"
rel="stylesheet">
<link href="../static/css/animate.css"
th:href="@{/css/animate.css}"
rel="stylesheet">
<link href="../static/css/plugins/chosen/chosen.css"
th:href="@{/css/plugins/chosen/chosen.css}"
rel="stylesheet">
<link href="../static/css/style.css?v=4.1.0"
th:href="@{/css/style.css}"
rel="stylesheet">
</head>
<div th:fragment="footer">
<script src="../static/js/jquery.min.js?v=2.1.4"
th:src="@{/js/jquery.min.js}"></script>
<script src="../static/js/bootstrap.min.js?v=3.3.7"
th:src="@{/js/bootstrap.min.js}"></script>
<script src="../static/js/plugins/bootstrap-table/bootstrap-table.min.js"
th:src="@{/js/plugins/bootstrap-table/bootstrap-table.min.js}"></script>
<script src="../static/js/plugins/bootstrap-table/bootstrap-table-mobile.min.js"
th:src="@{/js/plugins/bootstrap-table/bootstrap-table-mobile.min.js}"></script>
<script src="../static/js/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"
th:src="@{/js/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js}"></script>
<script src="../static/js/plugins/validate/jquery.validate.min.js"
th:src="@{/js/plugins/validate/jquery.validate.min.js}"></script>
<script src="../static/js/plugins/validate/messages_zh.min.js"
th:src="@{/js/plugins/validate/messages_zh.min.js}"></script>
<script src="../static/js/plugins/zTree/jquery.ztree.all.min.js"
th:src="@{/js/plugins/zTree/jquery.ztree.all.min.js}"></script>
<script src="../static/js/plugins/jqTreeGrid/jquery.treegrid.min.js"
th:src="@{/js/plugins/jqTreeGrid/jquery.treegrid.min.js}"></script>
<script src="../static/js/plugins/jqTreeGrid/jquery.treegrid.extension.js"
th:src="@{/js/plugins/jqTreeGrid/jquery.treegrid.extension.js}"></script>
<script src="../static/js/plugins/jqTreeGrid/jquery.treegrid.bootstrap3.js"
th:src="@{/js/plugins/jqTreeGrid/jquery.treegrid.bootstrap3.js}"></script>
<script src="../static/js/plugins/chosen/chosen.jquery.js"
th:src="@{/js/plugins/chosen/chosen.jquery.js}"></script>
<script src="../static/js/plugins/layer/layer.js"
th:src="@{/js/plugins/layer/layer.js}"></script>
<script src="../static/js/content.js?v=1.0.0"
th:src="@{/js/content.js}"></script>
<!--summernote-->
<script src="../static/js/plugins/summernote/summernote.js"
th:src="@{/js/plugins/summernote/summernote.js}"></script>
<script src="../static/js/plugins/summernote/summernote-zh-CN.min.js"
th:src="@{/js/plugins/summernote/summernote-zh-CN.min.js}"></script>
<script src="../static/js/ajax-util.js"
th:src="@{/js/ajax-util.js}"></script>
<script th:inline="javascript">
var ctx = [[${#request.getContextPath()}]];
</script>
</div>
4.1 添加用户管理页面
创建user文件夹,在其文件夹下创建main.html页面。
main.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<meta charset="utf-8">
<head th:include="include :: header"></head>
<style>
body{height:auto;font-family: "Microsoft YaHei";}
button{font-family: "SimSun","Helvetica Neue",Helvetica,Arial;}
</style>
<body class="gray-bg">
<div class="wrapper wrapper-content">
<div class="row">
<div class="col-sm-12">
<div class="ibox">
<div class="ibox-content">
<form id="userForm" class="form-horizontal m-t">
<input id="organizationId" name="organizationId" class="form-control" type="hidden">
<div class="form-group">
<label class="col-sm-1 control-label">帐号:</label>
<div class="col-sm-2">
<input id="username" name="username" class="form-control" type="text">
</div>
<label class="col-sm-1 control-label">电话:</label>
<div class="col-sm-2">
<input id="phone" name="phone" class="form-control" type="text">
</div>
</div>
</form>
</div>
</div>
<div class="ibox">
<div class="ibox-content">
<div class="fixed-table-toolbar">
<div class="columns pull-left">
<button id="serachBtn" class="btn btn-success" onclick="javascript:reLoad()">
<i class="fa fa-search" aria-hidden="true"></i>查询
</button>
<button shiro:hasPermission="system:upmsUser:add" type="button" class="btn btn-primary" onclick="add()">
<i class="fa fa-plus" aria-hidden="true"></i>添加
</button>
<button shiro:hasPermission="system:upmsUser:batchRemove" type="button" class="btn btn-danger"
onclick="batchRemove()">
<i class="fa fa-trash" aria-hidden="true"></i>删除
</button>
</div>
</div>
<table id="exampleTable" data-mobile-responsive="true">
</table>
</div>
</div>
</div>
</div>
</div>
<div th:include="include :: footer"></div>
<script type="text/javascript" src="/js/appjs/system/urmsUser/main.js"
th:src="@{/js/appjs/system/urmsUser/main.js}"></script>
<div>
<script type="text/javascript">
var s_edit_h = 'hidden';
var s_remove_h = 'hidden';
var s_resetPwd_h = 'hidden';
</script>
</div>
<div shiro:hasPermission="system:upmsUser:edit">
<script type="text/javascript">
s_edit_h = '';
</script>
</div>
<div shiro:hasPermission="system:upmsUser:remove">
<script type="text/javascript">
var s_remove_h = '';
</script>
</div>
<div shiro:hasPermission="system:upmsUser:resetPwd">
<script type="text/javascript">
var s_resetPwd_h = '';
</script>
</div>
</div>
</body>
</html>
接着按照BootStrap 框架提供的网格插件的方式操作表格,为了方便操作,可以将所有的js代码放在统一的文件main.js。
main.js
var prefix = ctx + "/user";
$(function() {
load();
getOrganizationTree();
});
function load() {
$('#exampleTable')
.bootstrapTable(
{
method : 'get', // 服务器数据的请求方式 get or post
url : prefix + "/list", // 服务器数据的加载地址
showRefresh : true,
showToggle : true,
showColumns : true,
iconSize : 'outline',
toolbar : '#exampleToolbar',
striped : true, // 设置为true会有隔行变色效果
dataType : "json", // 服务器返回的数据类型
pagination : true, // 设置为true会在底部显示分页条
singleSelect : false, // 设置为true将禁止多选
pageSize : 10, // 如果设置了分页,每页数据条数
pageNumber : 1, // 如果设置了分布,首页页码
sidePagination : "server", // 设置在哪里进行分页,可选值为"client" 或者 "server"
sortable : true,
sortOrder: "asc",
queryParams : function(params) {
return {
//说明:传入后台的参数包括offset开始索引,limit步长,sort排序列,order:desc或者,以及所有列的键值对
order : params.order,
sort : params.sort,
organizationId: $('#organizationId').val(),
username:$('#username').val(),
phone : $('#phone').val(),
limit : params.limit,
offset : params.offset
};
},
columns : [
{
checkbox : true
},
{
field : 'userId',
title : '编号'
},
{
field : 'username',
title : '帐号' ,
sortable: true
},
{
field : 'realname',
title : '姓名'
},
{
field : 'sex',
title : '性别',
formatter : function(value, row, index) {
if(value == 1) {
return "<span class='label label-success'>男</span>";
}else if(value == 0) {
return "<span class='label label-danger'>女</span>";
}
return "<b style='color=red'>未知</b>";
}
},
{
field : 'upmsOrganizationDO.name',
title : '组织'
},
{
field : 'phone',
title : '电话'
},
{
field : 'email',
title : '邮箱'
},
{
field : 'locked',
title : '状态',
formatter : function(value, row, index) {
if(value == 1) {
return "<span class='label label-warning'>锁定</span>";
}else if(value == 0) {
return "<span class='label label-success'>正常</span>";
}
return "<span class='label label-danger'>未知</span>";
}
},
{
field : 'ctime',
title : '创建时间'
},
{
title : '操作',
field : 'id',
align : 'center',
formatter : function(value, row, index) {
var e = '<a class="btn btn-primary btn-sm '+s_edit_h+'" href="#" mce_href="#" title="编辑" οnclick="edit(\''
+ row.userId
+ '\')"><i class="fa fa-edit"></i></a> ';
var d = '<a class="btn btn-warning btn-sm '+s_remove_h+'" href="#" title="删除" mce_href="#" οnclick="remove(\''
+ row.userId
+ '\')"><i class="fa fa-remove"></i></a> ';
var f = '<a class="btn btn-success btn-sm" href="#" title="备用" mce_href="#" οnclick="resetPwd(\''
+ row.userId
+ '\')"><i class="fa fa-key"></i></a> ';
return e + d ;
}
} ]
});
}
function reLoad() {
$('#exampleTable').bootstrapTable('refresh');
}
4.2 添加用户首页跳转
@GetMapping("/main")
public String urmsUserMain(){
return "user/main";
}
4.3 用户分页查询展示
在mapper层中添加分页查询的方法,主要包括查询总记录数和分页列表数。
// 总记录数
int count(Map<String,Object> map);
// 分页列表
List<UrmsUser> list(Map<String,Object> map);
<sql id="selectUserVoWhere">
from urms_user uu
<where>
<if test="userId != null and userId != ''"> and uu.user_id = #{userId} </if>
<if test="username != null and username != ''"> and uu.username like concat('%', #{username}, '%') </if>
<if test="password != null and password != ''"> and uu.password = #{password} </if>
<if test="salt != null and salt != ''"> and uu.salt = #{salt} </if>
<if test="realname != null and realname != ''"> and uu.realname like concat('%', #{realname}, '%') </if>
<if test="avatar != null and avatar != ''"> and uu.avatar = #{avatar} </if>
<if test="phone != null and phone != ''"> and uu.phone like concat('%', #{phone}, '%') </if>
<if test="email != null and email != ''"> and uu.email = #{email} </if>
<if test="sex != null and sex != ''"> and uu.sex = #{sex} </if>
<if test="locked != null and locked != ''"> and uu.locked = #{locked} </if>
<if test="ctime != null and ctime != ''"> and uu.ctime = #{ctime} </if>
</where>
</sql>
<select id="count" resultType="int">
select count(1)
<include refid="selectUserVoWhere"/>
</select>
<select id="list" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
<include refid="selectUserVoWhere"/>
<choose>
<when test="sort != null and sort.trim() != ''">
order by ${sort} ${order}
</when>
<otherwise>
order by user_id desc
</otherwise>
</choose>
<if test="offset != null and limit != null">
limit #{offset}, #{limit}
</if>
</select>
在service层添加对应的方法。
/**
* 总记录数
* @param map
* @return
*/
int count(Map<String,Object> map);
/**
* 分页列数
* @param map
* @return
*/
List<UrmsUser> list(Map<String,Object> map);
@Override
public int count(Map<String, Object> map) {
return urmsUserMapper.count(map);
}
@Override
public List<UrmsUser> list(Map<String, Object> map) {
return urmsUserMapper.list(map);
}
然后需要封装请求分页数据和响应分页数据,在common包下创建Query.java类,用于存放请求分页数据。
/**
* 查询参数
*/
public class Query extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 1L;
// 行数
private int offset;
// 每页条数
private int limit;
public Query(Map<String, Object> params) {
this.putAll(params);
// 分页参数
this.offset = Integer.parseInt(params.get("offset").toString());
this.limit = Integer.parseInt(params.get("limit").toString());
this.put("offset", offset);
this.put("page", offset / limit + 1);
this.put("limit", limit);
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.put("offset", offset);
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
}
创建util包,在该包下创建PageUtils.java类,用于封装分页响应数据。
public class PageUtils implements Serializable {
private static final long serialVersionUID = 1L;
private int total;
private List<?> rows;
public PageUtils(List<?> list, int total) {
this.rows = list;
this.total = total;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public List<?> getRows() {
return rows;
}
public void setRows(List<?> rows) {
this.rows = rows;
}
}
最后在控制器中添加分页查询方法。
/**
* 分页查询
* @param params
* @return
*/
@ResponseBody
@GetMapping("/list")
public PageUtils list(@RequestParam Map<String, Object> params){
//查询列表数据
Query query = new Query(params);
int total = urmsUserService.count(query);
List<UrmsUser> urmsUserList = urmsUserService.list(query);
PageUtils pageUtils = new PageUtils(urmsUserList, total);
return pageUtils;
}
5.测试效果
启动项目,测试效果。
源码下载地址:源码下载