TechnicianMomentServiceImpl.java 35 KB

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