From 17d293ec0ab9ccfbe65463ffb0a0f67caeeeca26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E9=B9=8F?= <812968653@qq.com> Date: Mon, 17 Aug 2026 22:04:50 +0800 Subject: [PATCH] =?UTF-8?q?bug=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../brokerage/BrokerageWithdrawTypeEnum.java | 16 +++--- .../BrokerageWithdrawController.java | 44 ++++++++++++++ .../vo/withdraw/BrokerageWithdrawExcelVO.java | 57 +++++++++++++++++++ .../withdraw/AppBrokerageWithdrawRespVO.java | 6 ++ .../BrokerageWithdrawServiceImpl.java | 13 ++--- .../module/member/client/service/ChatApi.java | 5 ++ .../member/client/service/ChatApiImpl.java | 30 ++++++++++ .../member/client/tool/ChatApiTool.java | 1 + .../client/vo/chat/user/ChatBanUserReqVO.java | 22 +++++++ .../service/user/MemberUserServiceImpl.java | 14 ++++- 10 files changed, 190 insertions(+), 18 deletions(-) create mode 100644 yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawExcelVO.java create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatBanUserReqVO.java diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java index 2ecedc2..465f914 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java @@ -18,8 +18,12 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable { WALLET(1, "钱包"), BANK(2, "银行卡"), WECHAT(3, "微信"), // 手动打款 - ALIPAY(4, "支付宝"), - WECHAT_API(5, "支付宝零钱"), // 自动打款,通过微信转账 API + ALIPAY(4, "支付宝"), // 手动打款 + /** + * 历史命名 WECHAT_API;App 端佣金提现实际传 type=5 + 支付宝账号,运营后台手动打款。 + * 切勿再走微信转账 API。 + */ + WECHAT_API(5, "支付宝"), ; public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawTypeEnum::getType).toArray(Integer[]::new); @@ -39,13 +43,11 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable { } /** - * 是否通过支付平台的 API 打款 - * - * @param type 类型 - * @return 是否 + * 是否通过支付平台的 API 打款。 + * 当前业务均为运营手动打款,不再走微信/支付宝自动转账。 */ public static boolean isApi(Integer type) { - return WECHAT_API.getType().equals(type); + return false; } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java index 66df7cf..fba271a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java @@ -1,10 +1,15 @@ package cn.iocoder.yudao.module.trade.controller.admin.brokerage; +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO; +import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawExcelVO; import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO; import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRejectReqVO; import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRespVO; @@ -22,9 +27,15 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.annotation.security.PermitAll; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Optional; +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; @@ -81,6 +92,39 @@ public class BrokerageWithdrawController { return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap)); } + @GetMapping("/export-excel") + @Operation(summary = "导出佣金提现 Excel") + @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')") + @ApiAccessLog(operateType = EXPORT) + public void exportBrokerageWithdrawExcel(@Valid BrokerageWithdrawPageReqVO pageVO, + HttpServletResponse response) throws IOException { + pageVO.setPageSize(PageParam.PAGE_SIZE_NONE); + PageResult pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(pageVO); + Map userMap = memberUserApi.getUserMap( + convertSet(pageResult.getList(), BrokerageWithdrawDO::getUserId)); + + List excelList = new ArrayList<>(pageResult.getList().size()); + for (BrokerageWithdrawDO row : pageResult.getList()) { + BrokerageWithdrawExcelVO excel = new BrokerageWithdrawExcelVO(); + excel.setId(row.getId()); + excel.setUserId(row.getUserId()); + excel.setUserNickname(Optional.ofNullable(userMap.get(row.getUserId())) + .map(MemberUserRespDTO::getNickname).orElse(null)); + excel.setPriceYuan(row.getPrice() == null ? "0" : MoneyUtils.fenToYuanStr(row.getPrice())); + excel.setFeePriceYuan(row.getFeePrice() == null ? "0" : MoneyUtils.fenToYuanStr(row.getFeePrice())); + excel.setTypeName("支付宝"); + excel.setAccountNo(row.getAccountNo()); + excel.setName(row.getName()); + excel.setStatus(row.getStatus()); + excel.setAuditReason(row.getAuditReason()); + excel.setAuditTime(row.getAuditTime()); + excel.setCreateTime(row.getCreateTime()); + excel.setRemark(row.getRemark()); + excelList.add(excel); + } + ExcelUtils.write(response, "佣金提现.xls", "数据", BrokerageWithdrawExcelVO.class, excelList); + } + // TODO @luchi:update-transferred,url 改成这个。和 update-paid 、update-refunded 保持一致 @PostMapping("/update-transfer") @Operation(summary = "更新转账订单为转账成功") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawExcelVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawExcelVO.java new file mode 100644 index 0000000..a6c3291 --- /dev/null +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawExcelVO.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.trade.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 佣金提现 Excel VO + */ +@Data +public class BrokerageWithdrawExcelVO { + + @ExcelProperty("编号") + private Long id; + + @ExcelProperty("用户编号") + private Long userId; + + @ExcelProperty("用户昵称") + private String userNickname; + + @ExcelProperty("提现金额(元)") + private String priceYuan; + + @ExcelProperty("手续费(元)") + private String feePriceYuan; + + @ExcelProperty("提现方式") + private String typeName; + + @ExcelProperty("支付宝账号") + private String accountNo; + + @ExcelProperty("真实姓名") + private String name; + + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BROKERAGE_WITHDRAW_STATUS) + private Integer status; + + @ExcelProperty("审核原因") + private String auditReason; + + @ExcelProperty("审核时间") + private LocalDateTime auditTime; + + @ExcelProperty("申请时间") + private LocalDateTime createTime; + + @ExcelProperty("备注") + private String remark; + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java index 3d0b988..1bc3638 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java @@ -30,6 +30,12 @@ public class AppBrokerageWithdrawRespVO { @Schema(description = "提现时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; + @Schema(description = "审核驳回原因", example = "信息不完整") + private String auditReason; + + @Schema(description = "转账失败原因", example = "用户未确认收款") + private String transferErrorMsg; + // ========== 微信转账专属 ========== @Schema(description = "转账单编号", example = "1024") diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java index 549fda1..c0589a4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java @@ -124,17 +124,14 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) .setBizType(PayWalletBizTypeEnum.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString()) .setPrice(withdraw.getPrice())); - // 1.2 微信 API - } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) { - // TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈? - createPayTransfer(withdraw); - // 1.3 剩余类型,都是手动打款,所以不处理 + // 1.2 历史 type=5 曾走微信转账 API;现业务为支付宝账号提现,统一手动打款 + // 若再走 createPayTransfer,无小程序 openid 会 NPE → 管理端「系统异常」 } else { - // TODO 可优化:未来可以考虑,接入支付宝、银联等 API 转账,实现自动打款 - log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,无需处理]", withdraw.getId(), withdraw.getType()); + log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,账号={}]", + withdraw.getId(), withdraw.getType(), withdraw.getAccountNo()); } - // 2. 非支付 API,则直接体现成功 + // 2. 非支付 API,则直接提现成功 if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) { brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(), new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus())); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java index bffa8c1..e5c2789 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApi.java @@ -29,6 +29,11 @@ public interface ChatApi { */ boolean createOrUpdate(@Valid ChatCreateOrUpdateUserReqVO dto); + /** + * 封禁聊天用户并推送下线 + */ + boolean banUser(Long userId, String reason); + /** * 根据userId查询用户详情 */ diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java index 32aadd8..7ed76e8 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/service/ChatApiImpl.java @@ -121,6 +121,36 @@ public class ChatApiImpl implements ChatApi { } } + /** + * 封禁聊天用户并推送下线 + */ + @Override + public boolean banUser(Long userId, String reason) { + if (userId == null) { + return false; + } + ChatApiTool.API api = setApiConfig(ChatApiTool.USER_BAN); + try { + ChatBanUserReqVO dto = new ChatBanUserReqVO(userId, reason); + String body = JSONUtil.toJsonStr(dto); + String res = HttpRequest.post(api.getFullPath()).header(apiCountersign(body)).body(body).execute().body(); + log.debug("封禁聊天用户,res:{}", res); + if (ObjectUtil.isEmpty(res)) { + log.error("封禁聊天用户,res为空"); + return false; + } + ChatBaseRespVO baseRes = JSONUtil.toBean(res, ChatBaseRespVO.class); + if (ObjectUtil.isEmpty(baseRes) || !baseRes.isSuccess()) { + log.error("封禁聊天用户,返回错误,res:{}", res); + return false; + } + return true; + } catch (Exception e) { + log.error("封禁聊天用户,意外的错误,userId={}", userId, e); + return false; + } + } + /** * 根据userId查询用户详情 */ diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java index d4f8731..3d2e0a4 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/tool/ChatApiTool.java @@ -21,6 +21,7 @@ public class ChatApiTool { public static final API USER_LOGIN = API.builder().summary("用户登录").url("/third/user/login").method(HttpMethod.POST).build(); public static final API USER_GET = API.builder().summary("根据userId查询用户详情").url("/third/user/getUserByUserId").method(HttpMethod.POST).build(); public static final API USER_SAVE_OR_UPDATE = API.builder().summary("创建或者更新用户").url("/third/user/createOrUpdate").method(HttpMethod.POST).build(); + public static final API USER_BAN = API.builder().summary("封禁用户并踢下线").url("/third/user/ban").method(HttpMethod.POST).build(); public static final API GET_USER_ONLINE_STATUS_BY_ID = API.builder().summary("查询用户在线状态").url("/third/user/getUserOnlineStatusById").method(HttpMethod.POST).build(); // 朋友圈 diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatBanUserReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatBanUserReqVO.java new file mode 100644 index 0000000..f7ab04d --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/client/vo/chat/user/ChatBanUserReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.member.client.vo.chat.user; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "封禁聊天用户") +public class ChatBanUserReqVO { + + @NotNull(message = "用户id不可为空") + @Schema(description = "用户id") + private Long userId; + + @Schema(description = "封禁原因") + private String reason; +} diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java index 02b149e..8682a28 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java @@ -478,7 +478,7 @@ public class MemberUserServiceImpl implements MemberUserService { } /** - * 清理会员全部 OAuth2 access + refresh,App 下次请求会 401 并退出登录。 + * 清理会员全部 OAuth2 access + refresh,并通知 IM 立刻踢下线。 */ private void kickMemberLogin(Long userId) { try { @@ -487,6 +487,11 @@ public class MemberUserServiceImpl implements MemberUserService { log.warn("踢出会员登录态失败, userId={}", userId, e); throw e; } + try { + chatApi.banUser(userId, "账号已被禁用"); + } catch (Exception e) { + log.warn("通知聊天服务封禁用户失败, userId={}", userId, e); + } } @VisibleForTesting @@ -577,12 +582,15 @@ public class MemberUserServiceImpl implements MemberUserService { /** * 获取用户的微信信息 + * 有微信号即可解锁;二维码为可选展示字段。 */ @Override public MemberWechatBO getMemberWechat(Long memberId) { MemberUserDO detail = memberUserMapper.selectById(memberId); - if (ObjectUtil.isEmpty(detail)) return null; - if (ObjectUtil.isEmpty(detail.getWechatNum()) || ObjectUtil.isEmpty(detail.getWechatQrCode())) { + if (ObjectUtil.isEmpty(detail)) { + return null; + } + if (ObjectUtil.isEmpty(detail.getWechatNum())) { return null; } MemberWechatBO result = new MemberWechatBO();