ai批量插用户订单数据
This commit is contained in:
1 parent
aee1fb1647
commit
645bfa21ad
15 files changed
+559
No files matched your search
@@ -5,6 +5,8 @@
|
||||
<runConfiguration name="PjcloudAdminApplication" type="Spring Boot" />
|
||||
<runConfiguration name="CanteenApp" type="Spring Boot" />
|
||||
<runConfiguration name="ThirdPartyApp" type="Spring Boot" />
|
||||
<runConfiguration name="WalletApp" type="Spring Boot" />
|
||||
<runConfiguration name="HardApp" type="Spring Boot" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
+16
@@ -67,4 +67,20 @@ public interface RemoteHardOrderService {
|
||||
R<List<com.bsd.pjcloud.hard.api.vo.order.DailyMenuNetSoldVO>> listDailyMenuNetSold(
|
||||
@RequestBody com.bsd.pjcloud.hard.api.dto.order.remote.DailyMenuNetSoldQueryDTO dto);
|
||||
|
||||
/** AI 喂数:精简写入演示订单,返回 orderId */
|
||||
@NoToken
|
||||
@PostMapping("/hard-order/inner/ai-feed-create")
|
||||
R<Long> aiFeedCreateOrder(@RequestBody com.bsd.pjcloud.hard.api.dto.order.remote.AiFeedOrderCreateDTO dto);
|
||||
|
||||
/** AI 喂数:批量写入演示订单 */
|
||||
@NoToken
|
||||
@PostMapping("/hard-order/inner/ai-feed-create-batch")
|
||||
R<com.bsd.pjcloud.hard.api.dto.order.remote.AiFeedOrderBatchResultDTO> aiFeedCreateOrdersBatch(
|
||||
@RequestBody java.util.List<com.bsd.pjcloud.hard.api.dto.order.remote.AiFeedOrderCreateDTO> dtos);
|
||||
|
||||
/** AI 喂数清理:软删 AIFEED- 订单 */
|
||||
@NoToken
|
||||
@PostMapping("/hard-order/inner/ai-feed-delete")
|
||||
R<Integer> aiFeedDeleteOrders(@RequestBody List<Long> orderIds);
|
||||
|
||||
}
|
||||
+29
@@ -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<List<DailyMenuNetSoldVO>> 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<Long> 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<com.bsd.pjcloud.hard.api.dto.order.remote.AiFeedOrderBatchResultDTO> aiFeedCreateOrdersBatch(
|
||||
@RequestBody List<AiFeedOrderCreateDTO> dtos) {
|
||||
return R.ok(aiFeedOrderService.createDemoOrdersBatch(dtos));
|
||||
}
|
||||
|
||||
@Inner
|
||||
@Operation(summary = "AI喂数删除演示订单", description = "仅软删 orderNo 以 AIFEED- 开头的订单")
|
||||
@PostMapping("/inner/ai-feed-delete")
|
||||
public R<Integer> aiFeedDeleteOrders(@RequestBody List<Long> orderIds) {
|
||||
return R.ok(aiFeedOrderService.softDeleteByIds(orderIds));
|
||||
}
|
||||
/* ************************************* end 远程调用 ************************************* */
|
||||
|
||||
/* ************************************* start app start ************************************* */
|
||||
|
||||
+19
@@ -309,4 +309,23 @@ public interface RemoteUserService {
|
||||
@GetMapping("/user/getUserListByRoleCode")
|
||||
R<List<UserSimpleVo>> getUserListByRoleCode(@RequestParam("code") String code);
|
||||
|
||||
/**
|
||||
* AI 喂数:创建普通用户(手机号=账号,姓名=昵称,角色 GENERAL_USER + WALLET_AUTO_CREATE)。
|
||||
* 手机号已存在则返回已有 userId。
|
||||
*/
|
||||
@NoToken
|
||||
@PostMapping("/user/inner/ai-feed-create")
|
||||
R<Long> aiFeedCreateUser(@RequestBody com.bsd.pjcloud.admin.api.dto.AiFeedUserCreateDTO dto);
|
||||
|
||||
/** AI 喂数:批量建用户 */
|
||||
@NoToken
|
||||
@PostMapping("/user/inner/ai-feed-create-batch")
|
||||
R<com.bsd.pjcloud.admin.api.dto.AiFeedUserBatchResultDTO> aiFeedCreateUsersBatch(
|
||||
@RequestBody java.util.List<com.bsd.pjcloud.admin.api.dto.AiFeedUserCreateDTO> users);
|
||||
|
||||
/** AI 喂数清理:软删演示用户 */
|
||||
@NoToken
|
||||
@PostMapping("/user/inner/ai-feed-delete")
|
||||
R<Boolean> aiFeedDeleteUsers(@RequestBody List<Long> userIds);
|
||||
|
||||
}
|
||||
+38
@@ -761,6 +761,44 @@ public class SysUserController {
|
||||
return R.ok(userService.list(Wrappers.<SysUser>lambdaQuery().in(SysUser::getUserId, userIds)));
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 喂数:按手机号创建普通用户;已存在则直接返回 userId。
|
||||
* 角色固定 GENERAL_USER + WALLET_AUTO_CREATE;不异步开钱包。
|
||||
*/
|
||||
@Inner
|
||||
@PostMapping("/inner/ai-feed-create")
|
||||
public R<Long> 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<com.bsd.pjcloud.admin.api.dto.AiFeedUserBatchResultDTO> aiFeedCreateUsersBatch(
|
||||
@RequestBody List<com.bsd.pjcloud.admin.api.dto.AiFeedUserCreateDTO> 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<Boolean> aiFeedDeleteUsers(@RequestBody List<Long> userIds) {
|
||||
return userService.aiFeedDeleteUsers(userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按用户 ID 批量查询用户、部门及身份协议编码。
|
||||
*
|
||||
|
||||
+6
@@ -92,4 +92,10 @@ public interface SysUserMapper extends PjcloudBaseMapper<SysUser> {
|
||||
* 用户在/非编人员统计
|
||||
*/
|
||||
List<UserStaffingTotalVo> staffingListByDept(@Param("dto") UserDTO dto, DataScope dataScope);
|
||||
|
||||
/**
|
||||
* AI 喂数:多行 INSERT 用户(自行带 tenant_id,忽略租户插件改写)
|
||||
*/
|
||||
@com.baomidou.mybatisplus.annotation.InterceptorIgnore(tenantLine = "true")
|
||||
int insertAiFeedBatch(@Param("list") List<SysUser> list);
|
||||
}
|
||||
+15
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -34,4 +38,15 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper
|
||||
public interface SysUserRoleMapper extends PjcloudBaseMapper<SysUserRole> {
|
||||
|
||||
/** AI 喂数:多行插入用户角色 */
|
||||
@Insert("""
|
||||
<script>
|
||||
INSERT INTO sys_user_role (user_id, role_id) VALUES
|
||||
<foreach collection="list" item="r" separator=",">
|
||||
(#{r.userId}, #{r.roleId})
|
||||
</foreach>
|
||||
</script>
|
||||
""")
|
||||
int insertAiFeedBatch(@Param("list") List<SysUserRole> list);
|
||||
|
||||
}
|
||||
+20
@@ -159,6 +159,26 @@ public interface SysUserService extends IService<SysUser> {
|
||||
*/
|
||||
R<Boolean> lockUser(String username);
|
||||
|
||||
/**
|
||||
* AI 喂数清理:软删用户及关联角色/身份/关系/钱包(不校验余额与流水,演示数据专用)。
|
||||
*
|
||||
* @param userIds 用户 ID 列表
|
||||
* @return 是否成功
|
||||
*/
|
||||
R<Boolean> aiFeedDeleteUsers(List<Long> 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<com.bsd.pjcloud.admin.api.dto.AiFeedUserCreateDTO> users);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*
|
||||
|
||||
+170
@@ -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<SysUserMapper, SysUser> 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<AiFeedUserCreateDTO> users) {
|
||||
AiFeedUserBatchResultDTO result = new AiFeedUserBatchResultDTO();
|
||||
if (CollUtil.isEmpty(users)) {
|
||||
return result;
|
||||
}
|
||||
// 同批内手机号去重,后者覆盖前者姓名/部门
|
||||
Map<String, AiFeedUserCreateDTO> 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<String> phones = new ArrayList<>(unique.keySet());
|
||||
Map<String, SysUser> existMap = baseMapper.selectList(Wrappers.<SysUser>lambdaQuery().in(SysUser::getPhone, phones))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(SysUser::getPhone, u -> u, (a, b) -> a));
|
||||
|
||||
List<SysUser> toCreate = new ArrayList<>();
|
||||
List<AiFeedUserBatchResultDTO.Item> 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<Long> 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<SysUser> chunk : CollUtil.split(toCreate, 200)) {
|
||||
baseMapper.insertAiFeedBatch(chunk);
|
||||
}
|
||||
List<SysUserRole> 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<SysUserRole> chunk : CollUtil.split(roles, 500)) {
|
||||
sysUserRoleMapper.insertAiFeedBatch(chunk);
|
||||
}
|
||||
}
|
||||
result.getItems().addAll(pendingCreated);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前操作人的角色上下文,供一次或一批用户创建复用。
|
||||
*
|
||||
@@ -970,6 +1095,51 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 喂数清理:软删演示用户。跳过钱包余额/流水校验,避免充值后无法清理。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Boolean> aiFeedDeleteUsers(List<Long> userIds) {
|
||||
if (CollUtil.isEmpty(userIds)) {
|
||||
return R.ok(true);
|
||||
}
|
||||
List<Long> distinctIds = userIds.stream().filter(Objects::nonNull).distinct().toList();
|
||||
if (CollUtil.isEmpty(distinctIds)) {
|
||||
return R.ok(true);
|
||||
}
|
||||
List<SysUser> 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<Long> existIds = userList.stream().map(SysUser::getUserId).toList();
|
||||
sysUserRoleMapper.delete(Wrappers.<SysUserRole>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) {
|
||||
|
||||
@@ -562,4 +562,22 @@
|
||||
order by a.dept_id
|
||||
</select>
|
||||
|
||||
<!-- AI 喂数多行插入:一次 SQL 写入多用户 -->
|
||||
<insert id="insertAiFeedBatch">
|
||||
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
|
||||
<foreach collection="list" item="u" separator=",">
|
||||
(
|
||||
#{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}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
</mapper>
|
||||
+11
@@ -25,6 +25,17 @@ public interface RemoteWalletService {
|
||||
@GetMapping("/wallet-manage/get-or-create-wallet")
|
||||
R<WalletManageEntity> getOrCreateWallet(@RequestParam Long userId, @RequestParam(required = false) WalletTypeEnum walletTypeCode);
|
||||
|
||||
/** AI 喂数:确保饭卡钱包(免角色校验、免绑卡) */
|
||||
@NoToken
|
||||
@GetMapping("/wallet-manage/inner/ai-feed-ensure-eat")
|
||||
R<WalletManageEntity> aiFeedEnsureEatWallet(@RequestParam Long userId);
|
||||
|
||||
/** AI 喂数:批量开饭卡并设定余额 */
|
||||
@NoToken
|
||||
@PostMapping("/wallet-manage/inner/ai-feed-ensure-eat-batch")
|
||||
R<com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO> aiFeedEnsureEatWalletsBatch(
|
||||
@RequestBody java.util.List<com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureDTO> reqs);
|
||||
|
||||
@NoToken
|
||||
@GetMapping("/wallet-manage/get-wallet")
|
||||
R<WalletManageEntity> getWallet(@RequestParam Long userId, @RequestParam WalletTypeEnum walletTypeCode);
|
||||
|
||||
+19
@@ -80,6 +80,25 @@ public class WalletManageController {
|
||||
return R.ok(wallet);
|
||||
}
|
||||
|
||||
/** AI 喂数:确保饭卡钱包(免角色校验、免绑卡) */
|
||||
@Inner
|
||||
@GetMapping("/inner/ai-feed-ensure-eat")
|
||||
public R<WalletManageEntity> 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<com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureResultDTO> aiFeedEnsureEatWalletsBatch(
|
||||
@RequestBody List<com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureDTO> reqs) {
|
||||
return R.ok(walletManageService.aiFeedEnsureEatWalletsBatch(reqs));
|
||||
}
|
||||
|
||||
@Inner
|
||||
@GetMapping("/get-wallet-by-wallet-id")
|
||||
public R getWalletByWalletTypeId(@RequestParam Long userId, @RequestParam Long walletTypeId) {
|
||||
|
||||
+21
@@ -25,6 +25,27 @@ import java.util.List;
|
||||
@Mapper
|
||||
public interface WalletManageMapper extends PjcloudBaseMapper<WalletManageEntity> {
|
||||
|
||||
/**
|
||||
* AI 喂数:多行插入饭卡钱包(自行带 tenant_id)
|
||||
*/
|
||||
@com.baomidou.mybatisplus.annotation.InterceptorIgnore(tenantLine = "true")
|
||||
@org.apache.ibatis.annotations.Insert("""
|
||||
<script>
|
||||
INSERT INTO wallet_manage (
|
||||
id, user_id, wallet_type_id, dept_id, balance, total_recharge, total_expense, freeze_price,
|
||||
create_by, update_by, create_time, update_time, del_flag, tenant_id
|
||||
) VALUES
|
||||
<foreach collection="list" item="w" separator=",">
|
||||
(
|
||||
#{w.id}, #{w.userId}, #{w.walletTypeId}, #{w.deptId}, #{w.balance}, #{w.totalRecharge},
|
||||
#{w.totalExpense}, #{w.freezePrice}, #{w.createBy}, #{w.updateBy}, #{w.createTime}, #{w.updateTime},
|
||||
#{w.delFlag}, #{w.tenantId}
|
||||
)
|
||||
</foreach>
|
||||
</script>
|
||||
""")
|
||||
int insertAiFeedBatch(@Param("list") List<WalletManageEntity> list);
|
||||
|
||||
/**
|
||||
* 构建钱包账户通用筛选条件,并以钱包主键作为最终排序条件保证分片导出顺序稳定。
|
||||
*
|
||||
|
||||
+14
@@ -55,6 +55,20 @@ public interface WalletManageService extends IService<WalletManageEntity> {
|
||||
*/
|
||||
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<com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureDTO> reqs);
|
||||
|
||||
/**
|
||||
* 获取钱包(没有缓存)
|
||||
*/
|
||||
|
||||
+161
@@ -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<WalletManageMapper, Wal
|
||||
return baseMapper.selectOne(walletQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 喂数专用:只确保饭卡钱包行存在,不做角色校验、不抢卡绑卡。
|
||||
* <p>历史演示导入可能留下同用户多条饭卡钱包;此处用 list+取最早一条,
|
||||
* 避免调用业务 {@link #getWallet} 的 selectOne 在重复数据上直接报错。</p>
|
||||
*/
|
||||
@Override
|
||||
public WalletManageEntity aiFeedEnsureEatWallet(Long userId) {
|
||||
if (userId == null) {
|
||||
return null;
|
||||
}
|
||||
WalletTypeEntity eatType = walletTypeMapper.selectOne(new LambdaQueryWrapper<WalletTypeEntity>()
|
||||
.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<WalletManageEntity> list = baseMapper.selectList(new LambdaQueryWrapper<WalletManageEntity>()
|
||||
.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<com.bsd.pjcloud.wallet.api.dto.AiFeedWalletEnsureDTO> 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<WalletTypeEntity>()
|
||||
.eq(WalletTypeEntity::getUniqueCode, WalletTypeEnum.EAT.getCode())
|
||||
.last("LIMIT 1"));
|
||||
if (ObjectUtil.isEmpty(eatType)) {
|
||||
log.warn("|{}|AI喂数批量开饭卡失败,EAT钱包类型未配置", TAG);
|
||||
return result;
|
||||
}
|
||||
List<Long> 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<Long, WalletManageEntity> walletByUser = baseMapper.selectList(new LambdaQueryWrapper<WalletManageEntity>()
|
||||
.in(WalletManageEntity::getUserId, userIds)
|
||||
.eq(WalletManageEntity::getWalletTypeId, eatType.getId())
|
||||
.orderByAsc(WalletManageEntity::getId))
|
||||
.stream()
|
||||
.collect(Collectors.toMap(WalletManageEntity::getUserId, w -> w, (a, b) -> a));
|
||||
|
||||
Map<Long, Integer> balanceByUser = new HashMap<>();
|
||||
Map<Long, Long> 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<WalletManageEntity> toInsert = new ArrayList<>();
|
||||
List<WalletManageEntity> 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<WalletManageEntity> 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);
|
||||
|
||||
Reference in new issue
Block a user