Merge remote-tracking branch 'origin/main'

This commit is contained in:
attiya 2025-11-11 16:24:45 +08:00
commit a97e3b166a
8 changed files with 252 additions and 137 deletions

View File

@ -2,6 +2,7 @@ package com.cdzy.user.controller;
import com.cdzy.common.model.response.JsonResult;
import com.cdzy.user.model.dto.EbikeUserCyclingDto;
import com.cdzy.user.model.dto.EbikeUserCyclingEndDto;
import com.cdzy.user.model.entity.EbikeOrder;
import com.cdzy.user.model.vo.EbikeOrderVo;
import com.cdzy.user.service.EbikeOrderService;
@ -30,120 +31,10 @@ public class EbikeOrderController {
@Resource
private EbikeOrderService ebikeOrderService;
/**
* 开始骑行生成订单开锁
*
* @param orderDto 骑行信息 返回订单开始时间开始点位
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@PostMapping("saveRide")
public JsonResult<?> saveRide(@RequestBody @Validated EbikeUserCyclingDto orderDto) {
EbikeOrderVo result = ebikeOrderService.saveRide(orderDto);
return JsonResult.success(result);
}
/**
* 查看用户是否有未完成订单
*
* @param userId 用户id
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@GetMapping("checkHistoryOrder")
public JsonResult<?> checkHistoryOrder(@Validated @NotNull(message = "用户id不能为空") @RequestParam("userId") Long userId) {
EbikeOrder userOrders = ebikeOrderService.checkHistoryOrder(userId);
return JsonResult.success(userOrders);
}
/**
* 订单支付
*
* @param orderPaymentDto 支付信息
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@PostMapping("payment")
public JsonResult<?> payment(@RequestBody @Validated FeignOrderPaymentDto orderPaymentDto) {
ebikeOrderService.payment(orderPaymentDto);
return JsonResult.success();
}
/**
* 订单退款申请
*
* @param orderId 订单ID
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@GetMapping("refundApply")
public JsonResult<?> refundApply(@RequestParam("orderId") Long orderId) {
ebikeOrderService.refundApply(orderId);
return JsonResult.success();
}
/**
* 订单退款
*
* @param orderId 订单ID
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@GetMapping("refund")
public JsonResult<?> refund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.refund(orderId);
return JsonResult.success();
}
/**
* 订单退款完成
*
* @param orderId 订单ID
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@GetMapping("doneRefund")
public JsonResult<?> doneRefund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.doneRefund(orderId);
return JsonResult.success();
}
/**
* 订单退款失败
*
* @param orderId 订单ID
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@GetMapping("failRefund")
public JsonResult<?> failRefund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.failRefund(orderId);
return JsonResult.success();
}
/**
* 订单退款驳回
*
* @param orderId 订单ID
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@GetMapping("rejectRefund")
public JsonResult<?> rejectRefund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.rejectRefund(orderId);
return JsonResult.success();
}
/**
* 根据用户订单表主键获取详细信
*
* @param orderId 用户订单表主键
* @ 用户订单表详情
*/
@GetMapping("getInfo/{orderId}")
public JsonResult<?> getInfo(@PathVariable("orderId") Long orderId) {
EbikeOrder userOrder = ebikeOrderService.getById(orderId);
return JsonResult.success(userOrder);
}
/**
* 车辆列表
*
* @param feignEbikeBikeRadiusVo 坐标信息
* @ {@code 200} 添加成功{@code 500} 添加失败
*/
@PostMapping("bikeList")
public JsonResult<?> bikeList(@RequestBody @Validated FeignEbikeBikeRadiusVo feignEbikeBikeRadiusVo) {
@ -161,4 +52,114 @@ public class EbikeOrderController {
FeignEbikeUserBikeInfo result = ebikeOrderService.queryBikeInfo(bikeCode);
return JsonResult.success(result);
}
/**
* 开始骑行生成订单开锁
*
* @param orderDto 骑行信息 返回订单开始时间开始点位
*/
@PostMapping("saveRide")
public JsonResult<?> saveRide(@RequestBody @Validated EbikeUserCyclingDto orderDto) {
EbikeOrder result = ebikeOrderService.saveRide(orderDto);
return JsonResult.success(result);
}
/**
* 查看用户是否有未完成订单
*
* @param userId 用户id
*/
@GetMapping("checkHistoryOrder")
public JsonResult<?> checkHistoryOrder(@Validated @NotNull(message = "用户id不能为空") @RequestParam("userId") Long userId) {
EbikeOrder userOrders = ebikeOrderService.checkHistoryOrder(userId);
return JsonResult.success(userOrders);
}
/**
* 完成骑行关锁还车
*
* @param endDto 骑行信息
*/
@PostMapping("doneRide")
public JsonResult<?> doneRide(@RequestBody @Validated EbikeUserCyclingEndDto endDto) {
ebikeOrderService.doneRide(endDto);
return JsonResult.success();
}
/**
* 订单支付
*
* @param orderPaymentDto 支付信息
*/
@PostMapping("payment")
public JsonResult<?> payment(@RequestBody @Validated FeignOrderPaymentDto orderPaymentDto) {
ebikeOrderService.payment(orderPaymentDto);
return JsonResult.success();
}
/**
* 订单退款申请
*
* @param orderId 订单ID
*/
@GetMapping("refundApply")
public JsonResult<?> refundApply(@RequestParam("orderId") Long orderId) {
ebikeOrderService.refundApply(orderId);
return JsonResult.success();
}
/**
* 订单退款
*
* @param orderId 订单ID
*/
@GetMapping("refund")
public JsonResult<?> refund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.refund(orderId);
return JsonResult.success();
}
/**
* 订单退款完成
*
* @param orderId 订单ID
*/
@GetMapping("doneRefund")
public JsonResult<?> doneRefund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.doneRefund(orderId);
return JsonResult.success();
}
/**
* 订单退款失败
*
* @param orderId 订单ID
*/
@GetMapping("failRefund")
public JsonResult<?> failRefund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.failRefund(orderId);
return JsonResult.success();
}
/**
* 订单退款驳回
*
* @param orderId 订单ID
*/
@GetMapping("rejectRefund")
public JsonResult<?> rejectRefund(@RequestParam("orderId") Long orderId) {
ebikeOrderService.rejectRefund(orderId);
return JsonResult.success();
}
/**
* 根据用户订单表主键获取详细信
*
* @param orderId 用户订单表主键
*/
@GetMapping("getInfo/{orderId}")
public JsonResult<?> getInfo(@PathVariable("orderId") Long orderId) {
EbikeOrder userOrder = ebikeOrderService.getById(orderId);
return JsonResult.success(userOrder);
}
}

View File

@ -39,6 +39,6 @@ public class EbikeUserCyclingDto {
@Column(typeHandler = PGpointTypeHandler.class)
@JsonSerialize(using = PGpointSerializer.class)
@JsonDeserialize(using = PGpointDeserializer.class)
@NotNull(message = "骑行起始点不能为空")
@NotNull(message = "骑行结束点不能为空")
private PGpoint startPoint;
}

View File

@ -0,0 +1,44 @@
package com.cdzy.user.model.dto;
import com.cdzy.user.handler.PGpointDeserializer;
import com.cdzy.user.handler.PGpointSerializer;
import com.cdzy.user.handler.PGpointTypeHandler;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.mybatisflex.annotation.Column;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.postgresql.geometric.PGpoint;
/**
* 用户骑行结束信息
*
* @author: yanglei
* @since: 2025-11-11 14:46
*/
@Data
public class EbikeUserCyclingEndDto {
/**
* 用户ID关联用户表
*/
@NotNull(message = "用户ID不能为空")
private Long userId;
/**
* 骑行设备编号仅骑行订单有效
*/
@NotBlank(message = "车辆编号不能为空")
private String bikeCode;
/**
* 骑行结束点
*/
@Column(typeHandler = PGpointTypeHandler.class)
@JsonSerialize(using = PGpointSerializer.class)
@JsonDeserialize(using = PGpointDeserializer.class)
@NotNull(message = "骑行起始点不能为空")
private PGpoint endPoint;
}

View File

@ -128,6 +128,31 @@ public class EbikeOrder implements Serializable {
*/
private Integer chargeDurationMinutes;
/**
* 禁停区调度费)
*/
private BigDecimal noParkingZoneFee;
/**
* 停车区外调度费
*/
private BigDecimal outOfParkingAreaFee;
/**
* 运营区域外调度费
*/
private BigDecimal outOfServiceAreaFee;
/**
* 封顶金额)
*/
private BigDecimal maxFeeAmount;
/**
* 头盔管理费)
*/
private BigDecimal helmetManagementFee;
/**
* 地理位置GeoHash编码用于区域优惠分析
*/

View File

@ -18,7 +18,7 @@ public class EbikeOrderVo {
private Long orderId;
/**
* 订单编号
* 订单创建时间
*/
private LocalDateTime orderTime;
}

View File

@ -1,6 +1,7 @@
package com.cdzy.user.service;
import com.cdzy.user.model.dto.EbikeUserCyclingDto;
import com.cdzy.user.model.dto.EbikeUserCyclingEndDto;
import com.cdzy.user.model.entity.EbikeOrder;
import com.cdzy.user.model.vo.EbikeOrderVo;
import com.ebike.feign.model.dto.FeignEbikeDto;
@ -26,7 +27,7 @@ public interface EbikeOrderService extends IService<EbikeOrder> {
* @param orderDto 用户骑行信息
* @return 骑行订单
*/
EbikeOrderVo saveRide(EbikeUserCyclingDto orderDto);
EbikeOrder saveRide(EbikeUserCyclingDto orderDto);
/**
* 检查历史订单
@ -91,4 +92,6 @@ public interface EbikeOrderService extends IService<EbikeOrder> {
* @return 车辆基本信息
*/
FeignEbikeUserBikeInfo queryBikeInfo(String bikeCode);
void doneRide(EbikeUserCyclingEndDto endDto);
}

View File

@ -9,10 +9,10 @@ import com.cdzy.user.enums.OrderType;
import com.cdzy.user.mapper.EbikeOrderMapper;
import com.cdzy.user.model.dto.EbikeUnlockResultDto;
import com.cdzy.user.model.dto.EbikeUserCyclingDto;
import com.cdzy.user.model.dto.EbikeUserCyclingEndDto;
import com.cdzy.user.model.entity.EbikeOrder;
import com.cdzy.user.model.vo.EbikeOrderVo;
import com.cdzy.user.service.EbikeOrderService;
import com.cdzy.user.utils.RedisUtil;
import com.ebike.feign.clients.OperationsFeignClient;
import com.ebike.feign.model.dto.FeignEbikeDto;
import com.ebike.feign.model.dto.FeignEbikeUserBikeInfo;
@ -27,6 +27,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
@ -52,7 +54,7 @@ public class EbikeOrderImpl extends ServiceImpl<EbikeOrderMapper, EbikeOrder> im
@Transactional
@Override
public EbikeOrderVo saveRide(EbikeUserCyclingDto orderDto) {
public EbikeOrder saveRide(EbikeUserCyclingDto orderDto) {
Long userId = orderDto.getUserId();
String bikeCode = orderDto.getBikeCode();
// 校验用户当前是否存在订单
@ -65,29 +67,16 @@ public class EbikeOrderImpl extends ServiceImpl<EbikeOrderMapper, EbikeOrder> im
throw new EbikeException("请完成未支付订单后再试");
}
}
// 创建订单
FeignEbikeUserBikeInfo bikeInfo = queryBikeInfo(bikeCode);
if (bikeInfo == null) {
log.error("开锁成功后查询车辆信息为空, bikeCode: {}, userId: {}", bikeCode, userId);
throw new EbikeException("当前车辆信息不存在");
}
EbikeOrder order = EbikeOrder.builder()
.userId(userId)
.operatorId(bikeInfo.getOperatorId())
.bikeCode(orderDto.getBikeCode())
.orderType(OrderType.ONCE)
.startLocation(orderDto.getStartPoint())
.startTime(LocalDateTime.now())
.baseFee(bikeInfo.getBaseFee())
.durationFee(bikeInfo.getDurationFee())
.freeDurationMinutes(bikeInfo.getFreeDurationMinutes())
.chargeDurationMinutes(bikeInfo.getChargeDurationMinutes())
.createTime(LocalDateTime.now())
.build();
save(order);
try {
// 尝试开锁
EbikeUnlockResultDto unlockResult = attemptUnlockWithValidation(bikeCode, userId);
@ -101,11 +90,7 @@ public class EbikeOrderImpl extends ServiceImpl<EbikeOrderMapper, EbikeOrder> im
.updateTime(LocalDateTime.now())
.build();
updateById(orders);
EbikeOrderVo userOrder = new EbikeOrderVo();
userOrder.setOrderId(order.getOrderId());
userOrder.setOrderTime(order.getCreateTime());
return userOrder;
return orders;
} catch (Exception e) {
log.error("开锁失败, userId={}, bikeCode={}", userId, bikeCode, e);
throw new RuntimeException("开锁失败", e);
@ -174,6 +159,49 @@ public class EbikeOrderImpl extends ServiceImpl<EbikeOrderMapper, EbikeOrder> im
return jsonResult.getData();
}
@Transactional
@Override
public void doneRide(EbikeUserCyclingEndDto endDto) {
// 先校验是否有当前订单
EbikeOrder order = getOrder(endDto);
order.setEndTime(LocalDateTime.now());
order.setEndLocation(endDto.getEndPoint());
// 获取车辆信息,计算费用
FeignEbikeUserBikeInfo bikeInfo = queryBikeInfo(endDto.getBikeCode());
BigDecimal totalAmount = costCalculation(order.getStartTime(), order.getEndTime(), bikeInfo);
order.setOrderStatus(OrderStatus.PENDING_PAYMENT);
order.setTotalAmount(totalAmount);
order.setActualAmount(totalAmount);
updateById(order);
// 关锁
//生成支付订单
}
private BigDecimal costCalculation(LocalDateTime startTime, LocalDateTime endTime, FeignEbikeUserBikeInfo bikeInfo) {
BigDecimal baseFee = bikeInfo.getBaseFee();
BigDecimal durationFee = bikeInfo.getDurationFee();
Integer freeDurationMinutes = bikeInfo.getFreeDurationMinutes();
Integer chargeDurationMinutes = bikeInfo.getChargeDurationMinutes();
// 骑行时长
long totalRideMinutes = Duration.between(startTime, endTime).toMinutes();
// 如果骑行时长小于等于免费时长只收取起步费
if (totalRideMinutes <= freeDurationMinutes) {
return baseFee;
}
// 计算计费时长 = 总时长 - 免费时长
long chargeableMinutes = totalRideMinutes - freeDurationMinutes;
// 计算计费单位数量向上取整(计费时长 / 计费时长单位)
long chargeUnits = (chargeableMinutes + chargeDurationMinutes - 1) / chargeDurationMinutes;
// 计算总费用起步费用 + 计费单位数量 * 时长费用
BigDecimal durationCost = durationFee.multiply(BigDecimal.valueOf(chargeUnits));
BigDecimal totalCost = baseFee.add(durationCost);
log.info("费用计算: 总时长={}分钟, 免费时长={}分钟, 计费时长={}分钟, 计费单位={}个, 总费用={}",
totalRideMinutes, freeDurationMinutes, chargeableMinutes, chargeUnits, totalCost);
return totalCost;
}
/**
* 校验车辆是否可用
*
@ -189,11 +217,15 @@ public class EbikeOrderImpl extends ServiceImpl<EbikeOrderMapper, EbikeOrder> im
}
}
private EbikeOrder getOrder(EbikeUserCyclingDto orderDto) {
/**
* 查询当前用户骑行订单
*
* @param endDto 用户骑行结束请求参数
*/
private EbikeOrder getOrder(EbikeUserCyclingEndDto endDto) {
QueryWrapper queryWrapper = QueryWrapper.create()
.where(EBIKE_ORDER.BIKE_CODE.eq(orderDto.getBikeCode()))
.where(EBIKE_ORDER.USER_ID.eq(orderDto.getUserId()))
.where(EBIKE_ORDER.BIKE_CODE.eq(endDto.getBikeCode()))
.where(EBIKE_ORDER.USER_ID.eq(endDto.getUserId()))
.where(EBIKE_ORDER.ORDER_STATUS.eq(OrderStatus.IN_PROGRESS));
EbikeOrder userOrders = ebikeOrderTransactionMapper.selectOneByQuery(queryWrapper);
if (userOrders == null) {

View File

@ -203,6 +203,11 @@ CREATE TABLE "public"."ebike_order" (
"duration_fee" numeric(10,2),
"free_duration_minutes" int4,
"charge_duration_minutes" int4,
"no_parking_zone_fee" numeric(10,2),
"out_of_parking_area_fee" numeric(10,2),
"out_of_service_area_fee" numeric(10,2),
"max_fee_amount" numeric(10,2),
"helmet_management_fee" numeric(10,2),
"geo_hash" varchar(12) COLLATE "pg_catalog"."default",
"start_location" point DEFAULT NULL,
"end_location" point DEFAULT NULL,
@ -233,6 +238,11 @@ COMMENT ON COLUMN "public"."ebike_order"."base_fee" IS '起步费用';
COMMENT ON COLUMN "public"."ebike_order"."duration_fee" IS '时长费用';
COMMENT ON COLUMN "public"."ebike_order"."free_duration_minutes" IS '免费时长(分钟)';
COMMENT ON COLUMN "public"."ebike_order"."charge_duration_minutes" IS '时长(分钟)';
COMMENT ON COLUMN "public"."ebike_order"."no_parking_zone_fee" IS '禁停区调度费(元)';
COMMENT ON COLUMN "public"."ebike_order"."out_of_parking_area_fee" IS '停车区外调度费(元)';
COMMENT ON COLUMN "public"."ebike_order"."out_of_service_area_fee" IS '运营区域外调度费(元)';
COMMENT ON COLUMN "public"."ebike_order"."max_fee_amount" IS '封顶金额(元)';
COMMENT ON COLUMN "public"."ebike_order"."helmet_management_fee" IS '头盔管理费(元)';
COMMENT ON COLUMN "public"."ebike_order"."geo_hash" IS '地理位置GeoHash编码用于区域优惠分析';
COMMENT ON COLUMN "public"."ebike_order"."start_location" IS '骑行初始点';
COMMENT ON COLUMN "public"."ebike_order"."end_location" IS '骑行还车点';