1.登录增强 2.用户表增加身份证字段 3.钉钉跳转修复

This commit is contained in:
zjf committed 2026-09-21 14:59:18 +08:00
1 parent fff9b6e40f
commit 93b288ed9d
21 files changed
+256 -66

No files matched your search

@@ -131,29 +131,40 @@ public class ValidateCodeFilter extends OncePerRequestFilter {
String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
throw new ValidateCodeException(MsgUtils.getMessage(ErrorCodes.AUTH_CAPTCHA_INVALID));
throw new ValidateCodeException(invalidCodeMessage(mobile));
}
Object codeObj = redisTemplate.opsForValue().get(key);
if (codeObj == null) {
throw new ValidateCodeException(MsgUtils.getMessage(ErrorCodes.AUTH_CAPTCHA_INVALID));
throw new ValidateCodeException(invalidCodeMessage(mobile));
}
String saveCode = codeObj.toString();
if (StrUtil.isBlank(saveCode)) {
redisTemplate.delete(key);
throw new ValidateCodeException(MsgUtils.getMessage(ErrorCodes.AUTH_CAPTCHA_INVALID));
throw new ValidateCodeException(invalidCodeMessage(mobile));
}
if (!StrUtil.equals(saveCode, code)) {
redisTemplate.delete(key);
throw new ValidateCodeException(MsgUtils.getMessage(ErrorCodes.AUTH_CAPTCHA_INVALID));
throw new ValidateCodeException(invalidCodeMessage(mobile));
}
redisTemplate.delete(key);
}
/**
* 短信登录与图形验证码使用不同提示:已使用/过期的短信码对用户更友好。
*/
private String invalidCodeMessage(String mobile) {
// mobile 参数形如 SMS@138xxxx
if (StrUtil.isNotBlank(mobile) && StrUtil.contains(mobile, "SMS")) {
return MsgUtils.getMessage(ErrorCodes.SYS_APP_SMS_CODE_INVALID);
}
return MsgUtils.getMessage(ErrorCodes.AUTH_CAPTCHA_INVALID);
}
/**
* 是否需要校验客户端,根据client 查询客户端配置
*
@@ -41,6 +41,12 @@ public class CanteenCategoryEntity extends Model<CanteenCategoryEntity> {
@Schema(description="分类名称")
private String name;
/**
* 分类英文名称
*/
@Schema(description = "分类英文名称")
private String nameEn;
/**
* 分类图
*/
@@ -39,6 +39,18 @@ public class CanteenSpuEntity extends Model<CanteenSpuEntity> {
@Schema(description="商品名称")
private String name;
/**
* 菜品英文名称
*/
@Schema(description = "菜品英文名称")
private String nameEn;
/**
* 套餐名(仅 spuType=2 时使用)
*/
@Schema(description = "套餐名")
private String comboName;
/**
* 商品简介
*/
@@ -24,6 +24,18 @@ public class ADCanteenSpuListVO {
@Schema(description="商品名称")
private String name;
/**
* 菜品英文名称
*/
@Schema(description = "菜品英文名称")
private String nameEn;
/**
* 套餐名(仅套餐时有值)
*/
@Schema(description = "套餐名")
private String comboName;
/**
* 商品简介
*/
@@ -43,6 +55,18 @@ public class ADCanteenSpuListVO {
@Schema(description="商品分类编号")
private Long categoryId;
/**
* 分类名称
*/
@Schema(description = "分类名称")
private String categoryName;
/**
* 分类英文名称
*/
@Schema(description = "分类英文名称")
private String categoryNameEn;
/**
* 商品封面图
*/
@@ -36,6 +36,16 @@ public class DailyMenuAppVO {
*/
private String name;
/**
* 菜品英文名称。
*/
private String nameEn;
/**
* 套餐名(仅套餐时有值)。
*/
private String comboName;
/**
* 商品销售价格,单位为分。
*/
@@ -56,6 +66,11 @@ public class DailyMenuAppVO {
*/
private String categoryName;
/**
* 菜品分类英文名称;分类不存在或已删除时为空。
*/
private String categoryNameEn;
/**
* 菜品封面图地址。
*/
@@ -407,6 +407,8 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
CanteenSpuEntity entity = canteenSpuEntities.get(i);
item.setIntroduction(entity.getIntroduction());
item.setDescription(entity.getDescription());
item.setNameEn(entity.getNameEn());
item.setComboName(entity.getComboName());
int type = entity.getSpuType() == null ? 1 : entity.getSpuType();
item.setSpuType(type);
if (type == 2) {
@@ -415,9 +417,13 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
else {
item.setComboItems(Collections.emptyList());
}
if (item.getCategoryId() != null && listMap.get(item.getCategoryId()) != null
&& listMap.get(item.getCategoryId()).get(0).getCategoryType() != null) {
item.setCategoryType(listMap.get(item.getCategoryId()).get(0).getCategoryType());
if (item.getCategoryId() != null && listMap.get(item.getCategoryId()) != null) {
CanteenCategoryEntity category = listMap.get(item.getCategoryId()).get(0);
if (category.getCategoryType() != null) {
item.setCategoryType(category.getCategoryType());
}
item.setCategoryName(category.getName());
item.setCategoryNameEn(category.getNameEn());
}
}
// 有每日菜谱则 stock=线下剩余,避免安卓仍展示 canteen_spu.stock
@@ -434,6 +440,8 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
ADCanteenSpuListVO vo = BeanUtils.toBean(detail, ADCanteenSpuListVO.class);
vo.setIntroduction(detail.getIntroduction());
vo.setDescription(detail.getDescription());
vo.setNameEn(detail.getNameEn());
vo.setComboName(detail.getComboName());
int type = detail.getSpuType() == null ? 1 : detail.getSpuType();
vo.setSpuType(type);
if (type == 2) {
@@ -444,8 +452,12 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
}
if (detail.getCategoryId() != null) {
CanteenCategoryEntity category = canteenCategoryMapper.selectById(detail.getCategoryId());
if (category != null && category.getCategoryType() != null) {
vo.setCategoryType(category.getCategoryType());
if (category != null) {
if (category.getCategoryType() != null) {
vo.setCategoryType(category.getCategoryType());
}
vo.setCategoryName(category.getName());
vo.setCategoryNameEn(category.getNameEn());
}
}
overlayDailyRemainOnAdVos(Collections.singletonList(vo), day, hourType, offline);
@@ -763,6 +775,7 @@ public class CanteenSpuServiceImpl extends ServiceImpl<CanteenSpuMapper, Canteen
}
if (!Objects.equals(entity.getSpuType(), 2)) {
entity.setComboItems(Collections.emptyList());
entity.setComboName("");
}
}
@@ -7,6 +7,7 @@
<id property="id" column="id"/>
<result property="parentId" column="parent_id"/>
<result property="name" column="name"/>
<result property="nameEn" column="name_en"/>
<result property="picUrl" column="pic_url"/>
<result property="sort" column="sort"/>
<result property="status" column="status"/>
@@ -27,10 +27,13 @@
<result property="merchantId" column="merchant_id"/>
<result property="spuId" column="spu_id"/>
<result property="name" column="name"/>
<result property="nameEn" column="name_en"/>
<result property="comboName" column="combo_name"/>
<result property="price" column="price"/>
<result property="marketPrice" column="market_price"/>
<result property="categoryId" column="category_id"/>
<result property="categoryName" column="category_name"/>
<result property="categoryNameEn" column="category_name_en"/>
<result property="picUrl" column="pic_url"/>
<result property="sliderPicUrls" column="slider_pic_urls"/>
<result property="introduction" column="introduction"/>
@@ -80,6 +83,8 @@
t.merchant_id,
sp.id AS spu_id,
sp.name,
sp.name_en,
sp.combo_name,
sp.price,
sp.market_price,
sp.pic_url,
@@ -96,6 +101,7 @@
sp.is_show,
c.id AS category_id,
c.name AS category_name,
c.name_en AS category_name_en,
IFNULL(sp.spu_type, 1) AS spu_type,
t.hour_type,
<!-- 数据权限拦截器会在当前查询外层按部门和创建人过滤,必须在临时结果集中透传这两个字段。 -->
@@ -132,6 +138,8 @@
t.merchant_id,
sp.id AS spu_id,
sp.name,
sp.name_en,
sp.combo_name,
sp.price,
sp.market_price,
sp.pic_url,
@@ -148,6 +156,7 @@
sp.is_show,
c.id AS category_id,
c.name AS category_name,
c.name_en AS category_name_en,
IFNULL(sp.spu_type, 1) AS spu_type,
t.hour_type
FROM canteen_daily_menu t
@@ -172,6 +181,8 @@
t.merchant_id,
sp.id AS spu_id,
sp.name,
sp.name_en,
sp.combo_name,
sp.price,
sp.market_price,
sp.pic_url,
@@ -188,6 +199,7 @@
sp.is_show,
c.id AS category_id,
c.name AS category_name,
c.name_en AS category_name_en,
IFNULL(sp.spu_type, 1) AS spu_type,
t.hour_type,
<!-- 数据权限拦截器会在当前查询外层按部门和创建人过滤,必须在临时结果集中透传这两个字段。 -->
@@ -217,6 +229,8 @@
m.hour_type,
s.id as spu_id,
s.`name`,
s.name_en,
s.combo_name,
s.price,
IFNULL(m.online_stock, 600) as stock,
IFNULL(m.online_stock, 600) as online_stock,
@@ -227,6 +241,8 @@
s.sort,
s.is_show,
c.id as category_id,
c.name as category_name,
c.name_en as category_name_en,
IFNULL(s.spu_type, 1) as spu_type
FROM `canteen_daily_menu` m
left JOIN canteen_spu s
@@ -255,6 +271,8 @@
m.merchant_id,
s.id AS spu_id,
s.name,
s.name_en,
s.combo_name,
s.price,
s.market_price,
IFNULL(m.online_stock, 600) AS stock,
@@ -271,6 +289,7 @@
s.is_show,
c.id AS category_id,
c.name AS category_name,
c.name_en AS category_name_en,
IFNULL(s.spu_type, 1) AS spu_type
FROM canteen_daily_menu m
INNER JOIN canteen_spu s ON m.spu_id = s.id
@@ -6,6 +6,8 @@
<resultMap id="canteenSpuMap" type="com.bsd.pjcloud.canteen.entity.CanteenSpuEntity">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="nameEn" column="name_en"/>
<result property="comboName" column="combo_name"/>
<result property="introduction" column="introduction"/>
<result property="description" column="description"/>
<result property="categoryId" column="category_id"/>
@@ -357,7 +357,7 @@ hard.car.batch.same.user.mch=Batch cart must share user/merchant/type
hard.car.save.fail=Failed to save cart products
common.idempotent.retry=Duplicate request. Please try again later
auth.captcha.empty=Verification code is required
auth.captcha.invalid=Invalid verification code
auth.captcha.invalid=Incorrect or expired verification code
auth.wx.auth.fail=WeChat authorization failed: {0}
auth.wx.openid.missing=WeChat authorization failed; user identifier not received
auth.wx.auth.expired=WeChat authorization expired; please authorize again
@@ -366,7 +366,7 @@ auth.apple.not.enabled=Apple login is not enabled
auth.mini.app.login.fail=WeChat mini program login failed
auth.social.auth.expired=Authorization expired; please authorize again
sys.app.sms.send.fail=Failed to send SMS code; please try again later
sys.app.sms.code.invalid=SMS code is invalid or expired
sys.app.sms.code.invalid=Incorrect or expired verification code
sys.user.password.same=New password must differ from old password
sys.user.unbind.type.invalid=Unbind account type does not exist
third.recharge.biz.id.invalid=Invalid recharge business ID
@@ -357,7 +357,7 @@ hard.car.batch.same.user.mch=批量购物车必须属于同一用户、商户和
hard.car.save.fail=购物车商品保存失败
common.idempotent.retry=重复请求,请稍后重试
auth.captcha.empty=验证码不能为空
auth.captcha.invalid=验证码不合法
auth.captcha.invalid=验证码不正确或已过期
auth.wx.auth.fail=微信授权失败:{0}
auth.wx.openid.missing=微信授权失败,未获取到用户标识
auth.wx.auth.expired=微信授权已过期,请重新授权
@@ -366,7 +366,7 @@ auth.apple.not.enabled=未启用Apple登录
auth.mini.app.login.fail=微信小程序登录失败
auth.social.auth.expired=授权已过期,请重新授权
sys.app.sms.send.fail=短信验证码发送失败,请稍后重试
sys.app.sms.code.invalid=短信验证码错误或已过期
sys.app.sms.code.invalid=验证码不正确或已过期
sys.user.password.same=新旧密码不能相同
sys.user.unbind.type.invalid=解绑账号类型不存在
third.recharge.biz.id.invalid=充值业务id解析错误
@@ -357,7 +357,7 @@ hard.car.batch.same.user.mch=批量購物車必須屬於同一用戶、商戶和
hard.car.save.fail=購物車商品保存失敗
common.idempotent.retry=重複請求,請稍後重試
auth.captcha.empty=驗證碼不能為空
auth.captcha.invalid=驗證碼不合法
auth.captcha.invalid=驗證碼不正確或已過期
auth.wx.auth.fail=微信授權失敗:{0}
auth.wx.openid.missing=微信授權失敗,未獲取到用戶標識
auth.wx.auth.expired=微信授權已過期,請重新授權
@@ -366,7 +366,7 @@ auth.apple.not.enabled=未啟用Apple登錄
auth.mini.app.login.fail=微信小程序登錄失敗
auth.social.auth.expired=授權已過期,請重新授權
sys.app.sms.send.fail=短信驗證碼發送失敗,請稍後重試
sys.app.sms.code.invalid=短信驗證碼錯誤或已過期
sys.app.sms.code.invalid=驗證碼不正確或已過期
sys.user.password.same=新舊密碼不能相同
sys.user.unbind.type.invalid=解綁賬號類型不存在
third.recharge.biz.id.invalid=充值業務id解析錯誤
@@ -3,6 +3,7 @@ package com.bsd.pjcloud.thirdparty.controller;
import com.bsd.pjcloud.common.security.annotation.Inner;
import com.bsd.pjcloud.thirdparty.service.SmsService;
import com.bsd.pjcloud.thirdparty.utils.sms.AliyunSMS;
import com.bsd.pjcloud.thirdparty.utils.sms.SmsHTTPSend;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -17,6 +18,8 @@ public class SmsController {
private final SmsService smsService;
private final AliyunSMS aliyunSMS;
private final SmsHTTPSend smsHTTPSend;
// 简单发送
@PostMapping("/sendMsg")
public void sendSimpleMsg(@RequestParam String mobile,@RequestParam String templateId,@RequestParam("clientIp") String clientIp) {
@@ -32,11 +35,18 @@ public class SmsController {
smsService.sendSms(mobile, templateId, params,clientIp);
}
// 阿里云发送验证码
/**
* 发送登录/注册验证码(联通短信通道)
*/
@PostMapping("/sendVerificationCode")
@Inner(value = false)
public void sendALiVerificationCode(@RequestParam("mobile") String mobile, @RequestParam("code") String code) {
aliyunSMS.sendVerificationCode(mobile, code);
String content = "您的验证码是" + code + ",5分钟内有效,仅用于完成本次验证。";
Boolean ok = smsHTTPSend.smsSendByPost(content, mobile);
if (!Boolean.TRUE.equals(ok)) {
// 联通通道失败时回退阿里云,避免验证码完全发不出
aliyunSMS.sendVerificationCode(mobile, code);
}
}
@@ -191,11 +191,11 @@ LianTongSMS:
#企业编号
SP_CODE: '11122'
#登录用户名
LOGIN_NAME: ''
LOGIN_NAME: 'zianxin'
#登录用户密码
PASSWORD: 'Aa123456'
#签名id
AUTOGRAPH_ID: ''
AUTOGRAPH_ID: '13725'
hikvision:
@@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 部门邀请注册请求
* 部门邀请注册请求(手机号 + 短信验证码)
*/
@Data
@Schema(description = "部门邀请注册")
@@ -13,18 +13,26 @@ public class DeptInviteRegisterDTO {
@Schema(description = "邀请 token", requiredMode = Schema.RequiredMode.REQUIRED)
private String token;
@Schema(description = "账号(可与手机号相同)", requiredMode = Schema.RequiredMode.REQUIRED)
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String password;
@Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED)
private String phone;
@Schema(description = "短信验证码", requiredMode = Schema.RequiredMode.REQUIRED)
private String smsCode;
@Schema(description = "姓名(选填,默认用手机号脱敏)")
private String name;
@Schema(description = "身份证号(选填)")
private String idCard;
@Schema(description = "性别 0未知 1男 2女")
private Integer gender;
/** @deprecated 兼容旧客户端;新流程以手机号为账号 */
@Schema(description = "账号(可选,默认手机号)")
private String username;
/** @deprecated 兼容旧客户端;新流程自动生成密码 */
@Schema(description = "密码(可选,未传则自动生成)")
private String password;
}
@@ -157,6 +157,12 @@ public class SysUser extends Model<SysUser> implements Serializable {
@Schema(description = "手机号")
private String phone;
/**
* 身份证号(选填)
*/
@Schema(description = "身份证号")
private String idCard;
/**
* 性别:0未知 1男 2女(骑手派单等)
*/
@@ -550,11 +550,15 @@ public class SysUserController {
}
/**
* 忘记密码-发送短信验证码
* 忘记密码-发送短信验证码(仅已注册手机号)
*/
@Inner(value = false)
@GetMapping("/forgot-password/sms-code")
public R<Boolean> sendForgotPasswordSmsCode(@RequestParam("mobile") String mobile) {
long count = userService.count(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getPhone, mobile));
if (count <= 0) {
return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_APP_PHONE_UNREGISTERED, mobile));
}
return sysMessageService.sendSmsCode(mobile);
}
@@ -63,12 +63,23 @@ public interface SysMessageService extends IService<SysMessageEntity> {
/**
* 发送手机验证码
* <p>未注册手机号也会发送;返回 data=true 表示该手机号已注册,false 表示未注册(可引导邀请注册)。
*
* @param mobile mobile
* @return code
* @return data=是否已注册
*/
R<Boolean> sendSmsCode(String mobile);
/**
* 校验短信验证码(登录 Redis 键 SMS@mobile)
*
* @param mobile 手机号
* @param code 验证码
* @param consume 是否校验成功后删除
* @return 是否通过
*/
boolean verifySmsCode(String mobile, String code, boolean consume);
/**
* 发送短信
*
@@ -29,6 +29,7 @@ import com.bsd.pjcloud.admin.mapper.SysUserMapper;
import com.bsd.pjcloud.admin.mapper.SysUserRoleMapper;
import com.bsd.pjcloud.admin.service.SysDeptInviteService;
import com.bsd.pjcloud.admin.service.SysDeptService;
import com.bsd.pjcloud.admin.service.SysMessageService;
import com.bsd.pjcloud.admin.service.SysRoleService;
import com.bsd.pjcloud.admin.service.SysUserIdentityService;
import com.bsd.pjcloud.admin.utils.flow.FlowUtil;
@@ -80,6 +81,7 @@ public class SysDeptInviteServiceImpl extends ServiceImpl<SysDeptInviteMapper, S
private final RemoteMchService remoteMchService;
private final RemoteWalletService remoteWalletService;
private final FlowUtil flowUtil;
private final SysMessageService sysMessageService;
/** 邀请码类型:6 位数字 / 8 位数字 / UUID(默认) */
private static final String TOKEN_DIGIT6 = "DIGIT6";
@@ -90,6 +92,9 @@ public class SysDeptInviteServiceImpl extends ServiceImpl<SysDeptInviteMapper, S
/** 中国大陆手机号 */
private static final Pattern PHONE_PATTERN = Pattern.compile(
"^1(3\\d|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$");
/** 18 位身份证(含 X) */
private static final Pattern ID_CARD_PATTERN = Pattern.compile(
"^[1-9]\\d{5}(19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$");
@Override
@Transactional(rollbackFor = Exception.class)
@@ -285,39 +290,56 @@ public class SysDeptInviteServiceImpl extends ServiceImpl<SysDeptInviteMapper, S
if (dto == null || StrUtil.isBlank(dto.getToken())) {
return R.failed("邀请码无效");
}
if (StrUtil.isBlank(dto.getUsername()) || StrUtil.isBlank(dto.getPassword())
|| StrUtil.isBlank(dto.getName()) || StrUtil.isBlank(dto.getPhone())) {
return R.failed("请完整填写账号、密码、姓名、手机号");
if (StrUtil.isBlank(dto.getPhone()) || StrUtil.isBlank(dto.getSmsCode())) {
return R.failed("请填写手机号和验证码");
}
String username = dto.getUsername().trim();
String password = dto.getPassword();
String phone = dto.getPhone().trim();
String usernameErr = validateUsername(username);
if (usernameErr != null) {
return R.failed(usernameErr);
}
String passwordErr = validateInvitePassword(password);
if (passwordErr != null) {
return R.failed(passwordErr);
}
String nameErr = validateInviteName(dto.getName());
if (nameErr != null) {
return R.failed(nameErr);
}
if (!PHONE_PATTERN.matcher(phone).matches()) {
return R.failed("请输入正确的手机号");
}
if (!sysMessageService.verifySmsCode(phone, dto.getSmsCode().trim(), false)) {
return R.failed("验证码错误或已过期");
}
SysDeptInviteEntity invite = requireValidInvite(dto.getToken());
boolean usernameExists = sysUserMapper.exists(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
if (usernameExists) {
return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERNAME_EXISTING, username));
}
boolean phoneExists = sysUserMapper.exists(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getPhone, phone));
if (phoneExists) {
return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_PHONE_EXISTING, phone));
}
// 账号默认用手机号前缀 u,兼容旧客户端显式传 username/password
String username = StrUtil.blankToDefault(StrUtil.trim(dto.getUsername()), "u" + phone);
String usernameErr = validateUsername(username);
if (usernameErr != null) {
// 纯数字账号不符合「字母开头」规则时,强制用 u+手机号
username = "u" + phone;
}
boolean usernameExists = sysUserMapper.exists(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
if (usernameExists) {
username = "u" + phone + RandomUtil.randomNumbers(4);
}
String password = StrUtil.isNotBlank(dto.getPassword()) ? dto.getPassword()
: (RandomUtil.randomNumbers(6) + RandomUtil.randomString("abcdef", 2) + "Aa1");
if (StrUtil.isNotBlank(dto.getPassword())) {
String passwordErr = validateInvitePassword(password);
if (passwordErr != null) {
return R.failed(passwordErr);
}
}
String displayName = StrUtil.blankToDefault(StrUtil.trim(dto.getName()),
"用户" + phone.substring(Math.max(0, phone.length() - 4)));
String nameErr = validateInviteName(displayName);
if (nameErr != null) {
displayName = "用户" + phone.substring(Math.max(0, phone.length() - 4));
}
String idCard = StrUtil.trim(dto.getIdCard());
if (StrUtil.isNotBlank(idCard) && !ID_CARD_PATTERN.matcher(idCard).matches()) {
return R.failed("身份证号格式不正确");
}
MchManageVo mch = resolveMerchantAlongUnitChain(invite.getDeptId());
String attr = mch != null ? StrUtil.blankToDefault(mch.getMerchantAttr(), MerchantAttrEnum.NORMAL.getCode())
: MerchantAttrEnum.NORMAL.getCode();
@@ -327,11 +349,11 @@ public class SysDeptInviteServiceImpl extends ServiceImpl<SysDeptInviteMapper, S
SysUser sysUser = new SysUser();
sysUser.setUsername(username);
sysUser.setPhone(phone);
sysUser.setName(PersonNameUtils.normalize(dto.getName()));
sysUser.setIdCard(StrUtil.blankToDefault(idCard, null));
sysUser.setName(PersonNameUtils.normalize(displayName));
sysUser.setNickname(sysUser.getName());
sysUser.setGender(dto.getGender());
sysUser.setGender(dto.getGender() == null ? 0 : dto.getGender());
sysUser.setDeptId(invite.getDeptId());
// 与后台「新增用户审批」一致:正常账号 + 审核中状态,并真正发起 ADD_USER_FLOW 写入 processId
sysUser.setLockFlag(UserStateEnum.NORMAL.getCode());
sysUser.setCreateBy(username);
sysUser.setUpdateBy(username);
@@ -340,7 +362,6 @@ public class SysDeptInviteServiceImpl extends ServiceImpl<SysDeptInviteMapper, S
sysUser.setProcessStatus(needAudit ? TaskCheckStatusEnum.PADDING.getCode() : TaskCheckStatusEnum.RESULT.getCode());
sysUser.setProcessTime(LocalDateTime.now());
sysUser.setStaffingType(UserStaffingTypeEnum.NONE_STAFFING.getType());
// 邀请注册已自设密码,无需强制改密;EXPIRE(9) 会被当成凭证过期无法登录
sysUser.setPasswordExpireFlag(UserExpireEnum.NORMAL.getCode());
sysUserMapper.insert(sysUser);
@@ -379,6 +400,7 @@ public class SysDeptInviteServiceImpl extends ServiceImpl<SysDeptInviteMapper, S
log.error("|{}|创建用户钱包失败error:|{}|", TAG, e.getMessage());
}
});
// 保留验证码供前端立即短信登录(登录时再消费)
return R.ok(true, "注册成功");
}
@@ -122,6 +122,10 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
private final FileTemplate fileTemplate;
private final RemoterThirdPartySmsService remoterThirdPartySmsService;
private static final int SMS_CODE_LENGTH = 6;
/** 短信验证码有效期(秒) */
private static final long SMS_CODE_TTL_SECONDS = 300L;
/** 短信重发间隔(秒),与前端倒计时一致 */
private static final long SMS_SEND_INTERVAL_SECONDS = 60L;
/**
* 发送信息
@@ -267,28 +271,29 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
*/
@Override
public R<Boolean> sendSmsCode(String mobile) {
List<SysUser> userList = userMapper.selectList(Wrappers.<SysUser>query().lambda().eq(SysUser::getPhone, mobile));
if (CollUtil.isEmpty(userList)) {
log.info("手机号未注册:{}", mobile);
return R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_APP_PHONE_UNREGISTERED, mobile));
}
boolean registered = CollUtil.isNotEmpty(
userMapper.selectList(Wrappers.<SysUser>query().lambda().eq(SysUser::getPhone, mobile)));
String redisKey = CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.SMS.getType() + StringPool.AT + mobile;
String codeObj = redisTemplate.opsForValue().get(redisKey);
// 有未过期验证码时:仅限制 60 秒内不能重复发送;到期可覆盖重发(避免前端倒计时结束仍提示频繁)
if (StrUtil.isNotBlank(codeObj)) {
log.info("手机号验证码未过期:{},{}", mobile, codeObj);
return R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_APP_SMS_OFTEN));
Long ttl = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
if (ttl != null && ttl > SMS_CODE_TTL_SECONDS - SMS_SEND_INTERVAL_SECONDS) {
log.info("手机号验证码发送过频:{},剩余有效期{}秒", mobile, ttl);
return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_APP_SMS_OFTEN));
}
}
String code = RandomUtil.randomNumbers(SMS_CODE_LENGTH);
log.info("手机号生成验证码成功:{},{}", mobile, code);
redisTemplate.opsForValue().set(redisKey, code, SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
redisTemplate.opsForValue().set(redisKey, code, SMS_CODE_TTL_SECONDS, TimeUnit.SECONDS);
try {
remoterThirdPartySmsService.sendVerificationCode(mobile, code);
return R.ok(Boolean.TRUE);
// data=是否已注册,便于移动端未注册时引导邀请注册
return R.ok(registered);
} catch (Exception e) {
redisTemplate.delete(redisKey);
log.error("发送短信验证码失败: mobile={}", mobile, e);
@@ -296,6 +301,22 @@ public class SysMessageServiceImpl extends ServiceImpl<SysMessageMapper, SysMess
}
}
@Override
public boolean verifySmsCode(String mobile, String code, boolean consume) {
if (StrUtil.isBlank(mobile) || StrUtil.isBlank(code)) {
return false;
}
String redisKey = CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.SMS.getType() + StringPool.AT + mobile.trim();
String saveCode = redisTemplate.opsForValue().get(redisKey);
if (StrUtil.isBlank(saveCode) || !StrUtil.equals(saveCode, code.trim())) {
return false;
}
if (consume) {
redisTemplate.delete(redisKey);
}
return true;
}
/**
* 发送短信
*
@@ -637,6 +637,11 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
sysUser.setNickname(PersonNameUtils.normalize(userDto.getNickname()));
sysUser.setName(PersonNameUtils.normalize(userDto.getName()));
sysUser.setEmail(userDto.getEmail());
// 身份证选填:传空串则清空
if (userDto.getIdCard() != null) {
String idCard = StrUtil.trim(userDto.getIdCard());
sysUser.setIdCard(StrUtil.isBlank(idCard) ? "" : idCard);
}
// 个人资料:未传手机号时不修改(移动端手机号不可编辑)
if (StrUtil.isNotBlank(userDto.getPhone()) && !userDto.getPhone().contains("*")) {
sysUser.setPhone(userDto.getPhone());