TechnicianMomentServiceImpl.java 37 KB

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