【登录】安眼统一认证

This commit is contained in:
2025-10-30 10:14:51 +08:00
parent 4356433ee0
commit 051905a6f2
17 changed files with 1301 additions and 432 deletions
@@ -18,10 +18,18 @@ import java.util.List;
public interface AnYanClient {
/**
* 获取服务访问令牌
*
* @return
*/
String getAuthToken();
/**
* 获取服务访问令牌(密码模式)
*
* @return
*/
AuthResult getAuthTokenByPassword(String username, String password);
/**
* 刷新口令
*/
@@ -29,6 +37,7 @@ public interface AnYanClient {
/**
* 体检医院人数统计查询
*
* @param medicalYear 体检年份
* @return
*/
@@ -36,6 +45,7 @@ public interface AnYanClient {
/**
* 健康打卡数据统计查询(血压,减重,血糖)
*
* @param beginDate
* @param endDate
* @return
@@ -44,6 +54,7 @@ public interface AnYanClient {
/**
* 健康打卡异常数据详细查询(血压)
*
* @param healthSignListParam
* @return
*/
@@ -51,6 +62,7 @@ public interface AnYanClient {
/**
* 健康打卡异常数据详细查询(减重)
*
* @param healthSignListParam
* @return
*/
@@ -58,6 +70,7 @@ public interface AnYanClient {
/**
* 健康打卡异常数据详细查询(血糖)
*
* @param healthSignListParam
* @return
*/
@@ -65,18 +78,21 @@ public interface AnYanClient {
/**
* 油田医院数据查询
*
* @return
*/
List<HospitalListVO> hospitalList();
/**
* 健康小屋基础数据查询
*
* @return
*/
List<HealthRoomBaseVO> healthPlaceHomList();
/**
* 健康小屋详细数据查询
*
* @param healthRoomDTO
* @return
*/
@@ -84,6 +100,7 @@ public interface AnYanClient {
/**
* 当年体检异常人数统计
*
* @param medicalYear 年份
* @return
*/
@@ -91,6 +108,7 @@ public interface AnYanClient {
/**
* 当年体检异常人员列表
*
* @param param
* @return
*/
@@ -98,13 +116,23 @@ public interface AnYanClient {
/**
* 公网环境获取服务访问token (目前仅用于访问安眼大屏)
*
* @return
*/
String publicNetworkToken();
/**
* 公网环境获取服务访问token (目前仅用于访问安眼大屏, 不走缓存, 每次用现取)
*
* @return
*/
AuthResult publicNetworkTokenNoCache();
/**
* 获取用户信息
*
* @param token
* @return
*/
AnYanUserInfoResult getAnYanUserInfo(String token);
}
@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -20,6 +21,7 @@ import com.renkang.anyan.utils.PageResultToBeanUtil;
import com.renkang.anyan.utils.SM4Utils;
import com.renkang.anyan.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.jeecg.common.exception.ExceptionAssertsUtil;
import org.jeecg.util.RedisClientUtil;
@@ -31,9 +33,13 @@ import org.springframework.http.ResponseEntity;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -43,6 +49,7 @@ import java.util.stream.Collectors;
* @Date: 2025-8-1
* @Version: V1.0
*/
@Slf4j
@Service
@EnableRetry
@RequiredArgsConstructor
@@ -81,6 +88,42 @@ public class AnYanClientExecutor implements AnYanClient {
return new AuthResult();
}
/**
* @description: 通过密码获取令牌
* @author PengJ
* @date 2025/10/24 11:15
*/
@Override
public AuthResult getAuthTokenByPassword(String username, String password) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("grant_type", "password");
formData.add("username", username);
formData.add("password", password);
formData.add("client_id", anYanProperties.getClientId());
formData.add("client_secret", anYanProperties.getClientSecret());
ResponseEntity<AuthResult> authResult = RestTemplateUtil.postExchangeForGetTokenByPassword(AnYanApiEnum.QUERY_AUTH_TOKEN, formData, headers, AuthResult.class);
if (HttpStatus.OK.equals(authResult.getStatusCode())
&& ObjectUtils.isNotEmpty(authResult.getBody())) {
return authResult.getBody();
} else {
log.error("------安眼系统获取token失败(密码模式){}------", authResult.getBody());
ExceptionAssertsUtil.fail("------安眼系统获取token失败------" + authResult.getBody());
return null;
}
} catch (HttpClientErrorException e) {
String replace = Objects.requireNonNull(e.getMessage()).replace("401 Unauthorized: ", "");
replace = replace.replace("\"", "");
AuthResult bean = JSONUtil.toBean(replace, AuthResult.class);
ExceptionAssertsUtil.fail(bean.getMessage());
} catch (Exception e) {
ExceptionAssertsUtil.fail(e.getMessage());
}
return null;
}
@Override
public void refreshToken() {
AuthResult authResult = getAuthTokenExecutor();
@@ -99,7 +142,9 @@ public class AnYanClientExecutor implements AnYanClient {
@Override
@Retryable(maxAttempts = 2)
public List<MedicalPeopleNumStatisticsVO> examinationStatistics(Integer medicalYear) {
if(null == medicalYear){ medicalYear = DateUtil.year(new Date()); }
if (null == medicalYear) {
medicalYear = DateUtil.year(new Date());
}
JSONObject param = new JSONObject();
param.put("medicalYear", String.valueOf(medicalYear));
AnYanResult<Object> result = RestTemplateUtil.postExchange(AnYanApiEnum.HEALTH_EXAMINATION_STATISTICS, buildTokenHeader(), param);
@@ -164,7 +209,9 @@ public class AnYanClientExecutor implements AnYanClient {
@Override
@Retryable(maxAttempts = 2)
public MedicalAbnormalStatisticsVO medicalAbnormalStatistics(Integer medicalYear) {
if(null == medicalYear){ medicalYear = DateUtil.year(new Date()); }
if (null == medicalYear) {
medicalYear = DateUtil.year(new Date());
}
JSONObject param = new JSONObject();
param.put("medicalYear", medicalYear);
AnYanResult<Object> result = RestTemplateUtil.postExchange(AnYanApiEnum.MEDICAL_ABNORMAL_STATISTICS, buildTokenHeader(), param);
@@ -223,4 +270,17 @@ public class AnYanClientExecutor implements AnYanClient {
return null;
}
/**
* @description: 根据token获取用户信息
* @author PengJ
* @date 2025/10/24 17:00
*/
@Override
public AnYanUserInfoResult getAnYanUserInfo(String token) {
HttpHeaders headers = new HttpHeaders();
headers.add(AnYanConstants.AUTHORIZATION, AnYanConstants.TOKEN_TYPE + token);
ResponseEntity<AnYanUserInfoResult> exchange = RestTemplateUtil.getExchange(AnYanApiEnum.USER_INFO_UNIT_V2, headers, AnYanUserInfoResult.class);
return BeanUtil.toBean(exchange.getBody(), AnYanUserInfoResult.class);
}
}
@@ -2,6 +2,7 @@ package com.renkang.restaurant.config;
import com.renkang.anyan.client.AnYanClient;
import com.renkang.anyan.config.AnYanProperties;
import com.renkang.anyan.enums.AnYanApiEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.ExceptionAssertsUtil;
@@ -12,6 +13,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
@@ -20,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description: AnYanClient xj安眼系统接口调用客户端
@@ -39,7 +42,11 @@ public class AnYanRestTemplateConfig {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.of(properties.getTimeout(), ChronoUnit.MILLIS))
.setReadTimeout(Duration.of(properties.getReadTimeout(), ChronoUnit.MILLIS))
.additionalMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8), new MappingJackson2HttpMessageConverter())
.additionalMessageConverters(
new StringHttpMessageConverter(StandardCharsets.UTF_8),
new MappingJackson2HttpMessageConverter(),
new FormHttpMessageConverter()
)
.additionalInterceptors(authInterceptor)
.build();
}
@@ -48,12 +55,23 @@ public class AnYanRestTemplateConfig {
* 自定义拦截器处理接口鉴权失败, 配合spring retry使用从而避免因token失效导致的接口调用失败
*/
public ClientHttpRequestInterceptor authInterceptor() {
// 添加重试次数限制
AtomicInteger retryCount = new AtomicInteger(0);
int maxRetries = 5;
return (httpRequest, bytes, clientHttpRequestExecution) -> {
ClientHttpResponse execute;
try {
// 获取请求URL
String url = httpRequest.getURI().toString();
// 排除特定接口不执行拦截逻辑
boolean shouldSkip = url.contains(AnYanApiEnum.QUERY_AUTH_TOKEN.getMethodPath());
execute = clientHttpRequestExecution.execute(httpRequest, bytes);
if (shouldSkip) {
return execute;
}
HttpStatus httpStatus = execute.getStatusCode();
if (Arrays.asList(HttpStatus.UNAUTHORIZED,HttpStatus.FORBIDDEN).contains(httpStatus)) {
if (Arrays.asList(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN).contains(httpStatus)
&& retryCount.getAndIncrement() < maxRetries) {
SpringContextUtils.getBean("anYanClientExecutor", AnYanClient.class).refreshToken();
}
} catch (Exception e) {
@@ -9,6 +9,7 @@ import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Objects;
@@ -40,6 +41,7 @@ public class RestTemplateUtil {
/**
* 请求接口访问 token
*
* @param api 接口
* @param requestBody 参数
* @return ResponseEntity
@@ -50,8 +52,25 @@ public class RestTemplateUtil {
return getRestTemplate().exchange(url, api.getHttpMethod(), requestEntity, responseType);
}
/**
* 公网请求接口访问 token(密码模式)
*
* @param api 接口
* @param formData 参数
* @return ResponseEntity
*/
public static <T> ResponseEntity<T> postExchangeForGetTokenByPassword(AnYanApiEnum api, MultiValueMap<String, String> formData, HttpHeaders headers, Class<T> responseType) {
String url = getProperties().getPublicHost() + api.getMethodPath();
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
log.info("开始发送请求到: {}", url);
ResponseEntity<T> response = getRestTemplate().exchange(url, api.getHttpMethod(), requestEntity, responseType);
log.info("收到响应: {}", response);
return response;
}
/**
* 公网请求接口访问 token
*
* @param api 接口
* @param requestBody 参数
* @return ResponseEntity
@@ -64,6 +83,7 @@ public class RestTemplateUtil {
/**
* 通用调用方法
*
* @param api 请求接口
* @param headers 请求头
* @param requestBody body参数
@@ -75,8 +95,22 @@ public class RestTemplateUtil {
return responseHandler(getRestTemplate().exchange(url, api.getHttpMethod(), requestEntity, AnYanResult.class));
}
/**
* 通用调用方法
*
* @param api 请求接口
* @param headers 请求头
* @return Result对象
*/
public static <T> ResponseEntity<T> getExchange(AnYanApiEnum api, HttpHeaders headers, Class<T> responseType) {
String url = getProperties().getHost() + api.getMethodPath() + api.getMethodParam();
HttpEntity<Object> requestEntity = new HttpEntity<>(headers);
return getRestTemplate().exchange(url, api.getHttpMethod(), requestEntity, responseType);
}
/**
* ResponseEntity 转 Result
*
* @param entity
* @return
*/
@@ -2,6 +2,7 @@ package com.renkang.anyan.enums;
import lombok.Getter;
import org.springframework.http.HttpMethod;
/**
* @Description: AnYanApiEnum xj安眼系统接口枚举类
* @Author: feng
@@ -68,7 +69,13 @@ public enum AnYanApiEnum {
/**
* 12.从外网地址获取安眼token接口
*/
PUBLIC_NETWORK_AUTH_TOKEN("/aygc-service-sys/0/api/xj/outside/getToken","", HttpMethod.POST);
PUBLIC_NETWORK_AUTH_TOKEN("/aygc-service-sys/0/api/xj/outside/getToken", "", HttpMethod.POST),
/**
* 13.获取员工信息及单位-V2
*/
USER_INFO_UNIT_V2("/iosp-iam/v2/users/self", "?containsUnit=true", HttpMethod.GET),
;
/**
* 接口路径
@@ -0,0 +1,32 @@
package com.renkang.anyan.model.dto;
import lombok.Data;
/**
* @author PengJ
* @description: 密码鉴权入参
* @date 2025/10/24 11:04
*/
@Data
public class AuthByPasswordParam {
/**
* grant_type
*/
private String grant_type = "password";
/**
* username
*/
private String username;
/**
* password
*/
private String password;
/**
* client_id
*/
private String client_id;
/**
* client_secret
*/
private String client_secret;
}
@@ -0,0 +1,149 @@
package com.renkang.anyan.model.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Huawei
* @version 1.0
* @description: TODO
* @date 2025/10/24 16:59
*/
@NoArgsConstructor
@Data
public class AnYanUserInfoResult {
@JsonProperty("id")
private String id;
@JsonProperty("loginName")
private String loginName;
@JsonProperty("email")
private String email;
@JsonProperty("organizationId")
private Integer organizationId;
@JsonProperty("realName")
private String realName;
@JsonProperty("phone")
private String phone;
@JsonProperty("gender")
private Integer gender;
@JsonProperty("tenantName")
private String tenantName;
@JsonProperty("tenantNum")
private String tenantNum;
@JsonProperty("roleMergeFlag")
private Integer roleMergeFlag;
@JsonProperty("tenantId")
private Integer tenantId;
@JsonProperty("currentRoleId")
private String currentRoleId;
@JsonProperty("currentRoleCode")
private String currentRoleCode;
@JsonProperty("currentRoleName")
private String currentRoleName;
@JsonProperty("currentRoleLevel")
private String currentRoleLevel;
@JsonProperty("employeeUnitDTO")
private EmployeeUnitDTO employeeUnitDTO;
@NoArgsConstructor
@Data
public static class EmployeeUnitDTO {
/**
* 职工编号
*/
@JsonProperty("employeeNum")
private String employeeNum;
/**
* 姓名
*/
@JsonProperty("name")
private String name;
/**
* 邮箱
*/
@JsonProperty("email")
private String email;
/**
* 手机
*/
@JsonProperty("mobile")
private String mobile;
/**
* 性别
*/
@JsonProperty("gender")
private Integer gender;
@JsonProperty("status")
private Object status;
/**
* 启用状态
*/
@JsonProperty("enabledFlag")
private String enabledFlag;
@JsonProperty("unitName")
private String unitName;
/**
* 所属二级单位unitCode
*/
@JsonProperty("unitCode")
private String unitCode;
/**
* 科级单位名称
*/
@JsonProperty("sectionUnitName")
private String sectionUnitName;
/**
* 科级单位unitCode
*/
@JsonProperty("sectionUnitCode")
private String sectionUnitCode;
@JsonProperty("directDepartmentName")
private String directDepartmentName;
/**
* 直属部门unitCode
*/
@JsonProperty("directDepartmentCode")
private String directDepartmentCode;
/**
* 层级路径(unitCode拼接)
*/
@JsonProperty("levelPath")
private String levelPath;
/**
* 完整组织机构路径(unitName拼接)
*/
@JsonProperty("unitNamePath")
private String unitNamePath;
/**
* 固定电话
*/
@JsonProperty("attribute1")
private String attribute1;
/**
* 职务
*/
@JsonProperty("attribute2")
private String attribute2;
/**
* 生日或年龄
*/
@JsonProperty("attribute3")
private Object attribute3;
}
}
@@ -28,4 +28,12 @@ public class AuthResult {
* satoken
*/
private String satoken;
/**
* message
*/
private String message;
/**
* success
*/
private boolean success;
}
@@ -87,9 +87,11 @@ public class ShiroConfig {
filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除
filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除
filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
filterChainDefinitionMap.put("/sys/loginAnYan", "anon"); //登录接口排除(三方授权)
filterChainDefinitionMap.put("/sys/loginDoctor", "anon"); //登录接口排除
filterChainDefinitionMap.put("/conDoctor/loginDoctor", "anon"); //登录接口排除
filterChainDefinitionMap.put("/sys/mLogin", "anon"); //登录接口排除
filterChainDefinitionMap.put("/sys/mLoginAnYan", "anon"); //登录接口排除(三方授权)
filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
filterChainDefinitionMap.put("/sys/thirdLogin/**", "anon"); //第三方登录
filterChainDefinitionMap.put("/sys/exchangeToken", "anon"); // 食堂token置换
+4
View File
@@ -80,6 +80,10 @@
<groupId>com.renkang</groupId>
<artifactId>health-watch-api</artifactId>
</dependency>
<dependency>
<groupId>com.renkang</groupId>
<artifactId>xj-anyan-api</artifactId>
</dependency>
</dependencies>
@@ -5,7 +5,6 @@ import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.xkcoding.http.util.StringUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -40,15 +39,16 @@ import org.jeecg.modules.system.bean.request.QueryWeightMaxParam;
import org.jeecg.modules.system.bean.request.QueryqueryGroupAParam;
import org.jeecg.modules.system.bean.request.UserFilter;
import org.jeecg.modules.system.entity.HealthUserEmployeeEx;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.entity.SysUserDepartChangeApply;
import org.jeecg.modules.system.manager.DictCacheManager;
import org.jeecg.modules.system.manager.UserCacheManager;
import org.jeecg.modules.system.service.IHealthUserEmployeeExService;
import org.jeecg.modules.system.service.ISysUserDepartChangeApplyService;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.system.vo.*;
import org.jeecg.modules.system.vo.ItemVo;
import org.jeecg.modules.system.vo.NoSelfManagerUser;
import org.jeecg.modules.system.vo.SysDepartVo;
import org.jeecg.modules.system.vo.UserEmployeeImportVo;
import org.jeecg.redis.RedisStreamKeyEnum;
import org.jeecg.redis.RedisStreamUtil;
import org.jeecg.util.AsyncUtils;
@@ -154,6 +154,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
* 1.因feign调用无法接收IPage接口类修饰的数据此接口换成实现类Page
* 2.更明确的传参方式声明,所有参数统一使用RequestParam传参(feign调用须指明传参方式)
* 3.该接口不支持获取员工信息中的家庭成员数量
*
* @return 带分页的员工列表
*/
@Operation(summary = "feign服务间调用员工分页列表", description = "feign服务间调用员工分页列表", hidden = true)
@@ -204,25 +205,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
@RequiresPermissions("system:health_user_employee_ex:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody @Validated UserEmployee userEmployee) {
// 密码解密
String key = RSAEncryptUtils.decrypt1(userEmployee.getPassword(), CommonConstant.PRIVATE_KEY);
String s = CheckPasswordUtil.checkPasswordRule(key);
if (StrUtil.isNotBlank(s)) {
return Result.error(s);
}
userEmployee.setPassword(key);
boolean success = healthUserEmployeeExService.saveAndUser(userEmployee);
if (success) {
if (!GlobalUtils.isQh()) {
redisStreamUtil.add(RedisStreamKeyEnum.EMPLOYEE_ASYNC_ADD, userEmployee);
}
// 添加缓存
AsyncUtils.execute(asyncTaskExecutor, ClientSignThreadLocal.getClientSignThreadLocal(),
() -> userCacheManager.addUser(empConvertUser(userEmployee)));
return Result.ok("添加成功");
} else {
return Result.error("添加失败");
}
return healthUserEmployeeExService.addUserInfo(userEmployee);
}
/**
@@ -261,42 +244,9 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
@RequiresPermissions("system:health_user_employee_ex:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody UserEmployee userEmployee) {
//通过userId获取用户修改前的部门信息
SysUser sysUser = sysUserService.getUserById(userEmployee.getId());
userEmployee.setPassword(null);
//获取登录人信息
LoginUser loginUser = GlobalUtils.getLoginUser();
boolean success = healthUserEmployeeExService.updateByIdAndUser(userEmployee);
if (success) {
//编辑成功之后新增部门迁移列表
//若原有的部门跟编辑不一样则新增迁移列表,反之不用
if (Objects.nonNull(sysUser) && Objects.nonNull(sysUser.getDepart())
&&!sysUser.getDepart().getOrgCode().equals(userEmployee.getOrgCode())){
SysUserDepartChangeApply departChangeApply = healthUserEmployeeExService.addUserDepartChange(sysUser, loginUser);
SysDepart sysDepart = sysCache.getDepartByOrgCode(userEmployee.getOrgCode());
departChangeApply.setToDepartId(sysDepart.getId());//迁入部门id
departChangeApply.setToDepartCode(userEmployee.getOrgCode());//迁入部门code
departChangeApply.setMemo("用户编辑数据");
sysUserDepartChangeApplyService.save(departChangeApply);
}
if (!GlobalUtils.isQh()) {
redisStreamUtil.add(RedisStreamKeyEnum.EMPLOYEE_ASYNC_EDIT, userEmployee);
}
// 编辑缓存
AsyncUtils.execute(asyncTaskExecutor, ClientSignThreadLocal.getClientSignThreadLocal(), () -> {
userCacheManager.updateUser(empConvertUser(userEmployee));
// 刷新用户缓存信息
userCacheManager.updateUserCache(userEmployee.getId());
});
String orgCode = userEmployee.getOrgCode();
if(StringUtil.isNotEmpty(orgCode)){
sendUpdateMsgToRedisStream(userEmployee.getId(),orgCode);
}
return Result.ok("编辑成功");
} else {
return Result.error("编辑失败");
}
return healthUserEmployeeExService.updateUserInfo(userEmployee, loginUser);
}
private void sendUpdateMsgToRedisStream(String userId, String orgCode) {
@@ -501,6 +451,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
mv.addObject(NormalExcelConstants.MAP_LIST, mapList);
return mv;
}
/**
* 通过excel导入数据
*
@@ -564,8 +515,10 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
healthUserEmployeeExService.updateUserItem(infoId);
return Result.OK("开始更新数据并导入数据");
}
/**
* 员工更新单位模版下载
*
* @return
*/
@GetMapping("/departTemp")
@@ -645,6 +598,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
healthUserEmployeeExService.updateUserDepart(infoId);
return Result.OK("开始更新数据并导入数据");
}
private void delCacheBatch(List<SysUser> users) {
if (CollectionUtil.isEmpty(users)) {
return;
@@ -684,6 +638,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
/**
* 员工-分页列表查询 筛选用
*
* @param userFilter
* @param pageNo
* @param pageSize
@@ -705,6 +660,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
/**
* 医疗点业务员工分页列表(目前用于服务间调用)
* ps: 获取未分配医疗点的员工列表 , 调用方需处理数据权限的问题
*
* @return 带分页的员工列表
*/
@Operation(summary = "医疗点业务员工分页列表(目前用于服务间调用)", description = "医疗点业务员工分页列表(目前用于服务间调用)", hidden = true)
@@ -715,6 +671,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
/**
* 通过ID获取员工常用的基本信息(适合业务服务种员工信息补全) 目前适用于业务服务种feign调用
*
* @param ids
* @return
*/
@@ -726,6 +683,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
/**
* 通过民族、政治面貌、岗位层级、婚姻状况、健康现状查询员工基本信息 (适合业务服务种员工信息补全) 目前适用于业务服务种feign调用
*
* @param healthUserEmployeeEx
* @return
*/
@@ -738,6 +696,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
/**
* 通过ID获取员工常用的基本信息(适合业务服务种员工信息补全) 目前适用于业务服务种feign调用
*
* @param ids
* @return
*/
@@ -755,6 +714,7 @@ public class HealthUserEmployeeExController extends JeecgController<HealthUserEm
/**
* 移动端用户获取身高体重
*
* @param userFlag 不传从token中取ID, 传了就用用户标识,可传身份证和ID(适用于食堂接口的用户身高体重数据补全)
* @return
*/
@@ -1,6 +1,5 @@
package org.jeecg.modules.system.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
@@ -13,7 +12,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.jeecg.base.UserBase;
@@ -28,9 +26,10 @@ import org.jeecg.common.util.encryption.EncryptedString;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.global.GlobalUtils;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.modules.manager.ISysCache;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.SysUserRoleMapper;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysRoleIndex;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.entity.SysUserPost;
import org.jeecg.modules.system.model.SysLoginModel;
import org.jeecg.modules.system.service.*;
import org.jeecg.modules.system.service.impl.SysBaseApiImpl;
@@ -49,7 +48,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
/**
* @Author scott
@@ -74,26 +72,24 @@ public class LoginController {
@Autowired
private ISysDepartService sysDepartService;
@Autowired
private ISysTenantService sysTenantService;
@Autowired
private ISysDictService sysDictService;
@Resource
private BaseCommonService baseCommonService;
@Autowired
private JeecgBaseConfig jeecgBaseConfig;
@Autowired
private SysUserRoleMapper sysUserRoleMapper;
@Autowired
private CacheManager cacheManager;
@Autowired
private ISysCache sysCache;
@Autowired
private ISysUserDeviceService sysUserDeviceService;
@Autowired
private ISysUserPostService sysUserPostService;
@Autowired
private TaskExecutor taskExecutor;
@Autowired
private ILoginAnYanService iLoginService;
@Autowired
private LoginService loginService;
@Operation(summary = "登录接口")
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Result<JSONObject> login(@RequestBody SysLoginModel sysLoginModel) {
@@ -144,7 +140,7 @@ public class LoginController {
boolean qh = GlobalUtils.isQh();
SysUser sysUser = sysUserService.getUserByNameOrWorkNoOrMobile(username, password, null);
if (sysUser == null) {
addLoginFailOvertimes(username);
loginService.addLoginFailOvertimes(username);
result.error500("账号或密码错误");
return result;
}
@@ -161,7 +157,7 @@ public class LoginController {
}
// 用户角色信息
handleUserRoles(sysUser);
loginService.handleUserRoles(sysUser);
// 用户岗位信息
if (!qh) {
SysUserPost sysUserPost = sysUserPostService.getById(sysUser.getId());
@@ -170,7 +166,7 @@ public class LoginController {
}
}
// 用户管理部门
handleUserDepartCodes(sysUser);
loginService.handleUserDepartCodes(sysUser);
//用户登录信息
userInfo(sysUser, result);
//update-begin--Author:liusq Date:20210126 for:登录成功,删除redis中的验证码
@@ -188,6 +184,27 @@ public class LoginController {
return result;
}
/**
* @description: 安眼统一认证(pc端)
* @author PengJ
* @date 2025/10/24 13:39
*/
@Operation(summary = "安眼统一认证(pc端)")
@RequestMapping(value = "/loginAnYan", method = RequestMethod.POST)
public Result<JSONObject> loginAnYan(@RequestBody SysLoginModel sysLoginModel) {
return iLoginService.loginAnYan(sysLoginModel);
}
/**
* @description: 安眼统一认证(移动端)
* @author PengJ
* @date 2025/10/24 13:39
*/
@Operation(summary = "安眼统一认证(移动端)")
@RequestMapping(value = "/mLoginAnYan", method = RequestMethod.POST)
public Result<JSONObject> mLoginAnYan(@RequestBody SysLoginModel sysLoginModel) {
return iLoginService.mLoginAnYan(sysLoginModel);
}
/**
* 医护人员登录接口
@@ -198,7 +215,7 @@ public class LoginController {
@PostMapping(value = "medicalStaffLogin")
public Result<JSONObject> medicalStaffLogin(@RequestBody SysLoginModel sysLoginModel) {
// 验证码校验
boolean checkCode = checkVerificationCode(sysLoginModel.getCaptcha(), sysLoginModel.getCheckKey());
boolean checkCode = loginService.checkVerificationCode(sysLoginModel.getCaptcha(), sysLoginModel.getCheckKey());
if (!checkCode) {
// 改成特殊的code 便于前端判断
return Result.error(HttpStatus.PRECONDITION_FAILED.value(), "验证码错误");
@@ -230,16 +247,16 @@ public class LoginController {
}
// 角色信息
handleUserRoles(sysUser);
loginService.handleUserRoles(sysUser);
// 管理部门
handleUserDepartCodes(sysUser);
loginService.handleUserDepartCodes(sysUser);
// 生成token
String token = JwtUtil.sign(sysUser);
// 缓存redis以及设置token过去时间
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
cacheLoginUserInfo(username, sysUser, "登录成功[医护人员]");
loginService.cacheLoginUserInfo(username, sysUser, "登录成功[医护人员]");
JSONObject obj = new JSONObject();
obj.put("token", token);
@@ -247,53 +264,6 @@ public class LoginController {
return Result.OK("登录成功", obj);
}
private boolean checkVerificationCode(String captcha, String checkKey) {
// 验证码校验
if (StrUtil.isBlank(captcha) || StrUtil.isBlank(checkKey)) {
return false;
}
String lowerCaseCaptcha = captcha.toLowerCase();
String origin = lowerCaseCaptcha + checkKey + jeecgBaseConfig.getSignatureSecret();
String realKey = Md5Util.md5Encode(origin, "utf-8");
Object checkCode = redisUtil.get(realKey);
if (checkCode == null || !checkCode.toString().equals(lowerCaseCaptcha)) {
log.warn("验证码错误,key= {} , Ui checkCode= {}, Redis checkCode = {}", checkKey, lowerCaseCaptcha, checkCode);
return false;
}
return true;
}
private void cacheLoginUserInfo(String username, SysUser sysUser, String loginSuccessTips) {
if (StrUtil.isBlank(username) || sysUser == null) {
return;
}
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
LoginUser loginUser = new LoginUser();
sysUser.setClient(GlobalUtils.loginClient());
BeanUtils.copyProperties(sysUser, loginUser);
baseCommonService.addLog("用户名: " + username + "," + loginSuccessTips, CommonConstant.LOG_TYPE_1, null, loginUser);
Cache cache = cacheManager.getCache(CacheConstant.SYS_USERS_CACHE);
if (Objects.nonNull(cache)) {
cache.put(sysUser.getId(), loginUser);
}
}
private void handleUserRoles(SysUser user) {
if (ObjectUtil.isNull(user)) {
return;
}
List<SysRole> list = sysUserRoleMapper.getUserRolesByUserId(user.getId());
if (CollectionUtils.isEmpty(list)) {
return;
}
String roleCodes = list.stream().map(SysRole::getRoleCode)
.filter(StrUtil::isNotBlank)
.collect(Collectors.joining(","));
user.setRoleCodes(roleCodes);
}
/**
* 【vue3专用】获取用户信息
*/
@@ -321,7 +291,7 @@ public class LoginController {
//update-begin---author:liusq ---date:2022-06-29 for:接口返回值修改,同步修改这里的判断逻辑-----------
//update-end---author:scott ---date::2022-06-20 forvue3前端,支持自定义首页--------------
// 用户角色
handleUserRoles(sysUser);
loginService.handleUserRoles(sysUser);
sysUser.setClient(GlobalUtils.loginClient());
obj.put("userInfo", sysUser);
obj.put("sysAllDictItems", sysDictService.queryAllDictItems());
@@ -578,7 +548,7 @@ public class LoginController {
//update-end-author:taoyan date:2022-9-13 for: VUEN-2245 【漏洞】发现新漏洞待处理20220906
if (!smscode.equals(code)) {
//update-begin-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
addLoginFailOvertimes(phone);
loginService.addLoginFailOvertimes(phone);
//update-end-author:taoyan date:2022-11-7 for: issues/4109 平台用户登录失败锁定用户
result.setMessage("手机验证码错误");
return result;
@@ -742,7 +712,7 @@ public class LoginController {
// personType=1 员工
SysUser sysUser = sysUserService.getUserByNameOrWorkNoOrMobile(username, password, null);
if (sysUser == null) {
addLoginFailOvertimes(username);
loginService.addLoginFailOvertimes(username);
result.error500("账号或密码错误");
return result;
}
@@ -800,9 +770,9 @@ public class LoginController {
//5. 设置登录用户信息
obj.put("userInfo", sysUser);
// 用户角色
handleUserRoles(sysUser);
loginService.handleUserRoles(sysUser);
// 用户管理部门
handleUserDepartCodes(sysUser);
loginService.handleUserDepartCodes(sysUser);
//6. 生成token
String token = JwtUtil.sign(sysUser);
// 设置超时时间
@@ -824,52 +794,12 @@ public class LoginController {
//录入登录用户的设备型号
if (StringUtils.isNotBlank(sysLoginModel.getDeviceType()) && StringUtils.isNotBlank(sysLoginModel.getDeviceSystem())) {
taskExecutor.execute(() ->
insertUserDeviceType(sysUser.getId(), sysLoginModel.getDeviceType(), sysLoginModel.getDeviceSystem()));
loginService.insertUserDeviceType(sysUser.getId(), sysLoginModel.getDeviceType(), sysLoginModel.getDeviceSystem()));
}
return result;
}
public void insertUserDeviceType(String userId, String deviceType, String deviceSystem) {
LambdaQueryWrapper<SysUserDevice> wrapper = Wrappers.lambdaQuery(SysUserDevice.class)
.eq(SysUserDevice::getUserId, userId)
.eq(SysUserDevice::getDeviceType, deviceType)
.eq(SysUserDevice::getDeviceSystem, deviceSystem);
Optional<SysUserDevice> findOptional = sysUserDeviceService.getOneOpt(wrapper);
Date now = new Date();
if (findOptional.isPresent()) {
SysUserDevice sysUserDevice = findOptional.get();
sysUserDevice.setLastLoginTime(now);
sysUserDeviceService.updateById(sysUserDevice);
} else {
SysUserDevice sysUserDevice = new SysUserDevice();
sysUserDevice.setUserId(userId);
sysUserDevice.setDeviceSystem(deviceSystem);
sysUserDevice.setDeviceType(deviceType);
sysUserDevice.setCreateTime(now);
sysUserDevice.setLastLoginTime(now);
sysUserDeviceService.save(sysUserDevice);
}
}
private void handleUserDepartCodes(SysUser sysUser) {
if (sysUser == null || StrUtil.isBlank(sysUser.getDepartIds())) {
return;
}
String[] split = sysUser.getDepartIds().split(",");
List<String> codes = new ArrayList<>();
for (String departId : split) {
SysDepart departById = sysCache.getDepartById(departId);
if (departById != null) {
codes.add(departById.getOrgCode());
}
}
if (CollectionUtil.isNotEmpty(codes)) {
sysUser.setDepartCodes(String.join(",", codes));
}
}
/**
* 第三方登录获取token
*
@@ -893,7 +823,7 @@ public class LoginController {
// username 泛指账号 包含(username/工号/电话)
SysUser sysUser = sysUserService.getUserByNameOrWorkNoOrMobile(username, password, null);
if (sysUser == null) {
addLoginFailOvertimes(username);
loginService.addLoginFailOvertimes(username);
result.error500("账号或密码错误");
return result;
}
@@ -903,7 +833,7 @@ public class LoginController {
}
// 用户角色
handleUserRoles(sysUser);
loginService.handleUserRoles(sysUser);
//6. 生成token
String token = JwtUtil.sign(sysUser);
// 设置超时时间
@@ -996,41 +926,6 @@ public class LoginController {
return Result.OK(result);
}
/**
* 登录失败超出次数5 返回true
*
* @param username
* @return
*/
private boolean isLoginFailOvertimes(String username) {
String key = CommonConstant.LOGIN_FAIL + username;
Object failTime = redisUtil.get(key);
if (failTime != null) {
Integer val = Integer.parseInt(failTime.toString());
if (val > 5) {
return true;
}
}
return false;
}
/**
* 记录登录失败次数
*
* @param username
*/
private void addLoginFailOvertimes(String username) {
String key = CommonConstant.LOGIN_FAIL + username;
Object failTime = redisUtil.get(key);
Integer val = 0;
if (failTime != null) {
val = Integer.parseInt(failTime.toString());
}
// 1小时
redisUtil.set(key, ++val, 3600);
}
@Operation(summary = "登录接口专家端专用")
@RequestMapping(value = "/loginDoctor", method = RequestMethod.POST)
public Result<JSONObject> loginDoctor(@RequestBody SysLoginModel sysLoginModel) {
@@ -1060,7 +955,7 @@ public class LoginController {
break;
}
if (Objects.isNull(sysUser)) {
addLoginFailOvertimes(username);
loginService.addLoginFailOvertimes(username);
result.error500("用户名或密码错误");
return result;
}
@@ -1128,9 +1023,9 @@ public class LoginController {
LoginUser cacheUser = cache.get(sysUser.getId(), LoginUser.class);
if (cacheUser == null) {
// 用户角色
handleUserRoles(sysUser);
loginService.handleUserRoles(sysUser);
// 用户管理部门
handleUserDepartCodes(sysUser);
loginService.handleUserDepartCodes(sysUser);
LoginUser loginUser = new LoginUser();
BeanUtils.copyProperties(sysUser, loginUser);
cache.put(sysUser.getId(), loginUser);
@@ -59,9 +59,11 @@ public interface IHealthUserEmployeeExService extends IService<HealthUserEmploye
UserEmployee selectArchivesById(String id);
Result<String> addUserInfo(UserEmployee userEmployee);
boolean saveAndUser(UserEmployee userEmployee);
boolean updateByIdAndUser(UserEmployee userEmployee);
Result<String> updateUserInfo(UserEmployee userEmployee, LoginUser loginUser);
boolean saveAndUserDepartManager(UserEmployee userEmployee);
@@ -0,0 +1,29 @@
package org.jeecg.modules.system.service;
import com.alibaba.fastjson.JSONObject;
import org.jeecg.common.api.vo.Result;
import org.jeecg.modules.system.model.SysLoginModel;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author PengJ
* @description: 登录Service
* @date 2025/10/24 18:10
*/
public interface ILoginAnYanService {
/**
* @description: 安眼统一认证(pc端)
* @author PengJ
* @date 2025/10/24 18:11
*/
Result<JSONObject> loginAnYan(@RequestBody SysLoginModel sysLoginModel);
/**
* @description: 安眼统一认证(移动端)
* @author PengJ
* @date 2025/10/24 18:11
*/
Result<JSONObject> mLoginAnYan(@RequestBody SysLoginModel sysLoginModel);
}
@@ -0,0 +1,210 @@
package org.jeecg.modules.system.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.jeecg.common.constant.CacheConstant;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.Md5Util;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.config.JeecgBaseConfig;
import org.jeecg.global.GlobalUtils;
import org.jeecg.modules.base.service.BaseCommonService;
import org.jeecg.modules.manager.ISysCache;
import org.jeecg.modules.system.entity.*;
import org.jeecg.modules.system.mapper.SysUserRoleMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author Huawei
* @version 1.0
* @description: TODO
* @date 2025/10/29 16:01
*/
@Slf4j
@Service
public class LoginService {
@Autowired
private RedisUtil redisUtil;
@Autowired
private ISysUserPostService sysUserPostService;
@Autowired
private ISysCache sysCache;
@Autowired
private SysUserRoleMapper sysUserRoleMapper;
@Autowired
private ISysUserDeviceService sysUserDeviceService;
@Autowired
private JeecgBaseConfig jeecgBaseConfig;
@Resource
private BaseCommonService baseCommonService;
@Autowired
private CacheManager cacheManager;
/**
* 记录登录失败次数
*
* @param username
*/
public void addLoginFailOvertimes(String username) {
String key = CommonConstant.LOGIN_FAIL + username;
Object failTime = redisUtil.get(key);
Integer val = 0;
if (failTime != null) {
val = Integer.parseInt(failTime.toString());
}
// 1小时
redisUtil.set(key, ++val, 3600);
}
/**
* 登录失败超出次数5 返回true
*
* @param username
* @return
*/
public boolean isLoginFailOvertimes(String username) {
String key = CommonConstant.LOGIN_FAIL + username;
Object failTime = redisUtil.get(key);
if (failTime != null) {
Integer val = Integer.parseInt(failTime.toString());
if (val > 5) {
return true;
}
}
return false;
}
/**
* 处理用户相关信息(角色、岗位、管理部门)
*/
public void processUserInfo(SysUser sysUser) {
// 用户角色信息
handleUserRoles(sysUser);
// 用户岗位信息
if (!GlobalUtils.isQh()) {
SysUserPost sysUserPost = sysUserPostService.getById(sysUser.getId());
if (ObjectUtil.isNotNull(sysUserPost)) {
sysUser.setPostId(sysUserPost.getPostId());
}
}
// 用户管理部门
handleUserDepartCodes(sysUser);
}
/**
* 校验验证码
*/
public boolean checkVerificationCode(String captcha, String checkKey) {
// 验证码校验
if (StrUtil.isBlank(captcha) || StrUtil.isBlank(checkKey)) {
return false;
}
String lowerCaseCaptcha = captcha.toLowerCase();
String origin = lowerCaseCaptcha + checkKey + jeecgBaseConfig.getSignatureSecret();
String realKey = Md5Util.md5Encode(origin, "utf-8");
Object checkCode = redisUtil.get(realKey);
if (checkCode == null || !checkCode.toString().equals(lowerCaseCaptcha)) {
log.warn("验证码错误,key= {} , Ui checkCode= {}, Redis checkCode = {}", checkKey, lowerCaseCaptcha, checkCode);
return false;
}
return true;
}
/**
* 缓存用户信息
*/
public void cacheLoginUserInfo(String username, SysUser sysUser, String loginSuccessTips) {
if (StrUtil.isBlank(username) || sysUser == null) {
return;
}
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
LoginUser loginUser = new LoginUser();
sysUser.setClient(GlobalUtils.loginClient());
BeanUtils.copyProperties(sysUser, loginUser);
baseCommonService.addLog("用户名: " + username + "," + loginSuccessTips, CommonConstant.LOG_TYPE_1, null, loginUser);
Cache cache = cacheManager.getCache(CacheConstant.SYS_USERS_CACHE);
if (Objects.nonNull(cache)) {
cache.put(sysUser.getId(), loginUser);
}
}
/**
* @description: 用户角色信息
* @author PengJ
* @date 2025/10/27 15:21
*/
public void handleUserRoles(SysUser user) {
if (ObjectUtil.isNull(user)) {
return;
}
List<SysRole> list = sysUserRoleMapper.getUserRolesByUserId(user.getId());
if (CollectionUtils.isEmpty(list)) {
return;
}
String roleCodes = list.stream().map(SysRole::getRoleCode)
.filter(StrUtil::isNotBlank)
.collect(Collectors.joining(","));
user.setRoleCodes(roleCodes);
}
/**
* @description: 用户部门信息处理
*/
public void handleUserDepartCodes(SysUser sysUser) {
if (sysUser == null || StrUtil.isBlank(sysUser.getDepartIds())) {
return;
}
String[] split = sysUser.getDepartIds().split(",");
List<String> codes = new ArrayList<>();
for (String departId : split) {
SysDepart departById = sysCache.getDepartById(departId);
if (departById != null) {
codes.add(departById.getOrgCode());
}
}
if (CollectionUtil.isNotEmpty(codes)) {
sysUser.setDepartCodes(String.join(",", codes));
}
}
/**
* @description: 插入用户设备信息
*/
public void insertUserDeviceType(String userId, String deviceType, String deviceSystem) {
LambdaQueryWrapper<SysUserDevice> wrapper = Wrappers.lambdaQuery(SysUserDevice.class)
.eq(SysUserDevice::getUserId, userId)
.eq(SysUserDevice::getDeviceType, deviceType)
.eq(SysUserDevice::getDeviceSystem, deviceSystem);
Optional<SysUserDevice> findOptional = sysUserDeviceService.getOneOpt(wrapper);
Date now = new Date();
if (findOptional.isPresent()) {
SysUserDevice sysUserDevice = findOptional.get();
sysUserDevice.setLastLoginTime(now);
sysUserDeviceService.updateById(sysUserDevice);
} else {
SysUserDevice sysUserDevice = new SysUserDevice();
sysUserDevice.setUserId(userId);
sysUserDevice.setDeviceSystem(deviceSystem);
sysUserDevice.setDeviceType(deviceType);
sysUserDevice.setCreateTime(now);
sysUserDevice.setLastLoginTime(now);
sysUserDeviceService.save(sysUserDevice);
}
}
}
@@ -32,7 +32,6 @@ import org.jeecg.common.exception.ExceptionAssertsUtil;
import org.jeecg.common.export.PoiExportHandler;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.util.DictUtil;
import org.jeecg.common.system.vo.DictModel;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.system.vo.SysUserModel;
import org.jeecg.common.util.*;
@@ -82,6 +81,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -164,6 +165,8 @@ public class HealthUserEmployeeExServiceImpl extends ServiceImpl<HealthUserEmplo
private UserDataRecordBloodAboMapper aboMapper;
@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired
private AsyncTaskExecutor asyncTaskExecutor;
// 档案模块数据导出code
private final String exportCode = "sysDataUserExportCode";
@@ -430,6 +433,7 @@ public class HealthUserEmployeeExServiceImpl extends ServiceImpl<HealthUserEmplo
/**
* 脏数据处理
*
* @param value
* @return
*/
@@ -747,7 +751,8 @@ public class HealthUserEmployeeExServiceImpl extends ServiceImpl<HealthUserEmplo
bwhWrapper.isNotNull(UserDataRecordBWH::getWaist);
value = Optional.ofNullable(recordBWHMapper.selectOne(bwhWrapper, false)).map(o -> String.valueOf(o.getWaist())).orElse(null);
break;
default:break;
default:
break;
}
return value;
}
@@ -824,6 +829,30 @@ public class HealthUserEmployeeExServiceImpl extends ServiceImpl<HealthUserEmplo
return userEmployee;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> addUserInfo(UserEmployee userEmployee) {
// 密码解密
String key = RSAEncryptUtils.decrypt1(userEmployee.getPassword(), CommonConstant.PRIVATE_KEY);
String s = CheckPasswordUtil.checkPasswordRule(key);
if (StrUtil.isNotBlank(s)) {
return Result.error(s);
}
userEmployee.setPassword(key);
boolean success = saveAndUser(userEmployee);
if (success) {
if (!GlobalUtils.isQh()) {
redisStreamUtil.add(RedisStreamKeyEnum.EMPLOYEE_ASYNC_ADD, userEmployee);
}
// 添加缓存
AsyncUtils.execute(asyncTaskExecutor, ClientSignThreadLocal.getClientSignThreadLocal(),
() -> userCacheManager.addUser(empConvertUser(userEmployee)));
return Result.ok("添加成功");
} else {
return Result.error("添加失败");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveAndUser(UserEmployee userEmployee) {
@@ -883,6 +912,55 @@ public class HealthUserEmployeeExServiceImpl extends ServiceImpl<HealthUserEmplo
@Override
@Transactional(rollbackFor = Exception.class)
public Result<String> updateUserInfo(UserEmployee userEmployee, LoginUser loginUser) {
//将原来编辑用户逻辑迁移到service层
//通过userId获取用户修改前的部门信息
SysUser sysUser = sysUserService.getUserById(userEmployee.getId());
userEmployee.setPassword(null);
boolean success = updateByIdAndUser(userEmployee);
if (success) {
//编辑成功之后新增部门迁移列表
//若原有的部门跟编辑不一样则新增迁移列表,反之不用
if (Objects.nonNull(sysUser) && Objects.nonNull(sysUser.getDepart())
&& !sysUser.getDepart().getOrgCode().equals(userEmployee.getOrgCode())) {
SysUserDepartChangeApply departChangeApply = addUserDepartChange(sysUser, loginUser);
SysDepart sysDepart = sysCache.getDepartByOrgCode(userEmployee.getOrgCode());
departChangeApply.setToDepartId(sysDepart.getId());//迁入部门id
departChangeApply.setToDepartCode(userEmployee.getOrgCode());//迁入部门code
departChangeApply.setMemo("用户编辑数据");
sysUserDepartChangeApplyService.save(departChangeApply);
}
if (!GlobalUtils.isQh()) {
redisStreamUtil.add(RedisStreamKeyEnum.EMPLOYEE_ASYNC_EDIT, userEmployee);
}
// 编辑缓存
AsyncUtils.execute(asyncTaskExecutor, ClientSignThreadLocal.getClientSignThreadLocal(), () -> {
userCacheManager.updateUser(empConvertUser(userEmployee));
// 刷新用户缓存信息
userCacheManager.updateUserCache(userEmployee.getId());
});
String orgCode = userEmployee.getOrgCode();
if (com.xkcoding.http.util.StringUtil.isNotEmpty(orgCode)) {
sendUpdateMsgToRedisStream(userEmployee.getId(), orgCode);
}
return Result.ok("编辑成功");
} else {
return Result.error("编辑失败");
}
}
private void sendUpdateMsgToRedisStream(String userId, String orgCode) {
if (sysCache.getDepartByOrgCode(orgCode) == null) {
return;
}
SysUserModel userModel = new SysUserModel();
userModel.setId(userId);
userModel.setDepartIds(sysCache.getDepartByOrgCode(orgCode).getId());
userModel.setOrgCode(orgCode);
redisStreamUtil.add(RedisStreamKeyEnum.CHANGE_USER_DEPART, userModel);
}
public boolean updateByIdAndUser(UserEmployee userEmployee) {
// 添加账号 电话 身份证号校验
/* List<String> checkExistUser = sysBaseAPI.checkExistUser(userEmployee.getId(), userEmployee.getUsername(), userEmployee.getPhone(), userEmployee.getIdCard());
@@ -0,0 +1,353 @@
package org.jeecg.modules.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.renkang.anyan.client.AnYanClient;
import com.renkang.anyan.model.vo.AnYanUserInfoResult;
import com.renkang.anyan.model.vo.AuthResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.FillRuleConstant;
import org.jeecg.common.exception.ExceptionAssertsUtil;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.CheckPasswordUtil;
import org.jeecg.common.util.FillRuleUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.system.bean.UserEmployee;
import org.jeecg.modules.system.entity.HealthUserEmployeeEx;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.model.SysLoginModel;
import org.jeecg.modules.system.service.*;
import org.jeecg.util.RSAEncryptUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
/**
* @author Huawei
* @version 1.0
* @description: TODO
* @date 2025/10/24 18:06
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class LoginAnYanServiceImpl implements ILoginAnYanService {
private final ISysUserService sysUserService;
private final IHealthUserEmployeeExService healthUserEmployeeExService;
private final ISysDepartService sysDepartService;
private final ISysDictService sysDictService;
private final AnYanClient anYanClient;
private final RedisUtil redisUtil;
private final TaskExecutor taskExecutor;
private final LoginService loginService;
private static final String CLIENT_TYPE_PC = "pc";
private static final String CLIENT_TYPE_APP = "app";
/**
* @description: 安眼登录(pc
* @author PengJ
* @date 2025/10/27 13:31
*/
@Override
public Result<JSONObject> loginAnYan(SysLoginModel sysLoginModel) {
Result<JSONObject> result;
String username = sysLoginModel.getUsername();
try {
result = loginBusiness(sysLoginModel, username, CLIENT_TYPE_PC);
} catch (Exception e) {
log.error("安眼登录异常(pc: ", e);
return Result.error(e.getMessage());
}
return result;
}
/**
* @description: 安眼app登录
* @author PengJ
* @date 2025/10/27 15:35
*/
@Override
public Result<JSONObject> mLoginAnYan(SysLoginModel sysLoginModel) {
Result<JSONObject> result;
String username = sysLoginModel.getUsername();
try {
result = loginBusiness(sysLoginModel, username, CLIENT_TYPE_APP);
} catch (Exception e) {
log.error("安眼登录异常(app: ", e);
return Result.error(e.getMessage());
}
return result;
}
/**
* @description: 登录业务处理
* @author PengJ
* @date 2025/10/27 13:31
*/
public Result<JSONObject> loginBusiness(SysLoginModel sysLoginModel, String username, String clientType) {
// 密码解密
String decryptedPassword = RSAEncryptUtils.decrypt1(sysLoginModel.getPassword(), CommonConstant.PRIVATE_KEY);
if (StrUtil.isBlank(decryptedPassword)) {
return Result.error("密码解密失败");
}
// 获取安眼token
AuthResult anYanAuthResult = anYanClient.getAuthTokenByPassword(username, decryptedPassword);
if (anYanAuthResult == null || StringUtils.isBlank(anYanAuthResult.getAccess_token())) {
return Result.error(anYanAuthResult != null ? anYanAuthResult.getMessage() : "认证服务器无响应");
}
// 授权成功获取用户信息
AnYanUserInfoResult anYanUserInfo = anYanClient.getAnYanUserInfo(anYanAuthResult.getAccess_token());
if (anYanUserInfo == null) {
return Result.error("无法获取用户信息");
}
// 检查并更新用户信息
checkUserAndUpdate(anYanUserInfo, decryptedPassword);
// 获取本地用户信息
SysUser sysUser = sysUserService.getUserByNameOrWorkNoOrMobile(username, decryptedPassword, null);
if (sysUser == null) {
loginService.addLoginFailOvertimes(username);
return Result.error("账号或密码错误");
}
// 校验用户有效性
Result<JSONObject> result = sysUserService.checkUserIsEffective(sysUser);
if (!result.isSuccess()) {
return result;
}
// 弱密码校验
String checkMsg = CheckPasswordUtil.checkPasswordRule(decryptedPassword);
if (StrUtil.isNotBlank(checkMsg)) {
return Result.error(checkMsg);
}
// 处理用户相关信息(角色、岗位、管理部门)
loginService.processUserInfo(sysUser);
// 用户登录信息处理
userInfo(sysUser, result, clientType);
// 清除登录失败记录
redisUtil.del(CommonConstant.LOGIN_FAIL + username);
// 缓存登录用户信息
if (CLIENT_TYPE_PC.equals(clientType)) {
loginService.cacheLoginUserInfo(username, sysUser, "登录成功!");
} else if (CLIENT_TYPE_APP.equals(clientType)) {
loginService.cacheLoginUserInfo(username, sysUser, "登录成功[移动端]");
}
if (clientType.equals(CLIENT_TYPE_APP)) {
//录入登录用户的设备型号
if (com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotBlank(sysLoginModel.getDeviceType()) && com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotBlank(sysLoginModel.getDeviceSystem())) {
taskExecutor.execute(() ->
loginService.insertUserDeviceType(sysUser.getId(), sysLoginModel.getDeviceType(), sysLoginModel.getDeviceSystem()));
}
}
return result;
}
/**
* @description: 检查并更新用户信息
* @author PengJ
* @date 2025/10/27 9:50
*/
private void checkUserAndUpdate(AnYanUserInfoResult anYanUserInfo, String password) {
List<SysUser> list = sysUserService.list(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getWorkNo, anYanUserInfo.getEmployeeUnitDTO().getEmployeeNum())
);
AnYanUserInfoResult.EmployeeUnitDTO employeeUnitDTO = anYanUserInfo.getEmployeeUnitDTO();
String departId = checkDepartAndUpdate(employeeUnitDTO);
if (list.isEmpty()) {
UserEmployee user = getUserEmployee(anYanUserInfo, password, employeeUnitDTO);
user.setPassword(password);
user.setOrgCode(departId);
user.setPersonType("1");
user.setStatus(1);
healthUserEmployeeExService.saveAndUser(user);
} else if (list.size() == 1) {
SysUser sysUser = list.get(0);
UserEmployee user = getUserEmployee(anYanUserInfo, password, employeeUnitDTO);
HealthUserEmployeeEx extension = user.getExtension();
extension.setId(sysUser.getId());
user.setExtension(extension);
user.setId(sysUser.getId());
user.setPassword(null);
user.setOrgCode(departId);
LoginUser loginUser = new LoginUser();
BeanUtil.copyProperties(user, loginUser);
healthUserEmployeeExService.updateUserInfo(user, loginUser);
} else {
ExceptionAssertsUtil.fail("用户工号重复");
}
}
/**
* @description: 用户信息赋值
* @author PengJ
* @date 2025/10/27 11:29
*/
@NotNull
private static UserEmployee getUserEmployee(AnYanUserInfoResult anYanUserInfo, String password, AnYanUserInfoResult.EmployeeUnitDTO employeeUnitDTO) {
HealthUserEmployeeEx healthUserEmployeeEx = new HealthUserEmployeeEx();
healthUserEmployeeEx.setEmpSysno(employeeUnitDTO.getEmployeeNum());
UserEmployee user = new UserEmployee();
user.setExtension(healthUserEmployeeEx);
user.setWorkNo(employeeUnitDTO.getEmployeeNum());
user.setUsername(anYanUserInfo.getLoginName());
user.setRealname(employeeUnitDTO.getName());
user.setEmail(employeeUnitDTO.getEmail());
user.setPhone(employeeUnitDTO.getMobile());
user.setSex(anYanUserInfo.getGender());
return user;
}
/**
* @description: 获取用户信息
* @author PengJ
* @date 2025/10/27 15:22
*/
private void userInfo(SysUser sysUser, Result<JSONObject> result, String clientType) {
String userId = sysUser.getId();
String username = sysUser.getUsername();
// 获取用户部门信息
JSONObject obj = new JSONObject(new LinkedHashMap<>());
//1.生成token
String token = JwtUtil.sign(sysUser);
// 设置token缓存有效时间
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
obj.put("token", token);
//2.设置登录租户
Result<JSONObject> loginTenantError = sysUserService.setLoginTenant(sysUser, obj, username, result);
if (loginTenantError != null) {
return;
}
if (clientType.equals(CLIENT_TYPE_PC)) {
//设置登录部门
List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
obj.put("departs", departs);
if (departs == null || departs.isEmpty()) {
obj.put("multi_depart", 0);
} else if (departs.size() == 1) {
sysUserService.updateUserDepart(userId, departs.get(0).getOrgCode(), null);
obj.put("multi_depart", 1);
} else {
//查询当前是否有登录部门
// update-begin--Author:wangshuai Date:20200805 for:如果用戶为选择部门,数据库为存在上一次登录部门,则取一条存进去
SysUser sysUserById = sysUserService.getById(sysUser.getId());
if (oConvertUtils.isEmpty(sysUserById.getOrgCode())) {
sysUserService.updateUserDepart(userId, departs.get(0).getOrgCode(), null);
}
// update-end--Author:wangshuai Date:20200805 for:如果用戶为选择部门,数据库为存在上一次登录部门,则取一条存进去
obj.put("multi_depart", 2);
}
//获取字典数据
obj.put("sysAllDictItems", sysDictService.queryAllDictItems());
} else if (clientType.equals(CLIENT_TYPE_APP)) {
String orgCode = sysUser.getOrgCode();
if (oConvertUtils.isEmpty(orgCode)) {
//如果当前用户无选择部门 查看部门关联信息
List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
if (!departs.isEmpty()) {
orgCode = departs.get(0).getOrgCode();
sysUser.setOrgCode(orgCode);
this.sysUserService.updateUserDepart(sysUser.getId(), orgCode, null);
}
}
}
//设置登录用户信息
obj.put("userInfo", sysUser);
result.setResult(obj);
result.success("登录成功");
}
/**
* @description: 检查并更新部门信息(返回所属orgId)
* @author PengJ
* @date 2025/10/29 9:37
*/
private String checkDepartAndUpdate(AnYanUserInfoResult.EmployeeUnitDTO employeeUnitDTO) {
String levelPath = employeeUnitDTO.getLevelPath();
String orgId = "";
if (StringUtils.isNotBlank(levelPath)) {
String parentId = "";
String[] unitCodes = levelPath.split("\\|");
for (String unitCode : unitCodes) {
if (StringUtils.isBlank(unitCode) || "00000100".equals(unitCode)) {
parentId = sysDepartService.getOne(new LambdaQueryWrapper<SysDepart>()
.and(wrapper ->
wrapper.isNull(SysDepart::getParentId)
.or().eq(SysDepart::getParentId, "")
).eq(SysDepart::getOrgCategory, "1")
.eq(SysDepart::getOrgCode, "A01")
).getId();
continue;
}
SysDepart depart = sysDepartService.getOne(new LambdaQueryWrapper<SysDepart>()
.eq(SysDepart::getId, unitCode)
);
String orgName = "";
if (unitCode.equals(employeeUnitDTO.getUnitCode())) {
orgName = employeeUnitDTO.getUnitName();
} else if (unitCode.equals(employeeUnitDTO.getSectionUnitCode())) {
orgName = employeeUnitDTO.getSectionUnitName();
}
boolean save;
// 部门不存在
if (depart == null) {
depart = new SysDepart();
depart.setId(unitCode);
depart.setParentId(parentId);
JSONObject formData = new JSONObject();
formData.put("parentId", parentId);
String[] codeArray = (String[]) FillRuleUtil.executeRule(FillRuleConstant.DEPART, formData);
depart.setOrgCode(codeArray[0]);
String orgType = codeArray[1];
depart.setOrgType(String.valueOf(orgType));
depart.setDepartName(orgName);
depart.setCreateTime(new Date());
depart.setStatus("1");
depart.setDelFlag("0");
depart.setOrgCategory("2");
save = sysDepartService.save(depart);
} else {
save = sysDepartService.update(new LambdaUpdateWrapper<SysDepart>()
.eq(SysDepart::getId, depart.getId())
.set(SysDepart::getDepartName, orgName)
.set(SysDepart::getUpdateTime, new Date())
);
}
if (save) {
// 仅在父节点为叶子节点时更新为非叶子节点
SysDepart parentDepart = sysDepartService.getOne(new LambdaQueryWrapper<SysDepart>()
.eq(SysDepart::getId, parentId)
);
if (parentDepart != null && !parentDepart.getIzLeaf().equals(1)) {
sysDepartService.update(new LambdaUpdateWrapper<SysDepart>()
.eq(SysDepart::getId, parentId)
.set(SysDepart::getIzLeaf, "1")
);
}
}
parentId = depart.getId();
orgId = depart.getId();
}
}
return orgId;
}
}