mirror of
https://gitee.com/dromara/MaxKey.git
synced 2026-05-15 04:52:09 +08:00
v 1.5.0 RC2
v 1.5.0 RC2
This commit is contained in:
@@ -41,4 +41,9 @@ public class RedisRemeberMeService extends AbstractRemeberMeService {
|
||||
conn.close();
|
||||
}
|
||||
|
||||
public void setConnectionFactory(RedisConnectionFactory connectionFactory) {
|
||||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ import org.springframework.stereotype.Component;
|
||||
@PropertySource("classpath:/config/applicationConfig.properties")
|
||||
public class ApplicationConfig {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(ApplicationConfig.class);
|
||||
@Autowired
|
||||
DataSoruceConfig dataSoruceConfig;
|
||||
|
||||
@Autowired
|
||||
EmailConfig emailConfig;
|
||||
@Autowired
|
||||
@@ -45,8 +44,8 @@ public class ApplicationConfig {
|
||||
@Value("${config.server.default.uri}")
|
||||
String defaultUri;
|
||||
|
||||
@Value("${config.server.manage.uri}")
|
||||
String manageUri;
|
||||
@Value("${config.server.management.uri}")
|
||||
String managementUri;
|
||||
|
||||
/*
|
||||
* //is enable whiteList for ipAddress filter boolean whiteList;
|
||||
@@ -80,14 +79,6 @@ public class ApplicationConfig {
|
||||
|
||||
}
|
||||
|
||||
public DataSoruceConfig getDataSoruceConfig() {
|
||||
return dataSoruceConfig;
|
||||
}
|
||||
|
||||
public void setDataSoruceConfig(DataSoruceConfig dataSoruceConfig) {
|
||||
this.dataSoruceConfig = dataSoruceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the characterEncodingConfig
|
||||
*/
|
||||
@@ -179,12 +170,12 @@ public class ApplicationConfig {
|
||||
this.emailConfig = emailConfig;
|
||||
}
|
||||
|
||||
public String getManageUri() {
|
||||
return manageUri;
|
||||
public String getManagementUri() {
|
||||
return managementUri;
|
||||
}
|
||||
|
||||
public void setManageUri(String manageUri) {
|
||||
this.manageUri = manageUri;
|
||||
public void setManagementUri(String managementUri) {
|
||||
this.managementUri = managementUri;
|
||||
}
|
||||
|
||||
public String getDefaultUri() {
|
||||
|
||||
@@ -12,25 +12,25 @@ import org.springframework.context.annotation.PropertySource;
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@PropertySource("classpath:/config/applicationConfig.properties")
|
||||
@PropertySource("classpath:/application.properties")
|
||||
public class CharacterEncodingConfig {
|
||||
|
||||
/**
|
||||
* 源字符集.
|
||||
*/
|
||||
@Value("${config.characterencoding.charset.from}")
|
||||
@Value("${server.servlet.encoding.charset.from:UTF-8}")
|
||||
String fromCharSet;
|
||||
|
||||
/**
|
||||
* 目标字符集.
|
||||
*/
|
||||
@Value("${config.characterencoding.charset.to}")
|
||||
@Value("${server.servlet.encoding.charset:UTF-8}")
|
||||
String toCharSet;
|
||||
|
||||
/**
|
||||
* 转换标志.
|
||||
*/
|
||||
@Value("${config.characterencoding.encoding}")
|
||||
@Value("${server.servlet.encoding.enabled:false}")
|
||||
boolean encoding = false;
|
||||
|
||||
public CharacterEncodingConfig() {
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
package org.maxkey.config;
|
||||
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.mybatis.jpa.dialect.Dialect;
|
||||
import org.maxkey.crypto.password.PasswordReciprocal;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
/*
|
||||
* 数据源配置.
|
||||
*
|
||||
* @author Crystal.Sea
|
||||
* dataSource.driverClassName=com.mysql.jdbc.Driver
|
||||
* dataSource.url=jdbc:mysql://192.168.1.49/parasecdb?autoReconnect=true&characterEncoding=UTF-8
|
||||
* dataSource.username=root
|
||||
* dataSource.password=connsec
|
||||
* dataSource.type=mysql
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@PropertySource("classpath:/config/applicationConfig.properties")
|
||||
public class DataSoruceConfig {
|
||||
|
||||
/*
|
||||
* 数据库类型
|
||||
*/
|
||||
@Value("${config.datasource.database:mysql}")
|
||||
String database;
|
||||
/*
|
||||
* jdbc驱动类
|
||||
*/
|
||||
@Value("${config.datasource.driverclass:com.mysql.jdbc.Driver}")
|
||||
String driverClass;
|
||||
/*
|
||||
* jdbc连接地址
|
||||
*/
|
||||
@Value("${config.datasource.url:"
|
||||
+ "jdbc:mysql://localhost/maxkey?autoReconnect=true&characterEncoding=UTF-8}")
|
||||
String url;
|
||||
/*
|
||||
* 数据库用户名
|
||||
*/
|
||||
@Value("${config.datasource.username:root}")
|
||||
String username;
|
||||
/*
|
||||
* 数据库密码
|
||||
*/
|
||||
@Value("${config.datasource.password:maxkey}")
|
||||
String password;
|
||||
|
||||
/*
|
||||
* 数据库密码是否加密
|
||||
*/
|
||||
@Value("${config.datasource.password.encrypt}")
|
||||
boolean encrypt = false;
|
||||
|
||||
/*
|
||||
* 数据库dialect for mybatis
|
||||
*/
|
||||
String dialect;
|
||||
|
||||
public DataSoruceConfig() {
|
||||
super();
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得数据库密码 如果是加密密码(encrypt==true),则进行解密.
|
||||
*
|
||||
* @return decodePassword
|
||||
*/
|
||||
public String getPassword() {
|
||||
String decodePassword = "";
|
||||
LogFactory.getLog(DataSoruceConfig.class).debug("password is " + password);
|
||||
if (encrypt) {
|
||||
decodePassword = PasswordReciprocal.getInstance().decoder(password);
|
||||
} else {
|
||||
decodePassword = password;
|
||||
}
|
||||
LogFactory.getLog(DataSoruceConfig.class)
|
||||
.debug("password is " + password + " , decodePassword is " + decodePassword);
|
||||
return decodePassword;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* @return the database
|
||||
*/
|
||||
public String getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param database the database to set
|
||||
*/
|
||||
public void setDatabase(String database) {
|
||||
this.database = database;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @return the driverClass
|
||||
*/
|
||||
public String getDriverClass() {
|
||||
return driverClass;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param driverClass the driverClass to set
|
||||
*/
|
||||
public void setDriverClass(String driverClass) {
|
||||
this.driverClass = driverClass;
|
||||
}
|
||||
|
||||
public boolean isEncrypt() {
|
||||
return encrypt;
|
||||
}
|
||||
|
||||
public void setEncrypt(boolean encrypt) {
|
||||
this.encrypt = encrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDialect.
|
||||
* @return the dialect
|
||||
*/
|
||||
public String getDialect() {
|
||||
if (this.dialect == null) {
|
||||
this.dialect = Dialect.getDialectMap().get(database);
|
||||
}
|
||||
return dialect;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param dialect the dialect to set
|
||||
*/
|
||||
public void setDialect(String dialect) {
|
||||
this.dialect = dialect;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DataSoruceConfig [database=" + database
|
||||
+ ", driverClass=" + driverClass
|
||||
+ ", url=" + url
|
||||
+ ", username=" + username
|
||||
+ ", password=" + password
|
||||
+ ", encrypt=" + encrypt
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,24 +5,28 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
@Configuration
|
||||
@PropertySource("classpath:/config/applicationConfig.properties")
|
||||
@PropertySource("classpath:/application.properties")
|
||||
public class EmailConfig {
|
||||
|
||||
@Value("${config.email.username}")
|
||||
@Value("${spring.mail.username}")
|
||||
private String username;
|
||||
@Value("${config.email.password}")
|
||||
|
||||
@Value("${spring.mail.password}")
|
||||
private String password;
|
||||
@Value("${config.email.smtpHost}")
|
||||
|
||||
@Value("${spring.mail.host}")
|
||||
private String smtpHost;
|
||||
@Value("${config.email.senderMail}")
|
||||
private String senderMail;
|
||||
@Value("${config.email.port}")
|
||||
|
||||
@Value("${spring.mail.port}")
|
||||
private Integer port;
|
||||
@Value("${config.email.ssl}")
|
||||
|
||||
@Value("${spring.mail.properties.ssl}")
|
||||
private boolean ssl;
|
||||
|
||||
@Value("${spring.mail.properties.sender}")
|
||||
private String sender;
|
||||
|
||||
public EmailConfig() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -67,18 +71,14 @@ public class EmailConfig {
|
||||
this.smtpHost = smtpHost;
|
||||
}
|
||||
|
||||
/*
|
||||
* @return the senderMail
|
||||
*/
|
||||
public String getSenderMail() {
|
||||
return senderMail;
|
||||
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param senderMail the senderMail to set
|
||||
*/
|
||||
public void setSenderMail(String senderMail) {
|
||||
this.senderMail = senderMail;
|
||||
public void setSender(String sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.maxkey.config;
|
||||
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import com.google.code.kaptcha.impl.DefaultKaptcha;
|
||||
import com.google.code.kaptcha.util.Config;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class KaptchaAutoConfiguration {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(KaptchaAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* Captcha Producer Config .
|
||||
* @return Producer
|
||||
* @throws IOException kaptcha.properties is null
|
||||
*/
|
||||
@Bean (name = "captchaProducer")
|
||||
public Producer captchaProducer() throws IOException {
|
||||
Resource resource = new ClassPathResource("/kaptcha.properties");
|
||||
_logger.debug("Kaptcha config file " + resource.getURL());
|
||||
DefaultKaptcha kaptcha = new DefaultKaptcha();
|
||||
Properties properties = new Properties();
|
||||
properties.load(resource.getInputStream());
|
||||
Config config = new Config(properties);
|
||||
kaptcha.setConfig(config);
|
||||
return kaptcha;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.maxkey.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
|
||||
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
|
||||
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource("classpath:/application.properties")
|
||||
@PropertySource("classpath:/config/applicationConfig.properties")
|
||||
public class MvcAutoConfiguration {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(MvcAutoConfiguration.class);
|
||||
|
||||
@Value("${config.server.domain.sub}")
|
||||
String subDomainName;
|
||||
@Value("${spring.servlet.multipart.max-file-size:4194304}")
|
||||
int maxUploadSize;
|
||||
@Value("${spring.messages.basename:classpath:messages/message}")
|
||||
String messagesBasename;
|
||||
|
||||
/**
|
||||
* cookieLocaleResolver .
|
||||
* @return cookieLocaleResolver
|
||||
*/
|
||||
@Bean (name = "localeResolver")
|
||||
public CookieLocaleResolver cookieLocaleResolver() {
|
||||
_logger.debug("subDomainName " + subDomainName);
|
||||
CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
|
||||
cookieLocaleResolver.setCookieName("maxkey_lang");
|
||||
cookieLocaleResolver.setCookieDomain(subDomainName);
|
||||
cookieLocaleResolver.setCookieMaxAge(604800);
|
||||
return cookieLocaleResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息处理,可以直接使用properties的key值,返回的是对应的value值
|
||||
* messageSource .
|
||||
* @return messageSource
|
||||
*/
|
||||
@Bean (name = "messageSource")
|
||||
public ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource() {
|
||||
_logger.debug("Basename " + messagesBasename);
|
||||
ReloadableResourceBundleMessageSource messageSource =
|
||||
new ReloadableResourceBundleMessageSource();
|
||||
messageSource.setBasename(messagesBasename);
|
||||
messageSource.setUseCodeAsDefaultMessage(false);
|
||||
return messageSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locale Change Interceptor and Resolver definition .
|
||||
* @return localeChangeInterceptor
|
||||
*/
|
||||
//@Primary
|
||||
@Bean (name = "localeChangeInterceptor")
|
||||
public LocaleChangeInterceptor localeChangeInterceptor() {
|
||||
LocaleChangeInterceptor localeChangeInterceptor =
|
||||
new LocaleChangeInterceptor();
|
||||
localeChangeInterceptor.setParamName("language");
|
||||
return localeChangeInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* upload file support .
|
||||
* @return multipartResolver
|
||||
*/
|
||||
@Bean (name = "multipartResolver")
|
||||
public CommonsMultipartResolver commonsMultipartResolver() {
|
||||
_logger.debug("maxUploadSize " + maxUploadSize);
|
||||
CommonsMultipartResolver multipartResolver =
|
||||
new CommonsMultipartResolver();
|
||||
multipartResolver.setMaxUploadSize(maxUploadSize);
|
||||
return multipartResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* handlerMapping .
|
||||
* @return handlerMapping
|
||||
*/
|
||||
@Bean (name = "handlerMapping")
|
||||
public RequestMappingHandlerMapping requestMappingHandlerMapping(
|
||||
LocaleChangeInterceptor localeChangeInterceptor) {
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping =
|
||||
new RequestMappingHandlerMapping();
|
||||
requestMappingHandlerMapping.setInterceptors(localeChangeInterceptor);
|
||||
return requestMappingHandlerMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* jaxb2Marshaller .
|
||||
* @return jaxb2Marshaller
|
||||
*/
|
||||
@Bean (name = "jaxb2Marshaller")
|
||||
public Jaxb2Marshaller jaxb2Marshaller() {
|
||||
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
|
||||
jaxb2Marshaller.setClassesToBeBound(org.maxkey.domain.xml.UserInfoXML.class);;
|
||||
return jaxb2Marshaller;
|
||||
}
|
||||
|
||||
/**
|
||||
* marshallingHttpMessageConverter .
|
||||
* @return marshallingHttpMessageConverter
|
||||
*/
|
||||
@Bean (name = "marshallingHttpMessageConverter")
|
||||
public MarshallingHttpMessageConverter marshallingHttpMessageConverter(
|
||||
Jaxb2Marshaller jaxb2Marshaller) {
|
||||
MarshallingHttpMessageConverter marshallingHttpMessageConverter =
|
||||
new MarshallingHttpMessageConverter();
|
||||
marshallingHttpMessageConverter.setMarshaller(jaxb2Marshaller);
|
||||
marshallingHttpMessageConverter.setUnmarshaller(jaxb2Marshaller);
|
||||
ArrayList<MediaType> mediaTypesList = new ArrayList<MediaType>();
|
||||
mediaTypesList.add(MediaType.APPLICATION_XML);
|
||||
marshallingHttpMessageConverter.setSupportedMediaTypes(mediaTypesList);
|
||||
return marshallingHttpMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* mappingJacksonHttpMessageConverter .
|
||||
* @return mappingJacksonHttpMessageConverter
|
||||
*/
|
||||
@Bean (name = "mappingJacksonHttpMessageConverter")
|
||||
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
|
||||
MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter =
|
||||
new MappingJackson2HttpMessageConverter();
|
||||
ArrayList<MediaType> mediaTypesList = new ArrayList<MediaType>();
|
||||
mediaTypesList.add(MediaType.APPLICATION_JSON);
|
||||
mappingJacksonHttpMessageConverter.setSupportedMediaTypes(mediaTypesList);
|
||||
return mappingJacksonHttpMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* AnnotationMethodHandlerAdapter
|
||||
* requestMappingHandlerAdapter .
|
||||
* @return requestMappingHandlerAdapter
|
||||
*/
|
||||
@Bean (name = "requestMappingHandlerAdapter")
|
||||
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
|
||||
MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter,
|
||||
MarshallingHttpMessageConverter marshallingHttpMessageConverter) {
|
||||
RequestMappingHandlerAdapter requestMappingHandlerAdapter =
|
||||
new RequestMappingHandlerAdapter();
|
||||
List<HttpMessageConverter<?>> httpMessageConverterList =
|
||||
new ArrayList<HttpMessageConverter<?>>();
|
||||
httpMessageConverterList.add(mappingJacksonHttpMessageConverter);
|
||||
httpMessageConverterList.add(marshallingHttpMessageConverter);
|
||||
requestMappingHandlerAdapter.setMessageConverters(httpMessageConverterList);
|
||||
return requestMappingHandlerAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* restTemplate .
|
||||
* @return restTemplate
|
||||
*/
|
||||
@Bean (name = "restTemplate")
|
||||
public RestTemplate restTemplate(
|
||||
MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter,
|
||||
MarshallingHttpMessageConverter marshallingHttpMessageConverter) {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
List<HttpMessageConverter<?>> httpMessageConverterList =
|
||||
new ArrayList<HttpMessageConverter<?>>();
|
||||
httpMessageConverterList.add(mappingJacksonHttpMessageConverter);
|
||||
httpMessageConverterList.add(marshallingHttpMessageConverter);
|
||||
restTemplate.setMessageConverters(httpMessageConverterList);
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class MailOtpAuthn extends AbstractOptAuthn {
|
||||
email.setAuthenticator(
|
||||
new DefaultAuthenticator(emailConfig.getUsername(), emailConfig.getPassword()));
|
||||
|
||||
email.setFrom(emailConfig.getSenderMail());
|
||||
email.setFrom(emailConfig.getSender());
|
||||
email.setSubject(subject);
|
||||
email.setMsg(
|
||||
MessageFormat.format(
|
||||
|
||||
@@ -6,134 +6,140 @@ import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
public class RedisConnectionFactory {
|
||||
|
||||
public static class DEFAULT_CONFIG{
|
||||
/**
|
||||
* Redis默认服务器IP
|
||||
*/
|
||||
public static String DEFAULT_ADDRESS = "127.0.0.1";
|
||||
/**
|
||||
* Redis默认端口号
|
||||
*/
|
||||
public static int DEFAULT_PORT = 6379;
|
||||
/**
|
||||
* 访问密码
|
||||
*/
|
||||
public static String DEFAULT_AUTH = "admin";
|
||||
/**
|
||||
* 可用连接实例的最大数目,默认值为8;<br>
|
||||
*如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
|
||||
**/
|
||||
public static int DEFAULT_MAX_ACTIVE = 5000;
|
||||
|
||||
/**
|
||||
* 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
|
||||
*/
|
||||
public static int DEFAULT_MAX_IDLE = 5000;
|
||||
|
||||
/**
|
||||
* 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
|
||||
*/
|
||||
public static int DEFAULT_MAX_WAIT_MILLIS = 10000;
|
||||
|
||||
public static int DEFAULT_TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
|
||||
*/
|
||||
public static boolean DEFAULT_TEST_ON_BORROW = true;
|
||||
/**
|
||||
* 默认过期时间
|
||||
*/
|
||||
public static int DEFAULT_LIFETIME = 600;
|
||||
}
|
||||
public static class DEFAULT_CONFIG {
|
||||
/**
|
||||
* Redis默认服务器IP
|
||||
*/
|
||||
public static String DEFAULT_ADDRESS = "127.0.0.1";
|
||||
/**
|
||||
* Redis默认端口号
|
||||
*/
|
||||
public static int DEFAULT_PORT = 6379;
|
||||
/**
|
||||
* 访问密码
|
||||
*/
|
||||
public static String DEFAULT_AUTH = "admin";
|
||||
/**
|
||||
* 可用连接实例的最大数目,默认值为8;<br>
|
||||
* 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
|
||||
**/
|
||||
public static int DEFAULT_MAX_ACTIVE = 5000;
|
||||
|
||||
JedisPoolConfig poolConfig;
|
||||
|
||||
private JedisPool jedisPool = null;
|
||||
|
||||
private String hostname;
|
||||
/**
|
||||
* 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
|
||||
*/
|
||||
public static int DEFAULT_MAX_IDLE = 5000;
|
||||
|
||||
/**
|
||||
* 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
|
||||
*/
|
||||
public static int DEFAULT_MAX_WAIT_MILLIS = 10000;
|
||||
|
||||
public static int DEFAULT_TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
|
||||
*/
|
||||
public static boolean DEFAULT_TEST_ON_BORROW = true;
|
||||
/**
|
||||
* 默认过期时间
|
||||
*/
|
||||
public static int DEFAULT_LIFETIME = 600;
|
||||
}
|
||||
|
||||
JedisPoolConfig poolConfig;
|
||||
|
||||
private JedisPool jedisPool = null;
|
||||
|
||||
private String hostName;
|
||||
private int port;
|
||||
private String password;
|
||||
private int timeOut;
|
||||
|
||||
public RedisConnectionFactory() {
|
||||
|
||||
}
|
||||
|
||||
public void initConnectionFactory() {
|
||||
if (jedisPool == null) {
|
||||
try {
|
||||
if (this.hostName == null || hostName.equals("")) {
|
||||
hostName = DEFAULT_CONFIG.DEFAULT_ADDRESS;
|
||||
}
|
||||
if (port == 0) {
|
||||
port = DEFAULT_CONFIG.DEFAULT_PORT;
|
||||
}
|
||||
if (timeOut == 0) {
|
||||
timeOut = DEFAULT_CONFIG.DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
if (this.password == null || this.password.equals("") || this.password.equalsIgnoreCase("password")) {
|
||||
this.password = null;
|
||||
}
|
||||
jedisPool = new JedisPool(poolConfig, hostName, port, timeOut, password);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized RedisConnection getConnection() {
|
||||
initConnectionFactory();
|
||||
RedisConnection redisConnection = new RedisConnection(this);
|
||||
return redisConnection;
|
||||
}
|
||||
|
||||
public Jedis open() {
|
||||
return jedisPool.getResource();
|
||||
}
|
||||
|
||||
public void close(Jedis conn) {
|
||||
// jedisPool.returnResource(conn);
|
||||
conn.close();
|
||||
}
|
||||
|
||||
|
||||
public String getHostName() {
|
||||
return hostName;
|
||||
}
|
||||
|
||||
public void setHostName(String hostName) {
|
||||
this.hostName = hostName;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public int getTimeOut() {
|
||||
return timeOut;
|
||||
}
|
||||
|
||||
public void setTimeOut(int timeOut) {
|
||||
this.timeOut = timeOut;
|
||||
}
|
||||
|
||||
public void setPoolConfig(JedisPoolConfig poolConfig) {
|
||||
this.poolConfig = poolConfig;
|
||||
}
|
||||
|
||||
public JedisPoolConfig getPoolConfig() {
|
||||
return poolConfig;
|
||||
}
|
||||
|
||||
|
||||
public RedisConnectionFactory() {
|
||||
|
||||
}
|
||||
|
||||
public void initConnectionFactory() {
|
||||
if(jedisPool==null){
|
||||
try {
|
||||
if(this.hostname==null||hostname.equals("")){
|
||||
hostname= DEFAULT_CONFIG.DEFAULT_ADDRESS;
|
||||
}
|
||||
if(port==0){
|
||||
port= DEFAULT_CONFIG.DEFAULT_PORT;
|
||||
}
|
||||
if(timeOut==0){
|
||||
timeOut=DEFAULT_CONFIG.DEFAULT_TIMEOUT;
|
||||
}
|
||||
|
||||
if(this.password==null||this.password.equals("")||this.password.equalsIgnoreCase("password")){
|
||||
this.password=null;
|
||||
}
|
||||
jedisPool = new JedisPool(poolConfig, hostname, port, timeOut, password);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized RedisConnection getConnection(){
|
||||
initConnectionFactory();
|
||||
RedisConnection redisConnection=new RedisConnection(this);
|
||||
return redisConnection;
|
||||
}
|
||||
|
||||
public Jedis open(){
|
||||
return jedisPool.getResource();
|
||||
}
|
||||
|
||||
public void close(Jedis conn){
|
||||
//jedisPool.returnResource(conn);
|
||||
conn.close();
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public int getTimeOut() {
|
||||
return timeOut;
|
||||
}
|
||||
|
||||
public void setTimeOut(int timeOut) {
|
||||
this.timeOut = timeOut;
|
||||
}
|
||||
|
||||
public void setPoolConfig(JedisPoolConfig poolConfig) {
|
||||
this.poolConfig = poolConfig;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package org.maxkey.web;
|
||||
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.maxkey.config.ApplicationConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
|
||||
/**
|
||||
* ImageEndpoint Producer Image and captcha.
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class ImageEndpoint {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(ImageEndpoint.class);
|
||||
|
||||
@Autowired
|
||||
private Producer captchaProducer;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("applicationConfig")
|
||||
ApplicationConfig applicationConfig;
|
||||
|
||||
/**
|
||||
* captcha image Producer.
|
||||
*
|
||||
* @param request HttpServletRequest
|
||||
* @param response HttpServletResponse
|
||||
*/
|
||||
@RequestMapping(value = "/captcha")
|
||||
public void captchaHandleRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||
try {
|
||||
|
||||
String kaptchaText = captchaProducer.createText();
|
||||
if (applicationConfig.getLoginConfig().getCaptchaType()
|
||||
.equalsIgnoreCase("Arithmetic")) {
|
||||
Integer intParamA = Integer.valueOf(kaptchaText.substring(0, 1));
|
||||
Integer intParamB = Integer.valueOf(kaptchaText.substring(1, 2));
|
||||
Integer calculateValue = 0;
|
||||
if ((intParamA > intParamB) && ((intParamA + intParamB) % 5 > 3)) {
|
||||
calculateValue = intParamA - intParamB;
|
||||
kaptchaText = intParamA + "-" + intParamB + "=?";
|
||||
} else {
|
||||
calculateValue = intParamA + intParamB;
|
||||
kaptchaText = intParamA + "+" + intParamB + "=?";
|
||||
}
|
||||
_logger.trace("Sesssion id " + request.getSession().getId()
|
||||
+ " , Arithmetic calculate Value is " + calculateValue);
|
||||
request.getSession().setAttribute(
|
||||
WebConstants.KAPTCHA_SESSION_KEY, calculateValue + "");
|
||||
} else {
|
||||
// store the text in the session
|
||||
request.getSession().setAttribute(WebConstants.KAPTCHA_SESSION_KEY, kaptchaText);
|
||||
}
|
||||
_logger.trace("Sesssion id " + request.getSession().getId()
|
||||
+ " , Captcha Text is " + kaptchaText);
|
||||
|
||||
// create the image with the text
|
||||
BufferedImage bufferedImage = captchaProducer.createImage(kaptchaText);
|
||||
producerImage(request,response,bufferedImage);
|
||||
} catch (Exception e) {
|
||||
_logger.error("captcha Producer Error " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Session Image Producer.
|
||||
*
|
||||
* @param request HttpServletRequest
|
||||
* @param response HttpServletResponse
|
||||
*/
|
||||
|
||||
@RequestMapping("/image/{id}")
|
||||
public void imageHandleRequest(HttpServletRequest request, HttpServletResponse response,
|
||||
@PathVariable("id") String id) {
|
||||
try {
|
||||
// get session image bytes
|
||||
byte[] image = (byte[]) request.getSession().getAttribute(id);
|
||||
producerImage(request,response,byte2BufferedImage(image));
|
||||
} catch (Exception e) {
|
||||
_logger.error("captcha Producer Error " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* producerImage.
|
||||
* @param request HttpServletRequest
|
||||
* @param response HttpServletResponse
|
||||
* @param bufferedImage BufferedImage
|
||||
* @throws IOException error
|
||||
*/
|
||||
public static void producerImage(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
BufferedImage bufferedImage) throws IOException {
|
||||
// Set to expire far in the past.
|
||||
response.setDateHeader("Expires", 0);
|
||||
// Set standard HTTP/1.1 no-cache headers.
|
||||
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
||||
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
|
||||
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
|
||||
// Set standard HTTP/1.0 no-cache header.
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
// return a jpeg/gif
|
||||
response.setContentType("image/gif");
|
||||
|
||||
// create the image
|
||||
if (bufferedImage != null) {
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
// write the data out
|
||||
ImageIO.write(bufferedImage, "gif", out);
|
||||
try {
|
||||
out.flush();
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* byte2BufferedImage.
|
||||
* @param imageByte bytes
|
||||
* @return
|
||||
*/
|
||||
public static BufferedImage byte2BufferedImage(byte[] imageByte) {
|
||||
try {
|
||||
InputStream in = new ByteArrayInputStream(imageByte);
|
||||
BufferedImage bufferedImage = ImageIO.read(in);
|
||||
return bufferedImage;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* bufferedImage2Byte.
|
||||
* @param bufferedImage BufferedImage
|
||||
* @return
|
||||
*/
|
||||
public static byte[] bufferedImage2Byte(BufferedImage bufferedImage) {
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ImageIO.write(bufferedImage, "gif", byteArrayOutputStream);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setCaptchaProducer(Producer captchaProducer) {
|
||||
this.captchaProducer = captchaProducer;
|
||||
}
|
||||
|
||||
public void setApplicationConfig(ApplicationConfig applicationConfig) {
|
||||
this.applicationConfig = applicationConfig;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.maxkey.web.image;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.maxkey.config.ApplicationConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
|
||||
/**
|
||||
* AbstractImageEndpoint Producer Image .
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
|
||||
public class AbstractImageEndpoint {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(AbstractImageEndpoint.class);
|
||||
|
||||
@Autowired
|
||||
@Qualifier("applicationConfig")
|
||||
ApplicationConfig applicationConfig;
|
||||
|
||||
/**
|
||||
* producerImage.
|
||||
* @param request HttpServletRequest
|
||||
* @param response HttpServletResponse
|
||||
* @param bufferedImage BufferedImage
|
||||
* @throws IOException error
|
||||
*/
|
||||
public static void producerImage(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
BufferedImage bufferedImage) throws IOException {
|
||||
// Set to expire far in the past.
|
||||
response.setDateHeader("Expires", 0);
|
||||
// Set standard HTTP/1.1 no-cache headers.
|
||||
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
||||
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
|
||||
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
|
||||
// Set standard HTTP/1.0 no-cache header.
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
// return a jpeg/gif
|
||||
response.setContentType("image/gif");
|
||||
_logger.trace("create the image");
|
||||
// create the image
|
||||
if (bufferedImage != null) {
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
// write the data out
|
||||
ImageIO.write(bufferedImage, "gif", out);
|
||||
try {
|
||||
out.flush();
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* byte2BufferedImage.
|
||||
* @param imageByte bytes
|
||||
* @return
|
||||
*/
|
||||
public static BufferedImage byte2BufferedImage(byte[] imageByte) {
|
||||
try {
|
||||
InputStream in = new ByteArrayInputStream(imageByte);
|
||||
BufferedImage bufferedImage = ImageIO.read(in);
|
||||
return bufferedImage;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* bufferedImage2Byte.
|
||||
* @param bufferedImage BufferedImage
|
||||
* @return
|
||||
*/
|
||||
public static byte[] bufferedImage2Byte(BufferedImage bufferedImage) {
|
||||
try {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ImageIO.write(bufferedImage, "gif", byteArrayOutputStream);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void setApplicationConfig(ApplicationConfig applicationConfig) {
|
||||
this.applicationConfig = applicationConfig;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.maxkey.web.image;
|
||||
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.maxkey.web.WebConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
|
||||
/**
|
||||
* ImageCaptchaEndpoint Producer captcha.
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class ImageCaptchaEndpoint extends AbstractImageEndpoint {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(ImageCaptchaEndpoint.class);
|
||||
|
||||
@Autowired
|
||||
private Producer captchaProducer;
|
||||
|
||||
/**
|
||||
* captcha image Producer.
|
||||
*
|
||||
* @param request HttpServletRequest
|
||||
* @param response HttpServletResponse
|
||||
*/
|
||||
@RequestMapping(value = "/captcha")
|
||||
public void captchaHandleRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||
try {
|
||||
|
||||
String kaptchaText = captchaProducer.createText();
|
||||
if (applicationConfig.getLoginConfig().getCaptchaType()
|
||||
.equalsIgnoreCase("Arithmetic")) {
|
||||
Integer intParamA = Integer.valueOf(kaptchaText.substring(0, 1));
|
||||
Integer intParamB = Integer.valueOf(kaptchaText.substring(1, 2));
|
||||
Integer calculateValue = 0;
|
||||
if ((intParamA > intParamB) && ((intParamA + intParamB) % 5 > 3)) {
|
||||
calculateValue = intParamA - intParamB;
|
||||
kaptchaText = intParamA + "-" + intParamB + "=?";
|
||||
} else {
|
||||
calculateValue = intParamA + intParamB;
|
||||
kaptchaText = intParamA + "+" + intParamB + "=?";
|
||||
}
|
||||
_logger.trace("Sesssion id " + request.getSession().getId()
|
||||
+ " , Arithmetic calculate Value is " + calculateValue);
|
||||
request.getSession().setAttribute(
|
||||
WebConstants.KAPTCHA_SESSION_KEY, calculateValue + "");
|
||||
} else {
|
||||
// store the text in the session
|
||||
request.getSession().setAttribute(WebConstants.KAPTCHA_SESSION_KEY, kaptchaText);
|
||||
}
|
||||
_logger.trace("Sesssion id " + request.getSession().getId()
|
||||
+ " , Captcha Text is " + kaptchaText);
|
||||
|
||||
// create the image with the text
|
||||
BufferedImage bufferedImage = captchaProducer.createImage(kaptchaText);
|
||||
producerImage(request,response,bufferedImage);
|
||||
} catch (Exception e) {
|
||||
_logger.error("captcha Producer Error " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setCaptchaProducer(Producer captchaProducer) {
|
||||
this.captchaProducer = captchaProducer;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.maxkey.web.image;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
|
||||
/**
|
||||
* ImageEndpoint Producer Image and captcha.
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class ImageEndpoint extends AbstractImageEndpoint {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(ImageEndpoint.class);
|
||||
|
||||
/**
|
||||
* Session Image Producer.
|
||||
*
|
||||
* @param request HttpServletRequest
|
||||
* @param response HttpServletResponse
|
||||
*/
|
||||
|
||||
@RequestMapping("/image/{id}")
|
||||
public void imageHandleRequest(HttpServletRequest request, HttpServletResponse response,
|
||||
@PathVariable("id") String id) {
|
||||
try {
|
||||
// get session image bytes
|
||||
byte[] image = (byte[]) request.getSession().getAttribute(id);
|
||||
producerImage(request,response,byte2BufferedImage(image));
|
||||
} catch (Exception e) {
|
||||
_logger.error("captcha Producer Error " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user