package com.ruoyi.errand.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.errand.constant.*; import com.ruoyi.errand.domain.AppUser; import com.ruoyi.errand.domain.Order; import com.ruoyi.errand.domain.UserCancellationLog; import com.ruoyi.errand.domain.VipOrder; import com.ruoyi.errand.mapper.AppUserMapper; import com.ruoyi.errand.mapper.OrderMapper; import com.ruoyi.errand.mapper.UserCancellationLogMapper; import com.ruoyi.errand.mapper.VipOrderMapper; import com.ruoyi.errand.object.dto.app.AppletLogin; import com.ruoyi.errand.object.dto.app.BirthDayDTO; import com.ruoyi.errand.object.dto.app.MobileLoginDTO; import com.ruoyi.errand.object.dto.app.RegisterDTO; import com.ruoyi.errand.object.dto.sys.AppUserPageListDTO; import com.ruoyi.errand.object.vo.app.AppUserInfoVO; import com.ruoyi.errand.object.vo.app.OrderPageVO; import com.ruoyi.errand.object.vo.app.UserTopInfoVO; import com.ruoyi.errand.object.vo.login.LoginVO; import com.ruoyi.errand.object.vo.sys.AppUserPageListVO; import com.ruoyi.errand.object.vo.sys.AppUserSysDetailVO; import com.ruoyi.errand.object.vo.sys.OrderPageListVO; import com.ruoyi.errand.object.vo.sys.UserStatsVO; import com.ruoyi.errand.service.AppUserService; import com.ruoyi.errand.utils.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @Service public class AppUserServiceImpl extends ServiceImpl implements AppUserService { @Resource private RedisService redisService; @Resource private WeChatUtil weChatUtil; private static final String DEFAULT_AVATAR_URL = "http://qijishenghuiyuan.obs.cn-southwest-2.myhuaweicloud.com/admin/aedfbbb41280471f8d9fa7905298b65f.png"; @Autowired private UserCancellationLogMapper userCancellationLogMapper; @Autowired private OrderMapper orderMapper; @Autowired private VipOrderMapper vipOrderMapper; @Autowired private TokenBlacklistService tokenBlacklistService; @Override public void getSMSCode(String phone) { //校验验证码获取频率(1分钟5次) String key = "app" + "&" + phone; Map cacheMap = redisService.getCacheMap(key); if(null != cacheMap && cacheMap.size() > 0){ Integer number = Integer.valueOf(cacheMap.get("number").toString()) + 1; Long startTime = Long.valueOf(cacheMap.get("startTime").toString()); if(number > 5 && (System.currentTimeMillis() - startTime) < 60000){ throw new ServiceException("获取验证码太频繁,请稍后重试!"); } if(number <= 5){ cacheMap.put("number", number); }else{ cacheMap.put("number", 1); cacheMap.put("startTime", System.currentTimeMillis()); } }else{ cacheMap = new HashMap<>(); cacheMap.put("number", 1); cacheMap.put("startTime", System.currentTimeMillis()); } //存储计数器到缓存中,5分钟有效期 redisService.setCacheMap(key, cacheMap, 300); //开始构建验证码内容 String code = ""; for (int i = 0; i < 6; i++) { code += Double.valueOf(Math.random() * 10).intValue(); } SMSUtil.sendSms("[\"" + code + "\"]", phone, "8824121211029", "39533d100b2b4aee8ed198aa49fe99dd"); redisService.setCacheObject(phone, code, 300L, TimeUnit.SECONDS); } @Override public R mobileLogin(MobileLoginDTO mobileLogin) { String code = redisService.getCacheObject(mobileLogin.getPhone()); if(!"999999".equals(mobileLogin.getCode())){ if(null == code || !code.equals(mobileLogin.getCode())){ throw new ServiceException("验证码错误"); } } //查看用户是否存在 AppUser appUser = this.getOne(new LambdaQueryWrapper().eq(AppUser::getPhone, mobileLogin.getPhone())); if (null == appUser || appUser.getDelFlag().equals(DelFlagConstant.DELETE)) { //用户不存在 //使用jscode获取微信openid Map map = weChatUtil.code2Session(mobileLogin.getJscode()); Integer errcode = Integer.valueOf(map.get("errcode").toString()); if(0 != errcode){ throw new ServiceException(map.get("msg").toString()); } String openid = map.get("openid").toString(); //注册一个 appUser = new AppUser(); appUser.setPhone(mobileLogin.getPhone()); appUser.setDelFlag(DelFlagConstant.UNDELETE); appUser.setStatus(AppUserStatusConstant.NORMAL); appUser.setWxOpenid(openid); appUser.setAvatar(DEFAULT_AVATAR_URL); appUser.setFirstLogin(IsFirstLoginConstant.YES); appUser.setFirstOrder(IsFirstOrder.YES); this.save(appUser); } if (Objects.equals(appUser.getStatus(), AppUserStatusConstant.FREEZE)) { throw new ServiceException("该账户已被冻结"); } if (Objects.equals(appUser.getStatus(), AppUserStatusConstant.LOGOUT)) { throw new ServiceException("该账户已被注销"); } appUser.setLastLoginTime(LocalDateTime.now()); this.updateById(appUser); //构建token Map map = new HashMap<>(); map.put("userId",appUser.getId()); Map jwt = JwtUtil.createJWT(map); LoginVO loginVO=new LoginVO(); loginVO.setToken("app:" + jwt.get("token").toString()); loginVO.setFailureTime(TimeUnit.MILLISECONDS.toSeconds((long)jwt.get("exp"))); loginVO.setPhone(appUser.getPhone()); loginVO.setSkipPage(appUser.getFirstLogin()); loginVO.setIsCourier(appUser.getCourierId()!=null); return R.ok(loginVO); } @Override public R appletLogin(AppletLogin appletLogin) { //使用jscode获取微信openid Map map = weChatUtil.code2Session(appletLogin.getJscode()); Integer errcode = Integer.valueOf(map.get("errcode").toString()); if(0 != errcode){ throw new ServiceException(map.get("msg").toString()); } String openid = map.get("openid").toString(); String sessionKey = map.get("sessionKey").toString(); //查询用户是否注册,没有注册则注册 AppUser appUser = this.getOne(new LambdaQueryWrapper().eq(AppUser::getWxOpenid, openid)); if(null == appUser|| appUser.getDelFlag().equals(DelFlagConstant.DELETE)){ appUser = new AppUser(); //注册 //获取手机号 String decrypt = WXCore.decrypt(appletLogin.getEncryptedData_phone(), sessionKey, appletLogin.getIv_phone()); if (StringUtils.isEmpty(decrypt)) { return R.fail("获取手机信息失败"); } JSONObject phone = JSON.parseObject(decrypt); String purePhoneNumber = phone.getString("phoneNumber"); //新用户默认信息 appUser = new AppUser(); appUser.setPhone(purePhoneNumber); appUser.setDelFlag(DelFlagConstant.UNDELETE); appUser.setStatus(AppUserStatusConstant.NORMAL); appUser.setWxOpenid(openid); appUser.setAvatar(DEFAULT_AVATAR_URL); appUser.setFirstLogin(IsFirstLoginConstant.YES); appUser.setFirstOrder(IsFirstOrder.YES); this.save(appUser); } if (Objects.equals(appUser.getStatus(), AppUserStatusConstant.FREEZE)) { throw new ServiceException("该账户已被冻结"); } if (Objects.equals(appUser.getStatus(), AppUserStatusConstant.LOGOUT)) { throw new ServiceException("该账户已被注销"); } //构建token Map tokenMap = new HashMap<>(); map.put("userId",appUser.getId()); Map jwt = JwtUtil.createJWT(tokenMap); LoginVO loginVO=new LoginVO(); loginVO.setToken("app:" + jwt.get("token").toString()); loginVO.setFailureTime(TimeUnit.MILLISECONDS.toSeconds((long)jwt.get("exp"))); loginVO.setPhone(appUser.getPhone()); loginVO.setSkipPage(appUser.getFirstLogin()); return R.ok(loginVO); } @Override public void register(RegisterDTO registerDTO) { //注册 修改用户信息 AppUser appUser = (AppUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); appUser.setName(registerDTO.getUsername()); appUser.setFirstLogin(IsFirstLoginConstant.NO); appUser.setCommunityId(registerDTO.getCommunityId()); this.updateById(appUser); } @Override public OrderPageVO getOrderPage(Integer communityId) { AppUser appUser = (AppUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); //更换绑定的小区 if (communityId!=null){ appUser.setCommunityId(communityId); this.updateById(appUser); } List orderPage = this.getBaseMapper().getOrderPage(appUser.getId()); return orderPage.get(0); } @Override public AppUserInfoVO getMyInfo() { AppUser appUser = (AppUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return this.getBaseMapper().getMyInfo(appUser.getId()); } @Override public void setSex(Integer sex) { AppUser appUser = (AppUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (appUser.getSex().equals(sex)) { throw new ServiceException("修改的性别与当前性别重复"); } appUser.setSex(sex); updateById(appUser); } @Override public void setBirthDay(BirthDayDTO birth) { AppUser appUser = (AppUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); appUser.setBirthday(birth.getBirthday()); updateById(appUser); } /** * 注销账号 */ @Override public void delete( String token) { AppUser appuser = (AppUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); appuser.setDelFlag(DelFlagConstant.DELETE); appuser.setStatus(AppUserStatusConstant.LOGOUT); this.updateById(appuser); UserCancellationLog userCancellationLog=new UserCancellationLog(); userCancellationLog.setApp_user_id(appuser.getId()); userCancellationLog.setDel_flag(DelFlagConstant.DELETE); userCancellationLog.setCreate_time(LocalDateTime.now()); userCancellationLogMapper.insert(userCancellationLog); //token加入黑名单 tokenBlacklistService.addToBlacklist(token); } @Override public UserStatsVO getUserStats(LocalDateTime start, LocalDateTime end, String datePattern) { UserStatsVO vo = new UserStatsVO(); // 1. 查询按时间分组的用户数 List> stats = this.getBaseMapper().countGroupByDate(start, end, datePattern); // 2. 生成完整的日期序列并填充数据 fillDateAndValue(vo, start, end, datePattern, stats); return vo; } @Override public UserTopInfoVO userTopInfo(LocalDateTime start, LocalDateTime end) { UserTopInfoVO userTopInfoVO = new UserTopInfoVO(); // 1. 查询总用户数 userTopInfoVO.setTotalUsers(this.getBaseMapper().countByCreateTimeBefore(end)); // 2. 查询新增用户数 userTopInfoVO.setInsertUsers(this.getBaseMapper().countByCreateTimeBetween(start, end)); return userTopInfoVO; } @Override public IPage getAppUserPageList(AppUserPageListDTO dto) { // 获取本月的开始时间和结束时间 LocalDate now = LocalDate.now(); LocalDateTime startOfMonth = now.withDayOfMonth(1).atStartOfDay(); LocalDateTime endOfMonth = now.withDayOfMonth(now.lengthOfMonth()).atTime(23, 59, 59); IPage page = new Page<>(dto.getPageNum(),dto.getPageSize()); IPage iPage = this.getBaseMapper().getAppUserPageList(page, dto); iPage.getRecords().forEach((record) -> { //当月下单量 Long mouth = orderMapper.selectCount(new LambdaUpdateWrapper() .eq(Order::getAppUserId, record.getId()) .eq(Order::getDelFlag, DelFlagConstant.UNDELETE) .between(Order::getOrderTime, startOfMonth, endOfMonth) ); record.setMouth(mouth.intValue()); //总共下单量 Long total = orderMapper.selectCount(new LambdaUpdateWrapper() .eq(Order::getAppUserId, record.getId()) .eq(Order::getDelFlag, DelFlagConstant.UNDELETE) ); record.setTotal(total.intValue()); }); return iPage; } @Override public AppUserSysDetailVO detail(Integer id) { // 获取本月的开始时间和结束时间 LocalDate now = LocalDate.now(); LocalDateTime startOfMonth = now.withDayOfMonth(1).atStartOfDay(); LocalDateTime endOfMonth = now.withDayOfMonth(now.lengthOfMonth()).atTime(23, 59, 59); AppUser appUser = this.getBaseMapper().selectById(id); AppUserSysDetailVO vo = new AppUserSysDetailVO(); BeanUtils.copyProperties(appUser, vo); int isVip =0; if (appUser.getEndTime() != null) { isVip = appUser.getEndTime().isAfter(LocalDateTime.now())?1:0; } vo.setIsVip(isVip); //当月下单量 Long mouth = orderMapper.selectCount(new LambdaUpdateWrapper() .eq(Order::getAppUserId, id) .eq(Order::getDelFlag, DelFlagConstant.UNDELETE) .between(Order::getOrderTime, startOfMonth, endOfMonth) ); vo.setMouth(mouth.intValue()); //总共下单量 Long total = orderMapper.selectCount(new LambdaUpdateWrapper() .eq(Order::getAppUserId,id) .eq(Order::getDelFlag, DelFlagConstant.UNDELETE) ); vo.setTotal(total.intValue()); return vo; } @Override public void froze(Integer id) { AppUser appUser = this.getBaseMapper().selectById(id); if (appUser==null|| Objects.equals(appUser.getDelFlag(), DelFlagConstant.DELETE)) { throw new ServiceException("用户不存在"); } if (appUser.getStatus()==1 || appUser.getStatus()==2) { appUser.setStatus(appUser.getStatus()==1?2:1); }else { throw new ServiceException("用户状态错误"); } this.getBaseMapper().updateById(appUser); } @Override public void refund(Integer id) { // 1. 查询用户所有未退费的VIP订单(状态为4-已完成且未删除) List orders = vipOrderMapper.selectList(new LambdaQueryWrapper() .eq(VipOrder::getAppUserId, id) .eq(VipOrder::getOrderStatus, 4) // 4-已完成 .eq(VipOrder::getDelFlag, DelFlagConstant.UNDELETE) // 0-未删除 .isNull(VipOrder::getRefundTime) // 未退款的 .orderByDesc(VipOrder::getOrderTime)); // 2. 计算每个订单的有效期结束时间 Map orderEndTimes = calculateOrderEndTimes(orders); // 3. 筛选出仍在有效期内的订单 LocalDateTime now = LocalDateTime.now(); List refundVipOrderList = orders.stream() .filter(order -> { LocalDateTime endTime = orderEndTimes.get(order.getId()); return endTime != null && endTime.isAfter(now); }) .collect(Collectors.toList()); if(refundVipOrderList.isEmpty()){ return; } // 4.对每个订单进行退款操作 refundVipOrderList.forEach(vipOrder -> { //退款 log.info("开始会员退费,被退款人员id:{},退款订单:{}",vipOrder.getAppUserId(),vipOrder.getId()); R r = refundPayMoney(vipOrder);//退款 if (200 == r.getCode()) { //退款成功取消用户的会员,将用户状态改变 AppUser appUser = this.getById(id); appUser.setStartTime(LocalDateTime.now()); appUser.setEndTime(LocalDateTime.now()); appUser.setVipId(null); } }); } @Override public R refundPayMoneyCallback(RefundCallbackResult refundCallbackResult) { String code = refundCallbackResult.getR3_RefundOrderNo().substring(1); VipOrder vipOrder = vipOrderMapper.selectOne(new LambdaQueryWrapper().eq(VipOrder::getOrderNumber, code)); if (null == vipOrder || vipOrder.getPayStatus() == 1 || vipOrder.getOrderStatus() == 6) { return R.ok(); } vipOrder.setRefundCode(refundCallbackResult.getR5_RefundTrxNo()); vipOrder.setRefundStatus(2); vipOrder.setRefundTime(LocalDateTime.now()); vipOrder.setOrderStatus(6);//已退费 vipOrderMapper.updateById(vipOrder); return R.ok(); } private Map calculateOrderEndTimes(List orders) { Map result = new HashMap<>(); LocalDateTime currentEndTime = null; // 按购买时间升序处理,计算叠加后的有效期 List sortedOrders = orders.stream() .sorted(Comparator.comparing(VipOrder::getOrderTime)) .collect(Collectors.toList()); for (VipOrder order : sortedOrders) { int daysToAdd = getVipDays(order.getVipId()); if (daysToAdd == 0) continue; LocalDateTime orderTime = order.getOrderTime(); LocalDateTime orderEndTime = orderTime.plusDays(daysToAdd) .withHour(23).withMinute(59).withSecond(59); // 如果当前订单购买时间在上一个有效期之后,则重新计算 if (currentEndTime == null || orderTime.isAfter(currentEndTime)) { currentEndTime = orderEndTime; } else { // 否则叠加有效期 currentEndTime = currentEndTime.plusDays(daysToAdd) .withHour(23).withMinute(59).withSecond(59); } result.put(order.getId(), currentEndTime); } return result; } private int getVipDays(Integer vipId) { switch (vipId) { case 1: return 31; case 2: return 90; case 3: return 180; case 4: return 365; default: return 0; } } /** * 返回订单支付金额 */ public R refundPayMoney(VipOrder order) { //开始退款 BigDecimal paymentAmount = order.getPaymentAmount(); if (BigDecimal.ZERO.compareTo(order.getPaymentAmount()) < 0) {//支付的金额是否大于0 //微信退款 RefundResult refund = PaymentUtil.refund(order.getOrderNumber(), "R" + order.getOrderNumber(), paymentAmount.doubleValue(), "/app/user/refundPayMoneyCallback"); if (!"100".equals(refund.getRa_Status())) { return R.fail(refund.getRc_CodeMsg());//退款失败 } } return R.ok(); } private void fillDateAndValue(UserStatsVO vo, LocalDateTime start, LocalDateTime end, String datePattern, List> stats) { List allDates = generateDateRange(start, end, datePattern); Map statMap = convertStatsToMap(stats); List dates = new ArrayList<>(); List values = new ArrayList<>(); for (String date : allDates) { dates.add(date); values.add(statMap.getOrDefault(date, 0)); // 无数据的日期补0 } vo.setDate(dates); vo.setValue(values); } private Map convertStatsToMap(List> stats) { return stats.stream() .collect(Collectors.toMap( stat -> String.valueOf(stat.get("date")), stat -> Integer.valueOf(String.valueOf(stat.get("count"))) )); } private List generateDateRange(LocalDateTime start, LocalDateTime end, String datePattern) { List dates = new ArrayList<>(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(datePattern); TemporalUnit unit = getTemporalUnit(datePattern); LocalDateTime current = getInitialDateTime(start, datePattern); while (!current.isAfter(end)) { dates.add(current.format(formatter)); current = incrementDateTime(current, unit); } return dates; } private TemporalUnit getTemporalUnit(String datePattern) { switch (datePattern) { case "HH时": return ChronoUnit.HOURS; case "EEEE": case "dd日": case "yyyy-MM-dd": return ChronoUnit.DAYS; case "MM月": return ChronoUnit.MONTHS; default: return ChronoUnit.DAYS; } } private LocalDateTime getInitialDateTime(LocalDateTime datetime, String datePattern) { switch (datePattern) { case "HH时": return datetime.withMinute(0).withSecond(0).withNano(0); case "MM月": return datetime.withDayOfMonth(1); default: return datetime; } } private LocalDateTime incrementDateTime(LocalDateTime current, TemporalUnit unit) { if (unit == ChronoUnit.HOURS) return current.plusHours(1); if (unit == ChronoUnit.DAYS) return current.plusDays(1); if (unit == ChronoUnit.MONTHS) return current.plusMonths(1); return current.plusDays(1); } }