package com.yida.data.user.service.impl;


import cc.mrbird.febs.common.redis.service.RedisService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yida.data.common.core.entity.CurrentUser;
import com.yida.data.common.core.entity.Dict;
import com.yida.data.common.core.entity.apply.enums.DelFlagType;
import com.yida.data.common.core.entity.constant.AppConstant;
import com.yida.data.common.core.entity.constant.CachePrefixConstant;
import com.yida.data.common.core.entity.message.EduMessageStrategy;
import com.yida.data.common.core.entity.notice.app.AppNotice;
import com.yida.data.common.core.entity.notice.app.inside.PushMessage;
import com.yida.data.common.core.entity.school.EduYidaAppAccount;
import com.yida.data.common.core.entity.system.*;
import com.yida.data.common.core.entity.system.enums.RoleEnum;
import com.yida.data.common.core.entity.system.enums.SysFunctionEnum;
import com.yida.data.common.core.entity.user.*;
import com.yida.data.common.core.enums.DictType;
import com.yida.data.common.core.enums.UserTypeEnum;
import com.yida.data.common.core.exception.FebsException;
import com.yida.data.common.core.utils.*;
import com.yida.data.common.core.vo.ImportPercentVO;
import com.yida.data.common.core.vo.NameValueVO;
import com.yida.data.common.service.CommonService;
import com.yida.data.school.dto.news.SysNoticeSaveDTO;
import com.yida.data.school.dto.transaction.ListBuyStudentDTO;
import com.yida.data.school.feign.news.RemoteNoticeSystemService;
import com.yida.data.system.feign.RemoteCommonService;
import com.yida.data.system.feign.RemoteDeptService;
import com.yida.data.system.vo.ClassInfoVO;
import com.yida.data.user.config.ExportStudentClient;
import com.yida.data.user.dto.*;
import com.yida.data.user.dto.h5.BindStudentDTO;
import com.yida.data.user.excel.CardDataDataExcelListener;
import com.yida.data.user.excel.SelectStudentDormExportData;
import com.yida.data.user.excel.SelectStudentExportData;
import com.yida.data.user.listener.ImportStudentTypeListener;
import com.yida.data.user.mapper.EduParentMapper;
import com.yida.data.user.mapper.EduParentStudentMapper;
import com.yida.data.user.mapper.EduStaffMapper;
import com.yida.data.user.mapper.EduStudentApplyMapper;
import com.yida.data.user.mapper.EduStudentMapper;
import com.yida.data.user.service.*;
import com.yida.data.user.vo.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StopWatch;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class EduStudentServiceImpl extends ServiceImpl<EduStudentMapper, EduStudent> implements EduStudentService {

    private final EduParentMapper eduParentMapper;
    private final EduUserFaceService eduUserFaceService;
    private final EduUserDeptService eduUserDeptService;
    private final EduParentTypeService eduParentTypeService;
    private final EduStudentDormService eduStudentDormService;
    private final EduFaceGroupUserService eduFaceGroupUserService;

    private final EduParentStudentService eduParentStudentService;
    private final EduMessageStrategyService eduMessageStrategyService;

    private final EduParentStudentMapper eduParentStudentMapper;
    private final EduStudentApplyMapper eduStudentApplyMapper;
    private final EduStaffService eduStaffService;

    @Lazy
    @Resource
    private ImportWelcomeStudentService importWelcomeStudentService;

    private final RemoteDeptService remoteDeptService;
    private final RemoteCommonService remoteCommonService;
    private final RemoteNoticeSystemService remoteNoticeSystemService;

    private final WxUtil wxUtil;
    private final CommonService commonService;
    private final RedisService redisService;

    @Value("${febs.uploadUrl}")
    private String uploadUrl;

    @Lazy
    @Resource
    private ExportStudentClient exportStudentClient;

    private static final ExecutorService UPDATE_PARENT_THREAD_POOL =
        new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    @Override
    public IPage<EduStudent> findStudentPage(PageStudentDTO dto) {
        //查询后台用户身份
        CurrentUser currentUser = FebsUtil.getCurrentUser();
        String rolePerms = FebsUtil.getCurrentUser().getRolePerms();
        //学校管理员
        if (rolePerms.contains(RoleEnum.ROLE_SCHOOL_PRINCIPAL.getValue())) {
            dto.setSchoolId(FebsUtil.getTopDeptId());
        } else {
            if (ObjectUtil.isNull(dto.getClassId())) {
                dto.setClassIdList(eduStaffService.listClassIdByStaffId());
            } else {
                dto.setClassIdList(Collections.singletonList(dto.getClassId()));
            }
        }
        IPage<EduStudent> studentPage = baseMapper.selectStudentPage(dto.toPage().setOptimizeCountSql(false), dto);
        if (CollUtil.isNotEmpty(studentPage.getRecords())) {
            studentPage.setRecords(fillStudentDept(studentPage.getRecords()));
        }
        return studentPage;
    }

    @Override
    public EduStudent saveStudent(EduStudent student, CurrentUser currentUser, Boolean isWx) {

        if (currentUser != null && currentUser.getDeptId() != null && currentUser.getDeptType() == 0) {
            student.setSchoolId(currentUser.getDeptId());
        }

        //判断学生是否已经存在
        long count = count(Wrappers.lambdaQuery(new EduStudent())
            .eq(EduStudent::getSchoolId, student.getSchoolId())
            .eq(EduStudent::getStuNumber, student.getStuNumber())
            .ne(ObjectUtil.isNotNull(student.getId()), EduStudent::getId, student.getId()));
        Asserts.isFalse(count > 0, "学号已存在");

        Dept school = commonService.getDept(student.getSchoolId());
        EduStudent existStudent = null;
        if (student.getId() != null) {
            existStudent = getById(student.getId());
            //清除小程序url缓存链接
            if (!ObjectUtil.equals(student.getAvatar(), existStudent.getAvatar())) {
                commonService.delUserGenerateUrlLinkCache(student.getSchoolId(), student.getId(), UserTypeEnum.PARENT_USER_TYPE
                    .getType());
            }
        }

        //只有当前孩子作为家长的家长
        List<EduParent> lateParent = null;
        // 微信app与token
        boolean containWX = school.getSchoolType().contains(Dept.TYPE_QYWX);
        boolean containYida = school.getSchoolType().contains(Dept.TYPE_YIDA_APP);
        boolean containDahua = school.getSchoolType().contains(Dept.TYPE_DAHUA);
        EduApp app;
        String token = null;
        EduYidaApp yidaApp = null;
        EduYidaAppAccount yidaAppAccount = null;
        if (containWX) {
            app = commonService.getAppByCodeAndSchool(AppConstant.CONTACT_SCHOOL, null, student.getSchoolId());
            token = wxUtil.getAccessToken(app.getWxCorpId(), app.getWxSecret());
        }
        if (containYida) {
            yidaApp = commonService.getYidaApp(school.getDeptId());
            yidaAppAccount = commonService.getYidaAppAccount(school.getDeptId(), 0);
        }
        //判断是新增还是修改
        if (student.getId() == null) {
            if (containDahua) {
                EduUserDept campus = eduUserDeptService.getById(student.getCampusId());
                EduUserDept section = eduUserDeptService.getById(student.getSectionId());
                EduUserDept grade = eduUserDeptService.getById(student.getGradeId());
                EduUserDept clazz = eduUserDeptService.getById(student.getClassId());
                String cardNum = DaHuaUtil.generateCardNum();
                student.setDahuaCode(student.getStuNumber().length() > 20
                    ? DaHuaUtil.generateCode()
                    : student.getStuNumber());
                student.setDahuaId(
                    DaHuaUtil.addUser(student.getStuName(), student.getDahuaCode(), 0, 1, cardNum,
                        school.getDahuaId(), campus.getDaHuaId(), section.getDaHuaId(), grade.getDaHuaId(),
                        clazz.getDaHuaId(), student.getAvatar())
                );
            }
            //新增学生
            save(student);
        } else {
            if (containDahua &&
                !(student.getCampusId().equals(existStudent.getCampusId())
                    && student.getSectionId().equals(existStudent.getSectionId())
                    && student.getGradeId().equals(existStudent.getGradeId())
                    && student.getClassId().equals(existStudent.getClassId()))) {
                EduUserDept campus = eduUserDeptService.getById(student.getCampusId());
                EduUserDept section = eduUserDeptService.getById(student.getSectionId());
                EduUserDept grade = eduUserDeptService.getById(student.getGradeId());
                EduUserDept clazz = eduUserDeptService.getById(student.getClassId());
                DaHuaUtil.updateUser(existStudent.getDahuaId(), student.getStuName(),
                    existStudent.getDahuaCode(), 0, 1, school.getDahuaId(), campus.getDaHuaId(), section.getDaHuaId(),
                    grade.getDaHuaId(), clazz.getDaHuaId(), student.getAvatar());
            }
            lateParent = eduParentStudentMapper.listOnlyOneChild(student.getId());
            eduParentStudentMapper.delete(
                Wrappers.lambdaQuery(new EduParentStudent()).eq(EduParentStudent::getStudentId, student.getId()));
        }
        // 保存人脸
        if (StrUtil.isNotBlank(student.getAvatar())) {
            if (existStudent == null || !student.getAvatar().equals(existStudent.getAvatar())) {
                eduUserFaceService.updateUserFace(student.getAvatar(), student.getId(), student.getStuName(), 0);
            }
        }
        // 企业微信 学生保存
        if (!isWx && containWX) {
            // 不是企业微信回调  同步进行企业微信修改
            student.setClassWxId(eduUserDeptService.getById(student.getClassId()).getWxId());
            if (existStudent != null) {
                EduStudent exist = getById(student.getId());
                student.setWxId(exist.getWxId());
//                student.setType(exist.getType());
                wxUtil.updateStudent(token, Collections.singletonList(student));
                // 修改了学号
                if (StrUtil.isNotBlank(student.getStuNumber()) && !StrUtil.equals(exist.getStuNumber(),
                    student.getStuNumber())) {
                    Asserts.isTrue(exist.getUpdateWx() == 0, "学号修改次数已达上限，仅能修改一次");
                    student.setWxId(student.getStuNumber());
                    student.setUpdateWx(1);
                }
            } else {
                if (StrUtil.isNotBlank(student.getStuNumber())) {
                    student.setWxId(student.getStuNumber());
                } else {
                    student.setWxId(StrUtil.uuid().replace("-", ""));
                    student.setStuNumber(student.getWxId());
                }
                wxUtil.createStudent(token, Collections.singletonList(student));
            }
        }
        //保存学生信息
        updateById(student);
        // 添加家长信息
        List<EduParent> parents = student.getParents();
        log.info("parents:{}", parents);
        if (CollUtil.isNotEmpty(parents)) {
            for (EduParent parent : parents) {
                EduParent exist = eduParentMapper.selectOne(Wrappers.lambdaQuery(new EduParent())
                    .eq(EduParent::getMobile, parent.getMobile()).eq(EduParent::getSchoolId, student.getSchoolId()));
                if (exist != null) {
                    parent.setId(exist.getId());
                    if (!isWx && containWX) {
                        // 家长已存在   进行家长信息更新
                        List<EduParentStudent> parentStudentRelations = eduParentStudentMapper
                            .listParentStudent(parent.getId());
                        parent.setWxId(exist.getWxId());
                        parent.setChildren(new ArrayList<>());
                        // 添加该家长其它的学生
                        for (EduParentStudent parentStudentRelation : parentStudentRelations) {
                            Map<String, Object> relation = new HashMap<>();
                            relation.put("student_userid", getById(parentStudentRelation.getStudentId()).getWxId());
                            relation.put("relation", parentStudentRelation.getParentTypeName());
                            parent.getChildren().add(relation);
                        }
                        // 添加当前学生
                        parent.getChildren().add(new HashMap<String, Object>() {
                            {
                                put("student_userid", student.getWxId());
                                put("relation", parent.getParentType());
                            }
                        });
                        // 更新微信端家长信息和学生关系
                        wxUtil.updateParent(token, Collections.singletonList(parent));
                    }
                } else {
                    // 家长不存在且不是微信回调  添加家长信息
                    parent.setId(null);
                    parent.setWxId(StrUtil.uuid().replace("-", ""));
                    parent.setSchoolId(student.getSchoolId());
                    eduParentMapper.insert(parent);
                    if (!isWx && containWX) {
                        parent.setChildren(new ArrayList<>());
                        parent.getChildren().add(new HashMap<String, Object>() {
                            {
                                put("student_userid", student.getWxId());
                                put("relation", parent.getParentType());
                            }
                        });
                        wxUtil.createParent(token, Collections.singletonList(parent));
                    }
                }
                // 学校绑定了易达app
                if (containYida) {
                    EduYidaAppAccount finalYidaAppAccount = yidaAppAccount;
                    EduYidaApp finalYidaApp = yidaApp;
                    UPDATE_PARENT_THREAD_POOL.execute(() -> {
                        parent.setYidaAppUserId(AppUtil.userSave(parent.getMobile(), "用户" + parent.getMobile(), finalYidaApp));
                        AppUtil.subscribeFollow(finalYidaAppAccount.getYidaAppAccountId(),
                            parent.getYidaAppUserId(), finalYidaApp);
                        if (exist == null) {
                            // 推送关注消息
                            AppNotice appNotice = new AppNotice();
                            appNotice.setUserIds(Collections.singletonList(parent.getYidaAppUserId()));
                            PushMessage message = new PushMessage();
                            message.setMsgType(PushMessage.TEXT);
                            EduMessageStrategy data = eduMessageStrategyService
                                .getOne(Wrappers.lambdaQuery(new EduMessageStrategy())
                                    .eq(EduMessageStrategy::getSchoolId, student.getSchoolId())
                                    .eq(EduMessageStrategy::getType, 1)
                                    .eq(EduMessageStrategy::getSubType, 1)
                                    .eq(EduMessageStrategy::getDelFlag, DelFlagType.NORMAL_TYPE.getValue()));
                            message.setContent(data != null ? data.getContent() :
                                String.format("亲爱的%s，欢迎加入%s智慧校园！您可以在这里接收到学生在学校的动态消息，了解学生在学校的情况，更好地陪伴孩子成长！"
                                    , student.getStuName() + parent.getParentType(), school.getDeptName()));
                            message.setAccountId(finalYidaAppAccount.getYidaAppAccountId());
                            appNotice.setMsgList(Collections.singletonList(message));
                            appNotice.setPushMessage(new PushMessage(school.getDeptName() + "智慧校园",
                                finalYidaAppAccount.getYidaAppAccountId()));
                            AppUtil.sendMsg(appNotice, finalYidaApp);
                        }
                        eduParentMapper.updateById(parent);
                    });
                }
                EduParentType parentType = eduParentTypeService.getParentType(parent.getParentType());
                parent.setParentTypeId(parentType.getId());
                // 保存学生与家长对应关系
                EduParentStudent eduParentStudent = new EduParentStudent();
                eduParentStudent.setParentId(parent.getId());
                eduParentStudent.setStudentId(student.getId());
                eduParentStudent.setParentTypeId(parentType.getId());
                eduParentStudentMapper.insert(eduParentStudent);
            }
        }
        // 删除企业微信 无用的家长信息
        if (CollUtil.isNotEmpty(lateParent) && containWX) {
            List<String> delParentIds = new ArrayList<>();
            // 删除没有学生的家长
            for (EduParent oneLateParent : lateParent) {
                if (oneLateParent.getWxId() != null) {
                    boolean flag = true;
                    for (EduParent parent : parents) {
                        if (oneLateParent.getWxId().equals(parent.getWxId())) {
                            flag = false;
                            break;
                        }
                    }
                    if (flag && oneLateParent.getWxId() != null) {
                        delParentIds.add(oneLateParent.getWxId());
                    }
                }
            }
            log.info("del parent,param:[{}],parents:[{}]", delParentIds, parents);
            if (CollUtil.isNotEmpty(delParentIds)) {
                wxUtil.deleteParent(token, delParentIds);
            }
        }
        redisService.hdel(CachePrefixConstant.STUDENT_DATA, student.getId().toString());

        return student;
    }

    @Override
    public void delStudentBatchIds(List<Long> studentIds, Boolean isWx) {
        List<EduStudent> studentList = list(Wrappers.lambdaQuery(new EduStudent()).in(EduStudent::getId,
            studentIds));
        // 删除住宿记录
        eduStudentDormService.cancelBatch(
            studentList.stream().filter(x -> x.getDormRoomId() != null).map(EduStudent::getId)
                .collect(Collectors.toList()));
        // 删除人脸相关信息
        List<EduFaceGroupUser> faceList =
            eduFaceGroupUserService.list(Wrappers.<EduFaceGroupUser>lambdaQuery()
                .eq(EduFaceGroupUser::getUserType, 0)
                .in(EduFaceGroupUser::getUserId, studentIds));
        if (CollUtil.isNotEmpty(faceList)) {
            // 分组处理数据
            Map<Long, List<EduFaceGroupUser>> faceGroupMap = faceList.stream().collect(
                Collectors.groupingBy(EduFaceGroupUser::getFaceGroupId));
            faceGroupMap.forEach((faceGroupId, userList) ->
                eduFaceGroupUserService.delUserBatch(DelFaceGroupUserDTO.builder()
                    .faceGroupId(faceGroupId)
                    .faceIds(userList.stream().map(EduFaceGroupUser::getId).collect(Collectors.toList()))
                    .build())
            );
        }
        // 所有学生的家长
        List<EduParent> parentList = eduParentMapper.listParentByStudent(studentIds);
        // 先删除学生数据
        removeByIds(studentIds);
        eduParentStudentMapper
            .delete(Wrappers.lambdaQuery(new EduParentStudent()).in(EduParentStudent::getStudentId, studentIds));
        // 学校
        Dept school = commonService.getDept(studentList.get(0).getSchoolId());
        // 学校的绑定关系
        boolean containsWx = school.getSchoolType().contains(Dept.TYPE_QYWX);
        boolean containsYida = school.getSchoolType().contains(Dept.TYPE_YIDA_APP);
        boolean containsDahua = school.getSchoolType().contains(Dept.TYPE_DAHUA);
        String wxAccessToken = null;
        EduYidaApp yidaApp = null;
        EduYidaAppAccount yidaAppAccount = null;
        if (containsYida) {
            yidaApp = commonService.getYidaApp(school.getDeptId());
            yidaAppAccount = commonService.getYidaAppAccount(school.getDeptId(), 0);
        }
        if (!isWx && containsWx) {
            EduApp wxApp = commonService.getAppByCodeAndSchool(AppConstant.CONTACT_SCHOOL, null, school.getDeptId());
            wxAccessToken = wxUtil.getAccessToken(wxApp.getWxCorpId(), wxApp.getWxSecret());
        }
        // 删除大华学生信息
        if (containsDahua) {
            DaHuaUtil.delUser(studentList.stream().map(x -> x.getDahuaId().toString()).collect(Collectors.joining(",")));
        }
        log.info("家长列表:[{}]", parentList);
        // 遍历学生的家长
        for (EduParent parent : parentList) {
            // 查询家长现在关联的学生
            List<EduStudent> containStudent = baseMapper.listStudentByParent(parent.getId());
            log.info("学生列表:[{}]", containStudent);
            // 还有其它学生
            if (containStudent.size() > 0) {
                // 学校绑定了微信
                if (!isWx && containsWx) {
                    // 每个学生
                    List<Map<String, Object>> child = new ArrayList<>();
                    for (EduStudent eduStudent : containStudent) {
                        child.add(new HashMap<String, Object>() {{
                            put("student_userid", eduStudent.getWxId());
                            put("relation", eduStudent.getRelation());
                        }});
                    }
                    parent.setChildren(child);
                    // 修改家长学生
                    wxUtil.updateParent(wxAccessToken, Collections.singletonList(parent));
                }
            } else { //家长已经没有其他学生
                // 绑定了微信 并且不是微信回调
                if (!isWx && containsWx) {
                    wxUtil.deleteParent(wxAccessToken, Collections.singletonList(parent.getWxId()));
                }
                // 绑定了易达app  取消关注
                if (containsYida) {
                    AppUtil.subscribeUnFollow(yidaAppAccount.getYidaAppAccountId(),
                        parent.getYidaAppUserId(), yidaApp);
                }
                eduParentMapper.deleteById(parent.getId());
            }
        }
        // 删除学生需要在删除家长之后操作
        if (!isWx && containsWx) {
            List<String> wxIds = studentList.stream().map(EduStudent::getWxId)
                .filter(StrUtil::isNotBlank).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(wxIds)) {
                wxUtil.deleteStudent(wxAccessToken, wxIds);
            }
        }
        redisService.hdel(CachePrefixConstant.STUDENT_DATA, studentIds.stream().map(Object::toString).toArray(String[]::new));

        studentList.forEach(s -> {
            log.info("删除class学生数据:{}", s);
            redisService
                .del(CachePrefixConstant.CLASS_STUDENT_DATA + s.getClassId());

            //删除学生缓存小程序链接
            commonService.delUserGenerateUrlLinkCache(s.getSchoolId(), s.getId(), UserTypeEnum.PARENT_USER_TYPE.getType());
        });
    }

    @Override
    public EduStudent getStudent(Long studentId) {
        EduStudent student = baseMapper.getStudentWithParent(studentId, null, null);
        if (student != null) {
            student = fillStudentDept(Collections.singletonList(student)).get(0);
        }
        return student;
    }

    @Override
    public List<EduStudent> getStudentDeptByIds(List<Long> studentIdList) {
        List<EduStudent> studentList = baseMapper.selectBatchIds(studentIdList);
        Map<Long, String> currentDeptCache = new HashMap<>();
        return studentList.parallelStream().peek(student -> {
            //学校名称
            if (currentDeptCache.containsKey(student.getSchoolId())) {
                student.setSchoolName(currentDeptCache.get(student.getSchoolId()));
            } else {
                student.setSchoolName(commonService.getDept(student.getSchoolId()).getDeptName());
                currentDeptCache.put(student.getSchoolId(), student.getSchoolName());
            }
            //校区名称
            if (currentDeptCache.containsKey(student.getCampusId())) {
                student.setCampusName(currentDeptCache.get(student.getCampusId()));
            } else {
                student.setCampusName(commonService.getUserDept(student.getCampusId()).getDeptName());
                currentDeptCache.put(student.getCampusId(), student.getCampusName());
            }
            //学段名称
            if (currentDeptCache.containsKey(student.getSectionId())) {
                student.setSectionName(currentDeptCache.get(student.getSectionId()));
            } else {
                student.setSectionName(commonService.getUserDept(student.getSectionId()).getDeptName());
                currentDeptCache.put(student.getSectionId(), student.getSectionName());
            }
            //年级名称
            if (currentDeptCache.containsKey(student.getGradeId())) {
                student.setGradeName(currentDeptCache.get(student.getGradeId()));
            } else {
                student.setGradeName(commonService.getUserDept(student.getGradeId()).getDeptName());
                currentDeptCache.put(student.getGradeId(), student.getGradeName());
            }

            //班级名称
            if (currentDeptCache.containsKey(student.getClassId())) {
                student.setClassName(currentDeptCache.get(student.getClassId()));
            } else {
                student.setClassName(commonService.getUserDept(student.getClassId()).getDeptName());
                currentDeptCache.put(student.getClassId(), student.getClassName());
            }
        }).collect(Collectors.toList());
    }

    @Override
    public void importStudent(String path, CurrentUser currentUser, Long schoolId, Long key, String filename) {
        try {
            Dept school = commonService.getDept(schoolId);
            Map<Long, EduUserDept> cache = new HashMap<>();
            // 解压文件到本地
            File zipFile = new File(path);
            String zipPath = ZipUtil.unzip(zipFile, CharsetUtil.CHARSET_GBK).getAbsolutePath();
            // 循环拿到最底层文件夹
            List<File> face = FileUtil.loopFiles(zipPath, x -> {
                String suffix = FileUtil.getSuffix(x);
                return (!"txt".equals(suffix)) && (!"xlsx".equals(suffix));
            });
            List<File> data = FileUtil.loopFiles(zipPath, x -> {
                String suffix = FileUtil.getSuffix(x);
                return "xlsx".equals(suffix);
            });
            int count = face.size() + data.size();
            int complete = 0;
            int percent = 0;
            // 保存数据
            for (File excel : data) {
                try {
                    try {
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    String[] split = StrUtil.split(excel.getAbsolutePath(), "\\");
                    String s = split[split.length - 2];
                    String[] clazzName = s.split("\\.");
                    Long clazzId = Long.valueOf(clazzName[0]);
                    EduUserDept clazz;
                    EduUserDept grade;
                    EduUserDept section;
                    EduUserDept campus;
                    // 班级
                    if (cache.containsKey(clazzId)) {
                        clazz = cache.get(clazzId);
                    } else {
                        clazz = eduUserDeptService.getById(clazzId);
                        cache.put(clazzId, clazz);
                    }
                    // 年级
                    if (cache.containsKey(clazz.getParentId())) {
                        grade = cache.get(clazz.getParentId());
                    } else {
                        grade = eduUserDeptService.getById(clazz.getParentId());
                        cache.put(clazz.getParentId(), grade);
                    }
                    // 学段
                    if (cache.containsKey(grade.getParentId())) {
                        section = cache.get(grade.getParentId());
                    } else {
                        section = eduUserDeptService.getById(grade.getParentId());
                        cache.put(grade.getParentId(), section);
                    }
                    // 校区
                    if (cache.containsKey(section.getParentId())) {
                        campus = cache.get(section.getParentId());
                    } else {
                        campus = eduUserDeptService.getById(section.getParentId());
                        cache.put(section.getParentId(), campus);
                    }

                    Workbook workbook = ExcelUtil.readExcel(FileUtil.getInputStream(excel), "xlsx");
                    List<Map<Integer, Cell>> mapList = ExcelUtil.readData(workbook.getSheetAt(0), 1);
                    for (Map<Integer, Cell> map : mapList) {
                        if (map.get(1) != null) {
                            String name = ExcelUtil.getCellValue(map.get(1));
                            if (StrUtil.isBlank(name)) {
                                break;
                            }
                            String number = ExcelUtil.getCellValue(map.get(3));
                            List<EduParent> parents = new ArrayList<>();
                            for (int i = 5; ; i += 2) {
                                if (map.containsKey(i) && map.get(i) != null) {
                                    String mobile = ExcelUtil.getCellValue(map.get(i));
                                    String relation = ExcelUtil.getCellValue(map.get(i + 1));
                                    if (StrUtil.isNotBlank(mobile) && StrUtil.isNotBlank(relation)) {
                                        EduParent parent = new EduParent();
                                        parent.setMobile(mobile);
                                        parent.setParentType(relation);
                                        parents.add(parent);
                                    }
                                }
                                break;
                            }
                            EduStudent student = new EduStudent();
                            student.setStuNumber(number);
                            student.setStuName(name);
                            student.setSchoolId(school.getDeptId());
                            student.setSchoolName(school.getDeptName());
                            student.setClassId(clazz.getDeptId());
                            student.setClassName(clazz.getDeptName());
                            student.setGradeId(grade.getDeptId());
                            student.setGradeName(grade.getDeptName());
                            student.setSectionId(section.getDeptId());
                            student.setSectionName(section.getDeptName());
                            student.setCampusId(campus.getDeptId());
                            student.setCampusName(campus.getDeptName());
                            student.setParents(parents);
                            student.setWxId(number);
                            student.setClassWxId(clazz.getWxId());
                            if (count(Wrappers.lambdaQuery(new EduStudent()).eq(EduStudent::getSchoolId,
                                school.getDeptId()).eq(EduStudent::getStuNumber, student.getStuNumber())) == 0) {
                                saveStudent(student, currentUser, false);
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                complete++;
                if (complete / count != percent) {
                    percent = complete / count;
                    redisService.hset(CachePrefixConstant.IMPORT_STUDENT_KEY + currentUser.getUsername(),
                        key.toString(), percent);
                }
            }
            // 保存人脸
            for (File pic : face) {
                try {
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                String name = FileUtil.getName(pic);
                String number = name.split("\\.")[0];
                EduStudent student = getOne(Wrappers.lambdaQuery(new EduStudent()).eq(EduStudent::getSchoolId,
                    school.getDeptId()).eq(EduStudent::getStuNumber, number));
                if (student != null) {
                    eduUserFaceService.uploadImage(pic, student.getId(), student.getStuName(), 0);
                }
                complete++;
                if (complete / count != percent) {
                    percent = complete / count;
                    redisService.hset(CachePrefixConstant.IMPORT_STUDENT_KEY + currentUser.getUsername(),
                        key.toString(), percent);
                }
            }
        } finally {
            redisService.hdel(CachePrefixConstant.IMPORT_STUDENT_KEY + currentUser.getUsername(), key.toString());
            redisService.hdel(CachePrefixConstant.IMPORT_STUDENT_FILE + currentUser.getUsername(), key.toString());
        }
    }

    @Override
    public void importCard(String path, CurrentUser currentUser, Long schoolId, Long key, String filename) {
        try {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            // 调用导入方法
            List<CardErrorExportDTO> errorList = new ArrayList<>();
            EasyExcel.read(new File(path), CardImportDTO.class,
                new CardDataDataExcelListener(importWelcomeStudentService, errorList, schoolId, currentUser))
                .sheet().headRowNumber(1).doRead();
            stopWatch.stop();
            log.info("导入校园卡号数据总共耗时: {}", stopWatch.getTotalTimeSeconds() + "秒");
            // 判断是否有错误数据
            if (CollUtil.isNotEmpty(errorList)) {
                // 错误信息文件名称
                String fileName = com.yida.data.common.core.utils.FileUtil.getLocalUploadAddress() + UUID.randomUUID() + ".xlsx";
                EasyExcel.write(fileName, CardErrorExportDTO.class)
                    .sheet("错误信息")
                    .doWrite(errorList);
                // 上传到媒资
                String url = com.yida.data.common.core.utils.FileUtil.uploadFileToMediaServer(uploadUrl, new File(fileName));
                // 保存导入不成功的数据和原因
                remoteNoticeSystemService.sendNotice(
                    SysNoticeSaveDTO.builder()
                        .userId(Collections.singletonList(currentUser.getUserId()))
                        .content("校园卡号部分数据导入失败. 详情信息请点击下载  <a style='color: blue' href='" + url + "'>错误信息.xlsx</a>")
                        .createDate(LocalDateTime.now())
                        .build());
            } else {
                remoteNoticeSystemService.sendNotice(
                    SysNoticeSaveDTO.builder()
                        .userId(Collections.singletonList(currentUser.getUserId()))
                        .content("校园卡号导入成功")
                        .createDate(LocalDateTime.now())
                        .build());
            }
        } catch (Exception e) {
            log.error("导入校园卡号数据失败: {}", e.getMessage(), e);
            remoteNoticeSystemService.sendNotice(
                SysNoticeSaveDTO.builder()
                    .userId(Collections.singletonList(currentUser.getUserId()))
                    .content("校园卡号导入失败")
                    .createDate(LocalDateTime.now())
                    .build());
        } finally {
            // 清空当前导入的redis学校数据
            redisService.hdel(CachePrefixConstant.IMPORT_STUDENT_KEY + currentUser.getUsername(), key.toString());
            FileUtil.del(path);
        }
    }

    @Override
    public Integer importStaffHeartbeat(String username, Long key) {
        Object value = redisService.hget(CachePrefixConstant.IMPORT_STUDENT_KEY + username, key.toString());
        if (value != null) {
            return Integer.valueOf(value.toString());
        }
        return 100;
    }

    @Override
    public void importStudentUpdateType(MultipartFile file, Long schoolId) {
        CurrentUser currentUser = FebsUtil.getCurrentUser();
        ImportPercentVO percent =
            (ImportPercentVO) redisService.hget(CachePrefixConstant.IMPORT_STUDENT_TYPE, currentUser.getUserId().toString());
        Asserts.isTrue(percent == null || percent.getFinish() == 1, "有文件正在导入，请稍后重试");
        try {
            int rowNum = ExcelUtil.readExcel(file.getInputStream(), "xlsx").getSheetAt(0).getLastRowNum();
            ImportPercentVO percentVO = new ImportPercentVO();
            percentVO.setTotal(Long.valueOf(rowNum));
            EasyExcel.read(file.getInputStream(), ImportStudentTypeDTO.class,
                new ImportStudentTypeListener(redisService, baseMapper, percentVO, schoolId,
                    currentUser.getUserId().toString())).sheet().doRead();
        } catch (IOException e) {
            log.error("导入失败", e);
            throw new FebsException("导入失败");
        }
    }

    @Override
    public List<Map<String, String>> listImportKey(String username) {
        List<Map<String, String>> list = new ArrayList<>();
        Map<Object, Object> keyList = redisService.hmget(CachePrefixConstant.IMPORT_STUDENT_KEY + username);
        if (CollUtil.isNotEmpty(keyList)) {
            for (Map.Entry<Object, Object> objectEntry : keyList.entrySet()) {
                Map<String, String> res = new HashMap<>();
                Object key = objectEntry.getKey();
                Object value = objectEntry.getValue();
                Object filename = redisService.hget(
                    CachePrefixConstant.IMPORT_STUDENT_FILE + username,
                    key.toString());
                if (filename != null && StrUtil.isNotBlank(filename.toString())) {
                    res.put("percent", value.toString());
                    res.put("filename", filename.toString());
                    res.put("key", key.toString());
                    list.add(res);
                }
            }

        }
        return list;
    }

    @Override
    public List<EduStudent> listStudentByParent(Long parentId, String applyCode) {
        List<EduStudent> studentList = baseMapper.listStudentByParent(parentId);
        if (CollUtil.isNotEmpty(studentList)) {
            studentList = fillStudentDept(studentList);
        }
        return studentList;
    }

    @Override
    public void initStudent(List<EduStudent> studentList) {
        //直接添加学生，不验证是否已经存在
        saveBatch(studentList);
        Dept school = commonService.getDept(studentList.get(0).getSchoolId());
        Map<String, EduParentType> parentTypeCache = new HashMap<>();
        List<EduParentStudent> parentStudentList = new ArrayList<>();
        for (EduStudent student : studentList) {
            List<EduParent> parentList = student.getParents();
            // 绑定大华
            if (school.getSchoolType().contains(Dept.TYPE_DAHUA)) {
                EduUserDept campus = eduUserDeptService.getById(student.getCampusId());
                EduUserDept section = eduUserDeptService.getById(student.getSectionId());
                EduUserDept grade = eduUserDeptService.getById(student.getGradeId());
                EduUserDept clazz = eduUserDeptService.getById(student.getClassId());
                String cardNum = DaHuaUtil.generateCardNum();
                student.setDahuaCode(student.getWxId().length() > 20 ? DaHuaUtil.generateCode() : student.getWxId());
                student.setDahuaId(
                    DaHuaUtil.addUser(student.getStuName(), student.getDahuaCode(), 0, 1, cardNum,
                        school.getDahuaId(), campus.getDaHuaId(), section.getDaHuaId(), grade.getDaHuaId(),
                        clazz.getDaHuaId(), ""));
            }
            if (CollUtil.isNotEmpty(parentList)) {
                for (EduParent parent : parentList) {
                    EduParent existParent =
                        eduParentMapper.selectOne(Wrappers.lambdaQuery(new EduParent()).eq(EduParent::getWxId,
                            parent.getWxId()).eq(EduParent::getSchoolId, student.getSchoolId()));
                    EduParentType parentType;
                    // 本地缓存加速
                    if (parentTypeCache.containsKey(parent.getParentType())) {
                        parentType = parentTypeCache.get(parent.getParentType());
                    } else {
                        parentType = eduParentTypeService.getParentType(parent.getParentType());
                        parentTypeCache.put(parent.getParentType(), parentType);
                    }
                    if (existParent == null) {
                        eduParentMapper.insert(parent);
                    } else {
                        parent.setId(existParent.getId());
                    }
                    EduParentStudent parentStudent = new EduParentStudent();
                    parentStudent.setStudentId(student.getId());
                    parentStudent.setParentId(parent.getId());
                    parentStudent.setParentTypeId(parentType.getId());
                    parentStudentList.add(parentStudent);
                }
            }
        }
        // 绑定大华
        if (school.getSchoolType().contains(Dept.TYPE_DAHUA)) {
            updateBatchById(studentList);
        }
        eduParentStudentService.saveBatch(parentStudentList);
    }

    @Override
    public StudentClassInfoVO getClassInfoByStudent(Long studentId, String parentMobile) {
        StudentClassInfoVO studentClassInfoVO = baseMapper.getStudentInfo(studentId, parentMobile);
        // 查询班级年级等信息
        ClassInfoVO classInfo = eduUserDeptService.getClassInfo(studentClassInfoVO.getClassId());
        BeanUtils.copyProperties(classInfo, studentClassInfoVO);
        return studentClassInfoVO;
    }

    @Override
    public void downloadTemplate(HttpServletResponse response, CurrentUser currentUser) {
//        List tree = remoteDeptService.getDeptTree(currentUser.getDeptId(), 1).getData();
        List tree = eduUserDeptService.getUserDeptTree(currentUser.getDeptId());
        // 根据树形结构建造文件夹
        String tempDir = StrUtil.uuid().replace("-", "");
        FileUtil.mkdir(tempDir);
        InputStream xlsxStream =
            new BufferedInputStream(getClass().getClassLoader().getResourceAsStream("template/student.xlsx"));
        InputStream txtStream =
            new BufferedInputStream(getClass().getClassLoader().getResourceAsStream("template/student.txt"));
        for (Object o : tree) {
//            mkdir(tempDir, (Map<String, Object>) o, xlsxStream, txtStream);
            mkdir(tempDir, JSONUtil.parseObj(JSONUtil.toJsonStr(o)), xlsxStream, txtStream);
        }
        try {
            xlsxStream.close();
            txtStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        File file = ZipUtil.zip(tempDir);

        try {
            com.yida.data.common.core.utils.FileUtil.download(file, "学生导入模板.zip", false, response);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("student import template download fail,msg:[{}]", e.getMessage());
        }
    }

    @Override
    public void downloadCardTemplate(HttpServletResponse response, CurrentUser currentUser) {
        try {
            String fileName = "校园卡号导入模板.xls";
            // 获取导入文件模板
            ClassPathResource classPathResource = new ClassPathResource("template/" + fileName);
            InputStream inputStream = classPathResource.getInputStream();
            com.yida.data.common.core.utils.FileUtil
                .download(com.yida.data.common.core.utils.FileUtil.inputStreamToFile(inputStream), fileName, false, response);
        } catch (Exception e) {
            throw new FebsException("下载校园卡号导入模板失败");
        }
    }

    @Override
    public List<String> getParentByPhoneAndSchoolId(List<String> phone, Long schoolId) {
        return baseMapper.getParentByPhoneAndSchoolId(phone, schoolId);

    }

    private File mkdir(String root, Map<String, Object> tree, InputStream xlsxStream, InputStream txtStream) {
        // 建立当前层次目录
        File dir = null;
        String dirPath = null;
        if ("1".equals(tree.get("type").toString())) {
            dirPath = root + "\\" + tree.get("id").toString() + "." + tree.get("label");
        } else {
            dirPath = root + "\\" + tree.get("label");
        }
        dir = FileUtil.mkdir(dirPath);
        if ("0".equals(tree.get("type").toString())) {
            FileWriter.create(FileUtil.newFile(dir.getAbsolutePath() + "\\" +
                "请阅读我！！！.txt")).writeFromStream(getClass().getClassLoader().getResourceAsStream(
                "template/student.txt"), true);
        }
        // 建立子部门目录
        if (tree.containsKey("children") && CollUtil.isNotEmpty((List) tree.get("children"))) {
            for (Map<String, Object> child : (List<Map<String, Object>>) tree.get("children")) {
                File localtion = mkdir(root + "/" + tree.get("label"), child, xlsxStream, txtStream);
                if ("1".equals(child.get("type").toString())) {
                    FileWriter.create(FileUtil.newFile(localtion.getAbsolutePath() + "\\" +
                        "student.xlsx"))
                        .writeFromStream(getClass().getClassLoader().getResourceAsStream("template/student.xlsx"),
                            true);
                }
            }
        }
        return dir;
    }

    @Override
    public Page<EduStudent> listNotBuyStudent(ListBuyStudentDTO dto) {

        Page<EduStudent> res = baseMapper.listNotBuyStudent(Page.of(dto.getPageNum(), dto.getPageSize()), dto);
        if (CollUtil.isNotEmpty(res.getRecords())) {
            fillStudentDept(res.getRecords());
        }
        return res;
    }

    @Override
    public List<String> findParentWxId(List<Long> studentId) {
        if (CollectionUtil.isEmpty(studentId)) {
            return new ArrayList<>();
        }
        return baseMapper.findParentWxId(studentId);
    }

    @Override
    public List<EduStudent> listAllNotBuyStudent(ListBuyStudentDTO listBuyStudentDTO) {
        return baseMapper.listNotBuyStudent(listBuyStudentDTO);
    }

    @Override
    public List<ListParentByStudentIdsVO> listParentByStudentIds(List<Long> studentIds) {
        if (CollectionUtil.isNotEmpty(studentIds)) {
            return baseMapper.listParentByStudentIds(studentIds);
        }
        return new ArrayList<>();
    }

    @Override
    @Transactional
    public void syncQywxStudent() {
        log.info("开始同步学生数据");
        // 查询所有绑定了企业微信的学校信息
        List<Dept> schoolList = remoteDeptService.listSchoolBySchoolType(Dept.TYPE_QYWX).getData();
        if (CollUtil.isNotEmpty(schoolList)) {
            for (Dept school : schoolList) {
                log.info("[{}], schoolId:[{}]开始同步学生数据", school.getDeptName(), school.getDeptId());
                Map<Long, EduUserDept> classCache = new HashMap<>();
                EduApp qywxApp = commonService
                    .getAppByCodeAndSchool(AppConstant.CONTACT_SCHOOL, null, school.getDeptId());
                String accessToken = wxUtil.getAccessToken(qywxApp.getWxCorpId(), qywxApp.getWxSecret());
                // 判断学校是否绑定了大华
                boolean bindDaHuaFlag = false;
                if (school.getSchoolType().contains(Dept.TYPE_DAHUA)) {
                    bindDaHuaFlag = true;
                }

                // 所有的学生
                JSONArray studentList = wxUtil.getStudentList(accessToken, school.getWxId());
                Set<String> studentWxIds = new HashSet<>();
                Set<String> parentWxIds = new HashSet<>();
                for (Object studentO : studentList) {
                    JSONObject student = (JSONObject) studentO;
                    // 是否存在该学生
                    String studentWxId = student.getStr("student_userid");
                    String studentName = student.getStr("name");
                    JSONArray studentClass = student.getJSONArray("department");
                    JSONArray wxParents = student.getJSONArray("parents");
                    // 查出来的学生与家长
                    for (Object parent : wxParents) {
                        parentWxIds.add(((JSONObject) parent).getStr("parent_userid"));
                    }
                    studentWxIds.add(studentWxId);
                    EduStudent eduStudent = baseMapper.getStudentWithParent(null, studentWxId, school.getDeptId());
                    if (eduStudent == null) {
                        eduStudent = new EduStudent();
                        eduStudent.setWxId(studentWxId);
                        eduStudent.setStuNumber(studentWxId);
                        eduStudent.setStuName(studentName);
                        eduStudent.setSchoolId(school.getDeptId());
                        eduStudent.setSchoolName(school.getDeptName());
                        // 部门
                        Long classWxId = Long.valueOf(studentClass.get(0).toString());
                        EduUserDept clazz;
                        if (classCache.containsKey(classWxId)) {
                            clazz = classCache.get(classWxId);
                        } else {
                            clazz = eduUserDeptService.getClassBySchoolAndWx(school.getDeptId(), classWxId);
                            classCache.put(classWxId, clazz);
                        }
                        if (clazz == null) {
                            continue;
                        }
                        EduUserDept grade = eduUserDeptService.getById(clazz.getParentId());
                        EduUserDept section = eduUserDeptService.getById(grade.getParentId());
                        EduUserDept campus = eduUserDeptService.getById(section.getParentId());
                        eduStudent.setCampusId(campus.getDeptId());
                        eduStudent.setCampusName(campus.getDeptName());
                        eduStudent.setSectionId(section.getDeptId());
                        eduStudent.setSectionName(section.getDeptName());
                        eduStudent.setGradeId(grade.getDeptId());
                        eduStudent.setGradeName(grade.getDeptName());
                        eduStudent.setClassId(clazz.getDeptId());
                        eduStudent.setClassName(clazz.getDeptName());
                        // 调用大华接口
                        if (bindDaHuaFlag) {
                            log.info("新增学生处理的学生数据: {}", eduStudent);
                            String cardNum = DaHuaUtil.generateCardNum();
                            eduStudent.setDahuaCode(eduStudent.getStuNumber().length() > 20
                                ? DaHuaUtil.generateCode()
                                : eduStudent.getStuNumber());
                            eduStudent.setDahuaId(
                                DaHuaUtil.addUser(eduStudent.getStuName(), eduStudent.getDahuaCode(),
                                    0, 1, cardNum,
                                    school.getDahuaId(), campus.getDaHuaId(), section.getDaHuaId(),
                                    grade.getDaHuaId(), clazz.getDaHuaId(), "")
                            );
                        }
                        save(eduStudent);
                    } else {
                        // 学生是否更改
                        boolean editFlag = false;
                        if (!eduStudent.getStuName().equals(studentName)) {
                            eduStudent.setStuName(studentName);
                            editFlag = true;
                        }
                        EduUserDept existClass = eduUserDeptService.getById(eduStudent.getClassId());
                        if (!studentClass.contains(existClass.getWxId())) {
                            Long classWxId = Long.valueOf(studentClass.get(0).toString());
                            EduUserDept clazz;
                            if (classCache.containsKey(classWxId)) {
                                clazz = classCache.get(classWxId);
                            } else {
                                log.info("schoolId:[{}],classWxId:[{}]", school.getDeptId(), classWxId);
                                clazz = eduUserDeptService.getClassBySchoolAndWx(school.getDeptId(), classWxId);
                                classCache.put(classWxId, clazz);
                            }
                            if (clazz == null) {
                                continue;
                            }
                            EduUserDept grade = eduUserDeptService.getById(clazz.getParentId());
                            EduUserDept section = eduUserDeptService.getById(grade.getParentId());
                            EduUserDept campus = eduUserDeptService.getById(section.getParentId());
                            eduStudent.setCampusId(campus.getDeptId());
                            eduStudent.setCampusName(campus.getDeptName());
                            eduStudent.setSectionId(section.getDeptId());
                            eduStudent.setSectionName(section.getDeptName());
                            eduStudent.setGradeId(grade.getDeptId());
                            eduStudent.setGradeName(grade.getDeptName());
                            eduStudent.setClassId(clazz.getDeptId());
                            eduStudent.setClassName(clazz.getDeptName());
                            editFlag = true;
                            // 调用大华接口
                            if (bindDaHuaFlag) {
                                log.info("编辑学生处理的学生数据: {}", eduStudent);
                                if (Objects.nonNull(eduStudent.getDahuaId())) {
                                    DaHuaUtil.updateUser(eduStudent.getDahuaId(), eduStudent.getStuName(),
                                        eduStudent.getDahuaCode(), 0, 1,
                                        school.getDahuaId(), campus.getDaHuaId(), section.getDaHuaId(),
                                        grade.getDaHuaId(), clazz.getDaHuaId(), "");
                                } else {
                                    String cardNum = DaHuaUtil.generateCardNum();
                                    eduStudent.setDahuaCode(eduStudent.getStuNumber().length() > 20
                                        ? DaHuaUtil.generateCode()
                                        : eduStudent.getStuNumber());
                                    eduStudent.setDahuaId(
                                        DaHuaUtil.addUser(eduStudent.getStuName(), eduStudent.getDahuaCode(),
                                            0, 1, cardNum,
                                            school.getDahuaId(), campus.getDaHuaId(), section.getDaHuaId(),
                                            grade.getDaHuaId(), clazz.getDaHuaId(), "")
                                    );
                                }
                            }
                        }
                        // 学生被更改了 修改
                        if (editFlag) {
                            updateById(eduStudent);
                        }
                    }
                    List<EduParent> eduParentList = eduStudent.getParents();
                    List<Long> eduParentListCopy = CollUtil.isNotEmpty(eduParentList) ?
                        eduParentList.stream().map(EduParent::getId).collect(Collectors.toList()) : new ArrayList<>();
                    if (CollUtil.isNotEmpty(wxParents)) {
                        for (Object wxParentO : wxParents) {
                            JSONObject wxParent = (JSONObject) wxParentO;
                            boolean existParent = false;
                            // 是否存在该家长
                            if (CollUtil.isNotEmpty(eduParentList)) {
                                for (EduParent parent : eduParentList) {
                                    if (wxParent.getStr("parent_userid").equals(parent.getWxId())) {
                                        // 修改了家长手机号
                                        if (!wxParent.getStr("mobile").equals(parent.getMobile())) {
                                            parent.setMobile(wxParent.getStr("mobile"));
                                            eduParentMapper.updateById(parent);
                                        }
                                        // 修改了关系  进行替换
                                        if (!wxParent.getStr("relation").equals(parent.getParentType())) {
                                            EduParentType parentType = eduParentTypeService
                                                .getParentType(wxParent.getStr("relation"));
                                            eduParentStudentService.remove(Wrappers.<EduParentStudent>lambdaQuery()
                                                .eq(EduParentStudent::getStudentId, eduStudent.getId())
                                                .eq(EduParentStudent::getParentId, parent.getId()));
                                            EduParentStudent parentStudent = new EduParentStudent();
                                            parentStudent.setStudentId(eduStudent.getId());
                                            parentStudent.setParentId(parent.getId());
                                            parentStudent.setParentTypeId(parentType.getId());
                                            eduParentStudentService.save(parentStudent);
                                        }
                                        // 删除列表中的该元素
                                        eduParentListCopy.remove(parent.getId());
                                        existParent = true;
                                    }
                                }
                            }
                            // 家长关系不存在
                            if (!existParent) {
                                EduParent existEduParent = eduParentMapper.selectOne(Wrappers.<EduParent>lambdaQuery()
                                    .eq(EduParent::getWxId, wxParent.getStr("parent_userid"))
                                    .eq(EduParent::getSchoolId, school.getDeptId()));
                                if (existEduParent == null) {
                                    existEduParent = new EduParent();
                                    existEduParent.setMobile(wxParent.getStr("mobile"));
                                    existEduParent.setSchoolId(school.getDeptId());
                                    existEduParent.setWxId(wxParent.getStr("parent_userid"));
                                    eduParentMapper.insert(existEduParent);
                                }
                                EduParentType relation = eduParentTypeService
                                    .getParentType(wxParent.getStr("relation"));
                                EduParentStudent eduParentStudent = new EduParentStudent();
                                eduParentStudent.setStudentId(eduStudent.getId());
                                eduParentStudent.setParentId(existEduParent.getId());
                                eduParentStudent.setParentTypeId(relation.getId());
                                eduParentStudentService.save(eduParentStudent);
                            }
                        }
                    }
                    //家长还有剩余  关系需要被删除
                    if (CollUtil.isNotEmpty(eduParentListCopy)) {
                        eduParentStudentService.remove(Wrappers.<EduParentStudent>lambdaQuery()
                            .eq(EduParentStudent::getStudentId, eduStudent.getId())
                            .in(EduParentStudent::getParentId, eduParentListCopy));
                    }
                }
                // 删除多余的学生与家长
                remove(Wrappers.<EduStudent>lambdaQuery().eq(EduStudent::getSchoolId, school.getDeptId())
                    .notIn(EduStudent::getWxId, studentWxIds));
                eduParentMapper.delete(Wrappers.<EduParent>lambdaQuery().eq(EduParent::getSchoolId, school.getDeptId())
                    .notIn(EduParent::getWxId, parentWxIds));
                eduParentStudentMapper.delNotExistRaltion();
            }
        }
        log.info("同步学生数据结束");
    }

    @Override
    public List<EduStudent> listStudent(ListStudentDTO dto) {
        List<EduStudent> studentList = baseMapper.listStudent(dto);
        // 不数据为空且查询信息才填充学校信息
        if (CollUtil.isNotEmpty(studentList) && (dto == null || dto.getIsQuerySchoolName())) {
            fillStudentDept(studentList);
        }
        return studentList;
    }


    @Override
    public List<EduStudent> listStudentByClassId(ListStudentDTO dto) {
        return baseMapper.listStudentByCondition(dto);
    }

    @Override
    public void exportStudent(PageStudentDTO dto, String key) {
        SelectStudentExportData data = new SelectStudentExportData();
        BeanUtils.copyProperties(dto, data);
        data.setKey(key);
        data.setFunctionName("学生导出");
        exportStudentClient.exportExcelData(data);
    }

    @Override
    public Object getStudentExportInfo(String key) {
        return exportStudentClient.selectExportInfo(key);
    }

    @Override
    public DownLoadPicVO exportStudentPic(PageStudentDTO dto) {
        String uuid = IdUtil.simpleUUID();
        List<EduStudent> studentList = baseMapper.selectStudent(dto);
        DownLoadPicVO vo = new DownLoadPicVO();
        vo.setFinish(0);
        vo.setTotalNum(studentList.size());
        vo.setCurrentNum(0);
        vo.setFileName(uuid);
        redisService.hset(CachePrefixConstant.USER_PIC_DOWNLOAD, uuid, vo);
        downLoadZipPic(studentList, uuid);
        return vo;
    }


    @Async
    public void downLoadZipPic(List<EduStudent> studentList, String uuid) {
        File file = null;
        try {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            String fileName = com.yida.data.common.core.utils.FileUtil.getLocalUploadAddress() + uuid + ".zip";
            file = new File(fileName);
            ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file));
            int i = 0;
            for (EduStudent student : studentList) {
                if (ObjectUtil.isNotNull(student.getAvatar())) {
                    URL url = new URL(student.getAvatar());
                    HttpURLConnection urlcon = (HttpURLConnection) url.openConnection();
                    String message = urlcon.getHeaderField(0);
                    if (StringUtils.isNotEmpty(message)) {
                        if (message.startsWith("HTTP/1.1 404")) {
                            continue;
                        }
                        zos.putNextEntry(new ZipEntry(
                            (ObjectUtil.isNotNull(student.getDahuaCode()) ? student.getDahuaCode() : student.getStuNumber())
                                + ".jpg"));
                        InputStream fis = urlcon.getInputStream();
                        byte[] buffer = new byte[1024];
                        int r = 0;
                        while ((r = fis.read(buffer)) != -1) {
                            zos.write(buffer, 0, r);
                        }
                        fis.close();
                    }
                }
                DownLoadPicVO vo = new DownLoadPicVO();
                vo.setFinish(0);
                vo.setTotalNum(studentList.size());
                vo.setCurrentNum(i);
                vo.setFileName(uuid);
                redisService.hset(CachePrefixConstant.USER_PIC_DOWNLOAD, uuid, vo);
                i++;
            }
            zos.flush();
            zos.close();
            String url = com.yida.data.common.core.utils.FileUtil.uploadFileToMediaServer(uploadUrl, new File(fileName));
            DownLoadPicVO vo = new DownLoadPicVO();
            vo.setFinish(1);
            vo.setTotalNum(studentList.size());
            vo.setCurrentNum(studentList.size());
            vo.setFileName(uuid);
            vo.setFailPath(url);
            redisService.hset(CachePrefixConstant.USER_PIC_DOWNLOAD, uuid, vo);
            stopWatch.stop();
            log.info("导出学生照片数据总共耗时: {}", stopWatch.getTotalTimeSeconds() + "秒");
        } catch (IOException exception) {
            exception.printStackTrace();
        } finally {
            //删除本地文件
            if (file != null) {
                if (FileUtil.exist(file)) {
                    FileUtil.del(file);
                }
            }
        }
    }


    @Override
    public List<EduTeacherDept> listTeacherByStudentId(Long classId) {
        return baseMapper.listTeacherByStudentId(classId);
    }

    @Override
    public StaStudentVO staStudentDistribu(List<Long> schoolIds) {
        StaStudentVO vo = new StaStudentVO();
        // 查询学生信息
        List<EduStudent> studentList =
            CollUtil.isNotEmpty(schoolIds) ? listStudent(ListStudentDTO.builder().schoolIds(schoolIds).build())
                : Collections.emptyList();
        if (CollUtil.isNotEmpty(studentList)) {
            Map<String, Integer> genderCount = new HashMap<>();
            Map<String, Integer> sectionCount = new HashMap<>();
            Map<Integer, Integer> typeCount = new HashMap<>();
            Map<String, Integer> familyTypeCount = new HashMap<>();
            List<Dict> familyTypeList = remoteCommonService.getDict(DictType.FAMILY_TYPE.getValue()).getData();
            for (EduStudent student : studentList) {
                // 性别
                genderCount.put(student.getStuSex(), genderCount.getOrDefault(student.getStuSex(), 0) + 1);
                // 学段
                sectionCount.put(student.getSectionName(), sectionCount.getOrDefault(student.getSectionName(), 0) + 1);
                // 类型
                typeCount.put(student.getType(), typeCount.getOrDefault(student.getType(), 0) + 1);
                // 家庭类型
                if (StrUtil.isNotBlank(student.getFamilyType())) {
                    for (String familyType : student.getFamilyType().split(",")) {
                        familyTypeCount.put(familyType, familyTypeCount.getOrDefault(familyType, 0) + 1);
                    }
                }
            }
            // 性别数据
            if (!genderCount.isEmpty()) {
                List<NameValueVO<String, Integer>> genderList = new ArrayList<>();
                for (Entry<String, Integer> gender : genderCount.entrySet()) {
                    genderList.add(
                        new NameValueVO<String, Integer>(gender.getKey() == null ? "未知" : "0".equals(gender.getKey()) ? "男" : "女",
                            gender.getValue()));
                }
                vo.setGender(genderList);
            }
            // 学段数据
            if (!sectionCount.isEmpty()) {
                List<NameValueVO<String, Integer>> sectionList = new ArrayList<>();
                for (Entry<String, Integer> section : sectionCount.entrySet()) {
                    sectionList
                        .add(new NameValueVO<String, Integer>(Optional.ofNullable(section.getKey()).orElse("未知"),
                            section.getValue()));
                }
                vo.setSection(sectionList);
            }
            // 类型数据
            if (!typeCount.isEmpty()) {
                List<NameValueVO<String, Integer>> typeList = new ArrayList<>();
                for (Entry<Integer, Integer> type : typeCount.entrySet()) {
                    typeList.add(new NameValueVO<String, Integer>(type.getKey() == null ? "未知" : type.getKey() == 0 ? "走读" : "住宿",
                        type.getValue()));
                }
                vo.setType(typeList);
            }
            // 家庭类型
            if (!familyTypeCount.isEmpty()) {
                List<NameValueVO<String, Integer>> familyTypes = new ArrayList<>();
                for (Entry<String, Integer> familyType : familyTypeCount.entrySet()) {
                    for (Dict familyTypeData : familyTypeList) {
                        if (familyTypeData.getValue().equals(familyType.getKey())) {
                            familyTypes.add(new NameValueVO<String, Integer>(familyTypeData.getLabel(), familyType.getValue()));
                            break;
                        }
                    }
                }
                vo.setFamilyType(familyTypes);
            }
        }
        vo.setNum(studentList.size());
        return vo;
    }

    private List<EduStudent> fillStudentDept(List<EduStudent> studentList) {
        Map<Long, String> deptCache = new HashMap<>();
        return studentList.parallelStream().peek(student -> {
            if (deptCache.containsKey(student.getSchoolId())) {
                student.setSchoolName(deptCache.get(student.getSchoolId()));
            } else {
                student.setSchoolName(commonService.getDept(student.getSchoolId()).getDeptName());
                deptCache.put(student.getSchoolId(), student.getSchoolName());
            }
        }).collect(Collectors.toList());
    }

    @Override
    public List<Long> listBaseStudentIds(EduStudent eduStudent) {
        return baseMapper.listBaseStudentIds(eduStudent);
    }

    @Override
    public List<EduStudent> listBaseStudent(EduStudent eduStudent) {
        return baseMapper.listBaseStudent(eduStudent);
    }

    @Override
    public List<EduStudent> bindStudent(BindStudentDTO dto) {
        EduStudent student = null;
        try {
            // 验证学生信息是否存在
            student = getOne(new LambdaQueryWrapper<>(new EduStudent())
                    .eq(EduStudent::getSchoolId, dto.getSchoolId())
                    .eq(EduStudent::getClassId, dto.getClassId())
                    .eq(EduStudent::getStuName, dto.getStuName())
//                .and(wrapper -> wrapper
//                        .eq(EduStudent::getIdNumber, dto.getIdNumber())
//                        .or()
//                        .eq(EduStudent::getStuNumber, dto.getIdNumber()))
            );
        } catch (Exception e) {
            log.error("查询学生信息错误, params: {}", dto, e);
            throw new FebsException("查询学生信息错误");
        }

        if (Objects.isNull(student)) {
            throw new FebsException("学生信息不存在或学生信息错误");
        }
        // 判断是否有关联关系
        List<EduParentStudent> list = eduParentStudentService.list(new LambdaQueryWrapper<>(new EduParentStudent())
            .eq(EduParentStudent::getParentId, dto.getParentId())
            .eq(EduParentStudent::getStudentId, student.getId()));
        if (CollUtil.isNotEmpty(list)) {
            throw new FebsException("请勿重复绑定");
        }
        Dept school = commonService.getDept(dto.getSchoolId());
        // 判断是否调用企业微信接口
        boolean containWx = school.getSchoolType().contains(Dept.TYPE_QYWX);
        boolean isCreateParent = true;
        // 家长信息
        EduParent parent = eduParentMapper.selectById(dto.getParentId());
        parent.setChildren(new ArrayList<>());

        if (containWx) {
            // 判断是新增还是修改家长信息
            List<EduParentStudent> parentStudentRelations = eduParentStudentMapper.listParentStudent(parent.getId());
            if (CollUtil.isNotEmpty(parentStudentRelations)) {
                isCreateParent = false;
                for (EduParentStudent parentStudentRelation : parentStudentRelations) {
                    Map<String, Object> relation = new HashMap<>();
                    relation.put("student_userid", getById(parentStudentRelation.getStudentId()).getWxId());
                    relation.put("relation", parentStudentRelation.getParentTypeName());
                    parent.getChildren().add(relation);
                }
            }
        }
        // 查询家长类型
        EduParentType parentType = eduParentTypeService.getParentType(dto.getParentType());
        // 保存学生与家长对应关系
        EduParentStudent eduParentStudent = new EduParentStudent();
        eduParentStudent.setParentId(dto.getParentId());
        eduParentStudent.setStudentId(student.getId());
        eduParentStudent.setParentTypeId(parentType.getId());
        eduParentStudentMapper.insert(eduParentStudent);
        // 调用企业微信接口
        if (containWx) {
            // 添加当前学生
            parent.getChildren().add(MapUtil.builder(new HashMap<String, Object>())
                .put("student_userid", student.getWxId())
                .put("relation", dto.getParentType())
                .build());
            EduApp app = commonService.getAppByCodeAndSchool(AppConstant.CONTACT_SCHOOL, null, dto.getSchoolId());
            String token = wxUtil.getAccessToken(app.getWxCorpId(), app.getWxSecret());
            // 创建企业微信家长信息
            if (isCreateParent) {
                wxUtil.createParent(token, Collections.singletonList(parent));
            } else {
                wxUtil.updateParent(token, Collections.singletonList(parent));
            }
        }
        return listStudentByParent(dto.getParentId(), "");
    }

    @Override
    public List<Long> listDormIdsByDeptId(ListStudentDTO dto) {

        return baseMapper.listDormIdsByDeptId(dto);
    }

    @Override
    public List<EduStudent> listBaseStudentNoJoin(EduStudent eduStudent) {
        return baseMapper.listBaseStudentNoJoin(eduStudent);
    }

    @Override
    public IPage<OutPageStudentVO> listStudentOutPage(OutPageStudentDTO dto, HttpServletRequest request) {

        // 判断是否在白名单
        WhiteList whiteList = commonService.getWhiteList(SysFunctionEnum.HAI_PU_TIAN.getValue());
        // 当前请求ip地址
//        String ipAddress = FebsUtil.getHttpServletRequestIpAddress();
        String ipAddress = ServletUtil.getClientIP(request, "");
        log.info("当前ip地址: {}", ipAddress);
        if (Objects.isNull(whiteList) || StrUtil.isEmpty(whiteList.getRequestIp())
            || !whiteList.getRequestIp().contains(ipAddress)) {
            throw new FebsException("非法ip地址");
        }
        // 判断当前学校是否开通了此功能
        EduDeptFunction function = commonService.getDeptFunction(SysFunctionEnum.HAI_PU_TIAN.getValue(), dto.getSchoolId());
        if (Objects.isNull(function)) {
            throw new FebsException("当前学校暂不支持此功能");
        }
        // TODO 2023/3/8 需要处理请求次数

        Dict dict = commonService.getDictByTypeAndValueOrLabel("apply_type", null, "食堂消费");
        if (Objects.isNull(dict)) {
            throw new FebsException("当前学校暂不支持此功能");
        }
        IPage<OutPageStudentVO> page = baseMapper.listStudentOutPage(new Page<>(dto.getPageNum(), dto.getPageSize()), dto);
        page.getRecords().forEach(outPageStudentVO -> {
            // 查询是否是会员
            EduStudentApply studentApply = eduStudentApplyMapper
                .getStudentApply(outPageStudentVO.getStudentId(), dict.getValue());
            if (Objects.isNull(studentApply)) {
                // 非会员需要设备头像为空
                outPageStudentVO.setAvatar("");
            }
        });
        return page;
    }

    @Override
    public void updateStudentCache() {
        log.info("开始更新学生信息缓存");
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        redisService.del(CachePrefixConstant.STUDENT_DATA);
        List<EduStudent> studentList = baseMapper.getStudentListWithParent(null, null, null);
        for (EduStudent student : studentList) {
            redisService.hset(CachePrefixConstant.STUDENT_DATA, student.getId().toString(), student);
        }
        stopWatch.stop();
        log.info("更新学生信息缓存结束，总共耗时：[{}]", stopWatch.getTotalTimeSeconds() + "秒");
    }

    @Override
    public List<EduStudent> selectStudent(PageStudentDTO dto) {
        return baseMapper.selectStudent(dto);
    }

    @Override
    public List<EduStudent> listStudentDorm(List<Long> dormRoomIds, SelectStudentDormExportData selectStudentDormExportData) {
        return baseMapper.listStudentDorm(dormRoomIds, selectStudentDormExportData);
    }
}