diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/CustomSessionRegistry.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/CustomSessionRegistry.java new file mode 100644 index 0000000..7c97b2d --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/CustomSessionRegistry.java @@ -0,0 +1,151 @@ +package com.sztzjy.forex.trading_trading.config.security; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.log.LogMessage; +import org.springframework.security.core.session.*; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * 自定义session管理 + * 该方法创建是为解决同一个用户在同一台电脑调同一个接口SessionRegistryImpl的registerNewSession会生成不同的会话id(相当于一个用户在缓存中有好几个会话id) + * 故重构该方法便于后台管理sessionId + * + * @author 陈沅 + * @date 2023/07/06 22:27:47 + */ + +@Component +public class CustomSessionRegistry implements SessionRegistry { + protected final Log logger = LogFactory.getLog(CustomSessionRegistry.class); + private final ConcurrentMap> principals; + private final Map sessionIds; + + public CustomSessionRegistry() { + this.principals = new ConcurrentHashMap(); + this.sessionIds = new ConcurrentHashMap(); + } + + public CustomSessionRegistry(ConcurrentMap> principals, Map sessionIds) { + this.principals = principals; + this.sessionIds = sessionIds; + } + + public List getAllPrincipals() { + return new ArrayList(this.principals.keySet()); + } + + public List getAllSessions(Object principal, boolean includeExpiredSessions) { + Set sessionsUsedByPrincipal = (Set)this.principals.get(principal); + if (sessionsUsedByPrincipal == null) { + return Collections.emptyList(); + } else { + List list = new ArrayList(sessionsUsedByPrincipal.size()); + Iterator var5 = sessionsUsedByPrincipal.iterator(); + + while(true) { + SessionInformation sessionInformation; + do { + do { + if (!var5.hasNext()) { + return list; + } + + String sessionId = (String)var5.next(); + sessionInformation = this.getSessionInformation(sessionId); + } while(sessionInformation == null); + } while(!includeExpiredSessions && sessionInformation.isExpired()); + + list.add(sessionInformation); + } + } + } + + public SessionInformation getSessionInformation(String sessionId) { + Assert.hasText(sessionId, "SessionId required as per interface contract"); + return (SessionInformation)this.sessionIds.get(sessionId); + } + + public void onApplicationEvent(AbstractSessionEvent event) { + String oldSessionId; + if (event instanceof SessionDestroyedEvent) { + SessionDestroyedEvent sessionDestroyedEvent = (SessionDestroyedEvent)event; + oldSessionId = sessionDestroyedEvent.getId(); + this.removeSessionInformation(oldSessionId); + } else if (event instanceof SessionIdChangedEvent) { + SessionIdChangedEvent sessionIdChangedEvent = (SessionIdChangedEvent)event; + oldSessionId = sessionIdChangedEvent.getOldSessionId(); + if (this.sessionIds.containsKey(oldSessionId)) { + Object principal = ((SessionInformation)this.sessionIds.get(oldSessionId)).getPrincipal(); + this.removeSessionInformation(oldSessionId); + this.registerNewSession(sessionIdChangedEvent.getNewSessionId(), principal); + } + } + + } + + public void refreshLastRequest(String sessionId) { + Assert.hasText(sessionId, "SessionId required as per interface contract"); + SessionInformation info = this.getSessionInformation(sessionId); + if (info != null) { + info.refreshLastRequest(); + } + + } + + public void registerNewSession(String sessionId, Object principal) { + Assert.hasText(sessionId, "SessionId required as per interface contract"); + Assert.notNull(principal, "Principal required as per interface contract"); + if(principal instanceof JwtUser){ + sessionId = ((JwtUser) principal).getUsername(); + } + if (this.getSessionInformation(sessionId) != null) { + this.removeSessionInformation(sessionId); + } + + if (this.logger.isDebugEnabled()) { + this.logger.debug(LogMessage.format("Registering session %s, for principal %s", sessionId, principal)); + } + + this.sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date())); + String finalSessionId = sessionId; + this.principals.compute(principal, (key, sessionsUsedByPrincipal) -> { + if (sessionsUsedByPrincipal == null) { + sessionsUsedByPrincipal = new CopyOnWriteArraySet(); + } + + ((Set)sessionsUsedByPrincipal).add(finalSessionId); + this.logger.trace(LogMessage.format("Sessions used by '%s' : %s", principal, sessionsUsedByPrincipal)); + return (Set)sessionsUsedByPrincipal; + }); + } + + public void removeSessionInformation(String sessionId) { + Assert.hasText(sessionId, "SessionId required as per interface contract"); + SessionInformation info = this.getSessionInformation(sessionId); + if (info != null) { + if (this.logger.isTraceEnabled()) { + this.logger.debug("Removing session " + sessionId + " from set of registered sessions"); + } + + this.sessionIds.remove(sessionId); + this.principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> { + this.logger.debug(LogMessage.format("Removing session %s from principal's set of registered sessions", sessionId)); + sessionsUsedByPrincipal.remove(sessionId); + if (sessionsUsedByPrincipal.isEmpty()) { + this.logger.debug(LogMessage.format("Removing principal %s from registry", info.getPrincipal())); + sessionsUsedByPrincipal = null; + } + + this.logger.trace(LogMessage.format("Sessions used by '%s' : %s", info.getPrincipal(), sessionsUsedByPrincipal)); + return sessionsUsedByPrincipal; + }); + } + } +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/JwtUser.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/JwtUser.java index 5245794..752064f 100644 --- a/src/main/java/com/sztzjy/forex/trading_trading/config/security/JwtUser.java +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/JwtUser.java @@ -7,6 +7,7 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; +import java.util.Objects; /** * jwt用户对象 @@ -53,4 +54,18 @@ public class JwtUser implements UserDetails { public boolean isEnabled() { return false; } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JwtUser)) return false; + JwtUser user = (JwtUser) o; + return username.equals(user.username); + } + + @Override + public int hashCode() { + return Objects.hash(username); + } } diff --git a/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebSecurityConfig.java b/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebSecurityConfig.java index 6e0ec44..aa25b43 100644 --- a/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebSecurityConfig.java +++ b/src/main/java/com/sztzjy/forex/trading_trading/config/security/WebSecurityConfig.java @@ -12,6 +12,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -54,6 +55,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { Map handlerMethods = handlerMapping.getHandlerMethods(); Set anonymousUrls = discoveryAnonymousUrls(handlerMethods); http + .sessionManagement(sessionManagement -> + sessionManagement + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .maximumSessions(1) + .sessionRegistry(sessionRegistry()) + ) .addFilterBefore(new AuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) .cors().and() .headers().addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Headers", "Authorization")) @@ -64,12 +71,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .authenticationEntryPoint(customAuthenticationEntryPoint) .accessDeniedHandler(customAccessDeniedHandler) // 防止iframe 造成跨域 - .and().headers().frameOptions().disable() + .and().headers().frameOptions().disable().and() // 不创建会话 - .and() - .sessionManagement() - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and() + // .and() + // .sessionManagement() + // .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + // .sessionRegistry(sessionRegistry()) + // .and() // 所有请求都需要认证 .authorizeRequests() .antMatchers("/swagger-resources/**").permitAll() @@ -82,6 +90,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { .anyRequest().authenticated(); } + /** * 认证相关的核心接口 * @@ -114,4 +123,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { return patterns; } + + @Bean + public SessionRegistry sessionRegistry() { + return new CustomSessionRegistry(); + } + } diff --git a/src/main/java/com/sztzjy/forex/trading_trading/controller/LogController.java b/src/main/java/com/sztzjy/forex/trading_trading/controller/LogController.java index 7c4e493..0431c30 100644 --- a/src/main/java/com/sztzjy/forex/trading_trading/controller/LogController.java +++ b/src/main/java/com/sztzjy/forex/trading_trading/controller/LogController.java @@ -1,6 +1,7 @@ package com.sztzjy.forex.trading_trading.controller; import com.sztzjy.forex.trading_trading.annotation.AnonymousAccess; +import com.sztzjy.forex.trading_trading.config.security.JwtUser; import com.sztzjy.forex.trading_trading.dto.PageVO; import com.sztzjy.forex.trading_trading.entity.Log; import com.sztzjy.forex.trading_trading.service.LogService; @@ -11,11 +12,17 @@ import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; +import org.springframework.security.core.session.SessionInformation; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.List; + @Api(tags = "系统:操作日志") @RestController @RequestMapping("api/syslog") @@ -38,4 +45,6 @@ public class LogController { pageVO.setPageInfo(page, page.getContent()); return new ResultEntity<>(HttpStatus.OK, pageVO); } + + } diff --git a/src/main/java/com/sztzjy/forex/trading_trading/controller/StatisticalController.java b/src/main/java/com/sztzjy/forex/trading_trading/controller/StatisticalController.java new file mode 100644 index 0000000..ba45c82 --- /dev/null +++ b/src/main/java/com/sztzjy/forex/trading_trading/controller/StatisticalController.java @@ -0,0 +1,43 @@ +package com.sztzjy.forex.trading_trading.controller; + + +import com.sztzjy.forex.trading_trading.annotation.Permission; +import com.sztzjy.forex.trading_trading.annotation.aspect.PermissionType; +import com.sztzjy.forex.trading_trading.config.security.JwtUser; +import com.sztzjy.forex.trading_trading.config.security.TokenProvider; +import com.sztzjy.forex.trading_trading.mappers.MemberMapper; +import com.sztzjy.forex.trading_trading.service.MemberService; +import com.sztzjy.forex.trading_trading.util.ResultEntity; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; + +@Api(tags = "统计管理") +@RestController +@RequiredArgsConstructor +@RequestMapping("api/statistical") +public class StatisticalController { + + + private final HttpServletRequest request; + + private final MemberService memberService; + + @Permission(codes = PermissionType.DATA_STATISTICS) + @ApiOperation("教师端----班级成绩走势统计") + @GetMapping("classScoreTrend") + public ResultEntity>> classScoreTrend() { + JwtUser currentUser = TokenProvider.getJWTUser(request); + return new ResultEntity(HttpStatus.OK,memberService.classScoreTrend(currentUser.getSchoolId())); + } + + +} diff --git a/src/main/java/com/sztzjy/forex/trading_trading/controller/TrainingLessonPlanController.java b/src/main/java/com/sztzjy/forex/trading_trading/controller/TrainingLessonPlanController.java index 6cbfa6c..ce95f3b 100644 --- a/src/main/java/com/sztzjy/forex/trading_trading/controller/TrainingLessonPlanController.java +++ b/src/main/java/com/sztzjy/forex/trading_trading/controller/TrainingLessonPlanController.java @@ -30,7 +30,6 @@ public class TrainingLessonPlanController { private final TrainingLessonPlanService trainingLessonPlanService; private final HttpServletRequest request; - private final IFileUtil fileUtil; @Permission(codes = PermissionType.TRAINING_PLAN_MANAGEMENT_ADD) @@ -64,7 +63,7 @@ public class TrainingLessonPlanController { } @Permission(codes = PermissionType.TRAINING_PLAN_MANAGEMENT_SEARCH) - @ApiOperation("根据教案名字查询教案") + @ApiOperation("根据教案名字查询教案(分页展示)") @GetMapping("findByName") public ResultEntity> findByName(@ApiParam("教案名称") @RequestParam String name, @ApiParam("分页索引:{0}为第一页") @RequestParam(required = false) Integer index, diff --git a/src/main/java/com/sztzjy/forex/trading_trading/controller/UserController.java b/src/main/java/com/sztzjy/forex/trading_trading/controller/UserController.java index cc8e49b..4fabc82 100644 --- a/src/main/java/com/sztzjy/forex/trading_trading/controller/UserController.java +++ b/src/main/java/com/sztzjy/forex/trading_trading/controller/UserController.java @@ -14,20 +14,21 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.session.SessionInformation; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.HashSet; +import java.util.List; +import java.util.Set; @Api(tags = "用户管理模块") @RestController @@ -35,8 +36,10 @@ import javax.annotation.Resource; @RequiredArgsConstructor public class UserController { - @Autowired - private RoleAuthorityService roleAuthorityService; + + private final RoleAuthorityService roleAuthorityService; + + private final SessionRegistry sessionRegistry; @AnonymousAccess @OperateLog(description = "用户登录") @@ -51,11 +54,11 @@ public class UserController { } catch (Exception e) { throw new IllegalArgumentException("密码错误"); } - String md5Pwd =RsaUtil.calculateMD5(password); + String md5Pwd = RsaUtil.calculateMD5(password); String hashPwd = RsaUtil.formatHash(md5Pwd); JwtUser jwtUser = TzApi.foreignExchangeTradingLogin(username, hashPwd); jwtUser.setAuthorityCodes(roleAuthorityService.getAuthorityByRoleId(jwtUser.getRoleId())); - Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); + Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); String token = TokenProvider.createToken(jwtUser); return new ResultEntity(LoginResult.create(jwtUser, token)); @@ -69,12 +72,49 @@ public class UserController { public ResultEntity loginByZhiYunToken(@ApiParam("智云平台token") @RequestParam String zhiYunToken) { Assert.isTrue(StringUtils.hasText(zhiYunToken), "token不能为空"); JwtUser jwtUser = TokenProvider.getJWTUserByZhiYun(zhiYunToken); - Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); + Authentication authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); String token = TokenProvider.createToken(jwtUser); return new ResultEntity(LoginResult.create(jwtUser, token)); } + + + @ApiOperation(value = "注销登录", httpMethod = "POST") + @PostMapping("/logout") + public ResultEntity logout(HttpServletRequest request) { + // 获取当前认证信息 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + // 执行注销操作 + new SecurityContextLogoutHandler().logout(request, null, authentication); + sessionRegistry.removeSessionInformation(request.getSession().getId()); + } + return new ResultEntity(HttpStatus.OK); + } + + + +// @Permission() + @AnonymousAccess + @ApiOperation("获取当前在线用户") + @GetMapping("online-users") + public Set getOnlineUsers() { + List principals = sessionRegistry.getAllPrincipals(); + Set usernames = new HashSet<>(); + + for (Object principal : principals) { + if (principal instanceof JwtUser) { + JwtUser user = (JwtUser) principal; + List sessions = sessionRegistry.getAllSessions(user, false); + if (!sessions.isEmpty()) { + usernames.add(user.getUsername()); + } + } + } + return usernames; + } + } diff --git a/src/main/java/com/sztzjy/forex/trading_trading/mappers/MemberMapper.java b/src/main/java/com/sztzjy/forex/trading_trading/mappers/MemberMapper.java index 3854dc9..2fe2e6f 100644 --- a/src/main/java/com/sztzjy/forex/trading_trading/mappers/MemberMapper.java +++ b/src/main/java/com/sztzjy/forex/trading_trading/mappers/MemberMapper.java @@ -3,10 +3,13 @@ package com.sztzjy.forex.trading_trading.mappers; import com.sztzjy.forex.trading_trading.dto.MemberVO; import com.sztzjy.forex.trading_trading.entity.Member; import com.sztzjy.forex.trading_trading.entity.MemberExample; + import java.util.List; +import java.util.Map; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; @Mapper public interface MemberMapper { @@ -62,7 +65,6 @@ public interface MemberMapper { List selectByExample(MemberExample example); - List pagedListMembers(MemberExample example); /** @@ -104,4 +106,28 @@ public interface MemberMapper { * @mbg.generated Fri Jun 30 14:15:42 CST 2023 */ int updateByPrimaryKey(Member record); + + @Select("SELECT" + + " training_name," + + " class_grade," + + " (" + + " SUM((" + + " SELECT" + + " actual_score " + + " FROM" + + " sys_grade_weight " + + " WHERE" + + " max_profit > sys_member.yield " + + " AND min_profit <= sys_member.yield " + + " AND weight_id = ( SELECT weight_id FROM sys_grade_weight WHERE school_id = " + + "sys_member.school_id ORDER BY create_time DESC LIMIT 1 )))+ 50 " +//该位置+50是报告的分数 + " )/ count( 1 ) AS avgScore " + + "FROM" + + " sys_member " + + "WHERE" + + " school_id = #{schoolId} " + + "GROUP BY" + + " training_name," + + " class_grade") + List> classScoreTrend(@Param("schoolId") Integer schoolId); } \ No newline at end of file diff --git a/src/main/java/com/sztzjy/forex/trading_trading/service/MemberService.java b/src/main/java/com/sztzjy/forex/trading_trading/service/MemberService.java index 59050d5..6163d1a 100644 --- a/src/main/java/com/sztzjy/forex/trading_trading/service/MemberService.java +++ b/src/main/java/com/sztzjy/forex/trading_trading/service/MemberService.java @@ -20,6 +20,7 @@ import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; @Service public class MemberService { @@ -132,4 +133,8 @@ public class MemberService { fileUtil.download(response, fileName); fileUtil.remove(file); } + + public List> classScoreTrend(Integer schoolId){ + return memberMapper.classScoreTrend(schoolId); + } }