UserApiController.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. package com.zsElectric.boot.app.api;
  2. import cn.hutool.core.img.ImgUtil;
  3. import cn.hutool.core.util.ObjectUtil;
  4. import cn.hutool.extra.qrcode.QrCodeUtil;
  5. import com.fasterxml.jackson.databind.JsonNode;
  6. import com.fasterxml.jackson.databind.ObjectMapper;
  7. import com.zsElectric.boot.app.model.entity.*;
  8. import com.zsElectric.boot.app.service.*;
  9. import com.zsElectric.boot.core.web.Result;
  10. import jakarta.servlet.http.HttpServletRequest;
  11. import jakarta.servlet.http.HttpServletResponse;
  12. import lombok.RequiredArgsConstructor;
  13. import lombok.extern.slf4j.Slf4j;
  14. import okhttp3.MediaType;
  15. import okhttp3.OkHttpClient;
  16. import okhttp3.Request;
  17. import org.springframework.beans.factory.annotation.Value;
  18. import org.springframework.data.redis.core.RedisTemplate;
  19. import org.springframework.transaction.annotation.Transactional;
  20. import org.springframework.web.bind.annotation.*;
  21. import org.springframework.web.multipart.MultipartFile;
  22. import java.time.LocalDateTime;
  23. import java.util.*;
  24. import java.util.concurrent.TimeUnit;
  25. /**
  26. * 用户API接口 - 面向小程序/APP
  27. */
  28. @Slf4j
  29. @RestController
  30. @RequestMapping("/userApi")
  31. @RequiredArgsConstructor
  32. public class UserApiController {
  33. private final UserInfoService userInfoService;
  34. private final UserAccountService userAccountService;
  35. private final BannerInfoService bannerInfoService;
  36. private final UserFeedbackService userFeedbackService;
  37. private final NewUserDiscountService newUserDiscountService;
  38. private final ChargeOrderInfoService chargeOrderInfoService;
  39. private final UserFirmService userFirmService;
  40. private final FirmInfoService firmInfoService;
  41. private final AdvertisingService advertisingService;
  42. private final RedisTemplate<String, Object> redisTemplate;
  43. private final OkHttpClient okHttpClient = new OkHttpClient();
  44. private final ObjectMapper objectMapper = new ObjectMapper();
  45. private final String TOKEN_PREFIX = "user:token:";
  46. @Value("${wx.miniapp.app-id:}")
  47. private String wxAppid;
  48. @Value("${wx.miniapp.app-secret:}")
  49. private String wxAppSecret;
  50. /**
  51. * 新用户活动信息查询
  52. */
  53. @PostMapping("/checkNewUser")
  54. @Transactional(rollbackFor = Exception.class)
  55. public Result<?> checkNewUser(@RequestBody Map<String, Object> data, HttpServletRequest request) {
  56. UserInfo cacheUser = getUserFromToken(request);
  57. if (cacheUser == null) {
  58. return Result.failed("登录已过期");
  59. }
  60. List<NewUserDiscount> list = newUserDiscountService.lambdaQuery()
  61. .eq(NewUserDiscount::getStatus, 1)
  62. .le(NewUserDiscount::getBeginTime, LocalDateTime.now())
  63. .ge(NewUserDiscount::getEndTime, LocalDateTime.now())
  64. .list();
  65. if (list == null || list.isEmpty()) {
  66. return Result.success("暂无新用户活动");
  67. }
  68. NewUserDiscount discountInfo = list.get(0);
  69. long orderCount = chargeOrderInfoService.lambdaQuery()
  70. .eq(ChargeOrderInfo::getUserId, cacheUser.getId())
  71. .count();
  72. if (orderCount > 0) {
  73. return Result.success("不是新用户");
  74. }
  75. Map<String, Object> result = new HashMap<>();
  76. result.put("discountInfo", discountInfo);
  77. return Result.success(result);
  78. }
  79. /**
  80. * 微信小程序登录
  81. */
  82. @PostMapping("/login")
  83. @Transactional(rollbackFor = Exception.class)
  84. public Result<?> login(@RequestBody Map<String, Object> data, HttpServletRequest request) {
  85. UserInfo cacheUser = getUserFromToken(request);
  86. // 检查是否已登录
  87. if (cacheUser != null && cacheUser.getId() != null) {
  88. String token = request.getHeader("token");
  89. if (token == null) {
  90. return Result.failed("token不能为空");
  91. }
  92. if (data.get("checkStatus") != null) {
  93. UserInfo userInfo = userInfoService.getById(cacheUser.getId());
  94. saveUserToCache(token, userInfo);
  95. Map<String, Object> result = new HashMap<>();
  96. result.put("userInfo", userInfo);
  97. result.put("token", token);
  98. return Result.success(result);
  99. }
  100. Map<String, Object> result = new HashMap<>();
  101. result.put("userInfo", cacheUser);
  102. result.put("token", token);
  103. return Result.success(result);
  104. }
  105. String code = (String) data.get("code");
  106. log.info("[微信登录]code:{}", code);
  107. log.info("[微信登录]wxAppid:{}", wxAppid);
  108. try {
  109. // 调用微信接口获取openid
  110. String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wxAppid +
  111. "&secret=" + wxAppSecret +
  112. "&grant_type=authorization_code&js_code=" + code;
  113. String body = okHttpClient.newCall(new Request.Builder().url(url).get().build())
  114. .execute().body().string();
  115. log.info("[微信登录]回复报文:{}", body);
  116. JsonNode jsonNode = objectMapper.readTree(body);
  117. String openid = jsonNode.has("openid") ? jsonNode.get("openid").asText() : null;
  118. if (openid == null) {
  119. log.error("[微信登录]失败,回复报文:{}", body);
  120. return Result.failed("登录失败,请稍后再试");
  121. }
  122. // 查询或创建用户
  123. UserInfo userInfo = userInfoService.lambdaQuery()
  124. .eq(UserInfo::getWechat, openid)
  125. .one();
  126. if (userInfo == null) {
  127. userInfo = new UserInfo();
  128. userInfo.setWechat(openid);
  129. userInfoService.save(userInfo);
  130. // 创建账户
  131. UserAccount userAccount = new UserAccount();
  132. userAccount.setUserId(userInfo.getId());
  133. userAccountService.save(userAccount);
  134. }
  135. // 生成token并保存到缓存
  136. String loginToken = UUID.randomUUID().toString().replace("-", "");
  137. saveUserToCache(loginToken, userInfo);
  138. Map<String, Object> result = new HashMap<>();
  139. result.put("userInfo", userInfo);
  140. result.put("token", loginToken);
  141. return Result.success(result);
  142. } catch (Exception e) {
  143. log.error("[微信登录]异常", e);
  144. return Result.failed("登录失败,请稍后再试");
  145. }
  146. }
  147. /**
  148. * 获取手机号
  149. */
  150. @PostMapping("/getPhone")
  151. @Transactional(rollbackFor = Exception.class)
  152. public Result<?> getPhone(@RequestBody Map<String, Object> data, HttpServletRequest request) {
  153. UserInfo cacheUser = getUserFromToken(request);
  154. if (cacheUser == null) {
  155. return Result.failed("登录已过期");
  156. }
  157. String code = (String) data.get("code");
  158. try {
  159. // 获取access_token
  160. String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" +
  161. wxAppid + "&secret=" + wxAppSecret;
  162. String tokenBody = okHttpClient.newCall(new Request.Builder().url(tokenUrl).get().build())
  163. .execute().body().string();
  164. log.info("[获取token]回复报文:{}", tokenBody);
  165. JsonNode tokenNode = objectMapper.readTree(tokenBody);
  166. String accessToken = tokenNode.has("access_token") ? tokenNode.get("access_token").asText() : null;
  167. if (accessToken == null) {
  168. log.error("[获取token]失败,回复报文:{}", tokenBody);
  169. return Result.failed("获取失败,请稍后再试");
  170. }
  171. // 获取手机号
  172. Map<String, Object> phoneParams = new HashMap<>();
  173. phoneParams.put("code", code);
  174. okhttp3.RequestBody requestBody = okhttp3.RequestBody.create(
  175. MediaType.parse("application/json"), objectMapper.writeValueAsString(phoneParams));
  176. String phoneUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
  177. String phoneBody = okHttpClient.newCall(new Request.Builder().url(phoneUrl).post(requestBody).build())
  178. .execute().body().string();
  179. log.info("[获取手机号]回复报文:{}", phoneBody);
  180. JsonNode phoneNode = objectMapper.readTree(phoneBody);
  181. Integer errcode = phoneNode.has("errcode") ? phoneNode.get("errcode").asInt() : -1;
  182. if (errcode != 0) {
  183. log.error("[获取手机号]失败,回复报文:{}", phoneBody);
  184. return Result.failed("获取失败,请稍后再试");
  185. }
  186. JsonNode phoneInfo = phoneNode.get("phone_info");
  187. String phone = phoneInfo.has("phoneNumber") ? phoneInfo.get("phoneNumber").asText() : null;
  188. // 检查手机号是否已存在
  189. UserInfo existUser = userInfoService.lambdaQuery()
  190. .eq(UserInfo::getPhone, phone)
  191. .ne(UserInfo::getGroupId, -1L)
  192. .one();
  193. if (existUser != null) {
  194. // 删除原账户的微信绑定
  195. UserInfo oldUser = new UserInfo();
  196. oldUser.setId(cacheUser.getId());
  197. oldUser.setWechat(cacheUser.getId() + "_" + cacheUser.getWechat());
  198. userInfoService.updateById(oldUser);
  199. // 更新现有用户的微信id
  200. existUser.setWechat(cacheUser.getWechat());
  201. userInfoService.updateById(existUser);
  202. String token = request.getHeader("token");
  203. saveUserToCache(token, existUser);
  204. Map<String, Object> result = new HashMap<>();
  205. result.put("userInfo", existUser);
  206. return Result.success(result);
  207. }
  208. // 更新当前用户的手机号
  209. UserInfo updateUser = new UserInfo();
  210. updateUser.setId(cacheUser.getId());
  211. updateUser.setPhone(phone);
  212. userInfoService.updateById(updateUser);
  213. UserInfo userInfo = userInfoService.getById(cacheUser.getId());
  214. String token = request.getHeader("token");
  215. saveUserToCache(token, userInfo);
  216. Map<String, Object> result = new HashMap<>();
  217. result.put("userInfo", userInfo);
  218. return Result.success(result);
  219. } catch (Exception e) {
  220. log.error("[获取手机号]异常", e);
  221. return Result.failed("获取失败,请稍后再试");
  222. }
  223. }
  224. /**
  225. * 获取用户账户信息
  226. */
  227. @PostMapping("/getUserAccount")
  228. public Result<?> getUserAccount(HttpServletRequest request) {
  229. UserInfo cacheUser = getUserFromToken(request);
  230. if (cacheUser == null) {
  231. return Result.failed("登录已过期");
  232. }
  233. UserInfo user = userInfoService.getById(cacheUser.getId());
  234. if (user == null) {
  235. return Result.failed("未查询到用户信息");
  236. }
  237. if (ObjectUtil.isNotEmpty(user.getFirmId())) {
  238. FirmInfo firmInfo = firmInfoService.getById(user.getFirmId());
  239. if (ObjectUtil.isNotEmpty(firmInfo)) {
  240. user.setFirmInfoName(firmInfo.getName());
  241. user.setFirmType(firmInfo.getStatus());
  242. }
  243. }
  244. Map<String, Object> result = new HashMap<>();
  245. result.put("accountInfo", user);
  246. return Result.success(result);
  247. }
  248. /**
  249. * 获取Banner列表
  250. */
  251. @PostMapping("/getBanners")
  252. public Result<?> getBanners() {
  253. List<BannerInfo> banners = bannerInfoService.lambdaQuery()
  254. .eq(BannerInfo::getStatus, 1)
  255. .orderByAsc(BannerInfo::getSort)
  256. .list();
  257. Map<String, Object> result = new HashMap<>();
  258. result.put("banners", banners);
  259. return Result.success(result);
  260. }
  261. /**
  262. * 获取广告位
  263. */
  264. @PostMapping("/getAdvertising")
  265. public Result<?> getAdvertising() {
  266. List<Advertising> banners = advertisingService.lambdaQuery()
  267. .eq(Advertising::getStatus, 1)
  268. .orderByAsc(Advertising::getSort)
  269. .list();
  270. Map<String, Object> result = new HashMap<>();
  271. result.put("banners", banners);
  272. return Result.success(result);
  273. }
  274. /**
  275. * 新增用户反馈
  276. */
  277. @PostMapping("/addUserFeedback")
  278. @Transactional(rollbackFor = Exception.class)
  279. public Result<?> addUserFeedback(@RequestBody UserFeedback data, HttpServletRequest request) {
  280. UserInfo cacheUser = getUserFromToken(request);
  281. if (cacheUser == null) {
  282. return Result.failed("登录已过期");
  283. }
  284. data.setUserId(cacheUser.getId());
  285. boolean saved = userFeedbackService.save(data);
  286. if (!saved) {
  287. return Result.failed("提交失败");
  288. }
  289. return Result.success();
  290. }
  291. /**
  292. * 查看用户反馈
  293. */
  294. @PostMapping("/getMyFeekBack")
  295. public Result<?> getMyFeekBack(HttpServletRequest request) {
  296. UserInfo cacheUser = getUserFromToken(request);
  297. if (cacheUser == null) {
  298. return Result.failed("登录已过期");
  299. }
  300. List<UserFeedback> list = userFeedbackService.lambdaQuery()
  301. .eq(UserFeedback::getUserId, cacheUser.getId())
  302. .orderByDesc(UserFeedback::getCreateTime)
  303. .list();
  304. Map<String, Object> result = new HashMap<>();
  305. result.put("list", list);
  306. return Result.success(result);
  307. }
  308. /**
  309. * 上传文件
  310. */
  311. @PostMapping("/upFile")
  312. public Result<?> upFile(@RequestParam("file") MultipartFile file) {
  313. try {
  314. // TODO: 实现文件上传逻辑,使用ossProperties配置
  315. String fileUrl = "/uploads/" + file.getOriginalFilename();
  316. Map<String, Object> result = new HashMap<>();
  317. result.put("fileUrl", fileUrl);
  318. return Result.success(result);
  319. } catch (Exception e) {
  320. log.error("文件上传失败", e);
  321. return Result.failed("文件上传失败");
  322. }
  323. }
  324. /**
  325. * 获取邀请员工的二维码
  326. */
  327. @GetMapping("/get-invite-qr")
  328. public void getInviteQr(HttpServletRequest request, HttpServletResponse response) {
  329. try {
  330. UserInfo cacheUser = getUserFromToken(request);
  331. if (cacheUser == null) {
  332. return;
  333. }
  334. UserFirm firm = userFirmService.lambdaQuery()
  335. .eq(UserFirm::getUserId, cacheUser.getId())
  336. .one();
  337. if (ObjectUtil.isEmpty(firm)) {
  338. return;
  339. }
  340. if (firm.getUserType() == 2) {
  341. return;
  342. }
  343. String content = "https://cd.api.zswlgz.com/deviceid?frimId=" + firm.getFirmId();
  344. QrCodeUtil.generate(content, 300, 300, ImgUtil.IMAGE_TYPE_PNG, response.getOutputStream());
  345. } catch (Exception e) {
  346. log.error("生成二维码失败", e);
  347. }
  348. }
  349. /**
  350. * 邀请员工加入企业
  351. */
  352. @PostMapping("/add-firm-user")
  353. public Result<?> addFirmUser(HttpServletRequest request, @RequestParam("firmId") Long firmId) {
  354. UserInfo cacheUser = getUserFromToken(request);
  355. if (cacheUser == null) {
  356. return Result.failed("登录已过期");
  357. }
  358. UserFirm userFirm = new UserFirm();
  359. userFirm.setUserId(cacheUser.getId());
  360. userFirm.setFirmId(firmId);
  361. userFirm.setUserType(2L);
  362. try {
  363. boolean saved = userFirmService.save(userFirm);
  364. if (saved) {
  365. FirmInfo firmInfo = firmInfoService.getById(firmId);
  366. return Result.success("你已经成功加入" + firmInfo.getName() + ",现在充电享受企业专享价");
  367. }
  368. } catch (Exception e) {
  369. return Result.failed(e.getMessage());
  370. }
  371. return Result.failed("加入失败");
  372. }
  373. /**
  374. * 从请求头获取token并从缓存中获取用户信息
  375. */
  376. private UserInfo getUserFromToken(HttpServletRequest request) {
  377. String token = request.getHeader("token");
  378. if (token == null) {
  379. return null;
  380. }
  381. Object obj = redisTemplate.opsForValue().get(TOKEN_PREFIX + token);
  382. if (obj instanceof UserInfo) {
  383. // 刷新token过期时间
  384. redisTemplate.expire(TOKEN_PREFIX + token, 30, TimeUnit.MINUTES);
  385. return (UserInfo) obj;
  386. }
  387. return null;
  388. }
  389. /**
  390. * 保存用户信息到缓存
  391. */
  392. private void saveUserToCache(String token, UserInfo userInfo) {
  393. redisTemplate.opsForValue().set(TOKEN_PREFIX + token, userInfo, 30, TimeUnit.MINUTES);
  394. }
  395. }