|
|
@@ -0,0 +1,319 @@
|
|
|
+package com.fs.app.utils;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import okhttp3.*;
|
|
|
+
|
|
|
+import javax.crypto.Mac;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.InvalidKeyException;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+import java.util.function.Supplier;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 内容审核
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+public final class ContentCheckUtil {
|
|
|
+
|
|
|
+ private static final OkHttpClient client = new OkHttpClient();
|
|
|
+
|
|
|
+ private static final String HOST_TEXT = "tms.tencentcloudapi.com";
|
|
|
+
|
|
|
+ private static final String HOST_IMG = "ims.tencentcloudapi.com";
|
|
|
+
|
|
|
+ private static final String SECRET_ID = "AKID0tdCbmiT3Qis6A2hARVMl3A6tTu9CmIg";
|
|
|
+
|
|
|
+ private static final String SECRET_KEY = "JQN8h4HJHbqtvnR3Xp9GI8mGpBNRQDvV";
|
|
|
+
|
|
|
+ private static final char[] HEX_CODE = "0123456789ABCDEF".toCharArray();
|
|
|
+
|
|
|
+ private static final String SUGGESTION_PASS = "Pass";
|
|
|
+
|
|
|
+ private static final String SUGGESTION_REVIEW = "Review";
|
|
|
+
|
|
|
+ private static final String SUGGESTION_BLOCK = "Block";
|
|
|
+
|
|
|
+ private static final String DEFAULT_REGION = "ap-beijing";
|
|
|
+
|
|
|
+ private static final Map<String, String> SUGGESTION_MAP = new HashMap<>();
|
|
|
+
|
|
|
+ private static final Map<String, String> LABEL_MAP = new HashMap<>();
|
|
|
+
|
|
|
+ static {
|
|
|
+ SUGGESTION_MAP.put("Block", "建议直接做违规处置");
|
|
|
+ SUGGESTION_MAP.put("Review", "建议人工二次确认");
|
|
|
+ SUGGESTION_MAP.put("Pass", "建议通过");
|
|
|
+ LABEL_MAP.put("Normal", "正常");
|
|
|
+ LABEL_MAP.put("Porn", "色情");
|
|
|
+ LABEL_MAP.put("Abuse", "谩骂");
|
|
|
+ LABEL_MAP.put("Ad", "广告;以及其他令人反感、不安全或不适宜的内容类型");
|
|
|
+ LABEL_MAP.put("Illegal", "非法的");
|
|
|
+ LABEL_MAP.put("Teenager", "青少年");
|
|
|
+ }
|
|
|
+
|
|
|
+// public static void main(String[] args) {
|
|
|
+// R r = checkText("操你妈");
|
|
|
+// log.info("{}", r);
|
|
|
+// R r1 = checkImage("https://cdn.his.cdwjyyh.com/fs/20260320/989bf692ddd0465cb363f514a1576454.png");
|
|
|
+// System.out.println(r1);
|
|
|
+// log.info("{}", r1);
|
|
|
+// }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文本违规审核
|
|
|
+ *
|
|
|
+ * @param text 待审核内容
|
|
|
+ */
|
|
|
+ public static R checkText(String text) {
|
|
|
+ return checkText(text, DEFAULT_REGION);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 图片违规审核内容
|
|
|
+ *
|
|
|
+ * @param fileUrl 图片所在网络地址
|
|
|
+ */
|
|
|
+ public static R checkImage(String fileUrl) {
|
|
|
+ return checkImage(fileUrl, DEFAULT_REGION);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 文本违规审核
|
|
|
+ *
|
|
|
+ * @param text 待审核内容
|
|
|
+ * @param region 区域
|
|
|
+ */
|
|
|
+ public static R checkText(String text, String region) {
|
|
|
+ String version = "2020-12-29";
|
|
|
+ String action = "TextModeration";
|
|
|
+ // 用Supplier封装文本审核的请求体构建逻辑(函数式编程核心)
|
|
|
+ Supplier<String> bodySupplier = () -> {
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("BizType", "text");
|
|
|
+ obj.put("Content", Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8)));
|
|
|
+ return obj.toJSONString();
|
|
|
+ };
|
|
|
+ return commonModeration(HOST_TEXT, version, action, bodySupplier, region, "文本审核未通过!");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 图片违规审核
|
|
|
+ *
|
|
|
+ * @param fileUrl 图片所在网络地址
|
|
|
+ * @param region 区域
|
|
|
+ */
|
|
|
+ public static R checkImage(String fileUrl, String region) {
|
|
|
+ String version = "2020-12-29";
|
|
|
+ String action = "ImageModeration";
|
|
|
+ // 用Supplier封装图片审核的请求体构建逻辑
|
|
|
+ Supplier<String> bodySupplier = () -> {
|
|
|
+ JSONObject obj = new JSONObject();
|
|
|
+ obj.put("BizType", "picture");
|
|
|
+ obj.put("DataId", UUID.randomUUID().toString());
|
|
|
+ obj.put("FileUrl", fileUrl);
|
|
|
+ return obj.toJSONString();
|
|
|
+ };
|
|
|
+ return commonModeration(HOST_IMG, version, action, bodySupplier, region, "图片审核未通过!");
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通用审核方法
|
|
|
+ *
|
|
|
+ * @param host 接口域名
|
|
|
+ * @param version 接口版本
|
|
|
+ * @param action 接口动作
|
|
|
+ * @param bodySupplier 请求体构建器
|
|
|
+ * @param region 区域
|
|
|
+ * @param failMsg 审核失败提示语
|
|
|
+ * @return 审核结果R
|
|
|
+ */
|
|
|
+ private static R commonModeration(
|
|
|
+ String host,
|
|
|
+ String version,
|
|
|
+ String action,
|
|
|
+ Supplier<String> bodySupplier,
|
|
|
+ String region,
|
|
|
+ String failMsg) {
|
|
|
+ try {
|
|
|
+ // 构建请求
|
|
|
+ Request request = buildRequest(host, SECRET_ID, SECRET_KEY, version, action, bodySupplier.get(), region, "");
|
|
|
+ Response response = client.newCall(request).execute();
|
|
|
+
|
|
|
+ // 处理响应
|
|
|
+ if (response.isSuccessful() && null != response.body()) {
|
|
|
+ String respBody = response.body().string();
|
|
|
+ log.info("审核结果:{}", respBody);
|
|
|
+ ResponseResult responseResult = JSONObject.parseObject(respBody, ResponseResult.class);
|
|
|
+
|
|
|
+ // 判断审核结果
|
|
|
+ if (responseResult.getResponse().getSuggestion().equals(SUGGESTION_PASS)) {
|
|
|
+ return R.ok().put("success", true);
|
|
|
+ } else {
|
|
|
+ Map<String, Object> checkDetail = new HashMap<>();
|
|
|
+ checkDetail.put("Suggestion", SUGGESTION_MAP.get(responseResult.getResponse().getSuggestion()));
|
|
|
+ checkDetail.put("Type", LABEL_MAP.get(responseResult.getResponse().getLabel()));
|
|
|
+ return R.error(failMsg)
|
|
|
+ .put("checkDetail", checkDetail)
|
|
|
+ .put("success", false);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return R.error(failMsg.replace("未通过", "失败") + "!!")
|
|
|
+ .put("success", false);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("{}:", failMsg.replace("未通过", "失败"), e);
|
|
|
+ return R.error(e.getMessage())
|
|
|
+ .put("success", false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建请求信息
|
|
|
+ *
|
|
|
+ * @param host 主机信息
|
|
|
+ * @param secretId 密钥id
|
|
|
+ * @param secretKey 密钥key
|
|
|
+ * @param version api版本
|
|
|
+ * @param action 公共参数
|
|
|
+ * @param body 请求体
|
|
|
+ * @param region 地区
|
|
|
+ * @param token 令牌
|
|
|
+ */
|
|
|
+ public static Request buildRequest(
|
|
|
+ String host, String secretId, String secretKey,
|
|
|
+ String version, String action,
|
|
|
+ String body, String region, String token
|
|
|
+ ) throws NoSuchAlgorithmException, InvalidKeyException {
|
|
|
+ String endpoint = "https://" + host;
|
|
|
+ String contentType = "application/json; charset=utf-8";
|
|
|
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
|
|
+ String auth = getAuth(secretId, secretKey, host, contentType, timestamp, body);
|
|
|
+ return new Request.Builder()
|
|
|
+ .header("Host", host)
|
|
|
+ .header("X-TC-Timestamp", timestamp)
|
|
|
+ .header("X-TC-Version", version)
|
|
|
+ .header("X-TC-Action", action)
|
|
|
+ .header("X-TC-Region", region)
|
|
|
+ .header("X-TC-Token", token)
|
|
|
+ .header("X-TC-RequestClient", "SDK_JAVA_BAREBONE")
|
|
|
+ .header("Authorization", auth)
|
|
|
+ .url(endpoint)
|
|
|
+ .post(RequestBody.create(MediaType.parse(contentType), body))
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取签名
|
|
|
+ *
|
|
|
+ * @param secretId 密钥id
|
|
|
+ * @param secretKey 密钥key
|
|
|
+ * @param host 主机信息
|
|
|
+ * @param contentType 内容类型
|
|
|
+ * @param timestamp 时间戳
|
|
|
+ * @param body 加密内容
|
|
|
+ */
|
|
|
+ private static String getAuth(
|
|
|
+ String secretId, String secretKey, String host, String contentType,
|
|
|
+ String timestamp, String body
|
|
|
+ ) throws NoSuchAlgorithmException, InvalidKeyException {
|
|
|
+ String canonicalUri = "/";
|
|
|
+ String canonicalQueryString = "";
|
|
|
+ String canonicalHeaders = "content-type:" + contentType + "\nhost:" + host + "\n";
|
|
|
+ String signedHeaders = "content-type;host";
|
|
|
+ String hashedRequestPayload = sha256Hex(body.getBytes(StandardCharsets.UTF_8));
|
|
|
+ String canonicalRequest = "POST"
|
|
|
+ + "\n"
|
|
|
+ + canonicalUri
|
|
|
+ + "\n"
|
|
|
+ + canonicalQueryString
|
|
|
+ + "\n"
|
|
|
+ + canonicalHeaders
|
|
|
+ + "\n"
|
|
|
+ + signedHeaders
|
|
|
+ + "\n"
|
|
|
+ + hashedRequestPayload;
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
|
+ String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
|
|
|
+ String service = host.split("\\.")[0];
|
|
|
+ String credentialScope = date + "/" + service + "/" + "tc3_request";
|
|
|
+ String hashedCanonicalRequest =
|
|
|
+ sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
|
|
|
+ String stringToSign =
|
|
|
+ "TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
|
|
|
+ byte[] secretDate = hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
|
|
|
+ byte[] secretService = hmac256(secretDate, service);
|
|
|
+ byte[] secretSigning = hmac256(secretService, "tc3_request");
|
|
|
+ String signature =
|
|
|
+ printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
|
|
|
+ return "TC3-HMAC-SHA256 "
|
|
|
+ + "Credential="
|
|
|
+ + secretId
|
|
|
+ + "/"
|
|
|
+ + credentialScope
|
|
|
+ + ", "
|
|
|
+ + "SignedHeaders="
|
|
|
+ + signedHeaders
|
|
|
+ + ", "
|
|
|
+ + "Signature="
|
|
|
+ + signature;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+
|
|
|
+ */
|
|
|
+ public static String sha256Hex(byte[] b) throws NoSuchAlgorithmException {
|
|
|
+ MessageDigest md;
|
|
|
+ md = MessageDigest.getInstance("SHA-256");
|
|
|
+ byte[] d = md.digest(b);
|
|
|
+ return printHexBinary(d).toLowerCase();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String printHexBinary(byte[] data) {
|
|
|
+ StringBuilder r = new StringBuilder(data.length * 2);
|
|
|
+ for (byte b : data) {
|
|
|
+ r.append(HEX_CODE[(b >> 4) & 0xF]);
|
|
|
+ r.append(HEX_CODE[(b & 0xF)]);
|
|
|
+ }
|
|
|
+ return r.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static byte[] hmac256(byte[] key, String msg) throws NoSuchAlgorithmException, InvalidKeyException {
|
|
|
+ Mac mac = Mac.getInstance("HmacSHA256");
|
|
|
+ SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
|
|
|
+ mac.init(secretKeySpec);
|
|
|
+ return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Data
|
|
|
+ public static class ResponseResult {
|
|
|
+
|
|
|
+ private Integer retcode;
|
|
|
+
|
|
|
+ private String retmsg;
|
|
|
+
|
|
|
+ private CheckResponse Response;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Data
|
|
|
+ public static class CheckResponse {
|
|
|
+
|
|
|
+ private String RequestId;
|
|
|
+
|
|
|
+ private String BizType;
|
|
|
+
|
|
|
+ private String Label;
|
|
|
+
|
|
|
+ private String Suggestion;
|
|
|
+
|
|
|
+ }
|
|
|
+}
|