jinshihui 1 mês atrás
pai
commit
e3274cd093
18 arquivos alterados com 1102 adições e 22 exclusões
  1. 8 3
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/CancelOrderApplicationController.java
  2. 186 0
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TechnicianMomentController.java
  3. 2 0
      nightFragrance-framework/src/main/java/com/ylx/framework/web/service/WxTokenService.java
  4. 19 1
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/CancelOrderApplication.java
  5. 56 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/MomentMedia.java
  6. 111 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/TechnicianMoment.java
  7. 33 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MomentQueryDTO.java
  8. 61 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentDetailVO.java
  9. 99 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentListVO.java
  10. 28 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentMediaVO.java
  11. 13 18
      nightFragrance-massage/src/main/java/com/ylx/massage/enums/OrderStatusEnum.java
  12. 19 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MomentMediaMapper.java
  13. 36 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TechnicianMomentMapper.java
  14. 51 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/ITechnicianMomentService.java
  15. 2 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CancelOrderApplicationServiceImpl.java
  16. 254 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TechnicianMomentServiceImpl.java
  17. 29 0
      nightFragrance-massage/src/main/resources/mapper/massage/MomentMediaMapper.xml
  18. 95 0
      nightFragrance-massage/src/main/resources/mapper/massage/TechnicianMomentMapper.xml

+ 8 - 3
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/CancelOrderApplicationController.java

@@ -64,13 +64,13 @@ public class CancelOrderApplicationController extends BaseController {
                     CancelOrderApplication::getOrderNo, cancelOrderApplication.getOrderNo());
             // 技师姓名模糊查询
             queryWrapper.like(StringUtils.isNotBlank(cancelOrderApplication.getTechName()),
-                    CancelOrderApplication::getTechName, cancelOrderApplication.getTechName());
+                    CancelOrderApplication::getTechName, cancelOrderApplication.getCName());
             // 技师电话模糊查询
             queryWrapper.like(StringUtils.isNotBlank(cancelOrderApplication.getTechPhone()),
-                    CancelOrderApplication::getTechPhone, cancelOrderApplication.getTechPhone());
+                    CancelOrderApplication::getTechPhone, cancelOrderApplication.getCPhone());
             // 技师昵称模糊查询
             queryWrapper.like(StringUtils.isNotBlank(cancelOrderApplication.getTechNickName()),
-                    CancelOrderApplication::getTechNickName, cancelOrderApplication.getTechNickName());
+                    CancelOrderApplication::getTechNickName, cancelOrderApplication.getCNickName());
             // 用户电话模糊查询
             queryWrapper.like(StringUtils.isNotBlank(cancelOrderApplication.getUserPhone()),
                     CancelOrderApplication::getUserPhone, cancelOrderApplication.getUserPhone());
@@ -85,6 +85,11 @@ public class CancelOrderApplicationController extends BaseController {
             // 执行分页查询
             Page<CancelOrderApplication> pageResult = cancelOrderApplicationService.page(page, queryWrapper);
             log.info("查询退单申请列表成功,共查询到{}条记录", pageResult.getTotal());
+            pageResult.getRecords().forEach(application -> {
+                application.setCName(application.getTechName());
+                application.setCNickName(application.getTechNickName());
+                application.setCPhone(application.getTechPhone());
+            });
             return R.ok(pageResult);
         } catch (Exception e) {
             log.error("查询退单申请列表失败", e);

+ 186 - 0
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TechnicianMomentController.java

@@ -0,0 +1,186 @@
+package com.ylx.web.controller.massage;
+
+import com.ylx.common.core.controller.BaseController;
+import com.ylx.common.core.domain.R;
+import com.ylx.massage.domain.vo.MomentDetailVO;
+import com.ylx.massage.domain.vo.MomentListVO;
+import com.ylx.massage.service.ITechnicianMomentService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import java.util.List;
+
+/**
+ * 技师动态控制器
+ */
+@RestController
+@RequestMapping("api/moment/v1")
+@Api(tags = {"技师动态管理"})
+@Slf4j
+public class TechnicianMomentController extends BaseController {
+
+    @Autowired
+    private ITechnicianMomentService momentService;
+
+    /**
+     * 获取推荐动态列表
+     * 展示全部的技师动态(不分地区)
+     * 根据日期倒序展示,同一天的动态按照浏览量最高的优先展示
+     *
+     * @param pageNum  页码,默认1
+     * @param pageSize 每页数量,默认10
+     * @return R<List<MomentListVO>> 动态列表
+     */
+    @GetMapping("/recommended")
+    @ApiOperation("获取推荐动态列表")
+    public R<List<MomentListVO>> getRecommendedMoments(
+            @ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
+            @ApiParam("每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            List<MomentListVO> list = momentService.getRecommendedMoments(pageNum, pageSize);
+            return R.ok(list);
+        } catch (Exception e) {
+            log.error("获取推荐动态列表失败", e);
+            return R.fail("获取推荐动态列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取同城商户动态列表
+     * 展示用户同城的所有动态
+     * 按照最新发布时间排序展示
+     *
+     * @param cityCode 城市编码
+     * @param pageNum  页码,默认1
+     * @param pageSize 每页数量,默认10
+     * @return R<List<MomentListVO>> 动态列表
+     */
+    @GetMapping("/sameCity")
+    @ApiOperation("获取同城商户动态列表")
+    public R<List<MomentListVO>> getSameCityMoments(
+            @ApiParam("城市编码") @RequestParam String cityCode,
+            @ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
+            @ApiParam("每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            if (cityCode == null || cityCode.trim().isEmpty()) {
+                return R.fail("城市编码不能为空");
+            }
+            List<MomentListVO> list = momentService.getSameCityMoments(cityCode, pageNum, pageSize);
+            return R.ok(list);
+        } catch (Exception e) {
+            log.error("获取同城商户动态列表失败", e);
+            return R.fail("获取同城商户动态列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取附近动态列表
+     * 根据用户地理位置,展示距离10km以内的技师动态
+     * 根据日期倒序展示,同一天的动态按照距离最近的优先展示
+     *
+     * @param latitude  用户纬度
+     * @param longitude 用户经度
+     * @param pageNum   页码,默认1
+     * @param pageSize  每页数量,默认10
+     * @return 动态列表
+     */
+    @GetMapping("/nearby")
+    @ApiOperation("获取附近动态列表")
+    public R<List<MomentListVO>> getNearbyMoments(
+            @ApiParam("用户纬度") @RequestParam java.math.BigDecimal latitude,
+            @ApiParam("用户经度") @RequestParam java.math.BigDecimal longitude,
+            @ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
+            @ApiParam("每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            if (latitude == null || longitude == null) {
+                return R.fail("地理位置信息不能为空");
+            }
+            List<MomentListVO> list = momentService.getNearbyMoments(latitude, longitude, pageNum, pageSize);
+            return R.ok(list);
+        } catch (Exception e) {
+            log.error("获取附近动态列表失败", e);
+            return R.fail("获取附近动态列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取动态详情
+     * 包含动态标题、技师昵称头像、技师状态、浏览量、封面图等信息
+     * 每点击查看一次,浏览量+1
+     *
+     * @param momentId 动态ID
+     * @return 动态详情
+     */
+    @GetMapping("/detail/{momentId}")
+    @ApiOperation("获取动态详情")
+    public R<MomentDetailVO> getMomentDetail(
+            @ApiParam("动态ID") @PathVariable Long momentId) {
+        try {
+            if (momentId == null) {
+                return R.fail("动态ID不能为空");
+            }
+            MomentDetailVO detail = momentService.getMomentDetail(momentId);
+            return R.ok(detail);
+        } catch (Exception e) {
+            log.error("获取动态详情失败", e);
+            return R.fail("获取动态详情失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 统一查询接口
+     * 根据queryType参数决定查询类型:1-推荐,2-同城,3-附近
+     *
+     * @param queryType 查询类型:1-推荐,2-同城,3-附近
+     * @param cityCode  城市编码(同城查询时必填)
+     * @param latitude  用户纬度(附近查询时必填)
+     * @param longitude 用户经度(附近查询时必填)
+     * @param pageNum   页码,默认1
+     * @param pageSize  每页数量,默认10
+     * @return 动态列表
+     */
+    @GetMapping("/list")
+    @ApiOperation("统一查询接口")
+    public R<List<MomentListVO>> getMomentList(
+            @ApiParam("查询类型:1-推荐,2-同城,3-附近") @RequestParam Integer queryType,
+            @ApiParam("城市编码") @RequestParam(required = false) String cityCode,
+            @ApiParam("用户纬度") @RequestParam(required = false) java.math.BigDecimal latitude,
+            @ApiParam("用户经度") @RequestParam(required = false) java.math.BigDecimal longitude,
+            @ApiParam("页码") @RequestParam(defaultValue = "1") Integer pageNum,
+            @ApiParam("每页数量") @RequestParam(defaultValue = "10") Integer pageSize) {
+        try {
+            List<MomentListVO> list;
+
+            switch (queryType) {
+                case 1:
+                    // 推荐
+                    list = momentService.getRecommendedMoments(pageNum, pageSize);
+                    break;
+                case 2:
+                    // 同城
+                    if (cityCode == null || cityCode.trim().isEmpty()) {
+                        return R.fail("同城查询时城市编码不能为空");
+                    }
+                    list = momentService.getSameCityMoments(cityCode, pageNum, pageSize);
+                    break;
+                case 3:
+                    // 附近
+                    if (latitude == null || longitude == null) {
+                        return R.fail("附近查询时地理位置信息不能为空");
+                    }
+                    list = momentService.getNearbyMoments(latitude, longitude, pageNum, pageSize);
+                    break;
+                default:
+                    return R.fail("查询类型不正确,必须是1-推荐、2-同城、3-附近");
+            }
+
+            return R.ok(list);
+        } catch (Exception e) {
+            log.error("查询动态列表失败", e);
+            return R.fail("查询动态列表失败:" + e.getMessage());
+        }
+    }
+}

+ 2 - 0
nightFragrance-framework/src/main/java/com/ylx/framework/web/service/WxTokenService.java

@@ -1,5 +1,6 @@
 package com.ylx.framework.web.service;
 
+import com.alibaba.fastjson.JSON;
 import com.ylx.common.core.domain.model.WxLoginUser;
 import com.ylx.common.core.redis.RedisCache;
 import com.ylx.common.utils.ServletUtils;
@@ -68,6 +69,7 @@ public class WxTokenService {
                 String uuid = (String) claims.get(wx_prefix);
                 String userKey = getTokenKey(uuid);
                 WxLoginUser user = redisCache.getCacheObject(userKey);
+                log.info("user对象的值:{}", JSON.toJSONString(user));
                 return user;
             } catch (Exception e) {
                 e.printStackTrace();

+ 19 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/domain/CancelOrderApplication.java

@@ -60,16 +60,34 @@ public class CancelOrderApplication implements Serializable {
      */
     private String techName;
 
+    /**
+     * 技术姓名
+     */
+    @TableField(exist = false)
+    private String cName;
+
     /**
      * 技师昵称
      */
     private String techNickName;
 
+    /**
+     * 技师昵称
+     */
+    @TableField(exist = false)
+    private String cNickName;
+
      /**
      * 技师电话
      */
     private String techPhone;
 
+    /**
+     * 技师电话
+     */
+    @TableField(exist = false)
+    private String cPhone;
+
     /**
      * 服务项目名称
      */
@@ -96,7 +114,7 @@ public class CancelOrderApplication implements Serializable {
     private BigDecimal refundAmount;
 
     /**
-     * 订单状态(订单状态(-1:待付款 0:待接单 1:已接单 6:已出发 2:已到达 3:服务中 4:待评价 5:已完成 6:退单待审核 7:退单审核通过 8:退单审核拒绝  -2:已取消 -3:已拒绝)
+     * 订单状态(订单状态(-1:待付款 0:待接单 1:已接单 6:已出发 2:已到达 3:服务中 4:待评价 5:已完成 7:退单待审核 8:退单审核通过   -2:已取消 -3:已拒绝)
      */
      private Integer orderStatus;
 

+ 56 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/MomentMedia.java

@@ -0,0 +1,56 @@
+package com.ylx.massage.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+/**
+ * 动态媒体表
+ */
+@Data
+@TableName("t_moment_media")
+public class MomentMedia {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 动态ID
+     */
+    private Long momentId;
+
+    /**
+     * 媒体URL
+     */
+    private String mediaUrl;
+
+    /**
+     * 媒体类型:1-图片,2-视频
+     */
+    private Integer mediaType;
+
+    /**
+     * 排序
+     */
+    private Integer sortOrder;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 文件大小(字节)
+     */
+    private Long fileSize;
+
+    /**
+     * 文件格式:jpg/png/mp4等
+     */
+    private String fileFormat;
+}

+ 111 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/TechnicianMoment.java

@@ -0,0 +1,111 @@
+package com.ylx.massage.domain;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 技师动态表
+ */
+@Data
+@TableName("t_technician_moment")
+public class TechnicianMoment {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 技师ID
+     */
+    private String technicianId;
+
+    /**
+     * 动态标题
+     */
+    private String title;
+
+    /**
+     * 动态内容
+     */
+    private String content;
+
+    /**
+     * 媒体类型:1-图片,2-视频
+     */
+    private Integer mediaType;
+
+    /**
+     * 封面图URL
+     */
+    private String coverUrl;
+
+    /**
+     * 浏览量
+     */
+    private Integer viewCount;
+
+    /**
+     * 发布时间
+     */
+    private LocalDateTime publishTime;
+
+    /**
+     * 状态:1-正常,2-删除
+     */
+    private Integer status;
+
+    /**
+     * 城市编码
+     */
+    private String cityCode;
+
+    /**
+     * 纬度
+     */
+    private BigDecimal latitude;
+
+    /**
+     * 经度
+     */
+    private BigDecimal longitude;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 审核状态:0-草稿,1-审核中,2-审核通过,3-审核拒绝
+     */
+    private Integer auditStatus;
+
+    /**
+     * 拒绝原因
+     */
+    private String rejectReason;
+
+    /**
+     * 可见范围:1-公开,2-仅粉丝,3-私密
+     */
+    private Integer visibleRange;
+
+    /**
+     * 发布地址
+     */
+    private String address;
+
+    /**
+     * POI名称
+     */
+    private String poiName;
+}

+ 33 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/MomentQueryDTO.java

@@ -0,0 +1,33 @@
+package com.ylx.massage.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 动态查询请求DTO
+ */
+@Data
+@ApiModel("动态查询请求")
+public class MomentQueryDTO {
+
+    @ApiModelProperty("页码")
+    private Integer pageNum = 1;
+
+    @ApiModelProperty("每页数量")
+    private Integer pageSize = 10;
+
+    @ApiModelProperty("城市编码")
+    private String cityCode;
+
+    @ApiModelProperty("用户纬度")
+    private BigDecimal latitude;
+
+    @ApiModelProperty("用户经度")
+    private BigDecimal longitude;
+
+    @ApiModelProperty("查询类型:1-推荐,2-同城,3-附近")
+    private Integer queryType;
+}

+ 61 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentDetailVO.java

@@ -0,0 +1,61 @@
+package com.ylx.massage.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 动态详情VO
+ */
+@Data
+@ApiModel("动态详情")
+public class MomentDetailVO {
+
+    @ApiModelProperty("动态ID")
+    private Long id;
+
+    @ApiModelProperty("动态标题")
+    private String title;
+
+    @ApiModelProperty("动态内容")
+    private String content;
+
+    @ApiModelProperty("媒体类型:1-图片,2-视频")
+    private Integer mediaType;
+
+    @ApiModelProperty("封面图URL")
+    private String coverUrl;
+
+    @ApiModelProperty("浏览量")
+    private Integer viewCount;
+
+    @ApiModelProperty("发布时间")
+    private LocalDateTime publishTime;
+
+    @ApiModelProperty("发布地址")
+    private String address;
+
+    @ApiModelProperty("POI名称")
+    private String poiName;
+
+    @ApiModelProperty("技师ID")
+    private Long technicianId;
+
+    @ApiModelProperty("技师昵称")
+    private String technicianName;
+
+    @ApiModelProperty("技师头像")
+    private String technicianAvatar;
+
+    @ApiModelProperty("技师状态:1-可服务,2-可预约")
+    private Integer technicianStatus;
+
+    @ApiModelProperty("媒体列表")
+    private List<MomentMediaVO> mediaList;
+
+    @ApiModelProperty("距离(km)")
+    private Double distance;
+}

+ 99 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentListVO.java

@@ -0,0 +1,99 @@
+package com.ylx.massage.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 动态列表项VO
+ * <p>
+ * 用于返回技师动态列表中的单条动态信息,包含动态基本信息、
+ * 技师信息、状态信息等。主要用于推荐、同城、附近等动态列表接口。
+ * </p>
+ *
+ * @author ylx
+ * @since 2025-01-19
+ */
+@Data
+@ApiModel("动态列表项")
+public class MomentListVO {
+
+    /**
+     * 动态ID
+     * 动态的主键标识
+     */
+    @ApiModelProperty("动态ID")
+    private Long id;
+
+    /**
+     * 动态标题
+     * 动态的标题文字,用于列表展示
+     */
+    @ApiModelProperty("动态标题")
+    private String title;
+
+    /**
+     * 封面图URL
+     * 动态封面图片的访问地址,默认取第一张图片或视频封面图
+     */
+    @ApiModelProperty("封面图URL")
+    private String coverUrl;
+
+    /**
+     * 浏览量
+     * 动态被查看的总次数
+     */
+    @ApiModelProperty("浏览量")
+    private Integer viewCount;
+
+    /**
+     * 发布时间
+     * 动态发布的时间戳
+     */
+    @ApiModelProperty("发布时间")
+    private LocalDateTime publishTime;
+
+    /**
+     * 技师ID
+     * 发布该动态的技师主键标识
+     */
+    @ApiModelProperty("技师ID")
+    private Long technicianId;
+
+    /**
+     * 技师昵称
+     */
+    @ApiModelProperty("技师昵称")
+    private String technicianName;
+
+    /**
+     * 技师头像
+     * 技师头像图片的访问地址
+     */
+    @ApiModelProperty("技师头像")
+    private String technicianAvatar;
+
+    /**
+     * 技师状态
+     * <ul>
+     *   <li>1 - 可服务:当前时间技师空闲,可以接单</li>
+     *   <li>2 - 可预约:当前时间技师"已接单-服务中"</li>
+     * </ul>
+     */
+    @ApiModelProperty("技师状态:1-可服务,2-可预约")
+    private Integer technicianStatus;
+
+    /**
+     * 距离
+     * 用户与动态发布位置的距离,单位:千米(km)
+     * <ul>
+     *   <li>推荐动态:该字段为null</li>
+     *   <li>同城动态:该字段为null</li>
+     *   <li>附近动态:该字段有具体数值,保留两位小数</li>
+     * </ul>
+     */
+    @ApiModelProperty("距离(km)")
+    private Double distance;
+}

+ 28 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/MomentMediaVO.java

@@ -0,0 +1,28 @@
+package com.ylx.massage.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 动态媒体VO
+ */
+@Data
+@ApiModel("动态媒体")
+public class MomentMediaVO {
+
+    @ApiModelProperty("媒体ID")
+    private Long id;
+
+    @ApiModelProperty("媒体URL")
+    private String mediaUrl;
+
+    @ApiModelProperty("媒体类型:1-图片,2-视频")
+    private Integer mediaType;
+
+    @ApiModelProperty("排序")
+    private Integer sortOrder;
+
+    @ApiModelProperty("文件格式")
+    private String fileFormat;
+}

+ 13 - 18
nightFragrance-massage/src/main/java/com/ylx/massage/enums/OrderStatusEnum.java

@@ -19,6 +19,11 @@ public enum OrderStatusEnum {
 
     //进行中(待接单,已接单,已出发,已到达,服务中)
 
+    /**
+     * 待付款
+     */
+    WAIT_PAY(-1, "待付款"),
+
     /**
      * 待接单
      */
@@ -55,34 +60,24 @@ public enum OrderStatusEnum {
     COMPLETE(5, "已完成"),
 
     /**
-     * 退单待审核
-     */
-    CANCEL_APPLICATION_PENDING(6, "退单待审核"),
-
-    /**
-     * 退单审核通过
-     */
-    CANCEL_APPLICATION_PASS(7, "退单审核通过"),
-
-    /**
-     * 退单审核拒绝
+     * 已取消
      */
-    CANCEL_APPLICATION_REFUSE(8, "退单审核拒绝"),
+    CANCEL(-2, "已取消"),
 
     /**
-     * 待付款
+     * 已拒绝
      */
-    WAIT_PAY(-1, "待付款"),
+    REFUSE(-3, "已拒绝"),
 
     /**
-     * 已取消
+     * 退单待审核
      */
-    CANCEL(-2, "已取消"),
+    CANCEL_APPLICATION_PENDING(7, "退单待审核"),
 
     /**
-     * 已拒绝
+     * 退单审核通过
      */
-    REFUSE(-3, "已拒绝");
+    CANCEL_APPLICATION_PASS(8, "退单审核通过");
 
 
     private final Integer code;

+ 19 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/MomentMediaMapper.java

@@ -0,0 +1,19 @@
+package com.ylx.massage.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ylx.massage.domain.MomentMedia;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 动态媒体Mapper接口
+ */
+@Mapper
+public interface MomentMediaMapper extends BaseMapper<MomentMedia> {
+
+    /**
+     * 根据动态ID查询媒体列表
+     */
+    List<MomentMedia> selectMediaListByMomentId(Long momentId);
+}

+ 36 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TechnicianMomentMapper.java

@@ -0,0 +1,36 @@
+package com.ylx.massage.mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.massage.domain.TechnicianMoment;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 技师动态Mapper接口
+ */
+@Mapper
+public interface TechnicianMomentMapper extends BaseMapper<TechnicianMoment> {
+
+    /**
+     * 查询推荐动态(全部动态,按日期倒序,同一天按浏览量倒序)
+     */
+    List<TechnicianMoment> selectRecommendedMoments(Page<TechnicianMoment> page);
+
+    /**
+     * 查询同城动态(按城市和发布时间倒序)
+     */
+    List<TechnicianMoment> selectSameCityMoments(Page<TechnicianMoment> page, @Param("cityCode") String cityCode);
+
+    /**
+     * 查询附近动态(10km内,按日期倒序,同一天按距离排序)
+     */
+    List<TechnicianMoment> selectNearbyMoments(@Param("latitude") BigDecimal latitude, @Param("longitude") BigDecimal longitude, @Param("limit") Integer limit, @Param("offset") Integer offset);
+
+    /**
+     * 增加浏览量
+     */
+    int incrementViewCount(@Param("momentId") Long momentId);
+}

+ 51 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/ITechnicianMomentService.java

@@ -0,0 +1,51 @@
+package com.ylx.massage.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ylx.massage.domain.vo.MomentDetailVO;
+import com.ylx.massage.domain.vo.MomentListVO;
+
+import java.util.List;
+
+/**
+ * 技师动态服务接口
+ */
+public interface ITechnicianMomentService extends IService<com.ylx.massage.domain.TechnicianMoment> {
+
+    /**
+     * 查询推荐动态列表(不分地区,按日期倒序,同一天按浏览量倒序)
+     *
+     * @param pageNum  页码
+     * @param pageSize 每页数量
+     * @return 动态列表
+     */
+    List<MomentListVO> getRecommendedMoments(Integer pageNum, Integer pageSize);
+
+    /**
+     * 查询同城动态列表(按城市和发布时间倒序)
+     *
+     * @param cityCode 城市编码
+     * @param pageNum  页码
+     * @param pageSize 每页数量
+     * @return 动态列表
+     */
+    List<MomentListVO> getSameCityMoments(String cityCode, Integer pageNum, Integer pageSize);
+
+    /**
+     * 查询附近动态列表(10km内,按日期倒序,同一天按距离排序)
+     *
+     * @param latitude  用户纬度
+     * @param longitude 用户经度
+     * @param pageNum   页码
+     * @param pageSize  每页数量
+     * @return 动态列表
+     */
+    List<MomentListVO> getNearbyMoments(java.math.BigDecimal latitude, java.math.BigDecimal longitude, Integer pageNum, Integer pageSize);
+
+    /**
+     * 查询动态详情(浏览量+1)
+     *
+     * @param momentId 动态ID
+     * @return 动态详情
+     */
+    MomentDetailVO getMomentDetail(Long momentId);
+}

+ 2 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CancelOrderApplicationServiceImpl.java

@@ -104,6 +104,8 @@ public class CancelOrderApplicationServiceImpl extends ServiceImpl<com.ylx.massa
         application.setTechId(order.getcJsId());
         application.setTechName(technician != null ? technician.getcName() : null);
         application.setTechNickName(technician != null ? technician.getcNickName() : null);
+        // 设置技师手机号
+        application.setTechPhone(technician != null ? technician.getcPhone() : null);
 
         // 从订单明细中提取项目名称
         String projectName = extractProjectName(order);

+ 254 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TechnicianMomentServiceImpl.java

@@ -0,0 +1,254 @@
+package com.ylx.massage.service.impl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ylx.common.exception.ServiceException;
+import com.ylx.massage.domain.MomentMedia;
+import com.ylx.massage.domain.TechnicianMoment;
+import com.ylx.massage.domain.TJs;
+import com.ylx.massage.domain.vo.MomentDetailVO;
+import com.ylx.massage.domain.vo.MomentListVO;
+import com.ylx.massage.domain.vo.MomentMediaVO;
+import com.ylx.massage.mapper.MomentMediaMapper;
+import com.ylx.massage.mapper.TechnicianMomentMapper;
+import com.ylx.massage.mapper.TJsMapper;
+import com.ylx.massage.service.ITechnicianMomentService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 技师动态服务实现类
+ */
+@Service
+@Slf4j
+public class TechnicianMomentServiceImpl extends ServiceImpl<TechnicianMomentMapper, TechnicianMoment>
+        implements ITechnicianMomentService {
+
+    @Autowired
+    private TechnicianMomentMapper momentMapper;
+
+    @Autowired
+    private MomentMediaMapper mediaMapper;
+
+    @Autowired
+    private TJsMapper tJsMapper;
+
+    /**
+     * 查询推荐动态列表(不分地区,按日期倒序,同一天按浏览量倒序)
+     *
+     * @param pageNum  页码,默认1
+     * @param pageSize 每页数量,默认10
+     * @return R<List<MomentListVO>> 动态列表
+     */
+    @Override
+    public List<MomentListVO> getRecommendedMoments(Integer pageNum, Integer pageSize) {
+        Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
+        List<TechnicianMoment> moments = momentMapper.selectRecommendedMoments(page);
+        if (moments == null || moments.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 查询技师信息
+        List<String> technicianIds = moments.stream().map(TechnicianMoment::getTechnicianId).collect(Collectors.toList());
+        Map<String, TJs> technicianMap = getTechnicianMap(technicianIds);
+
+        // 组装VO
+        return moments.stream().map(moment -> {
+            MomentListVO vo = new MomentListVO();
+            BeanUtils.copyProperties(moment, vo);
+
+            TJs technician = technicianMap.get(moment.getTechnicianId());
+            if (technician != null) {
+                vo.setTechnicianName(technician.getcNickName());
+                vo.setTechnicianAvatar(technician.getcPortrait());
+                vo.setTechnicianStatus(getTechnicianStatus(technician));
+            }
+            return vo;
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * 查询同城动态列表(按城市和发布时间倒序)
+     */
+    @Override
+    public List<MomentListVO> getSameCityMoments(String cityCode, Integer pageNum, Integer pageSize) {
+        Page<TechnicianMoment> page = new Page<>(pageNum, pageSize);
+        List<TechnicianMoment> moments = momentMapper.selectSameCityMoments(page, cityCode);
+        if (moments == null || moments.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 查询技师信息
+        List<String> technicianIds = moments.stream().map(TechnicianMoment::getTechnicianId).collect(Collectors.toList());
+        Map<String, TJs> technicianMap = getTechnicianMap(technicianIds);
+
+        // 组装VO
+        return moments.stream().map(moment -> {
+            MomentListVO vo = new MomentListVO();
+            BeanUtils.copyProperties(moment, vo);
+
+            TJs technician = technicianMap.get(moment.getTechnicianId());
+            if (technician != null) {
+                vo.setTechnicianName(technician.getcNickName());
+                vo.setTechnicianAvatar(technician.getcPortrait());
+                vo.setTechnicianStatus(getTechnicianStatus(technician));
+            }
+            return vo;
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * 查询附近动态列表(10km内,按日期倒序,同一天按距离排序)
+     */
+    @Override
+    public List<MomentListVO> getNearbyMoments(BigDecimal latitude, BigDecimal longitude, Integer pageNum, Integer pageSize) {
+        if (latitude == null || longitude == null) {
+            throw new ServiceException("地理位置信息不能为空");
+        }
+
+        int offset = (pageNum - 1) * pageSize;
+        List<TechnicianMoment> moments = momentMapper.selectNearbyMoments(latitude, longitude, pageSize, offset);
+
+        if (moments == null || moments.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 查询技师信息
+        List<String> technicianIds = moments.stream()
+                .map(TechnicianMoment::getTechnicianId)
+                .collect(Collectors.toList());
+
+        Map<String, TJs> technicianMap = getTechnicianMap(technicianIds);
+
+        // 组装VO(需要计算距离)
+        return moments.stream().map(moment -> {
+            MomentListVO vo = new MomentListVO();
+            BeanUtils.copyProperties(moment, vo);
+
+            TJs technician = technicianMap.get(String.valueOf(moment.getTechnicianId()));
+            if (technician != null) {
+                vo.setTechnicianName(technician.getcNickName());
+                vo.setTechnicianAvatar(technician.getcPortrait());
+                vo.setTechnicianStatus(getTechnicianStatus(technician));
+
+                // 计算距离
+                if (moment.getLatitude() != null && moment.getLongitude() != null) {
+                    double distance = calculateDistance(
+                            latitude.doubleValue(), longitude.doubleValue(),
+                            moment.getLatitude().doubleValue(), moment.getLongitude().doubleValue()
+                    );
+                    vo.setDistance(Math.round(distance * 100.0) / 100.0); // 保留两位小数
+                }
+            }
+
+            return vo;
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * 查询动态详情(浏览量+1)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MomentDetailVO getMomentDetail(Long momentId) {
+        if (momentId == null) {
+            throw new ServiceException("动态ID不能为空");
+        }
+
+        // 查询动态信息
+        TechnicianMoment moment = momentMapper.selectById(momentId);
+        if (moment == null) {
+            throw new ServiceException("动态不存在");
+        }
+
+        // 增加浏览量
+        momentMapper.incrementViewCount(momentId);
+        moment.setViewCount(moment.getViewCount() + 1);
+
+        // 查询技师信息
+        TJs technician = tJsMapper.selectById(String.valueOf(moment.getTechnicianId()));
+
+        // 查询媒体列表
+        List<MomentMedia> mediaList = mediaMapper.selectMediaListByMomentId(momentId);
+        List<MomentMediaVO> mediaVOList = mediaList.stream().map(media -> {
+            MomentMediaVO mediaVO = new MomentMediaVO();
+            BeanUtils.copyProperties(media, mediaVO);
+            return mediaVO;
+        }).collect(Collectors.toList());
+
+        // 组装VO
+        MomentDetailVO vo = new MomentDetailVO();
+        BeanUtils.copyProperties(moment, vo);
+
+        if (technician != null) {
+            vo.setTechnicianName(technician.getcNickName());
+            vo.setTechnicianAvatar(technician.getcPortrait());
+            vo.setTechnicianStatus(getTechnicianStatus(technician));
+        }
+
+        vo.setMediaList(mediaVOList);
+
+        // 计算距离(如果有位置信息)
+        if (moment.getLatitude() != null && moment.getLongitude() != null) {
+            // 这里可以传入用户位置计算距离,暂时不计算
+            vo.setDistance(null);
+        }
+
+        return vo;
+    }
+
+    /**
+     * 批量查询技师信息
+     *
+     * @param technicianIds 技师ID列表
+     * @return Map<String, TJs> 技师ID-技师实体映射
+     */
+    private Map<String, TJs> getTechnicianMap(List<String> technicianIds) {
+        List<String> ids = technicianIds.stream()
+                .map(String::valueOf)
+                .collect(Collectors.toList());
+        return tJsMapper.selectBatchIds(ids).stream().collect(Collectors.toMap(TJs::getId, t -> t));
+    }
+
+    /**
+     * 获取技师状态
+     * 1-可服务(当前时间技师空闲)
+     * 2-可预约(当前时间技师"已接单-服务中")
+     */
+    private Integer getTechnicianStatus(TJs technician) {
+        if (technician.getnStatus() != null && technician.getnStatus() == 0) {
+            return 1; // 可服务
+        } else if (technician.getnStatus() != null && technician.getnStatus() == 1) {
+            return 2; // 可预约(服务中)
+        }
+        return null;
+    }
+
+    /**
+     * 计算两点之间的距离(单位:km)
+     * 使用Haversine公式
+     */
+    private double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
+        final double R = 6371; // 地球半径,单位km
+
+        double dLat = Math.toRadians(lat2 - lat1);
+        double dLon = Math.toRadians(lon2 - lon1);
+
+        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+                Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
+                        Math.sin(dLon / 2) * Math.sin(dLon / 2);
+
+        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+        return R * c;
+    }
+}

+ 29 - 0
nightFragrance-massage/src/main/resources/mapper/massage/MomentMediaMapper.xml

@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ylx.massage.mapper.MomentMediaMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.ylx.massage.domain.MomentMedia">
+        <id column="id" property="id"/>
+        <result column="moment_id" property="momentId"/>
+        <result column="media_url" property="mediaUrl"/>
+        <result column="media_type" property="mediaType"/>
+        <result column="sort_order" property="sortOrder"/>
+        <result column="create_time" property="createTime"/>
+        <result column="file_size" property="fileSize"/>
+        <result column="file_format" property="fileFormat"/>
+    </resultMap>
+
+    <!-- 根据动态ID查询媒体列表 -->
+    <select id="selectMediaListByMomentId" resultMap="BaseResultMap">
+        SELECT
+            *
+        FROM
+            t_moment_media
+        WHERE
+            moment_id = #{momentId}
+        ORDER BY
+            sort_order ASC
+    </select>
+
+</mapper>

+ 95 - 0
nightFragrance-massage/src/main/resources/mapper/massage/TechnicianMomentMapper.xml

@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ylx.massage.mapper.TechnicianMomentMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.ylx.massage.domain.TechnicianMoment">
+        <id column="id" property="id"/>
+        <result column="technician_id" property="technicianId"/>
+        <result column="title" property="title"/>
+        <result column="content" property="content"/>
+        <result column="media_type" property="mediaType"/>
+        <result column="cover_url" property="coverUrl"/>
+        <result column="view_count" property="viewCount"/>
+        <result column="publish_time" property="publishTime"/>
+        <result column="status" property="status"/>
+        <result column="city_code" property="cityCode"/>
+        <result column="latitude" property="latitude"/>
+        <result column="longitude" property="longitude"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="audit_status" property="auditStatus"/>
+        <result column="reject_reason" property="rejectReason"/>
+        <result column="visible_range" property="visibleRange"/>
+        <result column="address" property="address"/>
+        <result column="poi_name" property="poiName"/>
+    </resultMap>
+
+    <!-- 查询推荐动态:按日期倒序,同一天按浏览量倒序 -->
+    <select id="selectRecommendedMoments" resultMap="BaseResultMap">
+        SELECT
+            tm.*
+        FROM
+            t_technician_moment tm
+        WHERE
+            tm.status = 1
+            AND tm.audit_status = 2
+            AND tm.visible_range = 1
+        ORDER BY
+            tm.view_count DESC,
+            tm.publish_time DESC
+    </select>
+
+    <!-- 查询同城动态:按城市和发布时间倒序展示 -->
+    <select id="selectSameCityMoments" resultMap="BaseResultMap">
+        SELECT
+            tm.*
+        FROM
+            t_technician_moment tm
+        WHERE
+            tm.status = 1
+            AND tm.audit_status = 2
+            AND tm.visible_range = 1
+            AND tm.city_code = #{cityCode}
+        ORDER BY
+            tm.publish_time DESC
+    </select>
+
+    <!-- 查询附近动态:10km内,按日期倒序,同一天按距离排序 -->
+    <select id="selectNearbyMoments" resultMap="BaseResultMap">
+        SELECT
+            tm.*,
+            ST_Distance_Sphere(
+                POINT(tm.longitude, tm.latitude),
+                POINT(#{longitude}, #{latitude})
+            ) / 1000 AS distance
+        FROM
+            t_technician_moment tm
+        WHERE
+            tm.status = 1
+            AND tm.audit_status = 2
+            AND tm.visible_range = 1
+            AND tm.latitude IS NOT NULL
+            AND tm.longitude IS NOT NULL
+            AND ST_Distance_Sphere(
+                POINT(tm.longitude, tm.latitude),
+                POINT(#{longitude}, #{latitude})
+            ) / 1000 &lt;= 10
+        ORDER BY
+            DATE(tm.publish_time) DESC,
+            distance ASC,
+            tm.publish_time DESC
+        LIMIT #{limit} OFFSET #{offset}
+    </select>
+
+    <!-- 增加浏览量 -->
+    <update id="incrementViewCount">
+        UPDATE
+            t_technician_moment
+        SET
+            view_count = view_count + 1
+        WHERE
+            id = #{momentId}
+    </update>
+
+</mapper>