项目实战三-元数据模块-数据字典条目
项目整合
control层请求参数的封装
- 整合点梳理
-
控制器中的list方法
public Map<String,Object> list(long page, long size,String keyword){};
- 需要接收参数,而且有可能有n个搜索参数,因此可以封装
-
前面讲过思路: 将接收参数封装成一个query对象,不同的页面搜索使用不同的query类,而且每次查询的结果封装给这个query对象
public Map<String,Object> list(DictTypeQuery query){};
-
-
在pojo下新增一个query包,下面存放各个页面的query类
//基类PageQuery @Data public class PageQuery { private static final int DEFAULT_SIZE = 10; private long page; private long size; //当前页的数据 private List<?> data; //总数 private long count; //总页数 private long pages; public long getPage() { return Math.max(page,1); } public long getSize() { return (size < 1) ? DEFAULT_SIZE : size; } } //KeywordQuery @EqualsAndHashCode(callSuper = true) @Data public class KeywordQuery extends PageQuery { private String keyword; } //数据字典类型页面 @EqualsAndHashCode(callSuper = true) @Data public class DictTypeQuery extends KeywordQuery {} //省份页面 @EqualsAndHashCode(callSuper = true) @Data public class ProvinceQuery extends KeywordQuery{}
- service层更新
-
数据字典类型页面
//DictTypeService public interface DictTypeService extends IService<DictType> { void list(DictTypeQuery query); } //DictTypeServiceImpl @Service @Transactional public class DictTypeServiceImpl extends ServiceImpl<DictTypeMapper, DictType> implements DictTypeService { @Autowired private DictTypeMapper mapper; @Override public void list(DictTypeQuery query) { //配置搜索条件 LambdaQueryWrapper<DictType> queryWrapper = new LambdaQueryWrapper<>(); String keyword = query.getKeyword(); if(!StringUtils.isEmpty(keyword)){ queryWrapper.like(DictType::getName,keyword).or() .like(DictType::getValue,keyword).or() .like(DictType::getIntro,keyword); } //根据id降序 queryWrapper.orderByDesc(DictType::getId); MpPage<DictType> page = new MpPage<>(query); //查询 mapper.selectPage(page,queryWrapper); //填充结果 page.updateQuery(); } }
-
省份页面
public interface ProvinceService extends IService<Province> { void list(ProvinceQuery query); } @Service @Transactional public class ProvinceServiceImpl extends ServiceImpl<ProvinceMapper, Province> implements ProvinceService { @Autowired private ProvinceMapper mapper; @Override public void list(ProvinceQuery query) { String keyword = query.getKeyword(); //配置搜索条件 LambdaQueryWrapper<Province> queryWrapper = new LambdaQueryWrapper<>(); if(!StringUtils.isEmpty(keyword)){ queryWrapper.like(Province::getName,keyword).or() .like(Province::getPlate,keyword); } //根据id降序 queryWrapper.orderByDesc(Province::getId); mapper.selectPage(new MpPage<>(query),queryWrapper).updateQuery(); } }
-
-
MpPage是Page的增强类,用于抽取冗余代码
//com.zh.jk.common.enhance public class MpPage<T> extends Page<T> { private PageQuery query; public MpPage(PageQuery query) { super(query.getPage(),query.getSize()); this.query = query; } public void updateQuery() { //填充结果 query.setCount(getTotal()); query.setPages(getPages()); query.setData(getRecords()); query.setPages(getCurrent()); query.setSize(getSize()); } }
- controller
- DictTypeController、ProvinceController的list方法如下
//DictTypeController @GetMapping public Map<String,Object> list(DictTypeQuery query) { service.list(query); Map<String,Object> map = new HashMap<>(); map.put("code",0); map.put("msg",""); map.put("count",query.getCount()); map.put("data",query.getData()); return map; } //ProvinceController @GetMapping public Map<String,Object> list(ProvinceQuery query) { service.list(query); Map<String,Object> map = new HashMap<>(); map.put("code",0); map.put("msg",""); map.put("count",query.getCount()); map.put("data",query.getData()); return map; }
service层优化
- 项目的整体思路
- controller调用service,serivce调用mapper,mapper调用数据库
- service层是依赖于mybatis-plus插件
- ClassService继承与IService
- ClassServiceImpl继承与ServiceImpl
优化service层(与上面代码进行对比查看)
- 优化掉成员mapper
- DictTypeServiceImpl继承
ServiceImpl<DictTypeMapper, DictType>
- 可以看到已经传递了DictTypeMapper泛型,查看ServiceImpl类,内部有个baseMapper成员
- 因此DictTypeServiceImpl内部不需要在设置成员,直接使用baseMapper
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> { protected Log log = LogFactory.getLog(this.getClass()); //会自动注入M类型的对象 @Autowired protected M baseMapper; ... }
- DictTypeServiceImpl继承
- 优化配置搜索条件
- 会发现关键字搜索那块,ProvinceServiceImpl/DictTypeServiceImpl二者非常相似,可以抽取
- 新建一个MpQueryWrapper类继承自LambdaQueryWrapper,做一个增强,将搜索配置条件那块代码抽取进去
- 改造如下
-
MpQueryWrapper
//com.zh.jk.common.enhance public class MpQueryWrapper<T> extends LambdaQueryWrapper<T> { @SafeVarargs public final MpQueryWrapper<T> like(Object val, SFunction<T, ?>... funcs) { if (val == null) return this;; String str = val.toString(); if (str.length() == 0) return this; // this.like(funs[0],val).or().like(funs[1],val).or().like(funs[2],val).or(); //nested 会自动去掉最后一个or.(),返回值是MpQueryWrapper的父类,因此这要做一个强制转换 return (MpQueryWrapper<T>) nested((w)->{ for (SFunction<T, ?> func : funcs) { w.like(func,str).or(); } }); } }
-
DictTypeServiceImpl
@Service @Transactional public class DictTypeServiceImpl extends ServiceImpl<DictTypeMapper, DictType> implements DictTypeService { @Override public void list(DictTypeQuery query) { //配置搜索条件 MpQueryWrapper<DictType> queryWrapper = new MpQueryWrapper<>(); queryWrapper.like(query.getKeyword(),DictType::getName,DictType::getValue,DictType::getIntro); //根据id降序 queryWrapper.orderByDesc(DictType::getId); MpPage<DictType> page = new MpPage<>(query); //查询 //selectPage返回值是MpPage<DictType> baseMapper.selectPage(page,queryWrapper); //填充结果 page.updateQuery(); } }
-
ProvinceServiceImpl
@Service @Transactional public class ProvinceServiceImpl extends ServiceImpl<ProvinceMapper, Province> implements ProvinceService { @Override public void list(ProvinceQuery query) { String keyword = query.getKeyword(); //配置搜索条件 MpQueryWrapper<Province> queryWrapper = new MpQueryWrapper<>(); queryWrapper.like(query.getKeyword(),Province::getName,Province::getPlate); //根据id降序 queryWrapper.orderByDesc(Province::getId); baseMapper.selectPage(new MpPage<>(query),queryWrapper).updateQuery(); } }
-
controller层返回给客户端的结果优化
-
通常一个后台项目返回给客户端的n个接口数据格式是一样的,统一的规范,比如规范如下:
{ "code": 1, "msg": "成功或者失败信息", "data":[ ... ] }
-
在pojo下新建一个result包,下面新建一个类R
//com.zh.jk.pojo.result /** * 返回给用户的JSON对象 */ public class R extends HashMap<String,Object>implements Jsonable { private static final String K_CODE = "code"; private static final String K_MSG = "msg"; private static final String K_DATA = "data"; private static final int CODE_SUCCESS = 0; private static final int CODE_ERROR_DEFAULT = CodeMsg.BAD_REQUEST.getCode(); public R() { this(true); } public R(boolean success) { this(success,null); } public R(boolean success, String msg) { this(success? CODE_SUCCESS : CODE_ERROR_DEFAULT,msg); } public R(int code,String msg) { put(K_CODE,code); put(K_MSG,msg); } public R(CodeMsg codeMsg) { this(codeMsg.getCode(),codeMsg.getMsg()); } public R(String msg,Object data) { this(data); put(K_MSG,msg); } public R(Object data) { put(K_CODE,CODE_SUCCESS); put(K_DATA,data); } }
-
在common下新建一个util包,内部新建一个Rs类,专门针对R类扩展的一些方法
//com.zh.jk.common.util public class Rs { private static final String K_COUNT = "count"; public static R ok(PageQuery query) { R r = new R(query.getData()); r.put(K_COUNT,query.getCount()); return r; } public static R error(String msg) { return new R(false,msg); } public static R error(Throwable t) { if (t instanceof CommonException){ CommonException e = (CommonException)t; return new R(e.getCode(),e.getMessage()); }else { return error(t.getMessage()); } } public static R ok(CodeMsg codeMsg) { return new R(true,codeMsg.getMsg()); } public static R ok(String msg) { return new R(true,msg); } public static R ok(Object data) { return new R(data); } public static R ok() { return new R(); } public static R raise(String msg) throws CommonException { throw new CommonException(msg); } public static R raise(CodeMsg codeMsg) throws CommonException { throw new CommonException(codeMsg); } }
-
DictTypeController控制器内部方法改造
@GetMapping public R list(DictTypeQuery query) { service.list(query); return Rs.ok(query); } @PostMapping("/remove") public R remove(String id){ //id = 10 或者 id = 10,20,30 String[] idStrs = id.split(","); //批量删除 //数组转化为集合 //Arrays.asList(idStrs) if (service.removeByIds(Arrays.asList(idStrs))){ return Rs.ok("删除成功"); }else { return Rs.error("删除失败"); } } @PostMapping("/save") public R save(DictType dictType){ if ( service.saveOrUpdate(dictType)){ return Rs.ok("保存成功"); }else { return Rs.error("保存失败"); } }
统一异常处理
- 如果因为某些原因java代码抛出异常了,通常情况下返回给客户端的是非常长的异常信息,那么可以进行异常的统一处理,友好的返回给客户端
- 客户端收到的状态码不能是直接设定,必须是异常形式抛出,才会有效
-
result包下新建一个CodeMsg枚举类,存放各种结果编码
/常用编码 public enum CodeMsg { BAD_REQUEST(400, "请求出错"), UNAUTHORIZED(401, "未授权"), FORBIDDEN(403, "禁止访问"), NOT_FOUND(404, "资源不存在"), INTERNAL_SERVER_ERROR(500, "服务器内部错误"), OPERATE_OK(0, "操作成功"), SAVE_OK(0, "保存成功"), REMOVE_OK(0, "删除成功"), OPERATE_ERROR(40001, "操作失败"), SAVE_ERROR(40002, "保存失败"), REMOVE_ERROR(40003, "删除失败"), UPLOAD_IMG_ERROR(40004, "图片上传失败"), WRONG_USERNAME(50001, "用户名不存在"), WRONG_PASSWORD(50002, "密码错误"), USER_LOCKED(50003, "用户被锁定,无法正常登录"), WRONG_CAPTCHA(50004, "验证码错误"), NO_TOKEN(60001, "没有Token,请登录"), TOKEN_EXPIRED(60002, "Token过期,请重新登录"), NO_PERMISSION(60003, "没有相关的操作权限"); private final int code; private final String msg; CodeMsg(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } }
-
enhance下新建一个Jsonable,只要一个类实现这个接口就可以将对象转化为json字符串,让R类实现
public interface Jsonable { default String jsonString() throws Exception { ObjectMapper mapper = new ObjectMapper(); //如果json对象的属性为null,则不需要转化 mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); return mapper.writeValueAsString(this); } }
-
需要在application.yml中添加配置
spring: # 如果json中的某个属性值为null,这不需要转化为json字符串 jackson: default-property-inclusion: non_null
-
-
common包下新建一个exception包,自定义异常类CommonException
//com.zh.jk.common.exception @EqualsAndHashCode(callSuper = true) @Data public class CommonException extends RuntimeException { private int code; public CommonException() { this(CodeMsg.BAD_REQUEST.getCode(), null); } public CommonException(String msg) { this(msg, null); } public CommonException(int code, String msg) { this(code, msg, null); } public CommonException(String msg, Throwable cause) { this(CodeMsg.BAD_REQUEST.getCode(), msg, cause); } public CommonException(int code, String msg, Throwable cause) { super(msg, cause); this.code = code; } public CommonException(CodeMsg codeMsg) { this(codeMsg, null); } public CommonException(CodeMsg codeMsg, Throwable cause) { this(codeMsg.getCode(), codeMsg.getMsg(), cause); } public int getCode() { return code; } }
springMVC处理异常
- springMVC可以通过给一个类添加(
@ControllerAdvice
)注解,拦截所有异常@ControllerAdvice(basePackages = "com.zh.jk")
: basePackages表示扫描所需要异常处理的包
- 定义相关异常的方法,方法上加入注解
@ExceptionHandler(value属性为异常的类)
:- 一旦抛出异常会主动调用该方法
- 具体异常:
@ExceptionHandler(value = MethodArgumentNotValidException.class)
- 未知异常:
@ExceptionHandler(value = Throwable.class)
-
定义CommonExceptionHandler类,统一拦截所有异常
//com.zh.jk.common.exception //拦截所有异常 @ControllerAdvice public class CommonExceptionHandler { @ExceptionHandler(Throwable.class) public void handle(Throwable t, HttpServletRequest request, HttpServletResponse response) throws Exception { //设置返回数据的格式 response.setContentType("application/json;charset=UTF-8"); //所有异常都为400 response.setStatus(400); //异常信息写回给客户端 response.getWriter().write(Rs.error(t).jsonString()); //在debug下才会打印 Debugs.run(t::printStackTrace); } }
-
在util包下新建Debugs,专门用于设置整个项目是否是debug环境
package com.zh.jk.common.util; public class Debugs { public static final boolean DEBUG = false; public static void run(Runnable runnable) { if (!DEBUG) return; if (runnable == null) return; runnable.run(); } }
-
- 使用
@RestControllerAdvice
简化CommonExceptionHandler-
该注解是
@ControllerAdvice、@ResponseBody
的组合注解//拦截所有异常 @RestControllerAdvice @Slf4j public class CommonExceptionHandler { @ExceptionHandler(Throwable.class) @ResponseStatus(code = HttpStatus.BAD_REQUEST) public R handle(Throwable t) { log.error(null,t); return Rs.error(t); } }
- Slf4j代替Debugs
- ResponseStatus代替response.setStatus
- 直接返回R可以直接转json
-
-
DictTypeController控制器内部方法改造
@PostMapping("/remove") public R remove(String id){ //id = 10 或者 id = 10,20,30 String[] idStrs = id.split(","); //批量删除 //数组转化为集合 //Arrays.asList(idStrs) if (service.removeByIds(Arrays.asList(idStrs))){ return Rs.ok("删除成功"); }else { throw new CommonException("删除失败"); } } @PostMapping("/save") public R save(DictType dictType){ if ( service.saveOrUpdate(dictType)){ return Rs.ok("保存成功"); }else { throw new CommonException("保存失败"); } }
数据字典条目开发
-
新建查询条件类DictItemQuery
//com.zh.jk.pojo.query @EqualsAndHashCode(callSuper = true) @Data public class DictItemQuery extends KeywordQuery { /** * 所属的类型 */ private Integer typeId; }
-
service层
//com.zh.jk.service public interface DictItemService extends IService<DictItem> { void list(DictItemQuery query); } //DictItemServiceImpl @Service @Transactional public class DictItemServiceImpl extends ServiceImpl<DictItemMapper, DictItem> implements DictItemService { @Override public void list(DictItemQuery query) { //查询条件 MpQueryWrapper<DictItem> wrapper = new MpQueryWrapper<>(); wrapper.like(query.getKeyword(),DictItem::getName,DictItem::getValue); Integer typeId = query.getTypeId(); //联合外键查询!!!上面用or,这个要用eq,相当于AND if (typeId != null && typeId >0 ){ wrapper.eq(DictItem::getTypeId,typeId); } //排序方式 wrapper.orderByDesc(DictItem::getId); //查询 baseMapper.selectPage(new MpPage<>(query),wrapper).updateQuery(); } }
-
controller层改造
-
新建一个BaseController,抽取基层代码
public abstract class BaseController<T> { protected abstract IService<T> getService(); @PostMapping("/remove") public R remove(String id){ //id = 10 或者 id = 10,20,30 String[] idStrs = id.split(","); //批量删除 //数组转化为集合 //Arrays.asList(idStrs) if (getService().removeByIds(Arrays.asList(idStrs))){ //return Rs.ok("删除成功"); return Rs.ok(CodeMsg.REMOVE_OK); }else { //throw new CommonException("删除失败"); return Rs.raise(CodeMsg.REMOVE_ERROR); } } @PostMapping("/save") public R save(T entity){ if ( getService().saveOrUpdate(entity)){ //return Rs.ok("保存成功"); return Rs.ok(CodeMsg.SAVE_OK); }else { //throw new CommonException("保存失败"); return Rs.raise(CodeMsg.SAVE_ERROR); } } }
-
DictItemController/DictTypeController/ProvinceController如下:
//DictItemController @RestController @RequestMapping("/dictItems") public class DictItemController extends BaseController<DictItem> { @Autowired private DictItemService service; @GetMapping public R list(DictItemQuery query) { service.list(query); return Rs.ok(query); } @Override protected IService<DictItem> getService() { return service; } } //DictTypeController @RestController @RequestMapping("/dictTypes") public class DictTypeController extends BaseController<DictType> { @Autowired private DictTypeService service; @GetMapping public R list(DictTypeQuery query) { service.list(query); return Rs.ok(query); } //获取所有数据,不分页查询 @GetMapping("/list") public R list() { return Rs.ok(service.list()); } @Override protected IService<DictType> getService() { return service; } } //ProvinceController @RestController @RequestMapping("/provinces") public class ProvinceController extends BaseController<Province> { @Autowired private ProvinceService service; @GetMapping public Map<String,Object> list(ProvinceQuery query) { service.list(query); return Rs.ok(query); } @Override protected IService<Province> getService() { return service; } }
-
前端代码改造
- metadata下新建dictItem文件夹,下面新建list.html/save.html,代码略
-