package com.engine.salary.service.impl;
import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
import com.engine.salary.entity.salaryformula.ExpressFormula;
import com.engine.salary.entity.salaryformula.param.SalaryFormulaMockParam;
import com.engine.salary.entity.salaryformula.param.SalaryFormulaSaveParam;
import com.engine.salary.entity.salaryformula.po.FormulaPO;
import com.engine.salary.entity.salaryformula.po.FormulaVar;
import com.engine.salary.enums.salaryformula.ReferenceTypeEnum;
import com.engine.salary.enums.salaryformula.ReturnTypeEnum;
import com.engine.salary.enums.salaryformula.ValidateTypeEnum;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.mapper.formula.FormulaMapper;
import com.engine.salary.mapper.formula.FormulaVarMapper;
import com.engine.salary.service.FormulaRunService;
import com.engine.salary.service.RemoteExcelService;
import com.engine.salary.service.SalaryFormulaService;
import com.engine.salary.util.db.MapperProxyFactory;
import com.engine.salary.util.valid.ValidUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import dm.jdbc.util.IdGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.BeanUtils;
import weaver.hrm.User;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 薪酬管理公式编辑器
*
Copyright: Copyright (c) 2022
* Company: 泛微软件
*
* @author qiantao
* @version 1.0
**/
@Slf4j
public class SalaryFormulaServiceImpl extends Service implements SalaryFormulaService {
private static final ObjectMapper objectMapper = new ObjectMapper();
private FormulaMapper getFormulaMapper() {
return MapperProxyFactory.getProxy(FormulaMapper.class);
}
private FormulaVarMapper getFormulaVarMapper() {
return MapperProxyFactory.getProxy(FormulaVarMapper.class);
}
private FormulaRunService getFormulaRunService(User user) {
return (FormulaRunService) ServiceUtil.getService(FormulaRunServiceImpl.class, user);
}
private RemoteExcelService getRemoteExcelService(User user) {
return ServiceUtil.getService(RemoteExcelServiceImpl.class, user);
}
@Override
public List listExpressFormula(Collection formulaIds) {
formulaIds = formulaIds.stream().filter(id -> id != null && id > 0).collect(Collectors.toList());
if (CollectionUtils.isEmpty(formulaIds)) {
return Collections.emptyList();
}
try {
// 当前租户自己新建的公式
List expressFormulas = getFormulaMapper().listByIds(Lists.newArrayList(formulaIds));
return expressFormulas.stream().filter(Objects::nonNull).map(m -> {
ExpressFormula expressFormula = new ExpressFormula();
BeanUtils.copyProperties(m, expressFormula);
List formulaVarPOS = getFormulaVarMapper().listSome(FormulaVar.builder().formulaId(m.getId()).build());
expressFormula.setParameters(formulaVarPOS);
return expressFormula;
}).collect(Collectors.toList());
} catch (Exception e) {
log.info("获取公示详情失败", e);
throw new SalaryRunTimeException("获取公示详情失败");
}
}
@Override
public ExpressFormula getExpressFormula(Long formulaId) {
if (formulaId == null || formulaId <= 0) {
return null;
}
try {
// 当前租户自己新建的公式
FormulaPO formulaPO = getFormulaMapper().getById(formulaId);
ExpressFormula expressFormula = new ExpressFormula();
BeanUtils.copyProperties(formulaPO, expressFormula);
List formulaVarPOS = getFormulaVarMapper().listSome(FormulaVar.builder().formulaId(formulaId).build());
expressFormula.setParameters(formulaVarPOS);
return expressFormula;
} catch (Exception e) {
log.info("获取公示详情失败", e);
throw new SalaryRunTimeException("获取公示详情失败");
}
}
@Override
public FormulaPO save(SalaryFormulaSaveParam param) {
ValidUtil.doValidator(param);
if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == null) {
throw new SalaryRunTimeException("引用类型异常");
}
if (ReturnTypeEnum.parseByValue(param.getReturnType()) == null) {
throw new SalaryRunTimeException("返回类型异常");
}
if (ValidateTypeEnum.parseByValue(param.getValidateType()) == null) {
throw new SalaryRunTimeException("校验类型异常");
}
List parameters = param.getParameters();
//防止参数名和字段名呈现一对多的问题
if (CollectionUtils.isNotEmpty(parameters)) {
List notRepeatingNameSize = parameters.stream().map(FormulaVar::getFieldName).distinct().collect(Collectors.toList());
if (notRepeatingNameSize.size() < parameters.size()) {
throw new SalaryRunTimeException("公式参数配置异常!参数名称重复,请清空公式内容后,再重新打开编辑");
}
}
String extendParam = param.getExtendParam();
if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.SQL) {
if (extendParam == null) {
throw new SalaryRunTimeException("未设置SQL返回值");
}
String sqlReturnKey = "";
try {
JsonNode jsonNode = objectMapper.readTree(extendParam);
JsonNode sqlReturnKeyNode = jsonNode.get("sqlReturnKey");
if (sqlReturnKeyNode != null) {
sqlReturnKey = sqlReturnKeyNode.asText();
}
} catch (JsonProcessingException e) {
log.error("express execute fail, sql extendParam parse fail", e);
}
if (StringUtils.isBlank(sqlReturnKey)) {
throw new SalaryRunTimeException("未设置SQL返回值");
}
//将select因XSS过滤造成的异常字符转换回来
param.setFormula(param.getFormula().replaceAll("select", "select"));
param.setFormula(param.getFormula().replaceAll("SELECT", "SELECT"));
param.setFormula(param.getFormula().replaceAll("join", "join"));
param.setFormula(param.getFormula().replaceAll("JOIN", "JOIN"));
param.setFormula(param.getFormula().replaceAll("and", "and"));
param.setFormula(param.getFormula().replaceAll("AND", "AND"));
param.setFormula(param.getFormula().replaceAll("or", "or"));
param.setFormula(param.getFormula().replaceAll("OR", "OR"));
param.setFormula(param.getFormula().replaceAll("in", "in"));
param.setFormula(param.getFormula().replaceAll("IN", "IN"));
param.setFormula(param.getFormula().replaceAll("like", "like"));
param.setFormula(param.getFormula().replaceAll("LIKE", "like"));
param.setFormula(param.getFormula().replaceAll("exists", "exists"));
param.setFormula(param.getFormula().replaceAll("EXISTS", "EXISTS"));
param.setFormula(param.getFormula().replaceAll("between", "between"));
param.setFormula(param.getFormula().replaceAll("BETWEEN", "BETWEEN"));
}
// 解析公式中的参数
if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.FORMULA) {
List parameterList = parseFormulaParameters(param.getFormula(), ReferenceTypeEnum.FORMULA);
param.setParameters(parameterList);
}else if(ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.SQL){
List parameterList = parseFormulaParameters(param.getFormula(), ReferenceTypeEnum.SQL);
param.setParameters(parameterList);
}
//试运行公式
checkRun(param);
FormulaPO formulaPO = new FormulaPO();
String formula = param.getFormula();
long formulaId = IdGenerator.generate();
formulaPO.setId(formulaId);
formulaPO.setName(param.getName());
formulaPO.setDescription(param.getDescription());
formulaPO.setModule(param.getModule());
formulaPO.setUseFor(param.getUseFor());
formulaPO.setReferenceType(param.getReferenceType());
formulaPO.setReturnType(param.getReturnType());
formulaPO.setValidateType(param.getValidateType());
formulaPO.setExtendParam(extendParam);
formulaPO.setFormula(formula);
formulaPO.setDeleteType(NumberUtils.INTEGER_ZERO);
Date now = new Date();
formulaPO.setCreateTime(now);
formulaPO.setUpdateTime(now);
formulaPO.setCreator((long) user.getUID());
/*
公式内容以如下显示方式存储
{薪资项目.输入项1}+{薪资项目.输入项2}
转换为实际的运行脚本
*/
String formulaRunScript = formula;
for (FormulaVar po : parameters) {
formulaRunScript = formulaRunScript.replace(po.getFieldName(), po.getFieldId());
}
formulaPO.setFormulaRunScript(formulaRunScript);
getFormulaMapper().insertIgnoreNull(formulaPO);
for (FormulaVar po : parameters) {
po.setId(IdGenerator.generate());
po.setFormulaId(formulaId);
po.setDeleteType(NumberUtils.INTEGER_ZERO);
po.setCreator((long) user.getUID());
po.setCreateTime(now);
po.setUpdateTime(now);
getFormulaVarMapper().insertIgnoreNull(po);
}
return formulaPO;
}
public List parseFormulaParameters(String formula, ReferenceTypeEnum referenceTypeEnum){
List parameters = new ArrayList<>();
// 获取公式中所有可选的变量项目
Map> allParameters = getRemoteExcelService(user).allFieldList(referenceTypeEnum);
List groups = new ArrayList<>(allParameters.keySet());
StringBuilder reg =new StringBuilder("(");
for(int i =0; i < groups.size(); i++ ){
reg.append("\\{"+ groups.get(i) +"\\.");
if(i+1 != groups.size()){
reg.append("|");
}
}
reg.append(").*?}");
// 提取公式中所有的变量
Pattern pattern = Pattern.compile(reg.toString());
Matcher matcher = pattern.matcher(formula);
List vars = new ArrayList<>();
while (matcher.find()) {
String var = matcher.group(0).replaceAll("\\{", "").replaceAll("}", "");
if(StringUtils.isNotBlank(var)){
vars.add(var);
}
}
for(String var : vars){
String[] split = var.split("\\.");
if(split.length==2){
List formulaVars = allParameters.get(split[0]);
if(formulaVars == null){
throw new SalaryRunTimeException("保存失败,公式变量"+split[0]+"输入有误!");
}
Optional field = formulaVars.stream().filter(v -> Objects.equals(v.getName(), split[1])).findFirst();
if(field.isPresent()){
FormulaVar formulaVar = field.get();
String fieldName = "{" + split[0] + "." + split[1] +"}";
formulaVar.setFieldName(fieldName);
parameters.add(formulaVar);
}else{
throw new SalaryRunTimeException("保存失败,公式变量"+split[0]+split[1]+"输入有误!");
}
}else if(split.length > 2){
// 变量名称中包含.
List formulaVars = allParameters.get(split[0]);
if(formulaVars == null){
throw new SalaryRunTimeException("保存失败,公式变量"+split[0]+"输入有误!");
}
StringBuilder field = new StringBuilder("");
for(int i =1; i< split.length; i++){
if(i != 1){
field.append(".");
}
field = field.append(split[i]);
}
String finalField = field.toString();
Optional optional = formulaVars.stream().filter(v -> Objects.equals(v.getName(), finalField)).findFirst();
if(optional.isPresent()){
FormulaVar formulaVar = optional.get();
String fieldName = "{" + split[0] + "." + field +"}";
formulaVar.setFieldName(fieldName);
parameters.add(optional.get());
}else{
throw new SalaryRunTimeException("保存失败,公式变量"+split[0]+split[1]+"输入有误!");
}
}
}
return parameters;
}
/**
* 模拟运行公式
*
* @param param
*/
private void checkRun(SalaryFormulaSaveParam param) {
//返回类型
String returnType = param.getReturnType();
ReturnTypeEnum returnTypeEnum = ReturnTypeEnum.parseByValue(returnType);
/*
公式内容以如下显示方式存储
{薪资项目.输入项1}+{薪资项目.输入项2}
转换为实际的运行脚本
*/
String formulaRunScript = param.getFormula();
List parameters = param.getParameters();
for (int i = 0; i < parameters.size(); i++) {
FormulaVar po = parameters.get(i);
formulaRunScript = formulaRunScript.replace(po.getFieldName(), po.getFieldId());
po.setContent(String.valueOf(i + 1));
}
//验证公式是否可运行
if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.FORMULA) {
ExpressFormula test = ExpressFormula.builder().formulaRunScript(formulaRunScript).extendParam(param.getExtendParam()).referenceType(param.getReferenceType()).build();
Object run = null;
try {
run = getFormulaRunService(user).run(test, parameters, DataCollectionEmployee.builder().employeeId((long) user.getUID()).build());
} catch (Exception e) {
log.error("express execute fail ", e);
throw new SalaryRunTimeException("公式模拟运行出错,请检查公式配置!", e);
}
if (run != null && StringUtils.isNotBlank(String.valueOf(run)) && returnTypeEnum == ReturnTypeEnum.NUMBER) {
//返回结果不是数字
if (!NumberUtils.isCreatable(String.valueOf(run))) {
throw new SalaryRunTimeException("返回结果不是数值");
}
}
}
if (ReferenceTypeEnum.parseByValue(param.getReferenceType()) == ReferenceTypeEnum.SQL) {
if (formulaRunScript.contains(";") || formulaRunScript.contains("--")) {
throw new SalaryRunTimeException("SQL配置异常,请去除';'或者'--'");
}
}
}
@Override
public Object mock(SalaryFormulaMockParam param) {
ValidUtil.doValidator(param);
FormulaPO po = getFormulaMapper().getById(param.getId());
ReturnTypeEnum returnTypeEnum = ReturnTypeEnum.parseByValue(po.getReturnType());
//验证公式是否可运行
ExpressFormula test = ExpressFormula.builder().formulaRunScript(po.getFormulaRunScript()).extendParam(po.getExtendParam()).referenceType(po.getReferenceType()).build();
Object run = null;
try {
run = getFormulaRunService(user).run(test, param.getParameters(), DataCollectionEmployee.builder().employeeId((long) user.getUID()).build());
} catch (Exception e) {
log.error("express execute fail ", e);
throw new SalaryRunTimeException("公式测试运行出错,请检查公式配置!", e);
}
if (run != null && StringUtils.isNotBlank(String.valueOf(run)) && returnTypeEnum == ReturnTypeEnum.NUMBER) {
//返回结果不是数字
if (!NumberUtils.isCreatable(String.valueOf(run))) {
throw new SalaryRunTimeException("返回结果不是数值");
}
}
return run;
}
@Override
public void initFunction() {
}
@Override
public void deleteNotIn(List formulaIds) {
Date today = new Date();
Calendar c = Calendar.getInstance();
c.setTime(today);
c.add(Calendar.DAY_OF_MONTH, -1);
Date yesterday = c.getTime();
if (CollectionUtils.isNotEmpty(formulaIds)) {
List allFormula = getFormulaMapper().listAll();
//待删除的公式
List needDeleteIds = allFormula.stream().map(FormulaPO::getId).filter(id -> !formulaIds.contains(id)).collect(Collectors.toList());
List> partition = Lists.partition(needDeleteIds, 1000);
partition.forEach(list -> {
getFormulaMapper().deleteIn(list, yesterday);
getFormulaVarMapper().deleteInFormulaIds(list, yesterday);
});
}
}
@Override
public List listVarByFormulaIds(List effectiveFormulaIds) {
List vars = new ArrayList<>();
if (CollectionUtils.isNotEmpty(effectiveFormulaIds)) {
List> partition = Lists.partition(effectiveFormulaIds, 999);
partition.forEach(list -> {
vars.addAll(getFormulaVarMapper().listSome(FormulaVar.builder().formulaIds(list).build()));
});
}
return vars;
}
@Override
public List listByCode(String code) {
return getFormulaVarMapper().listSome(FormulaVar.builder().fieldId(code).build());
}
@Override
public void updateVar(FormulaVar formulaVar) {
getFormulaVarMapper().updateIgnoreNull(formulaVar);
}
@Override
public List listByIds(List formulaIds) {
if (CollectionUtils.isEmpty(formulaIds)) {
return new ArrayList<>();
}
return getFormulaMapper().listByIds(formulaIds);
}
@Override
public void update(FormulaPO formulaPO) {
getFormulaMapper().updateIgnoreNull(formulaPO);
}
}