java个人记录

 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、帆软报表使用接口接入

 

添加一个这个插件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值