diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java index da4a654..e6f8595 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java @@ -70,4 +70,8 @@ public class ScenicConfigEntity { private String storeConfigJson; private BigDecimal brokerDirectRate; private Integer faceDetectHelperThreshold; + + private String watermarkType; + private String watermarkScenicText; + private String watermarkDtFormat; } diff --git a/src/main/java/com/ycwl/basic/task/ImageWatermarkTask.java b/src/main/java/com/ycwl/basic/task/ImageWatermarkTask.java new file mode 100644 index 0000000..1e12c33 --- /dev/null +++ b/src/main/java/com/ycwl/basic/task/ImageWatermarkTask.java @@ -0,0 +1,168 @@ +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; + + +@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<Task> 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<SourceEntity> 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_w", watermarkedFile.getName()); + adapter.setAcl(StorageAcl.PUBLIC_READ, "photo_w", 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/java/com/ycwl/basic/utils/WxMpUtil.java b/src/main/java/com/ycwl/basic/utils/WxMpUtil.java index 6d7138b..69f34b6 100644 --- a/src/main/java/com/ycwl/basic/utils/WxMpUtil.java +++ b/src/main/java/com/ycwl/basic/utils/WxMpUtil.java @@ -14,7 +14,8 @@ import java.io.FileOutputStream; import java.util.Date; public class WxMpUtil { - private static final String GET_WXA_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacode?access_token=%s"; + private static final String GET_WXA_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s"; + private static final String GET_URL_LICK_URL = "https://api.weixin.qq.com/wxa/generate_urllink?access_token=%s"; private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; private static String ACCESS_TOKEN = ""; @@ -61,6 +62,33 @@ public class WxMpUtil { } } + public static String generateUrlLink(String appId, String appSecret, String path, String query) throws Exception { + String url = String.format(GET_URL_LICK_URL, getAccessToken(appId, appSecret)); + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + JSONObject json = new JSONObject(); + json.put("path", path); + json.put("query", query); + StringEntity entity = new StringEntity(json.toJSONString(), "utf-8"); + httpPost.setEntity(entity); + httpPost.setHeader("Content-Type", "application/json"); + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + if (response.getStatusLine().getStatusCode() != 200) { + expireTime = new Date(); + throw new Exception("获取小程序码失败"); + } + HttpEntity responseEntity = response.getEntity(); + if (responseEntity != null) { + String responseStr = EntityUtils.toString(responseEntity); + JSONObject jsonObject = JSONObject.parseObject(responseStr); + return jsonObject.getString("url_link"); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + public static void main(String[] args) throws Exception { generateWXAQRCode("wxe7ff26af70bfc37c", "5252fbbc68513bc77b7cc0052b9f9695", "trial", "pages/home/index?scenicId=3955650120997015552", "sxlj_t.jpg"); } diff --git a/src/main/resources/mapper/ScenicMapper.xml b/src/main/resources/mapper/ScenicMapper.xml index b13c589..ba5cc15 100644 --- a/src/main/resources/mapper/ScenicMapper.xml +++ b/src/main/resources/mapper/ScenicMapper.xml @@ -107,7 +107,10 @@ face_detect_helper_threshold=#{faceDetectHelperThreshold}, store_type=#{storeType}, store_config_json=#{storeConfigJson}, - broker_direct_rate=#{brokerDirectRate} + broker_direct_rate=#{brokerDirectRate}, + watermark_type=#{watermarkType}, + watermark_scenic_text=#{watermarkScenicText}, + watermark_dt_format=#{watermarkDtFormat} </set> where id = #{id} </update> diff --git a/src/main/resources/mapper/SourceMapper.xml b/src/main/resources/mapper/SourceMapper.xml index 6906906..19ba769 100644 --- a/src/main/resources/mapper/SourceMapper.xml +++ b/src/main/resources/mapper/SourceMapper.xml @@ -38,6 +38,11 @@ </set> where member_id = #{memberId} and face_id = #{faceId} and `type` = #{type} </update> + <update id="updateWaterUrl"> + update member_source + set water_url = #{waterUrl} + where member_id = #{memberId} and source_id = #{sourceId} and `type` = #{type} + </update> <delete id="deleteById"> delete from source where id = #{id} </delete>