DingMod.java 19.1 KB
package vion.third;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.github.linpeilie.Converter;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.codec.binary.Base64;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.lang.Opt;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.crypto.SecureUtil;
import org.dromara.hutool.crypto.digest.mac.HMac;
import org.dromara.hutool.http.HttpUtil;
import org.dromara.hutool.http.client.Request;
import org.dromara.hutool.http.client.Response;
import org.dromara.hutool.json.JSONArray;
import org.dromara.hutool.json.JSONObject;
import org.dromara.hutool.json.JSONUtil;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
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 java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
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 RedissonClient redissonClient;

    /**
     * 获取钉钉token
     *
     * @return java.lang.String
     */
    public String getToken() {
        return (String) Opt.ofNullable(redissonClient.getBucket(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.ACCESS_TOKEN.getVal()).get())
                .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");
                        redissonClient.getBucket(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.ACCESS_TOKEN.getVal()).set(accessToken, Duration.ofSeconds(7000));
                        return accessToken;
                    }
                    return "";
                });
    }

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

        List<String> userIdList = new ArrayList<>();
        Long offset = 0L;
        boolean flag = true;
        while (flag) {
            // 2:试用期 3:正式 5:待离职 -1:无状态
            JSONObject paramJson = JSONUtil.ofObj()
                    .set("status_list", "2,3,5,-1")
                    .set("size", 50)
                    .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.partition(userIdList, 90);
        userIdListSplit.forEach(tmpList -> {
            JSONObject paramJson = JSONUtil.ofObj()
                    .set("agentid", 2358374016L)
                    .set("userid_list", String.join(",", tmpList))
                    .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((String) 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();
                    redissonClient.getBucket(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.USER_ID.getVal() + one.getId()).set(user);
                    redissonClient.getBucket(RedisKeyEnum.DING_PREFIX.getVal() + RedisKeyEnum.USER_NAME.getVal() + one.getUsername()).set(user);
                }
            }
        });
    }

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

        List<Long> deptIdList = ListUtil.of(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.ofObj().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 = JSONUtil.ofObj()
                    .set("clientId", appKey)
                    .set("clientSecret", appSecret)
                    .set("code", dto.getAuthCode())
                    .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");
            Response response = Request.of("https://api.dingtalk.com/v1.0/contact/users/me")
                    .header("x-acs-dingtalk-access-token", accessToken)
                    .send();
            if (!response.isOk()) {
                log.error("钉钉回调接口获取unionid失败:{}", response.bodyStr());
                return response.bodyStr();
            }
            JSONObject userInfoObj = JSONUtil.parseObj(response.bodyStr());
            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失败:{}", 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, User::getEmployeeStatus)
                    .eq(User::getUserid, userid).one();
            UserVO userVO = converter.convert(user, UserVO.class);
            // 禁用用户或离职用户禁止登录系统
            if (userVO.getStatus() == 1 || userVO.getEmployeeStatus() == 5) {
                return "用户已禁用禁止登录系统,请联系管理员!";
            }
            Long id = userVO.getId();
            setRolesAndPerms(id, userVO);
            if (CollUtil.isEmpty(userVO.getRoleVOList())) {
                return "用户未分配角色,请联系管理员!";
            }

            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);
            if (CollUtil.isEmpty(userVO.getRoleVOList())) {
                return "用户未分配角色,请联系管理员!";
            }
            StpUtil.login(id);

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

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

            try {
                if (ArrayUtil.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 "钉钉回调异常,请联系相关人员";
    }

    /**
     * 钉钉工作台应用获取应用程序token
     *
     * @param authCode  授权码
     * @return java.lang.Object
     */
    public Object getPlatformToken(String authCode) {
        var accessTokenStr = HttpUtil.get(StrUtil.format("https://oapi.dingtalk.com/gettoken?appkey={}&appsecret={}",
                appKey, appSecret));
        if (!StrUtil.contains(accessTokenStr, "access_token")) {
            log.error("获取钉钉access_token失败:{}", accessTokenStr);
            return "获取钉钉access_token失败";
        }
        var accessToken = JSONUtil.parseObj(accessTokenStr).getStr("access_token");

        var userInfoStr = HttpUtil.post("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token=" + accessToken,
                JSONUtil.ofObj().set("code", authCode));
        if (!StrUtil.contains(userInfoStr, "\"errmsg\":\"ok\"")) {
            log.error("获取钉钉用户信息失败:{}", userInfoStr);
            return "获取钉钉用户信息失败";
        }
        var userid = JSONUtil.parseObj(userInfoStr).getJSONObject("result").getStr("userid");
        User user = userService.lambdaQuery()
                .select(User::getId, User::getUserid, User::getUsername, User::getPhone, User::getStatus, User::getEmployeeStatus)
                .eq(User::getUserid, userid).one();
        if (ObjUtil.isNull(user)) {
            return "该用户不存在,请联系管理员!";
        }
        UserVO userVO = converter.convert(user, UserVO.class);
        // 禁用用户或离职用户禁止登录系统
        if (userVO.getStatus() == 1 || userVO.getEmployeeStatus() == 5) {
            return "用户已禁用禁止登录系统,请联系管理员!";
        }
        Long id = userVO.getId();
        setRolesAndPerms(id, userVO);
        if (CollUtil.isEmpty(userVO.getRoleVOList())) {
            return "用户未分配角色,请联系管理员!";
        }

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

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

    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() {
        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, StandardCharsets.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);
    }

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