1、代码记录
1.01、动态获取类里的键值对,判空
if (!filterUtil.filterFieldNotNull(req, "idCard", "idCardFirstImg", "idCardSecondImg", "email", "agencyCertificationNo", "agencyCertificationImg", "operationCertificationNo", "operationCertificationImg", "professionalField", "patentType")) {
throw new RuntimeException("存在必填字段值为空");
}
public boolean filterFieldNotNull(Object obj, String... excludeFieldNames) {
List<String> excludeFields = new ArrayList<>();
if (Objects.nonNull(excludeFieldNames) && excludeFieldNames.length > 0) {
excludeFields = Arrays.asList(excludeFieldNames);
}
Class<?> aClass = obj.getClass();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
try {
declaredField.setAccessible(true);
if (excludeFields.contains(declaredField.getName())) {
continue;
}
Object value = declaredField.get(obj);
if (value == null) {
log.info("字段:{},值为null", declaredField.getName());
return false;
}
Class<?> type = declaredField.getType();
if (type.equals(String.class) && StringUtils.isEmpty(String.valueOf(value))) {
log.info("字段:{},值为空字符串", declaredField.getName());
return false;
} else if (type.equals(List.class) && CollectionUtils.isEmpty(castList(value, Integer.class))) {
log.info("字段:{},值为空集合", declaredField.getName());
return false;
}
} catch (Exception e) {
log.error("", e);
throw new RuntimeException("系统错误!");
}
}
return true;
}
- 功能:校验对象中指定字段是否为空,并支持排除某些字段。
- 优点:
- 使用反射动态处理字段,适配性强。
- 支持灵活配置需要排除的字段。
- 适用场景:
- 用于快速验证对象的字段值完整性,尤其是在参数校验场景中。
- 潜在问题:
- 反射的性能相对较低,不适合高频调用。
- 如果字段比较复杂(嵌套对象),需要扩展逻辑进行递归校验。
1.02、三种分页的实现方式
@Configuration
@MapperScan(basePackages = "com.zhizai.tpa.sso.tpassorbac.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM));
return interceptor;
}
}
1.03、支持feigh塞cookie
import feign.Feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
* 支持feigh塞cookie
*/
@Slf4j
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignConfiguration {
// 这里会由容器自动注入HttpMessageConverters的对象工厂
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
/**
* new一个form编码器,实现支持form表单提交
*/
@Bean
Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> converters) {
return new SpringFormEncoder(new SpringEncoder(converters));
}
@Bean
public Logger logger() {
return new Logger() {
@Override
protected void log(String configKey, String format, Object... args) {
log.debug(String.format(methodTag(configKey) + format, args));
}
};
}
/**
* 配置忽略head内容,否则会导致服务器端报错
* see http://www.amoyiki.com/post/Spring-Cloud-4-Feign-slow.html
*/
private static Set<String> headNameList = new HashSet<String>() {
{
add("content-type");
add("content-length");
add("accept-encoding");
}};
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = requestAttributes.getRequest();
Enumeration<String> attributeNames = request.getHeaderNames();
if (attributeNames != null) {
while (attributeNames.hasMoreElements()) {
String name = attributeNames.nextElement();
String value = request.getHeader(name);
if (headNameList.contains(name)) {
continue;
}
requestTemplate.header(name,value);
}
}
}
};
}
}
1.04、数组返回优化
emptyList()
作用:返回一个空的List(使用前提是不会再对返回的list进行增加和删除操作);
好处:
1. new ArrayList()创建时有初始大小,占用内存,emptyList()不用创建一个新的对象,可以减少内存开销;
2. 方法返回一个emptyList()时,不会报空指针异常,如果直接返回Null,没有进行非空判断就会报空指针异常;
注意:此List与常用的List不同,它是Collections类里的静态内部类,在继承AbstractList后并没有实现add()、remove()等方法,所以返回的List不能进行增加和删除元素操作。
1.05、日志打印
1.06、导入导出Excel(含合并单元格)
导入Excel并回显json数据
/**
* 合同管理-合同专利服务明细表-Excel数据回显
*
* @param file excel文件
* @return
*/
@Operation(summary = "合同管理-合同专利服务明细表-Excel数据回显")
@PostMapping("/contract/patent-service/excel-template/data-echo")
public Response<List<ContractPatentServiceReq>> contractPatentServiceExcelDataEcho(@RequestParam("file") MultipartFile file) {
return ResponseUtil.createResponse(importManagementService.contractPatentServiceExcelDataEcho(file));
}
@Override
public List<ContractPatentServiceReq> contractPatentServiceExcelDataEcho(MultipartFile file) {
String errorPrefix = "合同专利excel导入-";
String filename = file.getOriginalFilename();
String excelSuffix = getExcelSuffix(filename);
String[] arrays = {"序号", "* 服务名称", "* 单价(元)", "账期", "支付节点", "支付方式(按比例/固定金额)", "支付比例", "支付金额(元)", "支付周期(天)"};
String excel2003 = "xls";
String excel2007 = "xlsx";
if (!excel2003.equals(excelSuffix) && !excel2007.equals(excelSuffix)) {
throw new RuntimeException(errorPrefix + "请上传正确的excel文件");
}
Workbook workbook = null;
InputStream inputStream = null;
List<ContractPatentServiceExcelVo> excelVos = new ArrayList<>();
try {
inputStream = file.getInputStream();
// 获取文件大小
long size = file.getSize(); //size = 49665018
// 208006015
// 定义 50MB 的大小限制
long maxSize = 50 * 1024 * 1024; // 50 MB
// 检查文件大小是否超过限制
if (size > maxSize) {
throw new RuntimeException("文件大小不能超过 50MB");
}
IOUtils.setByteArrayMaxOverride(300_000_000);
if (excel2003.equals(excelSuffix)) {
workbook = new HSSFWorkbook(inputStream);
}
if (excel2007.equals(excelSuffix)) {
workbook = new XSSFWorkbook(inputStream);
}
// 验证sheet
Sheet checkSheet = workbook.getSheetAt(0);
Row firstRow = checkSheet.getRow(0);
for (int i = 0; i < arrays.length; i++) {
String columnValue = arrays[i];
String cellValue = firstRow.getCell(i).getStringCellValue();
if (!columnValue.equals(cellValue)) {
//System.out.println("对比出错:" + columnValue + " 和 " + cellValue);
log.error("对比出错:" + columnValue + " 和 " + cellValue);
throw new RuntimeException(errorPrefix + "请上传正确的excel文件");
}
}
// 获取sheet
Sheet sheet = workbook.getSheetAt(0);
int rowCount = sheet.getLastRowNum(); // 获取总行数
// 合并区域处理,第一个合并区域值
// 获取合并区域
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
for (int i = 1; i <= rowCount; i++) { // 从第二行开始遍历每一行
// System.out.println("正在处理第"+i+"行");
ContractPatentServiceExcelVo excelVo = new ContractPatentServiceExcelVo();
Row row = sheet.getRow(i); // 获取第i行
// String serviceName = getStringByExcelCell(row.getCell(0)); // 序号
// 获取当前行服务名称(B列)
String serviceName = getServiceNameFromMergedCells(row, 1, mergedRegions);// 服务名称
//String serviceName = getStringByExcelCell(row.getCell(1)); // 服务名称
//Double price = row.getCell(2) != null ? row.getCell(2).getNumericCellValue() : null; // 单价(元)
// 获取当前行单价(C列)
Double price = getPriceFromMergedCells(row, 2, mergedRegions);
if (serviceName == null || price == null) {
throw new RuntimeException(errorPrefix + "第" + (i + 1) + "行'服务名称'或'单价(元)'不能为空,请检查");
}
String paymentDays = getStringByExcelCell(row.getCell(3)); // 账期
String processStep = getStringByExcelCell(row.getCell(4)); // 工序名称(中文)
String paymentMethod = row.getCell(5) != null ? row.getCell(5).getStringCellValue() : null; // 支付方式(0-百分比,1-固定)
Integer paymentRatio = row.getCell(6) != null ? (int) Math.floor(row.getCell(6).getNumericCellValue()) : null; // 支付比例(百分比)
Double paymentAmount = row.getCell(7) != null ? row.getCell(7).getNumericCellValue() : null; // 支付金额(元)
Integer paymentCycle = row.getCell(8) != null ? (int) Math.floor(row.getCell(8).getNumericCellValue()) : null; // 支付周期(天)
excelVo.setServiceName(serviceName);
excelVo.setPrice((long) Math.floor(price * 100));
excelVo.setPaymentDays(paymentDays);
excelVo.setProcessStep(processStep);
if (paymentMethod != null) {
excelVo.setPaymentMethod(paymentMethod.equals("按比例") ? 0 : 1);
}
excelVo.setPaymentRatio(paymentRatio);
excelVo.setPaymentAmount(paymentAmount != null ? (long) Math.floor((paymentAmount * 100)) : null);
excelVo.setPaymentCycle(paymentCycle);
excelVos.add(excelVo);
}
} catch (IOException e) {
log.error("{}从Excel获取数据失败:{}", errorPrefix, e.getMessage());
throw new RuntimeException("从Excel获取数据失败:" + e.getMessage());
} finally {
// 关闭资源
try {
if (workbook != null) {
workbook.close();
}
inputStream.close();
} catch (IOException e) {
log.error("关闭资源失败:{}", e.getMessage());
}
}
log.info("数据为:{}", excelVos);
if (excelVos.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<ContractPatentServiceExcelVo>> resultMap = new LinkedHashMap<>();
for (ContractPatentServiceExcelVo vo : excelVos) {
// 根据 serviceName 和 price 生成 key
String key = vo.getServiceName() + "SPLIT" + vo.getPrice();
// 如果 Map 中没有该 key,则创建一个新的 List
resultMap.putIfAbsent(key, new ArrayList<>());
// 获取该 key 对应的 List,并将当前 vo 添加进去
resultMap.get(key).add(vo);
}
List<ContractPatentServiceReq> resultList = new ArrayList<>();
resultMap.forEach((k, values) -> {
ContractPatentServiceReq req = new ContractPatentServiceReq();
String[] split = k.split("SPLIT");
req.setServiceName(split[0]);
req.setPrice(Long.valueOf(split[1]));
req.setContractPatentServiceExcelVos(values);
resultList.add(req);
});
return resultList;
}
private Double getPriceFromMergedCells(Row row, int columnIndex, List<CellRangeAddress> mergedRegions) {
// 获取合并单元格的价格(C列)
for (CellRangeAddress range : mergedRegions) {
if (range.isInRange(row.getRowNum(), columnIndex)) {
// 如果是合并单元格,返回合并单元格的第一个单元格的值
Cell cell = row.getSheet().getRow(range.getFirstRow()).getCell(range.getFirstColumn());
return getNumericValueByExcelCell(cell);
}
}
return getNumericValueByExcelCell(row.getCell(columnIndex));
}
private Double getNumericValueByExcelCell(Cell cell) {
return cell != null && cell.getCellType() == CellType.NUMERIC ? cell.getNumericCellValue() : null;
}
private String getServiceNameFromMergedCells(Row row, int columnIndex, List<CellRangeAddress> mergedRegions) {
// 获取合并单元格的值
for (CellRangeAddress range : mergedRegions) {
// 检查当前单元格是否在合并区域内
if (range.isInRange(row.getRowNum(), columnIndex)) {
// 如果是合并单元格,返回合并单元格的第一个单元格的值
Cell cell = row.getSheet().getRow(range.getFirstRow()).getCell(range.getFirstColumn());
return getStringByExcelCell(cell);
}
}
// 如果没有合并,直接获取当前单元格的值
return getStringByExcelCell(row.getCell(columnIndex));
}
重点代码:
private String getServiceNameFromMergedCells(Row row, int columnIndex, List<CellRangeAddress> mergedRegions) { // 获取合并单元格的值 for (CellRangeAddress range : mergedRegions) { // 检查当前单元格是否在合并区域内 if (range.isInRange(row.getRowNum(), columnIndex)) { // 如果是合并单元格,返回合并单元格的第一个单元格的值 Cell cell = row.getSheet().getRow(range.getFirstRow()).getCell(range.getFirstColumn()); return getStringByExcelCell(cell); } } // 如果没有合并,直接获取当前单元格的值 return getStringByExcelCell(row.getCell(columnIndex)); }
1.07、mybatis代码同时实现新增、更新与删除
// 更新合同类型
contractManagementVo.getContractTypes().forEach(type -> type.setContractManagementId(contractManagementEntity.getId()));
contractManagementTypeMapper.insertOrUpdate(contractManagementVo.getContractTypes());
contractManagementTypeMapper.delete(new QueryWrapper<ContractManagementTypeEntity>().lambda()
.eq(ContractManagementTypeEntity::getContractManagementId, contractManagementEntity.getId())
.notIn(ContractManagementTypeEntity::getId, contractManagementVo.getContractTypes().stream().map(ContractManagementTypeEntity::getId).collect(Collectors.toList())));
1.08、切面实现
1.08.1、记录接口调用情况
package xyz.zroid.ioc.engine.aop.operationlog;
import com.alibaba.fastjson2.JSON;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import xyz.zroid.ioc.engine.client.SystemClient;
import xyz.zroid.ioc.engine.common.OperationLog;
import xyz.zroid.ioc.engine.dto.response.EnginClientResponse;
import java.lang.reflect.Method;
@Component
@Aspect
@Slf4j
public class OperationLogAspect {
@Resource
private SystemClient systemClient;
@Pointcut("@annotation(xyz.zroid.ioc.engine.common.OperationLog)")
public void operationLogPointCut() {
}
@AfterReturning(pointcut = "operationLogPointCut()")
public void doAfterReturning(JoinPoint joinPoint){
try {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
OperationLog operationLog = method.getAnnotation(OperationLog.class);
String s = operationLog.operationMsg();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
EnginClientResponse<String> save = systemClient.save(operationLog.operationMsg(), request.getRemoteAddr());
if ( save.getCode() != 0) {
log.error("保存日志错误response:{}", JSON.toJSONString(save));
}
} catch (Exception e) {
log.error("保存日志错误:{}",e.getMessage());
e.printStackTrace();
}
}
}
package xyz.zroid.ioc.engine.common;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/**
* 操作信息
*/
String operationMsg() default "";
}
package xyz.zroid.ioc.engine.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 返回消息封装类
*
* @param <T>
* @author stegoshen
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class EnginClientResponse<T> {
private int code;
private String msg;
private T data;
}
1.08.2、权限校验
校验部分:
package com.zhizai.tpa.sso.reportflow.aspect;
import com.zhizai.tpa.sso.reportflow.client.TucServiceClient;
import com.zhizai.tpa.sso.reportflow.common.enums.VerifyDownLoadPermissions;
import com.zhizai.tpa.sso.reportflow.response.ClientResponse;
import com.zhizai.tpa.sso.reportflow.vo.ResourceVo;
import com.zhizai.tpa.sso.reportflow.vo.UserPrincipal;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Aspect
@Component
@Slf4j
public class PermissionCheckAspect {
@Resource
private TucServiceClient tucServiceClient;
private static Set<String> resourceInfoCode = new HashSet<>(Arrays.asList("download", "analysisPatentMatchingDegreeDownloadReport", "proposal_list_download_btn", "contractManagementDownload"));
@Around("@annotation(verifyDownLoadPermissions)")
public Object verifyDownLoadPermissions(ProceedingJoinPoint joinPoint, VerifyDownLoadPermissions verifyDownLoadPermissions) throws Throwable {
ClientResponse<UserPrincipal> selfUserInfo = getSelfUserInfo();
Set<ResourceVo> resources = selfUserInfo.getData().getResources();
// 判断 resources 里是否有任意一个 code 在 resourceInfoCode 里
boolean hasPermission = resources.stream()
.map(ResourceVo::getCode)
.anyMatch(resourceInfoCode::contains);
// 允许执行方法
if (hasPermission) {
return joinPoint.proceed();
} else {
throw new RuntimeException("权限不足,无法下载文件");
}
}
public ClientResponse<UserPrincipal> getSelfUserInfo() {
ClientResponse<UserPrincipal> response = tucServiceClient.selfDetail();
if (response.getCode() != 0 || (response.getMessage() != null && !response.getMessage().equals("操作成功"))) {
throw new RuntimeException("用户信息获取失败,请重新登录!");
}
return response;
}
}
package com.zhizai.tpa.sso.reportflow.common.enums;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VerifyDownLoadPermissions {
}
使用自定义注解@VerifyDownLoadPermissions()
/**
* 校验权限后获取文件
*
* @param folderName 文件夹名称
* @param fileName 文件名称
* @param effectiveMinutes 有效时间(分钟)默认30分钟
* @return
*/
@Operation(summary = "获取上传文件")
@GetMapping("/verify/get/file")
@VerifyDownLoadPermissions()
public void verifyGetFileByName(@RequestParam(value = "folderName", required = false) String folderName,
@RequestParam("fileName") String fileName,
@RequestParam(value = "effectiveMinutes", required = false) Integer effectiveMinutes,
HttpServletResponse response) throws
ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
// 设定额外的查询参数
Map<String, String> queryParams = new HashMap<>();
queryParams.put("response-content-disposition", "attachment; filename=\"" + fileName + "\"");
if (effectiveMinutes == null) {
effectiveMinutes = 30;
}
String minioFileName = fileName;
if (StringUtils.isNotBlank(folderName)) {
minioFileName = folderName + "/" + fileName;
}
// 从 MinIO 获取文件流
try (InputStream inputStream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(minioConfig.getBucketName() + "-" + profileConfig.getActiveProfile())
.object(minioFileName)
.build());
OutputStream outputStream = response.getOutputStream()) {
// 设置 HTTP 头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()));
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
// 以流的方式写入响应
IOUtils.copy(inputStream, outputStream);
outputStream.flush();
}
}
1.09、LocalDateTime获取当月最大日期
if (startDate.getMonth() == endDate.getMonth()) { startDate = startDate.minusMonths(1); endDate = endDate.minusMonths(1); // 获取当月最大日(注意:此毫秒为0!) endDate = endDate.withDayOfMonth(endDate.toLocalDate().lengthOfMonth()); }
1.10、list转化为map使用
// 使用stream按contractManagementId分组 Map<Long, ContractManagementFirstPartEntity> firstPartMap = firstPartEntities.stream() .collect(Collectors.toMap( ContractManagementFirstPartEntity::getContractManagementId, entity -> entity, // 如果有相同的key,保留已有的值或新值 (existing, replacement) -> existing // 这里选择保留已存在的值 ));
// 使用stream按contractManagementId分组为列表 Map<Long, List<ContractManagementFirstPartEntity>> firstPartMap = firstPartEntities.stream() .collect(Collectors.groupingBy( ContractManagementFirstPartEntity::getContractManagementId ));
1.11、根据当前时间和其他时间,获取秒
UserVo first = UserVoData.getFirst(); LocalDateTime createTime = first.getCreateTime(); long seconds = Duration.between(createTime, now).getSeconds();
1.12、spring security登录token自定义配置
/**
* 对登录信息封装,以便spring security进行认证,并在人称成功后生成token
*
* @param type
* @param account
* @param password
* @param rememberMe
* @param request
* @return
*/
private String doLogin(AuthenticationType type, String account, String password, boolean rememberMe, HttpServletRequest request, CustomerStopWatch stopWatch) {
AuthenticationVo authenticationVo = AuthenticationVo.builder().account(account).authenticationType(type).stopWatch(stopWatch).build();
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationVo, password));
SecurityContextHolder.getContext().setAuthentication(authentication);
return jwtUtil.createJWT(authentication, rememberMe);
}
package com.zhizai.tpa.sso.tpassorbac.businessservice.customsecurity;
import com.zhizai.tpa.sso.tpassorbac.businessservice.MsgCodeService;
import com.zhizai.tpa.sso.tpassorbac.businessservice.UserService;
import com.zhizai.tpa.sso.tpassorbac.common.AuthenticationType;
import com.zhizai.tpa.sso.tpassorbac.common.ResultErrorMsg;
import com.zhizai.tpa.sso.tpassorbac.entity.UserInfo;
import com.zhizai.tpa.sso.tpassorbac.exception.CustomerException;
import com.zhizai.tpa.sso.tpassorbac.util.CustomerStopWatch;
import com.zhizai.tpa.sso.tpassorbac.util.CacheKeyPrefixUtil;
import com.zhizai.tpa.sso.tpassorbac.util.PasswordUtil;
import com.zhizai.tpa.sso.tpassorbac.vo.AuthenticationVo;
import com.zhizai.tpa.sso.tpassorbac.vo.UserPrincipal;
import io.jsonwebtoken.lang.Collections;
import lombok.SneakyThrows;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @Editor zjhan
* @Date 2021/12/27 21:22
* @Description 自定义认证器
* <p>
* 1. 认证用户是否为有效用户
* 2. 每天验证失败次数限制
* 3. 获取认证用户权限信息
* 4. 获取认证用户角色信息
* 5. 将权限,角色,用户信息封装并存储到redis中
*/
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
private UserService userService;
private MsgCodeService msgCodeService;
private CacheKeyPrefixUtil cacheKeyPrefixUtil;
private RedissonClient client;
@Value("${spring.privateKey}")
private String privateKey;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired
public void setMsgCodeService(MsgCodeService msgCodeService) {
this.msgCodeService = msgCodeService;
}
@Autowired
public void setRedisKeyPrefixUtil(CacheKeyPrefixUtil cacheKeyPrefixUtil) {
this.cacheKeyPrefixUtil = cacheKeyPrefixUtil;
}
@Autowired
public void setClient(RedissonClient client) {
this.client = client;
}
/**
* 认证用户
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@SneakyThrows
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取用户信息
AuthenticationVo authenticationVo = (AuthenticationVo) authentication.getPrincipal();
CustomerStopWatch stopWatch = authenticationVo.getStopWatch();
stopWatch.start("check login failed times");
// 获取用户账号
String account = authenticationVo.getAccount();
long failedTimes = client.getAtomicLong(cacheKeyPrefixUtil.getLoginFailedTimesKey(account)).get();
if (failedTimes > 5) {
throw new CustomerException(ResultErrorMsg.INCORRECT_OVER);
}
// 获取登录方式:手机密码/验证码/域账号
AuthenticationType authenticationType = authenticationVo.getAuthenticationType();
// 获取密码/验证码
String password = (String) authentication.getCredentials();
stopWatch.stop();
switch (authenticationType) {
case CODE:
return loginByCode(failedTimes, authentication, account, password, stopWatch);
case PHONE:
return loginByPhone(failedTimes, authentication, account, password, stopWatch);
case ACCOUNT_ENCRYPT:
return loginByAccount(failedTimes, authentication, account, password, stopWatch, true);
case PERSON_LOGIN:
return personLogin(failedTimes, authentication, account, stopWatch);
default:
return loginByAccount(failedTimes, authentication, account, password, stopWatch, false);
}
}
/**
* 根据账号登陆
*
* @param failedTimes
* @param authentication
* @param username 账号
* @param password 密码
* @return
*/
private UsernamePasswordAuthenticationToken loginByAccount(long failedTimes, Authentication authentication,
String username, String password, CustomerStopWatch stopWatch,
boolean usingEncrypt) throws Exception {
// 加载用户
stopWatch.start("load user from db");
UserInfo userInfoDB = loadUserByUserAccount(username);
stopWatch.stop();
stopWatch.start("check user");
if (userInfoDB == null) {
throw new CustomerException(ResultErrorMsg.ACCOUNT_NOT_EXIST);
}
String passwordAfterDecrypt = password;
if (usingEncrypt) {
passwordAfterDecrypt = PasswordUtil.decryptRSA(password, privateKey);
}
if (userService.matchesPassword(passwordAfterDecrypt, userInfoDB.getPassword())) {
stopWatch.stop();
stopWatch.start("load user detail");
UserPrincipal userPrincipal = dealMatchPwdTrue(username, failedTimes, userInfoDB);
stopWatch.stop();
return new UsernamePasswordAuthenticationToken(userPrincipal, authentication.getCredentials(), userPrincipal.getAuthorities());
} else {
long retryTimes = client.getAtomicLong(cacheKeyPrefixUtil.getLoginFailedTimesKey(username)).incrementAndGet();
client.getAtomicLong(cacheKeyPrefixUtil.getLoginFailedTimesKey(username)).expire(24, TimeUnit.HOURS);
stopWatch.stop();
throw new BadCredentialsException("账号密码错误,请重试");
// throw new BadCredentialsException("密码错误,已尝试" + retryTimes + "次");
}
}
/**
* 根据手机验证码登录
*
* @param authentication
* @param phone
* @param code
* @return
*/
private UsernamePasswordAuthenticationToken loginByCode(long failedTimes, Authentication authentication, String phone,
String code, CustomerStopWatch stopWatch) throws CustomerException, ParseException {
stopWatch.start("check code");
boolean isMatch = msgCodeService.validateMsgCode(phone, code);
// 验证码不匹配,直接返回
if (!isMatch) {
long retryTimes = client.getAtomicLong(cacheKeyPrefixUtil.getLoginFailedTimesKey(phone)).incrementAndGet();
client.getAtomicLong(cacheKeyPrefixUtil.getLoginFailedTimesKey(phone)).expire(24, TimeUnit.HOURS);
stopWatch.stop();
throw new BadCredentialsException("验证码错误,已尝试" + retryTimes + "次");
}
stopWatch.stop();
stopWatch.start("load user from db");
UserInfo userInfo = loadUserByUserPhone(phone);
stopWatch.stop();
stopWatch.start("check user");
if (Objects.isNull(userInfo)) {
stopWatch.stop();
throw new CustomerException(ResultErrorMsg.USER_NOT_REGISTER);
}
logger.info("mobile msgCode login success : user_name is={},mobile ={} ,msgCode ={}",
userInfo.getUserName(), phone, code);
stopWatch.stop();
stopWatch.start("load user detail");
UserPrincipal userPrincipal = dealMatchPwdTrue(phone, failedTimes, userInfo);
stopWatch.stop();
return new UsernamePasswordAuthenticationToken(userPrincipal, authentication.getCredentials(), userPrincipal.getAuthorities());
}
/**
* 根据手机密码登录
*
* @param failedTimes
* @param authentication
* @param phone
* @param password
* @return
*/
private UsernamePasswordAuthenticationToken loginByPhone(long failedTimes, Authentication authentication, String phone,
String password, CustomerStopWatch stopWatch) throws Exception {
stopWatch.start("load user from db");
UserInfo userInfo = loadUserByUserPhone(phone);
stopWatch.stop();
stopWatch.start("check user");
if (Objects.isNull(userInfo)) {
stopWatch.stop();
throw new CustomerException(ResultErrorMsg.ACCOUNT_NOT_EXIST);
}
boolean isPwdCorrect = userService.matchesPassword(password, userInfo.getPassword());
stopWatch.stop();
stopWatch.start("load user detail");
//若密码正确
if (isPwdCorrect) {
UserPrincipal userDetails = dealMatchPwdTrue(phone, failedTimes, userInfo);
stopWatch.stop();
return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities());
} else {
long retryTimes = client.getAtomicLong(cacheKeyPrefixUtil.getLoginFailedTimesKey(phone)).incrementAndGet();
client.getAtomicLong(cacheKeyPrefixUtil.getLoginFailedTimesKey(phone)).expire(24, TimeUnit.HOURS);
stopWatch.stop();
throw new BadCredentialsException("账号密码错误,请重试");
// throw new BadCredentialsException("密码错误,已尝试" + retryTimes + "次");
}
}
/**
* 从数据库加载用户信息
*
* @param username
* @return
*/
private UserInfo loadUserByUserAccount(String username) {
return userService.getByUserAccount(username);
}
private UserInfo loadUserByUserPhone(String phone) {
return userService.getOneByMobile(phone);
}
private void checkUserDetail(UserPrincipal principal) throws CustomerException {
switch (principal.getUserStats()) {
case 2:
throw new CustomerException(ResultErrorMsg.USER_IS_DISABLED);
default:
}
// if (Collections.isEmpty(principal.getResources())) {
// throw new CustomerException(ResultErrorMsg.USER_NO_PERMISSION);
// }
}
private UserPrincipal dealMatchPwdTrue(String account, long failedTimes, UserInfo userInfo) throws CustomerException, ParseException {
userService.logOut(userInfo.getId());
UserPrincipal userDetails = userService.getUserById(userInfo.getId(), true);
checkUserDetail(userDetails);
userService.login(userInfo.getId());
if (failedTimes > 0) {
client.getKeys().delete(cacheKeyPrefixUtil.getLoginFailedTimesKey(account));
}
return userDetails;
}
/**
* 根据账号登陆
*
* @param failedTimes
* @param authentication
* @param username 账号
* @return
*/
private UsernamePasswordAuthenticationToken personLogin(long failedTimes, Authentication authentication,
String username, CustomerStopWatch stopWatch) throws Exception {
// 加载用户
stopWatch.start("load user from db");
UserInfo userInfoDB = loadUserByUserAccount(username);
stopWatch.stop();
stopWatch.start("check user");
if (userInfoDB == null) {
throw new CustomerException(ResultErrorMsg.ACCOUNT_NOT_EXIST);
}
stopWatch.stop();
stopWatch.start("load user detail");
UserPrincipal userPrincipal = dealMatchPwdTrue(username, failedTimes, userInfoDB);
stopWatch.stop();
return new UsernamePasswordAuthenticationToken(userPrincipal, authentication.getCredentials(), userPrincipal.getAuthorities());
}
/**
* 是否启用
*
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
/**
* 根据账号登陆
*
* @param failedTimes
* @param authentication
* @param username 账号
* @return
*/
private UsernamePasswordAuthenticationToken personLogin(long failedTimes, Authentication authentication,
String username, CustomerStopWatch stopWatch) throws Exception {
// 加载用户
stopWatch.start("load user from db");
UserInfo userInfoDB = loadUserByUserAccount(username);
stopWatch.stop();
stopWatch.start("check user");
if (userInfoDB == null) {
throw new CustomerException(ResultErrorMsg.ACCOUNT_NOT_EXIST);
}
stopWatch.stop();
stopWatch.start("load user detail");
UserPrincipal userPrincipal = dealMatchPwdTrue(username, failedTimes, userInfoDB);
stopWatch.stop();
return new UsernamePasswordAuthenticationToken(userPrincipal, authentication.getCredentials(), userPrincipal.getAuthorities());
}
1.13、map使用
List<Long> existPatentAdministrationIds = update.stream().map(PatentAdministrationEntity::getId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
Map<Long, ApplyAndSupplierDTO> supplierDtoMap = applyBySupplierIds.getData().stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(
ApplyAndSupplierDTO::getSupplierId, // Key提取函数
dto -> dto, // Value提取函数
(existing, replacement) -> existing // 处理重复key(保留已有值)
));
1.14、数据切割
List<Long> ids = list.stream().map(CompanyOptionVo::getId).collect(Collectors.toList());
//分批查询
List<List<Long>> partition = Lists.partition(ids, 20);
List<Long> belongApplicantIds = new ArrayList<>();
for (List<Long> idList : partition) {
List<SupplierInfo> supplierInfoList = supplierInfoService.getSupplierByBelongApplicantId(idList);
if (!CollectionUtils.isEmpty(supplierInfoList)) {
belongApplicantIds.addAll(supplierInfoList.stream().map(SupplierInfo::getBelongApplicantId).collect(Collectors.toList()));
}
}
2、sql记录
2.1、查看是否有重复值
select dm.module_code ,count(1) from dim_module dm group by dm.module_code having count(1)>1;
2.2、sql批量插入
<insert id="batchInsert" parameterType="java.util.List">
insert into USER (id, name) values
<foreach collection="list" item="model" index="index" separator=",">
(#{model.id}, #{model.name})
</foreach>
</insert>建议20-50的插入数据(未验证)
当需要大批量插入时,可考虑修改为:
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
SimpleTableMapper mapper = session.getMapper(SimpleTableMapper.class);
List<SimpleTableRecord> records = getRecordsToInsert(); // not shown
BatchInsert<SimpleTableRecord> batchInsert = insert(records)
.into(simpleTable)
.map(id).toProperty("id")
.map(firstName).toProperty("firstName")
.map(lastName).toProperty("lastName")
.map(birthDate).toProperty("birthDate")
.map(employed).toProperty("employed")
.map(occupation).toProperty("occupation")
.build()
.render(RenderingStrategy.MYBATIS3);
batchInsert.insertStatements().stream().forEach(mapper::insert);
session.commit();
} finally {
session.close();
}
2.3、原生mybatis写法
2.3.1、批量新增
<insert id="insert" parameterType="java.util.List">
INSERT INTO sa_task_juhe_data_detail_sync (source_of_income, money, sa_task_juhe_data_id)
VALUES
<foreach collection="pos" item="item" separator=",">
( #{item.sourceOfIncome}, #{item.money}, #{item.saTaskJuheDataId})
</foreach>
</insert>
2.3.2、批量新增与修改
因为是id为主键,所以里面必须有id,不然会更新无效
<update id="insertOrUpdateBatch" keyProperty="id" useGeneratedKeys="true">
INSERT INTO sa_task_juhe_data_detail_sync (id, source_of_income, money, sa_task_juhe_data_id)
values
<foreach collection="vos" item="vo" separator=",">
(#{vo.id}, #{vo.sourceOfIncome}, #{vo.money}, #{vo.saTaskJuheDataId})
</foreach>
on duplicate key update
id = values(id),
source_of_income = values(source_of_income),
money = values(money),
sa_task_juhe_data_id = values(sa_task_juhe_data_id)
</update>
2.3.3、批量删除
<delete id="deletedByIds">
DELETE FROM sa_task_juhe_data_detail_sync
WHERE id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
2.3.4、分页查询
<sql id="Vo_Column_List">
id as id,
create_time as createTime,
delete_time as deleteTime,
delete_id as deleteId,
deleted as deleted,
specify_update_time as specifyUpdateTime,
update_time as updateTime,
update_id as updateId,
create_id as createId
</sql>
<select id="searchPage" resultType="com.tengyun.model.po.SaTaskJuheDataSyncPo">
select
<include refid="Vo_Column_List"/>
from sa_task_juhe_data_sync
<!-- <if test="query.dataSource != null and query.dataSource != ''">-->
<!-- and data_source = #{query.dataSource}-->
<!-- </if>-->
order by specify_update_time desc
<bind name="offset" value="(query.pageNum-1)*query.pageSize"/>
limit #{offset}, #{query.pageSize}
</select>
3、redis
3.1、获取自增id,并设置过期时间
/**
* 根据模块年月日生成唯一编号
* @return 12位数字ID
*/
public String generateNumericByModule(String moduleNum) {
String keyWord = moduleNum + YEAR_MONTH_DAY_DATE_FORMAT.format(new Date());
Long serialNumbers = redisUtils.incr(keyWord);
// 设置过期时间,一天后自动销毁
redisUtils.expire(keyWord, 1, TimeUnit.DAYS);
return keyWord + "" + String.format("%0" + 4 + "d", serialNumbers);
}
3.2、linux服务登录redis并查看
redis是否在运行
ps aux | grep redis
找到bin目录下的redis-cli,输入下面命令连接
./redis-cli -h 127.0.0.1 -p 6379
auth 密码
清除全部key
flushall
查看全部key
keys *
4、服务器
4.1、启动nacos
cd /usr/local/nacos/bin/
单机启动命令
sh startup.sh -m standalone
10、帆软报表使用接口接入
添加一个这个插件