[add] app注册、登录、实名认证、人员展示、通知
This commit is contained in:
@ -8,6 +8,8 @@ import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* app菜单
|
||||
@ -85,6 +87,18 @@ public class AppMenu extends BaseEntity {
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 父菜单名称
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String parentName;
|
||||
|
||||
/**
|
||||
* 子菜单
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<AppMenu> children = new ArrayList<>();
|
||||
|
||||
@Serial
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
@ -3,6 +3,8 @@ package org.dromara.app.system.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.dromara.app.system.domain.AppMenu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @description 针对表【app_menu(app菜单)】的数据库操作Mapper
|
||||
@ -10,6 +12,14 @@ import org.dromara.app.system.domain.AppMenu;
|
||||
*/
|
||||
public interface AppMenuMapper extends BaseMapper<AppMenu> {
|
||||
|
||||
/**
|
||||
* 根据用户ID查询菜单
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 菜单列表
|
||||
*/
|
||||
List<AppMenu> selectMenuTreeByUserId(Long userId);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.dromara.app.system.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.dromara.app.system.domain.AppMenu;
|
||||
import org.dromara.app.system.domain.dto.menu.AppMenuCreateReq;
|
||||
@ -9,6 +8,8 @@ import org.dromara.app.system.domain.dto.menu.AppMenuQueryReq;
|
||||
import org.dromara.app.system.domain.dto.menu.AppMenuUpdateReq;
|
||||
import org.dromara.app.system.domain.vo.menu.AppMenuVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @description 针对表【app_menu(app菜单)】的数据库操作Service
|
||||
@ -16,6 +17,22 @@ import org.dromara.app.system.domain.vo.menu.AppMenuVo;
|
||||
*/
|
||||
public interface AppMenuService extends IService<AppMenu> {
|
||||
|
||||
/**
|
||||
* 根据id查询app菜单
|
||||
*
|
||||
* @param id app菜单id
|
||||
* @return app菜单视图
|
||||
*/
|
||||
AppMenuVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询菜单树信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 菜单列表
|
||||
*/
|
||||
List<AppMenu> selectMenuTreeByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 新增app菜单
|
||||
*
|
||||
@ -32,6 +49,14 @@ public interface AppMenuService extends IService<AppMenu> {
|
||||
*/
|
||||
Boolean updateByReq(AppMenuUpdateReq req);
|
||||
|
||||
/**
|
||||
* 校验app菜单名称唯一
|
||||
*
|
||||
* @param menu app菜单
|
||||
* @return 是否唯一
|
||||
*/
|
||||
Boolean checkMenuNameUnique(AppMenu menu);
|
||||
|
||||
/**
|
||||
* 获取app菜单视图
|
||||
*
|
||||
@ -49,11 +74,11 @@ public interface AppMenuService extends IService<AppMenu> {
|
||||
LambdaQueryWrapper<AppMenu> buildQueryWrapper(AppMenuQueryReq req);
|
||||
|
||||
/**
|
||||
* 获取app菜单分页对象视图
|
||||
* 获取app菜单列表对象视图
|
||||
*
|
||||
* @param menuPage app菜单分页对象
|
||||
* @return app菜单分页对象视图
|
||||
* @param menuList app菜单列表对象
|
||||
* @return app菜单列表对象视图
|
||||
*/
|
||||
Page<AppMenuVo> getVoPage(Page<AppMenu> menuPage);
|
||||
List<AppMenuVo> getVoList(List<AppMenu> menuList);
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
package org.dromara.app.system.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.dromara.app.system.domain.AppRole;
|
||||
import org.dromara.app.system.domain.dto.role.AppRoleQueryReq;
|
||||
import org.dromara.app.system.domain.vo.role.AppRoleVo;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
@ -10,4 +14,28 @@ import org.dromara.app.system.domain.AppRole;
|
||||
*/
|
||||
public interface AppRoleService extends IService<AppRole> {
|
||||
|
||||
/**
|
||||
* 获取app角色视图
|
||||
*
|
||||
* @param menu app角色
|
||||
* @return app角色视图
|
||||
*/
|
||||
AppRoleVo getVo(AppRole menu);
|
||||
|
||||
/**
|
||||
* 构建查询条件封装
|
||||
*
|
||||
* @param req 查询条件
|
||||
* @return 查询条件封装
|
||||
*/
|
||||
LambdaQueryWrapper<AppRole> buildQueryWrapper(AppRoleQueryReq req);
|
||||
|
||||
/**
|
||||
* 获取app角色视图列表
|
||||
*
|
||||
* @param rolePage app角色列表
|
||||
* @return app角色视图列表
|
||||
*/
|
||||
Page<AppRoleVo> getVoList(Page<AppRole> rolePage);
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.dromara.app.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.dromara.app.system.domain.AppMenu;
|
||||
import org.dromara.app.system.domain.dto.menu.AppMenuCreateReq;
|
||||
@ -11,10 +10,16 @@ import org.dromara.app.system.domain.dto.menu.AppMenuUpdateReq;
|
||||
import org.dromara.app.system.domain.vo.menu.AppMenuVo;
|
||||
import org.dromara.app.system.mapper.AppMenuMapper;
|
||||
import org.dromara.app.system.service.AppMenuService;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -26,6 +31,43 @@ import java.util.List;
|
||||
public class AppMenuServiceImpl extends ServiceImpl<AppMenuMapper, AppMenu>
|
||||
implements AppMenuService {
|
||||
|
||||
/**
|
||||
* 根据id查询app菜单
|
||||
*
|
||||
* @param id app菜单id
|
||||
* @return app菜单视图
|
||||
*/
|
||||
@Override
|
||||
public AppMenuVo queryById(Long id) {
|
||||
AppMenu menu = this.getById(id);
|
||||
if (menu == null) {
|
||||
throw new ServiceException("app菜单不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return this.getVo(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户ID查询菜单树信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 菜单列表
|
||||
*/
|
||||
@Override
|
||||
public List<AppMenu> selectMenuTreeByUserId(Long userId) {
|
||||
List<AppMenu> menus;
|
||||
if (LoginHelper.isSuperAdmin(userId)) {
|
||||
menus = this.lambdaQuery()
|
||||
.in(AppMenu::getMenuType, SystemConstants.TYPE_DIR, SystemConstants.TYPE_MENU)
|
||||
.eq(AppMenu::getStatus, SystemConstants.NORMAL)
|
||||
.orderByAsc(AppMenu::getParentId)
|
||||
.orderByAsc(AppMenu::getOrderNum)
|
||||
.list();
|
||||
} else {
|
||||
menus = baseMapper.selectMenuTreeByUserId(userId);
|
||||
}
|
||||
return getChildPerms(menus, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增app菜单
|
||||
*
|
||||
@ -48,6 +90,23 @@ public class AppMenuServiceImpl extends ServiceImpl<AppMenuMapper, AppMenu>
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验app菜单名称唯一
|
||||
*
|
||||
* @param menu app菜单
|
||||
* @return 是否唯一
|
||||
*/
|
||||
@Override
|
||||
public Boolean checkMenuNameUnique(AppMenu menu) {
|
||||
boolean exists = this.exists(
|
||||
new LambdaQueryWrapper<AppMenu>()
|
||||
.eq(AppMenu::getMenuName, menu.getMenuName())
|
||||
.eq(AppMenu::getParentId, menu.getParentId())
|
||||
.ne(ObjectUtil.isNotNull(menu.getId()), AppMenu::getId, menu.getId())
|
||||
);
|
||||
return !exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取app菜单视图
|
||||
*
|
||||
@ -88,29 +147,53 @@ public class AppMenuServiceImpl extends ServiceImpl<AppMenuMapper, AppMenu>
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取app菜单分页对象视图
|
||||
* 获取app菜单列表对象视图
|
||||
*
|
||||
* @param menuPage app菜单分页对象
|
||||
* @return app菜单分页对象视图
|
||||
* @param menuList app菜单列表对象
|
||||
* @return app菜单列表对象视图
|
||||
*/
|
||||
@Override
|
||||
public Page<AppMenuVo> getVoPage(Page<AppMenu> menuPage) {
|
||||
List<AppMenu> menuList = menuPage.getRecords();
|
||||
Page<AppMenuVo> menuVoPage = new Page<>(
|
||||
menuPage.getCurrent(),
|
||||
menuPage.getSize(),
|
||||
menuPage.getTotal());
|
||||
if (CollUtil.isEmpty(menuList)) {
|
||||
return menuVoPage;
|
||||
}
|
||||
List<AppMenuVo> menuVoList = menuList.stream().map(entity -> {
|
||||
public List<AppMenuVo> getVoList(List<AppMenu> menuList) {
|
||||
return menuList.stream().map(entity -> {
|
||||
AppMenuVo menuVo = new AppMenuVo();
|
||||
BeanUtils.copyProperties(entity, menuVo);
|
||||
return menuVo;
|
||||
}).toList();
|
||||
menuVoPage.setRecords(menuVoList);
|
||||
return menuVoPage;
|
||||
}
|
||||
|
||||
/* 根据父节点的ID获取所有子节点
|
||||
*
|
||||
* @param list 分类表
|
||||
* @param parentId 传入的父节点ID
|
||||
* @return String
|
||||
*/
|
||||
private List<AppMenu> getChildPerms(List<AppMenu> list, int parentId) {
|
||||
List<AppMenu> returnList = new ArrayList<>();
|
||||
for (AppMenu t : list) {
|
||||
// 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
|
||||
if (t.getParentId() == parentId) {
|
||||
recursionFn(list, t);
|
||||
returnList.add(t);
|
||||
}
|
||||
}
|
||||
return returnList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归列表
|
||||
*/
|
||||
private void recursionFn(List<AppMenu> list, AppMenu t) {
|
||||
// 得到子节点列表
|
||||
List<AppMenu> childList = StreamUtils.filter(list, n -> n.getParentId().equals(t.getId()));
|
||||
t.setChildren(childList);
|
||||
for (AppMenu tChild : childList) {
|
||||
// 判断是否有子节点
|
||||
if (list.stream().anyMatch(n -> n.getParentId().equals(tChild.getId()))) {
|
||||
recursionFn(list, tChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
package org.dromara.app.system.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.dromara.app.system.domain.AppRole;
|
||||
import org.dromara.app.system.domain.dto.role.AppRoleQueryReq;
|
||||
import org.dromara.app.system.domain.vo.role.AppRoleVo;
|
||||
import org.dromara.app.system.mapper.AppRoleMapper;
|
||||
import org.dromara.app.system.service.AppRoleService;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -15,6 +19,38 @@ import org.springframework.stereotype.Service;
|
||||
public class AppRoleServiceImpl extends ServiceImpl<AppRoleMapper, AppRole>
|
||||
implements AppRoleService {
|
||||
|
||||
/**
|
||||
* 获取app角色视图
|
||||
*
|
||||
* @param menu app角色
|
||||
* @return app角色视图
|
||||
*/
|
||||
@Override
|
||||
public AppRoleVo getVo(AppRole menu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件封装
|
||||
*
|
||||
* @param req 查询条件
|
||||
* @return 查询条件封装
|
||||
*/
|
||||
@Override
|
||||
public LambdaQueryWrapper<AppRole> buildQueryWrapper(AppRoleQueryReq req) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取app角色视图列表
|
||||
*
|
||||
* @param rolePage app角色列表
|
||||
* @return app角色视图列表
|
||||
*/
|
||||
@Override
|
||||
public Page<AppRoleVo> getVoList(Page<AppRole> rolePage) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -31,4 +31,26 @@
|
||||
icon,create_dept,remark,create_by,update_by,
|
||||
create_time,update_time
|
||||
</sql>
|
||||
|
||||
<select id="selectMenuTreeByUserId" parameterType="Long" resultType="org.dromara.app.system.domain.AppMenu">
|
||||
select distinct m.id,
|
||||
m.parent_id,
|
||||
m.menu_name,
|
||||
m.path,
|
||||
m.component,
|
||||
m.query_param,
|
||||
m.visible,
|
||||
m.status,
|
||||
m.perms,
|
||||
m.menu_type,
|
||||
m.icon,
|
||||
m.order_num,
|
||||
m.create_time
|
||||
from app_menu m
|
||||
left join app_role_menu rm on m.id = rm.menu_id and m.status = '0'
|
||||
left join app_role r on rm.role_id = r.id and r.status = '0'
|
||||
where m.menu_type in ('M', 'C')
|
||||
and r.id in (select role_id from sys_user_role where user_id = #{userId})
|
||||
order by m.parent_id, m.order_num
|
||||
</select>
|
||||
</mapper>
|
||||
|
@ -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,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;
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
package org.dromara.contractor.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.contractor.domain.dto.constructionuser.AppRealNameAuthenticationReq;
|
||||
import org.dromara.contractor.domain.dto.constructionuser.SubConstructionUserUpdateClockReq;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/23 16:48
|
||||
* @Version 1.0
|
||||
*/
|
||||
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/contractor/app")
|
||||
public class AppController extends BaseController {
|
||||
|
||||
private final ISubConstructionUserService constructionUserService;
|
||||
|
||||
@Log(title = "施工人员-实名认证", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/realNameAuthentication")
|
||||
public R<Void> realNameAuthentication(@RequestBody AppRealNameAuthenticationReq req) {
|
||||
// return toAjax(constructionUserService.updateClock(req));
|
||||
return toAjax(constructionUserService.realNameAuthentication(req));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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 userId;
|
||||
|
||||
/**
|
||||
* 人员姓名
|
||||
*/
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.dromara.contractor.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;
|
||||
}
|
||||
|
||||
}
|
@ -5,14 +5,16 @@ 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;
|
||||
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.bind.annotation.RequestBody;
|
||||
import org.dromara.contractor.domain.vo.constructionuser.SubConstructionUserVo;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -174,9 +176,46 @@ public interface ISubConstructionUserService extends IService<SubConstructionUse
|
||||
PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* @description app 实名认证
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/23 16:59
|
||||
**/
|
||||
Boolean realNameAuthentication(@RequestBody AppRealNameAuthenticationReq req);
|
||||
* 获取施工人员身份证信息
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 查询施工人员
|
||||
*
|
||||
@ -957,13 +978,192 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
|
||||
}
|
||||
|
||||
/**
|
||||
* @description app 实名认证
|
||||
* @author 铁憨憨
|
||||
* @date 2025/7/23 17:00
|
||||
**/
|
||||
* 获取施工人员身份证信息
|
||||
*
|
||||
* @param file 图片文件
|
||||
* @param idCardSide 身份证正反面(front/back)
|
||||
* @return 身份证信息
|
||||
*/
|
||||
@Override
|
||||
public Boolean realNameAuthentication(AppRealNameAuthenticationReq req) {
|
||||
return null;
|
||||
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::getUserId, userId)
|
||||
.count();
|
||||
if (count > 0) {
|
||||
throw new ServiceException("当前系统用户已关联施工人员信息");
|
||||
}
|
||||
user.setUserId(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::getUserId, sysUserId)
|
||||
.one();
|
||||
if (constructionUser == null) {
|
||||
throw new ServiceException("实名认证信息不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return constructionUser;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
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.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.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -60,7 +60,7 @@ public class BusAttendance extends BaseEntity {
|
||||
private Date clockDate;
|
||||
|
||||
/**
|
||||
* 打卡状态(1正常,2迟到,3早退,4缺勤,5补卡)
|
||||
* 打卡状态
|
||||
*/
|
||||
private String clockStatus;
|
||||
|
||||
|
@ -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,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;
|
||||
|
||||
}
|
@ -89,6 +89,11 @@ public class BusLeaveVo implements Serializable {
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 分包公司id
|
||||
*/
|
||||
private Long contractorId;
|
||||
|
||||
/**
|
||||
* 班组长id
|
||||
*/
|
||||
|
@ -13,6 +13,7 @@ import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeek
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
|
||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -96,4 +97,13 @@ 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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* 管理员审核施工人员请假申请
|
||||
*
|
||||
|
@ -19,18 +19,21 @@ import org.dromara.contractor.domain.SubConstructionUser;
|
||||
import org.dromara.contractor.service.ISubConstructionUserService;
|
||||
import org.dromara.project.domain.BusAttendance;
|
||||
import org.dromara.project.domain.BusProjectTeamMember;
|
||||
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.enums.BusAttendanceClockStatusEnum;
|
||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
|
||||
import org.dromara.project.domain.enums.BusAttendanceStatusEnum;
|
||||
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.project.service.IBusAttendanceService;
|
||||
import org.dromara.project.service.IBusConstructionBlacklistService;
|
||||
import org.dromara.project.service.IBusProjectService;
|
||||
import org.dromara.project.service.IBusProjectTeamMemberService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -404,4 +407,24 @@ 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,19 +12,24 @@ 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.domain.enums.SubConstructionUserRoleEnum;
|
||||
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.dto.leave.BusLeaveManagerReviewReq;
|
||||
import org.dromara.project.domain.dto.leave.BusLeaveQueryReq;
|
||||
import org.dromara.project.domain.enums.*;
|
||||
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;
|
||||
@ -48,6 +53,15 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
|
||||
@Resource
|
||||
private IBusProjectTeamService projectTeamService;
|
||||
|
||||
@Resource
|
||||
private IBusProjectTeamMemberService projectTeamMemberService;
|
||||
|
||||
@Resource
|
||||
private ISubConstructionUserService constructionUserService;
|
||||
|
||||
@Resource
|
||||
private ISubContractorService contractorService;
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
private IBusAttendanceService attendanceService;
|
||||
@ -88,6 +102,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 +185,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::getUserId, 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());
|
||||
|
@ -0,0 +1,90 @@
|
||||
package org.dromara.system.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 系统通知
|
||||
*
|
||||
* @TableName sys_notifications
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "sys_notifications")
|
||||
public class SysNotifications implements Serializable {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 接收通知的用户ID
|
||||
*/
|
||||
private Long recipientId;
|
||||
|
||||
/**
|
||||
* 发送通知的用户ID(系统通知则为null)
|
||||
*/
|
||||
private Long senderId;
|
||||
|
||||
/**
|
||||
* 通知类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 通知标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 通知的主要内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 通知状态(0未读 1已读)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 用户读取通知的时间
|
||||
*/
|
||||
private Date readAt;
|
||||
|
||||
/**
|
||||
* 点击通知后跳转的目标URL
|
||||
*/
|
||||
private String actionUrl;
|
||||
|
||||
/**
|
||||
* 通知附件
|
||||
*/
|
||||
private String file;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
@Serial
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package org.dromara.system.domain.dto.notifications;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 16:35
|
||||
*/
|
||||
@Data
|
||||
public class SysNotificationsCreateBatchReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 7786269708778844999L;
|
||||
|
||||
/**
|
||||
* 接收通知的用户ID
|
||||
*/
|
||||
private List<Long> recipientIds;
|
||||
|
||||
/**
|
||||
* 发送通知的用户ID(系统通知则为null)
|
||||
*/
|
||||
private Long senderId;
|
||||
|
||||
/**
|
||||
* 通知类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 通知标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 通知的主要内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 点击通知后跳转的目标URL
|
||||
*/
|
||||
private String actionUrl;
|
||||
|
||||
/**
|
||||
* 通知附件
|
||||
*/
|
||||
private String file;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.dromara.system.domain.dto.notifications;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 16:37
|
||||
*/
|
||||
@Data
|
||||
public class SysNotificationsQueryReq implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 2227178507874081416L;
|
||||
|
||||
/**
|
||||
* 接收通知的用户ID
|
||||
*/
|
||||
private Long recipientId;
|
||||
|
||||
/**
|
||||
* 发送通知的用户ID(系统通知则为null)
|
||||
*/
|
||||
private Long senderId;
|
||||
|
||||
/**
|
||||
* 通知类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 通知标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 通知的主要内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 通知状态(0未读 1已读)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package org.dromara.system.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-07-24 16:31
|
||||
*/
|
||||
@Data
|
||||
public class SysNotificationsVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -2755920219118735240L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 接收通知的用户ID
|
||||
*/
|
||||
private Long recipientId;
|
||||
|
||||
/**
|
||||
* 发送通知的用户ID(系统通知则为null)
|
||||
*/
|
||||
private Long senderId;
|
||||
|
||||
/**
|
||||
* 通知类型
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 通知标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 通知的主要内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 通知状态(0未读 1已读)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 用户读取通知的时间
|
||||
*/
|
||||
private Date readAt;
|
||||
|
||||
/**
|
||||
* 点击通知后跳转的目标URL
|
||||
*/
|
||||
private String actionUrl;
|
||||
|
||||
/**
|
||||
* 通知附件
|
||||
*/
|
||||
private String file;
|
||||
|
||||
/**
|
||||
* 附件列表
|
||||
*/
|
||||
private List<SysOssVo> fileVo;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package org.dromara.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.dromara.system.domain.SysNotifications;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @description 针对表【sys_notifications(系统通知)】的数据库操作Mapper
|
||||
* @createDate 2025-07-24 16:19:58
|
||||
*/
|
||||
public interface SysNotificationsMapper extends BaseMapper<SysNotifications> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,68 @@
|
||||
package org.dromara.system.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.dromara.system.domain.SysNotifications;
|
||||
import org.dromara.system.domain.dto.notifications.SysNotificationsCreateBatchReq;
|
||||
import org.dromara.system.domain.dto.notifications.SysNotificationsQueryReq;
|
||||
import org.dromara.system.domain.vo.SysNotificationsVo;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @description 针对表【sys_notifications(系统通知)】的数据库操作Service
|
||||
* @createDate 2025-07-24 16:19:58
|
||||
*/
|
||||
public interface SysNotificationsService extends IService<SysNotifications> {
|
||||
|
||||
/**
|
||||
* 查询系统通知
|
||||
*
|
||||
* @param id 系统通知主键
|
||||
* @return 系统通知
|
||||
*/
|
||||
SysNotificationsVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 批量发送通知
|
||||
*
|
||||
* @param req 通知
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean sendBatch(SysNotificationsCreateBatchReq req);
|
||||
|
||||
/**
|
||||
* 删除通知
|
||||
*
|
||||
* @param ids 通知ID串
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean deleteByIds(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获取系统通知
|
||||
*
|
||||
* @param notifications 系统通知
|
||||
* @return 系统通知
|
||||
*/
|
||||
SysNotificationsVo getVo(SysNotifications notifications);
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param req 查询参数
|
||||
* @return 查询条件
|
||||
*/
|
||||
LambdaQueryWrapper<SysNotifications> buildQueryWrapper(SysNotificationsQueryReq req);
|
||||
|
||||
/**
|
||||
* 获取系统通知分页
|
||||
*
|
||||
* @param notificationsPage 系统通知分页
|
||||
* @return 系统通知分页
|
||||
*/
|
||||
Page<SysNotificationsVo> getVoPage(Page<SysNotifications> notificationsPage);
|
||||
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
package org.dromara.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.sse.dto.SseMessageDto;
|
||||
import org.dromara.common.sse.utils.SseMessageUtils;
|
||||
import org.dromara.system.domain.SysNotifications;
|
||||
import org.dromara.system.domain.dto.notifications.SysNotificationsCreateBatchReq;
|
||||
import org.dromara.system.domain.dto.notifications.SysNotificationsQueryReq;
|
||||
import org.dromara.system.domain.vo.SysNotificationsVo;
|
||||
import org.dromara.system.mapper.SysNotificationsMapper;
|
||||
import org.dromara.system.service.ISysOssService;
|
||||
import org.dromara.system.service.SysNotificationsService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @description 针对表【sys_notifications(系统通知)】的数据库操作Service实现
|
||||
* @createDate 2025-07-24 16:19:58
|
||||
*/
|
||||
@Service
|
||||
public class SysNotificationsServiceImpl extends ServiceImpl<SysNotificationsMapper, SysNotifications>
|
||||
implements SysNotificationsService {
|
||||
|
||||
@Resource
|
||||
private ISysOssService ossService;
|
||||
|
||||
@Resource
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 查询系统通知
|
||||
*
|
||||
* @param id 系统通知主键
|
||||
* @return 系统通知
|
||||
*/
|
||||
@Override
|
||||
public SysNotificationsVo queryById(Long id) {
|
||||
SysNotifications notifications = this.getById(id);
|
||||
if (notifications == null) {
|
||||
throw new ServiceException("当前通知不存在", HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return this.getVo(notifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量发送通知
|
||||
*
|
||||
* @param req 通知
|
||||
* @return 是否成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean sendBatch(SysNotificationsCreateBatchReq req) {
|
||||
List<Long> recipientIds = req.getRecipientIds();
|
||||
if (CollUtil.isEmpty(recipientIds)) {
|
||||
throw new ServiceException("请选择接收人", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
List<SysNotifications> sysNotificationsList = recipientIds.stream().map(recipientId -> {
|
||||
SysNotifications notifications = new SysNotifications();
|
||||
notifications.setRecipientId(recipientId);
|
||||
notifications.setSenderId(req.getSenderId());
|
||||
notifications.setType(req.getType());
|
||||
notifications.setTitle(req.getTitle());
|
||||
notifications.setContent(req.getContent());
|
||||
notifications.setActionUrl(req.getActionUrl());
|
||||
notifications.setFile(req.getFile());
|
||||
notifications.setRemark(req.getRemark());
|
||||
return notifications;
|
||||
}).toList();
|
||||
boolean result = this.saveBatch(sysNotificationsList);
|
||||
if (!result) {
|
||||
throw new ServiceException("通知发送失败", HttpStatus.ERROR);
|
||||
}
|
||||
// 通过 sse 发送通知
|
||||
scheduledExecutorService.schedule(() -> {
|
||||
SseMessageDto dto = new SseMessageDto();
|
||||
dto.setMessage(StringUtils.isNotBlank(req.getTitle()) ? req.getTitle() : req.getContent().substring(0, 50));
|
||||
dto.setUserIds(recipientIds);
|
||||
SseMessageUtils.publishMessage(dto);
|
||||
}, 2, TimeUnit.SECONDS);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除通知
|
||||
*
|
||||
* @param ids 通知ID串
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteByIds(Collection<Long> ids) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统通知
|
||||
*
|
||||
* @param notifications 系统通知
|
||||
* @return 系统通知
|
||||
*/
|
||||
@Override
|
||||
public SysNotificationsVo getVo(SysNotifications notifications) {
|
||||
SysNotificationsVo sysNotificationsVo = new SysNotificationsVo();
|
||||
if (notifications == null) {
|
||||
return sysNotificationsVo;
|
||||
}
|
||||
BeanUtils.copyProperties(notifications, sysNotificationsVo);
|
||||
// 关联文件信息
|
||||
Set<Long> ossIds = Arrays.stream(notifications.getFile().split(",")).map(Long::parseLong).collect(Collectors.toSet());
|
||||
if (CollUtil.isNotEmpty(ossIds)) {
|
||||
sysNotificationsVo.setFileVo(ossService.listByIds(ossIds));
|
||||
}
|
||||
return sysNotificationsVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询条件
|
||||
*
|
||||
* @param req 查询参数
|
||||
* @return 查询条件
|
||||
*/
|
||||
@Override
|
||||
public LambdaQueryWrapper<SysNotifications> buildQueryWrapper(SysNotificationsQueryReq req) {
|
||||
LambdaQueryWrapper<SysNotifications> lqw = new LambdaQueryWrapper<>();
|
||||
if (req == null) {
|
||||
return lqw;
|
||||
}
|
||||
Long recipientId = req.getRecipientId();
|
||||
Long senderId = req.getSenderId();
|
||||
String type = req.getType();
|
||||
String title = req.getTitle();
|
||||
String content = req.getContent();
|
||||
String status = req.getStatus();
|
||||
lqw.like(StringUtils.isNotBlank(title), SysNotifications::getTitle, title);
|
||||
lqw.like(StringUtils.isNotBlank(content), SysNotifications::getContent, content);
|
||||
lqw.eq(StringUtils.isNotBlank(type), SysNotifications::getType, type);
|
||||
lqw.eq(StringUtils.isNotBlank(status), SysNotifications::getStatus, status);
|
||||
lqw.eq(ObjectUtils.isNotEmpty(recipientId), SysNotifications::getRecipientId, recipientId);
|
||||
lqw.eq(ObjectUtils.isNotEmpty(senderId), SysNotifications::getSenderId, senderId);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统通知分页
|
||||
*
|
||||
* @param notificationsPage 系统通知分页
|
||||
* @return 系统通知分页
|
||||
*/
|
||||
@Override
|
||||
public Page<SysNotificationsVo> getVoPage(Page<SysNotifications> notificationsPage) {
|
||||
List<SysNotifications> notificationsList = notificationsPage.getRecords();
|
||||
Page<SysNotificationsVo> notificationsVoPage = new Page<>(
|
||||
notificationsPage.getCurrent(),
|
||||
notificationsPage.getSize(),
|
||||
notificationsPage.getTotal()
|
||||
);
|
||||
if (CollUtil.isEmpty(notificationsList)) {
|
||||
return notificationsVoPage;
|
||||
}
|
||||
List<SysNotificationsVo> notificationsVoList = notificationsList.stream().map(this::getVo).toList();
|
||||
notificationsVoPage.setRecords(notificationsVoList);
|
||||
return notificationsVoPage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?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.system.mapper.SysNotificationsMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.dromara.system.domain.SysNotifications">
|
||||
<id property="id" column="id"/>
|
||||
<result property="recipientId" column="recipient_id"/>
|
||||
<result property="senderId" column="sender_id"/>
|
||||
<result property="type" column="type"/>
|
||||
<result property="title" column="title"/>
|
||||
<result property="content" column="content"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="readAt" column="read_at"/>
|
||||
<result property="actionUrl" column="action_url"/>
|
||||
<result property="file" column="file"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id,recipient_id,sender_id,type,title,content,
|
||||
status,read_at,action_url,file,remark,
|
||||
create_time,update_time
|
||||
</sql>
|
||||
</mapper>
|
Reference in New Issue
Block a user