From daf9cbb62dcbb94cd57327e28f8ab8d28c6fc31a Mon Sep 17 00:00:00 2001 From: Lemon Date: Mon, 11 May 2026 13:41:48 +0800 Subject: [PATCH 01/15] =?UTF-8?q?bugfix=20=E4=BD=99=E9=A2=9D=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E6=97=B6=E6=9F=A5=E8=AF=A2=E6=9C=80=E8=BF=91=E4=B8=80?= =?UTF-8?q?=E5=B9=B4=E7=9A=84=E6=94=AF=E4=BB=98=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java index 52ab69f7e..cab28f0ae 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java @@ -2101,6 +2101,11 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService { List resultList = Lists.newArrayList(); // 查询会员的余额充值记录 按照充值时间正序 List memberAdapayRecords = memberAdapayRecordService.selectAvailableBalance(memberId); + // 筛选该list最近一年的数据 + memberAdapayRecords = memberAdapayRecords.stream() + .filter(record -> DateUtils.date2LocalDateTime(record.getCreateTime()) + .isAfter(LocalDateTime.now().minusYears(1))) + .collect(Collectors.toList()); // 定义一个临时金额等于消费金额 BigDecimal tempAmount = new BigDecimal(amount.toString()); From 03f9bbbd1500c7519cbc6bcf92206569a180c063 Mon Sep 17 00:00:00 2001 From: Lemon Date: Mon, 11 May 2026 14:39:38 +0800 Subject: [PATCH 02/15] =?UTF-8?q?add=20=E6=96=B0=E5=A2=9E=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E7=94=A8=E6=88=B7=E4=BD=99=E9=A2=9D=E4=B8=B4=E6=97=B6?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/uniapp/customer/TempController.java | 20 +++++++++++++++++++ .../test/java/SpringBootTestController.java | 15 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java index d2eb68aca..6254147b3 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java @@ -94,6 +94,26 @@ public class TempController extends BaseController { @Autowired private EBikeSendCommandService eBikeSendCommandService; + /** + * 临时退款用户余额 + * @param dto + * @return + */ + public RestApiResponse tempRefundAmount(@RequestBody ApplyRefundDTO dto) { + RestApiResponse response = null + try { + String mode = pileMerchantInfoService.getDelayModeByWechatAppId(dto.getWechatAppId()); + // 获取处理逻辑 + AbstractProgramLogic orderLogic = ProgramLogicFactory.getProgramLogic(mode); + orderLogic.refundBalance(dto); + response = new RestApiResponse<>(); + } catch (Exception e) { + logger.error("临时退款用户余额 error", e); + response = new RestApiResponse<>(e); + } + return response; + } + /** * 电单车开始充电 * http://localhost:8080/temp/tempStartCharging diff --git a/jsowell-admin/src/test/java/SpringBootTestController.java b/jsowell-admin/src/test/java/SpringBootTestController.java index a5a4e655f..0e68fe16f 100644 --- a/jsowell-admin/src/test/java/SpringBootTestController.java +++ b/jsowell-admin/src/test/java/SpringBootTestController.java @@ -313,6 +313,21 @@ public class SpringBootTestController { private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10, JsowellThreadFactory.forName("test-thread-factory")); + @Test + public void testRefundAmount() { + ApplyRefundDTO dto = new ApplyRefundDTO(); + dto.setMemberId("42833346"); + dto.setRefundAmount(new BigDecimal("13.12")); + dto.setWechatAppId(Constants.XIXIAO_APP_ID); + dto.setRefundType("2"); + dto.setWalletCode("8986380447445731"); + + String mode = pileMerchantInfoService.getDelayModeByWechatAppId(dto.getWechatAppId()); + // 获取处理逻辑 + AbstractProgramLogic orderLogic = ProgramLogicFactory.getProgramLogic(mode); + orderLogic.refundBalance(dto); + } + @Test public void deleteThirdPartyStationRelationTest() { ThirdPartyStationRelationDTO dto = new ThirdPartyStationRelationDTO(); From 7a1f7c04b3470adcd0533d9e0dee8f1a33fdaf81 Mon Sep 17 00:00:00 2001 From: Lemon Date: Mon, 11 May 2026 14:43:14 +0800 Subject: [PATCH 03/15] update --- .../java/com/jsowell/api/uniapp/customer/TempController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java index 6254147b3..dc1612775 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java @@ -100,7 +100,7 @@ public class TempController extends BaseController { * @return */ public RestApiResponse tempRefundAmount(@RequestBody ApplyRefundDTO dto) { - RestApiResponse response = null + RestApiResponse response = null; try { String mode = pileMerchantInfoService.getDelayModeByWechatAppId(dto.getWechatAppId()); // 获取处理逻辑 From c320f243469ad0e4e97b6cd8c26d9b4ceb9ac20d Mon Sep 17 00:00:00 2001 From: Lemon Date: Mon, 11 May 2026 14:53:11 +0800 Subject: [PATCH 04/15] update --- .../java/com/jsowell/api/uniapp/customer/TempController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java index dc1612775..8ca840e3c 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java @@ -99,6 +99,7 @@ public class TempController extends BaseController { * @param dto * @return */ + @PostMapping("/tempRefundAmount") public RestApiResponse tempRefundAmount(@RequestBody ApplyRefundDTO dto) { RestApiResponse response = null; try { From 0d1c635a55a52d771f1a25e54141b42545a06933 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Wed, 13 May 2026 11:01:45 +0800 Subject: [PATCH 05/15] add readme.md --- README.md | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..d2d49cb74 --- /dev/null +++ b/README.md @@ -0,0 +1,254 @@ +# jsowell-charger-web + +万车充运营管理平台,包含一套 Java 8 + Spring Boot 的多模块后端,以及一个 Vue 2 的管理端前端工程。 + +项目主要面向充电桩运营场景,覆盖设备管理、订单支付、会员能力、第三方平台对接、设备通信与定时任务等业务。 + +## 项目组成 + +- 后端:Maven 多模块工程,根目录 `pom.xml` +- 前端:`jsowell-charge-ui/`,基于 Vue 2 + Vue CLI +- 基础设施示例:`docker/docker-compose.yml`,包含 `RabbitMQ` 与 `Nacos` +- 补充资料:`docs/` + +## 技术栈 + +### 后端 + +- Java 8 +- Spring Boot 2.5.14 +- MyBatis / PageHelper +- Dubbo 3.3.0 +- Nacos 2.0.3 +- Netty 4.1.75.Final +- Redis / RabbitMQ / MySQL + +### 前端 + +- Vue 2.6 +- Vue CLI 4 +- Element UI +- Axios + +## 目录结构 + +```text +. +├── jsowell-admin/ 后端启动模块,Web 服务入口 +├── jsowell-framework/ 核心框架层 +├── jsowell-system/ 系统管理模块 +├── jsowell-common/ 通用工具与基础能力 +├── jsowell-pile/ 充电桩核心业务 +├── jsowell-netty/ 设备通信与协议处理 +├── jsowell-thirdparty/ 第三方平台与支付对接 +├── jsowell-quartz/ 定时任务 +├── jsowell-generator/ 代码生成器 +├── jsowell-settlement/ 结算相关模块 +├── jsowell-charge-ui/ Vue 2 管理端 +├── docker/ Docker Compose 与部署说明 +└── docs/ 业务文档、接口文档、SQL 与方案记录 +``` + +## 模块说明 + +| 模块 | 说明 | +| --- | --- | +| `jsowell-admin` | Spring Boot 启动入口,包含控制器、配置、测试等 | +| `jsowell-framework` | 登录鉴权、拦截器、基础框架能力 | +| `jsowell-system` | 用户、角色、菜单、字典等系统管理能力 | +| `jsowell-common` | 常量、工具类、注解、异常定义 | +| `jsowell-pile` | 订单、会员、计费、支付、运营核心业务 | +| `jsowell-netty` | 充电桩 TCP/MQTT 通信与协议适配 | +| `jsowell-thirdparty` | 微信、支付宝、地图、外部平台接口 | +| `jsowell-quartz` | 定时任务调度 | +| `jsowell-generator` | 代码生成模板与生成器 | +| `jsowell-settlement` | 分账、清算与结算扩展能力 | + +## 环境要求 + +### 本地开发建议 + +- JDK 1.8 +- Maven 3.6+ +- Node.js 14.x 或兼容的 LTS 版本 +- npm 6+ +- MySQL 5.7+ +- Redis +- RabbitMQ +- Nacos + +## 快速开始 + +### 1. 克隆并编译后端 + +```bash +git clone +cd jsowell-charger-web +mvn clean compile +``` + +### 2. 启动基础依赖 + +仓库中提供了 `RabbitMQ` 和 `Nacos` 的容器示例: + +```bash +cd docker +docker-compose up -d +``` + +更多说明见 [docker/说明.md](/Users/think/Developer/Workspace/jsowell-charger-web/docker/%E8%AF%B4%E6%98%8E.md)。 + +说明: + +- `docker-compose.yml` 当前只包含 `RabbitMQ` 和 `Nacos` +- `MySQL`、`Redis` 需要自行准备 +- 首次启动前请确认宿主机挂载目录和权限配置正确 + +### 3. 配置后端环境 + +后端配置文件位于: + +- [jsowell-admin/src/main/resources/application.yml](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-admin/src/main/resources/application.yml) +- [jsowell-admin/src/main/resources/application-dev.yml](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-admin/src/main/resources/application-dev.yml) +- [jsowell-admin/src/main/resources/application-sit.yml](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-admin/src/main/resources/application-sit.yml) +- [jsowell-admin/src/main/resources/application-pre.yml](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-admin/src/main/resources/application-pre.yml) +- [jsowell-admin/src/main/resources/application-prd.yml](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-admin/src/main/resources/application-prd.yml) + +重点检查这些配置: + +- `spring.datasource` +- `spring.redis` +- `spring.rabbitmq` +- `dubbo.registry` +- `mybatis` + +默认服务端口: + +- 应用:`8080` +- Prometheus / Actuator:`8091` + +建议: + +- 本地开发优先使用 `dev` 环境 +- 不要把本地密钥、生产连接串或私有凭证提交回仓库 + +### 4. 启动后端 + +在仓库根目录执行: + +```bash +mvn -pl jsowell-admin spring-boot:run -Dspring-boot.run.profiles=dev +``` + +或先打包再运行: + +```bash +mvn clean package -DskipTests +java -jar jsowell-admin/target/jsowell-admin.jar --spring.profiles.active=dev +``` + +后端启动类: + +- [JsowellApplication.java](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-admin/src/main/java/com/jsowell/JsowellApplication.java) + +### 5. 启动前端 + +```bash +cd jsowell-charge-ui +npm install +npm run dev +``` + +前端默认开发端口为 `8081`,并通过代理转发到本地后端 `http://localhost:8080`。 + +前端常用环境文件: + +- [jsowell-charge-ui/.env.exploitation](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-charge-ui/.env.exploitation) +- [jsowell-charge-ui/.env.development](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-charge-ui/.env.development) +- [jsowell-charge-ui/.env.staging](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-charge-ui/.env.staging) +- [jsowell-charge-ui/.env.production](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-charge-ui/.env.production) + +## 常用命令 + +### 后端 + +```bash +# 编译全部模块 +mvn clean compile + +# 打包 +mvn clean package -DskipTests + +# 运行全部测试 +mvn test + +# 仅运行 admin 模块测试 +mvn -pl jsowell-admin test +``` + +### 前端 + +```bash +# 本地开发 +npm run dev + +# 预发构建 +npm run build:pre + +# 测试环境构建 +npm run build:sit + +# 生产构建 +npm run build:prd + +# 代码检查 +npm run lint +``` + +## 开发约定 + +### 后端 + +- 使用 4 空格缩进 +- 类名使用 `PascalCase` +- 方法与变量使用 `camelCase` +- 常量使用 `UPPER_SNAKE_CASE` +- 尽量将改动收敛在单个业务模块内 + +### 前端 + +- 遵循 `jsowell-charge-ui/.editorconfig` +- ESLint 配置见 `jsowell-charge-ui/.eslintrc.js` +- 默认风格为 2 空格、单引号、无分号 + +## 测试 + +后端测试主要位于: + +- [jsowell-admin/src/test/java](/Users/think/Developer/Workspace/jsowell-charger-web/jsowell-admin/src/test/java) + +建议在提交前至少执行: + +```bash +mvn -pl jsowell-admin test +cd jsowell-charge-ui && npm run lint +``` + +## 文档与资料 + +- [CLAUDE.md](/Users/think/Developer/Workspace/jsowell-charger-web/CLAUDE.md):仓库架构补充说明 +- [docs/](/Users/think/Developer/Workspace/jsowell-charger-web/docs/):接口文档、需求说明、实现记录 +- [docs/sql/](/Users/think/Developer/Workspace/jsowell-charger-web/docs/sql/):SQL 脚本 +- [docker/](/Users/think/Developer/Workspace/jsowell-charger-web/docker/):容器与部署辅助文件 + +## 提交建议 + +- 提交信息建议使用简短前缀,如:`add ...`、`update ...`、`bugfix ...` +- 涉及数据库或接口变更时,请补充变更说明与示例 +- 前端页面改动建议附带截图 + +## 注意事项 + +- 当前仓库包含多环境配置文件,启动前请先确认本地依赖地址是否可用 +- 第三方平台、支付、短信等配置较多,建议优先使用本地或测试环境参数联调 +- 若仅进行管理端页面开发,通常只需保证本地后端接口和前端代理可用 From 0c68b7e03330c71d5906a057df6b8a1426f4a183 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Wed, 13 May 2026 17:18:28 +0800 Subject: [PATCH 06/15] =?UTF-8?q?=E5=85=AC=E5=85=B1=E7=99=BB=E9=99=86?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E6=96=B9=E6=B3=95V2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jsowell/service/MemberService.java | 247 ++++++++++++++++ .../java/com/jsowell/service/TempService.java | 21 ++ .../MemberServiceRegisterAndLoginTest.java | 275 ++++++++++++++++++ .../pile/dto/ImportMemberBalanceDTO.java | 14 + 4 files changed, 557 insertions(+) create mode 100644 jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java diff --git a/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java b/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java index 98a49dec1..0f7fa3453 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java @@ -277,6 +277,253 @@ public class MemberService { } } + /** + * 公共登录注册方法 V2 + * + *

该方法与 {@link #memberRegisterAndLogin(MemberRegisterAndLoginDTO)} 保持相同业务逻辑, + * 但将流程拆成更清晰的几个步骤:校验请求、获取锁、查找或创建会员、同步第三方身份、生成 token。

+ * + * @param dto 登录/注册入参 + * @return 服务端生成的会员 token + */ + protected String memberRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) { + String phoneNumber = dto.getMobileNumber(); + String firstLevelMerchantId = dto.getFirstLevelMerchantId(); + log.info("公共登录注册方法V2, phoneNumber:{}, firstLevelMerchantId:{}, openId:{}", phoneNumber, firstLevelMerchantId, dto.getOpenId()); + + // 1. 先做参数校验,尽早拦截无效请求。 + validateMemberRegisterAndLoginV2Request(dto); + + // 2. 使用“手机号 + 一级运营商”作为锁粒度,串行化同一会员的注册/登录流程。 + String lockKey = buildMemberRegisterAndLoginLockKey(phoneNumber, firstLevelMerchantId); + String requestId = IdUtils.fastUUID(); + Boolean isLock = false; + try { + // 3. 获取锁,防止并发下重复注册或重复创建钱包。 + isLock = acquireMemberRegisterAndLoginLock(lockKey, requestId, phoneNumber, firstLevelMerchantId); + + // 4. 会员不存在就静默注册,已存在则按请求来源同步 openId / buyerId。 + MemberBasicInfo memberBasicInfo = findOrCreateMemberForRegisterAndLoginV2(dto); + + // 5. 最终统一生成会员 token 返回前端。 + return JWTUtils.createMemberToken(memberBasicInfo.getMemberId(), memberBasicInfo.getNickName()); + } finally { + releaseMemberRegisterAndLoginLock(lockKey, requestId, phoneNumber, firstLevelMerchantId, isLock); + } + } + + /** + * 校验 V2 登录/注册请求中的关键参数。 + * + * @param dto 登录/注册入参 + */ + private void validateMemberRegisterAndLoginV2Request(MemberRegisterAndLoginDTO dto) { + if (StringUtils.isBlank(dto.getMobileNumber())) { + throw new BusinessException(ReturnCodeEnum.CODE_GET_MOBILE_NUMBER_BY_CODE_ERROR); + } + if (StringUtils.isBlank(dto.getFirstLevelMerchantId())) { + throw new BusinessException(ReturnCodeEnum.CODE_GET_MERCHANT_ID_BY_APP_ID_ERROR); + } + if (isWechatLiteRequest(dto) && StringUtils.isBlank(dto.getOpenId())) { + throw new BusinessException(ReturnCodeEnum.CODE_OPEN_ID_IS_NULL_ERROR); + } + } + + /** + * 生成登录/注册流程使用的分布式锁 key。 + * + * @param phoneNumber 手机号 + * @param firstLevelMerchantId 一级运营商 ID + * @return 分布式锁 key + */ + private String buildMemberRegisterAndLoginLockKey(String phoneNumber, String firstLevelMerchantId) { + return CacheConstants.USER_APP_REGISTER + phoneNumber + ":" + firstLevelMerchantId; + } + + /** + * 获取登录/注册流程的分布式锁,避免同一会员并发注册。 + * + * @param lockKey 锁 key + * @param requestId 当前请求唯一标识 + * @param phoneNumber 手机号 + * @param firstLevelMerchantId 一级运营商 ID + * @return 是否成功获取锁 + */ + private boolean acquireMemberRegisterAndLoginLock(String lockKey, String requestId, String phoneNumber, String firstLevelMerchantId) { + boolean isLock = redisCache.lock(lockKey, requestId, 60); + if (!isLock) { + log.warn("获取注册锁失败,可能有并发请求正在处理, phoneNumber:{}, merchantId:{}", phoneNumber, firstLevelMerchantId); + throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING); + } + return true; + } + + /** + * 释放登录/注册流程的分布式锁。 + * + * @param lockKey 锁 key + * @param requestId 当前请求唯一标识 + * @param phoneNumber 手机号 + * @param firstLevelMerchantId 一级运营商 ID + * @param isLock 是否持有锁 + */ + private void releaseMemberRegisterAndLoginLock(String lockKey, String requestId, String phoneNumber, String firstLevelMerchantId, Boolean isLock) { + if (!Boolean.TRUE.equals(isLock)) { + return; + } + try { + Object lockValue = redisCache.getCacheObject(lockKey); + if (lockValue != null && requestId.equals(lockValue.toString())) { + redisCache.unLock(lockKey); + } + } catch (Exception e) { + log.error("释放注册锁失败, phoneNumber:{}, merchantId:{}, error:{}", phoneNumber, firstLevelMerchantId, e.getMessage()); + } + } + + /** + * 根据手机号和运营商查询会员,不存在则静默注册,存在则同步第三方身份信息。 + * + * @param dto 登录/注册入参 + * @return 已存在或新创建的会员信息 + */ + private MemberBasicInfo findOrCreateMemberForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) { + MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(dto.getMobileNumber(), dto.getFirstLevelMerchantId()); + if (Objects.isNull(memberBasicInfo)) { + return registerMemberForRegisterAndLoginV2(dto); + } + syncMemberThirdPartyIdentityForRegisterAndLoginV2(memberBasicInfo, dto); + return memberBasicInfo; + } + + /** + * 执行静默注册流程,并在唯一索引冲突时回查已创建的会员。 + * + * @param dto 登录/注册入参 + * @return 注册完成后的会员信息 + */ + private MemberBasicInfo registerMemberForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) { + MemberBasicInfo memberBasicInfo = buildNewMemberForRegisterAndLoginV2(dto); + MemberTransactionDTO memberTransactionDTO = buildMemberTransactionForRegisterAndLoginV2(memberBasicInfo, dto.getFirstLevelMerchantId()); + try { + transactionService.createMember(memberTransactionDTO); + return memberBasicInfo; + } catch (DuplicateKeyException e) { + return reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(dto); + } + } + + /** + * 构造一个新的会员基础信息对象。 + * + * @param dto 登录/注册入参 + * @return 待入库的会员基础信息 + */ + private MemberBasicInfo buildNewMemberForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) { + String memberId = generateNewMemberId(); + MemberBasicInfo memberBasicInfo = new MemberBasicInfo(); + memberBasicInfo.setStatus(Constants.ONE); + memberBasicInfo.setMemberId(memberId); + memberBasicInfo.setNickName("会员" + memberId); + memberBasicInfo.setMobileNumber(dto.getMobileNumber()); + memberBasicInfo.setMerchantId(Long.valueOf(dto.getFirstLevelMerchantId())); + fillMemberThirdPartyIdentityForRegisterAndLoginV2(memberBasicInfo, dto); + return memberBasicInfo; + } + + /** + * 组装会员注册事务对象,必要时附带创建钱包信息。 + * + * @param memberBasicInfo 会员基础信息 + * @param firstLevelMerchantId 一级运营商 ID + * @return 注册事务对象 + */ + private MemberTransactionDTO buildMemberTransactionForRegisterAndLoginV2(MemberBasicInfo memberBasicInfo, String firstLevelMerchantId) { + MemberTransactionDTO memberTransactionDTO = new MemberTransactionDTO(); + memberTransactionDTO.setMemberBasicInfo(memberBasicInfo); + if (MerchantUtils.isXiXiaoMerchant(firstLevelMerchantId)) { + MemberWalletInfo memberWalletInfo = MemberWalletInfo.builder() + .memberId(memberBasicInfo.getMemberId()) + .merchantId(MerchantUtils.XIXIAO_MERCHANT_ID) + .walletCode(memberBasicInfoService.generateWalletCode()) + .build(); + memberTransactionDTO.setMemberWalletInfo(memberWalletInfo); + } + return memberTransactionDTO; + } + + /** + * 在注册发生唯一索引冲突时,回查已经被并发请求创建好的会员。 + * + * @param dto 登录/注册入参 + * @return 已存在的会员信息 + */ + private MemberBasicInfo reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) { + log.warn("会员注册时检测到唯一索引冲突,重新查询已存在的会员, phoneNumber:{}, merchantId:{}", dto.getMobileNumber(), dto.getFirstLevelMerchantId()); + MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(dto.getMobileNumber(), dto.getFirstLevelMerchantId()); + if (memberBasicInfo == null) { + log.error("唯一索引冲突后重新查询会员信息为空, phoneNumber:{}, merchantId:{}", dto.getMobileNumber(), dto.getFirstLevelMerchantId()); + throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR); + } + return memberBasicInfo; + } + + /** + * 同步已有会员的第三方身份标识。 + * + * @param memberBasicInfo 已存在会员 + * @param dto 登录/注册入参 + */ + private void syncMemberThirdPartyIdentityForRegisterAndLoginV2(MemberBasicInfo memberBasicInfo, MemberRegisterAndLoginDTO dto) { + boolean needUpdate = false; + if (isWechatLiteRequest(dto) && !StringUtils.equals(memberBasicInfo.getOpenId(), dto.getOpenId())) { + memberBasicInfo.setOpenId(dto.getOpenId()); + needUpdate = true; + } + if (isAlipayLiteRequest(dto) && !StringUtils.equals(memberBasicInfo.getBuyerId(), dto.getBuyerId())) { + memberBasicInfo.setBuyerId(dto.getBuyerId()); + needUpdate = true; + } + if (needUpdate) { + memberBasicInfoService.updateMemberBasicInfo(memberBasicInfo); + } + } + + /** + * 在新建会员时,根据请求来源写入第三方身份标识。 + * + * @param memberBasicInfo 待入库会员 + * @param dto 登录/注册入参 + */ + private void fillMemberThirdPartyIdentityForRegisterAndLoginV2(MemberBasicInfo memberBasicInfo, MemberRegisterAndLoginDTO dto) { + if (isWechatLiteRequest(dto) && StringUtils.isNotBlank(dto.getOpenId())) { + memberBasicInfo.setOpenId(dto.getOpenId()); + } + if (isAlipayLiteRequest(dto) && StringUtils.isNotBlank(dto.getBuyerId())) { + memberBasicInfo.setBuyerId(dto.getBuyerId()); + } + } + + /** + * 判断当前请求是否来自微信小程序。 + * + * @param dto 登录/注册入参 + * @return 是否为微信小程序请求 + */ + private boolean isWechatLiteRequest(MemberRegisterAndLoginDTO dto) { + return AdapayPayChannelEnum.WX_LITE.getValue().equals(dto.getRequestSource()); + } + + /** + * 判断当前请求是否来自支付宝小程序。 + * + * @param dto 登录/注册入参 + * @return 是否为支付宝小程序请求 + */ + private boolean isAlipayLiteRequest(MemberRegisterAndLoginDTO dto) { + return AdapayPayChannelEnum.ALIPAY_LITE.getValue().equals(dto.getRequestSource()); + } + private String generateNewMemberId() { while (true) { String memberId = IdUtils.getMemberId(); diff --git a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java index fd923ec6f..c70507198 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java @@ -1553,5 +1553,26 @@ public class TempService { } } + + /** + * 从文件中导入会员余额 + */ + public void batchImportMemberBalance(List list) { + if (CollectionUtils.isEmpty(list)) { + return; + } + list.parallelStream().forEach(this::importMemberBalance); + } + + private void importMemberBalance(ImportMemberBalanceDTO memberBalanceDTO) { + // 1. 根据手机号查询万车充会员信息 + MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(memberBalanceDTO.getPhone(), "1"); + // 2. 如果没有万车充会员信息,则自动注册万车充会员,并创建“南通晨鸣中锦置业有限责任公司”运营商钱包 + if (memberBasicInfo == null) { + + } + + // 3. 根据balance添加余额 + } } diff --git a/jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java b/jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java new file mode 100644 index 000000000..ca297aaae --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java @@ -0,0 +1,275 @@ +package com.jsowell.service; + +import com.alipay.api.AlipayClient; +import com.jsowell.alipay.factory.AlipayClientFactory; +import com.jsowell.common.constant.CacheConstants; +import com.jsowell.common.core.redis.RedisCache; +import com.jsowell.common.enums.adapay.AdapayPayChannelEnum; +import com.jsowell.common.enums.ykc.ReturnCodeEnum; +import com.jsowell.common.exception.BusinessException; +import com.jsowell.common.util.JWTUtils; +import com.jsowell.pile.domain.MemberBasicInfo; +import com.jsowell.pile.dto.MemberRegisterAndLoginDTO; +import com.jsowell.pile.service.MemberBasicInfoService; +import com.jsowell.pile.transaction.dto.MemberTransactionDTO; +import com.jsowell.pile.transaction.service.TransactionService; +import com.jsowell.pile.util.MerchantUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.dao.DuplicateKeyException; + +import java.lang.reflect.Field; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +class MemberServiceRegisterAndLoginTest { + + private static final LoginInvoker OLD_LOGIN = MemberService::memberRegisterAndLogin; + private static final LoginInvoker V2_LOGIN = MemberService::memberRegisterAndLoginV2; + + @BeforeAll + static void initJwtConfig() { + setStaticField(JWTUtils.class, "secret", "dGVzdC1zZWNyZXQ="); + setStaticField(JWTUtils.class, "serviceExpireTime", 60); + } + + @Test + void memberRegisterAndLoginV2_shouldCreateMemberAndWallet_whenXiXiaoMerchantFirstLogin() { + RedisCache redisCache = mock(RedisCache.class); + TransactionService transactionService = mock(TransactionService.class); + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + + when(redisCache.lock(anyString(), anyString(), eq(60))).thenReturn(true); + when(memberBasicInfoService.selectInfoByMobileNumber("13800138000", MerchantUtils.XIXIAO_MERCHANT_ID)).thenReturn(null); + when(memberBasicInfoService.queryMemberInfoByMemberId(anyString())).thenReturn(null); + when(memberBasicInfoService.generateWalletCode()).thenReturn("WALLET-001"); + + MemberService service = newMemberService(redisCache, transactionService, memberBasicInfoService); + MemberRegisterAndLoginDTO dto = MemberRegisterAndLoginDTO.builder() + .mobileNumber("13800138000") + .firstLevelMerchantId(MerchantUtils.XIXIAO_MERCHANT_ID) + .openId("wx-open-id") + .requestSource(AdapayPayChannelEnum.WX_LITE.getValue()) + .build(); + + String memberToken = service.memberRegisterAndLoginV2(dto); + + assertNotNull(memberToken); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MemberTransactionDTO.class); + verify(transactionService).createMember(captor.capture()); + + MemberTransactionDTO memberTransactionDTO = captor.getValue(); + assertNotNull(memberTransactionDTO.getMemberBasicInfo()); + assertEquals("13800138000", memberTransactionDTO.getMemberBasicInfo().getMobileNumber()); + assertEquals("wx-open-id", memberTransactionDTO.getMemberBasicInfo().getOpenId()); + assertNotNull(memberTransactionDTO.getMemberWalletInfo()); + assertEquals(MerchantUtils.XIXIAO_MERCHANT_ID, memberTransactionDTO.getMemberWalletInfo().getMerchantId()); + assertEquals("WALLET-001", memberTransactionDTO.getMemberWalletInfo().getWalletCode()); + verify(memberBasicInfoService, never()).updateMemberBasicInfo(any(MemberBasicInfo.class)); + } + + @Test + void memberRegisterAndLoginV2_shouldUpdateOpenId_whenExistingWechatMemberOpenIdChanged() { + RedisCache redisCache = mock(RedisCache.class); + TransactionService transactionService = mock(TransactionService.class); + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + + when(redisCache.lock(anyString(), anyString(), eq(60))).thenReturn(true); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("M001") + .nickName("会员M001") + .mobileNumber("13800138001") + .merchantId(99L) + .openId("old-open-id") + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13800138001", "99")).thenReturn(memberBasicInfo); + + MemberService service = newMemberService(redisCache, transactionService, memberBasicInfoService); + MemberRegisterAndLoginDTO dto = MemberRegisterAndLoginDTO.builder() + .mobileNumber("13800138001") + .firstLevelMerchantId("99") + .openId("new-open-id") + .requestSource(AdapayPayChannelEnum.WX_LITE.getValue()) + .build(); + + String memberToken = service.memberRegisterAndLoginV2(dto); + + assertNotNull(memberToken); + assertEquals("new-open-id", memberBasicInfo.getOpenId()); + verify(memberBasicInfoService).updateMemberBasicInfo(memberBasicInfo); + verify(transactionService, never()).createMember(any(MemberTransactionDTO.class)); + } + + @Test + void memberRegisterAndLoginV2_shouldThrow_whenRegisterLockNotAcquired() { + RedisCache redisCache = mock(RedisCache.class); + TransactionService transactionService = mock(TransactionService.class); + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + + when(redisCache.lock(anyString(), anyString(), eq(60))).thenReturn(false); + + MemberService service = newMemberService(redisCache, transactionService, memberBasicInfoService); + MemberRegisterAndLoginDTO dto = MemberRegisterAndLoginDTO.builder() + .mobileNumber("13800138002") + .firstLevelMerchantId("88") + .openId("wx-open-id") + .requestSource(AdapayPayChannelEnum.WX_LITE.getValue()) + .build(); + + BusinessException exception = assertThrows(BusinessException.class, () -> service.memberRegisterAndLoginV2(dto)); + + assertEquals(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING.getCode(), exception.getCode()); + verify(memberBasicInfoService, never()).selectInfoByMobileNumber(anyString(), anyString()); + verify(redisCache, never()).unLock(CacheConstants.USER_APP_REGISTER + "13800138002:88"); + verify(transactionService, never()).createMember(any(MemberTransactionDTO.class)); + } + + @Test + void memberRegisterAndLoginV2_shouldMatchOriginal_whenExistingAlipayMemberBuyerIdChanged() { + exerciseExistingAlipayBuyerIdUpdate(OLD_LOGIN); + exerciseExistingAlipayBuyerIdUpdate(V2_LOGIN); + } + + @Test + void memberRegisterAndLoginV2_shouldMatchOriginal_whenDuplicateKeyHappensDuringRegister() { + exerciseDuplicateKeyFallback(OLD_LOGIN); + exerciseDuplicateKeyFallback(V2_LOGIN); + } + + @Test + void memberRegisterAndLoginV2_shouldMatchOriginal_whenLockNotAcquired() { + exerciseLockFailure(OLD_LOGIN); + exerciseLockFailure(V2_LOGIN); + } + + private static void exerciseExistingAlipayBuyerIdUpdate(LoginInvoker invoker) { + RedisCache redisCache = mock(RedisCache.class); + TransactionService transactionService = mock(TransactionService.class); + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + + when(redisCache.lock(anyString(), anyString(), eq(60))).thenReturn(true); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("A001") + .nickName("会员A001") + .mobileNumber("13900139000") + .merchantId(66L) + .buyerId("old-buyer-id") + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13900139000", "66")).thenReturn(memberBasicInfo); + + MemberService service = newMemberService(redisCache, transactionService, memberBasicInfoService); + MemberRegisterAndLoginDTO dto = MemberRegisterAndLoginDTO.builder() + .mobileNumber("13900139000") + .firstLevelMerchantId("66") + .buyerId("new-buyer-id") + .requestSource(AdapayPayChannelEnum.ALIPAY_LITE.getValue()) + .build(); + + String memberToken = invoker.invoke(service, dto); + + assertNotNull(memberToken); + assertEquals("new-buyer-id", memberBasicInfo.getBuyerId()); + verify(memberBasicInfoService).updateMemberBasicInfo(memberBasicInfo); + verify(transactionService, never()).createMember(any(MemberTransactionDTO.class)); + } + + private static void exerciseDuplicateKeyFallback(LoginInvoker invoker) { + RedisCache redisCache = mock(RedisCache.class); + TransactionService transactionService = mock(TransactionService.class); + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + + when(redisCache.lock(anyString(), anyString(), eq(60))).thenReturn(true); + + MemberBasicInfo existingMember = MemberBasicInfo.builder() + .memberId("D001") + .nickName("会员D001") + .mobileNumber("13700137000") + .merchantId(77L) + .openId("wx-dup-open-id") + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13700137000", "77")).thenReturn(null, existingMember); + when(memberBasicInfoService.queryMemberInfoByMemberId(anyString())).thenReturn(null); + doThrow(new DuplicateKeyException("duplicate key")).when(transactionService).createMember(any(MemberTransactionDTO.class)); + + MemberService service = newMemberService(redisCache, transactionService, memberBasicInfoService); + MemberRegisterAndLoginDTO dto = MemberRegisterAndLoginDTO.builder() + .mobileNumber("13700137000") + .firstLevelMerchantId("77") + .openId("wx-dup-open-id") + .requestSource(AdapayPayChannelEnum.WX_LITE.getValue()) + .build(); + + String memberToken = invoker.invoke(service, dto); + + assertNotNull(memberToken); + verify(transactionService).createMember(any(MemberTransactionDTO.class)); + verify(memberBasicInfoService, times(2)).selectInfoByMobileNumber("13700137000", "77"); + verify(memberBasicInfoService, never()).updateMemberBasicInfo(any(MemberBasicInfo.class)); + } + + private static void exerciseLockFailure(LoginInvoker invoker) { + RedisCache redisCache = mock(RedisCache.class); + TransactionService transactionService = mock(TransactionService.class); + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + + when(redisCache.lock(anyString(), anyString(), eq(60))).thenReturn(false); + + MemberService service = newMemberService(redisCache, transactionService, memberBasicInfoService); + MemberRegisterAndLoginDTO dto = MemberRegisterAndLoginDTO.builder() + .mobileNumber("13600136000") + .firstLevelMerchantId("55") + .openId("wx-lock-open-id") + .requestSource(AdapayPayChannelEnum.WX_LITE.getValue()) + .build(); + + BusinessException exception = assertThrows(BusinessException.class, () -> invoker.invoke(service, dto)); + + assertEquals(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING.getCode(), exception.getCode()); + verify(memberBasicInfoService, never()).selectInfoByMobileNumber(anyString(), anyString()); + verify(transactionService, never()).createMember(any(MemberTransactionDTO.class)); + } + + private static MemberService newMemberService(RedisCache redisCache, + TransactionService transactionService, + MemberBasicInfoService memberBasicInfoService) { + AlipayClientFactory alipayClientFactory = mock(AlipayClientFactory.class); + when(alipayClientFactory.getAlipayClient()).thenReturn(mock(AlipayClient.class)); + + MemberService service = new MemberService(alipayClientFactory); + setField(service, "redisCache", redisCache); + setField(service, "transactionService", transactionService); + setField(service, "memberBasicInfoService", memberBasicInfoService); + return service; + } + + private static void setField(Object target, String fieldName, Object value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void setStaticField(Class targetClass, String fieldName, Object value) { + try { + Field field = targetClass.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(null, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @FunctionalInterface + private interface LoginInvoker { + String invoke(MemberService service, MemberRegisterAndLoginDTO dto); + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java new file mode 100644 index 000000000..b6089d61e --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java @@ -0,0 +1,14 @@ +package com.jsowell.pile.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ImportMemberBalanceDTO { + // 手机号 + private String phone; + + // 余额 + private BigDecimal balance; +} From 5e35a8c82dba190fb91ab275115d54e379a136e8 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Thu, 14 May 2026 08:58:16 +0800 Subject: [PATCH 07/15] =?UTF-8?q?update=20=E5=85=AC=E5=85=B1=E7=99=BB?= =?UTF-8?q?=E9=99=86=E6=B3=A8=E5=86=8C=E6=96=B9=E6=B3=95=E4=BD=BF=E7=94=A8?= =?UTF-8?q?V2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jsowell/service/AgentDevService.java | 2 +- .../java/com/jsowell/service/MemberService.java | 16 +++++++++++++--- .../MemberServiceRegisterAndLoginTest.java | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java b/jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java index 8332b6b65..f548df5ff 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/AgentDevService.java @@ -646,7 +646,7 @@ public class AgentDevService { .mobileNumber(phoneNumber) .requestSource(dto.getRequestSource()) .build(); - return memberService.memberRegisterAndLogin(loginDTO); // 其他一级运营商,微信一键登录 + return memberService.memberRegisterAndLoginV2(loginDTO); // 其他一级运营商,微信一键登录 } /** diff --git a/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java b/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java index 0f7fa3453..e133014bf 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java @@ -159,13 +159,23 @@ public class MemberService { public String memberRegisterAndLoginBySMS(MemberRegisterAndLoginDTO dto) { // 校验短信验证码 两种情况不能通过校验,1-验证码错误;2-超时 验证码10分钟有效 checkVerificationCode(dto); - return memberRegisterAndLogin(dto); // 短信验证码登录 + return memberRegisterAndLoginV2(dto); // 短信验证码登录 } /** * 公共登录注册方法 + * + *

该方法已废弃,当前会员注册/登录主流程统一走 + * {@link #memberRegisterAndLoginV2(MemberRegisterAndLoginDTO)}。

+ * + *

保留该方法的原因:

+ *

1. 便于和 V2 做逻辑对照,确认重构前后行为一致。

+ *

2. 作为短期回溯参考,降低生产切换风险。

+ *

3. 避免在一次重构中直接删除旧实现,影响排查问题时的可读性。

+ * * @return token返给前端 */ + @Deprecated protected String memberRegisterAndLogin(MemberRegisterAndLoginDTO dto) { String phoneNumber = dto.getMobileNumber(); String firstLevelMerchantId = dto.getFirstLevelMerchantId(); @@ -566,7 +576,7 @@ public class MemberService { .mobileNumber(mobileNumber) .requestSource(dto.getRequestSource()) .build(); - return memberRegisterAndLogin(loginDTO); // 微信小程序一键登录 + return memberRegisterAndLoginV2(loginDTO); // 微信小程序一键登录 } /** @@ -616,7 +626,7 @@ public class MemberService { .mobileNumber(dto.getMobileNumber()) .buyerId(dto.getUserId()) .build(); - return memberRegisterAndLogin(loginDTO); // 支付宝小程序一键登录 + return memberRegisterAndLoginV2(loginDTO); // 支付宝小程序一键登录 } /** diff --git a/jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java b/jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java index ca297aaae..ba4a2f916 100644 --- a/jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java +++ b/jsowell-admin/src/test/java/com/jsowell/service/MemberServiceRegisterAndLoginTest.java @@ -123,7 +123,7 @@ class MemberServiceRegisterAndLoginTest { BusinessException exception = assertThrows(BusinessException.class, () -> service.memberRegisterAndLoginV2(dto)); - assertEquals(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING.getCode(), exception.getCode()); + assertEquals(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING.getValue(), exception.getCode()); verify(memberBasicInfoService, never()).selectInfoByMobileNumber(anyString(), anyString()); verify(redisCache, never()).unLock(CacheConstants.USER_APP_REGISTER + "13800138002:88"); verify(transactionService, never()).createMember(any(MemberTransactionDTO.class)); @@ -230,7 +230,7 @@ class MemberServiceRegisterAndLoginTest { BusinessException exception = assertThrows(BusinessException.class, () -> invoker.invoke(service, dto)); - assertEquals(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING.getCode(), exception.getCode()); + assertEquals(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_PROCESSING.getValue(), exception.getCode()); verify(memberBasicInfoService, never()).selectInfoByMobileNumber(anyString(), anyString()); verify(transactionService, never()).createMember(any(MemberTransactionDTO.class)); } From 02c7a069cc1038dbe07b49cd04f55b6ba4f8edf1 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Thu, 14 May 2026 10:51:39 +0800 Subject: [PATCH 08/15] update --- .../java/com/jsowell/service/TempService.java | 110 +++++++++++-- .../TempServiceImportMemberBalanceTest.java | 144 ++++++++++++++++++ .../BatchImportMemberBalanceResultDTO.java | 14 ++ .../dto/ImportMemberBalanceItemResultDTO.java | 25 +++ 4 files changed, 282 insertions(+), 11 deletions(-) create mode 100644 jsowell-admin/src/test/java/com/jsowell/service/TempServiceImportMemberBalanceTest.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/dto/BatchImportMemberBalanceResultDTO.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceItemResultDTO.java diff --git a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java index c70507198..373223840 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java @@ -21,6 +21,7 @@ import com.jsowell.common.constant.Constants; import com.jsowell.common.constant.RabbitConstants; import com.jsowell.common.core.domain.ykc.TransactionRecordsData; import com.jsowell.common.core.redis.RedisCache; +import com.jsowell.common.enums.MemberWalletEnum; import com.jsowell.common.enums.adapay.AdapayStatusEnum; import com.jsowell.common.enums.adapay.MerchantDelayModeEnum; import com.jsowell.common.enums.ykc.*; @@ -65,6 +66,10 @@ import java.util.stream.Collectors; @Service public class TempService { + private static final String JSOWELL_MEMBER_MERCHANT_ID = "1"; + + private static final String NANTONG_CHENMING_WALLET_MERCHANT_ID = "575"; + private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired @@ -1557,22 +1562,105 @@ public class TempService { /** * 从文件中导入会员余额 */ - public void batchImportMemberBalance(List list) { + public BatchImportMemberBalanceResultDTO batchImportMemberBalance(List list) { + BatchImportMemberBalanceResultDTO result = new BatchImportMemberBalanceResultDTO(); if (CollectionUtils.isEmpty(list)) { - return; + return result; + } + + result.setTotalCount(list.size()); + List itemResults = list.parallelStream() + .map(this::importMemberBalanceSafely) + .collect(Collectors.toList()); + + long successCount = itemResults.stream().filter(ImportMemberBalanceItemResultDTO::isSuccess).count(); + List failedList = itemResults.stream() + .filter(item -> !item.isSuccess()) + .collect(Collectors.toList()); + + result.setSuccessCount((int) successCount); + result.setFailCount(failedList.size()); + result.setFailedList(failedList); + + logger.info("批量导入会员余额完成, totalCount:{}, successCount:{}, failCount:{}", + result.getTotalCount(), result.getSuccessCount(), result.getFailCount()); + return result; + } + + private ImportMemberBalanceItemResultDTO importMemberBalanceSafely(ImportMemberBalanceDTO memberBalanceDTO) { + String phone = memberBalanceDTO != null ? memberBalanceDTO.getPhone() : null; + try { + importMemberBalance(memberBalanceDTO); + return ImportMemberBalanceItemResultDTO.success(phone); + } catch (Exception e) { + logger.error("导入会员余额失败, phone:{}, param:{}", phone, JSON.toJSONString(memberBalanceDTO), e); + return ImportMemberBalanceItemResultDTO.fail(phone, e.getMessage()); } - list.parallelStream().forEach(this::importMemberBalance); } private void importMemberBalance(ImportMemberBalanceDTO memberBalanceDTO) { - // 1. 根据手机号查询万车充会员信息 - MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(memberBalanceDTO.getPhone(), "1"); - // 2. 如果没有万车充会员信息,则自动注册万车充会员,并创建“南通晨鸣中锦置业有限责任公司”运营商钱包 - if (memberBasicInfo == null) { - + if (memberBalanceDTO == null) { + throw new BusinessException("", "导入会员余额失败,参数不能为空"); + } + String phone = memberBalanceDTO.getPhone(); + BigDecimal balance = memberBalanceDTO.getBalance(); + if (StringUtils.isBlank(phone)) { + throw new BusinessException("", "导入会员余额失败,手机号不能为空"); + } + if (balance == null || balance.compareTo(BigDecimal.ZERO) <= 0) { + throw new BusinessException("", "导入会员余额失败,余额必须大于0"); } - // 3. 根据balance添加余额 - } -} + // 1. 根据手机号查询万车充会员信息;不存在则静默注册一个属于万车充体系的会员。 + MemberBasicInfo memberBasicInfo = findOrCreateJsowellMember(phone); + // 2. 给“南通晨鸣中锦置业有限责任公司”运营商钱包增加本金余额。 + increaseMemberWalletBalance(memberBasicInfo.getMemberId(), balance); + + logger.info("导入会员余额成功, phone:{}, memberId:{}, merchantId:{}, balance:{}", + phone, memberBasicInfo.getMemberId(), NANTONG_CHENMING_WALLET_MERCHANT_ID, balance); + } + + /** + * 根据手机号查询万车充会员;若不存在,则自动注册后重新查询。 + */ + private MemberBasicInfo findOrCreateJsowellMember(String phone) { + MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(phone, JSOWELL_MEMBER_MERCHANT_ID); + if (memberBasicInfo != null) { + return memberBasicInfo; + } + + logger.info("导入会员余额时未查询到万车充会员,开始自动注册, phone:{}, merchantId:{}", + phone, JSOWELL_MEMBER_MERCHANT_ID); + MemberRegisterAndLoginDTO loginDTO = MemberRegisterAndLoginDTO.builder() + .mobileNumber(phone) + .firstLevelMerchantId(JSOWELL_MEMBER_MERCHANT_ID) + .build(); + memberService.memberRegisterAndLoginV2(loginDTO); + + memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(phone, JSOWELL_MEMBER_MERCHANT_ID); + if (memberBasicInfo == null) { + logger.error("导入会员余额时自动注册后仍未查询到会员, phone:{}, merchantId:{}", + phone, JSOWELL_MEMBER_MERCHANT_ID); + throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR); + } + return memberBasicInfo; + } + + /** + * 给指定会员在目标运营商钱包中增加本金余额。 + * + *

如果该运营商钱包不存在,现有余额逻辑会自动创建钱包并记录流水。

+ */ + private void increaseMemberWalletBalance(String memberId, BigDecimal balance) { + UpdateMemberBalanceDTO updateMemberBalanceDTO = UpdateMemberBalanceDTO.builder() + .memberId(memberId) + .type(MemberWalletEnum.TYPE_IN.getValue()) + .subType(MemberWalletEnum.SUBTYPE_TOP_UP.getValue()) + .updatePrincipalBalance(balance) + .targetMerchantId(NANTONG_CHENMING_WALLET_MERCHANT_ID) + .build(); + memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO); + } + +} diff --git a/jsowell-admin/src/test/java/com/jsowell/service/TempServiceImportMemberBalanceTest.java b/jsowell-admin/src/test/java/com/jsowell/service/TempServiceImportMemberBalanceTest.java new file mode 100644 index 000000000..7735fe9a4 --- /dev/null +++ b/jsowell-admin/src/test/java/com/jsowell/service/TempServiceImportMemberBalanceTest.java @@ -0,0 +1,144 @@ +package com.jsowell.service; + +import com.jsowell.pile.dto.BatchImportMemberBalanceResultDTO; +import com.jsowell.common.enums.MemberWalletEnum; +import com.jsowell.pile.domain.MemberBasicInfo; +import com.jsowell.pile.dto.ImportMemberBalanceDTO; +import com.jsowell.pile.dto.ImportMemberBalanceItemResultDTO; +import com.jsowell.pile.dto.MemberRegisterAndLoginDTO; +import com.jsowell.pile.service.MemberBasicInfoService; +import com.jsowell.pile.vo.web.UpdateMemberBalanceDTO; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class TempServiceImportMemberBalanceTest { + + @Test + void importMemberBalance_shouldIncreaseWalletBalance_whenMemberAlreadyExists() throws Exception { + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + MemberService memberService = mock(MemberService.class); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("M1001") + .mobileNumber("13800000001") + .merchantId(1L) + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13800000001", "1")).thenReturn(memberBasicInfo); + + TempService tempService = newTempService(memberBasicInfoService, memberService); + ImportMemberBalanceDTO dto = new ImportMemberBalanceDTO(); + dto.setPhone("13800000001"); + dto.setBalance(new BigDecimal("88.50")); + + invokeImportMemberBalance(tempService, dto); + + verify(memberService, never()).memberRegisterAndLoginV2(any(MemberRegisterAndLoginDTO.class)); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UpdateMemberBalanceDTO.class); + verify(memberBasicInfoService).updateMemberBalance(captor.capture()); + UpdateMemberBalanceDTO updateDto = captor.getValue(); + assertEquals("M1001", updateDto.getMemberId()); + assertEquals(MemberWalletEnum.TYPE_IN.getValue(), updateDto.getType()); + assertEquals(MemberWalletEnum.SUBTYPE_TOP_UP.getValue(), updateDto.getSubType()); + assertEquals(new BigDecimal("88.50"), updateDto.getUpdatePrincipalBalance()); + assertEquals("575", updateDto.getTargetMerchantId()); + } + + @Test + void importMemberBalance_shouldRegisterMemberBeforeIncreaseBalance_whenMemberDoesNotExist() throws Exception { + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + MemberService memberService = mock(MemberService.class); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("M2002") + .mobileNumber("13800000002") + .merchantId(1L) + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13800000002", "1")).thenReturn(null, memberBasicInfo); + + TempService tempService = newTempService(memberBasicInfoService, memberService); + ImportMemberBalanceDTO dto = new ImportMemberBalanceDTO(); + dto.setPhone("13800000002"); + dto.setBalance(new BigDecimal("100")); + + invokeImportMemberBalance(tempService, dto); + + ArgumentCaptor loginCaptor = ArgumentCaptor.forClass(MemberRegisterAndLoginDTO.class); + verify(memberService).memberRegisterAndLoginV2(loginCaptor.capture()); + MemberRegisterAndLoginDTO loginDTO = loginCaptor.getValue(); + assertEquals("13800000002", loginDTO.getMobileNumber()); + assertEquals("1", loginDTO.getFirstLevelMerchantId()); + + ArgumentCaptor balanceCaptor = ArgumentCaptor.forClass(UpdateMemberBalanceDTO.class); + verify(memberBasicInfoService).updateMemberBalance(balanceCaptor.capture()); + assertEquals("M2002", balanceCaptor.getValue().getMemberId()); + assertEquals("575", balanceCaptor.getValue().getTargetMerchantId()); + assertEquals(new BigDecimal("100"), balanceCaptor.getValue().getUpdatePrincipalBalance()); + } + + @Test + void batchImportMemberBalance_shouldReturnSummary_whenMixedRecords() { + MemberBasicInfoService memberBasicInfoService = mock(MemberBasicInfoService.class); + MemberService memberService = mock(MemberService.class); + + MemberBasicInfo memberBasicInfo = MemberBasicInfo.builder() + .memberId("M3003") + .mobileNumber("13800000003") + .merchantId(1L) + .build(); + when(memberBasicInfoService.selectInfoByMobileNumber("13800000003", "1")).thenReturn(memberBasicInfo); + + TempService tempService = newTempService(memberBasicInfoService, memberService); + + ImportMemberBalanceDTO successDto = new ImportMemberBalanceDTO(); + successDto.setPhone("13800000003"); + successDto.setBalance(new BigDecimal("50")); + + ImportMemberBalanceDTO failDto = new ImportMemberBalanceDTO(); + failDto.setPhone(""); + failDto.setBalance(new BigDecimal("20")); + + BatchImportMemberBalanceResultDTO result = tempService.batchImportMemberBalance(Arrays.asList(successDto, failDto)); + + assertNotNull(result); + assertEquals(2, result.getTotalCount()); + assertEquals(1, result.getSuccessCount()); + assertEquals(1, result.getFailCount()); + assertEquals(1, result.getFailedList().size()); + ImportMemberBalanceItemResultDTO failedItem = result.getFailedList().get(0); + assertEquals("", failedItem.getPhone()); + } + + private static TempService newTempService(MemberBasicInfoService memberBasicInfoService, MemberService memberService) { + TempService tempService = new TempService(); + setField(tempService, "memberBasicInfoService", memberBasicInfoService); + setField(tempService, "memberService", memberService); + return tempService; + } + + private static void invokeImportMemberBalance(TempService tempService, ImportMemberBalanceDTO dto) throws Exception { + Method method = TempService.class.getDeclaredMethod("importMemberBalance", ImportMemberBalanceDTO.class); + method.setAccessible(true); + method.invoke(tempService, dto); + } + + private static void setField(Object target, String fieldName, Object value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatchImportMemberBalanceResultDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatchImportMemberBalanceResultDTO.java new file mode 100644 index 000000000..df7091a9c --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/BatchImportMemberBalanceResultDTO.java @@ -0,0 +1,14 @@ +package com.jsowell.pile.dto; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class BatchImportMemberBalanceResultDTO { + private int totalCount; + private int successCount; + private int failCount; + private List failedList = new ArrayList<>(); +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceItemResultDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceItemResultDTO.java new file mode 100644 index 000000000..4f887f7cf --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceItemResultDTO.java @@ -0,0 +1,25 @@ +package com.jsowell.pile.dto; + +import lombok.Data; + +@Data +public class ImportMemberBalanceItemResultDTO { + private boolean success; + private String phone; + private String errorMessage; + + public static ImportMemberBalanceItemResultDTO success(String phone) { + ImportMemberBalanceItemResultDTO result = new ImportMemberBalanceItemResultDTO(); + result.setSuccess(true); + result.setPhone(phone); + return result; + } + + public static ImportMemberBalanceItemResultDTO fail(String phone, String errorMessage) { + ImportMemberBalanceItemResultDTO result = new ImportMemberBalanceItemResultDTO(); + result.setSuccess(false); + result.setPhone(phone); + result.setErrorMessage(errorMessage); + return result; + } +} From 7e0fb2e3285b1697ca6c95651a0ffd3ae51ed9d0 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Thu, 14 May 2026 13:35:49 +0800 Subject: [PATCH 09/15] =?UTF-8?q?update=20=E6=B7=BB=E5=8A=A0=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=AF=BC=E5=85=A5=E4=BC=9A=E5=91=98=E4=BD=99=E9=A2=9D?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/uniapp/customer/TempController.java | 16 +++++++ .../test/java/SpringBootTestController.java | 46 +++---------------- .../pile/dto/ImportMemberBalanceDTO.java | 6 +++ 3 files changed, 29 insertions(+), 39 deletions(-) diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java index 8ca840e3c..6119852a7 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java @@ -1143,4 +1143,20 @@ public class TempController extends BaseController { } return response; } + + /** + * 批量导入会员余额 + */ + @PostMapping("/batchImportMemberBalance") + public RestApiResponse batchImportMemberBalance(@RequestBody List list) { + RestApiResponse response = null; + try { + tempService.batchImportMemberBalance(list); + response = new RestApiResponse<>(); + } catch (Exception e) { + logger.error("批量导入会员余额 error", e); + response = new RestApiResponse<>(e); + } + return response; + } } diff --git a/jsowell-admin/src/test/java/SpringBootTestController.java b/jsowell-admin/src/test/java/SpringBootTestController.java index 0e68fe16f..e72081dce 100644 --- a/jsowell-admin/src/test/java/SpringBootTestController.java +++ b/jsowell-admin/src/test/java/SpringBootTestController.java @@ -51,17 +51,12 @@ import com.jsowell.common.util.id.SnowflakeIdWorker; import com.jsowell.common.util.id.UUID; import com.jsowell.common.util.ip.AddressUtils; import com.jsowell.framework.async.JsowellThreadFactory; -import com.jsowell.netty.handler.yunkuaichong.HeartbeatRequestHandler; -import com.jsowell.netty.handler.yunkuaichong.TransactionRecordsRequestHandler; -import com.jsowell.netty.service.camera.impl.CameraBusinessServiceImpl; -import com.jsowell.netty.service.yunkuaichong.YKCBusinessService; import com.jsowell.pile.domain.*; import com.jsowell.pile.domain.ykcCommond.IssueQRCodeCommand; import com.jsowell.pile.domain.ykcCommond.ProofreadTimeCommand; import com.jsowell.pile.dto.*; import com.jsowell.pile.dto.amap.GetStationInfoDTO; import com.jsowell.pile.dto.lutongyunting.BindCouponDTO; -import com.jsowell.pile.mapper.MemberBasicInfoMapper; import com.jsowell.pile.mapper.PileBillingTemplateMapper; import com.jsowell.pile.service.*; import com.jsowell.pile.service.batteryreport.BatteryChargeReportService; @@ -75,7 +70,6 @@ import com.jsowell.pile.vo.base.MemberWalletVO; import com.jsowell.pile.vo.base.PileInfoVO; import com.jsowell.pile.vo.uniapp.customer.*; import com.jsowell.pile.vo.web.*; -import com.jsowell.service.MemberService; import com.jsowell.service.OrderService; import com.jsowell.service.PileService; import com.jsowell.service.TempService; @@ -146,51 +140,27 @@ public class SpringBootTestController { @Autowired private AdapayMemberAccountService adapayMemberAccountService; - @Autowired - private PileMsgRecordService pileMsgRecordService; - @Autowired private StationSplitConfigService stationSplitConfigService; @Autowired private PileStationInfoService pileStationInfoService; - @Autowired - private YKCPushCommandService ykcPushBusinessService; - - @Autowired - private HeartbeatRequestHandler heartbeatRequestHandler; - - @Autowired - private YKCBusinessService ykcBusinessService; - @Autowired private PileBillingTemplateMapper pileBillingTemplateMapper; - @Autowired - private PileRemoteService pileRemoteService; - - @Autowired - private MemberService memberService; - @Autowired private OrderService orderService; @Autowired private PileBillingTemplateService pileBillingTemplateService; - @Autowired - private MemberBasicInfoMapper memberBasicInfoMapper; - @Autowired private SimCardService simCardService; @Autowired private PileBasicInfoService pileBasicInfoService; - @Autowired - private WechatPayService wechatPayService; - @Autowired private OrderBasicInfoService orderBasicInfoService; @@ -231,9 +201,6 @@ public class SpringBootTestController { @Autowired private PileConnectorInfoService pileConnectorInfoService; - @Autowired - private TransactionRecordsRequestHandler transactionRecordsRequestHandler; - @Autowired private MemberBasicInfoService memberBasicInfoService; @@ -243,9 +210,6 @@ public class SpringBootTestController { @Autowired private OrderPileOccupyService orderPileOccupyService; - @Autowired - private CameraBusinessServiceImpl cameraBusinessServiceImpl; - @Autowired private LTYTService ltytService; @@ -267,9 +231,6 @@ public class SpringBootTestController { @Autowired private MemberWalletInfoService memberWalletInfoService; - @Autowired - private MemberGroupService memberGroupService; - @Autowired private HuaWeiService huaWeiService; @@ -313,6 +274,13 @@ public class SpringBootTestController { private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10, JsowellThreadFactory.forName("test-thread-factory")); + @Test + public void importTest() { + List list = Lists.newArrayList(); + list.add(new ImportMemberBalanceDTO("18512341235", new BigDecimal("100"))); + tempService.batchImportMemberBalance(list); + } + @Test public void testRefundAmount() { ApplyRefundDTO dto = new ApplyRefundDTO(); diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java index b6089d61e..af5361165 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java @@ -1,10 +1,16 @@ package com.jsowell.pile.dto; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.math.BigDecimal; @Data +@AllArgsConstructor +@NoArgsConstructor +@Builder public class ImportMemberBalanceDTO { // 手机号 private String phone; From 382f60ed0d42d7c03a8178746c43ceff19e2da08 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Thu, 14 May 2026 14:09:35 +0800 Subject: [PATCH 10/15] update --- .../api/uniapp/customer/TempController.java | 27 ++++++++++++++----- .../pile/dto/ImportMemberBalanceDTO.java | 3 +++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java index 6119852a7..82ac18138 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/customer/TempController.java @@ -35,7 +35,9 @@ import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.core.factory.SmsFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -1147,15 +1149,28 @@ public class TempController extends BaseController { /** * 批量导入会员余额 */ - @PostMapping("/batchImportMemberBalance") - public RestApiResponse batchImportMemberBalance(@RequestBody List list) { - RestApiResponse response = null; + @PostMapping(value = "/batchImportMemberBalance", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public RestApiResponse batchImportMemberBalance(@RequestParam("file") MultipartFile file) { + RestApiResponse response; try { - tempService.batchImportMemberBalance(list); - response = new RestApiResponse<>(); + if (file == null || file.isEmpty()) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + + ExcelUtil util = new ExcelUtil<>(ImportMemberBalanceDTO.class); + List list = util.importExcel(file.getInputStream()); + if (CollectionUtils.isEmpty(list)) { + throw new BusinessException("00300001", "Excel中未解析到有效数据"); + } + + logger.info("批量导入会员余额, fileName:{}, totalCount:{}", file.getOriginalFilename(), list.size()); + response = new RestApiResponse<>(tempService.batchImportMemberBalance(list)); + } catch (BusinessException e) { + logger.warn("批量导入会员余额 warn", e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); } catch (Exception e) { logger.error("批量导入会员余额 error", e); - response = new RestApiResponse<>(e); + response = new RestApiResponse<>("00300002", "批量导入会员余额异常"); } return response; } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java index af5361165..27a1c3197 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/ImportMemberBalanceDTO.java @@ -1,5 +1,6 @@ package com.jsowell.pile.dto; +import com.jsowell.common.annotation.Excel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -13,8 +14,10 @@ import java.math.BigDecimal; @Builder public class ImportMemberBalanceDTO { // 手机号 + @Excel(name = "phone") private String phone; // 余额 + @Excel(name = "balance/100") private BigDecimal balance; } From 61603f56d4ddb9f83a546c702a4948bc3010d5ff Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Thu, 14 May 2026 14:36:37 +0800 Subject: [PATCH 11/15] update --- .../main/resources/mapper/pile/MemberWalletInfoMapper.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml b/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml index 8f5cc314b..ed393f696 100644 --- a/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml +++ b/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml @@ -521,7 +521,7 @@ from member_wallet_info where del_flag = '0' - and member_id = #{memberId,jdbcType=INTEGER} + and member_id = #{memberId,jdbcType=VARCHAR} and merchant_id = #{merchantId,jdbcType=VARCHAR} @@ -533,7 +533,7 @@ from member_wallet_info where del_flag = '0' - and member_id = #{memberId,jdbcType=INTEGER} + and member_id = #{memberId,jdbcType=VARCHAR} and merchant_id = #{merchantId,jdbcType=VARCHAR} @@ -544,7 +544,7 @@ from member_wallet_info where del_flag = '0' - and member_id = #{memberId,jdbcType=INTEGER} + and member_id = #{memberId,jdbcType=VARCHAR} and merchant_id is not null From fa641a85631e9a3de80fb9dac43dc564edca18e7 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Thu, 14 May 2026 17:12:04 +0800 Subject: [PATCH 12/15] update --- .../com/jsowell/service/MemberService.java | 43 ++++++++---- .../java/com/jsowell/service/TempService.java | 9 ++- .../com/jsowell/common/util/id/IdUtils.java | 15 ++-- .../jsowell/pile/domain/MemberWalletInfo.java | 4 +- .../pile/mapper/MemberBasicInfoMapper.java | 8 +++ .../pile/mapper/MemberWalletInfoMapper.java | 4 +- .../pile/service/MemberBasicInfoService.java | 8 +++ .../pile/service/MemberWalletInfoService.java | 4 +- .../impl/MemberBasicInfoServiceImpl.java | 22 +++++- .../impl/MemberWalletInfoServiceImpl.java | 4 +- .../mapper/pile/MemberBasicInfoMapper.xml | 8 +++ .../mapper/pile/MemberWalletInfoMapper.xml | 70 +++++++++---------- 12 files changed, 129 insertions(+), 70 deletions(-) diff --git a/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java b/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java index e133014bf..b8f64a187 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/MemberService.java @@ -70,6 +70,10 @@ import java.util.stream.Collectors; @Service public class MemberService { + private static final int MAX_MEMBER_ID_GENERATE_RETRY_TIMES = 20; + + private static final int MAX_MEMBER_REGISTER_RETRY_TIMES = 5; + private final Logger log = LoggerFactory.getLogger(this.getClass()); @Autowired @@ -413,14 +417,21 @@ public class MemberService { * @return 注册完成后的会员信息 */ private MemberBasicInfo registerMemberForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) { - MemberBasicInfo memberBasicInfo = buildNewMemberForRegisterAndLoginV2(dto); - MemberTransactionDTO memberTransactionDTO = buildMemberTransactionForRegisterAndLoginV2(memberBasicInfo, dto.getFirstLevelMerchantId()); - try { - transactionService.createMember(memberTransactionDTO); - return memberBasicInfo; - } catch (DuplicateKeyException e) { - return reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(dto); + for (int attempt = 1; attempt <= MAX_MEMBER_REGISTER_RETRY_TIMES; attempt++) { + MemberBasicInfo memberBasicInfo = buildNewMemberForRegisterAndLoginV2(dto); + MemberTransactionDTO memberTransactionDTO = buildMemberTransactionForRegisterAndLoginV2(memberBasicInfo, dto.getFirstLevelMerchantId()); + try { + transactionService.createMember(memberTransactionDTO); + return memberBasicInfo; + } catch (DuplicateKeyException e) { + MemberBasicInfo existedMember = reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(dto, memberBasicInfo.getMemberId(), attempt); + if (existedMember != null) { + return existedMember; + } + } } + log.error("会员注册重试多次后仍失败, phoneNumber:{}, merchantId:{}", dto.getMobileNumber(), dto.getFirstLevelMerchantId()); + throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR); } /** @@ -468,12 +479,14 @@ public class MemberService { * @param dto 登录/注册入参 * @return 已存在的会员信息 */ - private MemberBasicInfo reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto) { - log.warn("会员注册时检测到唯一索引冲突,重新查询已存在的会员, phoneNumber:{}, merchantId:{}", dto.getMobileNumber(), dto.getFirstLevelMerchantId()); + private MemberBasicInfo reloadMemberAfterDuplicateKeyForRegisterAndLoginV2(MemberRegisterAndLoginDTO dto, String memberId, int attempt) { + log.warn("会员注册时检测到唯一索引冲突,重新查询已存在的会员, phoneNumber:{}, merchantId:{}, candidateMemberId:{}, attempt:{}", + dto.getMobileNumber(), dto.getFirstLevelMerchantId(), memberId, attempt); MemberBasicInfo memberBasicInfo = memberBasicInfoService.selectInfoByMobileNumber(dto.getMobileNumber(), dto.getFirstLevelMerchantId()); if (memberBasicInfo == null) { - log.error("唯一索引冲突后重新查询会员信息为空, phoneNumber:{}, merchantId:{}", dto.getMobileNumber(), dto.getFirstLevelMerchantId()); - throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR); + log.warn("唯一索引冲突后未查询到手机号对应会员,准备重新生成memberId重试, phoneNumber:{}, merchantId:{}, candidateMemberId:{}, attempt:{}", + dto.getMobileNumber(), dto.getFirstLevelMerchantId(), memberId, attempt); + return null; } return memberBasicInfo; } @@ -535,14 +548,14 @@ public class MemberService { } private String generateNewMemberId() { - while (true) { + for (int attempt = 1; attempt <= MAX_MEMBER_ID_GENERATE_RETRY_TIMES; attempt++) { String memberId = IdUtils.getMemberId(); - // 通过memberId查询是否已经存在 - MemberVO memberVO = memberBasicInfoService.queryMemberInfoByMemberId(memberId); - if (memberVO == null) { + if (!memberBasicInfoService.existsByMemberId(memberId)) { return memberId; } + log.warn("生成memberId命中已存在记录,准备重试, memberId:{}, attempt:{}", memberId, attempt); } + throw new BusinessException(ReturnCodeEnum.CODE_MEMBER_REGISTER_AND_LOGIN_ERROR); } /** diff --git a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java index 373223840..b3a4a94cf 100644 --- a/jsowell-admin/src/main/java/com/jsowell/service/TempService.java +++ b/jsowell-admin/src/main/java/com/jsowell/service/TempService.java @@ -1594,7 +1594,8 @@ public class TempService { return ImportMemberBalanceItemResultDTO.success(phone); } catch (Exception e) { logger.error("导入会员余额失败, phone:{}, param:{}", phone, JSON.toJSONString(memberBalanceDTO), e); - return ImportMemberBalanceItemResultDTO.fail(phone, e.getMessage()); + String errorMessage = e.getClass().getSimpleName() + ": " + e.getMessage(); + return ImportMemberBalanceItemResultDTO.fail(phone, errorMessage); } } @@ -1613,6 +1614,8 @@ public class TempService { // 1. 根据手机号查询万车充会员信息;不存在则静默注册一个属于万车充体系的会员。 MemberBasicInfo memberBasicInfo = findOrCreateJsowellMember(phone); + logger.info("导入会员余额-会员准备完成, phone:{}, memberId:{}, memberMerchantId:{}, balance:{}", + phone, memberBasicInfo.getMemberId(), memberBasicInfo.getMerchantId(), balance); // 2. 给“南通晨鸣中锦置业有限责任公司”运营商钱包增加本金余额。 increaseMemberWalletBalance(memberBasicInfo.getMemberId(), balance); @@ -1653,6 +1656,8 @@ public class TempService { *

如果该运营商钱包不存在,现有余额逻辑会自动创建钱包并记录流水。

*/ private void increaseMemberWalletBalance(String memberId, BigDecimal balance) { + logger.info("导入会员余额-开始增加钱包余额, memberId:{}, targetMerchantId:{}, balance:{}", + memberId, NANTONG_CHENMING_WALLET_MERCHANT_ID, balance); UpdateMemberBalanceDTO updateMemberBalanceDTO = UpdateMemberBalanceDTO.builder() .memberId(memberId) .type(MemberWalletEnum.TYPE_IN.getValue()) @@ -1661,6 +1666,8 @@ public class TempService { .targetMerchantId(NANTONG_CHENMING_WALLET_MERCHANT_ID) .build(); memberBasicInfoService.updateMemberBalance(updateMemberBalanceDTO); + logger.info("导入会员余额-钱包余额增加完成, memberId:{}, targetMerchantId:{}, balance:{}", + memberId, NANTONG_CHENMING_WALLET_MERCHANT_ID, balance); } } diff --git a/jsowell-common/src/main/java/com/jsowell/common/util/id/IdUtils.java b/jsowell-common/src/main/java/com/jsowell/common/util/id/IdUtils.java index cbcfc9207..ea4cb6ce4 100644 --- a/jsowell-common/src/main/java/com/jsowell/common/util/id/IdUtils.java +++ b/jsowell-common/src/main/java/com/jsowell/common/util/id/IdUtils.java @@ -147,15 +147,12 @@ public class IdUtils { * 生成八位会员id */ public static String getMemberId() { - long id = Long.parseLong(SnowflakeIdWorker.getSnowflakeId()); - StringBuilder sb = new StringBuilder(id + ""); - StringBuilder reverse = sb.reverse();// 将id翻转:我们发现id很长,且高位很长部分是一样的数 - id = new Long(reverse.toString()) / 1000;// 切去部分长度 - while (id > 100000000) { - id /= 10; - } - Integer num = Integer.parseInt(id + ""); - return String.valueOf(num); + long snowflakeId = Long.parseLong(SnowflakeIdWorker.getSnowflakeId()); + + // 对雪花 ID 做轻量混洗,再压缩到 8 位数字区间,分布比原来的“反转后截断”更均匀。 + long mixed = snowflakeId ^ (snowflakeId >>> 33) ^ (snowflakeId >>> 17); + long memberId = Math.floorMod(mixed, 90000000L) + 10000000L; + return String.valueOf(memberId); } /** diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/domain/MemberWalletInfo.java b/jsowell-pile/src/main/java/com/jsowell/pile/domain/MemberWalletInfo.java index 1f9415f8a..179be9b29 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/domain/MemberWalletInfo.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/domain/MemberWalletInfo.java @@ -22,7 +22,7 @@ public class MemberWalletInfo { /** * 主键 */ - private Integer id; + private Long id; /** * 会员id @@ -66,4 +66,4 @@ public class MemberWalletInfo { * 删除标识(0-正常;1-删除) */ private String delFlag; -} \ No newline at end of file +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberBasicInfoMapper.java b/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberBasicInfoMapper.java index a49ff56d5..1070c0364 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberBasicInfoMapper.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberBasicInfoMapper.java @@ -93,6 +93,14 @@ public interface MemberBasicInfoMapper { */ MemberBasicInfo selectInfoByMemberId(String memberId); + /** + * 判断会员id是否已存在。 + * + * @param memberId 会员id + * @return 1-存在;null-不存在 + */ + Integer existsByMemberId(@Param("memberId") String memberId); + /** * 更新会员余额 * @param memberId 会员id diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberWalletInfoMapper.java b/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberWalletInfoMapper.java index 386913244..b77912b8d 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberWalletInfoMapper.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/mapper/MemberWalletInfoMapper.java @@ -7,7 +7,7 @@ import org.apache.ibatis.annotations.Param; import java.util.List; public interface MemberWalletInfoMapper { - int deleteByPrimaryKey(Integer id); + int deleteByPrimaryKey(Long id); int insert(MemberWalletInfo record); @@ -17,7 +17,7 @@ public interface MemberWalletInfoMapper { int insertSelective(MemberWalletInfo record); - MemberWalletInfo selectByPrimaryKey(Integer id); + MemberWalletInfo selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(MemberWalletInfo record); diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberBasicInfoService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberBasicInfoService.java index 386af72e1..83038944e 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberBasicInfoService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberBasicInfoService.java @@ -91,6 +91,14 @@ public interface MemberBasicInfoService { */ MemberBasicInfo selectInfoByMemberId(String memberId); + /** + * 判断指定会员id是否已经存在。 + * + * @param memberId 会员id + * @return true-已存在;false-不存在 + */ + boolean existsByMemberId(String memberId); + String generateWalletCode(); /** diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberWalletInfoService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberWalletInfoService.java index 06c73c307..4bd83e6bc 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberWalletInfoService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/MemberWalletInfoService.java @@ -6,13 +6,13 @@ import com.jsowell.pile.vo.base.MemberWalletVO; import java.util.List; public interface MemberWalletInfoService { - int deleteByPrimaryKey(Integer id); + int deleteByPrimaryKey(Long id); int insert(MemberWalletInfo record); int insertSelective(MemberWalletInfo record); - MemberWalletInfo selectByPrimaryKey(Integer id); + MemberWalletInfo selectByPrimaryKey(Long id); MemberWalletInfo selectByMemberId(String memberId, String merchantId); diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberBasicInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberBasicInfoServiceImpl.java index 2cb20b99a..7d1455f3d 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberBasicInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberBasicInfoServiceImpl.java @@ -180,6 +180,11 @@ public class MemberBasicInfoServiceImpl implements MemberBasicInfoService { return memberBasicInfoMapper.selectInfoByMemberId(memberId); } + @Override + public boolean existsByMemberId(String memberId) { + return memberBasicInfoMapper.existsByMemberId(memberId) != null; + } + /** * 生成钱包卡号WalletCode * @return @@ -267,8 +272,16 @@ public class MemberBasicInfoServiceImpl implements MemberBasicInfoService { createBy = SecurityUtils.getLoginUser().getUserId() + ""; } - // 查询用户余额 - MemberWalletInfo walletInfo = memberWalletInfoService.selectByMemberId(memberId, targetMerchantId); + log.info("修改用户余额-开始查询钱包, memberId:{}, targetMerchantId:{}, type:{}, subType:{}, updatePrincipalBalance:{}, updateGiftBalance:{}", + memberId, targetMerchantId, type, dto.getSubType(), updatePrincipalBalance, updateGiftBalance); + MemberWalletInfo walletInfo; + try { + walletInfo = memberWalletInfoService.selectByMemberId(memberId, targetMerchantId); + } catch (Exception e) { + log.error("修改用户余额-查询钱包异常, memberId:{}, targetMerchantId:{}, memberIdLength:{}, exceptionType:{}", + memberId, targetMerchantId, memberId == null ? null : memberId.length(), e.getClass().getName(), e); + throw e; + } log.info("修改用户余额-根据会员id:{}, 目标运营商id:{}, 查询结果:{}", memberId, targetMerchantId, JSON.toJSONString(walletInfo)); if (walletInfo == null) { log.info("修改用户余额-根据会员id:{}, 目标运营商id:{}, 查询会员信息为空, 新建会员钱包", memberId, targetMerchantId); @@ -282,6 +295,11 @@ public class MemberBasicInfoServiceImpl implements MemberBasicInfoService { .version(0) .build(); memberWalletInfoService.insertSelective(walletInfo); + log.info("修改用户余额-会员钱包创建完成, memberId:{}, targetMerchantId:{}, walletId:{}, walletCode:{}", + memberId, targetMerchantId, walletInfo.getId(), walletInfo.getWalletCode()); + } else { + log.info("修改用户余额-命中会员钱包, memberId:{}, targetMerchantId:{}, walletId:{}, walletCode:{}, version:{}", + memberId, targetMerchantId, walletInfo.getId(), walletInfo.getWalletCode(), walletInfo.getVersion()); } // 钱包编号 diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberWalletInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberWalletInfoServiceImpl.java index 4be888a50..e8369c07e 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberWalletInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/MemberWalletInfoServiceImpl.java @@ -25,7 +25,7 @@ public class MemberWalletInfoServiceImpl implements MemberWalletInfoService { private PileMerchantInfoService pileMerchantInfoService; @Override - public int deleteByPrimaryKey(Integer id) { + public int deleteByPrimaryKey(Long id) { return memberWalletInfoMapper.deleteByPrimaryKey(id); } @@ -40,7 +40,7 @@ public class MemberWalletInfoServiceImpl implements MemberWalletInfoService { } @Override - public MemberWalletInfo selectByPrimaryKey(Integer id) { + public MemberWalletInfo selectByPrimaryKey(Long id) { return memberWalletInfoMapper.selectByPrimaryKey(id); } diff --git a/jsowell-pile/src/main/resources/mapper/pile/MemberBasicInfoMapper.xml b/jsowell-pile/src/main/resources/mapper/pile/MemberBasicInfoMapper.xml index 1b262b865..d165862fc 100644 --- a/jsowell-pile/src/main/resources/mapper/pile/MemberBasicInfoMapper.xml +++ b/jsowell-pile/src/main/resources/mapper/pile/MemberBasicInfoMapper.xml @@ -173,6 +173,14 @@ and member_id = #{memberId,jdbcType=VARCHAR} + + update member_wallet_info diff --git a/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml b/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml index ed393f696..15aea28ec 100644 --- a/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml +++ b/jsowell-pile/src/main/resources/mapper/pile/MemberWalletInfoMapper.xml @@ -4,7 +4,7 @@ - + @@ -22,17 +22,17 @@ id, member_id, merchant_id, wallet_code, principal_balance, gift_balance, version, create_by, create_time, update_by, update_time, del_flag - select from member_wallet_info - where id = #{id,jdbcType=INTEGER} + where id = #{id,jdbcType=BIGINT} - + delete from member_wallet_info - where id = #{id,jdbcType=INTEGER} + where id = #{id,jdbcType=BIGINT} @@ -157,7 +157,7 @@ del_flag = #{delFlag,jdbcType=CHAR}, - where id = #{id,jdbcType=INTEGER} + where id = #{id,jdbcType=BIGINT} @@ -173,7 +173,7 @@ update_by = #{updateBy,jdbcType=VARCHAR}, update_time = #{updateTime,jdbcType=TIMESTAMP}, del_flag = #{delFlag,jdbcType=CHAR} - where id = #{id,jdbcType=INTEGER} + where id = #{id,jdbcType=BIGINT} @@ -181,63 +181,63 @@ - when id = #{item.id,jdbcType=INTEGER} then #{item.memberId,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.memberId,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.merchantId,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.merchantId,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.walletCode,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.walletCode,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.principalBalance,jdbcType=DECIMAL} + when id = #{item.id,jdbcType=BIGINT} then #{item.principalBalance,jdbcType=DECIMAL} - when id = #{item.id,jdbcType=INTEGER} then #{item.giftBalance,jdbcType=DECIMAL} + when id = #{item.id,jdbcType=BIGINT} then #{item.giftBalance,jdbcType=DECIMAL} - when id = #{item.id,jdbcType=INTEGER} then #{item.version,jdbcType=INTEGER} + when id = #{item.id,jdbcType=BIGINT} then #{item.version,jdbcType=INTEGER} - when id = #{item.id,jdbcType=INTEGER} then #{item.createBy,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.createBy,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.createTime,jdbcType=TIMESTAMP} + when id = #{item.id,jdbcType=BIGINT} then #{item.createTime,jdbcType=TIMESTAMP} - when id = #{item.id,jdbcType=INTEGER} then #{item.updateBy,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.updateBy,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.updateTime,jdbcType=TIMESTAMP} + when id = #{item.id,jdbcType=BIGINT} then #{item.updateTime,jdbcType=TIMESTAMP} - when id = #{item.id,jdbcType=INTEGER} then #{item.delFlag,jdbcType=CHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.delFlag,jdbcType=CHAR} where id in - #{item.id,jdbcType=INTEGER} + #{item.id,jdbcType=BIGINT} @@ -247,84 +247,84 @@ - when id = #{item.id,jdbcType=INTEGER} then #{item.memberId,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.memberId,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.merchantId,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.merchantId,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.walletCode,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.walletCode,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.principalBalance,jdbcType=DECIMAL} + when id = #{item.id,jdbcType=BIGINT} then #{item.principalBalance,jdbcType=DECIMAL} - when id = #{item.id,jdbcType=INTEGER} then #{item.giftBalance,jdbcType=DECIMAL} + when id = #{item.id,jdbcType=BIGINT} then #{item.giftBalance,jdbcType=DECIMAL} - when id = #{item.id,jdbcType=INTEGER} then #{item.version,jdbcType=INTEGER} + when id = #{item.id,jdbcType=BIGINT} then #{item.version,jdbcType=INTEGER} - when id = #{item.id,jdbcType=INTEGER} then #{item.createBy,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.createBy,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.createTime,jdbcType=TIMESTAMP} + when id = #{item.id,jdbcType=BIGINT} then #{item.createTime,jdbcType=TIMESTAMP} - when id = #{item.id,jdbcType=INTEGER} then #{item.updateBy,jdbcType=VARCHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.updateBy,jdbcType=VARCHAR} - when id = #{item.id,jdbcType=INTEGER} then #{item.updateTime,jdbcType=TIMESTAMP} + when id = #{item.id,jdbcType=BIGINT} then #{item.updateTime,jdbcType=TIMESTAMP} - when id = #{item.id,jdbcType=INTEGER} then #{item.delFlag,jdbcType=CHAR} + when id = #{item.id,jdbcType=BIGINT} then #{item.delFlag,jdbcType=CHAR} where id in - #{item.id,jdbcType=INTEGER} + #{item.id,jdbcType=BIGINT} @@ -363,7 +363,7 @@ values - #{id,jdbcType=INTEGER}, + #{id,jdbcType=BIGINT}, #{memberId,jdbcType=VARCHAR}, #{merchantId,jdbcType=VARCHAR}, @@ -380,7 +380,7 @@ on duplicate key update - id = #{id,jdbcType=INTEGER}, + id = #{id,jdbcType=BIGINT}, member_id = #{memberId,jdbcType=VARCHAR}, merchant_id = #{merchantId,jdbcType=VARCHAR}, @@ -439,7 +439,7 @@ values - #{id,jdbcType=INTEGER}, + #{id,jdbcType=BIGINT}, #{memberId,jdbcType=VARCHAR}, @@ -478,7 +478,7 @@ on duplicate key update - id = #{id,jdbcType=INTEGER}, + id = #{id,jdbcType=BIGINT}, member_id = #{memberId,jdbcType=VARCHAR}, From 7eec511c0e87bf3e36cf4ef39cb0fd1464a0da68 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Mon, 18 May 2026 11:02:32 +0800 Subject: [PATCH 13/15] =?UTF-8?q?update=20=E4=BC=98=E5=8C=96=E9=80=80?= =?UTF-8?q?=E4=BF=9D=E9=99=A9=E9=87=91=E9=A2=9D=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pile/service/impl/OrderBasicInfoServiceImpl.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java index cab28f0ae..a0a3b48b5 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java @@ -662,12 +662,12 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService { logger.error("启动失败退款, orderCode:{}, transactionCode:{}, 执行订单结算逻辑发生异常", orderInfo.getOrderCode(),transactionCode, e); } - // 退保险金额 - try { - refundInsurance(orderInfo); - } catch (Exception e) { - logger.error("启动失败退款, orderCode:{}, transactionCode:{}, 退保险发生异常", orderInfo.getOrderCode(),transactionCode, e); - } + // 退保险金额 2025-05-18注释,支付成功未启动订单,保险费退款集成到订单退款中 + // try { + // refundInsurance(orderInfo); + // } catch (Exception e) { + // logger.error("启动失败退款, orderCode:{}, transactionCode:{}, 退保险发生异常", orderInfo.getOrderCode(),transactionCode, e); + // } } /** From 8de1e9fb67e1298ba3faaa723aa8139d4a4a2df8 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Tue, 19 May 2026 15:58:01 +0800 Subject: [PATCH 14/15] =?UTF-8?q?update=20=E6=A0=A1=E9=AA=8C=E5=9B=BA?= =?UTF-8?q?=E4=BB=B6=E5=90=8D=E6=98=AF=E5=90=A6=E5=94=AF=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pile/PileFirmwareInfoController.java | 20 +++++++++++++++++++ .../pile/mapper/PileFirmwareInfoMapper.java | 9 +++++++++ .../pile/service/PileFirmwareInfoService.java | 8 ++++++++ .../impl/PileFirmwareInfoServiceImpl.java | 18 +++++++++++++++++ .../mapper/pile/PileFirmwareInfoMapper.xml | 8 +++++++- 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java index 1433de0ee..a8623e43e 100644 --- a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java +++ b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java @@ -1,10 +1,12 @@ package com.jsowell.web.controller.pile; import com.jsowell.common.annotation.Log; +import com.jsowell.common.constant.UserConstants; import com.jsowell.common.core.controller.BaseController; import com.jsowell.common.core.domain.AjaxResult; import com.jsowell.common.core.page.TableDataInfo; import com.jsowell.common.enums.BusinessType; +import com.jsowell.common.util.StringUtils; import com.jsowell.common.util.poi.ExcelUtil; import com.jsowell.pile.domain.PileFirmwareInfo; import com.jsowell.pile.service.PileFirmwareInfoService; @@ -59,6 +61,16 @@ public class PileFirmwareInfoController extends BaseController { return AjaxResult.success(pileFirmwareInfoService.selectPileFirmwareInfoById(id)); } + /** + * 校验固件名称是否唯一 + */ + @PreAuthorize("@ss.hasAnyPermi('pile:firmware:query,pile:firmware:add,pile:firmware:edit')") + @GetMapping("/checkNameUnique") + public AjaxResult checkNameUnique(PileFirmwareInfo pileFirmwareInfo) { + pileFirmwareInfo.setName(StringUtils.trim(pileFirmwareInfo.getName())); + return AjaxResult.success(pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo)); + } + /** * 新增充电桩固件信息 */ @@ -66,6 +78,10 @@ public class PileFirmwareInfoController extends BaseController { @Log(title = "充电桩固件信息", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody PileFirmwareInfo pileFirmwareInfo) { + pileFirmwareInfo.setName(StringUtils.trim(pileFirmwareInfo.getName())); + if (UserConstants.NOT_UNIQUE.equals(pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo))) { + return AjaxResult.error("新增固件'" + pileFirmwareInfo.getName() + "'失败,固件名称已存在"); + } return toAjax(pileFirmwareInfoService.insertPileFirmwareInfo(pileFirmwareInfo)); } @@ -76,6 +92,10 @@ public class PileFirmwareInfoController extends BaseController { @Log(title = "充电桩固件信息", businessType = BusinessType.UPDATE) @PutMapping public AjaxResult edit(@RequestBody PileFirmwareInfo pileFirmwareInfo) { + pileFirmwareInfo.setName(StringUtils.trim(pileFirmwareInfo.getName())); + if (UserConstants.NOT_UNIQUE.equals(pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo))) { + return AjaxResult.error("修改固件'" + pileFirmwareInfo.getName() + "'失败,固件名称已存在"); + } return toAjax(pileFirmwareInfoService.updatePileFirmwareInfo(pileFirmwareInfo)); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/mapper/PileFirmwareInfoMapper.java b/jsowell-pile/src/main/java/com/jsowell/pile/mapper/PileFirmwareInfoMapper.java index a1a6d4b30..b19a0a530 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/mapper/PileFirmwareInfoMapper.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/mapper/PileFirmwareInfoMapper.java @@ -1,6 +1,7 @@ package com.jsowell.pile.mapper; import com.jsowell.pile.domain.PileFirmwareInfo; +import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -29,6 +30,14 @@ public interface PileFirmwareInfoMapper { */ public List selectPileFirmwareInfoList(PileFirmwareInfo pileFirmwareInfo); + /** + * 根据固件名称查询充电桩固件信息 + * + * @param name 固件名称 + * @return 充电桩固件信息 + */ + public PileFirmwareInfo checkNameUnique(@Param("name") String name); + /** * 新增充电桩固件信息 * diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileFirmwareInfoService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileFirmwareInfoService.java index 4b757c819..06cbee763 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/PileFirmwareInfoService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/PileFirmwareInfoService.java @@ -30,6 +30,14 @@ public interface PileFirmwareInfoService { */ public List selectPileFirmwareInfoList(PileFirmwareInfo pileFirmwareInfo); + /** + * 校验固件名称是否唯一 + * + * @param pileFirmwareInfo 充电桩固件信息 + * @return 结果 + */ + public String checkNameUnique(PileFirmwareInfo pileFirmwareInfo); + /** * 新增充电桩固件信息 * diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileFirmwareInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileFirmwareInfoServiceImpl.java index 9fb339a7d..83b2359d0 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileFirmwareInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/PileFirmwareInfoServiceImpl.java @@ -3,8 +3,10 @@ package com.jsowell.pile.service.impl; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.jsowell.common.constant.Constants; +import com.jsowell.common.constant.UserConstants; import com.jsowell.common.core.page.PageResponse; import com.jsowell.common.util.DateUtils; +import com.jsowell.common.util.StringUtils; import com.jsowell.common.util.file.AliyunOssUploadUtils; import com.jsowell.pile.domain.PileFirmwareInfo; import com.jsowell.pile.domain.PileReservationInfo; @@ -50,6 +52,22 @@ public class PileFirmwareInfoServiceImpl implements PileFirmwareInfoService { return pileFirmwareInfoMapper.selectPileFirmwareInfoList(pileFirmwareInfo); } + /** + * 校验固件名称是否唯一 + * + * @param pileFirmwareInfo 充电桩固件信息 + * @return 结果 + */ + @Override + public String checkNameUnique(PileFirmwareInfo pileFirmwareInfo) { + Long id = StringUtils.isNull(pileFirmwareInfo.getId()) ? -1L : pileFirmwareInfo.getId(); + PileFirmwareInfo info = pileFirmwareInfoMapper.checkNameUnique(pileFirmwareInfo.getName()); + if (StringUtils.isNotNull(info) && info.getId().longValue() != id.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + /** * 新增充电桩固件信息 * diff --git a/jsowell-pile/src/main/resources/mapper/pile/PileFirmwareInfoMapper.xml b/jsowell-pile/src/main/resources/mapper/pile/PileFirmwareInfoMapper.xml index 836e2a5b9..29e8d98c5 100644 --- a/jsowell-pile/src/main/resources/mapper/pile/PileFirmwareInfoMapper.xml +++ b/jsowell-pile/src/main/resources/mapper/pile/PileFirmwareInfoMapper.xml @@ -36,6 +36,12 @@ where id = #{id} + + insert into pile_firmware_info @@ -96,4 +102,4 @@ #{id} - \ No newline at end of file + From eb5d1ce9bfca2c06571f655f0484accc09f6d954 Mon Sep 17 00:00:00 2001 From: jsowell <123@jsowell.com> Date: Wed, 20 May 2026 10:27:24 +0800 Subject: [PATCH 15/15] =?UTF-8?q?update=20checkNameUnique=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jsowell/web/controller/pile/PileFirmwareInfoController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java index a8623e43e..28d397774 100644 --- a/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java +++ b/jsowell-admin/src/main/java/com/jsowell/web/controller/pile/PileFirmwareInfoController.java @@ -68,7 +68,7 @@ public class PileFirmwareInfoController extends BaseController { @GetMapping("/checkNameUnique") public AjaxResult checkNameUnique(PileFirmwareInfo pileFirmwareInfo) { pileFirmwareInfo.setName(StringUtils.trim(pileFirmwareInfo.getName())); - return AjaxResult.success(pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo)); + return AjaxResult.success("操作成功", pileFirmwareInfoService.checkNameUnique(pileFirmwareInfo)); } /**