Guns二次開發(八):商品分類管理之【增】字訣

 

關於博客中使用的Guns版本問題請先閱讀   Guns二次開發目錄     

 

上一篇博客中,我們實現了商品分類管理的菜單查詢功能,接下啦,我們開始實現添加的功能。

 

1、修改添加頁面

(1)定義添加頁面的展示邏輯

       首先我們要清楚,我們添加的商品分類,是具有父子級關係的。所以在最終調用後端的添加接口的時候,是需要一個參數——父類分類id的,那麼這個父類id從哪裏來呢?

我們不妨先參考Guns已經實現的菜單管理功能:

 

      從【菜單管理】的添加頁面可以看到,每次添加的時候,都需要選擇父級編號,以此來確定當前新增菜單的父級菜單是誰。看到這裏,大家應該已經能夠發現,菜單管理的實現邏輯與我們即將要實現的商品分類邏輯整體上大同小異。爲了簡便開發,我們其實可以直接去把菜單管理的代碼扣過來用的,但是這裏我想帶領大家實現另一種思路。

      這種思路的產生,是由於每次打開添加頁面之後,我首先要做的第一件事情,都是先選擇父級菜單,這時候就會出現一種情況,當數據庫中有上百條父級菜單時,我每次添加子菜單,都得眼睛睜得大大的先把父級菜單找出來。而且如果是極端情況下,我要給同一個父菜單添加數十條子菜單,那我每次可能都得花大量的時間在選擇父級菜單上,這對於我們平臺的運營管理人員來說,會是一場災難。鑑於此,我便做出如下設想:爲何不讓用戶先選中父級菜單後再點擊【添加】按鈕,然後按照mvc的原理,後端通過父類id獲取父類信息,將信息渲染到頁面後再返回,此時打開的添加頁面便自然而然的擁有父類菜單的信息了。這樣一來,使用者不需要再在選擇父類菜單上花費精力,而只需關注自己添加子分類的內容即可。而且,如果用戶沒有選擇父級菜單,那我默認用戶就是添加頂級菜單,這種邏輯也是可以接受的。下面是實現之後的效果圖。

 

(2)逾期實現的效果圖

 

之後打開的添加頁面裏,已經有父類的信息了:

效果圖有了,那就開始吧。

 

(3)開始修改頁面的排版和字段名。

      修改顯示的字段名,通常要與列表頁面的字段名保持一致,還有類似類別id這個字段的值,是由數據庫自增產生的,不能由用戶設置,所以不能將這個值傳給後端,等等。

 

      頁面修改的具體過程,以及前端各種標籤的使用方法等,我就不在此贅述了,都是很基礎的東西,前面的查詢功能的實現,大家如果都跟着走了一遍,那這裏相信不用帶,你也能有能力獨立解決。沒錯,就是獨立解決的能力,我自工作以來至今,遇到的所有問題都是自己一個人獨立解決的,當然,這也得感謝網上大量的技術博客和開源項目。也正是因爲這一份份恩情,使我發願並以實際行動,盡力將自己的經驗通過博文的方式分享出來,以期給給後來者提供些許幫助。

 

(4)代碼修改點

老規矩,先將代碼修改點貼出來,完整的代碼還是在文章末尾貼出來。

 

開始之前,一定要先進行的操作:

 

 

A、第一處是修改【添加】按鈕的鼠標點擊事件,獲取父類分類的信息,並傳遞給後端接口。

 

B、後端接口獲取到前端傳來的父類分類信息,並保存到request域對象中

 

C、添加分類頁面獲取request域對象中的值,並且設置成不可修改

 

上述操作完成後,展示的效果圖便實現了。

 

2、實現添加分類頁面的提交按鈕功能

要修改提交按鈕點擊事件中的數據採集,原因

(1)原本的數據採集,獲取的是所有的字段的參數,但是我們實際並不需要這麼多數據,所以要修改數據採集。

(2)有些字段類似分類名稱和父類編號都是必傳字段,在調用後端接口之前,前端需要做數據校驗,如果沒有傳值,就沒必要調用後端接口,同時也要提示用戶輸入必傳字段

 

採集數據需要使用一個 CommonTool.js :

 

 

 

然後是提交按鈕觸發的事件:

 

(3)將common.js引入到公共頁面

因爲這個採集數據的方法,在以後的很多js文件中都會用到,所以,我將這個js引入到所有的頁面中,方便開發。

 

 

 

3、後端接口的實現邏輯

後端的新增分類管理接口,在Controller層我還定義了一個接收數據的實體類,有如下理由:

(1)Java單一職責原則,Model包下的類,是DAO層的實體類,裏面有很多與數據表相關的註解,他的定義就是爲了與數據庫表打交道。

(2)實際開發中,可能需要接受一些數據庫中沒有的字段,沒有必要往Entity實體類中添加數據庫不存在的字段映射屬性,雖然對於非數據庫字段,mybatis-plus可以加個@TableField(exist = false) 來區分。

(3)實際開發中,針對某些必傳字段,後端必須判斷前端傳來的是否爲空值,如果爲空值,就不能繼續後面的操作。

(4)實際開發中,後端往往要校驗某些字段的合法性,比如添加分類管理,後端必須判斷parentId字段是否爲0,如果不爲0,說明就不是頂級菜單,此時就要去數據庫查詢當前parentId是否合法等。

       綜上所述,前端傳來的數據,後端不可能直接拿來用,既然不能直接拿來用,再加上單一職責原則,定義一個新的類來接受前端數據就顯得合情合理合規範。

       我習慣於給前端接收參數的實體類,加一個“Form”後綴;返回給前端的數據的是實體類,我也會加一個“Dto”後綴。這也算是一種編碼規範吧,就跟SpringMVC分爲Controller,Service,Dao 三層開發一個意思。

       同時,我使用了@Valid註解,對前端傳入的某些必傳數據做後端校驗,這樣一來,就不用寫大量的代碼去判斷必傳字段是否爲空了。

 

 

下面看一下核心代碼點的截圖,詳細代碼最後附上:

controller層:

 

CategoryForm.java 類中,還需要配合 @Valid使用一些註解。跟多@Valid註解請參考 https://blog.csdn.net/weixin_41969587/article/details/88600234 

 

 

如果僅僅只是這樣設置了還不夠,我們製造一個異常,比如name字段的值爲空值:

 

如下圖,添加接口因爲name字段爲空而報錯了,這時報錯的信息提示的是服務器異常,但是這樣的報錯不夠友好,我們應該進行自定義。

 

此時也可以看到,接口返回的狀態碼是自定義的500:

 

經過一段時間的開發,如果你有留意,就會發現,guns其實是做了全局異常的處理的,只要返回的狀態碼不是200,控制檯就一定會有異常信息打印:

 

全局搜索的找到全局異常的處理類:

 

找到了全局異常所在的文件之後,我們要在這個全局異常之前(測試發現,其實不用在RuntimeException異常之前也能生效),添加一個對BindException異常的攔截:

 

再次測試,發現異常信息變了,通過下圖紅框中的內容,可以知道我們設置的攔截生效了:

 

此時還不夠,我們還需要從 BindException類中找到有用的信息並單獨顯示出來:

 

下面是修改後的方法:

 

 

重啓後再測試,發現異常提示信息已經足夠友好了:

 

 

業務層的方法:

 

 

 

4、添加成功之後在跳轉回原來的頁面

假如我原來是在第三頁,那麼我在添加成功之後,模態框退出了,然後我依舊希望在第三頁,並且數據是刷新過的。

 

 

 

 

 

 

 

 

 

 

5、條件查詢的補充

 

       有這麼一個場景,當我總共有10條記錄,每頁查詢5條,那麼總共有2頁,我點擊第二頁後,此時修改每頁查詢20條,點擊搜索後,就會發現沒有數據。原因是,點擊搜索時,獲取到的當前頁碼依舊是2,而每頁查詢20條,總共也才1頁,第二頁肯定沒有數據了。解決方法是,給設置每頁查詢條數的input標籤設置失去焦點事件,當修改了頁碼之後,將當前頁碼置爲1就可以了,如下圖:

 

6、具體的代碼

注意,如果你全部都引入了這裏的代碼,但是仍舊無法使用,那你還需要引入 Guns二次開發(七):商品分類管理之【查】字訣(3) 這篇博客的代碼,如果仍舊不行,可以直接引入我增刪改查都完成之後的代碼(暫未實現)。

先把設計到修改的所有文件的目錄結構貼出來。

 

 

 

 

 

 

 

(1)CategoryController.java

package cn.stylefeng.guns.elephish.controller;

import cn.stylefeng.guns.core.common.constant.factory.PageFactory;
import cn.stylefeng.guns.elephish.bean.PageInfo;
import cn.stylefeng.guns.elephish.bean.QueryParam;
import cn.stylefeng.guns.elephish.form.CategoryForm;
import cn.stylefeng.guns.elephish.wrapper.CategoryWrapper;
import cn.stylefeng.roses.core.base.controller.BaseController;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.plugins.Page;
import org.beetl.core.misc.BeetlUtil;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.beans.factory.annotation.Autowired;
import cn.stylefeng.guns.core.log.LogObjectHolder;
import org.springframework.web.bind.annotation.RequestParam;
import cn.stylefeng.guns.elephish.model.Category;
import cn.stylefeng.guns.elephish.service.ICategoryService;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;

/**
 * 分類管理控制器
 *
 * @Date 2020-04-13 11:02:46
 */
@Controller
@RequestMapping("/category")
public class CategoryController extends BaseController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private String PREFIX = "/elephish/category/";

    @Autowired
    private ICategoryService categoryService;


    /**
     * 跳轉到添加分類管理
     */
    @RequestMapping("/category_add")
    public String categoryAdd(Integer parentId,String parentName,Integer depth,
                              Integer currentPage,HttpServletRequest request) {

        request.setAttribute("parentId",parentId);
        request.setAttribute("parentName",parentName);
        request.setAttribute("depth",depth);
        request.setAttribute("currentPage",currentPage);
        return PREFIX + "category_add.html";
    }

    /**
     * 跳轉到修改分類管理
     */
    @RequestMapping("/category_update/{categoryId}")
    public String categoryUpdate(@PathVariable Integer categoryId, Model model) {
        Category category = categoryService.selectById(categoryId);
        model.addAttribute("item",category);
        LogObjectHolder.me().set(category);
        return PREFIX + "category_edit.html";
    }

    /**
     * 跳轉到分類管理首頁
     */
    @RequestMapping("")
    public String index() {

        return PREFIX + "category.html";
    }


    /**
     * 獲取分類管理列表
     */
    @RequestMapping(value = "/list")
    @ResponseBody
    public Object list(QueryParam queryParam,PageInfo pageInfo) {

        List<Map<String, Object>> list = categoryService.listCategory(queryParam,pageInfo);

        //因爲是自定義分頁,所以返回的數據格式需要做特殊封裝,主要是兩個屬性名的定義要固定
        JSONObject jo=new JSONObject();//也可以使用 Map<String,Object>

        //屬性名必須是【data】,對應的值是List<Map<String, Object>>格式
        jo.put("data",new CategoryWrapper(list).wrap());
        jo.put("pageInfo",pageInfo);//屬性名必須是 pageInfo,

        return jo;
    }

    /**
     * 新增分類管理
     */
    @RequestMapping(value = "/add")
    @ResponseBody
    public Object add(@Valid CategoryForm categoryForm) {

        /**
         * 1、修改接收數據的實體類,因爲如果直接使用DAO層的實體類來接收,
         * 會導致一些不需要的數據被寫進數據庫
         * 2、對必傳數據要判斷是否爲空
         * 3、只接收需要的數據,比如這個CategoryForm實體類,id這個字段我是不需要的,但是隻是
         * 添加這個接口不需要,我修改接口是需要的,此時不能在CategoryForm這個類中不定義id這個屬性。
         * 所以,正確的做法是,在添加業務裏,我不在乎你是否傳了id,因爲即使你傳了我也不會使用
         */
        categoryService.addCategory(categoryForm);
        return SUCCESS_TIP;
    }

    /**
     * 刪除分類管理
     */
    @RequestMapping(value = "/delete")
    @ResponseBody
    public Object delete(@RequestParam Integer categoryId) {
        categoryService.deleteById(categoryId);
        return SUCCESS_TIP;
    }

    /**
     * 修改分類管理
     */
    @RequestMapping(value = "/update")
    @ResponseBody
    public Object update(Category category) {
        categoryService.updateById(category);
        return SUCCESS_TIP;
    }

    /**
     * 分類管理詳情
     */
    @RequestMapping(value = "/detail/{categoryId}")
    @ResponseBody
    public Object detail(@PathVariable("categoryId") Integer categoryId) {
        return categoryService.selectById(categoryId);
    }
}

 

(2)CategoryMapper.xml (無變動,可忽略)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.stylefeng.guns.elephish.dao.CategoryMapper">

    <!-- 通用查詢映射結果 -->
    <resultMap id="BaseResultMap" type="cn.stylefeng.guns.elephish.model.Category">
        <id column="id" property="id" />
        <result column="parent_id" property="parentId" />
        <result column="parent_ids" property="parentIds" />
        <result column="name" property="name" />
        <result column="status" property="status" />
        <result column="sort" property="sort" />
        <result column="version" property="version" />
        <result column="creat_time" property="creatTime" />
        <result column="update_time" property="updateTime" />
    </resultMap>

    <!-- 通用查詢結果列 -->
    <sql id="Base_Column_List">
        id, parent_id, parent_ids, name, status, sort, version, creat_time, update_time
    </sql>


</mapper>

 

(3)CategoryMapper.java (無變動,可忽略)

package cn.stylefeng.guns.elephish.dao;

import cn.stylefeng.guns.elephish.model.Category;
import com.baomidou.mybatisplus.mapper.BaseMapper;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author hqq
 * @since 2020-04-13
 */
public interface CategoryMapper extends BaseMapper<Category> {


}

 

 

(4)CategoryForm.java

package cn.stylefeng.guns.elephish.form;

import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * 接收前端傳遞過來的參數的CategoryForm實體類
 * Created by hqq on 2020/4/19.
 */
@Data
public class CategoryForm {

    private int id;//分類編號

//    @NotNull(message = "分類父編號不能爲空")
    private int parentId;//分類父編號,修改時不需要

    @Size(max=10,message = "分類名稱的長度不能超過10")
    @NotBlank(message = "分類名稱不能爲空")
    private String name;//分類名稱

    @NotNull(message = "排序編號不能爲空")
    private Integer sort;//排序編號

    private int version;//樂觀鎖字段,修改時需要

    private int status;//狀態設置,僅添加時需要
}

 

(5)CategoryServiceImpl.java

package cn.stylefeng.guns.elephish.service.impl;

import cn.stylefeng.guns.core.common.constant.factory.ConstantFactory;
import cn.stylefeng.guns.core.common.exception.BizExceptionEnum;
import cn.stylefeng.guns.core.common.node.ZTreeNode;
import cn.stylefeng.guns.elephish.bean.PageInfo;
import cn.stylefeng.guns.elephish.bean.QueryParam;
import cn.stylefeng.guns.elephish.constants.LimitationConstant;
import cn.stylefeng.guns.elephish.constants.StatusConstant;
import cn.stylefeng.guns.elephish.constants.WrapperDictNameConstant;
import cn.stylefeng.guns.elephish.dao.ProductAttachMapper;
import cn.stylefeng.guns.elephish.dao.ProductAttributeGroupMapper;
import cn.stylefeng.guns.elephish.form.CategoryForm;
import cn.stylefeng.guns.elephish.model.Category;
import cn.stylefeng.guns.elephish.dao.CategoryMapper;
import cn.stylefeng.guns.elephish.model.ProductAttach;
import cn.stylefeng.guns.elephish.model.ProductAttributeGroup;
import cn.stylefeng.guns.elephish.service.ICategoryService;
import cn.stylefeng.guns.elephish.service.IProductAttributeGroupService;
import cn.stylefeng.guns.elephish.utils.DBUtil;
import cn.stylefeng.guns.elephish.utils.StringUtil;
import cn.stylefeng.guns.elephish.utils.TimeUtil;
import cn.stylefeng.roses.kernel.model.exception.ServiceException;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * <p>
 *  服務實現類
 * </p>
 *
 * @author hqq
 * @since 2020-04-13
 */
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements ICategoryService {

    @Autowired
    private CategoryMapper categoryMapper;



        /**
     * 自定義邏輯的添加商品分類實現
     * @param form
     */
    @Transactional
    @Override
    public void addCategory(CategoryForm form) {

        int parentId = form.getParentId();
        int status = form.getStatus();

        //判斷status字段是否合法
        if(!(status==PRODUCT_CATEGORY_STATUS_START || status==PRODUCT_CATEGORY_STATUS_STOP)){
            //ILLEGAL_PARAM(400,"非法參數"),
            throw new ServiceException(BizExceptionEnum.ILLEGAL_PARAM);
        }

        /**
         *雖然我前端做了必傳字段的半段,但是我在以往的很多博客中都說過,
         *永遠不要相信前端傳來的數據(即便前後端代碼都是同一個人寫),
         *該做的判斷還是要判斷,
         */
        //設置parentIds,默認值是0
        String parentIds = "[0],";
        int depth = 1;

        //判斷parentId是否合法
        if(parentId>0){
            Category cg=categoryMapper.selectById(parentId);
            if(cg ==null){
                //CATEGORY_ERROR_PARENT_NOT_EXIST(500,"數據庫沒有查找到對應的父類信息"),
                throw new ServiceException(BizExceptionEnum.CATEGORY_ERROR_PARENT_NOT_EXIST);
            }

            if(cg.getStatus() == PRODUCT_CATEGORY_STATUS_STOP){
                //CATEGORY_ERROR_PARENT_DELETED(500,"不能爲已廢棄的父類菜單添加子菜單"),
                throw new ServiceException(BizExceptionEnum.CATEGORY_ERROR_PARENT_DELETED);
            }

            //判斷當前是否是葉子節點,如果是,則無法添加子分類
            if(cg.getDepth() >= LimitationConstant.MALL_CATEGORY_TREE_MAX_DEPTH){
                //CATEGORY_ERROR_NO_NEXT_NODE(500,"當前節點已是葉子節點,無法繼續添加子分類"),
                throw new ServiceException(BizExceptionEnum.CATEGORY_ERROR_NO_NEXT_NODE);
            }

            parentIds = cg.getParentIds()+ "["+parentId+"],";
            depth = cg.getDepth()+1;
        }

        //開始執行添加操作
        Category category = new Category();
        category.setParentId(parentId);
        category.setParentIds(parentIds);
        category.setDepth(depth);
        category.setSort(form.getSort());
        category.setName(form.getName());
        category.setVersion(1);
        category.setStatus(status);//設置默認狀態
        int count = categoryMapper.insert(category);
        if(count==0){
            // ERROR_EXISTED_SAME_NAME_RECORD(500,"數據庫中已經存在同名的記錄"),
            throw new ServiceException(BizExceptionEnum.ERROR_EXISTED_SAME_NAME_RECORD);
        }


        //判斷分類名稱是否已經存在。status=1 ,2  時纔算重複
        Wrapper<Category> wrapper = new EntityWrapper<>();
        wrapper.eq("parent_id",parentId)
                .eq("name",form.getName())
                .in("status",new Integer[]{PRODUCT_CATEGORY_STATUS_START,PRODUCT_CATEGORY_STATUS_STOP});
        count = categoryMapper.selectCount(wrapper);
        //前面已經添加一次了,這時數據庫應該只有一條同名記錄,如果大於一條,說明名字重複
        //此時拋出異常,那麼整個事務都會回滾,添加操作失敗
        if(count>1){
            //ERROR_EXISTED_SAME_NAME_RECORD(500,"數據庫中已經存在同名的記錄"),
            throw new ServiceException(BizExceptionEnum.ERROR_EXISTED_SAME_NAME_RECORD);
        }
    }


    @Override
    public List<Map<String, Object>> listCategory(QueryParam queryParam, PageInfo pageInfo) {

        //設置排序
        String sortField = "sort";//排序字段
        boolean isAsc = true;//是否正序排序

        //構建查詢條件
        Wrapper<Category> wrapper = buildWrapper(queryParam, sortField,isAsc);

        Page<Map<String,Object>> page=new Page<>(pageInfo.getCurrentPage(),pageInfo.getLimit());
        List<Map<String, Object>> maps = categoryMapper.selectMapsPage(page, wrapper);

        //設置總頁數
        int total = (int) page.getTotal();
        pageInfo.setTotalPage((int)Math.ceil(1.0*total/pageInfo.getLimit()));//總頁數
        pageInfo.setTotalCount(total);//總記錄數

        if(maps.isEmpty()){
            return maps;
        }

        //設置查詢到的本頁記錄數,因爲默認值爲0,所以大於0的時候才需要設置
        pageInfo.setSize(maps.size());

        //如果不查詢子類菜單,直接返回
        if(!queryParam.isSearchChild()){
            return maps;
        }

        //遍歷查詢其子類
        List<Map<String, Object>> list = new ArrayList<>();
        for(int i = 0; i<maps.size();i++){
            findChildCategory(list, maps.get(i),sortField,isAsc,queryParam.getStatus());
        }

        return list;
    }

    /**
     * 封裝 category 的查詢條件,
     * 注意:這些查詢條件是針對頂級菜單的,
     * 子級菜單的查詢條件只有一個parent_id和排序方式
     * @param queryParam
     * @return
     */
    private Wrapper<Category> buildWrapper(QueryParam queryParam,String sortField,boolean isAsc){

        int status = queryParam.getStatus();

        Wrapper<Category> wrapper = new EntityWrapper<>();

        //設置排序字段和排序方式
        wrapper.orderBy(sortField,isAsc);

        //是否按照層級查詢
        Integer depth =queryParam.getDepth();
        if(depth != null){
            wrapper.eq("depth", depth);
        }

        //是否按照分類名稱查詢
        if(StringUtils.isNotBlank(queryParam.getName())){
            wrapper.like("name",queryParam.getName());
        }else{
            //只有不按分類名稱查詢,並且沒有指定深度,才設置默認的parentId爲0
            if(depth == null){
                wrapper.eq("parent_id", 0);
            }
        }

        //是否按照狀態查詢
        if(status> 0){
            if(!(status==PRODUCT_CATEGORY_STATUS_START || status==PRODUCT_CATEGORY_STATUS_STOP)){
                //ILLEGAL_STATUS_VALUE(400,"狀態字段的值異常"),
                throw new ServiceException(BizExceptionEnum.ILLEGAL_STATUS_VALUE);
            }

            wrapper.eq("status",queryParam.getStatus());
        }else{
            //否則,只查詢未刪除的記錄
            wrapper.in("status",new Integer[]{PRODUCT_CATEGORY_STATUS_START,PRODUCT_CATEGORY_STATUS_STOP});
        }

        return wrapper;

    }


    /**
     * 遞歸算法,算出子級菜單
     */
    private List<Map<String, Object>> findChildCategory(List<Map<String, Object>> result,
                  Map<String, Object> category,String sortField, boolean isAsc, int status){
        result.add(category);

        //封裝子級菜單的查詢條件,
        // 子級菜單的查詢條件只有一個parent_id和排序方式
        Wrapper<Category> wrapper = new EntityWrapper<>();
        wrapper.orderBy(sortField,isAsc)
                .eq("parent_id", new Integer(category.get("id").toString()));

        if(status>0){
            wrapper.eq("status",status);
        }else{
            //否則,只查詢未刪除的記錄
            wrapper.in("status",new Integer[]{PRODUCT_CATEGORY_STATUS_START,PRODUCT_CATEGORY_STATUS_STOP});

        }

        //查找子節點,遞歸算法一定要有一個退出的條件
        List<Map<String, Object>> childList = categoryMapper.selectMaps(wrapper);
        for (Map<String, Object> temp : childList) {
            findChildCategory(result,temp,sortField,isAsc, status);
        }
        return result;
    }
}

 

(6)ICategoryService.java

package cn.stylefeng.guns.elephish.service;

import cn.stylefeng.guns.elephish.bean.PageInfo;
import cn.stylefeng.guns.elephish.bean.QueryParam;
import cn.stylefeng.guns.elephish.form.CategoryForm;
import cn.stylefeng.guns.elephish.model.Category;
import com.baomidou.mybatisplus.service.IService;

import java.util.List;
import java.util.Map;

/**
 * <p>
 *  服務類
 * </p>
 *
 * @author hqq
 * @since 2020-04-13
 */
public interface ICategoryService extends IService<Category> {

    /**
     * 獲取分類管理列表
     */
    List<Map<String,Object>> listCategory(QueryParam queryParam, PageInfo pageInfo);

    /**
     * 自定義邏輯的添加商品分類實現
     */
    void addCategory(CategoryForm categoryForm);
}

 

(7)category.js

/**
 * 分類管理管理初始化
 */
var Category = {
    id: "CategoryTable",	//表格id
    seItem: null,		//選中的條目
    table: null,
    layerIndex: -1,
    maxDepth: 3 //最大的深度
};



/**
 * 初始化表格的列
 */
Category.initColumn = function () {
    return [
        {field: 'selectItem', radio: true , hiddenField:"[version],"},
            {title: '分類名稱', field: 'name', visible: true, align: 'center', valign: 'middle'},
            {title: '分類編號', field: 'id', visible: true, align: 'center', valign: 'middle'},
            {title: '分類父編號', field: 'parentId', visible: true, align: 'center', valign: 'middle'},
            {title: '層級', field: 'depth', align: 'center', valign: 'middle', sortable: true},
            {title: '排序', field: 'sort', visible: true, align: 'center', valign: 'middle'},
            {title: '狀態', field: 'statusName', visible: true, align: 'center', valign: 'middle'},
            {title: '創建時間', field: 'createTime', visible: true, align: 'center', valign: 'middle',
                formatter: function (value) {
                    return typeof(value)=="undefined"?"":moment(+value).format('YYYY-MM-DD HH:mm:ss');
                }
            },
            {title: '更新時間', field: 'updateTime', visible: true, align: 'center', valign: 'middle',
                formatter: function (value) {
                    return typeof(value)=="undefined"?"":moment(+value).format('YYYY-MM-DD HH:mm:ss');
                }
            },
            {title: '版本', field: 'version', align: 'center', valign: 'middle'}
    ];
};

/**
 * 檢查是否選中
 */
Category.check = function () {

    var selected = $('#' + this.id).bootstrapTreeTable('getSelections');;
    if(selected.length == 0){
        Feng.info("請先選中表格中的某一記錄!");
        return false;
    }else{

        Category.seItem = selected[0];
        return true;
    }
};



/**
 * 點擊添加分類管理
 */
Category.openAddCategory = function () {
    //默認用戶是添加頂級分類
    var parentId = 0;
    var parentName = '頂級';
    var depth = 1;

    //嘗試獲取用戶選中的item標籤
    var selected = $('#' + this.id).bootstrapTreeTable('getSelections');

    //如果用戶選中了某個單選框,說明是在這個單選框下添加
    if(selected.length > 0){
        var item = selected[0];
        parentName = item.name;//分類名

        //如果當前選中的分類已經被廢棄了,那麼就不允許添加子分類
        if(item.statusName == "已廢棄"){
            Feng.info("分類【"+parentName+"】已被廢棄,無法添加子分類!");
            return;
        }

        parentId = item.id;//分類id
        depth = item.depth;//分類的深度


        //我項目設計了分類管理項目的最大層級是3級,如果超過3級就不能添加
        if(depth >= Category.maxDepth){
            Feng.error("當前節點已是葉子節點,無法繼續添加子分類");
            return ;
        }
    }

    //拼接url上需要的參數
    var urlParams = '?parentId='+parentId+'&parentName='+parentName
        +'&depth='+depth+"&currentPage="+$("#currentPage").val();

    var index = layer.open({
        type: 2,
        title: '添加分類管理',
        area: ['800px', '420px'], //寬高
        fix: false, //不固定
        maxmin: true,
        content: Feng.ctxPath + '/category/category_add'+urlParams
    });
    this.layerIndex = index;


};


/**
 * 打開查看分類管理詳情
 */
Category.openCategoryDetail = function () {
    if (this.check()) {
        var index = layer.open({
            type: 2,
            title: '分類管理詳情',
            area: ['800px', '420px'], //寬高
            fix: false, //不固定
            maxmin: true,
            content: Feng.ctxPath + '/category/category_update/' + Category.seItem.id
        });
        this.layerIndex = index;
    }
};

/**
 * 重置查詢條件條件
 */
Category.reset = function () {
    $("#byStatus").find("option[text='正常']").attr("selected",true);
    $("#byStatus").find("option[text!='正常']").attr("selected",false);

    $("#searchChild").find("option[text='是']").attr("selected",true);
    $("#searchChild").find("option[text!='是']").attr("selected",false);

    $("#currentPage").val("1");//當前頁
    $("#limit").val("5");//每頁查詢條數
    $("#byName").val("");//分類名稱
    $("#byDepth").val("");//層級
    $("#searchChild").attr("disabled",false);
}

/**
 * 刪除分類管理
 */
Category.delete = function () {
    if (this.check()) {
        var ajax = new $ax(Feng.ctxPath + "/category/delete", function (data) {
            Feng.success("刪除成功!");
            Category.table.refresh();
        }, function (data) {
            Feng.error("刪除失敗!" + data.responseJSON.message + "!");
        });
        ajax.set("categoryId",this.seItem.id);
        ajax.start();
    }
};



/**
 * 條件查詢分類管理列表
 */
Category.search = function () {

    //當前頁面刷新
    var queryParams = Category.formParams();
    queryParams['currentPage'] = $("#currentPage").val();

    Category.table.refresh({query: queryParams});

};


$(function () {

    var defaultColunms = Category.initColumn();
    var table = new BSTreeTable(Category.id, "/category/list", defaultColunms);
    table.setExpandColumn(1);//設置第一列展示下拉列表
    table.setIdField("id");//分類編號
    table.setCodeField("id");//分類父編號,用於設置父子關係
    table.setParentCodeField("parentId");//分類父編號,用於設置父子關係
    table.setExpandAll(true);

    //設置請求時的參數
    var queryData = Category.formParams();
    queryData['limit'] = 5;//
    // alert(JSON.stringify(queryData));
    table.setData(queryData);

    table.init();
    Category.table = table;


    $("#limit").val("5");//設置每頁的查詢的默認條數

    //設置當前對象的名稱,分頁時需要使用
    PageTool.callerName="Category";

});


/**
 * 查詢表單提交參數對象
 * @returns {{}}
 */
Category.formParams = function() {
    var queryData = {};

    queryData['name'] = $("#byName").val().trim();//名稱條件
    queryData['depth'] = $("#byDepth").val();//層級條件
    queryData['status'] = $("#byStatus").val();//狀態條件
    queryData['searchChild'] = $("#searchChild").val();//是否查詢子菜單
    queryData['limit'] = $("#limit").val();//設置每頁查詢條數

    return queryData;
}

/**
 * 每頁查詢的頁碼數修改之後觸發失去焦點事件,
 * 將當前頁碼重置爲 1 .
 * 主要是爲了解決以下情況:
 * 假設總共10條記錄,每頁查詢3條,那麼總共就有4頁,當用戶在第三頁的時候,
 * 修改成每頁查詢10條,修改後點擊查詢,會出現沒有數據顯示的情況。
 * 原因是,用戶的當前頁碼 currentPage 的值依舊是3,
 * 而每頁查詢10條後,總共只有1頁,查詢第三頁時肯定沒有數據啦
 */
$("#limit").on('blur',function(){
    $("#currentPage").val(1);
});


/**
 * 【分類名稱】輸入框失去焦點事件
 */
$("#byName").on('blur',function(){
    Category.setSearchChildSelected();
});

/**
 *【深度】輸入框失去焦點事件
 */
$("#byDepth").on('blur',function(){
    Category.setSearchChildSelected();
});

/**
 * 設置【是否查詢子菜單】選擇框是否可用
 */
Category.setSearchChildSelected = function () {
    var byName = $("#byName").val().trim();
    var byDepth = $("#byDepth").val().trim();


    if(byName && !byDepth){
        //當選擇分類名稱查詢,不選擇層級查詢時,默認無法查詢子類菜單,這樣是爲了防止查重和查出不必要的數據
        $("#searchChild").val("false");
        $("#searchChild").attr("disabled",true);
    }else{
        //其它情況,都可以自主覺得是否查詢子菜單
        $("#searchChild").val("true");
        $("#searchChild").attr("disabled",false);
    }
}

 

(8)category_info.js

/**
 * 初始化分類管理詳情對話框
 */
var CategoryInfoDlg = {
    ztreeInstance: null
};



/**
 * 關閉此對話框
 */
CategoryInfoDlg.close = function() {
    parent.layer.close(window.parent.Category.layerIndex);
}


/**
 * 收集數據
 */
CategoryInfoDlg.collectData = function(type) {

    var fieldArr;//定義一個數組,封裝所有需要收集的數據的字段
    var needArr;//定義一個數組,封裝字段是否是必傳字段,注意必須與 fieldArr數組中的容量保持一致
    var fieldNameArr;//定義一個數組,封裝對應字段的字段名稱,用於收集信息出錯時定位錯誤信息
    var typeArr;//定義數組,封裝需要收集的字段的值的類型
    var strMaxLengthArr;//定義數組,指定字符串類型的字段的值的最大長度。-1表示該字段不用比較

    if(type === 'add'){
        //設置添加需要的字段
        fieldArr = ['parentId','name','sort','status'];
        needArr = [true,true,true,true];
        fieldNameArr = ['父類編號','分類名稱','排序編號','狀態設置'];
        typeArr = ['number','','number','number'];
        strMaxLengthArr = [11,10,11,2];

    }else if(type==='edit'){
        //設置修改需要的字段
        fieldArr = ['id','parentId','name','sort','version'];
        needArr = [true,true,true,true,true];
        fieldNameArr = ['分類編號','父類編號','分類名稱','排序編號','版本信息'];
        typeArr = ['number','number','','number','number'];
        strMaxLengthArr = [11,11,10,11,11];
    }else{
        return "收集信息出錯啦";
    }

    return CommonTool.collectData(fieldArr,needArr,fieldNameArr,typeArr,strMaxLengthArr);
}


/**
 * 提交添加
 */
CategoryInfoDlg.addSubmit = function() {

    //重新採集數據
    var result = this.collectData("add");

    //如果返回的字段是字符串類型的,說明出錯了,直接返回出錯信息
    if(typeof(result)=='string'){
        Feng.error(result);
        return ;
    }

    //提交信息
    var ajax = new $ax(Feng.ctxPath + "/category/add", function(data){
        Feng.success("添加成功!");
        //修改成功之後,刷新數據,依舊跳轉到原來的頁面
        var queryParams = window.parent.Category.formParams();
        queryParams['currentPage'] = $("#currentPage").val()
        window.parent.Category.table.refresh({query:queryParams });

        CategoryInfoDlg.close();
    },function(data){
        Feng.error("添加失敗:" + data.responseJSON.message + "!");
    });
    ajax.set(result);//設置請求參數
    ajax.start();

}

/**
 * 提交修改
 */
CategoryInfoDlg.editSubmit = function() {

    this.clearData();
    this.collectData();

    //提交信息
    var ajax = new $ax(Feng.ctxPath + "/category/update", function(data){
        Feng.success("修改成功!");
        window.parent.Category.table.refresh();
        CategoryInfoDlg.close();
    },function(data){
        Feng.error("修改失敗!" + data.responseJSON.message + "!");
    });
    ajax.set(this.categoryInfoData);
    ajax.start();
}

$(function() {

});

 

(9)category_add.html

@layout("/common/_container.html"){
<div class="ibox float-e-margins">

    <strong style="color:red;">
        注意:默認添加頂級分類,如果要添加子級分類,請先選中對應的父類分類。
        <br/>
        <span style="white-space: pre">          帶<b style='color:red'>*</b>號字段爲必填項</span>

    </strong>
    <br/>
    <br/>
    <div class="ibox-content">
        <div class="form-horizontal">
            <div class="row">
                <div class="col-sm-6 b-r">
                    <h3 style="color:green;text-align:center;">父類信息</h3>

                    <!--保存當前頁碼-->
                    <input id="currentPage" type="hidden" value="${nvl(currentPage,1)}">

                    <#input id="parentName" disabled="disabled"
                            value="${nvl(parentName,'無')}" name="父類名稱" underline="false"/>
                    <#input id="parentId" disabled="disabled"
                            value="${nvl(parentId,'無')}" name="父類編號" underline="false"/>
                    <#input id="parentDepth" disabled="disabled"
                            value="${nvl(depth,'無')}" name="父類層級" underline="false"/>
                </div>

                <div class="col-sm-6">
                    <h3 style="color:green;text-align:center;">新增分類信息</h3>
                        <#input id="name" name="分類名稱<b style='color:red'>*</b>" underline="false"/>
                        <#input id="sort" name="排序編號" value="1" underline="false"/>
                        <#SelectCon id="status" name="狀態設置<b style='color:red'>*</b>" >
                            <option value="1">啓用</option>
                            <option value="2">停用</option>
                        </#SelectCon>
                </div>
            </div>

            <div class="row btn-group-m-t">
                <div class="col-sm-10">
                    <#button btnCss="info" name="提交" id="ensure" icon="fa-check" clickFun="CategoryInfoDlg.addSubmit()"/>
                    <#button btnCss="danger" name="取消" id="cancel" icon="fa-eraser" clickFun="CategoryInfoDlg.close()"/>
                </div>
            </div>
        </div>

    </div>
</div>
<script src="${ctxPath}/static/modular/elephish/product/category/category_info.js?j=${date().time}"></script>
@}

 

 

(10)paging.js

/**
 * 本分頁功能是在bootrap-treetable.js基礎上實現的,使用前必須嚴格遵守guns生成的代碼規範
 * 使用說明:
 * (1)使用之前,請先配套獲取修改後的 bootstrap-treetable.js,並替換掉guns v5.1-final
 *      原來的bootstrap-treetable.js,
 *      guns原js修改的地方也才幾行代碼,你也可以自己修改,而不用獲取本處提供的
 * (2)將本文件 paging.js 引入到你需要分頁的html頁面中,比如本篇博客的 category.html頁面中
 * (3)在你需要插入【分頁條】的地方(html頁面中的某處位置)引入這段div :<div id="pageDiv"></div>
 * (4)接口返回的數據需是json格式的,並且必須保證用以下兩個字段封裝對應的數據:
        "data" : 列表所需要的實體類數據,用list集合封裝
        "pageInfo" : 分頁信息

        注:
        data 集合中實體類需符合原 guns v5.1-final 【菜單管理】列表查詢功能的標準;
        pageInfo 需包含以下5個字段:{
             currentPage : 要查詢第幾頁,默認是1,防止空指針
             totalPage :總頁數,默認值0
             totalCount : 總記錄數,默認值0
             limit :每頁查詢的條數,默認值每頁查詢1條,防止空指針
             size : 本頁實際查詢到的條數
        }
 * (5)在bootstrap表格相關的js中,本示例就category.js文件,在這個文件中,
 * 對象的名稱是"Category",此時需要在該js文件的$(function () {})中做如下
 * 賦值操作:
 * $(function () {
 *      PageTool.callerName("Category");
 * })
 *
 * (6)儘量使用guns代碼生成的頁面和js,要確保你的js中擁有guns代碼生成的
 * 兩個方法,A、對象.formParams()  ; B、對象.table.refresh();
 * 該分頁函數是以guns爲基礎做的補充,所以使用時請遵守Guns原有的規範
 *
 */
var PageTool = {
    callerName:"" //設置頂級調用者的名稱,用於創建該對象時使用
};


/**
 * 自定義的分頁之構建構建div裏面的內容
 *
 * 記住,使用之前,必須要在你需要顯示頁碼條的位置引入這段div標籤:
 * <div id="pageDiv"></div>
 */
PageTool.buildPageDiv = function  (pageInfo) {
    // alert(JSON.stringify(pageInfo));

    //先清空之前的內容
    $("#pageDiv").empty();

    var currentPage=pageInfo.currentPage;//當前頁
    var totalPage=pageInfo.totalPage;//總頁數
    var totalCount=pageInfo.totalCount;//符合條件的總條數(不包含子菜單)
    var size=pageInfo.size;//本頁查詢到記錄數(不包含子菜單)
    var limit=pageInfo.limit;//每頁查詢的記錄數

    //添加一個隱藏標籤,用於記錄當前頁碼
    var pageDiv = "<br/><input id='currentPage' type='hidden' value='"+currentPage+"'/>" +
        "<strong style='color:red'>注意:此處的分頁只相對同級別的父類菜單而言,所有子類菜單的記錄數不在分頁之內" +
        "</strong><br/><ul class='pagination'>";
    <!-- 首頁,如果當前頁是第一頁,則設置首頁不可用-->
    if(currentPage==1){
        pageDiv +="<li><a href='javascript:void(0)' disabled='true' style='color:black;font-weight:bold;'>首頁</a></li>";
    }else{
        pageDiv += "<li><a href='javascript:void(0)' οnclick='PageTool.pageSkip(this)' currentPage='1'>首頁</a></li>";
    }

    <!-- 上一頁,先判斷是不是第一頁,第一頁,就沒有上一頁 -->
    if(currentPage > 1){
        pageDiv += "<li><a href='javascript:void(0)' οnclick='PageTool.pageSkip(this)'" +
            " currentPage='"+(currentPage - 1)+"'>上一頁</a></li>";
    }

    <!-- #####展示頁碼功能 開始#####-->

    <!-- 定義兩個模板變量 -->
    var begin=1;
    var end=5;

    if(totalPage <= 5){
        //當總的頁數小於等於5的情況
        begin=1 ;
        end=totalPage;
    }else{
        // 總頁碼大於5的情況
        <!-- 如果當前頁碼爲 1 - 3,那麼 我們顯示前面5個頁碼,也就是1-5 -->
        if(currentPage<=3){
            begin=1 ;
            end=5;
        }else if(currentPage >= totalPage-2 ){
            <!-- 如果當前頁碼爲 末尾的3個,也就是 currentPage >=totalPage - 2 ,那麼 就顯示末尾5個頁碼 -->
            begin=totalPage-4 ;
            end=totalPage;
        }else{
            <!-- 其他中間的頁碼,顯示 currentPage - 2 到 currentPage + 2  -->
            begin=currentPage-2 ;
            end=currentPage+2;
        }
    }

    <!-- 遍歷頁碼  -->
    for(var i=begin; i <= end; i++){
        if(currentPage == i){
            <!-- 如果頁碼是當前頁,則設置標籤不可用 -->
            pageDiv += "<li><a href='javascript:void(0)' disabled='true' style='color:black;font-weight:bold;'>"+i+"</a></li>";

        }else{
            <!-- 如果頁碼不是當前頁,則加連接 -->
            pageDiv += "<li><a href='javascript:void(0)' οnclick='PageTool.pageSkip(this)' currentPage='"+i+"'>"+i+"</a></li>";
        }
    }
    <!-- #####展示頁碼功能 結束#####-->

    <!-- 下一頁,先判斷是不是最後一頁,最後一頁,就沒有下一頁 -->
    if(currentPage < totalPage ){
        pageDiv += "<li><a href='javascript:void(0)' οnclick='PageTool.pageSkip(this)' currentPage='"+(currentPage + 1)+"'>下一頁</a></li>";
    }

    <!-- 最後一頁 -->
    if(currentPage==totalPage) {
        pageDiv += "<li><a href='javascript:void(0)' disabled='true' style='color:black;font-weight:bold;'>末頁</a></li>";
    }else{
        pageDiv += "<li><a href='javascript:void(0)' οnclick='PageTool.pageSkip(this)' currentPage='"+(totalPage)+"'>末頁</a></li>";
    }

    pageDiv +="&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;當前第 "+(currentPage)+" 頁," +
        "顯示第 "+((currentPage-1)*limit+1)+" 到 "+((currentPage-1)*limit+size)+" 條記錄" +
        "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 總共 "+totalPage+" 頁,總共 "+totalCount+" 條記錄數," +
        "可直接搜索第<input value='"+currentPage+"' id='pageNow' style='width:40px;text-align:center;'/> 頁" +
        "<input type='button' class='btn btn-primary' currentPage='button' οnclick='PageTool.pageSkip(this)' value='點擊跳轉'></ul>";

    $("#pageDiv").append(pageDiv);
    // alert(pageDiv);
}



/**
 * 點擊指定頁碼刷新數據
 */
PageTool.pageSkip =function(obj) {

    //獲取設置當前頁碼
    var currentPage = obj.attributes["currentPage"].nodeValue;

    //如果是直接指定頁碼跳轉的請求,則要從input標籤中獲取值
    if(currentPage == "button"){
        currentPage = $("#pageNow").val();
    }

    //如果最終沒有值,設置默認值爲1
    if(!currentPage){
        currentPage = 1;
    }

    //獲取調用者的名稱,並轉換成對象
    if(!PageTool.callerName ||PageTool.callerName==''){
        Feng.error("請按paging.js開頭的說明,設置對象的名稱");
        return false;
    }
    //根據對象名稱獲取對象
    var caller = window[PageTool.callerName];
    if(!caller || typeof(caller)!='object' ){
        Feng.error("您設置的對象名稱無法轉換成對象,請閱讀paging.js文件的使用說明");
        return false;
    }

    var queryData = caller.formParams();
    queryData['currentPage'] = currentPage;//當前再第幾頁
    //刷新table數據
    caller.table.refresh({query: queryData});

    //當前方法被寫死了,必須動態獲取到
    // 先獲取公共的參數
    // var queryData = Category.formParams();
    //
    // //添加私有參數
    // queryData['currentPage'] = currentPage;//當前再第幾頁
    //
    // //刷新table數據
    // Category.table.refresh({query: queryData});

}

 

 

 

(11)GlobalExceptionHandler.java

/**
 * Copyright 2018-2020 stylefeng & fengshuonan (https://gitee.com/stylefeng)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.stylefeng.guns.core.aop;

import cn.stylefeng.guns.core.common.exception.BizExceptionEnum;
import cn.stylefeng.guns.core.common.exception.InvalidKaptchaException;
import cn.stylefeng.guns.core.log.LogManager;
import cn.stylefeng.guns.core.log.factory.LogTaskFactory;
import cn.stylefeng.guns.core.shiro.ShiroKit;
import cn.stylefeng.guns.elephish.utils.StringUtil;
import cn.stylefeng.roses.core.reqres.response.ErrorResponseData;
import cn.stylefeng.roses.kernel.model.exception.ServiceException;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.DisabledAccountException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;

import static cn.stylefeng.roses.core.util.HttpContext.getIp;
import static cn.stylefeng.roses.core.util.HttpContext.getRequest;

/**
 * 全局的的異常攔截器(攔截所有的控制器)(帶有@RequestMapping註解的方法上都會攔截)
 *
 * @author fengshuonan
 * @date 2016年11月12日 下午3:19:56
 */
@ControllerAdvice
@Order(-1)
public class GlobalExceptionHandler {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 攔截業務異常
     */
    @ExceptionHandler(ServiceException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponseData bussiness(ServiceException e) {
        LogManager.me().executeLog(LogTaskFactory.exceptionLog(ShiroKit.getUser().getId(), e));
        getRequest().setAttribute("tip", e.getMessage());
        log.error("業務異常:", e);
        return new ErrorResponseData(e.getCode(), e.getMessage());
    }

    /**
     * 攔截數據綁定異常
     * @param e
     * @return
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponseData bindException(BindException e) {
        //添加錯誤日誌
        LogManager.me().executeLog(LogTaskFactory.exceptionLog(ShiroKit.getUser().getId(), e));

        BindingResult result = e.getBindingResult();

        String errorMsg = "數據綁定異常";
        if (result.hasErrors()) {
            errorMsg = result.getFieldErrors().get(0).getDefaultMessage();
        }

        getRequest().setAttribute("tip", errorMsg);
        log.error("數據綁定異常:", errorMsg);

        return new ErrorResponseData(BizExceptionEnum.SERVER_ERROR.getCode(),errorMsg);
    }








    /**
     * 用戶未登錄異常
     */
    @ExceptionHandler(AuthenticationException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String unAuth(AuthenticationException e) {
        log.error("用戶未登陸:", e);
        return "/login.html";
    }

    /**
     * 賬號被凍結異常
     */
    @ExceptionHandler(DisabledAccountException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String accountLocked(DisabledAccountException e, Model model) {
        String username = getRequest().getParameter("username");
        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "賬號被凍結", getIp()));
        model.addAttribute("tips", "賬號被凍結");
        return "/login.html";
    }

    /**
     * 賬號密碼錯誤異常
     */
    @ExceptionHandler(CredentialsException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public String credentials(CredentialsException e, Model model) {
        String username = getRequest().getParameter("username");
        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "賬號密碼錯誤", getIp()));
        model.addAttribute("tips", "賬號密碼錯誤");
        return "/login.html";
    }

    /**
     * 驗證碼錯誤異常
     */
    @ExceptionHandler(InvalidKaptchaException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String credentials(InvalidKaptchaException e, Model model) {
        String username = getRequest().getParameter("username");
        LogManager.me().executeLog(LogTaskFactory.loginLog(username, "驗證碼錯誤", getIp()));
        model.addAttribute("tips", "驗證碼錯誤");
        return "/login.html";
    }

    /**
     * 無權訪問該資源異常
     */
    @ExceptionHandler(UndeclaredThrowableException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public ErrorResponseData credentials(UndeclaredThrowableException e) {
        getRequest().setAttribute("tip", "權限異常");
        log.error("權限異常!", e);
        return new ErrorResponseData(BizExceptionEnum.NO_PERMITION.getCode(), BizExceptionEnum.NO_PERMITION.getMessage());
    }




    /**
     * 攔截未知的運行時異常
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ErrorResponseData notFount(RuntimeException e) {
        LogManager.me().executeLog(LogTaskFactory.exceptionLog(ShiroKit.getUser().getId(), e));
        getRequest().setAttribute("tip", "服務器未知運行時異常");
        log.error("運行時異常:", e);
        return new ErrorResponseData(BizExceptionEnum.SERVER_ERROR.getCode(), e.getMessage());
    }
}

 

(12)common.js

var CommonTool={}

/**
 * 判斷是字符串是否爲null,'undifined',空串,空格 如果是,返回true;
 * 判斷對象是否爲空,如果爲空對象或集合,返回true
 *
 * @param value
 * @returns {boolean}
 */
CommonTool.isEmpty=function (value) {

    //判斷是否是undefined和""
    if(!value){
        return true;
    }

    //如果是字符串,還要去掉首尾空格後判斷長度是否爲0
    if(typeof(value) == 'string' && value.trim().length==0){
        return true;
    }


    //如果是對象
    if(typeof(value) == 'object' || JSON.stringify(value) === '{}'){
        return true;
    }
    return false;

}


/**
 * 收集請求參數的工具類,每一個字段的值只能是字符串或數字,不能爲對象或數組
 * @param fieldArr 定義一個數組,封裝所有收據數據的字段,必傳
 * @param needArr 定義一個數組,封裝字段是否是必傳字段,注意必須與 fieldArr中對應字段的角標一致,必傳
 * @param fieldNameArr 定義一個數組,封裝對應字段的字段名稱,可以爲null
 * @param typeArr 定義一個數組,用於判斷獲取的字段的值的是字符串還是數字,如果是字符串,就默認不判斷 。爲''時不判斷
 * @param strMaxLengthArr 定義數組,指定字符串類型的字段的值的最大長度。<=0 ,表示該字段不用比較
 * @returns 返回收集到的數據或者錯誤信息
 */
CommonTool.collectData = function (fieldArr,needArr,fieldNameArr,typeArr,strMaxLengthArr) {
    var result = {} ;

    //如果這三個數組中有空數組,直接返回空map
    if(!fieldArr ||!needArr || !fieldNameArr){
        return result;
    }

    //收集數據
    for(var i = 0; i<fieldArr.length;i++){
        var key = fieldArr[i];

        //獲取對話框中的值
        var value = $("#" + key).val();

        var fileName =fieldNameArr[i];

        //判斷是否爲null
        if(value){
            //判斷是否是數組或對象,如果是,則返回錯誤信息
            if(typeof (value)!='string'){
                return "【"+fileName+"】只能是字符串或數字,不能是【"+typeof (value)+"】";
            }
            //去除首尾空格
            value =value.trim();
        }

        //如果該字段必傳,但是沒有傳值,則前端提示錯誤信息,並終止後面的操作
        if(needArr && needArr[i] && !value){
            //將異常返回出去
            return "【"+fileName+"】不能爲空!";
        }

        //如果走到這裏了,發現還是空串,直接進入下一個循環
        if(!value){
            continue;
        }


        //判斷是否需要判斷類型,如果是,則判斷類型是否正確,

        if(typeArr && typeArr[i]){

            var type = typeArr[i];

            //判斷是否是數字
            if(type === 'number' && isNaN(value)){
                return "【"+fileName+"】只能是數字!";
            }

            //判斷是否是大寫字母
            if(type === 'capitalLetter' && !(value>='A' && value<='Z')){
                return "【"+fileName+"】只能是單個大寫字母!";
            }

        }

        //判斷長度
        if(strMaxLengthArr && strMaxLengthArr[i]  ){

            var maxLength = strMaxLengthArr[i];
            if( maxLength>0 && value.length>maxLength){
                return "【"+fileName+"】長度不能超過 "+maxLength+"";
            }


        }

        result[key] = value;
    }

    return result;

}

/**
 * 對數組中的元素進行笛卡爾積
 *  如: CommonTool.calcDescartes([[1,2,3],['a','b','c'],['X','Y','Z']])
 *  結果:返回一個數組,容量爲27 ,結果如下:
 *  [[1,"a","X"],[1,"a","Y"],[1,"a","Z"],[1,"b","X"],[1,"b","Y"],[1,"b","Z"],
 *  [1,"c","X"],[1,"c","Y"],[1,"c","Z"],[2,"a","X"],[2,"a","Y"],[2,"a","Z"],
 *  [2,"b","X"],[2,"b","Y"],[2,"b","Z"],[2,"c","X"],[2,"c","Y"],[2,"c","Z"],
 *  [3,"a","X"],[3,"a","Y"],[3,"a","Z"],[3,"b","X"],[3,"b","Y"],[3,"b","Z"],
 *  [3,"c","X"],[3,"c","Y"],[3,"c","Z"]]
 * @param array
 * @returns {*}
 */
CommonTool.calcDescartes=function (array) {
    if (array.length < 2) {
        return array[0] || [];
    }

    return [].reduce.call(array, function (col, set) {
        var res = [];
        col.forEach(function (c) {
            set.forEach(function (s) {
                var t = [].concat(Array.isArray(c) ? c : [c]);
                t.push(s);
                res.push(t);
            })
        });
        return res;
    });
}

 

 

(13)_container.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="renderer" content="webkit"/><!-- 讓360瀏覽器默認選擇webkit內核 -->

    <!-- 全局css -->
    <link rel="shortcut icon" href="${ctxPath}/static/favicon.ico">
    <link href="${ctxPath}/static/css/bootstrap.min.css?v=3.3.6" rel="stylesheet">
    <link href="${ctxPath}/static/css/font-awesome.css?v=4.4.0" rel="stylesheet">
    <link href="${ctxPath}/static/css/plugins/chosen/chosen.css" rel="stylesheet">
    <link href="${ctxPath}/static/css/plugins/bootstrap-table/bootstrap-table.min.css" rel="stylesheet">
    <link href="${ctxPath}/static/css/plugins/validate/bootstrapValidator.min.css" rel="stylesheet">
    <link href="${ctxPath}/static/css/style.css?v=4.1.0" rel="stylesheet">
    <link href="${ctxPath}/static/css/_fstyle.css" rel="stylesheet">
    <link href="${ctxPath}/static/css/plugins/iCheck/custom.css" rel="stylesheet">
    <link href="${ctxPath}/static/css/plugins/webuploader/webuploader.css?j=${date().time}" rel="stylesheet">
    <link href="${ctxPath}/static/css/plugins/ztree/zTreeStyle.css" rel="stylesheet">
    <link href="${ctxPath}/static/css/plugins/bootstrap-treetable/bootstrap-treetable.css" rel="stylesheet"/>


    <!-- 全局js -->
    <script src="${ctxPath}/static/js/jquery.min.js?v=2.1.4"></script>
    <script src="${ctxPath}/static/js/bootstrap.min.js?v=3.3.6"></script>
        <!-- 時間操作相關的common.js -->
    <script src="${ctxPath}/static/modular/elephish/common/moment.js"></script>
    <script src="${ctxPath}/static/modular/elephish/common/common.js?j=${date().time}"></script>
    <script src="${ctxPath}/static/js/plugins/ztree/jquery.ztree.all.min.js"></script>
    <script src="${ctxPath}/static/js/plugins/bootstrap-table/bootstrap-table.min.js"></script>
    <script src="${ctxPath}/static/js/plugins/validate/bootstrapValidator.min.js"></script>
    <script src="${ctxPath}/static/js/plugins/validate/zh_CN.js"></script>
    <script src="${ctxPath}/static/js/plugins/bootstrap-table/bootstrap-table-mobile.min.js"></script>
    <script src="${ctxPath}/static/js/plugins/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
    <script src="${ctxPath}/static/js/plugins/bootstrap-treetable/bootstrap-treetable.js?j=${date().time}"></script>
    <script src="${ctxPath}/static/js/plugins/layer/layer.js"></script>
    <script src="${ctxPath}/static/js/plugins/chosen/chosen.jquery.js"></script>
    <script src="${ctxPath}/static/js/plugins/iCheck/icheck.min.js"></script>
    <script src="${ctxPath}/static/js/plugins/laydate/laydate.js"></script>
    <!--<script src="${ctxPath}/static/js/plugins/webuploader/webuploader.min.js"></script>-->
    <script src="${ctxPath}/static/js/plugins/webuploader/webuploader.js?j=${date().time}"></script>
    <script src="${ctxPath}/static/js/common/ajax-object.js"></script>
    <script src="${ctxPath}/static/js/common/bootstrap-table-object.js?j=${date().time}"></script>
    <script src="${ctxPath}/static/js/common/tree-table-object.js?j=${date().time}"></script>
    <script src="${ctxPath}/static/js/common/web-upload-object.js?j=${date().time}"></script>
    <script src="${ctxPath}/static/js/common/ztree-object.js"></script>
    <script src="${ctxPath}/static/js/common/Feng.js"></script>




    <style type="text/css">
        table{  
            width:100px;  
            table-layout:fixed;/* 只有定義了表格的佈局算法爲fixed,下面td的定義才能起作用。 */  
        }
        td{  
            width:100%;  
            word-break:keep-all;/* 不換行 */  
            white-space:nowrap;/* 不換行 */  
            overflow:hidden;/* 內容超出寬度時隱藏超出部分的內容 */  
            text-overflow:ellipsis;/* 當對象內文本溢出時顯示省略標記(...) ;需與overflow:hidden;一起使用*/  
        }
    </style>

    <script type="text/javascript">
        Feng.addCtx("${ctxPath}");
        Feng.sessionTimeoutRegistry();
    </script>
</head>

<body class="gray-bg">
<div class="wrapper wrapper-content">
    ${layoutContent}
</div>
<script src="${ctxPath}/static/js/content.js?v=1.0.0"></script>
</body>
</html>

 

至此,商品分類管理的添加功能實現。

 

該系列更多文章請前往 Guns二次開發目錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章