[add] 获取身份证、银行卡信息,人员实名、打卡,获取打卡信息
This commit is contained in:
@ -80,7 +80,7 @@ public class AuthController {
|
||||
* @param body 登录信息
|
||||
* @return 结果
|
||||
*/
|
||||
@ApiEncrypt
|
||||
// @ApiEncrypt
|
||||
@PostMapping("/login")
|
||||
public R<LoginVo> login(@RequestBody String body) {
|
||||
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
|
||||
@ -183,7 +183,7 @@ public class AuthController {
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@ApiEncrypt
|
||||
// @ApiEncrypt
|
||||
@PostMapping("/register")
|
||||
public R<Void> register(@Validated @RequestBody RegisterBody user) {
|
||||
if (!configService.selectRegisterEnabled(user.getTenantId())) {
|
||||
|
@ -8,7 +8,7 @@ ruoyi:
|
||||
copyrightYear: 2024
|
||||
|
||||
captcha:
|
||||
enable: true
|
||||
enable: false
|
||||
# 页面 <参数设置> 可开启关闭 验证码校验
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
type: MATH
|
||||
|
@ -0,0 +1,18 @@
|
||||
package org.dromara.common.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-28 20:07
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class GeoPoint {
|
||||
private double lng;
|
||||
private double lat;
|
||||
private double alt;
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package org.dromara.common.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.constant.GeoJsonConstant;
|
||||
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;
|
||||
@ -267,4 +269,48 @@ public class JSTUtil {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package org.dromara.common.utils.baiduUtil;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.google.gson.Gson;
|
||||
import org.dromara.common.utils.baiduUtil.entity.AccessTokenResponse;
|
||||
import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.Const;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
@ -20,7 +21,7 @@ import static org.dromara.common.constant.businessConstant.REDIS_BAIDU_KEY;
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 9:46
|
||||
* @Version 1.0
|
||||
*
|
||||
* <p>
|
||||
* 获取百度AccessToken
|
||||
*/
|
||||
@Service
|
||||
@ -88,7 +89,7 @@ public class BaiDuCommon {
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200) {
|
||||
AccessTokenResponse tokenResponse = gson.fromJson(response.body(), AccessTokenResponse.class);
|
||||
AccessTokenResponse tokenResponse = JSONUtil.toBean(response.body(), AccessTokenResponse.class);
|
||||
return tokenResponse.getAccessToken();
|
||||
} else {
|
||||
throw new IOException("获取AccessToken失败,状态码: " + response.statusCode());
|
||||
|
@ -1,12 +1,12 @@
|
||||
package org.dromara.common.utils.baiduUtil;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.common.utils.baiduUtil.entity.face.ComparisonRes;
|
||||
import org.dromara.common.utils.baiduUtil.entity.face.HumanFaceReq;
|
||||
import org.dromara.common.utils.baiduUtil.entity.face.HumanFaceRes;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.dromara.common.utils.baiduUtil;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.IDCardInfo;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.OcrReq;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.Result;
|
||||
@ -9,20 +11,18 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:45
|
||||
* @Version 1.0
|
||||
*
|
||||
* <p>
|
||||
* 处理百度OCR相关逻辑:身份证识别、银行卡识别
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class BaiDuOCR {
|
||||
@Autowired
|
||||
@ -30,20 +30,19 @@ public class BaiDuOCR {
|
||||
@Autowired
|
||||
private HttpClient httpClient;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper; //ObjectMapper 是 Java 处理 JSON 的核心工具,项目中使用它进行 JSON 与 Java 对象的相互转换。
|
||||
private ObjectMapper objectMapper; //ObjectMapper 是 Java 处理 JSON 的核心工具,项目中使用它进行 JSON 与 Java 对象的相互转换。
|
||||
|
||||
|
||||
String baseUrlTemplate = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard?access_token=%s";
|
||||
String BASE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard?access_token=%s";
|
||||
|
||||
String baseUrlTemplate = "https://aip.baidubce.com/rest/2.0/ocr/v1/bankcard";
|
||||
String BASE_URL = "https://aip.baidubce.com/rest/2.0/ocr/v1/idcard";
|
||||
|
||||
|
||||
/**
|
||||
* @param vr 请求参数
|
||||
* @return 识别结果Result
|
||||
* @description 银行卡OCR识别
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/18 11:50
|
||||
* @param vr 请求参数
|
||||
* @return 识别结果Result
|
||||
**/
|
||||
public Result bankCardOCRRecognition(OcrReq vr) {
|
||||
// 先从缓存里面捞取token,若为空直接抛出异常
|
||||
@ -53,62 +52,41 @@ public class BaiDuOCR {
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求URL(包含token参数)
|
||||
String requestUrl = String.format(baseUrlTemplate, URLEncoder.encode(atStr, StandardCharsets.UTF_8));
|
||||
|
||||
// 准备请求体(将请求参数转为JSON)
|
||||
String requestBody = objectMapper.writeValueAsString(vr);
|
||||
if (requestBody == null) {
|
||||
throw new RuntimeException("请求参数序列化失败");
|
||||
String param;
|
||||
if (vr.getImage() != null) {
|
||||
String imgParam = URLEncoder.encode(vr.getImage(), StandardCharsets.UTF_8);
|
||||
param = "image=" + imgParam;
|
||||
} else if (vr.getUrl() != null) {
|
||||
param = "url=" + vr.getUrl();
|
||||
} else {
|
||||
throw new RuntimeException("请传入图片或图片URL");
|
||||
}
|
||||
|
||||
// 构建HTTP请求
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestUrl))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
String resultStr = HttpUtil.post(baseUrlTemplate, atStr, param);
|
||||
|
||||
// 发送请求并获取响应
|
||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
// 处理响应状态码(非200直接抛出异常)
|
||||
if (response.statusCode() != 200) {
|
||||
throw new RuntimeException("接口请求失败,状态码:" + response.statusCode() + ",响应信息:" + response.body());
|
||||
}
|
||||
|
||||
// 解析响应结果(若解析失败抛出异常)
|
||||
Result result = objectMapper.readValue(response.body(), Result.class);
|
||||
log.info("百度OCR识别结果:{}", resultStr);
|
||||
Result result = JSON.parseObject(resultStr, Result.class);
|
||||
if (result == null) {
|
||||
throw new RuntimeException("响应结果解析失败");
|
||||
throw new RuntimeException("未识别到银行卡信息");
|
||||
}
|
||||
|
||||
// // 若接口返回错误码(如百度API的error_code),抛出异常
|
||||
// if (result.getErrorCode() != null && result.getErrorCode() != 0) {
|
||||
// throw new RuntimeException("接口返回错误:" + result.getErrorMessage() + "(错误码:" + result.getErrorCode() + ")");
|
||||
// }
|
||||
|
||||
return result;
|
||||
|
||||
} catch (IOException e) {
|
||||
// IO异常(如序列化失败、网络IO错误)
|
||||
throw new RuntimeException("IO处理异常:" + e.getMessage(), e);
|
||||
} catch (InterruptedException e) {
|
||||
// 线程中断异常
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param request 请求参数
|
||||
* @return 识别结果WordsResult
|
||||
* @description 身份证OCR识别
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/18 16:53
|
||||
* @param request 请求参数
|
||||
* @return 识别结果WordsResult
|
||||
**/
|
||||
public WordsResult idCardOCR(OcrReq request) {
|
||||
// 获取AccessToken,为空直接抛异常
|
||||
@ -117,41 +95,28 @@ public class BaiDuOCR {
|
||||
throw new RuntimeException("获取访问令牌失败:token为空");
|
||||
}
|
||||
|
||||
// 构建请求URL
|
||||
String requestUrl = String.format(BASE_URL, accessToken);
|
||||
|
||||
try {
|
||||
// 准备请求体(序列化失败抛异常)
|
||||
String requestBody = objectMapper.writeValueAsString(request);
|
||||
if (requestBody == null) {
|
||||
throw new RuntimeException("请求参数序列化失败");
|
||||
}
|
||||
|
||||
// 构建HTTP请求
|
||||
HttpRequest httpRequest = HttpRequest.newBuilder()
|
||||
.uri(URI.create(requestUrl))
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
// 发送请求并获取响应
|
||||
HttpResponse<String> response = httpClient.send(
|
||||
httpRequest,
|
||||
HttpResponse.BodyHandlers.ofString()
|
||||
);
|
||||
|
||||
// 处理响应状态码(非200直接抛异常)
|
||||
if (response.statusCode() != 200) {
|
||||
throw new RuntimeException("接口请求失败,状态码:" + response.statusCode() + ",响应信息:" + response.body());
|
||||
String param = "id_card_side=" + request.getIdCardSide();
|
||||
if (request.getImage() != null) {
|
||||
String imgParam = URLEncoder.encode(request.getImage(), StandardCharsets.UTF_8);
|
||||
param = param + "&image=" + imgParam;
|
||||
} else if (request.getUrl() != null) {
|
||||
param = param + "&url=" + request.getUrl();
|
||||
} else {
|
||||
throw new RuntimeException("请传入图片或图片URL");
|
||||
}
|
||||
|
||||
// 处理响应内容(去除空格并解析)
|
||||
String responseBody = response.body().replaceAll("\\s+", "");
|
||||
IDCardInfo idCardInfo = objectMapper.readValue(responseBody, IDCardInfo.class);
|
||||
// 构建HTTP请求
|
||||
String result = HttpUtil.post(BASE_URL, accessToken, param);
|
||||
// 处理响应内容
|
||||
IDCardInfo idCardInfo = JSON.parseObject(result, IDCardInfo.class);
|
||||
if (idCardInfo == null) {
|
||||
throw new RuntimeException("响应结果解析失败");
|
||||
}
|
||||
|
||||
// 检查身份证状态(状态异常由validateImageStatus抛异常)
|
||||
validateImageStatus(idCardInfo.getImageStatus());
|
||||
|
||||
@ -166,20 +131,18 @@ public class BaiDuOCR {
|
||||
} catch (IOException e) {
|
||||
// IO异常(序列化、网络IO等)
|
||||
throw new RuntimeException("IO处理异常:" + e.getMessage(), e);
|
||||
} catch (InterruptedException e) {
|
||||
// 线程中断异常
|
||||
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||
throw new RuntimeException("请求被中断:" + e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param imageStatus 图片状态
|
||||
* @throws RuntimeException 当状态不正常时抛出运行时异常
|
||||
* @description 验证身份证图片状态
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/18 17:08
|
||||
* @param imageStatus 图片状态
|
||||
* @throws RuntimeException 当状态不正常时抛出运行时异常
|
||||
**/
|
||||
private void validateImageStatus(String imageStatus) {
|
||||
if (imageStatus == null || "normal".equals(imageStatus)) {
|
||||
|
@ -0,0 +1,77 @@
|
||||
package org.dromara.common.utils.baiduUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* http 工具类
|
||||
*/
|
||||
public class HttpUtil {
|
||||
|
||||
public static String post(String requestUrl, String accessToken, String params)
|
||||
throws Exception {
|
||||
String contentType = "application/x-www-form-urlencoded";
|
||||
return HttpUtil.post(requestUrl, accessToken, contentType, params);
|
||||
}
|
||||
|
||||
public static String post(String requestUrl, String accessToken, String contentType, String params)
|
||||
throws Exception {
|
||||
String encoding = "UTF-8";
|
||||
if (requestUrl.contains("nlp")) {
|
||||
encoding = "GBK";
|
||||
}
|
||||
return HttpUtil.post(requestUrl, accessToken, contentType, params, encoding);
|
||||
}
|
||||
|
||||
public static String post(String requestUrl, String accessToken, String contentType, String params, String encoding)
|
||||
throws Exception {
|
||||
String url = requestUrl + "?access_token=" + accessToken;
|
||||
return HttpUtil.postGeneralUrl(url, contentType, params, encoding);
|
||||
}
|
||||
|
||||
public static String postGeneralUrl(String generalUrl, String contentType, String params, String encoding)
|
||||
throws Exception {
|
||||
URL url = new URL(generalUrl);
|
||||
// 打开和URL之间的连接
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
// 设置通用的请求属性
|
||||
connection.setRequestProperty("Content-Type", contentType);
|
||||
connection.setRequestProperty("Connection", "Keep-Alive");
|
||||
connection.setUseCaches(false);
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
|
||||
// 得到请求的输出流对象
|
||||
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
|
||||
out.write(params.getBytes(encoding));
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
// 建立实际的连接
|
||||
connection.connect();
|
||||
// 获取所有响应头字段
|
||||
Map<String, List<String>> headers = connection.getHeaderFields();
|
||||
// 遍历所有的响应头字段
|
||||
for (String key : headers.keySet()) {
|
||||
System.err.println(key + "--->" + headers.get(key));
|
||||
}
|
||||
// 定义 BufferedReader输入流来读取URL的响应
|
||||
BufferedReader in = null;
|
||||
in = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream(), encoding));
|
||||
String result = "";
|
||||
String getLine;
|
||||
while ((getLine = in.readLine()) != null) {
|
||||
result += getLine;
|
||||
}
|
||||
in.close();
|
||||
System.err.println("result:" + result);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 9:56
|
||||
@ -12,7 +13,10 @@ import lombok.experimental.Accessors;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class AccessTokenResponse {
|
||||
public class AccessTokenResponse implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 5429166046159819095L;
|
||||
|
||||
private String accessToken;
|
||||
private int expiresIn;
|
||||
private String scope;
|
||||
|
@ -1,13 +1,14 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import org.dromara.system.domain.vo.SysOssVo;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:58
|
||||
* @Version 1.0
|
||||
*
|
||||
* <p>
|
||||
* 银行卡具体信息
|
||||
*/
|
||||
@Data
|
||||
@ -22,18 +23,23 @@ import lombok.Data;
|
||||
|
||||
public class BankData {
|
||||
|
||||
@JSONField(name = "bank_card_number",label = "银行卡卡号")
|
||||
@JSONField(name = "bank_card_number", label = "银行卡卡号")
|
||||
private String bankCardNumber;
|
||||
|
||||
@JSONField(name = "valid_date",label = "有效期")
|
||||
@JSONField(name = "valid_date", label = "有效期")
|
||||
private String validDate;
|
||||
|
||||
@JSONField(name = "bank_card_type",label = "银行卡类型,0:不能识别; 1:借记卡; 2:贷记卡(原信用卡大部分为贷记卡); 3:准贷记卡; 4:预付费卡")
|
||||
@JSONField(name = "bank_card_type", label = "银行卡类型,0:不能识别; 1:借记卡; 2:贷记卡(原信用卡大部分为贷记卡); 3:准贷记卡; 4:预付费卡")
|
||||
private int bankCardType;
|
||||
|
||||
@JSONField(name = "bank_name",label = "银行名,不能识别时为空")
|
||||
@JSONField(name = "bank_name", label = "银行名,不能识别时为空")
|
||||
private String bankName;
|
||||
|
||||
@JSONField(name = "holder_name",label = "持卡人姓名,不能识别时为空")
|
||||
@JSONField(name = "holder_name", label = "持卡人姓名,不能识别时为空")
|
||||
private String holderName;
|
||||
|
||||
/**
|
||||
* 银行卡图片信息
|
||||
*/
|
||||
private SysOssVo image;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
@ -11,6 +12,8 @@ import lombok.Data;
|
||||
*/
|
||||
@Data
|
||||
public class Field {
|
||||
@JSONField(name = "location")
|
||||
private Location location; // 位置信息
|
||||
@JSONField(name = "words")
|
||||
private String words; // 识别文字
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
|
@ -1,18 +1,26 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:56
|
||||
* @Version 1.0
|
||||
*
|
||||
* <p>
|
||||
* 位置信息(坐标)
|
||||
*/
|
||||
@Data
|
||||
public class Location {
|
||||
private int top; // 顶部坐标
|
||||
private int left; // 左侧坐标
|
||||
private int width; // 宽度
|
||||
private int height; // 高度
|
||||
@JSONField(name = "top")
|
||||
private int top;
|
||||
|
||||
@JSONField(name = "left")
|
||||
private int left;
|
||||
|
||||
@JSONField(name = "width")
|
||||
private int width;
|
||||
|
||||
@JSONField(name = "height")
|
||||
private int height;
|
||||
}
|
||||
|
@ -1,24 +1,30 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:47
|
||||
* @Version 1.0
|
||||
*
|
||||
* <p>
|
||||
* OCR请求参数(身份证/银行卡通用)
|
||||
*/
|
||||
@Data
|
||||
public class OcrReq {
|
||||
public class OcrReq implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -8670697823104813789L;
|
||||
|
||||
private String image; // 图像base64(与url二选一)
|
||||
|
||||
private String url; // 图像URL(与image二选一)
|
||||
|
||||
@JSONField(name = "id_card_side")
|
||||
@JsonProperty("id_card_side")
|
||||
private String idCardSide; // 身份证正反面(front/back,银行卡无需)
|
||||
|
||||
@JSONField(name = "detect_photo")
|
||||
@JsonProperty("detect_photo")
|
||||
private boolean detectPhoto;// 是是否开启银行卡质量类型(清晰模糊、边框/四角不完整)检测功能,默认不开启,即:false。
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
|
@ -1,33 +1,38 @@
|
||||
package org.dromara.common.utils.baiduUtil.entity.ocr;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
import org.dromara.system.domain.vo.SysOssVo;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:50
|
||||
* @Version 1.0
|
||||
*
|
||||
* <p>
|
||||
* 身份证识别字段集合
|
||||
*/
|
||||
@Data
|
||||
public class WordsResult {
|
||||
@JSONField(name = "姓名")
|
||||
private Field name; // 姓名
|
||||
private Field name;
|
||||
|
||||
@JSONField(name = "民族")
|
||||
private Field nation; // 民族
|
||||
private Field nation;
|
||||
|
||||
@JSONField(name = "住址")
|
||||
private Field address; // 住址
|
||||
private Field address;
|
||||
|
||||
@JSONField(name = "公民身份号码")
|
||||
private Field citizenIdentification; // 身份证号
|
||||
private Field citizenIdentification;
|
||||
|
||||
@JSONField(name = "出生")
|
||||
private Field birth; // 出生日期
|
||||
private Field birth;
|
||||
|
||||
@JSONField(name = "性别")
|
||||
private Field gender; // 性别
|
||||
@JSONField(name = "失效日期")
|
||||
private Field expirationDate; // 失效日期
|
||||
@JSONField(name = "签发机关")
|
||||
private Field issuingAuthority; // 签发机关
|
||||
@JSONField(name = "签发日期")
|
||||
private Field issueDate; // 签发日期
|
||||
private Field gender;
|
||||
|
||||
/**
|
||||
* 身份证图片信息
|
||||
*/
|
||||
private SysOssVo image;
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
package org.dromara.contractor.controller.app;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.BankData;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.WordsResult;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.domain.dto.constructionuser.SubConstructionUserAuthenticationReq;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserVo;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 施工人员app
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-07-23 18:44
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/app/contractor/constructionUser")
|
||||
public class SubConstructionUserAppController {
|
||||
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
/**
|
||||
* 获取当前登录用户实名信息
|
||||
*/
|
||||
@GetMapping("/loginUser")
|
||||
public R<SubConstructionUserVo> getLoginUserInfo() {
|
||||
SubConstructionUser constructionUser = constructionUserService.getBySysUserId(LoginHelper.getUserId());
|
||||
return R.ok(constructionUserService.getVo(constructionUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据身份证图片获取身份证信息
|
||||
*/
|
||||
@Log(title = "施工人员", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/idCard")
|
||||
public R<WordsResult> getIdCardMessage(@RequestParam("file") MultipartFile file, String idCardSide) {
|
||||
return R.ok(constructionUserService.getIdCardMessageByPic(file, idCardSide));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据银行卡图片获取银行卡信息
|
||||
*/
|
||||
@Log(title = "施工人员", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/bankCard")
|
||||
public R<BankData> getBankCardMessage(@RequestParam("file") MultipartFile file) {
|
||||
return R.ok(constructionUserService.getBankCardMessageByPic(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 施工人员实名认证
|
||||
*/
|
||||
@Log(title = "施工人员", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/authentication")
|
||||
public R<Long> insertByAuthentication(@RequestParam("file") MultipartFile file, @Validated SubConstructionUserAuthenticationReq req) {
|
||||
return R.ok(constructionUserService.insertByAuthentication(file, req));
|
||||
}
|
||||
|
||||
/**
|
||||
* 施工人员人脸比对
|
||||
*/
|
||||
@Log(title = "施工人员", businessType = BusinessType.OTHER)
|
||||
@PostMapping("/face/comparison")
|
||||
public R<Boolean> faceComparison(@RequestParam("file") MultipartFile file) {
|
||||
return R.ok(constructionUserService.faceComparison(file));
|
||||
}
|
||||
|
||||
}
|
@ -34,6 +34,11 @@ public class SubConstructionUser extends BaseEntity {
|
||||
*/
|
||||
private String facePic;
|
||||
|
||||
/**
|
||||
* 系统用户id
|
||||
*/
|
||||
private Long sysUserId;
|
||||
|
||||
/**
|
||||
* 人员姓名
|
||||
*/
|
||||
|
@ -0,0 +1,150 @@
|
||||
package org.dromara.contractor.domain.dto.constructionuser;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.constant.RegexConstants;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 10:48
|
||||
*/
|
||||
@Data
|
||||
public class SubConstructionUserAuthenticationReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1001024234468070802L;
|
||||
|
||||
/**
|
||||
* 人员姓名
|
||||
*/
|
||||
@NotBlank(message = "人员姓名不能为空")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 项目id
|
||||
*/
|
||||
@NotNull(message = "项目id不能为空")
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 分包公司id
|
||||
*/
|
||||
@NotNull(message = "分包公司id不能为空")
|
||||
private Long contractorId;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@NotBlank(message = "联系电话不能为空")
|
||||
@Pattern(regexp = RegexConstants.MOBILE, message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 0:保密 1:男 2女
|
||||
*/
|
||||
@NotBlank(message = "性别不能为空")
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 民族
|
||||
*/
|
||||
@NotBlank(message = "民族不能为空")
|
||||
private String nation;
|
||||
|
||||
/**
|
||||
* 身份证正面图片
|
||||
*/
|
||||
@NotBlank(message = "身份证正面图片不能为空")
|
||||
private String sfzFrontPic;
|
||||
|
||||
/**
|
||||
* 身份证反面图片
|
||||
*/
|
||||
@NotBlank(message = "身份证反面图片不能为空")
|
||||
private String sfzBackPic;
|
||||
|
||||
/**
|
||||
* 身份证号码
|
||||
*/
|
||||
@NotBlank(message = "身份证号码不能为空")
|
||||
private String sfzNumber;
|
||||
|
||||
/**
|
||||
* 身份证有效开始期
|
||||
*/
|
||||
@NotBlank(message = "身份证有效开始期不能为空")
|
||||
private String sfzStart;
|
||||
|
||||
/**
|
||||
* 身份证有效结束期
|
||||
*/
|
||||
@NotBlank(message = "身份证有效结束期不能为空")
|
||||
private String sfzEnd;
|
||||
|
||||
/**
|
||||
* 身份证地址
|
||||
*/
|
||||
@NotBlank(message = "身份证地址不能为空")
|
||||
private String sfzSite;
|
||||
|
||||
/**
|
||||
* 身份证出生日期
|
||||
*/
|
||||
@NotBlank(message = "身份证出生日期不能为空")
|
||||
private String sfzBirth;
|
||||
|
||||
/**
|
||||
* 籍贯
|
||||
*/
|
||||
@NotBlank(message = "籍贯不能为空")
|
||||
private String nativePlace;
|
||||
|
||||
/**
|
||||
* 银行卡图片
|
||||
*/
|
||||
@NotBlank(message = "银行卡图片不能为空")
|
||||
private String yhkPic;
|
||||
|
||||
/**
|
||||
* 银行卡号
|
||||
*/
|
||||
@NotBlank(message = "银行卡号不能为空")
|
||||
private String yhkNumber;
|
||||
|
||||
/**
|
||||
* 开户行
|
||||
*/
|
||||
private String yhkOpeningBank;
|
||||
|
||||
/**
|
||||
* 持卡人
|
||||
*/
|
||||
private String yhkCardholder;
|
||||
|
||||
/**
|
||||
* 工种
|
||||
*/
|
||||
@NotBlank(message = "工种不能为空")
|
||||
private String typeOfWork;
|
||||
|
||||
/**
|
||||
* 工资计量单位
|
||||
*/
|
||||
private String wageMeasureUnit;
|
||||
|
||||
/**
|
||||
* 特种工作证图片
|
||||
*/
|
||||
private String specialWorkPic;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.BankData;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.WordsResult;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.domain.dto.constructionuser.*;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserVo;
|
||||
@ -12,6 +14,7 @@ import org.dromara.contractor.domain.exportvo.BusConstructionUserExportVo;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserAttendanceMonthVo;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserAttendanceTotalVo;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserGisVo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -172,4 +175,47 @@ public interface ISubConstructionUserService extends IService<SubConstructionUse
|
||||
Page<SubConstructionUserAttendanceTotalVo> getAttendanceTotalVoPage(SubConstructionUserAttendanceQueryReq req,
|
||||
PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 获取施工人员身份证信息
|
||||
*
|
||||
* @param file 图片文件
|
||||
* @param idCardSide 身份证正反面(front/back)
|
||||
* @return 身份证信息
|
||||
*/
|
||||
WordsResult getIdCardMessageByPic(MultipartFile file, String idCardSide);
|
||||
|
||||
|
||||
/**
|
||||
* 获取施工人员银行卡信息
|
||||
*
|
||||
* @param file 图片文件
|
||||
* @return 银行卡信息
|
||||
*/
|
||||
BankData getBankCardMessageByPic(MultipartFile file);
|
||||
|
||||
/**
|
||||
* 实名认证
|
||||
*
|
||||
* @param file 人脸图片
|
||||
* @param req 身份信息认证对象
|
||||
* @return 是否认证成功
|
||||
*/
|
||||
Long insertByAuthentication(MultipartFile file, SubConstructionUserAuthenticationReq req);
|
||||
|
||||
/**
|
||||
* 人脸识别
|
||||
*
|
||||
* @param file 图片文件
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
Boolean faceComparison(MultipartFile file);
|
||||
|
||||
/**
|
||||
* 根据系统用户id查询施工人员
|
||||
*
|
||||
* @param sysUserId 系统用户id
|
||||
* @return 施工人员
|
||||
*/
|
||||
SubConstructionUser getBySysUserId(Long sysUserId);
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.dromara.contractor.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import cn.hutool.core.util.IdcardUtil;
|
||||
import cn.hutool.core.util.PhoneUtil;
|
||||
@ -18,8 +19,17 @@ import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.oss.core.OssClient;
|
||||
import org.dromara.common.oss.exception.OssException;
|
||||
import org.dromara.common.oss.factory.OssFactory;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.utils.IdCardEncryptorUtil;
|
||||
import org.dromara.common.utils.baiduUtil.BaiDuFace;
|
||||
import org.dromara.common.utils.baiduUtil.BaiDuOCR;
|
||||
import org.dromara.common.utils.baiduUtil.entity.face.HumanFaceReq;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.BankData;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.OcrReq;
|
||||
import org.dromara.common.utils.baiduUtil.entity.ocr.WordsResult;
|
||||
import org.dromara.contractor.constant.SubConstructionUserConstant;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.domain.SubConstructionUserFile;
|
||||
@ -48,7 +58,12 @@ import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.YearMonth;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@ -99,6 +114,12 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
|
||||
@Resource
|
||||
private IdCardEncryptorUtil idCardEncryptorUtil;
|
||||
|
||||
@Resource
|
||||
private BaiDuOCR baiDuOCR;
|
||||
|
||||
@Resource
|
||||
private BaiDuFace baiDuFace;
|
||||
|
||||
/**
|
||||
* 查询施工人员
|
||||
*
|
||||
@ -956,4 +977,193 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
|
||||
return constructionUserAttendanceTotalPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取施工人员身份证信息
|
||||
*
|
||||
* @param file 图片文件
|
||||
* @param idCardSide 身份证正反面(front/back)
|
||||
* @return 身份证信息
|
||||
*/
|
||||
@Override
|
||||
public WordsResult getIdCardMessageByPic(MultipartFile file, String idCardSide) {
|
||||
if (file == null) {
|
||||
throw new ServiceException("请上传图片", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (StringUtils.isBlank(idCardSide)) {
|
||||
throw new ServiceException("请选择身份证正反面", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (!"front".equals(idCardSide) && !"back".equals(idCardSide)) {
|
||||
throw new ServiceException("请选择正确的身份证正反面", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
String base64;
|
||||
try {
|
||||
// 获取文件字节数组
|
||||
byte[] bytes = file.getBytes();
|
||||
// Base64 编码
|
||||
base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("图片转换失败,请重新上传");
|
||||
}
|
||||
OcrReq request = new OcrReq();
|
||||
request.setImage(base64);
|
||||
request.setIdCardSide(idCardSide);
|
||||
WordsResult wordsResult = baiDuOCR.idCardOCR(request);
|
||||
if (wordsResult == null) {
|
||||
throw new ServiceException("识别失败,请重新上传");
|
||||
}
|
||||
// 获取数据成功,保存图片信息
|
||||
SysOssVo upload = ossService.upload(file);
|
||||
wordsResult.setImage(upload);
|
||||
return wordsResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取施工人员银行卡信息
|
||||
*
|
||||
* @param file 图片文件
|
||||
* @return 银行卡信息
|
||||
*/
|
||||
@Override
|
||||
public BankData getBankCardMessageByPic(MultipartFile file) {
|
||||
if (file == null) {
|
||||
throw new ServiceException("请上传图片", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
String base64;
|
||||
try {
|
||||
// 获取文件字节数组
|
||||
byte[] bytes = file.getBytes();
|
||||
// Base64 编码
|
||||
base64 = Base64.getEncoder().encodeToString(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("图片转换失败,请重新上传");
|
||||
}
|
||||
OcrReq request = new OcrReq();
|
||||
request.setImage(base64);
|
||||
request.setDetectPhoto(false);
|
||||
BankData res = baiDuOCR.bankCardOCRRecognition(request).getRes();
|
||||
if (res == null) {
|
||||
throw new ServiceException("识别失败,请重新上传");
|
||||
}
|
||||
// 获取数据成功,保存图片信息
|
||||
SysOssVo upload = ossService.upload(file);
|
||||
res.setImage(upload);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实名认证
|
||||
*
|
||||
* @param file 人脸图片
|
||||
* @param req 身份信息认证对象
|
||||
* @return 是否认证成功
|
||||
*/
|
||||
@Override
|
||||
public Long insertByAuthentication(MultipartFile file, SubConstructionUserAuthenticationReq req) {
|
||||
// 先进行人脸识别
|
||||
if (file == null) {
|
||||
throw new ServiceException("请上传图片", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
String base64;
|
||||
try {
|
||||
// 获取文件字节数组
|
||||
byte[] bytes = file.getBytes();
|
||||
// Base64 编码
|
||||
base64 = URLEncoder.encode(Base64.getEncoder().encodeToString(bytes), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("图片转换失败,请重新上传");
|
||||
}
|
||||
HumanFaceReq request = new HumanFaceReq();
|
||||
request.setImage(base64);
|
||||
baiDuFace.humanFace(request);
|
||||
// 人脸识别成功,保存人脸数据
|
||||
SubConstructionUser user = new SubConstructionUser();
|
||||
BeanUtils.copyProperties(req, user);
|
||||
SysOssVo upload = ossService.upload(file);
|
||||
user.setFacePic(upload.getOssId().toString());
|
||||
// 关联系统用户
|
||||
Long userId = LoginHelper.getUserId();
|
||||
// 判断当前系统用户是否已关联
|
||||
Long count = this.lambdaQuery()
|
||||
.eq(SubConstructionUser::getSysUserId, userId)
|
||||
.count();
|
||||
if (count > 0) {
|
||||
throw new ServiceException("当前用户已关联施工人员信息");
|
||||
}
|
||||
user.setSysUserId(userId);
|
||||
// 保存施工人员信息
|
||||
boolean save = this.save(user);
|
||||
if (!save) {
|
||||
throw new ServiceException("施工人员信息保存失败");
|
||||
}
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 人脸识别
|
||||
*
|
||||
* @param file 图片文件
|
||||
* @return 是否匹配成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean faceComparison(MultipartFile file) {
|
||||
if (file == null) {
|
||||
throw new ServiceException("请上传图片", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
String reqBase64;
|
||||
try {
|
||||
// 获取文件字节数组
|
||||
byte[] bytes = file.getBytes();
|
||||
// Base64 编码
|
||||
reqBase64 = URLEncoder.encode(Base64.getEncoder().encodeToString(bytes), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("图片转换失败,请重新上传");
|
||||
}
|
||||
HumanFaceReq request = new HumanFaceReq();
|
||||
request.setImage(reqBase64);
|
||||
Long userId = LoginHelper.getUserId();
|
||||
SubConstructionUser constructionUser = this.getById(userId);
|
||||
if (constructionUser == null || StringUtils.isBlank(constructionUser.getFacePic())) {
|
||||
throw new ServiceException("未进行实名认证");
|
||||
}
|
||||
String facePic = constructionUser.getFacePic();
|
||||
SysOssVo sysOssVo = ossService.getById(Long.parseLong(facePic));
|
||||
if (sysOssVo == null) {
|
||||
throw new ServiceException("未进行实名认证");
|
||||
}
|
||||
// 获取文件输入流
|
||||
OssClient storage = OssFactory.instance(sysOssVo.getService());
|
||||
String path = sysOssVo.getUrl();
|
||||
String faceBase64;
|
||||
try (InputStream is = storage.getObjectContent(path)) {
|
||||
byte[] bytes = IoUtil.readBytes(is);
|
||||
// Base64 编码
|
||||
faceBase64 = URLEncoder.encode(Base64.getEncoder().encodeToString(bytes), StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
// 针对单个文件处理异常,可以选择记录日志或终止处理
|
||||
throw new OssException("处理文件[" + path + "]失败,错误信息: " + e.getMessage());
|
||||
}
|
||||
HumanFaceReq faceReq = new HumanFaceReq();
|
||||
faceReq.setImage(faceBase64);
|
||||
List<HumanFaceReq> list = List.of(request, faceReq);
|
||||
double comparison = baiDuFace.comparison(list);
|
||||
return comparison > 80;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据系统用户id查询施工人员
|
||||
*
|
||||
* @param sysUserId 系统用户id
|
||||
* @return 施工人员
|
||||
*/
|
||||
@Override
|
||||
public SubConstructionUser getBySysUserId(Long sysUserId) {
|
||||
SubConstructionUser constructionUser = this.lambdaQuery()
|
||||
.eq(SubConstructionUser::getSysUserId, sysUserId)
|
||||
.one();
|
||||
if (constructionUser == null) {
|
||||
throw new ServiceException("实名认证信息不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return constructionUser;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
package org.dromara.project.controller.app;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceByDayReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceByMonthReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
|
||||
import org.dromara.project.service.IBusAttendanceService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 考勤app
|
||||
*
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 11:53
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/app/project/attendance")
|
||||
public class BusAttendanceAppController {
|
||||
|
||||
@Resource
|
||||
private IBusAttendanceService attendanceService;
|
||||
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
/**
|
||||
* 获取当前登录用户的考勤列表
|
||||
*
|
||||
* @param req 查询参数
|
||||
* @return 考勤列表
|
||||
*/
|
||||
@GetMapping("/list/loginUser")
|
||||
public R<List<BusAttendanceVo>> listLoginUser(BusAttendanceByDayReq req) {
|
||||
return R.ok(attendanceService.getDayByUserId(LoginHelper.getUserId(), req.getClockDate()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户月份考勤列表
|
||||
*/
|
||||
@GetMapping("/list/month/loginUser")
|
||||
public R<List<BusAttendanceMonthByUserIdVo>> listAttendanceMonthListByUserId(BusAttendanceByMonthReq req) {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
SubConstructionUser constructionUser = constructionUserService.getBySysUserId(userId);
|
||||
BusAttendanceMonthByUserIdReq dto = new BusAttendanceMonthByUserIdReq();
|
||||
dto.setUserId(constructionUser.getId());
|
||||
dto.setClockMonth(req.getClockMonth());
|
||||
return R.ok(attendanceService.listAttendanceMonthListByUserId(dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 人脸坐标打卡
|
||||
*/
|
||||
@PostMapping("/punch/card/face")
|
||||
public R<Boolean> punchCardByFace(@RequestPart("file") MultipartFile file, BusAttendancePunchCardByFaceReq req) {
|
||||
return R.ok(attendanceService.punchCardByFace(file, req));
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package org.dromara.project.controller.app;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveQueryReq;
|
||||
import org.dromara.project.domain.vo.leave.BusLeaveVo;
|
||||
import org.dromara.project.service.IBusLeaveService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 14:51
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/app/project/leave")
|
||||
public class BusLeaveAppController {
|
||||
|
||||
@Resource
|
||||
private IBusLeaveService leaveService;
|
||||
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
/**
|
||||
* 查询当前登录用户请假申请列表
|
||||
*/
|
||||
@GetMapping("/list/loginUser")
|
||||
public TableDataInfo<BusLeaveVo> listByLoginUser(BusLeaveQueryReq req, PageQuery pageQuery) {
|
||||
SubConstructionUser constructionUser = constructionUserService.getBySysUserId(LoginHelper.getUserId());
|
||||
req.setUserId(constructionUser.getId());
|
||||
return leaveService.queryPageList(req, pageQuery);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.dromara.project.controller.app;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.project.domain.vo.project.BusProjectContractorListVo;
|
||||
import org.dromara.project.service.IBusProjectService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 15:14
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/app/project/project")
|
||||
public class BusProjectAppController {
|
||||
|
||||
@Resource
|
||||
private IBusProjectService projectService;
|
||||
|
||||
/**
|
||||
* 查询项目以及项目下的分包公司列表
|
||||
*/
|
||||
@GetMapping("/list/project/contractorList")
|
||||
public R<List<BusProjectContractorListVo>> listProjectContractorList() {
|
||||
return R.ok(projectService.queryProjectContractorList());
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package org.dromara.project.controller.app;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.project.domain.dto.reissuecard.BusReissueCardQueryReq;
|
||||
import org.dromara.project.domain.vo.reissuecard.BusReissueCardVo;
|
||||
import org.dromara.project.service.IBusReissueCardService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 14:51
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/app/project/reissueCard")
|
||||
public class BusReissueCardAppController {
|
||||
|
||||
@Resource
|
||||
private IBusReissueCardService reissueCardService;
|
||||
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
/**
|
||||
* 查询当前登录用户补卡申请列表
|
||||
*/
|
||||
@GetMapping("/list/loginUser")
|
||||
public TableDataInfo<BusReissueCardVo> listByLoginUser(BusReissueCardQueryReq req, PageQuery pageQuery) {
|
||||
SubConstructionUser constructionUser = constructionUserService.getBySysUserId(LoginHelper.getUserId());
|
||||
req.setUserId(constructionUser.getId());
|
||||
return reissueCardService.queryPageList(req, pageQuery);
|
||||
}
|
||||
}
|
@ -124,6 +124,11 @@ public class BusLeave extends BaseEntity {
|
||||
*/
|
||||
private Long teamId;
|
||||
|
||||
/**
|
||||
* 分包公司id
|
||||
*/
|
||||
private Long contractorId;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
@ -0,0 +1,25 @@
|
||||
package org.dromara.project.domain.dto.attendance;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 12:28
|
||||
*/
|
||||
@Data
|
||||
public class BusAttendanceByDayReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1800816047235448533L;
|
||||
|
||||
/**
|
||||
* 打卡日期
|
||||
*/
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
private Date clockDate;
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.dromara.project.domain.dto.attendance;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 13:51
|
||||
*/
|
||||
@Data
|
||||
public class BusAttendanceByMonthReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 4257724767091558549L;
|
||||
|
||||
/**
|
||||
* 打卡月份
|
||||
*/
|
||||
private String clockMonth;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package org.dromara.project.domain.dto.attendance;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-28 18:43
|
||||
*/
|
||||
@Data
|
||||
public class BusAttendancePunchCardByFaceReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -8906344299387307633L;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
private String lng;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
private String lat;
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package org.dromara.project.domain.dto.leave;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 15:28
|
||||
*/
|
||||
@Data
|
||||
public class BusLeaveGangerReviewReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 6403690469809708710L;
|
||||
|
||||
/**
|
||||
* 主键id
|
||||
*/
|
||||
@NotNull(message = "主键不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 管理员意见(1未读 2同意 3拒绝)
|
||||
*/
|
||||
@NotNull(message = "管理员意见不能为空")
|
||||
private String gangerOpinion;
|
||||
|
||||
/**
|
||||
* 管理员说明
|
||||
*/
|
||||
private String gangerExplain;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.dromara.project.domain.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 15:52
|
||||
*/
|
||||
@Getter
|
||||
public enum SubConstructionUserRoleEnum {
|
||||
|
||||
NORMAL("普通用户", "1"),
|
||||
ADMIN("管理员", "2");
|
||||
|
||||
private final String text;
|
||||
|
||||
private final String value;
|
||||
|
||||
SubConstructionUserRoleEnum(String text, String value) {
|
||||
this.text = text;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
@ -7,12 +7,15 @@ import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.project.domain.BusAttendance;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryTwoWeekReq;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeekVo;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -96,4 +99,22 @@ public interface IBusAttendanceService extends IService<BusAttendance> {
|
||||
*/
|
||||
Page<BusAttendanceVo> getVoPage(Page<BusAttendance> attendancePage);
|
||||
|
||||
/**
|
||||
* 根据系统用户id和日期查询考勤
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @param clockDate 日期
|
||||
* @return 考勤
|
||||
*/
|
||||
List<BusAttendanceVo> getDayByUserId(Long userId, Date clockDate);
|
||||
|
||||
/**
|
||||
* 人脸打卡
|
||||
*
|
||||
* @param file 人脸图片
|
||||
* @param req 人脸打卡请求
|
||||
* @return 是否打卡成功
|
||||
*/
|
||||
Boolean punchCardByFace(MultipartFile file, BusAttendancePunchCardByFaceReq req);
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.project.domain.BusLeave;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveGangerReviewReq;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveManagerReviewReq;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveQueryReq;
|
||||
import org.dromara.project.domain.vo.leave.BusLeaveVo;
|
||||
@ -46,6 +47,14 @@ public interface IBusLeaveService extends IService<BusLeave> {
|
||||
*/
|
||||
List<BusLeaveVo> queryList(BusLeaveQueryReq req);
|
||||
|
||||
/**
|
||||
* 班长审核施工人员请假申请
|
||||
*
|
||||
* @param req 班长审核施工人员请假申请
|
||||
* @return 是否审核成功
|
||||
*/
|
||||
Boolean gangerReview(BusLeaveGangerReviewReq req);
|
||||
|
||||
/**
|
||||
* 管理员审核施工人员请假申请
|
||||
*
|
||||
|
@ -13,30 +13,41 @@ import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.domain.GeoPoint;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.utils.JSTUtil;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.project.domain.BusAttendance;
|
||||
import org.dromara.project.domain.BusProject;
|
||||
import org.dromara.project.domain.BusProjectPunchrange;
|
||||
import org.dromara.project.domain.BusProjectTeamMember;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryTwoWeekReq;
|
||||
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
|
||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
|
||||
import org.dromara.project.domain.enums.BusAttendanceStatusEnum;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryReq;
|
||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryTwoWeekReq;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeekVo;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceListByDay;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
|
||||
import org.dromara.project.mapper.BusAttendanceMapper;
|
||||
import org.dromara.project.service.*;
|
||||
import org.dromara.system.domain.vo.SysOssVo;
|
||||
import org.dromara.system.service.ISysOssService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.YearMonth;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -50,9 +61,15 @@ import java.util.stream.Collectors;
|
||||
public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, BusAttendance>
|
||||
implements IBusAttendanceService {
|
||||
|
||||
@Resource
|
||||
private ISysOssService ossService;
|
||||
|
||||
@Resource
|
||||
private IBusProjectService projectService;
|
||||
|
||||
@Resource
|
||||
private IBusProjectPunchrangeService projectPunchrangeService;
|
||||
|
||||
@Resource
|
||||
private IBusProjectTeamMemberService projectTeamMemberService;
|
||||
|
||||
@ -404,4 +421,141 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
||||
return attendanceVoPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据系统用户id和日期查询考勤
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @param clockDate 日期
|
||||
* @return 考勤
|
||||
*/
|
||||
@Override
|
||||
public List<BusAttendanceVo> getDayByUserId(Long userId, Date clockDate) {
|
||||
SubConstructionUser constructionUser = constructionUserService.getBySysUserId(userId);
|
||||
// 当日期未指定时,默认为今天
|
||||
if (clockDate == null) {
|
||||
clockDate = DateUtils.parseDateTime(FormatsType.YYYY_MM_DD, DateUtils.getDate());
|
||||
}
|
||||
LambdaQueryWrapper<BusAttendance> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(BusAttendance::getUserId, constructionUser.getId())
|
||||
.eq(BusAttendance::getClockDate, clockDate);
|
||||
return this.list(queryWrapper).stream().map(this::getVo).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 人脸打卡
|
||||
*
|
||||
* @param file 人脸图片
|
||||
* @param req 人脸打卡请求
|
||||
* @return 是否打卡成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean punchCardByFace(MultipartFile file, BusAttendancePunchCardByFaceReq req) {
|
||||
// 获取当前用户
|
||||
Long userId = LoginHelper.getUserId();
|
||||
synchronized (userId.toString().intern()) {
|
||||
// 记录当前打卡时间
|
||||
LocalTime now = LocalTime.now();
|
||||
Date nowDate = new Date();
|
||||
// 获取坐标
|
||||
String lat = req.getLat();
|
||||
String lng = req.getLng();
|
||||
// 校验用户是否合法
|
||||
SubConstructionUser constructionUser = constructionUserService.getBySysUserId(userId);
|
||||
Long projectId = constructionUser.getProjectId();
|
||||
if (projectId == null) {
|
||||
throw new ServiceException("当前用户未加入项目", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
BusProject project = projectService.getById(projectId);
|
||||
Long teamId = constructionUser.getTeamId();
|
||||
if (teamId == null) {
|
||||
throw new ServiceException("当前用户未加入班组", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
final String status = "1";
|
||||
if (constructionUser.getStatus().equals(status)) {
|
||||
throw new ServiceException("当前用户已离职", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
final String noClock = "1";
|
||||
if (constructionUser.getClock().equals(noClock)) {
|
||||
throw new ServiceException("当前用户已被禁止打卡", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// 判断用户是否已经被拉黑
|
||||
constructionBlacklistService.validUserInBlacklist(constructionUser.getId(), projectId);
|
||||
// todo 地理位置校验
|
||||
List<BusProjectPunchrange> punchranges = projectPunchrangeService.lambdaQuery()
|
||||
.eq(BusProjectPunchrange::getProjectId, projectId)
|
||||
.list();
|
||||
if (CollUtil.isEmpty(punchranges)) {
|
||||
throw new ServiceException("项目未配置考勤范围", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
List<String> punchRangeList = punchranges.stream().map(BusProjectPunchrange::getPunchRange).toList();
|
||||
List<GeoPoint> matchingRange = JSTUtil.findMatchingRange(lat, lng, punchRangeList);
|
||||
if (matchingRange == null) {
|
||||
throw new ServiceException("打卡位置不在范围内", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// 进行人脸比对
|
||||
Boolean result = constructionUserService.faceComparison(file);
|
||||
if (!result) {
|
||||
throw new ServiceException("人脸识别失败,请重新识别", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// 判断打卡状态
|
||||
String punchRange = project.getPunchRange();
|
||||
if (punchRange == null) {
|
||||
throw new ServiceException("未设置打卡时间范围", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
String[] time = punchRange.split(",");
|
||||
// 解析字符串为 LocalTime
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
|
||||
LocalTime startTime = LocalTime.parse(time[0], formatter);
|
||||
LocalTime endTime = LocalTime.parse(time[1], formatter);
|
||||
Date date = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
// 判断当前用户打卡状态
|
||||
List<BusAttendance> attendances = this.lambdaQuery()
|
||||
.eq(BusAttendance::getUserId, userId)
|
||||
.eq(BusAttendance::getClockDate, date)
|
||||
.list();
|
||||
BusAttendance attendance = new BusAttendance();
|
||||
if (CollUtil.isEmpty(attendances)) {
|
||||
// 上班打卡
|
||||
attendance.setCommuter(BusAttendanceCommuterEnum.CLOCKIN.getValue());
|
||||
// 上传人脸照
|
||||
SysOssVo upload = ossService.upload(file);
|
||||
attendance.setFacePic(upload.getOssId().toString());
|
||||
// 判断是否为迟到
|
||||
if (now.isAfter(startTime)) {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
|
||||
} else {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
||||
}
|
||||
} else if (attendances.size() == 1 && attendances.getFirst().getCommuter().equals(BusAttendanceCommuterEnum.CLOCKIN.getValue())) {
|
||||
// 下班打卡
|
||||
attendance.setCommuter(BusAttendanceCommuterEnum.CLOCKOUT.getValue());
|
||||
// 判断是否为早退
|
||||
if (now.isBefore(endTime)) {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVEEARLY.getValue());
|
||||
} else {
|
||||
attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
|
||||
}
|
||||
} else if (attendances.size() == 2) {
|
||||
throw new ServiceException("当前已完成打卡,请勿重复提交", HttpStatus.BAD_REQUEST);
|
||||
} else {
|
||||
throw new ServiceException("打卡异常,请联系管理员", HttpStatus.ERROR);
|
||||
}
|
||||
// 填充信息
|
||||
attendance.setUserId(userId);
|
||||
attendance.setUserName(constructionUser.getUserName());
|
||||
attendance.setProjectId(projectId);
|
||||
attendance.setClockDate(date);
|
||||
attendance.setClockTime(nowDate);
|
||||
// 记录打卡坐标
|
||||
attendance.setLat(lat);
|
||||
attendance.setLng(lng);
|
||||
attendance.setPunchRange(matchingRange.toString());
|
||||
// 上传人脸照
|
||||
SysOssVo upload = ossService.upload(file);
|
||||
attendance.setFacePic(upload.getOssId().toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,19 +12,23 @@ import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.contractor.service.ISubContractorService;
|
||||
import org.dromara.project.domain.BusAttendance;
|
||||
import org.dromara.project.domain.BusLeave;
|
||||
import org.dromara.project.domain.BusProjectTeam;
|
||||
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
|
||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
|
||||
import org.dromara.project.domain.enums.BusOpinionStatusEnum;
|
||||
import org.dromara.project.domain.enums.BusReviewStatusEnum;
|
||||
import org.dromara.project.domain.BusProjectTeamMember;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveGangerReviewReq;
|
||||
import org.dromara.project.domain.enums.*;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveManagerReviewReq;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveQueryReq;
|
||||
import org.dromara.project.domain.vo.leave.BusLeaveVo;
|
||||
import org.dromara.project.mapper.BusLeaveMapper;
|
||||
import org.dromara.project.service.IBusAttendanceService;
|
||||
import org.dromara.project.service.IBusLeaveService;
|
||||
import org.dromara.project.service.IBusProjectTeamMemberService;
|
||||
import org.dromara.project.service.IBusProjectTeamService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@ -52,6 +56,15 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
|
||||
@Resource
|
||||
private IBusAttendanceService attendanceService;
|
||||
|
||||
@Resource
|
||||
private IBusProjectTeamMemberService projectTeamMemberService;
|
||||
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
@Resource
|
||||
private ISubContractorService contractorService;
|
||||
|
||||
/**
|
||||
* 查询施工人员请假申请
|
||||
*
|
||||
@ -88,6 +101,63 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 班长审核施工人员请假申请
|
||||
*
|
||||
* @param req 班长审核施工人员请假申请
|
||||
* @return 是否审核成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean gangerReview(BusLeaveGangerReviewReq req) {
|
||||
Long id = req.getId();
|
||||
String gangerOpinion = req.getGangerOpinion();
|
||||
// 判断该请假记录是否存在
|
||||
BusLeave oldLeave = this.getById(id);
|
||||
if (oldLeave == null) {
|
||||
throw new ServiceException("施工人员请假申请不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// 如果审核状态相同,则返回
|
||||
if (Objects.equals(gangerOpinion, oldLeave.getGangerOpinion())) {
|
||||
throw new ServiceException("请勿重复操作", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// 如果已经审核过,则返回
|
||||
if (BusOpinionStatusEnum.PASS.getValue().equals(oldLeave.getManagerOpinion())) {
|
||||
throw new ServiceException("该请假已审核通过,请勿重复操作", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// 获取当前用户实名信息
|
||||
SubConstructionUser constructionUser = constructionUserService.getBySysUserId(LoginHelper.getUserId());
|
||||
// 判断当前用户是否有权审核
|
||||
Long teamId = oldLeave.getTeamId();
|
||||
Long count = projectTeamMemberService.lambdaQuery()
|
||||
.eq(BusProjectTeamMember::getProjectId, oldLeave.getProjectId())
|
||||
.eq(BusProjectTeamMember::getMemberId, constructionUser.getId())
|
||||
.eq(BusProjectTeamMember::getTeamId, teamId)
|
||||
.eq(BusProjectTeamMember::getPostId, BusProjectTeamMemberPostEnum.FOREMAN.getValue())
|
||||
.count();
|
||||
if (count <= 0) {
|
||||
throw new ServiceException("您无权审核该请假申请", HttpStatus.FORBIDDEN);
|
||||
}
|
||||
BusLeave newLeave = new BusLeave();
|
||||
newLeave.setId(id);
|
||||
newLeave.setGangerOpinion(gangerOpinion);
|
||||
newLeave.setGangerExplain(req.getGangerExplain());
|
||||
newLeave.setGangerTime(new Date());
|
||||
newLeave.setRemark(req.getRemark());
|
||||
boolean result = this.updateById(newLeave);
|
||||
if (!result) {
|
||||
throw new ServiceException("更新班长审核操作失败", HttpStatus.ERROR);
|
||||
}
|
||||
// 向分包管理员发送通知
|
||||
List<SubConstructionUser> constructionAdminUsers = constructionUserService.lambdaQuery()
|
||||
.eq(SubConstructionUser::getContractorId, oldLeave.getContractorId())
|
||||
.eq(SubConstructionUser::getUserRole, SubConstructionUserRoleEnum.ADMIN.getValue())
|
||||
.list();
|
||||
if (CollUtil.isNotEmpty(constructionAdminUsers)){
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员审核施工人员请假申请
|
||||
*
|
||||
@ -114,9 +184,20 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
|
||||
throw new ServiceException("请等待班组长审核通过后再进行操作", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// todo 判断当前用户是否为项目管理员
|
||||
// 判断是否为分包公司管理员
|
||||
Long contractorId = oldLeave.getContractorId();
|
||||
SubConstructionUser constructionUser = constructionUserService.lambdaQuery()
|
||||
.eq(SubConstructionUser::getSysUserId, LoginHelper.getUserId())
|
||||
.eq(SubConstructionUser::getContractorId, contractorId)
|
||||
.eq(SubConstructionUser::getUserRole, SubConstructionUserRoleEnum.ADMIN.getValue())
|
||||
.one();
|
||||
if (constructionUser == null) {
|
||||
throw new ServiceException("您无权审核该请假申请", HttpStatus.FORBIDDEN);
|
||||
}
|
||||
// 填充默认值,更新数据
|
||||
BusLeave leave = new BusLeave();
|
||||
leave.setId(id);
|
||||
leave.setManagerId(constructionUser.getId());
|
||||
leave.setManagerOpinion(managerOpinion);
|
||||
leave.setManagerExplain(req.getManagerExplain());
|
||||
leave.setManagerTime(new Date());
|
||||
|
Reference in New Issue
Block a user