(续前文)

9、Service实现类代码示例 ​

​	以用户管理模块为例,展示Service实现类代码。用户管理的Service实现类为UserManServiceImpl。​UserManServiceImpl除了没有deleteItems方法外,具备CRUD的其它常规方法。实际上​UserManService还有其它接口方法,如管理员修改密码,用户修改自身密码,设置用户角色列表,设置用户数据权限等,这些不属于常规CRUD方法,故不在此展示。

9.1、类定义及成员属性

​	UserManServiceImpl的类定义如下:
package com.abc.example.service.impl;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.github.pagehelper.PageInfo;
import com.abc.esbcommon.common.impexp.BaseExportObj;
import com.abc.esbcommon.common.impexp.BaseImportObj;
import com.abc.esbcommon.common.impexp.ExcelExportHandler;
import com.abc.esbcommon.common.impexp.ExcelImportHandler;
import com.abc.esbcommon.common.impexp.ImpExpFieldDef;
import com.abc.esbcommon.common.utils.FileUtil;
import com.abc.esbcommon.common.utils.LogUtil;
import com.abc.esbcommon.common.utils.Md5Util;
import com.abc.esbcommon.common.utils.ObjListUtil;
import com.abc.esbcommon.common.utils.ReflectUtil;
import com.abc.esbcommon.common.utils.TimeUtil;
import com.abc.esbcommon.common.utils.Utility;
import com.abc.esbcommon.common.utils.ValidateUtil;
import com.abc.esbcommon.entity.SysParameter;
import com.abc.example.common.constants.Constants;
import com.abc.example.config.UploadConfig;
import com.abc.example.dao.UserDao;
import com.abc.example.entity.Orgnization;
import com.abc.example.entity.User;
import com.abc.example.enumeration.EDeleteFlag;
import com.abc.example.enumeration.EIdType;
import com.abc.example.enumeration.ESex;
import com.abc.example.enumeration.EUserType;
import com.abc.example.exception.BaseException;
import com.abc.example.exception.ExceptionCodes;
import com.abc.example.service.BaseService;
import com.abc.example.service.DataRightsService;
import com.abc.example.service.IdCheckService;
import com.abc.example.service.SysParameterService;
import com.abc.example.service.TableCodeConfigService;
import com.abc.example.service.UserManService; /**
* @className : UserManServiceImpl
* @description : 用户对象管理服务实现类
* @summary :
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
@SuppressWarnings({ "unchecked", "unused" })
@Service
public class UserManServiceImpl extends BaseService implements UserManService{
// 用户对象数据访问类对象
@Autowired
private UserDao userDao; // 文件上传配置类对象
@Autowired
private UploadConfig uploadConfig; // 对象ID检查服务类对象
@Autowired
private IdCheckService ics; // 数据权限服务类对象
@Autowired
private DataRightsService drs; // 全局ID服务类对象
@Autowired
private TableCodeConfigService tccs; // 系统参数服务类对象
@Autowired
private SysParameterService sps; // 新增必选字段集
private String[] mandatoryFieldList = new String[]{"userName","password","userType","orgId"}; // 修改不可编辑字段集
private String[] uneditFieldList = new String[]{"password","salt","deleteFlag"}; }
​	UserManServiceImpl类继承BaseService,实现UserManService接口。BaseService提供参数校验接口、启动分页处理和获取用户账号信息的公共方法(参见上文的8.13.1)。
​ UserManServiceImpl类成员属性作用说明:
	UserDao userDao:用户对象数据访问类对象,用于访问数据库用户表。CRUD操作,与数据库紧密联系,userDao是核心对象。
UploadConfig uploadConfig:文件上传配置类对象,提供临时路径/tmp,导出Excel文件时,生成临时文件存于临时目录。上传Excel文件,临时文件也存于此目录。
IdCheckService ics:对象ID检查服务类对象,用于外键对象存在性检查。
DataRightsService drs:数据权限服务类对象,提供数据权限处理的相关接口方法。
TableCodeConfigService tccs:全局ID服务类对象,提供生成全局ID的接口方法。
SysParameterService sps:系统参数服务类对象,在Excel数据导入导出时,对枚举字段的枚举值翻译,需要根据系统参数表的配置记录进行翻译。
String[] mandatoryFieldList:新增必选字段集,新增对象时,规定哪些字段是必须的。
String[] uneditFieldList:不可编辑字段集,编辑对象时,规定哪些字段是需要不允许修改的,防止参数注入。

9.2、新增对象

​	新增对象的方法为addItem,下面是新增用户对象的方法:
	/**
* @methodName : addItem
* @description : 新增一个用户对象
* @remark : 参见接口类方法说明
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
@Override
public Map<String,Object> addItem(HttpServletRequest request, User item) {
// 输入参数校验
checkValidForParams(request, "addItem", item); // 检查参照ID的有效性
Integer orgId = item.getOrgId();
Orgnization orgnization = (Orgnization)ics.getObjById(EIdType.orgId,orgId); // 检查数据权限
drs.checkUserDrByOrgId(request, orgId); // 检查枚举值
int userType = item.getUserType().intValue();
EUserType eUserType = EUserType.getTypeByCode(userType);
int sex = item.getSex().intValue();
ESex eSex = ESex.getTypeByCode(sex); // 检查唯一性
String userName = item.getUserName();
String phoneNumber = item.getPhoneNumber();
String idNo = item.getIdNo();
String openId = item.getOpenId();
String woaOpenid = item.getWoaOpenid();
checkUniqueByUserName(userName);
checkUniqueByPhoneNumber(phoneNumber);
checkUniqueByIdNo(idNo);
checkUniqueByOpenId(openId);
checkUniqueByWoaOpenid(woaOpenid); // 业务处理
LocalDateTime current = LocalDateTime.now();
String salt = TimeUtil.format(current, "yyyy-MM-dd HH:mm:ss");
// 明文密码加密
String password = item.getPassword();
String encyptPassword = Md5Util.plaintPasswdToDbPasswd(password, salt, Constants.TOKEN_KEY);
item.setSalt(salt);
item.setPassword(encyptPassword); Long userId = 0L;
// 获取全局记录ID
Long globalRecId = tccs.getTableRecId("exa_users");
userId = globalRecId; // 获取操作人账号
String operatorName = getUserName(request); // 设置信息
item.setUserId(userId);
item.setOperatorName(operatorName); try {
// 插入数据
userDao.insertItem(item); } catch(Exception e) {
LogUtil.error(e);
throw new BaseException(ExceptionCodes.ADD_OBJECT_FAILED,e.getMessage());
} // 构造返回值
Map<String,Object> map = new HashMap<String,Object>();
map.put("userId", userId.toString()); return map;
}

9.2.1、新增对象的参数校验

​	首先是参数校验,使用checkValidForParams方法,此方法一般仅对输入参数进行值校验,如字段是否缺失,值类型是否匹配,数据格式是否正确等。
	/**
* @methodName : checkValidForParams
* @description : 输入参数校验
* @param request : request对象
* @param methodName : 方法名称
* @param params : 输入参数
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
@Override
public void checkValidForParams(HttpServletRequest request, String methodName, Object params) {
switch(methodName) {
case "addItem":
{
User item = (User)params; // 检查项: 必选字段
ReflectUtil.checkMandatoryFields(item,mandatoryFieldList); // 用户名格式校验
ValidateUtil.loginNameValidator("userName", item.getUserName()); // 手机号码格式校验
if (!item.getPhoneNumber().isEmpty()) {
ValidateUtil.phoneNumberValidator("phoneNumber", item.getPhoneNumber());
} // email格式校验
if (!item.getEmail().isEmpty()) {
ValidateUtil.emailValidator("email", item.getEmail());
}
}
break;
// case "editItem":
// ...
default:
break;
}
}
​	addItem方法的输入参数校验。首先是必选字段校验,检查必选字段是否都有值。然后是相关属性值的数据格式校验。
9.2.1.1、新增对象的必选字段校验
​	调用用ReflectUtil工具类的checkMandatoryFields,检查字符串类型和整数的字段,是否有值。
	/**
*
* @methodName : checkMandatoryFields
* @description : 检查必选字段
* @param <T> : 泛型类型
* @param item : T类型对象
* @param mandatoryFieldList: 必选字段名数组
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/26 1.0.0 sheng.zheng 初版
*
*/
public static <T> void checkMandatoryFields(T item,String[] mandatoryFieldList) {
// 获取对象item的运行时的类
Class<?> clazz = (Class<?>) item.getClass();
String type = "";
String shortType = "";
String error = "";
for(String propName : mandatoryFieldList) {
try {
Field field = clazz.getDeclaredField(propName);
field.setAccessible(true);
// 获取字段类型
type = field.getType().getTypeName();
// 获取类型的短名称
shortType = getShortTypeName(type); // 获取属性值
Object oVal = field.get(item);
if (oVal == null) {
// 如果必选字段值为null
error += propName + ",";
continue;
}
switch(shortType) {
case "Integer":
case "int":
{
Integer iVal = Integer.valueOf(oVal.toString());
if (iVal == 0) {
// 整型类型,有效值一般为非0
error += propName + ",";
}
}
break;
case "String":
{
String sVal = oVal.toString();
if (sVal.isEmpty()) {
// 字符串类型,有效值一般为非空串
error += propName + ",";
}
}
break;
case "Byte":
case "byte":
// 字节类型,一般用于枚举值字段,后面使用枚举值检查,此处忽略
break;
case "List":
{
List<?> list = (List<?>)oVal;
if (list.size() == 0) {
// 列表类型,无成员
error += propName + ",";
}
}
break;
default:
break;
}
}catch(Exception e) {
// 非属性字段
if (error.isEmpty()) {
error += propName;
}else {
error += "," + propName;
}
}
}
if (!error.isEmpty()) {
error = Utility.trimLeftAndRight(error,"\\,");
throw new BaseException(ExceptionCodes.ARGUMENTS_IS_EMPTY,error);
}
}
​	一般情况下,这个方法可以起作用。但特殊情况,如0值为有效值,-1为无效值,则会有误报情况。此时,可以使用另一个方法。
	/**
*
* @methodName : checkMandatoryFields
* @description : 检查必选字段
* @param <T> : 泛型类型
* @param item : 参考对象,属性字段值使用默认值或区别于有效默认值的无效值
* @param item2 : 被比较对象
* @param mandatoryFieldList: 必须字段属性名列表
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/06/17 1.0.0 sheng.zheng 初版
*
*/
public static <T> void checkMandatoryFields(T item,T item2,
String[] mandatoryFieldList) {
Class<?> clazz = (Class<?>) item.getClass();
String error = "";
for(String propName : mandatoryFieldList) {
try {
Field field = clazz.getDeclaredField(propName);
field.setAccessible(true);
// 获取属性值
Object oVal = field.get(item);
field.setAccessible(true);
// 获取属性值
Object oVal2 = field.get(item2);
if (oVal2 == null) {
// 新值为null
error += propName + ",";
continue;
}
if (oVal != null) {
if (oVal.equals(oVal2)) {
// 如果值相等
error += propName + ",";
}
} }catch(Exception e) {
// 非属性字段
if (error.isEmpty()) {
error += propName;
}else {
error += "," + propName;
}
}
}
if (!error.isEmpty()) {
error = Utility.trimLeftAndRight(error,"\\,");
throw new BaseException(ExceptionCodes.ARGUMENTS_IS_EMPTY,error);
}
}
​	这个方法,增加了参考对象item,一般使用默认值,新对象为item2,如果新对象的必选属性值为参考对象一致,则认为该属性未赋值。这对于默认值为有效值时,会有问题,此时调用方法前先将有效默认值设置为无效值。
​ 如User对象类,userType默认值为3,是有效值。可如下方法调用:
		User item = (User)params;

		// 检查项: 必选字段
User refItem = new User();
// 0为无效值
refItem.setUserType((byte)0);
ReflectUtil.checkMandatoryFields(refItem,item,mandatoryFieldList);
​	使用ReflectUtil的mandatoryFieldList的方法,可以大大简化代码。
9.2.1.2、数据格式校验
​	某些对象的某些属性值,有数据格式要求,此时需要进行数据格式校验。如用户对象的用户名(登录名),手机号码,email等。这些数据格式校验,可以累计起来,开发工具方法,便于其它对象使用。
​ 如下面是登录名的格式校验方法,支持以字母开头,后续可以是字母、数字或"_.-@#%"特殊符号,不支持中文。此校验方法没有长度校验,由于各属性的长度要求不同,可以另行检查。
	/**
*
* @methodName : checkLoginName
* @description : 检查登录名格式是否正确,
* 格式:字母开头,可以支持字母、数字、以及"_.-@#%"6个特殊符号
* @param loginName : 登录名
* @return :
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/06/16 1.0.0 sheng.zheng 初版
*
*/
public static boolean checkLoginName(String loginName) {
String pattern = "^[a-zA-Z]([a-zA-Z0-9_.\\-@#%]*)$";
boolean bRet = Pattern.matches(pattern,loginName);
return bRet;
} /**
*
* @methodName : loginNameValidator
* @description : 登录名称格式校验,格式错误抛出异常
* @param propName : 登录名称的提示名称
* @param loginName : 登录名称
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/06/16 1.0.0 sheng.zheng 初版
*
*/
public static void loginNameValidator(String propName,String loginName) {
boolean bRet = checkLoginName(loginName);
if (!bRet) {
throw new BaseException(ExceptionCodes.DATA_FORMAT_WRONG, propName + ":" + loginName);
}
}
​	根据loginNameValidator核心是checkLoginName,但loginNameValidator方法针对错误,直接抛出异常,调用时代码可以更加简洁。类似的思想,适用于数据权限检查,参照ID检查,枚举值检查,唯一键检查等。
            // 用户名格式校验
ValidateUtil.loginNameValidator("userName", item.getUserName());
​	使用checkLoginName方法,则需要如下:
            // 用户名格式校验
if(!ValidateUtil.checkLoginName(item.getUserName())){
throw new BaseException(ExceptionCodes.DATA_FORMAT_WRONG, "userName:" + item.getUserName());
}

9.2.2、参照ID检查

​	如果对象有外键,即参照对象,则外键(ID)必须有意义,即参照对象是存在的。
​ 使用集中式的对象ID检查服务类IdCheckService,根据ID类型和ID值,获取对象。如果类型指定的ID值对象不存在,则抛出异常。
        // 检查参照ID的有效性
Integer orgId = item.getOrgId();
Orgnization orgnization = (Orgnization)ics.getObjById(EIdType.orgId,orgId);
​	至于IdCheckService中,根据ID获取对象方法,可以直接查询数据库,也可以通过缓存,这个取决于对象管理。

9.2.3、数据权限检查

​	如果对象涉及数据权限,则需要检查操作者是否有权新增此对象。
​ 使用数据权限管理类DataRightsService的方法,由于对一个确定的应用,数据权限相关的字段个数是有限的,可以针对单个字段开发接口,如orgId是数据权限字段,则可以提供checkUserDrByOrgId方法,代码如下:
	/**
*
* @methodName : checkUserDrByOrgId
* @description : 检查当前用户是否对输入的组织ID有数据权限
* @param request : request对象
* @param orgId : 组织ID
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/05/29 1.0.0 sheng.zheng 初版
*
*/
@SuppressWarnings("unchecked")
@Override
public void checkUserDrByOrgId(HttpServletRequest request,Integer orgId) {
boolean bRights = false; // 获取账号缓存信息
String accountId = accountCacheService.getId(request);
// 获取用户类型
Integer userType = (Integer)accountCacheService.getAttribute(accountId,Constants.USER_TYPE);
// 获取数据权限缓存信息
Map<String,UserDr> fieldDrMap = null;
fieldDrMap = (Map<String,UserDr>)accountCacheService.getAttribute(accountId, Constants.DR_MAP);
if (userType != null || fieldDrMap == null) {
if (userType == EUserType.utAdminE.getCode()) {
// 如果为系统管理员
bRights = true;
return;
}
}else {
// 如果属性不存在
throw new BaseException(ExceptionCodes.TOKEN_EXPIRED);
} // 获取数据权限
UserDr userDr = null;
bRights = true;
List<UserCustomDr> userCustomDrList = null;
String propName = "orgId"; // 获取用户对此fieldId的权限
userDr = fieldDrMap.get(propName);
if (userDr.getDrType().intValue() == EDataRightsType.drtAllE.getCode()) {
// 如果为全部,有权限
return;
}
if (userDr.getDrType().intValue() == EDataRightsType.drtDefaultE.getCode()) {
// 如果为默认权限,进一步检查下级对象
List<Integer> drList = getDefaultDrList(orgId,propName);
boolean bFound = drList.contains(orgId);
if (!bFound) {
bRights = false;
}
}else if (userDr.getDrType().intValue() == EDataRightsType.drtCustomE.getCode()){
// 如果为自定义数据权限
List<Integer> orgIdList = null;
if (userCustomDrList == null) {
// 如果自定义列表为空,则获取
Long userId = (Long)accountCacheService.getAttribute(accountId,Constants.USER_ID);
userCustomDrList = getUserCustomDrList(userId,propName);
orgIdList = getUserCustomFieldList(userCustomDrList,propName);
if (orgIdList != null) {
boolean bFound = orgIdList.contains(orgId);
if (!bFound) {
bRights = false;
}
}
}
}
if (bRights == false) {
throw new BaseException(ExceptionCodes.ACCESS_FORBIDDEN);
}
}
​	当前用户的数据权限配置信息,使用key为属性名的字典Map<String,UserDr>保存到各访问用户的账号缓存中。根据request对象,获取当前操作者的数据权限配置信息。然后根据配置类型,检查输入权限值是否在用户许可范围内,如果不在,就抛出异常。

​	还可以提供更通用的单属性数据权限检查接口方法。
	/**
*
* @methodName : checkUserDrByDrId
* @description : 检查当前用户是否对指定权限字典的输入值有数据权限
* @param request : request对象
* @param propName : 权限属性名
* @param drId : 权限属性值
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/05/29 1.0.0 sheng.zheng 初版
*
*/
public void checkUserDrByDrId(HttpServletRequest request,String propName,Object drId);
​	多属性数据权限检查接口方法。
	/**
*
* @methodName : checkDataRights
* @description : 检查当前用户是否对给定对象有数据权限
* @param request : request对象,可从中获取当前用户的缓存信息
* @param params : 权限属性名与值的字典
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/03/13 1.0.0 sheng.zheng 初版
*
*/
public void checkUserDr(HttpServletRequest request,Map<String,Object> params);

9.2.4、枚举值检查

​	枚举类型,对应的属性数据类型一般是Byte,数据库使用tinyint。新增对象时,枚举字段的值要在枚举类型定义范围中,否则会有问题。
        // 检查枚举值
int userType = item.getUserType().intValue();
EUserType eUserType = EUserType.getTypeByCode(userType);
int sex = item.getSex().intValue();
ESex eSex = ESex.getTypeByCode(sex);
​	相关枚举类型,都提供getTypeByCode方法,实现枚举值有效性校验。如:
	/**
*
* @methodName : getType
* @description : 根据code获取枚举值
* @param code : code值
* @return : code对应的枚举值
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
public static EUserType getType(int code) {
// 返回值变量
EUserType eRet = null; for (EUserType item : values()) {
// 遍历每个枚举值
if (code == item.getCode()) {
// code匹配
eRet = item;
break;
}
} return eRet;
} // 检查并获取指定code的枚举值
public static EUserType getTypeByCode(int code) {
EUserType item = getType(code);
if (item == null) {
throw new BaseException(ExceptionCodes.INVALID_ENUM_VALUE,"EUserType with code="+code);
} return item;
}

9.2.5、唯一性检查

​	如果对象属性值有唯一性要求,则需要进行唯一性检查。
        // 检查唯一性
String userName = item.getUserName();
String phoneNumber = item.getPhoneNumber();
String idNo = item.getIdNo();
String openId = item.getOpenId();
String woaOpenid = item.getWoaOpenid();
checkUniqueByUserName(userName);
checkUniqueByPhoneNumber(phoneNumber);
checkUniqueByIdNo(idNo);
checkUniqueByOpenId(openId);
checkUniqueByWoaOpenid(woaOpenid);
​	相关唯一性检查方法,如下(也可以改为public以对外提供服务):
	/**
*
* @methodName : checkUniqueByUserName
* @description : 检查userName属性值的唯一性
* @param userName : 用户名
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
private void checkUniqueByUserName(String userName) {
User item = userDao.selectItemByUserName(userName);
if (item != null) {
// 如果唯一键对象已存在
throw new BaseException(ExceptionCodes.OBJECT_ALREADY_EXISTS,"userName=" + userName);
}
} /**
*
* @methodName : checkUniqueByPhoneNumber
* @description : 检查phoneNumber属性值的唯一性
* @param phoneNumber : 手机号码
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
private void checkUniqueByPhoneNumber(String phoneNumber) {
if (phoneNumber.equals("")) {
// 如果为例外值
return;
} User item = userDao.selectItemByPhoneNumber(phoneNumber);
if (item != null) {
// 如果唯一键对象已存在
throw new BaseException(ExceptionCodes.OBJECT_ALREADY_EXISTS,
"phoneNumber=" + phoneNumber);
}
} /**
*
* @methodName : checkUniqueByIdNo
* @description : 检查idNo属性值的唯一性
* @param idNo : 身份证号码
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
private void checkUniqueByIdNo(String idNo) {
if (idNo.equals("")) {
// 如果为例外值
return;
} User item = userDao.selectItemByIdNo(idNo);
if (item != null) {
// 如果唯一键对象已存在
throw new BaseException(ExceptionCodes.OBJECT_ALREADY_EXISTS,"idNo=" + idNo);
}
} /**
*
* @methodName : checkUniqueByOpenId
* @description : 检查openId属性值的唯一性
* @param openId : 微信小程序的openid
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
private void checkUniqueByOpenId(String openId) {
if (openId.equals("")) {
// 如果为例外值
return;
} User item = userDao.selectItemByOpenId(openId);
if (item != null) {
// 如果唯一键对象已存在
throw new BaseException(ExceptionCodes.OBJECT_ALREADY_EXISTS,"openId=" + openId);
}
} /**
*
* @methodName : checkUniqueByWoaOpenid
* @description : 检查woaOpenid属性值的唯一性
* @param woaOpenid : 微信公众号openid
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2023/05/17 1.0.0 sheng.zheng 初版
*
*/
private void checkUniqueByWoaOpenid(String woaOpenid) {
if (woaOpenid.equals("")) {
// 如果为例外值
return;
} User item = userDao.selectItemByWoaOpenid(woaOpenid);
if (item != null) {
// 如果唯一键对象已存在
throw new BaseException(ExceptionCodes.OBJECT_ALREADY_EXISTS,"woaOpenid=" + woaOpenid);
}
}

9.2.6、业务处理

​	如果新增对象时,需要一些内部处理,则在此处进行。如新增用户时,需要根据当前时间生成盐,然后将管理员输入的明文密码转为加盐Md5签名密码。
        // 业务处理
LocalDateTime current = LocalDateTime.now();
String salt = TimeUtil.format(current, "yyyy-MM-dd HH:mm:ss");
// 明文密码加密
String password = item.getPassword();
String encyptPassword = Md5Util.plaintPasswdToDbPasswd(password, salt, Constants.TOKEN_KEY);
item.setSalt(salt);
item.setPassword(encyptPassword);

9.2.7、获取全局ID

​	为当前对象分配全局ID。
        Long userId = 0L;
// 获取全局记录ID
Long globalRecId = tccs.getTableRecId("exa_users");
userId = globalRecId;
​	全局ID的获取使用全局ID服务类对象TableCodeConfigService,其提供单个ID和批量ID的获取接口。
	/**
*
* @methodName : getTableRecId
* @description : 获取指定表名的一条记录ID
* @param tableName : 表名
* @return : 记录ID
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/01/01 1.0.0 sheng.zheng 初版
*
*/
@Override
public Long getTableRecId(String tableName) {
int tableId = getTableId(tableName);
Long recId = getGlobalIdDao.getTableRecId(tableId);
return recId;
} /**
*
* @methodName : getTableRecIds
* @description : 获取指定表名的多条记录ID
* @param tableName : 表名
* @param recCount : 记录条数
* @return : 第一条记录ID
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/01/01 1.0.0 sheng.zheng 初版
*
*/
@Override
public Long getTableRecIds(String tableName,int recCount) {
int tableId = getTableId(tableName);
Long recId = getGlobalIdDao.getTableRecIds(tableId,recCount);
return recId;
}
​	getTableId是根据数据表名称,获取表ID(这些表是相对固定的,可以使用缓存字典来管理)。而getGlobalIdDao的方法就是调用数据库的函数exa_get_global_id,获取可用ID。
	/**
*
* @methodName : getTableRecId
* @description : 获取表ID的一个记录ID
* @param tableId : 表ID
* @return : 记录ID
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/01/01 1.0.0 sheng.zheng 初版
*
*/
@Select("SELECT exa_get_global_id(#{tableId}, 1)")
Long getTableRecId(@Param("tableId") Integer tableId); /**
*
* @methodName : getTableRecIds
* @description : 获取表ID的多个记录ID
* @param tableId : 表ID
* @param count : ID个数
* @return : 开始的记录ID
* @history :
* ------------------------------------------------------------------------------
* date version modifier remarks
* ------------------------------------------------------------------------------
* 2021/01/01 1.0.0 sheng.zheng 初版
*
*/
@Select("SELECT exa_get_global_id(#{tableId}, #{count})")
Long getTableRecIds(@Param("tableId") Integer tableId, @Param("count") Integer count);

9.2.8、设置记录的用户账号信息

​	从request对象中获取账号信息,并设置对象。
		// 获取操作人账号
String operatorName = getUserName(request); // 设置信息
item.setUserId(userId);
item.setOperatorName(operatorName);

9.2.9、新增记录

​	调用Dao的insertItem方法,新增记录。
		try {
// 插入数据
userDao.insertItem(item); } catch(Exception e) {
LogUtil.error(e);
throw new BaseException(ExceptionCodes.ADD_OBJECT_FAILED,e.getMessage());
}

9.2.10、缓存处理

​	新增用户对象,不涉及缓存处理。
​ 如果有的对象,涉及全集加载,如组织树,则新增对象时,组织树也会变化。为了避免无效加载,使用修改标记来表示集合被修改,获取全集时,再进行加载。这样,连续新增对象时,不会有无效加载。缓存涉及全集加载的,新增对象需设置修改标记。
​ 如果缓存不涉及全集的,则无需处理。如字典类缓存,新增时不必将新对象加入缓存,获取时,根据机制,缓存中不存在,会先请求数据库,此时可以加载到缓存中。

9.2.11、返回值处理

​	新增对象,如果是系统生成的ID,需要将ID值返回。
		// 构造返回值
Map<String,Object> map = new HashMap<String,Object>();
map.put("userId", userId.toString()); return map;
​	对于Long类型,由于前端可能损失精度,因此使用字符串类型传递。

(未完待续...)

Spring Boot实现高质量的CRUD-5的更多相关文章

  1. Spring Boot整合Mybatis并完成CRUD操作

    MyBatis 是一款优秀的持久层框架,被各大互联网公司使用,本文使用Spring Boot整合Mybatis,并完成CRUD操作. 为什么要使用Mybatis?我们需要掌握Mybatis吗? 说的官 ...

  2. Spring boot 实现高吞吐量异步处理(适用于高并发场景)

    技术要点 org.springframework.web.context.request.async.DeferredResult<T> 示例如下: 1.   新建Maven项目  asy ...

  3. 从头开始搭建一个Spring boot+ActiveMQ高可用分布式环境

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  4. Spring Boot整合Mybatis完成级联一对多CRUD操作

    在关系型数据库中,随处可见表之间的连接,对级联的表进行增删改查也是程序员必备的基础技能.关于Spring Boot整合Mybatis在之前已经详细写过,不熟悉的可以回顾Spring Boot整合Myb ...

  5. Github点赞超多的Spring Boot学习教程+实战项目推荐!

    Github点赞接近 100k 的Spring Boot学习教程+实战项目推荐!   很明显的一个现象,除了一些老项目,现在 Java 后端项目基本都是基于 Spring Boot 进行开发,毕竟它这 ...

  6. Spring Boot不同版本整合Redis的配置

    1. Spring Boot为1.4及其他低版本 1.1 POM.XML配置 <!--引入 spring-boot-starter-redis(1.4版本前)--> <depende ...

  7. 【ELK】4.spring boot 2.X集成ES spring-data-ES 进行CRUD操作 完整版+kibana管理ES的index操作

    spring boot 2.X集成ES 进行CRUD操作  完整版 内容包括: ============================================================ ...

  8. 15套java架构师、集群、高可用、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战视频教程

    * { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩展. ...

  9. Spring boot(三)整合mybaties+thymeleaf实现基础crud

    工程结构: 首先在pom文件中引入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xml ...

  10. Spring Boot使用Spring Data Jpa对MySQL数据库进行CRUD操作

    只需两步!Eclipse+Maven快速构建第一个Spring Boot项目 构建了第一个Spring Boot项目. Spring Boot连接MySQL数据库 连接了MySQL数据库. 本文在之前 ...

随机推荐

  1. vue3 封装el-table时,构造$children(类式写法)

    由于业务需求(组件封装),需要在获取el-table下面的el-table-column实例 在 vue2.x 当中直接使用this.$children就可以获取到该实例 但是 vue3.x 弃用了$ ...

  2. 万字详解 | Java 流式编程

    概述 Stream API 是 Java 中引入的一种新的数据处理方法.它提供了一种高效且易于使用的方法来处理数据集合.Stream API 支持函数式编程,可以让我们以简洁.优雅的方式进行数据操作, ...

  3. 5.mapper出错原因

    1.总结:前个星期mapper出错,很大原因是自己的项目结构创建有问题,大项目下应该是spring init那种项目结构形式,但是在创建多模块的时候应该是使用moudle形式的项目结构: 所以自己在运 ...

  4. 四月二十四号java基础知识

    1.输入输出是指程序与外部设备或其他计算机进行交互的操作2.流(stream)是指计算机各部件之间的数据流动流的内容上划分:流分为字节流和字符流3.输入流:将数据从外设或外存(如键盘.鼠标.文件等)传 ...

  5. 面试某大厂,被Channel给吊打了,这次一次性通关channel!

    目录 一 前言 面试题 然后我们进行一下扩展,玩转Channel! 二 解决面试题 1. 介绍一下Channel 2. Channel在go中起什么作用 3. Channel为什么需要两个队列实现 4 ...

  6. Spring自定义参数解析器设计

    作者:京东零售 王鹏超 1.什么是参数解析器 @RequstBody.@RequstParam 这些注解是不是很熟悉? 我们在开发Controller接口时经常会用到此类参数注解,那这些注解的作用是什 ...

  7. C# 从0到实战--程序入门:基本程序结构·hello,world

    为什么要写博客 某人是一名大学生,到了大二,学院开始教授.Net,从这里我接触到了C#和ASP.Net,这些技术让我感到了想不到的快速开发之震撼.于是突发奇想,写此博客来记录我的学习路程.博客不仅仅是 ...

  8. A-O-P 一篇概览

    一.什么是AOP? AOP 即 Aspect-oriented Programming,Aspect 切面,什么是切面,就是一条大路上的收费站,检查站,首先它是一个统一的功能单元,或是收费.或是检查, ...

  9. vue前端路由的两种模式,hash与history的区别

    1.直观区别: hash模式url带#号,history模式不带#号. 2.深层区别: hash模式url里面永远带着#号,我们在开发当中默认使用这个模式. 如果用户考虑url的规范那么就需要使用hi ...

  10. Centos环境下部分中间件“rabbitmq、rocketmq、clickhouse”部署

    部分中间件部署 目录 部分中间件部署 docker部署rabbitmq docker部署rocketmq 单机部署clickhouse docker部署rabbitmq # 拉镜像 docker pu ...