diff --git a/ebike-feign/src/main/java/com/ebike/feign/clients/OperationsFeignClient.java b/ebike-feign/src/main/java/com/ebike/feign/clients/OperationsFeignClient.java index 904ddd5..0195b19 100644 --- a/ebike-feign/src/main/java/com/ebike/feign/clients/OperationsFeignClient.java +++ b/ebike-feign/src/main/java/com/ebike/feign/clients/OperationsFeignClient.java @@ -8,6 +8,7 @@ import com.ebike.feign.model.dto.FeignEbikeUserBikeInfo; import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.validation.annotation.Validated; +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.RequestParam; @@ -34,7 +35,7 @@ public interface OperationsFeignClient { * * @return 结果 */ - @PostMapping("/ebikeBikeInfo/api/openLock") + @GetMapping("/ebikeBikeInfo/api/openLock") JsonResult openLock(@RequestParam("bikeCode")String bikeCode); /** @@ -42,7 +43,7 @@ public interface OperationsFeignClient { * * @return 结果 */ - @PostMapping("/ebikeBikeInfo/api/bikeInfo") + @GetMapping("/ebikeBikeInfo/api/bikeInfo") JsonResult bikeInfo(@RequestParam("bikeCode")String bikeCode); } diff --git a/ebike-user/src/main/java/com/cdzy/user/controller/EbikeOrderController.java b/ebike-user/src/main/java/com/cdzy/user/controller/EbikeOrderController.java index b90d7ae..8da1692 100644 --- a/ebike-user/src/main/java/com/cdzy/user/controller/EbikeOrderController.java +++ b/ebike-user/src/main/java/com/cdzy/user/controller/EbikeOrderController.java @@ -1,6 +1,5 @@ package com.cdzy.user.controller; -import com.cdzy.common.model.request.PageParam; import com.cdzy.common.model.response.JsonResult; import com.cdzy.user.model.dto.EbikeUserCyclingDto; import com.cdzy.user.model.entity.EbikeOrder; @@ -8,19 +7,15 @@ import com.cdzy.user.model.vo.EbikeOrderVo; import com.cdzy.user.service.EbikeOrderService; import com.ebike.feign.clients.OperationsFeignClient; import com.ebike.feign.model.dto.FeignEbikeDto; +import com.ebike.feign.model.dto.FeignEbikeUserBikeInfo; import com.ebike.feign.model.dto.FeignOrderPaymentDto; import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo; -import com.mybatisflex.core.paginate.Page; -import com.mybatisflex.core.query.QueryWrapper; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotNull; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Objects; - -import static com.cdzy.user.model.entity.table.EbikeOrderTableDef.EBIKE_ORDER; /** * 用户订单 控制层 @@ -147,20 +142,6 @@ public class EbikeOrderController { return JsonResult.success(userOrder); } - /** - * 分页查询用户订单。 - * - * @param pageParam 分页对象 - * @ 分页对象 - */ - @GetMapping("page") - public JsonResult page(@Validated PageParam pageParam, @RequestParam(value = "bikeId", required = false) Long bikeId) { - QueryWrapper queryWrapper = QueryWrapper.create() - .from(EBIKE_ORDER) - .where(EBIKE_ORDER.BIKE_ID.eq(bikeId, Objects.nonNull(bikeId))); - Page page = ebikeOrderService.page(pageParam.getPage(), queryWrapper); - return JsonResult.success(page); - } /** * 车辆列表 @@ -173,4 +154,15 @@ public class EbikeOrderController { List result = ebikeOrderService.userRadiusList(feignEbikeBikeRadiusVo); return JsonResult.success(result); } + + /** + * 获取车辆基本信息 + * + * @param bikeCode 车辆编码 + */ + @GetMapping("queryBikeInfo") + public JsonResult queryBikeInfo(@RequestParam("bikeCode") String bikeCode) { + FeignEbikeUserBikeInfo result = ebikeOrderService.queryBikeInfo(bikeCode); + return JsonResult.success(result); + } } diff --git a/ebike-user/src/main/java/com/cdzy/user/model/dto/EbikeRealNameSignDto.java b/ebike-user/src/main/java/com/cdzy/user/model/dto/EbikeRealNameSignDto.java new file mode 100644 index 0000000..df69aab --- /dev/null +++ b/ebike-user/src/main/java/com/cdzy/user/model/dto/EbikeRealNameSignDto.java @@ -0,0 +1,32 @@ +package com.cdzy.user.model.dto; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author: yanglei + * @since: 2025-11-11 09:22 + */ +@Data +public class EbikeRealNameSignDto { + + private String name; + private String idCard; + private String timestamp; + + public EbikeRealNameSignDto(String name, String idCard, String timestamp) { + this.name = name; + this.idCard = idCard; + this.timestamp = timestamp; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("name", this.name); + map.put("idCard", this.idCard); + map.put("timestamp", this.timestamp); + return map; + } +} diff --git a/ebike-user/src/main/java/com/cdzy/user/model/dto/EbikeRealNameVerifyDto.java b/ebike-user/src/main/java/com/cdzy/user/model/dto/EbikeRealNameVerifyDto.java new file mode 100644 index 0000000..e90a7db --- /dev/null +++ b/ebike-user/src/main/java/com/cdzy/user/model/dto/EbikeRealNameVerifyDto.java @@ -0,0 +1,22 @@ +package com.cdzy.user.model.dto; + +import lombok.Data; + +/** + * @author: yanglei + * @since: 2025-11-11 09:13 + */ + +@Data +public class EbikeRealNameVerifyDto { + + private String name; + + private String idCard; + + private String timestamp; + + private String sign; + + private String key; +} diff --git a/ebike-user/src/main/java/com/cdzy/user/model/entity/EbikeOrder.java b/ebike-user/src/main/java/com/cdzy/user/model/entity/EbikeOrder.java index 338a5ba..78505a3 100644 --- a/ebike-user/src/main/java/com/cdzy/user/model/entity/EbikeOrder.java +++ b/ebike-user/src/main/java/com/cdzy/user/model/entity/EbikeOrder.java @@ -52,11 +52,6 @@ public class EbikeOrder implements Serializable { */ private Long operatorId; - /** - * 车辆Id - */ - private Long bikeId; - /** * 车辆编码 */ @@ -114,9 +109,24 @@ public class EbikeOrder implements Serializable { private String discountDetails; /** - * 计费规则 + * 起步费用 */ - private String billingRules; + private BigDecimal baseFee; + + /** + * 时长费用 + */ + private BigDecimal durationFee; + + /** + * 免费时长(分钟) + */ + private Integer freeDurationMinutes; + + /** + * 时长(分钟) + */ + private Integer chargeDurationMinutes; /** * 地理位置GeoHash编码(用于区域优惠分析) diff --git a/ebike-user/src/main/java/com/cdzy/user/service/EbikeOrderService.java b/ebike-user/src/main/java/com/cdzy/user/service/EbikeOrderService.java index ed0316b..53b42eb 100644 --- a/ebike-user/src/main/java/com/cdzy/user/service/EbikeOrderService.java +++ b/ebike-user/src/main/java/com/cdzy/user/service/EbikeOrderService.java @@ -4,6 +4,7 @@ import com.cdzy.user.model.dto.EbikeUserCyclingDto; import com.cdzy.user.model.entity.EbikeOrder; import com.cdzy.user.model.vo.EbikeOrderVo; import com.ebike.feign.model.dto.FeignEbikeDto; +import com.ebike.feign.model.dto.FeignEbikeUserBikeInfo; import com.ebike.feign.model.dto.FeignOrderPaymentDto; import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo; import com.mybatisflex.core.service.IService; @@ -83,4 +84,11 @@ public interface EbikeOrderService extends IService { * @return 所有车辆信息 */ List userRadiusList(FeignEbikeBikeRadiusVo feignEbikeBikeRadiusVo); + + /** + * 获取车辆基本信息 + * @param bikeCode 车辆编码 + * @return 车辆基本信息 + */ + FeignEbikeUserBikeInfo queryBikeInfo(String bikeCode); } diff --git a/ebike-user/src/main/java/com/cdzy/user/service/impl/EbikeOrderImpl.java b/ebike-user/src/main/java/com/cdzy/user/service/impl/EbikeOrderImpl.java index 822bd8d..3ba9d28 100644 --- a/ebike-user/src/main/java/com/cdzy/user/service/impl/EbikeOrderImpl.java +++ b/ebike-user/src/main/java/com/cdzy/user/service/impl/EbikeOrderImpl.java @@ -14,18 +14,22 @@ 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; import com.ebike.feign.model.dto.FeignOrderPaymentDto; import com.ebike.feign.model.vo.FeignEbikeBikeInfoVo; import com.ebike.feign.model.vo.FeignEbikeBikeRadiusVo; import com.fasterxml.jackson.databind.ObjectMapper; import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.spring.service.impl.ServiceImpl; +import feign.FeignException; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; +import java.util.Objects; import static com.cdzy.user.model.entity.table.EbikeOrderTableDef.EBIKE_ORDER; @@ -36,7 +40,7 @@ import static com.cdzy.user.model.entity.table.EbikeOrderTableDef.EBIKE_ORDER; * @author: yanglei * @since: 2025-10-15 17:07 */ - +@Slf4j @Service public class EbikeOrderImpl extends ServiceImpl implements EbikeOrderService { @@ -58,34 +62,42 @@ public class EbikeOrderImpl extends ServiceImpl im Long userId = orderDto.getUserId(); // 校验用户当前是否存在订单 EbikeOrder history = checkHistoryOrder(userId); - if (history != null && history.getOrderStatus() == OrderStatus.IN_PROGRESS) { - throw new RuntimeException("请完成当前订单后再试"); + if (Objects.nonNull(history)) { + if (history.getOrderStatus() == OrderStatus.IN_PROGRESS) { + throw new RuntimeException("请完成当前订单后再试"); + } + if (history.getOrderStatus() == OrderStatus.PENDING_PAYMENT) { + throw new RuntimeException("请完成未支付订单后再试"); + } } - if (history != null && history.getOrderStatus() == OrderStatus.PENDING_PAYMENT) { - throw new RuntimeException("请完成未支付订单后再试"); + // 创建订单 + EbikeOrder order = EbikeOrder.builder() + .userId(orderDto.getUserId()) + .bikeCode(orderDto.getBikeCode()) + .orderType(OrderType.ONCE) + .startLocation(orderDto.getStartPoint()) + .startTime(LocalDateTime.now()) + .build(); + save(order); + // 开锁 + try { + JsonResult lockResult = operationsFeignClient.openLock(orderDto.getBikeCode()); + if (lockResult == null || !"200".equals(lockResult.getCode())) { + String errorMsg = (lockResult != null && lockResult.getMessage() != null) + ? lockResult.getMessage() + : "开锁服务返回异常"; + throw new EbikeException("开锁失败: " + errorMsg); + } + } catch (FeignException e) { + log.error("开锁服务调用失败, bikeCode={}, userId={}", orderDto.getBikeCode(), orderDto.getUserId(), e); + throw new EbikeException("开锁服务暂时不可用,请稍后重试"); + } catch (Exception e) { + log.error("开锁过程发生系统异常", e); + throw new EbikeException("系统繁忙,开锁失败"); } - EbikeOrder userOrders = new EbikeOrder(); - userOrders.setUserId(userId); - userOrders.setBikeCode(orderDto.getBikeCode()); - userOrders.setOrderType(OrderType.ONCE); - userOrders.setStartLocation(orderDto.getStartPoint()); - userOrders.setStartTime(LocalDateTime.now()); - save(userOrders); - // todo 开锁 1.开锁成功(骑行) 2.开锁失败(订单标记为已取消) - boolean unlockSuccess = false; - // 远程调用,是否开锁成功 - - // 开锁失败 -> 订单回滚 - if (!unlockSuccess) { - userOrders.setOrderStatus(OrderStatus.CANCELLED); - userOrders.setCancelTime(LocalDateTime.now()); - updateById(userOrders); - throw new EbikeException("开始失败!"); - } - // 状态返回 EbikeOrderVo userOrder = new EbikeOrderVo(); - userOrder.setOrderId(userOrders.getOrderId()); - userOrder.setOrderTime(userOrders.getCreateTime()); + userOrder.setOrderId(order.getOrderId()); + userOrder.setOrderTime(order.getCreateTime()); return userOrder; } @@ -141,6 +153,15 @@ public class EbikeOrderImpl extends ServiceImpl im return jsonResult.getData(); } + @Override + public FeignEbikeUserBikeInfo queryBikeInfo(String bikeCode) { + JsonResult jsonResult = operationsFeignClient.bikeInfo(bikeCode); + if (jsonResult.getCode() != Code.SUCCESS) { + throw new EbikeException("获取半径内车辆错误"); + } + return jsonResult.getData(); + } + /** * 校验车辆是否可用 * diff --git a/ebike-user/src/main/java/com/cdzy/user/utils/VerifyUtil.java b/ebike-user/src/main/java/com/cdzy/user/utils/VerifyUtil.java index b49c491..d3ae47a 100644 --- a/ebike-user/src/main/java/com/cdzy/user/utils/VerifyUtil.java +++ b/ebike-user/src/main/java/com/cdzy/user/utils/VerifyUtil.java @@ -11,6 +11,8 @@ import com.cdzy.common.secure.SecureSM2Component; import com.cdzy.user.config.RealNameVerifyConfig; import com.cdzy.user.config.WechatConfig; import com.cdzy.user.enums.RealNameAttestationStatus; +import com.cdzy.user.model.dto.EbikeRealNameSignDto; +import com.cdzy.user.model.dto.EbikeRealNameVerifyDto; import com.cdzy.user.model.dto.UserValidateDto; import com.cdzy.user.model.entity.EbikeUserRealInfo; import com.cdzy.user.model.vo.EbikeUserVo; @@ -24,6 +26,7 @@ import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -147,6 +150,7 @@ public class VerifyUtil { * @param userValidateDto 验证用户实名请求 * @return 验证结果 */ + @Transactional public JsonResult verifyRealName(UserValidateDto userValidateDto) { EbikeUserVo userInfo = ebikeUserService.getUserInfoByUserId(userValidateDto.getUserId()); if (userInfo == null) { @@ -160,16 +164,22 @@ public class VerifyUtil { } try { // 1. 加密数据 - Map params = new HashMap<>(); - params.put("name", securityContext.encrypt(userValidateDto.getName(), realNameVerifyConfig.getServerPublicKey())); - params.put("idcard", securityContext.encrypt(userValidateDto.getIdCard(), realNameVerifyConfig.getServerPublicKey())); - params.put("timestamp", securityContext.encrypt(String.valueOf(System.currentTimeMillis()), realNameVerifyConfig.getServerPublicKey())); - String sign = securityContext.sign(params, realNameVerifyConfig.getClientPrivateKey()); - params.put("sign", sign); - params.put("key", realNameVerifyConfig.getApiKey()); + String encryptedName = securityContext.encrypt(userValidateDto.getName(), realNameVerifyConfig.getServerPublicKey()); + String encryptedIdCard = securityContext.encrypt(userValidateDto.getIdCard(), realNameVerifyConfig.getServerPublicKey()); + String encryptedTimestamp = securityContext.encrypt(String.valueOf(System.currentTimeMillis()), realNameVerifyConfig.getServerPublicKey()); + + EbikeRealNameSignDto signData = new EbikeRealNameSignDto(encryptedName, encryptedIdCard, encryptedTimestamp); + String sign = securityContext.sign(signData.toMap(), realNameVerifyConfig.getClientPrivateKey()); + + EbikeRealNameVerifyDto ebikeRealNameVerifyDto = new EbikeRealNameVerifyDto(); + ebikeRealNameVerifyDto.setName(encryptedName); + ebikeRealNameVerifyDto.setIdCard(encryptedIdCard); + ebikeRealNameVerifyDto.setTimestamp(encryptedTimestamp); + ebikeRealNameVerifyDto.setSign(sign); + ebikeRealNameVerifyDto.setKey(realNameVerifyConfig.getApiKey()); // 2. 验证用户实名 - JsonNode result = httpPost("验证用户实名", REALNAME_VERIFY_URL, params, null); + JsonNode result = httpPost("验证用户实名", REALNAME_VERIFY_URL, ebikeRealNameVerifyDto, null); log.info("验证用户实名结果:{}", result); if (result == null) { log.error("验证用户实名失败"); @@ -247,10 +257,57 @@ public class VerifyUtil { paramJoiner.add(entry.getKey() + "=" + entry.getValue()); } String requestUrl = url + "?" + paramJoiner.toString(); + Request request = new Request.Builder() .url(requestUrl) .get() .build(); + + return executeAndParseResponse(func_, url, request); + } + + /** + * 发送HTTP POST参数请求。 + * + * @param func_ 功能描述 + * @param url 请求URL + * @param dto 请求参数 + * @return 响应结果 + */ + private JsonNode httpPost(String func_, String url, EbikeRealNameVerifyDto dto, Map body) { + StringBuilder queryString = new StringBuilder(); + appendParam(queryString, "name", dto.getName()); + appendParam(queryString, "idCard", dto.getIdCard()); + appendParam(queryString, "timestamp", dto.getTimestamp()); + appendParam(queryString, "sign", dto.getSign()); + appendParam(queryString, "key", dto.getKey()); + + String requestUrl = url; + if (!queryString.isEmpty()) { + requestUrl += "?" + queryString.toString(); + } + + Request.Builder builder = new Request.Builder().url(requestUrl); + + if (body != null) { + MediaType typeJson = MediaType.parse("application/json; charset=utf-8"); + try { + String jsonBody = objectMapper.writeValueAsString(body); + RequestBody requestBody = RequestBody.create(jsonBody, typeJson); + builder.post(requestBody); + } catch (JsonProcessingException e) { + log.error("{}, 请求体序列化失败", func_, e); + return null; + } + } else { + builder.post(RequestBody.create(new byte[0])); + } + + Request request = builder.build(); + return executeAndParseResponse(func_, url, request); + } + + private JsonNode executeAndParseResponse(String func_, String url, Request request) { try (Response response = client.newCall(request).execute()) { if (response.isSuccessful()) { if (response.body() != null) { @@ -272,59 +329,18 @@ public class VerifyUtil { } } - /** - * 发送HTTP POST参数请求。 - * - * @param func_ 功能描述 - * @param url 请求URL - * @param params 请求参数 - * @return 响应结果 - */ - private JsonNode httpPost(String func_, String url, Map params, Map body) { - StringJoiner paramJoiner = new StringJoiner("&"); - for (Map.Entry entry : params.entrySet()) { - paramJoiner.add(entry.getKey() + "=" + entry.getValue()); + + private void appendParam(StringBuilder sb, String key, String value) { + if (value == null) { + return; } - String requestUrl = url; - if (paramJoiner.length() > 0) - requestUrl += "?" + paramJoiner.toString(); - Request.Builder builder = new Request.Builder() - .url(requestUrl); - if (body != null) { - MediaType typeJson = MediaType.parse("application/json; charset=utf-8"); - try { - String jsonBody = objectMapper.writeValueAsString(body); - RequestBody requestBody = RequestBody.create(jsonBody, typeJson); - builder.post(requestBody); - } catch (JsonProcessingException e) { - log.error("{}, 请求体序列化失败", func_, e); - return null; - } - } else { - builder.post(RequestBody.create(null, new byte[0])); - } - Request request = builder.build(); - try (Response response = client.newCall(request).execute()) { - if (response.isSuccessful()) { - if (response.body() != null) { - String responseBody = response.body().string(); - return objectMapper.readTree(responseBody); - } - log.error("{}, 请求{}无返回结果", func_, url); - return null; - } - if (response.body() != null) { - log.error("{}, 请求{}失败, {}", func_, url, response.body().string()); - } else { - log.error("{}, 请求{}失败", func_, url); - } - return null; - } catch (Exception e) { - log.error("{}, 请求{}失败", func_, url, e); - return null; + if (!sb.isEmpty()) { + sb.append("&"); } + sb.append(key).append("=").append(value); } + private SecureComponent getSecureComponent() { if (Objects.isNull(secureComponent)) { secureComponent = new SecureSM2Component(); diff --git a/ebike-user/src/main/resources/db/init.sql b/ebike-user/src/main/resources/db/init.sql index 4f559da..ef69cb3 100644 --- a/ebike-user/src/main/resources/db/init.sql +++ b/ebike-user/src/main/resources/db/init.sql @@ -187,8 +187,7 @@ DROP TABLE IF EXISTS "public"."ebike_order"; CREATE TABLE "public"."ebike_order" ( "order_id" int8 NOT NULL DEFAULT nextval('ebike_order_order_id_seq'::regclass), "user_id" int8 NOT NULL, - "operator_id" int8 NOT NULL, - "bike_id" int8 NOT NULL, + "operator_id" int8, "bike_code" varchar(50) COLLATE "pg_catalog"."default", "order_type" int2 NOT NULL, "order_status" int2 NOT NULL, @@ -200,7 +199,10 @@ CREATE TABLE "public"."ebike_order" ( "payment_method" varchar(20) COLLATE "pg_catalog"."default", "coupon_ids" varchar(255) COLLATE "pg_catalog"."default", "discount_details" jsonb, - "billing_rules" jsonb, + "base_fee" numeric(10,2), + "duration_fee" numeric(10,2), + "free_duration_minutes" int4, + "charge_duration_minutes" int4, "geo_hash" varchar(12) COLLATE "pg_catalog"."default", "start_location" point DEFAULT NULL, "end_location" point DEFAULT NULL, @@ -216,7 +218,6 @@ CREATE TABLE "public"."ebike_order" ( COMMENT ON COLUMN "public"."ebike_order"."order_id" IS '主键订单Id'; COMMENT ON COLUMN "public"."ebike_order"."user_id" IS '用户ID,关联用户表'; COMMENT ON COLUMN "public"."ebike_order"."operator_id" IS '运营商id'; -COMMENT ON COLUMN "public"."ebike_order"."bike_id" IS '车辆id'; COMMENT ON COLUMN "public"."ebike_order"."bike_code" IS '车辆编号'; COMMENT ON COLUMN "public"."ebike_order"."order_type" IS '订单类型:1-单次骑行 2-骑行卡购买 3-会员卡续费'; COMMENT ON COLUMN "public"."ebike_order"."order_status" IS '状态:0-进行中 1-已取消 2-待支付 3-已支付 4-退款中 5-已退款 6-退款申请中 7-退款驳回 8-退款失败'; @@ -228,7 +229,10 @@ COMMENT ON COLUMN "public"."ebike_order"."payment_time" IS '支付成功时间'; COMMENT ON COLUMN "public"."ebike_order"."payment_method" IS '支付方式:wechat/alipay/balance'; COMMENT ON COLUMN "public"."ebike_order"."coupon_ids" IS '使用卡券ID集合(JSON数组)'; COMMENT ON COLUMN "public"."ebike_order"."discount_details" IS '优惠明细(结构化存储,便于对账)'; -COMMENT ON COLUMN "public"."ebike_order"."billing_rules" IS '计费规则'; +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"."geo_hash" IS '地理位置GeoHash编码(用于区域优惠分析)'; COMMENT ON COLUMN "public"."ebike_order"."start_location" IS '骑行初始点'; COMMENT ON COLUMN "public"."ebike_order"."end_location" IS '骑行还车点';