tomcat 加密数据源配置

配置数据源

1) 配置位置

可以选择以下位置中的Context></Context>标签中进行配置

  • %TOMCAT_HOME%\conf\server.xml。
  • %TOMCAT_HOME%\conf\context.xml。
  • 应用中META-INF/context.xml,这个是私有的,只对这个应用可见。

2) mysql配置样例

1
2
3
4
<Resource name= "jdbc/xxx"  auth= "Container"  type= "javax.sql.DataSource"
                maxActive= "100"  maxIdle= "30"  maxWait= "10000"
                username= "javauser"  password= "javadude"  driverClassName= "com.mysql.jdbc.Driver"
                url= "jdbc:mysql://localhost:3306/javatest" />

3) oracle配置样例

1
2
3
4
5
<Resource name= "jdbc/xxx"  auth= "Container"
               type= "javax.sql.DataSource"  driverClassName= "oracle.jdbc.OracleDriver"
               url= "jdbc:oracle:thin:@127.0.0.1:1521:mysid"
               username= "scott"  password= "tiger"  maxActive= "20"  maxIdle= "10"
               maxWait= "-1" />

4)参数说明

  • driverClassName:驱动类名称。
  • username:数据库用户名
  • password:数据库密码
  • url:访问的数据库路径。其中url的内容组成解析上篇博客中已经分析
  • maxActive:并发连接的最大数。设置为0则无限制。
  • maxWait:是等待连接的最大连接的时间。
  • maxIdle:是连接池中空闲的连接的个数。

3、web.xml配置(tomcat 1.6需要添加,1.7不需要)

1
2
3
4
5
6
<resource-ref>
     <description>DB Connection</description>
     <res-ref-name>jdbc/xxx</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
     <res-auth>Container</res-auth>
</resource-ref>

4、spring配置

1
2
3
<bean id= "dataSource"  class = "org.springframework.jndi.JndiObjectFactoryBean" >
    <property name= "jndiName" ><value>java:comp/env/jdbc/xxx</value></property>
</bean>
5. 加密数据源配置

1.	加入datasource.jar和驱动包到tomcat lib下面
<pre>2.	生成加密字符串 进入lib目录执行命令(XXX为要加密的字符串)
java -cp datasource.jar com.datasource.security.encrypt.CipherEncrypter XXX
3.	在server.xml  GlobalNamingResources标签中加入
<Resource name="jdbc/Quartz" auth="Container" type="javax.sql.DataSource"
		factory="com.datasource.sectuiry.MyBasicDataSourceFactory" maxActive="20" maxIdle="5"
		maxWait="10000" username="QkSk8tx1auc=" password="QkSk8tx1auc="
		driverClassName="oracle.jdbc.driver.OracleDriver"
		url="jdbc:oracle:thin:@10.118.242.50:1521:npdb"
		validationQuery="select 1 from dual"/>
4.	在context.xml中加入
<ResourceLink global="jdbc/Quartz" name="jdbc/Quartz" type="javax.sql.DataSource" />

 

加密数据源 源码

	<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>tomcat-dbcp</artifactId>
			<version>7.0.62</version>
		</dependency>


package com.datasource.security.encrypt;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class CipherEncrypter
{
  Cipher ecipher;
  Cipher dcipher;
  byte[] salt = { -87, -101, -56, 50, 86, 53, -29, 3 };

  int iterationCount = 19;
  private static CipherEncrypter cipherEncrypter;

  private CipherEncrypter(String passPhrase)
  {
    try
    {
      PBEKeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray());
      SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
      this.ecipher = Cipher.getInstance(key.getAlgorithm());
      this.dcipher = Cipher.getInstance(key.getAlgorithm());

      AlgorithmParameterSpec paramSpec = new PBEParameterSpec(this.salt, this.iterationCount);

      this.ecipher.init(1, key, paramSpec);
      this.dcipher.init(2, key, paramSpec);
    } catch (InvalidAlgorithmParameterException localInvalidAlgorithmParameterException) {
    } catch (InvalidKeySpecException localInvalidKeySpecException) {
    } catch (NoSuchPaddingException localNoSuchPaddingException) {
    } catch (NoSuchAlgorithmException localNoSuchAlgorithmException) {
    } catch (InvalidKeyException localInvalidKeyException) {
    }
  }

  private CipherEncrypter() {
    this("sfpay");
  }

  public static CipherEncrypter getInstance() {
    if (cipherEncrypter == null) {
      cipherEncrypter = new CipherEncrypter();
    }
    return cipherEncrypter;
  }

  public static String encrypt(String str) {
    try {
      byte[] utf8 = str.getBytes("UTF8");
      byte[] enc = getInstance().ecipher.doFinal(utf8);
      return new BASE64Encoder().encode(enc);
    } catch (BadPaddingException localBadPaddingException) {
    } catch (IllegalBlockSizeException localIllegalBlockSizeException) {
    } catch (UnsupportedEncodingException localUnsupportedEncodingException) {
    } catch (Exception localException) {
    }
    return null;
  }

  public static String decrypt(String str)
  {
    try {
      byte[] dec = new BASE64Decoder().decodeBuffer(str);

      byte[] utf8 = getInstance().dcipher.doFinal(dec);

      return new String(utf8, "UTF8");
    } catch (BadPaddingException localBadPaddingException) {
    } catch (IllegalBlockSizeException localIllegalBlockSizeException) {
    } catch (UnsupportedEncodingException localUnsupportedEncodingException) {
    } catch (IOException localIOException) {
    }
    return null;
  }

  public static void main(String[] args) {
    if (args.length != 1)
      return;
    System.out.println("encrypted string : [" + encrypt(args[0]) + "]");
  }
}
package com.security.datasource.security
import java.io.ByteArrayInputStream;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.sql.DataSource;

import org.apache.tomcat.dbcp.dbcp.BasicDataSource;
import org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory;

import com.datasource.security.encrypt.CipherEncrypter;

public class MyBasicDataSourceFactory extends BasicDataSourceFactory
{

	  protected static final String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
	  protected static final String PROP_DEFAULTREADONLY = "defaultReadOnly";
	  protected static final String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
	  protected static final String PROP_DEFAULTCATALOG = "defaultCatalog";
	  protected static final String PROP_DRIVERCLASSNAME = "driverClassName";
	  protected static final String PROP_MAXACTIVE = "maxActive";
	  protected static final String PROP_MAXIDLE = "maxIdle";
	  protected static final String PROP_MINIDLE = "minIdle";
	  protected static final String PROP_INITIALSIZE = "initialSize";
	  protected static final String PROP_MAXWAIT = "maxWait";
	  protected static final String PROP_TESTONBORROW = "testOnBorrow";
	  protected static final String PROP_TESTONRETURN = "testOnReturn";
	  protected static final String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
	  protected static final String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
	  protected static final String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
	  protected static final String PROP_TESTWHILEIDLE = "testWhileIdle";
	  protected static final String PROP_PASSWORD = "password";
	  protected static final String PROP_URL = "url";
	  protected static final String PROP_USERNAME = "username";
	  protected static final String PROP_VALIDATIONQUERY = "validationQuery";
	  protected static final String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout";
	  protected static final String PROP_INITCONNECTIONSQLS = "initConnectionSqls";
	  protected static final String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED = "accessToUnderlyingConnectionAllowed";
	  protected static final String PROP_REMOVEABANDONED = "removeAbandoned";
	  protected static final String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout";
	  protected static final String PROP_LOGABANDONED = "logAbandoned";
	  protected static final String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements";
	  protected static final String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements";
	  protected static final String PROP_CONNECTIONPROPERTIES = "connectionProperties";
	  protected static final String[] ALL_PROPERTIES = { 
	    "defaultAutoCommit", 
	    "defaultReadOnly", 
	    "defaultTransactionIsolation", 
	    "defaultCatalog", 
	    "driverClassName", 
	    "maxActive", 
	    "maxIdle", 
	    "minIdle", 
	    "initialSize", 
	    "maxWait", 
	    "testOnBorrow", 
	    "testOnReturn", 
	    "timeBetweenEvictionRunsMillis", 
	    "numTestsPerEvictionRun", 
	    "minEvictableIdleTimeMillis", 
	    "testWhileIdle", 
	    "password", 
	    "url", 
	    "username", 
	    "validationQuery", 
	    "validationQueryTimeout", 
	    "initConnectionSqls", 
	    "accessToUnderlyingConnectionAllowed", 
	    "removeAbandoned", 
	    "removeAbandonedTimeout", 
	    "logAbandoned", 
	    "poolPreparedStatements", 
	    "maxOpenPreparedStatements", 
	    "connectionProperties" };

	  public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment)
	    throws Exception
	  {
	    if ((obj == null) || (!(obj instanceof Reference))) {
	      return null;
	    }
	    Reference ref = (Reference)obj;
	    if (!"javax.sql.DataSource".equals(ref.getClassName())) {
	      return null;
	    }

	    Properties properties = new Properties();
	    for (int i = 0; i < ALL_PROPERTIES.length; i++) {
	      String propertyName = ALL_PROPERTIES[i];
	      RefAddr ra = ref.get(propertyName);
	      if (ra != null) {
	        String propertyValue = ra.getContent().toString();
	        properties.setProperty(propertyName, propertyValue);
	      }
	    }

	    return createDataSource(properties);
	  }

	  public static DataSource createDataSource(Properties properties)
	    throws Exception
	  {
	    final BasicDataSource dataSource = new BasicDataSource();
	    String value = null;

	    value = properties.getProperty("defaultAutoCommit");
	    if (value != null) {
	      dataSource.setDefaultAutoCommit(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("defaultReadOnly");
	    if (value != null) {
	      dataSource.setDefaultReadOnly(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("defaultTransactionIsolation");
	    if (value != null)
	    {
	      int level = -1;
	      if ("NONE".equalsIgnoreCase(value)) {
	        level = 0;
	      }
	      else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
	        level = 2;
	      }
	      else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
	        level = 1;
	      }
	      else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
	        level = 4;
	      }
	      else if ("SERIALIZABLE".equalsIgnoreCase(value))
	        level = 8;
	      else {
	        try
	        {
	          level = Integer.parseInt(value);
	        } catch (NumberFormatException e) {
	          System.err.println("Could not parse defaultTransactionIsolation: " + value);
	          System.err.println("WARNING: defaultTransactionIsolation not set");
	          System.err.println("using default value of database driver");

	          level = -1;
	        }
	      }
	      dataSource.setDefaultTransactionIsolation(level);
	    }

	    value = properties.getProperty("defaultCatalog");
	    if (value != null) {
	      dataSource.setDefaultCatalog(value);
	    }

	    value = properties.getProperty("driverClassName");
	    if (value != null) {
	      dataSource.setDriverClassName(value);
	    }

	    value = properties.getProperty("maxActive");
	    if (value != null) {
	      dataSource.setMaxActive(Integer.parseInt(value));
	    }

	    value = properties.getProperty("maxIdle");
	    if (value != null) {
	      dataSource.setMaxIdle(Integer.parseInt(value));
	    }

	    value = properties.getProperty("minIdle");
	    if (value != null) {
	      dataSource.setMinIdle(Integer.parseInt(value));
	    }

	    value = properties.getProperty("initialSize");
	    if (value != null) {
	      dataSource.setInitialSize(Integer.parseInt(value));
	    }

	    value = properties.getProperty("maxWait");
	    if (value != null) {
	      dataSource.setMaxWait(Long.parseLong(value));
	    }

	    value = properties.getProperty("testOnBorrow");
	    if (value != null) {
	      dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("testOnReturn");
	    if (value != null) {
	      dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("timeBetweenEvictionRunsMillis");
	    if (value != null) {
	      dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
	    }

	    value = properties.getProperty("numTestsPerEvictionRun");
	    if (value != null) {
	      dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
	    }

	    value = properties.getProperty("minEvictableIdleTimeMillis");
	    if (value != null) {
	      dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
	    }

	    value = properties.getProperty("testWhileIdle");
	    if (value != null) {
	      dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("password");
	    if (value != null) {
	      dataSource.setPassword(CipherEncrypter.decrypt(value.trim()));
	    }

	    value = properties.getProperty("url");
	    if (value != null) {
	      dataSource.setUrl(value);
	    }

	    value = properties.getProperty("username");
	    if (value != null) {
	      dataSource.setUsername(CipherEncrypter.decrypt(value.trim()));
	    }

	    value = properties.getProperty("validationQuery");
	    if (value != null) {
	      dataSource.setValidationQuery(value);
	    }

	    value = properties.getProperty("validationQueryTimeout");
	    if (value != null) {
	      dataSource.setValidationQueryTimeout(Integer.parseInt(value));
	    }

	    value = properties.getProperty("accessToUnderlyingConnectionAllowed");
	    if (value != null) {
	      dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("removeAbandoned");
	    if (value != null) {
	      dataSource.setRemoveAbandoned(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("removeAbandonedTimeout");
	    if (value != null) {
	      dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
	    }

	    value = properties.getProperty("logAbandoned");
	    if (value != null) {
	      dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("poolPreparedStatements");
	    if (value != null) {
	      dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue());
	    }

	    value = properties.getProperty("maxOpenPreparedStatements");
	    if (value != null) {
	      dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
	    }

	    value = properties.getProperty("initConnectionSqls");
	    if (value != null) {
	      StringTokenizer tokenizer = new StringTokenizer(value, ";");
	      dataSource.setConnectionInitSqls(Collections.list(tokenizer));
	    }

	    value = properties.getProperty("connectionProperties");
	    if (value != null) {
	      Properties p = getProperties(value);
	      Enumeration e = p.propertyNames();
	      while (e.hasMoreElements()) {
	        String propertyName = (String)e.nextElement();
	        dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
	      }

	    }

	    if (dataSource.getInitialSize() > 0) {
	      dataSource.getLogWriter();
	    }
	    
	   
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                	try {
						dataSource.close();
					} catch (SQLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
                }
            });
       
	    
	    return dataSource;
	  }

	  protected static Properties getProperties(String propText)
	    throws Exception
	  {
	    Properties p = new Properties();
	    if (propText != null) {
	      p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes()));
	    }
	    return p;
	  }

	
	}



6. httponly防XSS攻击

1)在tomcat7的/tomcat/apache-tomcat-7.0.62/conf/web.xml中设置:

web.xml
< session-config >
        < session-timeout >30</ session-timeout >
        < cookie-config >
            < http-only >true</ http-only >
        </ cookie-config >
</ session-config >

 

2) 默认对JSESSIONID的cookie已设置了HttpOnly, 具体的设置是在/tomcat/apache-tomcat-7.0.62/conf/context.xml配置

context.xml
< Context  useHttpOnly = "true" sessionCookieName = "JSESSIONID" sessionCookieDomain = "/servlet3"  ... >
...
</ Context >

7.jvm参数配置

1、打开/tomcat/apache-tomcat-7.0.62/bin/setenv.sh。

2、根据需要对以下参数进行调整

JAVA_OPTS="$JAVA_OPTS -server -Xms2048m -Xmx2048m -XX:PermSize=256M -XX:MaxPermSize=256M -Xss256k -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:-CMSParallelRemarkEnabled -XX:ParallelGCThreads=8 -XX:MaxTenuringThreshold=10 -XX:GCTimeRatio=19 -XX:+DisableExplicitGC"

参数说明

参数名
说明

-server

如果不配置该参数,JVM会根据应用服务器硬件配置自动选择不同模式,server模式启动比较慢,

但是运行期速度得到了优化,适合于服务器端运行的JVM。

-Xms

设置Java堆初始化时的大小,建议与-Xmx一致。

-Xmx

设置java heap的最大值,这个值决定了最多可用的Java堆内存。

-XX:PermSize

初始化永久内存区域大小,根据加载的class数量进行调整,建议与-XX:MaxPermSize设置成一致。

-XX:MaxPermSize

设置永久内存区域最大大小。

-Xss

设置每个线程的堆栈大小,根据应用的线程所需内存大小进行调整,在相同物理内存下,减小这个值能生成更多的线程。

-XX:+UseConcMarkSweepGC

指定在 老年代 使用 并发垃圾回收。gc thread 和 app thread 并行 ( 在 init-mark 和 remark 时

pause app thread)。app pause 时间较短 , 适合交互性强的系统 , 如 web server。

它可以并发执行收集操作,降低应用停止时间,同时它也是并行处理模式,可以有效地利用多处理器的系统的多进程处理。

-XX:+UseParNewGC

指定在新生代使用parallel collector, 是 UseParallelGC 的 gc 的升级版本 , 有更好的性能或者优点 , 可以和 CMS gc 一起使用

-XX:-CMSParallelRemarkEnabled

在使用 UseParNewGC 的情况下 , 尽量减少 mark 的时间。

-XX:ParallelGCThreads

配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

-XX:MaxTenuringThreshold

设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入老年代。

对于老年代比较多的应用,可以提高效率。如果将此值设置为一个较大值,

则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代即被回收的概率。

-XX:GCTimeRatio

设置垃圾回收时间占程序运行时间的百分比。该参数设置为n的话,

则垃圾回收时间占程序运行时间百分比的公式为1/(1+n) ,如果n=19表示java可以用5%的时间来做垃圾回收,1/(1+19)=1/20=5%。

-XX:+DisableExplicitGC

禁止 java 程序中显示的调用full gc, 如 System.gc() 的调用。 最好加上防止程序在代码里误用了,对性能造成冲击。









1
2
3
<bean id= "dataSource"  class = "org.springframework.jndi.JndiObjectFactoryBean" >
    <property name= "jndiName" ><value>java:comp/env/jdbc/xxx</value></property>
</bean>
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在分库分表中,需要配置多个数据源,以便应用程序能够连接到不同的分片数据库。一般来说,配置数据源需要以下几个步骤: 1. 导入相关的数据库驱动包,以便应用程序能够连接到数据库。不同的数据库有不同的驱动包,需要根据实际使用的数据库进行选择。 2. 配置数据库连接信息,包括数据库的地址、端口、用户名、密码等信息。这些信息可以保存在配置文件中,也可以在应用程序中直接硬编码。 3. 创建数据库连接池,以便应用程序能够高效地管理和复用数据库连接。连接池可以通过第三方库或应用程序框架提供的接口来创建和管理。 4. 在应用程序中使用数据源,通过数据源获取数据库连接,进行数据库操作。 具体地,对于Java Web应用程序来说,可以使用Java EE容器提供的JDBC数据源来管理和配置数据源。以Tomcat为例,配置数据源的步骤如下: 1. 在Tomcat的conf目录下,创建一个名为context.xml的文件,用于配置数据源。 2. 在context.xml文件中,使用JDBC数据源配置参数,如下所示: ``` <Context> <Resource name="jdbc/userDB" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/userdb" username="dbuser" password="dbpass" maxActive="100" maxIdle="30" maxWait="10000" validationQuery="select 1" /> </Context> ``` 上面的配置中,定义了一个名为jdbc/userDB的数据源,使用MySQL数据库驱动,连接到localhost上的userdb数据库。 3. 在应用程序中,使用JNDI API获取数据源,并从数据源中获取数据库连接,进行数据库操作。示例代码如下: ``` InitialContext ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/userDB"); Connection conn = ds.getConnection(); ``` 在分库分表的情况下,需要配置多个数据源,并根据实际情况选择合适的数据源进行操作。一般来说,不同的分片都有独立的数据源,应用程序需要根据数据的划分规则,选择合适的数据源进行操作。 在配置数据源时,需要注意数据库连接的安全性和稳定性。应该使用加密的方式保存数据库的用户名和密码,避免敏感信息泄露。同时,需要设置合适的连接池参数,以避免连接泄露和资源浪费,提高应用程序的性能和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值