DingMod.java 16.6 KB
package vion.third;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import vion.constant.RedisKeyEnum;
import vion.dto.DingDTO;
import vion.model.*;
import vion.service.*;
import vion.vo.RoleVO;
import vion.vo.UserVO;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author HlQ
 * @date 2023/11/13
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class DingMod {

    @Value("${appkey:}")
    private String appKey;
    @Value("${appsecret:}")
    private String appSecret;

    private final IUserService userService;
    private final IRoleService roleService;
    private final IResourceService resourceService;
    private final IRUserRoleService userRoleService;
    private final IRRoleResourceService roleResourceService;
    private final IDeptService deptService;
    private final Converter converter;
    private final RedisTemplate redisTemplate;

    /**
     * 获取钉钉token
     *
     * @return java.lang.String
     */
    public String getToken() {
        return (String) Opt.ofNullable(redisTemplate.opsForValue().get(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.ACCESS_TOKEN.getVal()))
                .orElseGet(() -> {
                    String res = HttpUtil.get("https://oapi.dingtalk.com/gettoken?appkey=" + appKey + "&appsecret=" + appSecret);
                    JSONObject jsonObj = JSONUtil.parseObj(res);
                    if (jsonObj.containsKey("access_token")) {
                        String accessToken = jsonObj.getStr("access_token");
                        redisTemplate.opsForValue().set(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.ACCESS_TOKEN.getVal(), accessToken, 7000, TimeUnit.SECONDS);
                        return accessToken;
                    }
                    return "";
                });
    }

    /**
     * 获取钉钉员工列表
     */
    public void getDingUserList() {
        String accessToken = getToken();

        List<String> userIdList = new ArrayList<>();
        Long offset = 0L;
        boolean flag = true;
        while (flag) {
            JSONObject paramJson = new JSONObject();
            // 2:试用期 3:正式 5:待离职 -1:无状态
            paramJson.set("status_list", "2,3,5,-1");
            paramJson.set("size", 50);
            paramJson.set("offset", offset);
            String res = HttpUtil.post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/queryonjob?access_token=" + accessToken, paramJson.toString());
            JSONObject jsonObject = JSONUtil.parseObj(res);
            if (jsonObject.getBool("success", false)) {
                JSONArray jsonArray = jsonObject.getJSONObject("result").getJSONArray("data_list");
                List<String> list = jsonArray.toList(String.class);
                userIdList.addAll(list);

                Long nextCursor = jsonObject.getJSONObject("result").getLong("next_cursor");
                if (ObjUtil.isNotNull(nextCursor)) {
                    offset = nextCursor;
                } else {
                    flag = false;
                }
            }
        }

        if (CollUtil.isEmpty(userIdList)) {
            log.error("获取钉钉用户id列表为空");
            return;
        }

        List<List<String>> userIdListSplit = ListUtil.split(userIdList, 90);
        userIdListSplit.forEach(tmpList -> {
            JSONObject paramJson = new JSONObject();
            paramJson.set("agentid", 2358374016L);
            paramJson.set("userid_list", String.join(",", tmpList));
            paramJson.set("field_filter_list", "sys00-name,sys00-mobile,sys00-mainDeptId,sys00-mainDept,sys01-employeeStatus");

            String res = HttpUtil.post("https://oapi.dingtalk.com/topapi/smartwork/hrm/employee/v2/list?access_token=" + accessToken, paramJson.toString());
            JSONObject jsonObject = JSONUtil.parseObj(res);
            if (jsonObject.getBool("success", false)) {
                JSONArray resArr = jsonObject.getJSONArray("result");
                for (Object o : resArr) {
                    User user = new User();
                    JSONObject jsonObj = JSONUtil.parseObj(o);
                    String userid = jsonObj.getStr("userid");
                    user.setUserid(userid);

                    JSONArray dataArr = jsonObj.getJSONArray("field_data_list");
                    for (Object o1 : dataArr) {
                        JSONObject tmpObj = JSONUtil.parseObj(o1);
                        if (StrUtil.equals("sys00-name", tmpObj.getStr("field_code"))) {
                            user.setUsername(tmpObj.getJSONArray("field_value_list").getByPath("[0].value", String.class));
                        } else if (StrUtil.equals("sys00-mobile", tmpObj.getStr("field_code"))) {
                            user.setPhone(Opt.ofNullable(tmpObj.getJSONArray("field_value_list").getByPath("[0].value", String.class)).orElse(""));
                        } else if (StrUtil.equals("sys00-mainDeptId", tmpObj.getStr("field_code"))) {
                            user.setDeptId(tmpObj.getJSONArray("field_value_list").getByPath("[0].value", Long.class));
                        } else if (StrUtil.equals("sys01-employeeStatus", tmpObj.getStr("field_code"))) {
                            user.setEmployeeStatus(tmpObj.getJSONArray("field_value_list").getByPath("[0].value", Integer.class));
                        }
                    }
                    userService.saveOrUpdate(user, Wrappers.<User>lambdaUpdate().eq(User::getUserid, userid));
                    User one = userService.lambdaQuery().eq(User::getUserid, userid).one();
                    redisTemplate.opsForValue().set(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.USER_ID.getVal() + one.getId(), user);
                    redisTemplate.opsForValue().set(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.USER_NAME.getVal() + one.getUsername(), user);
                }
            }
        });
    }

    /**
     * 获取钉钉部门列表
     */
    public void getDeptList() {
        String accessToken = getToken();

        List<Long> deptIdList = ListUtil.toList(1L);
        int idx = 0;
        while (idx < deptIdList.size()) {
            // 2:试用期 3:正式 5:待离职 -1:无状态
            Long value = deptIdList.get(idx++);
            String res = HttpUtil.post("https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token=" + accessToken, JSONUtil.createObj().set("dept_id", value).toString());
            JSONObject jsonObject = JSONUtil.parseObj(res);
            if (StrUtil.equals("ok", jsonObject.getStr("errmsg"))) {
                JSONArray resArr = jsonObject.getJSONArray("result");
                if (CollUtil.isNotEmpty(resArr)) {
                    for (Object o : resArr) {
                        Dept dept = new Dept();
                        JSONObject jsonObj = JSONUtil.parseObj(o);
                        Long deptId = jsonObj.getLong("dept_id");
                        dept.setDeptId(deptId);
                        dept.setParentId(jsonObj.getLong("parent_id"));
                        dept.setDeptName(jsonObj.getStr("name"));
                        deptService.saveOrUpdate(dept, Wrappers.<Dept>lambdaUpdate().eq(Dept::getDeptId, deptId));
                        deptIdList.add(deptId);
                    }
                }
            }
        }
    }

    /**
     * 钉钉工作消息推送
     */
    public String workMsg(JSONObject msg) {
        String token = getToken();
        String res = HttpUtil.post("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=" + token, msg.toString());
        log.info("钉钉工作通知消息推送:{}", res);
        return res;
    }

    /**
     * 钉钉回调接口
     *
     * @param target login,inside 区分是钉钉扫码登录还是钉钉内打开链接
     * @param dto    dto
     * @param res    httpServletResponse
     * @return java.lang.Object
     */
    public Object dingCallback(String target, DingDTO dto, HttpServletResponse res) {
        if (StrUtil.equals(target, "login")) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("clientId", appKey);
            jsonObject.set("clientSecret", appSecret);
            jsonObject.set("code", dto.getAuthCode());
            jsonObject.set("grantType", "authorization_code");
            String tokenRes = HttpUtil.post("https://api.dingtalk.com/v1.0/oauth2/userAccessToken", jsonObject.toString());
            JSONObject tokenObj = JSONUtil.parseObj(tokenRes);
            if (!tokenObj.containsKey("accessToken")) {
                log.error("钉钉回调接口获取accessToken失败:{}", tokenObj);
                return tokenObj;
            }
            String accessToken = tokenObj.getStr("accessToken");
            HttpResponse response = HttpRequest.get("https://api.dingtalk.com/v1.0/contact/users/me")
                    .header("x-acs-dingtalk-access-token", accessToken)
                    .execute();
            if (!response.isOk()) {
                log.error("钉钉回调接口获取unionid失败:{}", response.body());
                return response.body();
            }
            JSONObject userInfoObj = JSONUtil.parseObj(response.body());
            String unionId = userInfoObj.getStr("unionId");
            String response1 = HttpUtil.post("https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=" + getToken(), "{\"unionid\":\"" + unionId + "\"}");
            JSONObject useridObj = JSONUtil.parseObj(response1);
            if (!useridObj.containsKey("result")) {
                log.error("钉钉回调接口获取userid失败:{}", userInfoObj);
                return useridObj;
            }
            String userid = useridObj.getJSONObject("result").getStr("userid");
            User user = userService.lambdaQuery().select(User::getId, User::getUserid, User::getUsername, User::getPhone, User::getStatus).eq(User::getUserid, userid).one();
            UserVO userVO = converter.convert(user, UserVO.class);
            if (userVO.getStatus() == 1) {
                return "该用户禁止登录系统,请联系管理员!";
            }
            Long id = userVO.getId();
            setRolesAndPerms(id, userVO);

            StpUtil.login(id);
            String token = StpUtil.getTokenValue();
            userVO.setToken(token);

            StpUtil.getTokenSession().set("curLoginUser", userVO);
            return userVO;
        } else if (StrUtil.equals(target, "inside")) {
            String response = HttpUtil.post(buildSignUrl(), "{\"tmp_auth_code\":\"" + dto.getCode() + "\"}");
            JSONObject userInfoObj = JSONUtil.parseObj(response);
            if (!userInfoObj.containsKey("user_info")) {
                log.error("钉钉回调接口获取unionid失败:{}", userInfoObj);
                return userInfoObj;
            }
            String unionId = userInfoObj.getJSONObject("user_info").getStr("unionid");

            String response1 = HttpUtil.post("https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=" + getToken(), "{\"unionid\":\"" + unionId + "\"}");
            JSONObject useridObj = JSONUtil.parseObj(response1);
            if (!useridObj.containsKey("result")) {
                log.error("钉钉回调接口获取userid失败:{}", useridObj);
                return useridObj;
            }
            String userid = useridObj.getJSONObject("result").getStr("userid");
            User user = userService.lambdaQuery().select(User::getId, User::getUserid, User::getUsername, User::getPhone, User::getStatus).eq(User::getUserid, userid).one();
            UserVO userVO = converter.convert(user, UserVO.class);

            Long id = userVO.getId();
            setRolesAndPerms(id, userVO);
            StpUtil.login(id);

            String token = StpUtil.getTokenValue();
            userVO.setToken(token);

            StpUtil.getTokenSession().set("curLoginUser", userVO);

            try {
                if (ObjUtil.isAllNotEmpty(dto.getStoreId(), dto.getTaskId())) {
                    res.sendRedirect("https://yunwei.vionyun.com:8443/wap/workflow-process?token=" + token + "&storeId=" + dto.getStoreId() + "&taskId=" + dto.getTaskId());
                } else if (ObjUtil.isNotEmpty(dto.getTaskTempId())) {
                    res.sendRedirect("https://yunwei.vionyun.com:8443/wap/workflow-assign?token=" + token + "&id=" + dto.getTaskTempId());
                } else if (ObjUtil.isNotEmpty(dto.getUserId())) {
                    res.sendRedirect("https://yunwei.vionyun.com:8443/wap/workflow-list?token=" + token + "&activeUser=" + dto.getUserId());
                } else if (ObjUtil.isNotEmpty(dto.getDeliveryId())) {
                    res.sendRedirect("https://yunwei.vionyun.com:8443/wap/delivery-invoice?token=" + token + "&id=" + dto.getDeliveryId());
                } else if (ObjUtil.isNotEmpty(dto.getTaskUuid())) {
                    res.sendRedirect("https://yunwei.vionyun.com:8443/wap/workflow-process-view?token=" + token + "&taskUuid=" + dto.getTaskUuid());
                }
            } catch (IOException e) {
                log.error("钉钉回调接口重定向失败!", e);
                return "钉钉回调接口重定向失败,请联系相关人员";
            }
            return "success";
        }
        return "钉钉回调异常,请联系相关人员";
    }

    private void setRolesAndPerms(Long id, UserVO userVO) {
        List<RUserRole> userRoleList = userRoleService.lambdaQuery().eq(RUserRole::getUserId, id).list();
        List<RRoleResource> roleResourceList = Opt.ofEmptyAble(userRoleList)
                .map(urList -> urList.stream().map(RUserRole::getRoleId).collect(Collectors.toList()))
                .map(roleIdList -> {
                    // 获取用户关联的角色
                    List<Role> roles = roleService.listByIds(roleIdList);
                    List<RoleVO> roleVOS = converter.convert(roles, RoleVO.class);
                    userVO.setRoleVOList(roleVOS);
                    return roleResourceService.lambdaQuery().select(RRoleResource::getResourceId).in(RRoleResource::getRoleId, roleIdList).list();
                }).orElse(ListUtil.empty());
        // 获取角色关联的资源
        Opt.ofEmptyAble(roleResourceList)
                .map(rrList -> rrList.stream().map(RRoleResource::getResourceId).collect(Collectors.toList()))
                .ifPresent(resourceIdList -> {
                    List<Resource> resourceList = resourceService.listByIds(resourceIdList);
                    userVO.setResourceList(resourceList);
                });
    }

    /**
     * 构建获取用户信息url
     */
    String buildSignUrl() {
        try {
            String appSecret = "Ul5UTZqIost_kEAdfZXidvLoZhzvraYurm_g7PCHg-IrDMLHT7mdSgRS1iCHrDPt";
            String timestamp = String.valueOf(System.currentTimeMillis());

            HMac hMac = SecureUtil.hmacSha256(appSecret);
            byte[] signBytes = hMac.digest(timestamp);
            String sign = Base64.encode(signBytes);
            String encode = URLEncoder.encode(sign, "UTF-8");
            String encodeSign = encode.replace("+", "%20").replace("*", "%2A").replace("~", "%7E").replace("/", "%2F").replace("=", "%3D");
            return StrUtil.format("https://oapi.dingtalk.com/sns/getuserinfo_bycode?accessKey=dingkrzwks0jpi2di3uo&timestamp={}&signature={}", timestamp, encodeSign);
        } catch (UnsupportedEncodingException e) {
            log.error("通过appSecret计算出来的签名值失败", e);
        }
        return "";
    }

    public String robotPush(String access_token, String body) {
        String res = HttpUtil.post("https://oapi.dingtalk.com/robot/send?access_token=" + access_token, body);
        log.info("钉钉机器人消息推送:{}", res);
        return res;
    }
}