diff --git a/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java b/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java index 32623e55..9deefeec 100644 --- a/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java +++ b/src/main/java/com/ai/da/common/enums/AuthenticationOperationTypeEnum.java @@ -33,7 +33,11 @@ public enum AuthenticationOperationTypeEnum { */ UPDATE_USERINFO, - REGISTER; + REGISTER, + /** + * Global_Award 活动验证 + */ + GLOBAL_AWARD; public static AuthenticationOperationTypeEnum of(String name) { return Stream.of(AuthenticationOperationTypeEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null); diff --git a/src/main/java/com/ai/da/controller/GlobalAwardController.java b/src/main/java/com/ai/da/controller/GlobalAwardController.java index 9d59d3d5..2d461410 100644 --- a/src/main/java/com/ai/da/controller/GlobalAwardController.java +++ b/src/main/java/com/ai/da/controller/GlobalAwardController.java @@ -2,6 +2,7 @@ package com.ai.da.controller; import com.ai.da.common.response.Response; import com.ai.da.model.dto.ContestantDTO; +import com.ai.da.model.vo.CheckOTPVO; import com.ai.da.service.GlobalAwardService; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.*; @@ -38,6 +39,18 @@ public class GlobalAwardController { ContestantDTO dto = globalAwardService.getContestantByEmail(email); return Response.success(dto); } + + @GetMapping("/checkEmail") + public Response checkEmail(@RequestParam("email") String email) { + globalAwardService.checkEmail(email); + return Response.success(); + } + + @GetMapping("/checkCode") + public Response checkOTP(@RequestParam("email") String email, @RequestParam("code") String code) { + return Response.success(globalAwardService.checkOTP(email, code)); + } + } diff --git a/src/main/java/com/ai/da/model/dto/ContestantDTO.java b/src/main/java/com/ai/da/model/dto/ContestantDTO.java index 342be7a7..092d70b4 100644 --- a/src/main/java/com/ai/da/model/dto/ContestantDTO.java +++ b/src/main/java/com/ai/da/model/dto/ContestantDTO.java @@ -1,5 +1,6 @@ package com.ai.da.model.dto; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** @@ -24,6 +25,9 @@ public class ContestantDTO { * 是否确认覆盖已存在记录(false 表示发现已有记录时仅返回 existingRecord,不覆盖) */ private Boolean confirm = false; + + @NotBlank + private String secureToken; } diff --git a/src/main/java/com/ai/da/model/vo/CheckOTPVO.java b/src/main/java/com/ai/da/model/vo/CheckOTPVO.java new file mode 100644 index 00000000..4b85950a --- /dev/null +++ b/src/main/java/com/ai/da/model/vo/CheckOTPVO.java @@ -0,0 +1,16 @@ +package com.ai.da.model.vo; + +import com.ai.da.model.dto.ContestantDTO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CheckOTPVO { + + private String secureToken; + + private ContestantDTO contestantDTO; +} diff --git a/src/main/java/com/ai/da/service/GlobalAwardService.java b/src/main/java/com/ai/da/service/GlobalAwardService.java index 212a23e7..3b66579c 100644 --- a/src/main/java/com/ai/da/service/GlobalAwardService.java +++ b/src/main/java/com/ai/da/service/GlobalAwardService.java @@ -1,15 +1,23 @@ package com.ai.da.service; import com.ai.da.model.dto.ContestantDTO; +import com.ai.da.model.vo.CheckOTPVO; import org.springframework.web.multipart.MultipartFile; import java.util.Map; public interface GlobalAwardService { String uploadPdf(MultipartFile file, String email) throws Exception; + String uploadVideo(MultipartFile file, String email) throws Exception; + Map saveContestant(ContestantDTO request); + com.ai.da.model.dto.ContestantDTO getContestantByEmail(String email); + + void checkEmail(String email); + + CheckOTPVO checkOTP(String email, String otp); } diff --git a/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java b/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java index d1bac9bf..033817a7 100644 --- a/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/GlobalAwardServiceImpl.java @@ -1,41 +1,64 @@ package com.ai.da.service.impl; import com.ai.da.common.config.exception.BusinessException; +import com.ai.da.common.enums.AuthenticationOperationTypeEnum; +import com.ai.da.common.utils.*; +import com.ai.da.mapper.primary.AccountMapper; import com.ai.da.mapper.primary.ContestantMapper; +import com.ai.da.mapper.primary.NotificationMapper; +import com.ai.da.mapper.primary.entity.Account; import com.ai.da.mapper.primary.entity.Contestant; +import com.ai.da.mapper.primary.entity.Notification; import com.ai.da.model.dto.ContestantDTO; +import com.ai.da.model.dto.PublishSysNotificationDTO; +import com.ai.da.model.vo.CheckOTPVO; import com.ai.da.service.GlobalAwardService; +import com.ai.da.service.MessageCenterService; +import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import jakarta.annotation.Resource; -import com.ai.da.common.utils.MinioUtil; -import java.io.File; + import java.io.IOException; -import java.nio.file.Files; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; @Service @Slf4j +@RequiredArgsConstructor public class GlobalAwardServiceImpl implements GlobalAwardService { @Resource private ContestantMapper contestantMapper; + private final AccountMapper accountMapper; + + private final MessageCenterService messageCenterService; + + private final NotificationMapper notificationMapper; + + private final RedisUtil redisUtil; + @Value("${file.upload.dir:uploads}") private String uploadDir; private static final DateTimeFormatter YYYY_MM_DD = DateTimeFormatter.ofPattern("yyyy/MM"); + + private static final String tokenCacheKey = AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + ":"; + @Value("${minio.bucket:contestants}") private String minioBucket; + @Value("${global.award.link}") + private String link; + @Resource private MinioUtil minioUtil; @@ -111,6 +134,14 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { throw new IllegalArgumentException("email required"); } + String key = tokenCacheKey + request.getEmail(); + String tokenCache = redisUtil.getFromString(key); + if (StringUtils.isBlank(tokenCache)) { + throw new BusinessException("请先完成邮箱认证"); + } else if (!tokenCache.equals(request.getSecureToken())){ + throw new BusinessException("身份认证失败,请先完成邮箱认证"); + } + QueryWrapper qw = new QueryWrapper<>(); qw.eq("email", request.getEmail()); Contestant existing = contestantMapper.selectOne(qw); @@ -185,6 +216,59 @@ public class GlobalAwardServiceImpl implements GlobalAwardService { dto.setVideoPath(existing.getVideoPath()); return dto; } + + public void checkEmail(String email) { + List validRole = Arrays.asList(1, 2, 7, 8); + // 1. 验证邮箱在aida中有无账号 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(Account::getUserEmail, email); + List accounts = accountMapper.selectList(queryWrapper); + if (accounts.isEmpty()) { + throw new BusinessException("请注册并订阅AiDA,再重新提交申请"); + } + + // 2. 验证账号是否是付费用户(如果首次提交是,但是修改的时候已经不是了,how?不允许修改吗) + if (validRole.contains(accounts.getFirst().getSystemUser())) { + String randomVerifyCode = RandomsUtil.generateVerifyCode(100000L, 999999L); + LocalCacheUtils.setVerifyCodeCache( + AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + "_" + email, randomVerifyCode); + SendEmailUtil.send(email, null, + SendEmailUtil.LOGIN_TEMPLATE_ID, randomVerifyCode); + } else { + throw new BusinessException("请订阅AiDA,再重新提交申请"); + } + } + + public CheckOTPVO checkOTP(String email, String otp) { + String otpCache = LocalCacheUtils.getVerifyCodeCache(AuthenticationOperationTypeEnum.GLOBAL_AWARD.name() + "_" + email); + assert otpCache != null; + if (otpCache.equals(otp)) { + // 1. 生成唯一token + String secureToken = UUID.randomUUID().toString().replace("-", ""); + redisUtil.addToString(tokenCacheKey + email, secureToken, 3 * 24 * 60 * 60L); + + return new CheckOTPVO(secureToken, getContestantByEmail(email)); + } else { + throw new BusinessException("验证码错误,请重试"); + } + } + + // 发送站内信 + public void sendSiteMsg(String applicationId, Long userId) { + PublishSysNotificationDTO sysNotificationDTO = new PublishSysNotificationDTO(); + Notification notification = new Notification(); + notification.setType("system"); + notification.setReceiverId(userId); + sysNotificationDTO.setTitle("System Notification 系统通知"); + // todo + sysNotificationDTO.setContent(link + applicationId); + notification.setContent(JSON.toJSONString(sysNotificationDTO)); + notification.setIsRead(0); + notification.setCreateTime(LocalDateTime.now()); + notificationMapper.insert(notification); + // 这里推送消息是在接受到视频生成结束后发生的,所以UserContext中没有用户信息 + messageCenterService.pushMessage("system", userId); + } } diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 7f5c52e7..a53ef5de 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -158,4 +158,5 @@ google.client.id=157095842121-kdd1fdf8m8nudvj9sprstb2k2prnf9e4.apps.googleuserco #google.client.secret=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 google.client.secret=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 google.redirect.uri=https://develop.api.aida.com.hk/api/third/party/auth/google_callback -design.callback.url=https://develop.api.aida.com.hk/api/third/party/receiveDesignResults \ No newline at end of file +design.callback.url=https://develop.api.aida.com.hk/api/third/party/receiveDesignResults +global.award.link=https://develop.aida.com.hk/ \ No newline at end of file diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 02d5f809..3270fc19 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -156,4 +156,5 @@ FREEPIK_API_KEY=FPSX94e5917d376a4facb87dabbaa0319c72 google.client.id=29310152396-nnsd3h533fld665oguu8ovrt1nukmt46.apps.googleusercontent.com google.client.secret=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 google.redirect.uri=https://www.api.aida.com.hk/api/third/party/auth/google_callback -design.callback.url=https://api.aida.com.hk/api/third/party/receiveDesignResults \ No newline at end of file +design.callback.url=https://api.aida.com.hk/api/third/party/receiveDesignResults +global.award.link=https://www.aida.com.hk/ \ No newline at end of file