Spring Boot实现高质量的CRUD-4

(续前文)

8、Service实现类 ​

​	Service实现类提供Service接口类的代码实现,Service实现类的命名为XXXManServiceImpl。
​	Service实现类完成CRUD的核心处理代码,CRUD的不同方法,有各自常规的处理流程。

8.1、新增单个对象

​	新增单个对象的方法名为addItem,处理流程如下:
	// 新增对象
	public Map<String,Object> addItem(HttpServletRequest request, XXX item);

	step1、参数校验,检查必选字段是否都已赋值。考虑到类对象item的各个属性都有默认值,"赋值"这个概念需要重新定义,此处定义如下:
	1)值为null,表示未赋值;
	2)如果对象值为默认值,且默认值为无效值,如空串或0,则表示未赋值;
	3)如果对象值为默认值,且默认值为有效值,如枚举值,则表示已赋值;
	4)如果对象值不为默认值,表示已赋值。
	实际上,参数校验工作还有很多,如值范围,手机号码格式,email格式,字符串是否为指定形式的有效字符串等,取决于投入产出比,是否需要,视业务需求而定。
	step2、外键ID的有效性,如存在外键ID,则需检查参照的外键对象需要存在。
	step3、数据权限检查,如对象涉及数据权限,需检查操作者有新增此记录的权限。如用户数据权限中orgId为2,但新增用户数据时,orgId为3,不在操作者的权限范围内,因此无权新增此记录。
	step4、枚举值检查,如存在枚举值字段,则需要检查值的有效性,如userType,取值范围为1,2,3,则其它值无效。
	step5、唯一字段值检查。新增对象时,往往涉及唯一字段,如对象名称,要求唯一;还要如准唯一字段,如新增用户时,手机号码、证件号码等,如数据库中已存在此唯一值,则抛出异常。
	step6、字段业务处理。某些对象新增时,需要进行业务处理,如新增用户对象时,针对前端传入的密码,内部生成salt字段,然后进行加盐MD5,二次加密,生成数据库存储需要的密码。
	step7、根据request对象,获取操作者账号信息,并设置对象。
	step8、如果ID为全局ID,则需要获取全局ID,并设置对象。
	step9、调用Dao的insertItem方法,新增记录。如果涉及数据一致性问题,也一并处理。
	step10、如果涉及缓存(如树型结构对象),则刷新缓存。
	step11、如果ID为自增ID或全局ID,则构造返回值字典,key为ID属性名,值为ID值。

8.2、批量新增对象

​	批量新增对象的方法名为addItems,处理流程如下:
	// 批量新增对象
	public Map<String,Object> addItems(HttpServletRequest request, List<XXX> itemList);

	step1、参数校验。针对itemList中的每一项,进行如addItem方法的参数校验。
	step2、如果itemList无成员,则返回。
	step3、如果要求某个属性值必须为同一个值,则进行检查。如批量新增用户,要求orgId都为同一个值。
	step4、针对itemList中的每一项,检查外键ID的有效性,数据权限,枚举值有效性,唯一字段值的唯一性,以及可能的业务处理。
	step5、如为全局ID,则批量申请ID。
	step6、根据request对象,获取操作者账号信息。
	step7、针对itemList中的每一项,设置ID和操作者账号信息。
	step8、调用Dao的insertItems方法,新增记录。如果涉及数据一致性问题,也一并处理。
	step9、如果涉及缓存(如树型结构对象),则刷新缓存。
	step10、如果ID为自增ID或全局ID,则构造返回值字典,key为ID属性名,值为ID值。

8.3、编辑对象

​	编辑对象的方法名为editItem,处理流程如下:
	// 编辑对象
	public void editItem(HttpServletRequest request, Map<String, Object> params);

	step1、参数校验,必须包含key属性字段参数;参数格式检查,如日期格式。
	step2、根据key值,获取数据库中存在对象,如果对象不存在,则抛出异常。
	step3、数据权限检查,操作者有编辑此存在对象的数据权限。如果数据权限相关字段允许修改,且有新值,则新值也需要检查数据权限。
	step4、如果涉及外键ID值的修改,则检查参照的外键对象的存在性。
	step5、如果涉及枚举值的修改,则检查枚举值的有效性。
	step6、如果涉及唯一值属性的修改,则检查唯一值的有效性。
	step7、移除可能存在的不可编辑项,这是为了防止params中携带了不可编辑的参数项,有点类似于"注入"的情形。
	step8、根据request对象,获取操作者账号信息。
	step9、调用Dao的updateByKey方法,修改记录。如果涉及数据一致性问题,也一并处理。
	step10、如果涉及缓存,则刷新缓存。

8.4、批量修改对象

​	批量修改对象的方法名为updateItems,处理流程如下:
	// 批量修改对象
	public void updateItems(HttpServletRequest request, Map<String, Object> params);

	step1、参数校验,必须包含至少一个修改字段参数和一个条件字段参数。
	step2、增加数据权限范围,确保只修改权限许可的数据。
	step3、如果涉及外键ID值的修改,则检查参照的外键对象的存在性。
	step4、如果涉及枚举值的修改,则检查枚举值的有效性。
	step5、如果涉及唯一值属性的修改,则检查唯一值的有效性。
	step6、根据request对象,获取操作者账号信息。
	step7、调用Dao的updateItems方法,批量修改记录。如果涉及数据一致性问题,也一并处理。
	step8、如果涉及缓存,则刷新缓存。

8.5、删除对象

​	删除对象的方法名为deleteItem,如果为逻辑删除,则为禁用/启用。如为物理删除,则为删除。处理流程如下:
	// 删除对象
	public void deleteItem(HttpServletRequest request, Map<String, Object> params);

	step1、参数校验,必须包含key字段参数。
	step2、根据key值,获取数据库中存在对象,如果对象不存在,则抛出异常。
	step3、数据权限检查,操作者有删除或修改此存在对象的数据权限。
	step4、如为逻辑删除,获取deleteFlag参数,默认为删除。如果为启用,需要检查启用对象的唯一值是否存在冲突。因为对象被禁用后,相当于释放了一个唯一值;现在启用,就可能发生唯一值冲突的情况。逻辑删除,调用Dao的updateByKey方法,修改deleteFlag字段值,因此,还需要设置操作者账号信息。
	step5、如为物理删除,调用Dao的deleteByKey方法。
	step6、如果涉及缓存,则刷新缓存。

8.6、批量删除对象

​	批量删除对象的方法名为deleteItems,只支持物理删除。处理流程如下:
	// 批量删除对象
	public void deleteItems(HttpServletRequest request, Map<String, Object> params);

	step1、参数校验,必须包含至少一个条件参数。
	step2、增加数据权限范围,确保只删除权限许可的数据。
	step3、调用Dao的deleteItems方法。
	step4、如果涉及缓存,则刷新缓存。

8.7、根据key查询对象

​	根据key查询对象的方法名为getItem。处理流程如下:
	// 根据key获取一个XXX对象
	public XXX getItem(HttpServletRequest request, Map<String, Object> params)

	step1、参数校验,必须包含key字段参数。
	step2、调用Dao的selectItemByKey方法,获取对象。
	step3、检查数据权限,是否在权限许可范围。
	step4、如果涉及外键引用字段赋值,则针对查询结果对象,设置相关值。
	step5、返回对象。

8.8、分页查询对象

​	分页查询对象的方法名为queryItems。处理流程如下:
	// 分页查询对象
	public PageInfo<XXX> queryItems(HttpServletRequest request, Map<String, Object> params);

	step1、增加数据权限范围,确保只查询权限许可的数据。如果查询条件参数包含了权限相关字段列表,则取交集。
	step2、调用PageHelper.startPage,启动分页查询。
	step3、调用Dao的selectItems方法。
	step4、如果涉及外键引用字段赋值,则针对查询结果集的每一个对象,设置相关值。
	step5、返回分页对象。

8.9、查询对象列表

​	查询对象列表的方法名为getItems。处理流程如下:
	// 查询对象列表
	public List<XXX> getItems(HttpServletRequest request, Map<String, Object> params);

	step1、增加数据权限范围,确保只查询权限许可的数据。如果查询条件参数包含了权限相关字段列表,则取交集。
	step2、调用Dao的selectItems方法。
	step3、如果涉及外键引用字段赋值,则针对查询结果集的每一个对象,设置相关值。
	step4、返回对象对象。

8.10、新增或修改对象

​	新增或修改对象的方法名为FlushItem。处理流程如下:
	// 新增或修改对象
	public void flushItem(HttpServletRequest request,XXX item);

	step1、获取唯一键字段值,或准唯一字段,查询数据库中是否存在对应的对象。
	step2、如对象不存在,则调用addItem方法新增。
	step3、如对象已存在,则先设置key值,然后调用editItem方法修改。

8.11、导出Excel文件

​	导出Excel文件的方法名为exportExcelFile。处理流程如下:
	// 导出Excel文件
	public void exportExcelFile(HttpServletRequest request,HttpServletResponse response,
			Map<String,Object> params);

	step1、查询条件增加数据权限范围,确保只查询权限许可的数据。如果查询条件参数包含了权限相关字段列表,则取交集。
	step2、调用Dao的deleteItems方法,查询数据。如果涉及外键引用字段赋值,则针对查询结果集的每一个对象,设置相关值。
	step3、使用Excel导出处理框架,实现Excel数据导出。先定义导出字段列表。
	step4、导出类为BaseExportObj,负责将对象列表转为List<String[]>类型的表格数据dataRowList。
	step5、如果涉及枚举字段翻译,对dataRowList进行翻译处理。
	step6、使用ExcelExportHandler类,将dataRowList写入Excel文件。
	step7、调用download公共方法,实现文件下载。

8.12、导入Excel文件

​	导入Excel文件的方法名为importExcelFile。分为常规导入和异步导入。

8.12.1、常规导入处理

​	常规处理流程如下:
	// 导入Excel文件
	public List<String> importExcelFile(HttpServletRequest request,
			MultipartFile upfile, Map<String, Object> params);

	step1、如果导入对象涉及数据权限,则参数一般需要包含数据权限相关字段值;否则如果数据权限字段在Excel文件中某列,处理会变得复杂,此时,一般简单规定有全部数据权限的操作者方允许操作。
	step2、参数校验,必选字段是否包含。
	step3、数据权限检查。
	step4、使用Excel导入处理框架,实现Excel数据导人。先定义导入字段列表,确定必选标题列。此框架不要求列序号固定。
	step5、使用ExcelImportHandler类,将Excel数据读入到List<String[]>类型的dataRows中。如果必选列有缺失,则返回异常信息。
	step6、如果涉及枚举字段翻译,对dataRows进行翻译处理。
	step7、导入类为BaseImportObj,负责将List<String[]>类型的表格数据转为对象列表。
	step8、处理对象列表的入库。
	step9、对象列表的入库,有以下策略可选择:
	1)常规处理:如果对象存在,则修改;否则新增。这种策略为逐条处理,性能不高。
	2)自适应批量入库:先尝试批量新增,遇到异常,则缩小批量,如果为批量为1,且新增仍然失败,意味着此记录异常,加入异常列表。继续新增处理,如果成功,则扩大批量。参见:[https://www.cnblogs.com/alabo1999/p/15828334.html](一种基于二分法的变步长批量处理算法)。这种策略以新增为主,并允许有少量异常记录。
	step10、导入完成,返回异常记录列表。此时,正常数据已完成导入,未导入的异常数据有行号信息。

8.12.2、异步导入处理

​	异步导入处理,使用异步处理框架。异步处理框架是一个通用的异步处理框架。处理逻辑:前端提交任务,获取任务ID,然后根据这个任务ID进行轮询,获取任务处理信息和任务处理结果。后端使用异步处理方式,调用异步处理方法进行处理,完成后保存处理结果一段时间(默认30s),然后释放处理结果。参见:[https://www.cnblogs.com/alabo1999/p/16607827.html](Spring Boot异步请求处理框架)
​	由于异常处理方法,被反射调用,因此必须是public的方法。
​	处理流程如下:
	// 异步导入Excel文件
	public Map<String, Object> importExcelFile(HttpServletRequest request,
			 MultipartFile upfile, Map<String, Object> params);

	step1、参数校验,必选字段是否包含。
	step2、数据权限检查。
	step3、访问upfile参数,获取文件名和输入流InputStream。
	step4、根据request对象,获取操作者账号信息。
	step5、构造异步调用的参数集,为文件名,输入流和操作者账号信息。
	step6、获取异步处理方法asyncImpProc的Method对象。
	step7、调用异步处理的任务管理器的addTask方法,添加异步处理任务,并获取任务ID。
	step8、返回任务ID信息。	

	// 异常导入处理方法:asyncImpProc
	public void asyncImpProc(TaskInfo taskInfo);

	step1、TaskInfo为任务信息对象,包含了提交任务时的参数集。
	step2、使用Excel导入处理框架,实现Excel数据导人。
	step3、使用ExcelImportHandler类,将Excel数据读入到List<String[]>类型的dataRows中。如果必选列有缺失,则返回异常信息。
	step4、如果涉及枚举字段翻译,对dataRows进行翻译处理。
	step5、导入类为BaseImportObj,负责将List<String[]>类型的表格数据转为对象列表。	
	step6、处理对象列表的入库。有以下处理策略:
	1)常规处理:单线程处理,不管是逐条处理,还是分批处理,是在一个线程中逐步处理。
	2)多线程处理:可以开启临时线程池或利于已配置的线程池,进行多线程处理,最后通过List<CompletableFuture<List<String>>>合并异常记录列表。
	step7、处理过程中可以输出处理日志,以及进度百分比。	
	step8、任务信息taskInfo,设置异常记录列表作为处理结果。	

	// 获取导入处理信息:
	public Map<String, Object> getImpProcInfo(HttpServletRequest request,Map<String, Object> params);

	step1、参数校验,必须包含taskId参数。
	step2、根据taskId,从任务管理器中获取任务信息taskInfo。
	step3、如果任务信息不存在或已过期,则抛出异常。
	step4、如果任务信息有效,则构造并返回输出信息,一般为任务ID、处理状态、进度、提示消息、日志列表、处理结果。

8.13、相关的公共服务和方法

​	根据前面CRUD的常规方法的处理流程,可以看出需要一些公共的服务和方法,以免不同对象的CRUD重复开发。

8.13.1、服务基类BaseService

​	BaseService类,提供参数校验接口、启动分页处理和获取用户账号信息的公共方法
	/**
	 * 
	 * @methodName		: checkValidForParams
	 * @description		: 输入参数校验,子类根据需要重载此方法
	 * @param request	: request对象
	 * @param methodName: 方法名称
	 * @param params	: 输入参数
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public void checkValidForParams(HttpServletRequest request,String methodName, Object params) {
		
	}

	/**
	 * 
	 * @methodName		: processPageInfo
	 * @description		: 处理分页信息
	 * @param params	: 包含分页信息的map
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public void processPageInfo(Map<String,Object> params) {
    	// 分页处理
    	Integer pageNum = (Integer)params.get("pagenum");
    	if(Objects.isNull(pageNum)) {
    		pageNum = 1;
    	}
    	Integer pageSize = (Integer)params.get("pagesize");
    	if(Objects.isNull(pageSize)) {
    		pageSize = 10;
    	}
    	PageHelper.startPage(pageNum, pageSize);				
	}

	/**
	 * 
	 * @methodName		: getUserName
	 * @description		: 获取访问账户的用户名
	 * @param request	: request对象
	 * @return			: 访问账户的用户名
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public String getUserName(HttpServletRequest request) {
		String userName = "";
		if (request == null) {
			return userName;
		}
		// 获取accountId
		String accountId = accountCacheService.getId(request);
		// 获取用户名	
		Object userNameObj = accountCacheService.getAttribute(accountId, Constants.USER_NAME);		
		if (userNameObj != null) {
			userName = (String)userNameObj;
		}else {
			// 说明此时accountId无效,或属性"userName"未注册			
			return "";			
		}
		return userName;
	}
​	accountCacheService对象是自动注入的AccountCacheService类对象,AccountCacheService是类似于Session管理的类,用于管理账号缓存信息。在跨域服务时,SessionId可能经常发生改变,导致无法定位前端。此时可以通过添加名为x-SessionId的Header参数,根据x-SessionId来定位前端。另外,多机部署时,使用Session,需要Session共享,不如用Redis。AccountCacheService类可以方便用于Redis。

8.13.2、数据权限服务

​	数据权限服务DataRightsService类,提供数据权限服务的相关方法,包括获取当前用户的各个权限字段的权限类型、权限范围,检查某个权限字段值是否有权限等。

8.13.3、外键ID有效性检查

​	外键ID的对象是否存在,这个涉及全部对象类型。使用集中式的ID检查类IdCheckService。
​	先使用枚举类型EIdType定义各种ID,示例代码如下(可以继续添加业务涉及的各种ID):
public enum EIdType {
	userId (1,"userId"),
	funcId (2,"funcId"),
	roleId (3,"roleId"),
	drFieldId (4,"fieldId"),
	orgId (5,"orgId"),

	
	;	// 结束定义
	
	// 枚举值对应的code值
	int code;
	
	// 名称
	String name;

	// 构造函数
	private EIdType(int code,String name) {
	    this.code = code;
	    this.name = name;
	}

	// 属性code的getter方法
	public int getCode() {
	    return code;
	}

	// 属性code的setter方法
	public void setCode(int code) {
	    this.code = code;
	}
	
	// name的getter/setter方法
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 
	 * @methodName		: getType
	 * @description		: 根据code获取枚举值
	 * @param code		: code值 
	 * @return			: code对应的枚举值
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public static EIdType getType(int code) {
		// 返回值变量
		EIdType eRet = null;
		
		for (EIdType item : values()) {
			// 遍历每个枚举值
			if (code == item.getCode()) {
				// code匹配
				eRet = item;
				break;
			}
		}
		
		return eRet;
	}
​	IdCheckService接口类,提供一个方法,代码如下:
public interface IdCheckService {

	/**
	 * 
	 * @methodName		: getObjById
	 * @description		: 检查对象ID的有效性,如果有效则返回对象,如果无效,则抛出异常
	 * @param eIdType	: ID枚举类型
	 * @param id		: ID的值
	 * @param params	: 可选参数,适用于多个key字段的对象
	 * @return			: ID对应的对象
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * yyyy/mm/dd	1.0.0		author		    初版
	 *
	 */
	public Object getObjById(EIdType eIdType,Object id,Object...params);
}
IdCheckService实现类,示例代码如下:
@Service
public class IdCheckServiceImpl implements IdCheckService{
	@Autowired
	private UserDao userDao;	
	
	// 缓存数据服务类对象
	@Autowired
	private CacheDataService cds;
	
	// 公共配置数据服务类对象
	@Autowired
	private GlobalConfigService gcs;
		
	
	/**
	 * 
	 * @methodName		: getObjById
	 * @description		: 检查对象ID的有效性,如果有效则返回对象,如果无效,则抛出异常
	 * @remark			: 参见接口类相应方法说明
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2023/04/08	1.0.0		sheng.zheng		初版
	 *
	 */
	@Override
	public Object getObjById(EIdType eIdType,Object id,Object...params) {
		Object item = null;
		try {
			switch(eIdType) {
			case userId:
			{
				item = userDao.selectItemByKey((Long)id);
			}
				break;
			case orgId:
			{
				OrgnizationService os = (OrgnizationService)gcs.getDataServiceObject("OrgnizationService");
				item = os.getItemByKey((Integer)id, false);
			}
				break;
			default:
				break;		
			}		
		}catch(Exception e) {
			LogUtil.error(e);
			throw new BaseException(ExceptionCodes.ERROR,e.getMessage());
		}
		if (item == null) {
			throw new BaseException(ExceptionCodes.OBJECT_DOES_NOT_EXIST,
					eIdType.getName() + "=" + id);
		}			
		return item;
	}
}
各种对象,根据key值获取的方法可以不同,如直接查询数据库,或通过缓存获取等,具体实现方法,不同系统可以有不同的处理。

8.13.4、枚举值有效性检查

​	为了方便枚举值检查,所有枚举值都加一个getTypeByCode接口。下面是示例代码:
public enum EOrgType {
    otOurCompanyE	(1,"本公司"),
    otCustomE		(2,"客户公司"),
	;	// 结束定义
	
	// 枚举值对应的code值
	int code;
	
	// 描述
	String desc;

	// 构造函数
	private EOrgType(int code,String desc) {
	    this.code = code;
	    this.desc = desc;
	}

	// 属性code的getter方法
	public int getCode() {
	    return code;
	}

	// 属性code的setter方法
	public void setCode(int code) {
	    this.code = code;
	}
	
	// desc的getter/setter方法
	public String getDesc() {
		return desc;
	}
	public void setDesc(String desc) {
		this.desc = desc;
	}
	
	/**
	 * 
	 * @methodName	: getType
	 * @description	: 根据code获取枚举值
	 * @param code	: code值 
	 * @return		: code对应的枚举值
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2023/05/17	1.0.0		sheng.zheng		初版
	 *
	 */
	public static EOrgType getType(int code) {
		// 返回值变量
		EOrgType eRet = null;
		
		for (EOrgType item : values()) {
			// 遍历每个枚举值
			if (code == item.getCode()) {
				// code匹配
				eRet = item;
				break;
			}
		}
		
		return eRet;
	}

	// 检查并获取指定code的枚举值
	public static EOrgType getTypeByCode(int code) {
		EOrgType item = getType(code);
		if (item == null) {
			throw new BaseException(ExceptionCodes.INVALID_ENUM_VALUE,"EOrgType with code="+code);
		}
		
		return item;
	}	

8.13.5、缓存管理

​	缓存用于频繁访问的对象,使用缓存,可以减少数据库IO的开销,提升系统处理性能。
​	缓存要解决数据一致性问题。对于单机部署的系统,可以使用内存;对于多机部署的系统,需要使用Redis来实现缓存数据的一致性。但是还需要解决数据库与缓存数据一致性的问题。
​	另外,缓存数据还有生命周期,超过一定时间未访问的数据,可以移除,以避免内存需求无限扩大。
​	缓存一般使用字典管理对象,很少使用全集数据,因为维持全集数据的代价比较大,如增加、修改、删除一个对象都需要重新加载全部数据。
​	使用字典,修改和删除对象,直接移除此对象;增加对象时,新对象可以不必加入缓存。当需要根据key获取某个对象时,如果缓存中存在,则直接返回缓存对象;如果缓存中不存在,则查询数据库,如果查询到,则加入缓存并返回对象;如果查询不到,则加入空对象(确保短期内不再根据失败的key去查询数据库)。对于Redis,直接使用一个大字典,性能不高,可以针对不同的字典,加对象类别key,然后拆散字典。
​	使用全集(如列表或树对象),可以设置对象全集的修改标记,任何对象发生改变时,设置修改标记为true,但不立即加载全部数据。当请求全集数据时,检查修改标记,如为true,则重新加载全集数据,并复位修改标记。如修改标记为false,则直接返回缓存中的全集。
​	每次访问缓存对象,需要更新对象的最近访问时间,从而根据访问热度,移除过期(未访问的)缓存数据。​	Redis可以直接使用生命期管理,也可以用此方法代码管理。
​	为了便于缓存对象管理,使用下列CacheObj,来封装缓存对象。
@Data
public class CacheObj<T> {
	// 缓存对象
	private T itemObj;
	// 最后访问时间,UTC
	private long lastAccTime = 0;
	// 到期时间,UTC
	private long expiredTime = 0;
	// 最后查询数据库时间,UTC
	private long lastQueryTime = 0;

	/**
	 * 
	 * @methodName		: resetLastAccTime
	 * @description		: 重置最后访问时间,同时更新到期时间
	 * @param accTime	: 访问时间
	 * @param interval	: 到期时间间隔
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2022/06/04	1.0.0		sheng.zheng		初版
	 *
	 */
	public void resetLastAccTime(long accTime,long interval) {
		lastAccTime = accTime;
		expiredTime = accTime + interval;
	}
	
	// 是否到期
	public boolean isExpired() {
		return (lastAccTime >= expiredTime);
	}
	
	// 是否到期
	public boolean isExpired(long current) {
		return (current >= expiredTime);
	}	
	
	// 是否可以查询
	public boolean canQuery(long current,long interval) {
		return (current >= lastQueryTime + interval);
	}
}	
​	缓存对象的管理示例如下:
	// 组织缓存对象,key为orgId
	Map<Integer,CacheObj<Orgnization>> orgMap = new HashMap<Integer,CacheObj<Orgnization>>();

8.13.6、Excel导入导出处理

​	Excel导入导出框架的包括下列类:
	ImpExpFieldDef:导入导出字段定义;
	BaseExportObj:数据导出基类;
	ExcelExportHandler:Excel导出处理类;
	BaseImportObj:导入对象基类;
	ExcelImportHandler:Excel文件导入处理类;

代码见[https://gitee.com/shengzheng1999/esb-common/tree/master/src/main/java/com/abc/esbcommon/common/impexp]。

8.13.7、公共方法

​	常用的方法有:
	/**
	 * 
	 * @methodName		: checkKeyFields
	 * @description		: 检查关键字段是否缺失
	 * @param params	: 输入参数
	 * @param fieldNames: 关键字段名
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public static void checkKeyFields(Map<String,Object> params,String[] fieldNames) {
		for(int i = 0; i < fieldNames.length; i++) {
			String fieldName = fieldNames[i];
			if (!params.containsKey(fieldName)) {
				String prompt = ",缺失字段:" + fieldName;
				throw new BaseException(ExceptionCodes.ARGUMENTS_ERROR.getCode(),
						ExceptionCodes.ARGUMENTS_ERROR.getMessage() + prompt);
			}
		}
	}

	/**
	 * 
	 * @methodName		: checkOpKeyFields
	 * @description		: 检查可选关键字段是否存在
	 * @param params	: 输入参数
	 * @param fieldNames: 关键字段名
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public static void checkOpKeyFields(Map<String,Object> params,String[] fieldNames) {
		boolean bFound = false;
		String prompt = ",缺失可选字段:";
		for(int i = 0; i < fieldNames.length; i++) {
			String fieldName = fieldNames[i];
			if (params.containsKey(fieldName)) {
				bFound = true;
				break;
			}
			if (i != 0) {
				prompt += ",";
			}
			prompt += fieldName;			
		}
		if (!bFound) {
			throw new BaseException(ExceptionCodes.ARGUMENTS_ERROR.getCode(),
					ExceptionCodes.ARGUMENTS_ERROR.getMessage() + prompt);			
		}
	}

	/**
	 * 
	 * @methodName		: removeKeys
	 * @description		: 根据指定key数组,移除字典对应key的项
	 * @param params	: Map<String,Object>类型的字典
	 * @param keys		: 需要移除的key数组
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public static void removeKeys(Map<String,Object> params,String[] keys) {
		for (int i = 0; i < keys.length; i++) {
			String key = keys[i];
			if (params.containsKey(key)) {
				params.remove(key);
			}
		}
	}

	/**
	 * 
	 * @methodName		: download
	 * @description		: 下载指定路径的文件
	 * @param response	: reponse对象
	 * @param filePath	: 需要下载的文件路径
	 * @param filename	: 下载文件的文件名
	 * @param contentType	: response header中的ContentType,常用取值如下:
	 * 		普通二进制文件	: application/octet-stream
	 * 		Excel文件		: application/vnd.ms-excel
	 * 		文本文件		: text/plain; charset=utf-8
	 * 		html文件		: text/html; charset=utf-8
	 * 		xml文件			: text/xml; charset=utf-8
	 * 		jpeg文件		: image/jpeg
	 * @throws Exception	: 异常发生时,抛出。没有异常,说明下载成功。
	 * @history		:
	 * ------------------------------------------------------------------------------
	 * date			version		modifier		remarks                   
	 * ------------------------------------------------------------------------------
	 * 2021/01/01	1.0.0		sheng.zheng		初版
	 *
	 */
	public static void download(HttpServletResponse response,String filePath,
			String filename,String contentType) throws Exception;	

详细代码见[https://gitee.com/shengzheng1999/esb-common/tree/master/src/main/java/com/abc/esbcommon/common/utils]。

(未完待续...)

posted @ 2023-06-15 19:14  阿拉伯1999  阅读(92)  评论(0编辑  收藏  举报