1、修改用户名

2、更改账号绑定邮箱地址
This commit is contained in:
2024-09-25 16:15:18 +08:00
parent b43f9baead
commit 2bc5fef175
13 changed files with 243 additions and 7 deletions

View File

@@ -65,4 +65,7 @@ public class CommonConstant {
public static final Long MAXIMUM_USER_ID = 704L;
// public static final Long MAXIMUM_USER_ID = 225L;
// 激活更改邮箱 链接有效期 毫秒 3天
public static final Long CHANGE_MAILBOX_LINK_VALIDITY = 259200000L;
}

View File

@@ -23,7 +23,11 @@ public enum AuthenticationOperationTypeEnum {
/**
* 忘记密码
*/
FORGET_PWD;
FORGET_PWD,
/**
* 更改邮箱
*/
CHANGE_MAILBOX;
public static AuthenticationOperationTypeEnum of(String name) {
return Stream.of(AuthenticationOperationTypeEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null);

View File

@@ -51,7 +51,7 @@ public class AuthenticationFilter extends OncePerRequestFilter {
"/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back","/api/alipay-hk/trade/notify",
"/api/portfolio/page", "/api/portfolio/detail", "/api/portfolio/commentPage", "/api/portfolio/viewsIncrease",
"/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify",
"/notification"
"/notification","/api/account/activateNewEmail"
);
@Override

View File

@@ -2,6 +2,7 @@ package com.ai.da.common.security.jwt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.security.config.SecurityProperties;
import com.ai.da.model.vo.AuthPrincipalVo;
import com.alibaba.fastjson.JSON;
@@ -30,6 +31,7 @@ public class JWTTokenHelper {
private static final String ISSUER = "DWJ";
private static final String AUTHORITIES = "authorities";
private static final String CHANGE_MAILBOX = "changeMailbox";
public String createToken(AuthPrincipalVo principal) {
String token = Jwts.builder()
@@ -65,4 +67,21 @@ public class JWTTokenHelper {
token = token.replaceAll(securityProperties.getJwtTokenPrefix(), "");
return Jwts.parser().setSigningKey(securityProperties.getJwtSecret()).parseClaimsJws(token).getBody();
}
public String createToken(Long userId, String userEmail){
String token = Jwts.builder()
.setId(String.valueOf(userId))
.setSubject(userEmail + "_" + userId)
.setIssuedAt(new Date())
.setIssuer(ISSUER)
.claim(CHANGE_MAILBOX, JSON.toJSONString(new ArrayList<>()))//自定义属性 权限
.setExpiration(new Date(System.currentTimeMillis() + CommonConstant.CHANGE_MAILBOX_LINK_VALIDITY))
.signWith(SignatureAlgorithm.HS256, securityProperties.getJwtSecret())
.compact();
return token;
}
public String parseToEmailAndId(String token) {
return parser(token).getSubject();
}
}

View File

@@ -1,6 +1,7 @@
package com.ai.da.common.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
@@ -240,4 +241,17 @@ public class RedisUtil {
return redisTemplate.opsForValue().increment(key, 0);
}
public final static String NICKNAME_MODIFY_TIMES = "NicknameModifyTimes:";
public void increaseCount(String key) {
redisTemplate.opsForValue().increment(key);
}
public Long getIncrementCount(String key) {
return redisTemplate.opsForValue().increment(key, 0);
}
public void setKeyExpire(String key, Long expire) {
redisTemplate.expire(key, expire, TimeUnit.DAYS);
}
}

View File

@@ -53,6 +53,7 @@ public class SendEmailUtil {
* 绑定邮箱
*/
public static String BIND_MAILBOX_SUBJECT = "绑定邮箱";
public static String CHANGE_MAILBOX_SUBJECT = "Change Mailbox";
/**
* 登入模板id
*/
@@ -71,6 +72,8 @@ public class SendEmailUtil {
*/
public static Long BIND_MAILBOX_TEMPLATE_ID = 45619L;
public static Long CHANGE_MAILBOX_TEMPLATE_ID = 128210L;
public static Boolean send(String receiverAddress, String ip, Long templateId, String verifyCode) {
try {
@@ -92,7 +95,8 @@ public class SendEmailUtil {
req.setDestination(new String[]{receiverAddress});
String subject = templateId == LOGIN_TEMPLATE_ID ? LOGIN_SUBJECT :
templateId == UPDATE_PWD_TEMPLATE_ID ? FORGET_PWD_SUBJECT :
templateId == EXCEPTION_ID_TEMPLATE_ID ? EXCEPTION_ID_SUBJECT : BIND_MAILBOX_SUBJECT;
templateId == EXCEPTION_ID_TEMPLATE_ID ? EXCEPTION_ID_SUBJECT :
templateId == CHANGE_MAILBOX_TEMPLATE_ID ? CHANGE_MAILBOX_SUBJECT : BIND_MAILBOX_SUBJECT;
req.setSubject(subject);
req.setTemplate(contractTemplate(templateId, verifyCode, ip));
@@ -626,4 +630,49 @@ public class SendEmailUtil {
throw new BusinessException("failed.to.send.mail");
}
}
private final static Long CHANGE_MAILBOX_CONFIRM_CN = 128278L;
private final static Long CHANGE_MAILBOX_CONFIRM_EN = 128277L;
public static void changeMailboxConfirm(String receiverAddress, String language, String name, String link){
try{
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey此处还需注意密钥对的保密
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露并威胁账号下所有资源的安全性。以下代码示例仅供参考建议采用更安全的方式来使用密钥请参见https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
Credential cred = new Credential(SECRET_ID, SECRET_KEy);
// 实例化一个http选项可选的没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("ses.tencentcloudapi.com");
// 实例化一个client选项可选的没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
SesClient client = new SesClient(cred, "ap-hongkong", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
SendEmailRequest req = new SendEmailRequest();
req.setFromEmailAddress(SEND_ADDRESS);
req.setDestination(new String[]{receiverAddress});
Template template = new Template();
if (language.equals("ENGLISH")){
req.setSubject("Change the email address bound to the AiDA account");
template.setTemplateID(CHANGE_MAILBOX_CONFIRM_EN);
}else {
req.setSubject("更换AiDA账号绑定的邮箱地址");
template.setTemplateID(CHANGE_MAILBOX_CONFIRM_CN);
}
JSONObject param = new JSONObject();
param.put("userName", name);
param.put("link", link);
template.setTemplateData(param.toJSONString());
req.setTemplate(template);
// 返回的resp是一个SendEmailResponse的实例与请求对象对应
SendEmailResponse resp = client.SendEmail(req);
log.info("短信发送结果res###{}", SendEmailResponse.toJsonString(resp));
} catch (TencentCloudSDKException e) {
log.info("邮件发送失败###{}", e.toString());
throw new BusinessException("failed.to.send.mail");
}
}
}

View File

@@ -219,4 +219,40 @@ public class AccountController {
public Response<PersonalHomepageVO> getPersonalHomepage(@RequestParam("id") Long id){
return Response.success(accountService.getPersonalHomepage(id));
}
@ApiOperation(value = "getNicknameModifyTimes")
@GetMapping("/getNicknameModifyTimes")
public Response<Map<String, Long>> getNicknameModifyTimes(){
return Response.success(accountService.getNicknameModifyTimes());
}
@ApiOperation(value = "editUserName")
@GetMapping("/editUserName")
public Response<String> editUserName(@RequestParam("newUserName") String newUserName){
accountService.editUserName(newUserName);
return Response.success("success");
}
@ApiOperation(value = "verifyUserEmail")
@GetMapping("/verifyUserEmail")
public Response<String> verifyUserEmail(@RequestParam("verifyCode") String verifyCode){
accountService.verifyUserEmail(verifyCode);
return Response.success("success");
}
@ApiOperation(value = "changeUserEmail")
@GetMapping("/changeUserEmail")
public Response<String> changeUserEmail(@RequestParam("newMailbox") String newMailbox){
accountService.changeUserEmail(newMailbox);
return Response.success("success");
}
@ApiOperation(value = "activateNewEmail")
@GetMapping("/activateNewEmail")
public Response<String> activateNewEmail(@RequestParam("token") String token){
accountService.activateNewEmail(token);
return Response.success("success");
}
}

View File

@@ -15,7 +15,7 @@ public class EmailSendDTO {
private String email;
@NotBlank(message = "operationType.cannot.be.empty")
@ApiModelProperty("操作类型 LOGIN 注册 FORGET_PWD 忘记密码 BIND_MAILBOX 绑定邮箱")
@ApiModelProperty("操作类型 LOGIN 注册 FORGET_PWD 忘记密码 BIND_MAILBOX 绑定邮箱 CHANGE_MAILBOX 更改邮箱")
private String operationType;
@ApiModelProperty("异常ip")

View File

@@ -6,7 +6,9 @@ public enum SketchStyle implements IEnumDisplay{
MEDIUM("2"),
THIN("3");
THIN("3"),
CUSTOM("Custom");
private String value;

View File

@@ -167,4 +167,14 @@ public interface AccountService extends IService<Account> {
Boolean viewsIncrease(Long id);
void registerUserToVisitor();
Map<String, Long> getNicknameModifyTimes();
void editUserName(String newUserName);
void verifyUserEmail(String verifyCode);
void changeUserEmail(String newMailbox);
void activateNewEmail(String token);
}

View File

@@ -384,6 +384,10 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
result = SendEmailUtil.send(emailSendDTO.getEmail(), null,
SendEmailUtil.BIND_MAILBOX_TEMPLATE_ID, randomVerifyCode);
break;
case CHANGE_MAILBOX:
result = SendEmailUtil.send(emailSendDTO.getEmail(), null,
SendEmailUtil.CHANGE_MAILBOX_TEMPLATE_ID, randomVerifyCode);
break;
default:
}
if (!result) {
@@ -1580,8 +1584,99 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
}
private Long viewPersonalHomepageCount(Long accountId) {
redisUtil.getPersonalHomepageViewCount(accountId);
return null;
return redisUtil.getPersonalHomepageViewCount(accountId);
}
// 获取当前用户30天内 剩余昵称修改次数
public Map<String, Long> getNicknameModifyTimes(){
Long accountId = UserContext.getUserHolder().getId();
String key = RedisUtil.NICKNAME_MODIFY_TIMES + accountId;
Long times = redisUtil.getIncrementCount(key);
HashMap<String, Long> resp = new HashMap<>();
resp.put("remainingTimes", 5L - times);
resp.put("remainingDays", redisUtil.getExpire(key) == -1 ? 30L : (long) Math.ceil((double) redisUtil.getExpire(key) / (24 * 60 * 60)));
return resp;
}
// 修改用户名 允许用户30天内修改5次
@Transactional(rollbackFor = Exception.class)
public void editUserName(String newUserName){
Long accountId = UserContext.getUserHolder().getId();
// 判断当前用户是否还有修改昵称的次数
Map<String, Long> remainTimes = getNicknameModifyTimes();
Long remainingModifyTimes = remainTimes.get("remainingTimes");
if (remainingModifyTimes > 0){
Account account = new Account().setUserName(newUserName);
account.setId(accountId);
baseMapper.updateById(account);
String key = RedisUtil.NICKNAME_MODIFY_TIMES + accountId;
// 先判断有没有这个key,若没有这个key, 需要为key添加有效期
if (remainingModifyTimes == 5){
redisUtil.setKeyExpire(key, 30L);
}
// 增加修改次数
redisUtil.increaseCount(key);
}else {
throw new BusinessException("remaining.modifications", 1);
}
}
// 验证是否是本人进行邮箱绑定更改
public void verifyUserEmail(String verifyCode){
// 向旧邮箱发送验证码,以保证是当前邮箱拥有者在进行更改
String userEmail = baseMapper.selectById(UserContext.getUserHolder().getId()).getUserEmail();
//校验邮箱验证码
String verifyCodeCatch = LocalCacheUtils.getVerifyCodeCache(AuthenticationOperationTypeEnum.CHANGE_MAILBOX.name() + "_" + userEmail);
if (StringUtils.isBlank(verifyCodeCatch)) {
throw new BusinessException("the.verification.code.has.expired", ResultEnum.PROMPT.getCode());
}
if (!verifyCode.equals(verifyCodeCatch)) {
throw new BusinessException("verification.code.error", ResultEnum.PROMPT.getCode());
}
}
// 修改邮箱地址
public void changeUserEmail(String newMailbox){
AuthPrincipalVo userHolder = UserContext.getUserHolder();
Long accountId = userHolder.getId();
// 将新邮箱信息存储到redis
String key = RedisUtil.CHANGE_MAILBOX + accountId;
redisUtil.addToString(key, newMailbox, CommonConstant.CHANGE_MAILBOX_LINK_VALIDITY / 1000);
String username = userHolder.getUsername();
String token = jwtTokenHelper.createToken(accountId, newMailbox);
// 准备激活链接,链接应该要有有效期
String link = "?" + token;
// 向新邮箱发送邮件,邮件附带激活链接,点击链接进行验证
SendEmailUtil.changeMailboxConfirm(newMailbox, userHolder.getLanguage(), username, link);
}
// 验证激活链接
public void activateNewEmail(String token){
// 获取链接地址信息,更新指定用户邮箱
String emailAndId = jwtTokenHelper.parseToEmailAndId(token);
String newMailbox = emailAndId.substring(0, emailAndId.lastIndexOf("_"));
String accountId = emailAndId.substring(emailAndId.lastIndexOf("_") + 1);
// 与redis的数据对比
String key = RedisUtil.CHANGE_MAILBOX + accountId;
String preInfo = redisUtil.getFromString(key);
if (StringUtil.isNullOrEmpty(preInfo)){
throw new BusinessException("申请已过期", 1);
}else if (!preInfo.equals(newMailbox)){
throw new BusinessException("信息不匹配,请重新申请");
}
// 执行替换
log.info("执行邮箱替换用户id:{},新邮箱:{}", accountId, newMailbox);
Account account = new Account();
account.setUserEmail(newMailbox);
account.setId(Long.parseLong(accountId));
baseMapper.updateById(account);
log.info("邮箱绑定更改完成用户id:{},新邮箱:{}", accountId, newMailbox);
}
}