From 645bfa21ad0ad7e7700f59d91d1732e35df3aac6 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 28 Sep 2026 15:05:45 +0800 Subject: [PATCH] =?UTF-8?q?ai=E6=89=B9=E9=87=8F=E6=8F=92=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .run/批量启动.run.xml | 2 + .../api/feign/RemoteHardOrderService.java | 16 ++ .../controller/order/HardOrderController.java | 29 +++ .../admin/api/feign/RemoteUserService.java | 19 ++ .../admin/controller/SysUserController.java | 38 ++++ .../pjcloud/admin/mapper/SysUserMapper.java | 6 + .../admin/mapper/SysUserRoleMapper.java | 15 ++ .../pjcloud/admin/service/SysUserService.java | 20 +++ .../service/impl/SysUserServiceImpl.java | 170 ++++++++++++++++++ .../main/resources/mapper/SysUserMapper.xml | 18 ++ .../wallet/api/feign/RemoteWalletService.java | 11 ++ .../controller/WalletManageController.java | 19 ++ .../wallet/mapper/WalletManageMapper.java | 21 +++ .../wallet/service/WalletManageService.java | 14 ++ .../service/impl/WalletManageServiceImpl.java | 161 +++++++++++++++++ 15 files changed, 559 insertions(+) diff --git a/.run/批量启动.run.xml b/.run/批量启动.run.xml index 3e60f4b4..b445b3e4 100644 --- a/.run/批量启动.run.xml +++ b/.run/批量启动.run.xml @@ -5,6 +5,8 @@ + + \ No newline at end of file diff --git a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java index 77b0acaa..73b5e8a3 100644 --- a/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java +++ b/pjcloud-hardware/pjcloud-hardware-api/src/main/java/com/bsd/pjcloud/hard/api/feign/RemoteHardOrderService.java @@ -67,4 +67,20 @@ public interface RemoteHardOrderService { R> listDailyMenuNetSold( @RequestBody com.bsd.pjcloud.hard.api.dto.order.remote.DailyMenuNetSoldQueryDTO dto); + /** AI 喂数:精简写入演示订单,返回 orderId */ + @NoToken + @PostMapping("/hard-order/inner/ai-feed-create") + R aiFeedCreateOrder(@RequestBody com.bsd.pjcloud.hard.api.dto.order.remote.AiFeedOrderCreateDTO dto); + + /** AI 喂数:批量写入演示订单 */ + @NoToken + @PostMapping("/hard-order/inner/ai-feed-create-batch") + R aiFeedCreateOrdersBatch( + @RequestBody java.util.List dtos); + + /** AI 喂数清理:软删 AIFEED- 订单 */ + @NoToken + @PostMapping("/hard-order/inner/ai-feed-delete") + R aiFeedDeleteOrders(@RequestBody List orderIds); + } diff --git a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java index 9f0a8489..8085d1fb 100644 --- a/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java +++ b/pjcloud-hardware/pjcloud-hardware-biz/src/main/java/com/bsd/pjcloud/hard/controller/order/HardOrderController.java @@ -37,6 +37,7 @@ import com.bsd.pjcloud.hard.api.dto.order.app.HardAppCreateOrderDto; import com.bsd.pjcloud.hard.api.dto.order.app.HardAppCreateReserveOrdersDto; import com.bsd.pjcloud.hard.api.dto.order.app.HardAppCreateShopOrderDto; import com.bsd.pjcloud.hard.api.dto.order.app.HardAppCreateTakeOutOrderDto; +import com.bsd.pjcloud.hard.api.dto.order.remote.AiFeedOrderCreateDTO; import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateShopStatusDto; import com.bsd.pjcloud.hard.api.dto.order.remote.HardUpdateTakeOutStatusDto; import com.bsd.pjcloud.hard.api.dto.order.remote.DailyMenuNetSoldQueryDTO; @@ -99,6 +100,7 @@ public class HardOrderController { private final RemoteMchService mchService; private final RemoteUserService userService; + private final AiFeedOrderService aiFeedOrderService; /* ************************************* start 远程调用 start ************************************* */ @Inner @@ -153,6 +155,33 @@ public class HardOrderController { public R> listDailyMenuNetSold(@RequestBody DailyMenuNetSoldQueryDTO dto) { return R.ok(hardwareOrderService.listDailyMenuNetSold(dto)); } + + @Inner + @Operation(summary = "AI喂数创建演示订单", description = "精简写入 hard_order/hard_car,不做完整库存链路") + @PostMapping("/inner/ai-feed-create") + public R aiFeedCreateOrder(@RequestBody AiFeedOrderCreateDTO dto) { + try { + return R.ok(aiFeedOrderService.createDemoOrder(dto)); + } + catch (IllegalArgumentException ex) { + return R.failed(ex.getMessage()); + } + } + + @Inner + @Operation(summary = "AI喂数批量创建演示订单") + @PostMapping("/inner/ai-feed-create-batch") + public R aiFeedCreateOrdersBatch( + @RequestBody List dtos) { + return R.ok(aiFeedOrderService.createDemoOrdersBatch(dtos)); + } + + @Inner + @Operation(summary = "AI喂数删除演示订单", description = "仅软删 orderNo 以 AIFEED- 开头的订单") + @PostMapping("/inner/ai-feed-delete") + public R aiFeedDeleteOrders(@RequestBody List orderIds) { + return R.ok(aiFeedOrderService.softDeleteByIds(orderIds)); + } /* ************************************* end 远程调用 ************************************* */ /* ************************************* start app start ************************************* */ diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java index b17eb33b..b50df70c 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java @@ -309,4 +309,23 @@ public interface RemoteUserService { @GetMapping("/user/getUserListByRoleCode") R> getUserListByRoleCode(@RequestParam("code") String code); + /** + * AI 喂数:创建普通用户(手机号=账号,姓名=昵称,角色 GENERAL_USER + WALLET_AUTO_CREATE)。 + * 手机号已存在则返回已有 userId。 + */ + @NoToken + @PostMapping("/user/inner/ai-feed-create") + R aiFeedCreateUser(@RequestBody com.bsd.pjcloud.admin.api.dto.AiFeedUserCreateDTO dto); + + /** AI 喂数:批量建用户 */ + @NoToken + @PostMapping("/user/inner/ai-feed-create-batch") + R aiFeedCreateUsersBatch( + @RequestBody java.util.List users); + + /** AI 喂数清理:软删演示用户 */ + @NoToken + @PostMapping("/user/inner/ai-feed-delete") + R aiFeedDeleteUsers(@RequestBody List userIds); + } diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java index 040cfae4..df21687f 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java @@ -761,6 +761,44 @@ public class SysUserController { return R.ok(userService.list(Wrappers.lambdaQuery().in(SysUser::getUserId, userIds))); } + /** + * AI 喂数:按手机号创建普通用户;已存在则直接返回 userId。 + * 角色固定 GENERAL_USER + WALLET_AUTO_CREATE;不异步开钱包。 + */ + @Inner + @PostMapping("/inner/ai-feed-create") + public R aiFeedCreateUser(@RequestBody com.bsd.pjcloud.admin.api.dto.AiFeedUserCreateDTO dto) { + if (dto == null || StrUtil.isBlank(dto.getPhone()) || StrUtil.isBlank(dto.getName()) || dto.getDeptId() == null) { + return R.failed("phone/name/deptId 不能为空"); + } + try { + return R.ok(userService.aiFeedCreateOrGetUser(dto.getPhone(), dto.getName(), dto.getDeptId())); + } + catch (Exception ex) { + return R.failed(StrUtil.blankToDefault(ex.getMessage(), "创建用户失败")); + } + } + + /** AI 喂数:批量建用户 */ + @Inner + @PostMapping("/inner/ai-feed-create-batch") + public R aiFeedCreateUsersBatch( + @RequestBody List users) { + try { + return R.ok(userService.aiFeedCreateUsersBatch(users)); + } + catch (Exception ex) { + return R.failed(StrUtil.blankToDefault(ex.getMessage(), "批量创建用户失败")); + } + } + + /** AI 喂数清理:软删演示用户(含角色/身份/钱包,跳过余额校验) */ + @Inner + @PostMapping("/inner/ai-feed-delete") + public R aiFeedDeleteUsers(@RequestBody List userIds) { + return userService.aiFeedDeleteUsers(userIds); + } + /** * 按用户 ID 批量查询用户、部门及身份协议编码。 * diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserMapper.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserMapper.java index d2212090..0f541bd1 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserMapper.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserMapper.java @@ -92,4 +92,10 @@ public interface SysUserMapper extends PjcloudBaseMapper { * 用户在/非编人员统计 */ List staffingListByDept(@Param("dto") UserDTO dto, DataScope dataScope); + + /** + * AI 喂数:多行 INSERT 用户(自行带 tenant_id,忽略租户插件改写) + */ + @com.baomidou.mybatisplus.annotation.InterceptorIgnore(tenantLine = "true") + int insertAiFeedBatch(@Param("list") List list); } diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserRoleMapper.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserRoleMapper.java index a95c6542..ccc2b8d3 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserRoleMapper.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserRoleMapper.java @@ -21,7 +21,11 @@ package com.bsd.pjcloud.admin.mapper; import com.bsd.pjcloud.admin.api.entity.SysUserRole; import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** *

@@ -34,4 +38,15 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface SysUserRoleMapper extends PjcloudBaseMapper { + /** AI 喂数:多行插入用户角色 */ + @Insert(""" + + """) + int insertAiFeedBatch(@Param("list") List list); + } diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java index d4ac5c09..b828b9b2 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java @@ -159,6 +159,26 @@ public interface SysUserService extends IService { */ R lockUser(String username); + /** + * AI 喂数清理:软删用户及关联角色/身份/关系/钱包(不校验余额与流水,演示数据专用)。 + * + * @param userIds 用户 ID 列表 + * @return 是否成功 + */ + R aiFeedDeleteUsers(List userIds); + + /** + * AI 喂数专用:按手机号创建用户(独立实现,不走 saveUser/doSaveUser)。 + * 固定 GENERAL_USER + WALLET_AUTO_CREATE;已存在则返回原 userId;不发异步开钱包事件。 + */ + Long aiFeedCreateOrGetUser(String phone, String name, Long deptId); + + /** + * AI 喂数批量建用户(独立实现)。同批复用同一密码哈希,已存在手机号直接复用。 + */ + com.bsd.pjcloud.admin.api.dto.AiFeedUserBatchResultDTO aiFeedCreateUsersBatch( + java.util.List users); + /** * 修改密码 * diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java index 13e502b0..35873a60 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserServiceImpl.java @@ -38,6 +38,8 @@ import com.bsd.pjcloud.admin.api.constant.UserExpireEnum; import com.bsd.pjcloud.admin.api.constant.UserStaffingTypeEnum; import com.bsd.pjcloud.admin.api.constant.UserStateEnum; import com.bsd.pjcloud.admin.api.constant.user.UserIdentityTypeEnum; +import com.bsd.pjcloud.admin.api.dto.AiFeedUserBatchResultDTO; +import com.bsd.pjcloud.admin.api.dto.AiFeedUserCreateDTO; import com.bsd.pjcloud.admin.api.dto.ForgotPasswordDTO; import com.bsd.pjcloud.admin.api.dto.SysUserFaceDto; import com.bsd.pjcloud.admin.api.dto.UserDTO; @@ -215,6 +217,129 @@ public class SysUserServiceImpl extends ServiceImpl impl } } + /** 演示用户统一初始密码(仅喂数路径),整批只 BCrypt 一次 */ + private static final String AI_FEED_DEFAULT_PASSWORD = "AiFeed@123456"; + + /** + * AI 喂数专用建用户(独立实现,不改动 {@link #doSaveUser} 业务路径)。 + * 固定 GENERAL_USER + WALLET_AUTO_CREATE;不发异步开钱包事件(钱包由喂数侧同步开通)。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Long aiFeedCreateOrGetUser(String phone, String name, Long deptId) { + AiFeedUserCreateDTO dto = new AiFeedUserCreateDTO(); + dto.setPhone(phone); + dto.setName(name); + dto.setDeptId(deptId); + AiFeedUserBatchResultDTO batch = aiFeedCreateUsersBatch(List.of(dto)); + if (batch == null || CollUtil.isEmpty(batch.getItems()) || batch.getItems().get(0).getUserId() == null) { + throw new IllegalArgumentException("创建用户失败"); + } + return batch.getItems().get(0).getUserId(); + } + + /** + * AI 喂数批量建用户:一次查已存在手机号,整批共用密码哈希,用户/角色一次性 saveBatch。 + * name 为空时仅按手机号查已有用户(订单行引用已导入用户),不新建。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AiFeedUserBatchResultDTO aiFeedCreateUsersBatch(List users) { + AiFeedUserBatchResultDTO result = new AiFeedUserBatchResultDTO(); + if (CollUtil.isEmpty(users)) { + return result; + } + // 同批内手机号去重,后者覆盖前者姓名/部门 + Map unique = new LinkedHashMap<>(); + for (AiFeedUserCreateDTO u : users) { + if (u == null || StrUtil.isBlank(u.getPhone())) { + continue; + } + AiFeedUserCreateDTO copy = new AiFeedUserCreateDTO(); + copy.setPhone(StrUtil.trim(u.getPhone())); + copy.setName(StrUtil.isBlank(u.getName()) ? null : StrUtil.trim(u.getName())); + copy.setDeptId(u.getDeptId()); + unique.put(copy.getPhone(), copy); + } + if (unique.isEmpty()) { + return result; + } + List phones = new ArrayList<>(unique.keySet()); + Map existMap = baseMapper.selectList(Wrappers.lambdaQuery().in(SysUser::getPhone, phones)) + .stream() + .collect(Collectors.toMap(SysUser::getPhone, u -> u, (a, b) -> a)); + + List toCreate = new ArrayList<>(); + List pendingCreated = new ArrayList<>(); + LocalDateTime now = LocalDateTime.now(); + for (AiFeedUserCreateDTO u : unique.values()) { + AiFeedUserBatchResultDTO.Item item = new AiFeedUserBatchResultDTO.Item(); + item.setPhone(u.getPhone()); + SysUser exist = existMap.get(u.getPhone()); + if (exist != null) { + item.setUserId(exist.getUserId()); + item.setCreated(Boolean.FALSE); + result.getItems().add(item); + continue; + } + if (StrUtil.isBlank(u.getName()) || u.getDeptId() == null) { + continue; + } + SysUser sysUser = new SysUser(); + // 预分配雪花 ID,便于随后批量写角色关系 + sysUser.setUserId(com.baomidou.mybatisplus.core.toolkit.IdWorker.getId()); + sysUser.setUsername(u.getPhone()); + sysUser.setPhone(u.getPhone()); + sysUser.setName(u.getName()); + sysUser.setNickname(u.getName()); + sysUser.setDeptId(u.getDeptId()); + sysUser.setLockFlag(UserStateEnum.NORMAL.getCode()); + sysUser.setProcessStatus(TaskCheckStatusEnum.RESULT.getCode()); + sysUser.setProcessTime(now); + sysUser.setCreateBy(u.getPhone()); + sysUser.setUpdateBy(u.getPhone()); + sysUser.setPasswordModifyTime(now); + sysUser.setPasswordExpireFlag(UserExpireEnum.EXPIRE.getCode()); + toCreate.add(sysUser); + item.setUserId(sysUser.getUserId()); + item.setCreated(Boolean.TRUE); + pendingCreated.add(item); + } + if (!toCreate.isEmpty()) { + List roleIds = getNonAdminNewUserRoleIds(); + String encodedPwd = ENCODER.encode(AI_FEED_DEFAULT_PASSWORD); + Long tenantId = TenantContextHolder.getTenantId(); + LocalDateTime createTime = LocalDateTime.now(); + for (SysUser sysUser : toCreate) { + sysUser.setPassword(encodedPwd); + sysUser.setDelFlag(CommonConstants.STATUS_NORMAL); + sysUser.setTenantId(tenantId); + sysUser.setCreateTime(createTime); + sysUser.setUpdateTime(createTime); + } + // 多行 INSERT,避免无 rewriteBatchedStatements 时 saveBatch 仍接近逐条写库 + for (List chunk : CollUtil.split(toCreate, 200)) { + baseMapper.insertAiFeedBatch(chunk); + } + List roles = new ArrayList<>(toCreate.size() * Math.max(roleIds.size(), 1)); + for (SysUser sysUser : toCreate) { + for (Long roleId : roleIds) { + SysUserRole userRole = new SysUserRole(); + userRole.setUserId(sysUser.getUserId()); + userRole.setRoleId(roleId); + roles.add(userRole); + } + } + if (!roles.isEmpty()) { + for (List chunk : CollUtil.split(roles, 500)) { + sysUserRoleMapper.insertAiFeedBatch(chunk); + } + } + result.getItems().addAll(pendingCreated); + } + return result; + } + /** * 解析当前操作人的角色上下文,供一次或一批用户创建复用。 * @@ -970,6 +1095,51 @@ public class SysUserServiceImpl extends ServiceImpl impl return R.ok(); } + /** + * AI 喂数清理:软删演示用户。跳过钱包余额/流水校验,避免充值后无法清理。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public R aiFeedDeleteUsers(List userIds) { + if (CollUtil.isEmpty(userIds)) { + return R.ok(true); + } + List distinctIds = userIds.stream().filter(Objects::nonNull).distinct().toList(); + if (CollUtil.isEmpty(distinctIds)) { + return R.ok(true); + } + List userList = baseMapper.selectBatchIds(distinctIds); + if (CollUtil.isEmpty(userList)) { + return R.ok(true); + } + Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); + for (SysUser sysUser : userList) { + if (cache != null && StrUtil.isNotBlank(sysUser.getUsername())) { + cache.evict(sysUser.getUsername()); + } + } + List existIds = userList.stream().map(SysUser::getUserId).toList(); + sysUserRoleMapper.delete(Wrappers.lambdaQuery().in(SysUserRole::getUserId, existIds)); + sysUserRelationService.deleteUserRelationByUserIds(existIds); + sysUserIdentityService.deleteUserIdentityByUserIds(existIds); + this.removeBatchByIds(existIds); + try { + remoteWalletService.delWallet(existIds); + } + catch (Exception ex) { + log.warn("AI喂数清理钱包失败 userIds={}", existIds, ex); + } + for (Long userId : existIds) { + try { + remoteWalletService.userChange(userId, Boolean.TRUE); + } + catch (Exception ex) { + log.warn("AI喂数清理钱包同步失败 userId={}", userId, ex); + } + } + return R.ok(true); + } + @Override @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username") public R changePassword(UserDTO userDto) { diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml index edd7d48b..600e94c9 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserMapper.xml @@ -562,4 +562,22 @@ order by a.dept_id + + + INSERT INTO sys_user ( + user_id, username, password, phone, name, nickname, dept_id, + lock_flag, password_expire_flag, password_modify_time, + process_status, process_time, create_by, update_by, create_time, update_time, + del_flag, tenant_id + ) VALUES + + ( + #{u.userId}, #{u.username}, #{u.password}, #{u.phone}, #{u.name}, #{u.nickname}, #{u.deptId}, + #{u.lockFlag}, #{u.passwordExpireFlag}, #{u.passwordModifyTime}, + #{u.processStatus}, #{u.processTime}, #{u.createBy}, #{u.updateBy}, #{u.createTime}, #{u.updateTime}, + #{u.delFlag}, #{u.tenantId} + ) + + + diff --git a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletService.java b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletService.java index 28219d79..92e43ed8 100644 --- a/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletService.java +++ b/pjcloud-wallet/pjcloud-wallet-api/src/main/java/com/bsd/pjcloud/wallet/api/feign/RemoteWalletService.java @@ -25,6 +25,17 @@ public interface RemoteWalletService { @GetMapping("/wallet-manage/get-or-create-wallet") R getOrCreateWallet(@RequestParam Long userId, @RequestParam(required = false) WalletTypeEnum walletTypeCode); + /** AI 喂数:确保饭卡钱包(免角色校验、免绑卡) */ + @NoToken + @GetMapping("/wallet-manage/inner/ai-feed-ensure-eat") + R aiFeedEnsureEatWallet(@RequestParam Long userId); + + /** AI 喂数:批量开饭卡并设定余额 */ + @NoToken + @PostMapping("/wallet-manage/inner/ai-feed-ensure-eat-batch") + R aiFeedEnsureEatWalletsBatch( + @RequestBody java.util.List reqs); + @NoToken @GetMapping("/wallet-manage/get-wallet") R getWallet(@RequestParam Long userId, @RequestParam WalletTypeEnum walletTypeCode); diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletManageController.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletManageController.java index d6fb7a5c..ea195eeb 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletManageController.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/controller/WalletManageController.java @@ -80,6 +80,25 @@ public class WalletManageController { return R.ok(wallet); } + /** AI 喂数:确保饭卡钱包(免角色校验、免绑卡) */ + @Inner + @GetMapping("/inner/ai-feed-ensure-eat") + public R aiFeedEnsureEatWallet(@RequestParam Long userId) { + WalletManageEntity wallet = walletManageService.aiFeedEnsureEatWallet(userId); + if (ObjectUtil.isEmpty(wallet)) { + return R.failed("开通饭卡钱包失败"); + } + return R.ok(wallet); + } + + /** AI 喂数:批量开饭卡并设定余额 */ + @Inner + @PostMapping("/inner/ai-feed-ensure-eat-batch") + public R aiFeedEnsureEatWalletsBatch( + @RequestBody List reqs) { + return R.ok(walletManageService.aiFeedEnsureEatWalletsBatch(reqs)); + } + @Inner @GetMapping("/get-wallet-by-wallet-id") public R getWalletByWalletTypeId(@RequestParam Long userId, @RequestParam Long walletTypeId) { diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java index b3f87d63..c87e7ec5 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/mapper/WalletManageMapper.java @@ -25,6 +25,27 @@ import java.util.List; @Mapper public interface WalletManageMapper extends PjcloudBaseMapper { + /** + * AI 喂数:多行插入饭卡钱包(自行带 tenant_id) + */ + @com.baomidou.mybatisplus.annotation.InterceptorIgnore(tenantLine = "true") + @org.apache.ibatis.annotations.Insert(""" + + """) + int insertAiFeedBatch(@Param("list") List list); + /** * 构建钱包账户通用筛选条件,并以钱包主键作为最终排序条件保证分片导出顺序稳定。 * diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletManageService.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletManageService.java index 42430b34..a1cd8351 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletManageService.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/WalletManageService.java @@ -55,6 +55,20 @@ public interface WalletManageService extends IService { */ WalletManageEntity getOrCreateWallet(@NotNull Long userId, @NotNull WalletTypeEnum walletTypeCode); + /** + * AI 喂数:确保饭卡钱包存在。跳过 WALLET_AUTO_CREATE 角色校验,且不自动绑卡(避免抢卡锁拖慢导入)。 + * + * @param userId 用户 ID + * @return 饭卡钱包;用户不存在或类型未配置时返回 null + */ + WalletManageEntity aiFeedEnsureEatWallet(@NotNull Long userId); + + /** + * AI 喂数批量:确保饭卡并直接设定余额(不走充值流水,演示加速)。 + */ + com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO aiFeedEnsureEatWalletsBatch( + java.util.List reqs); + /** * 获取钱包(没有缓存) */ diff --git a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletManageServiceImpl.java b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletManageServiceImpl.java index 38307d0e..fe247c55 100644 --- a/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletManageServiceImpl.java +++ b/pjcloud-wallet/pjcloud-wallet-biz/src/main/java/com/bsd/pjcloud/wallet/service/impl/WalletManageServiceImpl.java @@ -60,7 +60,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; /** * 钱包管理 @@ -218,6 +220,165 @@ public class WalletManageServiceImpl extends ServiceImpl历史演示导入可能留下同用户多条饭卡钱包;此处用 list+取最早一条, + * 避免调用业务 {@link #getWallet} 的 selectOne 在重复数据上直接报错。

+ */ + @Override + public WalletManageEntity aiFeedEnsureEatWallet(Long userId) { + if (userId == null) { + return null; + } + WalletTypeEntity eatType = walletTypeMapper.selectOne(new LambdaQueryWrapper() + .eq(WalletTypeEntity::getUniqueCode, WalletTypeEnum.EAT.getCode()) + .last("LIMIT 1")); + if (ObjectUtil.isEmpty(eatType)) { + log.warn("|{}|AI喂数开通饭卡失败,userId:{},EAT钱包类型未配置", TAG, userId); + return null; + } + WalletManageEntity existing = findFirstEatWallet(userId, eatType.getId()); + if (ObjectUtil.isNotEmpty(existing)) { + return existing; + } + UserInfo user = userService.getInnerByUserId(userId).getData(); + if (ObjectUtil.isEmpty(user) || ObjectUtil.isEmpty(user.getSysUser())) { + log.warn("|{}|AI喂数开通饭卡失败,userId:{},用户不存在", TAG, userId); + return null; + } + Long deptId = user.getSysUser().getDeptId(); + WalletManageEntity insertObj = new WalletManageEntity(); + insertObj.setUserId(userId); + insertObj.setWalletTypeId(eatType.getId()); + insertObj.setDeptId(deptId); + try { + baseMapper.insert(insertObj); + return insertObj; + } + catch (Exception ex) { + WalletManageEntity again = findFirstEatWallet(userId, eatType.getId()); + if (ObjectUtil.isNotEmpty(again)) { + return again; + } + log.warn("|{}|AI喂数开通饭卡失败,userId:{},msg:{}", TAG, userId, ex.getMessage()); + return null; + } + } + + /** AI 喂数内部:同用户多条饭卡时取最早一条,不抛 TooManyResultsException */ + private WalletManageEntity findFirstEatWallet(Long userId, Long eatTypeId) { + List list = baseMapper.selectList(new LambdaQueryWrapper() + .eq(WalletManageEntity::getUserId, userId) + .eq(WalletManageEntity::getWalletTypeId, eatTypeId) + .orderByAsc(WalletManageEntity::getId) + .last("LIMIT 1")); + return ObjectUtil.isEmpty(list) ? null : list.get(0); + } + + /** + * AI 喂数批量开饭卡:一次查类型、一次查出已有钱包,缺失则插入,并按需直接写余额。 + */ + @Override + public com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO aiFeedEnsureEatWalletsBatch( + List reqs) { + com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO result = + new com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO(); + if (ObjectUtil.isEmpty(reqs)) { + return result; + } + WalletTypeEntity eatType = walletTypeMapper.selectOne(new LambdaQueryWrapper() + .eq(WalletTypeEntity::getUniqueCode, WalletTypeEnum.EAT.getCode()) + .last("LIMIT 1")); + if (ObjectUtil.isEmpty(eatType)) { + log.warn("|{}|AI喂数批量开饭卡失败,EAT钱包类型未配置", TAG); + return result; + } + List userIds = reqs.stream() + .filter(Objects::nonNull) + .map(com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureDTO::getUserId) + .filter(Objects::nonNull) + .distinct() + .toList(); + if (userIds.isEmpty()) { + return result; + } + Map walletByUser = baseMapper.selectList(new LambdaQueryWrapper() + .in(WalletManageEntity::getUserId, userIds) + .eq(WalletManageEntity::getWalletTypeId, eatType.getId()) + .orderByAsc(WalletManageEntity::getId)) + .stream() + .collect(Collectors.toMap(WalletManageEntity::getUserId, w -> w, (a, b) -> a)); + + Map balanceByUser = new HashMap<>(); + Map deptByUser = new HashMap<>(); + for (com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureDTO req : reqs) { + if (req == null || req.getUserId() == null) { + continue; + } + if (req.getBalanceFen() != null) { + balanceByUser.put(req.getUserId(), Math.max(req.getBalanceFen(), 0)); + } + if (req.getDeptId() != null) { + deptByUser.put(req.getUserId(), req.getDeptId()); + } + } + + List toInsert = new ArrayList<>(); + List toUpdate = new ArrayList<>(); + Long tenantId = TenantContextHolder.getTenantId(); + LocalDateTime now = LocalDateTime.now(); + for (Long userId : userIds) { + WalletManageEntity wallet = walletByUser.get(userId); + if (wallet == null) { + wallet = new WalletManageEntity(); + wallet.setId(com.baomidou.mybatisplus.core.toolkit.IdWorker.getId()); + wallet.setUserId(userId); + wallet.setWalletTypeId(eatType.getId()); + wallet.setDeptId(deptByUser.get(userId)); + Integer bal = balanceByUser.get(userId); + wallet.setBalance(bal == null ? 0 : bal); + wallet.setTotalRecharge(bal != null && bal > 0 ? bal : 0); + wallet.setTotalExpense(0); + wallet.setFreezePrice(0); + wallet.setDelFlag(com.bsd.pjcloud.common.core.constant.CommonConstants.STATUS_NORMAL); + wallet.setTenantId(tenantId); + wallet.setCreateBy("ai-feed"); + wallet.setUpdateBy("ai-feed"); + wallet.setCreateTime(now); + wallet.setUpdateTime(now); + toInsert.add(wallet); + walletByUser.put(userId, wallet); + } + else if (balanceByUser.containsKey(userId)) { + Integer bal = balanceByUser.get(userId); + wallet.setBalance(bal == null ? 0 : bal); + toUpdate.add(wallet); + } + } + if (!toInsert.isEmpty()) { + // 多行 INSERT,一次 SQL 写入,避免 saveBatch 在无 rewriteBatchedStatements 时仍接近逐条 + for (List chunk : cn.hutool.core.collection.CollUtil.split(toInsert, 200)) { + baseMapper.insertAiFeedBatch(chunk); + } + } + // 已有饭卡不再逐条改余额(喂数侧只给新建卡写 balance) + if (!toUpdate.isEmpty()) { + this.updateBatchById(toUpdate, 100); + } + for (Long userId : userIds) { + WalletManageEntity wallet = walletByUser.get(userId); + if (wallet == null || wallet.getId() == null) { + continue; + } + com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO.Item item = + new com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO.Item(); + item.setUserId(userId); + item.setWalletId(wallet.getId()); + result.getItems().add(item); + } + return result; + } + @Override public WalletManageEntity getWallet(Long userId, WalletTypeEnum walletTypeCode) { WalletTypeEntity walletType = walletTypeService.getByUniqueCode(walletTypeCode);