111
This commit is contained in:
1 parent
6b0b2a8916
commit
befc75593d
15 files changed
+381
-45
No files matched your search
+37
-4
@@ -4,6 +4,8 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
|
||||
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.code.BrokerageCodePageReqVO;
|
||||
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.code.BrokerageCodeSaveReqVO;
|
||||
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageCodeDO;
|
||||
@@ -11,6 +13,8 @@ import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageCodeMapper;
|
||||
import cn.iocoder.yudao.module.trade.framework.web.config.TradeBrokerageConfiguration;
|
||||
import cn.iocoder.yudao.module.trade.util.PosterPicGen;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -34,6 +38,8 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.BROKERAGE_C
|
||||
@Validated
|
||||
public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
|
||||
private static final String BROKERAGE_CODE_GENERATE_LOCK_KEY = "trade:brokerage-code:generate:";
|
||||
|
||||
@Resource
|
||||
private TradeBrokerageConfiguration brokerageConfiguration;
|
||||
|
||||
@@ -42,6 +48,10 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
|
||||
@Resource
|
||||
private FileApi fileApi;
|
||||
@Resource
|
||||
private MemberUserApi memberUserApi;
|
||||
@Resource
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
public Long createBrokerageCode(BrokerageCodeSaveReqVO createReqVO) {
|
||||
@@ -123,6 +133,21 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
* 生成用户的分销码
|
||||
*/
|
||||
private BrokerageCodeDO generateCode(Long userId) {
|
||||
RLock lock = redissonClient.getLock(BROKERAGE_CODE_GENERATE_LOCK_KEY + userId);
|
||||
lock.lock();
|
||||
try {
|
||||
// 加锁期间其他请求可能已生成,避免重复生成海报
|
||||
BrokerageCodeDO existingCode = brokerageCodeMapper.selectOne(BrokerageCodeDO::getMemberId, userId);
|
||||
if (ObjectUtil.isNotEmpty(existingCode)) {
|
||||
return existingCode;
|
||||
}
|
||||
return doGenerateCode(userId);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private BrokerageCodeDO doGenerateCode(Long userId) {
|
||||
boolean isRepet = true;
|
||||
String code = null;
|
||||
while (isRepet) {
|
||||
@@ -130,8 +155,16 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
BrokerageCodeDO brokerageCode = brokerageCodeMapper.selectOne(BrokerageCodeDO::getCode, code);
|
||||
isRepet = ObjectUtil.isNotEmpty(brokerageCode);
|
||||
}
|
||||
|
||||
// 查询用户获取用户头像
|
||||
String userAvatar = "";
|
||||
MemberUserRespDTO user = memberUserApi.getUser(userId);
|
||||
if (ObjectUtil.isNotEmpty(user) && ObjectUtil.isNotEmpty(user)) {
|
||||
userAvatar = user.getAvatar();
|
||||
}
|
||||
|
||||
String url = generatePosterUrl(code, userId);
|
||||
BrokerageCodeDO saveObj = BrokerageCodeDO.builder().memberId(userId).code(code).posterPic(generatePosterPic(code, url)).url(url).build();
|
||||
BrokerageCodeDO saveObj = BrokerageCodeDO.builder().memberId(userId).code(code).posterPic(generatePosterPic(userAvatar, code, url)).url(url).build();
|
||||
brokerageCodeMapper.insert(saveObj);
|
||||
return saveObj;
|
||||
}
|
||||
@@ -139,9 +172,9 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
/**
|
||||
* 生成分销海报图
|
||||
*/
|
||||
private String generatePosterPic(String code, String qrCodeUrl) {
|
||||
private String generatePosterPic(String userAvatar, String code, String qrCodeUrl) {
|
||||
try {
|
||||
byte[] fileInputStream = new PosterPicGen().setCode(code).setQrCodeUrl(qrCodeUrl).gen();
|
||||
byte[] fileInputStream = new PosterPicGen().setUserAvatar(userAvatar).setCode(code).setQrCodeUrl(qrCodeUrl).gen();
|
||||
String file = fileApi.createFile(fileInputStream);
|
||||
return file;
|
||||
} catch (Exception e) {
|
||||
@@ -155,4 +188,4 @@ public class BrokerageCodeServiceImpl implements BrokerageCodeService {
|
||||
return brokerageConfiguration.getDownloadUrl() + "?code=" + code + "&userId=" + userId;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -120,7 +120,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
|
||||
TradeConfigDO memberConfig = tradeConfigService.getTradeConfig();
|
||||
// 0 未启用分销功能
|
||||
if (memberConfig == null || !BooleanUtil.isTrue(memberConfig.getBrokerageEnabled())) {
|
||||
log.error("[addBrokerage][增加佣金失败:brokerageEnabled 未配置,userId({}) bizType({}) list({})", userId, bizType, list);
|
||||
log.error("[addRecentBrokerage][增加佣金失败:brokerageEnabled 未配置,userId({}) bizType({}) list({})", userId, bizType, list);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+118
-3
@@ -4,12 +4,18 @@ package cn.iocoder.yudao.module.trade.util;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.extra.qrcode.QrCodeUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 生成海报
|
||||
@@ -17,6 +23,18 @@ import java.io.ByteArrayOutputStream;
|
||||
@Slf4j
|
||||
public class PosterPicGen {
|
||||
|
||||
private static final int AVATAR_X = 20;
|
||||
|
||||
private static final int AVATAR_Y = 40;
|
||||
|
||||
private static final int AVATAR_SIZE = 48;
|
||||
|
||||
private static final int AVATAR_CORNER_RADIUS = 14;
|
||||
|
||||
private static final int AVATAR_DOWNLOAD_TIMEOUT = 5_000;
|
||||
|
||||
private static final int MAX_AVATAR_FILE_SIZE = 5 * 1024 * 1024;
|
||||
|
||||
private Graphics2D graphics;
|
||||
|
||||
private int pageWidth = 375;
|
||||
@@ -31,6 +49,8 @@ public class PosterPicGen {
|
||||
|
||||
private String qrCodeUrl = "";
|
||||
|
||||
private String userAvatar = "";
|
||||
|
||||
private String bottomDescription = "微信无法打开的用户请更换为其他方式扫码";
|
||||
|
||||
public PosterPicGen setCode(String code) {
|
||||
@@ -38,6 +58,11 @@ public class PosterPicGen {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PosterPicGen setUserAvatar(String userAvatar) {
|
||||
this.userAvatar = userAvatar;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PosterPicGen
|
||||
|
||||
setQrCodeUrl(String qrCodeUrl) {
|
||||
@@ -51,6 +76,8 @@ public class PosterPicGen {
|
||||
baseImage.getScaledInstance(this.pageWidth, this.pageHeight, Image.SCALE_SMOOTH);
|
||||
graphics = baseImage.createGraphics();
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
// 替换 SVG 左上角 48 x 48、圆角 14px 的默认头像
|
||||
drawUserAvatar();
|
||||
// 设置 title
|
||||
graphics.setFont(new Font("PingFang SC, Microsoft YaHei, sans-serif", Font.BOLD, 18));
|
||||
graphics.setColor(Color.decode("#333333"));
|
||||
@@ -72,9 +99,6 @@ public class PosterPicGen {
|
||||
int y = 350;
|
||||
graphics.drawImage(generate, x, y, qrWidth, qrHeight, null);
|
||||
}
|
||||
|
||||
graphics.dispose();
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(baseImage, "png", baos);
|
||||
return baos.toByteArray();
|
||||
@@ -82,6 +106,97 @@ public class PosterPicGen {
|
||||
e.printStackTrace();
|
||||
log.error("|生成海报|,意外的错误error:|{}|", e.getMessage());
|
||||
return null;
|
||||
} finally {
|
||||
if (graphics != null) {
|
||||
graphics.dispose();
|
||||
graphics = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawUserAvatar() {
|
||||
if (ObjectUtil.isEmpty(this.userAvatar)) {
|
||||
return;
|
||||
}
|
||||
BufferedImage avatar = loadUserAvatar();
|
||||
if (avatar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 对应 SVG 的 preserveAspectRatio="xMidYMid slice",居中裁剪后再缩放,避免头像变形
|
||||
int sourceSize = Math.min(avatar.getWidth(), avatar.getHeight());
|
||||
int sourceX = (avatar.getWidth() - sourceSize) / 2;
|
||||
int sourceY = (avatar.getHeight() - sourceSize) / 2;
|
||||
BufferedImage roundedAvatar = new BufferedImage(AVATAR_SIZE, AVATAR_SIZE, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D avatarGraphics = roundedAvatar.createGraphics();
|
||||
try {
|
||||
avatarGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
avatarGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
avatarGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
avatarGraphics.setColor(Color.WHITE);
|
||||
avatarGraphics.fill(new RoundRectangle2D.Double(0, 0, AVATAR_SIZE, AVATAR_SIZE,
|
||||
AVATAR_CORNER_RADIUS * 2.0, AVATAR_CORNER_RADIUS * 2.0));
|
||||
avatarGraphics.setComposite(AlphaComposite.SrcAtop);
|
||||
avatarGraphics.drawImage(avatar,
|
||||
0, 0, AVATAR_SIZE, AVATAR_SIZE,
|
||||
sourceX, sourceY, sourceX + sourceSize, sourceY + sourceSize, null);
|
||||
} finally {
|
||||
avatarGraphics.dispose();
|
||||
}
|
||||
graphics.drawImage(roundedAvatar, AVATAR_X, AVATAR_Y, null);
|
||||
}
|
||||
|
||||
private BufferedImage loadUserAvatar() {
|
||||
try (HttpResponse response = HttpRequest.get(this.userAvatar)
|
||||
.setConnectionTimeout(AVATAR_DOWNLOAD_TIMEOUT)
|
||||
.setReadTimeout(AVATAR_DOWNLOAD_TIMEOUT)
|
||||
.setMaxRedirectCount(3)
|
||||
.executeAsync()) {
|
||||
if (!response.isOk()) {
|
||||
log.warn("|生成海报-加载用户头像失败|, HTTP status:|{}|", response.getStatus());
|
||||
return null;
|
||||
}
|
||||
byte[] avatarBytes = readAvatarBytes(response);
|
||||
if (avatarBytes == null) {
|
||||
return null;
|
||||
}
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(avatarBytes)) {
|
||||
BufferedImage avatar = ImageIO.read(inputStream);
|
||||
if (avatar == null) {
|
||||
log.warn("|生成海报-加载用户头像失败|, 图片格式无法识别");
|
||||
}
|
||||
return avatar;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("|生成海报-加载用户头像失败|, error:|{}|", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readAvatarBytes(HttpResponse response) throws IOException {
|
||||
long contentLength = response.contentLength();
|
||||
if (contentLength > MAX_AVATAR_FILE_SIZE) {
|
||||
log.warn("|生成海报-加载用户头像失败|, 图片文件超过 {} 字节", MAX_AVATAR_FILE_SIZE);
|
||||
return null;
|
||||
}
|
||||
int initialSize = contentLength > 0 ? (int) contentLength : 8192;
|
||||
try (InputStream inputStream = response.bodyStream();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(initialSize)) {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] buffer = new byte[8192];
|
||||
int totalSize = 0;
|
||||
int readSize;
|
||||
while ((readSize = inputStream.read(buffer)) != -1) {
|
||||
totalSize += readSize;
|
||||
if (totalSize > MAX_AVATAR_FILE_SIZE) {
|
||||
log.warn("|生成海报-加载用户头像失败|, 图片文件超过 {} 字节", MAX_AVATAR_FILE_SIZE);
|
||||
return null;
|
||||
}
|
||||
outputStream.write(buffer, 0, readSize);
|
||||
}
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -74,8 +74,8 @@ public interface MemberErrorCode {
|
||||
ErrorCode PHOTO_TYPE_NOT_SUPPORT = new ErrorCode(1_004_013_001, "只有[{}]能删除");
|
||||
|
||||
//========== 审核管理 1-004-014-000 ==========
|
||||
ErrorCode CHECK_TEXT_NOT_PASS = new ErrorCode(1_004_014_001, "文字审核不通过,[{}]");
|
||||
ErrorCode CHECK_IMAGE_NOT_PASS = new ErrorCode(1_004_014_002, "图片审核不通过,[{}]");
|
||||
ErrorCode CHECK_TEXT_NOT_PASS = new ErrorCode(1_004_014_001, "文字审核不通过,{}");
|
||||
ErrorCode CHECK_IMAGE_NOT_PASS = new ErrorCode(1_004_014_002, "图片审核不通过,{}");
|
||||
|
||||
//========== 用户位置管理 1-004-015-000 ==========
|
||||
ErrorCode LOCATION_NOT_EXISTS = new ErrorCode(1_004_015_000, "用户位置不存在");
|
||||
|
||||
+2
-2
@@ -11,14 +11,14 @@ public final class ChatConstant {
|
||||
*/
|
||||
public static final Long COM_MESSAGE_USER_ID = -100L;
|
||||
public static final String COM_MESSAGE_USER_NAME = "消息通知";
|
||||
public static final String COM_MESSAGE_USER_AVATAR = "https://chatapi.zianxin.com/admin-api/infra/file/4/get/3dab7a0921d4d2ea6f7daee399114e9241145e754f7a1e4e1a955fada4542116.png";
|
||||
public static final String COM_MESSAGE_USER_AVATAR = "https://api.htysapp.com/admin-api/infra/file/4/get/d0c93df86b65174bc58513f1653bef14e6639ad64d7ee2fdc1e856e6aa2974f0.png";
|
||||
|
||||
/**
|
||||
* 客服用户id
|
||||
*/
|
||||
public static final Long COM_SERVICE_USER_ID = -200L;
|
||||
public static final String COM_SERVICE_USER_NAME = "荷塘月色客服";
|
||||
public static final String COM_SERVICE_USER_AVATAR = "https://chatapi.zianxin.com/admin-api/infra/file/4/get/b85e0bc2e4338c9919ac13e1f12cb3ac40dc7ba807a0e65ef83c822ef7d2f1a2.png";
|
||||
public static final String COM_SERVICE_USER_AVATAR = "https://api.htysapp.com/admin-api/infra/file/4/get/affa4cac924cb13fe5706100828685015d512bca7dfa0e26ef82422b31877abc.png";
|
||||
|
||||
public static final List<Long> IGNORE_USER_IDS = Arrays.asList(0L, COM_MESSAGE_USER_ID, COM_SERVICE_USER_ID);
|
||||
}
|
||||
+5
@@ -18,4 +18,9 @@ public interface ThirdAuthService {
|
||||
*/
|
||||
ThirdAuthVo imageAuditSync(String imageUrl);
|
||||
|
||||
/**
|
||||
* 视频审核-同步
|
||||
*/
|
||||
ThirdAuthVo videoAuditSync(String videoUrl);
|
||||
|
||||
}
|
||||
+136
-1
@@ -25,6 +25,11 @@ import java.util.*;
|
||||
@Slf4j
|
||||
public class ThirdAuthServiceImpl implements ThirdAuthService {
|
||||
|
||||
private static final int VIDEO_MODERATION_SUCCESS_CODE = 200;
|
||||
private static final int VIDEO_MODERATION_PROCESSING_CODE = 280;
|
||||
private static final int VIDEO_AUDIT_MAX_QUERY_COUNT = 20;
|
||||
private static final long VIDEO_AUDIT_QUERY_INTERVAL_MILLIS = 30_000L;
|
||||
|
||||
@Resource
|
||||
private ThirdAlConfig thirdAlConfig;
|
||||
|
||||
@@ -153,7 +158,7 @@ public class ThirdAuthServiceImpl implements ThirdAuthService {
|
||||
imageBatchModerationRequest.setServiceParameters(JSON.toJSONString(serviceParameters));
|
||||
ImageModerationResponse response = client.imageModerationWithOptions(imageBatchModerationRequest, runtime);
|
||||
if (response.getStatusCode() != 200) {
|
||||
throw new RuntimeException("文本审核失败code:" + response.getStatusCode());
|
||||
throw new RuntimeException("图片审核核失败code:" + response.getStatusCode());
|
||||
}
|
||||
ImageModerationResponseBody body = response.getBody();
|
||||
if (ObjectUtil.isEmpty(body)) {
|
||||
@@ -167,6 +172,25 @@ public class ThirdAuthServiceImpl implements ThirdAuthService {
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
throw new RuntimeException("result为null");
|
||||
}
|
||||
StringBuilder errorMsg = new StringBuilder();
|
||||
String firstErrorMsg = "你上传的图片";
|
||||
for (ImageModerationResponseBody.ImageModerationResponseBodyDataResult v : list) {
|
||||
// 检测结果
|
||||
String description = v.getDescription();
|
||||
// 置信分值
|
||||
Float confidence = v.getConfidence();
|
||||
if (confidence >= 80) {
|
||||
errorMsg.append(String.format("疑似%s,", description));
|
||||
}
|
||||
}
|
||||
// confidence最大的数据
|
||||
ImageModerationResponseBody.ImageModerationResponseBodyDataResult max = list.stream().max((pren, next) -> next.getConfidence().compareTo(pren.getConfidence())).orElse(null);
|
||||
if (ObjectUtil.isNotEmpty(max)) {
|
||||
firstErrorMsg = String.format("你发送的内容疑似%s,请重新上传", max.getDescription());
|
||||
}
|
||||
resultVo.setIsSuccess(false);
|
||||
resultVo.setErrorMsg(errorMsg.toString());
|
||||
resultVo.setFirstErrorMsg(firstErrorMsg);
|
||||
return resultVo;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -176,4 +200,115 @@ public class ThirdAuthServiceImpl implements ThirdAuthService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频审核-同步
|
||||
*/
|
||||
@Override
|
||||
public ThirdAuthVo videoAuditSync(String videoUrl) {
|
||||
ThirdAuthVo resultVo = new ThirdAuthVo();
|
||||
try {
|
||||
Client client = createClient();
|
||||
if (ObjectUtil.isEmpty(client)) return null;
|
||||
RuntimeOptions runtime = new RuntimeOptions();
|
||||
|
||||
Map<String, String> serviceParameters = new HashMap<>();
|
||||
serviceParameters.put("url", videoUrl);
|
||||
serviceParameters.put("dataId", String.valueOf(UUID.randomUUID()));
|
||||
serviceParameters.put("infoType", "customImage,textInImage,publicFigure");
|
||||
VideoModerationRequest request = new VideoModerationRequest();
|
||||
request.setService("videoDetection");
|
||||
request.setServiceParameters(JSON.toJSONString(serviceParameters));
|
||||
|
||||
VideoModerationResponse response = client.videoModerationWithOptions(request, runtime);
|
||||
if (response.getStatusCode() != VIDEO_MODERATION_SUCCESS_CODE) {
|
||||
throw new RuntimeException("视频审核任务提交失败code:" + response.getStatusCode());
|
||||
}
|
||||
VideoModerationResponseBody body = response.getBody();
|
||||
if (ObjectUtil.isEmpty(body)) {
|
||||
throw new RuntimeException("body为null");
|
||||
}
|
||||
if (!Objects.equals(body.getCode(), VIDEO_MODERATION_SUCCESS_CODE)) {
|
||||
throw new RuntimeException("视频审核任务提交失败code:" + body.getCode() + ",message:" + body.getMessage());
|
||||
}
|
||||
VideoModerationResponseBody.VideoModerationResponseBodyData data = body.getData();
|
||||
if (ObjectUtil.isEmpty(data) || ObjectUtil.isEmpty(data.getTaskId())) {
|
||||
throw new RuntimeException("taskId为null");
|
||||
}
|
||||
|
||||
Map<String, String> resultParameters = new HashMap<>();
|
||||
resultParameters.put("taskId", data.getTaskId());
|
||||
VideoModerationResultRequest resultRequest = new VideoModerationResultRequest();
|
||||
resultRequest.setService("videoDetection");
|
||||
resultRequest.setServiceParameters(JSON.toJSONString(resultParameters));
|
||||
|
||||
// 视频审核为异步任务,按官方建议每 30 秒轮询一次处理结果。
|
||||
for (int i = 0; i < VIDEO_AUDIT_MAX_QUERY_COUNT; i++) {
|
||||
VideoModerationResultResponse resultResponse = client.videoModerationResultWithOptions(resultRequest, runtime);
|
||||
if (resultResponse.getStatusCode() != VIDEO_MODERATION_SUCCESS_CODE) {
|
||||
throw new RuntimeException("视频审核结果查询失败code:" + resultResponse.getStatusCode());
|
||||
}
|
||||
VideoModerationResultResponseBody resultBody = resultResponse.getBody();
|
||||
if (ObjectUtil.isEmpty(resultBody)) {
|
||||
throw new RuntimeException("resultBody为null");
|
||||
}
|
||||
if (Objects.equals(resultBody.getCode(), VIDEO_MODERATION_PROCESSING_CODE)) {
|
||||
if (i < VIDEO_AUDIT_MAX_QUERY_COUNT - 1) {
|
||||
Thread.sleep(VIDEO_AUDIT_QUERY_INTERVAL_MILLIS);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!Objects.equals(resultBody.getCode(), VIDEO_MODERATION_SUCCESS_CODE)) {
|
||||
throw new RuntimeException("视频审核结果查询失败code:" + resultBody.getCode()
|
||||
+ ",message:" + resultBody.getMessage());
|
||||
}
|
||||
|
||||
VideoModerationResultResponseBody.VideoModerationResultResponseBodyData resultData = resultBody.getData();
|
||||
if (ObjectUtil.isEmpty(resultData)) {
|
||||
throw new RuntimeException("resultData为null");
|
||||
}
|
||||
if (successRiskLevelList.contains(resultData.getRiskLevel())) {
|
||||
resultVo.setIsSuccess(true);
|
||||
return resultVo;
|
||||
}
|
||||
|
||||
LinkedHashSet<String> descriptions = getVideoRiskDescriptions(resultData);
|
||||
String riskDescription = descriptions.isEmpty() ? "存在风险" : String.join(",", descriptions);
|
||||
resultVo.setIsSuccess(false);
|
||||
resultVo.setErrorMsg(String.format("疑似%s,请重新上传", riskDescription));
|
||||
resultVo.setFirstErrorMsg(String.format("你上传的视频疑似%s,请重新上传", riskDescription));
|
||||
return resultVo;
|
||||
}
|
||||
throw new RuntimeException("视频审核结果查询超时");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("|视频审核-同步|,线程被中断videoUrl:{}error:{}", videoUrl, e.getMessage(), e);
|
||||
resultVo.setIsSuccess(true);
|
||||
return resultVo;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("|视频审核-同步|,意外的错误videoUrl:{}error:{}", videoUrl, e.getMessage());
|
||||
resultVo.setIsSuccess(true);
|
||||
return resultVo;
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashSet<String> getVideoRiskDescriptions(
|
||||
VideoModerationResultResponseBody.VideoModerationResultResponseBodyData data) {
|
||||
LinkedHashSet<String> descriptions = new LinkedHashSet<>();
|
||||
if (ObjectUtil.isNotEmpty(data.getFrameResult())
|
||||
&& ObjectUtil.isNotEmpty(data.getFrameResult().getFrameSummarys())) {
|
||||
data.getFrameResult().getFrameSummarys().forEach(summary ->
|
||||
descriptions.add(ObjectUtil.isNotEmpty(summary.getDescription())
|
||||
? summary.getDescription() : summary.getLabel()));
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(data.getAudioResult())
|
||||
&& ObjectUtil.isNotEmpty(data.getAudioResult().getAudioSummarys())) {
|
||||
data.getAudioResult().getAudioSummarys().forEach(summary ->
|
||||
descriptions.add(ObjectUtil.isNotEmpty(summary.getDescription())
|
||||
? summary.getDescription() : summary.getLabel()));
|
||||
}
|
||||
descriptions.removeIf(ObjectUtil::isEmpty);
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -19,7 +19,6 @@ public class RecentSaveReqVO {
|
||||
private Long memberId;
|
||||
|
||||
@Schema(description = "主图", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotEmpty(message = "主图不能为空")
|
||||
private String coverPic;
|
||||
|
||||
@Schema(description = "视频")
|
||||
|
||||
+4
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.recent;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
@@ -53,6 +54,9 @@ public class AppRecentController {
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建朋友圈")
|
||||
public CommonResult<Long> createRecent(@Valid @RequestBody AppRecentSaveReqVO dto) {
|
||||
if (ObjectUtil.isAllEmpty(dto.getCoverVideo(), dto.getCoverPic())) {
|
||||
throw new RuntimeException("图片、视频必须上传其中之一");
|
||||
}
|
||||
dto.setMemberId(getLoginUserId());
|
||||
return success(recentService.createRecent(BeanUtils.toBean(dto, RecentSaveReqVO.class)));
|
||||
}
|
||||
|
||||
-1
@@ -14,7 +14,6 @@ public class AppRecentSaveReqVO {
|
||||
private Long memberId;
|
||||
|
||||
@Schema(description = "主图url")
|
||||
@NotEmpty(message = "主图不能为空")
|
||||
private String coverPic;
|
||||
|
||||
@Schema(description = "视频url")
|
||||
|
||||
+21
-2
@@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.member.enums.photo.PhotoResourceTypeEnum;
|
||||
import cn.iocoder.yudao.module.member.client.service.ThirdAuthService;
|
||||
import cn.iocoder.yudao.module.member.service.photo.BO.MemberPhotoTotalExtBO;
|
||||
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -26,6 +27,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.member.dal.mysql.photo.PhotoMapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
@@ -60,7 +62,7 @@ public class PhotoServiceImpl implements PhotoService {
|
||||
public Long appUpload(AppUploadReqVo dto) {
|
||||
// 校验图片
|
||||
if (ObjectUtil.equals(dto.getResourceType(), PhotoResourceTypeEnum.IMAGE)) {
|
||||
ThirdAuthVo textRes = thirdAuthService.imageAuditSync(dto.getAddress());
|
||||
ThirdAuthVo textRes = auditCommaSeparatedContent(dto.getAddress(), thirdAuthService::imageAuditSync);
|
||||
// 处理文字
|
||||
if (ObjectUtil.isEmpty(textRes)) {
|
||||
throw exception(MemberErrorCode.CHECK_TEXT_NOT_PASS, "未知错误");
|
||||
@@ -74,6 +76,23 @@ public class PhotoServiceImpl implements PhotoService {
|
||||
return photo.getId();
|
||||
}
|
||||
|
||||
private ThirdAuthVo auditCommaSeparatedContent(String content, Function<String, ThirdAuthVo> auditFunction) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return new ThirdAuthVo();
|
||||
}
|
||||
for (String item : StringUtils.split(content, ',')) {
|
||||
String auditContent = StringUtils.trim(item);
|
||||
if (StringUtils.isBlank(auditContent)) {
|
||||
continue;
|
||||
}
|
||||
ThirdAuthVo result = auditFunction.apply(auditContent);
|
||||
if (ObjectUtil.isEmpty(result) || result.isError()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return new ThirdAuthVo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePhoto(PhotoSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
@@ -164,4 +183,4 @@ public class PhotoServiceImpl implements PhotoService {
|
||||
public List<MemberPhotoTotalExtBO> getMemberPhotoTotalExtList(List<Long> memberId) {
|
||||
return photoMapper.getMemberPhotoExtList(memberId);
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-6
@@ -64,6 +64,7 @@ import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@@ -137,8 +138,12 @@ public class RecentServiceImpl implements RecentService {
|
||||
recentMapper.insert(recent);
|
||||
|
||||
// 文字和图片违规处理
|
||||
CompletableFuture<ThirdAuthVo> textFuture = CompletableFuture.supplyAsync(() -> thirdAuthService.textAuditSync(vo.getDescription()));
|
||||
CompletableFuture<ThirdAuthVo> imageFuture = CompletableFuture.supplyAsync(() -> thirdAuthService.imageAuditSync(vo.getCoverPic()));
|
||||
CompletableFuture<ThirdAuthVo> textFuture = CompletableFuture.supplyAsync(
|
||||
() -> auditCommaSeparatedContent(vo.getDescription(), thirdAuthService::textAuditSync)
|
||||
);
|
||||
CompletableFuture<ThirdAuthVo> imageFuture = CompletableFuture.supplyAsync(
|
||||
() -> auditCommaSeparatedContent(vo.getCoverPic(), thirdAuthService::imageAuditSync)
|
||||
);
|
||||
|
||||
try {
|
||||
ThirdAuthVo textRes = textFuture.get();
|
||||
@@ -155,10 +160,10 @@ public class RecentServiceImpl implements RecentService {
|
||||
throw exception(MemberErrorCode.CHECK_IMAGE_NOT_PASS, "未知错误");
|
||||
}
|
||||
if (imageRes.isError() && ObjectUtil.isNotEmpty(imageRes.getFirstErrorMsg())) {
|
||||
throw exception(MemberErrorCode.CHECK_TEXT_NOT_PASS, imageRes.getFirstErrorMsg());
|
||||
throw exception(MemberErrorCode.CHECK_IMAGE_NOT_PASS, imageRes.getFirstErrorMsg());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("|创建朋友圈|,以外的错误error:{}", e.getMessage());
|
||||
log.error("|创建朋友圈|,意外的错误error:{}", e.getMessage());
|
||||
throw exception(MemberErrorCode.EMPTY, e.getMessage());
|
||||
}
|
||||
|
||||
@@ -166,6 +171,23 @@ public class RecentServiceImpl implements RecentService {
|
||||
return recent.getId();
|
||||
}
|
||||
|
||||
private ThirdAuthVo auditCommaSeparatedContent(String content, Function<String, ThirdAuthVo> auditFunction) {
|
||||
if (StringUtils.isBlank(content)) {
|
||||
return new ThirdAuthVo();
|
||||
}
|
||||
for (String item : StringUtils.split(content, ',')) {
|
||||
String auditContent = StringUtils.trim(item);
|
||||
if (StringUtils.isBlank(auditContent)) {
|
||||
continue;
|
||||
}
|
||||
ThirdAuthVo result = auditFunction.apply(auditContent);
|
||||
if (ObjectUtil.isEmpty(result) || result.isError()) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return new ThirdAuthVo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 朋友圈数量统计
|
||||
*/
|
||||
@@ -642,7 +664,7 @@ public class RecentServiceImpl implements RecentService {
|
||||
return total;
|
||||
}
|
||||
// 当前用户`operateUserId`是否在3个小时前查看过`userid`的微信
|
||||
LocalDateTime time = LocalDateTime.now().minusHours(3);
|
||||
LocalDateTime time = LocalDateTime.now().plusHours(3);
|
||||
String timeStr = LocalDateTimeUtil.format(time, DatePattern.NORM_DATETIME_PATTERN);
|
||||
Long count = operationMapper.selectCount(new LambdaQueryWrapperX<OperationDO>()
|
||||
.eq(OperationDO::getOperatorMemberId, operateUserId)
|
||||
@@ -949,4 +971,4 @@ public class RecentServiceImpl implements RecentService {
|
||||
resultQuery.setExcludeUserIdList(excludeUserIdList.stream().collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+15
-14
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.member.dal.mysql.recent.OperationMapper">
|
||||
<sql id="selectA">
|
||||
<sql id="whereA">
|
||||
a.deleted = 0
|
||||
<if test="dto.memberRecentId != null">
|
||||
and a.member_recent_id = #{dto.memberRecentId}
|
||||
@@ -26,23 +26,24 @@
|
||||
<select id="totalGroupRecent"
|
||||
resultType="cn.iocoder.yudao.module.member.controller.admin.recent.vo.OperationTotalGroupRecentRespVO">
|
||||
select
|
||||
sum(if(a.biz_type = '1' and a.cancel_time is null, 1, 0)) as likeTotal,
|
||||
sum(if(a.biz_type = '1' and a.cancel_time is null and a.operator_member_id = #{dto.memberId}, 1, 0)) as
|
||||
likeItTotal,
|
||||
sum(if(a.biz_type = '1' and a.cancel_time is null and a.member_id = #{dto.memberId}, 1, 0)) as likeMeTotal,
|
||||
sum(if(a.biz_type = '2', 1, 0)) as reportTotal,
|
||||
sum(if(a.biz_type = '3', 1, 0)) as shieldTotal,
|
||||
sum(if(a.biz_type = '4' and a.cancel_time is null, 1, 0)) as userLikeTotal,
|
||||
sum(if(a.biz_type = '4' and a.cancel_time is null and a.operator_member_id = #{dto.memberId}, 1, 0)) as
|
||||
userLikeItTotal,
|
||||
sum(if(a.biz_type = '4' and a.cancel_time is null and a.member_id = #{dto.memberId}, 1, 0)) as userLikeMeTotal,
|
||||
a.biz_type as bizType,
|
||||
sum(case when a.biz_type = '1' and a.cancel_time is null then 1 else 0 end) as likeTotal,
|
||||
sum(case when a.biz_type = '1' and a.cancel_time is null
|
||||
and a.operator_member_id = #{dto.memberId} then 1 else 0 end) as likeItTotal,
|
||||
sum(case when a.biz_type = '1' and a.cancel_time is null
|
||||
and a.member_id = #{dto.memberId} then 1 else 0 end) as likeMeTotal,
|
||||
sum(case when a.biz_type = '2' then 1 else 0 end) as reportTotal,
|
||||
sum(case when a.biz_type = '3' then 1 else 0 end) as shieldTotal,
|
||||
sum(case when a.biz_type = '4' and a.cancel_time is null then 1 else 0 end) as userLikeTotal,
|
||||
sum(case when a.biz_type = '4' and a.cancel_time is null
|
||||
and a.operator_member_id = #{dto.memberId} then 1 else 0 end) as userLikeItTotal,
|
||||
sum(case when a.biz_type = '4' and a.cancel_time is null
|
||||
and a.member_id = #{dto.memberId} then 1 else 0 end) as userLikeMeTotal,
|
||||
a.member_recent_id as memberRecentId
|
||||
from member_operation a
|
||||
<where>
|
||||
<include refid="selectA"/>
|
||||
<include refid="whereA"/>
|
||||
</where>
|
||||
group by a.biz_type, a.member_recent_id
|
||||
group by a.member_recent_id
|
||||
</select>
|
||||
|
||||
<select id="totalGroupRecentByUser"
|
||||
|
||||
@@ -146,6 +146,10 @@ spring:
|
||||
logging:
|
||||
file:
|
||||
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
|
||||
level:
|
||||
cn.iocoder.yudao.module.member.dal.mysql: DEBUG
|
||||
cn.iocoder.yudao.module.trade.dal.mysql: DEBUG
|
||||
cn.iocoder.yudao.module.system.dal.mysql: DEBUG
|
||||
|
||||
--- #################### 微信公众号相关配置 ####################
|
||||
wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
|
||||
@@ -171,9 +175,9 @@ wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-sta
|
||||
# 芋道配置项,设置当前项目所有自定义的配置
|
||||
yudao:
|
||||
pay:
|
||||
order-notify-url: https://dzc001.fy.takin.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
|
||||
refund-notify-url: https://dzc001.fy.takin.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
|
||||
transfer-notify-url: https://dzc001.fy.takin.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
|
||||
order-notify-url: https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
|
||||
refund-notify-url: https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
|
||||
transfer-notify-url: https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
|
||||
demo: false # 开启演示模式
|
||||
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
|
||||
|
||||
@@ -239,10 +243,10 @@ tx-map:
|
||||
app-secret: uYWt4DalxHgFnk6d9ul2KLdPVM1ABD9m
|
||||
|
||||
al-config:
|
||||
access-key-id: LTAI5tJ7sSxB96XaY9T3Vq3D
|
||||
access-key-secret: EVXWFTS2SB6UkjUVFLhTDBfIvIXgI3
|
||||
face-scene-id: 1000016045
|
||||
face-verify-callback-url: https://dzc001.fy.takin.cc
|
||||
access-key-id: LTAI5tBrkf3ynA5CMLb4TYbo
|
||||
access-key-secret: u3Za5XigBfRXo0iJHSbPgOSZTDVN3C
|
||||
face-scene-id: 1000016556
|
||||
face-verify-callback-url: https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc
|
||||
face-verify-callback-token: uVuYOzoiQBWPp4A5kPgTddZlKWoSQlzc
|
||||
|
||||
brokerage:
|
||||
|
||||
@@ -261,7 +261,7 @@ al-config:
|
||||
access-key-id: LTAI5tBrkf3ynA5CMLb4TYbo
|
||||
access-key-secret: u3Za5XigBfRXo0iJHSbPgOSZTDVN3C
|
||||
face-scene-id: 1000016556
|
||||
face-verify-callback-url: https://dzc001.fy.takin.cc
|
||||
face-verify-callback-url: https://baac9a029ea84f36ba28c023fed4fc5f.hd.takin.cc
|
||||
face-verify-callback-token: uVuYOzoiQBWPp4A5kPgTddZlKWoSQlzc
|
||||
|
||||
brokerage:
|
||||
|
||||
Reference in new issue
Block a user