Advertisement

Spring boot开源项目之个人博客(12)—分类(标签)管理

阅读量:

Spring boot开源项目之个人博客(12)—分类(标签)管理

分类与标签管理功能存在高度重合的情况,则仅需记录分类管理相关内容。该功能涵盖了增删查改以及前端分页展示等功能,并且其中包含了一些必要的非空验证和重复验证操作。

1. 分页展示

前端分为两个页面:一个用于分页展示,并带有新增、编辑及删除等功能;分类与标签的管理不支持条件查询功能,在博客管理模块中实现其条件查询功能。另一个则是用于新增和编辑操作的表单提交页面。

该页面基于之前完成优化的博客管理系统分页展示模板进行构建,并详细阐述了如何利用theamleaf和springboot框架中的Pageable组件来实现前端分页展示的具体方法。

service层

复制代码
    @Transactional
    @Override
    public Page<Type> listType(Pageable pageable) {
        return typeRepository.findAll(pageable);
    }
    
    
      
      
      
      
      
    
    AI写代码

定义了查询方法,返回Page<Type>类型的列表。

controller层

复制代码
    @GetMapping("/types")
    public String types(@PageableDefault(size = 10, sort = {"id"}, direction = Sort.Direction.DESC)Pageable pageable, Model model){
        model.addAttribute("page", typeServiceImpl.listType(pageable));
        return "/admin/types";
    }
    
    
      
      
      
      
      
    
    AI写代码

把查到的分类列表用Model推到前端,page里的值有以下格式

复制代码
    {
      "content":[
    {"id":123,"title":"blog122","content":"this is blog content"},
    {"id":122,"title":"blog121","content":"this is blog content"},
    {"id":121,"title":"blog120","content":"this is blog content"},
    {"id":120,"title":"blog119","content":"this is blog content"},
    {"id":119,"title":"blog118","content":"this is blog content"},
    {"id":118,"title":"blog117","content":"this is blog content"},
    {"id":117,"title":"blog116","content":"this is blog content"},
    {"id":116,"title":"blog115","content":"this is blog content"},
    {"id":115,"title":"blog114","content":"this is blog content"},
    {"id":114,"title":"blog113","content":"this is blog content"},
    {"id":113,"title":"blog112","content":"this is blog content"},
    {"id":112,"title":"blog111","content":"this is blog content"},
    {"id":111,"title":"blog110","content":"this is blog content"},
    {"id":110,"title":"blog109","content":"this is blog content"},
    {"id":109,"title":"blog108","content":"this is blog content"}],
      "last":false,
      "totalPages":9,
      "totalElements":123,
      "size":15,
      "number":0,
      "first":true,
      "sort":[{
    "direction":"DESC",
    "property":"id",
    "ignoreCase":false,
    "nullHandling":"NATIVE",
    "ascending":false
      }],
      "numberOfElements":15
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

内容中的属性名称相当于实体类中的属性名称。除此之外还有一些 pagination information: 总页数和当前页数等。

@PageableDefault(size=10, sort="按id字段排序", direction="降序排列")Pageable pageable

通过在代码中标记分页属性来实现数据分页功能:具体包括每页最多容纳多少条数据以及排序方式等细节信息。若无特别标注,则会采用系统默认设置。具体来说,在本例中我们设置了每页最多十条数据,并按照id字段进行逆序排列。其作用是为了将新增分类项优先展示在顶部位置。

前端展示

复制代码
    <table class="ui celled table">
    <thead>
        <tr>
            <th></th>
            <th>名称</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="type,iterStat : ${page.content}">
            <td th:text="${iterStat.count}">1</td>
            <td th:text="${type.name}">刻意练习清单</td>
            <td>
                <a href="#" th:href="@{/admin/types/{id}/input(id=${type.id})}" class="ui mini teal basic button">编辑</a>
                <a href="#" th:href="@{/admin/types/{id}/delete(id=${type.id})}" class="ui mini red basic button">删除</a>
            </td>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <th colspan="6">
                <div class="ui mini pagination menu">
                    <a class="item" th:href="@{/admin/types(page=${page.number}-1)}" th:unless="${page.first}">上一页</a>
                    <a class="item" th:href="@{/admin/types(page=${page.number}+1)}" th:unless="${page.last}">下一页</a>
                </div>
                <a href="#" th:href="@{/admin/types/input}" class="ui mini right floated teal basic button">新增</a>
            </th>
        </tr>
    </tfoot>
    </table>
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

将content中的内容发送至type中。利用theamleaf提供的$th:each功能来完成遍历操作。iterStat的功能是按照顺序编号来进行分页处理,并确保每一页都从数字1开始计算编号。

th:text="${type.name}"会把对应属性中的值取出来填充到表格中相应的位置。

此处判断了是否处于首屏状态;当处于首屏状态时会隐藏上一屏按钮,并将该操作设置在前往上一屏的操作中执行。其中page.number用于表示当前的屏幕编号,默认从0开始计算。点击前往上一屏操作会将当前屏幕编号减小一个单位

下一页功能与上一页类似。

2. 增删查改
  • 新增、编辑

新增与编辑共享同一个页面,在功能实现上也有高度相似之处。将其整合在一起作为记录保存。具体区别体现在:在回调处理中无需传递id字段;而编辑操作则需传递被编辑分类对应的id值。存储到数据库时同样地,前者不使用id字段而后者则会采用该id进行操作。

service层

复制代码
    //新增
    @Transactional
    @Override
    public Type saveType(Type type) {
    return typeRepository.save(type);
    }
    //编辑更新
    @Transactional
    @Override
    public Type update(Long id, Type type) {
    Type t = getType(id);
    if (t == null){
        throw new NotFoundException("不存在");
    }
    BeanUtils.copyProperties(type, t);
    return typeRepository.save(t);
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

controller层

复制代码
    //新增
    //跳转到新增页面
    @GetMapping("/types/input")
    public String input(Model model){
    model.addAttribute("type", new Type());
    return "/admin/types-input";
    }
    //保存到数据库,进行非空、重复验证
    @PostMapping("/types")
    public String save(@Valid Type type, BindingResult result, RedirectAttributes attributes){
    Type type1 = typeServiceImpl.getType(type.getName());
    if (type1 != null){
        result.rejectValue("name", "nameError", "该分类已存在!");
    }
    if (result.hasErrors()){
        return "/admin/types-input";
    }
    Type t = typeServiceImpl.saveType(type);
    if (t == null){
        //新增失败
        attributes.addFlashAttribute("message", "新增失败");
    }else {
        //新增成功
        attributes.addFlashAttribute("message", "新增成功");
    }
    return "redirect:/admin/types";
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

需要注意的是,在操作完成后必须使用特定的方式将结果重定向到分页展示页面,并通过RedirectAttributes将操作结果的信息传递给前端。此外,在重复检测方面,系统会获取用户的分类对象并调用getType(String name)方法查找对应的目标对象。如果返回的对象不为空,则表示该名称已存在。同时,在前端验证信息时会使用@Valid注解,并且在后端处理中也实现了类似的非空验证逻辑

复制代码
    //编辑
    //带id跳转到新增页面
    @GetMapping("/types/{id}/input")
    public String editInput(@PathVariable Long id, Model model){
    model.addAttribute("type", typeServiceImpl.getType(id));
    return "/admin/types-input";
    }
    //带id更新编辑当前分类
    @PostMapping("/types/{id}")
    public String edit(@Valid Type type, BindingResult result, @PathVariable Long id, RedirectAttributes attributes){
    Type type1 = typeServiceImpl.getType(type.getName());
    if (type1 != null){
        result.rejectValue("name", "nameError", "该分类已存在!");
    }
    if (result.hasErrors()){
        return "/admin/types-input";
    }
    Type t = typeServiceImpl.update(id, type);
    if (t == null){
        //新增失败
        attributes.addFlashAttribute("message", "更新失败");
    }else {
        //新增成功
        attributes.addFlashAttribute("message", "更新成功");
    }
    return "redirect:/admin/types";
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

"@PathVariable负责获取请求路径中的参数信息。" 通过获取id值来定位当前需要编辑的对象,并将该对象的数据发送至前端端点。
typeServiceImpl.update(id, type)这一行代码表示在编辑操作中更新数据。由于这是简单的修改操作,只需将原有保存方法替换为update即可。

前端页面

复制代码
    <form action="#" method="post" th:object="${type}" th:action="*{id}==null ? @{/admin/types} : @{/admin/types/{id}(id=*{id})}" class="ui form">
    <input type="hidden" name="id" th:value="*{id}">
    <div class="required field">
        <div class="ui left labeled input">
            <label class="ui teal basic label">分类</label>
            <input type="text" name="name" placeholder="分类的名称" th:value="*{name}">
        </div>
    </div>
    
    <div class="ui error message"></div>
    <!--/*/
    	<div class="ui negative message" th:if="${#fields.hasErrors('name')}">
    	<i class="close icon"></i>
    	<div class="header">
    	验证失败:
    	</div>
    	<p th:errors="*{name}">This is a special notification which you can dismiss if 	you're bored with it.</p>
    	</div>
    	/*/-->
    <div class="ui center aligned container">
        <button type="button" class="ui button" onclick="window.history.go(-1)">返回</button>
        <button class="ui teal submit button">发布</button>
    </div>
    </form>
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

前端主要功能是一个form表单;采用post方式提交;用于通过三元条件判断实现的一个JavaScript表达式;在操作之前需要执行一些初始化步骤;首先,在controller层中获取type对象:当进行新增操作时会跳转至新增页面并创建新的Type实例;而当进行编辑操作时则会根据现有对象查找对应的Type实例;因此,在代码逻辑中可以通过检查是否有已存在的ID来确定当前的操作属于新增还是编辑;这里获取的值为'*{id}'(即已经定义好的object属性)。

  • 删除

这个就比较简单:

service层

复制代码
    @Transactional
    @Override
    public void deleteType(Long id) {
    typeRepository.deleteById(id);
    }
    
    
      
      
      
      
      
    
    AI写代码

controller层

复制代码
    @GetMapping("/types/{id}/delete")
    public String delete(@PathVariable Long id, RedirectAttributes attributes){
    typeServiceImpl.deleteType(id);
    attributes.addFlashAttribute("message", "删除成功");
    return "redirect:/admin/types";
    }
    
    
      
      
      
      
      
      
    
    AI写代码

最后需要注意用"redirect:/admin/types"的方式回到分页展示页。

3. 非空、重复验证

这一部分主要是对这个验证进行说明。前后端均进行了非空验证工作。前端实现相对较为简单,在登录功能的相关开发中也已完成基本实现,在稍作修改即可完成的基础上重点记录了后端的非空验证情况

主要采用了@Valid注解来实现后端非空验证功能;对于实体类中需要验证的属性,在其定义中添加了@NotBlank(message="分类名称不能为空")注解;其中message字段是可选配置项,在常规情况下建议设置该字段并将其内容展示至前端以供用户检查。

复制代码
    @NotBlank(message = "分类名称不能为空")
    private String name;
    
    
      
      
    
    AI写代码

然后需要在表单提交的回调方法中加上@Valid注解,这个才生效。

复制代码
    @PostMapping("/types")
    public String save(@Valid Type type, BindingResult result, RedirectAttributes attributes)
    
    
      
      
    
    AI写代码

由于引入了@NotBlank注解,在name属性为空时会触发校验失败,并通过以下步骤显示错误信息:首先定义type对象并绑定错误信息位置;其次将错误具体化并自定义显示内容;最后前端接收并处理后端返回的错误数据。

关于分类管理的相关知识点已基本完成记录;整体操作过程较为顺畅;接下来将转向博客的管理内容;其规模相比分类管理将有所提升;增加了额外的条件筛选功能;总体实现思路与之前一致。

全部评论 (0)

还没有任何评论哟~