文章目录
一、背景与需求
1.1 哪些情况下使用枚举
在实际开发中,我们经常会遇到需要使用枚举的情况。比如性别,某种设备的类型,节点的状态等等。这些状态类型固定,数量也不怎么多,不需要放着数据库中,每次查询还做下没必要的联表操作。同时,即使放数据库中,也会面临如何表记这些状态的问题。到底用数字,还是用字符串。有没有可能操作不当而引起数据混乱等。
1.2 古早的解决方案
一种比较糟糕的做法是,在全局变量相关的文件中定义这些枚举,无论用数字还是字符串。
/*性别枚举*/
int GENDER_MALE = 1;//男性
int GENDER_FEMALE = 2;//女性
/*设备类型枚举*/
int BURRY_TYPE_BURIAL = 1;//直埋
int DEVICE_TYPE_OVERHEAD = 2;//架空
int DEVICE_TYPE_EXPRESSWAY = 3;//高速
int DEVICE_TYPE_PIPELINE = 4;//管道
/*设备类型枚举*/
int VOLTAGE_V35 = 1; //35KV
int VOLTAGE_V110 = 2; //110KV
int VOLTAGE_V220 = 3; //220KV
int VOLTAGE_V500 = 4; //500KV
int VOLTAGE_VD800 = 5; //±800KV
int VOLTAGE_V1000 = 6; //1000KV
/*节点状态*/
int NODE_PROJECT = 1;//立项
int NODE_PROPREVIEW = 2;//项目预审
int NODE_BIND = 3;//标书
int NODE_CONTRACT = 4;//合同阶段
int NODE_MATERIAL = 5;//资料上传阶段
在写代码时,使用全局定义的枚举做逻辑判断
// 当节点确认时
if(NODE_PROPREVIEW == node.state)
{
// 流转到项目预审
}
else if(NODE_PROPREVIEW == node.state)
{
// 检测各专题分项是否合格
// 进入选择供应商阶段
}
这种代码如果在团队成员稳定,并且注释写的比较详细规范时,其实也没有问题。但如果团队常常有人员流动,或者一份代码可能流转到另外的团队去维护。这种代码可能就会比较糟糕。别人可能不知道某各结构中的变量属于哪个枚举,该枚举的定义在哪里。更有甚者,可能一些不负责任的程序员直接在代码中写数字或字符串。使代码难以维护。
1.3 现代的枚举解决方案
在现代的编程语言中,往往会提供枚举的功能。在定义数据时,结构体的相关变量直接定义成枚举,这样就避免了1.2中谈及的尴尬问题。
如下面的代码:
@AllArgsConstructor
public enum ContractTypeEnum {
DEFAULT(0, "default"),
BINDING(1, "可研编制"),
SUBJECT(2, "专题评估");
@Getter
@EnumValue
int code;
@Getter
String desc;
}
ProjectEntity projectEntity = checkProject(contractEntity.getProjectId());
switch (subjectType) {
case BINDING:
ProjectEvent projectSubmitEvent = new ProjectEvent(ProjectEventTypeEnum.PROJECT_CONTRACT_SUBMIT,
contractEntity.getId(), projectEntity, null, contractEntity);
mApplicationContext.publishEvent(projectSubmitEvent);
break;
case SUBJECT:
ProjectEvent SubjectSubmitEvent = new ProjectEvent(ProjectEventTypeEnum.SUBJECT_SIGN,
contractEntity.getId(), projectEntity, null, contractEntity);
mApplicationContext.publishEvent(SubjectSubmitEvent);
break;
}
return contractEntity;
1.4 前端下拉列表的挑战
在现代的开发中,往往有客户端,客户端需要知道某一类型的枚举都有哪些值,在前端如何表示。最常见的是注册账号时选择性别的下拉列表,设定设备时选择设备的类型等。
如果我们把枚举定义到了代码里,需要写一个通用的接口供前端。让前端通过枚举类型,查出对应枚举的各枚举值的信息以及一些其他相关信息。
1.5 有时类型需要动态添加枚举类型
有时候,枚举的类型数量也会发生变化,比如商品的类型。我们不希望每次商品类型增加时,所有相关微服务都要重新编译部署一遍,这大大增大了运营的成本和不确定性。
这种类型数据,我们认为不能算作枚举模块,因为接受该数据结构的字段,通常也是使用的诸如int、string的基础类型。故对于这种需求,我们还是使用传统的做法,在数据库中建立一个类型表。为了加速访问,我们可以使用redis做缓存。
为了减少前端的负担,我们可以把2种类型数据的查询使用统一的接口,让前端从这种细节中解脱出来,减少沟通的复杂度。
二、整体设计
2.1 需求应对
2.1.1 程序中的枚举
在程序中,我们可以把枚举都放在一个包下面,在程序启动时,通过扫描包的方式,把枚举相关信息加载道内存。
同时,我们还可以写一个自定义注解,加强枚举的信息。诸如下拉列表相关的配置。
在mybatis-plus中,天然支持实体字段使用枚举。
2.1.2 后台的类型枚举
对于后台需要动态添加的类型,我们可以创建2张数据表:
做一下这两张表的增删改查接口
2.1.3 程序枚举和后台枚举如何统一
可以在后台管理的服务中,顺便更新redis的缓存。在前端查询枚举时,先看程序枚举中有没有相关配置,没有的话就去redis里找找
2.2 程序架构
程序框架上,打算做3个模块。
2.2.1 framework-enums-core
放枚举相关的一些基础定义,枚举缓存的操作类等
pom文件引用如下:
<parent>
<artifactId>framework-enums</artifactId>
<groupId>indi.zhifa.recipe</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>framework-enums-core</artifactId>
<packaging>jar</packaging>
<dependencies>
<!--**********************web基础模块**************************-->
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-web-all</artifactId>
</dependency>
<!-- ******************lombok****************************-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2.2.2 framework-enums-mgr
放后台类型的一些操作类
pom文件引用如下:
<parent>
<artifactId>framework-enums</artifactId>
<groupId>indi.zhifa.recipe</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>framework-enums-mgr</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-enums-core</artifactId>
</dependency>
<!-- ******************lombok****************************-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2.2.3 framework-enums-cli
放枚举类的查询接口和方法
pom文件引用如下:
<parent>
<artifactId>framework-enums</artifactId>
<groupId>indi.zhifa.recipe</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>framework-enums-client</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-enums-core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2.3 分项代码
2.3.1 framework-enums-core
2.3.1.1 EnumDesc
该文件是枚举的注解,加上该注解的枚举,才会被放进枚举缓存(前端才能查得到)。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnumDesc {
/**
* 中文名
*/
String name() default "未知";
/**
* 描述
*/
String desc() default "未知枚举";
/**
* 默认选择
*/
int defaultIdx() default -1;
/**
* 默认选择
*/
String defaultItem() default "全部";
}
2.3.1.2 IEnumVo
该文件定义了前端可访问的枚举接口,可以有多种形式的枚举。我们这里表现为程序中的枚举和动态类型的枚举。
public interface IEnumVo {
/**
* 获得枚举码(类名)
*/
String getCode();
/**
* 获得枚举中文名
*/
String getName();
/**
* 获得枚举描述
*/
String getDescription();
/**
* 获得枚举子项
*/
IEnumItemDto[] getItems();
/**
* 默认选择项,如果是-1,则是额外选择项
*/
Integer getDefaultIdx();
/**
* 选择-1时的额外选择项,比如全部,默认,无
*/
String getDefaultItem();
}
2.3.1.2 IEnumItemVo
该文件定义了枚举项的接口
public interface IEnumItemVo {
/**
* 获取枚举项Id,如1,101等
*/
int getCode();
/**
* 获取枚举项的英文名,如MALE,FEMALE
*/
String getName();
/**
* 获取枚举项中文名,如男,女
*/
String getCname();
/**
* 获取枚举项的描述
*/
String getDescription();
/**
* 获取枚举项的具体枚举常数,如EGender.MALE
*/
Enum getEnumConstant();
}
2.3.1.3 EnumVo
程序枚举的Vo
@Data
@Schema(name = "枚举")
public class EnumVo implements IEnumVo {
@Schema(name = "英文名")
String code;
@Schema(name = "中文名")
String name;
@Schema(name = "描述")
String description;
@Schema(name = "枚举项")
EnumItemVo[] items;
@Schema(name = "默认选择")
Integer defaultIdx;
@Schema(name = "默认选择描述")
String defaultItem;
}
2.3.1.4 EnumVoDecorator
枚举VO的装饰器
public abstract class EnumVoDecorator implements IEnumVo {
final IEnumVo enumDto;
protected EnumVoDecorator(IEnumVo pEnumDto){
enumDto = pEnumDto;
}
@Override
public String getCode() {
return enumDto.getCode();
}
@Override
public String getName() {
return enumDto.getName();
}
@Override
public String getDescription() {
return enumDto.getDescription();
}
@Override
public IEnumItemVo[] getItems() {
return enumDto.getItems();
}
public Integer getDefaultIdx(){
return enumDto.getDefaultIdx();
}
public String getDefaultItem(){
return enumDto.getDefaultItem();
}
}
2.3.1.5 EnumNodeVo
枚举的节点,枚举中各枚举项被放入到map中
public class EnumNodeVo extends EnumVoDecorator {
private Map<Integer, EnumItemVo> mapByCode;
private Map<String, EnumItemVo> mapByName;
private Map<String, EnumItemVo> mapByCName;
public EnumNodeVo(EnumVo pEnumDto){
super(pEnumDto);
mapByName = new HashMap<>();
mapByCName = new HashMap<>();
mapByCode = new HashMap<>();
init();
}
public void init(){
EnumItemVo[] enumItemDtos = (EnumItemVo[])getItems();
for(EnumItemVo enumItemDto : enumItemDtos){
mapByCode.put(enumItemDto.getCode(),enumItemDto);
mapByName.put(enumItemDto.getName(),enumItemDto);
mapByCName.put(enumItemDto.getCname(),enumItemDto);
}
}
public EnumItemVo getByCode(Integer pCode){
EnumItemVo enumItemDto = mapByCode.get(pCode);
if(null == enumItemDto){
throw new ServiceException("没有找到Code为 "+pCode+" 的枚举项");
}
return enumItemDto;
}
public EnumItemVo getByName(String pName){
EnumItemVo enumItemDto = mapByName.get(pName);
if(null == enumItemDto){
throw new ServiceException("没有找到名为 "+pName+" 的枚举项");
}
return enumItemDto;
}
public EnumItemVo getByCName(String pCName){
EnumItemVo enumItemDto = mapByCName.get(pCName);
if(null == enumItemDto){
throw new ServiceException("没有找到名为 "+pCName+" 的枚举项");
}
return enumItemDto;
}
}
2.3.1.6 SysTypeEntity
类型的数据库实体
@Data
@TableName("sys_type")
@Schema(title = "类型项", description = "类型项")
public class SysTypeEntity extends BaseEntity {
@Schema(title = "类型码")
protected String code;
@Schema(title = "中文名")
protected String name;
@Schema(title = "描述")
protected String description;
@Schema(title = "默认选择",description = "-1表示默认选择非枚举项,可能是诸如全部,默认之类的选项")
protected Integer defaultIdx;
@Schema(title = "默认选择项",description = "默认")
protected String defaultItem;
}
2.3.1.7 SysTypeItemEntity
类型项
@Data
@TableName("sys_type_item")
@Schema(title = "类型项", description = "类型项")
public class SysTypeItemEntity extends SysBaseEntity {
@Schema(title = "类型", description = "类型")
private Long typeId;
@Schema(title = "类型码", description = "类型码")
private String typeCode;
@Schema(title = "类型项码", description = "类型项码")
private int code;
@Schema(title = "类型项英文名", description = "类型项英文名")
private String name;
@Schema(title = "类型项中文名", description = "类型项中文名")
private String cname;
@Schema(title = "类型项描述", description = "类型项描述")
private String description;
@Schema(title = "排序", description = "排序")
private Integer sortOrder;
}
2.3.1.8 SysTypeItemVo
类型项VO
public class SysTypeItemVo extends SysTypeItemEntity implements IEnumItemVo {
@Override
public Enum getEnumConstant() {
return null;
}
}
2.3.1.9 SysTypeVo
@Schema(title = "类型VO")
public class SysTypeVo extends SysTypeEntity implements IEnumVo {
@Setter
@Schema(title = "类型项")
List<SysTypeItemVo> items = new ArrayList<SysTypeItemVo>();
@Override
public IEnumItemVo[] getItems() {
return items.toArray(new SysTypeItemVo[0]);
}
}
2.3.1.10 ISysTypeMemo
类型缓存的操作接口
public interface ISysTypeMemo {
void memo(SysTypeVo pSysTypeVo);
void delete(SysTypeEntity pSysTypeVo);
SysTypeVo infoFromMemo(long pId);
SysTypeVo infoFromMemo(String pCode);
}
2.3.1.11 SysTypeMemoImpl
类型缓存的操作接口的实现
@Component
@RequiredArgsConstructor
public class SysTypeMemoImpl implements ISysTypeMemo {
private final String TYPE_MGR_PREFIX = "TYPE_MGR:";
private final String TYPE_MGR_CODE_PREFIX = "TYPE_MGR_CODE:";
private final RedisUtil mRedisUtil;
public void memo(SysTypeVo pSysTypeVo){
mRedisUtil.set(TYPE_MGR_PREFIX+pSysTypeVo.getId(),pSysTypeVo);
mRedisUtil.set(TYPE_MGR_CODE_PREFIX+pSysTypeVo.getCode(),pSysTypeVo.getId());
}
public void delete(SysTypeEntity pSysTypeVo){
mRedisUtil.delete(TYPE_MGR_PREFIX+pSysTypeVo.getId());
mRedisUtil.delete(TYPE_MGR_CODE_PREFIX+pSysTypeVo.getCode());
}
public SysTypeVo infoFromMemo(long pId){
SysTypeVo sysTypeVo = mRedisUtil.get(TYPE_MGR_PREFIX+pId,SysTypeVo.class);
return sysTypeVo;
}
public SysTypeVo infoFromMemo(String pCode){
Long id = mRedisUtil.get(TYPE_MGR_CODE_PREFIX+pCode,Long.class);
if(null == id){
return null;
}
return infoFromMemo(id);
}
}
2.3.2 framework-enums-mgr
2.3.2.1 dao层
由于代码都是mybatis-plus生成的,故略去一些,只展示dbService接口
ITypeDbService:
public interface ITypeDbService extends IZfDbService<SysTypeEntity> {
boolean existByCode(String pCode);
SysTypeEntity findByCode(String pCode);
}
ITypeItemDbService
public interface ITypeItemDbService extends IZfDbService<SysTypeItemEntity> {
List<SysTypeItemEntity> listByTypeId(Long pTypeId);
void removeByTypeId(Long pTypeId);
}
2.3.2.2 类型创建相关Dto
我更习惯在Dto层控制输入,所以创建、修改都会使用不同的dto
SysTypeCreateDto
@Data
@Schema(title = "类型创建DTO", description = "类型创建DTO")
public class SysTypeCreateDto {
@Schema(title = "类型码")
private String code;
@Schema(title = "中文名")
private String name;
@Schema(title = "描述")
private String description;
@Schema(title = "默认选择",description = "-1表示默认选择非枚举项,可能是诸如全部,默认之类的选项")
private Integer defaultIdx;
@Schema(title = "默认选择项",description = "默认")
private String defaultItem;
}
SysTypeEditDto
@Data
@Schema(title = "类型编辑DTO", description = "类型编辑DTO")
public class SysTypeEditDto {
@Schema(title = "中文名")
private String name;
@Schema(title = "描述")
private String description;
@Schema(title = "默认选择",description = "-1表示默认选择非枚举项,可能是诸如全部,默认之类的选项")
private Integer defaultIdx;
@Schema(title = "默认选择项",description = "默认")
private String defaultItem;
}
SysTypeItemCreateDto
@Data
@Schema(title = "类型项DTO", description = "类型项")
public class SysTypeItemCreateDto extends SysTypeItemEditDto{
@Schema(title = "类型项码", description = "类型项码")
protected Integer code;
@Schema(title = "类型项英文名", description = "类型项英文名")
protected String name;
}
SysTypeItemEditDto
@Data
@Schema(title = "类型修改DTO", description = "类型修改DTO")
public class SysTypeItemEditDto {
@Schema(title = "类型项中文名", description = "类型项中文名")
protected String cname;
@Schema(title = "类型项描述", description = "类型项描述")
protected String description;
}
SysTypeEditAllDto
```java
@Data
@Schema(title = "类型项编辑Dto", description = "类型项编辑Dto")
public class SysTypeEditAllDto extends SysTypeEditDto{
List<SysTypeItemCreateDto> items;
}
2.3.2.3 ITypeMgrService
类型增删改查的接口
public interface ITypeMgrService {
void init();
/**
* 创建类型
*
* @param pSysTypeCfg 类型配置
* @return
*/
SysTypeVo create(SysTypeCreateDto pSysTypeCfg);
SysTypeVo edit(Long pId, SysTypeEditDto pSysTypeEditCfg);
/**
* 修改类型信息
*
* @param pId 类型Id
* @param pSysTypeEditAllDto 类型配置,包括子项
* @return
*/
SysTypeVo edit(Long pId, SysTypeEditAllDto pSysTypeEditAllDto);
/**
* 查看类型信息
*
* @param pId 类型Id
* @return 类型VO
*/
SysTypeVo info(Long pId);
/**
* 通过
*
* @param pCode
* @return
*/
SysTypeVo info(String pCode);
/**
* 新增类型项
*
* @param pTypeId 类型Id
* @param pSysTypeItemCreateCfg 类型配置
* @return
*/
SysTypeItemEntity addItem(Long pTypeId, SysTypeItemCreateDto pSysTypeItemCreateCfg);
/**
* 编辑类型项
*
* @param pTypeItemId 类型Id
* @param pSysTypeItemEditCfg 类型配置
* @return 类型VO
*/
SysTypeItemEntity editItem(Long pTypeItemId, SysTypeItemEditDto pSysTypeItemEditCfg);
/**
* 删除类型Id
*
* @param pTypeItemId 类型Id
* @return 类型VO
*/
boolean deleteTypeItem(Long pTypeItemId);
/**
* 删除类型
*
* @param pTypeId 类型Id
* @return
*/
boolean deleteType(Long pTypeId);
}
2.3.2.4 TypeMgrServiceImpl
实现类
@RequiredArgsConstructor
@ZfFacade(name = "类型管理")
public class TypeMgrServiceImpl implements ITypeMgrService {
private final ITypeDbService mTypeDbService;
private final ITypeItemDbService mTypeItemDbService;
private final ISysTypeMemo mISysTypeMemo;
public void init(){
List<SysTypeEntity> sysTypeEntityList = mTypeDbService.list();
List<SysTypeVo> sysTypeVoList = DtoEntityUtil.trans(sysTypeEntityList,SysTypeVo.class);
for(SysTypeVo sysTypeVo : sysTypeVoList){
List<SysTypeItemEntity> itemEntityList = mTypeItemDbService.listByTypeId(sysTypeVo.getId());
List<SysTypeItemVo> sysTypeItemVoList = DtoEntityUtil.trans(itemEntityList,SysTypeItemVo.class);
sysTypeVo.setItems(sysTypeItemVoList);
mISysTypeMemo.memo(sysTypeVo);
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public SysTypeVo create(SysTypeCreateDto pSysTypeCfg) {
if(mTypeDbService.existByCode(pSysTypeCfg.getCode())){
throw new ServiceException("已经存在名为"+pSysTypeCfg.getCode()+"的类型");
}
SysTypeEntity typeEntity = DbDtoEntityUtil.createFromDto(pSysTypeCfg, SysTypeEntity.class);
mTypeDbService.save(typeEntity);
SysTypeVo sysTypeVo = DtoEntityUtil.trans(typeEntity,SysTypeVo.class);
mISysTypeMemo.memo(sysTypeVo);
return sysTypeVo;
}
@Transactional(rollbackFor = Exception.class)
@Override
public SysTypeVo edit(Long pId, SysTypeEditDto pSysTypeEditCfg) {
SysTypeEntity orgSysTypeEntity = mTypeDbService.check(pId);
SysTypeEntity newSysTypeEntity = DbDtoEntityUtil.editByDto(orgSysTypeEntity,pSysTypeEditCfg,SysTypeEntity.class);
mTypeDbService.updateById(newSysTypeEntity);
List<SysTypeItemEntity> sysTypeItemEntityList = mTypeItemDbService.listByTypeId(pId);
SysTypeVo sysTypeVo = DtoEntityUtil.trans(newSysTypeEntity,SysTypeVo.class);
List<SysTypeItemVo> sysTypeItemVoList = DtoEntityUtil.trans(sysTypeItemEntityList,SysTypeItemVo.class);
sysTypeVo.setItems(sysTypeItemVoList);
mISysTypeMemo.memo(sysTypeVo);
return sysTypeVo;
}
@Transactional(rollbackFor = Exception.class)
@Override
public SysTypeVo edit(Long pId, SysTypeEditAllDto pSysTypeEditAllDto) {
SysTypeEntity orgSysTypeEntity = mTypeDbService.check(pId);
SysTypeEntity newSysTypeEntity = DbDtoEntityUtil.editByDto(orgSysTypeEntity,pSysTypeEditAllDto,SysTypeEntity.class);
mTypeDbService.updateById(newSysTypeEntity);
mTypeItemDbService.removeByTypeId(pId);
List<SysTypeItemEntity> typeItemEntityList = DbDtoEntityUtil.trans(pSysTypeEditAllDto.getItems(),SysTypeItemEntity.class);
for(int i=0;i<typeItemEntityList.size();i++){
SysTypeItemEntity sysTypeItemEntity = typeItemEntityList.get(i);
sysTypeItemEntity.createInit();
sysTypeItemEntity.setSortOrder(i+1);
sysTypeItemEntity.setTypeId(pId);
sysTypeItemEntity.setTypeCode(newSysTypeEntity.getCode());
}
mTypeItemDbService.saveBatch(typeItemEntityList);
SysTypeVo sysTypeVo = DtoEntityUtil.trans(newSysTypeEntity,SysTypeVo.class);
List<SysTypeItemVo> sysTypeItemVoList = DtoEntityUtil.trans(typeItemEntityList,SysTypeItemVo.class);
sysTypeVo.setItems(sysTypeItemVoList);
mISysTypeMemo.memo(sysTypeVo);
return sysTypeVo;
}
protected SysTypeVo infoFromDb(Long pId){
SysTypeEntity sysTypeEntity = mTypeDbService.check(pId);
List<SysTypeItemEntity> sysTypeItemEntityList = mTypeItemDbService.listByTypeId(pId);
SysTypeVo sysTypeVo = DtoEntityUtil.trans(sysTypeEntity,SysTypeVo.class);
List<SysTypeItemVo> sysTypeItemVoList = DtoEntityUtil.trans(sysTypeItemEntityList,SysTypeItemVo.class);
sysTypeVo.setItems(sysTypeItemVoList);
return sysTypeVo;
}
@Override
public SysTypeVo info(Long pId) {
SysTypeVo sysTypeVo = mISysTypeMemo.infoFromMemo(pId);
if(null == sysTypeVo){
throw new ServiceException("缓存中没有找到id为"+pId+"的类型");
}
return sysTypeVo;
}
@Override
public SysTypeVo info(String pCode) {
SysTypeVo sysTypeVo = mISysTypeMemo.infoFromMemo(pCode);
if(null == sysTypeVo){
throw new ServiceException("缓存中没有找到code为"+pCode+"的类型");
}
return sysTypeVo;
}
@Transactional(rollbackFor = Exception.class)
@Override
public SysTypeItemEntity addItem(Long pTypeId, SysTypeItemCreateDto pSysTypeItemCreateCfg) {
SysTypeEntity sysTypeEntity = mTypeDbService.check(pTypeId);
SysTypeItemEntity sysTypeItemEntity = DbDtoEntityUtil.createFromDto(pSysTypeItemCreateCfg,SysTypeItemEntity.class);
sysTypeItemEntity.setTypeId(sysTypeEntity.getId());
sysTypeItemEntity.setTypeCode(sysTypeEntity.getCode());
List<SysTypeItemEntity> sysTypeItemEntityList = mTypeItemDbService.listByTypeId(pTypeId);
Set<Integer> codeSet = sysTypeItemEntityList.stream().map(SysTypeItemEntity::getCode).collect(Collectors.toSet());
if(codeSet.contains(pSysTypeItemCreateCfg.getCode())){
throw new ServiceException("类型"+sysTypeEntity.getCode()+"已存在编号为"+pSysTypeItemCreateCfg.getCode()+"的code的类型项");
}
Set<String> nameSet = sysTypeItemEntityList.stream().map(SysTypeItemEntity::getName).collect(Collectors.toSet());
if(nameSet.contains(pSysTypeItemCreateCfg.getName())){
throw new ServiceException("类型"+sysTypeEntity.getCode()+"已存在名为"+pSysTypeItemCreateCfg.getCode()+"的name的类型项");
}
sysTypeItemEntity.setSortOrder(sysTypeItemEntityList.size()+1);
mTypeItemDbService.save(sysTypeItemEntity);
SysTypeVo sysTypeVo = infoFromDb(pTypeId);
mISysTypeMemo.memo(sysTypeVo);
return sysTypeItemEntity;
}
@Transactional(rollbackFor = Exception.class)
@Override
public SysTypeItemEntity editItem(Long pTypeItemId, SysTypeItemEditDto pSysTypeItemEditCfg) {
SysTypeItemEntity orgSysTypeItemEntity = mTypeItemDbService.check(pTypeItemId);
SysTypeItemEntity newSysTypeItemEntity = DbDtoEntityUtil.editByDto(orgSysTypeItemEntity,pSysTypeItemEditCfg,SysTypeItemEntity.class);
mTypeItemDbService.updateById(newSysTypeItemEntity);
SysTypeVo sysTypeVo = infoFromDb(orgSysTypeItemEntity.getTypeId());
mISysTypeMemo.memo(sysTypeVo);
return newSysTypeItemEntity;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean deleteTypeItem(Long pTypeItemId) {
SysTypeItemEntity typeItemEntity = mTypeItemDbService.check(pTypeItemId);
mTypeItemDbService.removeById(pTypeItemId);
SysTypeVo sysTypeVo = infoFromDb(typeItemEntity.getTypeId());
mISysTypeMemo.memo(sysTypeVo);
return true;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean deleteType(Long pTypeId) {
SysTypeEntity sysTypeEntity = mTypeDbService.check(pTypeId);
mTypeDbService.removeById(pTypeId);
mISysTypeMemo.delete(sysTypeEntity);
return true;
}
}
2.3.2.5 SysTypeApi
类型的前端接口
@Api(tags = "1.后台类型管理")
@RequestMapping("/api/mgr/types")
@Slf4j
@ZfRestController
@RequiredArgsConstructor
public class SysTypeApi {
final ITypeMgrService mTypeMgrService;
@Operation(summary = "创建类型")
@PostMapping("")
SysTypeVo create( @Parameter(description = "类型配置") @RequestBody SysTypeCreateDto pSysTypeCfg){
SysTypeVo sysTypeVo = mTypeMgrService.create(pSysTypeCfg);
return sysTypeVo;
}
@Operation(summary = "修改类型")
@PutMapping ("/{id}")
SysTypeVo edit(
@Parameter(description = "类型Id") @PathVariable(name = "id") Long pId,
@Parameter(description = "类型配置") @RequestBody SysTypeEditDto pSysTypeEditCfg){
SysTypeVo sysTypeVo = mTypeMgrService.edit(pId,pSysTypeEditCfg);
return sysTypeVo;
}
@Operation(summary = "修改类型-全")
@PutMapping ("/{id}/whole")
SysTypeVo edit(
@Parameter(description = "类型Id") @PathVariable(name = "id") Long pId,
@Parameter(description = "类型配置") @RequestBody SysTypeEditAllDto pSysTypeEditCfg){
SysTypeVo sysTypeVo = mTypeMgrService.edit(pId,pSysTypeEditCfg);
return sysTypeVo;
}
@Operation(summary = "查询类型")
@GetMapping ("/{id}")
SysTypeVo info(
@Parameter(description = "类型Id") @PathVariable(name = "id") Long pId){
SysTypeVo sysTypeVo = mTypeMgrService.info(pId);
return sysTypeVo;
}
@Operation(summary = "查询类型-code")
@GetMapping ("/code/{code}")
SysTypeVo info(
@Parameter(description = "类型Id") @PathVariable(name = "code") String pCode){
SysTypeVo sysTypeVo = mTypeMgrService.info(pCode);
return sysTypeVo;
}
@Operation(summary = "添加类型项")
@PostMapping ("/{typeId}/items/create")
SysTypeItemEntity addItem(
@Parameter(description = "类型Id") @PathVariable(name = "typeId") Long pTypeId,
@Parameter(description = "类型项配置") @RequestBody SysTypeItemCreateDto pSysTypeItemCreateCfg){
SysTypeItemEntity sysTypeItemEntity = mTypeMgrService.addItem(pTypeId,pSysTypeItemCreateCfg);
return sysTypeItemEntity;
}
@Operation(summary = "修改类型项")
@PostMapping ("/{typeId}/items/{id}")
SysTypeItemEntity editItem(
@Parameter(description = "类型Id") @PathVariable(name = "id") Long pId,
@Parameter(description = "类型项配置") @RequestBody SysTypeItemEditDto pSysTypeItemEditCfg){
SysTypeItemEntity sysTypeItemEntity = mTypeMgrService.editItem(pId,pSysTypeItemEditCfg);
return sysTypeItemEntity;
}
@DeleteMapping ("/{id}")
@Operation(summary = "删除类型")
Boolean deleteTypeItem(
@Parameter(description = "类型Id") @PathVariable(name = "id") Long pId){
return mTypeMgrService.deleteType(pId);
}
@DeleteMapping ("/items/{id}")
@Operation(summary = "删除类型项")
boolean deleteType(
@Parameter(description = "类型Id") @PathVariable(name = "id") Long pId){
return mTypeMgrService.deleteTypeItem(pId);
}
}
2.3.3 framework-enums-client
2.3.3.1 EnumMemoConfig
枚举包的路径配置
@Data
@Configuration
@ConfigurationProperties(prefix = "enum-memo")
public class EnumMemoConfig {
String[] enumPackages;
}
2.3.3.2 IEnumMemoService
枚举缓存的接口
public interface IEnumMemoService {
/**
* 初始化Enum
*
*/
void initEnum(String[] pEnumBasePackages) throws InvocationTargetException, IllegalAccessException;
/**
* 筛选Enum
*
* @param pName enum名
* @return Map<String, List < EnumDto>> 所有的Enum
*/
IEnumVo findByName(String pName) throws ServiceException;
/**
* 使用枚举查找枚举节点
*
* @param pCls 类
* @return EnumNodeDto 枚举节点
*/
EnumNodeVo findByClass(Class pCls);
/**
* 获取所有的枚举
*
* @return List<EnumDto> 枚举列表
*/
EnumVo[] list();
/**
* 添加枚举
*
* @param pEnumDto 枚举
*/
void addEnumDto(EnumVo pEnumDto);
}
2.3.3.3 EnumMemoServiceImpl
枚举缓存的接口的实现
@Service
public class EnumMemoServiceImpl implements IEnumMemoService {
public Map<String, EnumVo> enumsByName;
public Map<Class, EnumNodeVo> enumMemoByClas;
private final ISysTypeMemo mSysTypeMemo;
public EnumMemoServiceImpl(){
enumsByName = new HashMap<String, EnumVo>();
enumMemoByClas = new HashMap<Class, EnumNodeVo>();
mSysTypeMemo = SpringUtil.getBean(SysTypeMemoImpl.class);
}
@Override
public void initEnum(String[] pEnumBasePackages) throws InvocationTargetException, IllegalAccessException {
List<Class<?>> classes = new ArrayList<>();
for(String path : pEnumBasePackages){
Set<Class<?>> cls = ClassUtil.scanPackage(path);
classes.addAll(cls);
}
for(Class cls : classes){
if(!ClassUtil.isEnum(cls)){
continue;
}
EnumDesc enumDesc = AnnotationUtils.findAnnotation(cls,EnumDesc.class);
if(null == enumDesc){
continue;
}
EnumVo enumDto = new EnumVo();
String enumCode = cls.getSimpleName();
enumDto.setCode(enumCode);
enumDto.setName(enumDesc.name());
enumDto.setDescription(enumDesc.desc());
enumDto.setDefaultIdx(enumDesc.defaultIdx());
enumDto.setDefaultItem(enumDesc.defaultItem());
Object[] constants = cls.getEnumConstants();
Method codeMethod = ClassUtil.getDeclaredMethod(cls, "getCode");
Method nameMethod = ClassUtil.getDeclaredMethod(cls, "getName");
Method descMethod = ClassUtil.getDeclaredMethod(cls, "getDescription");
List<EnumItemVo> enumItemDtoList = new ArrayList<>();
for(Object obj : constants){
String enumName = obj.toString();
if(enumName.equals("DEFAULT")){
continue;
}
EnumItemVo enumItemDto = new EnumItemVo();
enumItemDto.setName(enumName);
int code = (int)codeMethod.invoke(obj);
String name = (String)nameMethod.invoke(obj);
String desc = (String)descMethod.invoke(obj);
enumItemDto.setCode(code);
enumItemDto.setCname(name);
enumItemDto.setDescription(desc);
enumItemDtoList.add(enumItemDto);
}
enumDto.setItems(enumItemDtoList.toArray(new EnumItemVo[0]));
enumsByName.put(enumDto.getCode(),enumDto);
EnumNodeVo enumNodeDto = new EnumNodeVo(enumDto);
enumMemoByClas.put(cls,enumNodeDto);
}
}
@Override
public IEnumVo findByName(String pName) throws ServiceException {
EnumVo enumDto = enumsByName.get(pName);
if(null == enumDto){
SysTypeVo sysTypeVo = mSysTypeMemo.infoFromMemo(pName);
return sysTypeVo;
}
return enumDto;
}
@Override
public EnumNodeVo findByClass(Class pCls) {
EnumNodeVo enumNodeDto = enumMemoByClas.get(pCls);
return enumNodeDto;
}
@Override
public EnumVo[] list() {
EnumVo[] enumDtoArray = enumsByName.values().toArray(new EnumVo[0]);
return enumDtoArray;
}
@Override
public void addEnumDto(EnumVo pEnumDto) {
if(!enumsByName.containsKey(pEnumDto.getCode())){
enumsByName.put(pEnumDto.getCode(),pEnumDto);
}
}
}
2.4 使用与演示
2.4.1 类型管理
2.4.1.1 nacos 配置
bailan3-enums-mgr Group DEV
spring:
application:
name: bailan3-enums-mgr
datasource:
#数据库配置
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/platform?useUnicode=true&characterEncoding=utf-8
username: app
password: ILv0404@1314
hikari:
# 连接池最大连接数,默认是10
maximum-pool-size: 100
# 最小空闲链接
minimum-idle: 5
# 空闲连接存活最大时间,默认 600000(10分钟)
idle-timeout: 600000
# 数据库连接超时时间,默认30秒,即30000
connection-timeout: 30000
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟;正在使用的连接永远不会退休,只有在关闭后才会被删除。
max-lifetime: 1800000
# 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
pool-name: Hikari
redis:
host: localhost
port: 6379
password: ilv0404@1314
# 连接超时时间(记得添加单位,Duration)
timeout: 10000ms
lettuce:
shutdown-timeout: 100 # 关闭超时时间
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 # 连接池中的最小空闲连接
swagger:
enable: true
group-name: "类型管理接口"
api-package: indi.zhifa.recipe.bailan.framework.enums.controller.api
api-regex: "/api/**"
title: "类型管理接口"
description: "类型管理接口"
version: "1.0.0"
name: "芝法酱"
email: "hataksumo@163.com"
url: "https://github.com/hataksumo"
2.4.1.2 启动配置
server:
# 服务端口
port: 8081
spring:
application:
name: "bailan3-enums-mgr"
servlet:
multipart:
enabled : true
max-file-size: "256MB"
max-request-size: "256MB"
cloud:
nacos:
server-addr: localhost:8848
config:
namespace: bailan
name: bailan3-enums-mgr
file-extension: yml
group: DEV
discovery:
register-enabled: true
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
deregisterdrivers=true
useprefix=true
excludecategories=info,debug,result,commit,resultset
dateformat=yyyy-MM-dd HH:mm:ss
outagedetection=true
outagedetectioninterval=2
2.4.1.3 pom引用
<parent>
<artifactId>busy</artifactId>
<groupId>indi.zhifa.recipe</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>enums-mgr</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-web-all</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-enums-mgr</artifactId>
<version>${project.version}</version>
</dependency>
<!-- ******************lombok****************************-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2.4.1.4 启动类
@MapperScan(basePackages ={"indi.zhifa.recipe.bailan.framework.**.mapper"})
@SpringBootApplication(
scanBasePackages = {"indi.zhifa.recipe.bailan.framework","indi.zhifa.recipe.bailan.enumsmgr"}
)
public class EnumsMgrApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(EnumsMgrApplication.class, args);
}
}
2.4.1.5 MetaObjectHandler
@Component
public class MetaObjectHandler extends BaseMetaObjectHandlerAdapter {
}
2.4.1.6 AppInit
启动加载的代码
@Component
@RequiredArgsConstructor
public class AppInit implements CommandLineRunner {
private final ITypeMgrService mTypeMgrService;
@Override
public void run(String... args) throws Exception {
DtoEntityUtil.init();
mTypeMgrService.init();
}
}
2.4.1.7 测试过程
创建类型
全量更新类型
通过code查询
添加法器类型项
通过id查询
测试数据
{
"code": "gennshinn_major",
"defaultIdx": -1,
"defaultItem": "全部",
"description": "原神职业",
"name": "原神职业",
"items": [
{
"code": 1,
"name": "sword",
"cname": "剑",
"description": "剑"
},
{
"code": 2,
"name": "polearm",
"cname": "长柄武器",
"description": "长柄武器"
},
{
"code": 3,
"name": "claymore",
"cname": "大剑",
"description": "大剑"
},
{
"code": 4,
"name": "bow",
"cname": "弓",
"description": "弓"
},
{
"code": 5,
"name": "catalyst",
"cname": "法器",
"description": "法器"
}
]
}
2.4.2 枚举的客户端
平时的业务服务器,就相当于枚举的客户端,直接引入framework-enums-client即可
2.4.2.1 pom 引用
<parent>
<artifactId>bailan</artifactId>
<groupId>indi.zhifa.recipe</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>enum-client-test</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-web-all</artifactId>
</dependency>
<dependency>
<groupId>indi.zhifa.recipe</groupId>
<artifactId>framework-enums-client</artifactId>
<version>${project.version}</version>
</dependency>
<!-- ******************lombok****************************-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
2.4.2.2 启动文件和nacos配置
bailan3-enums-cli DEV
spring:
application:
name: bailan3-enums-cli
datasource:
#数据库配置
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/platform?useUnicode=true&characterEncoding=utf-8
username: app
password: ILv0404@1314
hikari:
# 连接池最大连接数,默认是10
maximum-pool-size: 100
# 最小空闲链接
minimum-idle: 5
# 空闲连接存活最大时间,默认 600000(10分钟)
idle-timeout: 600000
# 数据库连接超时时间,默认30秒,即30000
connection-timeout: 30000
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟;正在使用的连接永远不会退休,只有在关闭后才会被删除。
max-lifetime: 1800000
# 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
pool-name: Hikari
redis:
host: localhost
port: 6379
password: ilv0404@1314
# 连接超时时间(记得添加单位,Duration)
timeout: 10000ms
lettuce:
shutdown-timeout: 100 # 关闭超时时间
pool:
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 # 连接池中的最小空闲连接
swagger:
enable: true
group-name: "类型管理接口"
api-package: indi.zhifa.recipe.bailan.framework.enums.controller.api
api-regex: "/api/**"
title: "类型管理接口"
description: "类型管理接口"
version: "1.0.0"
name: "芝法酱"
email: "hataksumo@163.com"
url: "https://github.com/hataksumo"
mybatis-plus:
typeEnumsPackage: indi.zhifa.recipe.bailan.enumsclient.entity.enums
enum-memo:
enum-packages:
- indi.zhifa.recipe.bailan.enumsclient.entity.enums
bootstrap.yml
server:
# 服务端口
port: 8082
spring:
application:
name: "bailan3-enums-cli"
servlet:
multipart:
enabled : true
max-file-size: "256MB"
max-request-size: "256MB"
cloud:
nacos:
server-addr: localhost:8848
config:
namespace: bailan
name: bailan3-enums-cli
file-extension: yml
group: DEV
discovery:
register-enabled: true
2.4.2.3 启动类
@MapperScan(basePackages ={"indi.zhifa.recipe.bailan.framework.**.mapper"})
@SpringBootApplication(
scanBasePackages = {"indi.zhifa.recipe.bailan.framework","indi.zhifa.recipe.bailan.enumsclient"}
)
public class EnumClientTestApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(EnumClientTestApplication.class, args);
}
}
2.4.2.4 测试枚举
package indi.zhifa.recipe.bailan.enumsclient.entity.enums;
@EnumDesc(name = "角色",desc = "角色的枚举")
@RequiredArgsConstructor
public enum ERoles {
CUSTOMER(0,"客户","普通客户,只有浏览权限"),
SHOP(1,"商家","商家的枚举,可以创建商品"),
SYS_ADM(2,"系统管理员","系统管理员,可以编辑系统规则,调用危险接口调整数据"),
ACCOUNT_ADM(3,"账户管理员","账户管理员,可以对账户进行操作");
@EnumValue
@Getter
final int code;
@Getter
final String name;
@Getter
final String description;
}
package indi.zhifa.recipe.bailan.enumsclient.entity.enums;
@EnumDesc(name = "电压等级",desc = "电压等级")
@AllArgsConstructor
public enum EVoltage {
V35(1,"35kV","35千伏"),
V110(2,"110kV","110千伏"),
V220(3,"220kV","220千伏"),
V500(4,"500kV","500千伏"),
VD800(5,"±800kV","±800千伏"),
V1000(6,"1000kV","1000千伏");
@EnumValue
@Getter
int code;
@Getter
String name;
@Getter
String description;
}
2.4.2.5 AppInit
@RequiredArgsConstructor
@Component
public class AppInit implements CommandLineRunner {
private final IEnumMemoService mEnumMemoService;
private final EnumMemoConfig mEnumMemoConfig;
@Override
public void run(String... args) throws Exception {
DtoEntityUtil.init();
mEnumMemoService.initEnum(mEnumMemoConfig.getEnumPackages());
}
}
2.4.2.6 测试过程
查询电压等级:
查询原神职业:
在后端管理服务中,添加一个JLPT等级
查询JlptLevel
删除JLPT等级
再次查询JLPT等级