TechnicianMomentServiceImpl.java 39 KB

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