UserServiceImpl.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package com.kym.service.impl;
  2. import cn.dev33.satoken.stp.StpUtil;
  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.github.pagehelper.PageHelper;
  7. import com.github.yulichang.base.MPJBaseServiceImpl;
  8. import com.kym.common.R;
  9. import com.kym.common.config.WxConfig;
  10. import com.kym.common.constant.ResponseEnum;
  11. import com.kym.common.enums.WxApi;
  12. import com.kym.common.exception.BusinessException;
  13. import com.kym.common.utils.CommUtil;
  14. import com.kym.common.utils.HttpUtil;
  15. import com.kym.entity.*;
  16. import com.kym.entity.common.PageBean;
  17. import com.kym.entity.queryParams.CommonQueryParam;
  18. import com.kym.entity.queryParams.WxLoginParams;
  19. import com.kym.entity.vo.CustomUserVo;
  20. import com.kym.entity.vo.UserVo;
  21. import com.kym.entity.wechat.WxPhoneNum;
  22. import com.kym.mapper.MpRelationMapper;
  23. import com.kym.mapper.UserMapper;
  24. import com.kym.service.AccountService;
  25. import com.kym.service.CarsService;
  26. import com.kym.service.RefundLogService;
  27. import com.kym.service.UserService;
  28. import com.kym.service.cache.KymCache;
  29. import jakarta.annotation.PostConstruct;
  30. import lombok.SneakyThrows;
  31. import org.slf4j.Logger;
  32. import org.slf4j.LoggerFactory;
  33. import org.springframework.beans.BeanUtils;
  34. import org.springframework.context.annotation.Lazy;
  35. import org.springframework.scheduling.annotation.Async;
  36. import org.springframework.stereotype.Service;
  37. import org.springframework.transaction.annotation.Transactional;
  38. import java.time.LocalDate;
  39. import java.time.LocalDateTime;
  40. import java.time.LocalTime;
  41. import java.time.temporal.TemporalAdjusters;
  42. import java.util.Arrays;
  43. import java.util.List;
  44. import java.util.Map;
  45. import java.util.stream.Collectors;
  46. import static java.util.Map.of;
  47. /**
  48. * <p>
  49. * 用户表 服务实现类
  50. * </p>
  51. *
  52. * @author skyline
  53. * @since 2023-06-27
  54. */
  55. @Service
  56. public class UserServiceImpl extends MPJBaseServiceImpl<UserMapper, User> implements UserService {
  57. private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
  58. private final WxConfig wxConfig;
  59. private final AccountService accountService;
  60. private final RefundLogService refundLogService;
  61. private final CarsService carsService;
  62. private final MpRelationMapper mpRelationMapper;
  63. public UserServiceImpl(WxConfig wxConfig, @Lazy AccountService accountService, RefundLogService refundLogService,
  64. CarsService carsService, MpRelationMapper mpRelationMapper) {
  65. this.wxConfig = wxConfig;
  66. this.accountService = accountService;
  67. this.refundLogService = refundLogService;
  68. this.carsService = carsService;
  69. this.mpRelationMapper = mpRelationMapper;
  70. }
  71. @PostConstruct
  72. private void init() {
  73. // 初始化缓存
  74. QueryWrapper<User> wrapper = new QueryWrapper<User>().select("id", "station_id");
  75. List<User> users = baseMapper.selectList(wrapper);
  76. var map = users.stream().collect(Collectors.toMap(User::getId, User::getStationId));
  77. KymCache.INSTANCE.putUserId2StationId(map);
  78. }
  79. @Transactional(rollbackFor = Exception.class)
  80. @SneakyThrows
  81. @Override
  82. public R<?> wxLogin(WxLoginParams params) {
  83. // 微信登录
  84. var json = HttpUtil.getJson(WxApi.WX_MP_LOGIN.getApi(), Map.of(
  85. "appid", wxConfig.getAppid(),
  86. "secret", wxConfig.getSecret(),
  87. "js_code", params.getCode()
  88. ));
  89. Integer errorCode = json.getInteger("errcode");
  90. if (errorCode == null) {
  91. // 业务逻辑处理
  92. var openid = json.getString("openid");
  93. var unionid = json.getString("unionid") == null ? "" : json.getString("unionid");
  94. // 判断有没有,没有就新增
  95. var user = baseMapper.selectOne(new QueryWrapper<User>().eq("openid", openid));
  96. if (user != null) {
  97. // 登录逻辑
  98. if (CommUtil.isNotEmptyAndNull(unionid) && CommUtil.isEmptyOrNull(user.getUnionid())) {
  99. user.setUnionid(unionid);
  100. }
  101. return handleLogin(user);
  102. } else {
  103. if (CommUtil.isEmptyOrNull(params.getPhoneCode())) {
  104. throw new BusinessException(ResponseEnum.WX_MP_LOGIN_ERROR);
  105. }
  106. // 注册逻辑
  107. // 新增用户
  108. var newUser = new User();
  109. newUser.setOpenid(openid);
  110. newUser.setUnionid(unionid);
  111. // 手机号解密:先获取access_token,再请求手机号信息
  112. // access_token获取
  113. var accessTokenJson = HttpUtil.getJson(WxApi.WX_GET_ACCESS_TOKEN.getApi(), Map.of("appid", wxConfig.getAppid(), "secret", wxConfig.getSecret()));
  114. var accessToken = accessTokenJson.getString("access_token");
  115. var wxPhoneNum = HttpUtil.post(WxApi.WX_MP_GET_PHONE.getApi().replace("ACCESS_TOKEN", accessToken), Map.of("code", params.getPhoneCode()), WxPhoneNum.class);
  116. var mobilePhone = wxPhoneNum.getPhone_info().getPurePhoneNumber();
  117. newUser.setMobilePhone(mobilePhone);
  118. newUser.setUsername(mobilePhone);
  119. newUser.setAvatar(params.getAvatar());
  120. newUser.setNickname(params.getNickname());
  121. // 用户归属站点
  122. if (CommUtil.isNotEmptyAndNull(params.getShortId())) {
  123. newUser.setStationId(KymCache.INSTANCE.gesStationIdByShortId(params.getShortId()));
  124. } else {
  125. // todo 用户归属 扫设备二维码以外注册的用户,统一归属平台自己的商户,初始化系统时创建对应的商户
  126. newUser.setStationId("000");
  127. }
  128. KymCache.INSTANCE.putUserId2StationId(Map.of(newUser.getId(), newUser.getStationId()));
  129. baseMapper.insert(newUser);
  130. // 创建用户账户
  131. var account = new Account();
  132. account.setUserId(newUser.getId());
  133. accountService.save(account);
  134. // 登录逻辑
  135. return handleLogin(newUser);
  136. }
  137. } else {
  138. // 抛出异常
  139. LOGGER.error("微信登录异常,错误码{},异常信息{}", errorCode, json.getString("errmsg"));
  140. throw new BusinessException(ResponseEnum.WX_MP_LOGIN_ERROR);
  141. }
  142. }
  143. /**
  144. * 获取当前登录用户信息
  145. *
  146. * @return
  147. */
  148. @Override
  149. public UserVo getMe() {
  150. var userId = StpUtil.getLoginIdAsLong();
  151. var userVo = baseMapper.getMe(userId);
  152. var car = carsService.lambdaQuery().eq(Cars::getUserId, userId).eq(Cars::getIsDefault, 1).one();
  153. if (car != null) {
  154. userVo.setDefaultPlateNo(car.getPlateNo());
  155. userVo.setVin(car.getVin());
  156. }
  157. return userVo;
  158. }
  159. /**
  160. * 登录逻辑处理
  161. *
  162. * @param user
  163. * @return
  164. */
  165. private R<?> handleLogin(User user) {
  166. StpUtil.login(user.getId());
  167. // 用户名存入session,统一日志读取使用
  168. StpUtil.getSession().set("openid", user.getOpenid());
  169. StpUtil.getSession().set("unionid", user.getUnionid());
  170. StpUtil.getSession().set("userId", user.getId());
  171. StpUtil.getSession().set("username", user.getUsername());
  172. StpUtil.getSession().set("mobilePhone", user.getMobilePhone());
  173. StpUtil.getSession().set("stationId", user.getStationId());
  174. user.setLastLoginTime(LocalDateTime.now());
  175. updateById(user);
  176. LOGGER.info("用户:{}/{}登录成功,tokenName:{},tokenValue:{}", user.getMobilePhone(), user.getId(), StpUtil.getTokenName(), StpUtil.getTokenValue());
  177. updateUnionid();
  178. return R.success(of("userId", user.getId(), "satoken", StpUtil.getTokenValue()));
  179. }
  180. @Async
  181. protected void updateUnionid() {
  182. var unionid = StpUtil.getSession().getString("unionid");
  183. if (CommUtil.isNotEmptyAndNull(unionid)) {
  184. // 匹配公众号用户
  185. var wrapper = new LambdaUpdateWrapper<MpRelation>().eq(MpRelation::getUnionid, unionid).set(MpRelation::getUserId, StpUtil.getLoginIdAsLong()).set(MpRelation::getOpenid, StpUtil.getSession().getString("openid"));
  186. mpRelationMapper.update(null, wrapper);
  187. }
  188. }
  189. @Transactional(rollbackFor = Exception.class)
  190. @Override
  191. public void updateUser(UserVo userVo) {
  192. userVo.setId(StpUtil.getSession().getLong("userId"));
  193. var user = new User();
  194. BeanUtils.copyProperties(userVo, user);
  195. if (!CommUtil.isEmptyOrNull(user.getAvatar()) || !CommUtil.isEmptyOrNull(user.getMobilePhone()) || !CommUtil.isEmptyOrNull(user.getNickname())) {
  196. baseMapper.updateById(user);
  197. }
  198. // 更新车牌信息
  199. if (userVo.getDefaultPlateNo() != null) {
  200. var car = new Cars();
  201. car.setUserId(userVo.getId());
  202. car.setPlateNo(userVo.defaultPlateNo);
  203. // 设置为默认
  204. car.setIsDefault(true);
  205. if (userVo.getVin() != null) {
  206. car.setVin(userVo.getVin());
  207. }
  208. // 将用户名下其他车辆设为非默认
  209. var cars = carsService.listByMap(Map.of("user_id", userVo.getId()));
  210. cars.stream().filter(c -> !userVo.getDefaultPlateNo().equals(c.getEngineNo())).peek(s -> s.setIsDefault(false)).collect(Collectors.toList());
  211. carsService.updateBatchById(cars);
  212. var wrapper = new QueryWrapper<Cars>();
  213. wrapper.eq("plate_no", userVo.getDefaultPlateNo());
  214. carsService.saveOrUpdate(car, wrapper);
  215. }
  216. }
  217. @Override
  218. public PageBean<UserVo> listUserVo(Integer pageNum, Integer pageSize) {
  219. PageHelper.startPage(pageNum, pageSize);
  220. var userVoList = list().stream().map(user -> {
  221. var userVo = new UserVo();
  222. BeanUtils.copyProperties(user, userVo);
  223. return userVo;
  224. }).collect(Collectors.toList());
  225. return new PageBean<>(userVoList);
  226. }
  227. /**
  228. * 分页查询用户列表
  229. *
  230. * @param params
  231. * @return
  232. */
  233. @Override
  234. public PageBean<CustomUserVo> listCustomUser(CommonQueryParam params) {
  235. // 站点数据权限
  236. var adminStationIds = KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong());
  237. if (CommUtil.isEmptyOrNull(params.getStationId()) &&
  238. CommUtil.isNotEmptyAndNull(KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong()))) {
  239. params.setStationId(adminStationIds.get(0));
  240. }
  241. List<Long> userIds = lambdaQuery()
  242. .eq(CommUtil.isNotEmptyAndNull(params.getMobilePhone()), User::getMobilePhone, params.getMobilePhone())
  243. .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), User::getStationId, params.getStationId())
  244. .eq(CommUtil.isNotEmptyAndNull(params.getStatus()), User::getStatus, params.getStatus())
  245. .list().stream().map(User::getId)
  246. .toList();
  247. if (CommUtil.isEmptyOrNull(userIds)) {
  248. return new PageBean<>();
  249. }
  250. PageHelper.startPage(params.getPageNum(), params.getPageSize());
  251. var result = baseMapper.listUser(userIds);
  252. var page = new PageBean<>(result);
  253. // 用户余额,退款次数,退款金额
  254. var account = accountService.lambdaQuery().in(Account::getUserId, result.stream().map(CustomUserVo::getUserId).toList()).list();
  255. var user2Balance = account.stream().collect(Collectors.groupingBy(Account::getUserId, Collectors.summingInt(Account::getBalance)));
  256. var user2FrozenAmount = account.stream().collect(Collectors.groupingBy(Account::getUserId, Collectors.summingInt(Account::getFrozenAmount)));
  257. var refund = refundLogService.lambdaQuery().in(RefundLog::getUserId, result.stream().map(CustomUserVo::getUserId).toList()).list();
  258. // refund按照用户维度计算退款次数和退款总金额
  259. var user2RefundAmount = refund.stream().collect(Collectors.groupingBy(RefundLog::getUserId, Collectors.summingInt(RefundLog::getRefund)));
  260. var user2RefundDiscountAmount = refund.stream().collect(Collectors.groupingBy(RefundLog::getUserId, Collectors.summingInt(RefundLog::getDiscountAmount)));
  261. var user2RefundTimes = refund.stream().collect(Collectors.groupingBy(RefundLog::getUserId, Collectors.counting()));
  262. // 将用户余额,退款次数,退款金额放入result中
  263. var res = result.stream().peek(vo -> {
  264. vo.setBalance(user2Balance.getOrDefault(vo.getUserId(), 0));
  265. vo.setFrozenAmount(user2FrozenAmount.getOrDefault(vo.getUserId(), 0));
  266. vo.setRefundTimes(user2RefundTimes.getOrDefault(vo.getUserId(), 0L));
  267. vo.setRefundAmount(user2RefundAmount.getOrDefault(vo.getUserId(), 0));
  268. vo.setRefundDiscountAmount(user2RefundDiscountAmount.getOrDefault(vo.getUserId(), 0));
  269. vo.setStationName(KymCache.INSTANCE.getStationNameById(vo.getStationId()));
  270. }).toList();
  271. page.setList(res);
  272. return page;
  273. }
  274. /**
  275. * 统计指定日期注册用户数量
  276. *
  277. * @param statDay
  278. * @return
  279. */
  280. @Override
  281. public Map<String, Integer> countDailyRegister(LocalDate statDay, String... stationId) {
  282. LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
  283. wrapper.ge(User::getCreateTime, LocalDateTime.of(statDay, LocalTime.MIN));
  284. wrapper.lt(User::getCreateTime, LocalDateTime.of(statDay, LocalTime.MAX));
  285. if (stationId.length > 0) {
  286. wrapper.in(User::getStationId, Arrays.asList(stationId));
  287. }
  288. // 按照站点分组统计订单数量
  289. return list(wrapper).stream().collect(Collectors.groupingBy(User::getStationId, Collectors.summingInt(o -> 1)));
  290. }
  291. /**
  292. * 统计指定日期当月注册用户数量
  293. *
  294. * @param statDay
  295. * @return
  296. */
  297. @Override
  298. public Map<String, Integer> countMonthRegister(LocalDate statDay) {
  299. var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN);
  300. var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX);
  301. LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
  302. wrapper.ge(User::getCreateTime, startTime);
  303. wrapper.lt(User::getCreateTime, endTime);
  304. return list(wrapper).stream().collect(Collectors.groupingBy(User::getStationId, Collectors.summingInt(o -> 1)));
  305. }
  306. }