会员批量导入功能
parent
e17411b470
commit
39c8a49d43
|
|
@ -53,7 +53,9 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
|
|||
}
|
||||
|
||||
default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
|
||||
return selectList(CouponTemplateDO::getTakeType, takeType, CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus());
|
||||
return selectList(new LambdaQueryWrapperX<CouponTemplateDO>()
|
||||
.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
|
||||
.eq(CouponTemplateDO::getTakeType, takeType));
|
||||
}
|
||||
|
||||
default List<CouponTemplateDO> selectList(List<Integer> canTakeTypes, Integer productScope, Long productScopeValue, Integer count) {
|
||||
|
|
|
|||
|
|
@ -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<MemberUserImportRespVO> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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` })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,14 @@
|
|||
<MemberGroupSelect v-model="queryParams.groupId" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-hasPermi="['member:user:create']" @click="openForm('create')">
|
||||
<el-button @click="openForm('create')">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
<el-button @click="handleImport">
|
||||
<Icon class="mr-5px" icon="ep:upload-filled" />
|
||||
导入
|
||||
</el-button>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
|
|
@ -200,11 +204,62 @@
|
|||
<UserBalanceUpdateForm ref="UpdateBalanceFormRef" @success="getList" />
|
||||
<!-- 发送优惠券弹窗 -->
|
||||
<CouponSendForm ref="couponSendFormRef" />
|
||||
<!-- 导入弹窗 -->
|
||||
<el-dialog
|
||||
v-model="importDialogVisible"
|
||||
title="导入会员"
|
||||
width="500px"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="importUrl"
|
||||
:auto-upload="false"
|
||||
:disabled="importLoading"
|
||||
:headers="uploadHeaders"
|
||||
:limit="1"
|
||||
:on-error="submitImportError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitImportSuccess"
|
||||
accept=".xlsx, .xls"
|
||||
drag
|
||||
>
|
||||
<Icon icon="ep:upload" />
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<div class="el-upload__tip">
|
||||
<el-checkbox v-model="updateSupport" />
|
||||
是否更新已经存在的用户数据
|
||||
</div>
|
||||
<span>仅允许导入 xls、xlsx 格式文件。</span>
|
||||
<el-link
|
||||
:underline="false"
|
||||
style="font-size: 12px; vertical-align: baseline"
|
||||
type="primary"
|
||||
@click="downloadTemplate"
|
||||
>
|
||||
下载模板
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<el-button :disabled="importLoading" @click="importDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="importLoading" @click="submitImport" :loading="importLoading">
|
||||
开始导入
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as UserApi from '@/api/member/user'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import download from '@/utils/download'
|
||||
import UserForm from './UserForm.vue'
|
||||
import MemberTagSelect from '@/views/member/tag/components/MemberTagSelect.vue'
|
||||
import MemberLevelSelect from '@/views/member/level/components/MemberLevelSelect.vue'
|
||||
|
|
@ -279,6 +334,78 @@ const openCoupon = () => {
|
|||
}
|
||||
couponSendFormRef.value.open(selectedIds.value)
|
||||
}
|
||||
|
||||
// 导入相关
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
const importDialogVisible = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importLoading = ref(false)
|
||||
const fileList = ref([])
|
||||
const importUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/member/user/import'
|
||||
const uploadHeaders = ref()
|
||||
const updateSupport = ref(0)
|
||||
|
||||
// 打开导入对话框
|
||||
const handleImport = () => {
|
||||
importDialogVisible.value = true
|
||||
fileList.value = []
|
||||
updateSupport.value = 0
|
||||
}
|
||||
|
||||
// 提交导入
|
||||
const submitImport = async () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
// 提交请求
|
||||
uploadHeaders.value = {
|
||||
Authorization: 'Bearer ' + getAccessToken(),
|
||||
'tenant-id': getTenantId()
|
||||
}
|
||||
importLoading.value = true
|
||||
uploadRef.value!.submit()
|
||||
}
|
||||
|
||||
// 文件上传成功
|
||||
const submitImportSuccess = (response: any) => {
|
||||
importLoading.value = false
|
||||
if (response.code !== 0) {
|
||||
message.error(response.msg)
|
||||
return
|
||||
}
|
||||
// 拼接提示语
|
||||
const data = response.data
|
||||
let text = '成功导入数量:' + data.successCount + ';'
|
||||
text += '失败导入数量:' + data.failCount + ';'
|
||||
if (data.failCount > 0) {
|
||||
text += '失败原因:' + data.failReason
|
||||
}
|
||||
message.alert(text)
|
||||
getList() // 重新加载列表
|
||||
importDialogVisible.value = false
|
||||
}
|
||||
|
||||
// 上传错误提示
|
||||
const submitImportError = (): void => {
|
||||
importLoading.value = false
|
||||
message.error('上传失败,请您重新上传!')
|
||||
}
|
||||
|
||||
// 文件数超出提示
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
|
||||
// 下载导入模板
|
||||
const downloadTemplate = async () => {
|
||||
try {
|
||||
const res = await UserApi.importUserTemplate()
|
||||
download.excel(res, '会员导入模板.xlsx')
|
||||
} catch (error) {
|
||||
message.error('下载模板失败:' + error.message)
|
||||
}
|
||||
}
|
||||
/** 操作分发 */
|
||||
const handleCommand = (command: string, row: UserApi.UserVO) => {
|
||||
switch (command) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue