package com.tcm.training.admin.app.register.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.tcm.training.admin.app.auth.service.AppAuthSessionService;
|
import com.tcm.training.admin.app.auth.service.AppSmsCodeService;
|
import com.tcm.training.admin.app.auth.vo.AppLoginResponse;
|
import com.tcm.training.admin.app.auth.vo.AppLoginUserVO;
|
import com.tcm.training.admin.app.auth.vo.AppSmsCodeSendResponse;
|
import com.tcm.training.admin.app.integration.haizhentong.entity.HztAppUser;
|
import com.tcm.training.admin.app.integration.haizhentong.entity.HztClinic;
|
import com.tcm.training.admin.app.integration.haizhentong.mapper.HztAppUserMapper;
|
import com.tcm.training.admin.app.integration.haizhentong.mapper.HztClinicMapper;
|
import com.tcm.training.admin.app.share.service.AppShareService;
|
import com.tcm.training.admin.app.register.dto.AppRegisterOperatorCompleteRequest;
|
import com.tcm.training.admin.app.register.dto.AppRegisterOperatorPrepareRequest;
|
import com.tcm.training.admin.app.register.dto.AppRegisterUserRequest;
|
import com.tcm.training.admin.app.register.vo.AppRegisterClinicInfoVO;
|
import com.tcm.training.admin.app.register.vo.AppRegisterOperatorPrepareResponse;
|
import com.tcm.training.admin.app.register.vo.AppRegisterRegionDatasetVO;
|
import com.tcm.training.admin.app.register.vo.AppRegisterRegionOptionVO;
|
import com.tcm.training.admin.common.PhoneMasker;
|
import com.tcm.training.admin.utils.VzanHttpUtil;
|
import com.tcm.training.admin.live.entity.Clinic;
|
import com.tcm.training.admin.live.mapper.ClinicMapper;
|
import com.tcm.training.admin.member.entity.AppUser;
|
import com.tcm.training.admin.member.mapper.UserMapper;
|
import cn.dev33.satoken.stp.SaTokenInfo;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.util.List;
|
import java.util.UUID;
|
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.TimeUnit;
|
import java.util.regex.Pattern;
|
|
/**
|
* APP 注册服务,负责普通用户、经营者注册与海诊通资料同步。
|
*/
|
@Service
|
@Slf4j
|
public class AppRegisterService {
|
|
private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
|
private static final String REGISTER_TICKET_CACHE_PREFIX = "app:register:operator:";
|
private static final String INVITE_CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
private static final int INVITE_CODE_LENGTH = 6;
|
private static final long REGISTER_TICKET_EXPIRE_MINUTES = 10L;
|
|
private final UserMapper userMapper;
|
private final ClinicMapper clinicMapper;
|
private final AppSmsCodeService appSmsCodeService;
|
private final AppAuthSessionService appAuthSessionService;
|
private final AppRegisterRegionService appRegisterRegionService;
|
private final HztAppUserMapper hztAppUserMapper;
|
private final HztClinicMapper hztClinicMapper;
|
private final StringRedisTemplate stringRedisTemplate;
|
private final ObjectMapper objectMapper;
|
private final AppShareService appShareService;
|
private final VzanHttpUtil vzanHttpUtil;
|
|
/**
|
* 注入注册链路所需的本地库、海诊通库、验证码与缓存能力。
|
*/
|
public AppRegisterService(UserMapper userMapper,
|
ClinicMapper clinicMapper,
|
AppSmsCodeService appSmsCodeService,
|
AppAuthSessionService appAuthSessionService,
|
AppRegisterRegionService appRegisterRegionService,
|
HztAppUserMapper hztAppUserMapper,
|
HztClinicMapper hztClinicMapper,
|
StringRedisTemplate stringRedisTemplate,
|
ObjectMapper objectMapper,
|
AppShareService appShareService,
|
VzanHttpUtil vzanHttpUtil) {
|
this.userMapper = userMapper;
|
this.clinicMapper = clinicMapper;
|
this.appSmsCodeService = appSmsCodeService;
|
this.appAuthSessionService = appAuthSessionService;
|
this.appRegisterRegionService = appRegisterRegionService;
|
this.hztAppUserMapper = hztAppUserMapper;
|
this.hztClinicMapper = hztClinicMapper;
|
this.stringRedisTemplate = stringRedisTemplate;
|
this.objectMapper = objectMapper;
|
this.appShareService = appShareService;
|
this.vzanHttpUtil = vzanHttpUtil;
|
}
|
|
/**
|
* 为未注册或待注册手机号发送注册验证码。
|
*/
|
public AppSmsCodeSendResponse sendRegisterCode(String rawPhone) {
|
String phone = normalizePhone(rawPhone);
|
ensurePhoneAvailable(phone);
|
return appSmsCodeService.createRegisterCode(phone);
|
}
|
|
/**
|
* 提供 APP 注册页使用的省市区三级联动选项。
|
*/
|
public List<AppRegisterRegionOptionVO> listRegions(String parentCode) {
|
return appRegisterRegionService.listRegions(parentCode);
|
}
|
|
/**
|
* 提供 APP 滚轮地区选择器使用的完整数据集。
|
*/
|
public AppRegisterRegionDatasetVO getRegionDataset() {
|
return appRegisterRegionService.buildRegionDataset();
|
}
|
|
/**
|
* 完成普通用户注册,并按需绑定上级诊所用户。
|
* 普通用户注册会先查询海诊通数据源,因此这里不包裹本地事务,避免事务先绑定 master 导致跨库查询失效。
|
*/
|
public AppLoginResponse registerUser(AppRegisterUserRequest request) {
|
String phone = normalizePhone(request.getPhone());
|
String inviteCode = normalizeInviteCode(request.getInviteCode());
|
String bindTicket = normalizeBindTicket(request.getBindTicket());
|
|
ensurePhoneAvailable(phone);
|
appSmsCodeService.validateRegisterCode(phone, normalizeCode(request.getCode()));
|
|
AppUser user = new AppUser();
|
user.setPhone(phone);
|
user.setType(0);
|
user.setStatus(1);
|
|
// 优先同步海诊通同手机号用户资料,未命中时使用手机号兜底昵称。
|
HztAppUser hztAppUser = findHztAppUserByPhone(phone);
|
populateUserProfile(user, hztAppUser, phone);
|
|
// 绑定票据优先于邀请码,避免安装后丢失或误填导致关系错误。
|
bindSuperiorClinicIfNecessary(user, inviteCode, bindTicket);
|
|
userMapper.insert(user);
|
return createLoginResponse(user);
|
}
|
|
/**
|
* 校验经营者第一步,并返回海诊通诊所预填资料与注册凭证。
|
*/
|
public AppRegisterOperatorPrepareResponse prepareOperatorRegister(AppRegisterOperatorPrepareRequest request) {
|
String phone = normalizePhone(request.getPhone());
|
|
ensurePhoneAvailable(phone);
|
appSmsCodeService.validateRegisterCode(phone, normalizeCode(request.getCode()));
|
|
HztClinic hztClinic = findHztClinicByPhone(phone);
|
OperatorRegisterDraft draft = buildOperatorDraft(phone, hztClinic);
|
String registerTicket = createRegisterTicket();
|
cacheDraft(registerTicket, draft);
|
|
AppRegisterOperatorPrepareResponse response = new AppRegisterOperatorPrepareResponse();
|
response.setRegisterTicket(registerTicket);
|
response.setClinicMatched(hztClinic != null);
|
response.setClinicInfo(toClinicInfo(draft));
|
return response;
|
}
|
|
/**
|
* 完成经营者第二步,创建本地诊所与诊所用户。
|
*/
|
@Transactional
|
public AppLoginResponse completeOperatorRegister(AppRegisterOperatorCompleteRequest request) {
|
OperatorRegisterDraft draft = loadDraft(normalizeRegisterTicket(request.getRegisterTicket()));
|
ensurePhoneAvailable(draft.getPhone());
|
|
Clinic clinic = new Clinic();
|
clinic.setName(trimRequired(request.getInstitutionName(), "请输入机构名称"));
|
clinic.setContactName(trimToNull(draft.getContactName()));
|
clinic.setContactPhone(draft.getPhone());
|
clinic.setProvinceCode(trimToNull(request.getProvinceCode()));
|
clinic.setProvinceName(trimRequired(request.getProvinceName(), "请选择省份"));
|
clinic.setCityCode(trimToNull(request.getCityCode()));
|
clinic.setCityName(trimRequired(request.getCityName(), "请选择城市"));
|
clinic.setDistrictCode(trimToNull(request.getDistrictCode()));
|
clinic.setDistrictName(trimRequired(request.getDistrictName(), "请选择区县"));
|
clinic.setAddress(trimRequired(request.getDetailAddress(), "请输入详细地址"));
|
clinic.setBusinessLicenseUrl(trimToNull(request.getBusinessLicenseUrl()));
|
clinic.setInviteCode(generateInviteCode());
|
clinic.setStatus(1);
|
clinicMapper.insert(clinic);
|
|
AppUser user = new AppUser();
|
user.setNickname(clinic.getName());
|
user.setPhone(draft.getPhone());
|
user.setType(1);
|
user.setStatus(1);
|
user.setClinicId(clinic.getId());
|
userMapper.insert(user);
|
|
Integer provinceCode = parseRegionCode(clinic.getProvinceCode(), "省编码");
|
Integer cityCode = parseRegionCode(clinic.getCityCode(), "市编码");
|
Integer areaCode = parseRegionCode(clinic.getDistrictCode(), "区县编码");
|
log.info("调用微赞创建门店,入参 title={}, provinceCode={}, cityCode={}, areaCode={}, address={}, telephone={}"
|
, clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
|
try {
|
JsonNode vzanResponse = vzanHttpUtil.createStore(clinic.getName(), provinceCode, cityCode, areaCode, clinic.getAddress(), clinic.getContactPhone());
|
log.info("微赞创建门店成功,出参={}", vzanResponse);
|
// 注册阶段暂未绑定区域代理,parentMobile 传空,由微赞按一级代理处理。
|
log.info("调用微赞创建一级代理,入参 agentMobile={}, agentName={}, levelCode=1, storeName={}, parentMobile=null",
|
clinic.getContactPhone(), clinic.getName(), clinic.getName());
|
JsonNode vzanAgentResponse = vzanHttpUtil.createAgent(
|
clinic.getContactPhone(), clinic.getName(), 1, "18510939636", clinic.getName());
|
log.info("微赞创建一级代理成功,出参={}", vzanAgentResponse);
|
} catch (RuntimeException ex) {
|
log.error("微赞创建门店或一级代理失败,本地事务将回滚,clinicId={}", clinic.getId(), ex);
|
throw new IllegalStateException("微赞门店或一级代理创建失败", ex);
|
}
|
|
clearDraft(request.getRegisterTicket());
|
return createLoginResponse(user);
|
}
|
|
private Integer parseRegionCode(String code, String label) {
|
String normalized = code == null ? null : code.trim();
|
if (normalized == null || normalized.isEmpty()) {
|
throw new IllegalArgumentException(label + "不能为空");
|
}
|
try {
|
return Integer.valueOf(normalized);
|
} catch (NumberFormatException ex) {
|
throw new IllegalArgumentException(label + "格式错误", ex);
|
}
|
}
|
|
/**
|
* 使用海诊通用户快照填充本地用户资料。
|
*/
|
private void populateUserProfile(AppUser user, HztAppUser hztAppUser, String phone) {
|
if (hztAppUser == null) {
|
user.setNickname(resolveFallbackNickname(phone));
|
return;
|
}
|
String nickname = trimToNull(hztAppUser.getNickName());
|
user.setNickname(nickname == null ? resolveFallbackNickname(phone) : nickname);
|
user.setAvatar(trimToNull(hztAppUser.getAvatar()));
|
user.setGender(mapGender(hztAppUser.getSex()));
|
}
|
|
/**
|
* 按邀请码绑定本地上级诊所用户与诊所归属。
|
*/
|
private void bindSuperiorClinicIfNecessary(AppUser user, String inviteCode, String bindTicket) {
|
AppShareService.InviteBindingTarget bindingTarget = resolveInviteBindingTarget(inviteCode, bindTicket);
|
if (bindingTarget == null) {
|
return;
|
}
|
user.setClinicId(bindingTarget.getClinicId());
|
user.setSuperiorClinicUserId(bindingTarget.getSuperiorClinicUserId());
|
}
|
|
/**
|
* 注册成功后立即建立 APP 登录态并返回统一登录响应。
|
*/
|
private AppLoginResponse createLoginResponse(AppUser user) {
|
appAuthSessionService.login(user.getId());
|
return buildLoginResponse(user, appAuthSessionService.getTokenInfo());
|
}
|
|
/**
|
* 使用 Sa-Token 信息构造 APP 登录响应。
|
*/
|
private AppLoginResponse buildLoginResponse(AppUser user, SaTokenInfo tokenInfo) {
|
return new AppLoginResponse(
|
tokenInfo.getTokenValue(),
|
"Bearer",
|
tokenInfo.getTokenTimeout(),
|
buildLoginUser(user)
|
);
|
}
|
|
/**
|
* 将注册后的 APP 用户实体转换为登录响应用户摘要。
|
*/
|
private AppLoginUserVO buildLoginUser(AppUser user) {
|
AppLoginUserVO loginUser = new AppLoginUserVO();
|
loginUser.setId(user.getId());
|
loginUser.setNickname(resolveFallbackNickname(user));
|
loginUser.setPhone(user.getPhone());
|
loginUser.setAvatar(trimToNull(user.getAvatar()));
|
loginUser.setType(user.getType());
|
loginUser.setStatus(user.getStatus());
|
loginUser.setClinicId(user.getClinicId());
|
loginUser.setMemberExpireAt(user.getMemberExpireAt());
|
return loginUser;
|
}
|
|
/**
|
* 解析注册阶段应绑定的目标关系,票据优先于邀请码。
|
*/
|
private AppShareService.InviteBindingTarget resolveInviteBindingTarget(String inviteCode, String bindTicket) {
|
if (bindTicket != null) {
|
return appShareService.consumeBindTicket(bindTicket);
|
}
|
if (inviteCode == null) {
|
return null;
|
}
|
return appShareService.resolveInviteBindingTarget(inviteCode);
|
}
|
|
/**
|
* 按手机号查询海诊通普通用户资料。
|
*/
|
private HztAppUser findHztAppUserByPhone(String phone) {
|
return hztAppUserMapper.selectOne(new LambdaQueryWrapper<HztAppUser>()
|
.eq(HztAppUser::getPhone, phone)
|
.eq(HztAppUser::getDisabled, 0)
|
.orderByDesc(HztAppUser::getCreateTime)
|
.orderByDesc(HztAppUser::getId)
|
.last("limit 1"));
|
}
|
|
/**
|
* 按手机号查询海诊通诊所资料。
|
*/
|
private HztClinic findHztClinicByPhone(String phone) {
|
return hztClinicMapper.selectOne(new LambdaQueryWrapper<HztClinic>()
|
.eq(HztClinic::getPhone, phone)
|
.eq(HztClinic::getDisabled, 0)
|
.orderByDesc(HztClinic::getCreateTime)
|
.orderByDesc(HztClinic::getId)
|
.last("limit 1"));
|
}
|
|
/**
|
* 构造经营者注册草稿,避免第二步再次输入验证码。
|
*/
|
private OperatorRegisterDraft buildOperatorDraft(String phone, HztClinic hztClinic) {
|
OperatorRegisterDraft draft = new OperatorRegisterDraft();
|
draft.setPhone(phone);
|
if (hztClinic == null) {
|
return draft;
|
}
|
AppRegisterRegionService.RegionSelection regionSelection = appRegisterRegionService.resolveRegionSelection(
|
hztClinic.getProvinceCode(),
|
hztClinic.getProvinceName(),
|
hztClinic.getCityCode(),
|
hztClinic.getCityName(),
|
hztClinic.getDistrictCode(),
|
hztClinic.getDistrictName()
|
);
|
draft.setContactName(trimToNull(hztClinic.getPersonChargeName()));
|
draft.setInstitutionName(trimToNull(hztClinic.getClinicName()));
|
draft.setProvinceCode(trimToNull(regionSelection.getProvinceCode()));
|
draft.setProvinceName(trimToNull(regionSelection.getProvinceName()));
|
draft.setCityCode(trimToNull(regionSelection.getCityCode()));
|
draft.setCityName(trimToNull(regionSelection.getCityName()));
|
draft.setDistrictCode(trimToNull(regionSelection.getDistrictCode()));
|
draft.setDistrictName(trimToNull(regionSelection.getDistrictName()));
|
draft.setDetailAddress(trimToNull(hztClinic.getClinicAddress()));
|
return draft;
|
}
|
|
/**
|
* 将缓存草稿转换为前端回显结构。
|
*/
|
private AppRegisterClinicInfoVO toClinicInfo(OperatorRegisterDraft draft) {
|
AppRegisterClinicInfoVO clinicInfo = new AppRegisterClinicInfoVO();
|
clinicInfo.setInstitutionName(trimToNull(draft.getInstitutionName()));
|
clinicInfo.setContactName(trimToNull(draft.getContactName()));
|
clinicInfo.setProvinceCode(trimToNull(draft.getProvinceCode()));
|
clinicInfo.setProvinceName(trimToNull(draft.getProvinceName()));
|
clinicInfo.setCityCode(trimToNull(draft.getCityCode()));
|
clinicInfo.setCityName(trimToNull(draft.getCityName()));
|
clinicInfo.setDistrictCode(trimToNull(draft.getDistrictCode()));
|
clinicInfo.setDistrictName(trimToNull(draft.getDistrictName()));
|
clinicInfo.setDetailAddress(trimToNull(draft.getDetailAddress()));
|
return clinicInfo;
|
}
|
|
/**
|
* 将经营者第一步草稿缓存到 Redis。
|
*/
|
private void cacheDraft(String registerTicket, OperatorRegisterDraft draft) {
|
try {
|
stringRedisTemplate.opsForValue().set(
|
buildRegisterTicketKey(registerTicket),
|
objectMapper.writeValueAsString(draft),
|
REGISTER_TICKET_EXPIRE_MINUTES,
|
TimeUnit.MINUTES
|
);
|
} catch (Exception ex) {
|
throw new IllegalArgumentException("经营者注册暂不可用,请稍后重试");
|
}
|
}
|
|
/**
|
* 从 Redis 读取经营者注册草稿。
|
*/
|
private OperatorRegisterDraft loadDraft(String registerTicket) {
|
String cacheValue = stringRedisTemplate.opsForValue().get(buildRegisterTicketKey(registerTicket));
|
if (cacheValue == null || cacheValue.trim().isEmpty()) {
|
throw new IllegalArgumentException("注册凭证已失效,请重新填写");
|
}
|
try {
|
return objectMapper.readValue(cacheValue, OperatorRegisterDraft.class);
|
} catch (Exception ex) {
|
throw new IllegalArgumentException("注册凭证已失效,请重新填写");
|
}
|
}
|
|
/**
|
* 清理已经完成的经营者注册草稿。
|
*/
|
private void clearDraft(String registerTicket) {
|
stringRedisTemplate.delete(buildRegisterTicketKey(normalizeRegisterTicket(registerTicket)));
|
}
|
|
/**
|
* 构造经营者注册 ticket 的缓存 key。
|
*/
|
private String buildRegisterTicketKey(String registerTicket) {
|
return REGISTER_TICKET_CACHE_PREFIX + registerTicket;
|
}
|
|
/**
|
* 生成经营者第二步提交所需的注册 ticket。
|
*/
|
private String createRegisterTicket() {
|
return UUID.randomUUID().toString().replace("-", "");
|
}
|
|
/**
|
* 生成本地诊所邀请码,并确保在主库内唯一。
|
*/
|
private String generateInviteCode() {
|
for (int i = 0; i < 20; i++) {
|
String candidate = randomInviteCode();
|
Clinic exists = clinicMapper.selectOne(new LambdaQueryWrapper<Clinic>()
|
.eq(Clinic::getInviteCode, candidate)
|
.eq(Clinic::getDeleted, 0)
|
.last("limit 1"));
|
if (exists == null) {
|
return candidate;
|
}
|
}
|
throw new IllegalStateException("邀请码生成失败,请稍后重试");
|
}
|
|
/**
|
* 生成一次随机邀请码候选值。
|
*/
|
private String randomInviteCode() {
|
StringBuilder builder = new StringBuilder(INVITE_CODE_LENGTH);
|
for (int i = 0; i < INVITE_CODE_LENGTH; i++) {
|
int index = ThreadLocalRandom.current().nextInt(INVITE_CODE_CHARS.length());
|
builder.append(INVITE_CODE_CHARS.charAt(index));
|
}
|
return builder.toString();
|
}
|
|
/**
|
* 校验手机号在本地用户表中尚未注册。
|
*/
|
private void ensurePhoneAvailable(String phone) {
|
AppUser exists = userMapper.selectOne(new LambdaQueryWrapper<AppUser>()
|
.eq(AppUser::getPhone, phone)
|
.eq(AppUser::getDeleted, 0)
|
.last("limit 1"));
|
if (exists != null) {
|
throw new IllegalArgumentException("该手机号已注册");
|
}
|
}
|
|
/**
|
* 将海诊通性别枚举映射到本地用户性别定义。
|
*/
|
private Integer mapGender(Integer sex) {
|
if (sex == null) {
|
return null;
|
}
|
if (sex == 1) {
|
return 1;
|
}
|
if (sex == 2) {
|
return 0;
|
}
|
return null;
|
}
|
|
/**
|
* 生成普通用户未命中海诊通时的默认昵称。
|
*/
|
private String resolveFallbackNickname(String phone) {
|
String masked = PhoneMasker.mask(phone);
|
return masked == null ? phone : masked;
|
}
|
|
/**
|
* 优先使用用户昵称,缺失时回退到手机号脱敏昵称。
|
*/
|
private String resolveFallbackNickname(AppUser user) {
|
String nickname = trimToNull(user.getNickname());
|
if (nickname != null) {
|
return nickname;
|
}
|
return resolveFallbackNickname(user.getPhone());
|
}
|
|
/**
|
* 归一化并校验手机号格式。
|
*/
|
private String normalizePhone(String phone) {
|
String normalized = trimToNull(phone);
|
if (normalized == null || !PHONE_PATTERN.matcher(normalized).matches()) {
|
throw new IllegalArgumentException("请输入正确的手机号");
|
}
|
return normalized;
|
}
|
|
/**
|
* 归一化并校验验证码输入。
|
*/
|
private String normalizeCode(String code) {
|
String normalized = trimToNull(code);
|
if (normalized == null) {
|
throw new IllegalArgumentException("请输入验证码");
|
}
|
return normalized;
|
}
|
|
/**
|
* 归一化邀请码,统一转为大写便于匹配。
|
*/
|
private String normalizeInviteCode(String inviteCode) {
|
String normalized = trimToNull(inviteCode);
|
return normalized == null ? null : normalized.toUpperCase();
|
}
|
|
/**
|
* 归一化绑定票据。
|
*/
|
private String normalizeBindTicket(String bindTicket) {
|
return trimToNull(bindTicket);
|
}
|
|
/**
|
* 归一化经营者注册凭证。
|
*/
|
private String normalizeRegisterTicket(String registerTicket) {
|
String normalized = trimToNull(registerTicket);
|
if (normalized == null) {
|
throw new IllegalArgumentException("注册凭证已失效,请重新填写");
|
}
|
return normalized;
|
}
|
|
/**
|
* 要求字段非空并返回裁剪后的值。
|
*/
|
private String trimRequired(String value, String message) {
|
String normalized = trimToNull(value);
|
if (normalized == null) {
|
throw new IllegalArgumentException(message);
|
}
|
return normalized;
|
}
|
|
/**
|
* 将空白字符串统一归一化为 null。
|
*/
|
private String trimToNull(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
return trimmed.isEmpty() ? null : trimmed;
|
}
|
|
/**
|
* 经营者注册第一步的缓存草稿。
|
*/
|
@lombok.Getter
|
@lombok.Setter
|
@lombok.NoArgsConstructor
|
public static class OperatorRegisterDraft {
|
|
private String phone;
|
|
private String contactName;
|
|
private String institutionName;
|
|
private String provinceCode;
|
|
private String provinceName;
|
|
private String cityCode;
|
|
private String cityName;
|
|
private String districtCode;
|
|
private String districtName;
|
|
private String detailAddress;
|
}
|
}
|