This commit is contained in:
Jerry Yan 2025-03-17 18:35:06 +08:00
parent 7e8eebdef5
commit 180ba67de8
5 changed files with 210 additions and 2 deletions

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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");
}

View File

@ -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>

View File

@ -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>