From 39c8a49d439b976e982b866a442aa99cda7a8b3b Mon Sep 17 00:00:00 2001 From: lijun Date: Sat, 14 Feb 2026 23:43:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=9A=E5=91=98=E6=89=B9=E9=87=8F=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mysql/coupon/CouponTemplateMapper.java | 4 +- .../admin/user/MemberUserController.java | 18 ++ .../admin/user/vo/MemberUserImportRespVO.java | 28 +++ .../service/user/MemberUserService.java | 19 ++ .../service/user/MemberUserServiceImpl.java | 208 ++++++++++++++++++ .../src/api/member/user/index.ts | 10 + .../src/views/member/user/index.vue | 129 ++++++++++- 7 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/vo/MemberUserImportRespVO.java diff --git a/yanzhu-module-mall/yanzhu-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java b/yanzhu-module-mall/yanzhu-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java index b368ba6..4fdcf2c 100755 --- a/yanzhu-module-mall/yanzhu-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java +++ b/yanzhu-module-mall/yanzhu-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponTemplateMapper.java @@ -53,7 +53,9 @@ public interface CouponTemplateMapper extends BaseMapperX { } default List selectListByTakeType(Integer takeType) { - return selectList(CouponTemplateDO::getTakeType, takeType, CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()); + return selectList(new LambdaQueryWrapperX() + .eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) + .eq(CouponTemplateDO::getTakeType, takeType)); } default List selectList(List canTakeTypes, Integer productScope, Long productScopeValue, Integer count) { diff --git a/yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/MemberUserController.java b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/MemberUserController.java index 9dbc7ed..cd07609 100644 --- a/yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/MemberUserController.java +++ b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/MemberUserController.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import com.yanzhu.framework.common.pojo.CommonResult; import com.yanzhu.framework.common.pojo.PageResult; import com.yanzhu.module.member.controller.admin.user.vo.*; +import org.springframework.web.multipart.MultipartFile; import com.yanzhu.module.member.convert.user.MemberUserConvert; import com.yanzhu.module.member.dal.dataobject.group.MemberGroupDO; import com.yanzhu.module.member.dal.dataobject.level.MemberLevelDO; @@ -28,6 +29,8 @@ import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; import java.util.stream.Collectors; import static com.yanzhu.framework.common.pojo.CommonResult.success; @@ -128,4 +131,19 @@ public class MemberUserController { return success(MemberUserConvert.INSTANCE.convertPage(pageResult, tags, levels, groups)); } + @PostMapping("/import") + @Operation(summary = "导入会员用户") + @PreAuthorize("@ss.hasPermission('member:user:import')") + public CommonResult importUser(MultipartFile file) { + MemberUserImportRespVO respVO = memberUserService.importUser(file); + return success(respVO); + } + + @GetMapping("/import-template") + @Operation(summary = "下载会员导入模板") + @PreAuthorize("@ss.hasPermission('member:user:import')") + public void downloadImportTemplate(HttpServletResponse response) throws IOException { + memberUserService.downloadImportTemplate(response); + } + } diff --git a/yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/vo/MemberUserImportRespVO.java b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/vo/MemberUserImportRespVO.java new file mode 100644 index 0000000..8d7aed4 --- /dev/null +++ b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/controller/admin/user/vo/MemberUserImportRespVO.java @@ -0,0 +1,28 @@ +package com.yanzhu.module.member.controller.admin.user.vo; + +import lombok.Data; + +/** + * 会员用户导入响应 VO + * + * @author 研筑科技 + */ +@Data +public class MemberUserImportRespVO { + + /** + * 成功导入数量 + */ + private int successCount; + + /** + * 失败导入数量 + */ + private int failCount; + + /** + * 失败原因 + */ + private String failReason; + +} diff --git a/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserService.java b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserService.java index e86573f..a780280 100644 --- a/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserService.java +++ b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserService.java @@ -5,12 +5,15 @@ import com.yanzhu.framework.common.pojo.PageResult; import com.yanzhu.framework.common.validation.Mobile; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserBaseVO; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserCreateRespVO; +import com.yanzhu.module.member.controller.admin.user.vo.MemberUserImportRespVO; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserPageReqVO; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserUpdateReqVO; import com.yanzhu.module.member.controller.app.user.vo.*; import com.yanzhu.module.member.dal.dataobject.user.MemberUserDO; +import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; +import java.io.IOException; import java.util.Collection; import java.util.List; @@ -197,4 +200,20 @@ public interface MemberUserService { */ boolean updateUserPoint(Long userId, Integer point); + /** + * 【管理员】导入会员用户 + * + * @param file Excel文件 + * @return 导入结果 + */ + MemberUserImportRespVO importUser(MultipartFile file); + + /** + * 【管理员】下载会员导入模板 + * + * @param response Http响应 + * @throws IOException IO异常 + */ + void downloadImportTemplate(javax.servlet.http.HttpServletResponse response) throws IOException; + } diff --git a/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserServiceImpl.java b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserServiceImpl.java index ae06987..81ae4be 100644 --- a/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserServiceImpl.java +++ b/yanzhu-module-member/src/main/java/com/yanzhu/module/member/service/user/MemberUserServiceImpl.java @@ -12,14 +12,19 @@ import com.yanzhu.framework.common.util.object.BeanUtils; import com.yanzhu.framework.common.util.servlet.ServletUtils; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserBaseVO; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserCreateRespVO; +import com.yanzhu.module.member.controller.admin.user.vo.MemberUserImportRespVO; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserPageReqVO; import com.yanzhu.module.member.controller.admin.user.vo.MemberUserUpdateReqVO; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.web.multipart.MultipartFile; import com.yanzhu.module.member.controller.app.user.vo.*; import com.yanzhu.module.member.convert.auth.AuthConvert; import com.yanzhu.module.member.convert.user.MemberUserConvert; import com.yanzhu.module.member.dal.dataobject.user.MemberUserDO; import com.yanzhu.module.member.dal.mysql.user.MemberUserMapper; import com.yanzhu.module.member.mq.producer.user.MemberUserProducer; +import com.yanzhu.module.member.service.user.MemberUserService; import com.yanzhu.module.system.api.sms.SmsCodeApi; import com.yanzhu.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import com.yanzhu.module.system.api.social.SocialClientApi; @@ -34,7 +39,9 @@ import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; +import java.io.IOException; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -370,4 +377,205 @@ public class MemberUserServiceImpl implements MemberUserService { return true; } + @Override + @Transactional(rollbackFor = Exception.class) + public MemberUserImportRespVO importUser(MultipartFile file) { + MemberUserImportRespVO respVO = new MemberUserImportRespVO(); + int successCount = 0; + int failCount = 0; + StringBuilder failReason = new StringBuilder(); + + try (Workbook workbook = new XSSFWorkbook(file.getInputStream())) { + Sheet sheet = workbook.getSheetAt(0); + int rowCount = sheet.getPhysicalNumberOfRows(); + + // 跳过表头,从第二行开始读取数据 + for (int i = 1; i < rowCount; i++) { + Row row = sheet.getRow(i); + if (row == null) { + continue; + } + + try { + // 读取手机号 + Cell mobileCell = row.getCell(0); + if (mobileCell == null) { + failCount++; + failReason.append("第").append(i + 1).append("行:手机号为空\n"); + continue; + } + String mobile = mobileCell.getStringCellValue().trim(); + if (mobile.isEmpty()) { + failCount++; + failReason.append("第").append(i + 1).append("行:手机号为空\n"); + continue; + } + + // 读取昵称 + String nickname = ""; + Cell nicknameCell = row.getCell(1); + if (nicknameCell != null) { + nickname = nicknameCell.getStringCellValue().trim(); + } + + // 读取姓名 + String name = ""; + Cell nameCell = row.getCell(2); + if (nameCell != null) { + name = nameCell.getStringCellValue().trim(); + } + + // 读取性别 + Integer sex = null; + Cell sexCell = row.getCell(3); + if (sexCell != null) { + String sexStr = sexCell.getStringCellValue().trim(); + if ("男".equals(sexStr)) { + sex = 1; + } else if ("女".equals(sexStr)) { + sex = 0; + } + } + + // 读取状态 + Byte status = 1; // 默认启用 + Cell statusCell = row.getCell(4); + if (statusCell != null) { + String statusStr = statusCell.getStringCellValue().trim(); + if ("禁用".equals(statusStr)) { + status = 0; + } + } + + // 读取等级ID + Long levelId = null; + Cell levelIdCell = row.getCell(5); + if (levelIdCell != null) { + try { + levelId = (long) levelIdCell.getNumericCellValue(); + } catch (Exception e) { + // 忽略等级ID解析错误 + } + } + + // 读取分组ID + Long groupId = null; + Cell groupIdCell = row.getCell(6); + if (groupIdCell != null) { + try { + groupId = (long) groupIdCell.getNumericCellValue(); + } catch (Exception e) { + // 忽略分组ID解析错误 + } + } + + // 创建会员用户 + MemberUserBaseVO createReqVO = new MemberUserBaseVO(); + createReqVO.setMobile(mobile); + createReqVO.setNickname(nickname); + createReqVO.setName(name); + createReqVO.setSex(sex); + createReqVO.setStatus(status); + createReqVO.setLevelId(levelId); + createReqVO.setGroupId(groupId); + + // 校验手机唯一 + try { + validateMobileUnique(null, mobile); + + // 创建用户 + MemberUserDO user = new MemberUserDO(); + user.setMobile(mobile); + if (status != null) { + user.setStatus(Integer.valueOf(status)); + } + user.setNickname(nickname); + user.setName(name); + user.setSex(sex); + + // 使用手机号码的后6位作为密码 + String password = mobile.substring(mobile.length() - 6); + user.setPassword(encodePassword(password)); + + // 设置注册信息 + user.setRegisterIp(ServletUtils.getClientIP()); + user.setRegisterTerminal(TerminalEnum.H5.getTerminal()); + + // 插入数据库 + memberUserMapper.insert(user); + + // 发送 MQ 消息:用户创建 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + memberUserProducer.sendUserCreateMessage(user.getId()); + } + }); + + successCount++; + } catch (Exception e) { + failCount++; + failReason.append("第").append(i + 1).append("行:").append(e.getMessage()).append("\n"); + } + } catch (Exception e) { + failCount++; + failReason.append("第").append(i + 1).append("行:处理失败,").append(e.getMessage()).append("\n"); + } + } + } catch (Exception e) { + respVO.setFailCount(1); + respVO.setFailReason("文件解析失败:" + e.getMessage()); + return respVO; + } + + respVO.setSuccessCount(successCount); + respVO.setFailCount(failCount); + respVO.setFailReason(failReason.toString()); + return respVO; + } + + @Override + public void downloadImportTemplate(HttpServletResponse response) throws IOException { + // 创建工作簿 + try (Workbook workbook = new XSSFWorkbook()) { + // 创建工作表 + Sheet sheet = workbook.createSheet("会员导入模板"); + + // 创建表头行 + Row headerRow = sheet.createRow(0); + + // 设置表头单元格 + headerRow.createCell(0).setCellValue("手机号"); + headerRow.createCell(1).setCellValue("昵称"); + headerRow.createCell(2).setCellValue("姓名"); + headerRow.createCell(3).setCellValue("性别"); + headerRow.createCell(4).setCellValue("状态"); + headerRow.createCell(5).setCellValue("等级ID"); + headerRow.createCell(6).setCellValue("分组ID"); + + // 创建示例数据行 + Row exampleRow = sheet.createRow(1); + exampleRow.createCell(0).setCellValue("13800138000"); + exampleRow.createCell(1).setCellValue("示例用户1"); + exampleRow.createCell(2).setCellValue("张三"); + exampleRow.createCell(3).setCellValue("男"); + exampleRow.createCell(4).setCellValue("启用"); + exampleRow.createCell(5).setCellValue(1); + exampleRow.createCell(6).setCellValue(1); + + // 自动调整列宽 + for (int i = 0; i < 7; i++) { + sheet.autoSizeColumn(i); + } + + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=member_import_template.xlsx"); + + // 写入响应 + workbook.write(response.getOutputStream()); + response.flushBuffer(); + } + } + } diff --git a/yanzhu-ui/yanzhu-ui-admin-vue3/src/api/member/user/index.ts b/yanzhu-ui/yanzhu-ui-admin-vue3/src/api/member/user/index.ts index e511648..5bacacd 100644 --- a/yanzhu-ui/yanzhu-ui-admin-vue3/src/api/member/user/index.ts +++ b/yanzhu-ui/yanzhu-ui-admin-vue3/src/api/member/user/index.ts @@ -51,3 +51,13 @@ export const updateUserLevel = async (data: any) => { export const updateUserPoint = async (data: any) => { return await request.put({ url: `/member/user/update-point`, data }) } + +// 导入会员用户 +export const importUser = async (data: FormData) => { + return await request.post({ url: `/member/user/import`, data, headers: { 'Content-Type': 'multipart/form-data' } }) +} + +// 下载会员导入模板 +export const importUserTemplate = () => { + return request.download({ url: `/member/user/import-template` }) +} diff --git a/yanzhu-ui/yanzhu-ui-admin-vue3/src/views/member/user/index.vue b/yanzhu-ui/yanzhu-ui-admin-vue3/src/views/member/user/index.vue index e1bc86b..0611be6 100644 --- a/yanzhu-ui/yanzhu-ui-admin-vue3/src/views/member/user/index.vue +++ b/yanzhu-ui/yanzhu-ui-admin-vue3/src/views/member/user/index.vue @@ -58,10 +58,14 @@ - + 新增 + + + 导入 + 搜索 @@ -200,11 +204,62 @@ + + + + +
将文件拖到此处,或点击上传
+ +
+ +