基于spring security3的权限 应用

1.创建mavn项目,导入一些必须的包:
pom.xml
<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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ymq</groupId>
<artifactId>map</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>map Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hibernate-annotations.version>3.4.0.GA</hibernate-annotations.version>
<hibernate-c3p0.version>3.3.2.GA</hibernate-c3p0.version>
<hibernate-validator.version>4.0.2.GA</hibernate-validator.version>
<hibernate.show_sql>true</hibernate.show_sql>
<m.jdbc.url>jdbc:mysql://172.19.4.157/wxps</m.jdbc.url>
<m.jdbc.username>root</m.jdbc.username>
<m.jdbc.password>admin</m.jdbc.password>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>opensymphony</groupId>
<artifactId>sitemesh</artifactId>
<version>2.4.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.0.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>${hibernate-annotations.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate-c3p0.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.14.0-GA</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-proxool</artifactId>
<version>3.3.2.GA</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<finalName>map</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.1</version>
<configuration>
<path>/</path>
<port>8088</port>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
<tomcatWebXml>webapp/WEB-INF/web.xml</tomcatWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>

2.编写web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-dispatcher.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.sitemesh.webapp.SiteMeshFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>


3.编写spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">


<http auto-config="true" use-expressions="true">
<custom-filter ref="myFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
<intercept-url pattern="/login" filters="none"/>
<form-login login-page="/login"
authentication-failure-url="/login?auth-failure=true"
default-target-url="/index"/>
<logout logout-success-url="/login?logout-success=true"/>
<access-denied-handler error-page="/WEB-INF/jsp/error/403"/>

</http>

<beans:bean id="myFilter" class="security.MyFilterSecurityInterceptor">
<!-- 用户拥有的权限 -->
<beans:property name="authenticationManager" ref="myAuthenticationManager"/>
<!-- 用户是否拥有所请求资源的权限 -->
<beans:property name="accessDecisionManager" ref="myAccessDecisionManager"/>
<!-- 资源与权限对应关系 -->
<beans:property name="securityMetadataSource" ref="mySecurityMetadataSource"></beans:property>
</beans:bean>
<!-- 配置userDetailsService 来指定用户和权限: -->
<authentication-manager alias="myAuthenticationManager">
<authentication-provider user-service-ref="userDetailsService"/>
</authentication-manager>
<beans:bean id="userDetailsService" class="security.MyUserDetailsService"/>
<beans:bean id="myAccessDecisionManager" class="security.MyAccessDecisionManager"/>
<beans:bean id="mySecurityMetadataSource" class="security.MySecurityMetadataSource" init-method="initResourceMap"/>
</beans:beans>

4.编写spring-despatcher
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<context:component-scan base-package="dispatcher"/>
<!-- 显示层采用JSTL -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="30000000" />
</bean>

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
<property name="defaultEncoding" value="utf-8"/>
</bean>

</beans>


5.编写datasource配置文件

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang" xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">


<context:property-placeholder location="classpath:/resource.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="idleConnectionTestPeriod" value="120"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="schemaUpdate" value="${hibernate.schemaUpdate}"/>
<property name="packagesToScan" value="${hibernate.packageScan}"/>
<property name="hibernateProperties">
<value>
hibernate.dialect ${hibernate.dialect}
hibernate.show_sql ${hibernate.show_sql}
</value>
</property>
</bean>
</beans>


5.编写resource.properties
#datasource
jdbc.driverClassName org.gjt.mm.mysql.Driver
jdbc.url jdbc:mysql://172.19.4.157/ss
jdbc.username root
jdbc.password admin

#hibernate
hibernate.dialect org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql true
hibernate.schemaUpdate true
hibernate.packageScan entity

6.编写spring applicationContext.xml
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<import resource="spring-datasource.xml"/>

<context:component-scan base-package="*" />

<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"
/>
</beans>


7.实现spring security3 AccessDecisionManager,AbstractSecurityInterceptor,FilterInvocationSecurityMetadataSource,UserDetailsService和UserDetails类


/**
*
*/
package security;

import java.util.Collection;
import java.util.Iterator;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

/**
* @author administrator
*
*/
public class MyAccessDecisionManager implements AccessDecisionManager{


public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if(configAttributes == null){
return ;
}
System.out.println(object.toString()); //objectis a URL.
Iterator<ConfigAttribute>ite=configAttributes.iterator();
while(ite.hasNext()){
ConfigAttribute ca=ite.next();
String needRole=((SecurityConfig)ca).getAttribute();
for(GrantedAuthority ga:authentication.getAuthorities()){
if(needRole.equals(ga.getAuthority())){ //ga isuser's role.
return;
}
}
}
throw new AccessDeniedException("");
}


public boolean supports(ConfigAttribute attribute) {
return true;
}


public boolean supports(Class<?> clazz) {
return true;
}

}
/**
*
*/
package security;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

/**
* @author administrator
*
*/
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter{


private FilterInvocationSecurityMetadataSource securityMetadataSource;

public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource;
}

public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}


public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
System.out.println(filterConfig);
}


public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request,response,chain);
InterceptorStatusToken token = super.beforeInvocation(fi);
try{
fi.getChain().doFilter(request, response);
}finally{
super.afterInvocation(token, null);
}

}


public void destroy() {
// TODO Auto-generated method stub

}


@Override
public Class<? extends Object> getSecureObjectClass() {
// TODO Auto-generated method stub
return FilterInvocation.class;
}


@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}


}
/**
*
*/
package security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.AntUrlPathMatcher;
import org.springframework.security.web.util.UrlMatcher;
import org.springframework.stereotype.Service;

import service.SecurityService;
import entity.Role;
import entity.Url;

/**
* @author administrator
*
*/
@Service
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource{

@Autowired
private SecurityService securityService;

public static Map<String, Collection<ConfigAttribute>> resourceMap = null;
private UrlMatcher urlMatcher = new AntUrlPathMatcher();;

public void initResourceMap(){
resourceMap = new HashMap<String,Collection<ConfigAttribute>>();
List<Url> urls = securityService.listUrls();
for(Url url:urls){
Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
for(Role r:url.getRoles()){
configAttributes.add(new SecurityConfig(r.getName()));
}
resourceMap.put(url.getUrl(), configAttributes);
}

}

public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String url = ((FilterInvocation)object).getRequestUrl();
Iterator<String> ite =resourceMap.keySet().iterator();
while (ite.hasNext()) {
String resURL = ite.next();
if (urlMatcher.pathMatchesUrl(url, resURL)) {
return resourceMap.get(resURL);
}
}
return null;
}


public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}


public boolean supports(Class<?> clazz) {
return true;
}

}
package security;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import dao.UserDao;
import entity.Role;
import entity.User;

@Service
public class MyUserDetailsService implements UserDetailsService {

@Autowired
private UserDao userDao;

public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {

User u = userDao.listByName(username);
UserDetailsImpl userDetails = null;
if(u!=null){
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for(Role r:u.getRoles()){
authorities.add(new GrantedAuthorityImpl(r.getName()));
}
userDetails = new UserDetailsImpl(u);
userDetails.setAuthorities(authorities);
}
return userDetails;
}

}
package security;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import entity.User;

public class UserDetailsImpl extends User implements UserDetails{

private Collection<GrantedAuthority> authorities;
private static final long serialVersionUID = 1L;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public UserDetailsImpl(User u){
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.setEnabled(true);
this.setPassword(u.getPassword());
this.setUsername(u.getUsername());
//this.setAuthorities(u.getAuthorities());
this.setRemark(u.getRemark());
}

public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}

public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}

public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}

public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
}

public void setAuthorities(Collection<GrantedAuthority> authorities) {
this.authorities = authorities;
}


public boolean isAccountNonExpired() {
return this.accountNonExpired;
}

public boolean isAccountNonLocked() {
return this.accountNonLocked;
}

public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}


public boolean isEnabled() {
return this.enabled;
}


}



不想详解!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值