TechnicianMomentServiceImpl.java 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. package com.ylx.massage.service.impl;
  2. import cn.hutool.core.collection.CollUtil;
  3. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  5. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  6. import com.ylx.common.exception.ServiceException;
  7. import com.ylx.massage.domain.MaTechnician;
  8. import com.ylx.massage.domain.MomentMedia;
  9. import com.ylx.massage.domain.TJs;
  10. import com.ylx.massage.domain.TechnicianMoment;
  11. import com.ylx.massage.domain.dto.EditMomentDTO;
  12. import com.ylx.massage.domain.dto.MomentAuditDTO;
  13. import com.ylx.massage.domain.dto.MomentManageQueryDTO;
  14. import com.ylx.massage.domain.dto.PublishMomentDTO;
  15. import com.ylx.massage.domain.vo.*;
  16. import com.ylx.massage.mapper.MaTechnicianMapper;
  17. import com.ylx.massage.mapper.MomentMediaMapper;
  18. import com.ylx.massage.mapper.TJsMapper;
  19. import com.ylx.massage.mapper.TechnicianMomentMapper;
  20. import com.ylx.massage.service.ITechnicianMomentService;
  21. import com.ylx.merchant.domain.dto.MerchantMomentDTO;
  22. import com.ylx.merchant.domain.vo.MomentVO;
  23. import lombok.extern.slf4j.Slf4j;
  24. import org.apache.commons.lang3.StringUtils;
  25. import org.springframework.beans.BeanUtils;
  26. import org.springframework.beans.factory.annotation.Autowired;
  27. import org.springframework.stereotype.Service;
  28. import org.springframework.transaction.annotation.Transactional;
  29. import java.math.BigDecimal;
  30. import java.time.LocalDateTime;
  31. import java.util.*;
  32. import java.util.stream.Collectors;
  33. /**
  34. * 技师动态服务实现类
  35. */
  36. @Service
  37. @Slf4j
  38. public class TechnicianMomentServiceImpl extends ServiceImpl<TechnicianMomentMapper, TechnicianMoment> implements ITechnicianMomentService {
  39. @Autowired
  40. private TechnicianMomentMapper momentMapper;
  41. @Autowired
  42. private MomentMediaMapper mediaMapper;
  43. @Autowired
  44. private TJsMapper tJsMapper;
  45. @Autowired
  46. private MaTechnicianMapper maTechnicianMapper;
  47. /**
  48. * 查询推荐动态列表(不分地区,按日期倒序,同一天按浏览量倒序)
  49. *
  50. * @param pageNum 页码,默认1
  51. * @param pageSize 每页数量,默认10
  52. * @return R<List < MomentListVO>> 动态列表
  53. */
  54. @Override
  55. public List<MomentListVO> getRecommendedMoments(Integer pageNum, Integer pageSize) {
  56. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  57. List<TechnicianMoment> moments = momentMapper.selectRecommendedMoments(page);
  58. if (moments == null || moments.isEmpty()) {
  59. return new ArrayList<>();
  60. }
  61. // 查询技师信息
  62. List<Integer> technicianIds = moments.stream().map(TechnicianMoment::getTechnicianId).collect(Collectors.toList());
  63. Map<Integer, MaTechnician> technicianMap = getTechnicianMap(technicianIds);
  64. // 组装VO
  65. return moments.stream().map(moment -> {
  66. MomentListVO vo = new MomentListVO();
  67. BeanUtils.copyProperties(moment, vo);
  68. MaTechnician technician = technicianMap.get(moment.getTechnicianId());
  69. if (technician != null) {
  70. vo.setTechnicianNickName(technician.getTeNickName());
  71. vo.setTechnicianAvatar(technician.getTeAvatar());
  72. vo.setTechnicianStatus(getTechnicianStatus(technician));
  73. }
  74. return vo;
  75. }).collect(Collectors.toList());
  76. }
  77. /**
  78. * 查询同城动态列表(按城市和发布时间倒序)
  79. */
  80. @Override
  81. public List<MomentListVO> getSameCityMoments(String cityCode, Integer pageNum, Integer pageSize) {
  82. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  83. List<TechnicianMoment> moments = momentMapper.selectSameCityMoments(page, cityCode);
  84. if (moments == null || moments.isEmpty()) {
  85. return new ArrayList<>();
  86. }
  87. // 查询技师信息
  88. List<Integer> technicianIds = moments.stream().map(TechnicianMoment::getTechnicianId).collect(Collectors.toList());
  89. Map<Integer, MaTechnician> technicianMap = getTechnicianMap(technicianIds);
  90. // 组装VO
  91. return moments.stream().map(moment -> {
  92. MomentListVO vo = new MomentListVO();
  93. BeanUtils.copyProperties(moment, vo);
  94. MaTechnician technician = technicianMap.get(moment.getTechnicianId());
  95. if (technician != null) {
  96. vo.setTechnicianNickName(technician.getTeNickName());
  97. vo.setTechnicianAvatar(technician.getTeAvatar());
  98. vo.setTechnicianStatus(getTechnicianStatus(technician));
  99. }
  100. return vo;
  101. }).collect(Collectors.toList());
  102. }
  103. /**
  104. * 查询附近动态列表(10km内,按日期倒序,同一天按距离排序)
  105. *
  106. * @param longitude 经度
  107. * @param latitude 纬度
  108. * @param pageNum 页码,默认1
  109. * @param pageSize 每页数量,默认10
  110. * @return R<List < MomentListVO>> 动态列表
  111. */
  112. @Override
  113. public List<MomentListVO> getNearbyMoments(BigDecimal longitude, BigDecimal latitude, Integer pageNum, Integer pageSize) {
  114. if (latitude == null || longitude == null) {
  115. throw new ServiceException("地理位置信息不能为空");
  116. }
  117. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  118. List<TechnicianMoment> moments = momentMapper.selectNearbyMoments(page, longitude, latitude);
  119. if (moments == null || moments.isEmpty()) {
  120. return new ArrayList<>();
  121. }
  122. // 查询技师信息
  123. List<Integer> technicianIds = moments.stream().map(TechnicianMoment::getTechnicianId).collect(Collectors.toList());
  124. Map<Integer, MaTechnician> technicianMap = getTechnicianMap(technicianIds);
  125. // 组装VO(需要计算距离)
  126. return moments.stream().map(moment -> {
  127. MomentListVO vo = new MomentListVO();
  128. BeanUtils.copyProperties(moment, vo);
  129. MaTechnician technician = technicianMap.get(moment.getTechnicianId());
  130. if (technician != null) {
  131. vo.setTechnicianNickName(technician.getTeNickName());
  132. vo.setTechnicianAvatar(technician.getTeAvatar());
  133. vo.setTechnicianStatus(getTechnicianStatus(technician));
  134. // 计算距离
  135. if (moment.getLatitude() != null && moment.getLongitude() != null) {
  136. double distance = calculateDistance(
  137. latitude.doubleValue(), longitude.doubleValue(),
  138. moment.getLatitude().doubleValue(), moment.getLongitude().doubleValue()
  139. );
  140. vo.setDistance(Math.round(distance * 100.0) / 100.0); // 保留两位小数
  141. }
  142. }
  143. return vo;
  144. }).collect(Collectors.toList());
  145. }
  146. /**
  147. * 查询动态详情(浏览量+1)
  148. *
  149. * @param momentId 动态ID
  150. * @param longitude 用户经度
  151. * @param latitude 用户纬度
  152. * @return MomentDetailVO 动态详情
  153. */
  154. @Override
  155. @Transactional(rollbackFor = Exception.class)
  156. public MomentDetailVO getMomentDetail(Long momentId, BigDecimal longitude, BigDecimal latitude) {
  157. // 查询动态信息
  158. TechnicianMoment moment = momentMapper.selectById(momentId);
  159. if (moment == null) {
  160. throw new ServiceException("动态不存在");
  161. }
  162. // 增加浏览量
  163. momentMapper.incrementViewCount(momentId);
  164. moment.setViewCount(moment.getViewCount() + 1);
  165. // 查询商户信息
  166. MaTechnician technician = maTechnicianMapper.selectById(moment.getTechnicianId());
  167. // 查询媒体列表
  168. List<MomentMedia> mediaList = mediaMapper.selectMediaListByMomentId(momentId);
  169. List<MomentMediaVO> mediaVOList = mediaList.stream().map(media -> {
  170. MomentMediaVO mediaVO = new MomentMediaVO();
  171. BeanUtils.copyProperties(media, mediaVO);
  172. return mediaVO;
  173. }).collect(Collectors.toList());
  174. // 组装VO
  175. MomentDetailVO vo = new MomentDetailVO();
  176. BeanUtils.copyProperties(moment, vo);
  177. if (technician != null) {
  178. vo.setTechnicianNickName(technician.getTeNickName());
  179. vo.setTechnicianAvatar(technician.getTeAvatar());
  180. vo.setTechnicianStatus(getTechnicianStatus(technician));
  181. }
  182. vo.setMediaList(mediaVOList);
  183. // 计算距离(如果有位置信息)
  184. if (moment.getLatitude() != null && moment.getLongitude() != null) {
  185. if(latitude != null && longitude != null){
  186. // 传入用户位置,计算用户与技师之间的距离
  187. double distance = calculateDistance(
  188. latitude.doubleValue(), longitude.doubleValue(),
  189. moment.getLatitude().doubleValue(), moment.getLongitude().doubleValue()
  190. );
  191. vo.setDistance(Math.round(distance * 100.0) / 100.0); // 保留两位小数
  192. }
  193. }
  194. return vo;
  195. }
  196. /**
  197. * 批量查询技师信息
  198. *
  199. * @param technicianIds 技师ID列表
  200. * @return Map<String, TJs> 技师ID-技师实体映射
  201. */
  202. private Map<Integer, MaTechnician> getTechnicianMap(List<Integer> technicianIds) {
  203. return maTechnicianMapper.selectBatchIds(technicianIds).stream().collect(Collectors.toMap(MaTechnician::getId, t -> t));
  204. }
  205. /**
  206. * 获取技师状态
  207. * 1-可服务(当前时间技师空闲)
  208. * 2-可预约(当前时间技师"已接单-服务中")
  209. */
  210. private Integer getTechnicianStatus(MaTechnician technician) {
  211. if (technician.getServiceState() != null && technician.getServiceState() == 1) {
  212. return 1; // 可服务
  213. } else if (technician.getServiceState() != null && technician.getServiceState() == 0) {
  214. return 2; // 可预约(服务中)
  215. }
  216. return null;
  217. }
  218. /**
  219. * 计算两点之间的距离(单位:km)
  220. *
  221. * @param lat1 纬度1(用户纬度)
  222. * @param lon1 经度1(用户经度)
  223. * @param lat2 纬度2(技师纬度)
  224. * @param lon2 经度2(技师经度)
  225. * @return double 距离(单位:km)
  226. * <p>
  227. * 使用Haversine公式
  228. */
  229. private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
  230. final double R = 6371; // 地球半径,单位km
  231. double dLat = Math.toRadians(lat2 - lat1);
  232. double dLon = Math.toRadians(lon2 - lon1);
  233. double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  234. Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
  235. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  236. double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  237. return R * c;
  238. }
  239. /**
  240. * 发布动态或保存草稿
  241. *
  242. * @param dto 发布动态请求对象
  243. * @param openId 技师OpenID
  244. * @return Long 动态ID
  245. */
  246. @Override
  247. @Transactional(rollbackFor = Exception.class)
  248. public Long publishMoment(PublishMomentDTO dto, String openId) {
  249. // 根据openId查询技师信息
  250. LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
  251. queryWrapper.eq(MaTechnician::getCOpenid, openId);
  252. MaTechnician technician = maTechnicianMapper.selectOne(queryWrapper);
  253. if (technician == null) {
  254. throw new ServiceException("商户信息不存在");
  255. }
  256. //查询是否保存为草稿
  257. Integer isDraft = dto.getIsDraft();
  258. if (isDraft == 1) {
  259. //只校验标题
  260. if (dto.getTitle() == null || dto.getTitle().trim().isEmpty()) {
  261. throw new ServiceException("动态标题不能为空");
  262. }
  263. } else {
  264. // 校验动态标题
  265. if (dto.getTitle() == null || dto.getTitle().trim().isEmpty()) {
  266. throw new ServiceException("动态标题不能为空");
  267. }
  268. // 校验动态内容
  269. if (StringUtils.isBlank(dto.getContent())) {
  270. throw new ServiceException("动态内容不能为空");
  271. }
  272. // 校验媒体类型和媒体文件
  273. if (dto.getMediaType() == null) {
  274. throw new ServiceException("媒体类型不能为空");
  275. }
  276. if (dto.getMediaType() == 1) {
  277. // 图片类型
  278. if (dto.getImageUrls() == null || dto.getImageUrls().isEmpty()) {
  279. throw new ServiceException("图片不能为空");
  280. }
  281. if (dto.getImageUrls().size() > 9) {
  282. throw new ServiceException("最多只能上传9张图片");
  283. }
  284. } else if (dto.getMediaType() == 2) {
  285. // 视频类型
  286. if (StringUtils.isBlank(dto.getVideoUrl())) {
  287. throw new ServiceException("视频不能为空");
  288. }
  289. } else {
  290. throw new ServiceException("媒体类型不正确");
  291. }
  292. }
  293. // 创建动态对象
  294. TechnicianMoment moment = new TechnicianMoment();
  295. moment.setTechnicianId(technician.getId());
  296. moment.setTitle(dto.getTitle());
  297. moment.setContent(dto.getContent());
  298. moment.setMediaType(dto.getMediaType());
  299. moment.setLatitude(dto.getLatitude());
  300. moment.setLongitude(dto.getLongitude());
  301. moment.setLocation(dto.getLocation());
  302. moment.setAddress(dto.getAddress());
  303. if(Objects.nonNull(dto.getMediaType())){
  304. // 设置封面图
  305. if (dto.getMediaType() == 1) {
  306. // 图片类型,默认取第一张作为封面
  307. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  308. moment.setCoverUrl(dto.getImageUrls().get(0));
  309. }
  310. } else if (dto.getMediaType() == 2) {
  311. // 视频类型,使用视频URL作为封面
  312. moment.setCoverUrl(dto.getVideoCoverUrl());
  313. }
  314. }
  315. // 设置审核状态和发布时间
  316. if (dto.getIsDraft() != null && dto.getIsDraft() == 1) {
  317. // 保存草稿
  318. moment.setAuditStatus(0);
  319. moment.setPublishTime(null);
  320. } else {
  321. // 立即发布,进入待审核状态
  322. moment.setAuditStatus(1);
  323. moment.setPublishTime(LocalDateTime.now());
  324. }
  325. // 设置初始状态
  326. moment.setIsDelete(0);
  327. moment.setViewCount(0);
  328. moment.setCreateTime(LocalDateTime.now());
  329. moment.setUpdateTime(LocalDateTime.now());
  330. // 8. 保存动态到数据库
  331. int insertResult = momentMapper.insert(moment);
  332. if (insertResult <= 0) {
  333. throw new ServiceException("发布动态失败");
  334. }
  335. Long momentId = moment.getId();
  336. // 9. 保存媒体信息到媒体表
  337. if(Objects.nonNull(dto.getMediaType())){
  338. if (dto.getMediaType() == 1) {
  339. // 保存图片
  340. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  341. for (int i = 0; i < dto.getImageUrls().size(); i++) {
  342. MomentMedia media = new MomentMedia();
  343. media.setMomentId(momentId);
  344. media.setMediaUrl(dto.getImageUrls().get(i));
  345. media.setMediaType(1); // 图片
  346. media.setSortOrder(i + 1);
  347. media.setCreateTime(LocalDateTime.now());
  348. //获取文件的格式
  349. String fileFormat = dto.getImageUrls().get(i).substring(dto.getImageUrls().get(i).lastIndexOf(".") + 1);
  350. media.setFileFormat(fileFormat);
  351. mediaMapper.insert(media);
  352. }
  353. }
  354. } else if (dto.getMediaType() == 2) {
  355. // 保存视频
  356. MomentMedia media = new MomentMedia();
  357. media.setMomentId(momentId);
  358. media.setMediaUrl(dto.getVideoUrl());
  359. media.setMediaType(2); // 视频
  360. media.setSortOrder(1);
  361. media.setCreateTime(LocalDateTime.now());
  362. mediaMapper.insert(media);
  363. }
  364. }
  365. log.info("发布动态成功,动态ID:{},技师ID:{},是否草稿:{}", momentId, technician.getId(), dto.getIsDraft());
  366. return momentId;
  367. }
  368. /**
  369. * 编辑动态
  370. *
  371. * @param dto 编辑动态请求对象
  372. * @param openId 技师OpenID
  373. * @return Long 动态ID
  374. */
  375. @Override
  376. @Transactional(rollbackFor = Exception.class)
  377. public Long editMoment(EditMomentDTO dto, String openId) {
  378. // 1. 参数校验
  379. if (dto == null) {
  380. throw new ServiceException("编辑动态信息不能为空");
  381. }
  382. if (openId == null || openId.trim().isEmpty()) {
  383. throw new ServiceException("openId不能为空");
  384. }
  385. if (dto.getMomentId() == null) {
  386. throw new ServiceException("动态ID不能为空");
  387. }
  388. // 2. 根据openId查询商户信息
  389. LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
  390. queryWrapper.eq(MaTechnician::getCOpenid, openId);
  391. MaTechnician technician = maTechnicianMapper.selectOne(queryWrapper);
  392. if (technician == null) {
  393. throw new ServiceException("商户信息不存在");
  394. }
  395. // 3. 查询动态信息
  396. TechnicianMoment moment = momentMapper.selectById(dto.getMomentId());
  397. if (moment == null) {
  398. throw new ServiceException("动态不存在");
  399. }
  400. // 4. 验证动态是否属于当前商户
  401. if (!moment.getTechnicianId().equals(technician.getId())) {
  402. throw new ServiceException("无权编辑此动态");
  403. }
  404. // 5. 验证动态状态是否允许编辑(只能编辑草稿和审核拒绝的动态)
  405. if (moment.getAuditStatus() == null || (moment.getAuditStatus() != 0 && moment.getAuditStatus() != 3)) {
  406. throw new ServiceException("只能编辑草稿或审核拒绝的动态");
  407. }
  408. // 6. 校验媒体类型和媒体文件
  409. if (dto.getMediaType() == null) {
  410. throw new ServiceException("媒体类型不能为空");
  411. }
  412. if (dto.getMediaType() == 1) {
  413. // 图片类型
  414. if (dto.getImageUrls() == null || dto.getImageUrls().isEmpty()) {
  415. throw new ServiceException("图片不能为空");
  416. }
  417. if (dto.getImageUrls().size() > 9) {
  418. throw new ServiceException("最多只能上传9张图片");
  419. }
  420. } else if (dto.getMediaType() == 2) {
  421. // 视频类型
  422. if (dto.getVideoUrl() == null || dto.getVideoUrl().trim().isEmpty()) {
  423. throw new ServiceException("视频不能为空");
  424. }
  425. } else {
  426. throw new ServiceException("媒体类型不正确");
  427. }
  428. // 7. 更新动态对象
  429. moment.setTitle(dto.getTitle());
  430. moment.setContent(dto.getContent());
  431. moment.setMediaType(dto.getMediaType());
  432. moment.setLongitude(dto.getLongitude());
  433. moment.setLatitude(dto.getLatitude());
  434. moment.setLocation(dto.getLocation());
  435. moment.setAddress(dto.getAddress());
  436. // 8.设置封面图
  437. if (dto.getMediaType() == 1) {
  438. // 图片类型,默认取第一张作为封面
  439. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  440. moment.setCoverUrl(dto.getImageUrls().get(0));
  441. }
  442. } else if (dto.getMediaType() == 2) {
  443. // 视频类型,使用视频封面URL(如果提供了)或视频URL作为封面
  444. if (dto.getVideoCoverUrl() != null && !dto.getVideoCoverUrl().trim().isEmpty()) {
  445. moment.setCoverUrl(dto.getVideoCoverUrl());
  446. } else {
  447. moment.setCoverUrl(dto.getVideoUrl());
  448. }
  449. }
  450. // 9. 设置审核状态和发布时间
  451. if (dto.getIsDraft() != null && dto.getIsDraft() == 1) {
  452. // 保存草稿
  453. moment.setAuditStatus(0); // 草稿
  454. moment.setPublishTime(null);
  455. moment.setRejectReason(null); // 清空拒绝原因
  456. } else {
  457. // 提交发布,进入待审核状态
  458. moment.setAuditStatus(1);
  459. if (moment.getPublishTime() == null) {
  460. moment.setPublishTime(LocalDateTime.now());
  461. }
  462. moment.setRejectReason(null); // 清空拒绝原因
  463. }
  464. moment.setUpdateTime(LocalDateTime.now());
  465. // 10. 更新动态到数据库
  466. int updateResult = momentMapper.updateById(moment);
  467. if (updateResult <= 0) {
  468. throw new ServiceException("编辑动态失败");
  469. }
  470. Long momentId = moment.getId();
  471. // 11. 删除旧的媒体信息
  472. LambdaQueryWrapper<MomentMedia> mediaWrapper = new LambdaQueryWrapper<>();
  473. mediaWrapper.eq(MomentMedia::getMomentId, momentId);
  474. mediaMapper.delete(mediaWrapper);
  475. // 12. 保存新的媒体信息到媒体表
  476. if (dto.getMediaType() == 1) {
  477. // 保存图片
  478. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  479. for (int i = 0; i < dto.getImageUrls().size(); i++) {
  480. MomentMedia media = new MomentMedia();
  481. media.setMomentId(momentId);
  482. media.setMediaUrl(dto.getImageUrls().get(i));
  483. media.setMediaType(1); // 图片
  484. media.setSortOrder(i + 1);
  485. media.setCreateTime(LocalDateTime.now());
  486. // 获取文件的格式
  487. String fileFormat = dto.getImageUrls().get(i).substring(dto.getImageUrls().get(i).lastIndexOf(".") + 1);
  488. media.setFileFormat(fileFormat);
  489. mediaMapper.insert(media);
  490. }
  491. }
  492. } else if (dto.getMediaType() == 2) {
  493. // 保存视频
  494. MomentMedia media = new MomentMedia();
  495. media.setMomentId(momentId);
  496. media.setMediaUrl(dto.getVideoUrl());
  497. media.setMediaType(2); // 视频
  498. media.setSortOrder(1);
  499. media.setCreateTime(LocalDateTime.now());
  500. mediaMapper.insert(media);
  501. }
  502. log.info("编辑动态成功,动态ID:{},技师ID:{},是否草稿:{}", momentId, technician.getId(), dto.getIsDraft());
  503. return momentId;
  504. }
  505. /**
  506. * 查询草稿箱列表
  507. *
  508. * @param openId 技师OpenID
  509. * @param pageNum 页码
  510. * @param pageSize 每页数量
  511. * @return List<MomentListVO> 草稿列表
  512. */
  513. @Override
  514. public Page<MomentListVO> getDraftMoments(String openId, Integer pageNum, Integer pageSize) {
  515. if (openId == null || openId.trim().isEmpty()) {
  516. throw new ServiceException("openId不能为空");
  517. }
  518. // 1. 根据openId查询技师信息
  519. LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
  520. queryWrapper.eq(MaTechnician::getCOpenid, openId);
  521. MaTechnician technician = maTechnicianMapper.selectOne(queryWrapper);
  522. if (technician == null) {
  523. throw new ServiceException("商户信息不存在");
  524. }
  525. // 2. 查询该商户的草稿列表
  526. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  527. LambdaQueryWrapper<TechnicianMoment> momentWrapper = new LambdaQueryWrapper<>();
  528. momentWrapper.eq(TechnicianMoment::getTechnicianId, technician.getId())
  529. .eq(TechnicianMoment::getAuditStatus, 0) // 草稿状态
  530. .orderByDesc(TechnicianMoment::getCreateTime);
  531. Page<TechnicianMoment> momentPage = momentMapper.selectPage(page, momentWrapper);
  532. List<TechnicianMoment> moments = momentPage.getRecords();
  533. Page<MomentListVO> voPage = new Page<>(pageNum, pageSize);
  534. if (moments == null || moments.isEmpty()) {
  535. // 草稿列表为空时,返回空Page对象
  536. return voPage;
  537. }
  538. // 3. 组装VO
  539. List<MomentListVO> collect = moments.stream().map(moment -> {
  540. MomentListVO vo = new MomentListVO();
  541. BeanUtils.copyProperties(moment, vo);
  542. vo.setTechnicianNickName(technician.getTeNickName());
  543. vo.setTechnicianAvatar(technician.getTeAvatar());
  544. vo.setTechnicianStatus(getTechnicianStatus(technician));
  545. // 草稿的发布时间使用创建时间
  546. vo.setPublishTime(moment.getCreateTime());
  547. return vo;
  548. }).collect(Collectors.toList());
  549. //返回Page对象
  550. voPage.setTotal(momentPage.getTotal());
  551. voPage.setRecords(collect);
  552. return voPage;
  553. }
  554. /**
  555. * 查询我的动态列表(已发布的动态:审核中+审核通过)
  556. *
  557. * @param openId 技师OpenID
  558. * @param pageNum 页码
  559. * @param pageSize 每页数量
  560. * @return Page<MyMomentVO> 我的动态列表
  561. */
  562. @Override
  563. public Page<MyMomentVO> getMyMoments(String openId, Integer pageNum, Integer pageSize) {
  564. // 1. 参数校验
  565. if (openId == null || openId.trim().isEmpty()) {
  566. throw new ServiceException("openId不能为空");
  567. }
  568. // 2. 根据openId查询技师信息
  569. LambdaQueryWrapper<TJs> queryWrapper = new LambdaQueryWrapper<>();
  570. queryWrapper.eq(TJs::getcOpenId, openId);
  571. TJs technician = tJsMapper.selectOne(queryWrapper);
  572. if (technician == null) {
  573. throw new ServiceException("技师信息不存在");
  574. }
  575. // 3. 查询该技师的已发布动态(待审核、审核通过)
  576. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  577. LambdaQueryWrapper<TechnicianMoment> momentWrapper = new LambdaQueryWrapper<>();
  578. momentWrapper.eq(TechnicianMoment::getTechnicianId, technician.getId())
  579. .in(TechnicianMoment::getAuditStatus, 1, 2) // 1-待审核, 2-审核通过
  580. .orderByDesc(TechnicianMoment::getPublishTime);
  581. Page<TechnicianMoment> momentPage = momentMapper.selectPage(page, momentWrapper);
  582. // 4. 组装分页返回结果
  583. Page<MyMomentVO> voPage = new Page<>(pageNum, pageSize);
  584. voPage.setTotal(momentPage.getTotal());
  585. voPage.setCurrent(momentPage.getCurrent());
  586. voPage.setSize(momentPage.getSize());
  587. List<TechnicianMoment> moments = momentPage.getRecords();
  588. if (moments == null || moments.isEmpty()) {
  589. voPage.setRecords(new ArrayList<>());
  590. return voPage;
  591. }
  592. // 5. 组装VO列表
  593. List<MyMomentVO> voList = moments.stream().map(moment -> {
  594. MyMomentVO vo = new MyMomentVO();
  595. vo.setId(moment.getId());
  596. vo.setTitle(moment.getTitle());
  597. vo.setContent(moment.getContent());
  598. vo.setAuditStatus(moment.getAuditStatus());
  599. vo.setCoverUrl(moment.getCoverUrl());
  600. vo.setPublishTime(moment.getPublishTime());
  601. vo.setViewCount(moment.getViewCount());
  602. return vo;
  603. }).collect(Collectors.toList());
  604. voPage.setRecords(voList);
  605. log.info("查询我的动态列表成功,技师ID:{},页码:{},每页数量:{},总记录数:{}", technician.getId(), pageNum, pageSize, voPage.getTotal());
  606. return voPage;
  607. }
  608. /**
  609. * 根据技师ID查询动态列表(已发布的动态:审核中+审核通过)
  610. *
  611. * @param technicianId 技师ID
  612. * @return List<MomentListVO> 动态列表
  613. */
  614. @Override
  615. public List<MomentListVO> getMomentsByTechnicianId(String technicianId) {
  616. // 1. 参数校验
  617. if (technicianId == null || technicianId.trim().isEmpty()) {
  618. throw new ServiceException("技师ID不能为空");
  619. }
  620. // 2. 查询该技师的已发布动态(待审核、审核通过)
  621. LambdaQueryWrapper<TechnicianMoment> momentWrapper = new LambdaQueryWrapper<>();
  622. momentWrapper.eq(TechnicianMoment::getTechnicianId, technicianId)
  623. .in(TechnicianMoment::getAuditStatus, 1, 2) // 1-待审核, 2-审核通过
  624. .orderByDesc(TechnicianMoment::getPublishTime);
  625. List<TechnicianMoment> moments = momentMapper.selectList(momentWrapper);
  626. if (moments == null || moments.isEmpty()) {
  627. return new ArrayList<>();
  628. }
  629. // 3. 组装VO列表
  630. List<MomentListVO> voList = moments.stream().map(moment -> {
  631. MomentListVO vo = new MomentListVO();
  632. vo.setId(moment.getId());
  633. vo.setTitle(moment.getTitle());
  634. vo.setAuditStatus(moment.getAuditStatus());
  635. vo.setCoverUrl(moment.getCoverUrl());
  636. vo.setPublishTime(moment.getPublishTime());
  637. vo.setViewCount(moment.getViewCount());
  638. return vo;
  639. }).collect(Collectors.toList());
  640. log.info("根据技师ID查询动态列表成功,技师ID:{},记录数:{}", technicianId, voList.size());
  641. return voList;
  642. }
  643. /**
  644. * 查询待审核动态列表(管理后台)
  645. *
  646. * @param queryDTO 查询参数
  647. * @return Page<MomentManageVO> 分页结果
  648. */
  649. @Override
  650. public Page<MomentManageVO> getMomentManageList(MomentManageQueryDTO queryDTO) {
  651. // 1. 参数处理
  652. Integer pageNum = queryDTO.getPageNum() != null ? queryDTO.getPageNum() : 1;
  653. Integer pageSize = queryDTO.getPageSize() != null ? queryDTO.getPageSize() : 10;
  654. String technicianName = queryDTO.getTechnicianName();
  655. Integer auditStatus = queryDTO.getAuditStatus();
  656. String startTime = queryDTO.getStartTime();
  657. if (StringUtils.isNotBlank(startTime)) {
  658. startTime = startTime + " 00:00:00";
  659. }
  660. String endTime = queryDTO.getEndTime();
  661. if (StringUtils.isNotBlank(endTime)) {
  662. endTime = endTime + " 23:59:59";
  663. }
  664. // 2. 构建查询条件
  665. LambdaQueryWrapper<TechnicianMoment> queryWrapper = new LambdaQueryWrapper<>();
  666. // 如果传入了审核状态,则按照审核状态查询
  667. if (auditStatus != null) {
  668. queryWrapper.eq(TechnicianMoment::getAuditStatus, auditStatus);
  669. } else {
  670. // 默认查询全部的动态(待审核、审核通过、审核拒绝)
  671. queryWrapper.in(TechnicianMoment::getAuditStatus, 1, 2, 3);
  672. }
  673. // 技师姓名查询
  674. if (StringUtils.isNotBlank(technicianName)) {
  675. //根据技师姓名查询技师的ID
  676. LambdaQueryWrapper<MaTechnician> jsQueryWrapper = new LambdaQueryWrapper<>();
  677. jsQueryWrapper.like(MaTechnician::getTeName, technicianName);
  678. List<MaTechnician> technicians = maTechnicianMapper.selectList(jsQueryWrapper);
  679. if (CollUtil.isNotEmpty(technicians)) {
  680. // 构建查询条件,查询所有匹配商户的动态
  681. queryWrapper.in(TechnicianMoment::getTechnicianId, technicians.stream().map(MaTechnician::getId).collect(Collectors.toList()));
  682. } else {
  683. // 没有匹配的商户,查询空字符串列表
  684. queryWrapper.in(TechnicianMoment::getTechnicianId, Collections.singleton(""));
  685. }
  686. }
  687. // 时间范围查询
  688. if (startTime != null && !startTime.trim().isEmpty()) {
  689. queryWrapper.ge(TechnicianMoment::getPublishTime, startTime);
  690. }
  691. if (endTime != null && !endTime.trim().isEmpty()) {
  692. queryWrapper.le(TechnicianMoment::getPublishTime, endTime);
  693. }
  694. // 按创建时间倒序排序
  695. queryWrapper.orderByDesc(TechnicianMoment::getCreateTime);
  696. // 3. 分页查询
  697. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  698. Page<TechnicianMoment> momentPage = momentMapper.selectPage(page, queryWrapper);
  699. // 4. 组装VO
  700. Page<MomentManageVO> voPage = new Page<>(pageNum, pageSize);
  701. voPage.setTotal(momentPage.getTotal());
  702. voPage.setCurrent(momentPage.getCurrent());
  703. voPage.setSize(momentPage.getSize());
  704. List<TechnicianMoment> moments = momentPage.getRecords();
  705. if (moments == null || moments.isEmpty()) {
  706. voPage.setRecords(new ArrayList<>());
  707. return voPage;
  708. }
  709. // 5. 批量查询技师信息
  710. List<Integer> technicianIds = moments.stream().map(TechnicianMoment::getTechnicianId).collect(Collectors.toList());
  711. Map<Integer, MaTechnician> technicianMap = getTechnicianMap(technicianIds);
  712. // 6. 组装VO列表
  713. List<MomentManageVO> voList = moments.stream().map(moment -> {
  714. MomentManageVO vo = new MomentManageVO();
  715. BeanUtils.copyProperties(moment, vo);
  716. // 设置技师信息
  717. MaTechnician technician = technicianMap.get(moment.getTechnicianId());
  718. if (technician != null) {
  719. // 设置技师姓名
  720. vo.setTechnicianName(technician.getTeName());
  721. // 设置技师昵称
  722. vo.setTechnicianNickname(technician.getTeNickName());
  723. }
  724. return vo;
  725. }).collect(Collectors.toList());
  726. voPage.setRecords(voList);
  727. log.info("查询动态管理列表,页码:{},每页数量:{},总记录数:{}", pageNum, pageSize, voPage.getTotal());
  728. return voPage;
  729. }
  730. /**
  731. * 审核动态(通过/拒绝)
  732. *
  733. * @param momentId 动态ID
  734. * @param auditStatus 审核状态:2-通过,3-拒绝
  735. * @param rejectReason 拒绝原因(拒绝时必填)
  736. * @return Boolean 是否成功
  737. */
  738. @Override
  739. @Transactional(rollbackFor = Exception.class)
  740. public Boolean auditMoment(MomentAuditDTO dto) {
  741. // 1. 参数校验
  742. if (dto.getMomentId() == null) {
  743. throw new ServiceException("动态ID不能为空");
  744. }
  745. if (dto.getAuditStatus() == null || (dto.getAuditStatus() != 2 && dto.getAuditStatus() != 3)) {
  746. throw new ServiceException("审核状态不正确");
  747. }
  748. if (dto.getAuditStatus() == 3 && (dto.getRejectReason() == null || dto.getRejectReason().trim().isEmpty())) {
  749. throw new ServiceException("拒绝时必须填写拒绝原因");
  750. }
  751. // 2. 查询动态信息
  752. TechnicianMoment moment = momentMapper.selectById(dto.getMomentId());
  753. if (moment == null) {
  754. throw new ServiceException("动态不存在");
  755. }
  756. // 3. 校验动态状态
  757. if (moment.getAuditStatus() != 1) {
  758. throw new ServiceException("只能审核待审核状态的动态");
  759. }
  760. // 4. 更新审核状态
  761. moment.setAuditStatus(dto.getAuditStatus());
  762. // 审核通过默认上架状态和推荐状态
  763. if(dto.getAuditStatus()==2){
  764. moment.setIsRecommend(1);
  765. moment.setIsList(1);
  766. }
  767. moment.setRejectReason(dto.getRejectReason());
  768. moment.setUpdateTime(LocalDateTime.now());
  769. int updateResult = momentMapper.updateById(moment);
  770. if (updateResult <= 0) {
  771. throw new ServiceException("审核失败");
  772. }
  773. return true;
  774. }
  775. /**
  776. * 更新动态推荐状态
  777. *
  778. * @param momentId 动态ID
  779. * @param isRecommend 是否推荐:0-否,1-是
  780. * @return 是否成功
  781. */
  782. @Override
  783. @Transactional(rollbackFor = Exception.class)
  784. public Boolean updateRecommendStatus(Long momentId, Integer isRecommend) {
  785. if (momentId == null) {
  786. throw new ServiceException("动态ID不能为空");
  787. }
  788. if (isRecommend == null || (isRecommend != 0 && isRecommend != 1)) {
  789. throw new ServiceException("是否推荐参数不正确");
  790. }
  791. int rows = momentMapper.updateRecommendStatus(momentId, isRecommend);
  792. if (rows <= 0) {
  793. throw new ServiceException("动态不存在或已删除");
  794. }
  795. log.info("更新动态推荐状态成功,动态ID:{},是否推荐:{}", momentId, isRecommend);
  796. return true;
  797. }
  798. /**
  799. * 更新动态上架状态
  800. *
  801. * @param momentId 动态ID
  802. * @param isList 是否上架:0-否,1-是
  803. * @return 是否成功
  804. */
  805. @Override
  806. @Transactional(rollbackFor = Exception.class)
  807. public Boolean updateListStatus(Long momentId, Integer isList) {
  808. if (momentId == null) {
  809. throw new ServiceException("动态ID不能为空");
  810. }
  811. if (isList == null || (isList != 0 && isList != 1)) {
  812. throw new ServiceException("上架状态参数不正确");
  813. }
  814. int rows = momentMapper.updateListStatus(momentId, isList);
  815. if (rows <= 0) {
  816. throw new ServiceException("动态不存在或已删除");
  817. }
  818. log.info("更新动态上架状态成功,动态ID:{},是否上架:{}", momentId, isList);
  819. return true;
  820. }
  821. /**
  822. * 根据动态ID查询动态简要详情(包含媒体URL列表)
  823. *
  824. * @param momentId 动态ID
  825. * @return MomentSimpleDetailVO动态简要详情
  826. */
  827. @Override
  828. public MomentSimpleDetailVO getMomentSimpleDetail(Long momentId) {
  829. // 1. 参数校验
  830. if (momentId == null) {
  831. throw new ServiceException("动态ID不能为空");
  832. }
  833. // 2. 查询动态信息
  834. Map<String, Object> result = momentMapper.selectMomentSimpleDetail(momentId);
  835. if (result == null || result.isEmpty()) {
  836. throw new ServiceException("动态不存在");
  837. }
  838. // 3. 组装VO
  839. MomentSimpleDetailVO vo = new MomentSimpleDetailVO();
  840. vo.setId(((Number) result.get("id")).longValue());
  841. vo.setTitle((String) result.get("title"));
  842. vo.setContent((String) result.get("content"));
  843. vo.setMediaType((Integer) result.get("media_type"));
  844. vo.setAuditStatus((Integer) result.get("audit_status"));
  845. vo.setRejectReason((String) result.get("reject_reason"));
  846. // 4. 处理媒体URL列表
  847. String mediaUrlsStr = (String) result.get("media_urls");
  848. if (mediaUrlsStr != null && !mediaUrlsStr.trim().isEmpty()) {
  849. // 将逗号分隔的字符串转换为List
  850. List<String> mediaUrls = Arrays.asList(mediaUrlsStr.split(","));
  851. vo.setMediaUrls(mediaUrls);
  852. } else {
  853. vo.setMediaUrls(new ArrayList<>());
  854. }
  855. log.info("查询动态简要详情成功,动态ID:{}", momentId);
  856. return vo;
  857. }
  858. @Override
  859. public Page<MomentVO> getByMerchantMoment(MerchantMomentDTO dto) {
  860. Page<MomentVO> page = new Page<>(dto.getCurrent(), dto.getSize());
  861. return this.baseMapper.getMerchantProject(page, dto);
  862. }
  863. }