Springboot整合百度人脸识别功能实现注册和登录

超详细多种人脸识别教程请参考CSDN中CodeDevMaster大佬的教程,链接如下:Java借助百度云人脸识别实现人脸注册、登录功能的完整示例_百度api 视频流人脸识别 java-CSDN博客

7f73f3fd9cf64ab3b76a2daccd82c6a3.png

本文档的百度AI人脸识别主要用于人脸登录等功能类似于手机上的面容解锁,需要上传到百度AI接口两张照片进行校验,如果需要类似腾讯的实名认证人脸识别真实姓名+身份证号+人脸的功能请参考上面的文档,且需要在百度AI进行企业认证调用V4接口。据百度AI工程师说之前的老用户可以使用V3接口的实名认证人脸识别,现在的新用户都需要进行企业认证调用V4接口。详细操作请参考百度AI人脸识别官方文档和错误码文档

官方API:人脸对比 - 人脸识别_人脸检测_人脸对比_人脸搜索_活体检测_百度智能云 (baidu.com)

官方错误码文档:人脸识别人脸检测人脸对比人脸搜索活体检测_百度智能云 (baidu.com)

项目压缩包链接:https://download.csdn.net/download/2301_79357346/89241774

如文档有错误请与我联系,谢谢

准备工作:

登录百度智能云控制台:https://console.bce.baidu.com/,开通人脸识别

81d6f6518d2a428bb3b5cc4d63c8739c.png

选择人脸识别项,创建人脸识别应用

26381562d7ba49f997bf4beccdb91f27.png

人脸识别接口默认全部选择,也可以选择添加其他接口。

a29db13675824a23a0b4c8abf0c7b119.png

得到应用ID API KEYSecret Key

a43566ec25cf4c53bdac929007a78acd.png

添加依赖

    <dependencies>
        <!--单元测试启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--web启动器依赖(springMVC)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--druid-SpringBoot启动器依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--JDBC启动器依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--        MyBatis-Plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!--spring boot actuator依赖-->
        <!--实时监控数据,以前从来没使过            可不加-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--编码(辅助)工具包-->
        <!-- 注:不加可能会报错,加上就好使了
        它提供了大量用于处理字符串、日期的实用方法,以及其它一些如集合、数组、概率和随机等类库。-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <!--        分布式上传文件(静态资源)-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.15.1</version>
        </dependency>


        <!--百度AI-->
        <dependency>
            <groupId>com.baidu.aip</groupId>
            <artifactId>java-sdk</artifactId>
            <version>4.9.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--            可不加-->
            <!--spring boot maven插件 , 可以将项目运行依赖的jar包打到我们的项目中-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

项目结构参考如下

44afe35aae9e45818c79ba7232a63e3a.png

数据库参考如下

用户表要保证username用户名(账号)不能重复

63dbdd5182a14df6928057151c004993.png

创建BaiduAiConfig

package com.baidu.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class BaiduAiConfig {

    public static final String AppId =你的APPID;

    public static final String AK =你的API Key;

    public static final String SK =你的Secret Key;
}

我最初是在application.yml配置文件中写AppId、AK、SK但是类中获取不到所以才这么写的

创建OssUtil图片上传工具类

这里图片是保存到阿里云的OSS服务器中了,如果你没有阿里云的OSS服务器可以直接去阿里云官网免费注册

OSS文件上传详细教程请参考CSDN中liaozk_c大佬的教程:阿里云存储OSS文件上传详细教程_阿里oss文件上传-CSDN博客

02198358f1ae4aeb85c6e42d2df01d8a.png

package com.baidu.util;

import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.PutObjectResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@Component
public class OssUtil {

    private static String endPoint = "";                          // oss域名
    private static String accessKeyId = "";                    // accessKeyId
    private static String accessKeySecret = "";            // accessKeySecret
    private static String bucketName = "";                      // 桶名称
    private static String fileDir = "/img";                                // 当前文件所属文件夹(就是你想给文件传到那个文件夹下面)

    //log日志对象
    static Log log = LogFactory.getLog(OssUtil.class);
    /**
     * oss 工具客户端
     */
    private static OSSClient ossClient = null;

    /**
     * 上传文件至阿里云 OSS
     * @param file 待上传文件
     * 返回访问的全路径
     */
    public static String uploadFileToOss(MultipartFile file) {
        // 初始化oss
        initOSS(endPoint, accessKeyId, accessKeySecret);
        String visitUrl = null;
        try {
            // 获取文件名
            String orgName = file.getOriginalFilename();
            if (StringUtils.isEmpty(orgName)) {
                orgName = file.getName();
            }
            orgName = getFileName(orgName);
            //上传后的文件名 进行修饰
            String fileRelName = fileDir + orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.indexOf("."));
            // 上传至oss
            PutObjectResult result = ossClient.putObject(bucketName, fileRelName, file.getInputStream());
            if (result != null) {
                log.info("------OSS文件上传成功------");
            }
            // 拼接访问路径
            visitUrl = "https://" + bucketName + "." + endPoint + "/" + fileRelName;

            System.out.println("visit url : " + visitUrl);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return null;
        }
        return visitUrl;
    }

    /**
     * 删除文件
     * @param fileName
     */
    public static void deleteFileToOss(String fileName) {
        // 初始化oss
        initOSS(endPoint, accessKeyId, accessKeySecret);
        ossClient.deleteObject(bucketName, fileName);
    }

    /**
     * 初始化 oss 客户端
     * @return
     */
    private static OSSClient initOSS(String endpoint, String accessKeyId, String accessKeySecret) {
        if (ossClient == null) {
            synchronized (OSSClient.class) {
                if (ossClient == null) {
                    ossClient = new OSSClient(endpoint, new DefaultCredentialProvider(accessKeyId, accessKeySecret), new ClientConfiguration());
                    // 设置权限(公开读)
                    ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
                }
            }
        }
        return ossClient;
    }

    /**
     * 判断文件名是否带盘符,重新处理
     * @param fileName
     * @return
     */
    public static String getFileName(String fileName){
        //判断是否带有盘符信息
        // Check for Unix-style path
        int unixSep = fileName.lastIndexOf('/');
        // Check for Windows-style path
        int winSep = fileName.lastIndexOf('\\');
        // Cut off at latest possible point
        int pos = (winSep > unixSep ? winSep : unixSep);
        if (pos != -1) {
            // Any sort of path separator found...
            fileName = fileName.substring(pos + 1);
        }
        //替换上传文件名字的特殊字符
        fileName = fileName.replace("=", "").replace(",", "").replace("&", "").replace("#", "");
        return fileName;
    }

    public static String getEndPoint() {
        return endPoint;
    }

    public static void setEndPoint(String endPoint) {
        OssUtil.endPoint = endPoint;
    }

    public static String getAccessKeyId() {
        return accessKeyId;
    }

    public static void setAccessKeyId(String accessKeyId) {
        OssUtil.accessKeyId = accessKeyId;
    }

    public static String getAccessKeySecret() {
        return accessKeySecret;
    }

    public static void setAccessKeySecret(String accessKeySecret) {
        OssUtil.accessKeySecret = accessKeySecret;
    }

    public static String getBucketName() {
        return bucketName;
    }

    public static void setBucketName(String bucketName) {
        OssUtil.bucketName = bucketName;
    }

    public static String getFileDir() {
        return fileDir;
    }

    public static void setFileDir(String fileDir) {
        OssUtil.fileDir = fileDir;
    }

    public static OSSClient getOssClient() {
        return ossClient;
    }

    public static void setOssClient(OSSClient ossClient) {
        OssUtil.ossClient = ossClient;
    }
}

创建Base64Util

该类主要是负责把前端传回来的MultipartFile类型的人脸识别图片文件转换为百度AI需要的base64格式
package com.baidu.util;

import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;

/**
 * Base64 工具类
 */
public class Base64Util {
    private static final char last2byte = (char) Integer.parseInt("00000011", 2);
    private static final char last4byte = (char) Integer.parseInt("00001111", 2);
    private static final char last6byte = (char) Integer.parseInt("00111111", 2);
    private static final char lead6byte = (char) Integer.parseInt("11111100", 2);
    private static final char lead4byte = (char) Integer.parseInt("11110000", 2);
    private static final char lead2byte = (char) Integer.parseInt("11000000", 2);
    private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

    public Base64Util() {
    }

    public static String multipartFileToBase64(MultipartFile file) throws IOException {
        InputStream inputStream = file.getInputStream();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        int bytesRead;
        int bufferSize = 10 * 1024 * 1024;
        byte[] buffer = new byte[bufferSize]; // 可以根据需要调整缓冲区大小
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, bytesRead);
        }

        byte[] bytes = byteArrayOutputStream.toByteArray();
        String base64String = Base64.getEncoder().encodeToString(bytes);

        // 不需要显式关闭 inputStream,因为 MultipartFile 的实现会负责关闭它
        // byteArrayOutputStream.close(); // 如果需要,可以在这里关闭 ByteArrayOutputStream

        return base64String;
    }

    public static String encode(byte[] from) {
        StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3);
        int num = 0;
        char currentByte = 0;

        int i;
        for (i = 0; i < from.length; ++i) {
            for (num %= 8; num < 8; num += 6) {
                switch (num) {
                    case 0:
                        currentByte = (char) (from[i] & lead6byte);
                        currentByte = (char) (currentByte >>> 2);
                    case 1:
                    case 3:
                    case 5:
                    default:
                        break;
                    case 2:
                        currentByte = (char) (from[i] & last6byte);
                        break;
                    case 4:
                        currentByte = (char) (from[i] & last4byte);
                        currentByte = (char) (currentByte << 2);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6);
                        }
                        break;
                    case 6:
                        currentByte = (char) (from[i] & last2byte);
                        currentByte = (char) (currentByte << 4);
                        if (i + 1 < from.length) {
                            currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4);
                        }
                }

                to.append(encodeTable[currentByte]);
            }
        }

        if (to.length() % 4 != 0) {
            for (i = 4 - to.length() % 4; i > 0; --i) {
                to.append("=");
            }
        }

        return to.toString();
    }
}

创建CodeCompents

package com.baidu.compents;

public class CodeCompents {
    public final static String baidu_ai_imageType = "BASE64";
    public final static String baidu_ai_groupId = "groupId";
}

创建UserBaiduAIController

登录接口需要传入用户名(账号)和登录时的图片然后获取数据库中注册的人脸识别照片的URL传到百度AI进行比对,如果数据库中没有该用户的人脸识别照片的URL则为该用户没有注册人脸识别,直接返回请先注册人脸识别

注册接口需要传入用户名(账号)和注册的人脸图片,然后将人脸识别的照片上传到阿里云的OSS服务器并获取保存的URL,然后将URL给到用户类里对应的属性中,再调用修改方法对数据库中的信息修改将注册的人脸识别图片URL保存到数据库中

package com.baidu.controller;

import com.baidu.aip.face.AipFace;
import com.baidu.aip.face.MatchRequest;
import com.baidu.compents.CodeCompents;
import com.baidu.config.BaiduAiConfig;
import com.baidu.pojo.UserBaiduai;
import com.baidu.service.UserBaiduAIService;
import com.baidu.util.Base64Util;
import com.baidu.util.OssUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

@RestController
@RequestMapping("/baiduAI")
public class UserBaiduAIController {

    @Autowired
    private UserBaiduAIService userBaiduAIService;

    /**
     * 账号+人脸校验
     * @param userName
     * @param file
     * @throws Exception
     */
    @PostMapping("/loginBaiduAi")
    public void loginBaiduAi(String userName, MultipartFile file) throws Exception {
        // 传入appId、apiKey、secretkey。创建Java代码和百度云交互的Client对象
        AipFace client = new AipFace(BaiduAiConfig.AppId, BaiduAiConfig.AK, BaiduAiConfig.SK);
        // 登录图片
        // MultipartFile类型转换Base64字符串格式
        String loginImageBase64 = Base64Util.multipartFileToBase64(file);
        // 根据用户名获取用户信息
        UserBaiduai one = userBaiduAIService.findByName(userName);
        /*
        判断该用户是否注册了人脸
        如果没有注册则不进行校验
        如果注册了则进行校验
         */
        if (!ObjectUtils.isEmpty(one.getPhoto())){
            // 用户注册的人脸的照片存储路径
            String comparedImageUrl = one.getPhoto();
            /*
            传入参数进行校验
            返回值为两张照片的相似度
             */
            Double faceComparison = faceComparison(client, loginImageBase64, comparedImageUrl);
            if (faceComparison > 85) {
                System.out.println(one.getUsername());
                System.out.println("人脸识别登录成功");
            } else {
                System.out.println("人脸识别登录失败");
            }
        }else {
            System.out.println("请先录入人脸信息");
        }
    }

    @PostMapping("registerBaiduAi")
    public void registerBaiduAi(String userName,MultipartFile file) throws IOException, JSONException {
        // 传入appId、apiKey、secretkey。创建Java代码和百度云交互的Client对象
        AipFace client = new AipFace(BaiduAiConfig.AppId, BaiduAiConfig.AK, BaiduAiConfig.SK);
        // 根据用户名获取用户信息
        UserBaiduai one = userBaiduAIService.findByName(userName);
        // 判断用户是否注册过人脸识别
        if(!ObjectUtils.isEmpty(one.getPhoto())){
            // 如果有则先删除原来注册的人脸照片
            // 假设图片URL是https://canghai0190.oss-cn-beijing.aliyuncs.com/img/1714270552688.png
            // 那么我们要截取com/后面的文件路径名称传入方法进行删除
            int index = one.getPhoto().indexOf("com/");
            String fileName = null;
            if (index != -1) {
                fileName = one.getPhoto().substring(index + 4); // 截取 "com/" 后面的内容
            } else {
                fileName = "";
            }
            System.out.println(fileName);
            OssUtil.deleteFileToOss(fileName);
        }
        // MultipartFile类型转换Base64字符串格式
        String registerImageBase64 = Base64Util.multipartFileToBase64(file);
        // 传入可选参数调用接口
        HashMap<String, String> options = new HashMap<String, String>();
        options.put("user_info", "user's info");
        options.put("quality_control", "NORMAL");
        options.put("liveness_control", "LOW");
        options.put("action_type", "REPLACE");

        /*
         调用api方法完成人脸注册
         image 图片的url或者base64字符串
         imageType 图片形式(URL,BASE64)
         groupId 组Id(固定一个字符串)
         userId 用户Id
         options hashMap基本参数配置
         */
        JSONObject res = client.addUser(registerImageBase64, CodeCompents.baidu_ai_imageType, CodeCompents.baidu_ai_groupId, String.valueOf(userName), options);
        if (res.getInt("error_code")==0){
            //上传人脸识别图片到oss
            String url = OssUtil.uploadFileToOss(file);
            //将人脸识别信息和用户信息绑定存入数据库
            UserBaiduai userBaiduai = userBaiduAIService.findByName(userName);
            userBaiduai.setPhoto(url);
            userBaiduAIService.update(userBaiduai);
            //更新redis中的用户信息
//            updateUser(hmsUser);
            System.out.println("人脸注册成功");
        }else {
            System.out.println("人脸注册失败");
        }
        System.out.println(res.toString(2));
    }


    static Double faceComparison(AipFace client, String loginImageBase64, String comparedImageUrl) throws Exception {
        // 将图片的URL传递给百度API
        MatchRequest req2 = new MatchRequest(comparedImageUrl, "URL");
        // 将前端传过来的图片传递给百度API
        MatchRequest req1 = new MatchRequest(loginImageBase64, CodeCompents.baidu_ai_imageType);
        // 讲MatchRequest信息存入list集合中
        ArrayList<MatchRequest> requests = new ArrayList<>();
        requests.add(req1);
        requests.add(req2);

        // 进行人脸比对 返回值是json串
        JSONObject match = client.match(requests);
        System.out.println(match.toString(2));
        // 返回两张照片的相似度
        return match.getJSONObject("result").getDouble("score");
    }
}

测试

由于本人vue会的是在不多所以前端没有写出来,想要页面的请参考我最上面放的CodeDevMaster大佬的链接,教程最后面有vue前端的教程

为了测试我这边就使用Postman进行测试

需要的参数有

params中userName参数

fa7778e4032747be968c99618c9ac989.png

body中名为file的file类型的一个照片文件

1164709118074bbf84222044337e0f35.png

注册接口的测试结果

52466d0948754e7ab91c5e7fb23ff523.png登录接口的测试结果

登录接口人脸正确的结果

96be24f728e344f19a5367f5b46e7f5e.png

登录接口人脸识别的接口

6b8c22ac32d14e3583479f20b861c8dc.png

怎么判断两张照片是否为同一个人?

大家应该可以看到我上面两次测试都用红框框起来的score这个值,这个值的意思是传入百度AI的登录的人脸照片和数据库中注册的人脸照片的匹配值,匹配值越高则大概率是同一个人,匹配值越低则说明越不可能是同一个人,然后我们只需要根据这个值进行判断即可,一半是相似度大于等于85即可判断为同一个人,小于85则判断为不是同一个人

返回的JSON中各个属性是什么意思?

score	人脸相似度得分

face_list 人脸信息列表

face_token	人脸的唯一标志

log_id	是请求标识码,随机数,唯一

location	是人脸在图片中的位置
    
error_code	错误码

error_msg	误描述信息,帮助理解和解决发生的错误

  • 37
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,增加人脸识别功能需要用到一些第三方库,这里我以百度AI人脸识别为例进行说明。 1. 在百度AI官网申请并获取API Key和Secret Key。 2. 在Spring Boot项目中引入百度AI的Java SDK依赖,比如: ```xml <dependency> <groupId>com.baidu.aip</groupId> <artifactId>java-sdk</artifactId> <version>4.10.3</version> </dependency> ``` 3. 编写前端页面,在HTML中加入一个拍照的按钮,将拍摄到的图片传给后端,比如: ```html <input type="file" accept="image/*" capture="camera" id="uploadImg" onchange="upload()"> ``` ```javascript function upload() { var file = document.getElementById("uploadImg").files[0]; var formData = new FormData(); formData.append("file", file); $.ajax({ url: "/api/upload", type: "POST", data: formData, contentType: false, processData: false, success: function(result) { // 处理返回结果 } }); } ``` 4. 编写后端接口,将前端传来的图片进行人脸识别,比如: ```java @RestController @RequestMapping("/api") public class FaceRecognitionController { @Autowired private AipFace aipFace; @PostMapping("/upload") public String upload(@RequestParam("file") MultipartFile file) throws IOException { // 将MultipartFile转换成byte数组 byte[] imgBytes = file.getBytes(); // 设置请求参数 HashMap<String, String> options = new HashMap<>(); options.put("face_field", "age,beauty,expression,gender,glasses,race,quality"); options.put("max_face_num", "1"); options.put("face_type", "LIVE"); // 调用百度AI的人脸检测API JSONObject result = aipFace.detect(imgBytes, options); // 处理返回结果 if (result.getInt("error_code") == 0) { JSONArray faceList = result.getJSONArray("result"); if (faceList.length() > 0) { JSONObject faceInfo = faceList.getJSONObject(0); // 获取年龄、性别、颜值等信息 int age = faceInfo.getJSONObject("age").getInt("value"); String gender = faceInfo.getJSONObject("gender").getString("type"); double beauty = faceInfo.getJSONObject("beauty").getDouble("value"); // 返回处理结果 return "年龄:" + age + ",性别:" + gender + ",颜值:" + beauty; } else { return "未检测到人脸"; } } else { return "人脸检测失败"; } } } ``` 在上面的代码中,我们使用了百度AI的Java SDK来调用人脸检测API,将前端传来的图片转换成byte数组后进行检测,如果检测成功则返回人脸信息,否则返回错误信息。 以上就是Spring Boot项目增加人脸识别功能的前后端代码实现,当然,具体实现还需要根据您的具体需求进行相应的调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值