运维模块补充

This commit is contained in:
2025-09-17 16:34:02 +08:00
parent bc2d41ca89
commit 94e62580c3
30 changed files with 1237 additions and 50 deletions

View File

@ -1,5 +1,7 @@
package org.dromara.system.api;
import jakarta.validation.constraints.NotNull;
/**
* @author lilemy
* @date 2025-09-10 16:15
@ -14,4 +16,10 @@ public interface RemoteProjectService {
*/
String selectProjectNameById(Long projectId);
/**
*校验用户是否拥有操作项目的权限
* @param projectId
* @param userId
*/
void validAuth(Long projectId, Long userId);
}

View File

@ -189,4 +189,5 @@ public interface RemoteUserService {
*/
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
RemoteUserVo selectUserByPhonenumber(String phone);
}

View File

@ -0,0 +1,28 @@
package org.dromara.system.api.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @author lilemy
* @date 2025/5/27 9:16
*/
public class BigDecimalUtil {
/**
* 计算百分比
*
* @param dividend 被除数
* @param divisor 除数
* @return 百分比保留2位小数如 12.34%
*/
public static BigDecimal toPercentage(BigDecimal dividend, BigDecimal divisor) {
if (dividend == null || divisor == null || divisor.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.valueOf(0.00);
}
return dividend
.multiply(new BigDecimal("100"))
.divide(divisor, 2, RoundingMode.HALF_UP);
}
}

View File

@ -0,0 +1,100 @@
package org.dromara.system.api.utils;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author lilemy
* @date 2025/4/17 14:53
*/
public class DocumentUtil {
/**
* 在给定的 run 里插入图片,并按原始大小(或缩放后大小)设置宽高。
*
* @param run 要插入图片的 XWPFRun
* @param imagePath 本地图片路径或 URL这里示例用本地文件
* @param document 当前文档对象
* @param maxWidthPx 最大允许宽度(像素),如果原图更宽就按比例缩放;设置为 <=0 则不缩放
*/
public static void insertImageDynamic(XWPFRun run,
String imagePath,
XWPFDocument document,
int maxWidthPx) throws Exception {
// 1. 先把图片读到 byte[]
byte[] imgBytes = Files.readAllBytes(Paths.get(imagePath));
// 2. 用 ImageIO 读出宽高(像素)
BufferedImage img = ImageIO.read(new ByteArrayInputStream(imgBytes));
int widthPx = img.getWidth();
int heightPx = img.getHeight();
// 3. 如果指定了最大宽度,而且原图更宽,则按比例缩放
if (maxWidthPx > 0 && widthPx > maxWidthPx) {
double ratio = (double) maxWidthPx / widthPx;
widthPx = maxWidthPx;
heightPx = (int) (heightPx * ratio);
}
// 4. 把像素转换成 EMU
int widthEmu = Units.pixelToEMU(widthPx);
int heightEmu = Units.pixelToEMU(heightPx);
// 5. 插入图片
String lower = imagePath.toLowerCase();
int format = lower.endsWith(".png") ? XWPFDocument.PICTURE_TYPE_PNG
: lower.endsWith(".gif") ? XWPFDocument.PICTURE_TYPE_GIF
: lower.endsWith(".jpeg") ? XWPFDocument.PICTURE_TYPE_JPEG
: lower.endsWith(".jpg") ? XWPFDocument.PICTURE_TYPE_JPEG
: XWPFDocument.PICTURE_TYPE_PNG; // 默认当 PNG
try (InputStream picIn = new ByteArrayInputStream(imgBytes)) {
run.addPicture(picIn, format, imagePath, widthEmu, heightEmu);
}
}
/**
* 递归将 sourceDir 中的所有文件和子目录,按照相对于 rootDir 的路径写入 ZipOutputStream。
*
* @param rootDir 源目录的根,用来计算相对路径
* @param sourceDir 当前递归到的目录
* @param zos ZIP 输出流
*/
public static void zipDirectory(Path rootDir, Path sourceDir, ZipOutputStream zos) throws IOException {
// 遍历当前目录下的所有文件和文件夹
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
for (Path entry : stream) {
if (Files.isDirectory(entry)) {
// 如果是目录,递归
zipDirectory(rootDir, entry, zos);
} else {
// 如果是文件,创建一个 ZipEntry路径以 '/' 分隔
Path relativePath = rootDir.relativize(entry);
String zipEntryName = relativePath.toString().replace(File.separatorChar, '/');
ZipEntry zipEntry = new ZipEntry(zipEntryName);
zos.putNextEntry(zipEntry);
// 把文件内容写入 ZIP
try (InputStream is = Files.newInputStream(entry)) {
byte[] buffer = new byte[4096];
int len;
while ((len = is.read(buffer)) != -1) {
zos.write(buffer, 0, len);
}
}
zos.closeEntry();
}
}
}
}
}

View File

@ -0,0 +1,97 @@
package org.dromara.system.api.utils;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
/**
* @author lilemy
* @date 2025/4/23 10:42
*/
@Slf4j
public class Dxf2JsonUtil {
/**
* dxf转json
*
* @param exePath dxf2json.exe路径
* @param inputDXFPath 输入dxf文件路径
* @param outputJSONPath 输出json文件路径
* @param sourceEPSG 源坐标系
* @param targetEPSG 目标坐标系
*/
public static void dxf2json(String exePath, String inputDXFPath, String outputJSONPath, String sourceEPSG, String targetEPSG) {
// 判断对应文件是否存在
File exeFile = new File(exePath);
if (!exeFile.exists()) {
throw new ServiceException("转换程序不存在!");
}
File inputDXFFile = new File(inputDXFPath);
if (!inputDXFFile.exists()) {
throw new ServiceException("待转换 dxf 文件不存在!");
}
// 构造命令行参数
List<String> parameters = buildParameter(exePath, inputDXFPath, outputJSONPath, sourceEPSG, targetEPSG);
// 执行命令行
ProcessBuilder builder = new ProcessBuilder(parameters);
// 合并标准错误和输出
builder.redirectErrorStream(true);
try {
Process process = builder.start();
// 读取输出
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), "GBK")
);
String line;
log.info("dxf 转 json 程序开始执行,程序路径:{},输入 dxf 路径:{},输出 json 文件路径:{},源坐标系:{},模板坐标系:{}",
exePath, inputDXFPath, outputJSONPath, sourceEPSG, targetEPSG);
while ((line = reader.readLine()) != null) {
log.info("dxf 转 json 程序执行中:{}", line);
JSONObject jsonObject = JSONUtil.parseObj(line);
Integer code = jsonObject.get("code", Integer.class);
if (code != 0 && code != 200) {
throw new ServiceException("dxf 转 json 程序执行出错!");
}
}
int exitCode = process.waitFor();
log.info("dxf 转 json 程序执行完毕,程序退出码:{}", exitCode);
reader.close();
} catch (IOException | InterruptedException e) {
log.error("执行 dxf 转 json 命令行时出错", e);
}
}
/**
* 构造命令行参数
*
* @param exePath dxf2json.exe路径
* @param inputDXFPath 输入dxf文件路径
* @param outputJSONPath 输出json文件路径
* @param sourceEPSG 源坐标系
* @param targetEPSG 目标坐标系
* @return 命令行参数
*/
public static List<String> buildParameter(String exePath,
String inputDXFPath,
String outputJSONPath,
String sourceEPSG,
String targetEPSG) {
// 构造命令行
return Arrays.asList(
exePath,
inputDXFPath,
outputJSONPath,
sourceEPSG,
targetEPSG
);
}
}

View File

@ -0,0 +1,24 @@
package org.dromara.system.api.utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.http.HttpClient;
import java.time.Duration;
/**
* @Author 铁憨憨
* @Date 2025/7/18 10:16
* @Version 1.0
*
* HttpClient 设计为可重用、线程安全的组件,其内部维护了连接池等资源,适合在多个接口调用中共享使用,所以交给Spring Bean 管理
*/
@Configuration
public class HttpClientConfig {
@Bean
public HttpClient httpClient() {
return HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
}
}

View File

@ -0,0 +1,56 @@
package org.dromara.system.api.utils;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author lilemy
* @date 2025/6/25 10:57
*/
@Slf4j
@Component
public class IdCardEncryptorUtil {
@Value("${id-card.encrypt-key}")
private String encryptKeyHex;
private AES aes;
@PostConstruct
public void init() {
byte[] keyBytes = HexUtil.decodeHex(encryptKeyHex);
this.aes = SecureUtil.aes(keyBytes);
log.info("身份证 AES 加解密工具初始化成功");
}
/**
* 加密
*
* @param idCard 身份证号码
* @return 加密后的身份证号码
*/
public String encrypt(String idCard) {
return aes.encryptBase64(idCard);
}
/**
* 解密
*
* @param encrypted 密文
* @return 解密后的身份证号码
*/
public String decrypt(String encrypted) {
if (encrypted == null) {
return null;
}
return aes.decryptStr(encrypted);
}
}

View File

@ -0,0 +1,316 @@
//package org.dromara.system.api.utils;
//
//import cn.hutool.json.JSONUtil;
//import org.dromara.common.constant.GeoJsonConstant;
//import org.dromara.common.core.constant.HttpStatus;
//import org.dromara.common.core.exception.ServiceException;
//import org.dromara.common.domain.GeoPoint;
//import org.dromara.facility.domain.dto.geojson.FacFeatureByPlane;
//import org.dromara.facility.domain.dto.geojson.FacFeatureByPoint;
//import org.dromara.facility.domain.dto.geojson.FacGeometry;
//import org.dromara.facility.domain.dto.geojson.FacGeometryByPoint;
//import org.locationtech.jts.geom.*;
//import org.locationtech.jts.index.strtree.STRtree;
//
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//import java.util.stream.Collectors;
//
///**
// * @author lilemy
// * @date 2025/4/24 11:48
// */
//public class JSTUtil {
//
// private static final GeometryFactory geometryFactory = new GeometryFactory();
//
// /**
// * 获取最近点的名称
// *
// * @param target 目标点
// * @param nameGeoJson 点对象列表
// * @return 最近点的名称
// */
// public static String findNearestText(List<Double> target, List<FacFeatureByPoint> nameGeoJson) {
// Point targetPoint = geometryFactory.createPoint(new Coordinate(target.get(0), target.get(1)));
// FacFeatureByPoint nearestFeature = null;
// double minDistance = Double.MAX_VALUE;
// for (FacFeatureByPoint feature : nameGeoJson) {
// FacGeometryByPoint geometry = feature.getGeometry();
// List<Double> coords = geometry.getCoordinates();
// if (coords != null && coords.size() == 2) {
// Point currentPoint = geometryFactory.createPoint(new Coordinate(coords.get(0), coords.get(1)));
// double distance = targetPoint.distance(currentPoint);
// if (distance < minDistance) {
// minDistance = distance;
// nearestFeature = feature;
// }
// }
// }
// if (nearestFeature != null && nearestFeature.getProperties() != null) {
// return nearestFeature.getProperties().getText();
// }
// return null; // 如果没找到合适的点
// }
//
// /**
// * 点是否在平面内
// *
// * @param planeLists 平面坐标
// * @param pointList 点坐标
// * @return 点是否在平面内
// */
// public static Boolean pointIsWithInPlane(List<List<Double>> planeLists, List<Double> pointList) {
// // 构建平面
// Coordinate[] coordinates = getPlaneCoordinate(planeLists);
// Polygon polygon = geometryFactory.createPolygon(coordinates);
// // 构建待判断点
// Point point = geometryFactory.createPoint(new Coordinate(pointList.get(0), pointList.get(1)));
// // 判断是否在多边形内
// return polygon.contains(point);
// }
//
// /**
// * 获取平面内点列表集合
// *
// * @param planeLists 平面坐标列表
// * @param pointLists 点坐标列表集合
// * @return 平面内点坐标列表集合
// */
// public static List<List<Double>> getPointInPlaneList(List<List<Double>> planeLists, List<List<Double>> pointLists) {
// // 构建平面
// Coordinate[] coordinates = getPlaneCoordinate(planeLists);
// LinearRing shell = geometryFactory.createLinearRing(coordinates);
// Polygon polygon = geometryFactory.createPolygon(shell);
// // 获取平面内点结合
// return pointLists.stream().filter(pointList -> {
// // 构建待判断点
// Point point = geometryFactory.createPoint(new Coordinate(pointList.get(0), pointList.get(1)));
// // 判断是否在多边形内
// return polygon.contains(point);
// }).toList();
// }
//
// /**
// * 平面是否在平面内
// *
// * @param referencePlane 参考平面
// * @param comparePlane 比较平面
// * @return 平面是否在平面内
// */
// public static Boolean planeIsWithInPlane(List<List<Double>> referencePlane, List<List<Double>> comparePlane) {
// // 构建参考平面
// Coordinate[] referenceCoordinates = getPlaneCoordinate(referencePlane);
// Polygon referencePolygon = geometryFactory.createPolygon(referenceCoordinates);
// // 构建比较平面
// Coordinate[] compareCoordinates = getPlaneCoordinate(comparePlane);
// Polygon comparePolygon = geometryFactory.createPolygon(compareCoordinates);
// // 判断是否在多边形内
// return referencePolygon.contains(comparePolygon);
// }
//
// /**
// * 判断两个平面是否相交
// *
// * @param referencePlane 参考平面
// * @param comparePlane 待比较平面
// * @return 平面是否相交
// */
// public static Boolean arePolygonsIntersecting(List<List<Double>> referencePlane, List<List<Double>> comparePlane) {
// // 构建 Polygon A参考面
// Coordinate[] coordsA = referencePlane.stream()
// .map(p -> new Coordinate(p.getFirst(), p.get(1)))
// .toArray(Coordinate[]::new);
// Polygon polygonA = geometryFactory.createPolygon(coordsA);
// // 构建 Polygon B比较面
// Coordinate[] coordsB = comparePlane.stream()
// .map(p -> new Coordinate(p.getFirst(), p.get(1)))
// .toArray(Coordinate[]::new);
// Polygon polygonB = geometryFactory.createPolygon(coordsB);
// // 使用 JTS 判断是否相交
// return polygonA.intersects(polygonB);
// }
//
// /**
// * 获取平面坐标数组
// *
// * @param planeLists 平面坐标列表
// * @return 平面坐标数组
// */
// public static Coordinate[] getPlaneCoordinate(List<List<Double>> planeLists) {
// return planeLists.stream().map(planeList ->
// new Coordinate(planeList.getFirst(), planeList.get(1)))
// .toList().toArray(new Coordinate[0]);
// }
//
// /**
// * 获取二维坐标
// *
// * @param geometry 几何对象
// * @return 二维坐标
// */
// public static List<List<Double>> getTwoDimensionalCoordinates(FacGeometry geometry) {
// String type = geometry.getType();
// List<Object> coordinates = geometry.getCoordinates();
// return switch (type) {
// case GeoJsonConstant.POINT -> throw new ServiceException("点位无法创建方阵", HttpStatus.BAD_REQUEST);
// case GeoJsonConstant.LINE -> coordinates.stream()
// .filter(obj -> obj instanceof List<?>)
// .map(obj -> ((List<?>) obj).stream()
// .filter(num -> num instanceof Number)
// .map(num -> ((Number) num).doubleValue())
// .collect(Collectors.toList()))
// .collect(Collectors.toList());
// case GeoJsonConstant.POLYGON -> coordinates.stream()
// .filter(obj -> obj instanceof List<?>)
// .flatMap(obj -> ((List<?>) obj).stream())
// .filter(pointObj -> pointObj instanceof List<?>)
// .map(pointObj -> ((List<?>) pointObj).stream()
// .filter(num -> num instanceof Number)
// .map(num -> ((Number) num).doubleValue())
// .collect(Collectors.toList()))
// .collect(Collectors.toList());
// default -> throw new ServiceException("暂不支持该类型", HttpStatus.BAD_REQUEST);
// };
// }
//
// /**
// * 匹配最近的点,获取该点的名称
// *
// * @param polygon 平面
// * @param points 点列表集合
// * @return 最近点的名称
// */
// public static String findNearestPointText(List<List<Double>> polygon, List<FacFeatureByPoint> points) {
// if (polygon == null || polygon.size() < 3 || points == null || points.isEmpty()) {
// return null;
// }
// // 1. 构建 Polygon
// Coordinate[] polygonCoords = polygon.stream()
// .map(coord -> new Coordinate(coord.getFirst(), coord.get(1)))
// .toArray(Coordinate[]::new);
// Polygon jtsPolygon = geometryFactory.createPolygon(polygonCoords);
// // 2. 构建空间索引JTS STRtree
// STRtree spatialIndex = new STRtree();
// Map<Coordinate, FacFeatureByPoint> coordToFeatureMap = new HashMap<>();
// for (FacFeatureByPoint feature : points) {
// List<Double> coords = feature.getGeometry().getCoordinates();
// if (coords == null || coords.size() != 2) continue;
// Coordinate coord = new Coordinate(coords.get(0), coords.get(1));
// Point point = geometryFactory.createPoint(coord);
// // 用点的 Envelope 加入索引
// spatialIndex.insert(point.getEnvelopeInternal(), point);
// coordToFeatureMap.put(coord, feature);
// }
// // 3. 查询距离 polygon 最近的点
// // 用 polygon 中心点附近构造 Envelope扩大一些范围
// Envelope searchEnv = jtsPolygon.getEnvelopeInternal();
// searchEnv.expandBy(10); // 扩大搜索半径
// @SuppressWarnings("unchecked")
// List<Point> candidatePoints = spatialIndex.query(searchEnv);
// double minDistance = Double.MAX_VALUE;
// Coordinate nearestCoord = null;
// for (Point point : candidatePoints) {
// double distance = point.distance(jtsPolygon);
// if (distance < minDistance) {
// minDistance = distance;
// nearestCoord = point.getCoordinate();
// }
// }
// if (nearestCoord != null) {
// FacFeatureByPoint nearestFeature = coordToFeatureMap.get(nearestCoord);
// if (nearestFeature != null && nearestFeature.getProperties() != null) {
// return nearestFeature.getProperties().getText();
// }
// }
// return null;
// }
//
// /**
// * 匹配最近的面,获取该面的信息
// *
// * @param pointFeature 点位
// * @param polygons 平面列表
// * @return 最近面的信息
// */
// public static FacFeatureByPlane findNearestOrContainingPolygon(
// FacFeatureByPoint pointFeature,
// List<FacFeatureByPlane> polygons
// ) {
// if (pointFeature == null || polygons == null || polygons.isEmpty()) {
// return null;
// }
// List<Double> coords = pointFeature.getGeometry().getCoordinates();
// if (coords == null || coords.size() != 2) return null;
// Coordinate pointCoord = new Coordinate(coords.get(0), coords.get(1));
// Point point = geometryFactory.createPoint(pointCoord);
// FacFeatureByPlane nearestPolygon = null;
// double minDistance = Double.MAX_VALUE;
// for (FacFeatureByPlane polygonFeature : polygons) {
// if (polygonFeature == null || polygonFeature.getGeometry() == null) {
// continue; // 跳过空对象
// }
// List<List<Double>> polyCoords = polygonFeature.getGeometry().getCoordinates().getFirst();
// Coordinate[] polygonCoords = polyCoords.stream()
// .map(c -> new Coordinate(c.getFirst(), c.get(1)))
// .toArray(Coordinate[]::new);
// Polygon polygon = geometryFactory.createPolygon(polygonCoords);
// // 优先使用包含点的 polygon
// if (polygon.contains(point)) {
// return polygonFeature;
// }
// double distance = polygon.distance(point);
// if (distance < minDistance) {
// minDistance = distance;
// nearestPolygon = polygonFeature;
// }
// }
// return nearestPolygon;
// }
//
// /**
// * 判断一个点是否在多个区域中,返回第一个匹配区域的点集合(否则 null
// *
// * @param lat 纬度
// * @param lng 经度
// * @param rangeListJson 多边形列表,每个为 JSON 数组(多边形的点数组)
// * @return 匹配区域的 List<Point>,否则 null
// */
// public static List<GeoPoint> findMatchingRange(String lat, String lng, List<String> rangeListJson) {
// double latitude = Double.parseDouble(lat);
// double longitude = Double.parseDouble(lng);
// Point point = geometryFactory.createPoint(new Coordinate(longitude, latitude));
// for (String rangeJson : rangeListJson) {
// List<GeoPoint> polygonPoints = JSONUtil.toList(rangeJson, GeoPoint.class);
// if (polygonPoints.size() < 3) continue; // 不是有效多边形
//
// Polygon polygon = buildPolygon(polygonPoints);
// if (polygon.contains(point)) {
// return polygonPoints; // 找到匹配范围
// }
// }
// return null;
// }
//
// /**
// * 将点集合转换为 JTS 多边形
// */
// private static Polygon buildPolygon(List<GeoPoint> points) {
// Coordinate[] coordinates = points.stream()
// .map(p -> new Coordinate(p.getLng(), p.getLat()))
// .toArray(Coordinate[]::new);
//
// // 需要闭合坐标环(首尾相连)
// if (!coordinates[0].equals2D(coordinates[coordinates.length - 1])) {
// Coordinate[] closed = new Coordinate[coordinates.length + 1];
// System.arraycopy(coordinates, 0, closed, 0, coordinates.length);
// closed[closed.length - 1] = coordinates[0];
// coordinates = closed;
// }
//
// LinearRing shell = geometryFactory.createLinearRing(coordinates);
// return geometryFactory.createPolygon(shell);
// }
//
//}

View File

@ -0,0 +1,24 @@
package org.dromara.system.api.utils;
import cn.hutool.json.JSONArray;
/**
* @author lilemy
* @date 2025/5/30 14:55
*/
public class JsonDimensionUtil {
/**
* 判断 JSONArray 的嵌套维度
*
* @param array 需要判断的 JSONArray
* @return 返回维度层级1 表示一维2 表示二维3 表示三维 ...
*/
public static int getJsonArrayDepth(Object array) {
if (array instanceof JSONArray jsonArray && !jsonArray.isEmpty()) {
return 1 + getJsonArrayDepth(jsonArray.getFirst());
}
return 0;
}
}

View File

@ -0,0 +1,34 @@
//package org.dromara.system.api.utils;
//
//import cn.hutool.core.collection.CollUtil;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
//
//import java.util.Collections;
//import java.util.List;
//import java.util.function.Function;
//
///**
// * @author lilemy
// * @date 2025/5/28 11:25
// */
//public class PageConvertUtil {
//
// /**
// * 将 Page<T> 转换为 Page<V>
// *
// * @param source 原始分页数据
// * @param mapper 实体 -> VO 的转换函数
// * @return Page<V>
// */
// public static <T, V> Page<V> convert(Page<T> source, Function<T, V> mapper) {
// Page<V> target = new Page<>(source.getCurrent(), source.getSize(), source.getTotal());
// if (CollUtil.isEmpty(source.getRecords())) {
// target.setRecords(Collections.emptyList());
// } else {
// List<V> voList = source.getRecords().stream().map(mapper).toList();
// target.setRecords(voList);
// }
// return target;
// }
//
//}