自己的项目新增了一个社区的服务,社区可以发布文章,浏览文章,类似一些平台的文章管理的功能。那么发布的文章肯定是需要审核的,但是又不能全部为人工审核,这样的话效率太低也忙不过来,所以就想着先引入一个自动审核,只有当自动审核不通过之后,才进行人工审核。
首先介绍一下内容安全:
内容安全是识别服务,支持对图片、视频、文本、语音等对象进行多样化场景检测,有效降低内容违规风险。
目前很多平台都支持内容检测,如阿里云、腾讯云、百度AI、网易云等国内大型互联网公司都对外提供了API。
我因为已经在阿里云购买了云服务器,相对来说比较方便一些,当然阿里云提供的这个服务是收费的,但是不贵,开发测试是可以接收的。
阿里云收费标准:收费标准链接
在使用内容检测API之前,需要先注册阿里云账号,然后需要创建,记住自己的accesskey和secret。
这个创建一般一个账户只能绑定两个,也是一种可以访问你的用户的一个账号密码,可以供外接平台使用,这个密码创建之后是隐藏的,难以找到,所以建议第一次创建好了之后就记录下来。
那下面我们就来说明一下这个具体实现。
首先这个内容安全的接入分为内容安全增强版和内容安全1.0。这个区别就是内容安全1.0是需要企业认证的,也就是说个人用不了。所以你没有认证是无法调用内容安全的sdk的。
这里面注意切换版本
当然我也把文档地址给大家。大家也可以自己去看。如何调用文本检测接口进行文本内容审核_内容安全(Content Moderation)-阿里云帮助中心
下面我就来结合代码说明一下。
com.aliyun
green20220302
1.1.0
com.aliyun.oss
aliyun-sdk-oss
3.16.3
注意,这里的accesskey和secret可以直接在这里写自己的,也可以在nacos或者本地进行配置,反正能读取就行了。
方法返回值为map是因为后续需要调用该方法,通过它的返回结果来判断是否需要进一步的人工审核。
Config是一些配置,用于连接到阿里云这个接口的配置。
这里为了省劲,测试的时候只用了hashmap,追求线程安全的也可以用一下concurrent包下的map类。
同时不要忘了设置set_service,这个如果自己不设置的话是不成功的。
package com.neu.base.aliyun;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.green20220302.Client;
import com.aliyun.green20220302.models.TextModerationRequest;
import com.aliyun.green20220302.models.TextModerationResponse;
import com.aliyun.green20220302.models.TextModerationResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
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;
//传入文本,返回一个map,包含了文本的审核结果
public Map greenTextScanStrongVersion(String content) throws Exception {
System.out.println(accessKeyId);
Config config = new Config();
System.out.println("");
config.setAccessKeyId(accessKeyId);
config.setAccessKeySecret(secret);
//接入区域和地址请根据实际情况修改
config.setRegionId("cn-shanghai");
config.setEndpoint("green-cip.cn-shanghai.aliyuncs.com");
//连接时超时时间,单位毫秒(ms)。
config.setReadTimeout(6000);
//读取时超时时间,单位毫秒(ms)。
config.setConnectTimeout(3000);
// 注意,此处实例化的client请尽可能重复使用,避免重复建立连接,提升检测性能
Client client = new Client(config);
// 创建RuntimeObject实例并设置运行参数。
RuntimeOptions runtime = new RuntimeOptions();
runtime.readTimeout = 10000;
runtime.connectTimeout = 10000;
//检测参数构造
Map resultMap= new HashMap<>();
JSONObject serviceParameters = new JSONObject();
//设置要审核的内容
serviceParameters.put("content", content);
if (serviceParameters.get("content") == null || serviceParameters.getString("content").trim().length() == 0) {
System.out.println("text moderation content is empty");
resultMap.put("suggestion", "检测内容为空");
return resultMap;
}
TextModerationRequest textModerationRequest = new TextModerationRequest();
/*
文本检测service:内容安全控制台文本增强版规则配置的serviceCode,示例:chat_detection
*/
textModerationRequest.setService("comment_detection");
textModerationRequest.setServiceParameters(serviceParameters.toJSONString());
try {
// 调用方法获取检测结果。
TextModerationResponse response = client.textModerationWithOptions(textModerationRequest, runtime);
// 自动路由。
if (response != null) {
// 服务端错误,区域切换到cn-beijing。
if (500 == response.getStatusCode() || (response.getBody() != null && 500 == (response.getBody().getCode()))) {
// 接入区域和地址请根据实际情况修改。
config.setRegionId("cn-beijing");
config.setEndpoint("green-cip.cn-beijing.aliyuncs.com");
client = new Client(config);
response = client.textModerationWithOptions(textModerationRequest, runtime);
}
}
// 打印检测结果。
if (response != null) {
if (response.getStatusCode() == 200) {
TextModerationResponseBody result = response.getBody();
System.out.println(JSON.toJSONString(result));
Integer code = result.getCode();
if (code != null && code == 200) {
TextModerationResponseBody.TextModerationResponseBodyData data = result.getData();
if(data.getLabels().isEmpty()&& data.getReason().isEmpty()){
resultMap.put("suggestion", "pass");
return resultMap;
}
System.out.println("labels = [" + data.getLabels() + "]");
System.out.println("reason = [" + data.getReason() + "]");
String labels=data.getLabels();
String reason=data.getReason();
resultMap.put("labels", labels);
resultMap.put("reason", reason);
resultMap.put("suggestion", "block");
return resultMap;
} else {
System.out.println("text moderation not success. code:" + code);
String information="text moderation not success :"+code;
resultMap.put("information", "review");
return resultMap;
}
} else {
System.out.println("response not success. status:" + response.getStatusCode());
String information="response not success"+response.getStatusCode();
resultMap.put("information", "review");
return resultMap;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
我们只需要把写的工具类交给springboot容器管理,然后进行注入测试就可以了。一些springboottest注解我就不写了,这里只展示测试代码。
@Autowired
private GreenTextScan greenTextScan;
/**
* 测试文本内容审核
*/
@Test
public void testScanText() throws Exception {
Map map = greenTextScan.greenTextScanStrongVersion("neu帅不帅");
System.out.println(map.get("suggestion"));
System.out.println(map);
}
测试结果:
返回结果是pass的话只需要从自己的调用方法里面取出,然后进行判断就可以了。
当然你如果这么说
@Test
public void testScanText() throws Exception {
Map map = greenTextScan.greenTextScanStrongVersion("neu是不是傻子xuexiao");
System.out.println(map.get("suggestion"));
System.out.println(map);
}
当然奥,我肯定热爱neu奥。
为了证明,我痛花两条测试,日子艰难,痛失几分钱,希望大家给个关注和点赞吧哈哈哈,当然这样大家也可以看看自己是否测试成功。
管理控制台 - 用量统计 (aliyun.com)
图片审核我测试过公网的图片,好像符合https协议的才可以,而我的域名是http协议的,测试一下好像即使公网可以访问但也出了问题,所以这里我采用的是本地图片测试。也就是说把我放在公网minio里的文件进行下载到本地,设置成临时文件,由于文件的存活周期所以也不用担心。
而我们实际应用当中可能一次审核多张照片,但是这个功能在内容安全1.0版本是可以实现的,但是增强版不可以,无奈我们只能多次调用该接口,下面给出工具类。
同样不要忘了设置服务类型规则的设置。
public class GreenImageScan {
private String accessKeyId;
private String secret;
//服务是否部署在vpc上
public static boolean isVPC = false;
//文件上传token endpoint->token
public static Map tokenMap = new HashMap<>();
//上传文件请求客户端
public static OSS ossClient = null;
//内容增强扫描本地
public Map imageScanStrongLocalVersion(String url) throws Exception{
/**
* 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
* 常见获取环境变量方式:
* 方式一:
* 获取RAM用户AccessKey ID:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
* 获取RAM用户AccessKey Secret:System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
* 方式二:
* 获取RAM用户AccessKey ID:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_ID");
* 获取RAM用户AccessKey Secret:System.getProperty("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
*/
// 接入区域和地址请根据实际情况修改。
ImageModerationResponse response = invokeLocalFunction(url,accessKeyId, secret, "green-cip.cn-shanghai.aliyuncs.com");
Map resultMap=new HashMap<>();
try {
// 自动路由。
if (response != null) {
//区域切换到cn-beijing。
if (500 == response.getStatusCode() || (response.getBody() != null && 500 == (response.getBody().getCode()))) {
// 接入区域和地址请根据实际情况修改。
response = invokeLocalFunction(url,accessKeyId, secret, "green-cip.cn-beijing.aliyuncs.com");
}
}
// 打印检测结果。
if (response != null) {
if (response.getStatusCode() == 200) {
ImageModerationResponseBody body = response.getBody();
System.out.println("requestId=" + body.getRequestId());
System.out.println("code=" + body.getCode());
System.out.println("msg=" + body.getMsg());
resultMap.put("code",body.getCode());
resultMap.put("msg",body.getMsg());
resultMap.put("requestId",body.getRequestId());
if (body.getCode() == 200) {
ImageModerationResponseBody.ImageModerationResponseBodyData data = body.getData();
System.out.println("dataId=" + data.getDataId());
List results = data.getResult();
for (ImageModerationResponseBody.ImageModerationResponseBodyDataResult result : results) {
System.out.println("label=" + result.getLabel());
System.out.println("confidence=" + result.getConfidence());
}
resultMap.put("suggestion","pass");
return resultMap;
} else {
System.out.println("image moderation not success. code:" + body.getCode());
resultMap.put("information","image moderation not success. code:" + body.getCode());
resultMap.put("suggestion","review");
return resultMap;
}
} else {
System.out.println("response not success. status:" + response.getStatusCode());
resultMap.put("information","response not success. status:" + response.getStatusCode());
resultMap.put("suggestion","block");
return resultMap;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 创建请求客户端
*
* @param accessKeyId
* @param accessKeySecret
* @param endpoint
* @return
* @throws Exception
*/
public static Client createClient(String accessKeyId, String accessKeySecret, String endpoint) throws Exception {
Config config = new Config();
config.setAccessKeyId(accessKeyId);
config.setAccessKeySecret(accessKeySecret);
// 设置http代理。
//config.setHttpProxy("http://10.10.xx.xx:xxxx");
// 设置https代理。
//config.setHttpsProxy("https://10.10.xx.xx:xxxx");
// 接入区域和地址请根据实际情况修改
config.setEndpoint(endpoint);
return new Client(config);
}
/**
* 创建上传文件请求客户端
*
* @param tokenData
* @param isVPC
*/
public static void getOssClient(DescribeUploadTokenResponseBody.DescribeUploadTokenResponseBodyData tokenData, boolean isVPC) {
//注意,此处实例化的client请尽可能重复使用,避免重复建立连接,提升检测性能。
if (isVPC) {
ossClient = new OSSClientBuilder().build(tokenData.ossInternalEndPoint, tokenData.getAccessKeyId(), tokenData.getAccessKeySecret(), tokenData.getSecurityToken());
} else {
ossClient = new OSSClientBuilder().build(tokenData.ossInternetEndPoint, tokenData.getAccessKeyId(), tokenData.getAccessKeySecret(), tokenData.getSecurityToken());
}
}
/**
* 上传文件
*
* @param filePath
* @param tokenData
* @return
* @throws Exception
*/
public static String uploadFile(String filePath, DescribeUploadTokenResponseBody.DescribeUploadTokenResponseBodyData tokenData) throws Exception {
//将文件路径 filePath 根据点号 . 进行分割
String[] split = filePath.split("\\.");
String objectName;
if (split.length > 1) {
objectName = tokenData.getFileNamePrefix() + UUID.randomUUID() + "." + split[split.length - 1];
} else {
objectName = tokenData.getFileNamePrefix() + UUID.randomUUID();
}
PutObjectRequest putObjectRequest = new PutObjectRequest(tokenData.getBucketName(), objectName, new File(filePath));
ossClient.putObject(putObjectRequest);
return objectName;
}
public static ImageModerationResponse invokeLocalFunction(String url,String accessKeyId, String accessKeySecret, String endpoint) throws Exception {
//注意,此处实例化的client请尽可能重复使用,避免重复建立连接,提升检测性能。
Client client = createClient(accessKeyId, accessKeySecret, endpoint);
RuntimeOptions runtime = new RuntimeOptions();
//本地文件的完整路径,例如D:\localPath\exampleFile.png。
String filePath = url;
String bucketName = null;
DescribeUploadTokenResponseBody.DescribeUploadTokenResponseBodyData uploadToken = tokenMap.get(endpoint);
//获取文件上传token
if (uploadToken == null || uploadToken.expiration <= System.currentTimeMillis() / 1000) {
DescribeUploadTokenResponse tokenResponse = client.describeUploadToken();
uploadToken = tokenResponse.getBody().getData();
bucketName = uploadToken.getBucketName();
}
//上传文件请求客户端
getOssClient(uploadToken, isVPC);
//上传文件
String objectName = uploadFile(filePath, uploadToken);
// 检测参数构造。
Map serviceParameters = new HashMap<>();
//文件上传信息
serviceParameters.put("ossBucketName", bucketName);
serviceParameters.put("ossObjectName", objectName);
serviceParameters.put("dataId", UUID.randomUUID().toString());
ImageModerationRequest request = new ImageModerationRequest();
// 图片检测service:内容安全控制台图片增强版规则配置的serviceCode,示例:baselineCheck
request.setService("baselineCheck");
request.setServiceParameters(JSON.toJSONString(serviceParameters));
ImageModerationResponse response = null;
try {
response = client.imageModerationWithOptions(request, runtime);
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
}
这里我的思路是传入一个url,invokelocal那里的参数和imageScanlocal那里的参数传入的都是本地图片的绝对路径。
而我们这个图片拉取的时候是从minio里面拉取的,所以我们还需要从minio里下载图片,这个下载图片的方法我目前就先不给出了,额,因为我觉得不算是重点,如果大家需要的话可以评论留言,我会给出。
这里的url我没有给出,因为图片是需要本地的,我把url下载的图片保存到本地了,这里的url是minio的访问图片。
@Test
public void testScanImage() throws Exception {
String url="";
byte[] bytes = fileStorageService.downLoadFile(url);
//在本地创建一个临时文件,将bytes写入到临时文件中
// 获取文件扩展名
//文件扩展名
String fileExtension = url.substring(url.lastIndexOf("."));
System.out.println(fileExtension);
Path tempFilePath ;
try {
tempFilePath = Files.createTempFile("temp", fileExtension);
} catch (IOException e) {
e.printStackTrace();
return;
}
// 将字节流写入本地临时文件
try {
Files.write(tempFilePath, bytes, StandardOpenOption.CREATE);
System.out.println("文件下载成功,保存在:" + tempFilePath.toString());
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件下载失败");
}
String tempFileUrl = tempFilePath.toString();
Map map = greenImageScan.imageScanStrongLocalVersion(tempFileUrl);
System.out.println(map.get("suggestion"));
System.out.println(map);
}
这个就是测试结果了。又痛失我大洋了家人,给赞啊!
当然违规图片我就不给了,哈哈哈,好青年一枚奥。