# 阿里云内容安全
以下是项目内容安全的流程
下面是实现步骤:
# 内容安全第三方接口
概述:
内容安全是识别服务,支持对图片、视频、文本、语音等对象进行多样化场景检测,有效降低内容违规风险。 目前很多平台都支持内容检测,如阿里云、腾讯云、百度 AI、网易云等国内大型互联网公司都对外提供了 API。 按照性能和收费来看,黑马头条项目使用的就是阿里云的内容安全接口,使用到了图片和文本的审核。
下面链接查看阿里云收费标准:
https://www.aliyun.com/price/product/?spm=a2c4g.11186623.2.10.4146401eg5oeu8
# 准备工作
您在使用内容检测 API 之前,需要先注册阿里云账号,添加 Access Key 并签约云盾内容安全。
操作步骤
前往阿里云官网注册账号。如果已有注册账号,请跳过此步骤。
- 进入阿里云首页后,如果没有阿里云的账户需要先进行注册,才可以进行登录。由于注册较为简单,课程和讲义不在进行体现(注册可以使用多种方式,如淘宝账号、支付宝账号、微博账号等...)。
- 需要实名认证和活体认证。
打开云盾内容安全产品试用页面,单击立即开通,正式开通服务。
内容安全控制台
- 在 AccessKey 管理页面管理您的 AccessKeyID 和 AccessKeySecret。
管理自己的 AccessKey, 可以新建和删除 AccessKey
查看自己的 AccessKey,
AccessKey 默认是隐藏的,第一次申请的时候可以保存 AccessKey,点击显示,通过验证手机号后也可以查看
# 文本内容审核接口
文本垃圾内容检测:https://help.aliyun.com/document_detail/70439.html?spm=a2c4g.11186623.6.659.35ac3db3l0wV5k
文本垃圾内容 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
图片垃圾内容 Java SDK: https://help.aliyun.com/document_detail/53424.html?spm=a2c4g.11186623.6.715.c8f69b12ey35j4
# 项目集成
项目模块结构:
在 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 容器中,提供给其它模块使用。
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 中的阿里云内容安全的配置信息如下:
配置解释:
scenes:表示的是阿里云中提供的审核类型如下:
accessKeyId 和 secret 是在如下图中查看:
然后就可以进行使用了
# 项目代码演示
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 代码进行了文章内容的审核处理并返回结果