diff --git a/src/main/java/com/ycwl/basic/biz/OrderBiz.java b/src/main/java/com/ycwl/basic/biz/OrderBiz.java index 37e9f79..6991a56 100644 --- a/src/main/java/com/ycwl/basic/biz/OrderBiz.java +++ b/src/main/java/com/ycwl/basic/biz/OrderBiz.java @@ -240,18 +240,12 @@ public class OrderBiz { StatisticsRecordAddReq statisticsRecordAddReq = new StatisticsRecordAddReq(); statisticsRecordAddReq.setMemberId(order.getMemberId()); - Calendar calendar = Calendar.getInstance(); - calendar.setTime(goodsCreateTime); - calendar.set(Calendar.HOUR_OF_DAY, 21); - calendar.set(Calendar.MINUTE, 0); - calendar.set(Calendar.SECOND, 0); - // TODO: 他的购买的内容于内容生成当天晚9点之前算现场订单,否则算推送订单 - if(calendar.getTime().compareTo(payAt)>0){// + Long enterType = statisticsMapper.getUserRecentEnterType(order.getMemberId(), order.getCreateAt()); + if(!Long.valueOf(1014).equals(enterType)){// statisticsRecordAddReq.setType(StatisticEnum.ON_SITE_PAYMENT.code); }else { statisticsRecordAddReq.setType(StatisticEnum.POST_PAYMENT.code); } - calendar.clear(); statisticsRecordAddReq.setScenicId(order.getScenicId()); statisticsRecordAddReq.setMorphId(orderId); statisticsMapper.addStatisticsRecord(statisticsRecordAddReq); diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppGoodsController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppGoodsController.java index 8f4b7ef..3a80258 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppGoodsController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppGoodsController.java @@ -40,6 +40,19 @@ public class AppGoodsController { return ApiResponse.success(goodsDetailVOS); } + @PostMapping("/sourceGoodsList/preview") + public ApiResponse> sourceGoodsListPreview(@RequestBody GoodsReqQuery query) { + List goodsUrlList = goodsService.sourceGoodsListPreview(query); + return ApiResponse.success(goodsUrlList); + } + + @PostMapping("/sourceGoodsList/download") + public ApiResponse> sourceGoodsListDownload(@RequestBody GoodsReqQuery query) { + List goodsUrlList = goodsService.sourceGoodsListDownload(query); + return ApiResponse.success(goodsUrlList); + } + + @ApiOperation("成片vlog商品详情") @GetMapping("/getVideoGoodsDetail/{videoId}") @IgnoreToken diff --git a/src/main/java/com/ycwl/basic/image/watermark/ImageWatermarkFactory.java b/src/main/java/com/ycwl/basic/image/watermark/ImageWatermarkFactory.java index 5e456fb..cf48d4e 100644 --- a/src/main/java/com/ycwl/basic/image/watermark/ImageWatermarkFactory.java +++ b/src/main/java/com/ycwl/basic/image/watermark/ImageWatermarkFactory.java @@ -3,6 +3,7 @@ package com.ycwl.basic.image.watermark; import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum; import com.ycwl.basic.image.watermark.exception.ImageWatermarkUnsupportedException; import com.ycwl.basic.image.watermark.operator.IOperator; +import com.ycwl.basic.image.watermark.operator.DefaultImageWatermarkOperator; import com.ycwl.basic.image.watermark.operator.LeicaWatermarkOperator; import com.ycwl.basic.image.watermark.operator.NormalWatermarkOperator; @@ -12,13 +13,18 @@ public class ImageWatermarkFactory { if (type == null) { throw new ImageWatermarkUnsupportedException(watermarkType); } + return get(type); + } + public static IOperator get(ImageWatermarkOperatorEnum type) { switch (type) { + case WATERMARK: + return new DefaultImageWatermarkOperator(); case NORMAL: return new NormalWatermarkOperator(); case LEICA: return new LeicaWatermarkOperator(); default: - throw new ImageWatermarkUnsupportedException(watermarkType); + throw new ImageWatermarkUnsupportedException("不支持的类型"+type.name()); } } } diff --git a/src/main/java/com/ycwl/basic/image/watermark/entity/WatermarkInfo.java b/src/main/java/com/ycwl/basic/image/watermark/entity/WatermarkInfo.java index 9782e38..f4b36c0 100644 --- a/src/main/java/com/ycwl/basic/image/watermark/entity/WatermarkInfo.java +++ b/src/main/java/com/ycwl/basic/image/watermark/entity/WatermarkInfo.java @@ -9,6 +9,9 @@ import java.util.Date; @Data public class WatermarkInfo { private File originalFile; + /** + * 输出文件 + */ private File watermarkedFile; private File qrcodeFile; private String scenicLine; diff --git a/src/main/java/com/ycwl/basic/image/watermark/enums/ImageWatermarkOperatorEnum.java b/src/main/java/com/ycwl/basic/image/watermark/enums/ImageWatermarkOperatorEnum.java index 447c0a7..eea6983 100644 --- a/src/main/java/com/ycwl/basic/image/watermark/enums/ImageWatermarkOperatorEnum.java +++ b/src/main/java/com/ycwl/basic/image/watermark/enums/ImageWatermarkOperatorEnum.java @@ -1,6 +1,10 @@ package com.ycwl.basic.image.watermark.enums; +import lombok.Getter; + +@Getter public enum ImageWatermarkOperatorEnum { + WATERMARK("defW"), LEICA("leica"), NORMAL("normal"); diff --git a/src/main/java/com/ycwl/basic/image/watermark/operator/DefaultImageWatermarkOperator.java b/src/main/java/com/ycwl/basic/image/watermark/operator/DefaultImageWatermarkOperator.java new file mode 100644 index 0000000..3a1e3a6 --- /dev/null +++ b/src/main/java/com/ycwl/basic/image/watermark/operator/DefaultImageWatermarkOperator.java @@ -0,0 +1,46 @@ +package com.ycwl.basic.image.watermark.operator; + +import com.ycwl.basic.image.watermark.entity.WatermarkInfo; +import com.ycwl.basic.image.watermark.exception.ImageWatermarkException; +import lombok.extern.slf4j.Slf4j; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +@Slf4j +public class DefaultImageWatermarkOperator implements IOperator { + @Override + public File process(WatermarkInfo info) throws ImageWatermarkException { + BufferedImage baseImage; + BufferedImage watermarkImage; + InputStream logoInputStream = getClass().getResourceAsStream("/watermark.png"); + if (logoInputStream == null) { + throw new ImageWatermarkException("无法找到 watermark.png 资源文件"); + } + try { + baseImage = ImageIO.read(info.getOriginalFile()); + watermarkImage = ImageIO.read(logoInputStream); + } catch (IOException e) { + throw new ImageWatermarkException("图片打开失败"); + } + // 新图像画布 + BufferedImage newImage = new BufferedImage(baseImage.getWidth(), baseImage.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = newImage.createGraphics(); + g2d.drawImage(baseImage, 0, 0, null); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f)); + g2d.drawImage(watermarkImage, 0, 0, baseImage.getWidth(), baseImage.getHeight(), null); + try { + ImageIO.write(newImage, "jpg", info.getWatermarkedFile()); + } catch (IOException e) { + throw new ImageWatermarkException("图片保存失败"); + } finally { + g2d.dispose(); + } + return info.getWatermarkedFile(); + } +} diff --git a/src/main/java/com/ycwl/basic/image/watermark/operator/LeicaWatermarkOperator.java b/src/main/java/com/ycwl/basic/image/watermark/operator/LeicaWatermarkOperator.java index 962eafc..869d345 100644 --- a/src/main/java/com/ycwl/basic/image/watermark/operator/LeicaWatermarkOperator.java +++ b/src/main/java/com/ycwl/basic/image/watermark/operator/LeicaWatermarkOperator.java @@ -46,7 +46,7 @@ public class LeicaWatermarkOperator implements IOperator { public static int EXTRA_BORDER_PX = 0; public static Color BG_COLOR = Color.WHITE; public static int LOGO_SIZE = 50; - public static int LOGO_EXTRA_BORDER = 10; + public static int LOGO_EXTRA_BORDER = 20; public static int LOGO_FONT_SIZE = 38; public static Color logoTextColor = new Color(0x33, 0x33, 0x33); public static int QRCODE_SIZE = 80; diff --git a/src/main/java/com/ycwl/basic/mapper/SourceMapper.java b/src/main/java/com/ycwl/basic/mapper/SourceMapper.java index 8d20e49..b9e2240 100644 --- a/src/main/java/com/ycwl/basic/mapper/SourceMapper.java +++ b/src/main/java/com/ycwl/basic/mapper/SourceMapper.java @@ -2,6 +2,7 @@ package com.ycwl.basic.mapper; import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity; import com.ycwl.basic.model.pc.source.entity.SourceEntity; +import com.ycwl.basic.model.pc.source.entity.SourceWatermarkEntity; import com.ycwl.basic.model.pc.source.req.SourceReqQuery; import com.ycwl.basic.model.pc.source.resp.SourceRespVO; import org.apache.ibatis.annotations.Mapper; @@ -55,7 +56,6 @@ public interface SourceMapper { int addRelations(List list); int updateRelation(MemberSourceEntity memberSourceEntity); - int updateWaterUrl(MemberSourceEntity memberSourceEntity); List queryByRelation(SourceReqQuery sourceReqQuery); @@ -75,4 +75,7 @@ public interface SourceMapper { int deleteNotBuyRelations(Long scenicId, Date endDate); int deleteNotBuyFaceRelation(Long userId, Long faceId); + List listSourceWatermark(List sourceIds, Long faceId, String watermarkType); + + void addSourceWatermark(Long sourceId, Long faceId, String type, String url); } diff --git a/src/main/java/com/ycwl/basic/mapper/StatisticsMapper.java b/src/main/java/com/ycwl/basic/mapper/StatisticsMapper.java index 276194b..36fd8c8 100644 --- a/src/main/java/com/ycwl/basic/mapper/StatisticsMapper.java +++ b/src/main/java/com/ycwl/basic/mapper/StatisticsMapper.java @@ -91,4 +91,6 @@ public interface StatisticsMapper { int addStatisticsRecord(StatisticsRecordAddReq req); List getBrokerIdListForUser(Long memberId, Date startTime, Date endTime); + + Long getUserRecentEnterType(Long memberId, Date endTime); } diff --git a/src/main/java/com/ycwl/basic/model/mobile/goods/GoodsReqQuery.java b/src/main/java/com/ycwl/basic/model/mobile/goods/GoodsReqQuery.java index 9df1732..d77b333 100644 --- a/src/main/java/com/ycwl/basic/model/mobile/goods/GoodsReqQuery.java +++ b/src/main/java/com/ycwl/basic/model/mobile/goods/GoodsReqQuery.java @@ -14,6 +14,7 @@ public class GoodsReqQuery { @ApiModelProperty("是否已购买 0否 1是") private Integer isBuy; private Long faceId; + private Long goodsId; @ApiModelProperty("景区id") private Long scenicId; @ApiModelProperty("源素材商品类型 1视频 2图像") diff --git a/src/main/java/com/ycwl/basic/model/mobile/goods/GoodsUrlVO.java b/src/main/java/com/ycwl/basic/model/mobile/goods/GoodsUrlVO.java new file mode 100644 index 0000000..182fd7a --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/mobile/goods/GoodsUrlVO.java @@ -0,0 +1,26 @@ +package com.ycwl.basic.model.mobile.goods; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * @Author:longbinbin + * @Date:2024/12/5 15:10 + */ +@Data +@ApiModel("商品详情") +public class GoodsUrlVO { + @ApiModelProperty("商品类型 0:vlog 1:成片视频 2:源素材") + private Integer goodsType; + @ApiModelProperty("商品id goodsType=0时为videoId,goodsType=2时为sourceId") + private Long goodsId; + @ApiModelProperty("图片文件存储地址") + private String url; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private Date createTime; +} diff --git a/src/main/java/com/ycwl/basic/model/pc/source/entity/MemberSourceEntity.java b/src/main/java/com/ycwl/basic/model/pc/source/entity/MemberSourceEntity.java index 1e232ac..aeadd5d 100644 --- a/src/main/java/com/ycwl/basic/model/pc/source/entity/MemberSourceEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/source/entity/MemberSourceEntity.java @@ -15,5 +15,4 @@ public class MemberSourceEntity { private Integer isBuy; private Long orderId; private Integer isFree; - private String waterUrl; } diff --git a/src/main/java/com/ycwl/basic/model/pc/source/entity/SourceWatermarkEntity.java b/src/main/java/com/ycwl/basic/model/pc/source/entity/SourceWatermarkEntity.java new file mode 100644 index 0000000..2897dca --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/source/entity/SourceWatermarkEntity.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.model.pc.source.entity; + +import lombok.Data; + +@Data +public class SourceWatermarkEntity { + private Integer id; + private Long sourceId; + private Long faceId; + private String watermarkType; + private String watermarkUrl; +} diff --git a/src/main/java/com/ycwl/basic/service/impl/mobile/GoodsServiceImpl.java b/src/main/java/com/ycwl/basic/service/impl/mobile/GoodsServiceImpl.java index 4d9e020..1210d53 100644 --- a/src/main/java/com/ycwl/basic/service/impl/mobile/GoodsServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/impl/mobile/GoodsServiceImpl.java @@ -1,16 +1,27 @@ package com.ycwl.basic.service.impl.mobile; +import cn.hutool.extra.qrcode.QrCodeUtil; +import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.ycwl.basic.biz.OrderBiz; import com.ycwl.basic.biz.TaskStatusBiz; import com.ycwl.basic.constant.BaseContextHandler; +import com.ycwl.basic.constant.StorageConstant; +import com.ycwl.basic.image.watermark.ImageWatermarkFactory; +import com.ycwl.basic.image.watermark.entity.WatermarkInfo; +import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum; +import com.ycwl.basic.image.watermark.exception.ImageWatermarkException; +import com.ycwl.basic.image.watermark.operator.IOperator; import com.ycwl.basic.mapper.*; import com.ycwl.basic.model.mobile.goods.*; +import com.ycwl.basic.model.mobile.order.IsBuyRespVO; import com.ycwl.basic.model.mobile.order.PriceObj; import com.ycwl.basic.model.pc.face.entity.FaceEntity; import com.ycwl.basic.model.pc.face.resp.FaceRespVO; +import com.ycwl.basic.model.pc.mp.MpConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; +import com.ycwl.basic.model.pc.source.entity.SourceWatermarkEntity; import com.ycwl.basic.model.pc.source.req.SourceReqQuery; import com.ycwl.basic.model.pc.source.resp.SourceRespVO; import com.ycwl.basic.model.pc.task.entity.TaskEntity; @@ -25,21 +36,30 @@ import com.ycwl.basic.repository.VideoTaskRepository; import com.ycwl.basic.service.mobile.GoodsService; import com.ycwl.basic.repository.TemplateRepository; import com.ycwl.basic.service.task.TaskService; +import com.ycwl.basic.storage.StorageFactory; +import com.ycwl.basic.storage.adapters.IStorageAdapter; +import com.ycwl.basic.storage.enums.StorageAcl; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.DateUtils; +import com.ycwl.basic.utils.WxMpUtil; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** * @Author:longbinbin * @Date:2024/12/5 15:04 */ +@Slf4j @Service public class GoodsServiceImpl implements GoodsService { @Autowired @@ -427,4 +447,206 @@ public class GoodsServiceImpl implements GoodsService { return ApiResponse.success(goodsDetailVO); } + @Override + public List sourceGoodsListPreview(GoodsReqQuery query) { + List tmpFile = new ArrayList<>(); + FaceEntity face = faceRepository.getFace(query.getFaceId()); + if (face == null) { + return Collections.emptyList(); + } + Integer sourceType = query.getSourceType(); + SourceReqQuery sourceReqQuery = new SourceReqQuery(); + sourceReqQuery.setScenicId(query.getScenicId()); + sourceReqQuery.setIsBuy(query.getIsBuy()); + sourceReqQuery.setMemberId(face.getMemberId()); + sourceReqQuery.setType(sourceType); + sourceReqQuery.setFaceId(query.getFaceId()); + List list = sourceMapper.listUser(sourceReqQuery); + if (!Integer.valueOf(2).equals(query.getSourceType())) { + return list.stream().map(source -> { + GoodsUrlVO goodsUrlVO = new GoodsUrlVO(); + goodsUrlVO.setGoodsType(source.getType()); + goodsUrlVO.setGoodsId(source.getId()); + goodsUrlVO.setUrl(source.getVideoUrl()); + goodsUrlVO.setCreateTime(source.getCreateTime()); + return goodsUrlVO; + }).collect(Collectors.toList()); + } + List defaultUrlList = list.stream().map(source -> { + GoodsUrlVO goodsUrlVO = new GoodsUrlVO(); + goodsUrlVO.setGoodsType(source.getType()); + goodsUrlVO.setGoodsId(source.getId()); + goodsUrlVO.setUrl(source.getUrl()); + goodsUrlVO.setCreateTime(source.getCreateTime()); + return goodsUrlVO; + }).collect(Collectors.toList()); + IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), query.getScenicId(), query.getSourceType(), query.getFaceId()); + if (!isBuy.isBuy()) { + ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId()); + if (scenicConfig != null && ((scenicConfig.getAntiScreenRecordType() & 2) == 0)) { + // 未启用水印 + return defaultUrlList; + } + IStorageAdapter adapter; + if (scenicConfig != null && scenicConfig.getStoreType() != null) { + adapter = StorageFactory.get(scenicConfig.getStoreType()); + adapter.loadConfig(JSONObject.parseObject(scenicConfig.getStoreConfigJson(), Map.class)); + } else { + adapter = StorageFactory.use("assets-ext"); + } + IOperator operator = ImageWatermarkFactory.get(ImageWatermarkOperatorEnum.WATERMARK); + List watermarkEntityList = sourceMapper.listSourceWatermark(defaultUrlList.stream().map(GoodsUrlVO::getGoodsId).collect(Collectors.toList()), null, ImageWatermarkOperatorEnum.WATERMARK.getType()); + List collect = defaultUrlList.stream().peek(item -> { + Optional any = watermarkEntityList.stream() + .filter(watermark -> watermark.getSourceId().equals(item.getGoodsId())) + .findAny(); + if (any.isPresent()) { + item.setUrl(any.get().getWatermarkUrl()); + } else { + // 生成 + File dstFile = new File(item.getGoodsId() + ".jpg"); + File watermarkedFile = new File(item.getGoodsId() + "_" + ImageWatermarkOperatorEnum.WATERMARK.getType() + ".png"); + try { + HttpUtil.downloadFile(item.getUrl(), dstFile); + } catch (Exception e) { + log.error("downloadFile error", e); + return; + } + tmpFile.add(dstFile); + WatermarkInfo info = new WatermarkInfo(); + info.setOriginalFile(dstFile); + info.setWatermarkedFile(watermarkedFile); + try { + operator.process(info); + } catch (ImageWatermarkException e) { + log.error("process error", e); + return; + } + String url = adapter.uploadFile(watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName()); + adapter.setAcl(StorageAcl.PUBLIC_READ, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName()); + sourceMapper.addSourceWatermark(item.getGoodsId(), null, ImageWatermarkOperatorEnum.WATERMARK.getType(), url); + tmpFile.add(watermarkedFile); + item.setUrl(url); + } + }).collect(Collectors.toList()); + for (File file : tmpFile) { + file.delete(); + } + return collect; + } + return defaultUrlList; + } + + @Override + public List sourceGoodsListDownload(GoodsReqQuery query) { + List tmpFile = new ArrayList<>(); + FaceEntity face = faceRepository.getFace(query.getFaceId()); + if (face == null) { + return Collections.emptyList(); + } + IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), query.getScenicId(), query.getSourceType(), query.getFaceId()); + if (!isBuy.isBuy()) { + return Collections.emptyList(); + } + Integer sourceType = query.getSourceType(); + SourceReqQuery sourceReqQuery = new SourceReqQuery(); + sourceReqQuery.setScenicId(query.getScenicId()); + sourceReqQuery.setIsBuy(query.getIsBuy()); + sourceReqQuery.setMemberId(face.getMemberId()); + sourceReqQuery.setType(sourceType); + sourceReqQuery.setFaceId(query.getFaceId()); + List list = sourceMapper.listUser(sourceReqQuery); + if (query.getGoodsId() != null) { + list = list.stream().filter(source -> source.getId().equals(query.getGoodsId())).collect(Collectors.toList()); + } + if (!Integer.valueOf(2).equals(query.getSourceType())) { + return list.stream().map(source -> { + GoodsUrlVO goodsUrlVO = new GoodsUrlVO(); + goodsUrlVO.setGoodsType(source.getType()); + goodsUrlVO.setGoodsId(source.getId()); + goodsUrlVO.setUrl(source.getVideoUrl()); + goodsUrlVO.setCreateTime(source.getCreateTime()); + return goodsUrlVO; + }).collect(Collectors.toList()); + } + List defaultUrlList = list.stream().map(source -> { + GoodsUrlVO goodsUrlVO = new GoodsUrlVO(); + goodsUrlVO.setGoodsType(source.getType()); + goodsUrlVO.setGoodsId(source.getId()); + goodsUrlVO.setUrl(source.getUrl()); + goodsUrlVO.setCreateTime(source.getCreateTime()); + return goodsUrlVO; + }).collect(Collectors.toList()); + ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId()); + MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(face.getScenicId()); + if (scenicMpConfig == null) { + log.warn("未配置小程序参数,无法生成二维码"); + return defaultUrlList; + } + if (scenicConfig != null && scenicConfig.getWatermarkType() != null) { + ImageWatermarkOperatorEnum type = ImageWatermarkOperatorEnum.getByCode(scenicConfig.getWatermarkType()); + if (type != null) { + IStorageAdapter adapter; + if (scenicConfig != null && scenicConfig.getStoreType() != null) { + adapter = StorageFactory.get(scenicConfig.getStoreType()); + adapter.loadConfig(JSONObject.parseObject(scenicConfig.getStoreConfigJson(), Map.class)); + } else { + adapter = StorageFactory.use("assets-ext"); + } + IOperator operator = ImageWatermarkFactory.get(type); + List watermarkEntityList = sourceMapper.listSourceWatermark(list.stream().map(SourceRespVO::getId).collect(Collectors.toList()), face.getId(), type.getType()); + File qrcode = new File("qrcode_"+face.getId()+".jpg"); + try { + String urlLink = WxMpUtil.generateUrlLink(scenicMpConfig.getAppId(), scenicMpConfig.getAppSecret(), "pages/videoSynthesis/index", "scenicId=" + face.getScenicId() + "&faceId=" + face.getId()); + QrCodeUtil.generate(urlLink + "?cq=", 300, 300, qrcode); + } catch (Exception e) { + log.error("generateWXQRCode error", e); + return defaultUrlList; + } + tmpFile.add(qrcode); + List collect = defaultUrlList.stream().peek(item -> { + Optional any = watermarkEntityList.stream() + .filter(watermark -> watermark.getSourceId().equals(item.getGoodsId())) + .findAny(); + if (any.isPresent()) { + item.setUrl(any.get().getWatermarkUrl()); + } else { + // 生成 + File dstFile = new File(item.getGoodsId() + ".jpg"); + File watermarkedFile = new File(item.getGoodsId() + "_" + type.getType() + ".png"); + try { + HttpUtil.downloadFile(item.getUrl(), dstFile); + } catch (Exception e) { + log.error("downloadFile error", e); + return; + } + tmpFile.add(dstFile); + WatermarkInfo info = new WatermarkInfo(); + info.setOriginalFile(dstFile); + info.setQrcodeFile(qrcode); + info.setScenicLine(scenicConfig.getWatermarkScenicText()); + info.setDatetime(item.getCreateTime()); + info.setDtFormat(scenicConfig.getWatermarkDtFormat()); + info.setWatermarkedFile(watermarkedFile); + try { + operator.process(info); + } catch (ImageWatermarkException e) { + log.error("process error", e); + return; + } + String url = adapter.uploadFile(watermarkedFile, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName()); + adapter.setAcl(StorageAcl.PUBLIC_READ, StorageConstant.PHOTO_WATERMARKED_PATH, watermarkedFile.getName()); + sourceMapper.addSourceWatermark(item.getGoodsId(), face.getId(), type.getType(), url); + tmpFile.add(watermarkedFile); + item.setUrl(url); + } + }).collect(Collectors.toList()); + for (File file : tmpFile) { + file.delete(); + } + return collect; + } + } + return defaultUrlList; + } } diff --git a/src/main/java/com/ycwl/basic/service/mobile/GoodsService.java b/src/main/java/com/ycwl/basic/service/mobile/GoodsService.java index e1b4d0e..2755698 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/GoodsService.java +++ b/src/main/java/com/ycwl/basic/service/mobile/GoodsService.java @@ -44,4 +44,8 @@ public interface GoodsService { VideoTaskStatusVO getTaskStatusByScenicId(Long userId, Long scenicId); ApiResponse sourceGoodsInfo(Long sourceId); + + List sourceGoodsListPreview(GoodsReqQuery query); + + List sourceGoodsListDownload(GoodsReqQuery query); } diff --git a/src/main/java/com/ycwl/basic/task/ImageWatermarkTask.java b/src/main/java/com/ycwl/basic/task/ImageWatermarkTask.java deleted file mode 100644 index 02d869c..0000000 --- a/src/main/java/com/ycwl/basic/task/ImageWatermarkTask.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.ycwl.basic.task; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.extra.qrcode.QrCodeUtil; -import cn.hutool.http.HttpUtil; -import com.alibaba.fastjson.JSONObject; -import com.ycwl.basic.image.watermark.ImageWatermarkFactory; -import com.ycwl.basic.image.watermark.entity.WatermarkInfo; -import com.ycwl.basic.image.watermark.exception.ImageWatermarkException; -import com.ycwl.basic.image.watermark.operator.IOperator; -import com.ycwl.basic.mapper.SourceMapper; -import com.ycwl.basic.model.pc.face.entity.FaceEntity; -import com.ycwl.basic.model.pc.mp.MpConfigEntity; -import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; -import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; -import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity; -import com.ycwl.basic.model.pc.source.entity.SourceEntity; -import com.ycwl.basic.notify.entity.WxMpSrvConfig; -import com.ycwl.basic.repository.FaceRepository; -import com.ycwl.basic.repository.ScenicRepository; -import com.ycwl.basic.storage.StorageFactory; -import com.ycwl.basic.storage.adapters.IStorageAdapter; -import com.ycwl.basic.storage.enums.StorageAcl; -import com.ycwl.basic.utils.WxMpUtil; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import static com.ycwl.basic.constant.StorageConstant.PHOTO_WATERMARKED_PATH; - - -@Component -@EnableScheduling -@Slf4j -public class ImageWatermarkTask { - @Autowired - private FaceRepository faceRepository; - @Autowired - private ScenicRepository scenicRepository; - @Autowired - private SourceMapper sourceMapper; - - @Data - @AllArgsConstructor - public static class Task { - public Long memberId; - public Long faceId; - } - - public static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); - - public static void addTask(Long memberId, Long faceId) { - queue.add(new Task(memberId, faceId)); - } - - @Scheduled(fixedRate = 200L) - public void doTask() { - Task task = queue.poll(); - if (task == null) { - return; - } - log.info("poll task: {}/{}", task, queue.size()); - new Thread(() -> { - try { - runTask(task); - } catch (Exception e) { - log.error("run task error", e); - } - }).start(); - } - - public void runTask(Task task) { - // 生成二维码 - FaceEntity face = faceRepository.getFace(task.faceId); - if (face == null) { - return; - } - ScenicEntity scenic = scenicRepository.getScenic(face.getScenicId()); - if (scenic == null) { - return; - } - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId()); - MpConfigEntity scenicMpConfig = scenicRepository.getScenicMpConfig(face.getScenicId()); - if (scenicMpConfig == null) { - return; - } - List sourceEntities = sourceMapper.listImageByFaceRelation(task.memberId, task.faceId); - if (sourceEntities == null || sourceEntities.isEmpty()) { - return; - } - File qrcode = new File("qrcode_"+face.getMemberId()+".jpg"); - try { - String urlLink = WxMpUtil.generateUrlLink(scenicMpConfig.getAppId(), scenicMpConfig.getAppSecret(), "pages/videoSynthesis/index", "scenicId=" + face.getScenicId() + "&faceId=" + face.getId()); - QrCodeUtil.generate(urlLink + "?cq=", 300, 300, qrcode); - } catch (Exception e) { - log.error("generateWXQRCode error", e); - return; - } - IStorageAdapter adapter = StorageFactory.get(scenicConfig.getStoreType()); - adapter.loadConfig(JSONObject.parseObject(scenicConfig.getStoreConfigJson(), Map.class)); - // TODO - WatermarkInfo info = new WatermarkInfo(); - info.setQrcodeFile(qrcode); - final ThreadPoolExecutor executor = new ThreadPoolExecutor(16, 128, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(128)); - for (SourceEntity sourceEntity : sourceEntities) { - executor.execute(() -> { - String url; - try { - IOperator operator = ImageWatermarkFactory.get(scenicConfig.getWatermarkType()); - File dstFile = new File(sourceEntity.getId() + ".jpg"); - File watermarkedFile = new File(sourceEntity.getId() + "_w.png"); - try { - HttpUtil.downloadFile(sourceEntity.getUrl(), dstFile); - } catch (Exception e) { - log.error("downloadFile error", e); - return; - } - info.setOriginalFile(dstFile); - info.setScenicLine(scenicConfig.getWatermarkScenicText()); - info.setDatetime(sourceEntity.getCreateTime()); - info.setDtFormat(scenicConfig.getWatermarkDtFormat()); - info.setWatermarkedFile(watermarkedFile); - try { - operator.process(info); - } catch (ImageWatermarkException e) { - log.error("process error", e); - return; - } - url = adapter.uploadFile(watermarkedFile, PHOTO_WATERMARKED_PATH, watermarkedFile.getName()); - adapter.setAcl(StorageAcl.PUBLIC_READ, PHOTO_WATERMARKED_PATH, watermarkedFile.getName()); - } catch (ImageWatermarkException e) { - // 不支持 - url = sourceEntity.getUrl(); - } - - MemberSourceEntity memberSource = new MemberSourceEntity(); - memberSource.setMemberId(task.memberId); - memberSource.setScenicId(face.getScenicId()); - memberSource.setFaceId(task.faceId); - memberSource.setType(sourceEntity.getType()); - memberSource.setSourceId(sourceEntity.getId()); - memberSource.setWaterUrl(url); - sourceMapper.updateWaterUrl(memberSource); - }); - } - try { - Thread.sleep(2000L); - log.info("executor等待被结束![A:{}/T:{}/F:{}]", executor.getActiveCount(), executor.getTaskCount(), executor.getCompletedTaskCount()); - executor.shutdown(); - executor.awaitTermination(30, TimeUnit.SECONDS); - log.info("executor已结束![A:{}/T:{}/F:{}]", executor.getActiveCount(), executor.getTaskCount(), executor.getCompletedTaskCount()); - } catch (InterruptedException e) { - return; - } - } -} diff --git a/src/main/resources/mapper/SourceMapper.xml b/src/main/resources/mapper/SourceMapper.xml index 19ba769..df35dd5 100644 --- a/src/main/resources/mapper/SourceMapper.xml +++ b/src/main/resources/mapper/SourceMapper.xml @@ -16,6 +16,10 @@ (#{item.scenicId}, #{item.faceId}, #{item.memberId}, #{item.sourceId}, #{item.isBuy}, #{item.type}, #{item.orderId}, #{item.isFree}) + + insert source_watermark(source_id, face_id, watermark_type, watermark_url) + values (#{sourceId}, #{faceId}, #{type}, #{url}) + update source @@ -38,11 +42,6 @@ where member_id = #{memberId} and face_id = #{faceId} and `type` = #{type} - - update member_source - set water_url = #{waterUrl} - where member_id = #{memberId} and source_id = #{sourceId} and `type` = #{type} - delete from source where id = #{id} @@ -130,7 +129,7 @@ limit 1 + diff --git a/src/main/resources/mapper/StatisticsMapper.xml b/src/main/resources/mapper/StatisticsMapper.xml index d4cb6d7..7b37cf8 100644 --- a/src/main/resources/mapper/StatisticsMapper.xml +++ b/src/main/resources/mapper/StatisticsMapper.xml @@ -263,4 +263,14 @@ ) a order by createTime desc + \ No newline at end of file diff --git a/src/main/resources/watermark.png b/src/main/resources/watermark.png new file mode 100644 index 0000000..9cdd8d9 Binary files /dev/null and b/src/main/resources/watermark.png differ diff --git a/src/test/java/com/ycwl/basic/image/watermark/operator/WatermarkOperatorTest.java b/src/test/java/com/ycwl/basic/image/watermark/operator/WatermarkOperatorTest.java new file mode 100644 index 0000000..32d1e1a --- /dev/null +++ b/src/test/java/com/ycwl/basic/image/watermark/operator/WatermarkOperatorTest.java @@ -0,0 +1,31 @@ +package com.ycwl.basic.image.watermark.operator; + +import com.ycwl.basic.image.watermark.entity.WatermarkInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.File; + + +@SpringBootTest +@RunWith(SpringRunner.class) +public class WatermarkOperatorTest { + @Test + public void testProcess() throws Exception { + WatermarkInfo info = new WatermarkInfo(); + info.setOriginalFile(new File("e2d32de7-6e85-4e07-b42f-477347073539.jpg")); + info.setQrcodeFile(new File("cxzh_t.jpg")); + info.setScenicLine("川西竹海一日游!"); + info.setDatetimeLine("2XXX年XX月XX日 留念"); + info.setWatermarkedFile(new File("test2.png")); + IOperator operator = new NormalWatermarkOperator(); + operator.process(info); + info.setWatermarkedFile(new File("test1.png")); + operator = new LeicaWatermarkOperator(); + operator.process(info); + } + +} \ No newline at end of file