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.测试效果

启动项目,测试效果。
在这里插入图片描述
源码下载地址:源码下载