业务场景:老生常谈的CRUD,今天是处理一个针对告警关键词频表单的操作,核心字段为 关键词、是否开启 , 后台记录的是否开启字段并非为 是,否, 转换成了数值记录了 1表示开启, 2表示关掉。在实际开发中,对于一些状态类的字段,我们通常使用的是枚举,而保存到数据库时,我们是用的枚举的某一个属性进行保存的,这里就会有一个问题,在VO类中,如果我们直接使用枚举类型去映射数据库的对应字段保存时,往往就会因为类型不匹配导致映射失败,如果要解决这个问题,办法有很多种,Mybatis-plus提供了一种解决办法,就是使用@EnumValue、@JsonValue注解,这里我们就使用这种方式。
package com.xxx.service;
import java.util.*;
import com.xxx.delegate.ProdProblemRuleDelegate;
import com.xxx.model.ProdProblemRule;
import org.springframework.web.bind.annotation.*;
import com.xxx.model.ProdProblemRuleParam;
import org.springframework.validation.annotation.Validated;
import com.xxx.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import com.xxx.model.ResponseVo;
@RestController
@RequestMapping(value = "/prodProblemRule", produces = {"application/json;charset=UTF-8"})
@Validated
public class ProdProblemRuleController {
@Autowired(required=false)
private ProdProblemRuleDelegate delegate;
@RequestMapping(
value = "/delete",
produces = { "application/json" },
method = RequestMethod.POST)
public ResponseVo deleteByIds(@RequestBody List ids)
throws ServiceException {
return delegate.deleteByIds(ids);
}
@RequestMapping(
value = "/export",
produces = { "application/octet-stream" },
method = RequestMethod.POST)
public void exportExcel(HttpServletResponse response, @RequestBody ProdProblemRuleParam queryParam)
throws ServiceException {
delegate.exportExcel(response, queryParam);
}
@RequestMapping(
value = "/exportTemplate",
produces = { "application/octet-stream" },
method = RequestMethod.POST)
public void exportExcelTemplate(HttpServletResponse response)
throws ServiceException {
delegate.exportExcelTemplate(response);
}
@RequestMapping(
value = "/import",
produces = { "application/json" },
method = RequestMethod.POST)
public ResponseVo importExcel(
@RequestPart(value="file", required=true) MultipartFile file)
throws ServiceException {
return delegate.importExcel(file);
}
@RequestMapping(
value = "/search",
produces = { "application/json" },
method = RequestMethod.POST)
public ResponseVo selectPageList(@RequestBody ProdProblemRuleParam queryParam)
throws ServiceException {
return delegate.selectPageList(queryParam);
}
@RequestMapping(
value = "/upsert",
produces = { "application/json" },
method = RequestMethod.POST)
public ResponseVo upsertKeyword(@RequestBody ProdProblemRule entity)
throws ServiceException {
return delegate.upsertKeyword(entity);
}
}
package com.xxx.xxx.delegate;
import java.util.*;
import org.springframework.web.multipart.MultipartFile;
import com.xxx.xxx.model.ProdProblemRuleParam;
import javax.servlet.http.HttpServletResponse;
import com.xxx.xxx.model.ResponseVo;
import com.xxx.exception.ServiceException;
import com.xxx.xxx.model.ProdProblemRule;
public interface ProdProblemRuleDelegate {
ResponseVo deleteByIds(List ids)
throws ServiceException;
void exportExcel(HttpServletResponse response, ProdProblemRuleParam queryParam)
throws ServiceException;
void exportExcelTemplate(HttpServletResponse response)
throws ServiceException;
ResponseVo importExcel( MultipartFile file)
throws ServiceException;
ResponseVo selectPageList(ProdProblemRuleParam queryParam)
throws ServiceException;
ResponseVo upsertKeyword(ProdProblemRule entity)
throws ServiceException;
}
package com.xxx.impl;
import com.xxx.annotation.UserPermission;
import com.xxx.domain.model.warn.ProductionProblemKeyword;
import com.xxx.enums.warn.ProdProblemKeywordSwitchEnum;
import com.xxx.delegate.ProdProblemRuleDelegate;
import com.xxx.model.ProdProblemRule;
import com.xxx.model.ProdProblemRuleParam;
import com.xxx.model.ResponseVo;
import com.xxx.service.warn.ProductionProblemKeywordService;
import com.xxx.utils.ResponseUtils;
import com.xxx.utils.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
/**
* ProdProblemRuleDelegate实现层
*
*/
@RequiredArgsConstructor
@Slf4j
@Component
public class ProdProblemRuleDelegateImpl implements ProdProblemRuleDelegate {
private final ProductionProblemKeywordService service;
/**
* 根据id列表批量删除行
*
* @param ids id列表
* @return 返回信息
*/
@Override
@UserPermission(username = {"用户1","用户2"})
public ResponseVo deleteByIds(List ids) {
service.delete(ids);
return ResponseUtils.successResponse("删除成功");
}
/**
* exportExcel
*
* @param response 返回数据
* @param queryParam 请求参数
*/
@Override
@UserPermission(username = {"用户1","用户2"})
public void exportExcel(HttpServletResponse response, ProdProblemRuleParam queryParam) {
final List keywordList = StringUtils.splitToList(queryParam.getKeyword());
ProdProblemKeywordSwitchEnum switchEnum = ProdProblemKeywordSwitchEnum.getByLabel(queryParam.getIsAvailable());
service.download(response, keywordList, switchEnum);
}
/**
* exportExcelTemplate
*
* @param response 返回数据
*/
@Override
public void exportExcelTemplate(HttpServletResponse response) {
service.downloadTemplate(response);
}
/**
* 导入xlsx文件
*
* @param file 导入xlsx文件
* @return 导入结果
*/
@Override
@UserPermission(username = {"用户1","用户2"})
public ResponseVo importExcel(MultipartFile file) {
final String message = service.upload(file);
return ResponseUtils.successResponse(message);
}
/**
* 分页查询数据
*
* @param queryParam 查询参数
* @return 查询结果
*/
@Override
public ResponseVo selectPageList(ProdProblemRuleParam queryParam) {
final List keywordList = StringUtils.splitToList(queryParam.getKeyword());
ProdProblemKeywordSwitchEnum switchEnum = ProdProblemKeywordSwitchEnum.getByLabel(queryParam.getIsAvailable());
final Page page =
service.selectPage(keywordList, switchEnum, queryParam.getCurrentPage(), queryParam.getPageSize());
return ResponseUtils.successResponse(page, "查询成功");
}
/**
* 新增/更新entity,根据是否有id和keyword是否判断
*
* @param entity entity
* @return 新增/更新结果
*/
@Override
@UserPermission(username = {"用户1","用户2"})
public ResponseVo upsertKeyword(ProdProblemRule entity) {
final ProductionProblemKeyword keyword = new ProductionProblemKeyword();
keyword.setId(entity.getId());
keyword.setKeyword(entity.getKeyword());
keyword.setIsAvailable(ProdProblemKeywordSwitchEnum.getByLabel(entity.getIsAvailable()));
if (keyword.getId() == null) {
return ResponseUtils.successResponse(service.insert(keyword), "新增成功");
} else {
return ResponseUtils.successResponse(service.update(keyword), "修改成功");
}
}
}
package com.xxx.service.warn;
import com.xxx.domain.model.warn.ProductionProblemKeyword;
import com.xxx.enums.warn.ProdProblemKeywordSwitchEnum;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
@Service
public interface ProductionProblemKeywordService {
/**
* 分页查询数据
*
* @param keywordList 过滤条件:关键字列表,单个item时模糊查询,多个时in查询,为空时忽略
* @param switchEnum 过滤条件:是否开启,为空时忽略
* @param curPage 当前页号
* @param pageSize 分页大小
* @return 分页查询结果
*/
Page selectPage(List keywordList, ProdProblemKeywordSwitchEnum switchEnum, Integer curPage, Integer pageSize);
/**
* 新增数据
*
* @param keyword entity
* @return 新增后的数据(带id)
*/
ProductionProblemKeyword insert(ProductionProblemKeyword keyword);
/**
* 更新数据
*
* @param keyword entity
* @return 更新后的数据
*/
ProductionProblemKeyword update(ProductionProblemKeyword keyword);
/**
* 根据id列表批量删除行
*
* @param ids id列表
*/
void delete(List ids);
/**
* 根据查询条件导出xlsx文件
*
* @param response 返回数据
* @param keywordList 关键字列表
* @param switchEnum 开关枚举
*/
void download(HttpServletResponse response, List keywordList, ProdProblemKeywordSwitchEnum switchEnum);
/**
* 下载仅有表头的模板文件
*
* @param response 返回数据
*/
void downloadTemplate(HttpServletResponse response);
/**
* 通过xlsx文件导入数据
*
* @param file xlsx文件
* @return 导入结果信息
*/
String upload(MultipartFile file);
}
package com.xxx.service.impl.warn;
import com.xxx.domain.model.warn.ProductionProblemKeyword;
import com.xxx.enums.warn.ProdProblemKeywordSwitchEnum;
import com.xxx.service.dao.mapper.warn.ProductionProblemKeywordMapper;
import com.xxx.service.warn.ProductionProblemKeywordService;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
@Service
@RequiredArgsConstructor
@Slf4j
public class ProductionProblemKeywordServiceImpl implements ProductionProblemKeywordService {
private final ProductionProblemKeywordMapper mapper;
/**
* 分页查询数据
*
* @param keywordList 过滤条件:关键字列表,单个item时模糊查询,多个时in查询,为空时忽略
* @param switchEnum 过滤条件:是否开启,为空时忽略
* @param curPage 当前页号
* @param pageSize 分页大小
* @return 分页查询结果
*/
@Override
public Page selectPage(List keywordList, ProdProblemKeywordSwitchEnum switchEnum, Integer curPage, Integer pageSize) {
Page page = new Page<>(curPage, pageSize);
final QueryWrapper wrapper = getQueryWrapper(keywordList, switchEnum);
return mapper.selectPage(page, wrapper);
}
/**
* 新增数据
*
* @param keyword entity
* @return 新增后的数据(带id)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ProductionProblemKeyword insert(ProductionProblemKeyword keyword) {
final List keywordList = mapper.selectList(
new QueryWrapper().eq("keyword", keyword.getKeyword()));
if (CollectionUtils.isNotEmpty(keywordList)) {
throw new IllegalArgumentException(String.format("新增失败:已存在[%s]关键字", keyword.getKeyword()));
}
mapper.insert(keyword);
return keyword;
}
/**
* 更新数据
*
* @param keyword entity
* @return 更新后的数据
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ProductionProblemKeyword update(ProductionProblemKeyword keyword) {
mapper.updateById(keyword);
return keyword;
}
/**
* 根据id列表批量删除行
*
* @param ids id列表
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(List ids) {
mapper.deleteBatchIds(ids);
}
/**
* 根据查询条件导出xlsx文件
*
* @param response 返回数据
* @param keywordList 关键字列表
* @param switchEnum 开关枚举
*/
@Override
public void download(HttpServletResponse response, List keywordList, ProdProblemKeywordSwitchEnum switchEnum) {
final QueryWrapper wrapper = getQueryWrapper(keywordList, switchEnum);
final List data = mapper.selectList(wrapper);
download(response, data);
}
/**
* 下载仅有表头的模板文件
*
* @param response 返回数据
*/
@Override
public void downloadTemplate(HttpServletResponse response) {
//emptyList()返回一个空的List(使用前提是不会再对返回的list进行增加和删除操作);
//好处:
//1)new ArrayList()创建时有初始大小,占用内存,emptyList()不用创建一个新的对象,可以 //减少内存开销;
//2)方法返回一个emptyList()时,不会报空指针异常,如果直接返回Null,没有进行非空判断会报空指针异常;
//注意:此List与常用的List不同,它是Collections类里的静态内部类,在继承AbstractList后并没有实现add()、remove()等方法,所以返回的List不能进行增加和删除元素操作。
download(response, Collections.emptyList());
}
/**
* 通过xlsx文件导入数据
*
* @param file xlsx文件
* @return 导入结果信息
*/
@Override
public String upload(MultipartFile file) {
try {
final ProductProblemKeywordListener listener = new ProductProblemKeywordListener();
EasyExcel.read(file.getInputStream(), ProductionProblemKeyword.class, listener)
.sheet()
.doRead();
return listener.getFinishMessage();
} catch (IOException e) {
log.error(file.getOriginalFilename() + "file read or write error: " + e.getLocalizedMessage());
return "文件读写错误";
}
}
/**
* 构造查询条件
*
* @param keywordList 关键字列表
* @param switchEnum 是否开启
* @return 查询wrapper
*/
@NotNull
private QueryWrapper getQueryWrapper(List keywordList, ProdProblemKeywordSwitchEnum switchEnum) {
final QueryWrapper wrapper = new QueryWrapper<>();
if (CollectionUtils.isNotEmpty(keywordList)) {
if (keywordList.size() == 1) {
wrapper.like("keyword", keywordList.get(0));
} else {
wrapper.in("keyword", keywordList);
}
}
if (switchEnum != null) {
wrapper.eq("is_available", switchEnum.getValue());
}
return wrapper;
}
/**
* 通用方法下载xlsx文件
*
* @param response 返回流
* @param data 数据集合
* @param 泛型对象class
*/
private static void download(HttpServletResponse response, Collection data) {
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition",
"attachment; filename=" + URLEncoder.encode("records", "UTF-8") + ".xlsx");
try (ServletOutputStream out = response.getOutputStream()) {
ExcelWriter excelWriter = EasyExcel.write(out).build();
final ExcelWriterSheetBuilder builder = EasyExcel.writerSheet("data");
WriteSheet writeSheet = builder.head(ProductionProblemKeyword.class).build();
excelWriter.write(data, writeSheet);
excelWriter.finish();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
} catch (UnsupportedEncodingException e) {
log.error("unsupported encode type :{}", e.getMessage());
}
}
/**
* 对于ProductionProblemKeyword的Listener:用于数据导入
*/
private class ProductProblemKeywordListener implements ReadListener {
// 新增统计
private int countInsert = 0;
// 更细统计
private int countUpdate = 0;
// 错误发生行号列表
private final List errorRowIndexes = new ArrayList<>();
// 当前行计数
private int count = 1;
// 返回信息
@Getter
private String finishMessage;
/**
* 读取一行对象后的触发器
*
* @param row 一行数据的对象
* @param analysisContext analysisContext
*/
@Override
public void invoke(ProductionProblemKeyword row, AnalysisContext analysisContext) {
++count;
if (row.getIsAvailable() == null) {
errorRowIndexes.add(count);
return;
}
final ProductionProblemKeyword keyword = mapper.selectOne(new QueryWrapper()
.eq("keyword", row.getKeyword())
.last("limit 1"));
if (keyword == null) {
++countInsert;
mapper.insert(row);
} else {
++countUpdate;
keyword.setIsAvailable(row.getIsAvailable());
mapper.updateById(keyword);
}
}
/**
* 读取完所有行后的处理方法
*
* @param analysisContext analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
finishMessage = String.format(Locale.ROOT, "总计处理数据%d行. 新增%d行,更新%d行,异常行:%s",
count - 1, countInsert, countUpdate,
errorRowIndexes.stream().map(String::valueOf).collect(Collectors.joining(",")));
log.info(finishMessage);
}
}
}
package com.xxx.service.dao.mapper.warn;
import com.xxx.domain.model.warn.ProductionProblemKeyword;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductionProblemKeywordMapper extends BaseMapper {
}
注意两点:
- 用到的是 easyExcel框架的表格字段注解, @ExcelProperty 列头 value值,以及顺序index , 还有不需要展示在表格则用@ExcelIgnore 忽略,比如主键字段。无需展示给前端用户。
- 状态值字段,表示 告警的词频是否开启,数据库用1,2 数值表示,而返回前端需要转换对应的实质意义,string类型 :是,否。用枚举类型,并且需要在注解中添加一个自定义的转换类 converter
package com.xxx.domain.model.warn;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.xxx.enums.warn.ProdProblemKeywordSwitchEnum;
import lombok.Data;
import java.io.Serializable;
/**
* ProductionProblemKeyword
*/
@Data
@TableName("defect_keyword")
public class ProductionProblemKeyword implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键id
*/
@TableId(value = "id", type = IdType.AUTO)
@ExcelIgnore
private Integer id;
/**
* 缺陷关键字
*/
@TableField("keyword")
@ExcelProperty(value = "告警规则", index = 0)
@ColumnWidth(value = 15)
private String keyword;
/**
* 1:有效,2:无效
*/
@TableField("is_available")
@ExcelProperty(value = "是否开启", index = 1,
converter = ProdProblemKeywordSwitchEnum.KeywordSwitchEnumConverter.class)
@ColumnWidth(value = 15)
private ProdProblemKeywordSwitchEnum isAvailable;
}
包含有分页属性
package com.xxx.qualitybigdata.model;
import java.util.Objects;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProdProblemRuleParam implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("keyword")
private String keyword = null;
@JsonProperty("isAvailable")
private String isAvailable = null;
@JsonProperty("currentPage")
private Integer currentPage = null;
@JsonProperty("pageSize")
private Integer pageSize = null;
public ProdProblemRuleParam() {
super();
}
/**
* 预警规则查询参数(由任意分隔符分隔)
**/
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
/**
* 预警规则开关:1:有效,2:无效
**/
public String getIsAvailable() {
return isAvailable;
}
public void setIsAvailable(String isAvailable) {
this.isAvailable = isAvailable;
}
/**
* 单前页号
**/
public Integer getCurrentPage() {
return currentPage;
}
public void setCurrentPage(Integer currentPage) {
this.currentPage = currentPage;
}
/**
* 分页大小
**/
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProdProblemRuleParam prodProblemRuleParam = (ProdProblemRuleParam) o;
return Objects.equals(this.keyword,
prodProblemRuleParam.keyword)
&& Objects.equals(this.isAvailable,
prodProblemRuleParam.isAvailable)
&& Objects.equals(this.currentPage,
prodProblemRuleParam.currentPage)
&& Objects.equals(this.pageSize,
prodProblemRuleParam.pageSize);
}
@Override
public int hashCode() {
return Objects.hash(keyword
, isAvailable
, currentPage
, pageSize);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ProdProblemRuleParam { ");
sb.append(" keyword: ").append(keyword).append(", ");
sb.append(" isAvailable: ").append(isAvailable).append(", ");
sb.append(" currentPage: ").append(currentPage).append(", ");
sb.append(" pageSize: ").append(pageSize).append(", ");
sb.append("} ");
return sb.toString();
}
}
为了规范,在修改表单数据的时候,传参类,我们是另外定义了一个实体类,属性基本跟表实体类一样
package com.xxx.qualitybigdata.model;
import java.util.Objects;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProdProblemRule implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("id")
private Integer id = null;
@JsonProperty("keyword")
private String keyword = null;
@JsonProperty("isAvailable")
private String isAvailable = null;
public ProdProblemRule() {
super();
}
/**
* 主键id
**/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
/**
* 预警规则关键字
**/
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
/**
* 预警规则开关:1:有效,2:无效
**/
public String getIsAvailable() {
return isAvailable;
}
public void setIsAvailable(String isAvailable) {
this.isAvailable = isAvailable;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProdProblemRule prodProblemRule = (ProdProblemRule) o;
return Objects.equals(this.id,
prodProblemRule.id)
&& Objects.equals(this.keyword,
prodProblemRule.keyword)
&& Objects.equals(this.isAvailable,
prodProblemRule.isAvailable);
}
@Override
public int hashCode() {
return Objects.hash(id
, keyword
, isAvailable);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class ProdProblemRule { ");
sb.append(" id: ").append(id).append(", ");
sb.append(" keyword: ").append(keyword).append(", ");
sb.append(" isAvailable: ").append(isAvailable).append(", ");
sb.append("} ");
return sb.toString();
}
}
表中涉及到有一个类似状态的字段 是否开启,数据库保存的是数值1,2 表示是,否,所以我们用mp提供的一个注解功能 ,定义一个枚举类,进行使用注解
package com.xxx.enums.warn;
import com.xxx.utils.StringUtils;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum ProdProblemKeywordSwitchEnum {
OPEN(1, "是"),
CLOSE(2, "否");
// 数据库中的值
@EnumValue
private final Integer value;
// 前端展示的结果
private final String label;
/**
* Jackson序列化方法
*
* @return label
*/
@JsonValue
public String getLabel() {
return label;
}
/**
* 反序列化
*
* @param label label
* @return 枚举
*/
public static ProdProblemKeywordSwitchEnum getByLabel(String label) {
for (ProdProblemKeywordSwitchEnum item : values()) {
if (StringUtils.equals(label, item.getLabel())) {
return item;
}
}
return null;
}
/**
* EasyExcel映射转换类
*/
public static class KeywordSwitchEnumConverter implements Converter {
/**
* 标明Excel中的类型
*
* @return 标明Excel中的类型
*/
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 标明数据库中的类型
*
* @return 标明数据库中的类型
*/
@Override
public Class> supportJavaTypeKey() {
return Integer.class;
}
/**
* xlsx中cell转java对象方法
*
* @param cellData cell中的数据
* @param contentProperty contentProperty
* @param globalConfiguration globalConfiguration
* @return 转换后的枚举值
*/
@Override
public ProdProblemKeywordSwitchEnum convertToJavaData(ReadCellData> cellData,
ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
return getByLabel(cellData.getStringValue());
}
/**
* xlsx中java对象转cell数据方法
*
* @param value 枚举对象
* @param contentProperty contentProperty
* @param globalConfiguration globalConfiguration
* @return 写入到cell中的数据
*/
@Override
public WriteCellData> convertToExcelData(ProdProblemKeywordSwitchEnum value,
ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
return new WriteCellData<>(value.getLabel());
}
}
}
在前面的服务层中,我们可以看到在方法上有加了一个自定义注解, @UserPermission(username = {"用户1","用户2"}) ,传递的参数是用户信息,主要就是通过注解方式来做一个用户鉴权隔离,针对新增,修改,删除,导入的这些操作,给指定的用户赋予权限,那么前提就是我们要获取当前登录的用户人信息,然后通过对比是否是指定的用户群体,如果是,那么就可以支持其进行方法调用,否则就返回提示信息:用户没有权限
package com.xxx.annotation;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 注解:用于针对到用户级别的接口鉴权
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface UserPermission {
// 具有权限的用户列表
String[] username() default {};
}
AOP:Aspect Oriented Programming,翻译过来就是大名鼎鼎的“面向切面编程”,它是对面向对象的一种补充和完善。
AOP的使用场景一般有:数据源切换、事务管理、权限控制、日志打印等。根据它的名字我们不难理解,它的实现很像是将我们要实现的代码切入业务实现的逻辑中。它有以下特点:
1、侵入性小,几乎可以不改动原来逻辑的情况下把新的逻辑加入业务。
2、实现方便,使用几个注解就可以实现,使系统更容易扩展。
3、更好的复用代码,比如事务日志打印,简单逻辑适合所有情况。AOP中注解的含义
@Aspect:切面。表示一个横切进业务的一个对象。它里面包含切入点(Pointcut)和Advice(通知)。
@Pointcut:切入点。表示需要切入的位置,比如某些类或者某些方法,也就是先定一个范围。
@Before:Advice(通知)的一种,切入点的方法体执行之前执行。
@Around:Advice(通知)的一种,环绕切入点执行也就是把切入点包裹起来执行。
@After:Advice(通知)的一种,在切入点正常运行结束后执行。
@AfterReturning:Advice(通知)的一种,在切入点正常运行结束后执行,异常则不执行
@AfterThrowing:Advice(通知)的一种,在切入点运行异常时执行。
@Around的作用
- 既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作;
- 可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行;
- 可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;
- 虽然Around功能强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturing增强方法就可以解决的事情,就没有必要使用Around增强处理了。
- 执行顺序:正常的执行顺序是:@Around ->@Before->主方法体->@Around中pjp.proceed()->@After->@AfterReturning
如果异常在Around中pjp.proceed()之前,则执行顺序为:@Around -> @After -> @AfterThrowing
如果异常在Around中pjp.proceed()之后,则执行顺序为@Around ->@Before->主方法体->@Around中pjp.proceed()->@After->@AfterThrowing
根据切面编程的基本理解,我们在鉴权控制的设计,是需要根据是否有权限,来选择性的改变方法返回值,需要使用Around环绕通知。
package com.xxx.aop;
import com.xxx.annotation.UserPermission;
import com.xxx.service.UserService;
import com.xxx.utils.ResponseUtils;
import com.xxx.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 用户鉴权0aop
*
*/
@Component
@Aspect
@RequiredArgsConstructor
public class UserPermissionAspect {
private final UserService userService;
/**
* 针对到用户级别的接口鉴权
*
* @param joinPoint 切点
* @param userPermission 注解
* @return 切点返回结果或鉴权失败信息
* @throws Throwable 异常
*/
@Around(value = "@annotation(userPermission)")
public Object userPermission(ProceedingJoinPoint joinPoint, UserPermission userPermission) throws Throwable {
final String[] userList = userPermission.username();
//注入用户接口方法,获取当前登录的用户信息
final String Account = userService.getUser().getAccount();
for (String username : userList) {
//如果当前用户是指定用户群之一 那么执行目标方法
if (StringUtils.equalsIgnoreCase(username, Account)) {
return joinPoint.proceed();
}
}
//没有权限 那么就返回指定的 失败信息 给到前端
return ResponseUtils.unAuthResponse(null, "用户没有该接口的权限");
}
}