Merge remote-tracking branch 'origin/main'

This commit is contained in:
attiya 2025-11-11 11:22:39 +08:00
commit 420655a105
9 changed files with 223 additions and 117 deletions

View File

@ -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<FeignEbikeUserBikeInfo> bikeInfo(@RequestParam("bikeCode")String bikeCode);
}

View File

@ -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<EbikeOrder> page = ebikeOrderService.page(pageParam.getPage(), queryWrapper);
return JsonResult.success(page);
}
/**
* 车辆列表
@ -173,4 +154,15 @@ public class EbikeOrderController {
List<FeignEbikeDto> 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);
}
}

View File

@ -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<String, String> toMap() {
Map<String, String> map = new HashMap<>();
map.put("name", this.name);
map.put("idCard", this.idCard);
map.put("timestamp", this.timestamp);
return map;
}
}

View File

@ -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;
}

View File

@ -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编码用于区域优惠分析

View File

@ -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<EbikeOrder> {
* @return 所有车辆信息
*/
List<FeignEbikeDto> userRadiusList(FeignEbikeBikeRadiusVo feignEbikeBikeRadiusVo);
/**
* 获取车辆基本信息
* @param bikeCode 车辆编码
* @return 车辆基本信息
*/
FeignEbikeUserBikeInfo queryBikeInfo(String bikeCode);
}

View File

@ -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<EbikeOrderMapper, EbikeOrder> implements EbikeOrderService {
@ -58,34 +62,42 @@ public class EbikeOrderImpl extends ServiceImpl<EbikeOrderMapper, EbikeOrder> 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<EbikeOrderMapper, EbikeOrder> im
return jsonResult.getData();
}
@Override
public FeignEbikeUserBikeInfo queryBikeInfo(String bikeCode) {
JsonResult<FeignEbikeUserBikeInfo> jsonResult = operationsFeignClient.bikeInfo(bikeCode);
if (jsonResult.getCode() != Code.SUCCESS) {
throw new EbikeException("获取半径内车辆错误");
}
return jsonResult.getData();
}
/**
* 校验车辆是否可用
*

View File

@ -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<String, String> 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<String, Object> 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<String, String> params, Map<String, Object> body) {
StringJoiner paramJoiner = new StringJoiner("&");
for (Map.Entry<String, String> 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();

View File

@ -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 '骑行还车点';