From 5d7b65670282a4fad015e37d567cfa171b162052 Mon Sep 17 00:00:00 2001 From: huliguo <2023611923@qq.com> Date: 星期二, 20 五月 2025 12:25:19 +0800 Subject: [PATCH] 基础代码 --- pt-errand/src/main/java/com/ruoyi/errand/service/impl/AppUserServiceImpl.java | 557 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 557 insertions(+), 0 deletions(-) diff --git a/pt-errand/src/main/java/com/ruoyi/errand/service/impl/AppUserServiceImpl.java b/pt-errand/src/main/java/com/ruoyi/errand/service/impl/AppUserServiceImpl.java new file mode 100644 index 0000000..98b186f --- /dev/null +++ b/pt-errand/src/main/java/com/ruoyi/errand/service/impl/AppUserServiceImpl.java @@ -0,0 +1,557 @@ +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<AppUserMapper, AppUser> 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; + + + @Override + public void getSMSCode(String phone) { + //校验验证码获取频率(1分钟5次) + String key = "app" + "&" + phone; + Map<String, Object> 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<LoginVO> 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<AppUser>().eq(AppUser::getPhone, mobileLogin.getPhone())); + if (null == appUser || appUser.getDelFlag().equals(DelFlagConstant.DELETE)) { + //用户不存在 + //使用jscode获取微信openid + Map<String, Object> 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<String,Object> map = new HashMap<>(); + map.put("userId",appUser.getId()); + Map<String, Object> 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<LoginVO> appletLogin(AppletLogin appletLogin) { + //使用jscode获取微信openid + Map<String, Object> 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<AppUser>().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<String,Object> tokenMap = new HashMap<>(); + map.put("userId",appUser.getId()); + Map<String, Object> 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<OrderPageVO> 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() { + 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); + } + + @Override + public UserStatsVO getUserStats(LocalDateTime start, LocalDateTime end, String datePattern) { + UserStatsVO vo = new UserStatsVO(); + // 1. 查询按时间分组的用户数 + List<Map<String, Object>> 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<AppUserPageListVO> getAppUserPageList(AppUserPageListDTO dto) { + // 获取本月的开始时间和结束时间 + LocalDate now = LocalDate.now(); + LocalDateTime startOfMonth = now.withDayOfMonth(1).atStartOfDay(); + LocalDateTime endOfMonth = now.withDayOfMonth(now.lengthOfMonth()).atTime(23, 59, 59); + + IPage<AppUserPageListVO> page = new Page<>(dto.getPageNum(),dto.getPageSize()); + IPage<AppUserPageListVO> iPage = this.getBaseMapper().getAppUserPageList(page, dto); + iPage.getRecords().forEach((record) -> { + //当月下单量 + Long mouth = orderMapper.selectCount(new LambdaUpdateWrapper<Order>() + .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<Order>() + .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<Order>() + .eq(Order::getAppUserId, id) + .eq(Order::getDelFlag, DelFlagConstant.UNDELETE) + .between(Order::getOrderTime, startOfMonth, endOfMonth) + ); + vo.setMouth(mouth.intValue()); + + //总共下单量 + Long total = orderMapper.selectCount(new LambdaUpdateWrapper<Order>() + .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<VipOrder> orders = vipOrderMapper.selectList(new LambdaQueryWrapper<VipOrder>() + .eq(VipOrder::getAppUserId, id) + .eq(VipOrder::getOrderStatus, 4) // 4-已完成 + .eq(VipOrder::getDelFlag, DelFlagConstant.UNDELETE) // 0-未删除 + .isNull(VipOrder::getRefundTime) // 未退款的 + .orderByDesc(VipOrder::getOrderTime)); + + // 2. 计算每个订单的有效期结束时间 + Map<Long, LocalDateTime> orderEndTimes = calculateOrderEndTimes(orders); + + // 3. 筛选出仍在有效期内的订单 + LocalDateTime now = LocalDateTime.now(); + List<VipOrder> 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<VipOrder>().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<Long, LocalDateTime> calculateOrderEndTimes(List<VipOrder> orders) { + Map<Long, LocalDateTime> result = new HashMap<>(); + LocalDateTime currentEndTime = null; + + // 按购买时间升序处理,计算叠加后的有效期 + List<VipOrder> 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<Map<String, Object>> stats) { + List<String> allDates = generateDateRange(start, end, datePattern); + Map<String, Integer> statMap = convertStatsToMap(stats); + + List<String> dates = new ArrayList<>(); + List<Integer> 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<String, Integer> convertStatsToMap(List<Map<String, Object>> stats) { + return stats.stream() + .collect(Collectors.toMap( + stat -> String.valueOf(stat.get("date")), + stat -> Integer.valueOf(String.valueOf(stat.get("count"))) + )); + } + + private List<String> generateDateRange(LocalDateTime start, LocalDateTime end, String datePattern) { + List<String> 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); + } + +} -- Gitblit v1.7.1