流易

日日行,不怕千万里;常常做,不怕千万事。(https://github.com/poazy)

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

1、环境

  • Solr版本:Solr 4.2.1
  • Weblgoic版本:Weblogic 10.3.6

2、问题1

  • 此问题是由于web.xml中的<env-entry>引起的,如下:
    <env-entry>
        <env-entry-name>solr/home</env-entry-name>
        <env-entry-value>/put/your/solr/home/here</env-entry-value>
        <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>
  • 解决方法,将<env-entry-type>移到<env-entry-value>之前,如下:
    <env-entry>
        <env-entry-name>solr/home</env-entry-name>
        <env-entry-type>java.lang.String</env-entry-type>
        <env-entry-value>/put/your/solr/home/here</env-entry-value>
    </env-entry>

3、问题2

  • 报找不到方法:
    java.lang.NoSuchMethodError: org.apache.commons.lang.StringUtils.replaceEach(Ljava.lang.String;[Ljava.lang.String;[Ljava.lang.String;)Ljava.lang.String;
  • 解决方法,修改WEB-INF/weblgoic.xml文件,加入<prefer-web-inf-classes>true</prefer-web-inf-classes>配置,如下:
    <?xml version='1.0' encoding='UTF-8'?>
    <weblogic-web-app
        xmlns="http://www.bea.com/ns/weblogic/90"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-web-app.xsd">
    
        <container-descriptor>
    	<prefer-web-inf-classes>true</prefer-web-inf-classes>
    	<filter-dispatched-requests-enabled>false</filter-dispatched-requests-enabled>
        </container-descriptor>
    
        <context-root>solr</context-root>
    </weblogic-web-app>

4、问题3

  • Solr 应用启用时控制台不停的打印以下信息:
    2013-5-11 15:51:09 org.apache.zookeeper.ClientCnxn$SendThread run
    警告: Session 0x0 for server null, unexpected error, closing socket connection and attempting reconnect
    java.lang.IllegalArgumentException: No Configuration was registered that can handle the configuration named Client
    at com.bea.common.security.jdkutils.JAASConfiguration.getAppConfigurationEntry(JAASConfiguration.java:130)
    at org.apache.zookeeper.client.ZooKeeperSaslClient.<init>(ZooKeeperSaslClient.java:97)
    at org.apache.zookeeper.ClientCnxn$SendThread.startConnect(ClientCnxn.java:943)
    at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:993)
  • 解决方法,是修改源码org.apache.zookeeper.client.ZooKeeperSaslClient.java,并编译成class文件放到zookeeper-3.4.5.jar包相应的目录下覆盖之前的class文件,将修改后的包覆盖掉WEB-INF/lib下的zookeerper-3.4.5.jar包;详见以下修改后的源码(搜索 by duanbo 为修改的地方):
    org.apache.zookeeper.client.ZooKeeperSaslClient
    /**
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package org.apache.zookeeper.client;
    
    import org.apache.zookeeper.AsyncCallback;
    import org.apache.zookeeper.ClientCnxn;
    import org.apache.zookeeper.Login;
    import org.apache.zookeeper.Watcher.Event.KeeperState;
    import org.apache.zookeeper.ZooDefs;
    import org.apache.zookeeper.Environment;
    import org.apache.zookeeper.data.Stat;
    import org.apache.zookeeper.proto.GetSASLRequest;
    import org.apache.zookeeper.proto.SetSASLResponse;
    import org.apache.zookeeper.server.auth.KerberosName;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    import java.security.Principal;
    import java.security.PrivilegedActionException;
    import java.security.PrivilegedExceptionAction;
    import javax.security.auth.Subject;
    import javax.security.auth.callback.Callback;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.callback.NameCallback;
    import javax.security.auth.callback.PasswordCallback;
    import javax.security.auth.callback.UnsupportedCallbackException;
    import javax.security.auth.login.AppConfigurationEntry;
    import javax.security.auth.login.Configuration;
    import javax.security.auth.login.LoginException;
    import javax.security.sasl.AuthorizeCallback;
    import javax.security.sasl.RealmCallback;
    import javax.security.sasl.Sasl;
    import javax.security.sasl.SaslClient;
    import javax.security.sasl.SaslException;
    
    /**
     * This class manages SASL authentication for the client. It
     * allows ClientCnxn to authenticate using SASL with a Zookeeper server.
     */
    public class ZooKeeperSaslClient {
        public static final String LOGIN_CONTEXT_NAME_KEY = "zookeeper.sasl.clientconfig";
        private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperSaslClient.class);
        private static Login login = null;
        private SaslClient saslClient;
    
        private byte[] saslToken = new byte[0];
    
        public enum SaslState {
            INITIAL,INTERMEDIATE,COMPLETE,FAILED
        }
    
        private SaslState saslState = SaslState.INITIAL;
    
        private boolean gotLastPacket = false;
        /** informational message indicating the current configuration status */
        private final String configStatus;
    
        public SaslState getSaslState() {
            return saslState;
        }
    
        public String getLoginContext() {
            if (login != null)
                return login.getLoginContextName();
            return null;
        }
    
        public ZooKeeperSaslClient(final String serverPrincipal)
                throws LoginException {
            /**
             * ZOOKEEPER-1373: allow system property to specify the JAAS
             * configuration section that the zookeeper client should use.
             * Default to "Client".
             */
            String clientSection = System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "Client");
            // Note that 'Configuration' here refers to javax.security.auth.login.Configuration.
            AppConfigurationEntry entries[] = null;
            // SecurityException securityException = null; // delete by duanbo 2013-05-11
            RuntimeException securityException = null; // add by duanbo 2013-05-11
            try {
                entries = Configuration.getConfiguration().getAppConfigurationEntry(clientSection);
            } catch (/*SecurityException*/RuntimeException e) { // modify by duanbo 2013-05-11
                // handle below: might be harmless if the user doesn't intend to use JAAS authentication.
                securityException = e;
            }
            if (entries != null) {
                this.configStatus = "Will attempt to SASL-authenticate using Login Context section '" + clientSection + "'";
                this.saslClient = createSaslClient(serverPrincipal, clientSection);
            } else {
                // Handle situation of clientSection's being null: it might simply because the client does not intend to
                // use SASL, so not necessarily an error.
                saslState = SaslState.FAILED;
                String explicitClientSection = System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY);
                if (explicitClientSection != null) {
                    // If the user explicitly overrides the default Login Context, they probably expected SASL to
                    // succeed. But if we got here, SASL failed.
                    if (securityException != null) {
                        throw new LoginException("Zookeeper client cannot authenticate using the " + explicitClientSection +
                                " section of the supplied JAAS configuration: '" +
                                System.getProperty(Environment.JAAS_CONF_KEY) + "' because of a " +
                                "SecurityException: " + securityException);
                    } else {
                        throw new LoginException("Client cannot SASL-authenticate because the specified JAAS configuration " +
                                "section '" + explicitClientSection + "' could not be found.");
                    }
                } else {
                    // The user did not override the default context. It might be that they just don't intend to use SASL,
                    // so log at INFO, not WARN, since they don't expect any SASL-related information.
                    String msg = "Will not attempt to authenticate using SASL ";
                    if (securityException != null) {
                        msg += "(" + securityException.getLocalizedMessage() + ")";
                    } else {
                        msg += "(unknown error)";
                    }
                    this.configStatus = msg;
                }
                if (System.getProperty(Environment.JAAS_CONF_KEY)  != null) {
                    // Again, the user explicitly set something SASL-related, so they probably expected SASL to succeed.
                    if (securityException != null) {
                        throw new LoginException("Zookeeper client cannot authenticate using the '" +
                                System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "Client") +
                                "' section of the supplied JAAS configuration: '" +
                                System.getProperty(Environment.JAAS_CONF_KEY) + "' because of a " +
                                "SecurityException: " + securityException);
                    } else {
                        throw new LoginException("No JAAS configuration section named '" +
                                System.getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, "Client") +
                                "' was found in specified JAAS configuration file: '" +
                                System.getProperty(Environment.JAAS_CONF_KEY) + "'.");
                    }
                }
            }
        }
    
        /**
         * @return informational message indicating the current configuration status.
         */
        public String getConfigStatus() {
            return configStatus;
        }
    
        public boolean isComplete() {
            return (saslState == SaslState.COMPLETE);
        }
    
        public boolean isFailed() {
            return (saslState == SaslState.FAILED);
        }
    
        public static class ServerSaslResponseCallback implements AsyncCallback.DataCallback {
            public void processResult(int rc, String path, Object ctx, byte data[], Stat stat) {
                // processResult() is used by ClientCnxn's sendThread to respond to
                // data[] contains the Zookeeper Server's SASL token.
                // ctx is the ZooKeeperSaslClient object. We use this object's respondToServer() method
                // to reply to the Zookeeper Server's SASL token
                ZooKeeperSaslClient client = ((ClientCnxn)ctx).zooKeeperSaslClient;
                if (client == null) {
                    LOG.warn("sasl client was unexpectedly null: cannot respond to Zookeeper server.");
                    return;
                }
                byte[] usedata = data;
                if (data != null) {
                    LOG.debug("ServerSaslResponseCallback(): saslToken server response: (length="+usedata.length+")");
                }
                else {
                    usedata = new byte[0];
                    LOG.debug("ServerSaslResponseCallback(): using empty data[] as server response (length="+usedata.length+")");
                }
                client.respondToServer(usedata, (ClientCnxn)ctx);
            }
        }
    
        synchronized private SaslClient createSaslClient(final String servicePrincipal,
                                                         final String loginContext) throws LoginException {
            try {
                if (login == null) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("JAAS loginContext is: " + loginContext);
                    }
                    // note that the login object is static: it's shared amongst all zookeeper-related connections.
                    // createSaslClient() must be declared synchronized so that login is initialized only once.
                    login = new Login(loginContext, new ClientCallbackHandler(null));
                    login.startThreadIfNeeded();
                }
                Subject subject = login.getSubject();
                SaslClient saslClient;
                // Use subject.getPrincipals().isEmpty() as an indication of which SASL mechanism to use:
                // if empty, use DIGEST-MD5; otherwise, use GSSAPI.
                if (subject.getPrincipals().isEmpty()) {
                    // no principals: must not be GSSAPI: use DIGEST-MD5 mechanism instead.
                    LOG.info("Client will use DIGEST-MD5 as SASL mechanism.");
                    String[] mechs = {"DIGEST-MD5"};
                    String username = (String)(subject.getPublicCredentials().toArray()[0]);
                    String password = (String)(subject.getPrivateCredentials().toArray()[0]);
                    // "zk-sasl-md5" is a hard-wired 'domain' parameter shared with zookeeper server code (see ServerCnxnFactory.java)
                    saslClient = Sasl.createSaslClient(mechs, username, "zookeeper", "zk-sasl-md5", null, new ClientCallbackHandler(password));
                    return saslClient;
                }
                else { // GSSAPI.
                    final Object[] principals = subject.getPrincipals().toArray();
                    // determine client principal from subject.
                    final Principal clientPrincipal = (Principal)principals[0];
                    final KerberosName clientKerberosName = new KerberosName(clientPrincipal.getName());
                    // assume that server and client are in the same realm (by default; unless the system property
                    // "zookeeper.server.realm" is set).
                    String serverRealm = System.getProperty("zookeeper.server.realm",clientKerberosName.getRealm());
                    KerberosName serviceKerberosName = new KerberosName(servicePrincipal+"@"+serverRealm);
                    final String serviceName = serviceKerberosName.getServiceName();
                    final String serviceHostname = serviceKerberosName.getHostName();
                    final String clientPrincipalName = clientKerberosName.toString();
                    try {
                        saslClient = Subject.doAs(subject,new PrivilegedExceptionAction<SaslClient>() {
                            public SaslClient run() throws SaslException {
                                LOG.info("Client will use GSSAPI as SASL mechanism.");
                                String[] mechs = {"GSSAPI"};
                                LOG.debug("creating sasl client: client="+clientPrincipalName+";service="+serviceName+";serviceHostname="+serviceHostname);
                                SaslClient saslClient = Sasl.createSaslClient(mechs,clientPrincipalName,serviceName,serviceHostname,null,new ClientCallbackHandler(null));
                                return saslClient;
                            }
                        });
                        return saslClient;
                    }
                    catch (Exception e) {
                        LOG.error("Error creating SASL client:" + e);
                        e.printStackTrace();
                        return null;
                    }
                }
            } catch (LoginException e) {
                // We throw LoginExceptions...
                throw e;
            } catch (Exception e) {
                // ..but consume (with a log message) all other types of exceptions.
                LOG.error("Exception while trying to create SASL client: " + e);
                return null;
            }
        }
    
        public void respondToServer(byte[] serverToken, ClientCnxn cnxn) {
            if (saslClient == null) {
                LOG.error("saslClient is unexpectedly null. Cannot respond to server's SASL message; ignoring.");
                return;
            }
    
            if (!(saslClient.isComplete())) {
                try {
                    saslToken = createSaslToken(serverToken);
                    if (saslToken != null) {
                        sendSaslPacket(saslToken, cnxn);
                    }
                } catch (SaslException e) {
                    LOG.error("SASL authentication failed using login context '" +
                            this.getLoginContext() + "'.");
                    saslState = SaslState.FAILED;
                    gotLastPacket = true;
                }
            }
    
            if (saslClient.isComplete()) {
                // GSSAPI: server sends a final packet after authentication succeeds
                // or fails.
                if ((serverToken == null) && (saslClient.getMechanismName() == "GSSAPI"))
                    gotLastPacket = true;
                // non-GSSAPI: no final packet from server.
                if (saslClient.getMechanismName() != "GSSAPI") {
                    gotLastPacket = true;
                }
                // SASL authentication is completed, successfully or not:
                // enable the socket's writable flag so that any packets waiting for authentication to complete in
                // the outgoing queue will be sent to the Zookeeper server.
                cnxn.enableWrite();
            }
        }
    
        private byte[] createSaslToken() throws SaslException {
            saslState = SaslState.INTERMEDIATE;
            return createSaslToken(saslToken);
        }
    
        private byte[] createSaslToken(final byte[] saslToken) throws SaslException {
            if (saslToken == null) {
                // TODO: introspect about runtime environment (such as jaas.conf)
                saslState = SaslState.FAILED;
                throw new SaslException("Error in authenticating with a Zookeeper Quorum member: the quorum member's saslToken is null.");
            }
    
            Subject subject = login.getSubject();
            if (subject != null) {
                synchronized(login) {
                    try {
                        final byte[] retval =
                            Subject.doAs(subject, new PrivilegedExceptionAction<byte[]>() {
                                    public byte[] run() throws SaslException {
                                        LOG.debug("saslClient.evaluateChallenge(len="+saslToken.length+")");
                                        return saslClient.evaluateChallenge(saslToken);
                                    }
                                });
                        return retval;
                    }
                    catch (PrivilegedActionException e) {
                        String error = "An error: (" + e + ") occurred when evaluating Zookeeper Quorum Member's " +
                          " received SASL token.";
                        // Try to provide hints to use about what went wrong so they can fix their configuration.
                        // TODO: introspect about e: look for GSS information.
                        final String UNKNOWN_SERVER_ERROR_TEXT =
                          "(Mechanism level: Server not found in Kerberos database (7) - UNKNOWN_SERVER)";
                        if (e.toString().indexOf(UNKNOWN_SERVER_ERROR_TEXT) > -1) {
                            error += " This may be caused by Java's being unable to resolve the Zookeeper Quorum Member's" +
                              " hostname correctly. You may want to try to adding" +
                              " '-Dsun.net.spi.nameservice.provider.1=dns,sun' to your client's JVMFLAGS environment.";
                        }
                        error += " Zookeeper Client will go to AUTH_FAILED state.";
                        LOG.error(error);
                        saslState = SaslState.FAILED;
                        throw new SaslException(error);
                    }
                }
            }
            else {
                throw new SaslException("Cannot make SASL token without subject defined. " +
                  "For diagnosis, please look for WARNs and ERRORs in your log related to the Login class.");
            }
        }
    
        private void sendSaslPacket(byte[] saslToken, ClientCnxn cnxn)
          throws SaslException{
            if (LOG.isDebugEnabled()) {
                LOG.debug("ClientCnxn:sendSaslPacket:length="+saslToken.length);
            }
    
            GetSASLRequest request = new GetSASLRequest();
            request.setToken(saslToken);
            SetSASLResponse response = new SetSASLResponse();
            ServerSaslResponseCallback cb = new ServerSaslResponseCallback();
    
            try {
                cnxn.sendPacket(request,response,cb, ZooDefs.OpCode.sasl);
            } catch (IOException e) {
                throw new SaslException("Failed to send SASL packet to server.",
                    e);
            }
        }
    
        private void sendSaslPacket(ClientCnxn cnxn) throws SaslException {
            if (LOG.isDebugEnabled()) {
                LOG.debug("ClientCnxn:sendSaslPacket:length="+saslToken.length);
            }
            GetSASLRequest request = new GetSASLRequest();
            request.setToken(createSaslToken());
            SetSASLResponse response = new SetSASLResponse();
            ServerSaslResponseCallback cb = new ServerSaslResponseCallback();
            try {
                cnxn.sendPacket(request,response,cb, ZooDefs.OpCode.sasl);
            } catch (IOException e) {
                throw new SaslException("Failed to send SASL packet to server due " +
                  "to IOException:", e);
            }
        }
    
        // used by ClientCnxn to know whether to emit a SASL-related event: either AuthFailed or SaslAuthenticated,
        // or none, if not ready yet. Sets saslState to COMPLETE as a side-effect.
        public KeeperState getKeeperState() {
            if (saslClient != null) {
                if (saslState == SaslState.FAILED) {
                  return KeeperState.AuthFailed;
                }
                if (saslClient.isComplete()) {
                    if (saslState == SaslState.INTERMEDIATE) {
                        saslState = SaslState.COMPLETE;
                        return KeeperState.SaslAuthenticated;
                    }
                }
            }
            // No event ready to emit yet.
            return null;
        }
    
        // Initialize the client's communications with the Zookeeper server by sending the server the first
        // authentication packet.
        public void initialize(ClientCnxn cnxn) throws SaslException {
            if (saslClient == null) {
                saslState = SaslState.FAILED;
                throw new SaslException("saslClient failed to initialize properly: it's null.");
            }
            if (saslState == SaslState.INITIAL) {
                if (saslClient.hasInitialResponse()) {
                    sendSaslPacket(cnxn);
                }
                else {
                    byte[] emptyToken = new byte[0];
                    sendSaslPacket(emptyToken, cnxn);
                }
                saslState = SaslState.INTERMEDIATE;
            }
        }
    
        // The CallbackHandler interface here refers to
        // javax.security.auth.callback.CallbackHandler.
        // It should not be confused with Zookeeper packet callbacks like
        //  org.apache.zookeeper.server.auth.SaslServerCallbackHandler.
        public static class ClientCallbackHandler implements CallbackHandler {
            private String password = null;
    
            public ClientCallbackHandler(String password) {
                this.password = password;
            }
    
            public void handle(Callback[] callbacks) throws
              UnsupportedCallbackException {
                for (Callback callback : callbacks) {
                    if (callback instanceof NameCallback) {
                        NameCallback nc = (NameCallback) callback;
                        nc.setName(nc.getDefaultName());
                    }
                    else {
                        if (callback instanceof PasswordCallback) {
                            PasswordCallback pc = (PasswordCallback)callback;
                            if (password != null) {
                                pc.setPassword(this.password.toCharArray());
                            } else {
                                LOG.warn("Could not login: the client is being asked for a password, but the Zookeeper" +
                                  " client code does not currently support obtaining a password from the user." +
                                  " Make sure that the client is configured to use a ticket cache (using" +
                                  " the JAAS configuration setting 'useTicketCache=true)' and restart the client. If" +
                                  " you still get this message after that, the TGT in the ticket cache has expired and must" +
                                  " be manually refreshed. To do so, first determine if you are using a password or a" +
                                  " keytab. If the former, run kinit in a Unix shell in the environment of the user who" +
                                  " is running this Zookeeper client using the command" +
                                  " 'kinit <princ>' (where <princ> is the name of the client's Kerberos principal)." +
                                  " If the latter, do" +
                                  " 'kinit -k -t <keytab> <princ>' (where <princ> is the name of the Kerberos principal, and" +
                                  " <keytab> is the location of the keytab file). After manually refreshing your cache," +
                                  " restart this client. If you continue to see this message after manually refreshing" +
                                  " your cache, ensure that your KDC host's clock is in sync with this host's clock.");
                            }
                        }
                        else {
                            if (callback instanceof RealmCallback) {
                                RealmCallback rc = (RealmCallback) callback;
                                rc.setText(rc.getDefaultText());
                            }
                            else {
                                if (callback instanceof AuthorizeCallback) {
                                    AuthorizeCallback ac = (AuthorizeCallback) callback;
                                    String authid = ac.getAuthenticationID();
                                    String authzid = ac.getAuthorizationID();
                                    if (authid.equals(authzid)) {
                                        ac.setAuthorized(true);
                                    } else {
                                        ac.setAuthorized(false);
                                    }
                                    if (ac.isAuthorized()) {
                                        ac.setAuthorizedID(authzid);
                                    }
                                }
                                else {
                                    throw new UnsupportedCallbackException(callback,"Unrecognized SASL ClientCallback");
                                }
                            }
                        }
                    }
                }
            }
        }
    
        public boolean clientTunneledAuthenticationInProgress() {
            // TODO: Rather than checking a disjunction here, should be a single member
            // variable or method in this class to determine whether the client is
            // configured to use SASL. (see also ZOOKEEPER-1455).
            try {
      	    if ((System.getProperty(Environment.JAAS_CONF_KEY) != null) ||
                  ((javax.security.auth.login.Configuration.getConfiguration() != null) &&
                      (javax.security.auth.login.Configuration.getConfiguration().
                           getAppConfigurationEntry(System.
                           getProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY,"Client"))
                               != null))) {
                    // Client is configured to use a valid login Configuration, so
                    // authentication is either in progress, successful, or failed.
    
                    // 1. Authentication hasn't finished yet: we must wait for it to do so.
                    if ((isComplete() == false) &&
                        (isFailed() == false)) {
                        return true;
                    }
    
                    // 2. SASL authentication has succeeded or failed..
                    if (isComplete() || isFailed()) {
                        if (gotLastPacket == false) {
                            // ..but still in progress, because there is a final SASL
                            // message from server which must be received.
                        return true;
                        }
                    }
                }
                // Either client is not configured to use a tunnelled authentication
                // scheme, or tunnelled authentication has completed (successfully or
                // not), and all server SASL messages have been received.
                return false;
            } catch (/*SecurityException*/RuntimeException e) { // modify by duanbo 2013-05-11
                // Thrown if the caller does not have permission to retrieve the Configuration.
                // In this case, simply returning false is correct.
                if (LOG.isDebugEnabled() == true) {
                    LOG.debug("Could not retrieve login configuration: " + e);
                }
                return false;
            }
        }
    
    
    }

 

posted on 2013-06-14 23:16  流易  阅读(6149)  评论(0编辑  收藏  举报