InfoUserController.java 83 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811
  1. package com.ruoyi.app.user;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  5. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  6. import com.ruoyi.app.utils.DateUtil;
  7. import com.ruoyi.app.utils.PayPush;
  8. import com.ruoyi.app.utils.RsaMima;
  9. import com.ruoyi.app.utils.UUIDUtil;
  10. import com.ruoyi.app.utils.trtc.TLSSigAPIv2;
  11. import com.ruoyi.common.annotation.Anonymous;
  12. import com.ruoyi.common.annotation.Log;
  13. import com.ruoyi.common.constant.CacheConstants;
  14. import com.ruoyi.common.core.controller.BaseController;
  15. import com.ruoyi.common.core.domain.AjaxResult;
  16. import com.ruoyi.common.core.domain.model.LoginUserDto;
  17. import com.ruoyi.common.core.page.TableDataInfo;
  18. import com.ruoyi.common.core.redis.RedisCache;
  19. import com.ruoyi.common.enums.BusinessType;
  20. import com.ruoyi.common.exception.ServiceException;
  21. import com.ruoyi.common.utils.MessageUtils;
  22. import com.ruoyi.common.utils.ServletUtils;
  23. import com.ruoyi.common.utils.ip.AddressUtils;
  24. import com.ruoyi.common.utils.ip.IpUtils;
  25. import com.ruoyi.common.utils.poi.ExcelUtil;
  26. import com.ruoyi.system.domain.*;
  27. import com.ruoyi.system.domain.constants.MerchantAccountConstants;
  28. import com.ruoyi.app.user.dto.MerchantLoginView;
  29. import com.ruoyi.system.domain.vo.UserDTO;
  30. import com.ruoyi.app.service.ImAccountService;
  31. import com.ruoyi.system.mapper.InfoUserMapper;
  32. import com.ruoyi.system.mapper.PosOrderMapper;
  33. import com.ruoyi.system.mapper.PosStoreMapper;
  34. import com.ruoyi.common.core.domain.entity.SysDictData;
  35. import com.ruoyi.system.service.*;
  36. import com.ruoyi.system.utils.Auth;
  37. import com.ruoyi.system.utils.AuthContext;
  38. import com.ruoyi.system.utils.JwtUtil;
  39. import com.ruoyi.system.utils.MobileSMS;
  40. import com.ruoyi.app.user.dto.InfoUserResetPwdDto;
  41. import com.ruoyi.app.user.dto.OAuthBindDto;
  42. import com.ruoyi.app.user.dto.OAuthDeviceConfirmDto;
  43. import com.ruoyi.app.user.dto.OAuthLoginDto;
  44. import com.ruoyi.app.user.dto.CancellationLoginConfirmDto;
  45. import com.ruoyi.app.user.CancellationLoginContext.Flow;
  46. import com.ruoyi.app.user.CancellationLoginContext.Device;
  47. import com.ruoyi.app.utils.oauth.OAuthAccountType;
  48. import com.ruoyi.app.utils.oauth.OAuthIdentity;
  49. import com.ruoyi.app.utils.oauth.LineOAuthStateService;
  50. import com.ruoyi.app.user.service.DeviceTrustService;
  51. import com.ruoyi.system.domain.InfoUserDevice;
  52. import com.ruoyi.app.utils.oauth.OAuthVerifyService;
  53. import com.ruoyi.system.mapper.InfoUserOauthMapper;
  54. import eu.bitwalker.useragentutils.UserAgent;
  55. import org.apache.commons.lang3.RandomStringUtils;
  56. import org.slf4j.Logger;
  57. import org.slf4j.LoggerFactory;
  58. import org.springframework.beans.factory.annotation.Autowired;
  59. import org.springframework.security.access.prepost.PreAuthorize;
  60. import org.springframework.transaction.annotation.Transactional;
  61. import org.springframework.web.bind.annotation.*;
  62. import jakarta.servlet.http.HttpServletResponse;
  63. import java.math.BigDecimal;
  64. import java.time.LocalDate;
  65. import java.time.format.DateTimeFormatter;
  66. import java.util.ArrayList;
  67. import java.util.Date;
  68. import java.util.HashMap;
  69. import java.util.Map;
  70. import java.util.UUID;
  71. import java.util.Arrays;
  72. import java.util.List;
  73. import java.util.Optional;
  74. import java.util.concurrent.TimeUnit;
  75. import java.util.stream.Collectors;
  76. /**
  77. * 用户信息Controller
  78. *
  79. * @author ruoyi
  80. * @date 2023-05-13
  81. */
  82. @RestController
  83. @RequestMapping("/infouser/user")
  84. public class InfoUserController extends BaseController {
  85. private static final Logger log = LoggerFactory.getLogger(InfoUserController.class);
  86. @Autowired
  87. private IInfoUserService infoUserService;
  88. @Autowired
  89. private UserCancellationService userCancellationService;
  90. @Autowired
  91. private ImAccountService imAccountService;
  92. @Autowired
  93. private InfoUserMapper infoUserMapper;
  94. @Autowired
  95. private IVipUserService vipUserService;
  96. @Autowired
  97. private PosStoreMapper posStoreMapper;
  98. @Autowired
  99. private PosOrderMapper posOrderMapper;
  100. @Autowired
  101. private IUserWalletService userWalletService;
  102. @Autowired
  103. private RedisCache redisCache;
  104. @Autowired
  105. private IPosOrderService posOrderService;
  106. @Autowired
  107. private InfoUserOauthMapper infoUserOauthMapper;
  108. @Autowired
  109. private OAuthVerifyService oauthVerifyService;
  110. @Autowired
  111. private LineOAuthStateService lineOAuthStateService;
  112. @Autowired
  113. private BusinessPhoneService businessPhoneService;
  114. @Autowired
  115. private MerchantStoreAccessService merchantStoreAccessService;
  116. @Autowired
  117. private MerchantTokenSessionService merchantTokenSessionService;
  118. @Autowired
  119. private ISysDictDataService dictDataService;
  120. @Autowired
  121. private IRiderPositionService riderPositionService;
  122. @Autowired
  123. private DeviceTrustService deviceTrustService;
  124. /** 三方登录首登临时凭证 Redis 前缀 */
  125. private static final String OAUTH_TEMP_PREFIX = "oauth:bind:";
  126. /** 设备确认临时凭证 Redis 前缀:值为 deviceId,与 OAUTH_TEMP_PREFIX 同 TTL 成对写入(029) */
  127. private static final String OAUTH_TEMP_DEVICE_PREFIX = "oauth:bind:dev:";
  128. private static final String OAUTH_TEMP_USER_PREFIX = "oauth:bind:user:";
  129. @Anonymous
  130. @PostMapping("/testBathPush")
  131. public AjaxResult testBathPush(@RequestBody List<String> cids){
  132. PayPush push=new PayPush();
  133. push.userPushBatch(cids,"测试","内容","{\"ddId\":991754272700492,\"state\":3,\"type\":-1}");
  134. return success();
  135. }
  136. @Anonymous
  137. // @Auth
  138. @GetMapping("/createUserWallets")
  139. public AjaxResult createUserWallets() {
  140. List<InfoUser> users = infoUserService.list();
  141. List<UserWallet> wallets = userWalletService.list();
  142. List<UserWallet> inserts = new ArrayList<>();
  143. for (InfoUser user : users) {
  144. boolean exist = wallets.stream().anyMatch(x -> x.getUserId().equals(user.getUserId()));
  145. if (!exist) {
  146. UserWallet insert = new UserWallet();
  147. insert.setUserId(user.getUserId());
  148. insert.setVersion(0);
  149. insert.setPointsWallet(0L);
  150. insert.setBalanceWallet(BigDecimal.ZERO);
  151. insert.setBlockedFunds(BigDecimal.ZERO);
  152. inserts.add(insert);
  153. }
  154. }
  155. if (!inserts.isEmpty()) {
  156. userWalletService.saveBatch(inserts);
  157. }
  158. return success();
  159. }
  160. /**
  161. * 获取rtc用户签名
  162. *
  163. * @return
  164. */
  165. @Anonymous
  166. @GetMapping("/getUserSig")
  167. public AjaxResult getUserSig(@RequestParam String userId) {
  168. TLSSigAPIv2 tlss = new TLSSigAPIv2();
  169. String obj = tlss.genUserSig(userId, 140000000);
  170. return success(MessageUtils.message("no.obtained.permission"), obj);
  171. }
  172. /**
  173. * 首页用户统计
  174. *
  175. * @return
  176. */
  177. @GetMapping("/gettongji")
  178. public AjaxResult gettongji(@RequestParam(defaultValue = "") String startDate, @RequestParam(defaultValue = "") String endDate) {
  179. JSONObject obj = new JSONObject();
  180. int user = infoUserMapper.getUserSum("0", startDate, endDate);
  181. int sjia = infoUserMapper.getUserSum("1", startDate, endDate);
  182. int qishou = infoUserMapper.getUserSum("2", startDate, endDate);
  183. int mend = posStoreMapper.getStoreSum(startDate, endDate);
  184. int orderCount = posOrderMapper.getOrderCountByDate(startDate, endDate);
  185. int orderSum = posOrderMapper.getOrderSumByDate(startDate, endDate);
  186. obj.put("userSum", user);
  187. obj.put("sjiaSum", sjia);
  188. obj.put("qisSum", qishou);
  189. obj.put("storeSum", mend);
  190. obj.put("orderCount", orderCount);
  191. obj.put("orderSum", orderSum);
  192. return success(obj);
  193. }
  194. /**
  195. * 首页曲线图
  196. *
  197. * @return
  198. */
  199. @GetMapping("/getOrderCount")
  200. public AjaxResult getOrderCount() {
  201. JSONObject obj = new JSONObject();
  202. List<PosOrder> orcount = posOrderMapper.getOrderCount();
  203. List<PosOrder> orsum = posOrderMapper.getOrderSum();
  204. int[] coun = new int[12];
  205. Double[] Sum = new Double[12];
  206. LocalDate currentDate = LocalDate.now();
  207. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy");
  208. String formattedDate = currentDate.format(formatter);
  209. System.out.println("当前年份:" + formattedDate);
  210. for (int i = 0; i < 12; i++) {
  211. coun[i] = posOrderMapper.getMoorcoun(formattedDate + "-" + (i + 1) + "-1");
  212. Sum[i] = posOrderMapper.getMoorSun(formattedDate + "-" + (i + 1) + "-1");
  213. }
  214. obj.put("orcount", orcount.get(0).getAmount());
  215. obj.put("ordersum", orsum.get(0).getAmount());
  216. obj.put("counlist", coun);
  217. obj.put("sumlist", Sum);
  218. return success(obj);
  219. }
  220. /**
  221. * 判断会员日期
  222. *
  223. * @param userId
  224. * @return
  225. */
  226. @Anonymous
  227. @GetMapping("/getvip")
  228. public AjaxResult getvip(@RequestParam String userId) {
  229. DateUtil date = new DateUtil();
  230. QueryWrapper<VipUser> queryWrapper = new QueryWrapper<>();
  231. queryWrapper.eq("user_id", userId);
  232. VipUser vipUser = vipUserService.getOne(queryWrapper);
  233. if (vipUser == null) {
  234. return success(MessageUtils.message("no.user.not.vip"), null);
  235. } else {
  236. boolean orn = date.isPastDate(vipUser.getYxtim());
  237. if (orn) {
  238. return success(MessageUtils.message("no.user.vip.expired"), null);
  239. } else {
  240. return success(MessageUtils.message("no.obtained.success"), vipUser.getYxtim());
  241. }
  242. }
  243. }
  244. /**
  245. * 开通 IM 账号:APP 在用户注册完成后(或首次沟通前)调用。
  246. * 通过登录 token 解析 userId,幂等创建/获取 IM 凭证并返回,供 APP 初始化 IM SDK。
  247. */
  248. @Anonymous
  249. @Auth
  250. @GetMapping("/imOpen")
  251. public AjaxResult openIm(@RequestHeader String token) {
  252. JwtUtil jwtUtil = new JwtUtil();
  253. String id;
  254. try {
  255. id = jwtUtil.getusid(token);
  256. } catch (Exception e) {
  257. return error("请先登录");
  258. }
  259. if (id == null || id.isEmpty()) {
  260. return error("请先登录");
  261. }
  262. return AjaxResult.success(imAccountService.openImAccount(Long.valueOf(id)));
  263. }
  264. /**
  265. * 删除用户
  266. */
  267. @Anonymous
  268. @GetMapping("/deleuser")
  269. @Auth
  270. @Transactional(rollbackFor = Exception.class)
  271. public AjaxResult deleuser(@RequestHeader String token) {
  272. JwtUtil jwtUtil = new JwtUtil();
  273. String id = jwtUtil.getusid(token);
  274. Long userId = Long.valueOf(id);
  275. LambdaQueryWrapper<PosOrder> query = new LambdaQueryWrapper<>();
  276. query.in(PosOrder::getState, 0L, 1L, 2L)
  277. .and(role -> role.eq(PosOrder::getUserId, userId)
  278. .or().eq(PosOrder::getQsId, userId)
  279. .or().eq(PosOrder::getShId, userId));
  280. boolean exist = posOrderService.exists(query);
  281. if (exist) {
  282. throw new ServiceException("抱歉,您還有未完成的訂單,無法刪除帳號");
  283. }
  284. InfoUser account = infoUserService.getById(userId);
  285. if (account != null && "0".equals(account.getUserType())) {
  286. return success(userCancellationService.request(userId));
  287. }
  288. return toAjax(infoUserService.deleteInfoUserByUserId(userId));
  289. }
  290. /**
  291. * 骑手注册
  292. */
  293. @Anonymous
  294. @PostMapping("/addqishou")
  295. public AjaxResult addqishou(@RequestBody UserDTO userDTO) {
  296. String ncode = redisCache.getCacheObject(userDTO.getTelPhone().trim().replaceAll("\\+", ""));
  297. if (infoUserService.getinfouserName(userDTO.getUserName()) != null) {
  298. throw new ServiceException(MessageUtils.message("no.user.add") + userDTO.getUserName() + MessageUtils.message("no.user.login.exist"));
  299. }
  300. if ("".equals(userDTO.getPassword()) || userDTO.getPassword() == null) {
  301. throw new ServiceException(MessageUtils.message("no.user.password.not.null"));
  302. }
  303. if (ncode == null) {
  304. if (userDTO.getCode().equals("8888")) {
  305. return createQsUser(userDTO);
  306. } else {
  307. //验证码不正确
  308. throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error"));
  309. }
  310. } else {
  311. if (ncode.equals(userDTO.getCode()) || userDTO.getCode().equals("8888")) {
  312. return createQsUser(userDTO);
  313. } else {
  314. throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error"));
  315. }
  316. }
  317. }
  318. /**
  319. * 注册创建骑手用户
  320. *
  321. * @param userDTO
  322. * @return
  323. */
  324. public AjaxResult createQsUser(UserDTO userDTO) {
  325. businessPhoneService.ensureUnique(userDTO.getTelPhone(), null);
  326. InfoUser user = new InfoUser();
  327. UUIDUtil uuid = new UUIDUtil();
  328. user.setUserName(userDTO.getUserName());
  329. user.setPassword(userDTO.getPassword());
  330. user.setTelPhone(userDTO.getTelPhone());
  331. user.setUserType("2");
  332. user.setAuditStatus("0");
  333. user.setMycode(uuid.get8UUID());
  334. user.setStatus("0");
  335. user.setCid(userDTO.getCid());
  336. user.setCidType(userDTO.getCidType());
  337. user.setDeviceToken(userDTO.getDeviceToken());
  338. user.setVoIPToken(userDTO.getVoIPToken());
  339. int result = infoUserService.insertInfoUser(user);
  340. InfoUser infoUser = infoUserService.getinfouserName(userDTO.getUserName());
  341. createUserWallet(infoUser.getUserId());
  342. return toAjax(result);
  343. }
  344. /**
  345. * 商家注册
  346. */
  347. @Anonymous
  348. @PostMapping("/addshanghu")
  349. public AjaxResult addshanghu(@RequestBody UserDTO userDTO) {
  350. String ncode = redisCache.getCacheObject(userDTO.getTelPhone().trim().replaceAll("\\+", ""));
  351. if (infoUserService.getinfouserName(userDTO.getUserName()) != null) {
  352. throw new ServiceException(MessageUtils.message("no.user.add") + userDTO.getUserName() + MessageUtils.message("no.user.login.exist"));
  353. }
  354. if ("".equals(userDTO.getPassword()) || userDTO.getPassword() == null) {
  355. throw new ServiceException(MessageUtils.message("no.user.password.not.null"));
  356. }
  357. if (ncode == null) {
  358. if (userDTO.getCode().equals("8888")) {
  359. return createShUser(userDTO);
  360. } else {
  361. throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error"));
  362. }
  363. } else {
  364. if (ncode.equals(userDTO.getCode()) || userDTO.getCode().equals("8888")) {
  365. return createShUser(userDTO);
  366. } else {
  367. throw new ServiceException(MessageUtils.message("no.user.jcaptcha.error"));
  368. }
  369. }
  370. }
  371. /**
  372. * 注册创建商户用户
  373. *
  374. * @param userDTO
  375. * @return
  376. */
  377. public AjaxResult createShUser(UserDTO userDTO) {
  378. businessPhoneService.ensureUnique(userDTO.getTelPhone(), null);
  379. InfoUser user = new InfoUser();
  380. UUIDUtil uuid = new UUIDUtil();
  381. user.setUserName(userDTO.getUserName());
  382. user.setPassword(userDTO.getPassword());
  383. user.setTelPhone(userDTO.getTelPhone());
  384. user.setUserType("1");
  385. user.setAuditStatus("0");
  386. user.setMycode(uuid.get8UUID());
  387. user.setStatus("0");
  388. user.setCid(userDTO.getCid());
  389. user.setCidType(userDTO.getCidType());
  390. user.setDeviceToken(userDTO.getDeviceToken());
  391. user.setVoIPToken(userDTO.getVoIPToken());
  392. int result = infoUserService.insertInfoUser(user);
  393. InfoUser infoUser = infoUserService.getinfouserName(userDTO.getUserName());
  394. createUserWallet(infoUser.getUserId());
  395. return toAjax(result);
  396. }
  397. /**
  398. * 商户登录
  399. *
  400. * @param userDTO
  401. * @return
  402. */
  403. @Anonymous
  404. @PostMapping("/shanglodeing")
  405. public AjaxResult shanglodeing(@RequestBody UserDTO userDTO) {
  406. JwtUtil tokn = new JwtUtil();
  407. RsaMima rsa = new RsaMima();
  408. try {
  409. QueryWrapper<InfoUser> queryWrapper = new QueryWrapper<>();
  410. queryWrapper.eq("user_name", userDTO.getUserName());
  411. queryWrapper.eq("del_flag", "0");
  412. queryWrapper.in("user_type", "1", "3", "4", "5");
  413. InfoUser user = infoUserService.getOne(queryWrapper);
  414. if (user == null) {
  415. throw new ServiceException(MessageUtils.message("no.user.not.exist"));
  416. }
  417. String wmima = rsa.decryptByPrivateKey(userDTO.getPassword());
  418. String nmima = rsa.decryptByPrivateKey(user.getPassword());
  419. if (wmima.equals(nmima)) {
  420. if (!MerchantAccountConstants.STATUS_ENABLED.equals(user.getStatus())) {
  421. throw new ServiceException(MessageUtils.message("merchant.account.unavailable"));
  422. }
  423. if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(user.getUserType())) {
  424. merchantStoreAccessService.resolve(user.getUserId());
  425. }
  426. InfoUser info = new InfoUser();
  427. info.setUserId(user.getUserId());
  428. info.setLastLoginAt(new Date());
  429. if (userDTO.getCid() != null && !userDTO.getCid().isBlank()) {
  430. info.setCid(userDTO.getCid());
  431. info.setCidType(userDTO.getCidType());
  432. info.setDeviceToken(userDTO.getDeviceToken());
  433. info.setVoIPToken(userDTO.getVoIPToken());
  434. user.setCid(userDTO.getCid());
  435. user.setCidType(userDTO.getCidType());
  436. user.setDeviceToken(userDTO.getDeviceToken());
  437. user.setVoIPToken(userDTO.getVoIPToken());
  438. }
  439. infoUserService.updateInfoUser(info);
  440. user.setLastLoginAt(info.getLastLoginAt());
  441. // 根据客户端类型(APP端或PC端)选择对应的 token key
  442. String tokenKey = getTokenKeyByClientType();
  443. //app端
  444. if (tokenKey.equals(CacheConstants.SH_APP_TOKEN_KEY)) {
  445. // 删除该用户同类型客户端的旧 token
  446. redisCache.deleteKeys(tokenKey + user.getUserId() + ":" + "*");
  447. }
  448. // 补充登录用户信息
  449. LoginUserDto userDto = new LoginUserDto();
  450. userDto.setUserId(user.getUserId());
  451. userDto.setUserName(user.getUserName());
  452. userDto.setProvider("phone");
  453. fillLoginUserInfo(userDto);
  454. String token = JwtUtil.setToken(tokenKey, userDto);
  455. return success(MessageUtils.message("no.user.login.success"), merchantLoginView(user), token);
  456. } else {
  457. return error(MessageUtils.message("no.user.password.error"));
  458. }
  459. } catch (ServiceException e) {
  460. throw e;
  461. } catch (Exception e) {
  462. e.printStackTrace();
  463. return error(MessageUtils.message("no.system.error"));
  464. }
  465. }
  466. /**
  467. * 骑手登录
  468. *
  469. * @param userDTO
  470. * @return
  471. */
  472. @Anonymous
  473. @PostMapping("/syslodeing")
  474. public AjaxResult syslodeing(@RequestBody UserDTO userDTO) {
  475. JwtUtil tokn = new JwtUtil();
  476. RsaMima rsa = new RsaMima();
  477. try {
  478. QueryWrapper<InfoUser> queryWrapper = new QueryWrapper<>();
  479. queryWrapper.eq("user_name", userDTO.getUserName());
  480. queryWrapper.eq("del_flag", "0");
  481. queryWrapper.eq("user_type", "2");
  482. InfoUser user = infoUserService.getOne(queryWrapper);
  483. if (user == null) {
  484. return error(MessageUtils.message("no.user.not.exist"));
  485. }
  486. String wmima = rsa.decryptByPrivateKey(userDTO.getPassword());
  487. String nmima = rsa.decryptByPrivateKey(user.getPassword());
  488. if (wmima.equals(nmima)) {
  489. InfoUser info = new InfoUser();
  490. info.setUserId(user.getUserId());
  491. info.setCid(userDTO.getCid());
  492. info.setCidType(userDTO.getCidType());
  493. info.setDeviceToken(userDTO.getDeviceToken());
  494. info.setVoIPToken(userDTO.getVoIPToken());
  495. info.setOffline("1");
  496. infoUserService.saveOrUpdate(info);
  497. if (!"".equals(userDTO.getCid())) {
  498. user.setCid(userDTO.getCid());
  499. }
  500. ;
  501. //返回离线标识状态为在线
  502. user.setOffline("1");
  503. redisCache.deleteKeys(CacheConstants.QS_TOKEN_KEY + user.getUserId() + ":" + "*");
  504. // 补充登录用户信息
  505. LoginUserDto userDto = new LoginUserDto();
  506. userDto.setUserId(user.getUserId());
  507. userDto.setUserName(user.getUserName());
  508. userDto.setProvider("phone");
  509. fillLoginUserInfo(userDto);
  510. String token = JwtUtil.setToken(CacheConstants.QS_TOKEN_KEY, userDto);
  511. return success(MessageUtils.message("no.user.login.success"), user, token);
  512. } else {
  513. return error(MessageUtils.message("no.user.password.error"));
  514. }
  515. } catch (ServiceException e) {
  516. throw e;
  517. } catch (Exception e) {
  518. e.printStackTrace();
  519. return error(MessageUtils.message("no.system.error"));
  520. }
  521. }
  522. /**
  523. * 填充登录用户信息(IP地址、登录地点、浏览器、操作系统、登录时间等)
  524. *
  525. * @param userDto 登录用户DTO
  526. * @return 填充后的登录用户DTO
  527. */
  528. private LoginUserDto fillLoginUserInfo(LoginUserDto userDto) {
  529. // 获取IP地址
  530. String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
  531. userDto.setIpaddr(ip);
  532. // 获取登录地点
  533. userDto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
  534. // 获取浏览器和操作系统信息
  535. UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
  536. userDto.setBrowser(userAgent.getBrowser().getName());
  537. userDto.setOs(userAgent.getOperatingSystem().getName());
  538. // 设置登录时间
  539. userDto.setLoginTime(System.currentTimeMillis());
  540. return userDto;
  541. }
  542. /**
  543. * 判断客户端类型(APP端还是PC端)
  544. * 通过 User-Agent 判断:包含 Mobile、Android、iPhone、iPad 等关键词则为 APP 端
  545. *
  546. * @return true 表示 APP 端,false 表示 PC 端
  547. */
  548. private boolean isAppClient() {
  549. String userAgent = ServletUtils.getRequest().getHeader("User-Agent");
  550. if (userAgent == null || userAgent.isEmpty()) {
  551. // 如果没有 User-Agent,默认认为是 PC 端
  552. return false;
  553. }
  554. String ua = userAgent.toLowerCase();
  555. // 判断是否为移动设备(APP端)
  556. return ua.contains("mobile") || ua.contains("android") || ua.contains("iphone")
  557. || ua.contains("ipad") || ua.contains("ipod") || ua.contains("windows phone");
  558. }
  559. /**
  560. * 根据客户端类型获取对应的 token key
  561. *
  562. * @return token key
  563. */
  564. private String getTokenKeyByClientType() {
  565. return isAppClient() ? CacheConstants.SH_APP_TOKEN_KEY : CacheConstants.SH_PC_TOKEN_KEY;
  566. }
  567. private MerchantLoginView merchantLoginView(InfoUser user) {
  568. MerchantLoginView view = new MerchantLoginView();
  569. view.setUserId(user.getUserId());
  570. view.setUserName(user.getUserName());
  571. view.setNickName(user.getNickName());
  572. view.setUserType(user.getUserType());
  573. view.setStoreId(user.getStoreId());
  574. view.setMerchantOwnerId(user.getMerchantOwnerId());
  575. view.setStatus(user.getStatus());
  576. view.setSubaccountStatus(user.getSubaccountStatus());
  577. view.setLastLoginAt(user.getLastLoginAt());
  578. return view;
  579. }
  580. /**
  581. * 商家端退出登录,仅撤销当前 JWT 对应的会话。
  582. */
  583. @Anonymous
  584. @Auth
  585. @PostMapping("/merchantLogout")
  586. public AjaxResult merchantLogout(@RequestHeader String token) {
  587. merchantTokenSessionService.logoutCurrent(AuthContext.requireJti());
  588. return success();
  589. }
  590. private void createUserWallet(Long userId) {
  591. try {
  592. userWalletService.createUserWallet(userId);
  593. } catch (Exception e) {
  594. logger.error("用户钱包创建失败,userId:{}", userId, e);
  595. throw new ServiceException(MessageUtils.message("no.user.login.exist"));
  596. }
  597. }
  598. /**
  599. * 退出登录
  600. *
  601. * @param token
  602. * @return
  603. */
  604. @Anonymous
  605. @Auth
  606. @GetMapping("/logout")
  607. public AjaxResult logout(@RequestHeader String token) {
  608. JwtUtil jwtUtil = new JwtUtil();
  609. String id = jwtUtil.getusid(token);
  610. InfoUser user = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(id)));
  611. if (user == null) {
  612. throw new ServiceException("未查询到用户信息");
  613. }
  614. LambdaUpdateWrapper<InfoUser> updateWrapper = new LambdaUpdateWrapper<>();
  615. updateWrapper.eq(InfoUser::getUserId, user.getUserId())
  616. .set(InfoUser::getOffline, "1")
  617. .set(InfoUser::getCid, null)
  618. .set(InfoUser::getCidType, null);
  619. infoUserService.update(updateWrapper);
  620. //骑手删除定位信息
  621. if ("2".equals(user.getUserType())) {
  622. riderPositionService.remove(new LambdaQueryWrapper<RiderPosition>().eq(RiderPosition::getRiderId, user.getUserId()));
  623. }
  624. return success();
  625. }
  626. /**
  627. * 骑手切换离线状态(0:在线,1:离线)
  628. *
  629. * @param token
  630. * @return
  631. */
  632. @Anonymous
  633. @Auth
  634. @GetMapping("/qsSwitchOffline")
  635. public AjaxResult qsSwitchOffline(@RequestHeader String token, @RequestParam String offline) {
  636. JwtUtil jwtUtil = new JwtUtil();
  637. String id = jwtUtil.getusid(token);
  638. InfoUser user = infoUserService.getById(id);
  639. InfoUser qs = new InfoUser();
  640. qs.setUserId(user.getUserId());
  641. qs.setOffline(offline);
  642. infoUserService.saveOrUpdate(qs);
  643. return success();
  644. }
  645. /**
  646. * 获取手机短信
  647. *
  648. * @param phone
  649. * @return
  650. */
  651. @Anonymous
  652. @GetMapping("/getcode")
  653. public AjaxResult getcode(@RequestParam String phone) {
  654. // GET 查询参数中 "+" 会被解码成空格,此处还原:前导空格+数字 → +数字
  655. if (phone != null && phone.startsWith(" ") && phone.trim().matches("\\d+")) {
  656. phone = "+" + phone.trim();
  657. }
  658. MobileSMS sms = new MobileSMS();
  659. // System.out.println(phone.trim().replaceAll("\\+", ""));
  660. String toPhone = phone;
  661. String authcode = "1" + RandomStringUtils.randomNumeric(5);//生成随机数,我发现生成5位随机数时,如果开头为0,发送的短信只有4位,这里开头加个1,保证短信的正确性
  662. System.out.println("验证码:" + authcode);
  663. redisCache.setCacheObject(phone.trim().replaceAll("\\+", ""), authcode, 5, TimeUnit.MINUTES);//将验证码存入缓存
  664. // sms.getcode(phone, authcode);//发送短息
  665. sms.getTwCode(toPhone,authcode);
  666. return success(MessageUtils.message("no.sms.send.success"));
  667. }
  668. /**
  669. * 绑定手机号
  670. *
  671. * @param token
  672. * @param userDTO
  673. * @return
  674. */
  675. @Anonymous
  676. @Auth
  677. @PostMapping("/Bindingphone")
  678. public AjaxResult Bindingphone(@RequestHeader String token, @RequestBody UserDTO userDTO) {
  679. JwtUtil jwtUtil = new JwtUtil();
  680. String id = jwtUtil.getusid(token);
  681. String xcode = redisCache.getCacheObject(userDTO.getTelPhone().trim().replaceAll("\\+", ""));
  682. if (userDTO.getCode().equals(xcode)) {
  683. InfoUser user = infoUserService.getById(id);
  684. if (user == null) {
  685. return error(MessageUtils.message("no.user.not.exist"));
  686. } else {
  687. if (businessPhoneService.isBusinessUserType(user.getUserType())) {
  688. businessPhoneService.ensureUnique(userDTO.getTelPhone(), user.getUserId());
  689. }
  690. InfoUser infoUser = new InfoUser();
  691. infoUser.setUserId(user.getUserId());
  692. infoUser.setTelPhone(userDTO.getTelPhone());
  693. Boolean org = infoUserService.saveOrUpdate(infoUser);
  694. if (org) {
  695. return success();
  696. } else {
  697. return error();
  698. }
  699. }
  700. } else {
  701. return error(MessageUtils.message("no.user.jcaptcha.error"));
  702. }
  703. }
  704. /**
  705. * 用户修改密码
  706. *
  707. * @param token
  708. * @param userDTO
  709. * @return
  710. */
  711. @Anonymous
  712. @Auth
  713. @PostMapping("/setpassword")
  714. public AjaxResult setpassword(@RequestHeader String token, @RequestBody UserDTO userDTO) {
  715. JwtUtil jwtUtil = new JwtUtil();
  716. RsaMima rsa = new RsaMima();
  717. try {
  718. String id = jwtUtil.getusid(token);
  719. String wmima = rsa.decryptByPrivateKey(userDTO.getPassword());
  720. QueryWrapper<InfoUser> queryWrapper = new QueryWrapper<>();
  721. queryWrapper.eq("user_id", Long.valueOf(id));
  722. InfoUser user = infoUserService.getOne(queryWrapper);
  723. String nmima = rsa.decryptByPrivateKey(user.getPassword());
  724. if (wmima.equals(nmima)) {
  725. InfoUser infoUser = new InfoUser();
  726. infoUser.setUserId(user.getUserId());
  727. infoUser.setPassword(userDTO.getNewpassword());
  728. Boolean org = infoUserService.saveOrUpdate(infoUser);
  729. if (org) {
  730. return success(MessageUtils.message("no.user.password.modify.success"));
  731. } else {
  732. return error();
  733. }
  734. } else {
  735. return success(MessageUtils.message("no.user.password.old.error"));
  736. }
  737. } catch (Exception e) {
  738. e.printStackTrace();
  739. return error(MessageUtils.message("no.system.error"));
  740. }
  741. }
  742. /**
  743. * 修改用户信息
  744. *
  745. * @param token
  746. * @param infoUser
  747. * @return
  748. */
  749. @Anonymous
  750. @Auth
  751. @PostMapping("/setuser")
  752. public AjaxResult setuser(@RequestHeader String token, @RequestBody InfoUser infoUser) {
  753. JwtUtil jwtUtil = new JwtUtil();
  754. String id = jwtUtil.getusid(token);
  755. InfoUser current = infoUserService.getById(id);
  756. if (current != null && businessPhoneService.isBusinessUserType(current.getUserType())) {
  757. businessPhoneService.ensureUnique(infoUser.getTelPhone(), current.getUserId());
  758. }
  759. normalizeDeliveryType(infoUser);
  760. InfoUser user = new InfoUser();
  761. user.setUserId(Long.valueOf(id));
  762. user.setCid(infoUser.getCid());
  763. user.setCidType(infoUser.getCidType());
  764. user.setDeviceToken(infoUser.getDeviceToken());
  765. user.setVoIPToken(infoUser.getVoIPToken());
  766. user.setNickName(infoUser.getNickName());
  767. user.setAvatar(infoUser.getAvatar());
  768. user.setEmail(infoUser.getEmail());
  769. user.setSex(infoUser.getSex());
  770. user.setThiscode(infoUser.getThiscode());
  771. user.setAnnex(infoUser.getAnnex());
  772. user.setTelPhone(infoUser.getTelPhone());
  773. user.setDriversLicense(infoUser.getDriversLicense());
  774. user.setDrivingLicense(infoUser.getDrivingLicense());
  775. user.setVehiclePhotos(infoUser.getVehiclePhotos());
  776. user.setLicensePlate(infoUser.getLicensePlate());
  777. user.setVehicleType(infoUser.getVehicleType());
  778. user.setDeliveryType(infoUser.getDeliveryType());
  779. user.setRiderHeadPhoto(infoUser.getRiderHeadPhoto());
  780. user.setPreLaunchCity(infoUser.getPreLaunchCity());
  781. user.setBirthday(infoUser.getBirthday());
  782. user.setInsurancePhoto(infoUser.getInsurancePhoto());
  783. user.setPoliceCertificate(infoUser.getPoliceCertificate());
  784. user.setFullName(infoUser.getFullName());
  785. user.setIdNumber(infoUser.getIdNumber());
  786. user.setMerchantType(infoUser.getMerchantType());
  787. user.setBankAccount(infoUser.getBankAccount());
  788. user.setPassportPhoto(infoUser.getPassportPhoto());
  789. user.setBankPhoto(infoUser.getBankPhoto());
  790. user.setBankAccountName(infoUser.getBankAccountName());
  791. user.setBankName(infoUser.getBankName());
  792. user.setBankAccountNo(infoUser.getBankAccountNo());
  793. user.setMerchantIntroduction(infoUser.getMerchantIntroduction());
  794. user.setBusinessLicense(infoUser.getBusinessLicense());
  795. user.setDirectorsScreenshot(infoUser.getDirectorsScreenshot());
  796. user.setFoodPermit(infoUser.getFoodPermit());
  797. user.setUboStatement(infoUser.getUboStatement());
  798. user.setAddress(infoUser.getAddress());
  799. user.setLongitude(infoUser.getLongitude());
  800. user.setLatitude(infoUser.getLatitude());
  801. Boolean org = infoUserService.saveOrUpdate(user);
  802. if (org && Boolean.TRUE.equals(infoUser.getUpdateUserInfo())) {
  803. LambdaUpdateWrapper<InfoUser> auditUpdateWrapper = new LambdaUpdateWrapper<>();
  804. auditUpdateWrapper.eq(InfoUser::getUserId, Long.valueOf(id))
  805. .eq(InfoUser::getAuditStatus, "2")
  806. .set(InfoUser::getAuditStatus, "0")
  807. .set(InfoUser::getAuditRejectReason, null);
  808. infoUserService.update(auditUpdateWrapper);
  809. }
  810. if (org) {
  811. return success();
  812. } else {
  813. return error();
  814. }
  815. }
  816. /**
  817. * 获取台湾银行列表(商家审核资料「银行名称」下拉的数据源,商家App共用)
  818. * 数据取自字典 taiwan_bank_list,运营可在平台后台字典管理中维护
  819. */
  820. @Anonymous
  821. @GetMapping("/bankList")
  822. public AjaxResult bankList() {
  823. SysDictData query = new SysDictData();
  824. query.setDictType("taiwan_bank_list");
  825. query.setStatus("0");
  826. return success(dictDataService.selectDictDataList(query));
  827. }
  828. /**
  829. * 获取用户信息详细信息
  830. */
  831. @Anonymous
  832. @Auth
  833. @GetMapping("/getuserinfo")
  834. public AjaxResult getuserinfo(@RequestHeader String token) {
  835. JwtUtil jwtUtil = new JwtUtil();
  836. String id = jwtUtil.getusid(token);
  837. QueryWrapper<InfoUser> wrapper = new QueryWrapper<>();
  838. wrapper.eq("user_id", id);
  839. wrapper.eq("del_flag", "0");
  840. wrapper.eq("status", 0);
  841. InfoUser user = infoUserService.getOne(wrapper);
  842. if (user == null) {
  843. return error(MessageUtils.message("no.user.not.exist"));
  844. } else {
  845. DateUtil date = new DateUtil();
  846. QueryWrapper<VipUser> queryWrapper = new QueryWrapper<>();
  847. queryWrapper.eq("user_id", user.getUserId());
  848. VipUser vipUser = vipUserService.getOne(queryWrapper);
  849. if (vipUser == null) {
  850. user.setVip(null);
  851. } else {
  852. boolean orn = date.isPastDate(vipUser.getYxtim());
  853. if (orn) {
  854. user.setVip(null);
  855. } else {
  856. user.setVip(vipUser.getYxtim());
  857. }
  858. }
  859. LambdaQueryWrapper<UserWallet> walletQuery = new LambdaQueryWrapper<>();
  860. UserWallet userWallet = userWalletService.selectByUserId(user.getUserId());
  861. if (userWallet == null) {
  862. user.setPointsWallet(0L);
  863. } else {
  864. user.setPointsWallet(Long.valueOf(userWallet.getPointsWallet().toString()));
  865. }
  866. // 017 2026-09-24 增量:按本次登录会话回填渠道与第三方名称。
  867. // 渠道来自会话 claim;名称精确按 userId+providerUid 反查绑定,无 providerUid 的旧会话只展示渠道,不任意挑绑定。
  868. Map<String, Object> sessionClaims = JwtUtil.verifyToken(token);
  869. Object providerClaim = sessionClaims.get("provider");
  870. String oauthProvider = providerClaim == null || "null".equals(providerClaim)
  871. ? null : String.valueOf(providerClaim);
  872. if (oauthProvider != null && !"phone".equals(oauthProvider)) {
  873. user.setOauthProvider(oauthProvider);
  874. Object uidClaim = sessionClaims.get("providerUid");
  875. if (uidClaim != null && !"null".equals(uidClaim) && !String.valueOf(uidClaim).isEmpty()) {
  876. InfoUserOauth identity = infoUserOauthMapper.selectOne(
  877. new LambdaQueryWrapper<InfoUserOauth>()
  878. .eq(InfoUserOauth::getUserId, Long.valueOf(id))
  879. .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(oauthProvider))
  880. .eq(InfoUserOauth::getProviderUid, String.valueOf(uidClaim)));
  881. if (identity != null) {
  882. user.setProviderDisplayName(identity.getProviderDisplayName());
  883. }
  884. }
  885. }
  886. return success(user);
  887. }
  888. }
  889. /**
  890. * 校验商家是否已补充资料信息(商家App用):data=true 已补充,false 未补充(App引导去资料补充页)
  891. * 判定口径:商家名称(nickName)已有值
  892. */
  893. @Anonymous
  894. @Auth
  895. @GetMapping("/checkMerchantProfile")
  896. public AjaxResult checkMerchantProfile(@RequestHeader String token) {
  897. JwtUtil jwtUtil = new JwtUtil();
  898. String id = jwtUtil.getusid(token);
  899. QueryWrapper<InfoUser> wrapper = new QueryWrapper<>();
  900. wrapper.eq("user_id", id);
  901. wrapper.eq("del_flag", "0");
  902. wrapper.eq("status", 0);
  903. InfoUser user = infoUserService.getOne(wrapper);
  904. if (user == null) {
  905. return error(MessageUtils.message("no.user.not.exist"));
  906. }
  907. // 商家名称(nickName)为空即视为未补充资料
  908. boolean completed = user.getNickName() != null && !user.getNickName().trim().isEmpty();
  909. return success(completed);
  910. }
  911. /**
  912. * 获取新的token
  913. *
  914. * @param token
  915. * @return
  916. */
  917. @Anonymous
  918. @Auth
  919. @GetMapping("/gettoken")
  920. @Transactional(rollbackFor = Exception.class)
  921. public AjaxResult gettoken(@RequestHeader String token) {
  922. JwtUtil jwtUtil = new JwtUtil();
  923. String id = jwtUtil.getusid(token);
  924. QueryWrapper<InfoUser> wrapper = new QueryWrapper<>();
  925. wrapper.eq("user_id", id);
  926. wrapper.eq("status", 0);
  927. wrapper.eq("del_flag", "0");
  928. // 刷新会话也与注销共用账号锁,避免注销撤销后又签发新会话。
  929. wrapper.last("FOR UPDATE");
  930. InfoUser user = infoUserService.getOne(wrapper);
  931. if (user == null) {
  932. return error(MessageUtils.message("no.user.stop"));
  933. } else {
  934. // 必须走写 Redis 会话的重载:无会话 token 会被 @Auth(session=true) 切面拒掉(修复普通用户 getidlist 报商家会话无效)
  935. LoginUserDto userDto = new LoginUserDto();
  936. userDto.setUserId(user.getUserId());
  937. userDto.setUserName(user.getUserName());
  938. Map<String, Object> renewClaims = JwtUtil.verifyToken(token);
  939. Object provider = renewClaims.get("provider");
  940. userDto.setProvider(provider == null || "null".equals(provider) ? "phone" : String.valueOf(provider));
  941. Object providerUid = renewClaims.get("providerUid");
  942. userDto.setProviderUid(providerUid == null || "null".equals(providerUid)
  943. ? null : String.valueOf(providerUid));
  944. fillLoginUserInfo(userDto);
  945. String wtoken = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto);
  946. return success(MessageUtils.message("no.user.token.success"), wtoken);
  947. }
  948. }
  949. /**
  950. * 手机号登录/注册查会员账号。不能用 getOne:info_user 被会员/商家/骑手共用,
  951. * 同手机号可能命中多行(getOne 多行会抛底层异常导致登录接口 500),
  952. * 且必须限定会员类型,防止会员端登上 phone 字段同号的商家/骑手账号。
  953. */
  954. private InfoUser findPhoneLoginUser(String phone) {
  955. List<InfoUser> matches = infoUserService.list(new LambdaQueryWrapper<InfoUser>()
  956. .eq(InfoUser::getPhone, phone)
  957. .and(w -> w.eq(InfoUser::getDelFlag, "0").or(pending -> pending
  958. .eq(InfoUser::getDelFlag, "1").gt(InfoUser::getCancellationDeadlineAt, new Date())))
  959. .eq(InfoUser::getUserType, "0").last("FOR UPDATE"));
  960. if (matches.size() > 1) {
  961. log.warn("[Login] 手机号关联多个账号 phone={}", maskPhone(phone));
  962. throw new ServiceException(MessageUtils.message("no.user.phone.duplicate"));
  963. }
  964. return matches.isEmpty() ? null : matches.get(0);
  965. }
  966. /**
  967. * 用户登录或注册
  968. *
  969. * @param userDTO
  970. * @return
  971. */
  972. @Anonymous
  973. @PostMapping("/lodeing")
  974. @Transactional(rollbackFor = Exception.class)
  975. public AjaxResult lodeing(@RequestBody UserDTO userDTO) {
  976. JwtUtil tokn = new JwtUtil();
  977. String xcode = redisCache.getCacheObject(userDTO.getPhone().trim().replaceAll("\\+", ""));
  978. if (xcode == null) {
  979. if (userDTO.getCode().equals("8888")) {
  980. InfoUser infoUser = findPhoneLoginUser(userDTO.getPhone());
  981. if (infoUser == null) {
  982. return createUser(userDTO);
  983. } else {
  984. return userLoginUpdateInfo(userDTO, infoUser);
  985. }
  986. } else {
  987. return error(MessageUtils.message("no.user.jcaptcha.error"));
  988. }
  989. } else {
  990. if (xcode.equals(userDTO.getCode()) || userDTO.getCode().equals("8888")) {
  991. InfoUser infoUser = findPhoneLoginUser(userDTO.getPhone());
  992. if (infoUser == null) {
  993. return createUser(userDTO);
  994. } else {
  995. return userLoginUpdateInfo(userDTO, infoUser);
  996. }
  997. } else {
  998. return error(MessageUtils.message("no.user.jcaptcha.error"));
  999. }
  1000. }
  1001. }
  1002. /**
  1003. * 用户注册创建用户
  1004. *
  1005. * @param userDTO
  1006. * @return
  1007. */
  1008. public AjaxResult createUser(UserDTO userDTO) {
  1009. UUIDUtil uuid = new UUIDUtil();
  1010. InfoUser info = new InfoUser();
  1011. info.setPhone(userDTO.getPhone());
  1012. info.setNickName(userDTO.getPhone());
  1013. info.setCid(userDTO.getCid());
  1014. info.setCidType(userDTO.getCidType());
  1015. info.setDeviceToken(userDTO.getDeviceToken());
  1016. info.setVoIPToken(userDTO.getVoIPToken());
  1017. info.setUserType("0");
  1018. info.setMycode(uuid.get8UUID());
  1019. infoUserService.saveOrUpdate(info);
  1020. InfoUser inus = findPhoneLoginUser(userDTO.getPhone());
  1021. createUserWallet(inus.getUserId());
  1022. deviceTrustService.recordLogin(userDTO.getDeviceId(), userDTO.getPhone(), inus.getUserId());
  1023. redisCache.deleteKeys(CacheConstants.USER_TOKEN_KEY + inus.getUserId() + ":" + "*");
  1024. // 补充登录用户信息
  1025. LoginUserDto userDto = new LoginUserDto();
  1026. userDto.setUserId(inus.getUserId());
  1027. userDto.setUserName(inus.getPhone());
  1028. userDto.setProvider("phone");
  1029. fillLoginUserInfo(userDto);
  1030. String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto);
  1031. if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error"));
  1032. return success(MessageUtils.message("no.user.login.success"), inus, token);
  1033. }
  1034. /**
  1035. * 用户登录更新用户信息
  1036. * @param userDTO
  1037. * @param infoUser
  1038. * @return
  1039. */
  1040. public AjaxResult userLoginUpdateInfo(UserDTO userDTO, InfoUser infoUser) {
  1041. AjaxResult confirmation = cancellationPrompt(infoUser, new CancellationLoginContext(Flow.PHONE,
  1042. infoUser.getUserId(), infoUser.getPhone(), "phone", null, null,
  1043. new Device(userDTO.getDeviceId(), userDTO.getCid(), userDTO.getCidType(),
  1044. userDTO.getDeviceToken(), userDTO.getVoIPToken(), true)));
  1045. if (confirmation != null) return confirmation;
  1046. DateUtil date = new DateUtil();
  1047. QueryWrapper<InfoUser> query = new QueryWrapper<>();
  1048. query.eq("user_id", infoUser.getUserId());
  1049. query.eq("status", 0);
  1050. query.eq("del_flag", "0");
  1051. InfoUser user = infoUserService.getOne(query);
  1052. if (user == null) {
  1053. throw new ServiceException(MessageUtils.message("no.user.not.exist"));
  1054. }
  1055. user.setCid(userDTO.getCid());
  1056. user.setCidType(userDTO.getCidType());
  1057. user.setDeviceToken(userDTO.getDeviceToken());
  1058. user.setVoIPToken(userDTO.getVoIPToken());
  1059. infoUserService.saveOrUpdate(user);
  1060. deviceTrustService.recordLogin(userDTO.getDeviceId(), user.getPhone(), user.getUserId());
  1061. QueryWrapper<VipUser> queryWrapper = new QueryWrapper<>();
  1062. queryWrapper.eq("user_id", infoUser.getUserId());
  1063. VipUser vipUser = vipUserService.getOne(queryWrapper);
  1064. if (vipUser == null) {
  1065. user.setVip(null);
  1066. } else {
  1067. boolean orn = date.isPastDate(vipUser.getYxtim());
  1068. if (orn) {
  1069. user.setVip(null);
  1070. } else {
  1071. user.setVip(vipUser.getYxtim());
  1072. }
  1073. }
  1074. redisCache.deleteKeys(CacheConstants.USER_TOKEN_KEY + user.getUserId() + ":" + "*");
  1075. // 补充登录用户信息
  1076. LoginUserDto userDto = new LoginUserDto();
  1077. userDto.setUserId(user.getUserId());
  1078. userDto.setUserName(user.getPhone());
  1079. userDto.setProvider("phone");
  1080. fillLoginUserInfo(userDto);
  1081. String token = JwtUtil.setToken(CacheConstants.USER_TOKEN_KEY, userDto);
  1082. if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error"));
  1083. return success(MessageUtils.message("no.user.login.success"), user, token);
  1084. }
  1085. // ==================== 三方登录(017-oauth-login)====================
  1086. /**
  1087. * 三方登录第一步:校验 provider 凭证 → 已绑定直接登录;未绑定返回 needPhone + tempKey。
  1088. */
  1089. @Anonymous
  1090. @PostMapping("/oauthLogin")
  1091. @Transactional(rollbackFor = Exception.class)
  1092. public AjaxResult oauthLogin(@RequestBody OAuthLoginDto dto) {
  1093. if (dto == null || dto.getProvider() == null || dto.getProvider().isEmpty()) {
  1094. return error(MessageUtils.message("no.oauth.provider.blank"));
  1095. }
  1096. OAuthAccountType accountType = OAuthAccountType.forProvider(dto.getProvider());
  1097. if (LineOAuthStateService.isBusinessLine(dto.getProvider())) {
  1098. lineOAuthStateService.consume(dto.getProvider(), dto.getState());
  1099. }
  1100. log.info("[OAuth] oauthLogin provider={}", dto.getProvider());
  1101. OAuthIdentity identity = oauthVerifyService.verifyIdentity(dto.getProvider(), dto.getCredential());
  1102. String providerUid = identity.providerUid();
  1103. // 第三方显示名称:LINE 取 profile displayName;Apple 由 App 首次授权提交 fullName(017 2026-09-24 增量)
  1104. String displayName = identity.displayName();
  1105. if ((displayName == null || displayName.isEmpty()) && dto.getProvider().startsWith("apple")
  1106. && dto.getFullName() != null && !dto.getFullName().isBlank()) {
  1107. displayName = dto.getFullName().trim();
  1108. }
  1109. // 查是否已绑定
  1110. InfoUserOauth bind = infoUserOauthMapper.selectOne(
  1111. new LambdaQueryWrapper<InfoUserOauth>()
  1112. .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(dto.getProvider()))
  1113. .eq(InfoUserOauth::getProviderUid, providerUid));
  1114. if (bind != null) {
  1115. // 已绑定:校验用户正常后直接登录
  1116. log.info("[OAuth] 已绑定 provider={}, userId={}", dto.getProvider(), bind.getUserId());
  1117. QueryWrapper<InfoUser> q = new QueryWrapper<>();
  1118. q.eq("user_id", bind.getUserId()).eq("status", 0);
  1119. if (accountType == null || accountType == OAuthAccountType.USER) {
  1120. q.eq("user_type", "0").and(w -> w.eq("del_flag", "0").or(pending -> pending
  1121. .eq("del_flag", "1").gt("cancellation_deadline_at", new Date())));
  1122. } else {
  1123. q.eq("del_flag", "0");
  1124. }
  1125. if (accountType == null || accountType == OAuthAccountType.USER) q.last("FOR UPDATE");
  1126. InfoUser u = infoUserService.getOne(q);
  1127. if (u != null && (accountType == null || accountType == OAuthAccountType.USER)) {
  1128. AjaxResult confirmation = cancellationPrompt(u, new CancellationLoginContext(Flow.OAUTH,
  1129. u.getUserId(), u.getPhone(), dto.getProvider(), providerUid, displayName,
  1130. new Device(dto.getDeviceId(), dto.getCid(), dto.getCidType(),
  1131. dto.getDeviceToken(), dto.getVoIPToken(), true)));
  1132. if (confirmation != null) return confirmation;
  1133. }
  1134. if (u == null) {
  1135. log.warn("[OAuth] 已绑定但账号已停用 userId={}", bind.getUserId());
  1136. return error(MessageUtils.message("no.user.stop"));
  1137. }
  1138. if (accountType != null && !canOauthLogin(accountType, u)) {
  1139. return error(MessageUtils.message("no.user.stop"));
  1140. }
  1141. u.setCid(dto.getCid());
  1142. u.setCidType(dto.getCidType());
  1143. u.setDeviceToken(dto.getDeviceToken());
  1144. u.setVoIPToken(dto.getVoIPToken());
  1145. infoUserService.saveOrUpdate(u);
  1146. if (accountType == null || accountType == OAuthAccountType.USER) {
  1147. deviceTrustService.recordOauthLogin(dto.getDeviceId(), u.getPhone(), u.getUserId(), bind);
  1148. }
  1149. refreshOauthDisplayName(bind, displayName);
  1150. return issueOauthToken(u, dto.getProvider(), providerUid);
  1151. }
  1152. // 未绑定:缓存 {provider, providerUid},返回 needPhone + tempKey
  1153. log.info("[OAuth] 未绑定,返回 needPhone provider={}", dto.getProvider());
  1154. // (029:同设备已验证过手机号 → 免短信一键确认 deviceConfirm 分支)
  1155. String tempKey = UUID.randomUUID().toString().replace("-", "");
  1156. // 未绑定时把第三方名称一并带进临时凭证,绑定时落库;名称为空保持两段式(兼容旧缓存值)
  1157. String tempValue = displayName == null || displayName.isEmpty()
  1158. ? dto.getProvider() + "@" + providerUid
  1159. : dto.getProvider() + "@" + providerUid + "@" + displayName;
  1160. redisCache.setCacheObject(OAUTH_TEMP_PREFIX + tempKey, tempValue, 5, TimeUnit.MINUTES);
  1161. // 029:设备信任免短信确认仅用户端(会员)适用;业务账号绑定必须真实短信,不走免确认
  1162. InfoUserDevice trustedDevice = accountType == null || accountType == OAuthAccountType.USER
  1163. ? deviceTrustService.getTrust(dto.getDeviceId()) : null;
  1164. String trustedPhone = trustedDevice == null ? null : trustedDevice.getPhone();
  1165. if (trustedPhone != null) {
  1166. redisCache.setCacheObject(OAUTH_TEMP_DEVICE_PREFIX + tempKey, dto.getDeviceId(), 5, TimeUnit.MINUTES);
  1167. redisCache.setCacheObject(OAUTH_TEMP_USER_PREFIX + tempKey,
  1168. String.valueOf(trustedDevice.getUserId()), 5, TimeUnit.MINUTES);
  1169. log.info("[OAuth] 设备已信任,返回 deviceConfirm provider={}, deviceId={}, phone={}",
  1170. dto.getProvider(), dto.getDeviceId(), DeviceTrustService.maskPhone(trustedPhone));
  1171. Map<String, Object> data = new HashMap<>();
  1172. data.put("status", "deviceConfirm");
  1173. data.put("tempKey", tempKey);
  1174. data.put("maskedPhone", DeviceTrustService.maskPhone(trustedPhone));
  1175. return AjaxResult.success(MessageUtils.message("no.oauth.device.confirm"), data);
  1176. }
  1177. log.info("[OAuth] 未绑定,返回 needPhone provider={}, providerUid={}", dto.getProvider(), providerUid);
  1178. Map<String, Object> data = new HashMap<>();
  1179. data.put("status", "needPhone");
  1180. data.put("tempKey", tempKey);
  1181. return AjaxResult.success(MessageUtils.message("no.oauth.needphone"), data);
  1182. }
  1183. /**
  1184. * 三方登录第二步:手机号 + 短信验证 → 已注册关联 / 未注册新建 → 写绑定 → 签 token。
  1185. */
  1186. @Anonymous
  1187. @Transactional(rollbackFor = Exception.class)
  1188. @PostMapping("/oauthBindPhone")
  1189. public AjaxResult oauthBindPhone(@RequestBody OAuthBindDto dto) {
  1190. if (dto == null || dto.getTempKey() == null || dto.getTempKey().isEmpty()) {
  1191. log.warn("[OAuth] oauthBindPhone 缺 tempKey");
  1192. return error(MessageUtils.message("no.oauth.tempkey.missing"));
  1193. }
  1194. String tempCacheKey = OAUTH_TEMP_PREFIX + dto.getTempKey();
  1195. String cached = redisCache.getCacheObject(tempCacheKey);
  1196. if (cached == null || !redisCache.deleteObject(tempCacheKey)) {
  1197. log.warn("[OAuth] tempKey 已过期/不存在");
  1198. return error(MessageUtils.message("no.oauth.tempkey.expired"));
  1199. }
  1200. String[] cachedParts = cached.split("@", 3);
  1201. if (cachedParts.length < 2 || cachedParts[0].isEmpty() || cachedParts[1].isEmpty()) {
  1202. return error(MessageUtils.message("no.oauth.tempkey.expired"));
  1203. }
  1204. String provider = cachedParts[0];
  1205. String providerUid = cachedParts[1];
  1206. String displayName = cachedParts.length > 2 ? cachedParts[2] : null;
  1207. OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
  1208. log.info("[OAuth] 绑定流程 provider={}", provider);
  1209. // 期间已有绑定也必须验证本次手机号/业务账号,不能提前签发旧账号 token。
  1210. boolean businessAccount = accountType != null && !accountType.allowCreate();
  1211. // 本轮仅改变用户端换号;业务端原有绑定容错及角色校验保持原规则。
  1212. if (businessAccount) {
  1213. InfoUserOauth existing = infoUserOauthMapper.selectOne(new LambdaQueryWrapper<InfoUserOauth>()
  1214. .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider))
  1215. .eq(InfoUserOauth::getProviderUid, providerUid));
  1216. if (existing != null) {
  1217. InfoUser account = infoUserService.getById(existing.getUserId());
  1218. if (!canOauthLogin(accountType, account)) return error(MessageUtils.message("no.user.stop"));
  1219. refreshOauthDisplayName(existing, displayName);
  1220. return issueOauthToken(account, provider, providerUid);
  1221. }
  1222. }
  1223. InfoUser user;
  1224. if (businessAccount) {
  1225. // 017 2026-10-08 增量:业务端(骑手/商家/子账号)改用账号密码绑定。
  1226. // 子账号等业务账号不强制登记 tel_phone,短信流程对其不可达;userName+密码是业务端唯一且全覆盖的登录因子。
  1227. if (dto.getUserName() == null || dto.getUserName().isBlank()) {
  1228. return error(MessageUtils.message("no.oauth.username.blank"));
  1229. }
  1230. if (dto.getPassword() == null || dto.getPassword().isBlank()) {
  1231. return error(MessageUtils.message("no.user.password.not.null"));
  1232. }
  1233. // 已删除(del_flag!=0)或渠道角色不符的账号一律按不存在拒绝,与 shanglodeing 一致;
  1234. // userName 与登录路径同样按原串精确匹配(不 trim);不用 getOne,多行时给友好提示而非 500
  1235. List<InfoUser> matches = infoUserService.list(new LambdaQueryWrapper<InfoUser>()
  1236. .eq(InfoUser::getUserName, dto.getUserName())
  1237. .eq(InfoUser::getDelFlag, "0")
  1238. .in(InfoUser::getUserType, accountType.userTypes()));
  1239. if (matches.size() > 1) {
  1240. log.warn("[OAuth] 业务账号名关联多个账号 userName={}", dto.getUserName());
  1241. return error(MessageUtils.message("no.oauth.username.duplicate"));
  1242. }
  1243. user = matches.isEmpty() ? null : matches.get(0);
  1244. if (user == null) {
  1245. log.warn("[OAuth] 业务绑定账号不存在 userName={}", dto.getUserName());
  1246. return error(MessageUtils.message("no.user.not.exist"));
  1247. }
  1248. if (!verifyBusinessBindPassword(dto.getPassword(), user.getPassword())) {
  1249. log.warn("[OAuth] 业务绑定密码错误 userName={}", dto.getUserName());
  1250. return error(MessageUtils.message("no.user.password.error"));
  1251. }
  1252. if (!canOauthLogin(accountType, user)) {
  1253. return error(MessageUtils.message("no.user.stop"));
  1254. }
  1255. user.setCid(dto.getCid());
  1256. user.setCidType(dto.getCidType());
  1257. user.setDeviceToken(dto.getDeviceToken());
  1258. user.setVoIPToken(dto.getVoIPToken());
  1259. if (!infoUserService.saveOrUpdate(user)) {
  1260. throw new ServiceException(MessageUtils.message("no.system.error"));
  1261. }
  1262. log.info("[OAuth] 业务账号密码绑定 userName={}", dto.getUserName());
  1263. } else {
  1264. // 用户端保留既有校验:手机号 + 短信验证码(含万能码 8888 兼容)。
  1265. String phone = dto.getPhone();
  1266. if (phone == null || phone.isBlank()) {
  1267. return error(MessageUtils.message("no.oauth.phone.blank"));
  1268. }
  1269. String xcode = redisCache.getCacheObject(phone.trim().replaceAll("\\+", ""));
  1270. boolean codeOk = (xcode != null && xcode.equals(dto.getCode()))
  1271. || "8888".equals(dto.getCode());
  1272. if (!codeOk) {
  1273. log.warn("[OAuth] 短信验证码错误 phone={}", maskPhone(phone));
  1274. return error(MessageUtils.message("no.user.jcaptcha.error"));
  1275. }
  1276. // apple/google 用户端(accountType 为空)同样按会员渠道查询:
  1277. // getuser 不带 userType 过滤,同手机号命中商家/骑手账号会错绑或多行异常。
  1278. OAuthAccountType bindingChannel = accountType != null ? accountType : OAuthAccountType.USER;
  1279. user = findOauthBindingUser(bindingChannel, phone);
  1280. if (user != null) {
  1281. AjaxResult confirmation = cancellationPrompt(user, new CancellationLoginContext(Flow.BIND,
  1282. user.getUserId(), user.getPhone(), provider, providerUid, displayName,
  1283. new Device(dto.getDeviceId(), dto.getCid(), dto.getCidType(),
  1284. dto.getDeviceToken(), dto.getVoIPToken(), true)));
  1285. if (confirmation != null) {
  1286. redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
  1287. redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
  1288. return confirmation;
  1289. }
  1290. }
  1291. if (user != null && !"0".equals(user.getStatus())) {
  1292. // 停用账号不允许绑定三方(与 lodeing 一致,防止绕过停用)
  1293. log.warn("[OAuth] 账号已停用 phone={}", maskPhone(phone));
  1294. return error(MessageUtils.message("no.user.stop"));
  1295. }
  1296. if (user == null) {
  1297. // 新建:昵称=手机号(与 createUser 一致)
  1298. InfoUser info = new InfoUser();
  1299. info.setPhone(phone);
  1300. info.setNickName(phone);
  1301. info.setCid(dto.getCid());
  1302. info.setCidType(dto.getCidType());
  1303. info.setDeviceToken(dto.getDeviceToken());
  1304. info.setVoIPToken(dto.getVoIPToken());
  1305. info.setUserType("0");
  1306. info.setMycode(new UUIDUtil().get8UUID());
  1307. if (!infoUserService.saveOrUpdate(info)) {
  1308. throw new ServiceException(MessageUtils.message("no.system.error"));
  1309. }
  1310. user = findOauthBindingUser(bindingChannel, phone);
  1311. if (user == null) {
  1312. return error(MessageUtils.message("no.user.not.exist"));
  1313. }
  1314. createUserWallet(user.getUserId());
  1315. log.info("[OAuth] 新建账号 userId={}", user.getUserId());
  1316. } else {
  1317. user.setCid(dto.getCid());
  1318. user.setCidType(dto.getCidType());
  1319. user.setDeviceToken(dto.getDeviceToken());
  1320. user.setVoIPToken(dto.getVoIPToken());
  1321. if (!infoUserService.saveOrUpdate(user)) {
  1322. throw new ServiceException(MessageUtils.message("no.system.error"));
  1323. }
  1324. log.info("[OAuth] 关联已有账号 userId={}", user.getUserId());
  1325. }
  1326. }
  1327. // 先解绑旧用户,再写本次新绑定,避免误删新手机号的绑定。
  1328. if (!businessAccount) deviceTrustService.recordLogin(dto.getDeviceId(), user.getPhone(), user.getUserId());
  1329. InfoUserOauth bind = bindVerifiedIdentity(user, provider, providerUid, displayName);
  1330. if (!businessAccount) deviceTrustService.recordOauthLogin(dto.getDeviceId(), user.getPhone(), user.getUserId(), bind);
  1331. redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
  1332. redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
  1333. return issueOauthToken(user, provider, providerUid);
  1334. }
  1335. /**
  1336. * 设备信任免绑一键确认(029-device-trust-phone-bind):oauthLogin 返回 deviceConfirm 后,
  1337. * App 弹只读脱敏手机号确认框,用户点确认调本接口免短信完成绑定登录。
  1338. */
  1339. @Anonymous
  1340. @PostMapping("/oauthDeviceConfirm")
  1341. @Transactional(rollbackFor = Exception.class)
  1342. public AjaxResult oauthDeviceConfirm(@RequestBody OAuthDeviceConfirmDto dto) {
  1343. if (dto == null || dto.getTempKey() == null || dto.getTempKey().isEmpty()) {
  1344. log.warn("[OAuth] oauthDeviceConfirm 缺 tempKey");
  1345. return error(MessageUtils.message("no.oauth.tempkey.missing"));
  1346. }
  1347. if (dto.getDeviceId() == null || dto.getDeviceId().isEmpty()) {
  1348. log.warn("[OAuth] oauthDeviceConfirm 缺 deviceId tempKey={}", dto.getTempKey());
  1349. return error(MessageUtils.message("no.oauth.device.mismatch"));
  1350. }
  1351. String cached = redisCache.getCacheObject(OAUTH_TEMP_PREFIX + dto.getTempKey());
  1352. if (cached == null) {
  1353. log.warn("[OAuth] 设备确认 tempKey 已过期/不存在 tempKey={}", dto.getTempKey());
  1354. return error(MessageUtils.message("no.oauth.tempkey.expired"));
  1355. }
  1356. // 凭证与设备必须匹配:dev 键由 oauthLogin 的 deviceConfirm 分支写入,换设备重放即拒绝
  1357. String boundDevice = redisCache.getCacheObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
  1358. InfoUserDevice trust = deviceTrustService.getTrust(dto.getDeviceId());
  1359. Object expectedUser = redisCache.getCacheObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
  1360. if (boundDevice == null || !boundDevice.equals(dto.getDeviceId()) || trust == null
  1361. || expectedUser == null || !String.valueOf(trust.getUserId()).equals(String.valueOf(expectedUser)))
  1362. return error(MessageUtils.message("no.oauth.device.mismatch"));
  1363. String[] cachedParts = cached.split("@", 3);
  1364. if (cachedParts.length < 2) return error(MessageUtils.message("no.oauth.tempkey.expired"));
  1365. String provider = cachedParts[0];
  1366. String providerUid = cachedParts[1];
  1367. String displayName = cachedParts.length > 2 ? cachedParts[2] : null;
  1368. OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
  1369. if (accountType != null && accountType != OAuthAccountType.USER)
  1370. return error(MessageUtils.message("no.oauth.device.mismatch"));
  1371. // 用户行锁→设备行锁→绑定行锁;旧确认凭证不能绑定到新手机号,也不复建已消失的信任账号。
  1372. InfoUser user = findOauthBindingUser(OAuthAccountType.USER, trust.getPhone());
  1373. if (user == null || !java.util.Objects.equals(user.getUserId(), trust.getUserId()))
  1374. return error(MessageUtils.message("no.oauth.device.mismatch"));
  1375. if (!"0".equals(user.getStatus())) return error(MessageUtils.message("no.user.stop"));
  1376. deviceTrustService.requireConfirmation(dto.getDeviceId(), user.getUserId());
  1377. AjaxResult confirmation = cancellationPrompt(user, new CancellationLoginContext(Flow.DEVICE,
  1378. user.getUserId(), user.getPhone(), provider, providerUid, displayName,
  1379. new Device(dto.getDeviceId(), null, null, null, null, false)));
  1380. if (confirmation != null) {
  1381. if (!redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey()))
  1382. throw new ServiceException(MessageUtils.message("no.oauth.tempkey.expired"));
  1383. redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
  1384. redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
  1385. return confirmation;
  1386. }
  1387. if (!canOauthLogin(OAuthAccountType.USER, user)) throw new ServiceException(MessageUtils.message("no.user.stop"));
  1388. if (!redisCache.deleteObject(OAUTH_TEMP_PREFIX + dto.getTempKey()))
  1389. throw new ServiceException(MessageUtils.message("no.oauth.tempkey.expired"));
  1390. InfoUserOauth bind = bindVerifiedIdentity(user, provider, providerUid, displayName);
  1391. deviceTrustService.recordOauthLogin(dto.getDeviceId(), user.getPhone(), user.getUserId(), bind);
  1392. redisCache.deleteObject(OAUTH_TEMP_DEVICE_PREFIX + dto.getTempKey());
  1393. redisCache.deleteObject(OAUTH_TEMP_USER_PREFIX + dto.getTempKey());
  1394. return issueOauthToken(user, provider, providerUid);
  1395. }
  1396. /** 身份与手机号已验证后才调用;并发已有绑定只能复用同一用户,不得返回其他账号。 */
  1397. private InfoUserOauth bindVerifiedIdentity(InfoUser user, String provider, String providerUid, String displayName) {
  1398. InfoUserOauth bind = infoUserOauthMapper.selectOne(new LambdaQueryWrapper<InfoUserOauth>()
  1399. .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(provider))
  1400. .eq(InfoUserOauth::getProviderUid, providerUid).last("FOR UPDATE"));
  1401. if (bind != null) {
  1402. if (!java.util.Objects.equals(bind.getUserId(), user.getUserId()))
  1403. throw new ServiceException(MessageUtils.message("no.oauth.binding.conflict"));
  1404. refreshOauthDisplayName(bind, displayName);
  1405. return bind;
  1406. }
  1407. bind = new InfoUserOauth();
  1408. bind.setUserId(user.getUserId());
  1409. bind.setProvider("line".equals(provider) ? "line_user" : provider);
  1410. bind.setProviderUid(providerUid);
  1411. bind.setProviderDisplayName(displayName != null && !displayName.isBlank() ? displayName : null);
  1412. bind.setCreateTime(new Date());
  1413. if (infoUserOauthMapper.insert(bind) != 1) throw new ServiceException(MessageUtils.message("no.system.error"));
  1414. return bind;
  1415. }
  1416. /** 手机号脱敏:保留前 3 后 4,中间 ****(仅日志用)。 */
  1417. private static String maskPhone(String phone) {
  1418. if (phone == null || phone.length() < 8) {
  1419. return phone;
  1420. }
  1421. return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
  1422. }
  1423. /** 017 2026-09-24 增量:登录成功刷新第三方显示名称;名称为空/未变化保留旧值,失败不阻断登录。 */
  1424. private void refreshOauthDisplayName(InfoUserOauth bind, String displayName) {
  1425. if (bind == null || displayName == null || displayName.isBlank()
  1426. || displayName.equals(bind.getProviderDisplayName())) {
  1427. return;
  1428. }
  1429. bind.setProviderDisplayName(displayName);
  1430. infoUserOauthMapper.updateById(bind);
  1431. }
  1432. /** 三方登录签发 token(claim provider=本次登录渠道、providerUid=本次绑定身份),复用现有 JwtUtil + Redis 会话。 */
  1433. private AjaxResult issueOauthToken(InfoUser user, String provider, String providerUid) {
  1434. if (user == null) {
  1435. return error(MessageUtils.message("no.user.not.exist"));
  1436. }
  1437. if (!"0".equals(user.getStatus()) || !"0".equals(user.getDelFlag())) {
  1438. return error(MessageUtils.message("no.user.stop"));
  1439. }
  1440. OAuthAccountType accountType = OAuthAccountType.forProvider(provider);
  1441. if (accountType != null && !canOauthLogin(accountType, user)) {
  1442. return error(MessageUtils.message("no.user.stop"));
  1443. }
  1444. String tokenKey = accountType == null ? CacheConstants.USER_TOKEN_KEY : accountType.tokenKey();
  1445. redisCache.deleteKeys(tokenKey + user.getUserId() + ":*");
  1446. LoginUserDto dto = new LoginUserDto();
  1447. dto.setUserId(user.getUserId());
  1448. dto.setUserName(accountType == null || accountType.allowCreate()
  1449. ? user.getPhone() : user.getUserName());
  1450. dto.setProvider(provider);
  1451. dto.setProviderUid(providerUid);
  1452. fillLoginUserInfo(dto);
  1453. String token = JwtUtil.setToken(tokenKey, dto);
  1454. if (token == null || token.isBlank()) throw new ServiceException(MessageUtils.message("no.system.error"));
  1455. Object loginUser = accountType == OAuthAccountType.MERCHANT ? merchantLoginView(user) : user;
  1456. return success(MessageUtils.message("no.user.login.success"), loginUser, token);
  1457. }
  1458. /** 身份已验证且账号已锁定;返回提示时不得继续写入登录资料或签发会话。 */
  1459. private AjaxResult cancellationPrompt(InfoUser user, CancellationLoginContext context) {
  1460. if (user == null || !"0".equals(user.getUserType()) || !"1".equals(user.getDelFlag())) return null;
  1461. return success(userCancellationService.prepareLogin(context));
  1462. }
  1463. @Anonymous
  1464. @PostMapping("/confirmCancellationLogin")
  1465. @Transactional(rollbackFor = Exception.class)
  1466. public AjaxResult confirmCancellationLogin(@RequestBody CancellationLoginConfirmDto request) {
  1467. var confirmed = userCancellationService.confirmLogin(request == null ? null : request.confirmationKey());
  1468. InfoUser user = confirmed.user();
  1469. CancellationLoginContext context = confirmed.context();
  1470. Device device = context.device();
  1471. if (context.flow() == Flow.PHONE) return userLoginUpdateInfo(device.phoneLogin(user.getPhone()), user);
  1472. if (context.flow() == Flow.DEVICE) {
  1473. deviceTrustService.requireConfirmation(device.deviceId(), user.getUserId());
  1474. } else if (context.flow() == Flow.BIND) {
  1475. deviceTrustService.recordLogin(device.deviceId(), user.getPhone(), user.getUserId());
  1476. }
  1477. InfoUserOauth bind;
  1478. if (context.flow() == Flow.OAUTH) {
  1479. // 已绑定登录不允许在等待确认期间解绑后自动重建关系。
  1480. bind = infoUserOauthMapper.selectOne(new LambdaQueryWrapper<InfoUserOauth>()
  1481. .in(InfoUserOauth::getProvider, OAuthAccountType.bindingProviders(context.provider()))
  1482. .eq(InfoUserOauth::getProviderUid, context.providerUid()));
  1483. if (bind == null || !java.util.Objects.equals(user.getUserId(), bind.getUserId()))
  1484. throw new ServiceException(MessageUtils.message("no.oauth.binding.conflict"));
  1485. deviceTrustService.recordOauthLogin(device.deviceId(), user.getPhone(), user.getUserId(), bind);
  1486. InfoUserOauth locked = bind.getId() == null ? null : infoUserOauthMapper.selectByIdForUpdate(bind.getId());
  1487. if (locked == null || !java.util.Objects.equals(user.getUserId(), locked.getUserId())
  1488. || !java.util.Objects.equals(bind.getProvider(), locked.getProvider())
  1489. || !java.util.Objects.equals(context.providerUid(), locked.getProviderUid()))
  1490. throw new ServiceException(MessageUtils.message("no.oauth.binding.conflict"));
  1491. refreshOauthDisplayName(locked, context.displayName());
  1492. } else {
  1493. bind = bindVerifiedIdentity(user, context.provider(), context.providerUid(), context.displayName());
  1494. deviceTrustService.recordOauthLogin(device.deviceId(), user.getPhone(), user.getUserId(), bind);
  1495. }
  1496. if (device.updatePush()) {
  1497. user.setCid(device.cid()); user.setCidType(device.cidType());
  1498. user.setDeviceToken(device.deviceToken()); user.setVoIPToken(device.voIPToken());
  1499. if (!infoUserService.saveOrUpdate(user)) throw new ServiceException(MessageUtils.message("no.system.error"));
  1500. }
  1501. return issueOauthToken(user, context.provider(), context.providerUid());
  1502. }
  1503. private boolean canOauthLogin(OAuthAccountType channel, InfoUser user) {
  1504. if (user == null || !"0".equals(user.getStatus()) || !"0".equals(user.getDelFlag())
  1505. || !channel.userTypes().contains(user.getUserType())) {
  1506. return false;
  1507. }
  1508. if (!MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(user.getUserType())) {
  1509. return true;
  1510. }
  1511. if (!MerchantAccountConstants.STATUS_ENABLED.equals(user.getSubaccountStatus())) {
  1512. return false;
  1513. }
  1514. try {
  1515. merchantStoreAccessService.resolve(user.getUserId());
  1516. return true;
  1517. } catch (ServiceException exception) {
  1518. return false;
  1519. }
  1520. }
  1521. /**
  1522. * 017 2026-10-08 增量:业务端绑定密码比对,复用 shanglodeing/syslodeing 的 RSA 解密比较。
  1523. * 解密异常或比对不一致一律按密码错误拒绝,不向客户端区分失败原因。
  1524. */
  1525. private boolean verifyBusinessBindPassword(String submitted, String stored) {
  1526. try {
  1527. RsaMima rsa = new RsaMima();
  1528. String wmima = rsa.decryptByPrivateKey(submitted);
  1529. String nmima = rsa.decryptByPrivateKey(stored);
  1530. return wmima != null && wmima.equals(nmima);
  1531. } catch (Exception e) {
  1532. log.warn("[OAuth] 业务绑定密码解密失败 type={}", e.getClass().getSimpleName());
  1533. return false;
  1534. }
  1535. }
  1536. /**
  1537. * 按渠道查待绑定账号。不能用 getOne:会员/商家/骑手共用 info_user 表,
  1538. * 同手机号可能命中多行(如一号注册多个商家账号),getOne 多行会抛底层异常导致接口 500;
  1539. * 命中多行时明确提示用户联系平台,由平台侧人工处理绑定归属。
  1540. */
  1541. private InfoUser findOauthBindingUser(OAuthAccountType channel, String phone) {
  1542. LambdaQueryWrapper<InfoUser> query = new LambdaQueryWrapper<InfoUser>()
  1543. .eq(channel.allowCreate(), InfoUser::getPhone, phone)
  1544. .eq(!channel.allowCreate(), InfoUser::getTelPhone, phone)
  1545. .in(InfoUser::getUserType, channel.userTypes());
  1546. if (channel == OAuthAccountType.USER) {
  1547. query.and(w -> w.eq(InfoUser::getDelFlag, "0").or(pending -> pending
  1548. .eq(InfoUser::getDelFlag, "1").gt(InfoUser::getCancellationDeadlineAt, new Date()))).last("FOR UPDATE");
  1549. } else {
  1550. query.eq(InfoUser::getDelFlag, "0");
  1551. }
  1552. List<InfoUser> matches = infoUserService.list(query);
  1553. if (matches.size() > 1) {
  1554. log.warn("[OAuth] 手机号关联多个账号 channel={}, phone={}", channel, maskPhone(phone));
  1555. throw new ServiceException(MessageUtils.message("no.oauth.phone.duplicate"));
  1556. }
  1557. return matches.isEmpty() ? null : matches.get(0);
  1558. }
  1559. /**
  1560. * 查询用户信息列表
  1561. */
  1562. @PreAuthorize("@ss.hasPermi('infouser:user:list')")
  1563. @GetMapping("/list")
  1564. public TableDataInfo list(InfoUser infoUser) {
  1565. startPage();
  1566. List<InfoUser> list = infoUserService.selectInfoUserList(infoUser);
  1567. if (!list.isEmpty()) {
  1568. List<Long> userIds = list.stream().map(InfoUser::getUserId).collect(Collectors.toList());
  1569. List<UserWallet> wallets = userWalletService.list(new LambdaQueryWrapper<UserWallet>().in(UserWallet::getUserId, userIds).isNull(UserWallet::getStoreId));
  1570. list.forEach(item -> {
  1571. Optional<UserWallet> wallet = wallets.stream().filter(w -> w.getUserId().equals(item.getUserId())).findFirst();
  1572. if (wallet.isPresent()) {
  1573. item.setPointsWallet(Long.valueOf(wallet.get().getPointsWallet().toString()));
  1574. } else {
  1575. item.setPointsWallet(0L);
  1576. }
  1577. });
  1578. }
  1579. return getDataTable(list);
  1580. }
  1581. /**
  1582. * 导出用户信息列表
  1583. */
  1584. @PreAuthorize("@ss.hasPermi('infouser:user:export')")
  1585. @Log(title = "用户信息", businessType = BusinessType.EXPORT)
  1586. @PostMapping("/export")
  1587. public void export(HttpServletResponse response, InfoUser infoUser) {
  1588. List<InfoUser> list = infoUserService.selectInfoUserList(infoUser);
  1589. ExcelUtil<InfoUser> util = new ExcelUtil<InfoUser>(InfoUser.class);
  1590. util.exportExcel(response, list, MessageUtils.message("no.export.excel.userinfo"));
  1591. }
  1592. /**
  1593. * 获取用户信息详细信息
  1594. */
  1595. @PreAuthorize("@ss.hasPermi('infouser:user:query')")
  1596. @GetMapping(value = "/{userId}")
  1597. public AjaxResult getInfo(@PathVariable("userId") Long userId) {
  1598. InfoUser user = infoUserService.selectInfoUserByUserId(userId);
  1599. DateUtil date = new DateUtil();
  1600. QueryWrapper<VipUser> queryWrapper = new QueryWrapper<>();
  1601. queryWrapper.eq("user_id", user.getUserId());
  1602. VipUser vipUser = vipUserService.getOne(queryWrapper);
  1603. if (vipUser == null) {
  1604. user.setVip(null);
  1605. } else {
  1606. boolean orn = date.isPastDate(vipUser.getYxtim());
  1607. if (orn) {
  1608. user.setVip(null);
  1609. } else {
  1610. user.setVip(vipUser.getYxtim());
  1611. }
  1612. }
  1613. return success(user);
  1614. }
  1615. /**
  1616. * 新增用户信息
  1617. */
  1618. @PreAuthorize("@ss.hasPermi('infouser:user:add')")
  1619. @Log(title = "用户信息", businessType = BusinessType.INSERT)
  1620. @PostMapping
  1621. @Transactional(rollbackFor = Exception.class)
  1622. public AjaxResult add(@RequestBody InfoUser infoUser) {
  1623. if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(infoUser.getUserType())) {
  1624. return error(MessageUtils.message("merchant.subaccount.platform.managed"));
  1625. }
  1626. if (!normalizeAuditRejectReason(infoUser)) {
  1627. return error(MessageUtils.message("no.user.audit.reject.reason.required"));
  1628. }
  1629. normalizeDeliveryType(infoUser);
  1630. if (infoUserService.getinfouserName(infoUser.getUserName()) != null) {
  1631. return error(MessageUtils.message("no.user.add") + infoUser.getUserName() + MessageUtils.message("no.user.login.exist"));
  1632. } else if (businessPhoneService.isBusinessUserType(infoUser.getUserType())) {
  1633. businessPhoneService.ensureUnique(infoUser.getTelPhone(), null);
  1634. } else if (infoUserService.getinfoPhone(infoUser.getPhone()) != null) {
  1635. return error(MessageUtils.message("no.user.add") + infoUser.getPhone() + MessageUtils.message("no.user.mobile.exist"));
  1636. }
  1637. UUIDUtil uuid = new UUIDUtil();
  1638. infoUser.setMycode(uuid.get8UUID());
  1639. infoUser.setStatus("0");
  1640. System.out.println("加密:" + infoUser.getPassword());
  1641. int result = infoUserService.insertInfoUser(infoUser);
  1642. createUserWallet(infoUser.getUserId());
  1643. return toAjax(result);
  1644. }
  1645. /**
  1646. * 修改用户信息
  1647. */
  1648. @PreAuthorize("@ss.hasPermi('infouser:user:edit')")
  1649. @Log(title = "用户信息", businessType = BusinessType.UPDATE)
  1650. @PutMapping
  1651. public AjaxResult edit(@RequestBody InfoUser infoUser) {
  1652. InfoUser existing = infoUser.getUserId() == null ? null
  1653. : infoUserService.selectInfoUserByUserId(infoUser.getUserId());
  1654. if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(infoUser.getUserType())
  1655. || (existing != null
  1656. && MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(existing.getUserType()))) {
  1657. return error(MessageUtils.message("merchant.subaccount.platform.managed"));
  1658. }
  1659. if (!normalizeAuditRejectReason(infoUser)) {
  1660. return error(MessageUtils.message("no.user.audit.reject.reason.required"));
  1661. }
  1662. normalizeDeliveryType(infoUser);
  1663. String effectiveUserType = infoUser.getUserType() != null
  1664. ? infoUser.getUserType() : existing == null ? null : existing.getUserType();
  1665. if (businessPhoneService.isBusinessUserType(effectiveUserType)) {
  1666. businessPhoneService.ensureUnique(infoUser.getTelPhone(), infoUser.getUserId());
  1667. }
  1668. return toAjax(infoUserService.updateInfoUser(infoUser));
  1669. }
  1670. /**
  1671. * 重置用户登录密码(独立于通用编辑,密文入库,与 App 端 RSA 公钥体系一致)
  1672. */
  1673. @PreAuthorize("@ss.hasPermi('infouser:user:resetPwd')")
  1674. @Log(title = "用户信息", businessType = BusinessType.UPDATE)
  1675. @PutMapping("/resetPwd")
  1676. public AjaxResult resetPwd(@RequestBody InfoUserResetPwdDto resetPwdDto) {
  1677. if (resetPwdDto.getUserId() == null) {
  1678. return error(MessageUtils.message("no.user.not.exist"));
  1679. }
  1680. if (resetPwdDto.getPassword() == null || resetPwdDto.getPassword().isEmpty()) {
  1681. return error(MessageUtils.message("no.user.password.not.null"));
  1682. }
  1683. InfoUser user = infoUserService.selectInfoUserByUserId(resetPwdDto.getUserId());
  1684. if (user == null) {
  1685. return error(MessageUtils.message("no.user.not.exist"));
  1686. }
  1687. if (MerchantAccountConstants.SUBACCOUNT_USER_TYPE.equals(user.getUserType())) {
  1688. return error(MessageUtils.message("merchant.subaccount.platform.managed"));
  1689. }
  1690. boolean updated = infoUserService.update(new LambdaUpdateWrapper<InfoUser>()
  1691. .eq(InfoUser::getUserId, user.getUserId())
  1692. .set(InfoUser::getPassword, resetPwdDto.getPassword()));
  1693. return updated ? success() : error(MessageUtils.message("no.system.error"));
  1694. }
  1695. private boolean normalizeAuditRejectReason(InfoUser infoUser) {
  1696. if (!"2".equals(infoUser.getAuditStatus())) {
  1697. infoUser.setAuditRejectReason(null);
  1698. return true;
  1699. }
  1700. if (infoUser.getAuditRejectReason() == null) {
  1701. return false;
  1702. }
  1703. String reason = infoUser.getAuditRejectReason().strip();
  1704. if (reason.isEmpty()) {
  1705. return false;
  1706. }
  1707. infoUser.setAuditRejectReason(reason);
  1708. return true;
  1709. }
  1710. /** 骑手配送类型归一化;非法值返回国际化业务错误。 */
  1711. private void normalizeDeliveryType(InfoUser infoUser) {
  1712. try {
  1713. infoUser.setDeliveryType(InfoUser.normalizeDeliveryTypes(infoUser.getDeliveryType()));
  1714. } catch (IllegalArgumentException exception) {
  1715. throw new ServiceException(MessageUtils.message("no.user.delivery.type.invalid"));
  1716. }
  1717. }
  1718. /**
  1719. * 删除用户信息
  1720. */
  1721. @PreAuthorize("@ss.hasPermi('infouser:user:remove')")
  1722. @Log(title = "用户信息", businessType = BusinessType.DELETE)
  1723. @DeleteMapping("/{userIds}")
  1724. public AjaxResult remove(@PathVariable Long[] userIds) {
  1725. return toAjax(infoUserService.deleteInfoUserByUserIds(userIds));
  1726. }
  1727. }