封装过后的jsch

这里写自定义目录标题

maven依赖

		<dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.55</version>
        </dependency>

封装工具类

package com.wgcloud.util.common;

import org.apache.commons.lang3.StringUtils;

public class SSHDetails {
    private String pwd;
    private String host;
    private String user_name;
    private int port;
    private String source_path;
    private String agent_gz;
    private String HADOOP_CONF;
    private String target_dir;
    private String old_deploy_dir;
    private String redis_info;
    private String db_info;
    private String tmp_conf_path;

    public String getOld_deploy_dir() {
        return this.old_deploy_dir;
    }

    public void setOld_deploy_dir(String old_deploy_dir) {
        this.old_deploy_dir = old_deploy_dir;
    }

    public SSHDetails() {
    }

    public SSHDetails(String host, String user_name, String pwd, int port) {
        this.host = host;
        this.user_name = user_name;
        this.pwd = pwd;
        if (port == 0) {
            this.port = 22;
        } else {
            this.port = port;
        }

    }

    public SSHDetails(String host, String user_name, String pwd, String port) {
        this.host = host;
        this.user_name = user_name;
        this.pwd = pwd;
        if (StringUtils.isEmpty(port)) {
            this.port = 22;
        } else {
            this.port = Integer.parseInt(port);
        }

    }

    public String getTmp_conf_path() {
        return this.tmp_conf_path;
    }

    public void setTmp_conf_path(String tmp_conf_path) {
        this.tmp_conf_path = tmp_conf_path;
    }

    public String getUser_name() {
        return this.user_name;
    }

    public void setUser_name(String user_name) {
        this.user_name = user_name;
    }

    public String getPwd() {
        return this.pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getHost() {
        return this.host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return this.port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getAgent_gz() {
        return this.agent_gz;
    }

    public String getHADOOP_CONF() {
        return this.HADOOP_CONF;
    }

    public String getRedis_info() {
        return this.redis_info;
    }

    public String getSource_path() {
        return this.source_path;
    }

    public String getTarget_dir() {
        return this.target_dir;
    }

    public void setTarget_dir(String target_dir) {
        this.target_dir = target_dir;
    }

    public void setAgent_gz(String agent_gz) {
        this.agent_gz = agent_gz;
    }

    public void setHADOOP_CONF(String HADOOP_CONF) {
        this.HADOOP_CONF = HADOOP_CONF;
    }

    public void setRedis_info(String redis_info) {
        this.redis_info = redis_info;
    }

    public void setSource_path(String source_path) {
        this.source_path = source_path;
    }

    public String getDb_info() {
        return this.db_info;
    }

    public void setDb_info(String db_info) {
        this.db_info = db_info;
    }
}

package com.wgcloud.util.common;

import com.jcraft.jsch.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.Vector;

public class SSHOperate  implements Closeable {
    private static final Logger logger = LogManager.getLogger();
    private static final int SSH_TIME_OUT = 6000;
    public Session session;
    public ChannelSftp channelSftp;

    public SSHOperate(SSHDetails sshDetails) {
        this(sshDetails, 6000);
    }

    public SSHOperate(SSHDetails sshDetails, int time_out) {
        JSch jsch = new JSch();

        try {
            this.session = jsch.getSession(sshDetails.getUser_name(), sshDetails.getHost(), sshDetails.getPort());
            logger.debug("Session created.");
            String ftpPassword = sshDetails.getPwd();
            Properties config = new Properties();
            if (ftpPassword != null) {
                this.session.setPassword(ftpPassword);
            }

            config.put("StrictHostKeyChecking", "no");
            this.session.setConfig("PreferredAuthentications", "publickey,keyboard-interactive,password");
            this.session.setConfig(config);
            this.session.setTimeout(time_out);
            this.session.connect();
            logger.debug("Session connected.");
            this.channelSftp = (ChannelSftp)this.session.openChannel("sftp");
            this.channelSftp.connect();
        } catch (JSchException var6) {
            logger.error(var6);

        }
    }


    public Vector<ChannelSftp.LsEntry> listDir(String srcDir) throws Exception {
        return this.listDir(srcDir, "*");
    }


    public Vector<ChannelSftp.LsEntry> listDir(String srcDir, String regex) throws Exception {
        try {
            return srcDir.endsWith("/") ? this.channelSftp.ls(srcDir + regex) : this.channelSftp.ls(srcDir + "/" + regex);
        } catch (SftpException var4) {
            var4.printStackTrace();
            logger.error("按照正则获取远程目录下的文件对象集合!" + var4);
            throw new Exception();
        }
    }

    public void transferFile(String srcFile, String destFile) {
        try {
            this.channelSftp.get(srcFile, destFile);
        } catch (SftpException var4) {
            var4.printStackTrace();
            logger.error("使用sftp拉取远程服务器上的文件到本地! " + var4);

        }
    }

    public void transferPutFile(String srcFile, String destFile) {
        try {
            this.channelSftp.put(srcFile, destFile);
        } catch (SftpException var4) {
            var4.printStackTrace();
            logger.error("使用sftp推送本地文件到远程服务器失败! " + var4);

        }
    }

    public void scpMkdir(String currentLoadDir) {
        String mkdir = "mkdir -p " + currentLoadDir;

        try {
            this.execCommandBySSHNoRs(mkdir);
        } catch (IOException | InterruptedException | JSchException var4) {
            var4.printStackTrace();

        }
    }

    public String execCommandBySSH(String command) throws IOException, JSchException {
        command = FileNameUtils.normalize(command, true);
        logger.info("执行命令为: " + command);
        ChannelExec channelExec = (ChannelExec)this.session.openChannel("exec");
        InputStream in = channelExec.getInputStream();
        channelExec.setCommand(command);
        channelExec.setErrStream(System.err);
        channelExec.connect();
        String result = IOUtils.toString(in, StandardCharsets.UTF_8);
        channelExec.disconnect();
        return result;
    }

    public void execCommandBySSHNoRs(String command) throws JSchException, IOException, InterruptedException {
        logger.info("执行命令为: " + command);
        ChannelExec channelExec = (ChannelExec)this.session.openChannel("exec");
        channelExec.getInputStream();
        channelExec.setCommand(command);
        channelExec.setErrStream(System.err);
        channelExec.connect();
        Thread.sleep(1000L);
        channelExec.disconnect();
    }

    public void executeLocalShell(String executeShell) throws IOException, InterruptedException {
        logger.info("执行命令为 :" + executeShell);
        Process ps = Runtime.getRuntime().exec(new String[]{"sh", "-l", "-c", executeShell});
        ps.waitFor();
        BufferedReader br = new BufferedReader(new InputStreamReader(ps.getErrorStream()));
        StringBuilder sb = new StringBuilder();

        String line;
        while((line = br.readLine()) != null) {
            sb.append(line).append(System.lineSeparator());
        }

        if (!StringUtils.isEmpty(sb.toString())) {

        }
    }

    public String execCommandBySSHToReadLine(String command) throws JSchException, IOException, InterruptedException {
        logger.info("执行命令为 : " + command);
        StringBuilder result = new StringBuilder();
        ChannelExec channelExec = (ChannelExec)this.session.openChannel("exec");
        InputStream inputStream = channelExec.getInputStream();
        OutputStream outputStream = channelExec.getOutputStream();
        PrintWriter printWriter = new PrintWriter(outputStream);
        printWriter.println(command);
        Thread.sleep(3000L);
        printWriter.println("exit");
        printWriter.flush();
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));

        String msg;
        while((msg = in.readLine()) != null) {
            result.append(msg);
        }

        in.close();
        channelExec.disconnect();
        return result.toString();
    }

    public static SSHDetails getSSHDetails(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort) {
        return new SSHDetails(ftpHost, ftpUserName, ftpPassword, ftpPort);
    }

    public void close() {
        if (this.channelSftp != null) {
            this.channelSftp.disconnect();
        }

        if (this.session != null) {
            this.session.disconnect();
        }

    }
}

package com.wgcloud.util.common;

import java.io.File;
import java.util.ArrayList;

public class FileNameUtils {
    private static final int NOT_FOUND = -1;
    public static final char EXTENSION_SEPARATOR = '.';
    public static final String EXTENSION_SEPARATOR_STR = Character.toString('.');
    private static final char UNIX_SEPARATOR = '/';
    private static final char WINDOWS_SEPARATOR = '\\';
    private static final char SYSTEM_SEPARATOR;
    private static final char OTHER_SEPARATOR;

    public FileNameUtils() {
    }

    static boolean isSystemWindows() {
        return SYSTEM_SEPARATOR == '\\';
    }

    private static boolean isSeparator(final char ch) {
        return ch == '/' || ch == '\\';
    }

    public static String normalize(final String filename) {
        return doNormalize(filename, SYSTEM_SEPARATOR, true);
    }

    public static String normalize(final String filename, final boolean unixSeparator) {
        char separator = (char) (unixSeparator ? 47 : 92);
        return doNormalize(filename, (char)separator, true);
    }

    private static String doNormalize(final String filename, final char separator, final boolean keepSeparator) {
        if (filename == null) {
            return null;
        } else {
            failIfNullBytePresent(filename);
            int size = filename.length();
            if (size == 0) {
                return filename;
            } else {
                int prefix = getPrefixLength(filename);
                if (prefix < 0) {
                    return null;
                } else {
                    char[] array = new char[size + 2];
                    filename.getChars(0, filename.length(), array, 0);
                    char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR;

                    for(int i = 0; i < array.length; ++i) {
                        if (array[i] == otherSeparator) {
                            array[i] = separator;
                        }
                    }

                    boolean lastIsDirectory = true;
                    if (array[size - 1] != separator) {
                        array[size++] = separator;
                        lastIsDirectory = false;
                    }

                    int i;
                    for(i = prefix + 1; i < size; ++i) {
                        if (array[i] == separator && array[i - 1] == separator) {
                            System.arraycopy(array, i, array, i - 1, size - i);
                            --size;
                            --i;
                        }
                    }

                    for(i = prefix + 1; i < size; ++i) {
                        if (array[i] == separator && array[i - 1] == '.' && (i == prefix + 1 || array[i - 2] == separator)) {
                            if (i == size - 1) {
                                lastIsDirectory = true;
                            }

                            System.arraycopy(array, i + 1, array, i - 1, size - i);
                            size -= 2;
                            --i;
                        }
                    }

                    label109:
                    for(i = prefix + 2; i < size; ++i) {
                        if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && (i == prefix + 2 || array[i - 3] == separator)) {
                            if (i == prefix + 2) {
                                return null;
                            }

                            if (i == size - 1) {
                                lastIsDirectory = true;
                            }

                            for(int j = i - 4; j >= prefix; --j) {
                                if (array[j] == separator) {
                                    System.arraycopy(array, i + 1, array, j + 1, size - i);
                                    size -= i - j;
                                    i = j + 1;
                                    continue label109;
                                }
                            }

                            System.arraycopy(array, i + 1, array, prefix, size - i);
                            size -= i + 1 - prefix;
                            i = prefix + 1;
                        }
                    }

                    if (size <= 0) {
                        return "";
                    } else if (size <= prefix) {
                        return new String(array, 0, size);
                    } else if (lastIsDirectory && keepSeparator) {
                        return new String(array, 0, size);
                    } else {
                        return new String(array, 0, size - 1);
                    }
                }
            }
        }
    }

    private static void failIfNullBytePresent(final String path) {
        int len = path.length();

        for(int i = 0; i < len; ++i) {
            if (path.charAt(i) == 0) {
                throw new IllegalArgumentException("Null byte present in file/path name. There are no known legitimate use cases for such data, but several injection attacks may use it");
            }
        }

    }

    public static int getPrefixLength(final String filename) {
        if (filename == null) {
            return -1;
        } else {
            int len = filename.length();
            if (len == 0) {
                return 0;
            } else {
                char ch0 = filename.charAt(0);
                if (ch0 == ':') {
                    return -1;
                } else if (len == 1) {
                    if (ch0 == '~') {
                        return 2;
                    } else {
                        return isSeparator(ch0) ? 1 : 0;
                    }
                } else {
                    int posUnix;
                    if (ch0 == '~') {
                        int posUnix2 = filename.indexOf(47, 1);
                        posUnix = filename.indexOf(92, 1);
                        if (posUnix == -1 && posUnix2 == -1) {
                            return len + 1;
                        } else {
                            posUnix2 = posUnix2 == -1 ? posUnix : posUnix2;
                            posUnix = posUnix == -1 ? posUnix2 : posUnix;
                            return Math.min(posUnix2, posUnix) + 1;
                        }
                    } else {
                        char ch1 = filename.charAt(1);
                        if (ch1 == ':') {
                            ch0 = Character.toUpperCase(ch0);
                            if (ch0 >= 'A' && ch0 <= 'Z') {
                                return len != 2 && isSeparator(filename.charAt(2)) ? 3 : 2;
                            } else {
                                return ch0 == '/' ? 1 : -1;
                            }
                        } else if (isSeparator(ch0) && isSeparator(ch1)) {
                            posUnix = filename.indexOf(47, 2);
                            int posWin = filename.indexOf(92, 2);
                            if ((posUnix != -1 || posWin != -1) && posUnix != 2 && posWin != 2) {
                                posUnix = posUnix == -1 ? posWin : posUnix;
                                posWin = posWin == -1 ? posUnix : posWin;
                                return Math.min(posUnix, posWin) + 1;
                            } else {
                                return -1;
                            }
                        } else {
                            return isSeparator(ch0) ? 1 : 0;
                        }
                    }
                }
            }
        }
    }

    public static String normalizeNoEndSeparator(final String filename, final boolean unixSeparator) {
        char separator = (char) (unixSeparator ? 47 : 92);
        return doNormalize(filename, (char)separator, false);
    }

    public static String concat(final String basePath, final String fullFilenameToAdd) {
        int prefix = getPrefixLength(fullFilenameToAdd);
        if (prefix < 0) {
            return null;
        } else if (prefix > 0) {
            return normalize(fullFilenameToAdd);
        } else if (basePath == null) {
            return null;
        } else {
            int len = basePath.length();
            if (len == 0) {
                return normalize(fullFilenameToAdd);
            } else {
                char ch = basePath.charAt(len - 1);
                return isSeparator(ch) ? normalize(basePath + fullFilenameToAdd) : normalize(basePath + '/' + fullFilenameToAdd);
            }
        }
    }

    public static String getFullPath(final String filename) {
        return doGetFullPath(filename, true);
    }

    public static String getFullPathNoEndSeparator(final String filename) {
        return doGetFullPath(filename, false);
    }

    private static String doGetFullPath(final String filename, final boolean includeSeparator) {
        if (filename == null) {
            return null;
        } else {
            int prefix = getPrefixLength(filename);
            if (prefix < 0) {
                return null;
            } else if (prefix >= filename.length()) {
                return includeSeparator ? getPrefix(filename) : filename;
            } else {
                int index = indexOfLastSeparator(filename);
                if (index < 0) {
                    return filename.substring(0, prefix);
                } else {
                    int end = index + (includeSeparator ? 1 : 0);
                    if (end == 0) {
                        ++end;
                    }

                    return filename.substring(0, end);
                }
            }
        }
    }

    public static String getName(final String filename) {
        if (filename == null) {
            return null;
        } else {
            failIfNullBytePresent(filename);
            int index = indexOfLastSeparator(filename);
            return filename.substring(index + 1);
        }
    }

    public static int indexOfLastSeparator(final String filename) {
        if (filename == null) {
            return -1;
        } else {
            int lastUnixPos = filename.lastIndexOf(47);
            int lastWindowsPos = filename.lastIndexOf(92);
            return Math.max(lastUnixPos, lastWindowsPos);
        }
    }

    public static String getPrefix(final String filename) {
        if (filename == null) {
            return null;
        } else {
            int len = getPrefixLength(filename);
            if (len < 0) {
                return null;
            } else if (len > filename.length()) {
                failIfNullBytePresent(filename + '/');
                return filename + '/';
            } else {
                String path = filename.substring(0, len);
                failIfNullBytePresent(path);
                return path;
            }
        }
    }

    public static String getPath(final String filename) {
        return doGetPath(filename, 1);
    }

    private static String doGetPath(final String filename, final int separatorAdd) {
        if (filename == null) {
            return null;
        } else {
            int prefix = getPrefixLength(filename);
            if (prefix < 0) {
                return null;
            } else {
                int index = indexOfLastSeparator(filename);
                int endIndex = index + separatorAdd;
                if (prefix < filename.length() && index >= 0 && prefix < endIndex) {
                    String path = filename.substring(prefix, endIndex);
                    failIfNullBytePresent(path);
                    return path;
                } else {
                    return "";
                }
            }
        }
    }

    public static String getPathNoEndSeparator(final String filename) {
        return doGetPath(filename, 0);
    }

    public static String getBaseName(final String filename) {
        return removeExtension(getName(filename));
    }

    public static String removeExtension(final String filename) {
        if (filename == null) {
            return null;
        } else {
            failIfNullBytePresent(filename);
            int index = indexOfExtension(filename);
            return index == -1 ? filename : filename.substring(0, index);
        }
    }

    public static int indexOfExtension(final String filename) {
        if (filename == null) {
            return -1;
        } else {
            int extensionPos = filename.lastIndexOf(46);
            int lastSeparator = indexOfLastSeparator(filename);
            return lastSeparator > extensionPos ? -1 : extensionPos;
        }
    }

    public static String getExtension(final String filename) {
        if (filename == null) {
            return null;
        } else {
            int index = indexOfExtension(filename);
            return index == -1 ? "" : filename.substring(index + 1);
        }
    }

    static String[] splitOnTokens(final String text) {
        if (text.indexOf(63) == -1 && text.indexOf(42) == -1) {
            return new String[]{text};
        } else {
            char[] array = text.toCharArray();
            ArrayList<String> list = new ArrayList();
            StringBuilder buffer = new StringBuilder();
            char prevChar = 0;
            char[] var5 = array;
            int var6 = array.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                char ch = var5[var7];
                if (ch != '?' && ch != '*') {
                    buffer.append(ch);
                } else {
                    if (buffer.length() != 0) {
                        list.add(buffer.toString());
                        buffer.setLength(0);
                    }

                    if (ch == '?') {
                        list.add("?");
                    } else if (prevChar != '*') {
                        list.add("*");
                    }
                }

                prevChar = ch;
            }

            if (buffer.length() != 0) {
                list.add(buffer.toString());
            }

            return (String[])list.toArray(new String[list.size()]);
        }
    }

    static {
        SYSTEM_SEPARATOR = File.separatorChar;
        if (isSystemWindows()) {
            OTHER_SEPARATOR = '/';
        } else {
            OTHER_SEPARATOR = '\\';
        }

    }
}

使用实例

package com.wgcloud.util;


import com.jcraft.jsch.*;
import com.wgcloud.entity.SystemInfo;
import com.wgcloud.util.common.SSHDetails;
import com.wgcloud.util.common.SSHOperate;
import org.springframework.ui.Model;

import java.io.File;
import java.io.IOException;

public class HyrenUtil {
    public static String sftpAgentToTargetMachine(SystemInfo systemInfo, String agentUrl, Model model) throws JSchException, IOException, SftpException, InterruptedException {
        File file = new File(agentUrl);
        // Agent的生成目录,使用Agent的名称命名的
        String agentDir = "/home/hyren/ywAgent";
        SSHDetails sshDetails = SSHOperate.getSSHDetails(systemInfo.getHostname(), systemInfo.getUserName(),
                systemInfo.getPassword(), 22);
        // 一 : 将需要的文件SCP 到目标agent目录下
            model.addAttribute("message", "服务器连接超时");
            SSHOperate sshOperate = new SSHOperate(sshDetails);
            model.addAttribute("message", "系统异常");
            // 1 : 根据旧的部署目录来判断是否为第一次部署,如果部署第一次部署则先将进程kill,然后再将目录删除.
            sshOperate.execCommandBySSH(
                    "cd " + agentDir + File.separator + "; bash stop.sh");

            sshOperate.execCommandBySSH("rm -rf " + agentDir);
            // 创建远程目录
            mkdirToTarget(sshOperate, agentDir);
            // 开始传输的Agent包
            String targetDir = agentDir;
            sshOperate.channelSftp.put(file.getAbsolutePath()+File.separator+"wgcloud-agent-release.jar", targetDir);
            sshOperate.channelSftp.put(file.getAbsolutePath()+File.separator+"start.sh", targetDir);
            sshOperate.channelSftp.put(file.getAbsolutePath()+File.separator+"stop.sh", targetDir);
            sshOperate.execCommandBySSH(
                "cd " + agentDir + File.separator + "; bash start.sh");
            return targetDir;

    }
    public static String sftpAgentToTargetMachine(SystemInfo systemInfo, String agentUrl) throws JSchException, IOException, SftpException, InterruptedException {
        File file = new File(agentUrl);
        // Agent的生成目录,使用Agent的名称命名的
        String agentDir = "/home/hyren/ywAgent";
        SSHDetails sshDetails = SSHOperate.getSSHDetails(systemInfo.getHostname(), systemInfo.getUserName(),
                systemInfo.getPassword(), 22);
        // 一 : 将需要的文件SCP 到目标agent目录下
        SSHOperate sshOperate = new SSHOperate(sshDetails);
        // 1 : 根据旧的部署目录来判断是否为第一次部署,如果部署第一次部署则先将进程kill,然后再将目录删除.
        sshOperate.execCommandBySSH(
                "cd " + agentDir + File.separator + "; bash stop.sh");

        sshOperate.execCommandBySSH("rm -rf " + agentDir);
        // 创建远程目录
        mkdirToTarget(sshOperate, agentDir);
        // 开始传输的Agent包
        String targetDir = agentDir;
        sshOperate.channelSftp.put(file.getAbsolutePath()+File.separator+"wgcloud-agent-release.jar", targetDir);
        sshOperate.channelSftp.put(file.getAbsolutePath()+File.separator+"start.sh", targetDir);
        sshOperate.channelSftp.put(file.getAbsolutePath()+File.separator+"stop.sh", targetDir);
        sshOperate.execCommandBySSH(
                "cd " + agentDir + File.separator + "; bash start.sh");
        return targetDir;

    }
    public static void mkdirToTarget(SSHOperate sshOperate, String targetDir) throws IOException, JSchException {

            // 建立  .bin 隐藏目录
            sshOperate.execCommandBySSH("mkdir -p " + targetDir);
    }
    //停止服务
    public static void stopAgent(SystemInfo systemInfo) throws JSchException, IOException, SftpException, InterruptedException {
        // Agent的生成目录,使用Agent的名称命名的
        String agentDir = "/home/hyren/ywAgent";
        SSHDetails sshDetails = SSHOperate.getSSHDetails(systemInfo.getHostname(), systemInfo.getUserName(),
                systemInfo.getPassword(), 22);
        // 一 : 将需要的文件SCP 到目标agent目录下
        SSHOperate sshOperate = new SSHOperate(sshDetails);
        // 1 : 根据旧的部署目录来判断是否为第一次部署,如果部署第一次部署则先将进程kill,然后再将目录删除.
        sshOperate.execCommandBySSH(
                "cd " + agentDir + File.separator + "; bash stop.sh");

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值