From d4296102259c6129d8e1f735d86c0fc86f3a4776 Mon Sep 17 00:00:00 2001 From: sunzheng Date: Wed, 24 Jul 2024 16:09:18 +0800 Subject: [PATCH] =?UTF-8?q?add:=E5=AF=B9=E6=8E=A5=E9=A3=9E=E4=B9=A6?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jero/modules/system/entity/SysUser.java | 6 + .../controller/FeiShuDockingController.java | 91 +++ .../modules/feishu/dto/BotMessageDto.java | 27 + .../jero/modules/feishu/dto/InstanceDto.java | 68 ++ .../feishu/service/IFeiShuDockingService.java | 54 ++ .../impl/FeiShuDockingServiceImpl.java | 603 ++++++++++++++++++ .../modules/feishu/vo/bot/BotMessageVo.java | 35 + .../vo/definition/ApprovalsDefinitionVo.java | 80 +++ .../vo/definition/DefinitionExternal.java | 108 ++++ .../definition/DefinitionI18nResources.java | 61 ++ .../feishu/vo/definition/DefinitionTexts.java | 30 + .../vo/definition/DefinitionViewers.java | 36 ++ .../vo/instance/ApprovalsInstanceVo.java | 185 ++++++ .../feishu/vo/instance/InstanceForm.java | 22 + .../feishu/vo/instance/InstanceLink.java | 23 + .../feishu/vo/instance/InstanceTask.java | 153 +++++ .../src/main/resources/application-dev.yml | 41 ++ 17 files changed, 1623 insertions(+) create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/controller/FeiShuDockingController.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/dto/BotMessageDto.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/dto/InstanceDto.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/service/IFeiShuDockingService.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/service/impl/FeiShuDockingServiceImpl.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/bot/BotMessageVo.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/ApprovalsDefinitionVo.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionExternal.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionI18nResources.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionTexts.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionViewers.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/ApprovalsInstanceVo.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceForm.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceLink.java create mode 100644 laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceTask.java diff --git a/laws-module-system/src/main/java/com/jero/modules/system/entity/SysUser.java b/laws-module-system/src/main/java/com/jero/modules/system/entity/SysUser.java index 43941f9e..7c9cad96 100644 --- a/laws-module-system/src/main/java/com/jero/modules/system/entity/SysUser.java +++ b/laws-module-system/src/main/java/com/jero/modules/system/entity/SysUser.java @@ -258,6 +258,12 @@ public class SysUser implements Serializable { @TableField(exist = false) private String groupId; + /** + * 飞书的id + */ + @ApiModelProperty(value = "飞书的id") + private String feishuId; + /** * 拼接真实姓名和工号形成唯一的用户信息可用户查看 * 不能用get开头,否则会造成初始化queryWrapper的bug diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/controller/FeiShuDockingController.java b/laws-modules/src/main/java/com/jero/modules/feishu/controller/FeiShuDockingController.java new file mode 100644 index 00000000..6c28e09a --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/controller/FeiShuDockingController.java @@ -0,0 +1,91 @@ +package com.jero.modules.feishu.controller; + +import com.alibaba.fastjson.JSONObject; +import com.jero.common.api.vo.Result; +import com.jero.modules.feishu.dto.BotMessageDto; +import com.jero.modules.feishu.dto.InstanceDto; +import com.jero.modules.feishu.service.IFeiShuDockingService; +import com.jero.modules.feishu.vo.definition.ApprovalsDefinitionVo; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; + +/** + *

+ * 飞书对接 + *

+ */ +@Api(tags = "飞书对接") +@RestController +@RequestMapping("/feishu") +@Slf4j +public class FeiShuDockingController { + + @Resource + private IFeiShuDockingService feiShuDockingService; + + /** + * 获取token + * @return + */ + @GetMapping("/getAccessToken") + public String getAccessToken() { + return feiShuDockingService.getAccessToken(); + } + + /** + * 创建三方审批定义 + * @return + */ + @PostMapping("/createApprovalDefinition") + public String createApprovalDefinition() { + return feiShuDockingService.createApprovalDefinition(); + } + + /** + * 获取指定三方审批定义 + * @param approvalDefinitionCode + * @return + */ + @GetMapping("/getApprovalDefinition") + public ApprovalsDefinitionVo getApprovalDefinition(String approvalDefinitionCode) { + return feiShuDockingService.getApprovalDefinition(approvalDefinitionCode); + } + + /** + * 根据手机号获取用户的user_id + * @param phones + * @return + */ + @PostMapping("/getUserId") + public String getUserId(String phones) { + return feiShuDockingService.getUserId(phones); + } + + /** + * 同步三方审批实例 + * @return + */ + @PostMapping("/synchronizeApprovalInstance") + public void synchronizeApprovalInstance(@RequestBody InstanceDto instanceDto) { + feiShuDockingService.synchronizeApprovalInstance(instanceDto); + } + + /** + * 使用Bot发送富文本消息 + * @return + */ + @PostMapping("/sendHrefMessageByBot") + public void sendHrefMessageByBot(@RequestBody BotMessageDto botMessageDto) { + feiShuDockingService.sendHrefMessageByBot(botMessageDto); + } + + /** + * 根据预授权码获取用户信息 + */ + @PostMapping("/getUserInfoByCode") + public Result getUserInfoByCode(@RequestBody JSONObject codeObject) { + return feiShuDockingService.getUserInfoByCode(codeObject); + } +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/dto/BotMessageDto.java b/laws-modules/src/main/java/com/jero/modules/feishu/dto/BotMessageDto.java new file mode 100644 index 00000000..ff3df206 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/dto/BotMessageDto.java @@ -0,0 +1,27 @@ +package com.jero.modules.feishu.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class BotMessageDto { + + @ApiModelProperty("消息接收者的id,可以为多个,逗号拼接") + private String receiveIds; + + @ApiModelProperty("超链接的文字") + private String hrefText; + + @ApiModelProperty("超链接前面的普通文字") + private String textBeforeHref; + + @ApiModelProperty("超链接后面的普通文字") + private String textAfterHref; + + @ApiModelProperty("消息的标题") + private String title; + + @ApiModelProperty("超链接的跳转地址") + private String hrefUrl; + +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/dto/InstanceDto.java b/laws-modules/src/main/java/com/jero/modules/feishu/dto/InstanceDto.java new file mode 100644 index 00000000..c96ddec9 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/dto/InstanceDto.java @@ -0,0 +1,68 @@ +package com.jero.modules.feishu.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +public class InstanceDto { + + @ApiModelProperty("发起人的id") + private String promoterUserId; + + @ApiModelProperty("审核人的ids,可能为多个,逗号分割") + private String reviewedIds; + + @ApiModelProperty("标题") + private String title; + + @ApiModelProperty("审核实例的id") + private String processInstanceId; + + @ApiModelProperty("审核任务的ids,有几个审核人就对应几个任务id,逗号分割") + private String taskIds; + + /** + * 审批实例状态 + * 示例值:"PENDING" + * PENDING: 审批中 + * APPROVED: 审批流程结束,结果为同意 + * REJECTED: 审批流程结束,结果为拒绝 + * CANCELED: 审批发起人撤回 + * DELETED: 审批被删除 + * HIDDEN: 状态隐藏(不显示状态) + * TERMINATED:审批终止 + */ + @ApiModelProperty("审核实例的状态") + private String instanceStatus; + + @ApiModelProperty("审核实例的开始时间") + private Date instanceStartTime; + + /** + * 任务状态 + * 示例值:"PENDING" + * 可选值有: + * PENDING:待审批 + * APPROVED:任务同意 + * REJECTED:任务拒绝 + * TRANSFERRED:任务转交 + * DONE:任务通过但审批人未操作;审批人看不到这个任务, 若想要看到, 可以通过抄送该人. + */ + @ApiModelProperty("审核任务的状态,有几个审核人对应几个审核任务的状态,逗号分割") + private String taskStatus; + + @ApiModelProperty("发起人的PC跳转地址") + private String promoterPcLink; + + @ApiModelProperty("发起人的移动端跳转地址") + private String promoterMobileLink; + + @ApiModelProperty("审核人的PC端跳转地址,有几个审核人对应几条链接,逗号分割") + private String reviewPcLink; + + @ApiModelProperty("审核人的移动端跳转地址,有几个审核人对应几条链接,逗号分割") + private String reviewMobileLink; + +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/service/IFeiShuDockingService.java b/laws-modules/src/main/java/com/jero/modules/feishu/service/IFeiShuDockingService.java new file mode 100644 index 00000000..39d37bcb --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/service/IFeiShuDockingService.java @@ -0,0 +1,54 @@ +package com.jero.modules.feishu.service; + +import com.alibaba.fastjson.JSONObject; +import com.jero.common.api.vo.Result; +import com.jero.modules.feishu.dto.BotMessageDto; +import com.jero.modules.feishu.dto.InstanceDto; +import com.jero.modules.feishu.vo.definition.ApprovalsDefinitionVo; + +public interface IFeiShuDockingService { + + /** + * 获取飞书应用的token + * @return + */ + String getAccessToken(); + + /** + * 创建三方审批的定义 + * @return + */ + String createApprovalDefinition(); + + /** + * 查看指定三方审批定义 + * @return + */ + ApprovalsDefinitionVo getApprovalDefinition(String approvalDefinitionCode); + + /** + * 获取用户id + * @return + */ + String getUserId(String phones); + + /** + * 同步三方审批实例 + * @return + */ + void synchronizeApprovalInstance(InstanceDto instanceDto); + + /** + * 使用机器人发送消息 + * @param botMessageDto + * @return + */ + void sendHrefMessageByBot(BotMessageDto botMessageDto); + + /** + * 根据预授权码获取用户信息 + * @param codeObject + * @return + */ + Result getUserInfoByCode(JSONObject codeObject); +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/service/impl/FeiShuDockingServiceImpl.java b/laws-modules/src/main/java/com/jero/modules/feishu/service/impl/FeiShuDockingServiceImpl.java new file mode 100644 index 00000000..dd7b5e9f --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/service/impl/FeiShuDockingServiceImpl.java @@ -0,0 +1,603 @@ +package com.jero.modules.feishu.service.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.jero.common.api.CommonAPI; +import com.jero.common.api.vo.Result; +import com.jero.common.constant.CommonConstant; +import com.jero.common.exception.JeroBootException; +import com.jero.common.system.util.JwtUtil; +import com.jero.common.system.vo.SysDictItemCore; +import com.jero.common.util.RedisUtil; +import com.jero.common.util.oConvertUtils; +import com.jero.modules.feishu.dto.BotMessageDto; +import com.jero.modules.feishu.dto.InstanceDto; +import com.jero.modules.feishu.service.IFeiShuDockingService; +import com.jero.modules.feishu.vo.bot.BotMessageVo; +import com.jero.modules.feishu.vo.definition.*; +import com.jero.modules.feishu.vo.instance.ApprovalsInstanceVo; +import com.jero.modules.feishu.vo.instance.InstanceLink; +import com.jero.modules.feishu.vo.instance.InstanceTask; +import com.jero.modules.system.entity.*; +import com.jero.modules.system.service.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class FeiShuDockingServiceImpl implements IFeiShuDockingService { + + @Resource + private RestTemplate restTemplate; + @Resource + private Environment env; + @Resource + private ISysUserService sysUserService; + @Autowired + private RedisUtil redisUtil; + @Autowired + private ISysDepartService sysDepartService; + @Autowired + private ISysTenantService sysTenantService; + @Autowired + private ISysDictService sysDictService; + @Resource + private ISysUserRoleService sysUserRoleService; + @Resource + private ISysRoleService sysRoleService; + @Resource + private CommonAPI commonAPI; + @Value("${feishu.app_id}") + private String appId; + @Value("${feishu.app_secret}") + private String appSecret; + @Value("${feishu.managersIds}") + private String managersIds; + @Value("${feishu.approvalName}") + private String approvalName; + + private static final String USER_INFO = "userInfo"; + + @Override + public String getAccessToken() { + String url = env.getProperty("feishu.get_token"); + assert url != null; + JSONObject jsonObject = new JSONObject(); + jsonObject.put("app_id", appId); + jsonObject.put("app_secret", appSecret); + String res = restTemplate.postForObject(url, jsonObject, String.class); + JSONObject result = JSON.parseObject(res); + String code = result.getString("code"); + if ("0".equals(code)) { + return result.getString("tenant_access_token"); + } else { + throw new JeroBootException("获取飞书token失败"); + } + } + + @Override + public String createApprovalDefinition() { + String url = env.getProperty("feishu.create_approval_definition"); + assert url != null; + // 获取token + String accessToken = getAccessToken(); + // 构建对象 + ApprovalsDefinitionVo approvalsDefinitionVo = buildDefinitionVo(); + // 设置请求头 + HttpHeaders headers = createHeaders(accessToken); + // 构建包含查询参数的 URL + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url) + .queryParam("department_id_type", "open_department_id") + .queryParam("user_id_type", "user_id"); + // 创建 HttpEntity + HttpEntity entity = new HttpEntity<>(approvalsDefinitionVo, headers); + // 发送请求 + ResponseEntity response = restTemplate.exchange(builder.toUriString(), HttpMethod.POST, entity, String.class); + // 处理响应 + JSONObject jsonResponse = JSON.parseObject(response.getBody()); + if ("0".equals((String.valueOf(jsonResponse.get("code"))))) { + JSONObject data = (JSONObject) jsonResponse.get("data"); + return data.getString("approval_code"); + } else { + log.error(jsonResponse.get("msg").toString()); + throw new JeroBootException("创建三方审批定义失败"); + } + } + + // 构建创建审批定义的入参对象 + public ApprovalsDefinitionVo buildDefinitionVo() { + ApprovalsDefinitionVo approvalsDefinitionVo = new ApprovalsDefinitionVo(); + // 构建审批定义vo + approvalsDefinitionVo.setApproval_name("@i18n@1"); + approvalsDefinitionVo.setApproval_code("permission_test3"); + approvalsDefinitionVo.setGroup_code("work_group"); + approvalsDefinitionVo.setGroup_name("@i18n@2"); + // 构建三方审批相关对象. + DefinitionExternal definitionExternal = new DefinitionExternal(); + definitionExternal.setCreate_link_pc(""); + definitionExternal.setCreate_link_mobile(""); + definitionExternal.setSupport_pc(true); + definitionExternal.setSupport_mobile(true); + definitionExternal.setSupport_batch_read(true); + definitionExternal.setEnable_mark_readed(true); + // 构建可见人列表对象 + DefinitionViewers definitionViewers = new DefinitionViewers(); + definitionViewers.setViewer_type("NONE"); + List viewers = new ArrayList<>(); + viewers.add(definitionViewers); + // 构建语言文案 + List texts = new ArrayList<>(); + texts.add(new DefinitionTexts("@i18n@1",approvalName)); + texts.add(new DefinitionTexts("@i18n@2",approvalName)); + texts.add(new DefinitionTexts("@i18n@3","飞书")); + // 构建国际化文案对象 + DefinitionI18nResources definitionI18nResources = new DefinitionI18nResources(); + definitionI18nResources.setLocale("zh-CN"); + definitionI18nResources.setIs_default(true); + definitionI18nResources.setTexts(texts); + List i18n_resources = new ArrayList<>(); + i18n_resources.add(definitionI18nResources); + approvalsDefinitionVo.setExternal(definitionExternal); + approvalsDefinitionVo.setViewers(viewers); + approvalsDefinitionVo.setI18n_resources(i18n_resources); + List mangers = Arrays.asList(managersIds.split(",")); + String[] mangersString = mangers.toArray(new String[0]); + approvalsDefinitionVo.setManagers(mangersString); + return approvalsDefinitionVo; + } + + @Override + public ApprovalsDefinitionVo getApprovalDefinition(String approvalDefinitionCode) { + String url = env.getProperty("feishu.get_approval_definition"); + assert url != null; + // 获取token + String accessToken = getAccessToken(); + // 拼接URL + String fullUrl = url + "/" + approvalDefinitionCode; + // 设置请求头 + HttpHeaders headers = createHeaders(accessToken); + // 创建 HttpEntity + HttpEntity entity = new HttpEntity<>(headers); + // 发送请求 + ResponseEntity response = restTemplate.exchange(fullUrl, HttpMethod.GET, entity, String.class); + JSONObject jsonResponse = JSON.parseObject(response.getBody()); + if ("0".equals((String.valueOf(jsonResponse.get("code"))))) { + JSONObject dataJson = jsonResponse.getJSONObject("data"); + return JSON.toJavaObject(dataJson, ApprovalsDefinitionVo.class); + } else { + log.error(jsonResponse.get("msg").toString()); + throw new JeroBootException("查看三方审批定义失败"); + } + } + + @Override + public String getUserId(String phones) { + try { + String url = env.getProperty("feishu.get_user_id"); + assert url != null; + // 获取token + String accessToken = getAccessToken(); + // 构建请求对象 + JSONObject jsonObject = new JSONObject(); + List phonesList = Arrays.asList(phones.split(",")); + String[] phonesString = phonesList.toArray(new String[0]); + jsonObject.put("mobiles", phonesString); + // 设置请求头 + HttpHeaders headers = createHeaders(accessToken); + // 构建包含查询参数的 URL + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url) + .queryParam("user_id_type", "user_id"); + // 创建 HttpEntity + HttpEntity entity = new HttpEntity<>(jsonObject, headers); + // 发送请求 + ResponseEntity response = restTemplate.exchange(builder.toUriString(), HttpMethod.POST, entity, String.class); + // 处理响应 + JSONObject jsonResponse = JSON.parseObject(response.getBody()); + if ("0".equals((String.valueOf(jsonResponse.get("code"))))) { + JSONObject data = jsonResponse.getJSONObject("data"); + JSONArray userList = data.getJSONArray("user_list"); + // 将 JSONArray 转换为 List + List userListObjects = userList.toJavaList(JSONObject.class); + return userListObjects.get(0).getString("user_id"); + } else { + log.error("获取飞书用户id失败" + jsonResponse.get("msg").toString()); + return ""; + } + } catch (Exception e) { + log.error(e.getMessage()); + } + return ""; + } + + /** + * 同步三方审批实例 + * + * @return + */ + @Override + public void synchronizeApprovalInstance(InstanceDto instanceDto) { + String url = env.getProperty("feishu.synchronize_approval_instance"); + assert url != null; + try { + // 获取token + String accessToken = getAccessToken(); + // 构建对象 + ApprovalsInstanceVo approvalsInstanceVo = buildApprovalsInstanceVo(instanceDto); + // 设置请求头 + HttpHeaders headers = createHeaders(accessToken); + // 创建 HttpEntity + HttpEntity entity = new HttpEntity<>(approvalsInstanceVo, headers); + restTemplate.exchange(url, HttpMethod.POST, entity, String.class); + }catch (Exception e) { + log.error("同步三方审批实例异常" + e.getMessage()); + } + } + + private ApprovalsInstanceVo buildApprovalsInstanceVo(InstanceDto instanceDto) { + // 转换userId为飞书Id + // 发起人的id + String promoterUserId = instanceDto.getPromoterUserId(); + if (StringUtils.isNoneBlank(promoterUserId)) { + SysUser promoterUser = sysUserService.getById(promoterUserId); + promoterUserId = promoterUser.getFeishuId(); + } + // 审核人的id(可能为多个),查出对应的飞书id + String reviewedIds = instanceDto.getReviewedIds(); + List newReviewedIds = new ArrayList<>(); + if (StringUtils.isNotBlank(reviewedIds)) { + List reviewedIdList = Arrays.asList(reviewedIds.split(",")); + for (String reviewedId : reviewedIdList) { + SysUser reviewUser = sysUserService.getById(reviewedId); + if (!Objects.isNull(reviewUser) && StringUtils.isNotBlank(reviewUser.getFeishuId())) { + newReviewedIds.add(reviewUser.getFeishuId()); + } + } + } + // 任务id + String taskIds = instanceDto.getTaskIds(); + List taskIdList = new ArrayList<>(); + if (StringUtils.isNotBlank(taskIds)) { + taskIdList = Arrays.asList(instanceDto.getTaskIds().split(",")); + } + // 任务状态 + String taskStatus = instanceDto.getTaskStatus(); + List taskStatusList = new ArrayList<>(); + if (StringUtils.isNotBlank(taskIds)) { + taskStatusList = Arrays.asList(taskStatus.split(",")); + } + + // 替换跳转的地址为单点登录的地址 + String url = env.getProperty("feishu.sign_login_in"); + assert url != null; + // 审核人对应的 pc 跳转地址 + String reviewPcLink = instanceDto.getReviewPcLink(); + List reviewPcLinkList = new ArrayList<>(); + if (StringUtils.isNotBlank(reviewPcLink)) { + reviewPcLinkList = Arrays.asList(reviewPcLink.split(",")); + reviewPcLinkList = reviewPcLinkList.stream() + .map(link -> url.replace("{REDIRECT_URI}", link)) + .collect(Collectors.toList()); + } + // 审核人对应的 mobile 跳转地址 + String reviewMobileLink = instanceDto.getReviewMobileLink(); + List reviewMobileLinkList = new ArrayList<>(); + if (StringUtils.isNotBlank(reviewMobileLink)) { + reviewMobileLinkList = Arrays.asList(reviewMobileLink.split(",")); + reviewMobileLinkList = reviewMobileLinkList.stream() + .map(link -> url.replace("{REDIRECT_URI}", link)) + .collect(Collectors.toList()); + } + // 发起人对应的pc的跳转地址 + String promoterPcLink = instanceDto.getPromoterPcLink(); + if (StringUtils.isNotBlank(promoterPcLink)) { + promoterPcLink = url.replace("{REDIRECT_URI}", promoterPcLink); + } + // 发起人移动端的跳转地址 + String promoterMobileLink = instanceDto.getPromoterMobileLink(); + if (StringUtils.isNotBlank(promoterMobileLink)) { + promoterMobileLink = url.replace("{REDIRECT_URI}", promoterMobileLink); + } + // 审批定义的code + String approvalDefinition = createApprovalDefinition(); + // 发起人相关的信息 + ApprovalsInstanceVo approvalsInstanceVo = new ApprovalsInstanceVo(); + approvalsInstanceVo.setApproval_code(approvalDefinition); + approvalsInstanceVo.setStatus(instanceDto.getInstanceStatus()); + approvalsInstanceVo.setInstance_id(instanceDto.getProcessInstanceId()); + InstanceLink instanceLink = new InstanceLink(); + instanceLink.setPc_link(promoterPcLink); + instanceLink.setMobile_link(promoterMobileLink); + approvalsInstanceVo.setLinks(instanceLink); + approvalsInstanceVo.setTitle("@i18n@1"); + approvalsInstanceVo.setUser_id(promoterUserId); // 审批发起人的user_id + approvalsInstanceVo.setStart_time(String.valueOf(instanceDto.getInstanceStartTime().getTime())); + approvalsInstanceVo.setEnd_time("0"); + approvalsInstanceVo.setUpdate_time(String.valueOf(System.currentTimeMillis())); + approvalsInstanceVo.setUpdate_mode("UPDATE"); + approvalsInstanceVo.setDisplay_method("BROWSER"); + List instanceTasks = new ArrayList<>(); + for (int i = 0; i < newReviewedIds.size(); i++) { + // 审核人相关的信息 + InstanceTask instanceTask = new InstanceTask(); + instanceTask.setUser_id(newReviewedIds.get(i)); // 审批人的id + if (!taskIdList.isEmpty()) { + instanceTask.setTask_id(taskIdList.get(i)); + } + InstanceLink taskLink = new InstanceLink(); + if (!reviewPcLinkList.isEmpty()) { + taskLink.setPc_link(reviewPcLinkList.get(i)); + } + if (!reviewMobileLinkList.isEmpty()) { + taskLink.setMobile_link(reviewMobileLinkList.get(i)); + } + instanceTask.setLinks(taskLink); + if (!taskStatusList.isEmpty()) { + instanceTask.setStatus(taskStatusList.get(i)); + } + instanceTask.setTitle("@i18n@1"); + instanceTask.setCreate_time(String.valueOf(System.currentTimeMillis())); + instanceTask.setEnd_time(String.valueOf(System.currentTimeMillis())); + instanceTask.setUpdate_time(String.valueOf(System.currentTimeMillis())); + instanceTask.setDisplay_method("BROWSER"); + instanceTasks.add(instanceTask); + } + approvalsInstanceVo.setTask_list(instanceTasks); + // 国际化文案 + DefinitionI18nResources definitionI18nResources = new DefinitionI18nResources(); + definitionI18nResources.setLocale("zh-CN"); + definitionI18nResources.setIs_default(true); + List texts = new ArrayList<>(); + texts.add(new DefinitionTexts("@i18n@1",instanceDto.getTitle())); + definitionI18nResources.setTexts(texts); + List i18n_resources = new ArrayList<>(); + i18n_resources.add(definitionI18nResources); + approvalsInstanceVo.setI18n_resources(i18n_resources); + return approvalsInstanceVo; + } + + private HttpHeaders createHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Bearer " + accessToken); + headers.set("Content-Type", "application/json"); + return headers; + } + + @Override + public void sendHrefMessageByBot(BotMessageDto botMessageDto) { + String url = env.getProperty("feishu.bot_send_message"); + String signLoginUrl = env.getProperty("feishu.sign_login_in"); + assert url != null; + assert signLoginUrl != null; + try { + // 获取token + String accessToken = getAccessToken(); + // 构建参数 + List newPushRange = new ArrayList<>(); + if (StringUtils.isNotBlank(botMessageDto.getReceiveIds())) { + List pushRangeList = Arrays.asList(botMessageDto.getReceiveIds().split(",")); + for (String id : pushRangeList) { + SysUser reviewUser = sysUserService.getById(id); + if (!Objects.isNull(reviewUser) && StringUtils.isNotBlank(reviewUser.getFeishuId())) { + newPushRange.add(reviewUser.getFeishuId()); + } + } + } + if (newPushRange.isEmpty()) { + log.error("接收消息的飞书用户均不存在,消息发送终止"); + return; + } + // 将url替换为单点登录的地址 + String hrefUrl = botMessageDto.getHrefUrl(); + if (StringUtils.isNotBlank(hrefUrl)) { + hrefUrl = signLoginUrl.replace("{REDIRECT_URI}", hrefUrl); + } + // 构建消息内容部分 + JSONArray contentArray = new JSONArray(); + // 构建普通文本前缀 + if (StringUtils.isNotBlank(botMessageDto.getTextBeforeHref())) { + JSONObject textBeforeHrefObject = new JSONObject(); + textBeforeHrefObject.put("tag", "text"); + textBeforeHrefObject.put("text", botMessageDto.getTextBeforeHref()); + contentArray.add(textBeforeHrefObject); + } + // 构建超链接文本 + if (StringUtils.isNotBlank(botMessageDto.getHrefText()) && StringUtils.isNotBlank(hrefUrl)) { + JSONObject hrefContentObject = new JSONObject(); + hrefContentObject.put("tag", "a"); + hrefContentObject.put("href", hrefUrl); + hrefContentObject.put("text", botMessageDto.getHrefText()); + contentArray.add(hrefContentObject); + } + // 构建普通文本后缀 + if (StringUtils.isNotBlank(botMessageDto.getTextAfterHref())) { + JSONObject textAfterHrefObject = new JSONObject(); + textAfterHrefObject.put("tag", "text"); + textAfterHrefObject.put("text", botMessageDto.getTextAfterHref()); + contentArray.add(textAfterHrefObject); + } + + List contentList = new ArrayList<>(); + contentList.add(contentArray); + JSONObject zhCnObject = new JSONObject(); + zhCnObject.put("title", botMessageDto.getTitle()); + zhCnObject.put("content", contentList); + JSONObject finalObject = new JSONObject(); + finalObject.put("zh_cn", zhCnObject); + String content = finalObject.toJSONString(); + BotMessageVo botMessageVo = new BotMessageVo(); + botMessageVo.setMsg_type("post"); + botMessageVo.setContent(content); + + // 设置请求头 + HttpHeaders headers = createHeaders(accessToken); + // 构建包含查询参数的 URL + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url).queryParam("receive_id_type", "user_id"); + // 创建 HttpEntity + for (String pushId : newPushRange) { + String uuid = UUID.randomUUID().toString().replace("-", ""); + botMessageVo.setReceive_id(pushId); + botMessageVo.setUuid(uuid); + HttpEntity entity = new HttpEntity<>(botMessageVo, headers); + restTemplate.exchange(builder.toUriString(), HttpMethod.POST, entity, String.class); + } + } catch (Exception e) { + log.error("飞书发送消息异常" + e.getMessage()); + } + } + + + /** + * 根据预授权码获取用户信息 + * + * @param codeObject + * @return + */ + @Override + public Result getUserInfoByCode(JSONObject codeObject) { + String url = env.getProperty("feishu.get_userinfo_by_code"); + assert url != null; + String code = codeObject.getString("code"); + // 获取token + String accessToken = getAccessToken(); + // 设置请求头 + HttpHeaders headers = createHeaders(accessToken); + // 创建 HttpEntity + JSONObject jsonObject = new JSONObject(); + jsonObject.put("grant_type","authorization_code"); + jsonObject.put("code",code); + HttpEntity entity = new HttpEntity<>(jsonObject, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); + // 处理响应 + Result result = new Result<>(); + JSONObject jsonResponse = JSON.parseObject(response.getBody()); + if ("0".equals((String.valueOf(jsonResponse.get("code"))))) { + JSONObject dataJson = jsonResponse.getJSONObject("data"); + String feishuId = dataJson.getString("user_id"); + // 从sys_user表查询对应的用户信息 + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(SysUser::getFeishuId,feishuId); + SysUser suer = sysUserService.getOne(lambdaQueryWrapper); + //用户登录信息 + userInfo(suer, result); + } else { + log.error("获取用户信息失败"); + } + return result; + } + + private Result userInfo(SysUser sysUser, Result result) { + String multiDepart ="multi_depart"; + String syspassword = sysUser.getPassword(); + String username = sysUser.getUsername(); + //获取用户角色信息 + String userId = sysUser.getId(); + if (StringUtils.isNotBlank(userId)){ + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysUserRole::getUserId, userId); + List sysUserRoles = sysUserRoleService.list(wrapper); + if (!sysUserRoles.isEmpty()){ + List collect = sysUserRoles.stream().map(SysUserRole::getRoleId).distinct().collect(Collectors.toList()); + List roles = sysRoleService.listByIds(collect); + String code = roles.stream().map(SysRole::getRoleCode).collect(Collectors.joining(",")); + sysUser.setRoleCode(code); + } + } + // 获取用户部门信息 + JSONObject obj = new JSONObject(); + List departs = sysDepartService.queryUserDeparts(sysUser.getId()); + obj.put("departs", departs); + if (departs == null || departs.isEmpty()) { + obj.put(multiDepart, 0); + } else if (departs.size() == 1) { + sysUserService.updateUserDepart(username, departs.get(0).getOrgCode()); + obj.put(multiDepart, 1); + } else { + //查询当前是否有登录部门 + SysUser sysUserById = sysUserService.getById(sysUser.getId()); + if(oConvertUtils.isEmpty(sysUserById.getOrgCode())){ + sysUserService.updateUserDepart(username, departs.get(0).getOrgCode()); + } + obj.put(multiDepart, 2); + } + + if (CollectionUtils.isNotEmpty(departs)) { + List departIdList = departs.stream().map(SysDepart::getId).collect(Collectors.toList()); + sysUser.setDepartIds(StringUtils.join(departIdList, ",")); + } + + String tenantIds = sysUser.getRelTenantIds(); + if (oConvertUtils.isNotEmpty(tenantIds)) { + List tenantIdList = Arrays.asList(tenantIds.split(",")); + // 该方法仅查询有效的租户,如果返回0个就说明所有的租户均无效。 + List tenantList = sysTenantService.queryEffectiveTenant(tenantIdList); + if (tenantList.isEmpty()) { + return Result.error("与该用户关联的租户均已被冻结,无法登录!"); + } else { + obj.put("tenantList", tenantList); + } + } + // 生成token + String token = JwtUtil.sign(username, syspassword); + // 设置token缓存有效时间 + redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token); + redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000); + String departIds = sysUser.getDepartIds(); + if (StringUtils.isNotBlank(departIds)) { + String departIds_dicText = translateDictValue("id", "depart_name", "sys_depart", departIds, null); + sysUser.setDepartIds_dicText(departIds_dicText); + } + obj.put("token", token); + obj.put(USER_INFO, sysUser); + obj.put("sysAllDictItems", sysDictService.queryAllDictItems()); + result.setResult(obj); + result.setMessage("登录成功"); + return result; + } + + private String translateDictValue(String code, String text, String table, String key, List listDict) { + if (oConvertUtils.isEmpty(key)) { + return null; + } + StringBuilder textValue = new StringBuilder(); + String[] keys = key.split(","); + for (String k : keys) { + String tmpValue = null; + if (k.trim().length() == 0) { + continue; //跳过循环 + } + if (!org.springframework.util.StringUtils.isEmpty(table)) { + tmpValue = commonAPI.queryTableDictTextByKey(table, text, code, k.trim()); + } else { + List listNew = listDict.stream() + .filter(o -> Objects.equals(o.getDictCode(), code) && Objects.equals(o.getItemValue(), k.trim())).collect(Collectors.toList()); + if (!org.springframework.util.CollectionUtils.isEmpty(listNew)) { + tmpValue = listNew.get(0).getItemText(); + } + } + + if (tmpValue != null) { + if (!"".contentEquals(textValue)) { + textValue.append(","); + } + textValue.append(tmpValue); + } + } + return textValue.toString(); + } +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/bot/BotMessageVo.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/bot/BotMessageVo.java new file mode 100644 index 00000000..5178f3d0 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/bot/BotMessageVo.java @@ -0,0 +1,35 @@ +package com.jero.modules.feishu.vo.bot; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class BotMessageVo { + + /** + * 消息接收者的id + */ + @ApiModelProperty("消息接收者的ID") + private String receive_id; + + /** + * 消息类型 包括:text、post、image、file、audio、media、 + * sticker、interactive、share_chat、share_user等 + */ + @ApiModelProperty("消息类型") + private String msg_type; + + /** + * 消息内容,JSON结构序列化后的字符串 + */ + @ApiModelProperty("消息内容") + private String content; + + /** + * 由开发者生成的唯一字符串序列,用于发送消息请求去重; + * 持有相同uuid的请求1小时内至多成功发送一条消息 + */ + @ApiModelProperty("UUID") + private String uuid; + +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/ApprovalsDefinitionVo.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/ApprovalsDefinitionVo.java new file mode 100644 index 00000000..2caccb4d --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/ApprovalsDefinitionVo.java @@ -0,0 +1,80 @@ +package com.jero.modules.feishu.vo.definition; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ApprovalsDefinitionVo { + + /** + * + * 审批名称的国际化文案 Key,以 @i18n@ 开头,长度不得少于 9 个字符 + * 示例值: @i18n@1 + */ + @ApiModelProperty("审批名称的国际化文案") + private String approval_name; + + /** + * + * 审批定义 code,用户自定义,定义的唯一标识,如果不存在该 code,则创建,否则更新 + * 示例值: permission_test + */ + @ApiModelProperty("审批定义code") + private String approval_code; + + /** + * + * 审批定义所属审批分组,用户自定义; 如果group_code当前不存在,则会新建审批分组; + * 如果group_code已经存在,则会使用group_name更新审批分组名称 + * 示例值: work_group + */ + @ApiModelProperty("审批定义所属审批分组") + private String group_code; + + /** + * 分组名称,值的格式是 i18n key,文案放在 i18n_resource; + * 如果是 group_code 当前不存在,则该 group_name 必填,否则, + * 如果填写了则会更新分组名称,不填则不更新分组名称; + * 审批发起页 审批定义的分组名称来自该字段 + * 示例值: @i18n@2 + */ + @ApiModelProperty("审批定义所属审批分组") + private String group_name; + + /** + * 审批定义的说明,值的格式是 i18n key,文案放在 i18n_resource; + * 审批发起页 审批定义的说明内容来自该字段 + * 示例值: @i18n@2 + */ + @ApiModelProperty("审批定义的说明") + private String description; + + /** + * 三方审批相关 + */ + @ApiModelProperty("三方审批相关") + private DefinitionExternal external; + + /** + * 可见人列表(最大支持长度200),可通知配置多个可见人, + * 只有在配置的范围内用户可以在审批发起页看到该审批, + * 默认不传,则是任何人不可见 + */ + @ApiModelProperty("可见人列表") + private List viewers; + + /** + * 国际化文案 + */ + @ApiModelProperty("国际化文案") + private List i18n_resources; + + /** + * 根据user_id_type填写流程管理员id列表(最大支持长度200) + * 示例值:["19a294c2"] + */ + @ApiModelProperty("流程管理员id列表") + private String[] managers; +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionExternal.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionExternal.java new file mode 100644 index 00000000..499c6e24 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionExternal.java @@ -0,0 +1,108 @@ +package com.jero.modules.feishu.vo.definition; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 三方审批相关 + */ +@Data +public class DefinitionExternal { + + /** + * + * 列表中用于提示审批来自哪里,i18n key, + * 注意不需要“来自”前缀,审批中心会拼上前缀 + * 示例值:"@i18n@3" + */ + @ApiModelProperty("列表中用于提示审批来自哪里") + private String biz_name; + + /** + * 审批定义业务类别 + * 示例值:"permission" + */ + @ApiModelProperty("审批定义业务类别") + private String biz_type; + + /** + * 移动端发起链接,如果设置了该链接,则会在移动端审批发起页展示该审批, + * 用户点击后会跳转到该链接进行发起; + * 如果不填,则在mobile端不显示该审批 + */ + @ApiModelProperty("移动端发起链接") + private String create_link_mobile; + + /** + * PC端发起链接,如果设置了该链接,则会在PC端审批发起页展示该审批, + * 用户点击后会跳转到该链接进行发起; + * 如果不填,则在PC端不显示该审批; + */ + @ApiModelProperty("PC端发起链接") + private String create_link_pc; + + /** + * 审批实例、审批任务、审批抄送是否要在PC端展示, + * 如果为 true,则PC端列表会展示该定义下的实例信息,否则,不展示 + */ + @ApiModelProperty("审批实例、审批任务、审批抄送是否要在PC端展示") + private boolean support_pc; + + /** + * 审批实例、审批任务、审批抄送是否要在移动端展示, + * 如果为 true,则PC端列表会展示该定义下的实例信息,否则,不展示 + */ + @ApiModelProperty("审批实例、审批任务、审批抄送是否要在移动端展示") + private boolean support_mobile; + + /** + * 是否支持批量已读 + */ + @ApiModelProperty("是否支持批量已读") + private boolean support_batch_read; + + /** + * 是否支持批量已读(该字段无效) + */ + @ApiModelProperty("是否支持标注可读(该字段无效)") + private boolean enable_mark_readed; + + /** + * 是否支持快速操作 + */ + @ApiModelProperty("是否支持快速操作") + private boolean enable_quick_operate; + + /** + * 三方系统的操作回调 url,【待审批】列表的任务审批人点同意或拒绝操作后,审批中心调用该地址通知三方系统 + */ + @ApiModelProperty("三方系统的操作回调 url") + private String action_callback_url; + + /** + * 回调时带的 token, 用于业务系统验证请求来自审批, + */ + @ApiModelProperty("回调时带的token") + private String action_callback_token; + + /** + * 请求参数加密密钥,如果配置了该参数,则会对请求参数进行加密,业务需要对请求进行解密 + */ + @ApiModelProperty("请求参数加密密钥") + private String action_callback_key; + + /** + * 是否支持批量审批 + */ + @ApiModelProperty("是否支持批量审批") + private boolean allow_batch_operate; + + /** + * 审批流程数据是否不纳入效率统计 + */ + @ApiModelProperty("审批流程数据是否不纳入效率统计") + private boolean exclude_efficiency_statistics; + + + +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionI18nResources.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionI18nResources.java new file mode 100644 index 00000000..4e51a958 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionI18nResources.java @@ -0,0 +1,61 @@ +package com.jero.modules.feishu.vo.definition; + +import com.alibaba.fastjson.annotation.JSONField; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 国际化文案 + */ +@Data +public class DefinitionI18nResources { + + /** + * 语言可选值有: zh-CN:中文 en-US:英文 ja-JP:日文 + * 示例值:"zh-CN" + * zh-CN:中文 + * en-US:英文 + * ja-JP:日文 + */ + @ApiModelProperty("语言可选值有: zh-CN:中文 en-US:英文 ja-JP:日文") + private String locale; + + /** + * 是否默认语言,默认语言需要包含所有key,非默认语言如果key不存在会使用默认语言代替 + */ + @ApiModelProperty("是否默认语言,默认语言需要包含所有key,非默认语言如果key不存在会使用默认语言代替") + @JSONField(name = "is_default") + private boolean is_default; + + /** + * 示例值: "{ @i18n@1: 权限申请 }" + */ + @ApiModelProperty("{ @i18n@1: 权限申请 }") + private List texts; + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public boolean isIs_default() { + return is_default; + } + + public void setIs_default(boolean is_default) { + this.is_default = is_default; + } + + public List getTexts() { + return texts; + } + + public void setTexts(List texts) { + this.texts = texts; + } +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionTexts.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionTexts.java new file mode 100644 index 00000000..213393bb --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionTexts.java @@ -0,0 +1,30 @@ +package com.jero.modules.feishu.vo.definition; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class DefinitionTexts { + + public DefinitionTexts(String key, String value) { + this.key = key; + this.value = value; + } + + public DefinitionTexts() { + } + + /** + 文案key + 示例值:"@i18n@1" + */ + @ApiModelProperty("文案key") + private String key; + + /** + 文案 + 示例值:"people" + */ + @ApiModelProperty("文案") + private String value; +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionViewers.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionViewers.java new file mode 100644 index 00000000..a8803199 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/definition/DefinitionViewers.java @@ -0,0 +1,36 @@ +package com.jero.modules.feishu.vo.definition; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 可见人列表 + */ +@Data +public class DefinitionViewers { + + /** + 可见人类型 示例值:"USER" + * 可选值有: + TENANT:租户内可见 + DEPARTMENT:指定部门 + USER:指定用户 + NONE:任何人都不可见 + */ + @ApiModelProperty("可见人类型 TENANT:租户内可见 DEPARTMENT:指定部门 USER:指定用户 NONE:任何人都不可见") + private String viewer_type; + + /** + * 当 viewer_type 是 USER,根据user_id_type填写用户id + * 示例值:"19a294c2" + */ + @ApiModelProperty("当 viewer_type 是 USER,根据user_id_type填写用户id") + private String viewer_user_id; + + /** + * 当 viewer_type 为DEPARTMENT,根据department_id_type填写部门id + * 示例值:"od-ac9d697abfa990b715dcc33d58a62a9d" + */ + @ApiModelProperty("当 viewer_type 为DEPARTMENT,根据department_id_type填写部门id") + private String viewer_department_id; +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/ApprovalsInstanceVo.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/ApprovalsInstanceVo.java new file mode 100644 index 00000000..8a5fa8d9 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/ApprovalsInstanceVo.java @@ -0,0 +1,185 @@ +package com.jero.modules.feishu.vo.instance; + +import com.jero.modules.feishu.vo.definition.DefinitionI18nResources; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ApprovalsInstanceVo { + + /** + * 审批定义 code, 创建审批定义返回的值,表示该实例属于哪个流程; + * 该字段会影响到列表中该实例的标题,标题取自对应定义的 name 字段 + * 示例值:"81D31358-93AF-92D6-7425-01A5D67C4E71" + */ + @ApiModelProperty("审批定义 code") + private String approval_code; + + /** + * 审批实例状态 + * 示例值:"PENDING" + * PENDING: 审批中 + * APPROVED: 审批流程结束,结果为同意 + * REJECTED: 审批流程结束,结果为拒绝 + * CANCELED: 审批发起人撤回 + * DELETED: 审批被删除 + * HIDDEN: 状态隐藏(不显示状态) + * TERMINATED:审批终止 + */ + @ApiModelProperty("审批实例状态") + private String status; + + /** + * 审批实例扩展 JSON。单据编号通过传business_key字段来实现。 + * 以下示例值未转义,使用时请注意转义。 + * 你可查看请求体示例中转义后的 extra 示例值 + * 示例值:"{"xxx":"xxx","business_key":"xxx"}" + */ + @ApiModelProperty("审批实例扩展JSON") + private String extra; + + /** + * + * 审批实例唯一标识,用户自定义,需确保证租户和应用下都唯一 + * 示例值:"24492654" + */ + @ApiModelProperty("审批实例唯一标识") + private String instance_id; + + /** + * 审批实例链接集合 , + * 用于【已发起】列表的跳转,跳转回三方系统 + */ + @ApiModelProperty("审批实例链接集合") + private InstanceLink links; + + /** + * 审批展示名称,如果填写了该字段,则审批列表中的审批名称使用该字段, + * 如果不填该字段,则审批名称使用审批定义的名称 + * 示例值:"@i18n@1" + */ + @ApiModelProperty("审批展示名称") + private String title; + + /** + * 用户提交审批时填写的表单数据,用于所有审批列表中展示。可传多个值, + * 但审批中心pc展示前2个,移动端展示前3个,长度不超过2048字符 + * 示例值:[{ "name": "@i18n@2", "value": "@i18n@3" }] + */ + @ApiModelProperty("用户提交审批时填写的表单数据") + private List form; + + /** + * + * 审批发起人 user_id,发起人可在【已发起】列表中看到所有已发起的审批; + * 在【待审批】,【已审批】【抄送我】列表中,该字段展示审批是谁发起的。 + * 示例值:"a987sf9s" + */ + @ApiModelProperty("审批发起人 user_id") + private String user_id; + + /** + * 审批发起人 用户名,如果发起人不是真实的用户(例如是某个部门), + * 没有 user_id,则可以使用该字段传名称 + * 示例值:"@i18n@9" + */ + @ApiModelProperty("审批发起人 用户名") + private String user_name; + + /** + * 审批发起人 open id + * 示例值:"ou_be73cbc0ee35eb6ca54e9e7cc14998c1" + */ + @ApiModelProperty("审批发起人 open id") + private String open_id; + + /** + * 发起人部门,用于列表中展示发起人所属部门。不传则不展示。 + * 如果用户没加入任何部门,传 "",将展示租户名称传 department_name 展示部门名称 + * 示例值:"od-8ec33278bc2" + */ + @ApiModelProperty("发起人部门") + private String department_id; + + /** + * 审批发起人 部门,如果发起人不是真实的用户(例如是某个部门), + * 没有 department_id,则可以使用该字段传名称 + * 示例值:"@i18n@10" + */ + @ApiModelProperty("审批发起人 部门") + private String department_name; + + /** + * 审批发起时间,Unix毫秒时间戳 + * 示例值:"1556468012678" + */ + @ApiModelProperty("审批发起时间 Unix毫秒时间戳") + private String start_time; + + /** + * 审批实例结束时间,Unix毫秒时间戳 + * 示例值:"1556468012678" + */ + @ApiModelProperty("审批实例结束时间 Unix毫秒时间戳") + private String end_time; + + /** + * 审批实例最近更新时间;用于推送数据版本控制如果 update_mode 值为 UPDATE, + * 则只有传过来的 update_time 有变化时(变大),才会更新审批中心中的审批实例信息。 + * 使用该字段主要用来避免并发时老的数据更新了新的数据 + * 示例值:"1556468012678" + */ + @ApiModelProperty("审批实例最近更新时间 Unix毫秒时间戳") + private String update_time; + + /** + * 列表页打开审批实例的方式 + * 示例值:"BROWSER" + * BROWSER:跳转系统默认浏览器打开 + * SIDEBAR:飞书中侧边抽屉打开 + * NORMAL:飞书内嵌页面打开 + * TRUSTEESHIP:以托管打开 + */ + @ApiModelProperty("列表页打开审批实例的方式") + private String display_method; + + /** + * 更新方式, 当 update_mode=REPLACE时,每次都以当前推送的数据为最终数据,会删掉审批中心中多余的任务、抄送数据(不在这次推送的数据中); + * 当 update_mode=UPDATE时,则不会删除审批中心的数据,而只是进行新增和更新实例、任务数据 + * 示例值:"UPDATE" + * 可选值有: + * REPLACE:全量替换,默认值 + * UPDATE:增量更新 + */ + @ApiModelProperty("列表页打开审批实例的方式") + private String update_mode; + + /** + * 抄送列表 + */ + @ApiModelProperty("任务列表") + private List task_list; + + /** + * 国际化文案 + */ + @ApiModelProperty("国际化文案") + private List i18n_resources; + + /** + * 单据托管认证token,托管回调会附带此token,帮助业务方认证 + * 示例值:"788981c886b1c28ac29d1e68efd60683d6d90dfce80938ee9453e2a5f3e9e306" + */ + @ApiModelProperty("单据托管认证token") + private String trusteeship_url_token; + + /** + * 用户的类型,会影响请求参数用户标识域的选择,包括加签操作回传的目标用户, 目前仅支持 "user_id" + * 示例值:"user_id" + */ + @ApiModelProperty("用户的类型") + private String trusteeship_user_id_type; + +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceForm.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceForm.java new file mode 100644 index 00000000..14cf52bc --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceForm.java @@ -0,0 +1,22 @@ +package com.jero.modules.feishu.vo.instance; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class InstanceForm { + + /** + * 表单字段名称 + * 示例值:"@i18n@2" + */ + @ApiModelProperty("表单字段名称") + private String name; + + /** + * 表单值 + * 示例值:"@i18n@3" + */ + @ApiModelProperty("表单值") + private String value; +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceLink.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceLink.java new file mode 100644 index 00000000..3ebbaa90 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceLink.java @@ -0,0 +1,23 @@ +package com.jero.modules.feishu.vo.instance; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +public class InstanceLink { + + /** + * pc端的跳转链接,当用户使用飞书 pc 端时, + * 使用该字段进行跳转,托管的链接保持不变。 + */ + @ApiModelProperty("pc端的跳转链接") + private String pc_link; + + /** + * 移动端的跳转链接,当用户使用飞书 pc 端时, + * 使用该字段进行跳转,托管的链接保持不变。 + */ + @ApiModelProperty("移动端的跳转链接") + private String mobile_link; +} diff --git a/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceTask.java b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceTask.java new file mode 100644 index 00000000..571f0018 --- /dev/null +++ b/laws-modules/src/main/java/com/jero/modules/feishu/vo/instance/InstanceTask.java @@ -0,0 +1,153 @@ +package com.jero.modules.feishu.vo.instance; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +/** + * 任务列表 + */ +@Data +public class InstanceTask { + + /** + * 审批实例内的唯一标识,用于更新审批任务时定位数据 + */ + @ApiModelProperty("审批实例内的唯一标识") + private String task_id; + + /** + * 审批人 user_id,和 open_id 二者至少填一个。 + * 该任务会出现在审批人的【待审批】或【已审批】列表中 + */ + @ApiModelProperty("审批人 user_id") + private String user_id; + + /** + * 审批人 open_id,和 user_id 二者至少填一个 + * 示例值:"ou_be73cbc0ee35eb6ca54e9e7cc14998c1" + */ + @ApiModelProperty("审批人 open_id") + private String open_id; + + /** + * 审批任务名称 + * 示例值:"i18n1" + */ + @ApiModelProperty("审批任务名称") + private String title; + + /** + * 【待审批】或【已审批】中使用的跳转链接, + * 用于跳转回三方系统pc_link 和 mobile_link 必须填一个, + * 填写的是哪一端的链接,即会跳转到该链接,不受平台影响 + */ + @ApiModelProperty("跳转链接") + private InstanceLink links; + + /** + * 任务状态 + * 示例值:"PENDING" + * 可选值有: + * PENDING:待审批 + * APPROVED:任务同意 + * REJECTED:任务拒绝 + * TRANSFERRED:任务转交 + * DONE:任务通过但审批人未操作;审批人看不到这个任务, 若想要看到, 可以通过抄送该人. + */ + @ApiModelProperty("任务状态") + private String status; + + /** + * 扩展 json,任务结束原因需传complete_reason字段。枚举值与对应说明: + * approved:同意 + * rejected:拒绝 + * node_auto_reject:(因逻辑判断产生的)自动拒绝 + * specific_rollback:退回(包括退回到发起人、退回到中间任一审批人) + * add:并加签(添加新审批人,和我一起审批) + * add_pre:前加签(添加新审批人,在我之前审批) + * add_post:后加签(添加新审批人,在我之后审批) + * delete_assignee:减签 + * forward: 手动转交 + * forward_resign:离职自动转交 + * recall:撤销(撤回单据,单据失效) + * delete :删除审批单 + * admin_forward:管理员在后台操作转交 + * system_forward:系统自动转交 + * auto_skip:自动通过 + * manual_skip:手动跳过 + * submit_again:重新提交任务 + * restart:重新启动流程 + * others:其他(作为兜底) + * 示例值:"{"xxx":"xxx","complete_reason":"approved"}" + */ + @ApiModelProperty("扩展 json") + private String extra; + + /** + * 任务创建时间,Unix 毫秒时间戳 + * 示例值:"1556468012678" + */ + @ApiModelProperty("任务创建时间,Unix 毫秒时间戳") + private String create_time; + + /** + * 任务完成时间:未结束的审批为 0,Unix 毫秒时间戳 + * 示例值:"1556468012678" + */ + @ApiModelProperty("任务完成时间:未结束的审批为 0,Unix 毫秒时间戳") + private String end_time; + + /** + * task最近更新时间,用于推送数据版本控制; 更新策略同 instance 中的 update_time + * 示例值:"1556468012678" + */ + @ApiModelProperty("任务完成时间:未结束的审批为 0,Unix 毫秒时间戳") + private String update_time; + + /** + * 操作上下文,当用户操作时, + * 回调请求中带上该参数,用于传递该任务的上下文数据 + * 示例值:"123456" + */ + @ApiModelProperty("操作上下文") + private String action_context; + + /** + * 列表页打开审批任务的方式 + * 示例值:"BROWSER" + * 可选值有: + * BROWSER:跳转系统默认浏览器打开 + * SIDEBAR:飞书中侧边抽屉打开 + * NORMAL:飞书内嵌页面打开 + * TRUSTEESHIP:以托管模式打开 + */ + @ApiModelProperty("列表页打开审批任务的方式") + private String display_method; + + /** + * 三方任务支持不纳入效率统计。false:纳入效率统计。true:不纳入效率统计 + * 示例值:false + * 默认值:false + */ + @ApiModelProperty("三方任务支持不纳入效率统计") + private boolean exclude_statistics; + + /** + * 节点id:必须同时满足 + * 一个流程内,每个节点id唯一。如一个流程下「直属上级」、「隔级上级」等每个节点的Node_id均不一样 + * 同一个流程定义内,不同审批实例中的相同节点,Node_id要保持不变。例如张三和李四分别发起了请假申请,这2个审批实例中的「直属上级」节点的node_id应该保持一致 + * 示例值:"node" + */ + @ApiModelProperty("节点id") + private String node_id; + + /** + * 节点名称,如「财务审批」「法务审批」,支持中英日三种语言。示例:i18n@name。需要在i18n_resources中传该名称对应的国际化文案 + * 示例值:"i18n@name" + */ + @ApiModelProperty("节点名称") + private String node_name; + +} diff --git a/laws-single-startup/src/main/resources/application-dev.yml b/laws-single-startup/src/main/resources/application-dev.yml index b129a2e5..cfd9eca7 100644 --- a/laws-single-startup/src/main/resources/application-dev.yml +++ b/laws-single-startup/src/main/resources/application-dev.yml @@ -557,3 +557,44 @@ sys_role: # 超级管理员 super_admin: superAdmin +# 飞书对接: +feishu: + # 重定向地址 + redirect: + # 动态消息: + news_feed: http://localhost:3344/standardRegulationLibrary/DynamicMessageList + # 法规导入流程 + regulation_import_process: http://localhost:3344/standardRegulationLibrary/standardMasterPlan + # 市场清单发布流程 + market_publishing_process: http://localhost:3344/standardRegulationLibrary/marketRegulationsList/marketReg + # 节点催办 + node_urging: http://localhost:3344/workCenter/processCenter/processCenter + # 资料中心 + data_center: http://localhost:3344/standardRegulationLibrary/ProfileCenterList + + # app的id + app_id : cli_a61204ce741ed00b + # app秘钥 + app_secret: lblKYMV4im4nP4YkBdgefKLPEdGBwaXA + # 审批定义的名称: + approvalName: 奇瑞法规智库 + # 审批定义的流程管理员的user_id: + managersIds: aacb77d4 + # 基础地址: + base_url: https://open.feishu.cn/open-apis + # 获取token的地址 + get_token: ${feishu.base_url}/auth/v3/tenant_access_token/internal + # 获取用户id + get_user_id: ${feishu.base_url}/contact/v3/users/batch_get_id + # 创建三方审批定义 + create_approval_definition: ${feishu.base_url}/approval/v4/external_approvals + # 查看指定三方审批定义 + get_approval_definition: ${feishu.base_url}/approval/v4/external_approvals + # 同步三方审批实例 + synchronize_approval_instance: ${feishu.base_url}/approval/v4/external_instances + # 使用Bot发送消息 + bot_send_message: ${feishu.base_url}/im/v1/messages + # 飞书根据预登录code获取用户信息 + get_userinfo_by_code: ${feishu.base_url}/authen/v1/access_token + # 飞书单点登录地址 + sign_login_in: ${feishu.base_url}/authen/v1/index?redirect_uri={REDIRECT_URI}&app_id=${feishu.app_id}&state=feishu