Browse Source

连接池增加更多的属性

pull/12/head
richie 6 years ago
parent
commit
50997f04a5
  1. 41
      src/main/java/com/fr/plugin/db/redis/core/RedisPool.java
  2. 42
      src/main/java/com/fr/plugin/db/redis/core/pool/RedisConnectionPoolConfig.java
  3. 30
      src/main/java/com/fr/plugin/db/redis/ui/pool/RedisConnectionPoolConfigPane.java
  4. 8
      src/main/java/com/fr/plugin/db/redis/ui/proxy/RedisConnectionProxyConfigPane.java
  5. 9
      src/main/resources/com/fr/plugin/db/redis/locale/redis.properties
  6. 9
      src/main/resources/com/fr/plugin/db/redis/locale/redis_en_US.properties
  7. 11
      src/main/resources/com/fr/plugin/db/redis/locale/redis_zh_CN.properties

41
src/main/java/com/fr/plugin/db/redis/core/RedisPool.java

@ -1,5 +1,7 @@
package com.fr.plugin.db.redis.core; package com.fr.plugin.db.redis.core;
import com.fr.config.Configuration;
import com.fr.config.holder.ConfigChangeListener;
import com.fr.log.FineLoggerFactory; import com.fr.log.FineLoggerFactory;
import com.fr.plugin.db.redis.core.emb.Redis; import com.fr.plugin.db.redis.core.emb.Redis;
import com.fr.plugin.db.redis.core.emb.impl.ProxyRedis; import com.fr.plugin.db.redis.core.emb.impl.ProxyRedis;
@ -13,6 +15,8 @@ import com.fr.stable.StringUtils;
import com.fr.third.redis.clients.jedis.Jedis; import com.fr.third.redis.clients.jedis.Jedis;
import com.fr.third.redis.clients.jedis.JedisPool; import com.fr.third.redis.clients.jedis.JedisPool;
import com.fr.third.redis.clients.jedis.JedisPoolConfig; import com.fr.third.redis.clients.jedis.JedisPoolConfig;
import com.fr.transaction.Configurations;
import com.fr.transaction.ValidateProxy;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -24,16 +28,32 @@ import java.util.concurrent.ConcurrentHashMap;
*/ */
public class RedisPool { public class RedisPool {
private static final int TIME_OUT = 100 * 1000;
private static RedisPool pool = new RedisPool(); private static RedisPool pool = new RedisPool();
static {
ValidateProxy.getInstance().getValidateManager().registerListener(new ConfigChangeListener() {
@Override
public void change() {
pool.clearAll();
}
@Override
public boolean accept(Class<? extends Configuration> clazz) {
return RedisConnectionPoolConfig.class == clazz;
}
});
}
public static RedisPool getPool() { public static RedisPool getPool() {
return pool; return pool;
} }
private Map<String, JedisPool> jedisPoolMap = new ConcurrentHashMap<String, JedisPool>(); private Map<String, JedisPool> jedisPoolMap = new ConcurrentHashMap<String, JedisPool>();
private void clearAll() {
jedisPoolMap.clear();
}
public Redis getResource(String host, int port, String password) { public Redis getResource(String host, int port, String password) {
if (RedisConnectionProxyConfig.getInstance().isOpen()) { if (RedisConnectionProxyConfig.getInstance().isOpen()) {
try { try {
@ -42,10 +62,11 @@ public class RedisPool {
FineLoggerFactory.getLogger().error(e.getMessage(), e); FineLoggerFactory.getLogger().error(e.getMessage(), e);
} }
} else { } else {
JedisPool jedisPool = jedisPoolMap.get(host); String feature = host + ":" + port + "@" + password;
JedisPool jedisPool = jedisPoolMap.get(feature);
if (jedisPool == null) { if (jedisPool == null) {
jedisPool = createJedisPool(host, port, password); jedisPool = createJedisPool(host, port, password);
jedisPoolMap.put(host, jedisPool); jedisPoolMap.put(feature, jedisPool);
} }
return new SimpleRedis(jedisPool.getResource()); return new SimpleRedis(jedisPool.getResource());
} }
@ -58,8 +79,8 @@ public class RedisPool {
RedisConnectionPoolConfig poolConfig = RedisConnectionPoolConfig.getInstance(); RedisConnectionPoolConfig poolConfig = RedisConnectionPoolConfig.getInstance();
JedisPoolConfig config = new JedisPoolConfig(); JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(poolConfig.getMaxTotal()); config.setMaxTotal(poolConfig.getMaxTotal());
//连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true //连接耗尽时是否阻塞, false报异常,true阻塞直到超时, 默认true
config.setBlockWhenExhausted(true); config.setBlockWhenExhausted(poolConfig.getBlockWhenExhausted());
//设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数) //设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数)
config.setEvictionPolicyClassName("com.fr.third.org.apache.commons.pool2.impl.DefaultEvictionPolicy"); config.setEvictionPolicyClassName("com.fr.third.org.apache.commons.pool2.impl.DefaultEvictionPolicy");
//是否启用pool的jmx管理功能, 默认true //是否启用pool的jmx管理功能, 默认true
@ -67,9 +88,9 @@ public class RedisPool {
//MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 默认为"pool", JMX不熟,具体不知道是干啥的...默认就好. //MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 默认为"pool", JMX不熟,具体不知道是干啥的...默认就好.
config.setJmxNamePrefix("pool"); config.setJmxNamePrefix("pool");
//是否启用后进先出, 默认true //是否启用后进先出, 默认true
config.setLifo(true); config.setLifo(poolConfig.getLifo());
//最大空闲连接数 //最大空闲连接数
config.setMaxIdle(10); config.setMaxIdle(poolConfig.getMaxIdle());
//最大连接数,可配置 //最大连接数,可配置
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
config.setMaxWaitMillis(poolConfig.getMaxWait()); config.setMaxWaitMillis(poolConfig.getMaxWait());
@ -88,9 +109,9 @@ public class RedisPool {
//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 //逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
config.setTimeBetweenEvictionRunsMillis(-1); config.setTimeBetweenEvictionRunsMillis(-1);
if (StringUtils.isNotBlank(password)) { if (StringUtils.isNotBlank(password)) {
return new JedisPool(config, host, port, TIME_OUT, password); return new JedisPool(config, host, port, poolConfig.getTimeout(), password);
} else { } else {
return new JedisPool(config, host, port, TIME_OUT); return new JedisPool(config, host, port, poolConfig.getTimeout());
} }
} }

42
src/main/java/com/fr/plugin/db/redis/core/pool/RedisConnectionPoolConfig.java

@ -22,10 +22,16 @@ public class RedisConnectionPoolConfig extends DefaultConfiguration {
} }
private static final int MAX_TOTAL = 10; private static final int MAX_TOTAL = 10;
private static final int MAX_IDLE = 10;
private static final int MAX_WAIT = -1; private static final int MAX_WAIT = -1;
private static final int TIME_OUT = 100 * 1000;
private Conf<Integer> maxTotal = Holders.simple(MAX_TOTAL); private Conf<Integer> maxTotal = Holders.simple(MAX_TOTAL);
private Conf<Integer> maxWait = Holders.simple(MAX_WAIT); private Conf<Integer> maxWait = Holders.simple(MAX_WAIT);
private Conf<Integer> maxIdle = Holders.simple(MAX_IDLE);
private Conf<Boolean> blockWhenExhausted = Holders.simple(true);
private Conf<Boolean> lifo = Holders.simple(true);
private Conf<Integer> timeout = Holders.simple(TIME_OUT);
public int getMaxTotal() { public int getMaxTotal() {
return maxTotal.get(); return maxTotal.get();
@ -35,6 +41,14 @@ public class RedisConnectionPoolConfig extends DefaultConfiguration {
this.maxTotal.set(maxTotal); this.maxTotal.set(maxTotal);
} }
public int getMaxIdle() {
return maxIdle.get();
}
public void setMaxIdle(int maxIdle) {
this.maxIdle.set(maxIdle);
}
public int getMaxWait() { public int getMaxWait() {
return maxWait.get(); return maxWait.get();
} }
@ -43,11 +57,39 @@ public class RedisConnectionPoolConfig extends DefaultConfiguration {
this.maxWait.set(maxWait); this.maxWait.set(maxWait);
} }
public boolean getBlockWhenExhausted() {
return blockWhenExhausted.get();
}
public void setBlockWhenExhausted(boolean blockWhenExhausted) {
this.blockWhenExhausted.set(blockWhenExhausted);
}
public boolean getLifo() {
return lifo.get();
}
public void setLifo(boolean lifo) {
this.lifo.set(lifo);
}
public int getTimeout() {
return timeout.get();
}
public void setTimeout(int timeout) {
this.timeout.set(timeout);
}
@Override @Override
public Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
RedisConnectionPoolConfig cloned = (RedisConnectionPoolConfig) super.clone(); RedisConnectionPoolConfig cloned = (RedisConnectionPoolConfig) super.clone();
cloned.maxTotal = (Conf<Integer>) maxTotal.clone(); cloned.maxTotal = (Conf<Integer>) maxTotal.clone();
cloned.maxWait = (Conf<Integer>) maxWait.clone(); cloned.maxWait = (Conf<Integer>) maxWait.clone();
cloned.maxIdle = (Conf<Integer>) maxIdle.clone();
cloned.blockWhenExhausted = (Conf<Boolean>) blockWhenExhausted.clone();
cloned.lifo = (Conf<Boolean>) lifo.clone();
cloned.timeout = (Conf<Integer>) timeout.clone();
return cloned; return cloned;
} }
} }

30
src/main/java/com/fr/plugin/db/redis/ui/pool/RedisConnectionPoolConfigPane.java

@ -1,6 +1,7 @@
package com.fr.plugin.db.redis.ui.pool; package com.fr.plugin.db.redis.ui.pool;
import com.fr.design.dialog.BasicPane; import com.fr.design.dialog.BasicPane;
import com.fr.design.gui.icheckbox.UICheckBox;
import com.fr.design.gui.ilable.UILabel; import com.fr.design.gui.ilable.UILabel;
import com.fr.design.gui.itextfield.UIIntNumberField; import com.fr.design.gui.itextfield.UIIntNumberField;
import com.fr.design.gui.itextfield.UINumberField; import com.fr.design.gui.itextfield.UINumberField;
@ -21,6 +22,10 @@ public class RedisConnectionPoolConfigPane extends BasicPane {
private UIIntNumberField maxTotalNumberFiled; private UIIntNumberField maxTotalNumberFiled;
private UINumberField maxWaitNumberField; private UINumberField maxWaitNumberField;
private UIIntNumberField maxIdleNumberField;
private UICheckBox blockWhenExhaustedCheckBox;
private UICheckBox lifoCheckBox;
private UIIntNumberField timeoutNumberField;
public RedisConnectionPoolConfigPane() { public RedisConnectionPoolConfigPane() {
setLayout(new BorderLayout()); setLayout(new BorderLayout());
@ -28,14 +33,27 @@ public class RedisConnectionPoolConfigPane extends BasicPane {
maxWaitNumberField = new UINumberField(); maxWaitNumberField = new UINumberField();
maxWaitNumberField.setMinValue(-1); maxWaitNumberField.setMinValue(-1);
maxWaitNumberField.setMaxDecimalLength(0); maxWaitNumberField.setMaxDecimalLength(0);
maxIdleNumberField = new UIIntNumberField();
blockWhenExhaustedCheckBox = new UICheckBox();
blockWhenExhaustedCheckBox.setSelected(false);
lifoCheckBox = new UICheckBox();
timeoutNumberField = new UIIntNumberField();
JComponent[][] comps = new JComponent[][]{ JComponent[][] comps = new JComponent[][]{
{new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Max_Total") + ":"), maxTotalNumberFiled}, {new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Max_Total") + ":"), maxTotalNumberFiled},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Max_Wait") + ":"), maxWaitNumberField} {new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Max_Wait") + ":"), maxWaitNumberField},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Max_Idle") + ":"), maxIdleNumberField},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Block_When_Exhausted") + ":"), blockWhenExhaustedCheckBox},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Lifo") + ":"), lifoCheckBox},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Pool_Timeout") + ":"), timeoutNumberField}
}; };
double p = TableLayout.PREFERRED; double p = TableLayout.PREFERRED;
double f = TableLayout.FILL; double f = TableLayout.FILL;
double[] rowSize = new double[]{p, p}; double[] rowSize = new double[]{p, p, p, p, p, p};
double[] columnSize = new double[]{p, f}; double[] columnSize = new double[]{p, f};
add(TableLayoutHelper.createTableLayoutPane(comps, rowSize, columnSize), BorderLayout.CENTER); add(TableLayoutHelper.createTableLayoutPane(comps, rowSize, columnSize), BorderLayout.CENTER);
} }
@ -48,10 +66,18 @@ public class RedisConnectionPoolConfigPane extends BasicPane {
public void populate(RedisConnectionPoolConfig config) { public void populate(RedisConnectionPoolConfig config) {
maxTotalNumberFiled.setValue(config.getMaxTotal()); maxTotalNumberFiled.setValue(config.getMaxTotal());
maxWaitNumberField.setValue(config.getMaxWait()); maxWaitNumberField.setValue(config.getMaxWait());
maxIdleNumberField.setValue(config.getMaxIdle());
blockWhenExhaustedCheckBox.setSelected(config.getBlockWhenExhausted());
lifoCheckBox.setSelected(config.getLifo());
timeoutNumberField.setValue(config.getTimeout());
} }
public void update(RedisConnectionPoolConfig config) { public void update(RedisConnectionPoolConfig config) {
config.setMaxTotal((int)maxTotalNumberFiled.getValue()); config.setMaxTotal((int)maxTotalNumberFiled.getValue());
config.setMaxWait((int) maxWaitNumberField.getValue()); config.setMaxWait((int) maxWaitNumberField.getValue());
config.setMaxIdle((int) maxIdleNumberField.getValue());
config.setBlockWhenExhausted(blockWhenExhaustedCheckBox.isSelected());
config.setLifo(lifoCheckBox.isSelected());
config.setTimeout((int) timeoutNumberField.getValue());
} }
} }

8
src/main/java/com/fr/plugin/db/redis/ui/proxy/RedisConnectionProxyConfigPane.java

@ -41,6 +41,7 @@ public class RedisConnectionProxyConfigPane extends BasicPane {
openCheckBox.setSelected(false); openCheckBox.setSelected(false);
hostTextField = new UITextField(); hostTextField = new UITextField();
portNumberField = new UIIntNumberField(); portNumberField = new UIIntNumberField();
usernameTextField = new UITextField(); usernameTextField = new UITextField();
passwordTextField = new UIPassWordField(); passwordTextField = new UIPassWordField();
@ -50,8 +51,13 @@ public class RedisConnectionProxyConfigPane extends BasicPane {
UIButton fileSelectButton = new UIButton("..."); UIButton fileSelectButton = new UIButton("...");
UILabel description = new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Description"), SwingConstants.CENTER);
JComponent[][] comps = new JComponent[][]{ JComponent[][] comps = new JComponent[][]{
{new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Open") + ":"), openCheckBox}, {new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Open") + ":"), GUICoreUtils.createBorderLayoutPane(
openCheckBox, BorderLayout.WEST,
description, BorderLayout.CENTER
)},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Host") + ":"), hostTextField}, {new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Host") + ":"), hostTextField},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Port") + ":"), portNumberField}, {new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Port") + ":"), portNumberField},
{new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Username") + ":"), usernameTextField}, {new UILabel(Toolkit.i18nText("Plugin-Redis_Proxy_Username") + ":"), usernameTextField},

9
src/main/resources/com/fr/plugin/db/redis/locale/redis.properties

@ -21,11 +21,16 @@ Plugin-Redis_Pool_Max_Total=Max Total
Plugin-Redis_Script_Query_Text=Query Script Plugin-Redis_Script_Query_Text=Query Script
Plugin-Redis_Pool_Max_Wait=Max Wait Plugin-Redis_Pool_Max_Wait=Max Wait
Plugin-Redis_Pool_Config=Jedis Pool Config Plugin-Redis_Pool_Config=Jedis Pool Config
Plugin-Redis_Proxy_Config=Proxy Config Plugin-Redis_Proxy_Config=Jumpserver Config
Plugin-Redis_Proxy_Open=Open Proxy Server Plugin-Redis_Proxy_Open=Enable Jumpserver
Plugin-Redis_Proxy_Host=Host Plugin-Redis_Proxy_Host=Host
Plugin-Redis_Proxy_Port=Port Plugin-Redis_Proxy_Port=Port
Plugin-Redis_Proxy_Username=Username Plugin-Redis_Proxy_Username=Username
Plugin-Redis_Proxy_Password=Password Plugin-Redis_Proxy_Password=Password
Plugin-Redis_Proxy_Private_Key_Path=Key Path Plugin-Redis_Proxy_Private_Key_Path=Key Path
Plugin-Redis_Proxy_Private_Key_Tip=Path To Private Key in PEM Format Plugin-Redis_Proxy_Private_Key_Tip=Path To Private Key in PEM Format
Plugin-Redis_Pool_Block_When_Exhausted=Block When Exhausted
Plugin-Redis_Pool_Lifo=Lifo
Plugin-Redis_Pool_Max_Idle=Max Idle
Plugin-Redis_Pool_Timeout=Timeout
Plugin-Redis_Proxy_Description=Only Support SSH Protocol

9
src/main/resources/com/fr/plugin/db/redis/locale/redis_en_US.properties

@ -21,11 +21,16 @@ Plugin-Redis_Pool_Max_Total=Max Total
Plugin-Redis_Script_Query_Text=Query Script Plugin-Redis_Script_Query_Text=Query Script
Plugin-Redis_Pool_Max_Wait=Max Wait Plugin-Redis_Pool_Max_Wait=Max Wait
Plugin-Redis_Pool_Config=Jedis Pool Config Plugin-Redis_Pool_Config=Jedis Pool Config
Plugin-Redis_Proxy_Config=Proxy Config Plugin-Redis_Proxy_Config=Jumpserver Config
Plugin-Redis_Proxy_Open=Open Proxy Server Plugin-Redis_Proxy_Open=Enable Jumpserver
Plugin-Redis_Proxy_Host=Host Plugin-Redis_Proxy_Host=Host
Plugin-Redis_Proxy_Port=Port Plugin-Redis_Proxy_Port=Port
Plugin-Redis_Proxy_Username=Username Plugin-Redis_Proxy_Username=Username
Plugin-Redis_Proxy_Password=Password Plugin-Redis_Proxy_Password=Password
Plugin-Redis_Proxy_Private_Key_Path=Key Path Plugin-Redis_Proxy_Private_Key_Path=Key Path
Plugin-Redis_Proxy_Private_Key_Tip=Path To Private Key in PEM Format Plugin-Redis_Proxy_Private_Key_Tip=Path To Private Key in PEM Format
Plugin-Redis_Pool_Block_When_Exhausted=Block When Exhausted
Plugin-Redis_Pool_Lifo=Lifo
Plugin-Redis_Pool_Max_Idle=Max Idle
Plugin-Redis_Pool_Timeout=Timeout
Plugin-Redis_Proxy_Description=Only Support SSH Protocol

11
src/main/resources/com/fr/plugin/db/redis/locale/redis_zh_CN.properties

@ -22,11 +22,16 @@ Plugin-Redis_Pool_Max_Total=\u6700\u5927\u8FDE\u63A5\u6570
Plugin-Redis_Script_Query_Text=\u67E5\u8BE2\u811A\u672C Plugin-Redis_Script_Query_Text=\u67E5\u8BE2\u811A\u672C
Plugin-Redis_Pool_Max_Wait=\u6700\u5927\u7B49\u5F85\u65F6\u95F4 Plugin-Redis_Pool_Max_Wait=\u6700\u5927\u7B49\u5F85\u65F6\u95F4
Plugin-Redis_Pool_Config=\u8FDE\u63A5\u6C60\u914D\u7F6E Plugin-Redis_Pool_Config=\u8FDE\u63A5\u6C60\u914D\u7F6E
Plugin-Redis_Proxy_Config=\u4EE3\u7406\u670D\u52A1\u5668\u8BBE\u7F6E Plugin-Redis_Proxy_Config=\u8DF3\u677F\u670D\u52A1\u5668\u8BBE\u7F6E
Plugin-Redis_Proxy_Open=\u542F\u7528\u4EE3\u7406\u670D\u52A1\u5668 Plugin-Redis_Proxy_Open=\u542F\u7528\u8DF3\u677F\u670D\u52A1\u5668
Plugin-Redis_Proxy_Host=\u4E3B\u673A\u5730\u5740 Plugin-Redis_Proxy_Host=\u4E3B\u673A\u5730\u5740
Plugin-Redis_Proxy_Port=\u7AEF\u53E3 Plugin-Redis_Proxy_Port=\u7AEF\u53E3
Plugin-Redis_Proxy_Username=\u7528\u6237\u540D Plugin-Redis_Proxy_Username=\u7528\u6237\u540D
Plugin-Redis_Proxy_Password=\u5BC6\u7801 Plugin-Redis_Proxy_Password=\u5BC6\u7801
Plugin-Redis_Proxy_Private_Key_Path=\u79D8\u94A5\u6587\u4EF6 Plugin-Redis_Proxy_Private_Key_Path=\u79D8\u94A5\u6587\u4EF6
Plugin-Redis_Proxy_Private_Key_Tip=PEM\u683C\u5F0F\u7684\u79D8\u94A5\u6587\u4EF6\u8DEF\u5F84 Plugin-Redis_Proxy_Private_Key_Tip=PEM\u683C\u5F0F\u7684\u79D8\u94A5\u6587\u4EF6\u8DEF\u5F84
Plugin-Redis_Pool_Block_When_Exhausted=\u8FDE\u63A5\u8017\u5C3D\u65F6\u963B\u585E
Plugin-Redis_Pool_Lifo=\u540E\u8FDB\u5148\u51FA
Plugin-Redis_Pool_Max_Idle=\u6700\u5927\u7A7A\u95F2\u8FDE\u63A5\u6570
Plugin-Redis_Pool_Timeout=\u8D85\u65F6\u65F6\u95F4
Plugin-Redis_Proxy_Description=\u8DF3\u677F\u670D\u52A1\u5668\u8FDE\u63A5\u4EC5\u652F\u6301ssh\u534F\u8BAE\uFF08\u6CE8\u610F\uFF1A\u542F\u7528\u8DF3\u677F\u670D\u52A1\u5668\u5C06\u65E0\u6CD5\u4F7F\u7528\u8FDE\u63A5\u6C60\uFF09
Loading…
Cancel
Save