|
@@ -0,0 +1,408 @@
|
|
|
|
|
+package com.zhentao.controller;
|
|
|
|
|
+
|
|
|
|
|
+import com.zhentao.common.Result;
|
|
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
+import org.springframework.core.io.ByteArrayResource;
|
|
|
|
|
+import org.springframework.http.*;
|
|
|
|
|
+import org.springframework.util.LinkedMultiValueMap;
|
|
|
|
|
+import org.springframework.util.MultiValueMap;
|
|
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
|
|
+import java.io.InputStream;
|
|
|
|
|
+import java.net.HttpURLConnection;
|
|
|
|
|
+import java.net.URL;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.regex.Pattern;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 内容安全检测控制器
|
|
|
|
|
+ * 用于检测用户发布的文字、图片是否包含违规内容
|
|
|
|
|
+ * 集成微信小程序内容安全API
|
|
|
|
|
+ */
|
|
|
|
|
+@RestController
|
|
|
|
|
+@RequestMapping("/content-security")
|
|
|
|
|
+public class ContentSecurityController {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(ContentSecurityController.class);
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${wechat.miniapp.appid:}")
|
|
|
|
|
+ private String appId;
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${wechat.miniapp.secret:}")
|
|
|
|
|
+ private String appSecret;
|
|
|
|
|
+
|
|
|
|
|
+ private final RestTemplate restTemplate = new RestTemplate();
|
|
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
+
|
|
|
|
|
+ private String cachedAccessToken;
|
|
|
|
|
+ private long tokenExpireTime = 0;
|
|
|
|
|
+
|
|
|
|
|
+ private static final List<String> SENSITIVE_WORDS = Arrays.asList(
|
|
|
|
|
+ "色情", "裸体", "性爱", "约炮", "一夜情", "援交", "卖淫", "嫖娼", "小姐服务",
|
|
|
|
|
+ "做爱", "性交", "口交", "肛交", "自慰", "手淫", "阴茎", "阴道", "乳房",
|
|
|
|
|
+ "傻逼", "操你", "草泥马", "妈的", "他妈的", "狗日的", "王八蛋", "贱人", "婊子",
|
|
|
|
|
+ "滚蛋", "去死", "废物", "垃圾", "白痴", "智障",
|
|
|
|
|
+ "赌博", "毒品", "枪支", "炸弹", "恐怖", "暴力", "杀人", "自杀",
|
|
|
|
|
+ "刷单", "兼职日结", "高额返利", "免费领取", "中奖", "彩票内幕", "稳赚不赔",
|
|
|
|
|
+ "法轮功", "邪教"
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ private static final List<Pattern> SENSITIVE_PATTERNS = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ static {
|
|
|
|
|
+ for (String word : SENSITIVE_WORDS) {
|
|
|
|
|
+ StringBuilder patternStr = new StringBuilder();
|
|
|
|
|
+ for (int i = 0; i < word.length(); i++) {
|
|
|
|
|
+ if (i > 0) {
|
|
|
|
|
+ patternStr.append("[\\s\\*\\-\\_\\.]*");
|
|
|
|
|
+ }
|
|
|
|
|
+ patternStr.append(Pattern.quote(String.valueOf(word.charAt(i))));
|
|
|
|
|
+ }
|
|
|
|
|
+ SENSITIVE_PATTERNS.add(Pattern.compile(patternStr.toString(), Pattern.CASE_INSENSITIVE));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检测文字内容是否安全
|
|
|
|
|
+ */
|
|
|
|
|
+ @PostMapping("/check-text")
|
|
|
|
|
+ public Result<Map<String, Object>> checkText(@RequestBody Map<String, String> request) {
|
|
|
|
|
+ String content = request.get("content");
|
|
|
|
|
+
|
|
|
|
|
+ if (content == null || content.trim().isEmpty()) {
|
|
|
|
|
+ return Result.success(createSafeResult());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String localCheckResult = localTextCheck(content);
|
|
|
|
|
+ if (localCheckResult != null) {
|
|
|
|
|
+ logger.info("本地敏感词检测不通过: {}", localCheckResult);
|
|
|
|
|
+ return Result.success(createUnsafeResult("内容包含敏感词,请修改后重试"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, Object> wxResult = wxMsgSecCheck(content);
|
|
|
|
|
+ if (wxResult != null && Boolean.FALSE.equals(wxResult.get("safe"))) {
|
|
|
|
|
+ logger.info("微信内容安全检测不通过");
|
|
|
|
|
+ return Result.success(createUnsafeResult((String) wxResult.get("message")));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("微信内容安全检测异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return Result.success(createSafeResult());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 检测图片是否安全(同步检测)
|
|
|
|
|
+ */
|
|
|
|
|
+ @PostMapping("/check-image")
|
|
|
|
|
+ public Result<Map<String, Object>> checkImage(@RequestBody Map<String, String> request) {
|
|
|
|
|
+ String imageUrl = request.get("imageUrl");
|
|
|
|
|
+
|
|
|
|
|
+ if (imageUrl == null || imageUrl.trim().isEmpty()) {
|
|
|
|
|
+ return Result.success(createSafeResult());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("开始检测图片: {}", imageUrl);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 下载图片并调用微信图片安全检测API
|
|
|
|
|
+ Map<String, Object> wxResult = wxImgSecCheck(imageUrl);
|
|
|
|
|
+ if (wxResult != null && Boolean.FALSE.equals(wxResult.get("safe"))) {
|
|
|
|
|
+ logger.info("微信图片安全检测不通过: {}", imageUrl);
|
|
|
|
|
+ return Result.success(createUnsafeResult((String) wxResult.get("message")));
|
|
|
|
|
+ }
|
|
|
|
|
+ logger.info("图片检测通过: {}", imageUrl);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("微信图片安全检测异常: {}", imageUrl, e);
|
|
|
|
|
+ // 异常时默认通过,由人工审核
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return Result.success(createSafeResult());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 批量检测图片
|
|
|
|
|
+ */
|
|
|
|
|
+ @PostMapping("/check-images")
|
|
|
|
|
+ public Result<Map<String, Object>> checkImages(@RequestBody Map<String, Object> request) {
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ List<String> imageUrls = (List<String>) request.get("imageUrls");
|
|
|
|
|
+
|
|
|
|
|
+ if (imageUrls == null || imageUrls.isEmpty()) {
|
|
|
|
|
+ return Result.success(createSafeResult());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < imageUrls.size(); i++) {
|
|
|
|
|
+ String imageUrl = imageUrls.get(i);
|
|
|
|
|
+ if (imageUrl == null || imageUrl.trim().isEmpty()) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, Object> wxResult = wxImgSecCheck(imageUrl);
|
|
|
|
|
+ if (wxResult != null && Boolean.FALSE.equals(wxResult.get("safe"))) {
|
|
|
|
|
+ Map<String, Object> result = createUnsafeResult("第" + (i + 1) + "张图片包含违规内容,请更换后重试");
|
|
|
|
|
+ result.put("failedIndex", i);
|
|
|
|
|
+ return Result.success(result);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("检测第{}张图片异常: {}", i + 1, imageUrl, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return Result.success(createSafeResult());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String localTextCheck(String content) {
|
|
|
|
|
+ String lowerContent = content.toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ for (String word : SENSITIVE_WORDS) {
|
|
|
|
|
+ if (lowerContent.contains(word.toLowerCase())) {
|
|
|
|
|
+ return word;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < SENSITIVE_PATTERNS.size(); i++) {
|
|
|
|
|
+ if (SENSITIVE_PATTERNS.get(i).matcher(content).find()) {
|
|
|
|
|
+ return SENSITIVE_WORDS.get(i);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 调用微信文字内容安全检测API
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, Object> wxMsgSecCheck(String content) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String accessToken = getAccessToken();
|
|
|
|
|
+ if (accessToken == null || accessToken.isEmpty()) {
|
|
|
|
|
+ logger.warn("获取access_token失败,跳过微信内容检测");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String url = "https://api.weixin.qq.com/wxa/msg_sec_check?access_token=" + accessToken;
|
|
|
|
|
+
|
|
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> body = new HashMap<>();
|
|
|
|
|
+ body.put("content", content);
|
|
|
|
|
+ body.put("version", 2);
|
|
|
|
|
+ body.put("scene", 2);
|
|
|
|
|
+
|
|
|
|
|
+ HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);
|
|
|
|
|
+ ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
|
|
|
|
|
+
|
|
|
|
|
+ if (response.getStatusCode() == HttpStatus.OK) {
|
|
|
|
|
+ JsonNode jsonNode = objectMapper.readTree(response.getBody());
|
|
|
|
|
+ int errcode = jsonNode.has("errcode") ? jsonNode.get("errcode").asInt() : 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (errcode == 0) {
|
|
|
|
|
+ JsonNode result = jsonNode.get("result");
|
|
|
|
|
+ if (result != null) {
|
|
|
|
|
+ String suggest = result.has("suggest") ? result.get("suggest").asText() : "pass";
|
|
|
|
|
+ if ("risky".equals(suggest)) {
|
|
|
|
|
+ String label = result.has("label") ? result.get("label").asText() : "违规内容";
|
|
|
|
|
+ return createUnsafeResult("内容包含" + getLabelText(label) + ",请修改后重试");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return createSafeResult();
|
|
|
|
|
+ } else if (errcode == 87014) {
|
|
|
|
|
+ return createUnsafeResult("内容包含违规信息,请修改后重试");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logger.warn("微信内容检测返回错误码: {}", errcode);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("调用微信文字内容安全API异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 调用微信图片内容安全检测API(同步,通过上传图片文件)
|
|
|
|
|
+ * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/sec-center/sec-check/imgSecCheck.html
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, Object> wxImgSecCheck(String imageUrl) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String accessToken = getAccessToken();
|
|
|
|
|
+ if (accessToken == null || accessToken.isEmpty()) {
|
|
|
|
|
+ logger.warn("获取access_token失败,跳过微信图片检测");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 下载图片
|
|
|
|
|
+ byte[] imageBytes = downloadImage(imageUrl);
|
|
|
|
|
+ if (imageBytes == null || imageBytes.length == 0) {
|
|
|
|
|
+ logger.warn("下载图片失败: {}", imageUrl);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查图片大小(微信限制1MB)
|
|
|
|
|
+ if (imageBytes.length > 1024 * 1024) {
|
|
|
|
|
+ logger.warn("图片大小超过1MB,跳过微信检测: {} bytes", imageBytes.length);
|
|
|
|
|
+ // 大图片跳过微信检测,由人工审核
|
|
|
|
|
+ return createSafeResult();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 上传到微信进行检测
|
|
|
|
|
+ String url = "https://api.weixin.qq.com/wxa/img_sec_check?access_token=" + accessToken;
|
|
|
|
|
+
|
|
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
|
|
+ headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建文件资源
|
|
|
|
|
+ ByteArrayResource fileResource = new ByteArrayResource(imageBytes) {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public String getFilename() {
|
|
|
|
|
+ return "image.jpg";
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
|
|
|
+ body.add("media", fileResource);
|
|
|
|
|
+
|
|
|
|
|
+ HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
|
|
|
|
+ ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("微信图片检测响应: {}", response.getBody());
|
|
|
|
|
+
|
|
|
|
|
+ if (response.getStatusCode() == HttpStatus.OK) {
|
|
|
|
|
+ JsonNode jsonNode = objectMapper.readTree(response.getBody());
|
|
|
|
|
+ int errcode = jsonNode.has("errcode") ? jsonNode.get("errcode").asInt() : 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (errcode == 0) {
|
|
|
|
|
+ // 检测通过
|
|
|
|
|
+ return createSafeResult();
|
|
|
|
|
+ } else if (errcode == 87014) {
|
|
|
|
|
+ // 图片含有违法违规内容
|
|
|
|
|
+ return createUnsafeResult("图片包含违规内容,请更换后重试");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logger.warn("微信图片检测返回错误码: {}, errmsg: {}",
|
|
|
|
|
+ errcode, jsonNode.has("errmsg") ? jsonNode.get("errmsg").asText() : "");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("调用微信图片内容安全API异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 下载图片
|
|
|
|
|
+ */
|
|
|
|
|
+ private byte[] downloadImage(String imageUrl) {
|
|
|
|
|
+ HttpURLConnection connection = null;
|
|
|
|
|
+ InputStream inputStream = null;
|
|
|
|
|
+ ByteArrayOutputStream outputStream = null;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ URL url = new URL(imageUrl);
|
|
|
|
|
+ connection = (HttpURLConnection) url.openConnection();
|
|
|
|
|
+ connection.setRequestMethod("GET");
|
|
|
|
|
+ connection.setConnectTimeout(10000);
|
|
|
|
|
+ connection.setReadTimeout(30000);
|
|
|
|
|
+ connection.setRequestProperty("User-Agent", "Mozilla/5.0");
|
|
|
|
|
+
|
|
|
|
|
+ int responseCode = connection.getResponseCode();
|
|
|
|
|
+ if (responseCode != HttpURLConnection.HTTP_OK) {
|
|
|
|
|
+ logger.warn("下载图片失败,HTTP状态码: {}", responseCode);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ inputStream = connection.getInputStream();
|
|
|
|
|
+ outputStream = new ByteArrayOutputStream();
|
|
|
|
|
+
|
|
|
|
|
+ byte[] buffer = new byte[4096];
|
|
|
|
|
+ int bytesRead;
|
|
|
|
|
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
|
|
|
+ outputStream.write(buffer, 0, bytesRead);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return outputStream.toByteArray();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("下载图片异常: {}", imageUrl, e);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (inputStream != null) inputStream.close();
|
|
|
|
|
+ if (outputStream != null) outputStream.close();
|
|
|
|
|
+ if (connection != null) connection.disconnect();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // ignore
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private synchronized String getAccessToken() {
|
|
|
|
|
+ if (cachedAccessToken != null && System.currentTimeMillis() < tokenExpireTime) {
|
|
|
|
|
+ return cachedAccessToken;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (appId == null || appId.isEmpty() || appSecret == null || appSecret.isEmpty()) {
|
|
|
|
|
+ logger.warn("微信小程序appId或appSecret未配置");
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ String url = String.format(
|
|
|
|
|
+ "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
|
|
|
|
|
+ appId, appSecret
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
|
|
|
|
+ if (response.getStatusCode() == HttpStatus.OK) {
|
|
|
|
|
+ JsonNode jsonNode = objectMapper.readTree(response.getBody());
|
|
|
|
|
+ if (jsonNode.has("access_token")) {
|
|
|
|
|
+ cachedAccessToken = jsonNode.get("access_token").asText();
|
|
|
|
|
+ int expiresIn = jsonNode.has("expires_in") ? jsonNode.get("expires_in").asInt() : 7200;
|
|
|
|
|
+ tokenExpireTime = System.currentTimeMillis() + (expiresIn - 300) * 1000L;
|
|
|
|
|
+ return cachedAccessToken;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logger.error("获取access_token失败: {}", response.getBody());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("获取access_token异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String getLabelText(String label) {
|
|
|
|
|
+ Map<String, String> labelMap = new HashMap<>();
|
|
|
|
|
+ labelMap.put("100", "违规内容");
|
|
|
|
|
+ labelMap.put("10001", "广告内容");
|
|
|
|
|
+ labelMap.put("20001", "时政内容");
|
|
|
|
|
+ labelMap.put("20002", "色情内容");
|
|
|
|
|
+ labelMap.put("20003", "辱骂内容");
|
|
|
|
|
+ labelMap.put("20006", "违法犯罪内容");
|
|
|
|
|
+ labelMap.put("20008", "欺诈内容");
|
|
|
|
|
+ labelMap.put("20012", "低俗内容");
|
|
|
|
|
+ labelMap.put("20013", "版权内容");
|
|
|
|
|
+ labelMap.put("21000", "其他违规内容");
|
|
|
|
|
+ return labelMap.getOrDefault(label, "违规内容");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> createSafeResult() {
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
+ result.put("safe", true);
|
|
|
|
|
+ result.put("message", "");
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, Object> createUnsafeResult(String message) {
|
|
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
|
|
+ result.put("safe", false);
|
|
|
|
|
+ result.put("message", message);
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|