# 阿里云内容安全

以下是项目内容安全的流程

image-20240303164905639

下面是实现步骤:

# 内容安全第三方接口

概述

内容安全是识别服务,支持对图片、视频、文本、语音等对象进行多样化场景检测,有效降低内容违规风险。 目前很多平台都支持内容检测,如阿里云、腾讯云、百度 AI、网易云等国内大型互联网公司都对外提供了 API。 按照性能和收费来看,黑马头条项目使用的就是阿里云的内容安全接口,使用到了图片和文本的审核。

下面链接查看阿里云收费标准:

https://www.aliyun.com/price/product/?spm=a2c4g.11186623.2.10.4146401eg5oeu8

# 准备工作

您在使用内容检测 API 之前,需要先注册阿里云账号,添加 Access Key 并签约云盾内容安全。

操作步骤

  1. 前往阿里云官网注册账号。如果已有注册账号,请跳过此步骤。

    1. 进入阿里云首页后,如果没有阿里云的账户需要先进行注册,才可以进行登录。由于注册较为简单,课程和讲义不在进行体现(注册可以使用多种方式,如淘宝账号、支付宝账号、微博账号等...)。
    2. 需要实名认证和活体认证。
  2. 打开云盾内容安全产品试用页面,单击立即开通,正式开通服务。

image-20240303164927219

内容安全控制台

image-20240303164941621

  1. AccessKey 管理页面管理您的 AccessKeyID 和 AccessKeySecret。

image-20240303164956492

管理自己的 AccessKey, 可以新建和删除 AccessKey

查看自己的 AccessKey,

AccessKey 默认是隐藏的,第一次申请的时候可以保存 AccessKey,点击显示,通过验证手机号后也可以查看

image-20240303165029423

# 文本内容审核接口

文本垃圾内容检测:https://help.aliyun.com/document_detail/70439.html?spm=a2c4g.11186623.6.659.35ac3db3l0wV5k

image-20240303165041731

文本垃圾内容 Java SDK: https://help.aliyun.com/document_detail/53427.html?spm=a2c4g.11186623.6.717.466d7544QbU8Lr

# 图片审核接口

图片垃圾内容检测:https://help.aliyun.com/document_detail/70292.html?spm=a2c4g.11186623.6.616.5d7d1e7f9vDRz4

image-20240303165055116

图片垃圾内容 Java SDK: https://help.aliyun.com/document_detail/53424.html?spm=a2c4g.11186623.6.715.c8f69b12ey35j4

# 项目集成

项目模块结构:

image-20240303165124706

在 common 模块中添加如下的 sdk 工具类

在 common 模块中导入 阿里云 sdk 相关依赖

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-green</artifactId>
</dependency>

ClientUploader

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectResult;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.green.model.v20180509.UploadCredentialsRequest;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpResponse;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.IClientProfile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
 * 用于本地图片文件检测时,上传本地图片
 */
public class ClientUploader {
    private IClientProfile profile;
    private volatile UploadCredentials uploadCredentials;
    private Map<String, String> headers;
    private String prefix;
    private boolean internal = false;
    private Object lock = new Object();
    private ClientUploader(IClientProfile profile, String prefix, boolean internal) {
        this.profile = profile;
        this.uploadCredentials = null;
        this.headers = new HashMap<String, String>();
        this.prefix = prefix;
        this.internal = internal;
    }
    public static ClientUploader getImageClientUploader(IClientProfile profile, boolean internal){
        return  new ClientUploader(profile, "images", internal);
    }
    public static ClientUploader getVideoClientUploader(IClientProfile profile, boolean internal){
        return  new ClientUploader(profile, "videos", internal);
    }
    public static ClientUploader getVoiceClientUploader(IClientProfile profile, boolean internal){
        return  new ClientUploader(profile, "voices", internal);
    }
    public static ClientUploader getFileClientUploader(IClientProfile profile, boolean internal){
        return  new ClientUploader(profile, "files", internal);
    }
    /**
     * 上传并获取上传后的图片链接
     * @param filePath
     * @return
     */
    public String uploadFile(String filePath){
        FileInputStream inputStream = null;
        OSSClient ossClient = null;
        try {
            File file = new File(filePath);
            UploadCredentials uploadCredentials = getCredentials();
            if(uploadCredentials == null){
                throw new RuntimeException("can not get upload credentials");
            }
            ObjectMetadata meta = new ObjectMetadata();
            meta.setContentLength(file.length());
            inputStream = new FileInputStream(file);
            ossClient = new OSSClient(getOssEndpoint(uploadCredentials), uploadCredentials.getAccessKeyId(), uploadCredentials.getAccessKeySecret(), uploadCredentials.getSecurityToken());
            String object = uploadCredentials.getUploadFolder() + '/' + this.prefix + '/' + String.valueOf(filePath.hashCode());
            PutObjectResult ret = ossClient.putObject(uploadCredentials.getUploadBucket(), object, inputStream, meta);
            return "oss://" + uploadCredentials.getUploadBucket() + "/" + object;
        } catch (Exception e) {
            throw new RuntimeException("upload file fail.", e);
        } finally {
            if(ossClient != null){
                ossClient.shutdown();
            }
            if(inputStream != null){
                try {
                    inputStream.close();
                }catch (Exception e){
                }
            }
        }
    }
    private String getOssEndpoint(UploadCredentials uploadCredentials){
        if(this.internal){
            return uploadCredentials.getOssInternalEndpoint();
        }else{
            return uploadCredentials.getOssEndpoint();
        }
    }
    /**
     * 上传并获取上传后的图片链接
     * @param bytes
     * @return
     */
    public String uploadBytes(byte[] bytes){
        OSSClient ossClient = null;
        try {
            UploadCredentials uploadCredentials = getCredentials();
            if(uploadCredentials == null){
                throw new RuntimeException("can not get upload credentials");
            }
            ossClient = new OSSClient(getOssEndpoint(uploadCredentials), uploadCredentials.getAccessKeyId(), uploadCredentials.getAccessKeySecret(), uploadCredentials.getSecurityToken());
            String object = uploadCredentials.getUploadFolder() + '/' + this.prefix + '/' + UUID.randomUUID().toString();
            PutObjectResult ret = ossClient.putObject(uploadCredentials.getUploadBucket(), object, new ByteArrayInputStream(bytes));
            return "oss://" + uploadCredentials.getUploadBucket() + "/" + object;
        } catch (Exception e) {
            throw new RuntimeException("upload file fail.", e);
        } finally {
            if(ossClient != null){
                ossClient.shutdown();
            }
        }
    }
    public void addHeader(String key, String value){
        this.headers.put(key, value);
    }
    private UploadCredentials getCredentials() throws Exception{
        if(this.uploadCredentials == null || this.uploadCredentials.getExpiredTime() < System.currentTimeMillis()){
            synchronized(lock){
                if(this.uploadCredentials == null || this.uploadCredentials.getExpiredTime() < System.currentTimeMillis()){
                    this.uploadCredentials = getCredentialsFromServer();
                }
            }
        }
        return this.uploadCredentials;
    }
    /**
     * 从服务器端获取上传凭证
     * @return
     * @throws Exception
     */
    private UploadCredentials getCredentialsFromServer() throws Exception{
        UploadCredentialsRequest uploadCredentialsRequest =  new UploadCredentialsRequest();
        uploadCredentialsRequest.setAcceptFormat(FormatType.JSON); // 指定 api 返回格式
        uploadCredentialsRequest.setMethod(com.aliyuncs.http.MethodType.POST); // 指定请求方法
        uploadCredentialsRequest.setEncoding("utf-8");
        uploadCredentialsRequest.setProtocol(ProtocolType.HTTP);
        for (Map.Entry<String, String> kv : this.headers.entrySet()) {
            uploadCredentialsRequest.putHeaderParameter(kv.getKey(), kv.getValue());
        }
        uploadCredentialsRequest.setHttpContent(new JSONObject().toJSONString().getBytes("UTF-8"), "UTF-8", FormatType.JSON);
        IAcsClient client = null;
        try{
            client = new DefaultAcsClient(profile);
            HttpResponse httpResponse =  client.doAction(uploadCredentialsRequest);
            if (httpResponse.isSuccess()) {
                JSONObject scrResponse = JSON.parseObject(new String(httpResponse.getHttpContent(), "UTF-8"));
                if (200 == scrResponse.getInteger("code")) {
                    JSONObject data = scrResponse.getJSONObject("data");
                    return new UploadCredentials(data.getString("accessKeyId"), data.getString("accessKeySecret"),
                            data.getString("securityToken"), data.getLongValue("expiredTime"),
                            data.getString("ossEndpoint"), data.getString("ossInternalEndpoint"), data.getString("uploadBucket"), data.getString("uploadFolder"));
                }
                String requestId = scrResponse.getString("requestId");
                throw new RuntimeException("get upload credential from server fail. requestId:" + requestId + ", code:" + scrResponse.getInteger("code"));
            }
            throw new RuntimeException("get upload credential from server fail. http response status:" + httpResponse.getStatus());
        }finally {
            client.shutdown();
        }
    }
}

CustomLibUploader

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
/**
 * 用于自定义图库上传图片
 */
public class CustomLibUploader {
    public String uploadFile(String host, String uploadFolder, String ossAccessKeyId,
                             String policy, String signature,
                             String filepath) throws Exception {
        LinkedHashMap<String, String> textMap = new LinkedHashMap<String, String>();
        // key
        String objectName = uploadFolder + "/imglib_" + UUID.randomUUID().toString() + ".jpg";
        textMap.put("key", objectName);
        // Content-Disposition
        textMap.put("Content-Disposition", "attachment;filename="+filepath);
        // OSSAccessKeyId
        textMap.put("OSSAccessKeyId", ossAccessKeyId);
        // policy
        textMap.put("policy", policy);
        // Signature
        textMap.put("Signature", signature);
        Map<String, String> fileMap = new HashMap<String, String>();
        fileMap.put("file", filepath);
        String ret = formUpload(host, textMap, fileMap);
        System.out.println("[" + host + "] post_object:" + objectName);
        System.out.println("post reponse:" + ret);
        return objectName;
    }
    private static String formUpload(String urlStr, Map<String, String> textMap, Map<String, String> fileMap) throws Exception {
        String res = "";
        HttpURLConnection conn = null;
        String BOUNDARY = "9431149156168";
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(10000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("User-Agent",
                    "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
            conn.setRequestProperty("Content-Type",
                    "multipart/form-data; boundary=" + BOUNDARY);
            OutputStream out = new DataOutputStream(conn.getOutputStream());
            // text
            if (textMap != null) {
                StringBuffer strBuf = new StringBuffer();
                Iterator iter = textMap.entrySet().iterator();
                int i = 0;
                while (iter.hasNext()) {
                    Map.Entry entry = (Map.Entry) iter.next();
                    String inputName = (String) entry.getKey();
                    String inputValue = (String) entry.getValue();
                    if (inputValue == null) {
                        continue;
                    }
                    if (i == 0) {
                        strBuf.append("--").append(BOUNDARY).append(
                                "\r\n");
                        strBuf.append("Content-Disposition: form-data; name=\""
                                + inputName + "\"\r\n\r\n");
                        strBuf.append(inputValue);
                    } else {
                        strBuf.append("\r\n").append("--").append(BOUNDARY).append(
                                "\r\n");
                        strBuf.append("Content-Disposition: form-data; name=\""
                                + inputName + "\"\r\n\r\n");
                        strBuf.append(inputValue);
                    }
                    i++;
                }
                out.write(strBuf.toString().getBytes());
            }
            // file
            if (fileMap != null) {
                Iterator iter = fileMap.entrySet().iterator();
                while (iter.hasNext()) {
                    Map.Entry entry = (Map.Entry) iter.next();
                    String inputName = (String) entry.getKey();
                    String inputValue = (String) entry.getValue();
                    if (inputValue == null) {
                        continue;
                    }
                    File file = new File(inputValue);
                    String filename = file.getName();
                    String contentType = new MimetypesFileTypeMap().getContentType(file);
                    if (contentType == null || contentType.equals("")) {
                        contentType = "application/octet-stream";
                    }
                    StringBuffer strBuf = new StringBuffer();
                    strBuf.append("\r\n").append("--").append(BOUNDARY).append(
                            "\r\n");
                    strBuf.append("Content-Disposition: form-data; name=\""
                            + inputName + "\"; filename=\"" + filename
                            + "\"\r\n");
                    strBuf.append("Content-Type: " + contentType + "\r\n\r\n");
                    out.write(strBuf.toString().getBytes());
                    DataInputStream in = new DataInputStream(new FileInputStream(file));
                    int bytes = 0;
                    byte[] bufferOut = new byte[1024];
                    while ((bytes = in.read(bufferOut)) != -1) {
                        out.write(bufferOut, 0, bytes);
                    }
                    in.close();
                }
                StringBuffer strBuf = new StringBuffer();
                out.write(strBuf.toString().getBytes());
            }
            byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
            out.write(endData);
            out.flush();
            out.close();
            // 读取返回数据
            StringBuffer strBuf = new StringBuffer();
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    conn.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                strBuf.append(line).append("\n");
            }
            res = strBuf.toString();
            reader.close();
            reader = null;
        } catch (Exception e) {
            System.err.println("发送POST请求出错: " + urlStr);
            throw e;
        } finally {
            if (conn != null) {
                conn.disconnect();
                conn = null;
            }
        }
        return res;
    }
}

UploadCredentials

import java.io.Serializable;
public class UploadCredentials implements Serializable {
    private String accessKeyId;
    private String  accessKeySecret;
    private String  securityToken;
    private Long  expiredTime;
    private String  ossEndpoint;
    private String  ossInternalEndpoint;
    private String  uploadBucket;
    private String  uploadFolder;
    public UploadCredentials(String accessKeyId, String accessKeySecret, String securityToken, Long expiredTime, String ossEndpoint, String ossInternalEndpoint, String uploadBucket, String uploadFolder) {
        this.accessKeyId = accessKeyId;
        this.accessKeySecret = accessKeySecret;
        this.securityToken = securityToken;
        this.expiredTime = expiredTime;
        this.ossEndpoint = ossEndpoint;
        this.ossInternalEndpoint = ossInternalEndpoint;
        this.uploadBucket = uploadBucket;
        this.uploadFolder = uploadFolder;
    }
    public String getAccessKeyId() {
        return accessKeyId;
    }
    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }
    public String getAccessKeySecret() {
        return accessKeySecret;
    }
    public void setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }
    public String getSecurityToken() {
        return securityToken;
    }
    public void setSecurityToken(String securityToken) {
        this.securityToken = securityToken;
    }
    public Long getExpiredTime() {
        return expiredTime;
    }
    public void setExpiredTime(Long expiredTime) {
        this.expiredTime = expiredTime;
    }
    public String getOssEndpoint() {
        return ossEndpoint;
    }
    public void setOssEndpoint(String ossEndpoint) {
        this.ossEndpoint = ossEndpoint;
    }
    public String getUploadBucket() {
        return uploadBucket;
    }
    public void setUploadBucket(String uploadBucket) {
        this.uploadBucket = uploadBucket;
    }
    public String getUploadFolder() {
        return uploadFolder;
    }
    public void setUploadFolder(String uploadFolder) {
        this.uploadFolder = uploadFolder;
    }
    public String getOssInternalEndpoint() {
        return ossInternalEndpoint;
    }
    public void setOssInternalEndpoint(String ossInternalEndpoint) {
        this.ossInternalEndpoint = ossInternalEndpoint;
    }
}

GreenImageScan

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.green.model.v20180509.ImageSyncScanRequest;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.heima.common.aliyun.util.ClientUploader;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.*;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "aliyun")
public class GreenImageScan {
    private String accessKeyId;
    private String secret;
    private String scenes;
    public Map imageScan(List<byte[]> imageList) throws Exception {
        IClientProfile profile = DefaultProfile
            .getProfile("cn-shanghai", accessKeyId, secret);
        DefaultProfile
            .addEndpoint("cn-shanghai", "cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
        IAcsClient client = new DefaultAcsClient(profile);
        ImageSyncScanRequest imageSyncScanRequest = new ImageSyncScanRequest();
        // 指定 api 返回格式
        imageSyncScanRequest.setAcceptFormat(FormatType.JSON);
        // 指定请求方法
        imageSyncScanRequest.setMethod(MethodType.POST);
        imageSyncScanRequest.setEncoding("utf-8");
        // 支持 http 和 https
        imageSyncScanRequest.setProtocol(ProtocolType.HTTP);
        JSONObject httpBody = new JSONObject();
        /**
         * 设置要检测的场景,计费是按照该处传递的场景进行
         * 一次请求中可以同时检测多张图片,每张图片可以同时检测多个风险场景,计费按照场景计算
         * 例如:检测 2 张图片,场景传递 porn、terrorism,计费会按照 2 张图片鉴黄,2 张图片暴恐检测计算
         * porn: porn 表示色情场景检测
         */
        httpBody.put("scenes", Arrays.asList(scenes.split(",")));
        /**
         * 如果您要检测的文件存于本地服务器上,可以通过下述代码片生成 url
         * 再将返回的 url 作为图片地址传递到服务端进行检测
         */
        /**
         * 设置待检测图片, 一张图片一个 task
         * 多张图片同时检测时,处理的时间由最后一个处理完的图片决定
         * 通常情况下批量检测的平均 rt 比单张检测的要长,一次批量提交的图片数越多,rt 被拉长的概率越高
         * 这里以单张图片检测作为示例,如果是批量图片检测,请自行构建多个 task
         */
        ClientUploader clientUploader = ClientUploader.getImageClientUploader(profile, false);
        String url = null;
        List<JSONObject> urlList = new ArrayList<JSONObject>();
        for (byte[] bytes : imageList) {
            url = clientUploader.uploadBytes(bytes);
            JSONObject task = new JSONObject();
            task.put("dataId", UUID.randomUUID().toString());
            // 设置图片链接为上传后的 url
            task.put("url", url);
            task.put("time", new Date());
            urlList.add(task);
        }
        httpBody.put("tasks", urlList);
        imageSyncScanRequest.setHttpContent(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(httpBody.toJSONString()),
            "UTF-8", FormatType.JSON);
        /**
         * 请设置超时时间,服务端全链路处理超时时间为 10 秒,请做相应设置
         * 如果您设置的 ReadTimeout 小于服务端处理的时间,程序中会获得一个 read timeout 异常
         */
        imageSyncScanRequest.setConnectTimeout(3000);
        imageSyncScanRequest.setReadTimeout(10000);
        HttpResponse httpResponse = null;
        try {
            httpResponse = client.doAction(imageSyncScanRequest);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Map<String, String> resultMap = new HashMap<>();
        // 服务端接收到请求,并完成处理返回的结果
        if (httpResponse != null && httpResponse.isSuccess()) {
            JSONObject scrResponse = JSON.parseObject(org.apache.commons.codec.binary.StringUtils.newStringUtf8(httpResponse.getHttpContent()));
            System.out.println(JSON.toJSONString(scrResponse, true));
            int requestCode = scrResponse.getIntValue("code");
            // 每一张图片的检测结果
            JSONArray taskResults = scrResponse.getJSONArray("data");
            if (200 == requestCode) {
                for (Object taskResult : taskResults) {
                    // 单张图片的处理结果
                    int taskCode = ((JSONObject) taskResult).getIntValue("code");
                    // 图片要检测的场景的处理结果,如果是多个场景,则会有每个场景的结果
                    JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
                    if (200 == taskCode) {
                        for (Object sceneResult : sceneResults) {
                            String scene = ((JSONObject) sceneResult).getString("scene");
                            String label = ((JSONObject) sceneResult).getString("label");
                            String suggestion = ((JSONObject) sceneResult).getString("suggestion");
                            // 根据 scene 和 suggetion 做相关处理
                            //do something
                            System.out.println("scene = [" + scene + "]");
                            System.out.println("suggestion = [" + suggestion + "]");
                            System.out.println("suggestion = [" + label + "]");
                            if (!suggestion.equals("pass")) {
                                resultMap.put("suggestion", suggestion);
                                resultMap.put("label", label);
                                return resultMap;
                            }
                        }
                    } else {
                        // 单张图片处理失败,原因视具体的情况详细分析
                        System.out.println("task process fail. task response:" + JSON.toJSONString(taskResult));
                        return null;
                    }
                }
                resultMap.put("suggestion","pass");
                return resultMap;
            } else {
                /**
                 * 表明请求整体处理失败,原因视具体的情况详细分析
                 */
                System.out.println("the whole image scan request failed. response:" + JSON.toJSONString(scrResponse));
                return null;
            }
        }
        return null;
    }
}

GreenTextScan

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.green.model.v20180509.TextScanRequest;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.HttpResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.*;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "aliyun")
public class GreenTextScan {
    private String accessKeyId;
    private String secret;
    public Map greeTextScan(String content) throws Exception {
        System.out.println(accessKeyId);
        IClientProfile profile = DefaultProfile
                .getProfile("cn-shanghai", accessKeyId, secret);
        DefaultProfile.addEndpoint("cn-shanghai", "cn-shanghai", "Green", "green.cn-shanghai.aliyuncs.com");
        IAcsClient client = new DefaultAcsClient(profile);
        TextScanRequest textScanRequest = new TextScanRequest();
        textScanRequest.setAcceptFormat(FormatType.JSON); // 指定 api 返回格式
        textScanRequest.setHttpContentType(FormatType.JSON);
        textScanRequest.setMethod(com.aliyuncs.http.MethodType.POST); // 指定请求方法
        textScanRequest.setEncoding("UTF-8");
        textScanRequest.setRegionId("cn-shanghai");
        List<Map<String, Object>> tasks = new ArrayList<Map<String, Object>>();
        Map<String, Object> task1 = new LinkedHashMap<String, Object>();
        task1.put("dataId", UUID.randomUUID().toString());
        /**
         * 待检测的文本,长度不超过 10000 个字符
         */
        task1.put("content", content);
        tasks.add(task1);
        JSONObject data = new JSONObject();
        /**
         * 检测场景,文本垃圾检测传递:antispam
         **/
        data.put("scenes", Arrays.asList("antispam"));
        data.put("tasks", tasks);
        System.out.println(JSON.toJSONString(data, true));
        textScanRequest.setHttpContent(data.toJSONString().getBytes("UTF-8"), "UTF-8", FormatType.JSON);
        // 请务必设置超时时间
        textScanRequest.setConnectTimeout(3000);
        textScanRequest.setReadTimeout(6000);
        Map<String, String> resultMap = new HashMap<>();
        try {
            HttpResponse httpResponse = client.doAction(textScanRequest);
            if (httpResponse.isSuccess()) {
                JSONObject scrResponse = JSON.parseObject(new String(httpResponse.getHttpContent(), "UTF-8"));
                System.out.println(JSON.toJSONString(scrResponse, true));
                if (200 == scrResponse.getInteger("code")) {
                    JSONArray taskResults = scrResponse.getJSONArray("data");
                    for (Object taskResult : taskResults) {
                        if (200 == ((JSONObject) taskResult).getInteger("code")) {
                            JSONArray sceneResults = ((JSONObject) taskResult).getJSONArray("results");
                            for (Object sceneResult : sceneResults) {
                                String scene = ((JSONObject) sceneResult).getString("scene");
                                String label = ((JSONObject) sceneResult).getString("label");
                                String suggestion = ((JSONObject) sceneResult).getString("suggestion");
                                System.out.println("suggestion = [" + label + "]");
                                if (!suggestion.equals("pass")) {
                                    resultMap.put("suggestion", suggestion);
                                    resultMap.put("label", label);
                                    return resultMap;
                                }
                            }
                        } else {
                            return null;
                        }
                    }
                    resultMap.put("suggestion", "pass");
                    return resultMap;
                } else {
                    return null;
                }
            } else {
                return null;
            }
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

以上的代码可以查看阿里云的文档,这里只是拷贝下来进行了一些修改

由于这个 common 模块是公共的所以我们将需要用到的 Bean 通过 spring.factories 来注入到 IOC 容器中,提供给其它模块使用。

image-20240303165305042

spring.factories 内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
	com.heima.common.aliyun.GreenImageScan,\
  com.heima.common.aliyun.GreenTextScan

本项目是文章内容审核,审核功能在 wemedia 模块中使用到了,而这个模块配置了 nacos 其中 bootstrap.yml 内容如下:

server:
  port: 51803
spring:
  application:
    name: leadnews-wemedia
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yml

我们再看 naocs 中的阿里云内容安全的配置信息如下:

image-20240303165350661

配置解释:

scenes:表示的是阿里云中提供的审核类型如下:

image-20240303165407056

accessKeyId 和 secret 是在如下图中查看:

image-20240303165426939

然后就可以进行使用了

# 项目代码演示

controller

@RestController
@RequestMapping("/api/v1/news")
public class WmNewsController {
    @Autowired
    private WmNewsService wmNewsService;
		 ...
    @PostMapping("submit")
    public ResponseResult submitNews(@RequestBody WmNewsDto wmNewsDto)
    {
        return wmNewsService.submitNews(wmNewsDto);
    }
}

service

进行了一系列的判断和赋值后 进行文章的审核

@Override
public ResponseResult submitNews(WmNewsDto wmNewsDto) {
    // 条件判断
    if(wmNewsDto == null || wmNewsDto.getContent() == null)
    {
        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }
    // 1. 保存或修改文章
    WmNews wmNews = new WmNews();
    BeanUtils.copyProperties(wmNewsDto, wmNews);
    ...
    saveOrUpdateWmNews(wmNews);
    ...
    saveRelativeInfoForContent(strings, wmNews.getId());
    // 4. 不是草稿,保存文章封面图片与素材的关系,如果当前局部是自动,需要匹配封面图片
    saveRelativeInfoForCover(wmNewsDto, wmNews, strings);
    // 审核文章
    wmNewsAutoScanService.autoScanWmNews(wmNews.getId());
    return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}

我们查看审核文章的函数 往里面传入了一个文章的 id

下面是 审核文章 的逻辑层代码

@Override
@Async // 该注解表示该方法是一个异步方法
public void autoScanWmNews(Integer id) {
    // 1. 查询自媒体文章
    WmNews wmNews = wmNewsMapper.selectById(id);
    if(wmNews == null)
    {
        throw new RuntimeException("WmNewsAutoScanServiceImpl -> autoScanWmNews : 文章不存在");
    }
    // 判断是否为待审核的
    if(wmNews.getStatus().equals(WmNews.Status.SUBMIT.getCode()))
    {
        // 从内容中提取纯文本内容和图片
        Map<String, Object> textAndImages = handleTextAndImages(wmNews);
        // 2. 审核文本内容,阿里云接口
        boolean isTextScan = handleTextScan(textAndImages.get("content").toString(), wmNews);
        if(!isTextScan)
        {
            return;
        }
        // 3. 审核图片,阿里云接口
        boolean isImageScan = handleImageScan((List<String>) textAndImages.get("images"), wmNews);
        if(!isImageScan)
        {
            return;
        }
        // 4. 审核成功,保存 app 端的相关文章数据
        ResponseResult responseResult = saveAppArticle(wmNews);
        if(!responseResult.getCode().equals(200))
        {
            throw new RuntimeException("WmNewsAutoScanServiceImpl-文章审核,保存app端相关文章数据失败");
        }
        // 回填 article_id
        wmNews.setArticleId((Long) responseResult.getData());
        updateWmNews(wmNews, (short) 9, "审核成功");
    }
}

我们查看 handleTextScan 这个函数里面是 审核文章内容的真正调用了阿里云 sdk 的函数

下面是逻辑代码:

/**
 * 审核纯文本内容
 * @param content
 * @param wmNews
 * @return
 */
private boolean handleTextScan(String content, WmNews wmNews) {
    boolean flag = true;
    if(content == null || (wmNews.getTitle() + "-" + content).length() == 0)
    {
        return flag;
    }
    try {
        Map map = greenTextScan.greeTextScan(wmNews.getTitle() + "-" + content);
        if(map != null)
        {
            // 审核失败
            if(map.get("suggestion").equals("block"))
            {
                flag = false;
                updateWmNews(wmNews, (short) 2, "当前文章中存在违规内容");
            }
            // 不确定信息,需要人工审核
            if(map.get("suggestion").equals("review"))
            {
                flag = false;
                updateWmNews(wmNews, (short) 3, "当前文章中存在不确定内容");
            }
        }
    } catch (Exception e) {
        flag = false;
        throw new RuntimeException(e);
    }
    return flag;
}

greenTextScan.greeTextScan (wmNews.getTitle () + "-" + content); 这个函数就是调用了阿里云 sdk 代码进行了文章内容的审核处理并返回结果