rename com.imyeyu.server to com.imyeyu.api

This commit is contained in:
Timi
2025-07-22 15:26:14 +08:00
parent e816b885b2
commit 323e038e86
340 changed files with 1174 additions and 1175 deletions
@@ -0,0 +1,222 @@
package com.imyeyu.api.modules.common.task;
import freemarker.template.Template;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.blog.entity.CommentRemindQueue;
import com.imyeyu.api.modules.blog.service.CommentRemindQueueService;
import com.imyeyu.api.modules.common.bean.EmailException;
import com.imyeyu.api.modules.common.entity.CommentReply;
import com.imyeyu.api.modules.common.entity.EmailQueue;
import com.imyeyu.api.modules.common.entity.EmailQueueLog;
import com.imyeyu.api.modules.common.entity.User;
import com.imyeyu.api.modules.common.service.CommentReplyService;
import com.imyeyu.api.modules.common.service.EmailQueueService;
import com.imyeyu.api.modules.common.service.UserService;
import com.imyeyu.api.modules.common.vo.comment.CommentReplyView;
import com.imyeyu.spring.util.Redis;
import com.imyeyu.utils.Text;
import com.imyeyu.utils.Time;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 邮件推送任务
*
* @author 夜雨
* @since 2021-08-24 14:10
*/
@Slf4j
@Configuration
@EnableScheduling
@RequiredArgsConstructor
public class EmailTask implements TimiJava {
@Value("${spring.profiles.active}")
private String env;
@Value("${spring.mail.username}")
private String sendUser;
private final UserService userService;
private final JavaMailSender mailSender;
private final EmailQueueService service;
private final CommentReplyService commentReplyService;
private final CommentRemindQueueService commentRemindQueueService;
private final Redis<String, Long> redisUserEmailVerify;
private final Redis<String, Long> redisUserResetPWVerify;
private final FreeMarkerConfigurer freeMarkerConfigurer;
@Scheduled(fixedRate = Time.S * 8)
@Transactional(TimiServerDBConfig.ROLLBACKER)
public void traverseQueue() {
List<EmailQueue> emailQueueList = service.listAll();
if (TimiJava.isNotEmpty(emailQueueList)) {
for (EmailQueue emailQueue : emailQueueList) {
long now = System.currentTimeMillis();
if (emailQueue.getSendAt() < now) {
log.info(emailQueue.getUUID() + " 邮件推送:" + emailQueue.getBizType() + "." +emailQueue.getBizId());
EmailQueueLog emailQueueLog = new EmailQueueLog();
emailQueueLog.setUUID(emailQueue.getUUID());
emailQueueLog.setBizType(emailQueue.getBizType());
emailQueueLog.setBizId(emailQueue.getBizId());
emailQueueLog.setSendAt(emailQueue.getSendAt());
try {
String sendTo = switch (emailQueue.getBizType()) {
case REPLY_REMINAD -> sendEmail4ReplyRemind(emailQueue);
case EMAIL_VERIFY -> sendEmail4EmailVerify(emailQueue);
case RESET_PASSWORD -> sendEmail4ResetPassword(emailQueue);
};
emailQueueLog.setSendTo(sendTo);
emailQueueLog.setIsSent(true);
emailQueueLog.setCreatedAt(System.currentTimeMillis());
log.info(emailQueue.getUUID() + " 邮件 " + emailQueueLog.getSendTo() + " 推送成功:" + (emailQueueLog.getCreatedAt() - now) + " ms");
} catch (EmailException e) {
emailQueueLog.setIsSent(false);
log.error(emailQueue.getUUID() + " 邮件 " + e.getEmail() + " 推送中止:", e.getMessage());
emailQueueLog.setExceptionMsg(e.getMessage());
} catch (Exception e) {
emailQueueLog.setIsSent(false);
log.error(emailQueue.getUUID() + " 邮件 " + emailQueueLog.getSendTo() + " 推送异常", e);
emailQueueLog.setExceptionMsg(e.getMessage().substring(0,Math.min(200, e.getMessage().length())) + "...");
} finally {
emailQueueLog.setCreatedAt(now);
service.addLog(emailQueueLog);
service.destroy(emailQueue.getUUID());
}
}
}
}
}
/**
* 发送邮件
*
* @param to 目标邮箱
* @param subject 标题
* @param html HTML 字符串
* @throws Exception 发送异常
*/
private void sendEmail(String to, String subject, String html) throws Exception {
if (env.contains("dev")) {
log.info("skip send email in debug environment");
log.info("send title: {}", subject);
log.info("send to: {}", to);
log.info("send detail: \n{}", html);
return;
}
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(sendUser);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(html, true);
mailSender.send(message);
}
/**
* 邮箱验证
*
* @param emailQueue 邮件队列
* @return 发送目标
* @throws Exception 服务异常
*/
private String sendEmail4EmailVerify(EmailQueue emailQueue) throws Exception {
User user = userService.get(emailQueue.getBizId()); //.withData();
String key = Text.randomString(64);
redisUserEmailVerify.set(key, user.getId(), 600L);
Map<String, Object> model = new HashMap<>();
model.put("user", user);
model.put("url", "https://www.imyeyu.net/user/space/%s?action=EMAIL_VERIFY&key=%s".formatted(user.getId(), key));
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("EmailVerify.ftl");
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
sendEmail(user.getEmail(), "Hey! 请继续完成 夜雨博客 的邮箱验证!", html);
return user.getEmail();
}
/**
* 回复提醒邮件
*
* @param emailQueue 邮件队列
* @return 发送目标
* @throws Exception 服务异常
*/
private String sendEmail4ReplyRemind(EmailQueue emailQueue) throws Exception {
User user = userService.get(emailQueue.getBizId()); // TODO .withData();
List<CommentRemindQueue> reminds = commentRemindQueueService.listByUserId(emailQueue.getBizId());
if (reminds.isEmpty()) {
throw new EmailException(TimiCode.RESULT_NULL, "没有需要提醒的回复", user.getEmail());
}
// 回查数据
for (CommentRemindQueue remind : reminds) {
// 回复
CommentReply reply = commentReplyService.get(remind.getReplyId());
CommentReplyView replyView = new CommentReplyView();
BeanUtils.copyProperties(reply, replyView);
remind.setReply(replyView);
if (TimiJava.isNotEmpty(remind.getReply().getSenderId())) {
// 发送者
remind.getReply().setSender(userService.view(remind.getReply().getSenderId()));
}
}
Map<String, Object> model = new HashMap<>();
model.put("user", user);
model.put("reminds", reminds);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("ReplyRemind.ftl");
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
sendEmail(user.getEmail(), "Hey! 你在 夜雨博客 的评论收到新回复", html);
// 移除回复提醒队列
commentRemindQueueService.destroyByUserId(emailQueue.getBizId());
return user.getEmail();
}
/**
* 重置密码验证
*
* @param emailQueue 邮件队列
* @return 发送目标
* @throws Exception 服务异常
*/
private String sendEmail4ResetPassword(EmailQueue emailQueue) throws Exception {
User user = userService.get(emailQueue.getBizId()); // TODO .withData();
String key = Text.randomString(64);
redisUserResetPWVerify.set(key, user.getId(), 600L);
Map<String, Object> model = new HashMap<>();
model.put("user", user);
model.put("url", "https://www.imyeyu.net/user/pw-reset?key=" + key);
Template template = freeMarkerConfigurer.getConfiguration().getTemplate("ResetPassword.ftl");
String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
sendEmail(user.getEmail(), "Hey! 请继续完成 夜雨博客 的重置密码!", html);
return user.getEmail();
}
}
@@ -0,0 +1,166 @@
package com.imyeyu.api.modules.common.task;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.Language;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.java.ref.Ref;
import com.imyeyu.network.FormMap;
import com.imyeyu.api.config.dbsource.TimiServerDBConfig;
import com.imyeyu.api.modules.common.bean.SettingKey;
import com.imyeyu.api.modules.common.entity.Multilingual;
import com.imyeyu.api.modules.common.service.MultilingualService;
import com.imyeyu.api.modules.common.service.SettingService;
import com.imyeyu.utils.Digest;
import com.imyeyu.utils.Time;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.fluent.Request;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author 夜雨
* @since 2025-05-31 11:06
*/
@Slf4j
@Configuration
@EnableScheduling
@RequiredArgsConstructor
public class MultilingualTranslateTask {
/**
*
*
* @author 夜雨
* @since 2025-05-31 11:08
*/
@AllArgsConstructor
public enum BaiduLanguage {
ZH(Language.zh_CN),
EN(Language.en_US),
JP(Language.ja_JP),
KOR(Language.ko_KR),
RU(Language.ru_RU),
DE(Language.de_DE),
CHT(Language.zh_TW);
/** 标准映射 */
final Language language;
/**
* 获取排除语言列表
*
* @param baiduLanguage 排除语言
* @return 语言列表
*/
static List<BaiduLanguage> valuesWithout(BaiduLanguage... baiduLanguage) {
Set<BaiduLanguage> outList = Set.of(baiduLanguage);
List<BaiduLanguage> result = new ArrayList<>();
BaiduLanguage[] values = values();
for (int i = 0; i < values.length; i++) {
if (!outList.contains(values[i])) {
result.add(values[i]);
}
}
return result;
}
}
private final SettingService settingService;
private final MultilingualService service;
@Scheduled(fixedRate = Time.M * 10)
@Transactional(TimiServerDBConfig.ROLLBACKER)
public void handle() {
try {
List<Multilingual> list = service.listByNotTranslate();
Map<String, Multilingual> cnMap = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < Math.min(list.size() - i, 20); j++, i++) {
Multilingual multilingual = list.get(i);
sb.append(multilingual.getZhCN()).append("\r\n");
cnMap.put(multilingual.getZhCN(), multilingual);
}
i--;
List<BaiduLanguage> languageList = BaiduLanguage.valuesWithout(BaiduLanguage.ZH);
for (int j = 0; j < languageList.size(); j++) {
Map<String, String> result = doTranslate(sb.toString(), languageList.get(j));
for (Map.Entry<String, String> item : result.entrySet()) {
Multilingual multilingual = cnMap.get(item.getKey());
Language lang = languageList.get(j).language;
String value = multilingual.getValue(lang);
if (TimiJava.isEmpty(value)) {
Ref.setFieldValue(multilingual, lang.toString().replace("_", ""), item.getValue());
}
service.update(multilingual);
}
}
wait(1000);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 文本翻译
*
* @param text 原文本
* @param to 目标语言
* @return Map&lt;原数据,翻译结果&gt;
* @throws Exception 翻译异常
*/
private synchronized Map<String, String> doTranslate(String text, BaiduLanguage to) throws Exception {
String random = String.valueOf(Time.now());
String appId = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_APP_ID);
String key = settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_KEY);
FormMap<String, Object> args = new FormMap<>();
args.put("q", text);
args.put("from", BaiduLanguage.ZH.toString().toLowerCase());
args.put("to", to.toString().toLowerCase());
args.put("appid", appId);
args.put("salt", random);
args.put("sign", Digest.md5(appId + text + random + key));
String response = Request.post(settingService.getAsString(SettingKey.MULTILINGUAL_TRANSLATE_API))
.bodyForm(args.build())
.execute()
.returnContent()
.asString();
JsonObject jo = JsonParser.parseString(response).getAsJsonObject();
if (jo.has("error_code")) {
System.err.println(jo);
throw new TimiException(TimiCode.ERROR, jo.get("error_msg").getAsString());
}
JsonArray ja = jo.get("trans_result").getAsJsonArray();
JsonObject resultJO;
Map<String, String> result = new HashMap<>();
for (int i = 0; i < ja.size(); i++) {
resultJO = ja.get(i).getAsJsonObject();
result.put(resultJO.get("src").getAsString(), resultJO.get("dst").getAsString());
}
wait(200);
return result;
}
}