This commit is contained in:
zt
2025-05-07 11:13:51 +08:00
parent b370e1122c
commit ecfee76c8d
11 changed files with 115 additions and 34 deletions

View File

@ -1,6 +1,8 @@
package com.ruoyi.web.controller.common; package com.ruoyi.web.controller.common;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.util.SseUtil; import com.ruoyi.common.util.SseUtil;
import com.ruoyi.common.utils.SecurityUtils;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -17,6 +19,9 @@ public class SseController {
*/ */
@GetMapping(path = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) @GetMapping(path = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter subscribe(String userId,String prefix) { public SseEmitter subscribe(String userId,String prefix) {
if(StrUtil.isBlank(userId)){
userId = SecurityUtils.getAppUserId().toString();
}
return SseUtil.subscribe(userId,prefix); return SseUtil.subscribe(userId,prefix);
} }

View File

@ -400,6 +400,7 @@
*/ */
@ApiOperation("【首页】【招工列表】【项目详情】 项目详情·申请报名)") @ApiOperation("【首页】【招工列表】【项目详情】 项目详情·申请报名)")
//@PreAuthorize("@ss.hasPermi('wgzApp:user:userApplyForRegistration')") //@PreAuthorize("@ss.hasPermi('wgzApp:user:userApplyForRegistration')")
@RepeatSubmit
@GetMapping("/WgzAppUserApplyForRegistration/{id}") @GetMapping("/WgzAppUserApplyForRegistration/{id}")
public AjaxResult<Boolean> userApplyForRegistration(@NotNull(message = "主键不能为空") @PathVariable("id") Long id) { public AjaxResult<Boolean> userApplyForRegistration(@NotNull(message = "主键不能为空") @PathVariable("id") Long id) {
return AjaxResult.success(iBgtProjectRecruitApplyService.userApplyForRegistration(id)); return AjaxResult.success(iBgtProjectRecruitApplyService.userApplyForRegistration(id));

View File

@ -86,6 +86,20 @@ public class Constants
* 令牌前缀 * 令牌前缀
*/ */
public static final String LOGIN_USER_KEY = "login_user_key"; public static final String LOGIN_USER_KEY = "login_user_key";
/**
* 令牌前缀
*/
public static final String LOGIN_USER_TYPE = "login_user_type";
/**
* 令牌前缀
*/
public static final String LOGIN_USER_ID = "login_user_id";
/**
* 令牌前缀
*/
public static final String LOGIN_TYEO_SYS = "sys";
/** /**
* 用户ID * 用户ID

View File

@ -101,8 +101,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
// 过滤请求 // 过滤请求
.authorizeRequests() .authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问 // 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/app/login", "/captchaImage","/demo/tress/all").anonymous() .antMatchers("/login", "/captchaImage","/demo/tress/all").anonymous()
.antMatchers("/app/login","/wgz/app/wgzRegister", .antMatchers("/app/login","/wgz/app/wgzRegister","/sse/subscribe",
"/app/bgt/recruit/htmlList","/app/bgt/apply/htmlList","/common/annex/getHtmlWgzAnnex" "/app/bgt/recruit/htmlList","/app/bgt/apply/htmlList","/common/annex/getHtmlWgzAnnex"
,"/download-folders","/upload-zip").permitAll() ,"/download-folders","/upload-zip").permitAll()
.antMatchers( .antMatchers(

View File

@ -28,8 +28,10 @@ public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, S
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
throws IOException throws IOException
{ {
int code = HttpStatus.HTTP_UNAUTHORIZED; int code = HttpStatus.HTTP_UNAUTHORIZED;
String msg = StrUtil.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); // String msg = StrUtil.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
String msg = StrUtil.format("登录过期或其他设备登录");
ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(code, msg))); ServletUtils.renderString(response, JsonUtils.toJsonString(AjaxResult.error(code, msg)));
} }
} }

View File

@ -43,7 +43,8 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
if (Validator.isNotNull(loginUser)) { if (Validator.isNotNull(loginUser)) {
String userName = loginUser.getUsername(); String userName = loginUser.getUsername();
// 删除用户缓存记录 // 删除用户缓存记录
tokenService.delLoginUser(loginUser.getToken()); // tokenService.delLoginUser(loginUser.getToken());
tokenService.delLoginUser(loginUser.getUserType(),loginUser.getUser().getUserId().toString());
// 记录用户退出日志 // 记录用户退出日志
asyncService.recordLogininfor(userName, Constants.LOGOUT, "退出成功", request); asyncService.recordLogininfor(userName, Constants.LOGOUT, "退出成功", request);
} }

View File

@ -7,12 +7,15 @@ import cn.hutool.http.useragent.UserAgentUtil;
import com.ruoyi.common.constant.Constants; import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.BaseException;
import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.framework.config.properties.TokenProperties; import com.ruoyi.framework.config.properties.TokenProperties;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -29,6 +32,8 @@ import java.util.concurrent.TimeUnit;
@Component @Component
public class TokenService { public class TokenService {
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
@ -48,13 +53,23 @@ public class TokenService {
*/ */
public LoginUser getLoginUser(HttpServletRequest request) { public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌 // 获取请求携带的令牌
String token = getToken(request); String token = getToken(request);
// log.info("用户当前token{}", token);
if (Validator.isNotEmpty(token)) { if (Validator.isNotEmpty(token)) {
Claims claims = parseToken(token); Claims claims = parseToken(token);
// 解析对应的权限以及用户信息 // 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid); //String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey); String type = (String) claims.get(Constants.LOGIN_USER_TYPE);
String userId = (String) claims.get(Constants.LOGIN_USER_ID);
String userKey = getTokenKey(type, userId);
LoginUser user = redisCache.getCacheObject(userKey);
// log.info("用户当前类型:{}", claims);
if(!uuid.equals(user.getToken())){
throw new BaseException("999","您的账号在其他设备登录");
}
return user; return user;
} }
return null; return null;
@ -79,6 +94,13 @@ public class TokenService {
} }
} }
public void delLoginUser(String userType,String userId) {
if (Validator.isNotEmpty(userType) && Validator.isNotEmpty(userId)) {
String userKey = getTokenKey(userType,userId);
redisCache.deleteObject(userKey);
}
}
/** /**
* 创建令牌 * 创建令牌
* *
@ -93,6 +115,8 @@ public class TokenService {
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token); claims.put(Constants.LOGIN_USER_KEY, token);
claims.put(Constants.LOGIN_USER_TYPE, loginUser.getUserType());
claims.put(Constants.LOGIN_USER_ID, loginUser.getUser().getUserId().toString());
return createToken(claims); return createToken(claims);
} }
@ -119,7 +143,9 @@ public class TokenService {
loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE); loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE);
// 根据uuid将loginUser缓存 // 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken()); // String userKey = getTokenKey(loginUser.getToken());
String userType = loginUser.getUserType();
String userKey = getTokenKey(userType,loginUser.getUser().getUserId().toString());
redisCache.setCacheObject(userKey, loginUser, tokenProperties.getExpireTime(), TimeUnit.MINUTES); redisCache.setCacheObject(userKey, loginUser, tokenProperties.getExpireTime(), TimeUnit.MINUTES);
} }
@ -191,4 +217,8 @@ public class TokenService {
private String getTokenKey(String uuid) { private String getTokenKey(String uuid) {
return Constants.LOGIN_TOKEN_KEY + uuid; return Constants.LOGIN_TOKEN_KEY + uuid;
} }
private String getTokenKey(String userType,String userId) {
return Constants.LOGIN_TOKEN_KEY + userType+"-"+userId;
}
} }

View File

@ -1,6 +1,7 @@
package com.ruoyi.framework.web.service; package com.ruoyi.framework.web.service;
import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.Validator;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.enums.UserStatus;
@ -55,6 +56,6 @@ public class UserDetailsServiceImpl implements UserDetailsService
public UserDetails createLoginUser(SysUser user) public UserDetails createLoginUser(SysUser user)
{ {
return new LoginUser(user, permissionService.getMenuPermission(user)); return new LoginUser(user, permissionService.getMenuPermission(user)).setUserType(Constants.LOGIN_TYEO_SYS);
} }
} }

View File

@ -662,18 +662,21 @@ public class BgtProjectRecruitApplyServiceImpl extends ServicePlusImpl<BgtProjec
bgtProjectRecruitApply.setId(recruitApplyId); bgtProjectRecruitApply.setId(recruitApplyId);
bgtProjectRecruitApply.setStatus("7"); bgtProjectRecruitApply.setStatus("7");
if (baseMapper.updateById(bgtProjectRecruitApply) > 0) { if (baseMapper.updateById(bgtProjectRecruitApply) > 0) {
Long appUserId = SecurityUtils.getAppUserId();
BgtProjectRecruitApply by = baseMapper.selectById(recruitApplyId);
BgtProjectRecruit appById = iBgtProjectRecruitService.getAppById(by.getRecruitId());
//状态取消成功需要把对应消息的状态变更为【不需要操作】 //状态取消成功需要把对应消息的状态变更为【不需要操作】
iBgtMessageService.operation( iBgtMessageService.operation(
USERTYPE_WGZ, USERTYPE_WGZ,
recruitApply.getUserId(), recruitApply.getUserId(),
USERTYPE_BGT, USERTYPE_BGT,
SecurityUtils.getAppUserId(), appById.getUserId(),
recruitApply.getId(), recruitApply.getId(),
SqlHelper.table(BgtProjectRecruitApply.class).getTableName() SqlHelper.table(BgtProjectRecruitApply.class).getTableName()
); );
Long appUserId = SecurityUtils.getAppUserId();
BgtProjectRecruitApply by = baseMapper.selectById(recruitApplyId);
BgtProjectRecruit appById = iBgtProjectRecruitService.getAppById(by.getRecruitId());
Map<String, String> mp = new HashMap<>(); Map<String, String> mp = new HashMap<>();
mp.put("projectName", appById.getRecruitName()); mp.put("projectName", appById.getRecruitName());
//发送取消报名的系统消息 //发送取消报名的系统消息
@ -713,25 +716,28 @@ public class BgtProjectRecruitApplyServiceImpl extends ServicePlusImpl<BgtProjec
BgtProjectRecruitApply recruitApply = queryById(req.getRecruitApplyId()); BgtProjectRecruitApply recruitApply = queryById(req.getRecruitApplyId());
BgtProjectRecruit recruit = iBgtProjectRecruitService.getAppById(recruitApply.getRecruitId()); BgtProjectRecruit recruit = iBgtProjectRecruitService.getAppById(recruitApply.getRecruitId());
//数据库行级锁(判断是否满员或失效 //数据库行级锁(判断是否满员或失效
Map<String, Object> judMp = iWgzMessageService.JudgingRecruitment(recruit.getId(), recruit.getRecruitStaffNum(), recruit.getRecruitEndTime()); if (req.getStatus().equals("3")) {
String status = judMp.get("status").toString(); Map<String, Object> judMp = iWgzMessageService.JudgingRecruitment(recruit.getId(), recruit.getRecruitStaffNum(), recruit.getRecruitEndTime());
switch (status) { String status = judMp.get("status").toString();
case "1": switch (status) {
case "1":
// //异步修改状态为已招满 // //异步修改状态为已招满
// iAsyncService.updateRecruitStatus(recruit); // iAsyncService.updateRecruitStatus(recruit);
throw new RuntimeException("已招满!"); throw new RuntimeException("已招满!");
case "2": case "2":
throw new RuntimeException("已失效!"); throw new RuntimeException("已失效!");
} }
int count = ((Number) judMp.get("count")).intValue() + 1; int count = ((Number) judMp.get("count")).intValue() + 1;
if (count == recruit.getRecruitStaffNum()){ if (count == recruit.getRecruitStaffNum()){
iAsyncService.updateRecruitStatus(recruit); iAsyncService.updateRecruitStatus(recruit);
} }
//数据库行级锁(是否进入其他工地) //数据库行级锁(是否进入其他工地)
Integer i = iWgzService.QueryWhetherTheCurrentUserHasAnOngoingProject(byUserId.getUserId()); Integer i = iWgzService.QueryWhetherTheCurrentUserHasAnOngoingProject(byUserId.getUserId());
if (i>0) { if (i>0) {
throw new RuntimeException("您已在其他工地!"); throw new RuntimeException("您已在其他工地!");
}
} }
//3、更新报名状态、及更新消息的操作状态用户同意更新进场时间、状态用户拒绝更新状态 //3、更新报名状态、及更新消息的操作状态用户同意更新进场时间、状态用户拒绝更新状态
BgtProjectRecruitApply apply = new BgtProjectRecruitApply(); BgtProjectRecruitApply apply = new BgtProjectRecruitApply();
apply.setId(req.getRecruitApplyId()); apply.setId(req.getRecruitApplyId());

View File

@ -1,5 +1,6 @@
package com.ruoyi.common.util; package com.ruoyi.common.util;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
@ -8,6 +9,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -22,11 +25,27 @@ public class SseUtil {
public static final String FBS_PREFIX = "fbs-"; public static final String FBS_PREFIX = "fbs-";
public static final String ZBF_PREFIX = "zbf-"; public static final String ZBF_PREFIX = "zbf-";
public static SseEmitter subscribe(String userId,String prefix) { public static SseEmitter subscribe(String userId,String prefix) {
// Spring Boot 2.4.7中SseEmitter构造函数支持超时时间单位毫秒 // Spring Boot 2.4.7中SseEmitter构造函数支持超时时间单位毫秒 30 * 60
SseEmitter emitter = new SseEmitter(1 * 60 * 1000L); // 30分钟超时 SseEmitter emitter = new SseEmitter(30 * 60 * 1000L); // 30分钟超时
// 存储emitter并设置回调2.4.7中回调机制与主流版本一致) // 存储emitter并设置回调2.4.7中回调机制与主流版本一致)
String key = prefix + "-" +userId; String key = prefix + "-" +userId;
SseEmitter sseEmitter = emitterMap.get(key);
if(sseEmitter!=null){
try {
HashMap<String, Object> map = new HashMap<>();
map.put("code",401);
map.put("msg","您已在其他地方登录,请重新登录");
sseEmitter.send(SseEmitter.event()
// .id("init")
// .name(name)
.data(JSONUtil.toJsonStr(map))
);
} catch (IOException e) {
sseEmitter.completeWithError(e);
}
}
emitterMap.put(key, emitter); emitterMap.put(key, emitter);
// 利用闭包特性捕获userId和prefix // 利用闭包特性捕获userId和prefix
@ -34,10 +53,13 @@ public class SseUtil {
emitter.onTimeout(() -> { emitter.onTimeout(() -> {
emitter.complete(); emitter.complete();
emitterMap.remove(key); emitterMap.remove(key);
log.info("用户{}连接已超时", key); log.info("时间:{},用户{}连接已超时", LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss"),key);
});
emitter.onError(e -> {
emitter.completeWithError(e);
emitterMap.remove(key);
log.warn("用户{}连接异常: {}", key, e.getMessage());
}); });
emitter.onError(e -> emitterMap.remove(key));
String redisKey = "messageCount:"+prefix+":"+userId; String redisKey = "messageCount:"+prefix+":"+userId;
Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(redisKey); Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(redisKey);

View File

@ -713,7 +713,6 @@ public class WgzPayCalculationServiceImpl extends ServicePlusImpl<WgzPayCalculat
//金额*天数=实际工资 //金额*天数=实际工资
total = total.add(wgzPayCalculation.getRecruitAmount().multiply(new BigDecimal(wgzPayCalculation.getNum()))); total = total.add(wgzPayCalculation.getRecruitAmount().multiply(new BigDecimal(wgzPayCalculation.getNum())));
} }
return total; return total;
} }
} }