新增钉钉审批模板配置功能,包括相关实体、控制器、服务及接口的实现,支持审批模板的增删改查及从钉钉同步模板,增强了系统的审批流管理能力。
This commit is contained in:
@@ -1,14 +1,26 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.jeecg.dingtalk.api.core.response.Response;
|
||||
import com.jeecg.dingtalk.api.user.JdtUserAPI;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -22,6 +34,7 @@ import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.system.entity.SysDepart;
|
||||
import org.jeecg.modules.system.service.ISysDepartService;
|
||||
import org.jeecg.modules.system.service.impl.ThirdAppDingtalkServiceImpl;
|
||||
import org.jeecg.modules.xslmes.approval.action.ApprovalBizAction;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslMixerPsCompile;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslMixerPsCompileService;
|
||||
@@ -44,6 +57,11 @@ public class MesXslMixerPsCompileController extends JeecgController<MesXslMixerP
|
||||
@Autowired
|
||||
private ISysDepartService sysDepartService;
|
||||
|
||||
//update-begin---author:GHT ---date:2026-06-03 for:【钉钉PROC-GENERIC可行性验证】注入钉钉服务-----
|
||||
@Autowired
|
||||
private ThirdAppDingtalkServiceImpl dingtalkService;
|
||||
//update-end---author:GHT ---date:2026-06-03 for:【钉钉PROC-GENERIC可行性验证】注入钉钉服务-----
|
||||
|
||||
@Operation(summary = "MES密炼PS编制-分页列表查询")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslMixerPsCompile>> queryPageList(
|
||||
@@ -283,6 +301,122 @@ public class MesXslMixerPsCompileController extends JeecgController<MesXslMixerP
|
||||
}
|
||||
//update-end---author:jiangxh ---date:20260520 for:【密炼PS编制】保存前校验与冗余回填-----------
|
||||
|
||||
//update-begin---author:GHT ---date:2026-06-03 for:【钉钉PROC-GENERIC可行性验证】测试接口-----
|
||||
@Operation(summary = "钉钉审批测试:用当前登录人手机号获取钉钉userId,并尝试PROC-GENERIC发起审批")
|
||||
@PostMapping(value = "/ddApprovalTest")
|
||||
public Result<Map<String, Object>> ddApprovalTest() {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
// ① 取当前登录用户
|
||||
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
String username = loginUser.getUsername();
|
||||
String realname = oConvertUtils.isNotEmpty(loginUser.getRealname()) ? loginUser.getRealname() : username;
|
||||
String phone = loginUser.getPhone();
|
||||
result.put("step1_user", realname + "(" + username + ")");
|
||||
result.put("step1_phone", oConvertUtils.isNotEmpty(phone) ? phone : "【未配置手机号】");
|
||||
|
||||
if (oConvertUtils.isEmpty(phone)) {
|
||||
result.put("结论", "当前账号未配置手机号,无法查询钉钉userId");
|
||||
return Result.OK(result);
|
||||
}
|
||||
|
||||
// ② 获取 AccessToken
|
||||
String accessToken;
|
||||
try {
|
||||
accessToken = dingtalkService.getAccessToken();
|
||||
} catch (Exception e) {
|
||||
result.put("step2_accessToken", "获取失败: " + e.getMessage());
|
||||
return Result.OK(result);
|
||||
}
|
||||
if (oConvertUtils.isEmpty(accessToken)) {
|
||||
result.put("step2_accessToken", "获取失败,请在[系统配置-第三方应用]中检查钉钉配置");
|
||||
return Result.OK(result);
|
||||
}
|
||||
result.put("step2_accessToken", accessToken.substring(0, Math.min(10, accessToken.length())) + "...[已截断]");
|
||||
|
||||
// ③ 手机号查钉钉 userId
|
||||
Response<String> userIdResp = JdtUserAPI.getUseridByMobile(phone, accessToken);
|
||||
result.put("step3_getUseridByMobile_errcode", userIdResp.getErrcode());
|
||||
result.put("step3_getUseridByMobile_errmsg", userIdResp.getErrmsg());
|
||||
result.put("step3_dingtalkUserId", userIdResp.isSuccess() ? userIdResp.getResult() : "查询失败");
|
||||
|
||||
if (!userIdResp.isSuccess() || oConvertUtils.isEmpty(userIdResp.getResult())) {
|
||||
result.put("结论", "手机号未在钉钉通讯录中找到对应用户,请确认该手机号已在企业钉钉中注册");
|
||||
return Result.OK(result);
|
||||
}
|
||||
String dtUserId = userIdResp.getResult();
|
||||
|
||||
// ④ 用真实模板 processCode 发起审批实例
|
||||
try {
|
||||
String processCode = "PROC-71957EB4-6F64-4AD7-AA1C-3CD7E797687B"; // MES测试审批流
|
||||
String url = "https://api.dingtalk.com/v1.0/workflow/processInstances";
|
||||
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
|
||||
|
||||
// 表单字段(对应钉钉模板中的控件标题)
|
||||
JSONArray formValues = new JSONArray();
|
||||
for (String[] kv : new String[][]{
|
||||
{"PS编码", "TEST-" + System.currentTimeMillis() % 10000},
|
||||
{"类型", "MES测试"},
|
||||
{"发行日期", new SimpleDateFormat("yyyy-MM-dd").format(new Date())},
|
||||
{"发送部门", "技术部"},
|
||||
{"标题", "MES钉钉审批测试 " + now},
|
||||
}) {
|
||||
JSONObject f = new JSONObject();
|
||||
f.put("name", kv[0]);
|
||||
f.put("value", kv[1]);
|
||||
formValues.add(f);
|
||||
}
|
||||
|
||||
// 审批人:用当前登录人自己做测试审批人(and会签,只有1人)
|
||||
JSONArray approvers = new JSONArray();
|
||||
JSONObject approverNode = new JSONObject();
|
||||
approverNode.put("actionType", "AND");
|
||||
JSONArray approverIds = new JSONArray();
|
||||
approverIds.add(dtUserId);
|
||||
approverNode.put("userIds", approverIds);
|
||||
approvers.add(approverNode);
|
||||
|
||||
JSONObject reqBody = new JSONObject();
|
||||
reqBody.put("processCode", processCode);
|
||||
reqBody.put("originatorUserId", dtUserId);
|
||||
reqBody.put("deptId", -1);
|
||||
reqBody.put("approvers", approvers);
|
||||
reqBody.put("formComponentValues", formValues);
|
||||
|
||||
result.put("step4_请求体", reqBody);
|
||||
|
||||
HttpClient httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
HttpRequest httpReq = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("x-acs-dingtalk-access-token", accessToken)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(reqBody.toJSONString()))
|
||||
.timeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
String respBody = httpClient.send(httpReq, HttpResponse.BodyHandlers.ofString()).body();
|
||||
|
||||
JSONObject ddResp = JSONObject.parseObject(respBody);
|
||||
result.put("step4_钉钉响应", ddResp);
|
||||
if (ddResp.containsKey("instanceId") || ddResp.containsKey("processInstanceId")) {
|
||||
String iid = ddResp.containsKey("instanceId")
|
||||
? ddResp.getString("instanceId")
|
||||
: ddResp.getString("processInstanceId");
|
||||
result.put("结论", "✅ 审批实例创建成功!instanceId=" + iid + ",请到钉钉「待我审批」查看");
|
||||
} else {
|
||||
result.put("结论", "❌ 创建失败,code=" + ddResp.getString("code") + " msg=" + ddResp.getString("message"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("钉钉审批实例创建测试异常", e);
|
||||
result.put("step4_钉钉响应", "HTTP请求异常: " + e.getMessage());
|
||||
result.put("结论", "❌ 请求钉钉接口失败");
|
||||
}
|
||||
|
||||
return Result.OK(result);
|
||||
}
|
||||
//update-end---author:GHT ---date:2026-06-03 for:【钉钉PROC-GENERIC可行性验证】测试接口-----
|
||||
|
||||
//update-begin---author:jiangxh ---date:20260520 for:【密炼PS编制】仅编制状态允许删除-----------
|
||||
private String assertCompileStatusForDelete(MesXslMixerPsCompile entity) {
|
||||
if (entity == null) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 钉钉审批表单控件
|
||||
* 对应官方 SDK:FormComponent
|
||||
*
|
||||
* 支持的 componentType:
|
||||
* TextField 单行文本
|
||||
* TextareaField 多行文本
|
||||
* NumberField 数字输入
|
||||
* DDSelectField 单选
|
||||
* DDMultiSelectField 多选
|
||||
* DDDateField 日期
|
||||
* DDDateRangeField 时间区间
|
||||
* DDPhotoField 图片
|
||||
* DDAttachment 附件
|
||||
* DepartmentField 部门
|
||||
* InnerContactField 联系人
|
||||
* TextNote 说明文字
|
||||
* MoneyField 金额
|
||||
* PhoneField 电话
|
||||
* AddressField 省市区
|
||||
* StarRatingField 评分
|
||||
* TableField 明细(含子控件 children)
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class DingFormComponent {
|
||||
|
||||
/** 控件类型 */
|
||||
private String componentType;
|
||||
|
||||
/** 控件属性 */
|
||||
private DingFormComponentProps props;
|
||||
|
||||
/** 子控件列表,仅 TableField 使用 */
|
||||
private List<DingFormComponent> children;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 钉钉审批表单控件属性
|
||||
* 对应官方 SDK:FormComponentProps
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class DingFormComponentProps {
|
||||
|
||||
/** 控件唯一标识,建议格式:{componentType}-{bizKey} */
|
||||
private String componentId;
|
||||
|
||||
/** 控件标题(钉钉表单中显示的字段名) */
|
||||
private String label;
|
||||
|
||||
/** 占位提示文字 */
|
||||
private String placeholder;
|
||||
|
||||
/** 是否必填 */
|
||||
private Boolean required;
|
||||
|
||||
/** 日期格式,DDDateField/DDDateRangeField 使用,如 "yyyy-MM-dd" */
|
||||
private String format;
|
||||
|
||||
/** 单位,NumberField/DDDateField/DDDateRangeField 使用 */
|
||||
private String unit;
|
||||
|
||||
/** 说明内容,TextNote 使用 */
|
||||
private String content;
|
||||
|
||||
/** 说明文字超链接,TextNote 使用 */
|
||||
private String link;
|
||||
|
||||
/** 是否参与打印("0"=否),TextNote 使用 */
|
||||
private String print;
|
||||
|
||||
/** 单选/多选选项列表,DDSelectField/DDMultiSelectField 使用 */
|
||||
private List<SelectOption> options;
|
||||
|
||||
/** 业务别名,DDSelectField 使用(如 "staff_type") */
|
||||
private String bizAlias;
|
||||
|
||||
/** 金额大写显示("0"=否 "1"=是),MoneyField 使用 */
|
||||
private String upper;
|
||||
|
||||
/** 电话模式("phone"),PhoneField 使用 */
|
||||
private String mode;
|
||||
|
||||
/** 联系人选择模式("1"=多选),InnerContactField 使用 */
|
||||
private String choice;
|
||||
|
||||
/** 部门是否多选,DepartmentField 使用 */
|
||||
private Boolean multiple;
|
||||
|
||||
/** 省市区精度("city"=市级 "district"=区级),AddressField 使用 */
|
||||
private String addressModel;
|
||||
|
||||
/** 评分最大值,StarRatingField 使用 */
|
||||
private Integer limit;
|
||||
|
||||
/** 明细视图模式("table"/"list"),TableField 使用 */
|
||||
private String tableViewMode;
|
||||
|
||||
/** 明细打印方向(true=纵向 false=横向),TableField 使用 */
|
||||
private Boolean verticalPrint;
|
||||
|
||||
/** 明细汇总字段,TableField 使用 */
|
||||
private List<StatField> statField;
|
||||
|
||||
/** 可关联的审批单列表,RelateField 使用 */
|
||||
private List<AvailableTemplate> availableTemplates;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class SelectOption {
|
||||
private String key;
|
||||
private String value;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class StatField {
|
||||
private String componentId;
|
||||
private String label;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class AvailableTemplate {
|
||||
private String name;
|
||||
private String processCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建钉钉审批模板请求体
|
||||
* POST https://api.dingtalk.com/v1.0/workflow/forms
|
||||
* 对应官方 SDK:FormCreateRequest
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class DingFormCreateRequest {
|
||||
|
||||
/** 模板名称 */
|
||||
private String name;
|
||||
|
||||
/** 模板描述 */
|
||||
private String description;
|
||||
|
||||
/** 表单控件列表 */
|
||||
private List<DingFormComponent> formComponents;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 更新钉钉审批模板请求体
|
||||
* PUT https://api.dingtalk.com/v1.0/workflow/forms
|
||||
* 对应官方 SDK:FormUpdateRequest
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class DingFormUpdateRequest {
|
||||
|
||||
/** 要更新的模板 processCode */
|
||||
private String processCode;
|
||||
|
||||
/** 模板名称 */
|
||||
private String name;
|
||||
|
||||
/** 模板描述 */
|
||||
private String description;
|
||||
|
||||
/** 表单控件列表 */
|
||||
private List<DingFormComponent> formComponents;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 钉钉审批模板配置
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-06-03 for:【MESToDing审批配置】钉钉审批模板配置
|
||||
*/
|
||||
@Data
|
||||
@TableName("mes_xsl_ding_process_tpl")
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Schema(description = "钉钉审批模板配置")
|
||||
public class MesXslDingProcessTpl extends JeecgEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Excel(name = "模板名称", width = 20)
|
||||
@Schema(description = "模板名称")
|
||||
private String tplName;
|
||||
|
||||
@Excel(name = "钉钉processCode", width = 35)
|
||||
@Schema(description = "钉钉processCode")
|
||||
private String processCode;
|
||||
|
||||
@Excel(name = "业务类型标识", width = 20)
|
||||
@Schema(description = "业务类型标识(供审批流关联使用)")
|
||||
private String bizType;
|
||||
|
||||
@Excel(name = "表单字段映射", width = 30)
|
||||
@Schema(description = "表单字段映射JSON(钉钉字段名→MES字段名)")
|
||||
private String formFields;
|
||||
|
||||
@Excel(name = "状态", width = 10, dicCode = "mes_ding_tpl_status")
|
||||
@Dict(dicCode = "mes_ding_tpl_status")
|
||||
@Schema(description = "状态:0停用 1启用")
|
||||
private String status;
|
||||
|
||||
@Excel(name = "排序", width = 10)
|
||||
@Schema(description = "排序")
|
||||
private Integer sortNo;
|
||||
|
||||
@Excel(name = "备注", width = 30)
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
//update-begin---author:GHT ---date:2026-06-04 for:【MESToDing审批配置】绑定MES审批流(审批人来源)-----------
|
||||
@Schema(description = "绑定的MES审批流ID(用于发起审批时解析审批人)")
|
||||
private String flowId;
|
||||
//update-end---author:GHT ---date:2026-06-04 for:【MESToDing审批配置】绑定MES审批流(审批人来源)-----------
|
||||
|
||||
@Schema(description = "逻辑删除:0正常 1已删除")
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "所属部门编码")
|
||||
private String sysOrgCode;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
|
||||
|
||||
/**
|
||||
* 钉钉审批模板配置 Mapper
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-06-03
|
||||
*/
|
||||
public interface MesXslDingProcessTplMapper extends BaseMapper<MesXslDingProcessTpl> {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.xslmes.dingtalk.mapper.MesXslDingProcessTplMapper">
|
||||
</mapper>
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
|
||||
|
||||
/**
|
||||
* 钉钉审批模板配置 Service 接口
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-06-03
|
||||
*/
|
||||
public interface IMesXslDingProcessTplService extends IService<MesXslDingProcessTpl> {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.jeecg.modules.xslmes.dingtalk.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
|
||||
import org.jeecg.modules.xslmes.dingtalk.mapper.MesXslDingProcessTplMapper;
|
||||
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingProcessTplService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 钉钉审批模板配置 ServiceImpl
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-06-03
|
||||
*/
|
||||
@Service
|
||||
public class MesXslDingProcessTplServiceImpl extends ServiceImpl<MesXslDingProcessTplMapper, MesXslDingProcessTpl>
|
||||
implements IMesXslDingProcessTplService {
|
||||
}
|
||||
Reference in New Issue
Block a user