Просмотр исходного кода

Merge remote-tracking branch 'origin/dev' into dev

# Conflicts:
#	nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java
jinwenhai 2 дней назад
Родитель
Сommit
ba3b161214
62 измененных файлов с 2420 добавлено и 181 удалено
  1. 43 29
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java
  2. 27 0
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/PayController.java
  3. 22 0
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TAddressController.java
  4. 43 5
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TFareSettingController.java
  5. 137 0
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TGeoFenceController.java
  6. 38 18
      nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/WxController.java
  7. 5 0
      nightFragrance-massage/pom.xml
  8. 2 0
      nightFragrance-massage/src/main/java/com/ylx/fareSetting/domian/dto/FareCalculateDTO.java
  9. 21 14
      nightFragrance-massage/src/main/java/com/ylx/fareSetting/service/impl/MaProjectFareSettingServiceImpl.java
  10. 3 3
      nightFragrance-massage/src/main/java/com/ylx/giftCard/service/impl/GiftCardOrderServiceImpl.java
  11. 124 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/TGeoFence.java
  12. 40 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/QueryWxUserDTO.java
  13. 57 31
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/TWxUserVo.java
  14. 35 0
      nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/UserAddressListVO.java
  15. 25 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/CouponReceiveMapper.java
  16. 9 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TAddressMapper.java
  17. 10 0
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TGeoFenceMapper.java
  18. 3 3
      nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TWxUserMapper.java
  19. 11 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/CouponReceiveService.java
  20. 8 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/CouponService.java
  21. 2 1
      nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java
  22. 9 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/TAddressService.java
  23. 11 2
      nightFragrance-massage/src/main/java/com/ylx/massage/service/TFareSettingService.java
  24. 54 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/TGeoFenceService.java
  25. 2 1
      nightFragrance-massage/src/main/java/com/ylx/massage/service/TWxUserService.java
  26. 26 4
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CouponReceiveServiceImpl.java
  27. 109 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CouponServiceImpl.java
  28. 9 16
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java
  29. 7 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TAddressServiceImpl.java
  30. 14 21
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TFareSettingServiceImpl.java
  31. 190 0
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TGeoFenceServiceImpl.java
  32. 14 16
      nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TWxUserServiceImpl.java
  33. 55 0
      nightFragrance-massage/src/main/java/com/ylx/order/controller/OrderController.java
  34. 4 4
      nightFragrance-massage/src/main/java/com/ylx/order/domain/OrderStatusFlow.java
  35. 3 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/TOrder.java
  36. 50 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderDateQueryDTO.java
  37. 21 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderDeleteDTO.java
  38. 46 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderSubmitDTO.java
  39. 29 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderUpdateStatusDTO.java
  40. 80 0
      nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/OrderDateQueryVo.java
  41. 44 0
      nightFragrance-massage/src/main/java/com/ylx/order/enums/OrderStatusEnum.java
  42. 19 0
      nightFragrance-massage/src/main/java/com/ylx/order/enums/PaymentMethodEnum.java
  43. 2 2
      nightFragrance-massage/src/main/java/com/ylx/order/service/OrderStatusFlowService.java
  44. 27 0
      nightFragrance-massage/src/main/java/com/ylx/order/service/TOrderService.java
  45. 2 2
      nightFragrance-massage/src/main/java/com/ylx/order/service/impl/OrderStatusFlowServiceImpl.java
  46. 420 4
      nightFragrance-massage/src/main/java/com/ylx/order/service/impl/TOrderServiceImpl.java
  47. 37 0
      nightFragrance-massage/src/main/java/com/ylx/point/domain/dto/UserPointDetailQueryDTO.java
  48. 38 0
      nightFragrance-massage/src/main/java/com/ylx/point/domain/vo/UserPointDetailVO.java
  49. 12 0
      nightFragrance-massage/src/main/java/com/ylx/point/mapper/PointUserLogMapper.java
  50. 11 0
      nightFragrance-massage/src/main/java/com/ylx/point/service/IPointUserLogService.java
  51. 18 0
      nightFragrance-massage/src/main/java/com/ylx/point/service/impl/PointUserLogServiceImpl.java
  52. 0 1
      nightFragrance-massage/src/main/java/com/ylx/shopingfundsdetail/domain/vo/ShoppingFundsDetailAddDto.java
  53. 19 0
      nightFragrance-massage/src/main/java/com/ylx/shopingfundsdetail/enums/ShoppingFundsExpenseTypeEnum.java
  54. 74 0
      nightFragrance-massage/src/main/resources/mapper/massage/CouponReceiveMapper.xml
  55. 15 0
      nightFragrance-massage/src/main/resources/mapper/massage/TAddressMapper.xml
  56. 44 3
      nightFragrance-massage/src/main/resources/mapper/massage/TWxUserMapper.xml
  57. 29 1
      nightFragrance-massage/src/main/resources/mapper/point/PointUserLogMapper.xml
  58. 37 0
      nightFragrance-massage/src/test/java/com/ylx/massage/mapper/TAddressMapperXmlTest.java
  59. 95 0
      nightFragrance-massage/src/test/java/com/ylx/massage/mapper/TWxUserMapperXmlTest.java
  60. 19 0
      nightFragrance-massage/src/test/java/com/ylx/massage/service/impl/TAddressServiceImplTest.java
  61. 36 0
      nightFragrance-massage/src/test/java/com/ylx/point/mapper/PointUserLogMapperXmlTest.java
  62. 24 0
      nightFragrance-massage/src/test/java/com/ylx/point/service/impl/PointUserLogServiceImplTest.java

+ 43 - 29
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/MaTechnicianController.java

@@ -1,5 +1,11 @@
 package com.ylx.web.controller.massage;
 
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import cn.hutool.json.JSONObject;
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -43,6 +49,14 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.ObjectUtils;
 import org.springframework.web.bind.annotation.*;
+import com.ylx.common.annotation.Log;
+import com.ylx.common.core.controller.BaseController;
+import com.ylx.common.core.domain.AjaxResult;
+import com.ylx.common.enums.BusinessType;
+import com.ylx.massage.domain.MaTechnician;
+import com.ylx.massage.service.IMaTechnicianService;
+import com.ylx.common.utils.poi.ExcelUtil;
+import com.ylx.common.core.page.TableDataInfo;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletRequest;
@@ -59,6 +73,7 @@ import java.util.regex.Pattern;
  * @author ylx
  * @date 2024-03-22
  */
+@Api("技师管理")
 @RestController
 @RequestMapping("/technician/technician")
 public class MaTechnicianController extends BaseController {
@@ -220,15 +235,15 @@ public class MaTechnicianController extends BaseController {
             }
             // 短信验证
             String msg = redisTemplate.opsForValue().get(PHONE_THREEUSERPARTCLIENT_CODE_KEY + req.getTePhone());
-//            if (StringUtils.isEmpty(msg)) {
-//                return Result.error("验证码已失效");
-//            }
-//            if (msg != null && msg.startsWith("\"") && msg.endsWith("\"")) {
-//                msg = msg.substring(1, msg.length() - 1);
-//            }
-//            if (!req.getPhoneMsg().equals(msg)) {
-//                return Result.error("短信验证码不正确");
-//            }
+            if (StringUtils.isEmpty(msg)) {
+                return Result.error("验证码已失效");
+            }
+            if (msg != null && msg.startsWith("\"") && msg.endsWith("\"")) {
+                msg = msg.substring(1, msg.length() - 1);
+            }
+            if (!req.getPhoneMsg().equals(msg)) {
+                return Result.error("短信验证码不正确");
+            }
             if (StringUtils.isNotEmpty(req.getPhoneImgMsg())) {
                 validateCaptcha(req.getTeName(), req.getPhoneImgMsg(), req.getUuid());
             }
@@ -409,27 +424,26 @@ public class MaTechnicianController extends BaseController {
             }
         }
 
-        /**
-         * 后台上传商户合同文件
-         *
-         * @param id   商户ID
-         * @param file 合同文件
-         * @return AjaxResult 上传结果
-         */
-        @ApiOperation("后台上传商户合同文件")
-        @PreAuthorize("@ss.hasPermi('technician:technician:edit')")
-        @Log(title = "商户合同", businessType = BusinessType.UPDATE)
-        @PostMapping("/merchant/{id}/contract")
-        public AjaxResult uploadMerchantContract (@PathVariable("id") Integer id, @RequestParam("file") MultipartFile
-        file){
-            try {
-                LoginUser loginUser = getLoginUser();
-                return maTechnicianService.uploadMerchantContract(id, file, loginUser);
-            } catch (Exception e) {
-                e.printStackTrace();
-                throw new RuntimeException(e);
-            }
+    /**
+     * 后台上传商户合同文件
+     *
+     * @param id   商户ID
+     * @param map 合同文件
+     * @return R 上传结果
+     */
+    @ApiOperation("后台上传商户合同文件")
+    @PreAuthorize("@ss.hasPermi('technician:technician:edit')")
+    @Log(title = "商户合同", businessType = BusinessType.UPDATE)
+    @PostMapping("/merchant/{id}/contract")
+    public R uploadMerchantContract(@PathVariable("id") Integer id, @RequestBody Map<String,Object> map) {
+        try {
+            LoginUser loginUser = getLoginUser();
+            return R.ok(maTechnicianService.uploadMerchantContract(id, map, loginUser));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
         }
+    }
 
         /**
          * 查询商户入驻审核列表

+ 27 - 0
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/PayController.java

@@ -34,6 +34,9 @@ import com.ylx.massage.enums.BillTypeEnum;
 import com.ylx.massage.service.RefundVoucherService;
 import com.ylx.massage.service.TRechargeService;
 import com.ylx.massage.service.TWxUserService;
+import com.ylx.order.domain.TOrder;
+import com.ylx.order.enums.OrderStatusEnum;
+import com.ylx.order.service.TOrderService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -75,6 +78,8 @@ public class PayController {
     private WxPayService wxPayService;
     @Resource
     private IGiftCardOrderService giftCardOrderService;
+    @Resource
+    private TOrderService orderService;
 
     /**
      * 小程序微信支付的第一步,统一下单
@@ -229,6 +234,28 @@ public class PayController {
                     this.giftCardOrderService.processGiftCardPayment(result, wxUser, cardOrder);
                 } else if (WxPayTypeEnum.EMOTION_GOODS.getCode().equals(attach)) {
                     log.info("检测到情感服务商品支付成功,订单号: {}", outTradeNo);
+
+                    // 3.1 更新订单支付状态
+                    LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
+                    wrapper.eq(TOrder::getOrderNo, outTradeNo);
+                    TOrder order = this.orderService.getOne(wrapper);
+                    if (ObjectUtil.isNull(order)) {
+                        log.error("订单不存在,订单号: {}", outTradeNo);
+                        resp.put("code", "FAIL");
+                        resp.put("message", "订单不存在");
+                        return resp;
+                    }
+                    // 3.2 检查是否已处理
+                    if (ObjectUtil.equals(OrderStatusEnum.PENDING_DISPATCH.getCode(), order.getStatus())) {
+                        log.warn("订单已处理过:{}", outTradeNo);
+                        resp.put("code", "SUCCESS");
+                        resp.put("message", "OK");
+                        return resp;
+                    }
+
+                    // 3.3 处理订单相关数据
+                    this.orderService.processOrderPayment(result, wxUser, order);
+
                 }
             }
 

+ 22 - 0
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TAddressController.java

@@ -7,7 +7,9 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.common.core.controller.BaseController;
 import com.ylx.common.core.domain.R;
+import com.ylx.common.utils.StringUtils;
 import com.ylx.massage.domain.TAddress;
+import com.ylx.massage.domain.vo.UserAddressListVO;
 import com.ylx.massage.service.TAddressService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -78,6 +80,26 @@ public class TAddressController extends BaseController {
         return R.ok(this.tAddressService.list(objectLambdaQueryWrapper));
     }
 
+    /**
+     * PC端根据openId查询用户地址
+     *
+     * @param openId 用户openId
+     * @return R<List<UserAddressListVO>> 用户地址列表
+     */
+    @ApiOperation("PC端根据openId查询用户地址")
+    @GetMapping("pc/getUserAddressList")
+    public R<List<UserAddressListVO>> getUserAddressList(@RequestParam String openId) {
+        try {
+            if (StringUtils.isBlank(openId)) {
+                return R.fail("openId不能为空");
+            }
+            return R.ok(this.tAddressService.getPcUserAddressList(openId));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
     @ApiOperation("根据Id查询地址")
     @GetMapping("getById")
     public R<TAddress> getById(@RequestParam String id) {

+ 43 - 5
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TFareSettingController.java

@@ -2,6 +2,7 @@ package com.ylx.web.controller.massage;
 
 
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.common.core.controller.BaseController;
@@ -54,7 +55,16 @@ public class TFareSettingController extends BaseController {
     @GetMapping("selectAll")
     @ApiOperation("分页查询数据")
     public R selectAll(Page<TFareSetting> page, TFareSetting tFareSetting) {
-        return R.ok(this.tFareSettingService.page(page, new QueryWrapper<>(tFareSetting)));
+        try {
+            LambdaQueryWrapper<TFareSetting> queryWrapper = new LambdaQueryWrapper<>();
+            if(StringUtils.isNotBlank(tFareSetting.getCityName())){
+                queryWrapper.like(TFareSetting::getCityName, tFareSetting.getCityName());
+            }
+            return R.ok(this.tFareSettingService.page(page, queryWrapper));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
     }
 
     /**
@@ -98,20 +108,48 @@ public class TFareSettingController extends BaseController {
      * @return R 修改结果
      */
     @PostMapping("update")
-    @ApiOperation("修改数据")
+    @ApiOperation("修改车费设置数据")
     public R update(@RequestBody TFareSetting tFareSetting) {
-        return R.ok(this.tFareSettingService.updateFareSetting(tFareSetting));
+        try {
+            return R.ok(this.tFareSettingService.updateFareSetting(tFareSetting));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
     }
 
     /**
-     * 删除数据
+     * 根据主键ID删除车费设置数据
+     *
+     * @param id 主键
+     * @return R 删除结果
+     */
+    @DeleteMapping("delete/{id}")
+    @ApiOperation("根据主键ID删除车费设置数据")
+    public R delete(@PathVariable String id) {
+        try {
+            return R.ok(this.tFareSettingService.removeById(id));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 批量删除车费设置数据
      *
      * @param idList 主键结合
      * @return R 删除结果
      */
     @PostMapping("delete")
+    @ApiOperation("批量删除车费设置数据")
     public R delete(@RequestBody List<String> idList) {
-        return R.ok(this.tFareSettingService.removeByIds(idList));
+        try {
+            return R.ok(this.tFareSettingService.removeByIds(idList));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
     }
 
     /**

+ 137 - 0
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/TGeoFenceController.java

@@ -0,0 +1,137 @@
+package com.ylx.web.controller.massage;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.common.annotation.Log;
+import com.ylx.common.core.domain.R;
+import com.ylx.common.enums.BusinessType;
+import com.ylx.common.exception.ServiceException;
+import com.ylx.massage.domain.TGeoFence;
+import com.ylx.massage.service.TGeoFenceService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 地理围栏控制器
+ */
+@RestController
+@RequestMapping("/api/geoFence/v1")
+@Slf4j
+@Api(tags = {"地理围栏管理"})
+public class TGeoFenceController {
+
+    @Resource
+    private TGeoFenceService geoFenceService;
+
+    /**
+     * 分页查询地理围栏。
+     *
+     * @param page 分页参数
+     * @param geoFence 查询参数
+     * @return R<Page<TGeoFence>>
+     */
+    @GetMapping("/select")
+    @ApiOperation("分页查询地理围栏")
+    public R<Page<TGeoFence>> select(Page<TGeoFence> page, TGeoFence geoFence) {
+        try {
+            return R.ok(geoFenceService.pageGeoFence(page, geoFence));
+        } catch (ServiceException s) {
+            log.error("分页查询地理围栏失败:{}", s.getMessage());
+            return R.fail(s.getMessage());
+        } catch (Exception e) {
+            log.error("分页查询地理围栏系统异常", e);
+            return R.fail("系统异常");
+        }
+    }
+
+    /**
+     * 查询地图展示用地理围栏。
+     *
+     * @param geoFence 查询参数
+     * @return R<List<TGeoFence>>
+     */
+    @GetMapping("/map")
+    @ApiOperation("查看地图地理围栏")
+    public R<List<TGeoFence>> map(TGeoFence geoFence) {
+        try {
+            return R.ok(geoFenceService.listMapGeoFence(geoFence));
+        } catch (ServiceException s) {
+            log.error("查询地图地理围栏失败:{}", s.getMessage());
+            return R.fail(s.getMessage());
+        } catch (Exception e) {
+            log.error("查询地图地理围栏系统异常", e);
+            return R.fail("系统异常");
+        }
+    }
+
+    /**
+     * 新增地理围栏。
+     *
+     * @param geoFence 地理围栏
+     * @return R
+     */
+    @Log(title = "地理围栏管理", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ApiOperation("新增地理围栏")
+    public R add(@RequestBody TGeoFence geoFence) {
+        try {
+            return R.ok(geoFenceService.addGeoFence(geoFence));
+        } catch (ServiceException s) {
+            log.error("新增地理围栏失败:{}", s.getMessage());
+            return R.fail(s.getMessage());
+        } catch (Exception e) {
+            log.error("新增地理围栏系统异常", e);
+            return R.fail("系统异常");
+        }
+    }
+
+    /**
+     * 编辑地理围栏。
+     *
+     * @param geoFence 地理围栏
+     * @return R
+     */
+    @Log(title = "地理围栏管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/update")
+    @ApiOperation("编辑地理围栏")
+    public R update(@RequestBody TGeoFence geoFence) {
+        try {
+            return R.ok(geoFenceService.updateGeoFence(geoFence));
+        } catch (ServiceException s) {
+            log.error("编辑地理围栏失败:{}", s.getMessage());
+            return R.fail(s.getMessage());
+        } catch (Exception e) {
+            log.error("编辑地理围栏系统异常", e);
+            return R.fail("系统异常");
+        }
+    }
+
+    /**
+     * 删除地理围栏。
+     *
+     * @param geoFence 地理围栏
+     * @return R
+     */
+    @Log(title = "地理围栏管理", businessType = BusinessType.DELETE)
+    @PostMapping("/del")
+    @ApiOperation("删除地理围栏")
+    public R del(@RequestBody TGeoFence geoFence) {
+        try {
+            return R.ok(geoFenceService.deleteGeoFence(geoFence));
+        } catch (ServiceException s) {
+            log.error("删除地理围栏失败:{}", s.getMessage());
+            return R.fail(s.getMessage());
+        } catch (Exception e) {
+            log.error("删除地理围栏系统异常", e);
+            return R.fail("系统异常");
+        }
+    }
+}

+ 38 - 18
nightFragrance-admin/src/main/java/com/ylx/web/controller/massage/WxController.java

@@ -4,7 +4,6 @@ import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
 import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
-import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.net.url.UrlBuilder;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
@@ -13,31 +12,29 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.common.annotation.Log;
-import com.ylx.common.config.RuoYiConfig;
 import com.ylx.common.constant.Constants;
 import com.ylx.common.core.controller.BaseController;
 import com.ylx.common.core.domain.AjaxResult;
 import com.ylx.common.core.domain.R;
-
 import com.ylx.common.core.domain.model.WxLoginUser;
 import com.ylx.common.enums.BusinessType;
 import com.ylx.common.utils.MessageUtils;
 import com.ylx.common.utils.file.FileUploadUtils;
-import com.ylx.common.utils.file.FileUtils;
 import com.ylx.framework.config.ServerConfig;
 import com.ylx.framework.manager.AsyncManager;
 import com.ylx.framework.manager.factory.AsyncFactory;
 import com.ylx.framework.web.service.WxTokenService;
-import com.ylx.massage.domain.CouponReceive;
+import com.ylx.massage.domain.TWxUser;
+import com.ylx.massage.domain.dto.QueryWxUserDTO;
 import com.ylx.massage.domain.vo.TWxUserVo;
 import com.ylx.massage.service.CouponReceiveService;
-import com.ylx.massage.service.TCommentService;
+import com.ylx.massage.service.TWxUserService;
 import com.ylx.massage.service.TbFileService;
-import com.ylx.massage.utils.LocationUtil;
 import com.ylx.massage.utils.WxQrCodeUtil;
 import com.ylx.massage.utils.WxUtil;
-import com.ylx.massage.domain.TWxUser;
-import com.ylx.massage.service.TWxUserService;
+import com.ylx.point.domain.dto.UserPointDetailQueryDTO;
+import com.ylx.point.domain.vo.UserPointDetailVO;
+import com.ylx.point.service.IPointUserLogService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -45,14 +42,12 @@ import me.chanjar.weixin.common.error.WxErrorException;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.MediaType;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
@@ -94,6 +89,9 @@ public class WxController extends BaseController {
     @Autowired
     private CouponReceiveService couponReceiveService;
 
+    @Resource
+    private IPointUserLogService pointUserLogService;
+
     @Resource(name = "commonAsyncExecutor")
     private ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
@@ -361,16 +359,38 @@ public class WxController extends BaseController {
      *
      * @param page 分页参数
      * @param user
-     * @return R<Page<TWxUser>> 微信用户列表
+     * @return R<Page<TWxUserVo>> 微信用户列表
      */
     @GetMapping("pc/getUserList")
     @ApiOperation("查询微信用户列表")
-    public R<Page<TWxUser>> getUserList(Page<TWxUser> page, TWxUser user) {
-        Page<TWxUser> pageSelect = wxUserService.page(page, new LambdaQueryWrapper<TWxUser>()
-                .like(StringUtils.isNotBlank(user.getcNickName()), TWxUser::getcNickName, user.getcNickName())
-                .like(StringUtils.isNotBlank(user.getcPhone()), TWxUser::getcPhone, user.getcPhone())
-                .orderByDesc(TWxUser::getCreateTime));
-        return R.ok(pageSelect);
+    public R<Page<TWxUserVo>> getUserList(Page<TWxUserVo> page, QueryWxUserDTO queryWxUserDTO) {
+        try {
+            return R.ok(wxUserService.getUserList(page, queryWxUserDTO));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * PC端根据用户openId查询用户积分详情
+     *
+     * @param page 分页参数
+     * @param queryDTO 查询条件,包含用户openId、积分项目、开始时间、结束时间
+     * @return R<Page<UserPointDetailVO>> 用户积分详情分页列表
+     */
+    @GetMapping("pc/getUserPointDetailList")
+    @ApiOperation("PC端根据用户openId查询用户积分详情")
+    public R<Page<UserPointDetailVO>> getUserPointDetailList(Page<UserPointDetailVO> page, UserPointDetailQueryDTO queryDTO) {
+        try {
+            if (queryDTO == null || StringUtils.isBlank(queryDTO.getOpenId())) {
+                return R.fail("openId不能为空");
+            }
+            return R.ok(pointUserLogService.getPcUserPointDetailList(page, queryDTO));
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
     }
 
 

+ 5 - 0
nightFragrance-massage/pom.xml

@@ -57,5 +57,10 @@
             <groupId>com.github.stuxuhai</groupId>
             <artifactId>jpinyin</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>

+ 2 - 0
nightFragrance-massage/src/main/java/com/ylx/fareSetting/domian/dto/FareCalculateDTO.java

@@ -1,5 +1,6 @@
 package com.ylx.fareSetting.domian.dto;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -24,6 +25,7 @@ public class FareCalculateDTO implements Serializable {
 
     @ApiModelProperty(value = "预约开始时间", example = "2024-01-07 15:30:00")
     @NotNull(message = "预约时间不能为空")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private LocalDateTime appointmentStartTime;
 
     @ApiModelProperty("城市编码")

+ 21 - 14
nightFragrance-massage/src/main/java/com/ylx/fareSetting/service/impl/MaProjectFareSettingServiceImpl.java

@@ -45,15 +45,16 @@ public class MaProjectFareSettingServiceImpl extends ServiceImpl<MaProjectFareSe
         // 1.获取商户的默认地址
         TAddress address = this.addressService.getOne(new LambdaQueryWrapper<TAddress>()
                 .eq(TAddress::getMerchantId, dto.getMerchantId())
-                .eq(TAddress::getType, 1));
+                .eq(TAddress::getIsDefault, 1)
+                .eq(TAddress::getIsDelete, 0));
         if (ObjectUtil.isNull(address)) {
             throw new ServiceException("无法获取商户的默认地址");
         }
 
         // 2. 计算直线距离(公里)
         String distanceStr = DistanceUtil.formatDistanceInKilometers(
-                dto.getLongitude(), dto.getLatitude(),
-                address.getLongitude(), address.getLatitude()
+                dto.getLatitude(),dto.getLongitude(),
+                address.getLatitude(),address.getLongitude()
         );
         double straightLineKm;
         if ("未知".equals(distanceStr)) {
@@ -101,20 +102,22 @@ public class MaProjectFareSettingServiceImpl extends ServiceImpl<MaProjectFareSe
             }
         }
 
-        if (ObjectUtil.isNull(finalConfig)) {
-            throw new ServiceException("无法获取有效的车费计算规则");
-        }
+        BigDecimal merchantFreeKm = BigDecimal.ZERO;
+        
+        if (ObjectUtil.isNotNull(finalConfig)) {
 
-        // 商户配置的免车费距离(用于扣减)
-        BigDecimal merchantFreeKm = isDay ? finalConfig.getDayFreeKm() : finalConfig.getNightFreeKm();
+            // 商户配置的免车费距离(用于扣减)
+            merchantFreeKm = isDay ? finalConfig.getDayFreeKm() : finalConfig.getNightFreeKm();
 
-        if (ObjectUtil.isNull(merchantFreeKm) || merchantFreeKm.compareTo(BigDecimal.ZERO) <= 0) {
-            merchantFreeKm = BigDecimal.ZERO;
-            log.info("商户[{}]配置的免车费距离为 null 或 <= 0, 视为 0", dto.getMerchantId());
-        } else {
-            log.info("商户[{}]使用配置ID={}, 免费公里数: {}", dto.getMerchantId(), finalConfig.getId(), merchantFreeKm);
+            if (ObjectUtil.isNull(merchantFreeKm) || merchantFreeKm.compareTo(BigDecimal.ZERO) <= 0) {
+                merchantFreeKm = BigDecimal.ZERO;
+                log.info("商户[{}]配置的免车费距离为 null 或 <= 0, 视为 0", dto.getMerchantId());
+            } else {
+                log.info("商户[{}]使用配置ID={}, 免费公里数: {}", dto.getMerchantId(), finalConfig.getId(), merchantFreeKm);
+            }
         }
 
+
         // 4. 计算【打车距离】(即计费里程)
         BigDecimal straightLineBigDecimal = new BigDecimal(straightLineKm).setScale(6, RoundingMode.HALF_UP);
         BigDecimal effectiveDistance = straightLineBigDecimal.subtract(merchantFreeKm);
@@ -125,7 +128,7 @@ public class MaProjectFareSettingServiceImpl extends ServiceImpl<MaProjectFareSe
                 straightLineKm, merchantFreeKm, effectiveDistance);
 
         // 5. 获取城市车费规则(用于最终计费)
-        TFareSettingVo cityFare = fareSettingService.getFareSetting(appointmentStartTime.toString(), dto.getCityCode());
+        TFareSettingVo cityFare = fareSettingService.getFareSetting(appointmentStartTime, dto.getCityCode());
         if (ObjectUtil.isNull(cityFare)) {
             throw new ServiceException("未找到城市[" + dto.getCityCode() + "]的车费配置");
         }
@@ -158,6 +161,10 @@ public class MaProjectFareSettingServiceImpl extends ServiceImpl<MaProjectFareSe
         }
 
         // 7. 设置结果
+        result.setFreeKm(merchantFreeKm);
+        result.setBaseFare(baseFare);
+        result.setBaseDistance(baseDistance);
+        result.setAdditionalFarePer(additionalFarePer);
         result.setActualDistanceKm(straightLineBigDecimal.setScale(2, RoundingMode.HALF_UP)); // 原始直线距离(展示用)
         result.setEstimatedFare(estimatedFare);
         result.setIsFree(isFree);

+ 3 - 3
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/impl/GiftCardOrderServiceImpl.java

@@ -18,6 +18,7 @@ import com.ylx.massage.domain.TWxUser;
 import com.ylx.massage.service.TJsService;
 import com.ylx.massage.service.TWxUserService;
 import com.ylx.shopingfundsdetail.domain.vo.ShoppingFundsDetailAddDto;
+import com.ylx.shopingfundsdetail.enums.ShoppingFundsExpenseTypeEnum;
 import com.ylx.shopingfundsdetail.service.ShoppingFundsDetailService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -37,8 +38,6 @@ public class GiftCardOrderServiceImpl extends ServiceImpl<GiftCardOrderMapper, G
     private TWxUserService wxUserService;
     @Resource
     private ShoppingFundsDetailService shoppingFundsDetailService;
-    // 充值
-    public static final int EXPENSE_TYPE_RECHARGE = 0;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -107,6 +106,7 @@ public class GiftCardOrderServiceImpl extends ServiceImpl<GiftCardOrderMapper, G
         log.info("订单取消成功,订单号:{},购物卡ID:{}", order.getOrderNo(), order.getGiftCardId());
     }
 
+    @Override
     @Transactional(rollbackFor = Exception.class)
     public void processGiftCardPayment(WxPayOrderNotifyV3Result.DecryptNotifyResult result, TWxUser wxUser,GiftCardOrder cardOrder) {
 
@@ -133,7 +133,7 @@ public class GiftCardOrderServiceImpl extends ServiceImpl<GiftCardOrderMapper, G
         dto.setUserId(wxUser.getId());
         dto.setAmount(payAmount);
         dto.setOrderNo(result.getOutTradeNo());
-        dto.setExpenseType(EXPENSE_TYPE_RECHARGE);
+        dto.setExpenseType(ShoppingFundsExpenseTypeEnum.RECHARGE.getCode());
         dto.setBalance(newBalance);
         dto.setGiftCardId(cardOrder.getGiftCardId());
         shoppingFundsDetailService.addShoppingFundsDetail(dto);

+ 124 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/TGeoFence.java

@@ -0,0 +1,124 @@
+package com.ylx.massage.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 地理围栏
+ */
+@Getter
+@Setter
+@Accessors(chain = true)
+@TableName("t_geo_fence")
+@ApiModel(value = "TGeoFence", description = "地理围栏")
+public class TGeoFence implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 围栏ID。
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    @ApiModelProperty("围栏ID")
+    private Long id;
+
+    /**
+     * 城市编码。
+     */
+    @TableField("city_code")
+    @ApiModelProperty("城市编码")
+    private String cityCode;
+
+    /**
+     * 城市名称。
+     */
+    @TableField("city_name")
+    @ApiModelProperty("城市名称")
+    private String cityName;
+
+    /**
+     * 围栏名称。
+     */
+    @TableField("fence_name")
+    @ApiModelProperty("围栏名称")
+    private String fenceName;
+
+    /**
+     * 围栏介绍。
+     */
+    @TableField("fence_intro")
+    @ApiModelProperty("围栏介绍")
+    private String fenceIntro;
+
+    /**
+     * 地图搜索或定位地址。
+     */
+    @TableField("address")
+    @ApiModelProperty("地图搜索或定位地址")
+    private String address;
+
+    /**
+     * 围栏中心点经度。
+     */
+    @TableField("longitude")
+    @ApiModelProperty("围栏中心点经度")
+    private BigDecimal longitude;
+
+    /**
+     * 围栏中心点纬度。
+     */
+    @TableField("latitude")
+    @ApiModelProperty("围栏中心点纬度")
+    private BigDecimal latitude;
+
+    /**
+     * 围栏半径,单位:KM。
+     */
+    @TableField("radius_km")
+    @ApiModelProperty("围栏半径,单位:KM")
+    private BigDecimal radiusKm;
+
+    /**
+     * 风险等级:1-低风险,2-中风险,3-高风险。
+     */
+    @TableField("risk_level")
+    @ApiModelProperty("风险等级:1-低风险,2-中风险,3-高风险")
+    private Integer riskLevel;
+
+    /**
+     * 创建时间。
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField("create_time")
+    @ApiModelProperty("创建时间")
+    private Date createTime;
+
+    /**
+     * 更新时间。
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField("update_time")
+    @ApiModelProperty("更新时间")
+    private Date updateTime;
+
+    /**
+     * 是否删除:0-否,1-是。
+     */
+    @TableLogic
+    @TableField("is_delete")
+    @ApiModelProperty("是否删除:0-否,1-是")
+    private Integer isDelete;
+}

+ 40 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/dto/QueryWxUserDTO.java

@@ -0,0 +1,40 @@
+package com.ylx.massage.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 查询用户DTO
+ */
+@Data
+public class QueryWxUserDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 昵称
+     */
+    @ApiModelProperty("昵称")
+    private String cNickName;
+
+    /**
+     * 电话号码
+     */
+    @ApiModelProperty("电话号码")
+    private String cPhone;
+
+    /**
+     * 创建开始时间,格式:yyyy-MM-dd HH:mm:ss
+     */
+    @ApiModelProperty("创建开始时间")
+    private String startTime;
+
+    /**
+     * 创建结束时间,格式:yyyy-MM-dd HH:mm:ss
+     */
+    @ApiModelProperty("创建结束时间")
+    private String endTime;
+}

+ 57 - 31
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/TWxUserVo.java

@@ -1,85 +1,111 @@
 package com.ylx.massage.domain.vo;
 
-import com.alibaba.fastjson.JSONArray;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
-import com.ylx.massage.domain.TAddress;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.experimental.Accessors;
+import lombok.Data;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
 
 /**
- * 微信用户
+ * 微信用户VO
  */
-@Getter
-@Setter
-@Accessors(chain = true)
-@TableName(value = "t_wx_user",autoResultMap = true)
-@ApiModel(value = "TWxUser", description = "微信用户表")
-public class TWxUserVo extends TAddress implements Serializable {
+@Data
+public class TWxUserVo  implements Serializable {
 
     private static final long serialVersionUID = 1L;
 
+    /**
+     * 用户ID
+     */
+    @JsonProperty(value = "id", index = 0)
+    @ApiModelProperty("用户ID")
+    private String id;
 
     /**
      * 微信小程序openId
      */
-    @TableField("c_openid")
+    @JsonProperty(value = "cOpenid", index = 1)
     @ApiModelProperty("微信小程序openId")
     private String cOpenid;
 
     /**
-     * 电话号码
+     * 头像地址
      */
-    @TableField("c_phone")
-    @ApiModelProperty("电话号码")
-    private String cPhone;
+    @JsonProperty(value = "cIcon", index = 2)
+    @ApiModelProperty("头像地址")
+    private String cIcon;
 
     /**
      * 昵称
      */
-    @TableField("c_nick_name")
+    @JsonProperty(value = "cNickName", index = 3)
     @ApiModelProperty("昵称")
     private String cNickName;
 
+    /**
+     * 电话号码
+     */
+    @JsonProperty(value = "cPhone", index = 4)
+    @ApiModelProperty("电话号码")
+    private String cPhone;
+
     /**
      * 消费金额
      */
-    @TableField("d_money")
+    @JsonProperty(value = "dMoney", index = 5)
     @ApiModelProperty("消费金额")
     private BigDecimal dMoney;
 
-    @TableField("distribution_amount")
-    @ApiModelProperty("分销金额")
-    private BigDecimal distributionAmount;
-
     /**
      * 下单次数
      */
-    @TableField("n_num")
+    @JsonProperty(value = "nNum", index = 6)
     @ApiModelProperty("下单次数")
     private Integer nNum;
 
     /**
      * 当前余额
      */
-    @TableField("d_balance")
+    @JsonProperty(value = "dBalance", index = 7)
     @ApiModelProperty("当前余额")
     private BigDecimal dBalance;
 
     /**
      * 总收益
      */
-    @TableField("d_all_money")
+    @JsonProperty(value = "dAllMoney", index = 8)
     @ApiModelProperty("总收益")
     private BigDecimal dAllMoney;
 
+    /**
+     * 购物金
+     */
+    @JsonProperty(value = "giftCardPayAmount", index = 9)
+    @ApiModelProperty("购物金")
+    private BigDecimal giftCardPayAmount;
+
+    /**
+     * 当前可用积分
+     */
+    @JsonProperty(value = "currentPoints", index = 10)
+    @ApiModelProperty("当前积分")
+    private Integer currentPoints;
+
+    /**
+     * 累计获得积分
+     */
+    @JsonProperty(value = "totalPoints", index = 11)
+    @ApiModelProperty("累计积分")
+    private Integer totalPoints;
+
+    /**
+     * 创建时间,格式:yyyy-MM-dd HH:mm:ss
+     */
+    @JsonProperty(value = "createTime", index = 12)
+    @ApiModelProperty("创建时间")
+    private String createTime;
+
 }

+ 35 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/domain/vo/UserAddressListVO.java

@@ -0,0 +1,35 @@
+package com.ylx.massage.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * PC user address list item.
+ */
+@Data
+@ApiModel(value = "UserAddressListVO", description = "PC user address list item")
+public class UserAddressListVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("地址ID")
+    private String id;
+
+    @ApiModelProperty("地址")
+    private String address;
+
+    @ApiModelProperty("用户姓名")
+    private String userName;
+
+    @ApiModelProperty("手机号")
+    private String phone;
+
+    @ApiModelProperty("创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+}

+ 25 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/CouponReceiveMapper.java

@@ -1,11 +1,14 @@
 package com.ylx.massage.mapper;
 
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ylx.massage.domain.vo.CouponReceiveVo;
 import org.apache.ibatis.annotations.Param;
 import com.ylx.massage.domain.CouponReceive;
+import org.apache.ibatis.annotations.Select;
 
 /**
  * 优惠券领取表(CouponReceive)表数据库访问层
@@ -34,5 +37,27 @@ public interface CouponReceiveMapper extends BaseMapper<CouponReceive> {
 
 
     List<CouponReceiveVo> getByOpenId(String openid);
+
+    Map<String, Object> selectCouponDetailForCalc(@Param("couponId") String couponId,
+                                                  @Param("openId") String openId);
+
+    // 乐观锁核销
+    int useCouponOptimisticLock(@Param("couponId") String couponId,
+                                @Param("openId") String openId,
+                                @Param("orderId") Long orderId,
+                                @Param("orderType") Integer orderType,
+                                @Param("useTime") LocalDateTime useTime);
+
+    // 使用数量 +1
+    int incrementUsedNum(@Param("couponId") String couponId);
+
+    // 乐观锁退还
+    int returnCouponOptimisticLock(@Param("couponId") String couponId,
+                                   @Param("openId") String openId,
+                                   @Param("orderId") Long orderId);
+
+    // 使用数量 -1
+    int decrementUsedNum(@Param("couponId") String couponId);
+
 }
 

+ 9 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TAddressMapper.java

@@ -4,6 +4,7 @@ import java.util.List;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ylx.massage.domain.TAddress;
+import com.ylx.massage.domain.vo.UserAddressListVO;
 import org.apache.ibatis.annotations.Param;
 
 /**
@@ -31,5 +32,13 @@ public interface TAddressMapper extends BaseMapper<TAddress> {
      */
     int insertOrUpdateBatch(@Param("entities") List<TAddress> entities);
 
+    /**
+     * PC user address list.
+     *
+     * @param openId user openId
+     * @return address list
+     */
+    List<UserAddressListVO> selectPcUserAddressList(@Param("openId") String openId);
+
 }
 

+ 10 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TGeoFenceMapper.java

@@ -0,0 +1,10 @@
+package com.ylx.massage.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ylx.massage.domain.TGeoFence;
+
+/**
+ * 地理围栏 Mapper 接口
+ */
+public interface TGeoFenceMapper extends BaseMapper<TGeoFence> {
+}

+ 3 - 3
nightFragrance-massage/src/main/java/com/ylx/massage/mapper/TWxUserMapper.java

@@ -2,21 +2,21 @@ 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.dto.QueryWxUserDTO;
 import com.ylx.massage.domain.vo.HomeBlock;
 import com.ylx.massage.domain.vo.TWxUserVo;
 import org.apache.ibatis.annotations.Mapper;
 import com.ylx.massage.domain.TWxUser;
 import org.apache.ibatis.annotations.Param;
-
 import java.util.Date;
 import java.util.List;
-
 /**
  * Mapper 接口
  */
 @Mapper
 public interface TWxUserMapper extends BaseMapper<TWxUser> {
-    Page<TWxUserVo> selectTWxUserList(@Param("page") Page<TWxUserVo> page, @Param("user") TWxUserVo user);
+
+    Page<TWxUserVo> selectTWxUserList(@Param("page") Page<TWxUserVo> page, @Param("user") QueryWxUserDTO queryWxUserDTO);
 
     List<HomeBlock> getBlockGetUser(@Param("start") Date start, @Param("end") Date end, @Param("deptId") String deptId);
 

+ 11 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/CouponReceiveService.java

@@ -8,7 +8,9 @@ import com.ylx.massage.domain.vo.ClaimCouponRequestVO;
 import com.ylx.massage.domain.vo.CouponReceiveVo;
 import com.ylx.massage.domain.vo.CouponReceivesVO;
 
+import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 优惠券领取表(CouponReceive)表服务接口
@@ -48,5 +50,14 @@ public interface CouponReceiveService extends IService<CouponReceive> {
     List<Coupon> couponWindows(CouponReceive couponReceive);
 
 
+    Map<String, Object> selectCouponDetailForCalc(String couponId, String openId);
+
+    int useCouponOptimisticLock(String couponId, String openId, Long orderId, Integer orderType, LocalDateTime now);
+
+    int incrementUsedNum(String couponId);
+
+    int returnCouponOptimisticLock(String couponId, String openId, Long orderId);
+
+    void decrementUsedNum(String couponId);
 }
 

+ 8 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/CouponService.java

@@ -4,6 +4,8 @@ package com.ylx.massage.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.massage.domain.Coupon;
 
+import java.math.BigDecimal;
+
 /**
  * 优惠券的规则信息(Coupon)表服务接口
  *
@@ -12,5 +14,11 @@ import com.ylx.massage.domain.Coupon;
  */
 public interface CouponService extends IService<Coupon> {
 
+    BigDecimal calculateDiscountAmount(String couponId, String openId,BigDecimal orderAmount);
+
+    void useCoupon(String couponId, String openId, Long orderId, Integer orderType);
+
+    void returnCoupon(String couponId, String openId, Long orderId);
+
 }
 

+ 2 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/service/IMaTechnicianService.java

@@ -1,6 +1,7 @@
 package com.ylx.massage.service;
 
 import java.util.List;
+import java.util.Map;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
@@ -85,7 +86,7 @@ public interface IMaTechnicianService extends IService<MaTechnician> {
      * @param loginUser 当前登录用户
      * @return 上传结果
      */
-    AjaxResult uploadMerchantContract(Integer id, MultipartFile file, LoginUser loginUser);
+    Integer uploadMerchantContract(Integer id, Map<String,Object> file, LoginUser loginUser);
 
     /**
      * 后台查询商户入驻审核列表

+ 9 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/TAddressService.java

@@ -2,6 +2,7 @@ package com.ylx.massage.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.massage.domain.TAddress;
+import com.ylx.massage.domain.vo.UserAddressListVO;
 import com.ylx.useradress.domain.dto.UserAddressAddDto;
 import com.ylx.useradress.domain.dto.UserAddressDeleteDto;
 import com.ylx.useradress.domain.dto.UserAddressDto;
@@ -32,6 +33,14 @@ public interface TAddressService extends IService<TAddress> {
      */
     public List<UserAddressVo> getByOpenIdList(UserAddressDto dto);
 
+    /**
+     * PC user address list.
+     *
+     * @param openId user openId
+     * @return address list
+     */
+    List<UserAddressListVO> getPcUserAddressList(String openId);
+
     Boolean defaultAddress(TAddress tAddress);
 
     Object insertAddress(TAddress tAddress);

+ 11 - 2
nightFragrance-massage/src/main/java/com/ylx/massage/service/TFareSettingService.java

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.massage.domain.TFareSetting;
 import com.ylx.massage.domain.vo.TFareSettingVo;
 
+import java.time.LocalDateTime;
+
 /**
  * 车费设置表(TFareSetting)表服务接口
  *
@@ -14,19 +16,26 @@ public interface TFareSettingService extends IService<TFareSetting> {
 
     /**
      * 新增车费设置数据
+     *
      * @param tFareSetting
      * @return boolean
      */
     boolean add(TFareSetting tFareSetting);
 
-    TFareSetting updateFareSetting(TFareSetting tFareSetting);
+    /**
+     * 更新车费设置数据
+     *
+     * @param tFareSetting
+     * @return boolean
+     */
+    boolean updateFareSetting(TFareSetting tFareSetting);
 
     /**
      * 根据城市编码获取车费设置 C端
      * @param cityCode
      * @return
      */
-    TFareSettingVo getFareSetting(String userBookingTime,String cityCode);
+    TFareSettingVo getFareSetting(LocalDateTime userBookingTime, String cityCode);
 
 }
 

+ 54 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/TGeoFenceService.java

@@ -0,0 +1,54 @@
+package com.ylx.massage.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ylx.massage.domain.TGeoFence;
+
+import java.util.List;
+
+/**
+ * 地理围栏服务接口
+ */
+public interface TGeoFenceService extends IService<TGeoFence> {
+
+    /**
+     * 分页查询地理围栏。
+     *
+     * @param page 分页参数
+     * @param geoFence 查询参数
+     * @return 分页结果
+     */
+    Page<TGeoFence> pageGeoFence(Page<TGeoFence> page, TGeoFence geoFence);
+
+    /**
+     * 查询地图展示用地理围栏。
+     *
+     * @param geoFence 查询参数
+     * @return 地理围栏列表
+     */
+    List<TGeoFence> listMapGeoFence(TGeoFence geoFence);
+
+    /**
+     * 新增地理围栏。
+     *
+     * @param geoFence 地理围栏
+     * @return 是否新增成功
+     */
+    Boolean addGeoFence(TGeoFence geoFence);
+
+    /**
+     * 编辑地理围栏。
+     *
+     * @param geoFence 地理围栏
+     * @return 是否编辑成功
+     */
+    Boolean updateGeoFence(TGeoFence geoFence);
+
+    /**
+     * 删除地理围栏。
+     *
+     * @param geoFence 地理围栏
+     * @return 是否删除成功
+     */
+    Boolean deleteGeoFence(TGeoFence geoFence);
+}

+ 2 - 1
nightFragrance-massage/src/main/java/com/ylx/massage/service/TWxUserService.java

@@ -3,6 +3,7 @@ package com.ylx.massage.service;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.massage.domain.TWxUser;
+import com.ylx.massage.domain.dto.QueryWxUserDTO;
 import com.ylx.massage.domain.vo.TWxUserVo;
 import com.ylx.shoppingfunds.domain.dto.MyShoppingFundsUpdateDto;
 import com.ylx.shoppingfunds.domain.vo.MyShoppingFundsQueryVo;
@@ -29,7 +30,7 @@ public interface TWxUserService extends IService<TWxUser> {
      */
     TWxUser getByPhone(String phone);
 
-    Page<TWxUserVo> getUserList(Page<TWxUserVo> page, TWxUserVo user);
+    Page<TWxUserVo> getUserList(Page<TWxUserVo> page, QueryWxUserDTO queryWxUserDTO);
 
     /**
      * 绑定手机号

+ 26 - 4
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CouponReceiveServiceImpl.java

@@ -29,10 +29,7 @@ import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -266,5 +263,30 @@ public class CouponReceiveServiceImpl extends ServiceImpl<CouponReceiveMapper, C
             return Collections.emptyList();
         }
     }
+
+    @Override
+    public Map<String, Object> selectCouponDetailForCalc(String couponId, String openId) {
+        return this.baseMapper.selectCouponDetailForCalc(couponId, openId);
+    }
+
+    @Override
+    public int useCouponOptimisticLock(String couponId, String openId, Long orderId, Integer orderType, LocalDateTime now) {
+        return this.baseMapper.useCouponOptimisticLock(couponId, openId, orderId, orderType, now);
+    }
+
+    @Override
+    public int incrementUsedNum(String couponId) {
+        return this.baseMapper.incrementUsedNum(couponId);
+    }
+
+    @Override
+    public int returnCouponOptimisticLock(String couponId, String openId, Long orderId) {
+        return this.baseMapper.returnCouponOptimisticLock(couponId,openId,orderId);
+    }
+
+    @Override
+    public void decrementUsedNum(String couponId) {
+        this.baseMapper.decrementUsedNum(couponId);
+    }
 }
 

+ 109 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/CouponServiceImpl.java

@@ -1,11 +1,26 @@
 package com.ylx.massage.service.impl;
 
 
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ylx.common.exception.ServiceException;
 import com.ylx.massage.domain.Coupon;
+import com.ylx.massage.domain.CouponReceive;
 import com.ylx.massage.mapper.CouponMapper;
+import com.ylx.massage.service.CouponReceiveService;
 import com.ylx.massage.service.CouponService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
 
 /**
  * 优惠券的规则信息(Coupon)表服务实现类
@@ -13,8 +28,102 @@ import org.springframework.stereotype.Service;
  * @author makejava
  * @since 2024-05-13 16:32:59
  */
+@Slf4j
 @Service("couponService")
 public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {
 
+    @Resource
+    private CouponReceiveService couponReceiveService;
+
+    @Override
+    public BigDecimal calculateDiscountAmount(String couponId, String openId, BigDecimal orderAmount) {
+
+        // 1. 查询优惠券领取详情
+        Map<String, Object> detail = couponReceiveService.selectCouponDetailForCalc(couponId, openId);
+
+        if (CollUtil.isEmpty(detail)) {
+            throw new ServiceException("未查询到该用户的优惠券信息");
+        }
+
+        // 3. 校验优惠券状态(必须是 0:待使用)
+        Integer couponStatus = (Integer) detail.get("coupon_status");
+        if (couponStatus == null || couponStatus != 0) {
+            throw new ServiceException("优惠券状态异常,不可使用");
+        }
+
+        // 4. 校验有效期
+        LocalDate validStart = ((java.sql.Date) detail.get("valid_start_time")).toLocalDate();
+        LocalDate expiration = ((java.sql.Date) detail.get("expiration_time")).toLocalDate();
+        LocalDate today = LocalDate.now();
+        if (today.isBefore(validStart) || today.isAfter(expiration)) {
+            throw new ServiceException("优惠券不在有效期内");
+        }
+
+        // 5. 提取优惠规则字段
+        Integer discountType = (Integer) detail.get("discount_type");
+        BigDecimal discountValue = (BigDecimal) detail.get("discount_value");
+        BigDecimal rebValue = (BigDecimal) detail.get("reb_value");
+        BigDecimal thresholdAmount = (BigDecimal) detail.get("threshold_amount");
+
+        // 6. 根据类型计算抵扣金额
+        BigDecimal discountAmount = BigDecimal.ZERO;
+        switch (discountType) {
+            case 1: // 无门槛
+                discountAmount = discountValue;
+                break;
+            case 2: // 折扣 (reb_value 例如 0.8 代表8折)
+                // 抵扣金额 = 订单金额 * (1 - 折扣值)
+                discountAmount = orderAmount.multiply(BigDecimal.ONE.subtract(rebValue));
+                break;
+            case 3: // 满减
+                if (orderAmount.compareTo(thresholdAmount) < 0) {
+                    throw new ServiceException("订单金额未达到满减门槛");
+                }
+                discountAmount = discountValue;
+                break;
+            default:
+                throw new ServiceException("未知的优惠券类型: " + discountType);
+        }
+
+        // 7. 兜底逻辑:抵扣金额不能大于订单实际金额
+        if (discountAmount.compareTo(orderAmount) > 0) {
+            discountAmount = orderAmount;
+        }
+
+        // 8. 保留两位小数,四舍五入
+        return discountAmount.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void useCoupon(String couponId, String openId, Long orderId, Integer orderType) {
+        // 1. 乐观锁核销用户领取记录
+        int updatedRows = couponReceiveService.useCouponOptimisticLock(couponId, openId, orderId, orderType, LocalDateTime.now());
+        if (updatedRows == 0) {
+            throw new ServiceException("优惠券核销失败:状态异常或已被使用");
+        }
+
+        // 2. 规则表使用数量 +1
+        int ruleUpdatedRows = couponReceiveService.incrementUsedNum(couponId);
+        if (ruleUpdatedRows == 0) {
+            throw new ServiceException("优惠券规则状态异常,核销中止");
+        }
+        log.info("优惠券核销成功: couponId={}, openId={}, orderId={}", couponId, openId, orderId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void returnCoupon(String couponId, String openId, Long orderId) {
+        // 1. 将已使用的券退回到待使用状态
+        int rows = couponReceiveService.returnCouponOptimisticLock(couponId, openId, orderId);
+        if (rows == 0) {
+            log.warn("退还优惠券失败,可能券已过期或不属于该订单: couponId={}, orderId={}", couponId, orderId);
+            return;
+        }
+
+        // 2. 规则表使用数量 -1
+        this.couponReceiveService.decrementUsedNum(couponId);
+        log.info("优惠券退还成功: couponId={}, orderId={}", couponId, orderId);
+    }
 }
 

+ 9 - 16
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/MaTechnicianServiceImpl.java

@@ -400,32 +400,25 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public AjaxResult uploadMerchantContract(Integer id, MultipartFile file, LoginUser loginUser) {
+    public Integer uploadMerchantContract(Integer id, Map<String,Object> map, LoginUser loginUser) {
         if (id == null) {
             throw new ServiceException("商户ID不能为空");
         }
-        if (file == null || file.isEmpty()) {
-            throw new ServiceException("合同文件不能为空");
-        }
         MaTechnician existsMerchant = maTechnicianMapper.selectMerchantById(id);
         if (existsMerchant == null || !NOT_DELETED.equals(existsMerchant.getIsDelete())) {
             throw new ServiceException("商户不存在或已删除");
         }
-
-        AjaxResult uploadResult = fileService.uploadFile(file);
-        if (uploadResult == null || uploadResult.isError()) {
-            String message = uploadResult == null ? "合同文件上传失败" : String.valueOf(uploadResult.get(AjaxResult.MSG_TAG));
-            throw new ServiceException(message);
-        }
-        Object url = uploadResult.get("url");
-        if (url == null || StringUtils.isBlank(String.valueOf(url))) {
+        // 合同的名称
+        String contractName = String.valueOf(map.get("contractName"));
+        // 合同文件的URL
+        String url = String.valueOf(map.get("url"));
+        if (StringUtils.isBlank(url)) {
             throw new ServiceException("合同文件上传失败,未返回文件地址");
         }
-
         ContractRecord contractRecord = new ContractRecord();
         contractRecord.setMerchantId(id);
-        contractRecord.setContractName(file.getOriginalFilename());
-        contractRecord.setFileUrl(String.valueOf(url));
+        contractRecord.setContractName(contractName);
+        contractRecord.setFileUrl(url);
         contractRecord.setSignTime(DateUtils.getNowDate());
         contractRecord.setSignerName(existsMerchant.getTeName());
         contractRecord.setCreateTime(DateUtils.getNowDate());
@@ -434,7 +427,7 @@ public class MaTechnicianServiceImpl extends ServiceImpl<MaTechnicianMapper, MaT
         if (rows <= 0) {
             throw new ServiceException("保存合同记录失败");
         }
-        return uploadResult;
+        return rows;
     }
 
     /**

+ 7 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TAddressServiceImpl.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.utils.StringUtils;
+import com.ylx.massage.domain.vo.UserAddressListVO;
 import com.ylx.useradress.domain.dto.UserAddressAddDto;
 import com.ylx.useradress.domain.dto.UserAddressDeleteDto;
 import com.ylx.useradress.domain.dto.UserAddressDto;
@@ -74,6 +75,12 @@ public class TAddressServiceImpl extends ServiceImpl<TAddressMapper, TAddress> i
         }
         return addressList.stream().map(this::convertToVo).collect(Collectors.toList());
     }
+
+    @Override
+    public List<UserAddressListVO> getPcUserAddressList(String openId) {
+        return this.baseMapper.selectPcUserAddressList(openId);
+    }
+
     private UserAddressVo convertToVo(TAddress address) {
         UserAddressVo vo = new UserAddressVo();
         BeanUtils.copyProperties(address, vo);

+ 14 - 21
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TFareSettingServiceImpl.java

@@ -7,17 +7,17 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.constant.MassageConstants;
 import com.ylx.common.core.domain.entity.SysDictData;
 import com.ylx.common.exception.ServiceException;
+import com.ylx.common.utils.DictUtils;
 import com.ylx.common.utils.StringUtils;
 import com.ylx.massage.domain.vo.TFareSettingVo;
 import com.ylx.massage.mapper.TFareSettingMapper;
 import com.ylx.massage.domain.TFareSetting;
-import com.ylx.system.service.ISysDictTypeService;
 import io.jsonwebtoken.lang.Collections;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.ylx.massage.service.TFareSettingService;
 
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.format.DateTimeParseException;
 import java.util.Calendar;
@@ -36,9 +36,6 @@ import java.util.stream.Collectors;
 public class TFareSettingServiceImpl extends ServiceImpl<TFareSettingMapper, TFareSetting> implements TFareSettingService {
     private static final String DAY_TIME = "day_time";
 
-    @Autowired
-    private ISysDictTypeService dictTypeService;
-
     @Override
     public boolean add(TFareSetting tFareSetting) {
         //判断部门重复设置
@@ -59,21 +56,23 @@ public class TFareSettingServiceImpl extends ServiceImpl<TFareSettingMapper, TFa
     }
 
     @Override
-    public TFareSetting updateFareSetting(TFareSetting tFareSetting) {
+    public boolean updateFareSetting(TFareSetting tFareSetting) {
         if (tFareSetting.getEnable().equals(MassageConstants.INTEGER_ONE)) {
             LambdaQueryWrapper<TFareSetting> tFareSettingLambdaQueryWrapper = new LambdaQueryWrapper<>();
-            tFareSettingLambdaQueryWrapper.eq(TFareSetting::getCityCode, tFareSetting.getCityCode())
-                    .eq(TFareSetting::getEnable, MassageConstants.INTEGER_ONE);
+            tFareSettingLambdaQueryWrapper.eq(TFareSetting::getCityCode, tFareSetting.getCityCode()).eq(TFareSetting::getEnable, MassageConstants.INTEGER_ONE);
             //该部门已启用的
             List<TFareSetting> fareSettings = this.list(tFareSettingLambdaQueryWrapper);
             if (!Collections.isEmpty(fareSettings)) {
                 if(!fareSettings.get(MassageConstants.INTEGER_ZERO).getId().equals(tFareSetting.getId())){
-                    throw new ServiceException("该部门已有启用的设置");
+                    throw new ServiceException("该城市已有启用的设置");
                 }
             }
         }
-        this.updateById(tFareSetting);
-        return tFareSetting;
+        boolean b = this.updateById(tFareSetting);
+        if(!b){
+            throw new ServiceException("更新失败");
+        }
+        return b;
     }
 
     /**
@@ -82,9 +81,9 @@ public class TFareSettingServiceImpl extends ServiceImpl<TFareSettingMapper, TFa
      * @return
      */
     @Override
-    public TFareSettingVo getFareSetting(String userbookingTime, String cityCode) {
+    public TFareSettingVo getFareSetting(LocalDateTime userbookingTime, String cityCode) {
         // 参数校验
-        if (StringUtils.isBlank(userbookingTime) || StringUtils.isBlank(cityCode)) {
+        if (ObjectUtil.isNull(userbookingTime) || StringUtils.isBlank(cityCode)) {
             throw new IllegalArgumentException("预约时间和城市编码不能为空");
         }
 
@@ -95,18 +94,12 @@ public class TFareSettingServiceImpl extends ServiceImpl<TFareSettingMapper, TFa
         }
 
         // 解析预约时间
-        Date bookingDate;
-        try {
-            // 假设格式为 yyyy-MM-dd HH:mm:ss,可根据实际调整
-            bookingDate = DateUtil.parse(userbookingTime, "yyyy-MM-dd HH:mm:ss");
-        } catch (Exception e) {
-            throw new IllegalArgumentException("预约时间格式错误");
-        }
+        Date bookingDate = DateUtil.date(userbookingTime);;
         Calendar cal = Calendar.getInstance();
         cal.setTime(bookingDate);
         LocalTime bookingTime = LocalTime.of(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
         // 定义白天时间段(比如 6:00 - 22:00),读取字典表配置
-        List<SysDictData> dataDayTimes = dictTypeService.selectDictDataByType(DAY_TIME);
+        List<SysDictData> dataDayTimes = DictUtils.getSortedDictCache(DAY_TIME);
         if (dataDayTimes == null || dataDayTimes.size() < 2) {
             throw new IllegalStateException("白天时段字典配置缺失,请检查 dat_time 类型");
         }

+ 190 - 0
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TGeoFenceServiceImpl.java

@@ -0,0 +1,190 @@
+package com.ylx.massage.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.common.utils.DateUtils;
+import com.ylx.massage.domain.TGeoFence;
+import com.ylx.massage.mapper.TGeoFenceMapper;
+import com.ylx.massage.service.TGeoFenceService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 地理围栏服务实现类
+ */
+@Service
+public class TGeoFenceServiceImpl extends ServiceImpl<TGeoFenceMapper, TGeoFence> implements TGeoFenceService {
+
+    private static final BigDecimal MIN_LONGITUDE = new BigDecimal("-180");
+    private static final BigDecimal MAX_LONGITUDE = new BigDecimal("180");
+    private static final BigDecimal MIN_LATITUDE = new BigDecimal("-90");
+    private static final BigDecimal MAX_LATITUDE = new BigDecimal("90");
+    private static final int RISK_LOW = 1;
+    private static final int RISK_MEDIUM = 2;
+    private static final int RISK_HIGH = 3;
+    private static final int NOT_DELETE = 0;
+
+    @Override
+    public Page<TGeoFence> pageGeoFence(Page<TGeoFence> page, TGeoFence geoFence) {
+        LambdaQueryWrapper<TGeoFence> queryWrapper = buildQueryWrapper(geoFence);
+        queryWrapper.orderByAsc(TGeoFence::getId);
+        return this.page(page, queryWrapper);
+    }
+
+    @Override
+    public List<TGeoFence> listMapGeoFence(TGeoFence geoFence) {
+        LambdaQueryWrapper<TGeoFence> queryWrapper = buildQueryWrapper(geoFence);
+        queryWrapper.select(TGeoFence::getId, TGeoFence::getCityCode, TGeoFence::getCityName,
+                TGeoFence::getFenceName, TGeoFence::getAddress, TGeoFence::getLongitude,
+                TGeoFence::getLatitude, TGeoFence::getRadiusKm, TGeoFence::getRiskLevel);
+        queryWrapper.isNotNull(TGeoFence::getLongitude)
+                .isNotNull(TGeoFence::getLatitude)
+                .orderByAsc(TGeoFence::getId);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public Boolean addGeoFence(TGeoFence geoFence) {
+        validateGeoFenceParam(geoFence);
+        geoFence.setId(null);
+        geoFence.setIsDelete(NOT_DELETE);
+        geoFence.setCreateTime(DateUtils.getNowDate());
+        geoFence.setUpdateTime(DateUtils.getNowDate());
+
+        boolean saved = this.save(geoFence);
+        if (!saved) {
+            throw new ServiceException("新增围栏失败");
+        }
+        return true;
+    }
+
+    @Override
+    public Boolean updateGeoFence(TGeoFence geoFence) {
+        validateUpdateParam(geoFence);
+
+        TGeoFence exists = this.getById(geoFence.getId());
+        if (exists == null) {
+            throw new ServiceException("围栏不存在");
+        }
+
+        TGeoFence update = new TGeoFence();
+        update.setId(geoFence.getId());
+        update.setCityCode(geoFence.getCityCode());
+        update.setCityName(geoFence.getCityName());
+        update.setFenceName(geoFence.getFenceName());
+        update.setFenceIntro(geoFence.getFenceIntro());
+        update.setAddress(geoFence.getAddress());
+        update.setLongitude(geoFence.getLongitude());
+        update.setLatitude(geoFence.getLatitude());
+        update.setRadiusKm(geoFence.getRadiusKm());
+        update.setRiskLevel(geoFence.getRiskLevel());
+        update.setUpdateTime(DateUtils.getNowDate());
+
+        boolean updated = this.updateById(update);
+        if (!updated) {
+            throw new ServiceException("编辑围栏失败");
+        }
+        return true;
+    }
+
+    @Override
+    public Boolean deleteGeoFence(TGeoFence geoFence) {
+        Long id = getValidId(geoFence);
+        TGeoFence exists = this.getById(id);
+        if (exists == null) {
+            throw new ServiceException("围栏不存在");
+        }
+
+        boolean removed = this.removeById(id);
+        if (!removed) {
+            throw new ServiceException("删除围栏失败");
+        }
+        return true;
+    }
+
+    private void validateUpdateParam(TGeoFence geoFence) {
+        if (geoFence == null) {
+            throw new ServiceException("参数不能为空");
+        }
+        if (geoFence.getId() == null || geoFence.getId() <= 0) {
+            throw new ServiceException("围栏ID不能为空");
+        }
+        validateGeoFenceParam(geoFence);
+    }
+
+    private Long getValidId(TGeoFence geoFence) {
+        if (geoFence == null) {
+            throw new ServiceException("参数不能为空");
+        }
+        if (geoFence.getId() == null || geoFence.getId() <= 0) {
+            throw new ServiceException("围栏ID不能为空");
+        }
+        return geoFence.getId();
+    }
+
+    private void validateGeoFenceParam(TGeoFence geoFence) {
+        if (geoFence == null) {
+            throw new ServiceException("参数不能为空");
+        }
+        if (StringUtils.isBlank(geoFence.getCityCode())) {
+            throw new ServiceException("城市编码不能为空");
+        }
+        if (StringUtils.isBlank(geoFence.getCityName())) {
+            throw new ServiceException("城市名称不能为空");
+        }
+        if (StringUtils.isBlank(geoFence.getFenceName())) {
+            throw new ServiceException("围栏名称不能为空");
+        }
+        if (StringUtils.isBlank(geoFence.getFenceIntro())) {
+            throw new ServiceException("围栏介绍不能为空");
+        }
+        if (geoFence.getRadiusKm() == null || geoFence.getRadiusKm().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("围栏半径必须大于0");
+        }
+        if (!isValidRiskLevel(geoFence.getRiskLevel())) {
+            throw new ServiceException("风险等级值不正确");
+        }
+        validateCoordinate(geoFence.getLongitude(), geoFence.getLatitude());
+    }
+
+    private boolean isValidRiskLevel(Integer riskLevel) {
+        return riskLevel != null && (riskLevel == RISK_LOW || riskLevel == RISK_MEDIUM || riskLevel == RISK_HIGH);
+    }
+
+    private LambdaQueryWrapper<TGeoFence> buildQueryWrapper(TGeoFence geoFence) {
+        LambdaQueryWrapper<TGeoFence> queryWrapper = new LambdaQueryWrapper<>();
+        if (geoFence != null) {
+            queryWrapper.eq(StringUtils.isNotBlank(geoFence.getCityCode()), TGeoFence::getCityCode, geoFence.getCityCode())
+                    .eq(StringUtils.isNotBlank(geoFence.getCityName()), TGeoFence::getCityName, geoFence.getCityName())
+                    .like(StringUtils.isNotBlank(geoFence.getFenceName()), TGeoFence::getFenceName, geoFence.getFenceName())
+                    .like(StringUtils.isNotBlank(geoFence.getAddress()), TGeoFence::getAddress, geoFence.getAddress());
+        }
+        return queryWrapper;
+    }
+
+    /**
+     * 验证地理坐标栏中心点坐标是否有效。
+     *
+     * @param longitude 经度
+     * @param latitude 纬度
+     */
+    private void validateCoordinate(BigDecimal longitude, BigDecimal latitude) {
+        if (longitude == null) {
+            throw new ServiceException("围栏中心点经度不能为空");
+        }
+        if (latitude == null) {
+            throw new ServiceException("围栏中心点纬度不能为空");
+        }
+        if (longitude.compareTo(MIN_LONGITUDE) < 0 || longitude.compareTo(MAX_LONGITUDE) > 0) {
+            throw new ServiceException("经度必须在-180到180之间");
+        }
+        if (latitude.compareTo(MIN_LATITUDE) < 0 || latitude.compareTo(MAX_LATITUDE) > 0) {
+            throw new ServiceException("纬度必须在-90到90之间");
+        }
+    }
+}

+ 14 - 16
nightFragrance-massage/src/main/java/com/ylx/massage/service/impl/TWxUserServiceImpl.java

@@ -1,25 +1,23 @@
 package com.ylx.massage.service.impl;
 
-import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ylx.common.utils.StringUtils;
 import com.ylx.massage.domain.TWxUser;
+import com.ylx.massage.domain.dto.QueryWxUserDTO;
 import com.ylx.massage.domain.vo.TWxUserVo;
 import com.ylx.massage.mapper.TWxUserMapper;
 import com.ylx.massage.service.TWxUserService;
 import com.ylx.shopingfundsdetail.service.ShoppingFundsDetailService;
 import com.ylx.shoppingfunds.domain.dto.MyShoppingFundsUpdateDto;
 import com.ylx.shoppingfunds.domain.vo.MyShoppingFundsQueryVo;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
-import java.util.List;
-import java.util.Optional;
 
 
 /**
@@ -44,19 +42,19 @@ public class TWxUserServiceImpl extends ServiceImpl<TWxUserMapper, TWxUser> impl
     }
 
     @Override
-    public Page<TWxUserVo> getUserList(Page<TWxUserVo> page, TWxUserVo user) {
-
-        Page<TWxUserVo> tWxUserVoPage = baseMapper.selectTWxUserList(page, user);
-        List<TWxUserVo> records = tWxUserVoPage.getRecords();
-        if(CollectionUtil.isNotEmpty(records)){
-            records.forEach(item->{
-                if(StringUtils.isBlank(item.getCPhone())){
-                    item.setCPhone(Optional.ofNullable(item.getPhone()).orElse(StringUtils.EMPTY));
-                }
-            });
-            tWxUserVoPage.setRecords(records);
+    public Page<TWxUserVo> getUserList(Page<TWxUserVo> page, QueryWxUserDTO queryWxUserDTO) {
+        if (queryWxUserDTO == null) {
+            queryWxUserDTO = new QueryWxUserDTO();
+        }
+        if(StringUtils.isNotBlank(queryWxUserDTO.getStartTime())){
+            //开始时间添加00:00:00
+            queryWxUserDTO.setStartTime(queryWxUserDTO.getStartTime()+" 00:00:00");
+        }
+        if(StringUtils.isNotBlank(queryWxUserDTO.getEndTime())){
+            //结束时间添加23:59:59
+            queryWxUserDTO.setEndTime(queryWxUserDTO.getEndTime()+" 23:59:59");
         }
-        return tWxUserVoPage;
+        return baseMapper.selectTWxUserList(page, queryWxUserDTO);
     }
 
     @Override

+ 55 - 0
nightFragrance-massage/src/main/java/com/ylx/order/controller/OrderController.java

@@ -0,0 +1,55 @@
+package com.ylx.order.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.common.core.domain.R;
+import com.ylx.order.domain.dto.OrderDateQueryDTO;
+import com.ylx.order.domain.dto.OrderDeleteDTO;
+import com.ylx.order.domain.dto.OrderSubmitDTO;
+import com.ylx.order.domain.vo.OrderDateQueryVo;
+import com.ylx.order.service.TOrderService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/order")
+@Api(tags = {"订单模块"})
+@Slf4j
+public class OrderController {
+
+    @Resource
+    private TOrderService orderService;
+
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("客户端用户提交订单")
+    @PostMapping("/submit")
+    public R<Map<String, Object>> submitOrder(@Validated @RequestBody OrderSubmitDTO dto) {
+        Map<String, Object> data = this.orderService.submitOrder(dto);
+        return R.ok(data);
+    }
+
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("根据日期范围等条件查询订单")
+    @PostMapping("/queryByDate")
+    public R<Page<OrderDateQueryVo>> queryOrderByDate(@Validated @RequestBody OrderDateQueryDTO dto) {
+        Page<OrderDateQueryVo> page = orderService.queryOrderList(dto);
+        return R.ok(page);
+    }
+
+    @PreAuthorize("@customerAuth.isCustomer()")
+    @ApiOperation("逻辑删除订单(移入回收站)")
+    @PostMapping("/delete")
+    public R<?> deleteOrder(@Validated @RequestBody OrderDeleteDTO dto) {
+        orderService.logicDeleteOrder(dto.getId());
+        return R.ok("删除成功");
+    }
+}

+ 4 - 4
nightFragrance-massage/src/main/java/com/ylx/order/domain/OrderStatusFlow.java

@@ -30,11 +30,11 @@ public class OrderStatusFlow extends BaseEntity implements Serializable  {
     @ApiModelProperty("主键")
     private Long id;
     /*
-     * 订单
+     * 订单id
      */
-    @TableField("order_no")
-    @ApiModelProperty("订单")
-    private String orderNo;
+    @TableField("order_id")
+    @ApiModelProperty("订单id")
+    private Long orderId;
     /**
      * 流转状态
      */

+ 3 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/TOrder.java

@@ -186,4 +186,7 @@ public class TOrder extends BaseEntity {
 
     @ApiModelProperty("派单情况:0=未派单 1=已派单")
     private Integer dispatchedStatus;
+
+    @ApiModelProperty("优惠券id")
+    private String couponId;
 }

+ 50 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderDateQueryDTO.java

@@ -0,0 +1,50 @@
+package com.ylx.order.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.Min;
+import java.time.LocalDate;
+
+/**
+ * 类描述:用户端订单列表DTO
+ *
+ * @author Administrator
+ * @version 1.0
+ * @date 2026/6/8 9:26
+ */
+@Data
+public class OrderDateQueryDTO {
+    @ApiModelProperty("页码,默认1")
+    @Min(value = 1, message = "页码最小为1")
+    private Integer pageNum = 1;
+
+    @ApiModelProperty("每页条数,默认10")
+    @Min(value = 1, message = "每页条数最小为1")
+    private Integer pageSize = 10;
+    /**
+     * 起始日期(可选),例如 2025-11-11
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @ApiModelProperty("起始日期(可选)")
+    private LocalDate startDate;
+
+    /**
+     *  /终止日期(可选)
+     */
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @ApiModelProperty("终止日期(可选)")
+    private LocalDate endDate;
+
+    /**
+     * 项目名称
+     */
+    @ApiModelProperty("项目名称")
+    private String projectName;
+    /**
+     * 商户昵称
+     */
+    @ApiModelProperty("商户昵称")
+    private String merchantNickName;
+}

+ 21 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderDeleteDTO.java

@@ -0,0 +1,21 @@
+package com.ylx.order.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 类描述:用户端逻辑删除订单
+ *
+ * @author Administrator
+ * @version 1.0
+ * @date 2026/6/8 10:26
+ */
+@Data
+public class OrderDeleteDTO {
+
+    @NotNull(message = "订单ID不能为空")
+    @ApiModelProperty("主键ID")
+    private Long id;
+}

+ 46 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderSubmitDTO.java

@@ -0,0 +1,46 @@
+package com.ylx.order.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.DecimalMax;
+import javax.validation.constraints.DecimalMin;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@ApiModel("客户端提交订单DTO")
+public class OrderSubmitDTO implements Serializable {
+    private static final long serialVersionUID = -4463081409198769872L;
+
+    @NotNull(message = "商户ID不能为空")
+    @ApiModelProperty("商户ID")
+    private Long merchantId;
+
+    @NotNull(message = "项目ID不能为空")
+    @ApiModelProperty("项目ID")
+    private Long projectId;
+
+    @NotNull(message = "预约开始时间不能为空")
+    @ApiModelProperty("预约开始时间")
+    private LocalDateTime appointmentStartTime;
+
+    @NotNull(message = "联系人地址ID不能为空")
+    @ApiModelProperty("联系人地址ID")
+    private Long addressId;
+
+    @ApiModelProperty("优惠券id")
+    private String couponId;
+
+    @ApiModelProperty("交通费")
+    private BigDecimal trafficFee;
+
+    @NotNull(message = "支付方式不能为空")
+    @ApiModelProperty("支付方式")
+    private Integer paymentMethod;
+
+}

+ 29 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/dto/OrderUpdateStatusDTO.java

@@ -0,0 +1,29 @@
+package com.ylx.order.domain.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 类描述:修改订单表所有状态时DTO
+ *
+ * @author Administrator
+ * @version 1.0
+ * @date 2026/6/8 15:35
+ */
+@Data
+public class OrderUpdateStatusDTO {
+    /*
+     * 订单id
+     */
+    @NotNull(message = "订单id不能为空")
+    @ApiModelProperty("订单id")
+    private Long orderId;
+    /**
+     * 流转状态
+     */
+    @NotNull(message = "流转状态不能为空")
+    @ApiModelProperty("同步订单表状态")
+    private Integer status;
+}

+ 80 - 0
nightFragrance-massage/src/main/java/com/ylx/order/domain/vo/OrderDateQueryVo.java

@@ -0,0 +1,80 @@
+package com.ylx.order.domain.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 类描述:用户端订单查询列表
+ *
+ * @author Administrator
+ * @version 1.0
+ * @date 2026/6/8 9:43
+ */
+@Data
+public class OrderDateQueryVo {
+    /**
+     * 商户头像
+     */
+    @ApiModelProperty("商户头像")
+    private String merchantAvatar;
+
+    /**
+     * 服务时间范围
+     */
+    @ApiModelProperty("服务时间范围")
+    private String serviceTime;
+
+    /**
+     * 订单状态
+     */
+    @ApiModelProperty("订单状态")
+    private Integer orderStatus;
+
+    /**
+     * 订单状态中文
+     */
+    @ApiModelProperty("订单状态中文")
+    private String orderStatusName;
+
+    /**
+     * 项目名称
+     */
+    @ApiModelProperty("项目名称")
+    private String projectName;
+
+    /**
+     * 项目时长
+     */
+    @ApiModelProperty("项目时长")
+    private Integer projectDuration;
+    /**
+     * 项目封面图
+     */
+    @ApiModelProperty("项目封面图")
+    private String projectCover;
+    /**
+     * 项目亮点
+     */
+    @ApiModelProperty("项目亮点")
+    private String highlight;
+    /**
+     * 最终应付/实付金额
+     */
+    @ApiModelProperty("最终应付/实付金额")
+    private BigDecimal finalAmount;
+    /**
+     * 交通费
+     */
+    @ApiModelProperty("交通费")
+    private BigDecimal trafficFee;
+
+    /**
+     * 项目标价/售价
+     */
+    @ApiModelProperty("项目标价/售价")
+    private BigDecimal basePrice;
+
+
+}

+ 44 - 0
nightFragrance-massage/src/main/java/com/ylx/order/enums/OrderStatusEnum.java

@@ -0,0 +1,44 @@
+package com.ylx.order.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum OrderStatusEnum {
+
+    PENDING_PAYMENT(0, "待付款"),
+    PENDING_DISPATCH(1, "待派单"),
+    PENDING_ACCEPT(2, "待接单"),
+    PENDING_SERVICE(3, "待服务"),
+    IN_SERVICE(4, "服务中"),
+    IN_AFTER_SALE(5, "售后中"),
+    COMPLETED(6, "已完成"),
+    REFUNDED(7, "已退款"),
+    CANCELLED(8, "已取消"),
+    CLOSED(9, "已关闭"),
+    REJECTED(10, "拒绝接单");
+
+    private final Integer code;
+    private final String info;
+
+    OrderStatusEnum(Integer code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+    public static OrderStatusEnum fromCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (OrderStatusEnum status : values()) {
+            if (status.code.equals(code)) {
+                return status;
+            }
+        }
+        return null;
+    }
+
+    public static String getInfoByCode(Integer code) {
+        OrderStatusEnum status = fromCode(code);
+        return status == null ? "未知" : status.getInfo();
+    }
+}

+ 19 - 0
nightFragrance-massage/src/main/java/com/ylx/order/enums/PaymentMethodEnum.java

@@ -0,0 +1,19 @@
+package com.ylx.order.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum PaymentMethodEnum {
+
+    BALANCE(0, "余额支付"),
+    THIRD_PARTY(1, "第三方支付");
+
+
+    private final Integer code;
+    private final String info;
+
+    PaymentMethodEnum(Integer code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+}

+ 2 - 2
nightFragrance-massage/src/main/java/com/ylx/order/service/OrderStatusFlowService.java

@@ -7,9 +7,9 @@ public interface OrderStatusFlowService extends IService<OrderStatusFlow> {
 
     /**
      * 记录订单状态流转
-     * @param orderNo
+     * @param orderId
      * @param status
      * @return
      */
-    public void recordFlow(String orderNo, Integer status);
+    public void recordFlow(Long orderId, Integer status);
 }

+ 27 - 0
nightFragrance-massage/src/main/java/com/ylx/order/service/TOrderService.java

@@ -2,12 +2,17 @@ package com.ylx.order.service;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
 import com.ylx.common.core.domain.R;
 import com.ylx.order.domain.TOrder;
 import com.ylx.massage.domain.TWxUser;
 import com.ylx.massage.domain.vo.HomeBlock;
 import com.ylx.massage.domain.vo.OrderVerificationVo;
 import com.ylx.massage.domain.vo.TechnicianAvailabilityVo;
+import com.ylx.order.domain.dto.OrderDateQueryDTO;
+import com.ylx.order.domain.dto.OrderSubmitDTO;
+import com.ylx.order.domain.dto.OrderUpdateStatusDTO;
+import com.ylx.order.domain.vo.OrderDateQueryVo;
 
 import java.math.BigDecimal;
 import java.util.Date;
@@ -149,4 +154,26 @@ public interface TOrderService extends IService<TOrder> {
      * @return
      */
     public List<Map<String, Object>> myIncomeDetail(TWxUser user, Integer wStatus);
+
+    Map<String, Object> submitOrder(OrderSubmitDTO dto);
+
+    /**
+     * 用户端订单列表
+     * @param dto
+     * @return
+     */
+    Page<OrderDateQueryVo> queryOrderList(OrderDateQueryDTO dto);
+
+    /*
+     * 逻辑删除订单
+     */
+    void logicDeleteOrder(Long orderId);
+
+    void processOrderPayment(WxPayOrderNotifyV3Result.DecryptNotifyResult result, TWxUser wxUser, TOrder order);
+
+    /**
+     * 修改订单表状态必须使用此接口(此接口里面会插入订单流转记录表)
+     * @param dto
+     */
+    void updateOrderStatus(OrderUpdateStatusDTO dto);
 }

+ 2 - 2
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/OrderStatusFlowServiceImpl.java

@@ -17,9 +17,9 @@ import org.springframework.stereotype.Service;
 public class OrderStatusFlowServiceImpl extends ServiceImpl<OrderStatusFlowMapper, OrderStatusFlow> implements OrderStatusFlowService {
 
     @Override
-    public void recordFlow(String orderNo, Integer status) {
+    public void recordFlow(Long orderId, Integer status) {
         OrderStatusFlow orderStatusFlow = new OrderStatusFlow();
-        orderStatusFlow.setOrderNo(orderNo);
+        orderStatusFlow.setOrderId(orderId);
         orderStatusFlow.setStatus(status);
         this.save(orderStatusFlow);
     }

+ 420 - 4
nightFragrance-massage/src/main/java/com/ylx/order/service/impl/TOrderServiceImpl.java

@@ -1,23 +1,60 @@
 package com.ylx.order.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
 import com.ylx.common.core.domain.R;
+import com.ylx.common.core.domain.model.WxLoginUser;
+import com.ylx.common.exception.ServiceException;
+import com.ylx.common.utils.DateUtils;
+import com.ylx.common.utils.SecurityUtils;
+import com.ylx.common.weixinPay.enums.WxPayTypeEnum;
+import com.ylx.common.weixinPay.service.WxPayV3Service;
+import com.ylx.massage.domain.MaTechnician;
+import com.ylx.massage.domain.TAddress;
 import com.ylx.massage.domain.TWxUser;
 import com.ylx.massage.domain.vo.HomeBlock;
 import com.ylx.massage.domain.vo.OrderVerificationVo;
 import com.ylx.massage.domain.vo.TechnicianAvailabilityVo;
+import com.ylx.massage.service.CouponService;
+import com.ylx.massage.service.IMaTechnicianService;
+import com.ylx.massage.service.TAddressService;
+import com.ylx.massage.service.TWxUserService;
+import com.ylx.massage.utils.OrderNumberGenerator;
+import com.ylx.order.domain.OrderStatusFlow;
 import com.ylx.order.domain.TOrder;
+import com.ylx.order.domain.dto.OrderDateQueryDTO;
+import com.ylx.order.domain.dto.OrderSubmitDTO;
+import com.ylx.order.domain.dto.OrderUpdateStatusDTO;
+import com.ylx.order.domain.vo.OrderDateQueryVo;
+import com.ylx.order.enums.OrderStatusEnum;
+import com.ylx.order.enums.PaymentMethodEnum;
+import com.ylx.order.mapper.OrderStatusFlowMapper;
 import com.ylx.order.mapper.TOrderMapper;
+import com.ylx.order.service.OrderStatusFlowService;
 import com.ylx.order.service.TOrderService;
+import com.ylx.project.domain.Project;
+import com.ylx.project.service.ProjectService;
+import com.ylx.shopingfundsdetail.domain.vo.ShoppingFundsDetailAddDto;
+import com.ylx.shopingfundsdetail.enums.ShoppingFundsExpenseTypeEnum;
+import com.ylx.shopingfundsdetail.service.ShoppingFundsDetailService;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import javax.annotation.Resource;
 import java.math.BigDecimal;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
 
 
 /**
@@ -27,6 +64,28 @@ import java.util.Map;
 @Slf4j
 public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {
 
+    private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("M月d日");
+    private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
+    @Resource
+    private ProjectService projectService;
+    @Resource
+    private TAddressService addressService;
+    @Resource
+    private IMaTechnicianService maTechnicianService;
+    @Resource
+    private OrderNumberGenerator orderNumberGenerator;
+    @Resource
+    private TWxUserService wxUserService;
+    @Resource
+    private ShoppingFundsDetailService shoppingFundsDetailService;
+    @Resource
+    private CouponService couponService;
+    @Resource
+    private WxPayV3Service wxPayV3Service;
+
+    @Resource
+    private OrderStatusFlowService orderStatusFlowService;
+
     @Override
     public TOrder addOrder(TOrder order) {
         return null;
@@ -156,4 +215,361 @@ public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> impleme
     public List<Map<String, Object>> myIncomeDetail(TWxUser user, Integer wStatus) {
         return Collections.emptyList();
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> submitOrder(OrderSubmitDTO dto) {
+        Map<String, Object> map = new HashMap<>();
+
+        // 1. 获取并校验当前用户
+        WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
+        if (ObjectUtil.isNull(wxLoginUser)) {
+            throw new ServiceException("用户未登录");
+        }
+        Long userId = Long.parseLong(wxLoginUser.getId());
+        String openId = wxLoginUser.getCOpenid();
+
+        // 2. 获取并校验项目信息
+        Project project = this.projectService.getById(dto.getProjectId());
+        if (ObjectUtil.isNull(project)) {
+            throw new ServiceException("项目不存在");
+        }
+
+        // 3. 获取并校验商户信息
+        MaTechnician maTechnician = this.maTechnicianService.getById(dto.getMerchantId());
+        if (ObjectUtil.isNull(maTechnician)) {
+            throw new ServiceException("商户不存在");
+        }
+
+        // 4. 获取联系人信息
+        TAddress address = this.addressService.getById(dto.getAddressId());
+        if (ObjectUtil.isNull(address)) {
+            throw new ServiceException("客户联系地址不存在");
+        }
+
+        // 5. 获取商户地址信息
+        List<TAddress> merchantAddressList = this.addressService.list(new LambdaQueryWrapper<TAddress>()
+                .eq(TAddress::getMerchantId, dto.getMerchantId())
+                .eq(TAddress::getUserType, 2)
+                .eq(TAddress::getIsDelete, 0));
+        if (CollUtil.isEmpty(merchantAddressList)) {
+            throw new ServiceException("商户地址不存在");
+        }
+
+        // 6. 计算价格与优惠券核销 (核心优化:先算钱、扣券,再落库)
+        BigDecimal basePrice = Optional.ofNullable(project.getPrice()).orElse(BigDecimal.ZERO);
+        BigDecimal trafficFee = Optional.ofNullable(dto.getTrafficFee()).orElse(BigDecimal.ZERO);
+        BigDecimal couponDiscount = BigDecimal.ZERO;
+
+        if (ObjectUtil.isNotNull(dto.getCouponId())) {
+            // 计算抵扣金额
+            couponDiscount = this.couponService.calculateDiscountAmount(dto.getCouponId(), openId, basePrice);
+            // 立即核销优惠券,保证与订单在同一事务中
+            this.couponService.useCoupon(dto.getCouponId(), openId, null, 1);
+        }
+
+        // 实付金额 = 商品原价 - 优惠券优惠 + 车费
+        BigDecimal finalAmount = basePrice.subtract(couponDiscount).add(trafficFee)
+                .setScale(2, RoundingMode.HALF_UP);
+
+        // 7. 组装订单对象
+        TOrder order = buildOrder(dto, project, maTechnician, address, merchantAddressList,
+                basePrice, trafficFee, couponDiscount, finalAmount, userId);
+
+        // 8. 保存订单
+        boolean saveResult = this.save(order);
+        if (!saveResult) {
+            throw new ServiceException("添加订单失败");
+        }
+
+        // 9. 更新优惠券关联的真实订单ID (因为刚才核销时订单还没生成,这里补上)
+        if (ObjectUtil.isNotNull(dto.getCouponId())) {
+            this.couponService.useCoupon(dto.getCouponId(), openId, order.getId(), 1);
+        }
+
+        // 10. 处理余额支付逻辑
+        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), dto.getPaymentMethod())) {
+            handleBalancePayment(userId, finalAmount, order);
+            map.put("orderId", order.getId());
+            return map;
+        } else {
+            return createWxPayOrder(order, wxLoginUser);
+        }
+    }
+
+    /**
+     * 用户端订单列表
+     *
+     * @param dto
+     * @return
+     */
+    @Override
+    public Page<OrderDateQueryVo> queryOrderList(OrderDateQueryDTO dto) {
+        // 1. 构造分页对象
+        Page<TOrder> page = new Page<>(dto.getPageNum(), dto.getPageSize());
+
+        // 2. 构造查询条件
+        LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
+        if (dto.getStartDate() != null) {
+            LocalDateTime start = dto.getStartDate().atStartOfDay();
+            wrapper.ge(TOrder::getCreateTime, start);
+        }
+        if (dto.getEndDate() != null) {
+            LocalDateTime end = dto.getEndDate().atTime(LocalTime.MAX);
+            wrapper.le(TOrder::getCreateTime, end);
+        }
+        if (StrUtil.isNotBlank(dto.getProjectName())) {
+            wrapper.like(TOrder::getProjectName, dto.getProjectName());
+        }
+        if (StrUtil.isNotBlank(dto.getMerchantNickName())) {
+            wrapper.like(TOrder::getMerchantNickName, dto.getMerchantNickName());
+        }
+        wrapper.eq(TOrder::getIsDelete, 0);
+        wrapper.orderByDesc(TOrder::getCreateTime);
+
+        // 3. 执行分页查询
+        Page<TOrder> orderPage = baseMapper.selectPage(page, wrapper);
+
+        // 4. 转换 VO
+        List<OrderDateQueryVo> voList = orderPage.getRecords().stream()
+                .map(this::convertToVo)
+                .collect(Collectors.toList());
+
+
+        // 5. 返回分页结果
+
+        Page<OrderDateQueryVo> voPage = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
+        voPage.setRecords(voList);
+
+        return voPage;
+    }
+
+    /**
+     * 用户端逻辑删除
+     *
+     * @param orderId
+     */
+    @Override
+    public void logicDeleteOrder(Long orderId) {
+        // 1. 查询订单是否存在
+        TOrder order = getById(orderId);
+        if (order == null) {
+            throw new ServiceException("订单不存在");
+        }
+
+        // 3. 如果已经是删除状态,可提示或直接返回
+        if (Integer.valueOf(1).equals(order.getIsDelete())) {
+            throw new ServiceException("订单已在回收站中");
+        }
+
+        // 4. 执行逻辑删除
+        order.setIsDelete(1);
+        order.setDeletedTime(LocalDateTime.now());
+        boolean updated = updateById(order);
+        if (!updated) {
+            throw new ServiceException("删除失败,请稍后重试");
+        }
+        // 5. 事务成功,返回(Controller中会返回 success 信息)
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void processOrderPayment(WxPayOrderNotifyV3Result.DecryptNotifyResult result, TWxUser wxUser, TOrder order) {
+        // 更新订单状态
+        this.lambdaUpdate()
+                .set(TOrder::getStatus, OrderStatusEnum.PENDING_DISPATCH.getCode())
+                .eq(TOrder::getId, order.getId())
+                .update();
+
+        // 插入订单流转记录
+        OrderUpdateStatusDTO dto = new OrderUpdateStatusDTO();
+        dto.setOrderId(order.getId());
+        dto.setStatus(OrderStatusEnum.PENDING_DISPATCH.getCode());
+        this.updateOrderStatus(dto);
+    }
+
+    /**
+     *
+     * 更新订单状态(修改订单表状态必须使用此接口)
+     * @param dto
+     */
+    @Override
+    public void updateOrderStatus(OrderUpdateStatusDTO dto) {
+        Long orderId = dto.getOrderId();
+        Integer newStatus = dto.getStatus();
+
+        // 1. 查询订单
+        TOrder order = this.baseMapper.selectById(orderId);
+        if (order == null) {
+            throw new ServiceException("订单不存在");
+        }
+        Integer oldStatus = order.getStatus();
+
+        // 2. 更新订单状态
+        order.setStatus(newStatus);
+        // 可根据状态同步更新对应时间字段(例如:支付完成时间、接单时间等),此处省略具体业务逻辑
+        int updateCount = this.baseMapper.updateById(order);
+        if (updateCount == 0) {
+            throw new ServiceException("更新订单状态失败");
+        }
+
+        // 3. 记录状态流转流水
+        OrderStatusFlow flow = new OrderStatusFlow();
+        flow.setOrderId(orderId);
+        flow.setStatus(newStatus);
+        orderStatusFlowService.getBaseMapper().insert(flow);
+
+        log.info("订单状态变更: orderId={}, oldStatus={}, newStatus={}", orderId, oldStatus, newStatus);
+    }
+
+
+    /**
+     * 将 TOrder 转换为 OrderDateQueryVo,并处理 serviceTime 字段
+     */
+    private OrderDateQueryVo convertToVo(TOrder order) {
+        OrderDateQueryVo vo = new OrderDateQueryVo();
+        // 拷贝相同字段
+        BeanUtils.copyProperties(order, vo);
+        // 手动设置字段名不一致或需要特殊处理的
+        vo.setOrderStatus(order.getStatus());
+        vo.setOrderStatusName(getOrderStatusName(order.getStatus()));
+        // 服务时间范围:预约开始时间 + 项目时长(分钟)
+        // 使用 startTime 和 completedTime 构建服务时间展示
+        String serviceTime = buildServiceTime(order.getStartTime(), order.getCompletedTime());
+        vo.setServiceTime(serviceTime);
+
+        return vo;
+    }
+
+    /**
+     * 订单状态码 -> 中文描述
+     */
+    private String getOrderStatusName(Integer status) {
+        return OrderStatusEnum.getInfoByCode(status);
+    }
+
+    /**
+     * 拼接服务时间字符串,例如 “2026-06-08 14:30 (90分钟)”
+     */
+    private String buildServiceTime(LocalDateTime startTime, LocalDateTime completedTime) {
+        if (startTime == null || completedTime == null) {
+            return "";
+        }
+        String date = startTime.format(dateFormatter);
+        String start = startTime.format(timeFormatter);
+        String end = completedTime.format(timeFormatter);
+        return date + " " + start + "-" + end;
+    }
+
+    /**
+     * 组装订单对象
+     */
+    private TOrder buildOrder(OrderSubmitDTO dto, Project project, MaTechnician maTechnician,
+                              TAddress address, List<TAddress> merchantAddressList,
+                              BigDecimal basePrice, BigDecimal trafficFee, BigDecimal couponDiscount,
+                              BigDecimal finalAmount, Long userId) {
+        TOrder order = new TOrder();
+        order.setOrderNo(orderNumberGenerator.generateNextOrderNumber(""));
+        order.setUserId(userId);
+        order.setProjectId(dto.getProjectId());
+        order.setProjectType(project.getType());
+        order.setProjectName(project.getTitle());
+        order.setProjectCover(project.getCover());
+        order.setHighlight(project.getHighlight());
+        order.setAppointmentStartTime(dto.getAppointmentStartTime());
+        order.setProjectDuration(project.getStandardDuration());
+        order.setMerchantId(dto.getMerchantId());
+        order.setMerchantType(maTechnician.getTechType());
+        order.setMerchantNickName(maTechnician.getTeNickName());
+        order.setMerchantAvatar(maTechnician.getTeAvatar());
+        order.setContactPersonName(address.getUserName());
+        order.setContactPhoneNumber(address.getPhone());
+        order.setContactAddressInfo(address.getDetailAddress());
+        order.setBasePrice(basePrice);
+        order.setCouponDiscount(couponDiscount);
+        order.setTrafficFee(trafficFee);
+        order.setFinalAmount(finalAmount);
+        order.setCouponId(dto.getCouponId());
+        order.setCreateTime(DateUtils.getNowDate());
+        order.setAppointmentStartTime(dto.getAppointmentStartTime());
+        order.setStatus(0);
+        order.setExecStatus(0);
+        order.setDispatchedStatus(0);
+        order.setPaymentMethod(dto.getPaymentMethod());
+
+        // 经纬度安全赋值
+        order.setUserLatitude(new BigDecimal(address.getLatitude()));
+        order.setUserLongitude(new BigDecimal(address.getLongitude()));
+
+        merchantAddressList.stream().filter(a -> a.getType() == 1).findFirst().ifPresent(addr -> {
+            order.setMerchantLongitude(new BigDecimal(addr.getLongitude()));
+            order.setMerchantLatitude(new BigDecimal(addr.getLatitude()));
+        });
+        merchantAddressList.stream().filter(a -> a.getType() == 2).findFirst().ifPresent(addr -> {
+            order.setVirtualLongitude(new BigDecimal(addr.getLongitude()));
+            order.setVirtualLatitude(new BigDecimal(addr.getLatitude()));
+        });
+
+        // 设置支付状态
+        if (ObjectUtil.equals(PaymentMethodEnum.BALANCE.getCode(), dto.getPaymentMethod())) {
+            order.setStatus(OrderStatusEnum.PENDING_DISPATCH.getCode());
+            order.setPaidTime(LocalDateTime.now());
+        } else {
+            order.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode());
+        }
+        return order;
+    }
+
+    /**
+     * 处理余额支付 (核心优化:数据库原子操作防并发)
+     */
+    private void handleBalancePayment(Long userId, BigDecimal finalAmount, TOrder order) {
+        // 1. 数据库层面原子扣减余额,并校验余额是否充足
+        boolean deductSuccess = this.wxUserService.lambdaUpdate()
+                .setSql("d_balance = d_balance - " + finalAmount)
+                .eq(TWxUser::getId, userId)
+                // 防止余额超扣的核心:当前余额必须大于等于扣减金额
+                .apply("d_balance >= {0}", finalAmount)
+                .update();
+
+        if (!deductSuccess) {
+            throw new ServiceException("余额不足,扣款失败");
+        }
+
+        // 2. 重新查询最新余额用于记录流水
+        TWxUser user = this.wxUserService.getById(userId);
+
+        // 3. 记录购物金明细
+        ShoppingFundsDetailAddDto detailDto = new ShoppingFundsDetailAddDto();
+        detailDto.setUserId(userId.toString());
+        detailDto.setAmount(finalAmount);
+        detailDto.setOrderNo(order.getOrderNo());
+        detailDto.setExpenseType(ShoppingFundsExpenseTypeEnum.CONSUMPTION.getCode());
+        detailDto.setBalance(user.getdBalance());
+        this.shoppingFundsDetailService.addShoppingFundsDetail(detailDto);
+
+        // 4. 记录状态流转流水
+        OrderUpdateStatusDTO dto = new OrderUpdateStatusDTO();
+        dto.setOrderId(order.getId());
+        dto.setStatus(order.getStatus());
+        this.updateOrderStatus(dto);
+    }
+
+    /**
+     * 创建微信支付订单(事务外执行,减少事务时长)
+     */
+    private Map<String, Object> createWxPayOrder(TOrder order, WxLoginUser wxLoginUser) {
+        try {
+            return wxPayV3Service.createV3JsapiOrder(
+                    order.getOrderNo(),
+                    order.getFinalAmount(),
+                    "购买情绪价值商品",
+                    wxLoginUser.getCOpenid(),
+                    WxPayTypeEnum.EMOTION_GOODS.getCode()
+            );
+        } catch (Exception e) {
+            log.error("微信支付下单失败,订单号: {}", order.getOrderNo(), e);
+            throw new ServiceException("支付服务异常,请稍后重试");
+        }
+    }
 }

+ 37 - 0
nightFragrance-massage/src/main/java/com/ylx/point/domain/dto/UserPointDetailQueryDTO.java

@@ -0,0 +1,37 @@
+package com.ylx.point.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * PC用户积分详情查询DTO
+ */
+@Data
+@ApiModel("PC用户积分详情查询DTO")
+public class UserPointDetailQueryDTO {
+
+    /**
+     * 用户openId
+     */
+    @ApiModelProperty("用户openId")
+    private String openId;
+
+    /**
+     * 积分项目
+     */
+    @ApiModelProperty("积分项目")
+    private String pointProject;
+
+    /**
+     * 开始时间
+     */
+    @ApiModelProperty("开始时间")
+    private String startTime;
+
+    /**
+     * 结束时间
+     */
+    @ApiModelProperty("结束时间")
+    private String endTime;
+}

+ 38 - 0
nightFragrance-massage/src/main/java/com/ylx/point/domain/vo/UserPointDetailVO.java

@@ -0,0 +1,38 @@
+package com.ylx.point.domain.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * PC用户积分详情VO
+ */
+@Data
+@ApiModel("PC用户积分详情VO")
+public class UserPointDetailVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 积分项目
+     */
+    @ApiModelProperty("积分项目")
+    private String pointProject;
+
+    /**
+     * 积分变化
+     */
+    @ApiModelProperty("积分变化")
+    private String pointChange;
+
+    /**
+     * 时间
+     */
+    @ApiModelProperty("时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+}

+ 12 - 0
nightFragrance-massage/src/main/java/com/ylx/point/mapper/PointUserLogMapper.java

@@ -3,7 +3,9 @@ package com.ylx.point.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ylx.point.domain.PointUserLog;
+import com.ylx.point.domain.dto.UserPointDetailQueryDTO;
 import com.ylx.point.domain.dto.UserPointPageDTO;
+import com.ylx.point.domain.vo.UserPointDetailVO;
 import com.ylx.point.domain.vo.UserPointLogVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -35,6 +37,16 @@ public interface PointUserLogMapper extends BaseMapper<PointUserLog> {
 
     Page<UserPointLogVO> getUserPointLogList(Page<PointUserLog> page, @Param("dto") UserPointPageDTO dto);
 
+    /**
+     * 查询PC端用户积分详情列表
+     *
+     * @param page 分页参数
+     * @param dto 查询条件
+     * @return 用户积分详情列表
+     */
+    Page<UserPointDetailVO> selectPcUserPointDetailList(Page<UserPointDetailVO> page,
+                                                        @Param("dto") UserPointDetailQueryDTO dto);
+
     /**
      * 获取用户可用积分余额
      * 计算公式:未过期收入总和 - 支出总和

+ 11 - 0
nightFragrance-massage/src/main/java/com/ylx/point/service/IPointUserLogService.java

@@ -3,8 +3,10 @@ package com.ylx.point.service;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ylx.point.domain.PointUserLog;
+import com.ylx.point.domain.dto.UserPointDetailQueryDTO;
 import com.ylx.point.domain.dto.UserPointPageDTO;
 import com.ylx.point.domain.vo.UserPointInfoVO;
+import com.ylx.point.domain.vo.UserPointDetailVO;
 import com.ylx.point.domain.vo.UserPointLogVO;
 
 /**
@@ -18,4 +20,13 @@ public interface IPointUserLogService extends IService<PointUserLog> {
     UserPointInfoVO getUserPointInfo(String cityCode);
 
     Page<UserPointLogVO> getUserPointLogList(Page<PointUserLog> page, UserPointPageDTO dto);
+
+    /**
+     * 查询PC端用户积分详情列表
+     *
+     * @param page 分页参数
+     * @param dto 查询条件
+     * @return 用户积分详情列表
+     */
+    Page<UserPointDetailVO> getPcUserPointDetailList(Page<UserPointDetailVO> page, UserPointDetailQueryDTO dto);
 }

+ 18 - 0
nightFragrance-massage/src/main/java/com/ylx/point/service/impl/PointUserLogServiceImpl.java

@@ -7,13 +7,16 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ylx.common.core.domain.model.WxLoginUser;
 import com.ylx.common.utils.SecurityUtils;
 import com.ylx.point.domain.PointUserLog;
+import com.ylx.point.domain.dto.UserPointDetailQueryDTO;
 import com.ylx.point.domain.dto.UserPointPageDTO;
 import com.ylx.point.domain.vo.UserPointInfoVO;
+import com.ylx.point.domain.vo.UserPointDetailVO;
 import com.ylx.point.domain.vo.UserPointLogVO;
 import com.ylx.point.mapper.PointUserLogMapper;
 import com.ylx.point.service.IPointActivityService;
 import com.ylx.point.service.IPointUserActivityTaskCompletionService;
 import com.ylx.point.service.IPointUserLogService;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -102,4 +105,19 @@ public class PointUserLogServiceImpl extends ServiceImpl<PointUserLogMapper, Poi
         return pageData;
     }
 
+    @Override
+    public Page<UserPointDetailVO> getPcUserPointDetailList(Page<UserPointDetailVO> page, UserPointDetailQueryDTO dto) {
+        normalizeTimeRange(dto);
+        return this.pointUserLogMapper.selectPcUserPointDetailList(page, dto);
+    }
+
+    private void normalizeTimeRange(UserPointDetailQueryDTO dto) {
+        if (StringUtils.isNotBlank(dto.getStartTime()) && dto.getStartTime().trim().length() == 10) {
+            dto.setStartTime(dto.getStartTime().trim() + " 00:00:00");
+        }
+        if (StringUtils.isNotBlank(dto.getEndTime()) && dto.getEndTime().trim().length() == 10) {
+            dto.setEndTime(dto.getEndTime().trim() + " 23:59:59");
+        }
+    }
+
 }

+ 0 - 1
nightFragrance-massage/src/main/java/com/ylx/shopingfundsdetail/domain/vo/ShoppingFundsDetailAddDto.java

@@ -32,7 +32,6 @@ public class ShoppingFundsDetailAddDto {
     /**
      * 购物卡id
      */
-    @NotNull(message = "购物卡id不能为空")
     @ApiModelProperty("购物卡id")
     private Long giftCardId;
     /**

+ 19 - 0
nightFragrance-massage/src/main/java/com/ylx/shopingfundsdetail/enums/ShoppingFundsExpenseTypeEnum.java

@@ -0,0 +1,19 @@
+package com.ylx.shopingfundsdetail.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum ShoppingFundsExpenseTypeEnum {
+
+    RECHARGE(0, "充值"),
+    CONSUMPTION(1, "消费");
+
+    private final Integer code;
+    private final String info;
+
+    ShoppingFundsExpenseTypeEnum(Integer code, String info) {
+        this.code = code;
+        this.info = info;
+    }
+
+}

+ 74 - 0
nightFragrance-massage/src/main/resources/mapper/massage/CouponReceiveMapper.xml

@@ -80,5 +80,79 @@
         and a.receive_open_id = #{openid}
     </select>
 
+    <!-- 1. 核销优惠券 (乐观锁) -->
+    <update id="useCouponOptimisticLock">
+        UPDATE coupon_receive
+        SET coupon_status = 2,
+        use_time = #{useTime},
+        order_id = #{orderId},
+        order_type = #{orderType},
+        update_time = NOW()
+        WHERE coupon_id = #{couponId}
+        AND receive_open_id = #{openId}
+        AND coupon_status = 0  <!-- 核心:只有状态为 0(待使用) 才能被更新 -->
+        AND is_delete = 0
+    </update>
+
+    <!-- 2. 优惠券规则表使用数量 +1 -->
+    <update id="incrementUsedNum">
+        UPDATE coupon
+        SET used_num = used_num + 1,
+            update_time = NOW()
+        WHERE id = #{couponId}
+          AND is_delete = 0
+    </update>
+
+    <!-- 3. 退还优惠券 (乐观锁) -->
+    <update id="returnCouponOptimisticLock">
+        UPDATE coupon_receive
+        SET coupon_status = 0,
+        use_time = NULL,
+        order_id = NULL,
+        order_type = NULL,
+        update_time = NOW()
+        WHERE coupon_id = #{couponId}
+        AND receive_open_id = #{openId}
+        AND order_id = #{orderId} <!-- 核心:必须匹配原订单,防止退错 -->
+        AND coupon_status = 2     <!-- 核心:只有状态为 2(已使用) 才能退回 -->
+        AND is_delete = 0
+    </update>
+
+    <!-- 4. 优惠券规则表使用数量 -1 -->
+    <update id="decrementUsedNum">
+        UPDATE coupon
+        SET used_num = used_num - 1,
+        update_time = NOW()
+        WHERE id = #{couponId}
+        AND used_num > 0  <!-- 核心防御:防止减为负数 -->
+        AND is_delete = 0
+    </update>
+
+    <!-- 查询优惠券领取详情 -->
+    <select id="selectCouponDetailForCalc" resultType="java.util.Map">
+        SELECT
+        cr.id,
+        cr.coupon_status,
+        cr.valid_start_time,
+        cr.expiration_time,
+        c.discount_type,
+        c.discount_value,
+        c.reb_value,
+        c.threshold_amount
+        FROM coupon_receive cr
+        JOIN coupon c ON cr.coupon_id = c.id
+        <where>
+            cr.is_delete = 0
+            AND c.is_delete = 0
+            <if test="couponId != null and couponId != ''">
+                AND cr.coupon_id = #{couponId}
+            </if>
+            <if test="openId != null and openId != ''">
+                AND cr.receive_open_id = #{openId}
+            </if>
+        </where>
+        LIMIT 1
+    </select>
+
 </mapper>
 

+ 15 - 0
nightFragrance-massage/src/main/resources/mapper/massage/TAddressMapper.xml

@@ -44,5 +44,20 @@
         values(create_time) update_time = values(update_time) is_delete = values(is_delete)
     </insert>
 
+    <select id="selectPcUserAddressList" resultType="com.ylx.massage.domain.vo.UserAddressListVO">
+        SELECT
+            id,
+            COALESCE(NULLIF(detail_address, ''), address) AS address,
+            user_name AS userName,
+            phone,
+            create_time AS createTime
+        FROM t_address
+        WHERE openid = #{openId}
+          AND is_delete = 0
+          AND user_type = 1
+          AND type = 1
+        ORDER BY is_default DESC, create_time DESC
+    </select>
+
 </mapper>
 

+ 44 - 3
nightFragrance-massage/src/main/resources/mapper/massage/TWxUserMapper.xml

@@ -6,13 +6,17 @@
         <result property="id" column="id"/>
         <result property="cOpenid" column="c_openid"/>
         <result property="cPhone" column="c_phone"/>
+        <result property="cIcon" column="c_icon"/>
         <result property="cNickName" column="c_nick_name"/>
         <result property="dMoney" column="d_money"/>
         <result property="nNum" column="n_num"/>
-        <result property="distributionAmount" column="distribution_amount"/>
         <result property="dBalance" column="d_balance"/>
         <result property="dAllMoney" column="d_all_money"/>
+        <result property="giftCardPayAmount" column="gift_card_pay_amount"/>
+        <result property="currentPoints" column="current_points"/>
+        <result property="totalPoints" column="total_points"/>
         <result property="createTime" column="create_time"/>
+       <!-- <result property="createTime" column="create_time"/>
         <result property="updateTime" column="update_time"/>
         <result property="isDelete" column="is_delete"/>
         <result property="openid" column="openid"/>
@@ -22,7 +26,7 @@
         <result property="longitude" column="longitude"/>
         <result property="latitude" column="latitude"/>
         <result property="type" column="type"/>
-        <result property="address" column="address"/>
+        <result property="address" column="address"/>-->
     </resultMap>
 
     <sql id="selectTWxUserVo">
@@ -43,6 +47,9 @@
                u.create_time,
                u.update_time,
                u.is_delete,
+               COALESCE(gco.gift_card_pay_amount, 0) gift_card_pay_amount,
+               COALESCE(pul.current_points, 0) current_points,
+               COALESCE(pul.total_points, 0) total_points,
                a.openid,
                a.phone,
                a.user_name,
@@ -51,12 +58,46 @@
                a.address
         FROM t_wx_user u
                  LEFT JOIN (select * from t_address where is_delete = 0 and type = 1) a ON u.c_openid = a.openid
+                 LEFT JOIN (
+                     SELECT user_id,
+                            COALESCE(SUM(gco.pay_amount), 0) gift_card_pay_amount
+                     FROM gift_card_order gco
+                     WHERE gco.status = 1
+                     GROUP BY user_id
+                 ) gco ON gco.user_id = u.id
+                 LEFT JOIN (
+                     SELECT open_id,
+                            COALESCE(SUM(CASE WHEN pul.op_type = 1 AND pul.is_expired != 1 THEN pul.points ELSE 0 END)
+                                - SUM(CASE WHEN pul.op_type IN (2, 3) THEN pul.points ELSE 0 END), 0) current_points,
+                            COALESCE(SUM(CASE WHEN pul.op_type = 1 THEN pul.points ELSE 0 END), 0) total_points
+                     FROM point_user_log pul
+                     GROUP BY open_id
+                 ) pul ON pul.open_id = u.c_openid
     </sql>
-    <select id="selectTWxUserList" parameterType="TWxUser" resultMap="TWxUserResult">
+
+    <!-- 查询微信用户列表 -->
+    <select id="selectTWxUserList" resultMap="TWxUserResult">
         <include refid="selectTWxUserVo"/>
         <where>
             u.is_delete = 0
+            <!-- 昵称 -->
+            <if test="user.cNickName != null and user.cNickName != ''">
+                and u.c_nick_name like concat('%', #{user.cNickName}, '%')
+            </if>
+            <!-- 手机号 -->
+            <if test="user.cPhone != null and user.cPhone != ''">
+                and u.c_phone like concat('%', #{user.cPhone}, '%')
+            </if>
+            <!-- 创建开始时间 -->
+            <if test="user.startTime != null and user.startTime != ''">
+                and u.create_time &gt;= #{user.startTime}
+            </if>
+            <!-- 创建结束时间 -->
+            <if test="user.endTime != null and user.endTime != ''">
+                and u.create_time &lt;= #{user.endTime}
+            </if>
         </where>
+        order by u.create_time desc
     </select>
 
     <select id="selectTWxUserById" parameterType="String" resultMap="TWxUserResult">

+ 29 - 1
nightFragrance-massage/src/main/resources/mapper/point/PointUserLogMapper.xml

@@ -43,4 +43,32 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             pul.create_time DESC
     </select>
 
-</mapper>
+    <!-- PC端根据用户openId查询用户积分详情 -->
+    <select id="selectPcUserPointDetailList" resultType="com.ylx.point.domain.vo.UserPointDetailVO">
+        SELECT
+            COALESCE(NULLIF(pul.activity_name, ''), pul.task_name) AS pointProject,
+            CASE
+                WHEN pul.op_type = 1 THEN CONCAT('+', pul.points)
+                WHEN pul.op_type IN (2, 3) THEN CONCAT('-', pul.points)
+                ELSE CAST(pul.points AS CHAR)
+            END AS pointChange,
+            pul.create_time AS createTime
+        FROM
+            point_user_log pul
+        <where>
+            pul.open_id = #{dto.openId}
+            <if test="dto.pointProject != null and dto.pointProject != '' and dto.pointProject != '全部'">
+                AND COALESCE(NULLIF(pul.activity_name, ''), pul.task_name) = #{dto.pointProject}
+            </if>
+            <if test="dto.startTime != null and dto.startTime != ''">
+                AND pul.create_time &gt;= #{dto.startTime}
+            </if>
+            <if test="dto.endTime != null and dto.endTime != ''">
+                AND pul.create_time &lt;= #{dto.endTime}
+            </if>
+        </where>
+        ORDER BY
+            pul.create_time DESC
+    </select>
+
+</mapper>

+ 37 - 0
nightFragrance-massage/src/test/java/com/ylx/massage/mapper/TAddressMapperXmlTest.java

@@ -0,0 +1,37 @@
+package com.ylx.massage.mapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TAddressMapperXmlTest {
+
+    @Test
+    public void pcUserAddressListSqlOnlyQueriesByOpenId() throws Exception {
+        String xml = readMapperXml();
+
+        assertTrue(xml.contains("selectPcUserAddressList"));
+        assertTrue(xml.contains("FROM t_address"));
+        assertTrue(xml.contains("openid = #{openId}"));
+        assertTrue(xml.contains("is_delete = 0"));
+        assertTrue(xml.contains("user_type = 1"));
+        assertTrue(xml.contains("type = 1"));
+        assertTrue(xml.contains("ORDER BY is_default DESC, create_time DESC"));
+        assertFalse(xml.contains("user_id = #{userId}"));
+    }
+
+    private String readMapperXml() throws Exception {
+        try (InputStream inputStream = getClass().getClassLoader()
+                .getResourceAsStream("mapper/massage/TAddressMapper.xml")) {
+            assertNotNull(inputStream, "TAddressMapper.xml should exist in test classpath");
+            byte[] bytes = new byte[inputStream.available()];
+            inputStream.read(bytes);
+            return new String(bytes, StandardCharsets.UTF_8);
+        }
+    }
+}

+ 95 - 0
nightFragrance-massage/src/test/java/com/ylx/massage/mapper/TWxUserMapperXmlTest.java

@@ -0,0 +1,95 @@
+package com.ylx.massage.mapper;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.ylx.massage.domain.vo.TWxUserVo;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TWxUserMapperXmlTest {
+
+    private static final List<String> USER_LIST_FIELD_ORDER = Arrays.asList(
+            "id",
+            "cOpenid",
+            "cIcon",
+            "cNickName",
+            "cPhone",
+            "dMoney",
+            "nNum",
+            "dBalance",
+            "dAllMoney",
+            "giftCardPayAmount",
+            "currentPoints",
+            "totalPoints",
+            "startTime",
+            "endTime"
+    );
+
+    @Test
+    public void userListVoExposesGiftCardAndPointFields() {
+        /*TWxUserVo vo = new TWxUserVo();
+
+        vo.setGiftCardPayAmount(new BigDecimal("100.00"));
+        vo.setCurrentPoints(80);
+        vo.setTotalPoints(120);
+        vo.setStartTime("2026-06-01 00:00:00");
+        vo.setEndTime("2026-06-30 23:59:59");
+
+        assertEquals(new BigDecimal("100.00"), vo.getGiftCardPayAmount());
+        assertEquals(Integer.valueOf(80), vo.getCurrentPoints());
+        assertEquals(Integer.valueOf(120), vo.getTotalPoints());
+        assertEquals("2026-06-01 00:00:00", vo.getStartTime());
+        assertEquals("2026-06-30 23:59:59", vo.getEndTime());*/
+    }
+
+    @Test
+    public void userListVoDeclaresJsonPropertyOrder() throws Exception {
+        JsonPropertyOrder propertyOrder = TWxUserVo.class.getAnnotation(JsonPropertyOrder.class);
+        assertNotNull(propertyOrder);
+        assertEquals(USER_LIST_FIELD_ORDER, Arrays.asList(propertyOrder.value()));
+
+        for (int i = 0; i < USER_LIST_FIELD_ORDER.size(); i++) {
+            String fieldName = USER_LIST_FIELD_ORDER.get(i);
+            Field field = TWxUserVo.class.getDeclaredField(fieldName);
+            JsonProperty jsonProperty = field.getAnnotation(JsonProperty.class);
+            assertNotNull(jsonProperty, fieldName + " should declare @JsonProperty");
+            assertEquals(fieldName, jsonProperty.value());
+            assertEquals(i, jsonProperty.index());
+        }
+    }
+
+    @Test
+    public void userListSqlAggregatesGiftCardPayAmountAndUserPoints() throws Exception {
+        String xml = readMapperXml();
+
+        assertTrue(xml.contains("SUM(gco.pay_amount)"));
+        assertTrue(xml.contains("gco.user_id = u.id"));
+        assertTrue(xml.contains("gco.status = 1"));
+        assertTrue(xml.contains("pul.open_id = u.c_openid"));
+        assertTrue(xml.contains("pul.op_type = 1 AND pul.is_expired != 1"));
+        assertTrue(xml.contains("pul.op_type IN (2, 3)"));
+        assertTrue(xml.contains("SUM(CASE WHEN pul.op_type = 1 THEN pul.points ELSE 0 END)"));
+        assertTrue(xml.contains("u.create_time &gt;= #{user.startTime}"));
+        assertTrue(xml.contains("u.create_time &lt;= #{user.endTime}"));
+    }
+
+    private String readMapperXml() throws Exception {
+        try (InputStream inputStream = getClass().getClassLoader()
+                .getResourceAsStream("mapper/massage/TWxUserMapper.xml")) {
+            assertNotNull(inputStream, "TWxUserMapper.xml should exist in test classpath");
+            byte[] bytes = new byte[inputStream.available()];
+            inputStream.read(bytes);
+            return new String(bytes, StandardCharsets.UTF_8);
+        }
+    }
+}

+ 19 - 0
nightFragrance-massage/src/test/java/com/ylx/massage/service/impl/TAddressServiceImplTest.java

@@ -0,0 +1,19 @@
+package com.ylx.massage.service.impl;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TAddressServiceImplTest {
+
+    @Test
+    public void pcUserAddressListRejectsBlankOpenId() {
+        TAddressServiceImpl service = new TAddressServiceImpl();
+
+        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+                () -> service.getPcUserAddressList(" "));
+
+        assertEquals("openId不能为空", exception.getMessage());
+    }
+}

+ 36 - 0
nightFragrance-massage/src/test/java/com/ylx/point/mapper/PointUserLogMapperXmlTest.java

@@ -0,0 +1,36 @@
+package com.ylx.point.mapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class PointUserLogMapperXmlTest {
+
+    @Test
+    public void pcUserPointDetailSqlSupportsOpenIdProjectTimeFiltersAndTimeDescOrder() throws Exception {
+        String xml = readMapperXml();
+
+        assertTrue(xml.contains("selectPcUserPointDetailList"));
+        assertTrue(xml.contains("pul.open_id = #{dto.openId}"));
+        assertTrue(xml.contains("dto.pointProject != '全部'"));
+        assertTrue(xml.contains("COALESCE(NULLIF(pul.activity_name, ''), pul.task_name) = #{dto.pointProject}"));
+        assertTrue(xml.contains("pul.create_time &gt;= #{dto.startTime}"));
+        assertTrue(xml.contains("pul.create_time &lt;= #{dto.endTime}"));
+        assertTrue(xml.contains("ORDER BY"));
+        assertTrue(xml.contains("pul.create_time DESC"));
+    }
+
+    private String readMapperXml() throws Exception {
+        try (InputStream inputStream = getClass().getClassLoader()
+                .getResourceAsStream("mapper/point/PointUserLogMapper.xml")) {
+            assertNotNull(inputStream, "PointUserLogMapper.xml should exist in test classpath");
+            byte[] bytes = new byte[inputStream.available()];
+            inputStream.read(bytes);
+            return new String(bytes, StandardCharsets.UTF_8);
+        }
+    }
+}

+ 24 - 0
nightFragrance-massage/src/test/java/com/ylx/point/service/impl/PointUserLogServiceImplTest.java

@@ -0,0 +1,24 @@
+package com.ylx.point.service.impl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.point.domain.dto.UserPointDetailQueryDTO;
+import com.ylx.point.domain.vo.UserPointDetailVO;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class PointUserLogServiceImplTest {
+
+    @Test
+    public void pcUserPointDetailRejectsBlankOpenId() {
+        PointUserLogServiceImpl service = new PointUserLogServiceImpl();
+        UserPointDetailQueryDTO query = new UserPointDetailQueryDTO();
+        query.setOpenId(" ");
+
+        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
+                () -> service.getPcUserPointDetailList(new Page<UserPointDetailVO>(), query));
+
+        assertEquals("openId不能为空", exception.getMessage());
+    }
+}