运维模块补充

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;
// }
//
//}

View File

@ -58,18 +58,19 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<artifactId>hutool-all</artifactId>
<version>5.8.38</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>cn.hutool</groupId>-->
<!-- <artifactId>hutool-http</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>cn.hutool</groupId>-->
<!-- <artifactId>hutool-extra</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>

View File

@ -27,4 +27,9 @@ public class RemoteProjectServiceImpl implements RemoteProjectService {
public String selectProjectNameById(Long projectId) {
return projectService.getProjectNameById(projectId);
}
@Override
public void validAuth(Long projectId, Long userId) {
projectService.validAuth(projectId, userId);
}
}

View File

@ -488,4 +488,10 @@ public class RemoteUserServiceImpl implements RemoteUserService {
.collect(Collectors.toMap(SysPost::getPostId, SysPost::getPostName));
}
@Override
public RemoteUserVo selectUserByPhonenumber(String phone) {
SysUserVo sysUserVo = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phone));
return BeanUtil.copyProperties(sysUserVo, RemoteUserVo.class);
}
}

View File

@ -1,4 +1,4 @@
package org.dromara.ops;
package org.dromara;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;

View File

@ -1,4 +1,4 @@
package org.dromara.ops.personnel.controller;
package org.dromara.personnel.controller;
import java.util.List;
@ -17,9 +17,9 @@ import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.ops.personnel.domain.vo.OpsUserVo;
import org.dromara.ops.personnel.domain.bo.OpsUserBo;
import org.dromara.ops.personnel.service.IOpsUserService;
import org.dromara.personnel.domain.vo.OpsUserVo;
import org.dromara.personnel.domain.bo.OpsUserBo;
import org.dromara.personnel.service.IOpsUserService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**

View File

@ -0,0 +1,106 @@
package org.dromara.personnel.controller;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.personnel.domain.vo.OpsUserFilesVo;
import org.dromara.personnel.domain.bo.OpsUserFilesBo;
import org.dromara.personnel.service.IOpsUserFilesService;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 运维人员文件
* 前端访问路由地址为:/personnel/userFiles
*
* @author LionLi
* @date 2025-09-17
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/userFiles")
public class OpsUserFilesController extends BaseController {
private final IOpsUserFilesService opsUserFilesService;
/**
* 查询运维人员文件列表
*/
@SaCheckPermission("personnel:userFiles:list")
@GetMapping("/list")
public TableDataInfo<OpsUserFilesVo> list(OpsUserFilesBo bo, PageQuery pageQuery) {
return opsUserFilesService.queryPageList(bo, pageQuery);
}
/**
* 导出运维人员文件列表
*/
@SaCheckPermission("personnel:userFiles:export")
@Log(title = "运维人员文件", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(OpsUserFilesBo bo, HttpServletResponse response) {
List<OpsUserFilesVo> list = opsUserFilesService.queryList(bo);
ExcelUtil.exportExcel(list, "运维人员文件", OpsUserFilesVo.class, response);
}
/**
* 获取运维人员文件详细信息
*
* @param opsUserId 主键
*/
@SaCheckPermission("personnel:userFiles:query")
@GetMapping("/{opsUserId}")
public R<OpsUserFilesVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("opsUserId") Long opsUserId) {
return R.ok(opsUserFilesService.queryById(opsUserId));
}
/**
* 新增运维人员文件
*/
@SaCheckPermission("personnel:userFiles:add")
@Log(title = "运维人员文件", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody OpsUserFilesBo bo) {
return toAjax(opsUserFilesService.insertByBo(bo));
}
/**
* 修改运维人员文件
*/
@SaCheckPermission("personnel:userFiles:edit")
@Log(title = "运维人员文件", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody OpsUserFilesBo bo) {
return toAjax(opsUserFilesService.updateByBo(bo));
}
/**
* 删除运维人员文件
*
* @param opsUserIds 主键串
*/
@SaCheckPermission("personnel:userFiles:remove")
@Log(title = "运维人员文件", businessType = BusinessType.DELETE)
@DeleteMapping("/{opsUserIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable("opsUserIds") Long[] opsUserIds) {
return toAjax(opsUserFilesService.deleteWithValidByIds(List.of(opsUserIds), true));
}
}

View File

@ -1,4 +1,4 @@
package org.dromara.ops.personnel.domain;
package org.dromara.personnel.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
@ -48,10 +48,6 @@ public class OpsUser extends BaseEntity {
*/
private Long projectId;
/**
* 分包公司id
*/
private Long contractorId;
/**
* 班组id

View File

@ -0,0 +1,46 @@
package org.dromara.personnel.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 运维人员文件对象 ops_user_files
*
* @author LionLi
* @date 2025-09-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("ops_user_files")
public class OpsUserFiles extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 运维人员id
*/
@TableId(value = "ops_user_id")
private Long opsUserId;
/**
* 文件id
*/
private Long fileId;
/**
* 文件名称
*/
private String fileName;
/**
* 文件类型1、特种作业证图片
*/
private String fileType;
}

View File

@ -1,6 +1,6 @@
package org.dromara.ops.personnel.domain.bo;
package org.dromara.personnel.domain.bo;
import org.dromara.ops.personnel.domain.OpsUser;
import org.dromara.personnel.domain.OpsUser;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
@ -35,7 +35,7 @@ public class OpsUserBo extends BaseEntity {
/**
* 系统用户id
*/
@NotNull(message = "系统用户id不能为空", groups = { AddGroup.class, EditGroup.class })
@NotNull(message = "系统用户id不能为空", groups = { EditGroup.class })
private Long sysUserId;
/**
@ -50,22 +50,18 @@ public class OpsUserBo extends BaseEntity {
@NotNull(message = "项目id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long projectId;
/**
* 分包公司id
*/
@NotNull(message = "分包公司id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long contractorId;
/**
* 班组id
*/
@NotNull(message = "班组id不能为空", groups = { AddGroup.class, EditGroup.class })
// @NotNull(message = "班组id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long teamId;
/**
* 班组名称
*/
@NotBlank(message = "班组名称不能为空", groups = { AddGroup.class, EditGroup.class })
// @NotBlank(message = "班组名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String teamName;
/**

View File

@ -0,0 +1,47 @@
package org.dromara.personnel.domain.bo;
import org.dromara.personnel.domain.OpsUserFiles;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 运维人员文件业务对象 ops_user_files
*
* @author LionLi
* @date 2025-09-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = OpsUserFiles.class, reverseConvertGenerate = false)
public class OpsUserFilesBo extends BaseEntity {
/**
* 运维人员id
*/
private Long opsUserId;
/**
* 文件id
*/
@NotNull(message = "文件id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long fileId;
/**
* 文件名称
*/
@NotBlank(message = "文件名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String fileName;
/**
* 文件类型1、特种作业证图片
*/
@NotBlank(message = "文件类型1、特种作业证图片不能为空", groups = { AddGroup.class, EditGroup.class })
private String fileType;
}

View File

@ -0,0 +1,57 @@
package org.dromara.personnel.domain.vo;
import org.dromara.personnel.domain.OpsUserFiles;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 运维人员文件视图对象 ops_user_files
*
* @author LionLi
* @date 2025-09-17
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = OpsUserFiles.class)
public class OpsUserFilesVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 运维人员id
*/
@ExcelProperty(value = "运维人员id")
private Long opsUserId;
/**
* 文件id
*/
@ExcelProperty(value = "文件id")
private Long fileId;
/**
* 文件名称
*/
@ExcelProperty(value = "文件名称")
private String fileName;
/**
* 文件类型1、特种作业证图片
*/
@ExcelProperty(value = "文件类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "1=、特种作业证图片")
private String fileType;
}

View File

@ -1,4 +1,4 @@
package org.dromara.ops.personnel.domain.vo;
package org.dromara.personnel.domain.vo;
import java.util.Date;
@ -8,7 +8,7 @@ import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.ops.personnel.domain.OpsUser;
import org.dromara.personnel.domain.OpsUser;
import java.io.Serial;
import java.io.Serializable;
@ -58,11 +58,7 @@ public class OpsUserVo implements Serializable {
@ExcelProperty(value = "项目id")
private Long projectId;
/**
* 分包公司id
*/
@ExcelProperty(value = "分包公司id")
private Long contractorId;
/**
* 班组id

View File

@ -0,0 +1,15 @@
package org.dromara.personnel.mapper;
import org.dromara.personnel.domain.OpsUserFiles;
import org.dromara.personnel.domain.vo.OpsUserFilesVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 运维人员文件Mapper接口
*
* @author LionLi
* @date 2025-09-17
*/
public interface OpsUserFilesMapper extends BaseMapperPlus<OpsUserFiles, OpsUserFilesVo> {
}

View File

@ -1,7 +1,7 @@
package org.dromara.ops.personnel.mapper;
package org.dromara.personnel.mapper;
import org.dromara.ops.personnel.domain.OpsUser;
import org.dromara.ops.personnel.domain.vo.OpsUserVo;
import org.dromara.personnel.domain.OpsUser;
import org.dromara.personnel.domain.vo.OpsUserVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**

View File

@ -0,0 +1,69 @@
package org.dromara.personnel.service;
import org.dromara.personnel.domain.OpsUserFiles;
import org.dromara.personnel.domain.vo.OpsUserFilesVo;
import org.dromara.personnel.domain.bo.OpsUserFilesBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 运维人员文件Service接口
*
* @author LionLi
* @date 2025-09-17
*/
public interface IOpsUserFilesService {
/**
* 查询运维人员文件
*
* @param opsUserId 主键
* @return 运维人员文件
*/
OpsUserFilesVo queryById(Long opsUserId);
/**
* 分页查询运维人员文件列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 运维人员文件分页列表
*/
TableDataInfo<OpsUserFilesVo> queryPageList(OpsUserFilesBo bo, PageQuery pageQuery);
/**
* 查询符合条件的运维人员文件列表
*
* @param bo 查询条件
* @return 运维人员文件列表
*/
List<OpsUserFilesVo> queryList(OpsUserFilesBo bo);
/**
* 新增运维人员文件
*
* @param bo 运维人员文件
* @return 是否新增成功
*/
Boolean insertByBo(OpsUserFilesBo bo);
/**
* 修改运维人员文件
*
* @param bo 运维人员文件
* @return 是否修改成功
*/
Boolean updateByBo(OpsUserFilesBo bo);
/**
* 校验并批量删除运维人员文件信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -1,7 +1,7 @@
package org.dromara.ops.personnel.service;
package org.dromara.personnel.service;
import org.dromara.ops.personnel.domain.vo.OpsUserVo;
import org.dromara.ops.personnel.domain.bo.OpsUserBo;
import org.dromara.personnel.domain.vo.OpsUserVo;
import org.dromara.personnel.domain.bo.OpsUserBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;

View File

@ -0,0 +1,134 @@
package org.dromara.personnel.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.personnel.domain.bo.OpsUserFilesBo;
import org.dromara.personnel.domain.vo.OpsUserFilesVo;
import org.dromara.personnel.domain.OpsUserFiles;
import org.dromara.personnel.mapper.OpsUserFilesMapper;
import org.dromara.personnel.service.IOpsUserFilesService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 运维人员文件Service业务层处理
*
* @author LionLi
* @date 2025-09-17
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class OpsUserFilesServiceImpl implements IOpsUserFilesService {
private final OpsUserFilesMapper baseMapper;
/**
* 查询运维人员文件
*
* @param opsUserId 主键
* @return 运维人员文件
*/
@Override
public OpsUserFilesVo queryById(Long opsUserId){
return baseMapper.selectVoById(opsUserId);
}
/**
* 分页查询运维人员文件列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 运维人员文件分页列表
*/
@Override
public TableDataInfo<OpsUserFilesVo> queryPageList(OpsUserFilesBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<OpsUserFiles> lqw = buildQueryWrapper(bo);
Page<OpsUserFilesVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的运维人员文件列表
*
* @param bo 查询条件
* @return 运维人员文件列表
*/
@Override
public List<OpsUserFilesVo> queryList(OpsUserFilesBo bo) {
LambdaQueryWrapper<OpsUserFiles> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<OpsUserFiles> buildQueryWrapper(OpsUserFilesBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<OpsUserFiles> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(OpsUserFiles::getOpsUserId);
lqw.eq(bo.getFileId() != null, OpsUserFiles::getFileId, bo.getFileId());
lqw.like(StringUtils.isNotBlank(bo.getFileName()), OpsUserFiles::getFileName, bo.getFileName());
lqw.eq(StringUtils.isNotBlank(bo.getFileType()), OpsUserFiles::getFileType, bo.getFileType());
return lqw;
}
/**
* 新增运维人员文件
*
* @param bo 运维人员文件
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(OpsUserFilesBo bo) {
OpsUserFiles add = MapstructUtils.convert(bo, OpsUserFiles.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setOpsUserId(add.getOpsUserId());
}
return flag;
}
/**
* 修改运维人员文件
*
* @param bo 运维人员文件
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(OpsUserFilesBo bo) {
OpsUserFiles update = MapstructUtils.convert(bo, OpsUserFiles.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(OpsUserFiles entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除运维人员文件信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

View File

@ -1,5 +1,8 @@
package org.dromara.ops.personnel.service.impl;
package org.dromara.personnel.service.impl;
import jakarta.annotation.Resource;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
@ -9,12 +12,17 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.api.RemoteProjectService;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteUserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.dromara.ops.personnel.domain.bo.OpsUserBo;
import org.dromara.ops.personnel.domain.vo.OpsUserVo;
import org.dromara.ops.personnel.domain.OpsUser;
import org.dromara.ops.personnel.mapper.OpsUserMapper;
import org.dromara.ops.personnel.service.IOpsUserService;
import org.dromara.personnel.domain.bo.OpsUserBo;
import org.dromara.personnel.domain.vo.OpsUserVo;
import org.dromara.personnel.domain.OpsUser;
import org.dromara.personnel.mapper.OpsUserMapper;
import org.dromara.personnel.service.IOpsUserService;
import java.util.List;
import java.util.Map;
@ -33,6 +41,10 @@ public class OpsUserServiceImpl implements IOpsUserService {
private final OpsUserMapper baseMapper;
private final RemoteProjectService remoteProjectService;
@Autowired
private RemoteUserService remoteUserService;
/**
* 查询运维人员
*
@ -78,7 +90,6 @@ public class OpsUserServiceImpl implements IOpsUserService {
lqw.eq(bo.getSysUserId() != null, OpsUser::getSysUserId, bo.getSysUserId());
lqw.like(StringUtils.isNotBlank(bo.getUserName()), OpsUser::getUserName, bo.getUserName());
lqw.eq(bo.getProjectId() != null, OpsUser::getProjectId, bo.getProjectId());
lqw.eq(bo.getContractorId() != null, OpsUser::getContractorId, bo.getContractorId());
lqw.eq(bo.getTeamId() != null, OpsUser::getTeamId, bo.getTeamId());
lqw.like(StringUtils.isNotBlank(bo.getTeamName()), OpsUser::getTeamName, bo.getTeamName());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), OpsUser::getStatus, bo.getStatus());
@ -120,6 +131,13 @@ public class OpsUserServiceImpl implements IOpsUserService {
public Boolean insertByBo(OpsUserBo bo) {
OpsUser add = MapstructUtils.convert(bo, OpsUser.class);
validEntityBeforeSave(add);
Long userId = LoginHelper.getUserId();
remoteProjectService.validAuth(bo.getProjectId(), userId);
String phone = add.getPhone();
RemoteUserVo userVo = remoteUserService.selectUserByPhonenumber(phone);
if (userVo == null) {
throw new ServiceException("当前用户未在系统注册", HttpStatus.BAD_REQUEST);
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.personnel.mapper.OpsUserFilesMapper">
</mapper>

View File

@ -2,6 +2,6 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.ops.personnel.mapper.OpsUserMapper">
<mapper namespace="org.dromara.personnel.mapper.OpsUserMapper">
</mapper>