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