commit:1.6版本发布

This commit is contained in:
Jerry
2021-05-02 20:19:06 +08:00
parent 1a713fa34f
commit 5f9a18a8eb
78 changed files with 1183 additions and 826 deletions

View File

@@ -17,7 +17,7 @@
<!-- 业务组件依赖 -->
<dependency>
<groupId>com.orange.demo</groupId>
<artifactId>common-core</artifactId>
<artifactId>common-log</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>

View File

@@ -31,8 +31,8 @@
<sql id="inputFilterRef">
<if test="courseFilter != null">
<if test="courseFilter.courseName != null and courseFilter.courseName != ''">
<bind name = "safeCourseName" value = "'%' + courseFilter.courseName + '%'" />
AND zz_course.course_name LIKE #{safeCourseName}
<bind name = "safeCourseCourseName" value = "'%' + courseFilter.courseName + '%'" />
AND zz_course.course_name LIKE #{safeCourseCourseName}
</if>
<if test="courseFilter.priceStart != null">
AND zz_course.price &gt;= #{courseFilter.priceStart}

View File

@@ -18,8 +18,8 @@
<sql id="inputFilterRef">
<if test="schoolInfoFilter != null">
<if test="schoolInfoFilter.schoolName != null and schoolInfoFilter.schoolName != ''">
<bind name = "safeSchoolName" value = "'%' + schoolInfoFilter.schoolName + '%'" />
AND zz_school_info.school_name LIKE #{safeSchoolName}
<bind name = "safeSchoolInfoSchoolName" value = "'%' + schoolInfoFilter.schoolName + '%'" />
AND zz_school_info.school_name LIKE #{safeSchoolInfoSchoolName}
</if>
<if test="schoolInfoFilter.provinceId != null">
AND zz_school_info.province_id = #{schoolInfoFilter.provinceId}

View File

@@ -59,8 +59,8 @@
AND zz_student.status = #{studentFilter.status}
</if>
<if test="studentFilter.searchString != null and studentFilter.searchString != ''">
<bind name = "safeSearchString" value = "'%' + studentFilter.searchString + '%'" />
AND CONCAT(IFNULL(zz_student.login_mobile,''), IFNULL(zz_student.student_name,'')) LIKE #{safeSearchString}
<bind name = "safeStudentSearchString" value = "'%' + studentFilter.searchString + '%'" />
AND CONCAT(IFNULL(zz_student.login_mobile,''), IFNULL(zz_student.student_name,'')) LIKE #{safeStudentSearchString}
</if>
</if>
</sql>

View File

@@ -86,7 +86,6 @@ public class StudentDto {
/**
* 可用学币数量。
*/
@NotNull(message = "数据验证失败,剩余学币不能为空!", groups = {UpdateGroup.class})
private Integer leftCoin;
/**

View File

@@ -47,5 +47,5 @@ public class ApplicationConfig {
* Session的用户权限在Redis中的过期时间(秒)。
* 缺省值是 one day
*/
private int permRedisExpiredSeconds = 86400;
private int sessionExpiredSeconds = 86400;
}

View File

@@ -12,15 +12,15 @@ import com.orange.demo.common.core.object.TokenData;
import com.orange.demo.common.core.util.ApplicationContextHolder;
import com.orange.demo.common.core.util.JwtUtil;
import com.orange.demo.common.core.util.RedisKeyUtil;
import com.orange.demo.common.redis.cache.SessionCacheHelper;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -42,10 +42,7 @@ public class AuthenticationInterceptor implements HandlerInterceptor {
private final ApplicationConfig appConfig =
ApplicationContextHolder.getBean("applicationConfig");
private final JedisPool jedisPool = ApplicationContextHolder.getBean(JedisPool.class);
private final SessionCacheHelper cacheHelper =
ApplicationContextHolder.getBean("sessionCacheHelper");
private final RedissonClient redissonClient = ApplicationContextHolder.getBean(RedissonClient.class);
private final SysPermService sysPermService =
ApplicationContextHolder.getBean(SysPermService.class);
@@ -85,7 +82,12 @@ public class AuthenticationInterceptor implements HandlerInterceptor {
return false;
}
String sessionId = (String) c.get("sessionId");
TokenData tokenData = cacheHelper.getTokenData(sessionId);
String sessionIdKey = RedisKeyUtil.makeSessionIdKeyForRedis(sessionId);
RBucket<String> sessionData = redissonClient.getBucket(sessionIdKey);
TokenData tokenData = null;
if (sessionData.isExists()) {
tokenData = JSON.parseObject(sessionData.get(), TokenData.class);
}
if (tokenData == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
this.outputResponseMessage(response,
@@ -95,13 +97,11 @@ public class AuthenticationInterceptor implements HandlerInterceptor {
TokenData.addToRequest(tokenData);
// 如果url在权限资源白名单中则不需要进行鉴权操作
if (Boolean.FALSE.equals(tokenData.getIsAdmin()) && !whitelistPermSet.contains(url)) {
try (Jedis jedis = jedisPool.getResource()) {
if (!jedis.sismember(RedisKeyUtil.makeSessionPermIdKeyForRedis(tokenData.getSessionId()), url)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
this.outputResponseMessage(response,
ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION));
return false;
}
RSet<String> permSet = redissonClient.getSet(RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId));
if (!permSet.contains(url)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
this.outputResponseMessage(response, ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION));
return false;
}
}
if (JwtUtil.needToRefresh(c)) {

View File

@@ -1,6 +1,8 @@
package com.orange.demo.webadmin.upms.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import com.orange.demo.webadmin.config.ApplicationConfig;
import com.orange.demo.webadmin.upms.service.*;
@@ -12,10 +14,11 @@ import com.orange.demo.common.core.annotation.NoAuthInterface;
import com.orange.demo.common.core.annotation.MyRequestBody;
import com.orange.demo.common.core.constant.ApplicationConstant;
import com.orange.demo.common.core.constant.ErrorCodeEnum;
import com.orange.demo.common.core.object.ResponseResult;
import com.orange.demo.common.core.object.TokenData;
import com.orange.demo.common.core.object.*;
import com.orange.demo.common.core.util.*;
import com.orange.demo.common.redis.cache.SessionCacheHelper;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@@ -23,6 +26,7 @@ import org.springframework.web.bind.annotation.*;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 登录接口控制器类。
@@ -46,6 +50,8 @@ public class LoginController {
@Autowired
private ApplicationConfig appConfig;
@Autowired
private RedissonClient redissonClient;
@Autowired
private SessionCacheHelper cacheHelper;
@Autowired
private PasswordEncoder passwordEncoder;
@@ -77,38 +83,7 @@ public class LoginController {
errorMessage = "登录失败,用户账号被锁定!";
return ResponseResult.error(ErrorCodeEnum.INVALID_USER_STATUS, errorMessage);
}
boolean isAdmin = user.getUserType() == SysUserType.TYPE_ADMIN;
Map<String, Object> claims = new HashMap<>(3);
String sessionId = MyCommonUtil.generateUuid();
claims.put("sessionId", sessionId);
String token = JwtUtil.generateToken(claims, appConfig.getExpiration(), appConfig.getTokenSigningKey());
JSONObject jsonData = new JSONObject();
jsonData.put(TokenData.REQUEST_ATTRIBUTE_NAME, token);
jsonData.put("showName", user.getShowName());
jsonData.put("isAdmin", isAdmin);
TokenData tokenData = new TokenData();
tokenData.setSessionId(sessionId);
tokenData.setUserId(user.getUserId());
tokenData.setShowName(user.getShowName());
tokenData.setIsAdmin(isAdmin);
cacheHelper.putTokenData(sessionId, tokenData);
// 这里手动将TokenData存入request便于OperationLogAspect统一处理操作日志。
TokenData.addToRequest(tokenData);
Collection<SysMenu> menuList;
Collection<String> permCodeList;
if (isAdmin) {
menuList = sysMenuService.getAllMenuList();
permCodeList = sysPermCodeService.getAllPermCodeList();
} else {
menuList = sysMenuService.getMenuListByUserId(user.getUserId());
permCodeList = sysPermCodeService.getPermCodeListByUserId(user.getUserId());
}
jsonData.put("menuList", menuList);
jsonData.put("permCodeList", permCodeList);
if (user.getUserType() != SysUserType.TYPE_ADMIN) {
// 缓存用户的权限资源
sysPermService.putUserSysPermCache(sessionId, user.getUserId());
}
JSONObject jsonData = this.buildLoginData(user);
return ResponseResult.success(jsonData);
}
@@ -120,6 +95,8 @@ public class LoginController {
@PostMapping("/doLogout")
public ResponseResult<Void> doLogout() {
TokenData tokenData = TokenData.takeFromRequest();
String sessionIdKey = RedisKeyUtil.makeSessionIdKeyForRedis(tokenData.getSessionId());
redissonClient.getBucket(sessionIdKey).delete();
sysPermService.removeUserSysPermCache(tokenData.getSessionId());
cacheHelper.removeAllSessionCache(tokenData.getSessionId());
return ResponseResult.success();
@@ -184,4 +161,47 @@ public class LoginController {
}
return ResponseResult.success();
}
private JSONObject buildLoginData(SysUser user) {
boolean isAdmin = user.getUserType() == SysUserType.TYPE_ADMIN;
Map<String, Object> claims = new HashMap<>(3);
String sessionId = user.getLoginName() + "_" + MyCommonUtil.generateUuid();
claims.put("sessionId", sessionId);
String token = JwtUtil.generateToken(claims, appConfig.getExpiration(), appConfig.getTokenSigningKey());
JSONObject jsonData = new JSONObject();
jsonData.put(TokenData.REQUEST_ATTRIBUTE_NAME, token);
jsonData.put("showName", user.getShowName());
jsonData.put("isAdmin", isAdmin);
TokenData tokenData = new TokenData();
tokenData.setSessionId(sessionId);
tokenData.setUserId(user.getUserId());
tokenData.setLoginName(user.getLoginName());
tokenData.setShowName(user.getShowName());
tokenData.setIsAdmin(isAdmin);
tokenData.setLoginIp(IpUtil.getRemoteIpAddress(ContextUtil.getHttpRequest()));
tokenData.setLoginTime(new Date());
String sessionIdKey = RedisKeyUtil.makeSessionIdKeyForRedis(sessionId);
String sessionData = JSON.toJSONString(tokenData, SerializerFeature.WriteNonStringValueAsString);
RBucket<String> bucket = redissonClient.getBucket(sessionIdKey);
bucket.set(sessionData);
bucket.expire(appConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
// 这里手动将TokenData存入request便于OperationLogAspect统一处理操作日志。
TokenData.addToRequest(tokenData);
Collection<SysMenu> menuList;
Collection<String> permCodeList;
if (isAdmin) {
menuList = sysMenuService.getAllMenuList();
permCodeList = sysPermCodeService.getAllPermCodeList();
} else {
menuList = sysMenuService.getMenuListByUserId(user.getUserId());
permCodeList = sysPermCodeService.getPermCodeListByUserId(user.getUserId());
}
jsonData.put("menuList", menuList);
jsonData.put("permCodeList", permCodeList);
if (user.getUserType() != SysUserType.TYPE_ADMIN) {
// 缓存用户的权限资源
sysPermService.putUserSysPermCache(sessionId, user.getUserId());
}
return jsonData;
}
}

View File

@@ -0,0 +1,84 @@
package com.orange.demo.webadmin.upms.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.orange.demo.common.core.annotation.MyRequestBody;
import com.orange.demo.common.core.object.*;
import com.orange.demo.common.core.util.RedisKeyUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
/**
* 在线用户控制器对象。
*
* @author Jerry
* @date 2020-09-24
*/
@Slf4j
@RestController
@RequestMapping("/admin/upms/loginUser")
public class LoginUserController {
@Autowired
private RedissonClient redissonClient;
/**
* 显示在线用户列表。
*
* @param loginName 登录名过滤。
* @param pageParam 分页参数。
* @return 登录用户信息列表。
*/
@PostMapping("/list")
public ResponseResult<MyPageData<LoginUserInfo>> list(
@MyRequestBody String loginName, @MyRequestBody MyPageParam pageParam) {
List<LoginUserInfo> loginUserInfoList = new LinkedList<>();
int queryCount = pageParam.getPageNum() * pageParam.getPageSize();
int skipCount = (pageParam.getPageNum() - 1) * pageParam.getPageSize();
String patternKey;
if (StrUtil.isBlank(loginName)) {
patternKey = RedisKeyUtil.getSessionIdPrefix() + "*";
} else {
patternKey = RedisKeyUtil.getSessionIdPrefix(loginName) + "*";
}
long totalCount = 0L;
int pos = 0;
Iterable<String> keys = redissonClient.getKeys().getKeysByPattern(patternKey);
for (String key : keys) {
totalCount++;
if (pos++ < skipCount) {
continue;
}
loginUserInfoList.add(this.buildTokenDataByRedisKey(key));
}
return ResponseResult.success(new MyPageData<>(loginUserInfoList, totalCount));
}
/**
* 强制下线指定登录会话。
*
* @param sessionId 待强制下线的SessionId。
* @return 应答结果对象。
*/
@PostMapping("/delete")
public ResponseResult<Void> delete(@MyRequestBody String sessionId) {
// 为了保证被剔除用户正在进行的操作不被干扰这里只是删除sessionIdKey即可这样可以使强制下线操作更加平滑。
// 比如如果删除操作权限或数据权限的redis session key那么正在请求数据的操作就会报错。
redissonClient.getBucket(RedisKeyUtil.makeSessionIdKeyForRedis(sessionId)).delete();
return ResponseResult.success();
}
private LoginUserInfo buildTokenDataByRedisKey(String key) {
RBucket<String> sessionData = redissonClient.getBucket(key);
TokenData tokenData = JSON.parseObject(sessionData.get(), TokenData.class);
return BeanUtil.copyProperties(tokenData, LoginUserInfo.class);
}
}

View File

@@ -27,12 +27,12 @@
<sql id="inputFilterRef">
<if test="sysUserFilter != null">
<if test="sysUserFilter.loginName != null and sysUserFilter.loginName != ''">
<bind name = "safeLoginName" value = "'%' + sysUserFilter.loginName + '%'" />
AND zz_sys_user.login_name LIKE #{safeLoginName}
<bind name = "safeSysUserLoginName" value = "'%' + sysUserFilter.loginName + '%'" />
AND zz_sys_user.login_name LIKE #{safeSysUserLoginName}
</if>
<if test="sysUserFilter.showName != null and sysUserFilter.showName != ''">
<bind name = "safeShowName" value = "'%' + sysUserFilter.showName + '%'" />
AND zz_sys_user.show_name LIKE #{safeShowName}
<bind name = "safeSysUserShowName" value = "'%' + sysUserFilter.showName + '%'" />
AND zz_sys_user.show_name LIKE #{safeSysUserShowName}
</if>
<if test="sysUserFilter.userStatus != null">
AND zz_sys_user.user_status = #{sysUserFilter.userStatus}

View File

@@ -17,15 +17,16 @@ import com.orange.demo.webadmin.upms.model.SysPerm;
import com.orange.demo.webadmin.upms.model.SysPermCodePerm;
import com.orange.demo.webadmin.upms.model.SysPermModule;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import tk.mybatis.mapper.entity.Example;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 权限资源数据服务类。
@@ -47,7 +48,7 @@ public class SysPermServiceImpl extends BaseService<SysPerm, Long> implements Sy
@Autowired
private IdGeneratorWrapper idGenerator;
@Autowired
private JedisPool jedisPool;
private RedissonClient redissonClient;
@Autowired
private ApplicationConfig applicationConfig;
@@ -148,16 +149,9 @@ public class SysPermServiceImpl extends BaseService<SysPerm, Long> implements Sy
return permList;
}
String sessionPermKey = RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId);
try (Jedis jedis = jedisPool.getResource()) {
Transaction t = jedis.multi();
if (permList != null) {
for (String perm : permList) {
t.sadd(sessionPermKey, perm);
}
t.expire(sessionPermKey, applicationConfig.getPermRedisExpiredSeconds());
}
t.exec();
}
RSet<String> redisPermSet = redissonClient.getSet(sessionPermKey);
redisPermSet.addAll(permList.stream().map(Object::toString).collect(Collectors.toSet()));
redisPermSet.expire(applicationConfig.getSessionExpiredSeconds(), TimeUnit.SECONDS);
return permList;
}
@@ -168,10 +162,8 @@ public class SysPermServiceImpl extends BaseService<SysPerm, Long> implements Sy
*/
@Override
public void removeUserSysPermCache(String sessionId) {
try (Jedis jedis = jedisPool.getResource()) {
String sessionPermKey = RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId);
jedis.del(sessionPermKey);
}
String sessionPermKey = RedisKeyUtil.makeSessionPermIdKeyForRedis(sessionId);
redissonClient.getSet(sessionPermKey).deleteAsync();
}
/**

View File

@@ -40,8 +40,8 @@ spring:
# mybatis的基本配置
mybatis:
mapperLocations: classpath:com/orange/demo/webadmin/*/dao/mapper/*Mapper.xml
typeAliasesPackage: com.orange.demo.webadmin.*.model
mapperLocations: classpath:com/orange/demo/webadmin/*/dao/mapper/*Mapper.xml,com/orange/demo/common/log/dao/mapper/*Mapper.xml
typeAliasesPackage: com.orange.demo.webadmin.*.model,com.orange.demo.common.log.model
# mybatis的通用mapper的配置
mapper:
@@ -59,22 +59,24 @@ pagehelper:
# 存储session数据的Redis所有服务均需要因此放到公共配置中。
# 根据实际情况该Redis也可以用于存储其他数据。
redis:
jedis:
enabled: true
host: localhost
port: 6379
timeout: 60000
pool:
maxTotal: 20
maxIdle: 8
minIdle: 0
maxWait: 2000
# redisson的配置。每个服务可以自己的配置文件中覆盖此选项。
redisson:
# 如果该值为false系统将不会创建RedissionClient的bean。
enabled: true
# redis地址多个地址之间逗号分隔。如果是从主机制第一个为master,其余为slave
address: localhost:6379
# mode的可用值为single/cluster/sentinel/master-slave
mode: single
# single: 单机模式
# address: redis://localhost:6379
# cluster: 集群模式
# 每个节点逗号分隔同时每个节点前必须以redis://开头。
# address: redis://localhost:6379,redis://localhost:6378,...
# sentinel:
# 每个节点逗号分隔同时每个节点前必须以redis://开头。
# address: redis://localhost:6379,redis://localhost:6378,...
# master-slave:
# 每个节点逗号分隔第一个为主节点其余为从节点。同时每个节点前必须以redis://开头。
# address: redis://localhost:6379,redis://localhost:6378,...
address: redis://localhost:6379
# 链接超时,单位毫秒。
timeout: 6000
# 单位毫秒。分布式锁的超时检测时长。
@@ -161,7 +163,7 @@ application:
# 跨域的IP白名单列表多个IP之间逗号分隔(* 表示全部信任,空白表示禁用跨域信任)。
credentialIpList: "*"
# Session的用户和数据权限在Redis中的过期时间(秒)。
permRedisExpiredSeconds: 86400
sessionExpiredSeconds: 86400
sequence:
# Snowflake 分布式Id生成算法所需的WorkNode参数值。
@@ -220,7 +222,7 @@ application:
# 跨域的IP白名单列表多个IP之间逗号分隔(* 表示全部信任,空白表示禁用跨域信任)。
credentialIpList: "*"
# Session的用户和数据权限在Redis中的过期时间(秒)。
permRedisExpiredSeconds: 86400
sessionExpiredSeconds: 86400
sequence:
# Snowflake 分布式Id生成算法所需的WorkNode参数值。

View File

@@ -53,18 +53,22 @@
<root level="${OUTPUT_LOG_LEVEL}">
<AppenderRef ref="console"/>
</root>
<Logger name="com.orange.demo" additivity="false" level="info">
<AsyncLogger name="com.orange.demo" additivity="false" level="info">
<AppenderRef ref="console"/>
<AppenderRef ref="file_log"/>
</Logger>
</AsyncLogger>
<!-- 这里将dao的日志级别设置为DEBUG是为了SQL语句的输出 -->
<Logger name="com.orange.demo.webadmin.app.dao" additivity="false" level="debug">
<AsyncLogger name="com.orange.demo.webadmin.app.dao" additivity="false" level="debug">
<AppenderRef ref="console"/>
<AppenderRef ref="file_log"/>
</Logger>
<Logger name="com.orange.demo.webadmin.upms.dao" additivity="false" level="debug">
</AsyncLogger>
<AsyncLogger name="com.orange.demo.webadmin.upms.dao" additivity="false" level="debug">
<AppenderRef ref="console"/>
<AppenderRef ref="file_log"/>
</Logger>
</AsyncLogger>
<AsyncLogger name="com.orange.demo.common.log.dao" additivity="false" level="debug">
<AppenderRef ref="console"/>
<AppenderRef ref="file_log"/>
</AsyncLogger>
</Loggers>
</configuration>

View File

@@ -8,6 +8,22 @@ package com.orange.demo.common.core.constant;
*/
public final class ApplicationConstant {
/**
* 数据同步使用的缺省消息队列主题名称。
*/
public static final String DEFAULT_DATA_SYNC_TOPIC = "OrangeSingleDemo";
/**
* 全量数据同步中,新增数据对象的键名称。
*/
public static final String DEFAULT_FULL_SYNC_DATA_KEY = "data";
/**
* 全量数据同步中,原有数据对象的键名称。
*/
public static final String DEFAULT_FULL_SYNC_OLD_DATA_KEY = "oldData";
/**
* 全量数据同步中,数据对象主键的键名称。
*/
public static final String DEFAULT_FULL_SYNC_ID_KEY = "id";
/**
* 为字典表数据缓存时,缓存名称的固定后缀。
*/

View File

@@ -0,0 +1,27 @@
package com.orange.demo.common.core.exception;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 无效的Redis模式的自定义异常。
*
* @author Jerry
* @date 2020-09-24
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class InvalidRedisModeException extends RuntimeException {
private final String mode;
/**
* 构造函数。
*
* @param mode 错误的模式。
*/
public InvalidRedisModeException(String mode) {
super("Invalid Redis Mode [" + mode + "], only supports [single/cluster/sentinel/master_slave]");
this.mode = mode;
}
}

View File

@@ -0,0 +1,58 @@
package com.orange.demo.common.core.object;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
/**
* 在线登录用户信息。
*
* @author Jerry
* @date 2020-09-24
*/
@Data
@ToString
@Slf4j
public class LoginUserInfo {
/**
* 用户Id。
*/
private Long userId;
/**
* 用户所在部门Id。
* 仅当系统支持uaa时可用否则可以直接忽略该字段。保留该字段是为了保持单体和微服务通用代码部分的兼容性。
*/
private Long deptId;
/**
* 租户Id。
* 仅当系统支持uaa时可用否则可以直接忽略该字段。保留该字段是为了保持单体和微服务通用代码部分的兼容性。
*/
private Long tenantId;
/**
* 是否为超级管理员。
*/
private Boolean isAdmin;
/**
* 用户登录名。
*/
private String loginName;
/**
* 用户显示名称。
*/
private String showName;
/**
* 标识不同登录的会话Id。
*/
private String sessionId;
/**
* 登录IP。
*/
private String loginIp;
/**
* 登录时间。
*/
private Date loginTime;
}

View File

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.orange.demo.common.core.constant.ErrorCodeEnum;
import com.orange.demo.common.core.util.ContextUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -15,6 +16,7 @@ import java.io.PrintWriter;
* @author Jerry
* @date 2020-09-24
*/
@Slf4j
@Data
public class ResponseResult<T> {
@@ -160,6 +162,11 @@ public class ResponseResult<T> {
* @throws IOException 异常错误。
*/
public static <T> void output(int httpStatus, ResponseResult<T> responseResult) throws IOException {
if (httpStatus != HttpServletResponse.SC_OK) {
log.error(JSON.toJSONString(responseResult));
} else {
log.info(JSON.toJSONString(responseResult));
}
HttpServletResponse response = ContextUtil.getHttpResponse();
PrintWriter out = response.getWriter();
response.setContentType("application/json; charset=utf-8");

View File

@@ -5,6 +5,7 @@ import lombok.Data;
import lombok.ToString;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
* 基于Jwt用于前后端传递的令牌对象。
@@ -38,6 +39,10 @@ public class TokenData {
* 是否为超级管理员。
*/
private Boolean isAdmin;
/**
* 用户登录名。
*/
private String loginName;
/**
* 用户显示名称。
*/
@@ -51,6 +56,14 @@ public class TokenData {
* 仅当系统支持uaa时可用否则可以直接忽略该字段。保留该字段是为了保持单体和微服务通用代码部分的兼容性。
*/
private String uaaAccessToken;
/**
* 登录IP。
*/
private String loginIp;
/**
* 登录时间。
*/
private Date loginTime;
/**
* 将令牌对象添加到Http请求对象。

View File

@@ -17,7 +17,7 @@ import java.util.Map;
@Slf4j
public class JwtUtil {
private static final String TOKEN_PREFIX = "Bearer:";
private static final String TOKEN_PREFIX = "Bearer ";
private static final String CLAIM_KEY_CREATEDTIME = "CreatedTime";
/**
@@ -61,7 +61,7 @@ public class JwtUtil {
/**
* 获取token中的数据对象
*
* @param token 令牌信息(需要包含令牌前缀,如"Bearer:")
* @param token 令牌信息(需要包含令牌前缀,如"Bearer ")
* @return 令牌中的数据对象解析视频返回null。
*/
public static Claims parseToken(String token, String signingKey) {

View File

@@ -2,6 +2,7 @@ package com.orange.demo.common.core.util;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReflectUtil;
import com.orange.demo.common.core.exception.InvalidDataFieldException;
import com.orange.demo.common.core.annotation.*;
import com.orange.demo.common.core.exception.MyRuntimeException;
import com.orange.demo.common.core.object.TokenData;
@@ -141,6 +142,34 @@ public class MyModelUtil {
return columnInfo == null ? null : columnInfo.getFirst();
}
/**
* 映射Model对象的字段反射对象获取与该字段对应的数据库列名称。
* 如果没有匹配到ColumnName则立刻抛出异常。
*
* @param field 字段反射对象。
* @param modelClazz Model对象的Class类。
* @return 该字段所对应的数据表列名称。
*/
public static String safeMapToColumnName(Field field, Class<?> modelClazz) {
return safeMapToColumnName(field.getName(), modelClazz);
}
/**
* 映射Model对象的字段名称获取与该字段对应的数据库列名称。
* 如果没有匹配到ColumnName则立刻抛出异常。
*
* @param fieldName 字段名称。
* @param modelClazz Model对象的Class类。
* @return 该字段所对应的数据表列名称。
*/
public static String safeMapToColumnName(String fieldName, Class<?> modelClazz) {
String columnName = mapToColumnName(fieldName, modelClazz);
if (columnName == null) {
throw new InvalidDataFieldException(modelClazz.getSimpleName(), fieldName);
}
return columnName;
}
/**
* 映射Model对象的字段名称获取与该字段对应的数据库列名称和字段类型。
*

View File

@@ -8,6 +8,35 @@ package com.orange.demo.common.core.util;
*/
public class RedisKeyUtil {
/**
* 获取通用的session缓存的键前缀。
*
* @return session缓存的键前缀。
*/
public static String getSessionIdPrefix() {
return "SESSIONID__";
}
/**
* 获取指定用户Id的session缓存的键前缀。
*
* @param loginName 指定的用户登录名。
* @return session缓存的键前缀。
*/
public static String getSessionIdPrefix(String loginName) {
return "SESSIONID__" + loginName + "_";
}
/**
* 计算SessionId返回存储于Redis中的键。
*
* @param sessionId 会话Id。
* @return 会话存储于Redis中的键值。
*/
public static String makeSessionIdKeyForRedis(String sessionId) {
return "SESSIONID__" + sessionId;
}
/**
* 计算SessionId关联的权限数据存储于Redis中的键。
*

View File

@@ -20,11 +20,6 @@
<artifactId>common-core</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>

View File

@@ -1,7 +1,5 @@
package com.orange.demo.common.redis.cache;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.orange.demo.common.core.object.TokenData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
@@ -63,32 +61,6 @@ public class SessionCacheHelper {
return ((Set<String>) valueWrapper.get()).contains(filename);
}
/**
* 存放session的Token数据。
*
* @param sessionId 当前会话的SessionId。
* @param tokenData 当前会话的JWT Token对象。
*/
public void putTokenData(String sessionId, TokenData tokenData) {
if (sessionId == null || tokenData == null) {
return;
}
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.GLOBAL_CACHE.name());
cache.put(sessionId, JSON.toJSONString(tokenData));
}
/**
* 获取session的JWT Token对象。
*
* @param sessionId 当前会话的SessionId。
* @return 当前会话的JWT Token对象。
*/
public TokenData getTokenData(String sessionId) {
Cache cache = cacheManager.getCache(RedissonCacheConfig.CacheEnum.GLOBAL_CACHE.name());
String tokenString = cache.get(sessionId, String.class);
return JSONObject.parseObject(tokenString, TokenData.class);
}
/**
* 清除当前session的所有缓存数据。
*

View File

@@ -1,52 +0,0 @@
package com.orange.demo.common.redis.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Redis配置类。
*
* @author Jerry
* @date 2020-09-24
*/
@Configuration
@ConditionalOnProperty(name = "redis.jedis.enabled", havingValue = "true")
public class JedisConfig {
@Value("${redis.jedis.port}")
private Integer port;
@Value("${redis.jedis.host}")
private String redisHost;
@Value("${redis.jedis.timeout}")
private int timeout;
@Value("${redis.jedis.pool.maxTotal}")
private Integer maxTotal;
@Value("${redis.jedis.pool.maxIdle}")
private Integer maxIdle;
@Value("${redis.jedis.pool.minIdle}")
private Integer minIdle;
@Value("${redis.jedis.pool.maxWait}")
private Integer maxWait;
@Bean
public JedisPool getJedisPool() {
// Jedis配置信息
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxWaitMillis(maxWait);
jedisPoolConfig.setEvictorShutdownTimeoutMillis(2000);
return new JedisPool(jedisPoolConfig, redisHost, port);
}
}

View File

@@ -1,5 +1,8 @@
package com.orange.demo.common.redis.config;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.orange.demo.common.core.exception.InvalidRedisModeException;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
@@ -9,8 +12,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Redisson配置类。和Jedis一样都是Redis客户端但是Redisson提供了更多的数据结构抽象。
* 这里我们只是使用了Redisson的分布式锁以及map等数据结构作为字典缓存使用。更多用法请参考其文档。
* Redisson配置类。
*
* @author Jerry
* @date 2020-09-24
@@ -22,6 +24,15 @@ public class RedissonConfig {
@Value("${redis.redisson.lockWatchdogTimeout}")
private Integer lockWatchdogTimeout;
@Value("${redis.redisson.mode}")
private String mode;
/**
* 仅仅用于sentinel模式。
*/
@Value("${redis.redisson.masterName:}")
private String masterName;
@Value("${redis.redisson.address}")
private String address;
@@ -37,14 +48,45 @@ public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 这里config还支持其他redis集群模式可根据实际需求更换。
// 比如useClusterServers()/useMasterSlaveServers()等。
config.setLockWatchdogTimeout(lockWatchdogTimeout)
.useSingleServer()
.setAddress("redis://" + address)
.setConnectionPoolSize(poolSize)
.setConnectionMinimumIdleSize(minIdle)
.setConnectTimeout(timeout);
if ("single".equals(mode)) {
config.setLockWatchdogTimeout(lockWatchdogTimeout)
.useSingleServer()
.setAddress(address)
.setConnectionPoolSize(poolSize)
.setConnectionMinimumIdleSize(minIdle)
.setConnectTimeout(timeout);
} else if ("cluster".equals(mode)) {
String[] clusterAddresses = StrUtil.splitToArray(address, ',');
config.setLockWatchdogTimeout(lockWatchdogTimeout)
.useClusterServers()
.addNodeAddress(clusterAddresses)
.setConnectTimeout(timeout)
.setMasterConnectionPoolSize(poolSize);
} else if ("sentinel".equals(mode)) {
String[] sentinelAddresses = StrUtil.splitToArray(address, ',');
config.setLockWatchdogTimeout(lockWatchdogTimeout)
.useSentinelServers()
.setMasterName(masterName)
.addSentinelAddress(sentinelAddresses)
.setConnectTimeout(timeout)
.setMasterConnectionPoolSize(poolSize);
} else if ("master-slave".equals(mode)) {
String[] masterSlaveAddresses = StrUtil.splitToArray(address, ',');
if (masterSlaveAddresses.length == 1) {
throw new IllegalArgumentException(
"redis.redisson.address MUST have multiple redis addresses for master-slave mode.");
}
String[] slaveAddresses = new String[masterSlaveAddresses.length - 1];
ArrayUtil.copy(masterSlaveAddresses, 1, slaveAddresses, 0, slaveAddresses.length);
config.setLockWatchdogTimeout(lockWatchdogTimeout)
.useMasterSlaveServers()
.setMasterAddress(masterSlaveAddresses[0])
.addSlaveAddress(slaveAddresses)
.setConnectTimeout(timeout)
.setMasterConnectionPoolSize(poolSize);
} else {
throw new InvalidRedisModeException(mode);
}
return Redisson.create(config);
}
}

View File

@@ -1,3 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.orange.demo.common.redis.config.JedisConfig,\
com.orange.demo.common.redis.config.RedissonConfig

View File

@@ -28,12 +28,12 @@
<bean.query.version>1.1.5</bean.query.version>
<caffeine.version>2.8.1</caffeine.version>
<mapstruct.version>1.3.1.Final</mapstruct.version>
<disruptor.version>3.4.2</disruptor.version>
<!-- 数据库工具版本 -->
<druid.version>1.1.22</druid.version>
<mybatis-mapper.version>2.1.5</mybatis-mapper.version>
<mybatis-generator.version>1.3.7</mybatis-generator.version>
<pagehelper.version>1.3.0</pagehelper.version>
<jedis.version>3.2.0</jedis.version>
<redisson.version>3.12.3</redisson.version>
<qdox.version>2.0.0</qdox.version>
</properties>
@@ -121,6 +121,12 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- log4j2 全异步所依赖的Disruptor框架 -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${disruptor.version}</version>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>