bug修改
This commit is contained in:
1 parent
e046c67f64
commit
1024045a92
11 files changed
+104
-56
No files matched your search
@@ -1,14 +1,15 @@
|
||||
-- 航空账号(管理员开通:手机号+密码+有效期+设备唯一绑定)
|
||||
CREATE TABLE IF NOT EXISTS `member_airline_account` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
|
||||
`mobile` varchar(11) NOT NULL COMMENT '手机号',
|
||||
`mobile` varchar(11) NOT NULL COMMENT '手机号(同一手机号可在不同渠道各有一个账号)',
|
||||
`channel` varchar(8) NOT NULL DEFAULT 'CZ' COMMENT '品牌渠道(CZ南航/MU东航/HU海航)',
|
||||
`password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码(BCrypt)',
|
||||
`start_time` datetime NOT NULL COMMENT '生效开始时间',
|
||||
`end_time` datetime NOT NULL COMMENT '生效结束时间',
|
||||
`device_id` varchar(128) NULL DEFAULT NULL COMMENT '手机唯一识别号(首次登录绑定;重装/重启应由客户端保持不变)',
|
||||
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态(0开启 1关闭)',
|
||||
`remark` varchar(500) NULL DEFAULT NULL COMMENT '备注',
|
||||
`user_id` bigint NULL DEFAULT NULL COMMENT '关联会员用户编号 member_user.id',
|
||||
`user_id` bigint NULL DEFAULT NULL COMMENT '关联会员用户编号 member_user.id(已废弃,登录不再创建会员用户)',
|
||||
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
|
||||
`creator` varchar(64) NULL DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
@@ -16,8 +17,8 @@ CREATE TABLE IF NOT EXISTS `member_airline_account` (
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_mobile` (`mobile`, `deleted`, `tenant_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_device_id` (`device_id`, `deleted`, `tenant_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_mobile_channel` (`mobile`, `channel`, `deleted`, `tenant_id`) USING BTREE,
|
||||
UNIQUE KEY `uk_channel_device` (`channel`, `device_id`, `deleted`, `tenant_id`) USING BTREE,
|
||||
KEY `idx_end_time` (`end_time`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='航空账号';
|
||||
|
||||
@@ -40,8 +41,9 @@ ON DUPLICATE KEY UPDATE `permission` = VALUES(`permission`);
|
||||
-- 手机号:13800138000 密码:123456 有效期约一年 未绑定设备
|
||||
-- 密码哈希为 BCrypt(123456)
|
||||
INSERT INTO `member_airline_account`
|
||||
(`mobile`, `password`, `start_time`, `end_time`, `device_id`, `status`, `remark`, `user_id`, `tenant_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
(`mobile`, `channel`, `password`, `start_time`, `end_time`, `device_id`, `status`, `remark`, `user_id`, `tenant_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
|
||||
SELECT '13800138000',
|
||||
'CZ',
|
||||
'$2a$10$PplOV8yXcQo9xc32V8vwTuIMbMKgNtjXf3lu6l18LUTOy9YGDdAY6',
|
||||
NOW() - INTERVAL 1 DAY,
|
||||
NOW() + INTERVAL 365 DAY,
|
||||
@@ -58,5 +60,5 @@ SELECT '13800138000',
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `member_airline_account`
|
||||
WHERE `mobile` = '13800138000' AND `deleted` = b'0' AND `tenant_id` = 1
|
||||
WHERE `mobile` = '13800138000' AND `channel` = 'CZ' AND `deleted` = b'0' AND `tenant_id` = 1
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 航空账号增加品牌渠道(channel):CZ 南航 / MU 东航 / HU 海航
|
||||
-- 目的:三个 App 使用同一手机号分别登录,互不干扰。
|
||||
-- 唯一键调整:
|
||||
-- uk_mobile (mobile, deleted, tenant_id) -> 废弃
|
||||
-- uk_device_id (device_id, deleted, tenant_id) -> 废弃
|
||||
-- uk_mobile_channel (mobile, channel, deleted, tenant_id) -> 同一手机号每渠道一个账号
|
||||
-- uk_channel_device (channel, device_id, deleted, tenant_id) -> 同一设备每渠道绑定一个账号
|
||||
|
||||
ALTER TABLE `member_airline_account`
|
||||
ADD COLUMN `channel` varchar(8) NOT NULL DEFAULT 'CZ' COMMENT '品牌渠道(CZ南航/MU东航/HU海航)' AFTER `mobile`;
|
||||
|
||||
-- 存量数据归属渠道默认回填为 CZ(南航),如实际属于东航/海航请手工执行:
|
||||
-- UPDATE `member_airline_account` SET `channel` = 'MU' WHERE ...;
|
||||
-- UPDATE `member_airline_account` SET `channel` = 'HU' WHERE ...;
|
||||
|
||||
ALTER TABLE `member_airline_account`
|
||||
DROP INDEX `uk_mobile`,
|
||||
DROP INDEX `uk_device_id`,
|
||||
ADD UNIQUE KEY `uk_mobile_channel` (`mobile`, `channel`, `deleted`, `tenant_id`),
|
||||
ADD UNIQUE KEY `uk_channel_device` (`channel`, `device_id`, `deleted`, `tenant_id`);
|
||||
+6
@@ -7,6 +7,7 @@ import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
@@ -20,6 +21,11 @@ public class MemberAirlineAccountBaseVO {
|
||||
@Length(min = 11, max = 11, message = "手机号长度为 11 位")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道(CZ南航/MU东航/HU海航)", requiredMode = Schema.RequiredMode.REQUIRED, example = "CZ")
|
||||
@NotEmpty(message = "品牌渠道不能为空")
|
||||
@Pattern(regexp = "CZ|MU|HU", message = "品牌渠道仅支持 CZ(南航)/ MU(东航)/ HU(海航)")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "生效开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "开始时间不能为空")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
|
||||
+3
@@ -20,6 +20,9 @@ public class MemberAirlineAccountPageReqVO extends PageParam {
|
||||
@Schema(description = "手机号", example = "13800138000")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道(CZ南航/MU东航/HU海航)", example = "CZ")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "状态(0开启 1关闭)", example = "0")
|
||||
private Integer status;
|
||||
|
||||
|
||||
+3
@@ -15,6 +15,9 @@ public class MemberAirlineAccountRespVO {
|
||||
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13800138000")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道(CZ南航/MU东航/HU海航)", requiredMode = Schema.RequiredMode.REQUIRED, example = "CZ")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "生效开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime startTime;
|
||||
|
||||
|
||||
+6
@@ -6,6 +6,7 @@ import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@Schema(description = "用户 APP - 航空账号登录 Request VO")
|
||||
@Data
|
||||
@@ -16,6 +17,11 @@ public class AppAuthAirlineLoginReqVO {
|
||||
@Mobile
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "品牌渠道", requiredMode = Schema.RequiredMode.REQUIRED, example = "CZ")
|
||||
@NotEmpty(message = "品牌渠道不能为空")
|
||||
@Pattern(regexp = "CZ|MU|HU", message = "品牌渠道仅支持 CZ(南航)/ MU(东航)/ HU(海航)")
|
||||
private String channel;
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotEmpty(message = "密码不能为空")
|
||||
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
|
||||
|
||||
+3
-1
@@ -23,8 +23,10 @@ public class MemberAirlineAccountDO extends TenantBaseDO {
|
||||
|
||||
@TableId
|
||||
private Long id;
|
||||
/** 手机号 */
|
||||
/** 手机号(同一手机号可在不同渠道各有一个账号) */
|
||||
private String mobile;
|
||||
/** 品牌渠道:CZ 南航 / MU 东航 / HU 海航 */
|
||||
private String channel;
|
||||
/** 密码(BCrypt) */
|
||||
private String password;
|
||||
/** 生效开始时间 */
|
||||
|
||||
+9
-8
@@ -13,22 +13,23 @@ public interface MemberAirlineAccountMapper extends BaseMapperX<MemberAirlineAcc
|
||||
default PageResult<MemberAirlineAccountDO> selectPage(MemberAirlineAccountPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<MemberAirlineAccountDO>()
|
||||
.likeIfPresent(MemberAirlineAccountDO::getMobile, reqVO.getMobile())
|
||||
.eqIfPresent(MemberAirlineAccountDO::getChannel, reqVO.getChannel())
|
||||
.eqIfPresent(MemberAirlineAccountDO::getStatus, reqVO.getStatus())
|
||||
.likeIfPresent(MemberAirlineAccountDO::getDeviceId, reqVO.getDeviceId())
|
||||
.betweenIfPresent(MemberAirlineAccountDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(MemberAirlineAccountDO::getId));
|
||||
}
|
||||
|
||||
default MemberAirlineAccountDO selectByMobile(String mobile) {
|
||||
return selectOne(MemberAirlineAccountDO::getMobile, mobile);
|
||||
default MemberAirlineAccountDO selectByMobileAndChannel(String mobile, String channel) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberAirlineAccountDO>()
|
||||
.eq(MemberAirlineAccountDO::getMobile, mobile)
|
||||
.eq(MemberAirlineAccountDO::getChannel, channel));
|
||||
}
|
||||
|
||||
default MemberAirlineAccountDO selectByDeviceId(String deviceId) {
|
||||
return selectOne(MemberAirlineAccountDO::getDeviceId, deviceId);
|
||||
}
|
||||
|
||||
default MemberAirlineAccountDO selectByUserId(Long userId) {
|
||||
return selectOne(MemberAirlineAccountDO::getUserId, userId);
|
||||
default MemberAirlineAccountDO selectByDeviceIdAndChannel(String deviceId, String channel) {
|
||||
return selectOne(new LambdaQueryWrapperX<MemberAirlineAccountDO>()
|
||||
.eq(MemberAirlineAccountDO::getDeviceId, deviceId)
|
||||
.eq(MemberAirlineAccountDO::getChannel, channel));
|
||||
}
|
||||
|
||||
}
|
||||
+5
-6
@@ -26,18 +26,17 @@ public interface MemberAirlineAccountService {
|
||||
/** 管理端解绑设备,允许换机重新登录绑定 */
|
||||
void unbindDevice(Long id);
|
||||
|
||||
/** 登录成功后回写关联的会员用户编号 */
|
||||
void updateAirlineAccountUserId(Long id, Long userId);
|
||||
|
||||
/**
|
||||
* 登录校验:密码、有效期、设备绑定。
|
||||
* 首次登录绑定 deviceId;成功后返回账号(可能已写入 deviceId / userId)。
|
||||
* 手机号与设备号均按渠道(channel)隔离:同一手机号/同一设备可在 CZ/MU/HU 各绑定一个账号。
|
||||
* 首次登录绑定 deviceId;成功后返回账号(可能已写入 deviceId)。
|
||||
*/
|
||||
MemberAirlineAccountDO authenticateAndBindDevice(String mobile, String password, String deviceId);
|
||||
MemberAirlineAccountDO authenticateAndBindDevice(String mobile, String password, String deviceId, String channel);
|
||||
|
||||
/**
|
||||
* 会话校验:已登录用户打开 App 时检查账号是否禁用/过期/设备不匹配。
|
||||
* 注意:accountId 为登录 Token 中的 userId,即航空账号自身编号(不再是 member_user 编号)。
|
||||
*/
|
||||
void validateAirlineSession(Long userId, String deviceId);
|
||||
void validateAirlineSession(Long accountId, String deviceId);
|
||||
|
||||
}
|
||||
+13
-19
@@ -33,7 +33,7 @@ public class MemberAirlineAccountServiceImpl implements MemberAirlineAccountServ
|
||||
@Override
|
||||
public Long createAirlineAccount(MemberAirlineAccountCreateReqVO createReqVO) {
|
||||
validateTimeRange(createReqVO.getStartTime(), createReqVO.getEndTime());
|
||||
validateMobileUnique(null, createReqVO.getMobile());
|
||||
validateMobileUnique(null, createReqVO.getMobile(), createReqVO.getChannel());
|
||||
|
||||
MemberAirlineAccountDO account = BeanUtils.toBean(createReqVO, MemberAirlineAccountDO.class);
|
||||
account.setPassword(passwordEncoder.encode(createReqVO.getPassword()));
|
||||
@@ -46,7 +46,7 @@ public class MemberAirlineAccountServiceImpl implements MemberAirlineAccountServ
|
||||
public void updateAirlineAccount(MemberAirlineAccountUpdateReqVO updateReqVO) {
|
||||
validateExists(updateReqVO.getId());
|
||||
validateTimeRange(updateReqVO.getStartTime(), updateReqVO.getEndTime());
|
||||
validateMobileUnique(updateReqVO.getId(), updateReqVO.getMobile());
|
||||
validateMobileUnique(updateReqVO.getId(), updateReqVO.getMobile(), updateReqVO.getChannel());
|
||||
|
||||
MemberAirlineAccountDO updateObj = BeanUtils.toBean(updateReqVO, MemberAirlineAccountDO.class);
|
||||
if (StrUtil.isBlank(updateReqVO.getPassword())) {
|
||||
@@ -83,18 +83,10 @@ public class MemberAirlineAccountServiceImpl implements MemberAirlineAccountServ
|
||||
.set(MemberAirlineAccountDO::getDeviceId, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAirlineAccountUserId(Long id, Long userId) {
|
||||
MemberAirlineAccountDO update = new MemberAirlineAccountDO();
|
||||
update.setId(id);
|
||||
update.setUserId(userId);
|
||||
airlineAccountMapper.updateById(update);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MemberAirlineAccountDO authenticateAndBindDevice(String mobile, String password, String deviceId) {
|
||||
MemberAirlineAccountDO account = airlineAccountMapper.selectByMobile(mobile);
|
||||
public MemberAirlineAccountDO authenticateAndBindDevice(String mobile, String password, String deviceId, String channel) {
|
||||
MemberAirlineAccountDO account = airlineAccountMapper.selectByMobileAndChannel(mobile, channel);
|
||||
if (account == null) {
|
||||
throw exception(AIRLINE_ACCOUNT_BAD_CREDENTIALS);
|
||||
}
|
||||
@@ -114,8 +106,8 @@ public class MemberAirlineAccountServiceImpl implements MemberAirlineAccountServ
|
||||
|
||||
String boundDeviceId = account.getDeviceId();
|
||||
if (StrUtil.isBlank(boundDeviceId)) {
|
||||
// 首次绑定:设备不能已被其他账号占用
|
||||
MemberAirlineAccountDO byDevice = airlineAccountMapper.selectByDeviceId(deviceId);
|
||||
// 首次绑定:同渠道下设备不能已被其他账号占用(不同渠道互不影响)
|
||||
MemberAirlineAccountDO byDevice = airlineAccountMapper.selectByDeviceIdAndChannel(deviceId, channel);
|
||||
if (byDevice != null && !Objects.equals(byDevice.getId(), account.getId())) {
|
||||
throw exception(AIRLINE_ACCOUNT_DEVICE_USED);
|
||||
}
|
||||
@@ -131,11 +123,12 @@ public class MemberAirlineAccountServiceImpl implements MemberAirlineAccountServ
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateAirlineSession(Long userId, String deviceId) {
|
||||
if (userId == null) {
|
||||
public void validateAirlineSession(Long accountId, String deviceId) {
|
||||
if (accountId == null) {
|
||||
throw exception(AIRLINE_ACCOUNT_NOT_EXISTS);
|
||||
}
|
||||
MemberAirlineAccountDO account = airlineAccountMapper.selectByUserId(userId);
|
||||
// 登录签发的 Token 中 userId 即航空账号自身编号
|
||||
MemberAirlineAccountDO account = airlineAccountMapper.selectById(accountId);
|
||||
if (account == null) {
|
||||
throw exception(AIRLINE_ACCOUNT_NOT_EXISTS);
|
||||
}
|
||||
@@ -164,8 +157,9 @@ public class MemberAirlineAccountServiceImpl implements MemberAirlineAccountServ
|
||||
return account;
|
||||
}
|
||||
|
||||
private void validateMobileUnique(Long id, String mobile) {
|
||||
MemberAirlineAccountDO exist = airlineAccountMapper.selectByMobile(mobile);
|
||||
private void validateMobileUnique(Long id, String mobile, String channel) {
|
||||
// 同一手机号允许在不同渠道各建一个账号(CZ/MU/HU 互不干扰)
|
||||
MemberAirlineAccountDO exist = airlineAccountMapper.selectByMobileAndChannel(mobile, channel);
|
||||
if (exist == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+28
-16
@@ -411,25 +411,37 @@ public class MemberAuthServiceImpl implements MemberAuthService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AppAuthLoginRespVO airlineLogin(AppAuthAirlineLoginReqVO reqVO) {
|
||||
// 1. 校验航空账号:密码、有效期、设备绑定
|
||||
// 1. 校验航空账号:渠道、密码、有效期、设备绑定(手机号/设备号均按渠道隔离)
|
||||
MemberAirlineAccountDO account = airlineAccountService.authenticateAndBindDevice(
|
||||
reqVO.getMobile(), reqVO.getPassword(), reqVO.getDeviceId());
|
||||
reqVO.getMobile(), reqVO.getPassword(), reqVO.getDeviceId(), reqVO.getChannel());
|
||||
|
||||
// 2. 同步会员用户(不存在则创建),并覆盖密码保持一致
|
||||
MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), getClientIP(), getTerminal());
|
||||
Assert.notNull(user, "获取用户失败,结果为空");
|
||||
userService.updateUserPassword(user.getId(), reqVO.getPassword());
|
||||
// 2. 直接以航空账号身份签发 Token(不创建、不关联 member_user,避免污染会员表)
|
||||
return createTokenAfterAirlineLoginSuccess(account, reqVO.getMobile());
|
||||
}
|
||||
|
||||
// 3. 回写关联会员编号
|
||||
if (!Objects.equals(account.getUserId(), user.getId())) {
|
||||
MemberAirlineAccountDO update = new MemberAirlineAccountDO();
|
||||
update.setId(account.getId());
|
||||
update.setUserId(user.getId());
|
||||
airlineAccountService.updateAirlineAccountUserId(account.getId(), user.getId());
|
||||
}
|
||||
|
||||
// 4. 签发 Token(与密码登录一致)
|
||||
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, null);
|
||||
/**
|
||||
* 航空账号专用:签发 Token + 记录登录日志。
|
||||
* 注意:不能复用 {@link #createTokenAfterLoginSuccess} 和 {@link #createLoginLog},
|
||||
* 它们在成功时会更新 member_user(状态校验、最后登录时间),而航空账号没有对应的会员用户。
|
||||
* Token 的 userId 直接使用航空账号自身编号。
|
||||
*/
|
||||
private AppAuthLoginRespVO createTokenAfterAirlineLoginSuccess(MemberAirlineAccountDO account, String mobile) {
|
||||
// 插入登录日志(不更新 member_user 的最后登录信息)
|
||||
LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
|
||||
reqDTO.setLogType(LoginLogTypeEnum.LOGIN_MOBILE.getType());
|
||||
reqDTO.setTraceId(TracerUtils.getTraceId());
|
||||
reqDTO.setUserId(account.getId());
|
||||
reqDTO.setUserType(getUserType().getValue());
|
||||
reqDTO.setUsername(mobile);
|
||||
reqDTO.setUserAgent(ServletUtils.getUserAgent());
|
||||
reqDTO.setUserIp(getClientIP());
|
||||
reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
|
||||
loginLogApi.createLoginLog(reqDTO);
|
||||
// 创建 Token 令牌(userId 使用航空账号自身编号)
|
||||
OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
|
||||
.setUserId(account.getId()).setUserType(getUserType().getValue())
|
||||
.setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
|
||||
return AuthConvert.INSTANCE.convert(accessTokenRespDTO, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in new issue
Block a user