package com.ruoyi.app.user; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.ruoyi.app.utils.DateUtil; import com.ruoyi.app.utils.PayPush; import com.ruoyi.app.utils.RsaMima; import com.ruoyi.app.utils.UUIDUtil; import com.ruoyi.app.utils.trtc.TLSSigAPIv2; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.model.LoginUserDto; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.ip.AddressUtils; import com.ruoyi.common.utils.ip.IpUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.domain.*; import com.ruoyi.system.domain.constants.MerchantAccountConstants; import com.ruoyi.app.user.dto.MerchantLoginView; import com.ruoyi.system.domain.vo.UserDTO; import com.ruoyi.app.service.ImAccountService; import com.ruoyi.system.mapper.InfoUserMapper; import com.ruoyi.system.mapper.PosOrderMapper; import com.ruoyi.system.mapper.PosStoreMapper; import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.system.service.*; import com.ruoyi.system.utils.Auth; import com.ruoyi.system.utils.AuthContext; import com.ruoyi.system.utils.JwtUtil; import com.ruoyi.system.utils.MobileSMS; import com.ruoyi.app.user.dto.InfoUserResetPwdDto; import com.ruoyi.app.user.dto.OAuthBindDto; import com.ruoyi.app.user.dto.OAuthDeviceConfirmDto; import com.ruoyi.app.user.dto.OAuthLoginDto; import com.ruoyi.app.user.dto.CancellationLoginConfirmDto; import com.ruoyi.app.user.CancellationLoginContext.Flow; import com.ruoyi.app.user.CancellationLoginContext.Device; import com.ruoyi.app.utils.oauth.OAuthAccountType; import com.ruoyi.app.utils.oauth.OAuthIdentity; import com.ruoyi.app.utils.oauth.LineOAuthStateService; import com.ruoyi.app.user.service.DeviceTrustService; import com.ruoyi.system.domain.InfoUserDevice; import com.ruoyi.app.utils.oauth.OAuthVerifyService; import com.ruoyi.system.mapper.InfoUserOauthMapper; import eu.bitwalker.useragentutils.UserAgent; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import jakarta.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 用户信息Controller * * @author ruoyi * @date 2023-05-13 */ @RestController @RequestMapping("/infouser/user") public class InfoUserController extends BaseController { private static final Logger log = LoggerFactory.getLogger(InfoUserController.class); @Autowired private IInfoUserService infoUserService; @Autowired private UserCancellationService userCancellationService; @Autowired private ImAccountService imAccountService; @Autowired private InfoUserMapper infoUserMapper; @Autowired private IVipUserService vipUserService; @Autowired private PosStoreMapper posStoreMapper; @Autowired private PosOrderMapper posOrderMapper; @Autowired private IUserWalletService userWalletService; @Autowired private RedisCache redisCache; @Autowired private IPosOrderService posOrderService; @Autowired private InfoUserOauthMapper infoUserOauthMapper; @Autowired private OAuthVerifyService oauthVerifyService; @Autowired private LineOAuthStateService lineOAuthStateService; @Autowired private BusinessPhoneService businessPhoneService; @Autowired private MerchantStoreAccessService merchantStoreAccessService; @Autowired private MerchantTokenSessionService merchantTokenSessionService; @Autowired private ISysDictDataService dictDataService; @Autowired private IRiderPositionService riderPositionService; @Autowired private DeviceTrustService deviceTrustService; /** 三方登录首登临时凭证 Redis 前缀 */ private static final String OAUTH_TEMP_PREFIX = "oauth:bind:"; /** 设备确认临时凭证 Redis 前缀:值为 deviceId,与 OAUTH_TEMP_PREFIX 同 TTL 成对写入(029) */ private static final String OAUTH_TEMP_DEVICE_PREFIX = "oauth:bind:dev:"; private static final String OAUTH_TEMP_USER_PREFIX = "oauth:bind:user:"; @Anonymous @PostMapping("/testBathPush") public AjaxResult testBathPush(@RequestBody List cids){ PayPush push=new PayPush(); push.userPushBatch(cids,"测试","内容","{\"ddId\":991754272700492,\"state\":3,\"type\":-1}"); return success(); } @Anonymous // @Auth @GetMapping("/createUserWallets") public AjaxResult createUserWallets() { List users = infoUserService.list(); List wallets = userWalletService.list(); List inserts = new ArrayList<>(); for (InfoUser user : users) { boolean exist = wallets.stream().anyMatch(x -> x.getUserId().equals(user.getUserId())); if (!exist) { UserWallet insert = new UserWallet(); insert.setUserId(user.getUserId()); insert.setVersion(0); insert.setPointsWallet(0L); insert.setBalanceWallet(BigDecimal.ZERO); insert.setBlockedFunds(BigDecimal.ZERO); inserts.add(insert); } } if (!inserts.isEmpty()) { userWalletService.saveBatch(inserts); } return success(); } /** * 获取rtc用户签名 * * @return */ @Anonymous @GetMapping("/getUserSig") public AjaxResult getUserSig(@RequestParam String userId) { TLSSigAPIv2 tlss = new TLSSigAPIv2(); String obj = tlss.genUserSig(userId, 140000000); return success(MessageUtils.message("no.obtained.permission"), obj); } /** * 首页用户统计 * * @return */ @GetMapping("/gettongji") public AjaxResult gettongji(@RequestParam(defaultValue = "") String startDate, @RequestParam(defaultValue = "") String endDate) { JSONObject obj = new JSONObject(); int user = infoUserMapper.getUserSum("0", startDate, endDate); int sjia = infoUserMapper.getUserSum("1", startDate, endDate); int qishou = infoUserMapper.getUserSum("2", startDate, endDate); int mend = posStoreMapper.getStoreSum(startDate, endDate); int orderCount = posOrderMapper.getOrderCountByDate(startDate, endDate); int orderSum = posOrderMapper.getOrderSumByDate(startDate, endDate); obj.put("userSum", user); obj.put("sjiaSum", sjia); obj.put("qisSum", qishou); obj.put("storeSum", mend); obj.put("orderCount", orderCount); obj.put("orderSum", orderSum); return success(obj); } /** * 首页曲线图 * * @return */ @GetMapping("/getOrderCount") public AjaxResult getOrderCount() { JSONObject obj = new JSONObject(); List orcount = posOrderMapper.getOrderCount(); List orsum = posOrderMapper.getOrderSum(); int[] coun = new int[12]; Double[] Sum = new Double[12]; LocalDate currentDate = LocalDate.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy"); String formattedDate = currentDate.format(formatter); System.out.println("当前年份:" + formattedDate); for (int i = 0; i < 12; i++) { coun[i] = posOrderMapper.getMoorcoun(formattedDate + "-" + (i + 1) + "-1"); Sum[i] = posOrderMapper.getMoorSun(formattedDate + "-" + (i + 1) + "-1"); } obj.put("orcount", orcount.get(0).getAmount()); obj.put("ordersum", orsum.get(0).getAmount()); obj.put("counlist", coun); obj.put("sumlist", Sum); return success(obj); } /** * 判断会员日期 * * @param userId * @return */ @Anonymous @GetMapping("/getvip") public AjaxResult getvip(@RequestParam String userId) { DateUtil date = new DateUtil(); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", userId); VipUser vipUser = vipUserService.getOne(queryWrapper); if (vipUser == null) { return success(MessageUtils.message("no.user.not.vip"), null); } else { boolean orn = date.isPastDate(vipUser.getYxtim()); if (orn) { return success(MessageUtils.message("no.user.vip.expired"), null); } else { return success(MessageUtils.message("no.obtained.success"), vipUser.getYxtim()); } } } /** * 开通 IM 账号:APP 在用户注册完成后(或首次沟通前)调用。 * 通过登录 token 解析 userId,幂等创建/获取 IM 凭证并返回,供 APP 初始化 IM SDK。 */ @Anonymous @Auth @GetMapping("/imOpen") public AjaxResult openIm(@RequestHeader String token) { JwtUtil jwtUtil = new JwtUtil(); String id; try { id = jwtUtil.getusid(token); } catch (Exception e) { return error("请先登录"); } if (id == null || id.isEmpty()) { return error("请先登录"); } return AjaxResult.success(imAccountService.openImAccount(Long.valueOf(id))); } /** * 删除用户 */ @Anonymous @GetMapping("/deleuser") @Auth @Transactional(rollbackFor = Exception.class) public AjaxResult deleuser(@RequestHeader String token) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); Long userId = Long.valueOf(id); LambdaQueryWrapper query = new LambdaQueryWrapper<>(); query.in(PosOrder::getState, 0L, 1L, 2L) .and(role -> role.eq(PosOrder::getUserId, userId) .or().eq(PosOrder::getQsId, userId) .or().eq(PosOrder::getShId, userId)); boolean exist = posOrderService.exists(query); if (exist) { throw new ServiceException("抱歉,您還有未完成的訂單,無法刪除帳號"); } InfoUser account = infoUserService.getById(userId); if (account != null && "0".equals(account.getUserType())) { return success(userCancellationService.request(userId)); } return toAjax(infoUserService.deleteInfoUserByUserId(userId)); } /** * 骑手注册 */ @Anonymous @PostMapping("/addqishou") public AjaxResult addqishou(@RequestBody UserDTO userDTO) { String ncode = redisCache.getCacheObject(userDTO.getTelPhone().trim().replaceAll("\\+", "")); if (infoUserService.getinfouserName(userDTO.getUserName()) != null) { throw new ServiceException(MessageUtils.message("no.user.add") + userDTO.getUserName() + MessageUtils.message("no.user.login.exist")); } if ("".equals(userDTO.getPassword()) || userDTO.getPassword() == null) { throw new ServiceException(MessageUtils.message("no.user.password.not.null")); } if (ncode == null) { if (userDTO.getCode().equals("8888")) { return createQsUser(userDTO); } else { //验证码不正确 throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error")); } } else { if (ncode.equals(userDTO.getCode()) || userDTO.getCode().equals("8888")) { return createQsUser(userDTO); } else { throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error")); } } } /** * 注册创建骑手用户 * * @param userDTO * @return */ public AjaxResult createQsUser(UserDTO userDTO) { businessPhoneService.ensureUnique(userDTO.getTelPhone(), null); InfoUser user = new InfoUser(); UUIDUtil uuid = new UUIDUtil(); user.setUserName(userDTO.getUserName()); user.setPassword(userDTO.getPassword()); user.setTelPhone(userDTO.getTelPhone()); user.setUserType("2"); user.setAuditStatus("0"); user.setMycode(uuid.get8UUID()); user.setStatus("0"); user.setCid(userDTO.getCid()); user.setCidType(userDTO.getCidType()); user.setDeviceToken(userDTO.getDeviceToken()); user.setVoIPToken(userDTO.getVoIPToken()); int result = infoUserService.insertInfoUser(user); InfoUser infoUser = infoUserService.getinfouserName(userDTO.getUserName()); createUserWallet(infoUser.getUserId()); return toAjax(result); } /** * 商家注册 */ @Anonymous @PostMapping("/addshanghu") public AjaxResult addshanghu(@RequestBody UserDTO userDTO) { String ncode = redisCache.getCacheObject(userDTO.getTelPhone().trim().replaceAll("\\+", "")); if (infoUserService.getinfouserName(userDTO.getUserName()) != null) { throw new ServiceException(MessageUtils.message("no.user.add") + userDTO.getUserName() + MessageUtils.message("no.user.login.exist")); } if ("".equals(userDTO.getPassword()) || userDTO.getPassword() == null) { throw new ServiceException(MessageUtils.message("no.user.password.not.null")); } if (ncode == null) { if (userDTO.getCode().equals("8888")) { return createShUser(userDTO); } else { throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error")); } } else { if (ncode.equals(userDTO.getCode()) || userDTO.getCode().equals("8888")) { return createShUser(userDTO); } else { throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error")); } } } /** * 注册创建商户用户 * * @param userDTO * @return */ public AjaxResult createShUser(UserDTO userDTO) { businessPhoneService.ensureUnique(userDTO.getTelPhone(), null); InfoUser user = new InfoUser(); UUIDUtil uuid = new UUIDUtil(); user.setUserName(userDTO.getUserName()); user.setPassword(userDTO.getPassword()); user.setTelPhone(userDTO.getTelPhone()); user.setUserType("1"); user.setAuditStatus("0"); user.setMycode(uuid.get8UUID()); user.setStatus("0"); user.setCid(userDTO.getCid()); user.setCidType(userDTO.getCidType()); user.setDeviceToken(userDTO.getDeviceToken()); user.setVoIPToken(userDTO.getVoIPToken()); int result = infoUserService.insertInfoUser(user); InfoUser infoUser = infoUserService.getinfouserName(userDTO.getUserName()); createUserWallet(infoUser.getUserId()); return toAjax(result); } /** * 商户登录 * * @param userDTO * @return */ @Anonymous @PostMapping("/shanglodeing") public AjaxResult shanglodeing(@RequestBody UserDTO userDTO) { JwtUtil tokn = new JwtUtil(); RsaMima rsa = new RsaMima(); try { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_name", userDTO.getUserName()); queryWrapper.eq("del_flag", "0"); queryWrapper.in("user_type", "1", "3", "4", "5"); InfoUser user = infoUserService.getOne(queryWrapper); if (user == null) { throw new ServiceException(MessageUtils.message("no.user.not.exist")); } String wmima = rsa.decryptByPrivateKey(userDTO.getPassword()); String nmima = rsa.decryptByPrivateKey(user.getPassword()); if (wmima.equals(nmima)) { if (!MerchantAccountConstants.STATUS_ENABLED.equals(user.getStatus())) { throw new ServiceException(MessageUtils.message("merchant.account.unavailable")); } if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(user.getUserType())) { merchantStoreAccessService.resolve(user.getUserId()); } InfoUser info = new InfoUser(); info.setUserId(user.getUserId()); info.setLastLoginAt(new Date()); if (userDTO.getCid() != null && !userDTO.getCid().isBlank()) { info.setCid(userDTO.getCid()); info.setCidType(userDTO.getCidType()); info.setDeviceToken(userDTO.getDeviceToken()); info.setVoIPToken(userDTO.getVoIPToken()); user.setCid(userDTO.getCid()); user.setCidType(userDTO.getCidType()); user.setDeviceToken(userDTO.getDeviceToken()); user.setVoIPToken(userDTO.getVoIPToken()); } infoUserService.updateInfoUser(info); user.setLastLoginAt(info.getLastLoginAt()); // 根据客户端类型(APP端或PC端)选择对应的 token key String tokenKey = getTokenKeyByClientType(); //app端 if (tokenKey.equals(CacheConstants.SH_APP_TOKEN_KEY)) { // 删除该用户同类型客户端的旧 token redisCache.deleteKeys(tokenKey + user.getUserId() + ":" + "*"); } // 补充登录用户信息 LoginUserDto userDto = new LoginUserDto(); userDto.setUserId(user.getUserId()); userDto.setUserName(user.getUserName()); userDto.setProvider("phone"); fillLoginUserInfo(userDto); String token = JwtUtil.setToken(tokenKey, userDto); return success(MessageUtils.message("no.user.login.success"), merchantLoginView(user), token); } else { return error(MessageUtils.message("no.user.password.error")); } } catch (ServiceException e) { throw e; } catch (Exception e) { e.printStackTrace(); return error(MessageUtils.message("no.system.error")); } } /** * 骑手登录 * * @param userDTO * @return */ @Anonymous @PostMapping("/syslodeing") public AjaxResult syslodeing(@RequestBody UserDTO userDTO) { JwtUtil tokn = new JwtUtil(); RsaMima rsa = new RsaMima(); try { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_name", userDTO.getUserName()); queryWrapper.eq("del_flag", "0"); queryWrapper.eq("user_type", "2"); InfoUser user = infoUserService.getOne(queryWrapper); if (user == null) { return error(MessageUtils.message("no.user.not.exist")); } String wmima = rsa.decryptByPrivateKey(userDTO.getPassword()); String nmima = rsa.decryptByPrivateKey(user.getPassword()); if (wmima.equals(nmima)) { InfoUser info = new InfoUser(); info.setUserId(user.getUserId()); info.setCid(userDTO.getCid()); info.setCidType(userDTO.getCidType()); info.setDeviceToken(userDTO.getDeviceToken()); info.setVoIPToken(userDTO.getVoIPToken()); info.setOffline("1"); infoUserService.saveOrUpdate(info); if (!"".equals(userDTO.getCid())) { user.setCid(userDTO.getCid()); } ; //返回离线标识状态为在线 user.setOffline("1"); redisCache.deleteKeys(CacheConstants.QS_TOKEN_KEY + user.getUserId() + ":" + "*"); // 补充登录用户信息 LoginUserDto userDto = new LoginUserDto(); userDto.setUserId(user.getUserId()); userDto.setUserName(user.getUserName()); userDto.setProvider("phone"); fillLoginUserInfo(userDto); String token = JwtUtil.setToken(CacheConstants.QS_TOKEN_KEY, userDto); return success(MessageUtils.message("no.user.login.success"), user, token); } else { return error(MessageUtils.message("no.user.password.error")); } } catch (ServiceException e) { throw e; } catch (Exception e) { e.printStackTrace(); return error(MessageUtils.message("no.system.error")); } } /** * 填充登录用户信息(IP地址、登录地点、浏览器、操作系统、登录时间等) * * @param userDto 登录用户DTO * @return 填充后的登录用户DTO */ private LoginUserDto fillLoginUserInfo(LoginUserDto userDto) { // 获取IP地址 String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); userDto.setIpaddr(ip); // 获取登录地点 userDto.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); // 获取浏览器和操作系统信息 UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); userDto.setBrowser(userAgent.getBrowser().getName()); userDto.setOs(userAgent.getOperatingSystem().getName()); // 设置登录时间 userDto.setLoginTime(System.currentTimeMillis()); return userDto; } /** * 判断客户端类型(APP端还是PC端) * 通过 User-Agent 判断:包含 Mobile、Android、iPhone、iPad 等关键词则为 APP 端 * * @return true 表示 APP 端,false 表示 PC 端 */ private boolean isAppClient() { String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); if (userAgent == null || userAgent.isEmpty()) { // 如果没有 User-Agent,默认认为是 PC 端 return false; } String ua = userAgent.toLowerCase(); // 判断是否为移动设备(APP端) return ua.contains("mobile") || ua.contains("android") || ua.contains("iphone") || ua.contains("ipad") || ua.contains("ipod") || ua.contains("windows phone"); } /** * 根据客户端类型获取对应的 token key * * @return token key */ private String getTokenKeyByClientType() { return isAppClient() ? CacheConstants.SH_APP_TOKEN_KEY : CacheConstants.SH_PC_TOKEN_KEY; } private MerchantLoginView merchantLoginView(InfoUser user) { MerchantLoginView view = new MerchantLoginView(); view.setUserId(user.getUserId()); view.setUserName(user.getUserName()); view.setNickName(user.getNickName()); view.setUserType(user.getUserType()); view.setStoreId(user.getStoreId()); view.setMerchantOwnerId(user.getMerchantOwnerId()); view.setStatus(user.getStatus()); view.setSubaccountStatus(user.getSubaccountStatus()); view.setLastLoginAt(user.getLastLoginAt()); return view; } /** * 商家端退出登录,仅撤销当前 JWT 对应的会话。 */ @Anonymous @Auth @PostMapping("/merchantLogout") public AjaxResult merchantLogout(@RequestHeader String token) { merchantTokenSessionService.logoutCurrent(AuthContext.requireJti()); return success(); } private void createUserWallet(Long userId) { try { userWalletService.createUserWallet(userId); } catch (Exception e) { logger.error("用户钱包创建失败,userId:{}", userId, e); throw new ServiceException(MessageUtils.message("no.user.login.exist")); } } /** * 退出登录 * * @param token * @return */ @Anonymous @Auth @GetMapping("/logout") public AjaxResult logout(@RequestHeader String token) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); InfoUser user = infoUserService.getOne(new LambdaQueryWrapper().eq(InfoUser::getUserId, Long.valueOf(id))); if (user == null) { throw new ServiceException("未查询到用户信息"); } LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(InfoUser::getUserId, user.getUserId()) .set(InfoUser::getOffline, "1") .set(InfoUser::getCid, null) .set(InfoUser::getCidType, null); infoUserService.update(updateWrapper); //骑手删除定位信息 if ("2".equals(user.getUserType())) { riderPositionService.remove(new LambdaQueryWrapper().eq(RiderPosition::getRiderId, user.getUserId())); } return success(); } /** * 骑手切换离线状态(0:在线,1:离线) * * @param token * @return */ @Anonymous @Auth @GetMapping("/qsSwitchOffline") public AjaxResult qsSwitchOffline(@RequestHeader String token, @RequestParam String offline) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); InfoUser user = infoUserService.getById(id); InfoUser qs = new InfoUser(); qs.setUserId(user.getUserId()); qs.setOffline(offline); infoUserService.saveOrUpdate(qs); return success(); } /** * 获取手机短信 * * @param phone * @return */ @Anonymous @GetMapping("/getcode") public AjaxResult getcode(@RequestParam String phone) { // GET 查询参数中 "+" 会被解码成空格,此处还原:前导空格+数字 → +数字 if (phone != null && phone.startsWith(" ") && phone.trim().matches("\\d+")) { phone = "+" + phone.trim(); } MobileSMS sms = new MobileSMS(); // System.out.println(phone.trim().replaceAll("\\+", "")); String toPhone = phone; String authcode = "1" + RandomStringUtils.randomNumeric(5);//生成随机数,我发现生成5位随机数时,如果开头为0,发送的短信只有4位,这里开头加个1,保证短信的正确性 System.out.println("验证码:" + authcode); redisCache.setCacheObject(phone.trim().replaceAll("\\+", ""), authcode, 5, TimeUnit.MINUTES);//将验证码存入缓存 // sms.getcode(phone, authcode);//发送短息 sms.getTwCode(toPhone,authcode); return success(MessageUtils.message("no.sms.send.success")); } /** * 绑定手机号 * * @param token * @param userDTO * @return */ @Anonymous @Auth @PostMapping("/Bindingphone") public AjaxResult Bindingphone(@RequestHeader String token, @RequestBody UserDTO userDTO) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); String xcode = redisCache.getCacheObject(userDTO.getTelPhone().trim().replaceAll("\\+", "")); if (userDTO.getCode().equals(xcode)) { InfoUser user = infoUserService.getById(id); if (user == null) { return error(MessageUtils.message("no.user.not.exist")); } else { if (businessPhoneService.isBusinessUserType(user.getUserType())) { businessPhoneService.ensureUnique(userDTO.getTelPhone(), user.getUserId()); } InfoUser infoUser = new InfoUser(); infoUser.setUserId(user.getUserId()); infoUser.setTelPhone(userDTO.getTelPhone()); Boolean org = infoUserService.saveOrUpdate(infoUser); if (org) { return success(); } else { return error(); } } } else { return error(MessageUtils.message("no.user.jcaptcha.error")); } } /** * 用户修改密码 * * @param token * @param userDTO * @return */ @Anonymous @Auth @PostMapping("/setpassword") public AjaxResult setpassword(@RequestHeader String token, @RequestBody UserDTO userDTO) { JwtUtil jwtUtil = new JwtUtil(); RsaMima rsa = new RsaMima(); try { String id = jwtUtil.getusid(token); String wmima = rsa.decryptByPrivateKey(userDTO.getPassword()); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", Long.valueOf(id)); InfoUser user = infoUserService.getOne(queryWrapper); String nmima = rsa.decryptByPrivateKey(user.getPassword()); if (wmima.equals(nmima)) { InfoUser infoUser = new InfoUser(); infoUser.setUserId(user.getUserId()); infoUser.setPassword(userDTO.getNewpassword()); Boolean org = infoUserService.saveOrUpdate(infoUser); if (org) { return success(MessageUtils.message("no.user.password.modify.success")); } else { return error(); } } else { return success(MessageUtils.message("no.user.password.old.error")); } } catch (Exception e) { e.printStackTrace(); return error(MessageUtils.message("no.system.error")); } } /** * 修改用户信息 * * @param token * @param infoUser * @return */ @Anonymous @Auth @PostMapping("/setuser") public AjaxResult setuser(@RequestHeader String token, @RequestBody InfoUser infoUser) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); InfoUser current = infoUserService.getById(id); if (current != null && businessPhoneService.isBusinessUserType(current.getUserType())) { businessPhoneService.ensureUnique(infoUser.getTelPhone(), current.getUserId()); } normalizeDeliveryType(infoUser); InfoUser user = new InfoUser(); user.setUserId(Long.valueOf(id)); user.setCid(infoUser.getCid()); user.setCidType(infoUser.getCidType()); user.setDeviceToken(infoUser.getDeviceToken()); user.setVoIPToken(infoUser.getVoIPToken()); user.setNickName(infoUser.getNickName()); user.setAvatar(infoUser.getAvatar()); user.setEmail(infoUser.getEmail()); user.setSex(infoUser.getSex()); user.setThiscode(infoUser.getThiscode()); user.setAnnex(infoUser.getAnnex()); user.setTelPhone(infoUser.getTelPhone()); user.setDriversLicense(infoUser.getDriversLicense()); user.setDrivingLicense(infoUser.getDrivingLicense()); user.setVehiclePhotos(infoUser.getVehiclePhotos()); user.setLicensePlate(infoUser.getLicensePlate()); user.setVehicleType(infoUser.getVehicleType()); user.setDeliveryType(infoUser.getDeliveryType()); user.setRiderHeadPhoto(infoUser.getRiderHeadPhoto()); user.setPreLaunchCity(infoUser.getPreLaunchCity()); user.setBirthday(infoUser.getBirthday()); user.setInsurancePhoto(infoUser.getInsurancePhoto()); user.setPoliceCertificate(infoUser.getPoliceCertificate()); user.setFullName(infoUser.getFullName()); user.setIdNumber(infoUser.getIdNumber()); user.setMerchantType(infoUser.getMerchantType()); user.setBankAccount(infoUser.getBankAccount()); user.setPassportPhoto(infoUser.getPassportPhoto()); user.setBankPhoto(infoUser.getBankPhoto()); user.setBankAccountName(infoUser.getBankAccountName()); user.setBankName(infoUser.getBankName()); user.setBankAccountNo(infoUser.getBankAccountNo()); user.setMerchantIntroduction(infoUser.getMerchantIntroduction()); user.setBusinessLicense(infoUser.getBusinessLicense()); user.setDirectorsScreenshot(infoUser.getDirectorsScreenshot()); user.setFoodPermit(infoUser.getFoodPermit()); user.setUboStatement(infoUser.getUboStatement()); user.setAddress(infoUser.getAddress()); user.setLongitude(infoUser.getLongitude()); user.setLatitude(infoUser.getLatitude()); Boolean org = infoUserService.saveOrUpdate(user); if (org && Boolean.TRUE.equals(infoUser.getUpdateUserInfo())) { LambdaUpdateWrapper auditUpdateWrapper = new LambdaUpdateWrapper<>(); auditUpdateWrapper.eq(InfoUser::getUserId, Long.valueOf(id)) .eq(InfoUser::getAuditStatus, "2") .set(InfoUser::getAuditStatus, "0") .set(InfoUser::getAuditRejectReason, null); infoUserService.update(auditUpdateWrapper); } if (org) { return success(); } else { return error(); } } /** * 获取台湾银行列表(商家审核资料「银行名称」下拉的数据源,商家App共用) * 数据取自字典 taiwan_bank_list,运营可在平台后台字典管理中维护 */ @Anonymous @GetMapping("/bankList") public AjaxResult bankList() { SysDictData query = new SysDictData(); query.setDictType("taiwan_bank_list"); query.setStatus("0"); return success(dictDataService.selectDictDataList(query)); } /** * 获取用户信息详细信息 */ @Anonymous @Auth @GetMapping("/getuserinfo") public AjaxResult getuserinfo(@RequestHeader String token) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("user_id", id); wrapper.eq("del_flag", "0"); wrapper.eq("status", 0); InfoUser user = infoUserService.getOne(wrapper); if (user == null) { return error(MessageUtils.message("no.user.not.exist")); } else { DateUtil date = new DateUtil(); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", user.getUserId()); VipUser vipUser = vipUserService.getOne(queryWrapper); if (vipUser == null) { user.setVip(null); } else { boolean orn = date.isPastDate(vipUser.getYxtim()); if (orn) { user.setVip(null); } else { user.setVip(vipUser.getYxtim()); } } LambdaQueryWrapper walletQuery = new LambdaQueryWrapper<>(); UserWallet userWallet = userWalletService.selectByUserId(user.getUserId()); if (userWallet == null) { user.setPointsWallet(0L); } else { user.setPointsWallet(Long.valueOf(userWallet.getPointsWallet().toString())); } // 017 2026-09-24 增量:按本次登录会话回填渠道与第三方名称。 // 渠道来自会话 claim;名称精确按 userId+providerUid 反查绑定,无 providerUid 的旧会话只展示渠道,不任意挑绑定。 Map sessionClaims = JwtUtil.verifyToken(token); Object providerClaim = sessionClaims.get("provider"); String oauthProvider = providerClaim == null || "null".equals(providerClaim) ? null : String.valueOf(providerClaim); if (oauthProvider != null && !"phone".equals(oauthProvider)) { user.setOauthProvider(oauthProvider); Object uidClaim = sessionClaims.get("providerUid"); if (uidClaim != null && !"null".equals(uidClaim) && !String.valueOf(uidClaim).isEmpty()) { InfoUserOauth identity = infoUserOauthMapper.selectOne( new LambdaQueryWrapper() .eq(InfoUserOauth::getUserId, Long.valueOf(id)) .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(oauthProvider)) .eq(InfoUserOauth::getProviderUid, String.valueOf(uidClaim))); if (identity != null) { user.setProviderDisplayName(identity.getProviderDisplayName()); } } } return success(user); } } /** * 校验商家是否已补充资料信息(商家App用):data=true 已补充,false 未补充(App引导去资料补充页) * 判定口径:商家名称(nickName)已有值 */ @Anonymous @Auth @GetMapping("/checkMerchantProfile") public AjaxResult checkMerchantProfile(@RequestHeader String token) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("user_id", id); wrapper.eq("del_flag", "0"); wrapper.eq("status", 0); InfoUser user = infoUserService.getOne(wrapper); if (user == null) { return error(MessageUtils.message("no.user.not.exist")); } // 商家名称(nickName)为空即视为未补充资料 boolean completed = user.getNickName() != null && !user.getNickName().trim().isEmpty(); return success(completed); } /** * 获取新的token * * @param token * @return */ @Anonymous @Auth @GetMapping("/gettoken") @Transactional(rollbackFor = Exception.class) public AjaxResult gettoken(@RequestHeader String token) { JwtUtil jwtUtil = new JwtUtil(); String id = jwtUtil.getusid(token); QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("user_id", id); wrapper.eq("status", 0); wrapper.eq("del_flag", "0"); // 刷新会话也与注销共用账号锁,避免注销撤销后又签发新会话。 wrapper.last("FOR UPDATE"); InfoUser user = infoUserService.getOne(wrapper); if (user == null) { return error(MessageUtils.message("no.user.stop")); } else { // 必须走写 Redis 会话的重载:无会话 token 会被 @Auth(session=true) 切面拒掉(修复普通用户 getidlist 报商家会话无效) LoginUserDto userDto = new LoginUserDto(); userDto.setUserId(user.getUserId()); userDto.setUserName(user.getUserName()); Map renewClaims = JwtUtil.verifyToken(token); Object provider = renewClaims.get("provider"); userDto.setProvider(provider == null || "null".equals(provider) ? "phone" : String.valueOf(provider)); Object providerUid = renewClaims.get("providerUid"); userDto.setProviderUid(providerUid == null || "null".equals(providerUid) ? null : String.valueOf(providerUid)); fillLoginUserInfo(userDto); String wtoken = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto); return success(MessageUtils.message("no.user.token.success"), wtoken); } } /** * 手机号登录/注册查会员账号。不能用 getOne:info_user 被会员/商家/骑手共用, * 同手机号可能命中多行(getOne 多行会抛底层异常导致登录接口 500), * 且必须限定会员类型,防止会员端登上 phone 字段同号的商家/骑手账号。 */ private InfoUser findPhoneLoginUser(String phone) { List matches = infoUserService.list(new LambdaQueryWrapper() .eq(InfoUser::getPhone, phone) .and(w -> w.eq(InfoUser::getDelFlag, "0").or(pending -> pending .eq(InfoUser::getDelFlag, "1").gt(InfoUser::getCancellationDeadlineAt, new Date()))) .eq(InfoUser::getUserType, "0").last("FOR UPDATE")); if (matches.size() > 1) { log.warn("[Login] 手机号关联多个账号 phone={}", maskPhone(phone)); throw new ServiceException(MessageUtils.message("no.user.phone.duplicate")); } return matches.isEmpty() ? null : matches.get(0); } /** * 用户登录或注册 * * @param userDTO * @return */ @Anonymous @PostMapping("/lodeing") @Transactional(rollbackFor = Exception.class) public AjaxResult lodeing(@RequestBody UserDTO userDTO) { JwtUtil tokn = new JwtUtil(); String xcode = redisCache.getCacheObject(userDTO.getPhone().trim().replaceAll("\\+", "")); if (xcode == null) { if (userDTO.getCode().equals("8888")) { InfoUser infoUser = findPhoneLoginUser(userDTO.getPhone()); if (infoUser == null) { return createUser(userDTO); } else { return userLoginUpdateInfo(userDTO, infoUser); } } else { return error(MessageUtils.message("no.user.jcaptcha.error")); } } else { if (xcode.equals(userDTO.getCode()) || userDTO.getCode().equals("8888")) { InfoUser infoUser = findPhoneLoginUser(userDTO.getPhone()); if (infoUser == null) { return createUser(userDTO); } else { return userLoginUpdateInfo(userDTO, infoUser); } } else { return error(MessageUtils.message("no.user.jcaptcha.error")); } } } /** * 用户注册创建用户 * * @param userDTO * @return */ public AjaxResult createUser(UserDTO userDTO) { UUIDUtil uuid = new UUIDUtil(); InfoUser info = new InfoUser(); info.setPhone(userDTO.getPhone()); info.setNickName(userDTO.getPhone()); info.setCid(userDTO.getCid()); info.setCidType(userDTO.getCidType()); info.setDeviceToken(userDTO.getDeviceToken()); info.setVoIPToken(userDTO.getVoIPToken()); info.setUserType("0"); info.setMycode(uuid.get8UUID()); infoUserService.saveOrUpdate(info); InfoUser inus = findPhoneLoginUser(userDTO.getPhone()); createUserWallet(inus.getUserId()); deviceTrustService.recordLogin(userDTO.getDeviceId(), userDTO.getPhone(), inus.getUserId()); redisCache.deleteKeys(CacheConstants.USER_TOKEN_KEY + inus.getUserId() + ":" + "*"); // 补充登录用户信息 LoginUserDto userDto = new LoginUserDto(); userDto.setUserId(inus.getUserId()); userDto.setUserName(inus.getPhone()); userDto.setProvider("phone"); fillLoginUserInfo(userDto); String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto); if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error")); return success(MessageUtils.message("no.user.login.success"), inus, token); } /** * 用户登录更新用户信息 * @param userDTO * @param infoUser * @return */ public AjaxResult userLoginUpdateInfo(UserDTO userDTO, InfoUser infoUser) { AjaxResult confirmation = cancellationPrompt(infoUser, new CancellationLoginContext(Flow.PHONE, infoUser.getUserId(), infoUser.getPhone(), "phone", null, null, new Device(userDTO.getDeviceId(), userDTO.getCid(), userDTO.getCidType(), userDTO.getDeviceToken(), userDTO.getVoIPToken(), true))); if (confirmation != null) return confirmation; DateUtil date = new DateUtil(); QueryWrapper query = new QueryWrapper<>(); query.eq("user_id", infoUser.getUserId()); query.eq("status", 0); query.eq("del_flag", "0"); InfoUser user = infoUserService.getOne(query); if (user == null) { throw new ServiceException(MessageUtils.message("no.user.not.exist")); } user.setCid(userDTO.getCid()); user.setCidType(userDTO.getCidType()); user.setDeviceToken(userDTO.getDeviceToken()); user.setVoIPToken(userDTO.getVoIPToken()); infoUserService.saveOrUpdate(user); deviceTrustService.recordLogin(userDTO.getDeviceId(), user.getPhone(), user.getUserId()); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", infoUser.getUserId()); VipUser vipUser = vipUserService.getOne(queryWrapper); if (vipUser == null) { user.setVip(null); } else { boolean orn = date.isPastDate(vipUser.getYxtim()); if (orn) { user.setVip(null); } else { user.setVip(vipUser.getYxtim()); } } redisCache.deleteKeys(CacheConstants.USER_TOKEN_KEY + user.getUserId() + ":" + "*"); // 补充登录用户信息 LoginUserDto userDto = new LoginUserDto(); userDto.setUserId(user.getUserId()); userDto.setUserName(user.getPhone()); userDto.setProvider("phone"); fillLoginUserInfo(userDto); String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto); if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error")); return success(MessageUtils.message("no.user.login.success"), user, token); } // ==================== 三方登录(017-oauth-login)==================== /** * 三方登录第一步:校验 provider 凭证 → 已绑定直接登录;未绑定返回 needPhone + tempKey。 */ @Anonymous @PostMapping("/oauthLogin") @Transactional(rollbackFor = Exception.class) public AjaxResult oauthLogin(@RequestBody OAuthLoginDto dto) { if (dto == null || dto.getProvider() == null || dto.getProvider().isEmpty()) { return error(MessageUtils.message("no.oauth.provider.blank")); } OAuthAccountType accountType = OAuthAccountType.forProvider(dto.getProvider()); if (LineOAuthStateService.isBusinessLine(dto.getProvider())) { lineOAuthStateService.consume(dto.getProvider(), dto.getState()); } log.info("[OAuth] oauthLogin provider={}", dto.getProvider()); OAuthIdentity identity = oauthVerifyService.verifyIdentity(dto.getProvider(), dto.getCredential()); String providerUid = identity.providerUid(); // 第三方显示名称:LINE 取 profile displayName;Apple 由 App 首次授权提交 fullName(017 2026-09-24 增量) String displayName = identity.displayName(); if ((displayName == null || displayName.isEmpty()) && dto.getProvider().startsWith("apple") && dto.getFullName() != null && !dto.getFullName().isBlank()) { displayName = dto.getFullName().trim(); } // 查是否已绑定 InfoUserOauth bind = infoUserOauthMapper.selectOne( new LambdaQueryWrapper() .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(dto.getProvider())) .eq(InfoUserOauth::getProviderUid, providerUid)); if (bind != null) { // 已绑定:校验用户正常后直接登录 log.info("[OAuth] 已绑定 provider={}, userId={}", dto.getProvider(), bind.getUserId()); QueryWrapper q = new QueryWrapper<>(); q.eq("user_id", bind.getUserId()).eq("status", 0); if (accountType == null || accountType == OAuthAccountType.USER) { q.eq("user_type", "0").and(w -> w.eq("del_flag", "0").or(pending -> pending .eq("del_flag", "1").gt("cancellation_deadline_at", new Date()))); } else { q.eq("del_flag", "0"); } if (accountType == null || accountType == OAuthAccountType.USER) q.last("FOR UPDATE"); InfoUser u = infoUserService.getOne(q); if (u != null && (accountType == null || accountType == OAuthAccountType.USER)) { AjaxResult confirmation = cancellationPrompt(u, new CancellationLoginContext(Flow.OAUTH, u.getUserId(), u.getPhone(), dto.getProvider(), providerUid, displayName, new Device(dto.getDeviceId(), dto.getCid(), dto.getCidType(), dto.getDeviceToken(), dto.getVoIPToken(), true))); if (confirmation != null) return confirmation; } if (u == null) { log.warn("[OAuth] 已绑定但账号已停用 userId={}", bind.getUserId()); return error(MessageUtils.message("no.user.stop")); } if (accountType != null && !canOauthLogin(accountType, u)) { return error(MessageUtils.message("no.user.stop")); } u.setCid(dto.getCid()); u.setCidType(dto.getCidType()); u.setDeviceToken(dto.getDeviceToken()); u.setVoIPToken(dto.getVoIPToken()); infoUserService.saveOrUpdate(u); if (accountType == null || accountType == OAuthAccountType.USER) { deviceTrustService.recordOauthLogin(dto.getDeviceId(), u.getPhone(), u.getUserId(), bind); } refreshOauthDisplayName(bind, displayName); return issueOauthToken(u, dto.getProvider(), providerUid); } // 未绑定:缓存 {provider, providerUid},返回 needPhone + tempKey log.info("[OAuth] 未绑定,返回 needPhone provider={}", dto.getProvider()); // (029:同设备已验证过手机号 → 免短信一键确认 deviceConfirm 分支) String tempKey = UUID.randomUUID().toString().replace("-", ""); // 未绑定时把第三方名称一并带进临时凭证,绑定时落库;名称为空保持两段式(兼容旧缓存值) String tempValue = displayName == null || displayName.isEmpty() ? dto.getProvider() + "@" + providerUid : dto.getProvider() + "@" + providerUid + "@" + displayName; redisCache.setCacheObject(OAUTH_TEMP_PREFIX + tempKey, tempValue, 5, TimeUnit.MINUTES); // 029:设备信任免短信确认仅用户端(会员)适用;业务账号绑定必须真实短信,不走免确认 InfoUserDevice trustedDevice = accountType == null || accountType == OAuthAccountType.USER ? deviceTrustService.getTrust(dto.getDeviceId()) : null; String trustedPhone = trustedDevice == null ? null : trustedDevice.getPhone(); if (trustedPhone != null) { redisCache.setCacheObject(OAUTH_TEMP_DEVICE_PREFIX + tempKey, dto.getDeviceId(), 5, TimeUnit.MINUTES); redisCache.setCacheObject(OAUTH_TEMP_USER_PREFIX + tempKey, String.valueOf(trustedDevice.getUserId()), 5, TimeUnit.MINUTES); log.info("[OAuth] 设备已信任,返回 deviceConfirm provider={}, deviceId={}, phone={}", dto.getProvider(), dto.getDeviceId(), DeviceTrustService.maskPhone(trustedPhone)); Map data = new HashMap<>(); data.put("status", "deviceConfirm"); data.put("tempKey", tempKey); data.put("maskedPhone", DeviceTrustService.maskPhone(trustedPhone)); return AjaxResult.success(MessageUtils.message("no.oauth.device.confirm"), data); } log.info("[OAuth] 未绑定,返回 needPhone provider={}, providerUid={}", dto.getProvider(), providerUid); Map data = new HashMap<>(); data.put("status", "needPhone"); data.put("tempKey", tempKey); return AjaxResult.success(MessageUtils.message("no.oauth.needphone"), data); } /** * 三方登录第二步:手机号 + 短信验证 → 已注册关联 / 未注册新建 → 写绑定 → 签 token。 */ @Anonymous @Transactional(rollbackFor = Exception.class) @PostMapping("/oauthBindPhone") public AjaxResult oauthBindPhone(@RequestBody OAuthBindDto dto) { if (dto == null || dto.getTempKey() == null || dto.getTempKey().isEmpty()) { log.warn("[OAuth] oauthBindPhone 缺 tempKey"); return error(MessageUtils.message("no.oauth.tempkey.missing")); } String tempCacheKey = OAUTH_TEMP_PREFIX + dto.getTempKey(); String cached = redisCache.getCacheObject(tempCacheKey); if (cached == null || !redisCache.deleteObject(tempCacheKey)) { log.warn("[OAuth] tempKey 已过期/不存在"); return error(MessageUtils.message("no.oauth.tempkey.expired")); } String[] cachedParts = cached.split("@", 3); if (cachedParts.length < 2 || cachedParts[0].isEmpty() || cachedParts[1].isEmpty()) { return error(MessageUtils.message("no.oauth.tempkey.expired")); } String provider = cachedParts[0]; String providerUid = cachedParts[1]; String displayName = cachedParts.length > 2 ? cachedParts[2] : null; OAuthAccountType accountType = OAuthAccountType.forProvider(provider); log.info("[OAuth] 绑定流程 provider={}", provider); // 期间已有绑定也必须验证本次手机号/业务账号,不能提前签发旧账号 token。 boolean businessAccount = accountType != null && !accountType.allowCreate(); // 本轮仅改变用户端换号;业务端原有绑定容错及角色校验保持原规则。 if (businessAccount) { InfoUserOauth existing = infoUserOauthMapper.selectOne(new LambdaQueryWrapper() .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider)) .eq(InfoUserOauth::getProviderUid, providerUid)); if (existing != null) { InfoUser account = infoUserService.getById(existing.getUserId()); if (!canOauthLogin(accountType, account)) return error(MessageUtils.message("no.user.stop")); refreshOauthDisplayName(existing, displayName); return issueOauthToken(account, provider, providerUid); } } InfoUser user; if (businessAccount) { // 017 2026-10-08 增量:业务端(骑手/商家/子账号)改用账号密码绑定。 // 子账号等业务账号不强制登记 tel_phone,短信流程对其不可达;userName+密码是业务端唯一且全覆盖的登录因子。 if (dto.getUserName() == null || dto.getUserName().isBlank()) { return error(MessageUtils.message("no.oauth.username.blank")); } if (dto.getPassword() == null || dto.getPassword().isBlank()) { return error(MessageUtils.message("no.user.password.not.null")); } // 已删除(del_flag!=0)或渠道角色不符的账号一律按不存在拒绝,与 shanglodeing 一致; // userName 与登录路径同样按原串精确匹配(不 trim);不用 getOne,多行时给友好提示而非 500 List matches = infoUserService.list(new LambdaQueryWrapper() .eq(InfoUser::getUserName, dto.getUserName()) .eq(InfoUser::getDelFlag, "0") .in(InfoUser::getUserType, accountType.userTypes())); if (matches.size() > 1) { log.warn("[OAuth] 业务账号名关联多个账号 userName={}", dto.getUserName()); return error(MessageUtils.message("no.oauth.username.duplicate")); } user = matches.isEmpty() ? null : matches.get(0); if (user == null) { log.warn("[OAuth] 业务绑定账号不存在 userName={}", dto.getUserName()); return error(MessageUtils.message("no.user.not.exist")); } if (!verifyBusinessBindPassword(dto.getPassword(), user.getPassword())) { log.warn("[OAuth] 业务绑定密码错误 userName={}", dto.getUserName()); return error(MessageUtils.message("no.user.password.error")); } if (!canOauthLogin(accountType, user)) { return error(MessageUtils.message("no.user.stop")); } user.setCid(dto.getCid()); user.setCidType(dto.getCidType()); user.setDeviceToken(dto.getDeviceToken()); user.setVoIPToken(dto.getVoIPToken()); if (!infoUserService.saveOrUpdate(user)) { throw new ServiceException(MessageUtils.message("no.system.error")); } log.info("[OAuth] 业务账号密码绑定 userName={}", dto.getUserName()); } else { // 用户端保留既有校验:手机号 + 短信验证码(含万能码 8888 兼容)。 String phone = dto.getPhone(); if (phone == null || phone.isBlank()) { return error(MessageUtils.message("no.oauth.phone.blank")); } String xcode = redisCache.getCacheObject(phone.trim().replaceAll("\\+", "")); boolean codeOk = (xcode != null && xcode.equals(dto.getCode())) || "8888".equals(dto.getCode()); if (!codeOk) { log.warn("[OAuth] 短信验证码错误 phone={}", maskPhone(phone)); return error(MessageUtils.message("no.user.jcaptcha.error")); } // apple/google 用户端(accountType 为空)同样按会员渠道查询: // getuser 不带 userType 过滤,同手机号命中商家/骑手账号会错绑或多行异常。 OAuthAccountType bindingChannel = accountType != null ? accountType : OAuthAccountType.USER; user = findOauthBindingUser(bindingChannel, phone); if (user != null) { AjaxResult confirmation = cancellationPrompt(user, new CancellationLoginContext(Flow.BIND, user.getUserId(), user.getPhone(), provider, providerUid, displayName, new Device(dto.getDeviceId(), dto.getCid(), dto.getCidType(), dto.getDeviceToken(), dto.getVoIPToken(), true))); if (confirmation != null) { redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey()); redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey()); return confirmation; } } if (user != null && !"0".equals(user.getStatus())) { // 停用账号不允许绑定三方(与 lodeing 一致,防止绕过停用) log.warn("[OAuth] 账号已停用 phone={}", maskPhone(phone)); return error(MessageUtils.message("no.user.stop")); } if (user == null) { // 新建:昵称=手机号(与 createUser 一致) InfoUser info = new InfoUser(); info.setPhone(phone); info.setNickName(phone); info.setCid(dto.getCid()); info.setCidType(dto.getCidType()); info.setDeviceToken(dto.getDeviceToken()); info.setVoIPToken(dto.getVoIPToken()); info.setUserType("0"); info.setMycode(new UUIDUtil().get8UUID()); if (!infoUserService.saveOrUpdate(info)) { throw new ServiceException(MessageUtils.message("no.system.error")); } user = findOauthBindingUser(bindingChannel, phone); if (user == null) { return error(MessageUtils.message("no.user.not.exist")); } createUserWallet(user.getUserId()); log.info("[OAuth] 新建账号 userId={}", user.getUserId()); } else { user.setCid(dto.getCid()); user.setCidType(dto.getCidType()); user.setDeviceToken(dto.getDeviceToken()); user.setVoIPToken(dto.getVoIPToken()); if (!infoUserService.saveOrUpdate(user)) { throw new ServiceException(MessageUtils.message("no.system.error")); } log.info("[OAuth] 关联已有账号 userId={}", user.getUserId()); } } // 先解绑旧用户,再写本次新绑定,避免误删新手机号的绑定。 if (!businessAccount) deviceTrustService.recordLogin(dto.getDeviceId(), user.getPhone(), user.getUserId()); InfoUserOauth bind = bindVerifiedIdentity(user, provider, providerUid, displayName); if (!businessAccount) deviceTrustService.recordOauthLogin(dto.getDeviceId(), user.getPhone(), user.getUserId(), bind); redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey()); redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey()); return issueOauthToken(user, provider, providerUid); } /** * 设备信任免绑一键确认(029-device-trust-phone-bind):oauthLogin 返回 deviceConfirm 后, * App 弹只读脱敏手机号确认框,用户点确认调本接口免短信完成绑定登录。 */ @Anonymous @PostMapping("/oauthDeviceConfirm") @Transactional(rollbackFor = Exception.class) public AjaxResult oauthDeviceConfirm(@RequestBody OAuthDeviceConfirmDto dto) { if (dto == null || dto.getTempKey() == null || dto.getTempKey().isEmpty()) { log.warn("[OAuth] oauthDeviceConfirm 缺 tempKey"); return error(MessageUtils.message("no.oauth.tempkey.missing")); } if (dto.getDeviceId() == null || dto.getDeviceId().isEmpty()) { log.warn("[OAuth] oauthDeviceConfirm 缺 deviceId tempKey={}", dto.getTempKey()); return error(MessageUtils.message("no.oauth.device.mismatch")); } String cached = redisCache.getCacheObject(OAUTH_TEMP_PREFIX + dto.getTempKey()); if (cached == null) { log.warn("[OAuth] 设备确认 tempKey 已过期/不存在 tempKey={}", dto.getTempKey()); return error(MessageUtils.message("no.oauth.tempkey.expired")); } // 凭证与设备必须匹配:dev 键由 oauthLogin 的 deviceConfirm 分支写入,换设备重放即拒绝 String boundDevice = redisCache.getCacheObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey()); InfoUserDevice trust = deviceTrustService.getTrust(dto.getDeviceId()); Object expectedUser = redisCache.getCacheObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey()); if (boundDevice == null || !boundDevice.equals(dto.getDeviceId()) || trust == null || expectedUser == null || !String.valueOf(trust.getUserId()).equals(String.valueOf(expectedUser))) return error(MessageUtils.message("no.oauth.device.mismatch")); String[] cachedParts = cached.split("@", 3); if (cachedParts.length < 2) return error(MessageUtils.message("no.oauth.tempkey.expired")); String provider = cachedParts[0]; String providerUid = cachedParts[1]; String displayName = cachedParts.length > 2 ? cachedParts[2] : null; OAuthAccountType accountType = OAuthAccountType.forProvider(provider); if (accountType != null && accountType != OAuthAccountType.USER) return error(MessageUtils.message("no.oauth.device.mismatch")); // 用户行锁→设备行锁→绑定行锁;旧确认凭证不能绑定到新手机号,也不复建已消失的信任账号。 InfoUser user = findOauthBindingUser(OAuthAccountType.USER, trust.getPhone()); if (user == null || !java.util.Objects.equals(user.getUserId(), trust.getUserId())) return error(MessageUtils.message("no.oauth.device.mismatch")); if (!"0".equals(user.getStatus())) return error(MessageUtils.message("no.user.stop")); deviceTrustService.requireConfirmation(dto.getDeviceId(), user.getUserId()); AjaxResult confirmation = cancellationPrompt(user, new CancellationLoginContext(Flow.DEVICE, user.getUserId(), user.getPhone(), provider, providerUid, displayName, new Device(dto.getDeviceId(), null, null, null, null, false))); if (confirmation != null) { if (!redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey())) throw new ServiceException(MessageUtils.message("no.oauth.tempkey.expired")); redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey()); redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey()); return confirmation; } if (!canOauthLogin(OAuthAccountType.USER, user)) throw new ServiceException(MessageUtils.message("no.user.stop")); if (!redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey())) throw new ServiceException(MessageUtils.message("no.oauth.tempkey.expired")); InfoUserOauth bind = bindVerifiedIdentity(user, provider, providerUid, displayName); deviceTrustService.recordOauthLogin(dto.getDeviceId(), user.getPhone(), user.getUserId(), bind); redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey()); redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey()); return issueOauthToken(user, provider, providerUid); } /** 身份与手机号已验证后才调用;并发已有绑定只能复用同一用户,不得返回其他账号。 */ private InfoUserOauth bindVerifiedIdentity(InfoUser user, String provider, String providerUid, String displayName) { InfoUserOauth bind = infoUserOauthMapper.selectOne(new LambdaQueryWrapper() .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider)) .eq(InfoUserOauth::getProviderUid, providerUid).last("FOR UPDATE")); if (bind != null) { if (!java.util.Objects.equals(bind.getUserId(), user.getUserId())) throw new ServiceException(MessageUtils.message("no.oauth.binding.conflict")); refreshOauthDisplayName(bind, displayName); return bind; } bind = new InfoUserOauth(); bind.setUserId(user.getUserId()); bind.setProvider("line".equals(provider) ? "line_user" : provider); bind.setProviderUid(providerUid); bind.setProviderDisplayName(displayName != null && !displayName.isBlank() ? displayName : null); bind.setCreateTime(new Date()); if (infoUserOauthMapper.insert(bind) != 1) throw new ServiceException(MessageUtils.message("no.system.error")); return bind; } /** 手机号脱敏:保留前 3 后 4,中间 ****(仅日志用)。 */ private static String maskPhone(String phone) { if (phone == null || phone.length() < 8) { return phone; } return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4); } /** 017 2026-09-24 增量:登录成功刷新第三方显示名称;名称为空/未变化保留旧值,失败不阻断登录。 */ private void refreshOauthDisplayName(InfoUserOauth bind, String displayName) { if (bind == null || displayName == null || displayName.isBlank() || displayName.equals(bind.getProviderDisplayName())) { return; } bind.setProviderDisplayName(displayName); infoUserOauthMapper.updateById(bind); } /** 三方登录签发 token(claim provider=本次登录渠道、providerUid=本次绑定身份),复用现有 JwtUtil + Redis 会话。 */ private AjaxResult issueOauthToken(InfoUser user, String provider, String providerUid) { if (user == null) { return error(MessageUtils.message("no.user.not.exist")); } if (!"0".equals(user.getStatus()) || !"0".equals(user.getDelFlag())) { return error(MessageUtils.message("no.user.stop")); } OAuthAccountType accountType = OAuthAccountType.forProvider(provider); if (accountType != null && !canOauthLogin(accountType, user)) { return error(MessageUtils.message("no.user.stop")); } String tokenKey = accountType == null ? CacheConstants.USER_TOKEN_KEY : accountType.tokenKey(); redisCache.deleteKeys(tokenKey + user.getUserId() + ":*"); LoginUserDto dto = new LoginUserDto(); dto.setUserId(user.getUserId()); dto.setUserName(accountType == null || accountType.allowCreate() ? user.getPhone() : user.getUserName()); dto.setProvider(provider); dto.setProviderUid(providerUid); fillLoginUserInfo(dto); String token = JwtUtil.setToken(tokenKey, dto); if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error")); Object loginUser = accountType == OAuthAccountType.MERCHANT ? merchantLoginView(user) : user; return success(MessageUtils.message("no.user.login.success"), loginUser, token); } /** 身份已验证且账号已锁定;返回提示时不得继续写入登录资料或签发会话。 */ private AjaxResult cancellationPrompt(InfoUser user, CancellationLoginContext context) { if (user == null || !"0".equals(user.getUserType()) || !"1".equals(user.getDelFlag())) return null; return success(userCancellationService.prepareLogin(context)); } @Anonymous @PostMapping("/confirmCancellationLogin") @Transactional(rollbackFor = Exception.class) public AjaxResult confirmCancellationLogin(@RequestBody CancellationLoginConfirmDto request) { var confirmed = userCancellationService.confirmLogin(request == null ? null : request.confirmationKey()); InfoUser user = confirmed.user(); CancellationLoginContext context = confirmed.context(); Device device = context.device(); if (context.flow() == Flow.PHONE) return userLoginUpdateInfo(device.phoneLogin(user.getPhone()), user); if (context.flow() == Flow.DEVICE) { deviceTrustService.requireConfirmation(device.deviceId(), user.getUserId()); } else if (context.flow() == Flow.BIND) { deviceTrustService.recordLogin(device.deviceId(), user.getPhone(), user.getUserId()); } InfoUserOauth bind; if (context.flow() == Flow.OAUTH) { // 已绑定登录不允许在等待确认期间解绑后自动重建关系。 bind = infoUserOauthMapper.selectOne(new LambdaQueryWrapper() .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(context.provider())) .eq(InfoUserOauth::getProviderUid, context.providerUid())); if (bind == null || !java.util.Objects.equals(user.getUserId(), bind.getUserId())) throw new ServiceException(MessageUtils.message("no.oauth.binding.conflict")); deviceTrustService.recordOauthLogin(device.deviceId(), user.getPhone(), user.getUserId(), bind); InfoUserOauth locked = bind.getId() == null ? null : infoUserOauthMapper.selectByIdForUpdate(bind.getId()); if (locked == null || !java.util.Objects.equals(user.getUserId(), locked.getUserId()) || !java.util.Objects.equals(bind.getProvider(), locked.getProvider()) || !java.util.Objects.equals(context.providerUid(), locked.getProviderUid())) throw new ServiceException(MessageUtils.message("no.oauth.binding.conflict")); refreshOauthDisplayName(locked, context.displayName()); } else { bind = bindVerifiedIdentity(user, context.provider(), context.providerUid(), context.displayName()); deviceTrustService.recordOauthLogin(device.deviceId(), user.getPhone(), user.getUserId(), bind); } if (device.updatePush()) { user.setCid(device.cid()); user.setCidType(device.cidType()); user.setDeviceToken(device.deviceToken()); user.setVoIPToken(device.voIPToken()); if (!infoUserService.saveOrUpdate(user)) throw new ServiceException(MessageUtils.message("no.system.error")); } return issueOauthToken(user, context.provider(), context.providerUid()); } private boolean canOauthLogin(OAuthAccountType channel, InfoUser user) { if (user == null || !"0".equals(user.getStatus()) || !"0".equals(user.getDelFlag()) || !channel.userTypes().contains(user.getUserType())) { return false; } if (!MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(user.getUserType())) { return true; } if (!MerchantAccountConstants.STATUS_ENABLED.equals(user.getSubaccountStatus())) { return false; } try { merchantStoreAccessService.resolve(user.getUserId()); return true; } catch (ServiceException exception) { return false; } } /** * 017 2026-10-08 增量:业务端绑定密码比对,复用 shanglodeing/syslodeing 的 RSA 解密比较。 * 解密异常或比对不一致一律按密码错误拒绝,不向客户端区分失败原因。 */ private boolean verifyBusinessBindPassword(String submitted, String stored) { try { RsaMima rsa = new RsaMima(); String wmima = rsa.decryptByPrivateKey(submitted); String nmima = rsa.decryptByPrivateKey(stored); return wmima != null && wmima.equals(nmima); } catch (Exception e) { log.warn("[OAuth] 业务绑定密码解密失败 type={}", e.getClass().getSimpleName()); return false; } } /** * 按渠道查待绑定账号。不能用 getOne:会员/商家/骑手共用 info_user 表, * 同手机号可能命中多行(如一号注册多个商家账号),getOne 多行会抛底层异常导致接口 500; * 命中多行时明确提示用户联系平台,由平台侧人工处理绑定归属。 */ private InfoUser findOauthBindingUser(OAuthAccountType channel, String phone) { LambdaQueryWrapper query = new LambdaQueryWrapper() .eq(channel.allowCreate(), InfoUser::getPhone, phone) .eq(!channel.allowCreate(), InfoUser::getTelPhone, phone) .in(InfoUser::getUserType, channel.userTypes()); if (channel == OAuthAccountType.USER) { query.and(w -> w.eq(InfoUser::getDelFlag, "0").or(pending -> pending .eq(InfoUser::getDelFlag, "1").gt(InfoUser::getCancellationDeadlineAt, new Date()))).last("FOR UPDATE"); } else { query.eq(InfoUser::getDelFlag, "0"); } List matches = infoUserService.list(query); if (matches.size() > 1) { log.warn("[OAuth] 手机号关联多个账号 channel={}, phone={}", channel, maskPhone(phone)); throw new ServiceException(MessageUtils.message("no.oauth.phone.duplicate")); } return matches.isEmpty() ? null : matches.get(0); } /** * 查询用户信息列表 */ @PreAuthorize("@ss.hasPermi('infouser:user:list')") @GetMapping("/list") public TableDataInfo list(InfoUser infoUser) { startPage(); List list = infoUserService.selectInfoUserList(infoUser); if (!list.isEmpty()) { List userIds = list.stream().map(InfoUser::getUserId).collect(Collectors.toList()); List wallets = userWalletService.list(new LambdaQueryWrapper().in(UserWallet::getUserId, userIds).isNull(UserWallet::getStoreId)); list.forEach(item -> { Optional wallet = wallets.stream().filter(w -> w.getUserId().equals(item.getUserId())).findFirst(); if (wallet.isPresent()) { item.setPointsWallet(Long.valueOf(wallet.get().getPointsWallet().toString())); } else { item.setPointsWallet(0L); } }); } return getDataTable(list); } /** * 导出用户信息列表 */ @PreAuthorize("@ss.hasPermi('infouser:user:export')") @Log(title = "用户信息", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(HttpServletResponse response, InfoUser infoUser) { List list = infoUserService.selectInfoUserList(infoUser); ExcelUtil util = new ExcelUtil(InfoUser.class); util.exportExcel(response, list, MessageUtils.message("no.export.excel.userinfo")); } /** * 获取用户信息详细信息 */ @PreAuthorize("@ss.hasPermi('infouser:user:query')") @GetMapping(value = "/{userId}") public AjaxResult getInfo(@PathVariable("userId") Long userId) { InfoUser user = infoUserService.selectInfoUserByUserId(userId); DateUtil date = new DateUtil(); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", user.getUserId()); VipUser vipUser = vipUserService.getOne(queryWrapper); if (vipUser == null) { user.setVip(null); } else { boolean orn = date.isPastDate(vipUser.getYxtim()); if (orn) { user.setVip(null); } else { user.setVip(vipUser.getYxtim()); } } return success(user); } /** * 新增用户信息 */ @PreAuthorize("@ss.hasPermi('infouser:user:add')") @Log(title = "用户信息", businessType = BusinessType.INSERT) @PostMapping @Transactional(rollbackFor = Exception.class) public AjaxResult add(@RequestBody InfoUser infoUser) { if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(infoUser.getUserType())) { return error(MessageUtils.message("merchant.subaccount.platform.managed")); } if (!normalizeAuditRejectReason(infoUser)) { return error(MessageUtils.message("no.user.audit.reject.reason.required")); } normalizeDeliveryType(infoUser); if (infoUserService.getinfouserName(infoUser.getUserName()) != null) { return error(MessageUtils.message("no.user.add") + infoUser.getUserName() + MessageUtils.message("no.user.login.exist")); } else if (businessPhoneService.isBusinessUserType(infoUser.getUserType())) { businessPhoneService.ensureUnique(infoUser.getTelPhone(), null); } else if (infoUserService.getinfoPhone(infoUser.getPhone()) != null) { return error(MessageUtils.message("no.user.add") + infoUser.getPhone() + MessageUtils.message("no.user.mobile.exist")); } UUIDUtil uuid = new UUIDUtil(); infoUser.setMycode(uuid.get8UUID()); infoUser.setStatus("0"); System.out.println("加密:" + infoUser.getPassword()); int result = infoUserService.insertInfoUser(infoUser); createUserWallet(infoUser.getUserId()); return toAjax(result); } /** * 修改用户信息 */ @PreAuthorize("@ss.hasPermi('infouser:user:edit')") @Log(title = "用户信息", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestBody InfoUser infoUser) { InfoUser existing = infoUser.getUserId() == null ? null : infoUserService.selectInfoUserByUserId(infoUser.getUserId()); if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(infoUser.getUserType()) || (existing != null && MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(existing.getUserType()))) { return error(MessageUtils.message("merchant.subaccount.platform.managed")); } if (!normalizeAuditRejectReason(infoUser)) { return error(MessageUtils.message("no.user.audit.reject.reason.required")); } normalizeDeliveryType(infoUser); String effectiveUserType = infoUser.getUserType() != null ? infoUser.getUserType() : existing == null ? null : existing.getUserType(); if (businessPhoneService.isBusinessUserType(effectiveUserType)) { businessPhoneService.ensureUnique(infoUser.getTelPhone(), infoUser.getUserId()); } return toAjax(infoUserService.updateInfoUser(infoUser)); } /** * 重置用户登录密码(独立于通用编辑,密文入库,与 App 端 RSA 公钥体系一致) */ @PreAuthorize("@ss.hasPermi('infouser:user:resetPwd')") @Log(title = "用户信息", businessType = BusinessType.UPDATE) @PutMapping("/resetPwd") public AjaxResult resetPwd(@RequestBody InfoUserResetPwdDto resetPwdDto) { if (resetPwdDto.getUserId() == null) { return error(MessageUtils.message("no.user.not.exist")); } if (resetPwdDto.getPassword() == null || resetPwdDto.getPassword().isEmpty()) { return error(MessageUtils.message("no.user.password.not.null")); } InfoUser user = infoUserService.selectInfoUserByUserId(resetPwdDto.getUserId()); if (user == null) { return error(MessageUtils.message("no.user.not.exist")); } if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(user.getUserType())) { return error(MessageUtils.message("merchant.subaccount.platform.managed")); } boolean updated = infoUserService.update(new LambdaUpdateWrapper() .eq(InfoUser::getUserId, user.getUserId()) .set(InfoUser::getPassword, resetPwdDto.getPassword())); return updated ? success() : error(MessageUtils.message("no.system.error")); } private boolean normalizeAuditRejectReason(InfoUser infoUser) { if (!"2".equals(infoUser.getAuditStatus())) { infoUser.setAuditRejectReason(null); return true; } if (infoUser.getAuditRejectReason() == null) { return false; } String reason = infoUser.getAuditRejectReason().strip(); if (reason.isEmpty()) { return false; } infoUser.setAuditRejectReason(reason); return true; } /** 骑手配送类型归一化;非法值返回国际化业务错误。 */ private void normalizeDeliveryType(InfoUser infoUser) { try { infoUser.setDeliveryType(InfoUser.normalizeDeliveryTypes(infoUser.getDeliveryType())); } catch (IllegalArgumentException exception) { throw new ServiceException(MessageUtils.message("no.user.delivery.type.invalid")); } } /** * 删除用户信息 */ @PreAuthorize("@ss.hasPermi('infouser:user:remove')") @Log(title = "用户信息", businessType = BusinessType.DELETE) @DeleteMapping("/{userIds}") public AjaxResult remove(@PathVariable Long[] userIds) { return toAjax(infoUserService.deleteInfoUserByUserIds(userIds)); } }