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 @@ ...@@ -46,18 +46,6 @@
<artifactId>SensorsAnalyticsSDK</artifactId> <artifactId>SensorsAnalyticsSDK</artifactId>
<version>3.2.0</version> <version>3.2.0</version>
</dependency> </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> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <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; package cn.quantgroup.xyqb.controller.modifyphoneno;
import cn.quantgroup.xyqb.controller.IBaseController; import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.AuditReq; import cn.quantgroup.xyqb.controller.modifyphoneno.req.*;
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.resp.ProgressResp; 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.ModifyPhoneNo;
import cn.quantgroup.xyqb.entity.User; import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.entity.UserModifyPhoneRecord;
import cn.quantgroup.xyqb.model.JsonResult; import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.service.user.IModifyPhoneNoService; import cn.quantgroup.xyqb.service.user.IModifyPhoneNoService;
import com.fasterxml.jackson.databind.ObjectMapper; 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.annotation.Resource;
import javax.validation.Valid; import javax.validation.Valid;
import org.springframework.data.domain.Page; import java.text.SimpleDateFormat;
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;
/** /**
* 用户手机号修改相关api * 用户手机号修改相关api
...@@ -37,6 +33,7 @@ public class ModifyPhoneNoController implements IBaseController { ...@@ -37,6 +33,7 @@ public class ModifyPhoneNoController implements IBaseController {
@Resource @Resource
private IModifyPhoneNoService modifyPhoneNoService; private IModifyPhoneNoService modifyPhoneNoService;
@GetMapping("/progress") @GetMapping("/progress")
public JsonResult<ProgressResp> progress() { public JsonResult<ProgressResp> progress() {
User user = getCurrentUserFromRedis(); User user = getCurrentUserFromRedis();
...@@ -113,4 +110,41 @@ public class ModifyPhoneNoController implements IBaseController { ...@@ -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 ...@@ -16,5 +16,4 @@ public interface IUserHashMappingRepository extends JpaRepository<UserHashMappin
@Transactional @Transactional
void deleteByUserId(Long userId); void deleteByUserId(Long userId);
} }
...@@ -63,4 +63,6 @@ public interface IHttpService { ...@@ -63,4 +63,6 @@ public interface IHttpService {
String post(String uri, Map<String, String> headers, Map<String, String> parameters); 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> parameters);
String postJson(String uri, Map<String, String> headers, Map<String, String> parameters);
} }
...@@ -6,15 +6,6 @@ import cn.quantgroup.xyqb.service.http.IHttpService; ...@@ -6,15 +6,6 @@ import cn.quantgroup.xyqb.service.http.IHttpService;
import cn.quantgroup.xyqb.util.PasswordUtil; import cn.quantgroup.xyqb.util.PasswordUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps; 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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.Charsets; import org.apache.commons.codec.Charsets;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
...@@ -53,6 +44,12 @@ import org.apache.http.message.BasicNameValuePair; ...@@ -53,6 +44,12 @@ import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service; 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 * @author mengfan.feng
* @time 2015-08-13 10:19 * @time 2015-08-13 10:19
...@@ -90,8 +87,8 @@ public class HttpServiceImpl implements IHttpService { ...@@ -90,8 +87,8 @@ public class HttpServiceImpl implements IHttpService {
} }
@Override @Override
public String post(String uri, Map<String, String> headers, Map<String, String> parameters) { public String postJson(String uri, Map<String, String> headers, Map<String, String> parameters) {
return doHttp(RequestBuilder.post(), uri, headers, parameters, BodyType.FORM); return doHttp(RequestBuilder.post(), uri, headers, parameters, BodyType.JSON);
} }
@Override @Override
...@@ -99,6 +96,11 @@ public class HttpServiceImpl implements IHttpService { ...@@ -99,6 +96,11 @@ public class HttpServiceImpl implements IHttpService {
return doHttp(RequestBuilder.post(), uri, null, parameters, BodyType.JSON); 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 * Send Http
* *
......
package cn.quantgroup.xyqb.service.user; package cn.quantgroup.xyqb.service.user;
import cn.quantgroup.xyqb.controller.modifyphoneno.req.AuditReq; import cn.quantgroup.xyqb.controller.modifyphoneno.req.*;
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.resp.ProgressResp; import cn.quantgroup.xyqb.controller.modifyphoneno.resp.ProgressResp;
import cn.quantgroup.xyqb.entity.ModifyPhoneNo; import cn.quantgroup.xyqb.entity.ModifyPhoneNo;
import cn.quantgroup.xyqb.entity.UserModifyPhoneRecord;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
/** /**
...@@ -33,4 +31,10 @@ public interface IModifyPhoneNoService { ...@@ -33,4 +31,10 @@ public interface IModifyPhoneNoService {
void feedback(Long id); void feedback(Long id);
void audit(AuditReq auditReq); 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; ...@@ -6,9 +6,10 @@ import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.model.JsonResult; import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.LoginProperties; import cn.quantgroup.xyqb.model.LoginProperties;
import cn.quantgroup.xyqb.model.UserInfo; import cn.quantgroup.xyqb.model.UserInfo;
import javax.servlet.http.HttpServletRequest;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest;
/** /**
* Created by Miraculous on 15/7/5. * Created by Miraculous on 15/7/5.
...@@ -114,4 +115,6 @@ public interface IUserService { ...@@ -114,4 +115,6 @@ public interface IUserService {
* @param userId 用户id * @param userId 用户id
*/ */
void deregister(Long userId); void deregister(Long userId);
User submitModifyPhone(String prevPhoneNo, String curPhoneNo);
} }
...@@ -628,4 +628,29 @@ public class UserServiceImpl implements IUserService, IBaseController { ...@@ -628,4 +628,29 @@ public class UserServiceImpl implements IUserService, IBaseController {
/* 删除用户租户关联 羊小咩租户*/ /* 删除用户租户关联 羊小咩租户*/
productLoginRepository.deleteTenantAndPhoneNo("qtg", "yxm", user.getPhoneNo()); 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