package com.ylx.massage.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.json.JSONException; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ylx.common.config.WechatAccountConfig; import com.ylx.common.constant.MassageConstants; import com.ylx.common.exception.ServiceException; import com.ylx.common.utils.StringUtils; import com.ylx.massage.domain.*; import com.ylx.massage.domain.vo.TJsVo; import com.ylx.massage.enums.JsStatusEnum; import com.ylx.massage.mapper.TJsMapper; import com.ylx.massage.service.*; import com.ylx.massage.utils.DateTimeUtils; import com.ylx.massage.utils.LocationUtil; import com.ylx.massage.utils.VirtualTechnicianNicknameGenerator; import com.ylx.massage.utils.WeChatUtil; import com.ylx.order.service.TOrderService; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; /** * 技师表 服务实现类 */ @Service @Slf4j public class TJsServiceImpl extends ServiceImpl implements TJsService { @Resource private TJsMapper mapper; @Resource private WechatAccountConfig wxPayProperties; @Resource private LocationUtil locationUtil; @Resource private TCollectService collectService; @Resource private TXiangmuService xiangmuService; @Resource private TCommentService commentService; @Resource private TWxUserService wxUserService; @Resource private WeChatUtil weChatUtil; @Resource private TOrderService orderService; @Resource private TSignService signService; @Resource(name = "commonAsyncExecutor") private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Resource private VirtualTechnicianNicknameGenerator nicknameGenerator; @Override public Page getAll(Page page, TJsVo param) { return mapper.getAll(page, param); } @Override public boolean addJs(TJs js) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(TJs::getcOpenId, js.getcOpenId()); TJs cjs = this.getOne(queryWrapper); if (cjs != null) { throw new ServiceException("请勿重复申请"); } // cacheAddress(js); initialization(js); return this.save(js); } private void cacheAddress(TJs js) { if (StringUtils.isBlank(js.getcPhone())) { throw new ServiceException("手机号不能为空"); } if (StringUtils.isBlank(js.getName())) { throw new ServiceException("地址不能为空"); } if (StringUtils.isNotBlank(js.getId())) { TJs oldJs = this.getById(js.getId()); if (oldJs.getnTong().equals(JsStatusEnum.JS_RETURN.getCode())) { js.setnTong(JsStatusEnum.JS_NOT_PASS.getCode()); } } if (StringUtils.isNotBlank(js.getName())) { this.updateLocation(js); } // log.info("js:{}", js); // JSONObject jsonObject = new JSONObject(js.getcAddress()); // Object latitude = jsonObject.get("latitude"); // Object longitude = jsonObject.get("longitude"); // locationUtil.geoAdd(LocationUtil.GEO_KEY,js.getcOpenId(), Double.parseDouble(js.getLongitude().toString()), Double.parseDouble(js.getLatitude().toString())); } @Override public boolean wxUpdateJs(TJs js) { cacheAddress(js); return this.updateById(js); } @Override @Transactional(rollbackFor = Exception.class) public boolean auditing(TJs js) { TJs js1 = this.getById(js.getId()); LambdaQueryWrapper jsLambdaQueryWrapper = new LambdaQueryWrapper<>(); jsLambdaQueryWrapper.eq(TJs::getId, js.getId()).eq(TJs::getnTong, JsStatusEnum.JS_NOT_PASS.getCode()); js.setnTong(JsStatusEnum.JS_PASS.getCode()); js.setnB2(1); js.setcPhone(js1.getcPhone()); js.setcTime(new Date()); LambdaQueryWrapper wxLambdaQueryWrapper = new LambdaQueryWrapper<>(); wxLambdaQueryWrapper.eq(TWxUser::getcOpenid, js1.getcOpenId()); TWxUser tWxUser = new TWxUser(); //角色状态修改 tWxUser.setRole(1); wxUserService.update(tWxUser, wxLambdaQueryWrapper); //修改审核状态 boolean update = this.update(js, jsLambdaQueryWrapper); //修改新技师标识 /* js.setcOpenId(js1.getcOpenId()); // 发送审核结果通知 this.jsNotification(js);*/ return update; } @Override public void jsNotification(TJs js) { try { cn.hutool.json.JSONObject param = JSONUtil.createObj(); //是否审核通过 if (JsStatusEnum.JS_PASS.getCode().equals(js.getnTong())) { param.set("const1", JSONUtil.createObj().set("value", "审核通过可以接单")); } else { param.set("const1", JSONUtil.createObj().set("value", "审核驳回请重新提交")); } //审核时间 param.set("time2", JSONUtil.createObj().set("value", DateTimeUtils.formatDate(new Date(), DateTimeUtils.DATE_NUMBER_YEAR_MONTH_FORMAT))); //账号 param.set("character_string3", JSONUtil.createObj().set("value", js.getcPhone())); // weChatUtil.notification(js.getcOpenId(), wxPayProperties.getTemplateId2(), param); } catch (JSONException e) { e.printStackTrace(); throw new RuntimeException(e); } } @Override public boolean updateLocation(TJs js) { if (StringUtils.isBlank(js.getId())) { throw new ServiceException("ID不能为空"); } if (StringUtils.isBlank(js.getName())) { throw new ServiceException("地址不能为空"); } // JSONObject jsonObject = new JSONObject(js.getcAddress()); // Object latitude = jsonObject.get("latitude"); // Object longitude = jsonObject.get("longitude"); locationUtil.geoAdd(LocationUtil.GEO_KEY, js.getcPhone(), Double.parseDouble(js.getLongitude().toString()), Double.parseDouble(js.getLatitude().toString())); return this.updateById(js); } @Override public void newOrderNotification(String openId) { // 订单号 23010000010101 // 用户电话 15055556666 // 用户名称 李四 // 时间 2022年11月11日22:22 // 服务地址 湖南长沙岳麓 /*cn.hutool.json.JSONObject param = JSONUtil.createObj(); //订单号 param.set("character_string9", JSONUtil.createObj().set("value", "23010000010101")); //点话 param.set("phone_number14", JSONUtil.createObj().set("value", "18360233903")); param.set("thing18", JSONUtil.createObj().set("value", "李四")); param.set("time6", JSONUtil.createObj().set("value", "2022年11月11日 22:22")); param.set("thing27", JSONUtil.createObj().set("value", "湖南长沙岳麓"));*/ cn.hutool.json.JSONObject param = JSONUtil.createObj(); /** * 接单成功通知 */ //设置订单号 param.set("character_string1", JSONUtil.createObj().set("value", "123456")); //设置项目 param.set("thing4", JSONUtil.createObj().set("value", "按摩")); //商家名称 param.set("thing9", JSONUtil.createObj().set("value", "李强")); //联系电话 param.set("phone_number5", JSONUtil.createObj().set("value", "15296619636")); weChatUtil.notification(openId, wxPayProperties.getUserTemplate1(), param); } @Override public boolean upPost(TJs js) { if (StringUtils.isBlank(js.getId())) { throw new ServiceException("技师ID不能为空"); } if (js.getnStatus2() == null) { throw new ServiceException("岗位状态不能为空"); } TJs js2 = this.getById(js.getId()); TSign tSign = new TSign(); //上岗 if (Objects.equals(js.getnStatus2(), JsStatusEnum.POST_ON_DUTY.getCode())) { //岗位状态为1时,设置nStatus为1 js.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode()); //记录签到信息 tSign.setSingTime(new Date()); tSign.setOpenId(js2.getcOpenId()); tSign.setJsId(js2.getId()); tSign.setName(js2.getcName()); tSign.setDeptId(js2.getDeptId()); tSign.setDeptName(js2.getCity()); signService.save(tSign); } if (Objects.equals(js.getnStatus2(), JsStatusEnum.POST_NOT_ON_DUTY.getCode())) { //判断该技师是否有服务中的订单 TJs js1 = this.getById(js.getId()); if (js1.getnStatus().equals(JsStatusEnum.JS_SERVICE.getCode())) { throw new ServiceException("您有服务中的订单,不能下岗"); } js.setnStatus(JsStatusEnum.JS_NO_SERVICE.getCode()); LambdaQueryWrapper signLambdaQueryWrapper = new LambdaQueryWrapper<>(); signLambdaQueryWrapper.eq(TSign::getJsId, js.getId()).isNull(TSign::getLayoffTime).orderByAsc(TSign::getSingTime).last("LIMIT 1"); TSign singn = signService.getOne(signLambdaQueryWrapper); if (null != singn) { singn.setLayoffTime(new Date()); Long time = getaTime(singn); singn.setOnlineTime(time.intValue()); signService.updateById(singn); } // else { // tSign.setSingTime(DateTimeUtils.getStartDate(new Date())); // tSign.setLayoffTime(new Date()); // tSign.setOpenId(js2.getcOpenId()); // tSign.setJsId(js2.getId()); // tSign.setName(js2.getcName()); // Long time = getaTime(tSign); // tSign.setDeptId(js2.getDeptId()); // tSign.setDeptName(js2.getCity()); // tSign.setOnlineTime(time.intValue()); // signService.save(tSign); // } } return this.updateById(js); } private Long getaTime(TSign tSign) { Long layoff = DateTimeUtils.dateToStamp(tSign.getLayoffTime()); Long sing = DateTimeUtils.dateToStamp(tSign.getSingTime()); Long time = layoff - sing; time = time / 1000; time = time / 60; return time; } @Override public boolean block(TJs js) { if (StringUtils.isBlank(js.getId())) { throw new ServiceException("技师ID不能为空"); } if (js.getnTong() == null) { throw new ServiceException("拉黑状态不能为空"); } //恢复 if (Objects.equals(js.getnTong(), JsStatusEnum.JS_NOT_PASS.getCode())) { //岗位状态为1时,设置nStatus为1 js.setnStatus(JsStatusEnum.JS_NO_SERVICE.getCode()); } if (Objects.equals(js.getnTong(), JsStatusEnum.BLOCKED.getCode())) { //判断该技师是否有服务中的订单 TJs js1 = this.getById(js.getId()); if (js1.getnStatus().equals(JsStatusEnum.JS_SERVICE.getCode())) { throw new ServiceException("有服务中的订单,暂时不能拉黑"); } js.setnStatus(JsStatusEnum.JS_NO_SERVICE.getCode()); js.setnStatus2(JsStatusEnum.POST_NOT_ON_DUTY.getCode()); } return this.updateById(js); } @Override @Transactional(rollbackFor = Exception.class) public boolean pcAddJs(TJs js) { /*if (js.getCity() == null) { throw new ServiceException("所在城市不能为空"); }*/ //判断生活照图片列表不能为空 if (js.getcImgList() == null || js.getcImgList().isEmpty()) { throw new ServiceException("生活照图片列表不能为空"); } //判断身份证图片列表不能为空 if (js.getcSfzImg() == null || js.getcSfzImg().isEmpty()) { throw new ServiceException("身份证图片列表不能为空"); } //判断虚拟订单数量不能为空 if (js.getVirtualOrderNumber() == null) { throw new ServiceException("虚拟订单数量不能为空"); } if (js.getVirtualTechNumber() == null) { // 默认值为10 js.setVirtualTechNumber(10); } // 验证虚拟技师数量 Integer virtualTechNumber = js.getVirtualTechNumber(); if (virtualTechNumber < 0) { throw new ServiceException("虚拟技师数量不能为负数"); } // 0-真实技师 js.setTechType(0); //设置性别,默认:女 js.setnSex(0); //设置服务状态,默认:可服务 js.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode()); // 上岗状态:默认为未上岗 js.setnStatus2(JsStatusEnum.POST_NOT_ON_DUTY.getCode()); // 审核状态:默认为待审核 js.setnTong(JsStatusEnum.JS_NOT_PASS.getCode()); js.setDtCreateTime(LocalDateTime.now()); this.save(js); // 如果需要生成虚拟技师 if (virtualTechNumber > 0) { log.info("开始为真实技师[{}]生成{}个虚拟技师", js.getcName(), virtualTechNumber); generateVirtualTechnicians(js, virtualTechNumber); } String token = weChatUtil.getToken(); log.info("获取到的token: {}", token); //获取二维码ticket Map jsTicket = weChatUtil.getJsTicket(token, js.getId()); log.info("获取到的jsTicket: {}", jsTicket); js.setTicket(jsTicket.get("ticket").toString()); return this.updateById(js); } /** * 批量生成虚拟技师 *

* 业务规则: *

    *
  • 虚拟技师类型为 techType = 1
  • *
  • 除昵称外,其他信息与真实技师完全一致
  • *
  • 昵称随机生成(2-3个汉字,偏女性化,全表唯一)
  • *
  • 虚拟技师不需要手机号、OpenID等敏感信息
  • *
*

* * @param realTechnician 真实技师信息 * @param virtualTechNumber 需要生成的虚拟技师数量 */ private void generateVirtualTechnicians(TJs realTechnician, Integer virtualTechNumber) { try { // 批量生成唯一昵称 List nicknames = nicknameGenerator.batchGenerateUniqueNicknames(virtualTechNumber); log.info("成功生成{}个唯一昵称", nicknames.size()); // 批量创建虚拟技师对象 List virtualTechnicians = new ArrayList<>(virtualTechNumber); LocalDateTime now = LocalDateTime.now(); for (String nickname : nicknames) { TJs virtualJs = new TJs(); // 复制真实技师的所有字段(除ID、昵称、技师类型外) virtualJs.setcNickName(nickname); // 随机生成的昵称 virtualJs.setTechType(1); // 1-虚拟技师 virtualJs.setcName(realTechnician.getcName()); // 真实姓名(保持一致) virtualJs.setcPortrait(realTechnician.getcPortrait()); // 头像 virtualJs.setcPhone(realTechnician.getcPhone()); virtualJs.setcOpenId(realTechnician.getcOpenId()); virtualJs.setcAddress(realTechnician.getcAddress()); // 地址 virtualJs.setnSex(realTechnician.getnSex()); // 性别 virtualJs.setcImgList(realTechnician.getcImgList()); // 生活照列表 virtualJs.setcSfzImg(realTechnician.getcSfzImg()); // 身份证图片 virtualJs.setcVideo(realTechnician.getcVideo()); // 视频介绍 virtualJs.setcBhList(realTechnician.getcBhList()); // 可预约项目列表 virtualJs.setHealthCertificate(realTechnician.getHealthCertificate()); // 健康证 virtualJs.setBusinessLicense(realTechnician.getBusinessLicense()); // 营业执照 virtualJs.setCertification(realTechnician.getCertification()); // 资格证 virtualJs.setNoCrime(realTechnician.getNoCrime()); // 无犯罪记录 virtualJs.setDeptId(realTechnician.getDeptId()); // 部门ID virtualJs.setDaytimeMileage(realTechnician.getDaytimeMileage()); // 白天免车费公里数 virtualJs.setNigthMileage(realTechnician.getNigthMileage()); // 夜间免车费公里数 virtualJs.setcJianjie(realTechnician.getcJianjie()); // 个人简介 virtualJs.setnStar(MassageConstants.INTEGER_FIVE); // 评分 virtualJs.setnBili(realTechnician.getnBili()); // 佣金比例 virtualJs.setLongitude(realTechnician.getLongitude()); // 经度 virtualJs.setLatitude(realTechnician.getLatitude()); // 纬度 // 服务状态,默认:可服务 virtualJs.setnStatus(JsStatusEnum.JS_SERVICEABLE.getCode()); virtualJs.setnStatus2(JsStatusEnum.POST_NOT_ON_DUTY.getCode()); // 上岗状态 virtualJs.setnTong(JsStatusEnum.JS_NOT_PASS.getCode()); // 审核状态 virtualJs.setnB1(realTechnician.getnB1()); virtualJs.setnB2(realTechnician.getnB2()); virtualJs.setnB3(realTechnician.getnB3()); virtualJs.setDtCreateTime(now); // 创建时间 virtualJs.setVirtualOrderNumber(realTechnician.getVirtualOrderNumber()); // 虚拟订单数量 virtualJs.setVirtualTechNumber(0); // 虚拟技师数量(虚拟技师不再生成虚拟技师) virtualJs.setRecording(realTechnician.getRecording()); // 录音 virtualJs.setVideoRecording(realTechnician.getVideoRecording()); // 录像 virtualJs.setCommitment(realTechnician.getCommitment()); // 承诺书 virtualJs.setCity(realTechnician.getCity()); // 所在城市 virtualJs.setJsGrade(realTechnician.getJsGrade()); // 技师等级 virtualTechnicians.add(virtualJs); } // 批量保存虚拟技师 boolean saveResult = this.saveBatch(virtualTechnicians); if (saveResult) { log.info("成功批量保存{}个虚拟技师", virtualTechnicians.size()); } else { throw new ServiceException("批量保存虚拟技师失败"); } } catch (Exception e) { log.error("生成虚拟技师失败", e); throw new ServiceException("生成虚拟技师失败:" + e.getMessage()); } finally { // 清理昵称缓存,释放内存 nicknameGenerator.clearCache(); } } @Override public TJs getByJsId(String jsId, String openId) { if (jsId == null || jsId.trim().isEmpty()) { // 处理空或空白的jsId throw new ServiceException("Id为空"); } TJs js = this.getById(jsId); if (js == null) { // 处理getById返回null的情况 return null; // 或者返回一个新的TJs实例,具体看业务需求 } if (StringUtils.isBlank(js.getcBhList())) { // 处理js.getcBhList()返回null或空列表的情况 js.setProjects(new ArrayList<>()); // 设置空列表,避免后续调用空指针 return js; } //查询项目 List projectIds = Arrays.stream(js.getcBhList().split(",")).collect(Collectors.toList()); LambdaQueryWrapper xiangmuLambdaQueryWrapper = new LambdaQueryWrapper<>(); xiangmuLambdaQueryWrapper.in(TXiangmu::getcId, projectIds).orderByAsc(TXiangmu::getdPrice); List projects = xiangmuService.list(xiangmuLambdaQueryWrapper); js.setProjects(projects); //查询评论 LambdaQueryWrapper commentLambdaQueryWrapper = new LambdaQueryWrapper<>(); commentLambdaQueryWrapper.eq(TComment::getcJsid, jsId); List list = commentService.list(commentLambdaQueryWrapper); if (CollectionUtil.isNotEmpty(list)) { js.setComments(list); } //是否收藏 LambdaQueryWrapper objectLambdaQueryWrapper = new LambdaQueryWrapper<>(); objectLambdaQueryWrapper.eq(TCollect::getcJsId, jsId).eq(TCollect::getcWxOpenId, openId); TCollect collect = collectService.getOne(objectLambdaQueryWrapper); if (collect != null) { js.setIsCollection(true); } else { js.setIsCollection(false); } return js; } private static void initialization(TJs js) { // 评分默认最高 5 js.setnStar(MassageConstants.INTEGER_FIVE); // 已服务数量 0 js.setnNum(MassageConstants.INTEGER_ZERO); // 佣金比例 10 js.setnBili(MassageConstants.INTEGER_TEN); // 服务状态 js.setnStatus(JsStatusEnum.JS_NO_SERVICE.getCode()); // 上岗状态 js.setnStatus2(JsStatusEnum.POST_NOT_ON_DUTY.getCode()); // 审核状态 js.setnTong(JsStatusEnum.JS_NOT_PASS.getCode()); js.setnB1(MassageConstants.INTEGER_ZERO); js.setnB2(MassageConstants.INTEGER_ZERO); js.setnB3(MassageConstants.INTEGER_ZERO); js.setTechType(MassageConstants.INTEGER_ZERO); js.setDtCreateTime(LocalDateTime.now()); } }