first commit

This commit is contained in:
2026-07-13 08:57:05 +08:00
commit 3ecbd0957b
2536 changed files with 200576 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# Ss-Common模块
主要放常用的工具类
+51
View File
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 此处修改,继承自父模块 -->
<parent>
<groupId>com.zxdmy.excite</groupId>
<artifactId>shensong</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>ss-common</artifactId>
<version>2.0.0</version>
<!-- 以下两项可选 -->
<name>ss-common</name>
<description>
此模块主要继承公共的工具类。
</description>
<!-- 放置只有此模块用到的依赖项,其他模块用到的是本模块实现的工具类或服务 -->
<dependencies>
<!-- Redis https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!-- </dependency>-->
<!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.28.0</version>
</dependency>
<!-- 对象池,使用redis时必须引入 -->
<!-- 使用对象池,每次创建的对象并不实际销毁,而是缓存在对象池中,下次使用的时候,不用再重新创建,直接从对象池的缓存中取即可-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 图片验证码 https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,208 @@
package com.zxdmy.excite.common.base;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* 通用的控制类
* </p>
*
* @author 沈松
* @since 2021-09-05 0005 22:58
*/
public class BaseController {
@Resource
protected HttpServletRequest request;
@Resource
protected HttpServletResponse response;
@ModelAttribute
public void initReqAndRes(HttpServletRequest request, HttpServletResponse response) {
this.request = request;
this.response = response;
}
/**
* 请求成功消息(JSON code200;浏览器Status Code200
* 相关说明:
* --- JSON code:指的是返回的JSON数据中的code字段的值
* --- Status Code:指的是前端按下F12后,浏览器控制台中请求相关API后,返回给浏览器的状态值。
* 如果需要修改Status Code,则需要传入[org.springframework.http.HttpStatus]类中的各个参数,如:HttpStatus.OK.value()。
*
* @param message 消息
* @return 结果类
*/
public BaseResult success(String message) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(HttpStatus.OK.value(), message);
}
/**
* 请求成功消息(JSON code:自定义;浏览器Status Code200
*
* @param code 状态码
* @param message 消息
* @return 结果类
*/
public BaseResult success(int code, String message) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(code, message);
}
/**
* 请求成功实体(JSON code200;浏览器Status Code200
*
* @param data 实体
* @return 结果类
*/
public BaseResult success(Object data) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(HttpStatus.OK.value(), data);
}
/**
* 请求成功实体(JSON code:自定义;浏览器Status Code200
*
* @param code 状态码
* @param data 消息
* @return 结果类
*/
public BaseResult success(int code, Object data) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(code, data);
}
/**
* 请求成功消息(JSON code:自定义;浏览器Status Code:同返回码)
*
* @param httpStatus 状态码
* @param message 消息
* @return 结果类
*/
public BaseResult success(HttpStatus httpStatus, String message) {
response.setStatus(httpStatus.value());
return new BaseResult(httpStatus.value(), message);
}
/**
* 请求成功消息+数据(JSON code200;浏览器Status Code200
*
* @param message 消息
* @param data 数据
* @return 结果类
*/
public BaseResult success(String message, Object data) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(HttpStatus.OK.value(), message, data);
}
/**
* 请求成功消息+数据(JSON code:自定义;浏览器Status Code200
*
* @param code 状态码
* @param message 消息
* @param data 数据
* @return 结果类
*/
public BaseResult success(int code, String message, Object data) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(code, message, data);
}
/**
* 请求成功消息+数据(JSON code:自定义;浏览器Status Code:同返回码)
*
* @param httpStatus 状态码
* @param message 消息
* @param data 数据
* @return 结果类
*/
public BaseResult success(HttpStatus httpStatus, String message, Object data) {
response.setStatus(httpStatus.value());
return new BaseResult(httpStatus.value(), message, data);
}
/**
* 请求成功消息+数据+总数,用于分页显示(JSON code200;浏览器Status Code200
*
* @param message 消息
* @param data 数据
* @param count 记录数
* @return 结果类
*/
public BaseResult success(String message, Object data, int count) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(HttpStatus.OK.value(), message, data).put("count", count);
}
/**
* 请求成功消息+数据+总数,用于分页显示(JSON code:自定义;浏览器Status Code200
*
* @param code 状态码
* @param message 消息
* @param data 数据
* @param count 记录数
* @return 结果类
*/
public BaseResult success(int code, String message, Object data, int count) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(code, message, data).put("count", count);
}
/**
* 请求成功消息+数据+总数,用于分页显示(JSON code:自定义;浏览器Status Code:同返回码)
*
* @param httpStatus 状态码
* @param message 消息
* @param data 数据
* @param count 记录数
* @return 结果类
*/
public BaseResult success(HttpStatus httpStatus, String message, Object data, int count) {
response.setStatus(httpStatus.value());
return new BaseResult(httpStatus.value(), message, data).put("count", count);
}
/**
* 请求失败消息(JSON code400;浏览器Status Code200
*
* @param message 消息
* @return 结果类
*/
public BaseResult error(String message) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 请求失败消息(JSON code:自定义;浏览器Status Code200
*
* @param code 状态码
* @param message 消息
* @return 结果类
*/
public BaseResult error(int code, String message) {
response.setStatus(HttpStatus.OK.value());
return new BaseResult(code, message);
}
/**
* 请求失败消息(JSON code:自定义;浏览器Status Code:同返回码)
*
* @param httpStatus 状态码
* @param message 消息
* @return 结果类
*/
public BaseResult error(HttpStatus httpStatus, String message) {
response.setStatus(httpStatus.value());
return new BaseResult(httpStatus.value(), message);
}
}
@@ -0,0 +1,104 @@
package com.zxdmy.excite.common.base;
import java.util.HashMap;
/**
* <p>
* 通用的返回结果类
* </p>
*
* @author 沈松
* @since 2021-09-05 0005 23:01
*/
public class BaseResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/**
* 几个静态常量
*/
private static final Integer MAX_SUCCESS_VALUE = 299;
private static final String SUCCESS = "success";
private static final String CODE = "code";
private static final String MSG = "msg";
private static final String DATA = "data";
private static final String COUNT = "count";
private static final String PAGE = "page";
private static final String SIZE = "size";
/**
* 初始化 BaseResult 对象,其消息为空。
*/
public BaseResult() {
}
/**
* 封装结果类,消息。
*
* @param code 状态代码
* @param msg 消息内容
*/
public BaseResult(int code, String msg) {
if (code <= MAX_SUCCESS_VALUE) {
super.put(SUCCESS, true);
} else {
super.put(SUCCESS, false);
}
super.put(CODE, code);
super.put(MSG, msg);
}
/**
* 封装结果类,实体。
*
* @param code 状态代码
* @param data 消息实体
*/
public BaseResult(int code, Object data) {
if (code <= MAX_SUCCESS_VALUE) {
super.put(SUCCESS, true);
} else {
super.put(SUCCESS, false);
}
super.put(CODE, code);
if (null != data) {
super.put(DATA, data);
}
}
/**
* 封装结果类,消息+实体。
*
* @param code 状态类型
* @param msg 消息内容
* @param data 数据对象
*/
public BaseResult(int code, String msg, Object data) {
if (code <= MAX_SUCCESS_VALUE) {
super.put(SUCCESS, true);
} else {
super.put(SUCCESS, false);
}
super.put(CODE, code);
super.put(MSG, msg);
if (null != data) {
super.put(DATA, data);
}
}
/**
* 以上三类是默认的类型。如果还有其他参数要返回,使用.put(key,value)的链式调用方式追加。
*
* @param key 键
* @param value 值/任意数据对象
* @return 链式调用
*/
@Override
public BaseResult put(String key, Object value) {
super.put(key, value);
return this;
}
}
@@ -0,0 +1,43 @@
package com.zxdmy.excite.common.base;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 全局请求错误返回结果控制类
*
* @author 沈松
* @since 2021-09-11 0011 23:27
*/
@Controller
@AllArgsConstructor
@RequestMapping("/error")
public class ErrorController extends BaseController {
/**
* 请求资源不存在返回结果
*
* @return 错误信息
*/
@GetMapping(value = "/404")
public String error404() {
// return error(HttpStatus.NOT_FOUND, "请求的资源不存在");
return "error/404";
}
/**
* 服务器错误返回结果
*
* @return 错误信息
*/
@GetMapping(value = "/500")
@ResponseBody
public BaseResult error500() {
return error(HttpStatus.INTERNAL_SERVER_ERROR, "服务器内部错误:可能请求资源不存在");
}
}
@@ -0,0 +1,26 @@
package com.zxdmy.excite.common.config;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
/**
* 全局错误访问返回结果
*
* @author 沈松
* @since 2021-09-08 0008 21:00
*/
@Component
public class ErrorPageConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
errorPageRegistry.addErrorPages(
// 当遇到 NOT_FOUND 错误时,请求 /error/404 路由。
new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"),
// 当遇到 INTERNAL_SERVER_ERROR 错误时,请求 /error/500 路由。
new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500")
);
}
}
@@ -0,0 +1,67 @@
package com.zxdmy.excite.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* <p>
* 描述
* </p>
*
* @author 沈松
* @since 2021-10-07 0007 19:04
*/
@Component
@ConfigurationProperties(prefix = "excite")
public class ExciteConfig {
private String name;
/**
* 是否允许Redis缓存:true:允许 | false:不允许
* 此Redis缓存用到的地方主要有:用户角色、权限控制;
*/
private Boolean allowRedis;
/**
* RSA加密算法的公钥
*/
private String rsaPublicKey;
/**
* RSA加密算法的私钥
*/
private String rsaPrivateKey;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getAllowRedis() {
return allowRedis;
}
public void setAllowRedis(Boolean allowRedis) {
this.allowRedis = allowRedis;
}
public String getRsaPublicKey() {
return rsaPublicKey;
}
public void setRsaPublicKey(String rsaPublicKey) {
this.rsaPublicKey = rsaPublicKey;
}
public String getRsaPrivateKey() {
return rsaPrivateKey;
}
public void setRsaPrivateKey(String rsaPrivateKey) {
this.rsaPrivateKey = rsaPrivateKey;
}
}
@@ -0,0 +1,139 @@
package com.zxdmy.excite.common.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
import static com.google.code.kaptcha.Constants.*;
/**
* <p>
* Google验证码配置类
* </p>
*
* @author 沈松
* @since 2021-10-04 0004 20:12
*/
@Configuration
public class KaptchaConfig {
/**
* 验证码配置默认配置
*
* @return 配置信息
*/
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
/**
* 验证码数学题类配置(一位数加减乘除)
*
* @return 配置信息
*/
@Bean(name = "captchaProducerMathOne")
public DefaultKaptcha getKaptchaBeanMathOne() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.zxdmy.excite.common.utils.KaptchaMathOneTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度 默认为 5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
/**
* 验证码数学题类配置(两位数的加减乘除)
*
* @return 配置信息
*/
@Bean(name = "captchaProducerMathTwo")
public DefaultKaptcha getKaptchaBeanMathTwo() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.zxdmy.excite.common.utils.KaptchaMathTwoTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
@@ -0,0 +1,34 @@
package com.zxdmy.excite.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <p>
* Mybatis Plus 配置信息
* </p>
*
* @author 沈松
* @since 2021-09-01 0001 18:34
*/
@Configuration
@MapperScan({"com.zxdmy.excite.system.mapper", ""})
public class MybatisPlusConfig {
/**
* 分页插件配置
*
* @return MP拦截器
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
@@ -0,0 +1,100 @@
package com.zxdmy.excite.common.config;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* <p>
* Redis 相关配置,参考自:spring-boot-demo
* </p>
*
* @author 沈松
* @since 2021-09-30 0030 19:15
*/
@Component
@Configuration
@EnableCaching
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConfigurationProperties(prefix = "spring.redis")
public class RedisConfig {
/**
* 自定义属性:Redis key 的前缀
*/
private String prefix = "redis:prefix:";
/**
* 自定义属性:是否开启Redis自定义前缀,默认关闭。
*/
private Boolean allowPrefix = false;
/**
* 默认情况下的模板只能支持RedisTemplate<String, String>,也就是只能存入字符串,因此支持序列化
*/
@Bean
public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> template = new RedisTemplate<>();
// key采用String的序列化方式
template.setKeySerializer(new StringRedisSerializer());
// hash的key也采用String的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// value序列化方式采用jackson
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// hash的value序列化方式采用jackson
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheConfiguration redisCacheConfiguration = config
// 覆盖默认的构造key,否则会多出一个冒号
.computePrefixWith(cacheName -> this.allowPrefix ? this.prefix + cacheName + ":" : cacheName)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
// 不缓存空值
// .disableCachingNullValues()
;
return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public Boolean getAllowPrefix() {
return allowPrefix;
}
public void setAllowPrefix(Boolean allowPrefix) {
this.allowPrefix = allowPrefix;
}
}
@@ -0,0 +1,59 @@
package com.zxdmy.excite.common.config;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <p>
* SpringDoc API配置文档
* </p>
*
* @author 沈松
* @since 2021-09-10 0010 21:57
*/
// 全局只能定义一个,主要配置文档信息和安全配置
@OpenAPIDefinition(
// 配置接口文档基本信息,用于网页显示
info = @Info(
title = "ExciteCMS", //标题
version = "1.0", //版本号
description = "An integrated content management system based on Spring Boot (v2.5.4)" //描述信息
)
// 当然还可以添加其他的安全配置。
)
@Configuration // 如果需要在此处配置分组信息,则需要添加此注解。
public class SpringDocConfig {
@Bean
public GroupedOpenApi guestApi() {
return GroupedOpenApi.builder()
// 分组名
.group("guest")
// 扫描的包路径。可选项。
.packagesToScan("com.zxdmy.cms.excite.guest")
// 匹配的路径。可选项,可以与包路径二选一,也可以都用。
.pathsToMatch("/guest/**")
.build();
}
// @Bean
// public GroupedOpenApi thirdApi() {
// return GroupedOpenApi.builder()
// // 分组名
// .group("third")
// // 扫描的包路径。可选项。
// .packagesToScan("com.zxdmy.cms.excite.third")
// // 匹配的路径。可选项,可以与包路径二选一,也可以都用。
// .pathsToMatch("/third/**")
// .build();
// }
}
@@ -0,0 +1,51 @@
package com.zxdmy.excite.common.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.awt.image.BufferedImage;
import java.io.Serializable;
/**
* <p>
* 第三方验证码实体类
* </p>
*
* @author 沈松
* @since 2021-10-04 0004 20:02
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CaptchaDomain implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 验证码的Token
*/
private String token;
/**
* 验证码的字符。返回的JSON,禁止返回给前端。
*/
@JsonIgnore
private String text;
/**
* 验证码的验证字符。比如算式的结果等。
*/
@JsonIgnore
private String code;
/**
* 验证码缓冲图像
*/
@JsonIgnore
private BufferedImage image;
/**
* 验证码图片的Base64字符串
*/
private String base64;
}
@@ -0,0 +1,68 @@
package com.zxdmy.excite.common.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 组件配置信息表
* </p>
*
* @author 沈松
* @since 2022-01-03
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class GlobalConfig implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键,自增
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 配置信息的服务:如qiniualiyun
*/
private String confService;
/**
* 配置信息的主键:如qiniu、alipay等等
*/
private String confKey;
/**
* 配置信息的JSON值
*/
private String confValue;
/**
* 是否加密:1-是 0-否
*/
private Integer encrypt;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 删除时间
*/
private LocalDateTime deleteTime;
}
@@ -0,0 +1,56 @@
package com.zxdmy.excite.common.entity;
import java.util.ArrayList;
import java.util.List;
public class PageList<T> {
private int page;//当前页数
private int total;//总的页数
private int count;//总的记录数
private ArrayList<T> list;//当前页数据
/**
* @param list 当前页数据
* @param page 当前页数
* @param size 每页数据量
* @param count 总的记录数量
*/
public PageList(ArrayList<T> list, int page, int size, int count) {
this.page = page;
this.count = count;
this.list = list;
this.total = count % size == 0 ? count / size : (count / size) + 1;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public List<T> getList() {
return list;
}
public void setList(ArrayList<T> list) {
this.list = list;
}
}
@@ -0,0 +1,25 @@
package com.zxdmy.excite.common.enums;
public enum CloudCode {
FOLDER_IMAGE("https://ltcloudfile.oss-cn-hangzhou.aliyuncs.com/cloud/folder.jpg", "目录图标"),
PDF_IMAGE("https://ltcloudfile.oss-cn-hangzhou.aliyuncs.com/cloud/file.png", "PDF"),
FOLDER_NAME("文件夹", "目录格式");
private final String code;
private final String desc;
CloudCode(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return this.code;
}
public String getDesc() {
return this.desc;
}
}
@@ -0,0 +1,70 @@
package com.zxdmy.excite.common.enums;
/**
* <p>
* 通用的返回结果返回码枚举类
* </p>
*
* @author 沈松
* @since 2021-09-05 0005 23:59
*/
public enum ReturnCode {
// 以下是常用的HTTP状态码
/**
* 客户端请求成功。状态码:200
*/
OK(200, "OK"),
/**
* 客户端请求已创建。状态码:201
*/
CREATED(201, "Created"),
/**
* 客户端请求已接受。状态码:202
*/
ACCEPTED(202, "Accepted"),
/**
* 请求错误。状态码:400
*/
BAD_REQUEST(400, "Bad Request"),
/**
* 拒绝访问。状态码:401
*/
UNAUTHORIZED(401, "Unauthorized"),
/**
* 禁止访问。状态码:403
*/
FORBIDDEN(403, "Forbidden"),
/**
* 请求不存在。状态码:404
*/
NOT_FOUND(404, "Not Found"),
/**
* 方法不被允许。状态码:405
*/
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
// 当然还可以添加自定义的其他返回码。
INVALID_ID(40001, "不合法的ID"),
INVALID_FILE_TYPE(40002, "不合法的文件类型"),
INVALID_FILE_SIZE(40003, "不合法的文件大小"),
INVALID_URL(40004, "不合法的链接");
private final int code;
private final String reason;
ReturnCode(int code, String reason) {
this.code = code;
this.reason = reason;
}
public int getCode() {
return this.code;
}
public String getReason() {
return this.reason;
}
}
@@ -0,0 +1,73 @@
package com.zxdmy.excite.common.enums;
public enum SystemCode {
/**
* 系统菜单默认顺序:0
*/
MENU_DEFAULT_SORT(50, "菜单默认排序"),
/**
* 默认顺序:50
*/
SORT_DEFAULT(50, "默认排序"),
FOLDER_Y(0, "是文件夹"),
FOLDER_N(1, "是文件"),
/**
* 记录状态:正常,值:1
*/
STATUS_Y(1, "记录状态正常"),
/**
* 记录状态:异常,值:0
*/
STATUS_N(0, "记录状态封禁"),
/**
* 记录状态:正常锁定,值:2
*/
STATUS_Y_BLOCK(2, "状态正常,并且无法编辑状态"),
/**
* 记录可以编辑:1
*/
EDITABLE_Y(1, "记录可以编辑"),
/**
* 记录禁止编辑:0
*/
EDITABLE_N(0, "记录禁止编辑"),
/**
* 记录可以删除:1
*/
REMOVABLE_Y(1, "记录可以删除"),
/**
* 记录禁止删除:0
*/
REMOVABLE_N(0, "记录禁止删除"),
/**
* 是否删除:是 - 0
*/
DELETE_Y(1, "已删除"),
/**
* 是否删除:否 - 0
*/
DELETE_N(0, "未删除");
private final int code;
private final String desc;
SystemCode(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return this.code;
}
public String getDesc() {
return this.desc;
}
}
@@ -0,0 +1,85 @@
package com.zxdmy.excite.common.exception;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.zxdmy.excite.common.base.BaseController;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>
* 全局异常处理
* </p>
*
* @author 沈松
* @since 2021-10-12 0012 20:57
*/
@ControllerAdvice
public class GlobalException extends BaseController {
@ResponseBody
@ExceptionHandler
public Object handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 打印堆栈,以供调试
System.out.println("==============全局异常==============");
e.printStackTrace();
// 不同异常返回不同状态码
String result = "系统错误,请重试!";
// 如果是未登录异常
if (e instanceof NotLoginException) {
NotLoginException ee = (NotLoginException) e;
result = "用户未登录";
// 前后端同域,所以未登录就直接跳转至登录页面
response.sendRedirect(request.getContextPath() + "/system/login");
}
// 如果是角色异常
else if (e instanceof NotRoleException) {
NotRoleException ee = (NotRoleException) e;
result = "无此角色:" + ee.getRole();
}
// 如果是权限异常
else if (e instanceof NotPermissionException) {
NotPermissionException ee = (NotPermissionException) e;
result = "无此权限:" + ee.getCode();
}
// 如果是被封禁异常
else if (e instanceof DisableLoginException) {
DisableLoginException ee = (DisableLoginException) e;
result = "账号被封禁:" + ee.getDisableTime() + "秒后解封";
}
// 不支持的请求
else if (e instanceof HttpRequestMethodNotSupportedException) {
return "请求不支持";
}
// 信息校验失败提示
else if (e instanceof BindException) {
BindingResult bindingResult = ((BindException) e).getBindingResult();
StringBuilder errMsg = new StringBuilder();
if (bindingResult.hasErrors()) {
Integer i = 1;
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errMsg.append(i).append(". ").append(fieldError.getDefaultMessage()).append("");
i++;
}
}
result = errMsg.toString();
}
// 其他异常, 输出:500 + 异常信息
else {
result = e.getMessage();
}
// 返回给前端
return error(500, result);
}
}
@@ -0,0 +1,47 @@
package com.zxdmy.excite.common.exception;
/**
* <p>
* 服务层异常处理,继承RunTimeException,来保证对异常进行事务回滚
* </p>
*
* @author 沈松
* @since 2021-09-23 0023 19:54
*/
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 1L;
private String message;
private String details;
public ServiceException() {
}
public ServiceException(String message) {
this.message = message;
}
public ServiceException(String message, String details) {
this.message = message;
this.details = details;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
@@ -0,0 +1,16 @@
package com.zxdmy.excite.common.mapper;
import com.zxdmy.excite.common.entity.GlobalConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 组件配置信息表 Mapper 接口
* </p>
*
* @author 沈松
* @since 2022-01-03
*/
public interface GlobalConfigMapper extends BaseMapper<GlobalConfig> {
}
@@ -0,0 +1,128 @@
package com.zxdmy.excite.common.service;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CaptchaUtil;
import com.google.code.kaptcha.Producer;
import com.zxdmy.excite.common.entity.CaptchaDomain;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import sun.misc.BASE64Encoder;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.io.ByteArrayOutputStream;
import java.security.SecureRandom;
import java.util.Random;
import java.util.UUID;
/**
* <p>
* 验证码服务接口的实现
* </p>
*
* @author 沈松
* @since 2021-10-15 0015 10:18
*/
@Service
@AllArgsConstructor
public class CaptchaService {
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMathOne")
private Producer captchaProducerMathOne;
@Resource(name = "captchaProducerMathTwo")
private Producer captchaProducerMathTwo;
private static final String TYPE_CHAR = "char";
private static final String TYPE_MATH_ONE = "math";
private static final String TYPE_MATH_TWO = "math2";
/**
* Kaptcha生成验证码实体
*
* @param type 类型,char - 字符(缺省) | math - 一位数算式 | math2 - 两位数算式
* @return 验证码实体
*/
public CaptchaDomain createGoogleCaptcha(String type) {
// 定义验证码实体
CaptchaDomain captchaDomain = new CaptchaDomain();
// 一位数加减乘除
if (TYPE_MATH_ONE.equals(type)) {
// 生成文本
String producerText = captchaProducerMathOne.createText();
// 设置验证码字符
captchaDomain.setText(producerText.substring(0, producerText.indexOf("@")));
// 设置验证码答案码
captchaDomain.setCode(producerText.substring(producerText.indexOf("@") + 1));
// 设置验证码图片
captchaDomain.setImage(captchaProducerMathOne.createImage(captchaDomain.getText()));
}
// 两位数加减乘除
else if (TYPE_MATH_TWO.equals(type)) {
String producerText = captchaProducerMathTwo.createText();
captchaDomain.setText(producerText.substring(0, producerText.indexOf("@")));
captchaDomain.setCode(producerText.substring(producerText.indexOf("@") + 1));
captchaDomain.setImage(captchaProducerMathTwo.createImage(captchaDomain.getText()));
}
// 缺省情况:字符
else {
captchaDomain.setText(captchaProducer.createText());
captchaDomain.setCode(captchaDomain.getText());
captchaDomain.setImage(captchaProducer.createImage(captchaDomain.getText()));
}
// 生成base64
try {
// 定义字节数组输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 将图像以 jpg 的形式,写到字节数组输出流中
ImageIO.write(captchaDomain.getImage(), "jpg", outputStream);
// 对字节数组Base64编码
BASE64Encoder encoder = new BASE64Encoder();
// 写入base64格式
captchaDomain.setBase64("data:image/jpg;base64," + encoder.encode(outputStream.toByteArray()));
// 写入唯一Token
captchaDomain.setToken(UUID.randomUUID().toString());
// 返回结果
return captchaDomain;
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
public CaptchaDomain createHutoolCaptcha(Integer width, Integer height) {
CaptchaDomain captchaDomain = new CaptchaDomain();
// 生成 0、1、2 三个整数其一,随机对应下面的三种验证码类型
// 注意:这里只是尽可能多的展示每种验证码的使用方法,实际项目中选择一种即可。
Random random = new SecureRandom();
int type = random.nextInt(3);
AbstractCaptcha captcha = null;
// 【0】生成 线段干扰验证码
if (type == 0) {
//定义图形验证码的长和宽
captcha = CaptchaUtil.createLineCaptcha(width, height);
}
// 【1】生成 圆圈干扰验证码
else if (type == 1) {
//定义图形验证码的长、宽。还可以设置两个参数:验证码字符数、干扰元素个数
captcha = CaptchaUtil.createCircleCaptcha(width, height);
}
// 【2】生成 扭曲干扰验证码
else {
captcha = CaptchaUtil.createShearCaptcha(width, height);
}
// 信息配置
captchaDomain.setText(captcha.getCode());
captchaDomain.setCode(captcha.getCode());
captchaDomain.setBase64(captcha.getImageBase64Data());
captchaDomain.setImage(captcha.getImage());
captchaDomain.setToken(UUID.randomUUID().toString());
return captchaDomain;
}
}
@@ -0,0 +1,24 @@
package com.zxdmy.excite.common.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.zxdmy.excite.common.entity.GlobalConfig;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 组件配置信息表 服务类
* </p>
*
* @author 沈松
* @since 2022-01-03
*/
public interface IGlobalConfigService extends IService<GlobalConfig> {
boolean save(String confService, String confKey, Object object, boolean encrypt) throws JsonProcessingException;
Object get(String confService, String confKey, Object object);
List<Object> getList(String confService, Object object);
}
@@ -0,0 +1,176 @@
package com.zxdmy.excite.common.service;
import com.zxdmy.excite.common.config.RedisConfig;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* <p>
* Redis工具类
* </p>
*
* @author 沈松
* @since 2021-09-30 0030 19:15
*/
@Service
@AllArgsConstructor
public class RedisService {
private RedisTemplate<String, Serializable> redisTemplate;
private RedisConfig redisConfig;
/**
* 写入普通缓存,key由系统生成
*
* @param value 值
* @return 键
*/
public String set(Serializable value) {
// 随机生成key
String key = UUID.randomUUID().toString();
// 如果当前生成的key已经存在,重新生成
if (hasKey(key)) {
return set(value);
} else {
// 写入缓存
redisTemplate.opsForValue().set(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key, value);
// 返回key
return key;
}
}
/**
* 写入带过期时间的缓存,其中key由系统生成
*
* @param value 值
* @param expireTime 过期时间
* @return 键key
*/
public String set(Serializable value, Long expireTime) {
// 随机生成key
String key = UUID.randomUUID().toString();
// 如果当前随机生成的key已经存在,则重新生成
if (hasKey(key)) {
return set(value, expireTime);
} else {
// 写入缓存
redisTemplate.opsForValue().set(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key, value, expireTime, TimeUnit.SECONDS);
// 返回key
return key;
}
}
/**
* 写入普通缓存,指定key
*
* @param key 键
* @param value 值
* @return 结果
*/
public boolean set(String key, Serializable value) {
boolean result = false;
try {
redisTemplate.opsForValue().set(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入带过期时间的缓存,指定key
*
* @param key 键
* @param value 值
* @param expireTime 过期时间
* @return 结果
*/
public boolean set(String key, Serializable value, Long expireTime) {
boolean result = false;
try {
redisTemplate.opsForValue().set(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key, value, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 判断某个键是否存在
*
* @param key 键
* @return 存在与否
*/
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key));
}
/**
* 通过键获取值
*
* @param key 缓存的键
* @return 存在:值 | 不存在:null
*/
public Serializable get(String key) {
if (hasKey(key)) {
return redisTemplate.opsForValue().get(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key);
}
return null;
}
/**
* 获取某个键值的过期时间
*
* @param key 键
* @return 过期时间(秒),-1表示永久有效,-2表示不存在
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key, TimeUnit.SECONDS);
}
/**
* 通过键移出某个值
*
* @param key 键
*/
public void remove(String key) {
if (hasKey(key)) {
redisTemplate.delete(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key);
}
}
/**
* 删除包含指定前缀的值
*
* @param prefix 键
*/
public void removeByPrefix(String prefix) {
Set<String> keys = redisTemplate.keys((redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + prefix : prefix) + "*");
if (null != keys && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
/**
* 通过键移出某些值
*
* @param keys 批量的键,示例:remove("001","002","003")
*/
public void remove(String... keys) {
for (String key : keys) {
remove(redisConfig.getAllowPrefix() ? redisConfig.getPrefix() + key : key);
}
}
}
@@ -0,0 +1,149 @@
package com.zxdmy.excite.common.service.impl;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zxdmy.excite.common.config.ExciteConfig;
import com.zxdmy.excite.common.entity.GlobalConfig;
import com.zxdmy.excite.common.mapper.GlobalConfigMapper;
import com.zxdmy.excite.common.service.IGlobalConfigService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 组件配置信息表 服务实现类
* </p>
*
* @author 沈松
* @since 2022-01-03
*/
@Service
@AllArgsConstructor
public class GlobalConfigServiceImpl extends ServiceImpl<GlobalConfigMapper, GlobalConfig> implements IGlobalConfigService {
ObjectMapper objectMapper;
ExciteConfig exciteConfig;
/**
* 将配置信息保存/更新至数据库
*
* @param confService 配置服务名
* @param confKey 配置信息主键
* @param object 实体类
* @param encrypt 是否对value开启加密
* @return 结果
*/
@Override
public boolean save(String confService, String confKey, Object object, boolean encrypt) {
// 全局配置信息类
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setConfService(confService);
globalConfig.setConfKey(confKey);
// 实体类转JSON格式字符串
String confValue;
try {
confValue = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
return false;
}
// 如果开启加密,则对confValue:使用公钥加密
if (encrypt) {
RSA rsa = new RSA(null, exciteConfig.getRsaPublicKey());
confValue = rsa.encryptBase64(confValue, KeyType.PublicKey);
globalConfig.setEncrypt(1);
} else {
globalConfig.setEncrypt(0);
}
globalConfig.setConfValue(confValue);
globalConfig.setUpdateTime(LocalDateTime.now());
QueryWrapper<GlobalConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("conf_service", confService).eq("conf_key", confKey);
return this.saveOrUpdate(globalConfig, queryWrapper);
}
/**
* 从数据库中读取一条配置信息
*
* @param confService 配置信息服务名
* @param confKey 配置信息的key
* @param object 配置信息实体
* @return 配置信息实体
*/
@Override
public Object get(String confService, String confKey, Object object) {
// 根据要求查询指定【模块】和【key】的【value】
QueryWrapper<GlobalConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("conf_service", confService).eq("conf_key", confKey);
GlobalConfig globalConfig = this.getOne(queryWrapper);
String confValue = globalConfig.getConfValue();
// 为空:返回null
if (null == confValue || "".equals(confValue)) {
return null;
}
// 如果开启了加密:使用私钥进行解密
if (1 == globalConfig.getEncrypt()) {
RSA rsa = new RSA(exciteConfig.getRsaPrivateKey(), null);
confValue = rsa.decryptStr(confValue, KeyType.PrivateKey);
}
// 尝试转换成指定类型
try {
object = objectMapper.readValue(confValue, object.getClass());
} catch (IOException e) {
// 转换出错:报错并返回空
System.out.println(e.getMessage());
return null;
}
// 返回转换后的结果
return object;
}
/**
* 根据服务的名字,获取其所有的配置信息
*
* @param confService 服务名称
* @param object 类别
* @return 结果List<Object>
*/
@Override
public List<Object> getList(String confService, Object object) {
// 根据要求查询指定【服务模块】的【value】
QueryWrapper<GlobalConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("conf_service", confService);
List<GlobalConfig> globalConfigList = this.list(queryWrapper);
if (globalConfigList.size() == 0) {
return null;
}
List<Object> objectList = new ArrayList<>();
for (GlobalConfig globalConfig : globalConfigList) {
String confValue = globalConfig.getConfValue();
// 为空:继续下一个
if (null == confValue || "".equals(confValue)) {
continue;
}
// 如果开启了加密:使用私钥进行解密
if (1 == globalConfig.getEncrypt()) {
RSA rsa = new RSA(exciteConfig.getRsaPrivateKey(), null);
confValue = rsa.decryptStr(confValue, KeyType.PrivateKey);
}
// 尝试转换成指定类型
try {
objectList.add(objectMapper.readValue(confValue, object.getClass()));
} catch (IOException e) {
// 转换出错:报错并返回空
System.out.println(e.getMessage());
}
}
return objectList;
}
}
@@ -0,0 +1,114 @@
package com.zxdmy.excite.common.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 精确的浮点数运算工具类
*
* @author 沈松
* @since 2022/1/22 21:30
*/
public class ArithUtils {
/**
* 默认除法运算精度
*/
private static final int DEF_DIV_SCALE = 10;
/**
* 这个类不能实例化
*/
private ArithUtils() {
}
/**
* 提供精确的加法运算。
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
if (b1.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO.doubleValue();
}
return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = BigDecimal.ONE;
return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
}
}
@@ -0,0 +1,144 @@
package com.zxdmy.excite.common.utils;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 时间工具类
*
* @author 沈松
* @since 2022/1/22 21:31
*/
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
public static String YYYY = "yyyy";
public static String YYYY_MM = "yyyy-MM";
public static String YYYY_MM_DD = "yyyy-MM-dd";
public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
private static String[] parsePatterns = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
/**
* 获取当前Date型日期
*
* @return Date() 当前日期
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
*
* @return String
*/
public static String getDate() {
return dateTimeNow(YYYY_MM_DD);
}
public static final String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}
public static final String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
}
public static final String dateTimeNow(final String format) {
return parseDateToStr(format, new Date());
}
public static final String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
}
public static final String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
public static final Date dateTime(final String format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年/月/日 如2018/08/08
*/
public static final String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年/月/日 如20180808
*/
public static final String dateTime() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
try {
return parseDate(str.toString(), parsePatterns);
} catch (ParseException e) {
return null;
}
}
/**
* 获取服务器启动时间
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算相差天数
*/
public static int differentDaysByMillisecond(Date date1, Date date2) {
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
}
/**
* 计算两个时间差
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
}
}
@@ -0,0 +1,172 @@
package com.zxdmy.excite.common.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.*;
/**
* <p>
* 描述
* </p>
*
* @author 沈松
* @since 2021/12/9 16:54
*/
public class HttpServletRequestUtil {
/**
* 获取request对象
*
* @return request对象
*/
public static HttpServletRequest getRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
/**
* 获取请求的URI(非URL)
*
* @return 格式如:/system/user/list?size=10&page=2
*/
public static String getRequestURI() {
HttpServletRequest request = getRequest();
// URI中的查询字符串
String queryString = request.getQueryString();
// 请求URL中不含字符串
if (!StringUtils.hasText(queryString)) {
// 只返回URI
return request.getRequestURI();
}
// 否则返回包含查询的字符串
return request.getRequestURI() + "?" + queryString;
}
/**
* 获取浏览器的UA
*
* @return UA对象(Hutool工具箱中的)
*/
public static UserAgent getRequestUserAgent() {
HttpServletRequest request = getRequest();
return UserAgentUtil.parse(request.getHeader("User-Agent"));
}
/**
* 获取请求用户的IP
*
* @return IP字符串
*/
public static String getRemoteIP() {
HttpServletRequest request = getRequest();
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多次反向代理后会有多个IP值,第一个为真实IP。
int index = ip.indexOf(',');
if (index != -1) {
ip = ip.substring(0, index);
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
/**
* 取得请求头信息 name:value
*/
public static Map getHeaders() {
HttpServletRequest request = getRequest();
Map<String, String> map = new HashMap<>(32);
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
/**
* 获取请求体信息
*/
public static String getBody() {
HttpServletRequest request = getRequest();
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
return StreamUtils.copyToString(inputStream, Charset.forName("utf-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
}
}
return StrUtil.EMPTY;
}
public static String getAllRequestInfo() {
StringBuilder sb = new StringBuilder();
sb.append("请求详情为:").append(StrUtil.CRLF);
sb.append("RemoteAddress: ").append(getRemoteIP()).append(StrUtil.CRLF);
sb.append("Method: ").append(getRequest().getMethod()).append(StrUtil.CRLF);
sb.append("URI: ").append(getRequestURI()).append(StrUtil.CRLF);
sb.append("Headers: ").append(StrUtil.join(StrUtil.CRLF + " ", mapToList(getHeaders()))).append(StrUtil.CRLF);
sb.append("Body: ").append(getBody()).append(StrUtil.CRLF);
return sb.toString();
}
private static List mapToList(Map parameters) {
List parametersList = new ArrayList();
parameters.forEach((name, value) -> {
parametersList.add(name + "=" + value);
});
return parametersList;
}
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
System.out.println(e.getMessage());
}
return "未知";
}
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
System.out.println(e.getMessage());
}
return "127.0.0.1";
}
}
@@ -0,0 +1,28 @@
package com.zxdmy.excite.common.utils;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
/**
* <p>
* 验证码随机文本生成器之:一位数的加减乘除
* </p>
*
* @author 沈松
* @since 2021-10-04 0004 20:24
*/
public class KaptchaMathOneTextCreator extends DefaultTextCreator {
@Override
public String getText() {
// Random random = new SecureRandom();
SecureRandom random = new SecureRandom();
// 生成两个随机数,随机数范围:[0,10),并返回结果
Map<String, String> result = MyCaptchaUtil.mathTextCreator(random.nextInt(10), random.nextInt(10));
return result.get("resultString");
}
}
@@ -0,0 +1,28 @@
package com.zxdmy.excite.common.utils;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Random;
/**
* <p>
* 验证码随机文本生成器之:两位数的加减乘除
* </p>
*
* @author 沈松
* @since 2021-10-04 0004 20:26
*/
public class KaptchaMathTwoTextCreator extends DefaultTextCreator {
@Override
public String getText() {
// Random random = new SecureRandom();
SecureRandom random = new SecureRandom();
// 保存计算结果
Map<String, String> result = MyCaptchaUtil.mathTextCreator(random.nextInt(100), random.nextInt(100));
// 生成两个随机数,随机数范围:[0,100),并返回结果
return result.get("resultString");
}
}
@@ -0,0 +1,71 @@
package com.zxdmy.excite.common.utils;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* <p>
* 自定义的验证码工具类
* </p>
*
* @author 沈松
* @since 2021-10-04 0004 20:54
*/
public class MyCaptchaUtil {
/**
* 生成数学文本算式
*
* @param a 数字
* @param b 数字
* @return 字符
*/
public static Map<String, String> mathTextCreator(int a, int b) {
SecureRandom random = new SecureRandom();
// Random random = new SecureRandom();
// 生成随机操作,操作范围:[0,4),分别表示: + - * /
int op = random.nextInt(4);
// 定义计算的结果
Integer result = 0;
// 定义构建的算式字符串
StringBuilder resultString = new StringBuilder();
// 运算符:-
if (1 == op) {
if (a >= b) {
result = a - b;
resultString.append(a).append("-").append(b).append("=?@").append(result);
} else {
result = b - a;
resultString.append(b).append("-").append(a).append("=?@").append(result);
}
}
// *
else if (2 == op) {
result = a * b;
resultString.append(a).append("*").append(b).append("=?@").append(result);
}
// /
else if (3 == op) {
if (a != 0 && b % a == 0) {
result = b / a;
resultString.append(b).append("/").append(a).append("=?@").append(result);
} else if (b != 0 && a % b == 0) {
result = a / b;
resultString.append(a).append("/").append(b).append("=?@").append(result);
} else {
return mathTextCreator(a, b);
}
}
// +
else {
result = b + a;
resultString.append(a).append("+").append(b).append("=?@").append(result);
}
Map<String, String> ret = new HashMap<String, String>();
ret.put("resultCode", result.toString());
ret.put("resultString", resultString.toString());
return ret;
}
}
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zxdmy.excite.common.mapper.GlobalConfigMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.zxdmy.excite.common.entity.GlobalConfig">
<id column="id" property="id" />
<result column="conf_service" property="confService" />
<result column="conf_key" property="confKey" />
<result column="conf_value" property="confValue" />
<result column="create_time" property="createTime" />
<result column="encrypt" property="encrypt" />
<result column="update_time" property="updateTime" />
<result column="delete_time" property="deleteTime" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, conf_service, conf_key, conf_value, encrypt, create_time, update_time, delete_time
</sql>
</mapper>