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)));
}
}