
package com.tmerclub.cloud.user.manager;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.tmerclub.cloud.api.auth.constant.SysTypeEnum;
import com.tmerclub.cloud.api.auth.dto.AuthAccountDTO;
import com.tmerclub.cloud.api.auth.dto.AuthAccountUserDTO;
import com.tmerclub.cloud.api.auth.dto.UserRegisterDTO;
import com.tmerclub.cloud.api.auth.dto.UserRegisterExtensionDTO;
import com.tmerclub.cloud.api.auth.feign.AccountFeignClient;
import com.tmerclub.cloud.api.payment.constant.PaySysType;
import com.tmerclub.cloud.api.platform.feign.ConfigFeignClient;
import com.tmerclub.cloud.api.user.vo.UserManagerVO;
import com.tmerclub.cloud.common.constant.Constant;
import com.tmerclub.cloud.common.database.dto.PageDTO;
import com.tmerclub.cloud.common.database.vo.PageVO;
import com.tmerclub.cloud.common.exception.LuckException;
import com.tmerclub.cloud.common.leaf.manager.SegmentManager;
import com.tmerclub.cloud.common.response.ServerResponseEntity;
import com.tmerclub.cloud.common.security.AuthUserContext;
import com.tmerclub.cloud.common.util.*;
import com.tmerclub.cloud.user.constant.DistributedIdKey;
import com.tmerclub.cloud.user.constant.LevelTypeEnum;
import com.tmerclub.cloud.user.constant.SexTypeEnum;
import com.tmerclub.cloud.user.constant.UserStatusEnum;
import com.tmerclub.cloud.user.dto.*;
import com.tmerclub.cloud.user.handler.UserRowWriterHandler;
import com.tmerclub.cloud.user.listener.ShopCustomerExcelListener;
import com.tmerclub.cloud.user.listener.UserExcelListener;
import com.tmerclub.cloud.user.model.ShopCustomer;
import com.tmerclub.cloud.user.model.User;
import com.tmerclub.cloud.user.model.UserExtension;
import com.tmerclub.cloud.user.service.ShopCustomerService;
import com.tmerclub.cloud.user.service.UserLevelService;
import com.tmerclub.cloud.user.service.UserService;
import com.tmerclub.cloud.user.vo.ShopCustomerExcelVO;
import com.tmerclub.cloud.user.vo.UserExcelVO;
import com.tmerclub.cloud.user.vo.UserLevelVO;
import com.tmerclub.cloud.user.vo.UserVO;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.dubbo.config.annotation.DubboReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * 用户信息导入导出
 *
 * @author cl
 * @date 2021-05-11 09:33:25
 */
@Component
public class UserExcelManager {

    @Autowired
    private UserService userService;

    @Autowired
    private UserLevelService userLevelService;

    @Autowired
    private ThreadPoolExecutor userThreadPoolExecutor;
    @Autowired
    private SegmentManager segmentManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @DubboReference
    private AccountFeignClient accountFeignClient;

    @Autowired
    private ShopCustomerService shopCustomerService;

    @DubboReference
    private ConfigFeignClient configFeignClient;

    /**
     * 定义excel中一个sheet最大的行数 一百万行数据, 必须是PageDTO.MAX_PAGE_SIZE 的整数倍
     */
    public static final long SHEET_MAX_ROWS = 1000000;
    public static final String[] LEVEL_TYPE_MODE = {LevelTypeEnum.ORDINARY_USER.value().toString(), LevelTypeEnum.PAY_USER.value().toString()};
    public static final String[] SEX_MODE = {SexTypeEnum.FEMALE.desc(), SexTypeEnum.MALE.desc()};
    public static final int LEVEL_TYPE_INDEX = 4;
    public static final int LEVEL_NAME_INDEX = 5;
    public static final int SEX_INDEX = 8;
    public static final String FORMAT = "yyyy-MM-dd";
    public static final int YEAR_MONTH_DAY_LENGTH = 10;
    public static final int PHONE_LENGTH = 11;

    private static final Logger log = LoggerFactory.getLogger(UserExcelManager.class);

    /**
     * 导出用户信息
     *
     * @param response       响应
     * @param userManagerDTO 条件查询参数
     */
    public void exportUserInfoExcel(HttpServletResponse response, UserManagerDTO userManagerDTO) {
        PageDTO pageDTO = new PageDTO();
        pageDTO.setPageNum(PageDTO.DEFAULT_PAGE_NUM);
        pageDTO.setPageSize(PageDTO.MAX_PAGE_SIZE);
        userManagerDTO.setShopId(AuthUserContext.get().getTenantId());
        // 是否是商家端 商家端导出客户信息， 平台端导出用户信息
        boolean isShop = Objects.equals(AuthUserContext.get().getSysType(), SysTypeEnum.MULTISHOP.value());
        PageVO<UserManagerVO> userPage = userService.getUserInfoPage(pageDTO, userManagerDTO);
        long total = userPage.getTotal();
        if (total == 0) {
            throw new LuckException("无用户数据导出");
        }
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 总数大于PageDTO.MAX_PAGE_SIZE条
        if (total > PageDTO.MAX_PAGE_SIZE) {
            // 总共可以分多少页
            Integer pages = userPage.getPages();
            // 从第一页开始
            int page = 1;
            ExcelWriter excelWriter = null;
            try {
                // 初始化 ExcelWriter
                excelWriter = isShop ?
                        ExcelUtil.getExcelWriterMerge(response, ShopCustomerExcelVO.EXCEL_NAME, ShopCustomerExcelVO.MERGE_ROW_INDEX, ShopCustomerExcelVO.MERGE_COLUMN_INDEX).build() :
                        ExcelUtil.getExcelWriterMerge(response, UserExcelVO.EXCEL_NAME, UserExcelVO.MERGE_ROW_INDEX, UserExcelVO.MERGE_COLUMN_INDEX).build();
                // 大于 PageDTO.MAX_PAGE_SIZE行，进行分页查询
                // 计算有多少个sheet
                int sheetNum = countSlicesNum(SHEET_MAX_ROWS, total);
                for (int i = 0; i < sheetNum; i++) {
                    // 第i个sheet最多有多少行数据
                    long eachSheetRows = Math.min(total - i * SHEET_MAX_ROWS, SHEET_MAX_ROWS);
                    // 计算每个sheet的最大行数，要分多少次分页查询
                    int slicesNum = countSlicesNum(PageDTO.MAX_PAGE_SIZE.longValue(), eachSheetRows);
                    // 收集好获取到当前sheet的所有准备写入的用户数据
                    List<UserExcelVO> sheetDataList = new ArrayList<>(Constant.INITIAL_CAPACITY);
                    for (int j = 0; j < slicesNum; j++) {
                        if (page > pages) {
                            break;
                        }
                        // 多线程分页查询数据
                        PageDTO pageIndex = new PageDTO();
                        pageIndex.setPageSize(PageDTO.MAX_PAGE_SIZE);
                        // 第几页开始
                        pageIndex.setPageNum(page);
                        int tempPage = page;
                        CompletableFuture<List<UserExcelVO>> userFuture = CompletableFuture.supplyAsync(() -> {
                            RequestContextHolder.setRequestAttributes(requestAttributes);
                            List<UserManagerVO> userInfoList = userService.getUserInfoList(pageIndex, userManagerDTO);
                            List<UserExcelVO> list = BeanUtil.mapAsList(userInfoList, UserExcelVO.class);
                            // 给数组一个序列号
                            list.forEach(item -> item.setSeq(Integer.toString(list.indexOf(item) + (tempPage - 1) * pageIndex.getPageSize() + 1)));
                            return list;
                        }, userThreadPoolExecutor);
                        page++;
                        sheetDataList.addAll(userFuture.get());
                    }
                    // 导出excel
                    int sheetNo = i + 1;
                    WriteSheet writeSheet = isShop ?
                            EasyExcel.writerSheet(ShopCustomerExcelVO.SHEET_NAME + sheetNo).head(ShopCustomerExcelVO.class).build() :
                            EasyExcel.writerSheet(UserExcelVO.SHEET_NAME + sheetNo).head(UserExcelVO.class).build();
                    excelWriter.write(sheetDataList, writeSheet);
                }
            } catch (Exception e) {
                log.error("导出excel失败", e);
            } finally {
                if (excelWriter != null) {
                    excelWriter.finish();
                }
            }
        } else {
            // 小于等于 PageDTO.MAX_PAGE_SIZE行 直接导出
            if (isShop) {
                List<ShopCustomerExcelVO> customerList = BeanUtil.mapAsList(userPage.getList(), ShopCustomerExcelVO.class);
                customerList.forEach(item -> item.setSeq(Integer.toString(customerList.indexOf(item) + 1)));
                ExcelUtil.soleExcel(response, customerList, ShopCustomerExcelVO.EXCEL_NAME, ShopCustomerExcelVO.MERGE_ROW_INDEX, ShopCustomerExcelVO.MERGE_COLUMN_INDEX, ShopCustomerExcelVO.class);
            } else {
                List<UserExcelVO> userList = BeanUtil.mapAsList(userPage.getList(), UserExcelVO.class);
                userList.forEach(item -> item.setSeq(Integer.toString(userList.indexOf(item) + 1)));
                ExcelUtil.soleExcel(response, userList, UserExcelVO.EXCEL_NAME, UserExcelVO.MERGE_ROW_INDEX, UserExcelVO.MERGE_COLUMN_INDEX, UserExcelVO.class);
            }

        }
    }

    /**
     * 下载用户导入模板
     *
     * @param response 响应
     */
    public void downloadModel(HttpServletResponse response) {
        List<UserExcelDTO> list = new ArrayList<>(Constant.INITIAL_CAPACITY);
        list.add(new UserExcelDTO());
        Map<Integer, String[]> map = new HashMap<>(8);
        List<UserLevelVO> userLevelList = userLevelService.list(LevelTypeEnum.PAY_USER.value());
        List<String> levelNameList = userLevelList.stream().map(UserLevelVO::getLevelName).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(levelNameList)) {
            map.put(LEVEL_NAME_INDEX, levelNameList.toArray(new String[]{}));
        }
        map.put(LEVEL_TYPE_INDEX, LEVEL_TYPE_MODE);
        map.put(SEX_INDEX, SEX_MODE);
        ExcelWriter excelWriter = null;
        try {
            // 通联支付会员导入模板去除余额列
            Integer paySysType = configFeignClient.getPaySysType().getData();
            if (Objects.equals(paySysType, PaySysType.ALLINPAY.value())) {
                ExcelWriterBuilder excelWriterMerge = ExcelUtil.getExcelWriterMerge(response, AllinpayUserExcelDTO.EXCEL_NAME, AllinpayUserExcelDTO.MERGE_ROW_INDEX, AllinpayUserExcelDTO.MERGE_COLUMN_INDEX);
                excelWriter = ExcelUtil.getExcelModel(excelWriterMerge, map, 1).useDefaultStyle(false).build();
                if (!CollUtil.isEmpty(list)) {
                    WriteSheet sheetWriter = EasyExcel.writerSheet(AllinpayUserExcelDTO.SHEET_NAME)
                            .registerWriteHandler(new UserRowWriterHandler(true))
                            .head(AllinpayUserExcelDTO.class).build();
                    excelWriter.write(list, sheetWriter);
                }
            } else {
                // 先执行合并策略
                ExcelWriterBuilder excelWriterMerge = ExcelUtil.getExcelWriterMerge(response, UserExcelDTO.EXCEL_NAME, UserExcelDTO.MERGE_ROW_INDEX, UserExcelDTO.MERGE_COLUMN_INDEX);
                // useDefaultStyle false 不需要默认的头部加粗/自动换行样式
                excelWriter = ExcelUtil.getExcelModel(excelWriterMerge, map, 1).useDefaultStyle(false).build();
                // 业务代码
                if (CollUtil.isNotEmpty(list)) {
                    // 进行写入操作
                    WriteSheet sheetWriter = EasyExcel.writerSheet(UserExcelDTO.SHEET_NAME)
                            .registerWriteHandler(new UserRowWriterHandler(false))
                            .head(UserExcelDTO.class).build();
                    excelWriter.write(list, sheetWriter);
                }
            }
        } catch (Exception e) {
            log.error("导出用户信息模板excel失败", e);
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 分片个数，计算总数total分为每片为eachNum的片数
     *
     * @param eachNum 每片的个数
     * @param total   被分片的总数
     * @return 分片个数
     */
    private int countSlicesNum(Long eachNum, Long total) {
        boolean isZero = Objects.isNull(eachNum) || eachNum == 0 || Objects.isNull(total) || total == 0;
        if (isZero) {
            return 0;
        }
        // 分片数
        int pageSize = new BigDecimal(total).divide(new BigDecimal(eachNum), RoundingMode.DOWN).intValue();
        // 余数
        int mod = new BigDecimal(total).divideAndRemainder(new BigDecimal(eachNum))[1].intValue();
        if (mod > 0) {
            pageSize = pageSize + 1;
        }
        return pageSize;
    }

    /**
     * 导入用户
     *
     * @param file
     * @return
     */
    public String importUserExcel(MultipartFile file) {
        try {
            Map<String, List<String>> errorMap = new HashMap<>(16);
            UserExcelListener userExcelListener = new UserExcelListener(this, errorMap);
            EasyExcel.read(file.getInputStream(), UserExcelDTO.class, userExcelListener).sheet().doRead();
            return getUserExportInfo(errorMap);
        } catch (IOException e) {
            throw new LuckException(e.getMessage());
        } catch (ExcelDataConvertException e) {
            throw new LuckException("：数据格式错误");
        }
    }

    /**
     * 处理导入的需要响应的信息
     *
     * @param errorMap 响应信息的集合
     * @return 响应信息
     */
    private String getUserExportInfo(Map<String, List<String>> errorMap) {
        StringBuilder info = new StringBuilder(Constant.INITIAL_CAPACITY);
        List<String> importTotal = errorMap.get(UserExcelListener.IMPORT_ROWS);
        BigDecimal total = new BigDecimal("0");
        if (CollUtil.isNotEmpty(importTotal)) {
            for (String item : importTotal) {
                if (StrUtil.isNotBlank(item)) {
                    total = total.add(new BigDecimal(item));
                }
            }
        }
        info.append("共有： ").append(total.intValue()).append("条数据成功导入").append(StrUtil.LF);
        // 错误信息
        List<String> errorRows = errorMap.get(UserExcelListener.ERROR_ROWS);
        if (CollUtil.isNotEmpty(errorRows)) {
            info.append("用户信息错误行数： ").append(Arrays.toString(errorRows.toArray())).append(StrUtil.LF);
        }
        List<String> errors = errorMap.get(UserExcelListener.OTHER);
        if (CollUtil.isNotEmpty(errors)) {
            for (String error : errors) {
                info.append(error);
            }
        }
        return info.toString();
    }


    /**
     * 根据导入的用户数据，批量注册用户
     *
     * @param list     导入的用户信息
     * @param errorMap 错误信息集合
     */
    public void importExcel(List<UserExcelDTO> list, Map<String, List<String>> errorMap) {
        if (CollUtil.isEmpty(list)) {
            throw new LuckException("解析到0条数据");
        }
        int size = list.size();
        // 全平台统一账户信息
        List<AuthAccountDTO> accountDTOList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<User> userList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        // 用户扩展表信息集合
        List<UserExtension> userExtensionList = new ArrayList<>(Constant.INITIAL_CAPACITY);
        // 普通会员等级列表
        List<UserLevelVO> ordinaryLevels = userLevelService.list(LevelTypeEnum.ORDINARY_USER.value());
        // 付费会员等级列表
        List<UserLevelVO> payLevels = userLevelService.list(LevelTypeEnum.PAY_USER.value());
        Map<String, UserLevelVO> payLevelMap = payLevels.stream().collect(Collectors.toMap(UserLevelVO::getLevelName, u -> u));
        // 集合去重复
        list = list.stream().filter(user -> Objects.nonNull(user.getPhone())).collect(
                Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(UserExcelDTO::getPhone))), ArrayList::new));
        // 第几行数据有误 的集合
        List<Integer> errorRows = new ArrayList<>(Constant.INITIAL_CAPACITY);
        long currentTime = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        this.loadErrorData(list, errorMap, size);
        int row = 1;
        String ipAddr = IpHelper.getIpAddr();
        // 处理数据
        for (UserExcelDTO userExcelDTO : list) {
            row++;
            if (checkInfo(errorRows, row, userExcelDTO)) {
                continue;
            }
            // 会员类型(0普通会员/1付费会员)，不填默认为普通会员
            Integer levelType = Objects.isNull(userExcelDTO.getLevelType()) ? LevelTypeEnum.ORDINARY_USER.value() : userExcelDTO.getLevelType();
            Date vipEndTime = userExcelDTO.getVipEndTime();
            // 如果是付费会员，会员结束时间没填写，则不能导入
            if (Objects.equals(LevelTypeEnum.PAY_USER.value(), levelType) && Objects.isNull(vipEndTime)) {
                errorRows.add(row);
                continue;
            }
            // 成长值
            int growth = new BigDecimal(userExcelDTO.getGrowth()).intValue();
            // 会员等级
            Integer payUserLevel = null;
            if (Objects.equals(levelType, LevelTypeEnum.PAY_USER.value())) {
                levelType = getLevelType(payLevels, currentTime, levelType, vipEndTime);
                // 付费会员等级以导入为准，
                payUserLevel = Constant.USER_LEVEL_INIT;
                String importLevel = userExcelDTO.getLevel();
                if (!payLevelMap.containsKey(importLevel)) {
                    //输入了不存在的会员等级
                    errorRows.add(row);
                    continue;
                }

                payUserLevel = getPayUserLevel(payLevels, payLevelMap, payUserLevel, importLevel);
            }
            // 统一账户Id
            Long userId = segmentManager.getSegmentId(DistributedIdKey.tmerclub_USER);
            // 统一账户表信息
            AuthAccountDTO authAccountDTO = getInitAuthAccountDTO(ipAddr, userExcelDTO, userId);
            accountDTOList.add(authAccountDTO);
            // 用户表信息
            Integer normalLevel = getUserLevelByGrowth(ordinaryLevels, growth);
            User user = getInitUser(userExcelDTO, levelType, vipEndTime, userId, normalLevel, payUserLevel);
            userList.add(user);
            // 用户扩展表信息
            UserExtension userExtension = getInitUserExtension(userExcelDTO, growth, user);
            userExtensionList.add(userExtension);
        }
        // 批量注册用户
        batchRegisterUser(accountDTOList, userList, userExtensionList, errorMap);
        if (CollUtil.isNotEmpty(errorRows)) {
            List<String> collect = errorRows.stream().map(String::valueOf).collect(Collectors.toList());
            errorMap.get(UserExcelListener.ERROR_ROWS).addAll(collect);
        }
    }

    private static Integer getPayUserLevel(List<UserLevelVO> payLevels, Map<String, UserLevelVO> payLevelMap, Integer payUserLevel, String importLevel) {
        for (UserLevelVO payLevel : payLevels) {
            if (payLevel.getLevel() <= payLevelMap.get(importLevel).getLevel()) {
                payUserLevel = payLevel.getLevel();
            }
        }
        return payUserLevel;
    }

    private static boolean checkInfo(List<Integer> errorRows, int row, UserExcelDTO userExcelDTO) {
        // 手机号必须是11位
        String phone = userExcelDTO.getPhone();
        if (StrUtil.isBlank(phone) || phone.length() != PHONE_LENGTH) {
            errorRows.add(row);
            return true;
        }
        // 密码不能为空
        if (StrUtil.isBlank(userExcelDTO.getPassword())) {
            errorRows.add(row);
            return true;
        }
        // 密码格式错误
        if (!PasswordUtil.checkFormat(userExcelDTO.getPassword())) {
            errorRows.add(row);
            return true;
        }
        // 用户昵称最多15个字符
        if (StrUtil.isNotBlank(userExcelDTO.getNickName())
                && userExcelDTO.getNickName().length() > 15) {
            errorRows.add(row);
            return true;
        }
        // 昵称为空时 自动填入u+手机号后四位
        if (StrUtil.isBlank(userExcelDTO.getNickName())) {
            log.info("昵称为空时 自动填入u+手机号后四位");
            userExcelDTO.setNickName("u" + phone.substring(7));
        }
        //余额必须为整数
        if (!NumberUtil.isInteger(userExcelDTO.getBalance())) {
            errorRows.add(row);
            return true;
        }
        //积分必须为整数
        if (!NumberUtil.isInteger(userExcelDTO.getScore())) {
            errorRows.add(row);
            return true;
        }
        //成长值必须为整数
        if (!NumberUtil.isInteger(userExcelDTO.getGrowth())) {
            errorRows.add(row);
            return true;
        }
        return false;
    }

    private static Integer getLevelType(List<UserLevelVO> payLevels, long currentTime, Integer levelType, Date vipEndTime) {
        if (CollUtil.isEmpty(payLevels)) {
            throw new LuckException("请在平台端设置好付费会员的等级，才可以导入付费会员");
        }
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime ldt = LocalDateTime.parse(DateUtil.beginOfDay(vipEndTime).toString(), dtf);
        //获取毫秒数
        long milliSecond = ldt.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (currentTime >= milliSecond) {
            levelType = LevelTypeEnum.ORDINARY_USER.value();
        }
        return levelType;
    }

    /**
     * 过滤用户手机号已经存在的用户
     *
     * @param list 用户数据列表
     * @param size 数据数量
     */
    private void loadErrorData(List<UserExcelDTO> list, Map<String, List<String>> errorMap, int size) {
        List<String> errorPhones = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<String> phones = new ArrayList<>(Constant.INITIAL_CAPACITY);
        for (UserExcelDTO userExcelDTO : list) {
            phones.add(userExcelDTO.getPhone());
        }
        List<UserVO> userVOList = userService.getUserListByPhones(phones);
        phones = userVOList.stream().map(UserVO::getPhone).collect(Collectors.toList());
        Iterator<UserExcelDTO> iterator = list.iterator();
        while (iterator.hasNext()) {
            UserExcelDTO userExcelDTO = iterator.next();
            boolean phone = phones.contains(userExcelDTO.getPhone());
            if (phone) {
                errorPhones.add(userExcelDTO.getPhone());
                iterator.remove();
            } else {
                phones.add(userExcelDTO.getPhone());
            }
        }
        List<String> errorList = errorMap.get(UserExcelListener.OTHER);
        if (CollUtil.isNotEmpty(errorPhones)) {
            errorList.add("手机号码：" + errorPhones + "已存在)");
        }
    }

    /**
     * 批量注册用户
     *
     * @param accountDTOList    账户信息列表
     * @param userList          用户信息列表
     * @param userExtensionList 用户扩展信息列表
     * @param errorMap          错误信息集合
     */
    private void batchRegisterUser(List<AuthAccountDTO> accountDTOList, List<User> userList, List<UserExtension> userExtensionList, Map<String, List<String>> errorMap) {
        if (CollUtil.isEmpty(accountDTOList)) {
            return;
        }
        List<UserRegisterDTO> userRegisterList = BeanUtil.mapAsList(userList, UserRegisterDTO.class);
        List<UserRegisterExtensionDTO> userRegisterExtensionDTOList = BeanUtil.mapAsList(userExtensionList, UserRegisterExtensionDTO.class);
        // 只有先去全平台统一账户那里把账号注册成功了，才可以添加用户信息
        AuthAccountUserDTO authAccountUserDTO = new AuthAccountUserDTO();
        authAccountUserDTO.setAccountDTOList(accountDTOList);
        authAccountUserDTO.setUserRegisterList(userRegisterList);
        authAccountUserDTO.setUserRegisterExtensionDTOList(userRegisterExtensionDTOList);
        ServerResponseEntity<String> userRegisterResponse = accountFeignClient.batchRegisterAccount(authAccountUserDTO);
        if (!userRegisterResponse.isSuccess()) {
            throw new LuckException(userRegisterResponse.getMsg());
        }
        String row = userRegisterResponse.getData();
        errorMap.get(UserExcelListener.IMPORT_ROWS).add(row);
    }

    /**
     * 根据参数初始化用户扩展表信息
     *
     * @param userExcelDTO 导入参数
     * @param growth       成长值
     * @param user         用户信息
     * @return 用户扩展表信息
     */
    private UserExtension getInitUserExtension(UserExcelDTO userExcelDTO, int growth, User user) {
        UserExtension userExtension = new UserExtension();
        // 元转分
        BigDecimal price = new BigDecimal(userExcelDTO.getBalance());
        BigDecimal maxAmount = new BigDecimal(Long.valueOf(PriceUtil.MAX_AMOUNT).toString());
        if (price.compareTo(maxAmount) > 0) {
            price = maxAmount;
        }
        BigDecimal zero = new BigDecimal("0");
        if (zero.compareTo(price) > 0) {
            price = zero;
        }
        userExtension.setBalance(PriceUtil.toLongCent(price));
        userExtension.setActualBalance(userExtension.getBalance());
        userExtension.setGrowth(growth);
        userExtension.setLevel(user.getLevel());
        userExtension.setLevelType(user.getLevelType());
        userExtension.setScore(new BigDecimal(userExcelDTO.getScore()).longValue());
        userExtension.setVersion(0);
        userExtension.setSignDay(0);
        userExtension.setVipLevel(user.getVipLevel());
        userExtension.setUserId(user.getUserId());
        userExtension.setAllinpayBalance(0L);
        userExtension.setAllinpayActualBalance(0L);
        userExtension.setAllinpayRealNameSet(0);
        userExtension.setAllinpayPhoneBind(0);
        userExtension.setAllinpayPayPwdSet(0);
        userExtension.setAllinpayProtocolSign(0);
        userExtension.setAllinpayPayAcctBind(0);
        return userExtension;
    }

    /**
     * 根据参数初始化用户信息
     *
     * @param userExcelDTO 导入参数
     * @param levelType    会员类型
     * @param vipEndTime   vip结束时间
     * @param userId       用户id
     * @param normalLevel  普通会员等级
     * @param payUserLevel 付费会员等级
     * @return 用户信息
     */
    private User getInitUser(UserExcelDTO userExcelDTO, Integer levelType, Date vipEndTime, Long userId, Integer normalLevel, Integer payUserLevel) {
        // 用户信息
        User user = new User();
        user.setUserId(userId);
        user.setNickName(userExcelDTO.getNickName());
        user.setLevel(normalLevel);
        user.setVipLevel(Objects.equals(levelType, LevelTypeEnum.ORDINARY_USER.value()) ? null : payUserLevel);
        user.setStatus(UserStatusEnum.ENABLE.value());
        user.setPhone(userExcelDTO.getPhone());
        user.setSex(userExcelDTO.getSex());
        // 将日期强制格式化为yyyy-MM-dd
        String birthDate = dateStrFormatToStr(userExcelDTO.getBirthDate());
        user.setBirthDate(birthDate);
        user.setVipEndTime(Objects.equals(levelType, LevelTypeEnum.ORDINARY_USER.value()) ? null : vipEndTime);
        user.setLevelType(levelType);
        return user;
    }

    /**
     * 根据参数初始化 账户表
     *
     * @param ipAddr       ip
     * @param userExcelDTO 参数
     * @param userId       用户id
     * @return 账户信息
     */
    private AuthAccountDTO getInitAuthAccountDTO(String ipAddr, UserExcelDTO userExcelDTO, Long userId) {
        AuthAccountDTO authAccountDTO = new AuthAccountDTO();
        authAccountDTO.setCreateIp(ipAddr);
        authAccountDTO.setPassword(passwordEncoder.encode(userExcelDTO.getPassword()));
        authAccountDTO.setPhone(userExcelDTO.getPhone());
        // 普通用户，不是管理员
        authAccountDTO.setIsAdmin(0);
        authAccountDTO.setSysType(SysTypeEnum.ORDINARY.value());
        authAccountDTO.setStatus(UserStatusEnum.ENABLE.value());
        authAccountDTO.setUserId(userId);
        return authAccountDTO;
    }


    /**
     * 根据成长值获取成长值所在等级
     *
     * @param levelVOList 会员等级列表
     * @param growth      成长值
     * @return 会员等级
     */
    private Integer getUserLevelByGrowth(List<UserLevelVO> levelVOList, Integer growth) {
        int level = Constant.USER_LEVEL_INIT;
        for (UserLevelVO levelVO : levelVOList) {
            if (growth >= levelVO.getNeedGrowth()) {
                level = levelVO.getLevel();
            }
        }
        return level;
    }

    /**
     * 将日期格式为 yyyy-MM-dd
     *
     * @param dateStr 日期字符串
     * @return yyyy-MM-dd日期字符串
     */
    private String dateStrFormatToStr(String dateStr) {
        if (StrUtil.isNotBlank(dateStr) && dateStr.length() > YEAR_MONTH_DAY_LENGTH) {
            try {
                DateTime parse = DateUtil.parse(dateStr, FORMAT);
                dateStr = DateUtil.format(parse, FORMAT);
            } catch (Exception e) {
                log.info("导入时，用户出生日期格式化异常");
                dateStr = "";
            }
        }
        return dateStr;
    }

    /**
     * 下载客户导入模板
     *
     * @param response 响应
     */
    public void downloadShopCustomerModel(HttpServletResponse response) {
        List<ShopCustomerExcelDTO> list = new ArrayList<>(Constant.INITIAL_CAPACITY);
        list.add(new ShopCustomerExcelDTO());
        ExcelWriter excelWriter = null;
        try {
            // 先执行合并策略
            ExcelWriterBuilder excelWriterMerge = ExcelUtil.getExcelWriterMerge(response, ShopCustomerExcelDTO.EXCEL_NAME, ShopCustomerExcelDTO.MERGE_ROW_INDEX, ShopCustomerExcelDTO.MERGE_COLUMN_INDEX);
            // useDefaultStyle false 不需要默认的头部加粗/自动换行样式
            excelWriter = ExcelUtil.getExcelModel(excelWriterMerge, null, 1).useDefaultStyle(false).build();
            // 业务代码
            if (CollUtil.isNotEmpty(list)) {
                // 进行写入操作
                WriteSheet sheetWriter = EasyExcel.writerSheet(ShopCustomerExcelDTO.SHEET_NAME).head(ShopCustomerExcelDTO.class).build();
                excelWriter.write(list, sheetWriter);
            }
        } catch (Exception e) {
            log.error("导出客户信息模板excel失败", e);
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 根据导入的客户数据，批量注册客户
     *
     * @param list
     * @param errorMap
     * @return
     */
    public void importShopCustomerExcel(List<ShopCustomerExcelDTO> list, Map<String, List<String>> errorMap) {
        if (CollUtil.isEmpty(list)) {
            throw new LuckException("解析到0条数据");
        }
        int size = list.size();
        // 集合去重复
        try {
            list = list.stream().collect(
                    Collectors.collectingAndThen(
                            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(ShopCustomerExcelDTO::getPhone))), ArrayList::new));
        } catch (Exception e) {
            throw new LuckException("文件数据错误");
        }
        // 第几行数据有误 的集合
        List<Integer> errorRows = new ArrayList<>(Constant.INITIAL_CAPACITY);
        this.loadShopCustomerErrorData(list, errorMap);
        int row = 1;
        // 处理数据
        for (ShopCustomerExcelDTO shopCustomerExcelDTO : list) {
            row++;
            // 手机号必须是11位
            String phone = shopCustomerExcelDTO.getPhone();
            if (StrUtil.isBlank(phone) || phone.length() != PHONE_LENGTH) {
                errorRows.add(row);
            }
        }
        List<ShopCustomer> shopCustomers = BeanUtil.mapAsList(list, ShopCustomer.class);
        if (CollUtil.isNotEmpty(shopCustomers)) {
            shopCustomerService.saveBatch(shopCustomers);
            errorMap.get(ShopCustomerExcelListener.IMPORT_ROWS).add(String.valueOf(shopCustomers.size()));
        }
        if (CollUtil.isNotEmpty(errorRows)) {
            List<String> collect = errorRows.stream().map(String::valueOf).toList();
            errorMap.get(ShopCustomerExcelListener.ERROR_ROWS).addAll(collect);
        }
    }

    private void loadShopCustomerErrorData(List<ShopCustomerExcelDTO> list, Map<String, List<String>> errorMap) {
        List<String> errorPhones = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<String> errorUsers = new ArrayList<>(Constant.INITIAL_CAPACITY);
        List<String> phones = list.stream().map(ShopCustomerExcelDTO::getPhone).collect(Collectors.toList());
        List<UserVO> userVOList = userService.getUserListByPhones(phones);
        Map<String, UserVO> userMap = userVOList.stream().collect(Collectors.toMap(UserVO::getPhone, user -> user));

        ShopCustomerDTO shopCustomerDTO = new ShopCustomerDTO();
        shopCustomerDTO.setShopId(AuthUserContext.get().getTenantId());
        List<UserManagerVO> customers = shopCustomerService.listByParam(shopCustomerDTO);
        Map<Long, UserManagerVO> customerMap = customers.stream().collect(Collectors.toMap(UserManagerVO::getUserId, cus -> cus));
        Iterator<ShopCustomerExcelDTO> iterator = list.iterator();

        while (iterator.hasNext()) {
            ShopCustomerExcelDTO shopCustomerExcelDTO = iterator.next();
            //导入的用户是否存在
            UserVO userVO = userMap.get(shopCustomerExcelDTO.getPhone());
            if (Objects.isNull(userVO)) {
                errorUsers.add(shopCustomerExcelDTO.getPhone());
                iterator.remove();
                continue;
            }

            //判断店铺客户是否已存在
            boolean phone = Objects.nonNull(customerMap.get(userVO.getUserId()));
            if (!phone) {
                phones.add(shopCustomerExcelDTO.getPhone());
                continue;
            }
            errorPhones.add(shopCustomerExcelDTO.getPhone());
            iterator.remove();
        }
        for (ShopCustomerExcelDTO customerExcelDTO : list) {
            UserVO user = userMap.get(customerExcelDTO.getPhone());
            customerExcelDTO.setUserId(user.getUserId());
            customerExcelDTO.setShopId(AuthUserContext.get().getTenantId());
        }

        List<String> errorList = errorMap.get(UserExcelListener.OTHER);
        if (CollUtil.isNotEmpty(errorPhones)) {
            errorList.add("手机号码：" + errorPhones + "已存在)");
        }
        if (CollUtil.isNotEmpty(errorUsers)) {
            errorList.add("手机号：" + errorUsers + "用户不存在;");
        }

    }

    /**
     * 导入客户
     *
     * @param file
     * @return
     */
    public String importShopCustomerExcel(MultipartFile file) {
        try {
            Map<String, List<String>> errorMap = new HashMap<>(16);
            ShopCustomerExcelListener shopCustomerExcelListener = new ShopCustomerExcelListener(this, errorMap);
            EasyExcel.read(file.getInputStream(), ShopCustomerExcelDTO.class, shopCustomerExcelListener).sheet().doRead();
            return getUserExportInfo(errorMap);
        } catch (IOException e) {
            throw new LuckException(e.getMessage());
        }
    }

}
