This commit is contained in:
dzc committed 2026-07-15 20:40:50 +08:00
1 parent 6b0b2a8916
commit befc75593d
15 files changed
+381 -45

No files matched your search

@@ -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;
}
}
}
@@ -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;
}
@@ -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();
}
}
@@ -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, "用户位置不存在");
@@ -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);
}
@@ -18,4 +18,9 @@ public interface ThirdAuthService {
*/
ThirdAuthVo imageAuditSync(String imageUrl);
/**
* 视频审核-同步
*/
ThirdAuthVo videoAuditSync(String videoUrl);
}
@@ -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;
}
}
@@ -19,7 +19,6 @@ public class RecentSaveReqVO {
private Long memberId;
@Schema(description = "主图", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "主图不能为空")
private String coverPic;
@Schema(description = "视频")
@@ -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)));
}
@@ -14,7 +14,6 @@ public class AppRecentSaveReqVO {
private Long memberId;
@Schema(description = "主图url")
@NotEmpty(message = "主图不能为空")
private String coverPic;
@Schema(description = "视频url")
@@ -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);
}
}
}
@@ -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()));
}
}
}
@@ -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: