TechnicianMomentServiceImpl.java 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  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. List<Integer> ids = technicianIds.stream()
  204. .collect(Collectors.toList());
  205. return maTechnicianMapper.selectBatchIds(ids).stream().collect(Collectors.toMap(MaTechnician::getId, t -> t));
  206. }
  207. /**
  208. * 获取技师状态
  209. * 1-可服务(当前时间技师空闲)
  210. * 2-可预约(当前时间技师"已接单-服务中")
  211. */
  212. private Integer getTechnicianStatus(MaTechnician technician) {
  213. if (technician.getServiceState() != null && technician.getServiceState() == 1) {
  214. return 1; // 可服务
  215. } else if (technician.getServiceState() != null && technician.getServiceState() == 0) {
  216. return 2; // 可预约(服务中)
  217. }
  218. return null;
  219. }
  220. /**
  221. * 计算两点之间的距离(单位:km)
  222. *
  223. * @param lat1 纬度1(用户纬度)
  224. * @param lon1 经度1(用户经度)
  225. * @param lat2 纬度2(技师纬度)
  226. * @param lon2 经度2(技师经度)
  227. * @return double 距离(单位:km)
  228. * <p>
  229. * 使用Haversine公式
  230. */
  231. private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
  232. final double R = 6371; // 地球半径,单位km
  233. double dLat = Math.toRadians(lat2 - lat1);
  234. double dLon = Math.toRadians(lon2 - lon1);
  235. double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  236. Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
  237. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  238. double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  239. return R * c;
  240. }
  241. /**
  242. * 发布动态或保存草稿
  243. *
  244. * @param dto 发布动态请求对象
  245. * @param openId 技师OpenID
  246. * @return Long 动态ID
  247. */
  248. @Override
  249. @Transactional(rollbackFor = Exception.class)
  250. public Long publishMoment(PublishMomentDTO dto, String openId) {
  251. // 根据openId查询技师信息
  252. LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
  253. queryWrapper.eq(MaTechnician::getCOpenid, openId);
  254. MaTechnician technician = maTechnicianMapper.selectOne(queryWrapper);
  255. if (technician == null) {
  256. throw new ServiceException("商户信息不存在");
  257. }
  258. //查询是否保存为草稿
  259. Integer isDraft = dto.getIsDraft();
  260. if (isDraft == 1) {
  261. //只校验标题
  262. if (dto.getTitle() == null || dto.getTitle().trim().isEmpty()) {
  263. throw new ServiceException("动态标题不能为空");
  264. }
  265. } else {
  266. // 校验动态标题
  267. if (dto.getTitle() == null || dto.getTitle().trim().isEmpty()) {
  268. throw new ServiceException("动态标题不能为空");
  269. }
  270. // 校验动态内容
  271. if (StringUtils.isBlank(dto.getContent())) {
  272. throw new ServiceException("动态内容不能为空");
  273. }
  274. // 校验媒体类型和媒体文件
  275. if (dto.getMediaType() == null) {
  276. throw new ServiceException("媒体类型不能为空");
  277. }
  278. if (dto.getMediaType() == 1) {
  279. // 图片类型
  280. if (dto.getImageUrls() == null || dto.getImageUrls().isEmpty()) {
  281. throw new ServiceException("图片不能为空");
  282. }
  283. if (dto.getImageUrls().size() > 9) {
  284. throw new ServiceException("最多只能上传9张图片");
  285. }
  286. } else if (dto.getMediaType() == 2) {
  287. // 视频类型
  288. if (StringUtils.isBlank(dto.getVideoUrl())) {
  289. throw new ServiceException("视频不能为空");
  290. }
  291. } else {
  292. throw new ServiceException("媒体类型不正确");
  293. }
  294. }
  295. // 创建动态对象
  296. TechnicianMoment moment = new TechnicianMoment();
  297. moment.setTechnicianId(technician.getId());
  298. moment.setTitle(dto.getTitle());
  299. moment.setContent(dto.getContent());
  300. moment.setMediaType(dto.getMediaType());
  301. moment.setLatitude(dto.getLatitude());
  302. moment.setLongitude(dto.getLongitude());
  303. moment.setLocation(dto.getLocation());
  304. moment.setAddress(dto.getAddress());
  305. if(Objects.nonNull(dto.getMediaType())){
  306. // 设置封面图
  307. if (dto.getMediaType() == 1) {
  308. // 图片类型,默认取第一张作为封面
  309. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  310. moment.setCoverUrl(dto.getImageUrls().get(0));
  311. }
  312. } else if (dto.getMediaType() == 2) {
  313. // 视频类型,使用视频URL作为封面
  314. moment.setCoverUrl(dto.getVideoCoverUrl());
  315. }
  316. }
  317. // 设置审核状态和发布时间
  318. if (dto.getIsDraft() != null && dto.getIsDraft() == 1) {
  319. // 保存草稿
  320. moment.setAuditStatus(0);
  321. moment.setPublishTime(null);
  322. } else {
  323. // 立即发布,进入待审核状态
  324. moment.setAuditStatus(1);
  325. moment.setPublishTime(LocalDateTime.now());
  326. }
  327. // 设置初始状态
  328. moment.setStatus(1); // 正常
  329. moment.setViewCount(0);
  330. moment.setCreateTime(LocalDateTime.now());
  331. moment.setUpdateTime(LocalDateTime.now());
  332. // 8. 保存动态到数据库
  333. int insertResult = momentMapper.insert(moment);
  334. if (insertResult <= 0) {
  335. throw new ServiceException("发布动态失败");
  336. }
  337. Long momentId = moment.getId();
  338. // 9. 保存媒体信息到媒体表
  339. if(Objects.nonNull(dto.getMediaType())){
  340. if (dto.getMediaType() == 1) {
  341. // 保存图片
  342. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  343. for (int i = 0; i < dto.getImageUrls().size(); i++) {
  344. MomentMedia media = new MomentMedia();
  345. media.setMomentId(momentId);
  346. media.setMediaUrl(dto.getImageUrls().get(i));
  347. media.setMediaType(1); // 图片
  348. media.setSortOrder(i + 1);
  349. media.setCreateTime(LocalDateTime.now());
  350. //获取文件的格式
  351. String fileFormat = dto.getImageUrls().get(i).substring(dto.getImageUrls().get(i).lastIndexOf(".") + 1);
  352. media.setFileFormat(fileFormat);
  353. mediaMapper.insert(media);
  354. }
  355. }
  356. } else if (dto.getMediaType() == 2) {
  357. // 保存视频
  358. MomentMedia media = new MomentMedia();
  359. media.setMomentId(momentId);
  360. media.setMediaUrl(dto.getVideoUrl());
  361. media.setMediaType(2); // 视频
  362. media.setSortOrder(1);
  363. media.setCreateTime(LocalDateTime.now());
  364. mediaMapper.insert(media);
  365. }
  366. }
  367. log.info("发布动态成功,动态ID:{},技师ID:{},是否草稿:{}", momentId, technician.getId(), dto.getIsDraft());
  368. return momentId;
  369. }
  370. /**
  371. * 编辑动态
  372. *
  373. * @param dto 编辑动态请求对象
  374. * @param openId 技师OpenID
  375. * @return Long 动态ID
  376. */
  377. @Override
  378. @Transactional(rollbackFor = Exception.class)
  379. public Long editMoment(EditMomentDTO dto, String openId) {
  380. // 1. 参数校验
  381. if (dto == null) {
  382. throw new ServiceException("编辑动态信息不能为空");
  383. }
  384. if (openId == null || openId.trim().isEmpty()) {
  385. throw new ServiceException("openId不能为空");
  386. }
  387. if (dto.getMomentId() == null) {
  388. throw new ServiceException("动态ID不能为空");
  389. }
  390. // 2. 根据openId查询技师信息
  391. LambdaQueryWrapper<TJs> queryWrapper = new LambdaQueryWrapper<>();
  392. queryWrapper.eq(TJs::getcOpenId, openId);
  393. TJs technician = tJsMapper.selectOne(queryWrapper);
  394. if (technician == null) {
  395. throw new ServiceException("技师信息不存在");
  396. }
  397. // 3. 查询动态信息
  398. TechnicianMoment moment = momentMapper.selectById(dto.getMomentId());
  399. if (moment == null) {
  400. throw new ServiceException("动态不存在");
  401. }
  402. // 4. 验证动态是否属于当前技师
  403. if (!moment.getTechnicianId().equals(technician.getId())) {
  404. throw new ServiceException("无权编辑此动态");
  405. }
  406. // 5. 验证动态状态是否允许编辑(只能编辑草稿和审核拒绝的动态)
  407. if (moment.getAuditStatus() == null ||
  408. (moment.getAuditStatus() != 0 && moment.getAuditStatus() != 3)) {
  409. throw new ServiceException("只能编辑草稿或审核拒绝的动态");
  410. }
  411. // 6. 校验媒体类型和媒体文件
  412. if (dto.getMediaType() == null) {
  413. throw new ServiceException("媒体类型不能为空");
  414. }
  415. if (dto.getMediaType() == 1) {
  416. // 图片类型
  417. if (dto.getImageUrls() == null || dto.getImageUrls().isEmpty()) {
  418. throw new ServiceException("图片不能为空");
  419. }
  420. if (dto.getImageUrls().size() > 9) {
  421. throw new ServiceException("最多只能上传9张图片");
  422. }
  423. } else if (dto.getMediaType() == 2) {
  424. // 视频类型
  425. if (dto.getVideoUrl() == null || dto.getVideoUrl().trim().isEmpty()) {
  426. throw new ServiceException("视频不能为空");
  427. }
  428. } else {
  429. throw new ServiceException("媒体类型不正确");
  430. }
  431. // 7. 校验可见范围
  432. if (dto.getVisibleRange() == null || dto.getVisibleRange() != 1) {
  433. throw new ServiceException("可见范围只能选择公开");
  434. }
  435. // 8. 更新动态对象
  436. moment.setTitle(dto.getTitle());
  437. moment.setContent(dto.getContent());
  438. moment.setMediaType(dto.getMediaType());
  439. moment.setVisibleRange(dto.getVisibleRange());
  440. moment.setLongitude(dto.getLongitude());
  441. moment.setLatitude(dto.getLatitude());
  442. moment.setLocation(dto.getLocation());
  443. moment.setAddress(dto.getAddress());
  444. // 设置封面图
  445. if (dto.getMediaType() == 1) {
  446. // 图片类型,默认取第一张作为封面
  447. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  448. moment.setCoverUrl(dto.getImageUrls().get(0));
  449. }
  450. } else if (dto.getMediaType() == 2) {
  451. // 视频类型,使用视频封面URL(如果提供了)或视频URL作为封面
  452. if (dto.getVideoCoverUrl() != null && !dto.getVideoCoverUrl().trim().isEmpty()) {
  453. moment.setCoverUrl(dto.getVideoCoverUrl());
  454. } else {
  455. moment.setCoverUrl(dto.getVideoUrl());
  456. }
  457. }
  458. // 9. 设置审核状态和发布时间
  459. if (dto.getIsDraft() != null && dto.getIsDraft() == 1) {
  460. // 保存草稿
  461. moment.setAuditStatus(0); // 草稿
  462. moment.setPublishTime(null);
  463. moment.setRejectReason(null); // 清空拒绝原因
  464. } else {
  465. // 提交发布,进入待审核状态
  466. moment.setAuditStatus(1);
  467. if (moment.getPublishTime() == null) {
  468. moment.setPublishTime(LocalDateTime.now());
  469. }
  470. moment.setRejectReason(null); // 清空拒绝原因
  471. }
  472. moment.setUpdateTime(LocalDateTime.now());
  473. // 10. 更新动态到数据库
  474. int updateResult = momentMapper.updateById(moment);
  475. if (updateResult <= 0) {
  476. throw new ServiceException("编辑动态失败");
  477. }
  478. Long momentId = moment.getId();
  479. // 11. 删除旧的媒体信息
  480. LambdaQueryWrapper<MomentMedia> mediaWrapper = new LambdaQueryWrapper<>();
  481. mediaWrapper.eq(MomentMedia::getMomentId, momentId);
  482. mediaMapper.delete(mediaWrapper);
  483. // 12. 保存新的媒体信息到媒体表
  484. if (dto.getMediaType() == 1) {
  485. // 保存图片
  486. if (dto.getImageUrls() != null && !dto.getImageUrls().isEmpty()) {
  487. for (int i = 0; i < dto.getImageUrls().size(); i++) {
  488. MomentMedia media = new MomentMedia();
  489. media.setMomentId(momentId);
  490. media.setMediaUrl(dto.getImageUrls().get(i));
  491. media.setMediaType(1); // 图片
  492. media.setSortOrder(i + 1);
  493. media.setCreateTime(LocalDateTime.now());
  494. // 获取文件的格式
  495. String fileFormat = dto.getImageUrls().get(i).substring(dto.getImageUrls().get(i).lastIndexOf(".") + 1);
  496. media.setFileFormat(fileFormat);
  497. mediaMapper.insert(media);
  498. }
  499. }
  500. } else if (dto.getMediaType() == 2) {
  501. // 保存视频
  502. MomentMedia media = new MomentMedia();
  503. media.setMomentId(momentId);
  504. media.setMediaUrl(dto.getVideoUrl());
  505. media.setMediaType(2); // 视频
  506. media.setSortOrder(1);
  507. media.setCreateTime(LocalDateTime.now());
  508. mediaMapper.insert(media);
  509. }
  510. log.info("编辑动态成功,动态ID:{},技师ID:{},是否草稿:{}", momentId, technician.getId(), dto.getIsDraft());
  511. return momentId;
  512. }
  513. /**
  514. * 查询草稿箱列表
  515. *
  516. * @param openId 技师OpenID
  517. * @param pageNum 页码
  518. * @param pageSize 每页数量
  519. * @return List<MomentListVO> 草稿列表
  520. */
  521. @Override
  522. public Page<MomentListVO> getDraftMoments(String openId, Integer pageNum, Integer pageSize) {
  523. if (openId == null || openId.trim().isEmpty()) {
  524. throw new ServiceException("openId不能为空");
  525. }
  526. // 1. 根据openId查询技师信息
  527. LambdaQueryWrapper<MaTechnician> queryWrapper = new LambdaQueryWrapper<>();
  528. queryWrapper.eq(MaTechnician::getCOpenid, openId);
  529. MaTechnician technician = maTechnicianMapper.selectOne(queryWrapper);
  530. if (technician == null) {
  531. throw new ServiceException("商户信息不存在");
  532. }
  533. // 2. 查询该商户的草稿列表
  534. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  535. LambdaQueryWrapper<TechnicianMoment> momentWrapper = new LambdaQueryWrapper<>();
  536. momentWrapper.eq(TechnicianMoment::getTechnicianId, technician.getId())
  537. .eq(TechnicianMoment::getAuditStatus, 0) // 草稿状态
  538. .eq(TechnicianMoment::getStatus, 1) // 正常状态
  539. .orderByDesc(TechnicianMoment::getCreateTime);
  540. Page<TechnicianMoment> momentPage = momentMapper.selectPage(page, momentWrapper);
  541. List<TechnicianMoment> moments = momentPage.getRecords();
  542. Page<MomentListVO> voPage = new Page<>(pageNum, pageSize);
  543. if (moments == null || moments.isEmpty()) {
  544. // 草稿列表为空时,返回空Page对象
  545. return voPage;
  546. }
  547. // 3. 组装VO
  548. List<MomentListVO> collect = moments.stream().map(moment -> {
  549. MomentListVO vo = new MomentListVO();
  550. BeanUtils.copyProperties(moment, vo);
  551. vo.setTechnicianNickName(technician.getTeNickName());
  552. vo.setTechnicianAvatar(technician.getTeAvatar());
  553. vo.setTechnicianStatus(getTechnicianStatus(technician));
  554. // 草稿的发布时间使用创建时间
  555. vo.setPublishTime(moment.getCreateTime());
  556. return vo;
  557. }).collect(Collectors.toList());
  558. //返回Page对象
  559. voPage.setTotal(momentPage.getTotal());
  560. voPage.setRecords(collect);
  561. return voPage;
  562. }
  563. /**
  564. * 查询我的动态列表(已发布的动态:审核中+审核通过)
  565. *
  566. * @param openId 技师OpenID
  567. * @param pageNum 页码
  568. * @param pageSize 每页数量
  569. * @return Page<MyMomentVO> 我的动态列表
  570. */
  571. @Override
  572. public Page<MyMomentVO> getMyMoments(String openId, Integer pageNum, Integer pageSize) {
  573. // 1. 参数校验
  574. if (openId == null || openId.trim().isEmpty()) {
  575. throw new ServiceException("openId不能为空");
  576. }
  577. // 2. 根据openId查询技师信息
  578. LambdaQueryWrapper<TJs> queryWrapper = new LambdaQueryWrapper<>();
  579. queryWrapper.eq(TJs::getcOpenId, openId);
  580. TJs technician = tJsMapper.selectOne(queryWrapper);
  581. if (technician == null) {
  582. throw new ServiceException("技师信息不存在");
  583. }
  584. // 3. 查询该技师的已发布动态(待审核、审核通过)
  585. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  586. LambdaQueryWrapper<TechnicianMoment> momentWrapper = new LambdaQueryWrapper<>();
  587. momentWrapper.eq(TechnicianMoment::getTechnicianId, technician.getId())
  588. .in(TechnicianMoment::getAuditStatus, 1, 2) // 1-待审核, 2-审核通过
  589. .eq(TechnicianMoment::getStatus, 1) // 正常状态
  590. .orderByDesc(TechnicianMoment::getPublishTime);
  591. Page<TechnicianMoment> momentPage = momentMapper.selectPage(page, momentWrapper);
  592. // 4. 组装分页返回结果
  593. Page<MyMomentVO> voPage = new Page<>(pageNum, pageSize);
  594. voPage.setTotal(momentPage.getTotal());
  595. voPage.setCurrent(momentPage.getCurrent());
  596. voPage.setSize(momentPage.getSize());
  597. List<TechnicianMoment> moments = momentPage.getRecords();
  598. if (moments == null || moments.isEmpty()) {
  599. voPage.setRecords(new ArrayList<>());
  600. return voPage;
  601. }
  602. // 5. 组装VO列表
  603. List<MyMomentVO> voList = moments.stream().map(moment -> {
  604. MyMomentVO vo = new MyMomentVO();
  605. vo.setId(moment.getId());
  606. vo.setTitle(moment.getTitle());
  607. vo.setContent(moment.getContent());
  608. vo.setAuditStatus(moment.getAuditStatus());
  609. vo.setCoverUrl(moment.getCoverUrl());
  610. vo.setPublishTime(moment.getPublishTime());
  611. vo.setViewCount(moment.getViewCount());
  612. return vo;
  613. }).collect(Collectors.toList());
  614. voPage.setRecords(voList);
  615. log.info("查询我的动态列表成功,技师ID:{},页码:{},每页数量:{},总记录数:{}", technician.getId(), pageNum, pageSize, voPage.getTotal());
  616. return voPage;
  617. }
  618. /**
  619. * 根据技师ID查询动态列表(已发布的动态:审核中+审核通过)
  620. *
  621. * @param technicianId 技师ID
  622. * @return List<MomentListVO> 动态列表
  623. */
  624. @Override
  625. public List<MomentListVO> getMomentsByTechnicianId(String technicianId) {
  626. // 1. 参数校验
  627. if (technicianId == null || technicianId.trim().isEmpty()) {
  628. throw new ServiceException("技师ID不能为空");
  629. }
  630. // 2. 查询该技师的已发布动态(待审核、审核通过)
  631. LambdaQueryWrapper<TechnicianMoment> momentWrapper = new LambdaQueryWrapper<>();
  632. momentWrapper.eq(TechnicianMoment::getTechnicianId, technicianId)
  633. .in(TechnicianMoment::getAuditStatus, 1, 2) // 1-待审核, 2-审核通过
  634. .eq(TechnicianMoment::getStatus, 1) // 正常状态
  635. .orderByDesc(TechnicianMoment::getPublishTime);
  636. List<TechnicianMoment> moments = momentMapper.selectList(momentWrapper);
  637. if (moments == null || moments.isEmpty()) {
  638. return new ArrayList<>();
  639. }
  640. // 3. 组装VO列表
  641. List<MomentListVO> voList = moments.stream().map(moment -> {
  642. MomentListVO vo = new MomentListVO();
  643. vo.setId(moment.getId());
  644. vo.setTitle(moment.getTitle());
  645. vo.setAuditStatus(moment.getAuditStatus());
  646. vo.setCoverUrl(moment.getCoverUrl());
  647. vo.setPublishTime(moment.getPublishTime());
  648. vo.setViewCount(moment.getViewCount());
  649. return vo;
  650. }).collect(Collectors.toList());
  651. log.info("根据技师ID查询动态列表成功,技师ID:{},记录数:{}", technicianId, voList.size());
  652. return voList;
  653. }
  654. /**
  655. * 查询待审核动态列表(管理后台)
  656. *
  657. * @param queryDTO 查询参数
  658. * @return Page<MomentManageVO> 分页结果
  659. */
  660. @Override
  661. public Page<MomentManageVO> getMomentManageList(MomentManageQueryDTO queryDTO) {
  662. // 1. 参数处理
  663. Integer pageNum = queryDTO.getPageNum() != null ? queryDTO.getPageNum() : 1;
  664. Integer pageSize = queryDTO.getPageSize() != null ? queryDTO.getPageSize() : 10;
  665. String technicianName = queryDTO.getTechnicianName();
  666. Integer auditStatus = queryDTO.getAuditStatus();
  667. String startTime = queryDTO.getStartTime();
  668. if (StringUtils.isNotBlank(startTime)) {
  669. startTime = startTime + " 00:00:00";
  670. }
  671. String endTime = queryDTO.getEndTime();
  672. if (StringUtils.isNotBlank(endTime)) {
  673. endTime = endTime + " 23:59:59";
  674. }
  675. // 2. 构建查询条件
  676. LambdaQueryWrapper<TechnicianMoment> queryWrapper = new LambdaQueryWrapper<>();
  677. queryWrapper.eq(TechnicianMoment::getStatus, 1); // 只查询正常状态的动态
  678. // 如果传入了审核状态,则按照审核状态查询
  679. if (auditStatus != null) {
  680. queryWrapper.eq(TechnicianMoment::getAuditStatus, auditStatus);
  681. } else {
  682. // 默认查询全部的动态(待审核、审核通过、审核拒绝)
  683. queryWrapper.in(TechnicianMoment::getAuditStatus, 1, 2, 3);
  684. }
  685. // 技师姓名查询
  686. if (technicianName != null && !technicianName.trim().isEmpty()) {
  687. //根据技师姓名查询技师的ID
  688. LambdaQueryWrapper<TJs> jsQueryWrapper = new LambdaQueryWrapper<>();
  689. jsQueryWrapper.like(TJs::getcNickName, technicianName);
  690. List<TJs> technicians = tJsMapper.selectList(jsQueryWrapper);
  691. if (CollUtil.isNotEmpty(technicians)) {
  692. // 构建查询条件,查询所有匹配的技师的动态
  693. queryWrapper.in(TechnicianMoment::getTechnicianId, technicians.stream().map(TJs::getId).collect(Collectors.toList()));
  694. } else {
  695. // 没有匹配的技师,查询空字符串列表
  696. queryWrapper.in(TechnicianMoment::getTechnicianId, Collections.singleton(""));
  697. }
  698. }
  699. // 时间范围查询
  700. if (startTime != null && !startTime.trim().isEmpty()) {
  701. queryWrapper.ge(TechnicianMoment::getPublishTime, startTime);
  702. }
  703. if (endTime != null && !endTime.trim().isEmpty()) {
  704. queryWrapper.le(TechnicianMoment::getPublishTime, endTime);
  705. }
  706. // 按创建时间倒序排序
  707. queryWrapper.orderByDesc(TechnicianMoment::getCreateTime);
  708. // 3. 分页查询
  709. Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
  710. Page<TechnicianMoment> momentPage = momentMapper.selectPage(page, queryWrapper);
  711. // 4. 组装VO
  712. Page<MomentManageVO> voPage = new Page<>(pageNum, pageSize);
  713. voPage.setTotal(momentPage.getTotal());
  714. voPage.setCurrent(momentPage.getCurrent());
  715. voPage.setSize(momentPage.getSize());
  716. List<TechnicianMoment> moments = momentPage.getRecords();
  717. if (moments == null || moments.isEmpty()) {
  718. voPage.setRecords(new ArrayList<>());
  719. return voPage;
  720. }
  721. // 5. 批量查询技师信息
  722. List<Integer> technicianIds = moments.stream().map(TechnicianMoment::getTechnicianId).collect(Collectors.toList());
  723. Map<Integer, MaTechnician> technicianMap = getTechnicianMap(technicianIds);
  724. // 6. 组装VO列表
  725. List<MomentManageVO> voList = moments.stream().map(moment -> {
  726. MomentManageVO vo = new MomentManageVO();
  727. BeanUtils.copyProperties(moment, vo);
  728. // 设置技师信息
  729. MaTechnician technician = technicianMap.get(moment.getTechnicianId());
  730. if (technician != null) {
  731. // 设置技师姓名
  732. vo.setTechnicianName(technician.getTeName());
  733. // 设置技师昵称
  734. vo.setTechnicianNickname(technician.getTeNickName());
  735. }
  736. return vo;
  737. }).collect(Collectors.toList());
  738. voPage.setRecords(voList);
  739. log.info("查询动态管理列表,页码:{},每页数量:{},总记录数:{}", pageNum, pageSize, voPage.getTotal());
  740. return voPage;
  741. }
  742. /**
  743. * 审核动态(通过/拒绝)
  744. *
  745. * @param momentId 动态ID
  746. * @param auditStatus 审核状态:2-通过,3-拒绝
  747. * @param rejectReason 拒绝原因(拒绝时必填)
  748. * @return 是否成功
  749. */
  750. @Override
  751. @Transactional(rollbackFor = Exception.class)
  752. public Boolean auditMoment(MomentAuditDTO dto) {
  753. // 1. 参数校验
  754. if (dto.getMomentId() == null) {
  755. throw new ServiceException("动态ID不能为空");
  756. }
  757. if (dto.getAuditStatus() == null || (dto.getAuditStatus() != 2 && dto.getAuditStatus() != 3)) {
  758. throw new ServiceException("审核状态不正确");
  759. }
  760. if (dto.getAuditStatus() == 3 && (dto.getRejectReason() == null || dto.getRejectReason().trim().isEmpty())) {
  761. throw new ServiceException("拒绝时必须填写拒绝原因");
  762. }
  763. // 2. 查询动态信息
  764. TechnicianMoment moment = momentMapper.selectById(dto.getMomentId());
  765. if (moment == null) {
  766. throw new ServiceException("动态不存在");
  767. }
  768. // 3. 校验动态状态
  769. if (moment.getStatus() != 1) {
  770. throw new ServiceException("动态已删除,无法审核");
  771. }
  772. if (moment.getAuditStatus() != 1) {
  773. throw new ServiceException("只能审核待审核状态的动态");
  774. }
  775. // 4. 更新审核状态
  776. moment.setAuditStatus(dto.getAuditStatus());
  777. moment.setRejectReason(dto.getRejectReason());
  778. moment.setUpdateTime(LocalDateTime.now());
  779. int updateResult = momentMapper.updateById(moment);
  780. if (updateResult <= 0) {
  781. throw new ServiceException("审核失败");
  782. }
  783. log.info("审核动态成功,动态ID:{},审核结果:{},拒绝原因:{}", dto.getMomentId(), dto.getAuditStatus(), dto.getRejectReason());
  784. return true;
  785. }
  786. /**
  787. * 根据动态ID查询动态简要详情(包含媒体URL列表)
  788. *
  789. * @param momentId 动态ID
  790. * @return MomentSimpleDetailVO动态简要详情
  791. */
  792. @Override
  793. public MomentSimpleDetailVO getMomentSimpleDetail(Long momentId) {
  794. // 1. 参数校验
  795. if (momentId == null) {
  796. throw new ServiceException("动态ID不能为空");
  797. }
  798. // 2. 查询动态信息
  799. Map<String, Object> result = momentMapper.selectMomentSimpleDetail(momentId);
  800. if (result == null || result.isEmpty()) {
  801. throw new ServiceException("动态不存在");
  802. }
  803. // 3. 组装VO
  804. MomentSimpleDetailVO vo = new MomentSimpleDetailVO();
  805. vo.setId(((Number) result.get("id")).longValue());
  806. vo.setTitle((String) result.get("title"));
  807. vo.setContent((String) result.get("content"));
  808. vo.setMediaType((Integer) result.get("media_type"));
  809. vo.setAuditStatus((Integer) result.get("audit_status"));
  810. vo.setRejectReason((String) result.get("reject_reason"));
  811. // 4. 处理媒体URL列表
  812. String mediaUrlsStr = (String) result.get("media_urls");
  813. if (mediaUrlsStr != null && !mediaUrlsStr.trim().isEmpty()) {
  814. // 将逗号分隔的字符串转换为List
  815. List<String> mediaUrls = Arrays.asList(mediaUrlsStr.split(","));
  816. vo.setMediaUrls(mediaUrls);
  817. } else {
  818. vo.setMediaUrls(new ArrayList<>());
  819. }
  820. log.info("查询动态简要详情成功,动态ID:{}", momentId);
  821. return vo;
  822. }
  823. @Override
  824. public Page<MomentVO> getByMerchantMoment(MerchantMomentDTO dto) {
  825. Page<MomentVO> page = new Page<>(dto.getCurrent(), dto.getSize());
  826. return this.baseMapper.getMerchantProject(page, dto);
  827. }
  828. }