我的acegi配置


<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">


<!-- 验证处理,使用表单-->
<bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="authenticationFailureUrl">
<value>/login.jsp?login_error=1</value>
</property>
<property name="defaultTargetUrl">
<value>/protected/loginsuccess.jsp</value>
</property>
<property name="filterProcessesUrl">
<value>/j_acegi_security_check</value>
</property>
</bean>

<!--验证管理员,管理验证资讯提供者-->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="rememberMeAuthenticationProvider"/><!--Cookie登入-->
</list>
</property>
</bean>

<!-- 验证提供者,指定使用存储来源中的验证信息-->
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<!--<property name="userDetailsService"><ref local="inMemoryDaoImpl"/></property>配制用户-->
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="userCache" ref="userCache"/> <!--将用用户缓存,在缓存中找不到,再去源中查-->
</bean>
<!--
<bean id="jdbcDaoImpl" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
<property name="usersByUsernameQuery">
<value>SELECT username as USERNAME,password as PASSWORD,state as ENABLED FROM devpro.users WHERE username=?</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>
SELECT a.username as USERNAME,e.auth_type as AUTHORITY FROM devpro.users a,devpro.user_role b,devpro.role c,devpro.role_auth d,devpro.authorities e WHERE a.user_id=b.user_id and b.role_id = c.role_id and c.role_id=d.role_id and d.auth_id=e.auth_id and username=?
</value>
</property>
</bean>
-->
<bean id="jdbcDaoImpl" class="com.cenbow.util.JdbcDaoImpl">
<property name="dataSource" ref="dataSource"/>
<property name="usersByUsernameQuery">
<value>SELECT username as USERNAME,password as PASSWORD,state as ENABLED,user_id as id,name FROM devpro.users WHERE username=?</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>
SELECT a.username as USERNAME,e.auth_type as AUTHORITY,a.user_id as id,name FROM devpro.users a,devpro.user_role b,devpro.role c,devpro.role_auth d,devpro.authorities e WHERE a.user_id=b.user_id and b.role_id = c.role_id and c.role_id=d.role_id and d.auth_id=e.auth_id and username=?
</value>
</property>
</bean>
<!--<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
caterpillar=123456,ROLE_SUPERVISOR
user1=user1pwd,ROLE_USER
user2=user2pwd,disabled,ROLE_USER
</value>
</property>
</bean> -->
<bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
<property name="cache">
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
</property>
<property name="cacheName" value="userCache" />
</bean>
</property>
</bean>

<!-- 发生验证错误或权限错误时的处理 -->
<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
<property name="forceHttps" value="false"/>
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/protected/accessDenied.jsp"/>
</bean>
</property>
</bean>

<!-- FilterSecurityInterceptor 对 URI 进行保护 -->
<bean id="filterSecurityInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<!-- 验证管理员 -->
<property name="authenticationManager" ref="authenticationManager" />
<!-- 授权管理员 -->
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/protected/**=ROLE_USER
</value>
</property>
</bean>

<!-- 授权管理员 -->
<!--<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">-->
<bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
<!-- 是否全部弃权时视为通过() -->
<property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter" />
</list>
</property>
</bean>
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />

<!-- 利用cookie自动登入 -->
<bean id="rememberMeProcessingFilter"
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
<bean id="rememberMeServices"
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<!--<property name="userDetailsService" ref="inMemoryDaoImpl"/> -->
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="key" value="javauser"/>
</bean>
<bean id="rememberMeAuthenticationProvider"
class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key" value="javauser"/>
</bean>

<!--登出后显示-->
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/login.jsp"/> <!-- 登出后显示的页面 -->
<constructor-arg>
<list>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>

<bean id="securityContextHolderAwareRequestFilter" class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>
<!-- Filter Chain -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterSecurityInterceptor,logoutFilter,rememberMeProcessingFilter
</value>
</property>
</bean>
</beans>




/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.cenbow.util;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;

//import org.acegisecurity.userdetails.User;
import com.cenbow.util.User;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;

import org.springframework.context.ApplicationContextException;

import org.springframework.dao.DataAccessException;

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.object.MappingSqlQuery;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import java.util.List;

import javax.sql.DataSource;

/**
* <p>Retrieves user details (username, password, enabled flag, and authorities) from a JDBC location.</p>
* <p>A default database structure is assumed, (see {@link #DEF_USERS_BY_USERNAME_QUERY} and {@link
* #DEF_AUTHORITIES_BY_USERNAME_QUERY}, which most users of this class will need to override, if using an existing
* scheme. This may be done by setting the default query strings used. If this does not provide enough flexibility,
* another strategy would be to subclass this class and override the {@link MappingSqlQuery} instances used, via the
* {@link #initMappingSqlQueries()} extension point.</p>
* <p>In order to minimise backward compatibility issues, this DAO does not recognise the expiration of user
* accounts or the expiration of user credentials. However, it does recognise and honour the user enabled/disabled
* column.</p>
*
* @author Ben Alex
* @author colin sampaleanu
* @version $Id: JdbcDaoImpl.java 1784 2007-02-24 21:00:24Z luke_t $
*/
public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
//~ Static fields/initializers =====================================================================================

public static final String DEF_USERS_BY_USERNAME_QUERY =
"SELECT username,password,enabled FROM users WHERE username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"SELECT username,authority FROM authorities WHERE username = ?";

//~ Instance fields ================================================================================================
protected MappingSqlQuery authoritiesByUsernameMapping;
protected MappingSqlQuery usersByUsernameMapping;
private String authoritiesByUsernameQuery;
private String rolePrefix = "";
private String usersByUsernameQuery;
private boolean usernameBasedPrimaryKey = true;

//~ Constructors ===================================================================================================
public JdbcDaoImpl() {
usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
}

//~ Methods ========================================================================================================
/**
* Allows subclasses to add their own granted authorities to the list to be returned in the
* <code>User</code>.
*
* @param username the username, for use by finder methods
* @param authorities the current granted authorities, as populated from the <code>authoritiesByUsername</code>
* mapping
*/
protected void addCustomAuthorities(String username, List authorities) {
}

public String getAuthoritiesByUsernameQuery() {
return authoritiesByUsernameQuery;
}

public String getRolePrefix() {
return rolePrefix;
}

public String getUsersByUsernameQuery() {
return usersByUsernameQuery;
}

protected void initDao() throws ApplicationContextException {
initMappingSqlQueries();
}

/**
* Extension point to allow other MappingSqlQuery objects to be substituted in a subclass
*/
protected void initMappingSqlQueries() {
this.usersByUsernameMapping = new UsersByUsernameMapping(getDataSource());
this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource());
}

public boolean isUsernameBasedPrimaryKey() {
return usernameBasedPrimaryKey;
}

public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
List users = usersByUsernameMapping.execute(username);

if (users.size() == 0) {
throw new UsernameNotFoundException("User not found");
}

User user = (User) users.get(0); // contains no GrantedAuthority[]

List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername());

addCustomAuthorities(user.getUsername(), dbAuths);

if (dbAuths.size() == 0) {
throw new UsernameNotFoundException("User has no GrantedAuthority");
}

GrantedAuthority[] arrayAuths = (GrantedAuthority[]) dbAuths.toArray(new GrantedAuthority[dbAuths.size()]);

String returnUsername = user.getUsername();

if (!usernameBasedPrimaryKey) {
returnUsername = username;
}

return new User(returnUsername, user.getPassword(), user.isEnabled(), true, true, true, arrayAuths,user.getId(),user.getName());
}

/**
* Allows the default query string used to retrieve authorities based on username to be overriden, if
* default table or column names need to be changed. The default query is {@link
* #DEF_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped
* back to the same column names as in the default query.
*
* @param queryString The query string to set
*/
public void setAuthoritiesByUsernameQuery(String queryString) {
authoritiesByUsernameQuery = queryString;
}

/**
* Allows a default role prefix to be specified. If this is set to a non-empty value, then it is
* automatically prepended to any roles read in from the db. This may for example be used to add the
* <code>ROLE_</code> prefix expected to exist in role names (by default) by some other Acegi Security framework
* classes, in the case that the prefix is not already present in the db.
*
* @param rolePrefix the new prefix
*/
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}

/**
* If <code>true</code> (the default), indicates the {@link #getUsersByUsernameQuery()} returns a username
* in response to a query. If <code>false</code>, indicates that a primary key is used instead. If set to
* <code>true</code>, the class will use the database-derived username in the returned <code>UserDetails</code>.
* If <code>false</code>, the class will use the {@link #loadUserByUsername(String)} derived username in the
* returned <code>UserDetails</code>.
*
* @param usernameBasedPrimaryKey <code>true</code> if the mapping queries return the username <code>String</code>,
* or <code>false</code> if the mapping returns a database primary key.
*/
public void setUsernameBasedPrimaryKey(boolean usernameBasedPrimaryKey) {
this.usernameBasedPrimaryKey = usernameBasedPrimaryKey;
}

/**
* Allows the default query string used to retrieve users based on username to be overriden, if default
* table or column names need to be changed. The default query is {@link #DEF_USERS_BY_USERNAME_QUERY}; when
* modifying this query, ensure that all returned columns are mapped back to the same column names as in the
* default query. If the 'enabled' column does not exist in the source db, a permanent true value for this column
* may be returned by using a query similar to <br><pre>
* "SELECT username,password,'true' as enabled FROM users WHERE username = ?"</pre>
*
* @param usersByUsernameQueryString The query string to set
*/
public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
this.usersByUsernameQuery = usersByUsernameQueryString;
}

//~ Inner Classes ==================================================================================================
/**
* Query object to look up a user's authorities.
*/
protected class AuthoritiesByUsernameMapping extends MappingSqlQuery {

protected AuthoritiesByUsernameMapping(DataSource ds) {
super(ds, authoritiesByUsernameQuery);
declareParameter(new SqlParameter(Types.VARCHAR));
compile();
}

protected Object mapRow(ResultSet rs, int rownum)
throws SQLException {
String roleName = rolePrefix + rs.getString(2);
GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);

return authority;
}
}

/**
* Query object to look up a user.
*/
protected class UsersByUsernameMapping extends MappingSqlQuery {

protected UsersByUsernameMapping(DataSource ds) {
super(ds, usersByUsernameQuery);
declareParameter(new SqlParameter(Types.VARCHAR));
compile();
}

protected Object mapRow(ResultSet rs, int rownum)
throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
boolean enabled = rs.getBoolean(3);
Integer id = new Integer(rs.getInt(4));
String name = rs.getString(5);
UserDetails user = new User(username, password, enabled, true, true, true,
new GrantedAuthority[]{new GrantedAuthorityImpl("HOLDER")},id,name);

return user;
}
}
}



/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.cenbow.util;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.*;
import org.springframework.util.Assert;

/**
* Models core user information retieved by an {@link UserDetailsService}.<p>Implemented with value object
* semantics (immutable after construction, like a <code>String</code>). Developers may use this class directly,
* subclass it, or write their own {@link UserDetails} implementation from scratch.</p>
*
* @author Ben Alex
* @version $Id: User.java 1784 2007-02-24 21:00:24Z luke_t $
* @author xw
* @deprecated acegi的原User类,没有id,和name(用户始名)
*/
public class User implements UserDetails {
//~ Instance fields ================================================================================================

private static final long serialVersionUID = 1L;
private String password;
private String username;
private GrantedAuthority[] authorities;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
private Integer id;
/**
* 姓名
*/
private String name;

//~ Constructors ===================================================================================================
/**
* Construct the <code>User</code> with the details required by
* {@link org.acegisecurity.providers.dao.DaoAuthenticationProvider}.
*
* @param username the username presented to the
* <code>DaoAuthenticationProvider</code>
* @param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
* @param enabled set to <code>true</code> if the user is enabled
* @param authorities the authorities that should be granted to the caller
* if they presented the correct username and password and the user
* is enabled
*
* @throws IllegalArgumentException if a <code>null</code> value was passed
* either as a parameter or as an element in the
* <code>GrantedAuthority[]</code> array
*
* @deprecated use new constructor with extended properties (this
* constructor will be removed from release 1.0.0)
*/
public User(String username, String password, boolean enabled, GrantedAuthority[] authorities)
throws IllegalArgumentException {
this(username, password, enabled, true, true, authorities);
}

/**
* Construct the <code>User</code> with the details required by
* {@link org.acegisecurity.providers.dao.DaoAuthenticationProvider}.
*
* @param username the username presented to the
* <code>DaoAuthenticationProvider</code>
* @param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
* @param enabled set to <code>true</code> if the user is enabled
* @param accountNonExpired set to <code>true</code> if the account has not
* expired
* @param credentialsNonExpired set to <code>true</code> if the credentials
* have not expired
* @param authorities the authorities that should be granted to the caller
* if they presented the correct username and password and the user
* is enabled
*
* @throws IllegalArgumentException if a <code>null</code> value was passed
* either as a parameter or as an element in the
* <code>GrantedAuthority[]</code> array
*
* @deprecated use new constructor with extended properties (this
* constructor will be removed from release 1.0.0)
*/
public User(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, GrantedAuthority[] authorities)
throws IllegalArgumentException {
this(username, password, enabled, accountNonExpired, credentialsNonExpired, true, authorities);
}

/**
* Construct the <code>User</code> with the details required by
* {@link org.acegisecurity.providers.dao.DaoAuthenticationProvider}.
*
* @param username the username presented to the
* <code>DaoAuthenticationProvider</code>
* @param password the password that should be presented to the
* <code>DaoAuthenticationProvider</code>
* @param enabled set to <code>true</code> if the user is enabled
* @param accountNonExpired set to <code>true</code> if the account has not
* expired
* @param credentialsNonExpired set to <code>true</code> if the credentials
* have not expired
* @param accountNonLocked set to <code>true</code> if the account is not
* locked
* @param authorities the authorities that should be granted to the caller
* if they presented the correct username and password and the user
* is enabled
*
* @throws IllegalArgumentException if a <code>null</code> value was passed
* either as a parameter or as an element in the
* <code>GrantedAuthority[]</code> array
*/
public User(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked, GrantedAuthority[] authorities)
throws IllegalArgumentException {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}

this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
setAuthorities(authorities);
}

public User(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked, GrantedAuthority[] authorities,Integer id,String name)
throws IllegalArgumentException {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}

this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
setAuthorities(authorities);
this.id = id;
this.name = name;
}
//~ Methods ========================================================================================================
public boolean equals(Object rhs) {
if (!(rhs instanceof User) || (rhs == null)) {
return false;
}

User user = (User) rhs;

// We rely on constructor to guarantee any User has non-null and >0
// authorities
if (user.getAuthorities().length != this.getAuthorities().length) {
return false;
}

for (int i = 0; i < this.getAuthorities().length; i++) {
if (!this.getAuthorities()[i].equals(user.getAuthorities()[i])) {
return false;
}
}

// We rely on constructor to guarantee non-null username and password
return (this.getPassword().equals(user.getPassword()) && this.getUsername().equals(user.getUsername()) && (this.isAccountNonExpired() == user.isAccountNonExpired()) && (this.isAccountNonLocked() == user.isAccountNonLocked()) && (this.isCredentialsNonExpired() == user.isCredentialsNonExpired()) && (this.isEnabled() == user.isEnabled()));
}

public GrantedAuthority[] getAuthorities() {
return authorities;
}

public String getPassword() {
return password;
}

public String getUsername() {
return username;
}

public int hashCode() {
int code = 9792;

if (this.getAuthorities() != null) {
for (int i = 0; i < this.getAuthorities().length; i++) {
code = code * (this.getAuthorities()[i].hashCode() % 7);
}
}

if (this.getPassword() != null) {
code = code * (this.getPassword().hashCode() % 7);
}

if (this.getUsername() != null) {
code = code * (this.getUsername().hashCode() % 7);
}

if (this.isAccountNonExpired()) {
code = code * -2;
}

if (this.isAccountNonLocked()) {
code = code * -3;
}

if (this.isCredentialsNonExpired()) {
code = code * -5;
}

if (this.isEnabled()) {
code = code * -7;
}

return code;
}

public boolean isAccountNonExpired() {
return accountNonExpired;
}

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

public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}

public boolean isEnabled() {
return enabled;
}

protected void setAuthorities(GrantedAuthority[] authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority array");

for (int i = 0; i < authorities.length; i++) {
Assert.notNull(authorities[i],
"Granted authority element " + i + " is null - GrantedAuthority[] cannot contain any null elements");
}

this.authorities = authorities;
}

public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(super.toString()).append(": ");
sb.append("Username: ").append(this.username).append("; ");
sb.append("Password: [PROTECTED]; ");
sb.append("Enabled: ").append(this.enabled).append("; ");
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");

if (this.getAuthorities() != null) {
sb.append("Granted Authorities: ");

for (int i = 0; i < this.getAuthorities().length; i++) {
if (i > 0) {
sb.append(", ");
}

sb.append(this.getAuthorities()[i].toString());
}
} else {
sb.append("Not granted any authorities");
}

return sb.toString();
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值