diff --git a/docs/PRD-积分兑换洗车券功能.md b/docs/PRD-积分兑换洗车券功能.md new file mode 100644 index 000000000..bc1bfcfa9 --- /dev/null +++ b/docs/PRD-积分兑换洗车券功能.md @@ -0,0 +1,368 @@ +# PRD:优惠券功能 + +**版本**: v1.1 +**日期**: 2026-03-03 +**项目**: 万车充运营管理平台 +**状态**: 草稿 + +--- + +## 变更记录 + +| 版本 | 日期 | 变更内容 | +|------|------|---------| +| v1.0 | 2026-03-03 | 初稿:积分兑换洗车券功能 | +| v1.1 | 2026-03-03 | 新增:券创建权限体系(平台人员 vs 运营商人员);新增充电折扣券规划;更新数据库设计与业务规则 | + +--- + +## 一、背景与目标 + +### 背景 + +平台现有积分系统(`member_points_info`)支持充电消费奖励积分,但积分使用场景单一(仅支持抵扣充电费用)。为提升用户留存与活跃度,引入优惠券体系,首期以「积分兑换洗车券」为切入点,同时建立支持平台与运营商双层管理的券权限体系,为后续充电折扣券等功能奠定基础。 + +### 目标 + +- 为用户提供积分消耗渠道,提升积分价值感知 +- 通过洗车权益与充电绑定,增强用户充电粘性 +- 建立可扩展的优惠券基础架构,支持后续更多券类型 +- 建立平台与运营商双层券管理权限,支持精细化运营 + +### 成功指标 + +- 积分兑换率(兑换用户数 / 持有积分用户数)≥ 15% +- 洗车券核销率 ≥ 60% +- 功能上线后次月充电频次同比提升 ≥ 5% + +--- + +## 二、用户角色 + +| 角色 | 类型 | 说明 | +|------|------|------| +| **平台管理员** | 创券方 | 平台运营人员,可创建全平台范围或指定运营商范围的优惠券 | +| **运营商管理员** | 创券方 | 运营商后台人员,只能创建本运营商范围内可用的优惠券 | +| **会员用户** | 使用方 | 通过充电获得积分,在 App/小程序中兑换并使用券 | +| **合作洗车商** | 核销方 | 扫码核销洗车券(可通过独立 H5 页面或 API 对接) | + +--- + +## 三、券可用范围权限设计 + +### 3.1 核心规则 + +| 创建人角色 | 可设置的最大可用范围 | 说明 | +|-----------|-------------------|------| +| **平台管理员** | 全平台 | 可选:全平台 / 指定运营商 / 指定站点 | +| **运营商管理员** | 本运营商 | 可选:本运营商全部站点 / 指定站点(仅限本运营商下) | + +### 3.2 可用范围枚举 + +| 范围值 | 说明 | 可创建角色 | +|--------|------|----------| +| `1` - 全平台 | 所有运营商、所有站点均可使用 | 仅平台管理员 | +| `2` - 指定运营商 | 仅指定运营商下的站点可使用 | 平台管理员(可多选);运营商管理员(固定为本运营商) | +| `3` - 指定站点 | 仅指定站点可使用 | 平台管理员;运营商管理员(仅限本运营商下的站点) | + +### 3.3 业务规则 + +1. 运营商管理员创建券时,`scope_type` 最高只能选 `2`(本运营商),**不能选** `1`(全平台) +2. 运营商管理员创建的指定站点券,`scope_merchant_id` 强制写入本运营商 ID,不可修改 +3. 平台管理员可查看所有运营商的券;运营商管理员只能查看和管理本运营商创建的券 +4. 用户兑换或领取券时,系统根据券的 `scope_type` 及关联数据校验该用户是否符合领取资格(如充电站是否在券可用范围内) +5. 充电折扣券使用时额外校验:本次充电所在站点是否在券的可用范围内 + +--- + +## 四、功能范围(首期 v1.0) + +### 4.1 功能清单 + +| 模块 | 功能点 | 优先级 | +|------|--------|--------| +| 券模板管理 | 平台管理员创建/编辑/下架洗车券模板 | P0 | +| 券模板管理 | 运营商管理员创建/编辑/下架洗车券模板 | P0 | +| 券模板管理 | 设置券可用范围(全平台 / 指定运营商 / 指定站点) | P0 | +| 券模板管理 | 设置积分兑换比例(N 积分 = 1 张券) | P0 | +| 券模板管理 | 设置券有效期(固定日期 / 领取后 N 天) | P0 | +| 积分兑换 | 用户查看可兑换券列表 | P0 | +| 积分兑换 | 用户用积分兑换洗车券 | P0 | +| 积分兑换 | 兑换记录查询 | P0 | +| 我的券包 | 查看持有的洗车券(未使用/已使用/已过期) | P0 | +| 券核销 | 展示券码(二维码 / 核销码) | P0 | +| 券核销 | 合作商扫码核销 | P0 | +| 数据看板 | 兑换量、核销量、积分消耗统计 | P1 | +| 运营配置 | 单用户每日/每月兑换上限 | P1 | +| 运营配置 | 券库存上限控制 | P1 | + +### 4.2 首期不包含 + +- 现金购买券 +- 券转赠 +- 充电折扣券(规划于 v1.2) +- 第三方洗车平台 API 对接(仅本地核销) + +--- + +## 五、业务流程 + +### 5.1 创建券模板流程 + +``` +管理员进入券模板管理页 + → 选择券类型(首期仅洗车券) + → 系统根据登录角色自动限制可用范围选项 + ├─ 平台管理员:可选 全平台 / 指定运营商 / 指定站点 + └─ 运营商管理员:可选 本运营商 / 本运营商下指定站点 + → 填写积分兑换价格、库存、有效期、兑换限额等配置 + → 提交 → 系统二次校验 scope 权限边界 + → 保存,默认下架状态,手动上架后对用户可见 +``` + +### 5.2 积分兑换流程 + +``` +用户进入积分商城 + → 系统查询该用户可见的洗车券列表 + (过滤逻辑:券为上架状态 + 未过期 + 有库存 + 用户所在运营商/站点在券可用范围内) + → 用户查看洗车券兑换入口(展示所需积分、库存、有效期) + → 确认兑换 + → 系统校验:积分是否足够 + 库存是否充足 + 兑换限额检查 + → 原子操作:扣减积分 + 生成用户券记录 + → 写入积分变更日志(member_points_record,类型=积分兑换消耗) + → 返回兑换成功,展示券码 +``` + +### 5.3 洗车券核销流程 + +``` +用户出示洗车券二维码 + → 洗车商扫码 → 调用核销接口 + → 系统校验:券是否有效 + 是否已使用 + 是否在有效期内 + → 更新券状态为「已使用」,记录核销时间、核销门店 + → 核销成功响应 +``` + +--- + +## 六、数据库设计 + +### 6.1 券模板表 `coupon_template` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | bigint PK | 主键 | +| `name` | varchar(50) | 券名称,如"洗车券" | +| `type` | tinyint | 券类型:1=洗车券 2=充电折扣券 | +| `creator_type` | tinyint | 创建人类型:1=平台管理员 2=运营商管理员 | +| `creator_merchant_id` | bigint | 创建人所属运营商 ID(平台管理员为 NULL) | +| `scope_type` | tinyint | 可用范围:1=全平台 2=指定运营商 3=指定站点 | +| `points_cost` | int | 兑换所需积分(洗车券用) | +| `discount_rate` | decimal(4,2) | 折扣比例(充电折扣券用,如 0.85 表示 85 折) | +| `stock_total` | int | 总库存(-1 表示不限制) | +| `stock_remain` | int | 剩余库存 | +| `validity_type` | tinyint | 有效期类型:1=固定日期 2=领取后N天 | +| `valid_start_time` | datetime | 固定有效期开始(validity_type=1) | +| `valid_end_time` | datetime | 固定有效期结束(validity_type=1) | +| `valid_days` | int | 领取后有效天数(validity_type=2) | +| `daily_limit` | int | 单用户每日兑换上限(0=不限) | +| `monthly_limit` | int | 单用户每月兑换上限(0=不限) | +| `status` | tinyint | 状态:0=下架 1=上架 | +| `description` | varchar(500) | 券使用说明 | +| `create_by` | varchar(64) | 创建人账号 | +| `create_time` | datetime | 创建时间 | +| `update_time` | datetime | 更新时间 | +| `del_flag` | char(1) | 删除标志 | + +### 6.2 券可用范围明细表 `coupon_template_scope` + +> 用于存储 `scope_type=2`(指定运营商)或 `scope_type=3`(指定站点)时的具体关联数据 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | bigint PK | 主键 | +| `template_id` | bigint | 关联券模板 ID | +| `scope_type` | tinyint | 范围类型:2=运营商 3=站点 | +| `scope_id` | bigint | 运营商 ID 或站点 ID | + +### 6.3 用户券表 `member_coupon` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | bigint PK | 主键 | +| `coupon_no` | varchar(32) | 券编号(唯一,用于核销) | +| `template_id` | bigint | 关联券模板 ID | +| `member_id` | bigint | 关联会员 ID | +| `coupon_type` | tinyint | 券类型快照:1=洗车券 2=充电折扣券 | +| `points_cost` | int | 兑换时消耗的积分(快照,折扣券为 0) | +| `discount_rate` | decimal(4,2) | 折扣比例快照(洗车券为 NULL) | +| `status` | tinyint | 状态:0=未使用 1=已使用 2=已过期 | +| `source` | tinyint | 来源:1=积分兑换 | +| `exchange_time` | datetime | 兑换时间 | +| `expire_time` | datetime | 过期时间 | +| `use_time` | datetime | 核销时间 | +| `use_store_id` | bigint | 核销门店/站点 ID | +| `use_operator` | varchar(64) | 核销操作人 | +| `create_time` | datetime | 创建时间 | +| `del_flag` | char(1) | 删除标志 | + +### 6.4 依赖现有表 + +- `member_points_info`:积分余额更新 +- `member_points_record`:新增消耗类型(`type=3` 积分兑换) +- `pile_merchant_info`:运营商信息(校验 creator_merchant_id) +- `pile_station_info`:站点信息(校验 scope 范围) + +--- + +## 七、接口设计 + +### App/小程序端 + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/coupon/template/list` | GET | 获取用户可兑换券模板列表(按用户所属范围过滤) | +| `/coupon/exchange` | POST | 积分兑换(入参:templateId) | +| `/coupon/my/list` | GET | 我的券包列表(入参:status) | +| `/coupon/my/detail/{couponNo}` | GET | 券详情(含核销二维码) | + +### 管理后台 + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/admin/coupon/template/list` | GET | 券模板列表(运营商管理员仅返回本运营商数据) | +| `/admin/coupon/template/add` | POST | 新增券模板(含 scope 权限校验) | +| `/admin/coupon/template/edit` | PUT | 编辑券模板 | +| `/admin/coupon/template/changeStatus` | PUT | 上下架 | +| `/admin/coupon/record/list` | GET | 兑换记录查询(运营商管理员仅查本运营商数据) | +| `/admin/coupon/verify` | POST | 核销券(入参:couponNo) | +| `/admin/coupon/stats` | GET | 数据统计(运营商管理员仅查本运营商数据) | + +--- + +## 八、关键业务规则 + +1. **原子性保障**:扣减积分与生成券记录必须在同一事务中完成,防止积分扣了但券没生成 +2. **库存防超卖**:使用数据库乐观锁(`UPDATE ... WHERE stock_remain >= 1`)或 Redis 原子操作控制库存 +3. **幂等兑换**:前端提交带客户端请求 ID,防止重复点击导致多次扣分 +4. **券码安全**:`coupon_no` 使用 UUID 或带校验位的随机串,不可预测 +5. **过期处理**:Quartz 定时任务每日凌晨扫描并更新过期券状态 +6. **积分变更日志**:兑换成功后写入 `member_points_record`,确保积分流水可追溯 +7. **Scope 权限隔离**: + - 运营商管理员提交创建/编辑接口时,后端强制校验 `scope_type <= 2`,且 `creator_merchant_id` 必须与登录账号所属运营商一致,防止越权 + - 运营商管理员只能查看和操作本运营商创建的券模板,列表接口自动按 `creator_merchant_id` 过滤 +8. **折扣券使用校验**(v1.2 实现):充电结算时校验充电站是否在折扣券的 `scope` 范围内,不在范围内的券不可抵扣 + +--- + +## 九、非功能需求 + +| 项目 | 要求 | +|------|------| +| 性能 | 兑换接口 P99 < 500ms | +| 并发 | 支持同一券模板 100 QPS 并发兑换 | +| 可靠性 | 积分扣减与券生成保证原子性 | +| 安全 | 核销接口需鉴权;创券接口需强制 scope 边界校验,防止运营商越权 | + +--- + +## 十、数据库建表语句 + +```sql +-- ---------------------------- +-- 券模板表 +-- ---------------------------- +CREATE TABLE `coupon_template` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `name` VARCHAR(50) NOT NULL COMMENT '券名称,如洗车券', + `type` TINYINT NOT NULL DEFAULT 1 COMMENT '券类型:1=洗车券 2=充电折扣券', + `creator_type` TINYINT NOT NULL DEFAULT 1 COMMENT '创建人类型:1=平台管理员 2=运营商管理员', + `creator_merchant_id` BIGINT DEFAULT NULL COMMENT '创建人所属运营商ID,平台管理员为NULL', + `scope_type` TINYINT NOT NULL DEFAULT 1 COMMENT '可用范围:1=全平台 2=指定运营商 3=指定站点', + `points_cost` INT DEFAULT NULL COMMENT '兑换所需积分(洗车券使用)', + `discount_rate` DECIMAL(4, 2) DEFAULT NULL COMMENT '折扣比例(充电折扣券使用,如0.85表示85折)', + `stock_total` INT NOT NULL DEFAULT -1 COMMENT '总库存,-1表示不限制', + `stock_remain` INT NOT NULL DEFAULT -1 COMMENT '剩余库存,-1表示不限制', + `validity_type` TINYINT NOT NULL DEFAULT 2 COMMENT '有效期类型:1=固定日期 2=领取后N天', + `valid_start_time` DATETIME DEFAULT NULL COMMENT '固定有效期开始时间(validity_type=1时有效)', + `valid_end_time` DATETIME DEFAULT NULL COMMENT '固定有效期结束时间(validity_type=1时有效)', + `valid_days` INT DEFAULT NULL COMMENT '领取后有效天数(validity_type=2时有效)', + `daily_limit` INT NOT NULL DEFAULT 0 COMMENT '单用户每日兑换上限,0=不限', + `monthly_limit` INT NOT NULL DEFAULT 0 COMMENT '单用户每月兑换上限,0=不限', + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=下架 1=上架', + `description` VARCHAR(500) DEFAULT NULL COMMENT '券使用说明', + `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建人账号', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志:0=存在 2=删除', + PRIMARY KEY (`id`), + KEY `idx_creator_merchant` (`creator_merchant_id`, `status`, `del_flag`), + KEY `idx_scope_type` (`scope_type`, `status`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COMMENT = '优惠券模板表'; + + +-- ---------------------------- +-- 券可用范围明细表(scope_type=2 或 3 时使用) +-- ---------------------------- +CREATE TABLE `coupon_template_scope` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `template_id` BIGINT NOT NULL COMMENT '关联券模板ID', + `scope_type` TINYINT NOT NULL COMMENT '范围类型:2=运营商 3=站点', + `scope_id` BIGINT NOT NULL COMMENT '运营商ID 或 站点ID', + PRIMARY KEY (`id`), + KEY `idx_template_id` (`template_id`), + UNIQUE KEY `uk_template_scope` (`template_id`, `scope_type`, `scope_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COMMENT = '优惠券可用范围明细表'; + + +-- ---------------------------- +-- 用户券表 +-- ---------------------------- +CREATE TABLE `member_coupon` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `coupon_no` VARCHAR(32) NOT NULL COMMENT '券编号(唯一,用于核销)', + `template_id` BIGINT NOT NULL COMMENT '关联券模板ID', + `member_id` BIGINT NOT NULL COMMENT '关联会员ID', + `coupon_type` TINYINT NOT NULL COMMENT '券类型快照:1=洗车券 2=充电折扣券', + `points_cost` INT NOT NULL DEFAULT 0 COMMENT '兑换时消耗的积分快照,折扣券为0', + `discount_rate` DECIMAL(4, 2) DEFAULT NULL COMMENT '折扣比例快照,洗车券为NULL', + `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0=未使用 1=已使用 2=已过期', + `source` TINYINT NOT NULL DEFAULT 1 COMMENT '来源:1=积分兑换', + `exchange_time` DATETIME DEFAULT NULL COMMENT '兑换时间', + `expire_time` DATETIME NOT NULL COMMENT '过期时间', + `use_time` DATETIME DEFAULT NULL COMMENT '核销时间', + `use_store_id` BIGINT DEFAULT NULL COMMENT '核销门店/站点ID', + `use_operator` VARCHAR(64) DEFAULT NULL COMMENT '核销操作人', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标志:0=存在 2=删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_coupon_no` (`coupon_no`), + KEY `idx_member_id` (`member_id`, `status`), + KEY `idx_template_id` (`template_id`), + KEY `idx_expire_time` (`expire_time`, `status`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COMMENT = '会员优惠券表'; +``` + +> **索引说明** +> - `coupon_template.idx_creator_merchant`:运营商管理员查看本运营商券模板列表的高频查询 +> - `coupon_template_scope.uk_template_scope`:防止同一模板下重复添加相同范围记录 +> - `member_coupon.idx_expire_time`:供 Quartz 定时任务扫描过期券使用 +> - `member_coupon.uk_coupon_no`:唯一索引防止重复券码、保证核销安全 + +--- + +## 十一、后续迭代规划 + +| 阶段 | 功能 | +|------|------| +| v1.1 | 第三方洗车平台 API 对接(自动核销) | +| v1.2 | 充电折扣券:运营商/平台创建折扣券,充电结算时自动抵扣,校验站点范围 | +| v1.3 | 运营活动:充电满额赠券、节日送券 | +| v2.0 | 积分商城完整体验(多商品、排行榜) | diff --git a/jsowell-admin/src/main/java/com/jsowell/api/thirdparty/ChargeAlgorithmController.java b/jsowell-admin/src/main/java/com/jsowell/api/thirdparty/ChargeAlgorithmController.java index 632b79522..87928fff5 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/thirdparty/ChargeAlgorithmController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/thirdparty/ChargeAlgorithmController.java @@ -5,8 +5,7 @@ import com.jsowell.common.core.controller.BaseController; import com.jsowell.common.response.RestApiResponse; import com.jsowell.common.util.StringUtils; import com.jsowell.pile.dto.BatteryChargeReportDTO; -import com.jsowell.thirdparty.platform.service.impl.BatteryChargeReportService; -import com.jsowell.thirdparty.platform.service.impl.ChargeAlgorithmService; +import com.jsowell.pile.service.batteryreport.BatteryChargeReportService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -21,9 +20,6 @@ import org.springframework.web.bind.annotation.*; @RequestMapping("/chargealgorithm") public class ChargeAlgorithmController extends BaseController { - @Autowired - private ChargeAlgorithmService chargeAlgorithmService; - @Autowired private BatteryChargeReportService batteryChargeReportService; @@ -44,4 +40,18 @@ public class ChargeAlgorithmController extends BaseController { } return response; } + + @PostMapping("/getUrlByTaskId") + public RestApiResponse getUrlByTaskId(@RequestBody BatteryChargeReportDTO dto) { + RestApiResponse response = null; + try { + String url = batteryChargeReportService.getUrlByTaskId(dto.getTaskId(), dto.getReportType()); + response = new RestApiResponse<>(url); + }catch (Exception e) { + logger.error("算法应用推送订单信息 error, ", e); + response = new RestApiResponse<>(e); + } + return response; + } + } diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/OrderController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/OrderController.java index a511a9066..1975ebb97 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/OrderController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/OrderController.java @@ -22,7 +22,7 @@ import com.jsowell.pile.vo.uniapp.customer.ParkingOrderVO; import com.jsowell.pile.vo.uniapp.customer.UniAppOrderVO; import com.jsowell.service.OrderService; import com.jsowell.pile.dto.BatteryChargeReportDTO; -import com.jsowell.thirdparty.platform.service.impl.BatteryChargeReportService; +import com.jsowell.pile.service.batteryreport.BatteryChargeReportService; import com.jsowell.wxpay.dto.WechatSendMsgDTO; import com.jsowell.wxpay.service.WxAppletRemoteService; import org.springframework.beans.factory.annotation.Autowired; diff --git a/jsowell-admin/src/main/resources/application-dev.yml b/jsowell-admin/src/main/resources/application-dev.yml index e5ecb7476..5de23e895 100644 --- a/jsowell-admin/src/main/resources/application-dev.yml +++ b/jsowell-admin/src/main/resources/application-dev.yml @@ -36,11 +36,11 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://106.14.94.149:3306/jsowell_pre?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: jsowell_pre -# url: jdbc:mysql://192.168.2.46:3306/jsowell_prd_copy?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 -# username: jsowell_prd_copy - password: Js@160829 +# url: jdbc:mysql://106.14.94.149:3306/jsowell_pre?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 +# username: jsowell_pre + url: jdbc:mysql://192.168.0.32:3306/jsowell_dev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: jsowell_dev + password: 123456 # 从库数据源 slave: # 从数据源开关/默认关闭 @@ -262,7 +262,7 @@ batteryChargeReport: webDomainPrefix: https://wx.btiger.net/jeecg-boot getTaskIdApi: /api/docking/api/evaluate apiPrefix: /api/docking/report/ - token: MTc0NzcyMjgwMzg0NC1xNmFucG96cHR4aQ== + token: MTc3MzAzODY1ODYwNy1sbjdiM3h3dHJi mfrID: mfr8944567890598756 # dubbo配置 diff --git a/jsowell-admin/src/main/resources/application-prd.yml b/jsowell-admin/src/main/resources/application-prd.yml index 669e2e1b4..304beec3b 100644 --- a/jsowell-admin/src/main/resources/application-prd.yml +++ b/jsowell-admin/src/main/resources/application-prd.yml @@ -257,7 +257,7 @@ batteryChargeReport: webDomainPrefix: https://wx.btiger.net/jeecg-boot getTaskIdApi: /api/docking/api/evaluate apiPrefix: /api/docking/report/ - token: MTc0NzcyMjgwMzg0NC1xNmFucG96cHR4aQ== + token: MTc3MzAzODY1ODYwNy1sbjdiM3h3dHJi mfrID: mfr8944567890598756 # dubbo配置 diff --git a/jsowell-admin/src/main/resources/application-pre.yml b/jsowell-admin/src/main/resources/application-pre.yml index 53d67a247..eecf00098 100644 --- a/jsowell-admin/src/main/resources/application-pre.yml +++ b/jsowell-admin/src/main/resources/application-pre.yml @@ -280,7 +280,7 @@ batteryChargeReport: webDomainPrefix: https://wx.btiger.net/jeecg-boot getTaskIdApi: /api/docking/api/evaluate apiPrefix: /api/docking/report/ - token: MTc0NzcyMjgwMzg0NC1xNmFucG96cHR4aQ== + token: MTc3MzAzODY1ODYwNy1sbjdiM3h3dHJi mfrID: mfr8944567890598756 # dubbo配置 diff --git a/jsowell-admin/src/main/resources/application-sit.yml b/jsowell-admin/src/main/resources/application-sit.yml index fc31b5fd2..d1889bc1a 100644 --- a/jsowell-admin/src/main/resources/application-sit.yml +++ b/jsowell-admin/src/main/resources/application-sit.yml @@ -289,7 +289,7 @@ batteryChargeReport: webDomainPrefix: https://wx.btiger.net/jeecg-boot getTaskIdApi: /api/docking/api/evaluate apiPrefix: /api/docking/report/ - token: MTc0NzcyMjgwMzg0NC1xNmFucG96cHR4aQ== + token: MTc3MzAzODY1ODYwNy1sbjdiM3h3dHJi mfrID: mfr8944567890598756 # dubbo配置 diff --git a/jsowell-admin/src/main/resources/application.yml b/jsowell-admin/src/main/resources/application.yml index 7f073a6e6..b158f0628 100644 --- a/jsowell-admin/src/main/resources/application.yml +++ b/jsowell-admin/src/main/resources/application.yml @@ -156,7 +156,7 @@ batteryChargeReport: webDomainPrefix: https://wx.btiger.net/jeecg-boot getTaskIdApi: /api/docking/api/evaluate apiPrefix: /api/docking/report/ - token: MTc0NzcyMjgwMzg0NC1xNmFucG96cHR4aQ== + token: MTc3MzAzODY1ODYwNy1sbjdiM3h3dHJi mfrID: mfr8944567890598756 # sms4j diff --git a/jsowell-admin/src/test/java/PaymentTestController.java b/jsowell-admin/src/test/java/PaymentTestController.java index b39220367..1ab1b3fa1 100644 --- a/jsowell-admin/src/test/java/PaymentTestController.java +++ b/jsowell-admin/src/test/java/PaymentTestController.java @@ -851,6 +851,9 @@ public class PaymentTestController { } + /** + * 处理临时过度账户订单 + */ @Test public void queryAdapayData() { String startTime = "2025-01-01 00:00:00"; diff --git a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TransactionRecordsRequestHandler.java b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TransactionRecordsRequestHandler.java index 9754e52ef..0a87a1276 100644 --- a/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TransactionRecordsRequestHandler.java +++ b/jsowell-netty/src/main/java/com/jsowell/netty/handler/yunkuaichong/TransactionRecordsRequestHandler.java @@ -27,7 +27,7 @@ import com.jsowell.pile.service.*; import com.jsowell.pile.service.programlogic.AbstractProgramLogic; import com.jsowell.pile.service.programlogic.ProgramLogicFactory; import com.jsowell.thirdparty.common.CommonService; -import com.jsowell.thirdparty.platform.service.impl.BatteryChargeReportService; +import com.jsowell.pile.service.batteryreport.BatteryChargeReportService; import io.netty.channel.ChannelHandlerContext; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -768,12 +768,13 @@ public class TransactionRecordsRequestHandler extends AbstractYkcHandler { } } if (StringUtils.isBlank(taskId)) { - log.info("推送充电订单算法平台 taskId 为空"); + taskId = batteryChargeReportService.getTaskIdByOrderCode(orderCode); + // log.info("推送充电订单算法平台 taskId 为空"); } - log.info("推送充电订单算法平台 taskId:{}", taskId); + log.info("推送充电订单算法平台 orderCode:{}, taskId:{}", orderCode, taskId); // 再根据 tasKId 获取链接 String url = batteryChargeReportService.getUrlByTaskId(taskId, reportType); - log.info("推送充电订单算法平台 result:{}", url); + log.info("推送充电订单算法平台 orderCode:{}, result:{}", orderCode, url); return url; } diff --git a/jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/domain/BatteryChargeReportData.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/batteryreport/BatteryChargeReportData.java similarity index 98% rename from jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/domain/BatteryChargeReportData.java rename to jsowell-pile/src/main/java/com/jsowell/pile/domain/batteryreport/BatteryChargeReportData.java index dcf26c673..fea9e9793 100644 --- a/jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/domain/BatteryChargeReportData.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/batteryreport/BatteryChargeReportData.java @@ -1,4 +1,4 @@ -package com.jsowell.thirdparty.platform.domain; +package com.jsowell.pile.domain.batteryreport; import com.alibaba.fastjson2.annotation.JSONField; import lombok.AllArgsConstructor; diff --git a/jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/domain/BatteryReportResult.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/batteryreport/BatteryReportResult.java similarity index 92% rename from jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/domain/BatteryReportResult.java rename to jsowell-pile/src/main/java/com/jsowell/pile/domain/batteryreport/BatteryReportResult.java index 3692a9bea..00e6c2644 100644 --- a/jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/domain/BatteryReportResult.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/batteryreport/BatteryReportResult.java @@ -1,4 +1,4 @@ -package com.jsowell.thirdparty.platform.domain; +package com.jsowell.pile.domain.batteryreport; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatteryChargeReportDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatteryChargeReportDTO.java index 71cf5676a..fac3351bb 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatteryChargeReportDTO.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatteryChargeReportDTO.java @@ -23,4 +23,6 @@ public class BatteryChargeReportDTO { * 1-web; 2-pdf */ private String reportType; + + private String taskId; } diff --git a/jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/service/impl/BatteryChargeReportService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/batteryreport/BatteryChargeReportService.java similarity index 96% rename from jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/service/impl/BatteryChargeReportService.java rename to jsowell-pile/src/main/java/com/jsowell/pile/service/batteryreport/BatteryChargeReportService.java index b9d0e6715..942a1b678 100644 --- a/jsowell-thirdparty/src/main/java/com/jsowell/thirdparty/platform/service/impl/BatteryChargeReportService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/batteryreport/BatteryChargeReportService.java @@ -1,4 +1,4 @@ -package com.jsowell.thirdparty.platform.service.impl; +package com.jsowell.pile.service.batteryreport; import cn.hutool.http.HttpRequest; import com.alibaba.fastjson2.JSON; @@ -10,14 +10,15 @@ import com.jsowell.common.core.redis.RedisCache; import com.jsowell.common.util.DateUtils; import com.jsowell.common.util.StringUtils; import com.jsowell.pile.domain.ChargeAlgorithmRecord; +import com.jsowell.pile.domain.batteryreport.BatteryChargeReportData; +import com.jsowell.pile.domain.batteryreport.BatteryReportResult; import com.jsowell.pile.service.*; import com.jsowell.pile.thirdparty.ParameterConfigData; import com.jsowell.pile.vo.uniapp.customer.OrderVO; import com.jsowell.pile.vo.web.PileConnectorInfoVO; import com.jsowell.pile.vo.web.PileDetailVO; import com.jsowell.pile.vo.web.PileStationVO; -import com.jsowell.thirdparty.platform.domain.BatteryChargeReportData; -import com.jsowell.thirdparty.platform.domain.BatteryReportResult; +import org.apache.commons.lang3.RandomUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -139,7 +140,7 @@ public class BatteryChargeReportService { apiUrl = webDomainPrefix + apiPrefix + "pdf/" + taskId; } // 发送请求 - String result = HttpRequest.get(apiUrl).execute().body(); + String result = HttpRequest.get(apiUrl).header("token", token).execute().body(); log.info("发送获取报告result:{}", result); // 将返回结果转化为json @@ -216,6 +217,13 @@ public class BatteryChargeReportService { .map(PileConnectorInfoVO::getPileSn) .collect(Collectors.toSet()); + String batteryType = String.valueOf(Integer.parseInt(chargingHandshakeData.getBmsBatteryType())); + // 如果 batteryType != 1/2/3/4/5 + if (!StringUtils.equalsAny(batteryType, "1", "2", "3", "4", "5")) { + // 随机生成一个1-3的数 + batteryType = String.valueOf(RandomUtils.nextInt(1, 4)); + } + BatteryChargeReportData data = BatteryChargeReportData.builder() .mfrID(mfrID) .siteName(stationVO.getStationName()) @@ -235,7 +243,7 @@ public class BatteryChargeReportService { .doorStatus(Constants.zero) .bmsChargeMode(Integer.parseInt(bmsDemandAndChargerOutputData.getBmsChargingModel())) .vin(transactionRecordsData.getVinCode()) - .batteryType(String.valueOf(Integer.parseInt(chargingHandshakeData.getBmsBatteryType()))) + .batteryType(batteryType) .nominalEnergy(new BigDecimal(parameterConfigData.getBmsSumEnergy()).toBigInteger().toString()) .ratedCapacity(new BigDecimal(parameterConfigData.getBmsSumEnergy()).toBigInteger().toString()) .ratedVoltage(chargingHandshakeData.getBmsBatteryVoltage()) @@ -251,6 +259,7 @@ public class BatteryChargeReportService { .build(); + List chargingDetailInfos = transformData(map); diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/ChargeAlgorithmRecordServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/ChargeAlgorithmRecordServiceImpl.java index b7e7d0323..7932ab67f 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/ChargeAlgorithmRecordServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/ChargeAlgorithmRecordServiceImpl.java @@ -8,6 +8,7 @@ import com.jsowell.common.util.DateUtils; import com.jsowell.common.util.StringUtils; import com.jsowell.pile.dto.BatteryChargeReportDTO; import com.jsowell.pile.service.ChargeAlgorithmRecordService; +import com.jsowell.pile.service.batteryreport.BatteryChargeReportService; import com.jsowell.pile.vo.uniapp.customer.ChargeAlgorithmRecordVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -25,6 +26,9 @@ public class ChargeAlgorithmRecordServiceImpl implements ChargeAlgorithmRecordSe @Autowired private ChargeAlgorithmRecordMapper chargeAlgorithmRecordMapper; + @Autowired + private BatteryChargeReportService batteryChargeReportService; + /** * 查询电池充电算法记录 * @@ -125,16 +129,44 @@ public class ChargeAlgorithmRecordServiceImpl implements ChargeAlgorithmRecordSe */ @Override public String getUrlByParams(BatteryChargeReportDTO dto) { + // 参数校验 + if (dto == null || StringUtils.isBlank(dto.getOrderCode())) { + return StringUtils.EMPTY; + } + ChargeAlgorithmRecord record = queryRecordByOrderCode(dto.getOrderCode()); - String reportType = dto.getReportType(); + if (record == null) { + return StringUtils.EMPTY; + } + + // 获取记录中的URL + String urlFromRecord = getUrlFromRecordByType(record, dto.getReportType()); + + if (StringUtils.isNotBlank(urlFromRecord)) { + return urlFromRecord; + } + + // 如果记录中没有且taskId存在,通过服务获取 + if (StringUtils.isNotBlank(record.getTaskId())) { + return batteryChargeReportService.getUrlByTaskId(record.getTaskId(), dto.getReportType()); + } + + return StringUtils.EMPTY; + } + + /** + * 通过记录获取URL + * @param record + * @param reportType + * @return + */ + private String getUrlFromRecordByType(ChargeAlgorithmRecord record, String reportType) { if (StringUtils.equals(Constants.ONE, reportType)) { - // 1-web 端 return record.getWebUrl(); - }else if (StringUtils.equals(Constants.TWO, reportType)) { - // 2-pdf 端 + } else if (StringUtils.equals(Constants.TWO, reportType)) { return record.getPdfUrl(); } - return ""; + return null; } /**