package com.kym.service.impl; import cn.dev33.satoken.stp.StpUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.github.pagehelper.PageHelper; import com.github.yulichang.base.MPJBaseServiceImpl; import com.kym.common.R; import com.kym.common.config.WxConfig; import com.kym.common.constant.ResponseEnum; import com.kym.common.enums.WxApi; import com.kym.common.exception.BusinessException; import com.kym.common.utils.CommUtil; import com.kym.common.utils.HttpUtil; import com.kym.entity.*; import com.kym.entity.common.PageBean; import com.kym.entity.queryParams.CommonQueryParam; import com.kym.entity.queryParams.WxLoginParams; import com.kym.entity.vo.CustomUserVo; import com.kym.entity.vo.UserVo; import com.kym.entity.wechat.WxPhoneNum; import com.kym.mapper.MpRelationMapper; import com.kym.mapper.UserMapper; import com.kym.service.AccountService; import com.kym.service.CarsService; import com.kym.service.RefundLogService; import com.kym.service.UserService; import com.kym.service.cache.KymCache; import jakarta.annotation.PostConstruct; import lombok.SneakyThrows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.TemporalAdjusters; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static java.util.Map.of; /** *

* 用户表 服务实现类 *

* * @author skyline * @since 2023-06-27 */ @Service public class UserServiceImpl extends MPJBaseServiceImpl implements UserService { private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class); private final WxConfig wxConfig; private final AccountService accountService; private final RefundLogService refundLogService; private final CarsService carsService; private final MpRelationMapper mpRelationMapper; public UserServiceImpl(WxConfig wxConfig, @Lazy AccountService accountService, RefundLogService refundLogService, CarsService carsService, MpRelationMapper mpRelationMapper) { this.wxConfig = wxConfig; this.accountService = accountService; this.refundLogService = refundLogService; this.carsService = carsService; this.mpRelationMapper = mpRelationMapper; } @PostConstruct private void init() { // 初始化缓存 QueryWrapper wrapper = new QueryWrapper().select("id", "station_id"); List users = baseMapper.selectList(wrapper); var map = users.stream().collect(Collectors.toMap(User::getId, User::getStationId)); KymCache.INSTANCE.putUserId2StationId(map); } @Transactional(rollbackFor = Exception.class) @SneakyThrows @Override public R wxLogin(WxLoginParams params) { // 微信登录 var json = HttpUtil.getJson(WxApi.WX_MP_LOGIN.getApi(), Map.of( "appid", wxConfig.getAppid(), "secret", wxConfig.getSecret(), "js_code", params.getCode() )); Integer errorCode = json.getInteger("errcode"); if (errorCode == null) { // 业务逻辑处理 var openid = json.getString("openid"); var unionid = json.getString("unionid") == null ? "" : json.getString("unionid"); // 判断有没有,没有就新增 var user = baseMapper.selectOne(new QueryWrapper().eq("openid", openid)); if (user != null) { // 登录逻辑 if (CommUtil.isNotEmptyAndNull(unionid) && CommUtil.isEmptyOrNull(user.getUnionid())) { user.setUnionid(unionid); } return handleLogin(user); } else { if (CommUtil.isEmptyOrNull(params.getPhoneCode())) { throw new BusinessException(ResponseEnum.WX_MP_LOGIN_ERROR); } // 注册逻辑 // 新增用户 var newUser = new User(); newUser.setOpenid(openid); newUser.setUnionid(unionid); // 手机号解密:先获取access_token,再请求手机号信息 // access_token获取 var accessTokenJson = HttpUtil.getJson(WxApi.WX_GET_ACCESS_TOKEN.getApi(), Map.of("appid", wxConfig.getAppid(), "secret", wxConfig.getSecret())); var accessToken = accessTokenJson.getString("access_token"); var wxPhoneNum = HttpUtil.post(WxApi.WX_MP_GET_PHONE.getApi().replace("ACCESS_TOKEN", accessToken), Map.of("code", params.getPhoneCode()), WxPhoneNum.class); var mobilePhone = wxPhoneNum.getPhone_info().getPurePhoneNumber(); newUser.setMobilePhone(mobilePhone); newUser.setUsername(mobilePhone); newUser.setAvatar(params.getAvatar()); newUser.setNickname(params.getNickname()); // 用户归属站点 if (CommUtil.isNotEmptyAndNull(params.getShortId())) { newUser.setStationId(KymCache.INSTANCE.gesStationIdByShortId(params.getShortId())); } else { // todo 用户归属 扫设备二维码以外注册的用户,统一归属平台自己的商户,初始化系统时创建对应的商户 newUser.setStationId("000"); } KymCache.INSTANCE.putUserId2StationId(Map.of(newUser.getId(), newUser.getStationId())); baseMapper.insert(newUser); // 创建用户账户 var account = new Account(); account.setUserId(newUser.getId()); accountService.save(account); // 登录逻辑 return handleLogin(newUser); } } else { // 抛出异常 LOGGER.error("微信登录异常,错误码{},异常信息{}", errorCode, json.getString("errmsg")); throw new BusinessException(ResponseEnum.WX_MP_LOGIN_ERROR); } } /** * 获取当前登录用户信息 * * @return */ @Override public UserVo getMe() { var userId = StpUtil.getLoginIdAsLong(); var userVo = baseMapper.getMe(userId); var car = carsService.lambdaQuery().eq(Cars::getUserId, userId).eq(Cars::getIsDefault, 1).one(); if (car != null) { userVo.setDefaultPlateNo(car.getPlateNo()); userVo.setVin(car.getVin()); } return userVo; } /** * 登录逻辑处理 * * @param user * @return */ private R handleLogin(User user) { StpUtil.login(user.getId()); // 用户名存入session,统一日志读取使用 StpUtil.getSession().set("openid", user.getOpenid()); StpUtil.getSession().set("unionid", user.getUnionid()); StpUtil.getSession().set("userId", user.getId()); StpUtil.getSession().set("username", user.getUsername()); StpUtil.getSession().set("mobilePhone", user.getMobilePhone()); StpUtil.getSession().set("stationId", user.getStationId()); user.setLastLoginTime(LocalDateTime.now()); updateById(user); LOGGER.info("用户:{}/{}登录成功,tokenName:{},tokenValue:{}", user.getMobilePhone(), user.getId(), StpUtil.getTokenName(), StpUtil.getTokenValue()); updateUnionid(); return R.success(of("userId", user.getId(), "satoken", StpUtil.getTokenValue())); } @Async protected void updateUnionid() { var unionid = StpUtil.getSession().getString("unionid"); if (CommUtil.isNotEmptyAndNull(unionid)) { // 匹配公众号用户 var wrapper = new LambdaUpdateWrapper().eq(MpRelation::getUnionid, unionid).set(MpRelation::getUserId, StpUtil.getLoginIdAsLong()).set(MpRelation::getOpenid, StpUtil.getSession().getString("openid")); mpRelationMapper.update(null, wrapper); } } @Transactional(rollbackFor = Exception.class) @Override public void updateUser(UserVo userVo) { userVo.setId(StpUtil.getSession().getLong("userId")); var user = new User(); BeanUtils.copyProperties(userVo, user); if (!CommUtil.isEmptyOrNull(user.getAvatar()) || !CommUtil.isEmptyOrNull(user.getMobilePhone()) || !CommUtil.isEmptyOrNull(user.getNickname())) { baseMapper.updateById(user); } // 更新车牌信息 if (userVo.getDefaultPlateNo() != null) { var car = new Cars(); car.setUserId(userVo.getId()); car.setPlateNo(userVo.defaultPlateNo); // 设置为默认 car.setIsDefault(true); if (userVo.getVin() != null) { car.setVin(userVo.getVin()); } // 将用户名下其他车辆设为非默认 var cars = carsService.listByMap(Map.of("user_id", userVo.getId())); cars.stream().filter(c -> !userVo.getDefaultPlateNo().equals(c.getEngineNo())).peek(s -> s.setIsDefault(false)).collect(Collectors.toList()); carsService.updateBatchById(cars); var wrapper = new QueryWrapper(); wrapper.eq("plate_no", userVo.getDefaultPlateNo()); carsService.saveOrUpdate(car, wrapper); } } @Override public PageBean listUserVo(Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); var userVoList = list().stream().map(user -> { var userVo = new UserVo(); BeanUtils.copyProperties(user, userVo); return userVo; }).collect(Collectors.toList()); return new PageBean<>(userVoList); } /** * 分页查询用户列表 * * @param params * @return */ @Override public PageBean listCustomUser(CommonQueryParam params) { // 站点数据权限 var adminStationIds = KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong()); if (CommUtil.isEmptyOrNull(params.getStationId()) && CommUtil.isNotEmptyAndNull(KymCache.INSTANCE.getAdminUserStationIds(StpUtil.getLoginIdAsLong()))) { params.setStationId(adminStationIds.get(0)); } List userIds = lambdaQuery() .eq(CommUtil.isNotEmptyAndNull(params.getMobilePhone()), User::getMobilePhone, params.getMobilePhone()) .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), User::getStationId, params.getStationId()) .eq(CommUtil.isNotEmptyAndNull(params.getStatus()), User::getStatus, params.getStatus()) .list().stream().map(User::getId) .toList(); if (CommUtil.isEmptyOrNull(userIds)) { return new PageBean<>(); } PageHelper.startPage(params.getPageNum(), params.getPageSize()); var result = baseMapper.listUser(userIds); var page = new PageBean<>(result); // 用户余额,退款次数,退款金额 var account = accountService.lambdaQuery().in(Account::getUserId, result.stream().map(CustomUserVo::getUserId).toList()).list(); var user2Balance = account.stream().collect(Collectors.groupingBy(Account::getUserId, Collectors.summingInt(Account::getBalance))); var user2rechargeBalance = account.stream().collect(Collectors.groupingBy(Account::getUserId, Collectors.summingInt(Account::getRechargeBalance))); var user2GrantsBalance= account.stream().collect(Collectors.groupingBy(Account::getUserId, Collectors.summingInt(Account::getGrantsBalance))); var user2FrozenAmount = account.stream().collect(Collectors.groupingBy(Account::getUserId, Collectors.summingInt(Account::getFrozenAmount))); var refund = refundLogService.lambdaQuery().in(RefundLog::getUserId, result.stream().map(CustomUserVo::getUserId).toList()).list(); // refund按照用户维度计算退款次数和退款总金额 var user2RefundAmount = refund.stream().collect(Collectors.groupingBy(RefundLog::getUserId, Collectors.summingInt(RefundLog::getRefund))); var user2RefundDiscountAmount = refund.stream().collect(Collectors.groupingBy(RefundLog::getUserId, Collectors.summingInt(RefundLog::getDiscountAmount))); var user2RefundTimes = refund.stream().collect(Collectors.groupingBy(RefundLog::getUserId, Collectors.counting())); // 将用户余额,退款次数,退款金额放入result中 var res = result.stream().peek(vo -> { vo.setBalance(user2Balance.getOrDefault(vo.getUserId(), 0)); vo.setRechargeBalance(user2rechargeBalance.getOrDefault(vo.getUserId(), 0)); vo.setGrantsBalance(user2GrantsBalance.getOrDefault(vo.getUserId(), 0)); vo.setFrozenAmount(user2FrozenAmount.getOrDefault(vo.getUserId(), 0)); vo.setRefundTimes(user2RefundTimes.getOrDefault(vo.getUserId(), 0L)); vo.setRefundAmount(user2RefundAmount.getOrDefault(vo.getUserId(), 0)); vo.setRefundDiscountAmount(user2RefundDiscountAmount.getOrDefault(vo.getUserId(), 0)); vo.setStationName(KymCache.INSTANCE.getStationNameById(vo.getStationId())); }).toList(); page.setList(res); return page; } /** * 统计指定日期注册用户数量 * * @param statDay * @return */ @Override public Map countDailyRegister(LocalDate statDay, String... stationId) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.ge(User::getCreateTime, LocalDateTime.of(statDay, LocalTime.MIN)); wrapper.lt(User::getCreateTime, LocalDateTime.of(statDay, LocalTime.MAX)); if (stationId.length > 0) { wrapper.in(User::getStationId, Arrays.asList(stationId)); } // 按照站点分组统计订单数量 return list(wrapper).stream().collect(Collectors.groupingBy(User::getStationId, Collectors.summingInt(o -> 1))); } /** * 统计指定日期当月注册用户数量 * * @param statDay * @return */ @Override public Map countMonthRegister(LocalDate statDay) { var startTime = statDay.with(TemporalAdjusters.firstDayOfMonth()).atTime(LocalTime.MIN); var endTime = statDay.with(TemporalAdjusters.lastDayOfMonth()).atTime(LocalTime.MAX); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.ge(User::getCreateTime, startTime); wrapper.lt(User::getCreateTime, endTime); return list(wrapper).stream().collect(Collectors.groupingBy(User::getStationId, Collectors.summingInt(o -> 1))); } }