【代码全】使用SpringAOP编写日志记录(插入oracle数据库中)

代码较多,请耐心调试


首先oracle数据库表创建语句:

drop table cmu_system_log;

CREATE TABLE CMU_SYSTEM_LOG (
log_id INTEGER primary key ,
user_id INTEGER ,
username VARCHAR2(20)  ,
description VARCHAR2(50)  ,
methods VARCHAR2(500)  ,
log_type VARCHAR2(50) ,
request_ip INTEGER ,
exceptioncode VARCHAR2(255)  ,
exception_detail VARCHAR2(255)  ,
params VARCHAR2(255) ,
time DATE DEFAULT SYSDATE

);


需要在:Spring-mvc.xml中增加:

<!-- 最重要:如果放在spring-context.xml中,这里的aop设置将不会生效,AOP日志配置 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <bean id="systemLogAspect" class="com.security.annotation.SystemLogAspect"></bean>


pom.xml里面需要导入:

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.10</version>
        </dependency>

<dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-aop</artifactId>  
        <version>4.2.5.RELEASE</version>  
        </dependency>


编写以下主要四个文件:


SysLog.java

package com.security.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    /** 要执行的操作类型比如:add操作 **/
    public String operationType() default "";

    /** 要执行的具体操作比如:添加用户 **/
    public String operationName() default "";
}



SystemControllerLog.java

package com.security.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
    String description() default "";
}

最重要的文件:SystemLogAspect.java

package com.security.annotation;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.community.service.SystemLogService;
import com.community.util.IpUtil;
import com.oracle.pojo.SystemLog;
import com.oracle.pojo.Users;

@Aspect
@Component
public class SystemLogAspect {
    // 使用service注入功能把日志写进数据库中
    @Resource
    private SystemLogService systemLogService;

    private static final Logger logger = LoggerFactory
            .getLogger(SystemLogAspect.class);

    // Conntroller层的切点
    @Pointcut("@annotation(com.security.annotation.SysLog)")
    public void controllerAspect() {
    }

    /**
     * 编写后置通知,用于拦截Controller 层记录用户的操作
     *
     * joinPoint 切点
     */
    @After("controllerAspect()")
    public void after(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        Users user = (Users) SecurityUtils.getSubject().getPrincipal();
        String userName = null;
        String userId = null;
        if (user != null) {
            Subject currentUser = SecurityUtils.getSubject();
            userId = currentUser.getSession().getAttribute("_USER_ID")
                    .toString();
            userName = (String) user.getUsername();
        }
        // 请求的IP
        String ip = request.getRemoteAddr();
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operationType = "";
            String operationName = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operationType = method.getAnnotation(SysLog.class)
                                .operationType();
                        operationName = method.getAnnotation(SysLog.class)
                                .operationName();
                        break;
                    }
                }
            }
            // 数据库日志
            SystemLog log = new SystemLog();
            log.setUserId(new Integer(userId));// 登录的用户id
            log.setUsername(userName);// 这里需要获取用户名
            log.setDescription(operationName);
            log.setMethods((joinPoint.getTarget().getClass().getName() + "."
                    + joinPoint.getSignature().getName() + "()"));
            log.setLogType(operationType);
            log.setRequestIp(IpUtil.ipToInt(ip));
            log.setExceptioncode(null);
            log.setExceptionDetail(null);
            log.setParams(null);
            log.setTime(new Date());
            // 保存数据库
            systemLogService.insertSelective(log);
            System.out.println("=====controller后置通知成功结束=====");
        } catch (Exception e) {
            // 记录本地异常日志
            logger.error("===后置通知异常===");
            logger.error("异常信息:{}", e.getMessage());
        }
    }

    /**
     * 异常通知 用于拦截记录异常日志
     */
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        Users user = (Users) SecurityUtils.getSubject().getPrincipal();
        String userName = null;
        String userId = null;
        if (user != null) {
            Subject currentUser = SecurityUtils.getSubject();
            userId = currentUser.getSession().getAttribute("_USER_ID")
                    .toString();
            userName = (String) user.getUsername();
        }
        // 请求的IP
        String ip = request.getRemoteAddr();
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {

            params = Arrays.toString(joinPoint.getArgs());
        }
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operationType = "error";
            String operationName = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operationType = method.getAnnotation(SysLog.class)
                                .operationType();
                        operationName = method.getAnnotation(SysLog.class)
                                .operationName();
                        break;
                    }
                }
            }
            // ====数据库日志=====
            SystemLog log = new SystemLog();
            log.setUserId(new Integer(userId));// 登录的用户id
            log.setUsername(userName);// 这里需要获取用户名
            log.setDescription(operationName);
            log.setMethods((joinPoint.getTarget().getClass().getName() + "."
                    + joinPoint.getSignature().getName() + "()")
                    + "." + operationType);
            log.setLogType(operationType);
            log.setRequestIp(IpUtil.ipToInt(ip));
            log.setExceptioncode(null);
            log.setExceptionDetail(null);
            log.setParams(null);
            log.setTime(new Date());
            // 保存数据库
            systemLogService.insertSelective(log);
            System.out.println("=====异常通知结束=====");
        } catch (Exception ex) {
            // 记录本地异常日志
            logger.error("==异常通知异常==");
            logger.error("异常信息:{}", ex.getMessage());
        }
        // 记录本地异常日志
        logger.error("异常方法:{}异常代码:{}异常信息:{}参数:{}", joinPoint.getTarget()
                .getClass().getName()
                + joinPoint.getSignature().getName(), e.getClass().getName(),
                e.getMessage(), params);
    }
}


SystemServiceLog.java


package com.security.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemServiceLog {
    String description() default "";
}


SytemLogcontroller.java  负责与前端交互


package com.community.controller;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.community.pager.PageInfo;
import com.community.service.SystemLogService;

@Controller
@Scope(value = "prototype")
@RequestMapping(value = "systemlog")
public class SystemLogController extends BaseController {
    @Autowired
    SystemLogService systemLogService;

    /**
     * 跳转到日志展示界面 权限控制判断 目前没有增加判断,仅跳转使用
     */
    @RequestMapping(value = "tolist")
    public ModelAndView tolist(ModelMap map) {
        return new ModelAndView("/security/logs/logs");
    }

    /**
     * @param page 页数
     * @param rows 每页的数据量
     * @param searchvalue  搜索关键字
     * @param order  排序
     * @param sort  按。。顺序排 ,desc、asc
     * @param starttime 开始时间
     * @param endtime  结束时间
     * @param response 相应信息
     * @return
     */
    @RequestMapping(value = "list")
    @ResponseBody
    public Object list(Integer page, Integer rows, String searchvalue,
            String order, String sort, String starttime, String endtime,
            HttpServletResponse response) {
        String value = null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // ----非空判断----
        if (StringUtils.isNotEmpty(searchvalue)) {
            try {
                value = URLDecoder.decode(searchvalue, "UTF-8");
            } catch (UnsupportedEncodingException e1) {
                e1.printStackTrace();
            }
        }
        Date st = null;
        Date et = null;
        if (StringUtils.isNotEmpty(starttime)) {
            try {
                st = sdf.parse(starttime);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        if (StringUtils.isNoneEmpty(endtime)) {
            try {
                et = sdf.parse(endtime);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        // ---获取到登录账户的ID值
        Subject currentUser = SecurityUtils.getSubject();
        String userId = currentUser.getSession().getAttribute("_USER_ID")
                .toString();
        PageInfo pageInfo = new PageInfo(page, rows);
        Map<String, Object> condition = new HashMap<String, Object>();
        int start = (page - 1) * rows;
        int end = start + rows;
        condition.put("st", st);// 开始时间
        condition.put("et", et);// 结束时间
        condition.put("start", start);
        condition.put("end", end);
        condition.put("order", order);// 为空没有使用
        condition.put("sort", sort);// 为空没有使用
        condition.put("value", value);// 获取到搜索框的值(字符)
        condition.put("userId", userId);
        condition.put("searchvalue", searchvalue);
        pageInfo.setCondition(condition);
        systemLogService.findLogs(pageInfo);
        return pageInfo;
    }

}

systemLogMapper.xml

三个方法:

//插入到数据库

 <insert id="insertSelective" parameterType="com.oracle.pojo.SystemLog">
    <!--
      WARNING - @mbggenerated
      This element is automatically generated by MyBatis Generator, do not modify.
    -->
    insert into CMU_SYSTEM_LOG
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="logId != null">
        LOG_ID,
      </if>
      <if test="userId != null">
        USER_ID,
      </if>
      <if test="username != null">
        USERNAME,
      </if>
      <if test="description != null">
        DESCRIPTION,
      </if>
      <if test="methods != null">
        METHODS,
      </if>
      <if test="logType != null">
        LOG_TYPE,
      </if>
      <if test="requestIp != null">
        REQUEST_IP,
      </if>
      <if test="exceptioncode != null">
        EXCEPTIONCODE,
      </if>
      <if test="exceptionDetail != null">
        EXCEPTION_DETAIL,
      </if>
      <if test="params != null">
        PARAMS,
      </if>
      <if test="time != null">
        TIME,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="logId != null">
        #{logId,jdbcType=DECIMAL},
      </if>
      <if test="userId != null">
        #{userId,jdbcType=DECIMAL},
      </if>
      <if test="username != null">
        #{username,jdbcType=VARCHAR},
      </if>
      <if test="description != null">
        #{description,jdbcType=VARCHAR},
      </if>
      <if test="methods != null">
        #{methods,jdbcType=VARCHAR},
      </if>
      <if test="logType != null">
        #{logType,jdbcType=VARCHAR},
      </if>
      <if test="requestIp != null">
        #{requestIp,jdbcType=DECIMAL},
      </if>
      <if test="exceptioncode != null">
        #{exceptioncode,jdbcType=VARCHAR},
      </if>
      <if test="exceptionDetail != null">
        #{exceptionDetail,jdbcType=VARCHAR},
      </if>
      <if test="params != null">
        #{params,jdbcType=VARCHAR},
      </if>
      <if test="time != null">
        #{time,jdbcType=TIMESTAMP},
      </if>
    </trim>
  </insert>

//查询拉取数据(并且有模糊查询和时间判断)

  <select id="findLogsList" parameterType="com.community.pager.PageInfo" resultMap="BaseResultMap">
    SELECT * FROM (SELECT tt.*, ROWNUM AS rowno FROM(
    SELECT
    CSL.LOG_ID,
    CSL.USER_ID,
    CSL.USERNAME,
    CSL.DESCRIPTION,
    CSL.METHODS,
    CSL.LOG_TYPE,
    CSL.REQUEST_IP,
    CSL.EXCEPTIONCODE,
    CSL.EXCEPTION_DETAIL,
    CSL.PARAMS,
    CSL.TIME
    FROM(SELECT * FROM CMU_USERS START WITH USER_ID = #{condition.userId} CONNECT BY PRIOR USER_ID = PID) U
    LEFT OUTER JOIN CMU_SYSTEM_LOG CSL ON U.USER_ID = CSL.USER_ID
    where CSL.USER_ID is NOT NULL
      <if test="condition.searchvalue !=null and condition.searchvalue !='' ">
          and(CSL.USERNAME LIKE '%'||#{condition.searchvalue}||'%'
           or CSL.DESCRIPTION LIKE '%'||#{condition.value}||'%'
           or CSL.LOG_TYPE LIKE '%'||#{condition.searchvalue}||'%')
      </if>
      <if test="condition.st != null">
        and CSL.TIME >= #{condition.st}
    </if>
    <if test="condition.et != null">
        AND #{condition.et} >= CSL.TIME
    </if>
    order by TIME desc) tt
      WHERE ROWNUM &lt;= #{condition.end}) table_alias WHERE table_alias.rowno > #{condition.start}
  </select>

//计算数据的条数

  <select id="findLogsListCount" parameterType="com.community.pager.PageInfo" resultType="int">
      select count(*) FROM(SELECT * FROM CMU_USERS START WITH USER_ID = #{condition.userId} CONNECT BY PRIOR USER_ID = PID) U
    LEFT OUTER JOIN CMU_SYSTEM_LOG CSL ON U.USER_ID = CSL.USER_ID
    where CSL.USER_ID is NOT NULL
      <if test="condition.searchvalue !=null and condition.searchvalue !='' ">
          and(CSL.USERNAME LIKE '%'||#{condition.searchvalue}||'%'
           or CSL.DESCRIPTION LIKE '%'||#{condition.value}||'%'
           or CSL.LOG_TYPE LIKE '%'||#{condition.searchvalue}||'%')
      </if>
      <if test="condition.st != null">
        and CSL.TIME >= #{condition.st}
    </if>
    <if test="condition.et != null">
        AND #{condition.et} >= CSL.TIME
    </if>
  </select>

最后  最重要的是给你需要插入数据库的接口增加:@SysLog(operationType="submitLogin",operationName="代理商登录")   //举例


前端jsp使用的是easyUI框架:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var="ctx" value="${pageContext.request.contextPath}" />
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<style type="text/css">
#treeGrid tr td {
    white-space: nowrap;
    text-overflow: ellipsis;
    -o-text-overflow: ellipsis;
    overflow: hidden;
}
</style>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="edge" />
<title>日志列表</title>
<link rel="stylesheet" type="text/css"
    href="${ctx}/resources/easyui/themes/default/easyui.css">
<link rel="stylesheet" type="text/css"
    href="${ctx}/resources/easyui/themes/icon.css">
<script type="text/javascript"
    src="${ctx}/resources/easyui/jquery.min.js"></script>
<script type="text/javascript"
    src="${ctx}/resources/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript"
    src="${ctx}/resources/easyui/locale/easyui-lang-zh_CN.js"></script>
<script type="text/javascript" src="${ctx}/resources/js/extJs.js"></script>
<script type="text/javascript">
    var gUrl;
    $(document).ready(function() {
        gUrl = '${ctx}/systemlog/list';
        loadDatagrid(gUrl);
    });
    function loadDatagrid(gUrl) {
        $('#treeGrid').datagrid({
            method : 'post',
            url : gUrl,
            title : "日志列表",
            loadMsg : '正在加载信息...',
            pagination : true, // 分页
            fit : true,
            fitColumns : true,
            striped : true, // 是否显示行间隔色
            queryParams : queryParams,
            pageList : [ 15, 20, 30, 50 ],
            pageSize : 20,
            rownumbers : true,//行号
            sidePagination : "server", // 服务端处理分页  
            columns : [ [ {
                title : '登录名',
                field : 'username', // 字段  
                width : '10%'
            }, {
                field : 'description',
                title : '描述',
                width : '10%'
            }, {
                title : '日志类型',
                field : 'logType',
                width : '10%'
            }, {
                title : '调用方法',
                field : 'methods',
                width : '40%'
            }, {
                title : 'IP地址',
                field : 'requestIp',
                width : '10%',
                formatter : function(value, row, index) {
                    return _int2iP(value);
                }
            }, {
                title : '创建时间',
                field : 'time',
                align : 'center',
                valign : 'middle',
                width : 80,
                formatter : function(value, row, index) {
                    if (value) {
                        return dataformatter(value);
                    }
                }
            }, ] ],
        });
    }
    function doSearch(value) {
        var starttime = $("#starttime").datebox('getValue');
        var endtime = $("#endtime").datebox('getValue');
        gUrl = "${ctx}/systemlog/list?searchvalue="+encodeURI(encodeURI(value))+"&starttime="+ starttime + "&endtime="+endtime;
        loadDatagrid(gUrl);
    }
    function searchByTime(){
        var starttime = $("#starttime").datebox('getValue');
        var endtime = $("#endtime").datebox('getValue');
        var gUrl = "${ctx}/systemlog/list?starttime="+ starttime + "&endtime="+endtime;
        loadDatagrid(gUrl);
    }
    function dataformatter(value) {
        var date = new Date(value);
        var year = date.getFullYear().toString();
        var month = (date.getMonth() + 1);
        var day = date.getDate().toString();
        var hour = date.getHours().toString();
        var minutes = date.getMinutes().toString();
        var seconds = date.getSeconds().toString();
        if (month < 10) {
            month = "0" + month;
        }
        if (day < 10) {
            day = "0" + day;
        }
        if (hour < 10) {
            hour = "0" + hour;
        }
        if (minutes < 10) {
            minutes = "0" + minutes;
        }
        if (seconds < 10) {
            seconds = "0" + seconds;
        }
        return year + "-" + month + "-" + day + " " + hour + ":" + minutes
                + ":" + seconds;
    }

     /** 刷新页面 */
    function refresh() {
        $('#treeGrid').bootstrapTable('refresh');
    }

    function _int2iP(num) {
        var str;
        var tt = new Array();
        tt[0] = (num >>> 24) >>> 0;
        tt[1] = ((num << 8) >>> 24) >>> 0;
        tt[2] = (num << 16) >>> 24;
        tt[3] = (num << 24) >>> 24;
        str = String(tt[0]) + "." + String(tt[1]) + "." + String(tt[2]) + "."
                + String(tt[3]);
        return str;
    }
    /**查询条件与分页数据 */
    function queryParams(pageReqeust) {
        pageReqeust.enabled = $("#enabled").val();
        pageReqeust.querys = $("#querys").val();
        pageReqeust.pageNo = this.pageNumber;
        return pageReqeust;
    }
    $.extend($.fn.validatebox.defaults.rules, {
        TimesCheck: {
            validator: function (value, param) {
                var s = $('#starttime').datebox('getValue');
                //因为日期是统一格式的所以可以直接比较字符串 否则需要Date.parse(_date)转换
                return value >= s;
            },
            message: '结束时间必须大于开始时间!!!'
        }
    });
</script>
</head>
<body>
    <div class="easyui-layout" data-options="fit:true">
        <div data-options="region:'center',border:false"
            style="overflow: hidden;">
            <table id="treeGrid" toolbar="#toolbar"></table>
            <div id="toolbar">
            <div style="float: right;">
                <input id="ss" class="easyui-searchbox" searcher="doSearch" prompt="请输入要查询的条件(登录名、描述或日志类型,选择时间后也可点击此处)..." style="width: 450px; vertical-align: middle;"></input>
            </div>&nbsp;&nbsp;&nbsp;
            开始时间:&nbsp;&nbsp;<input type="text" class="easyui-datebox" name="starttime" id="starttime" editable="false" style="width:110px;" data-options="buttons:buttons,prompt:'请输入开始时间'">&nbsp;&nbsp;&nbsp;
            结束时间:&nbsp;&nbsp;<input type="text" class="easyui-datebox" name="endtime" id="endtime" editable="false" style="width:110px;" data-options="buttons:buttons,prompt:'请输入结束时间'" validType="TimesCheck['starttime']" invalidMessage="结束时间必须大于开始时间!">&nbsp;&nbsp;
            <a href="#" οnclick="javascript:searchByTime();return false;"class="easyui-linkbutton" iconCls="icon-search">查询</a>
            </div>
        </div>
</body>
</html>



最终显示:


本文编辑为:美推网www.zdflshop.com  (站长编辑)


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java°遇上鲸鱼

文章帮您解决了问题打赏1元即可

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值