Commit a4c79a5d authored by 王亮's avatar 王亮

Feature/modify phone no 20221117

parent d5f001a8
@startuml 用户注销
actor 用户 as user
participant 电商 as kdsp
participant 卡包 as kb
participant Talos as talos
participant 用户中心 as uc
participant 消息中心 as msg
database 数据库 as db
== 注销入口 /close/account/exist ==
hnote across: 注销入口API开放给电商,入参经过RSA证书加密
user -> kdsp : 获取注销入口
kdsp -> kb : 是否需要注销
note right
1. 用户已经注销,直接返回check=false
2. 有绑定关系,返回check=true,url
3. 检测是否开户,返回check=true,url
end note
kb -> db : FinanceUserRef,判断注销状态、绑定关系
kb -> uc : 获取是否存在金融用户
kb -> talos : 检测是否开户
alt 不需要注销
kb --> kdsp : check:false
kdsp --> user : check:false, 没有注销入口
else 需要注销
kb -> uc : 获取用户token
uc --> kb : 返回token和用户数据
kb --> kdsp : check:true, 组装带token的注销URL返回
kdsp --> user : check:true, 返回注销URL, 显示注销入口
end
hnote across: 以下API为金融API,校验金融token
== 检测在途 /close/account/check ==
user -> kb : 跳转注销页面,检测是否可以注销
kb-> talos : 检测是否有在途
talos --> kb : 返回是否有在途
kb --> user : 有在途,不允许注销,无在途可以注销
== 发送[注销/解绑]短信 /account/sendSms ==
user -> kb : 获取验证码
note right
校验短信发送规则
1. 当天最大发送10次
2. 1小时最大发送5次
3. 58秒内不能重复发送
end note
kb -> msg : 规则校验通过,发送短信
msg --> kb : 短信发送完成
note left
1. 保存验证码到redis
2. 更新短信校验规则到redis[发送次数和时间]
end note
== 确认注销 /close/account/confirm ==
user -> kb : 确认注销
note right
1. 短信验证码校验
2. 注销状态校验
3. 是否在途校验
end note
kb -> talos : 调用注销API
talos --> kb : 返回注销结果
note left
1. BusinessCode成功是0000 用户不存在是4001 用户已注销4002
2. 以上状态都认为注销成功
end note
alt API调用异常
kb -> db : UserLogoffLog,新增处理中[PROCESS]记录,job异步补偿
kb --> user : 返回注销中,job异常处理
else 注销失败
kb -> db : UserLogoffLog,新增注销失败[FAIL]记录
kb --> user : 返回注销失败
else 注销成功
kb -> db : FinanceUserRef,更新status=注销
kb -> db : UserLogoffLog,新增注销成功[SUCCESS]记录
kb --> kdsp : 异步通知电商清除缓存
kb --> user : 返回注销成功
note over kb, user: 注销成功,通知KDSP清除缓存,卡包清理缓存
end
== 确认解绑 /unbind/account/confirm ==
user -> kb : 确认解绑
note right
1. 短信验证码校验
2. 注销状态校验
3. 是否在途校验
4. 更新绑定状态
end note
kb -> db : FinanceUserRef,更新enable=无效
kb --> kdsp : 异步通知电商清除缓存
kb --> user : 解绑成功
note over kb, user: 解绑成功,通知KDSP清除缓存,卡包清理缓存
@enduml
......@@ -46,18 +46,6 @@
<artifactId>SensorsAnalyticsSDK</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-core</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>RELEASE</version>
</dependency>
<!-- swagger2 end -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
......
package cn.quantgroup.customer.enums;
public enum ErrorCodeEnum {
NET_ERROR(6001L, "网络通讯异常"),
RETURN_ERROR(7001L, "返回值异常"),
PARAM_ERROR(7002L, "参数异常"),
NO_TOKEN(8001L,"缺少token信息"),
ILLEGAL_TOKEN(8002L,"token信息有误"),
PHONE_EQUALS(8003L, "新手机号与原手机号相同"),
;
public String getMessage() {
return message;
}
public Long getCode() {
return code;
}
private Long code;
private String message;
ErrorCodeEnum(Long code, String message) {
this.code = code;
this.message = message;
}
}
package cn.quantgroup.xyqb.controller.modifyphoneno;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.AuditReq;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.ModifyPhoneNoQueryReq;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.Step1Req;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.Step2Req;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.*;
import cn.quantgroup.xyqb.controller.modifyphoneno.resp.ProgressResp;
import cn.quantgroup.xyqb.controller.modifyphoneno.resp.UserModifyPhoneRecordResp;
import cn.quantgroup.xyqb.entity.ModifyPhoneNo;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.UserModifyPhoneRecord;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.service.user.IModifyPhoneNoService;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.SimpleDateFormat;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
/**
* 用户手机号修改相关api
......@@ -37,6 +33,7 @@ public class ModifyPhoneNoController implements IBaseController {
@Resource
private IModifyPhoneNoService modifyPhoneNoService;
@GetMapping("/progress")
public JsonResult<ProgressResp> progress() {
User user = getCurrentUserFromRedis();
......@@ -113,4 +110,41 @@ public class ModifyPhoneNoController implements IBaseController {
}
/*------------------------------------------------------------------------------------------------------------*/
/**
* 客服系统修改手机号
* @param modifyPhoneRecord
* @return
*/
@PostMapping("/submitModify")
public JsonResult submitModify(@Valid @RequestBody ModifyPhoneRecord modifyPhoneRecord) {
modifyPhoneNoService.submitModify(modifyPhoneRecord);
return JsonResult.buildSuccessResult();
}
/**
* 金融修改手机号
* @param modifyPhoneRecord
* @return
*/
@PostMapping("/financial/submitModify")
public JsonResult financialSubmitModify(@Valid @RequestBody ModifyPhoneRecord modifyPhoneRecord) {
modifyPhoneNoService.financialSubmitModify(modifyPhoneRecord);
return JsonResult.buildSuccessResult();
}
@PostMapping( "/modifyPhoneNolist")
public JsonResult getModifyPhoneNolist(@RequestParam("userId") Long userId,
@RequestParam(value = "pageNo", defaultValue = "1", required = false) int pageNo,
@RequestParam(value = "pageSize", defaultValue = "10", required = false) int pageSize) {
Page<UserModifyPhoneRecordResp> pageData = modifyPhoneNoService.query(userId, pageNo, pageSize).map(record -> {
UserModifyPhoneRecordResp userModifyPhoneRecordResp = new UserModifyPhoneRecordResp();
BeanUtils.copyProperties(record, userModifyPhoneRecordResp);
userModifyPhoneRecordResp.setReason(record.getReason().name());
return userModifyPhoneRecordResp;
});
return JsonResult.buildSuccessResult("修改手机号列表", pageData);
}
}
package cn.quantgroup.xyqb.controller.modifyphoneno.req;
import cn.quantgroup.xyqb.entity.enums.Reason;
import cn.quantgroup.xyqb.util.ValidationUtil;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
@Data
public class ModifyPhoneRecord implements Serializable {
// @NotNull(message = "用户ID不能为空")
private Long userId;
@NotBlank(message = "原手机号不能为空")
@Pattern(regexp = ValidationUtil.phoneRegExp, message = "原手机号码格式错误")
private String prevPhoneNo;
@NotBlank(message = "新手机号不能为空")
@Pattern(regexp = ValidationUtil.phoneRegExp, message = "新手机号码格式错误")
private String curPhoneNo;
@NotNull(message = "修改原因不能为空")
private Reason reason;
@NotBlank(message = "操作人不能为空")
private String operator;
// @NotBlank(message = "备注不能为空")
private String remark;
}
package cn.quantgroup.xyqb.controller.modifyphoneno.resp;
import cn.quantgroup.xyqb.entity.enums.Reason;
import lombok.Data;
import java.sql.Timestamp;
@Data
public class UserModifyPhoneRecordResp {
private Long id;
private Long userId;
private String prevPhoneNo;
private String curPhoneNo;
private String reason;
private String operator;
private String remark;
private String financialResponse;
private Timestamp createdAt;
private Timestamp updatedAt;
}
package cn.quantgroup.xyqb.entity;
import cn.quantgroup.xyqb.entity.enums.Reason;
import cn.quantgroup.xyqb.util.encrypt.CryptConverter;
import javax.persistence.Convert;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
/**
* Created by 11 on 2017/3/22.
*/
@Data
@Entity
@Table(name = "user_modify_phone_record")
public class UserModifyPhoneRecord extends BaseEntity implements Serializable {
@Column(name = "user_id")
private Long userId;
@Column(name = "prev_phone_no")
@Convert(converter = CryptConverter.class)
private String prevPhoneNo;
@Column(name = "cur_phone_no")
@Convert(converter = CryptConverter.class)
private String curPhoneNo;
@Column(name = "reason")
private Reason reason;
@Column(name = "operator")
private String operator;
@Column(name = "remark")
private String remark;
@Column(name = "financial_response")
private String financialResponse;
}
package cn.quantgroup.xyqb.entity.enums;
/**
* Created by FrankChow on 15/7/15.
*/
public enum Reason {
ABANDONED("原注册手机已经废弃"),
SYNCHRONOUSCHANGE("电商侧手机号需同步修改金融侧"),
ORTHER("其它原因"),
;
private String name;
Reason(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
package cn.quantgroup.xyqb.repository;
import cn.quantgroup.xyqb.entity.UserModifyPhoneRecord;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface IModifyPhoneRecordRepository extends JpaRepository<UserModifyPhoneRecord, Long>, JpaSpecificationExecutor<UserModifyPhoneRecord> {
}
......@@ -16,5 +16,4 @@ public interface IUserHashMappingRepository extends JpaRepository<UserHashMappin
@Transactional
void deleteByUserId(Long userId);
}
......@@ -63,4 +63,6 @@ public interface IHttpService {
String post(String uri, Map<String, String> headers, Map<String, String> parameters);
String postJson(String uri, Map<String, String> parameters);
String postJson(String uri, Map<String, String> headers, Map<String, String> parameters);
}
......@@ -6,15 +6,6 @@ import cn.quantgroup.xyqb.service.http.IHttpService;
import cn.quantgroup.xyqb.util.PasswordUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.net.ssl.SSLContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.Charsets;
import org.apache.commons.collections.MapUtils;
......@@ -53,6 +44,12 @@ import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import javax.net.ssl.SSLContext;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
* @author mengfan.feng
* @time 2015-08-13 10:19
......@@ -90,8 +87,8 @@ public class HttpServiceImpl implements IHttpService {
}
@Override
public String post(String uri, Map<String, String> headers, Map<String, String> parameters) {
return doHttp(RequestBuilder.post(), uri, headers, parameters, BodyType.FORM);
public String postJson(String uri, Map<String, String> headers, Map<String, String> parameters) {
return doHttp(RequestBuilder.post(), uri, headers, parameters, BodyType.JSON);
}
@Override
......@@ -99,6 +96,11 @@ public class HttpServiceImpl implements IHttpService {
return doHttp(RequestBuilder.post(), uri, null, parameters, BodyType.JSON);
}
@Override
public String post(String uri, Map<String, String> headers, Map<String, String> parameters) {
return doHttp(RequestBuilder.post(), uri, headers, parameters, BodyType.FORM);
}
/**
* Send Http
*
......
package cn.quantgroup.xyqb.service.user;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.AuditReq;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.ModifyPhoneNoQueryReq;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.Step1Req;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.Step2Req;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.*;
import cn.quantgroup.xyqb.controller.modifyphoneno.resp.ProgressResp;
import cn.quantgroup.xyqb.entity.ModifyPhoneNo;
import cn.quantgroup.xyqb.entity.UserModifyPhoneRecord;
import org.springframework.data.domain.Page;
/**
......@@ -33,4 +31,10 @@ public interface IModifyPhoneNoService {
void feedback(Long id);
void audit(AuditReq auditReq);
void submitModify(ModifyPhoneRecord modifyPhoneRecord);
void financialSubmitModify(ModifyPhoneRecord modifyPhoneRecord);
Page<UserModifyPhoneRecord> query(Long userId, int pageNo, int pageSize);
}
......@@ -6,9 +6,10 @@ import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.LoginProperties;
import cn.quantgroup.xyqb.model.UserInfo;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
/**
* Created by Miraculous on 15/7/5.
......@@ -114,4 +115,6 @@ public interface IUserService {
* @param userId 用户id
*/
void deregister(Long userId);
User submitModifyPhone(String prevPhoneNo, String curPhoneNo);
}
......@@ -628,4 +628,29 @@ public class UserServiceImpl implements IUserService, IBaseController {
/* 删除用户租户关联 羊小咩租户*/
productLoginRepository.deleteTenantAndPhoneNo("qtg", "yxm", user.getPhoneNo());
}
@Override
@Transactional(rollbackFor = Exception.class)
public User submitModifyPhone(String prevPhoneNo, String curPhoneNo) {
//1. 判断新手机号是否存在
User newPhoneUser = findByPhoneInDb(curPhoneNo);
if (Objects.nonNull(newPhoneUser)) {
//新手机号已存在
throw new DataException("新手机号存在, 用户修改手机号后新手机号注册了。");
}
User oldPhoneUser = findByPhoneInDb(prevPhoneNo);
if (Objects.isNull(oldPhoneUser)) {
throw new DataException("旧手机号不存在, 可能已经修改成功了。");
}
//2. 执行修改
oldPhoneUser.setPhoneNo(curPhoneNo);
oldPhoneUser.setEncryptedPhoneNo(curPhoneNo);
User user = userRepository.saveAndFlush(oldPhoneUser);
//3. 发送事件
PhoneNoUpdateEvent phoneNoUpdateEvent = new PhoneNoUpdateEvent(this, user, prevPhoneNo);
applicationEventPublisher.publishEvent(phoneNoUpdateEvent);
return oldPhoneUser;
}
}
package cn.quantgroup.xyqb.util;
import cn.quantgroup.security.AESEncryption;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import org.apache.commons.lang3.StringUtils;
public class AESUtils {
public static AESEncryption encryption;
/**
* V2方法不再考虑历史数据兼容问题
*
* @param plainValue 明文
* @return 密文
*/
public static String encryptV2(final String plainValue) {
if (StringUtils.isNotEmpty(plainValue)) {
return encryption().encryptBase64(plainValue);
}
return plainValue;
}
/**
* V2方法不再考虑历史数据兼容问题
*
* @param cipherValue 明文
* @return 密文
*/
public static String decryptV2(String cipherValue) {
return StringUtils.isNotEmpty(cipherValue) ? encryption().decryptBase64(cipherValue)
: cipherValue;
}
public static AESEncryption encryption() {
if (encryption == null) {
Config config = ConfigService.getAppConfig();
String key = config.getProperty("keystone.security.key", "");
String iv = config.getProperty("keystone.security.iv", "");
encryption = new AESEncryption(key, iv, true);
}
return encryption;
}
}
package cn.quantgroup.xyqb.util.encrypt;
import cn.quantgroup.xyqb.util.AESUtils;
import javax.persistence.AttributeConverter;
public class CryptConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String attribute) {
return AESUtils.encryptV2(attribute);
}
@Override
public String convertToEntityAttribute(String dbData) {
return AESUtils.decryptV2(dbData);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment