package com.xjrsoft.module.organization.controller;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.type.TypeReference;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.xjrsoft.common.constant.GlobalConstant;
import com.xjrsoft.common.enums.DeleteMark;
import com.xjrsoft.common.enums.EnabledMark;
import com.xjrsoft.common.enums.GenderDictionaryEnum;
import com.xjrsoft.common.enums.RoleEnum;
import com.xjrsoft.common.enums.WorkflowIsRecycleType;
import com.xjrsoft.common.exception.MyException;
import com.xjrsoft.common.model.result.R;
import com.xjrsoft.common.model.result.RT;
import com.xjrsoft.common.page.ConventPage;
import com.xjrsoft.common.page.PageOutput;
import com.xjrsoft.common.sms.SmsCtcc;
import com.xjrsoft.common.utils.RedisUtil;
import com.xjrsoft.common.utils.TreeUtil;
import com.xjrsoft.common.utils.VoToColumnUtil;
import com.xjrsoft.config.CommonPropertiesConfig;
import com.xjrsoft.module.base.entity.BaseClass;
import com.xjrsoft.module.base.entity.BaseGrade;
import com.xjrsoft.module.base.service.IBaseClassService;
import com.xjrsoft.module.base.service.IBaseGradeService;
import com.xjrsoft.module.organization.dto.AddUserDto;
import com.xjrsoft.module.organization.dto.BindOpenidDto;
import com.xjrsoft.module.organization.dto.LoginResetPasswordDto;
import com.xjrsoft.module.organization.dto.RegisterDto;
import com.xjrsoft.module.organization.dto.ResetPasswordDto;
import com.xjrsoft.module.organization.dto.UpdateInfoDto;
import com.xjrsoft.module.organization.dto.UpdatePasswordDto;
import com.xjrsoft.module.organization.dto.UpdateUserDto;
import com.xjrsoft.module.organization.dto.UploadSignDto;
import com.xjrsoft.module.organization.dto.UserPageDto;
import com.xjrsoft.module.organization.dto.UserStudentAddDto;
import com.xjrsoft.module.organization.dto.UserStudentBindDto;
import com.xjrsoft.module.organization.dto.UserStudentDeleteDto;
import com.xjrsoft.module.organization.entity.Department;
import com.xjrsoft.module.organization.entity.Post;
import com.xjrsoft.module.organization.entity.Role;
import com.xjrsoft.module.organization.entity.User;
import com.xjrsoft.module.organization.entity.UserDeptRelation;
import com.xjrsoft.module.organization.entity.UserPostRelation;
import com.xjrsoft.module.organization.entity.UserRoleRelation;
import com.xjrsoft.module.organization.service.IDepartmentService;
import com.xjrsoft.module.organization.service.IPostService;
import com.xjrsoft.module.organization.service.IRoleService;
import com.xjrsoft.module.organization.service.IUserDeptRelationService;
import com.xjrsoft.module.organization.service.IUserPostRelationService;
import com.xjrsoft.module.organization.service.IUserRoleRelationService;
import com.xjrsoft.module.organization.service.IUserService;
import com.xjrsoft.module.organization.service.IUserStudentService;
import com.xjrsoft.module.organization.utils.OrganizationUtil;
import com.xjrsoft.module.organization.vo.*;
import com.xjrsoft.module.oss.factory.OssFactory;
import com.xjrsoft.module.student.entity.BaseStudentSchoolRoll;
import com.xjrsoft.module.system.entity.File;
import com.xjrsoft.module.system.service.IFileService;
import com.xjrsoft.module.teacher.entity.BaseTeacher;
import com.xjrsoft.module.workflow.constant.WorkflowConstant;
import com.xjrsoft.module.workflow.entity.WorkflowExtra;
import com.xjrsoft.module.workflow.mapper.WorkflowExtraMapper;
import com.xjrsoft.module.workflow.service.IWorkflowExecuteService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricProcessInstanceQuery;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户 前端控制器
 * </p>
 *
 * @author tzx
 * @since 2022-03-02
 */
@RestController
@RequestMapping(GlobalConstant.ORGANIZATION_MODULE_PREFIX + "/user")
@Api(value = GlobalConstant.ORGANIZATION_MODULE_PREFIX + "/user", tags = "用户")
@AllArgsConstructor
public class UserController {

    private final IUserService userService;

    private final IDepartmentService departmentService;

    private final IPostService postService;

    private final IRoleService roleService;

    private final IUserRoleRelationService userRoleRelationService;

    private final IUserDeptRelationService userDeptRelationService;

    private final IUserPostRelationService userPostRelationService;

    private final CommonPropertiesConfig propertiesConfig;

    private final WorkflowExtraMapper workflowExtraMapper;

    private final RedisUtil redisUtil;

    private final IFileService fileService;

    private final SmsCtcc smsCtcc;
    private final IBaseGradeService baseGradeService;
    private final IUserStudentService userStudentService;
    private final IBaseClassService baseClassService;
    private final IWorkflowExecuteService workflowExecuteService;

    private final HistoryService historyService;


    @GetMapping(value = "/list")
    @ApiOperation(value = "用户列表（不分页）")
    public R list(String keyword) {

        List<User> list = userService.list(Wrappers.lambdaQuery(User.class)
                .like(StrUtil.isNotBlank(keyword), User::getUserName, keyword)
                .like(StrUtil.isNotBlank(keyword), User::getCode, keyword)
                .like(StrUtil.isNotBlank(keyword), User::getName, keyword)
                .like(StrUtil.isNotBlank(keyword), User::getMobile, keyword)
                .select(User.class, x -> VoToColumnUtil.fieldsToColumns(UserListVo.class).contains(x.getProperty())));
        List<UserListVo> userListVos = BeanUtil.copyToList(list, UserListVo.class);
        return R.ok(userListVos);
    }

    @GetMapping(value = "/page")
    @ApiOperation(value = "用户列表（分页）")
    public R page(UserPageDto dto) {
        List<Long> deptIds = new ArrayList<>();
        if (ObjectUtil.isNotNull(dto.getDepartmentId())) {
            List<Department> list = redisUtil.get(GlobalConstant.DEP_CACHE_KEY, new TypeReference<List<Department>>() {
            });
            //当前部门的层级
            String hierarchy = list.stream().filter(x -> x.getId().equals(dto.getDepartmentId())).findFirst().orElse(new Department()).getHierarchy();
            if (StrUtil.isNotBlank(hierarchy)) {
                //层级里面包含当前部门层级的就是它的子集，如1-1，下面包含了1-1、1-1-2这种
                deptIds = list.stream().filter(x -> StrUtil.isNotBlank(x.getHierarchy()) && x.getHierarchy().contains(hierarchy)).map(Department::getId).collect(Collectors.toList());
            } else {
                //如果不存在层级就查询自己的数据
                deptIds.add(dto.getDepartmentId());
            }
        }

        //因为多表关联 会有多个表都使用了id字段，  所以必须专门指定主表的Id
        if (ObjectUtil.isNotNull(dto.getDepartmentId())) {//不为空联合查询
            MPJLambdaWrapper<User> queryUser = new MPJLambdaWrapper<>();
            queryUser
                    .disableSubLogicDel()
                    .distinct()
                    .like(StrUtil.isNotBlank(dto.getKeyword()), User::getName, dto.getKeyword())
                    .or(StrUtil.isNotBlank(dto.getKeyword()), x -> x.like(StrUtil.isNotBlank(dto.getKeyword()), User::getCode, dto.getKeyword()))
                    .in(ObjectUtil.isNotNull(dto.getDepartmentId()), UserDeptRelation::getDeptId, deptIds)
                    .like(StrUtil.isNotBlank(dto.getUserName()), User::getUserName, dto.getUserName())
                    .like(StrUtil.isNotBlank(dto.getCode()), User::getCode, dto.getCode())
                    .like(StrUtil.isNotBlank(dto.getName()), User::getName, dto.getName())
                    .like(StrUtil.isNotBlank(dto.getMobile()), User::getMobile, dto.getMobile())
                    .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 1), Role::getId, dto.getTreeId())
                    .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 2), UserDeptRelation::getDeptId, dto.getTreeId())
                    .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 3), BaseStudentSchoolRoll::getGradeId, dto.getTreeId())
                    .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 4), BaseStudentSchoolRoll::getClassId, dto.getTreeId())
                    .eq(ObjectUtil.isNotNull(dto.getUserType()), Role::getId, dto.getUserType())
                    .eq(ObjectUtil.isNotNull(dto.getEmployType()), BaseTeacher::getEmployType, dto.getEmployType())
                    .eq(ObjectUtil.isNotNull(dto.getEmployWay()), BaseTeacher::getEmployWay, dto.getEmployWay())
                    .orderByDesc(User::getId)
                    .select(User::getId)
                    .selectAs(BaseTeacher::getEmployWay, UserPageVo::getEmployWay)
                    .selectAs(BaseTeacher::getEmployType, UserPageVo::getEmployType)
                    .select(User.class, x -> VoToColumnUtil.fieldsToColumns(UserPageVo.class).contains(x.getProperty()))
                    .leftJoin(UserDeptRelation.class, UserDeptRelation::getUserId, User::getId)
                    .leftJoin(UserRoleRelation.class, UserRoleRelation::getUserId, User::getId)
                    .leftJoin(BaseTeacher.class, BaseTeacher::getUserId, User::getId)
                    .leftJoin(BaseStudentSchoolRoll.class, BaseStudentSchoolRoll::getUserId, User::getId)
                    .leftJoin(Role.class, Role::getId, UserRoleRelation::getRoleId);
            IPage<UserPageVo> page = userService.selectJoinListPage(ConventPage.getPage(dto), UserPageVo.class, queryUser);
            for (UserPageVo record : page.getRecords()) {
                record.setGenderCn(GenderDictionaryEnum.getValue(record.getGender()));
            }

            PageOutput<UserPageVo> pageOutput = ConventPage.getPageOutput(page, UserPageVo.class);

            return R.ok(pageOutput);
        } else {
//            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//            queryWrapper.like(StrUtil.isNotBlank(dto.getKeyword()), User::getName, dto.getKeyword())
//                    .or(StrUtil.isNotBlank(dto.getKeyword()), x -> x.like(StrUtil.isNotBlank(dto.getKeyword()), User::getCode, dto.getKeyword()))
//                    .like(StrUtil.isNotBlank(dto.getUserName()), User::getUserName, dto.getUserName())
//                    .like(StrUtil.isNotBlank(dto.getCode()), User::getCode, dto.getCode())
//                    .like(StrUtil.isNotBlank(dto.getName()), User::getName, dto.getName())
//                    .like(StrUtil.isNotBlank(dto.getMobile()), User::getMobile, dto.getMobile())
//                    .orderByDesc(User::getCreateDate)
//                    .select(User.class, x -> VoToColumnUtil.fieldsToColumns(UserPageVo.class).contains(x.getProperty()));
//
//            IPage<User> page = userService.page(ConventPage.getPage(dto),queryWrapper);

                    MPJLambdaWrapper<User> queryUser = new MPJLambdaWrapper<>();
                    queryUser
                            .disableSubLogicDel()
                            .distinct()
                            .like(StrUtil.isNotBlank(dto.getKeyword()), User::getName, dto.getKeyword())
                            .or(StrUtil.isNotBlank(dto.getKeyword()), x -> x.like(StrUtil.isNotBlank(dto.getKeyword()), User::getCode, dto.getKeyword()))
                            .like(StrUtil.isNotBlank(dto.getUserName()), User::getUserName, dto.getUserName())
                            .like(StrUtil.isNotBlank(dto.getCode()), User::getCode, dto.getCode())
                            .like(StrUtil.isNotBlank(dto.getName()), User::getName, dto.getName())
                            .like(StrUtil.isNotBlank(dto.getMobile()), User::getMobile, dto.getMobile())
                            .eq(ObjectUtil.isNotNull(dto.getUserType()), Role::getId, dto.getUserType())
                            .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 1), Role::getId, dto.getTreeId())
                            .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 2), UserDeptRelation::getDeptId, dto.getTreeId())
                            .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 3), BaseStudentSchoolRoll::getGradeId, dto.getTreeId())
                            .eq((ObjectUtil.isNotNull(dto.getTreeType()) && dto.getTreeType() == 4), BaseStudentSchoolRoll::getClassId, dto.getTreeId())
                            .eq(ObjectUtil.isNotNull(dto.getEmployType()), BaseTeacher::getEmployType, dto.getEmployType())
                            .eq(ObjectUtil.isNotNull(dto.getEmployWay()), BaseTeacher::getEmployWay, dto.getEmployWay())
                            .orderByDesc(User::getId)
                            .select(User::getId)
                            .selectAs(BaseTeacher::getEmployWay, UserPageVo::getEmployWay)
                            .selectAs(BaseTeacher::getEmployType, UserPageVo::getEmployType)
                            .select(User.class, x -> VoToColumnUtil.fieldsToColumns(UserPageVo.class).contains(x.getProperty()))
                            .leftJoin(UserRoleRelation.class, UserRoleRelation::getUserId, User::getId)
                            .leftJoin(BaseStudentSchoolRoll.class, BaseStudentSchoolRoll::getUserId, User::getId)
                            .leftJoin(UserDeptRelation.class, UserDeptRelation::getUserId, User::getId)
                            .leftJoin(BaseTeacher.class, BaseTeacher::getUserId, User::getId)
                            .leftJoin(Role.class, Role::getId, UserRoleRelation::getRoleId);
            IPage<UserPageVo> page = userService.selectJoinListPage(ConventPage.getPage(dto), UserPageVo.class,queryUser);
            for (UserPageVo record : page.getRecords()) {
                record.setGenderCn(GenderDictionaryEnum.getValue(record.getGender()));
            }
            PageOutput<UserPageVo> pageOutput = ConventPage.getPageOutput(page, UserPageVo.class);
            return R.ok(pageOutput);
        }
    }

    @GetMapping(value = "/info")
    @ApiOperation(value = "根据id查询用户信息")
    public R info(@RequestParam Long id) {
        User user = userService.getById(id);
        if (user == null) {
            R.error("找不到此用户！");
        }
        UserVo userVo = BeanUtil.toBean(user, UserVo.class);
        userVo.setGenderCn(GenderDictionaryEnum.getValue(userVo.getGender()));
        List<Long> deptIds = userDeptRelationService.list(Wrappers.lambdaQuery(UserDeptRelation.class)
                        .eq(UserDeptRelation::getUserId, user.getId()))
                .stream().map(UserDeptRelation::getDeptId).collect(Collectors.toList());
        String allDeptIdStr = StrUtil.join(StringPool.COMMA, deptIds);
        userVo.setDepartmentIds(allDeptIdStr);

        List<Long> roleIds = userRoleRelationService.list(
                Wrappers.lambdaQuery(UserRoleRelation.class)
                .eq(UserRoleRelation::getUserId, user.getId())
                ).stream().map(UserRoleRelation::getRoleId).collect(Collectors.toList());
        userVo.setRoleIds(roleIds);
        return R.ok(userVo);
    }

    @GetMapping(value = "/detail")
    @ApiOperation(value = "根据id查询用户信息")
    public RT<UserDetailVo> detail(@RequestParam Long id) {
        User user = userService.getById(id);
        if (user == null) {
            R.error("找不到此用户！");
        }
        UserDetailVo userVo = BeanUtil.toBean(user, UserDetailVo.class);
        List<Long> deptIds = userDeptRelationService.list(Wrappers.lambdaQuery(UserDeptRelation.class)
                        .eq(UserDeptRelation::getUserId, user.getId()))
                .stream().map(UserDeptRelation::getDeptId).collect(Collectors.toList());
        String allDeptIdStr = StrUtil.join(StringPool.COMMA, deptIds);
        userVo.setDepartmentIds(allDeptIdStr);

        return RT.ok(userVo);
    }

    @PostMapping
    @ApiOperation(value = "新增用户")
    public R add(@Valid @RequestBody AddUserDto dto) {
        if (!OrganizationUtil.validatePassword(dto.getPassword())) {
            return R.error("密码必须包含大写字母、小写字母、数字和特殊字符，长度8~16位");
        }
        return R.ok(userService.add(dto));
    }

    @PutMapping
    @ApiOperation(value = "修改用户 不能修改用户名")
    public R update(@Valid @RequestBody UpdateUserDto dto) {
        return R.ok(userService.update(dto));
    }

    @GetMapping(value = "/current/info")
    @ApiOperation(value = "当前登录用户信息")
    public R info() {
        SaSession tokenSession = StpUtil.getTokenSession();
        User user = tokenSession.get(GlobalConstant.LOGIN_USER_INFO_KEY, new User());

        List<Long> roleIds = userRoleRelationService.list(Wrappers.lambdaQuery(UserRoleRelation.class)
                        .eq(UserRoleRelation::getUserId, user.getId()))
                .stream().map(UserRoleRelation::getRoleId).collect(Collectors.toList());

        List<Long> deptIds = userDeptRelationService.list(Wrappers.lambdaQuery(UserDeptRelation.class)
                        .eq(UserDeptRelation::getUserId, user.getId()))
                .stream().map(UserDeptRelation::getDeptId).collect(Collectors.toList());

        List<Long> postIds = userPostRelationService.list(Wrappers.lambdaQuery(UserPostRelation.class)
                        .eq(UserPostRelation::getUserId, user.getId()))
                .stream().map(UserPostRelation::getPostId).collect(Collectors.toList());

        UserInfoVo vo = BeanUtil.toBean(user, UserInfoVo.class);
        vo.setGenderCn(GenderDictionaryEnum.getValue(vo.getGender()));
        if (roleIds.size() > 0) {

            List<Role> list = roleService.list(Wrappers.lambdaQuery(Role.class).in(Role::getId, roleIds));

            List<UserRoleVo> userRoleVoList = BeanUtil.copyToList(list, UserRoleVo.class);
            vo.setRoles(userRoleVoList);

        }

        if (deptIds.size() > 0) {
            List<Department> list = departmentService.list(Wrappers.lambdaQuery(Department.class).in(Department::getId, deptIds));

            List<UserDeptVo> userDeptVoList = BeanUtil.copyToList(list, UserDeptVo.class);
            vo.setDepartments(userDeptVoList);
        }

        if (postIds.size() > 0) {
            List<Post> list = postService.list(Wrappers.lambdaQuery(Post.class).in(Post::getId, postIds));

            List<UserPostVo> userPostVoList = BeanUtil.copyToList(list, UserPostVo.class);
            vo.setPosts(userPostVoList);
        }

        // 家长
        List<UserStudentVo> list = baseClassService.getStudents(user.getId());
        if (list != null) {
            vo.setStudents(list);
        }

        // 学生
        UserStudentVo userStudentVo = baseClassService.getClassInfo(user.getId());
        if (userStudentVo != null) {
            vo.setClassId(userStudentVo.getClassId());
            vo.setClassName(userStudentVo.getClassName());
        }

        List<File> fileList = fileService.list(Wrappers.lambdaQuery(File.class).eq(File::getFolderId, user.getSignFolderId()));
        if(!fileList.isEmpty()){
            vo.setSignUrl(fileList.get(0).getFileUrl());
        }
        return R.ok(vo);
    }

    @GetMapping(value = "/pending/count")
    @ApiOperation(value = "当前待办数量统计")
    public RT<PendingCountDto> pendingCount() {
        PendingCountDto pendingCountDto = new PendingCountDto();
        pendingCountDto.setWfPendingCount(workflowExecuteService.pendingCount());

        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .variableValueEquals(WorkflowConstant.PROCESS_START_USER_ID_KEY, StpUtil.getLoginIdAsLong())
                .variableValueEquals(WorkflowConstant.PROCESS_ISRECYCLE_FLAG_KEY, WorkflowIsRecycleType.NO.getCode());
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.orderByProcessInstanceStartTime().desc().list();

        //获取到所有流程id
        List<String> processIds = historicProcessInstances.stream().map(HistoricProcessInstance::getId).collect(Collectors.toList());
        int myApplicationCount = 0;
        for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
            if (!ObjectUtil.isNotNull(historicProcessInstance.getEndTime())) {
                myApplicationCount ++;
            }
        }
        pendingCountDto.setMyApplicationCount(myApplicationCount);

        return RT.ok(pendingCountDto);
    }

    @GetMapping(value = "/reset-user-page")
    @ApiOperation(value = "移动端用户列表")
    public RT<PageOutput<ResetUserPageVo>> resetUserPage(UserPageDto dto) {
        PageOutput<ResetUserPageVo> userPage = userService.getResetUserPage(dto);

        return RT.ok(userPage);
    }

    @GetMapping(value = "/reset-page-role-list")
    @ApiOperation(value = "移动端用户列表分类")
    public RT<List<ResetUserRoleVo>> resetUserTree() {
        List<ResetUserRoleVo> roleList =  new ArrayList<>();
        roleList.add(new ResetUserRoleVo(){{
            setId(null);
            setName("全部");
        }});
        roleList.add(new ResetUserRoleVo(){{
            setId(2L);
            setName("教师");
        }});
        roleList.add(new ResetUserRoleVo(){{
            setId(3L);
            setName("学生");
        }});
        roleList.add(new ResetUserRoleVo(){{
            setId(4L);
            setName("家长");
        }});

        return RT.ok(roleList);
    }

    @GetMapping(value = "/pc-role-tree")
    @ApiOperation(value = "pc端用户列表分类")
    public RT<List<ResetUserRoleVo>> pcRoleTree() {
        List<Department> departmentList = departmentService.list(
            new QueryWrapper<Department>().lambda()
            .eq(Department::getDeleteMark, DeleteMark.NODELETE.getCode())
        );
        List<ResetUserRoleVo> voList = new ArrayList<>();
        for (Department department : departmentList) {
            ResetUserRoleVo roleVo = BeanUtil.toBean(department, ResetUserRoleVo.class);
            roleVo.setType(2);
            voList.add(roleVo);
        }

        List<ResetUserRoleVo> treeVoList = TreeUtil.build(voList);
        List<ResetUserRoleVo> roleList =  new ArrayList<>();
        ResetUserRoleVo roleVo = new ResetUserRoleVo() {{
            setId(2L);
            setType(1);
            setName("教师");
            setChildren(treeVoList);
        }};
        roleList.add(roleVo);

        List<ResetUserRoleVo> gradeClassList = new ArrayList<>();
        baseClassService.list(
            new QueryWrapper<BaseClass>().lambda().eq(BaseClass::getDeleteMark, DeleteMark.NODELETE.getCode())
        ).forEach(e->{
            ResetUserRoleVo gradeClass = new ResetUserRoleVo();
            gradeClass.setType(4);
            gradeClass.setName(e.getName());
            gradeClass.setId(e.getId());
            gradeClass.setParentId(e.getGradeId());
            gradeClassList.add(gradeClass);
        });

        baseGradeService.list(
            new QueryWrapper<BaseGrade>().lambda().eq(BaseGrade::getDeleteMark, DeleteMark.NODELETE.getCode())
        ).forEach(e->{
            ResetUserRoleVo gradeClass = new ResetUserRoleVo();
            gradeClass.setType(3);
            gradeClass.setName(e.getName());
            gradeClass.setId(e.getId());
            gradeClass.setParentId(3L);
            gradeClassList.add(gradeClass);
        });
        List<ResetUserRoleVo> gradeClassTreeVoList = TreeUtil.build(gradeClassList);
        roleList.add(new ResetUserRoleVo(){{
            setId(3L);
            setType(1);
            setName("学生");
            setChildren(gradeClassTreeVoList);
        }});
        roleList.add(new ResetUserRoleVo(){{
            setId(4L);
            setType(1);
            setName("家长");
        }});

        return RT.ok(roleList);
    }

    @PutMapping("/update/info")
    @ApiOperation(value = "登陆人修改自己得用户信息")
    public R updateInfo(@RequestBody @Valid UpdateInfoDto dto) {
        User updateUserInfo = BeanUtil.toBean(dto, User.class);
        updateUserInfo.setId(StpUtil.getLoginIdAsLong());

        CompletableFuture.runAsync(() -> {
            List<User> list = userService.list();
            redisUtil.set(GlobalConstant.USER_CACHE_KEY, list);
        });
        return R.ok(userService.updateById(updateUserInfo));
    }

    @PutMapping("/update/password")
    @ApiOperation(value = "当前登录用户修改本人密码")
    public RT<Boolean> updatePassword(@RequestBody @Valid UpdatePasswordDto dto) {
        if (!OrganizationUtil.validatePassword(dto.getNewPassword())) {
            return RT.error("密码必须包含大写字母、小写字母、数字和特殊字符，长度8~16位");
        }
        User user = userService.getById(StpUtil.getLoginIdAsLong());

        if (!BCrypt.checkpw(dto.getOldPassword(), user.getPassword())) {
            return RT.error("当前密码填写错误！");
        }
        if (!StrUtil.equals(dto.getNewPassword(), dto.getConfirmPassword())) {
            return RT.error("2次密码输入不一致！");
        }
        return RT.ok(userService.updatePassword(dto));
    }

    @PutMapping("/login/reset-password")
    @ApiOperation(value = "登录后修改密码")
    public RT<Boolean> loginResetPassword(@RequestBody @Valid LoginResetPasswordDto dto) {
        if (!OrganizationUtil.validatePassword(dto.getNewPassword())) {
            return RT.error("密码必须包含大写字母、小写字母、数字和特殊字符，长度8~16位");
        }
        if (!StrUtil.equals(dto.getNewPassword(), dto.getConfirmPassword())) {
            return RT.error("2次密码输入不一致！");
        }
        UpdatePasswordDto pd = BeanUtil.toBean(dto, UpdatePasswordDto.class);
        return RT.ok(userService.updatePassword(pd));
    }

    @PostMapping("/update/avatar")
    @ApiOperation(value = "当前登录用户修改头像")
    public R uploadAvatar(@RequestParam("file") MultipartFile file) throws Exception {
        if (file.isEmpty()) {
            throw new MyException("上传文件不能为空");
        }

        //上传文件
        String suffix = Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().lastIndexOf(StringPool.DOT));
        String url = Objects.requireNonNull(OssFactory.build()).uploadSuffix(file.getBytes(), suffix);

        User updateUser = new User();
        updateUser.setId(StpUtil.getLoginIdAsLong());
        updateUser.setAvatar(url);
        userService.updateById(updateUser);

        SaSession tokenSession = StpUtil.getTokenSession();
        User user = tokenSession.get(GlobalConstant.LOGIN_USER_INFO_KEY, new User());

        user.setAvatar(url);
        tokenSession.set(GlobalConstant.LOGIN_USER_INFO_KEY, user);


        return R.ok(url);
    }

    @DeleteMapping
    @ApiOperation(value = "删除用户(可批量)")
    public R delete(@RequestBody List<Long> ids) {
        if (ids.contains(GlobalConstant.SUPER_ADMIN_USER_ID)) {
            R.error("管理员账户不能删除！");
        }

        if (ids.contains(StpUtil.getLoginIdAsLong())) {
            R.error("当前登录账户不能删除！");
        }
        //删除时需要同时删除用户部门关联表和用户角色关联表和用户岗位关系表数据。
        return R.ok(userService.deleteBatch(ids));
    }

    @GetMapping("/info/multi")
    @ApiOperation(value = "批量获取用户信息")
    public R usersInfo(@RequestParam String ids) {
        return R.ok(userService.getUsersInfo(ids));
    }

    @GetMapping("/enabled")
    @ApiOperation(value = "启用/禁用用户")
    public R enabled(@RequestParam Long id) {
        User user = userService.getOne(Wrappers.<User>query().lambda().select(User::getEnabledMark).eq(User::getId, id), false);
        if (user != null) {
            User updateUser = new User();
            updateUser.setId(id);
            updateUser.setEnabledMark(user.getEnabledMark() == EnabledMark.ENABLED.getCode() ? EnabledMark.DISABLED.getCode() : EnabledMark.ENABLED.getCode());
            return R.ok(userService.updateById(updateUser));
        }
        CompletableFuture.runAsync(() -> {
            List<User> list = userService.list();
            redisUtil.set(GlobalConstant.USER_CACHE_KEY, list);
        });
        return R.error("该用户不存在！");
    }

    @PutMapping("/reset-password")
    @ApiOperation(value = "重置密码")
    public RT<Boolean> resetPassword(@RequestBody ResetPasswordDto dto) {
        return RT.ok(userService.resetPassword(dto));
    }

    @PutMapping("/batch-reset-password")
    @ApiOperation(value = "批量重置密码")
    public RT<Boolean> batchResetPassword(@RequestBody List<Long> ids) {
        return RT.ok(userService.batchResetPassword(ids));
    }

    @PostMapping("/bind-unionid")
    @ApiOperation(value = "绑定微信 UnionId")
    public R bindUnionId(@RequestBody BindOpenidDto dto) {
        User user = userService.getOne(Wrappers.<User>query().lambda().select(User::getId, User::getOpenId, User::getUnionId).eq(User::getId, dto.getId()), false);
        if (user != null) {
            if (StrUtil.isNotBlank(user.getUnionId())) {
                return R.error("该用户已经绑定微信！");
            }

            long count = userService.count(Wrappers.<User>query().lambda().eq(User::getUnionId, dto.getOpenid()));
            if (count > 0) {
                return R.error("该用户已经绑定微信！");
            }

            User updateUser = new User();
            updateUser.setId(dto.getId());
//            updateUser.setOpenId(dto.getOpenid());
            updateUser.setUnionId(dto.getOpenid());
            return R.ok(userService.updateById(updateUser));
        } else {
            long count = userService.count(Wrappers.<User>query().lambda().eq(User::getOpenId, dto.getOpenid()));
            if (count > 0) {
                return R.error("该用户已经绑定微信！");
            }
        }
        return R.error("该用户不存在！");
    }

    @PostMapping("/bind-openid")
    @ApiOperation(value = "绑定微信 Openid")
    public R bindOpenid(@RequestBody BindOpenidDto dto) {
        return R.ok(userService.bindOpenid(dto));
    }

    @GetMapping("/unbind-openid")
    @ApiOperation(value = "取消绑定微信 UnionId")
    public R unbindOpenid(@RequestParam Long id) {
        return R.ok(userService.unbindOpenid(id));
    }

    @PostMapping("/register")
    @ApiOperation(value = "家长注册")
    public R register(@Valid @RequestBody RegisterDto dto) {
        // 验证验证码
        if (!smsCtcc.captchaVerify(dto.getMobile(), dto.getSmsCode())) {
            return R.error("验证码不正确！");
        }
        // 赋值家长角色
        List<Long> roleIds = new ArrayList<>();
        roleIds.add(RoleEnum.PARENT.getCode());
        dto.setRoleIds(roleIds);
        return R.ok(userService.add(dto));
    }

    @PostMapping("/bind-student")
    @ApiOperation(value = "绑定学生")
    public R bindStudent(@Valid @RequestBody UserStudentBindDto dto) {
        User user = userService.getOne(Wrappers.<User>query().lambda()
                .eq(User::getName, dto.getName())
                .eq(User::getCredentialNumber, dto.getIdCard()));
        UserStudentAddDto userStudentAddDto = new UserStudentAddDto();
        if (user == null) {
            return R.error("学生不存在！");
        }
        userStudentAddDto.setUserId(dto.getUserId());
        userStudentAddDto.setStudentId(user.getId());
        return R.ok(userStudentService.add(userStudentAddDto));
    }

    @DeleteMapping("/unbind-student")
    @ApiOperation(value = "解绑学生")
    public R unBindStudent(@Valid @RequestBody UserStudentDeleteDto dto) {
        return R.ok(userStudentService.delete(dto));
    }

    @GetMapping("/student")
    @ApiOperation(value = "获取绑定学生")
    public R unBindStudent(@Valid @RequestParam Long id) {
        return R.ok(baseClassService.getStudents(id));
    }

    @PostMapping("/upload-sign")
    @ApiOperation(value = "上传签名")
    public RT<Boolean> uploadSign(@Valid @RequestBody UploadSignDto dto) {
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        User user = userService.getById(loginIdAsLong);
        user.setSignFolderId(dto.getFolderId());
        user.setSignPassword(BCrypt.hashpw(dto.getPassword(), BCrypt.gensalt()));

        return RT.ok(userService.updateById(user));
    }

    @GetMapping("/getSign")
    @ApiOperation(value = "获取登录者的签名url")
    public RT<String> getSign() {
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        User user = userService.getById(loginIdAsLong);
        if(user.getSignFolderId() == null){
            return RT.error("未上传签名");
        }
        List<File> list = fileService.list(Wrappers.lambdaQuery(File.class).eq(File::getFolderId, user.getSignFolderId()));
        if(list.isEmpty()){
            return RT.error("签名文件丢失");
        }
        return RT.ok(list.get(0).getFileUrl());
    }

    @GetMapping("/check-password")
    @ApiOperation(value = "验证登录者的密码")
    public RT<Boolean> checkSignpassword(@RequestParam String password) {
        long loginIdAsLong = StpUtil.getLoginIdAsLong();
        User user = userService.getById(loginIdAsLong);
        if (!BCrypt.checkpw(password, user.getPassword())) {
            return RT.error("密码填写错误！");
        }
        return RT.ok(true);
    }
}
