Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19d6cebeb0 | ||
|
|
72b2413c00 | ||
|
|
c3ea84bfad | ||
|
|
61b8819057 | ||
|
|
01921f7fcf | ||
|
|
465a3257b5 |
@@ -133,6 +133,38 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
<version>4.0.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.websocket</groupId>
|
||||||
|
<artifactId>javax.websocket-api</artifactId>
|
||||||
|
<version>1.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-core</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-websocket</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-el</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-jasper</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.nutz</groupId>
|
<groupId>org.nutz</groupId>
|
||||||
<artifactId>nutzboot-parent</artifactId>
|
<artifactId>nutzboot-parent</artifactId>
|
||||||
@@ -151,6 +183,7 @@
|
|||||||
<easypoi.version>4.5.0</easypoi.version>
|
<easypoi.version>4.5.0</easypoi.version>
|
||||||
<jsoup.veision>1.15.3</jsoup.veision>
|
<jsoup.veision>1.15.3</jsoup.veision>
|
||||||
<mysql-connector-java.version>8.0.28</mysql-connector-java.version>
|
<mysql-connector-java.version>8.0.28</mysql-connector-java.version>
|
||||||
|
<tomcat.version>9.0.121</tomcat.version>
|
||||||
<sa-token.version>1.25.0</sa-token.version>
|
<sa-token.version>1.25.0</sa-token.version>
|
||||||
<easy-captcha.version>1.6.2</easy-captcha.version>
|
<easy-captcha.version>1.6.2</easy-captcha.version>
|
||||||
<emoji-java.veision>4.0.0</emoji-java.veision>
|
<emoji-java.veision>4.0.0</emoji-java.veision>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<nutzboot.version>2.6.0-SNAPSHOT</nutzboot.version>
|
<nutzboot.version>2.6.0-SNAPSHOT</nutzboot.version>
|
||||||
<nutz.version>1.r.70-SNAPSHOT</nutz.version>
|
<nutz.version>1.r.70-SNAPSHOT</nutz.version>
|
||||||
|
<tomcat.version>9.0.121</tomcat.version>
|
||||||
<mysql-connector-java.version>8.0.28</mysql-connector-java.version>
|
<mysql-connector-java.version>8.0.28</mysql-connector-java.version>
|
||||||
<jaxb-api.version>2.3.1</jaxb-api.version>
|
<jaxb-api.version>2.3.1</jaxb-api.version>
|
||||||
<slf4j.version>2.0.8</slf4j.version>
|
<slf4j.version>2.0.8</slf4j.version>
|
||||||
@@ -144,8 +145,22 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.nutz</groupId>
|
<groupId>org.nutz</groupId>
|
||||||
<artifactId>nutzboot-starter-jetty</artifactId>
|
<artifactId>nutzboot-starter-tomcat</artifactId>
|
||||||
<version>${nutzboot.version}</version>
|
<version>${nutzboot.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.gandon.tomcat</groupId>
|
||||||
|
<artifactId>juli-to-slf4j</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-websocket</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.nutz</groupId>
|
||||||
|
<artifactId>nutz-plugins-websocket</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.nutz</groupId>
|
<groupId>org.nutz</groupId>
|
||||||
@@ -424,6 +439,40 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- Servlet API 由 Tomcat 提供,防止旧 API 混入可执行 JAR。 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
<version>4.0.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.websocket</groupId>
|
||||||
|
<artifactId>javax.websocket-api</artifactId>
|
||||||
|
<version>1.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- 显式覆盖导入的 NutzBoot BOM,避免回退到其默认 Tomcat 8.5。 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-core</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-websocket</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-el</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-jasper</artifactId>
|
||||||
|
<version>${tomcat.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.nutz</groupId>
|
<groupId>org.nutz</groupId>
|
||||||
<artifactId>nutzboot-parent</artifactId>
|
<artifactId>nutzboot-parent</artifactId>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.budwk.app.web.commons.auth.satoken.SaTokenDaoRedisImpl;
|
|||||||
import com.budwk.app.web.commons.auth.satoken.StpInterfaceImpl;
|
import com.budwk.app.web.commons.auth.satoken.StpInterfaceImpl;
|
||||||
import com.budwk.app.web.commons.base.Globals;
|
import com.budwk.app.web.commons.base.Globals;
|
||||||
import com.budwk.app.web.commons.ext.pubsub.WebPubSub;
|
import com.budwk.app.web.commons.ext.pubsub.WebPubSub;
|
||||||
|
import com.budwk.app.web.commons.ext.handler.WkTomcatConfiguration;
|
||||||
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
|
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.beetl.core.GroupTemplate;
|
import org.beetl.core.GroupTemplate;
|
||||||
@@ -65,6 +66,7 @@ public class MainLauncher {
|
|||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
NbApp nb = new NbApp().setArgs(args).setPrintProcDoc(true);
|
NbApp nb = new NbApp().setArgs(args).setPrintProcDoc(true);
|
||||||
nb.getAppContext().setMainPackage("com.budwk");
|
nb.getAppContext().setMainPackage("com.budwk");
|
||||||
|
nb.addListener(new WkTomcatConfiguration());
|
||||||
nb.run();
|
nb.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.budwk.app.base.portal;
|
||||||
|
|
||||||
|
import com.budwk.app.base.portal.model.PortalTodoResponse;
|
||||||
|
|
||||||
|
/** 成功发送记录的持久化服务,不负责再次请求门户。 */
|
||||||
|
public interface PortalTodoLogService {
|
||||||
|
/**
|
||||||
|
* 保存一次已确认成功的发送快照;使用当前业务事务,外层回滚时记录也可能回滚。
|
||||||
|
* @param requestId 本次调用的32位关联编号,对应日志主键
|
||||||
|
* @param requestUrl 实际发送接口地址,不传认证头
|
||||||
|
* @param requestBody 实际发送的完整JSON字符串
|
||||||
|
* @param responseBody 门户原始响应正文
|
||||||
|
* @param response 解析后的成功结果,含HTTP状态、业务码、提示、data及traceId
|
||||||
|
* @param requestTime 请求开始的毫秒时间戳
|
||||||
|
* @param elapsedMillis 接口耗时,不含数据库保存时间
|
||||||
|
* @return 无返回值;保存异常由调用端单独处理,不改变门户调用结果
|
||||||
|
*/
|
||||||
|
void recordSuccess(String requestId, String requestUrl, String requestBody, String responseBody,
|
||||||
|
PortalTodoResponse response, long requestTime, long elapsedMillis);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.budwk.app.base.portal;
|
||||||
|
|
||||||
|
import com.budwk.app.base.portal.model.PortalTodoRequest;
|
||||||
|
import com.budwk.app.base.portal.model.PortalTodoSendRequest;
|
||||||
|
import com.budwk.app.base.portal.model.PortalTodoResponse;
|
||||||
|
|
||||||
|
/** 信息门户待办接口,可在业务 service 中注入后独立调用。 */
|
||||||
|
public interface PortalTodoService {
|
||||||
|
/**
|
||||||
|
* @param request 业务名称、唯一 ID、单据编码、接收人登录账号及标题、正文、完整 PC 链接
|
||||||
|
* @return 调用结果;success 表示门户成功,skipped 表示开关拦截,msg/traceId 用于排查
|
||||||
|
*/
|
||||||
|
PortalTodoResponse sendTodo(PortalTodoSendRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param request 与发送时完全一致的业务名称、唯一 ID、单据编码和接收人账号
|
||||||
|
* @return 调用结果;审核完成使用此方法,门户保留已办记录
|
||||||
|
*/
|
||||||
|
PortalTodoResponse doneTodo(PortalTodoRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param request 与发送时完全一致的业务名称、唯一 ID、单据编码和接收人账号
|
||||||
|
* @return 调用结果;撤销或删除业务时使用,失败不会抛出网络异常阻断业务
|
||||||
|
*/
|
||||||
|
PortalTodoResponse removeTodo(PortalTodoRequest request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# 门户待办调用
|
||||||
|
|
||||||
|
在业务 service 中注入 `PortalTodoService`。三个方法均返回 `PortalTodoResponse`,不会因门户 HTTP 失败主动抛出异常。
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Inject
|
||||||
|
private PortalTodoService portalTodoService;
|
||||||
|
|
||||||
|
// 同一待办发送、完成、删除时保留下面四项定位数据。
|
||||||
|
PortalTodoSendRequest request = new PortalTodoSendRequest();
|
||||||
|
request.setEntityName("职工福利审核");
|
||||||
|
request.setEntityId(taskId);
|
||||||
|
request.setEntityNumber(businessNumber);
|
||||||
|
request.setTargets(List.of(receiverLoginName));
|
||||||
|
request.setSubject("职工福利审核待办");
|
||||||
|
request.setContent("您有一条职工福利申请需要审核。");
|
||||||
|
request.setLink(auditPageUrl);
|
||||||
|
request.setMobileLink(h5AuditPageUrl);
|
||||||
|
request.setCreator(senderLoginName);
|
||||||
|
request.setCreatorName(senderName);
|
||||||
|
PortalTodoResponse result = portalTodoService.sendTodo(request);
|
||||||
|
```
|
||||||
|
|
||||||
|
审核完成时调用 `portalTodoService.doneTodo(request)`;撤销或删除时调用 `portalTodoService.removeTodo(request)`。这两个操作也可单独构造 `PortalTodoRequest`,只设置四项定位数据,不需要标题和内容。
|
||||||
|
|
||||||
|
- `success=true`:门户接口返回成功,实际展示仍以门户为准。
|
||||||
|
- `skipped=true`:开发模式或开关关闭,没有发送请求。
|
||||||
|
- 两者均为 `false`:校验、网络或门户业务失败,使用 `msg`、`code`、`traceId` 排查。
|
||||||
|
- `data` 按门户原值返回,不当作 `notifyId` 使用。
|
||||||
|
|
||||||
|
## 启用配置
|
||||||
|
|
||||||
|
1. 部署配置设置 `portal.todo.base-url`、`portal.todo.username`、`portal.todo.password`。密码按原始字符填写,不编码、不转义增删。
|
||||||
|
2. 按当前项目约定,`Globals.sso` 必须为 `true`(来自 `cas.enable`)。
|
||||||
|
3. 在现有系统参数管理中配置 `AppPortalTodo=true` 并刷新系统配置;未配置默认关闭,与 `AppSms` 无关。
|
||||||
|
4. 设置正确的项目 `AppDomain`,供 wf 构建审核链接。
|
||||||
|
|
||||||
|
wf 会自动同步通用任务变化,业务代码无需再次发送同一 wf 待办。HTTP 超时不自动重试;本地事务无法撤销已成功的外部调用,需要根据日志人工补偿。
|
||||||
|
|
||||||
|
PC链接使用任务的 `formKey`,手机链接使用 `h5FormKey`,补齐 `AppDomain` 并携带编码后的 `taskId`、`bizId`、`taskKey`。未配置手机表单时记录提示并回退到 `/platform/h5` 手机首页。独立调用的 `mobileLink` 应传完整HTTP(S)地址,未传也回退到项目手机首页。修改只影响新发送的待办,不更新门户已有链接。
|
||||||
|
|
||||||
|
## 发送记录与控制台排查
|
||||||
|
|
||||||
|
成功的 `send` 请求写入 `portal_todo_log`,每次请求一条,保存实际发送的完整JSON、原始响应正文、业务标识、标题正文、发送人、接收人账号数组、链接、请求时间、耗时及返回状态。失败、跳过、已办和删除不写成功发送记录。
|
||||||
|
|
||||||
|
控制台搜索 `——————————————学校OA待办发送`,按“调用编号”关联“请求开始”和“请求结束”。结束日志包含成功/失败/跳过、HTTP状态、业务码、提示、traceId、耗时和响应正文;无响应的网络异常仍输出结束记录。日志不打印Basic凭证或认证头。
|
||||||
|
|
||||||
|
成功发送但保存记录失败时另输出“发送成功但记录入库失败”,门户返回结果保持成功,不自动重发。保存沿用业务事务,外层回滚时记录也可能回滚;控制台“已写入当前事务”不表示外层事务已提交。
|
||||||
|
|
||||||
|
表结构通过现有实体扫描机制管理:`MainLauncher.init_sys` 仅在DEBUG日志级别时自动建表。生产部署需确认该表已创建,本功能不生成或执行额外SQL,不提供后台查询页面。
|
||||||
|
|
||||||
|
## 验证说明
|
||||||
|
|
||||||
|
`PortalTodoServiceTest` 使用本地模拟 HTTP 服务;`FlowPortalTodoServiceTest` 使用内存 Dao 替身。两者不连接真实门户或业务数据库。
|
||||||
|
|
||||||
|
串并行会签、转办、撤回和流程删除仍需在测试部署中走完整业务流程验收。提案附议等脱离标准任务状态的业务特例未单独适配。
|
||||||
|
|
||||||
|
现有 `activate` 方法只恢复 `INTERRUPT` 状态任务,并不恢复 `PENDING`;本次保持该业务规则,仅为实际恢复的任务重发待办。
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.budwk.app.base.portal.impl;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.budwk.app.base.portal.PortalTodoLogService;
|
||||||
|
import com.budwk.app.base.portal.model.PortalTodoLog;
|
||||||
|
import com.budwk.app.base.portal.model.PortalTodoResponse;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
|
||||||
|
/** 记录实际请求报文的业务快照,不读取可变化的人员或流程数据。 */
|
||||||
|
@IocBean
|
||||||
|
public class PortalTodoLogServiceImpl implements PortalTodoLogService {
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void recordSuccess(String requestId, String requestUrl, String requestBody, String responseBody,
|
||||||
|
PortalTodoResponse response, long requestTime, long elapsedMillis) {
|
||||||
|
// 防止跳过或HTTP失败被误记为成功发送;多接收人作为一次请求保留原数组。
|
||||||
|
if (response == null || !response.isSuccess() || response.isSkipped()
|
||||||
|
|| response.getHttpStatus() == null || response.getHttpStatus() < 200
|
||||||
|
|| response.getHttpStatus() >= 300) return;
|
||||||
|
JSONObject payload = JSONUtil.parseObj(requestBody);
|
||||||
|
PortalTodoLog record = new PortalTodoLog();
|
||||||
|
record.setId(requestId);
|
||||||
|
record.setAppName(payload.getStr("appName"));
|
||||||
|
record.setModuleName(payload.getStr("moduleName"));
|
||||||
|
record.setEntityName(payload.getStr("entityName"));
|
||||||
|
record.setEntityId(payload.getStr("entityId"));
|
||||||
|
record.setEntityNumber(payload.getStr("entityNumber"));
|
||||||
|
record.setSubject(payload.getStr("subject"));
|
||||||
|
record.setContent(payload.getStr("content"));
|
||||||
|
record.setCreator(payload.getStr("creator"));
|
||||||
|
record.setCreatorName(payload.getStr("creatorName"));
|
||||||
|
record.setTargets(JSONUtil.toJsonStr(payload.getJSONArray("targets")));
|
||||||
|
record.setLink(payload.getStr("link"));
|
||||||
|
record.setMobileLink(payload.getStr("mobileLink"));
|
||||||
|
record.setRequestUrl(requestUrl);
|
||||||
|
record.setRequestBody(requestBody);
|
||||||
|
record.setResponseBody(responseBody);
|
||||||
|
record.setRequestTime(requestTime);
|
||||||
|
record.setElapsedMillis(elapsedMillis);
|
||||||
|
record.setHttpStatus(response.getHttpStatus());
|
||||||
|
record.setSuccess(response.isSuccess());
|
||||||
|
record.setCode(response.getCode());
|
||||||
|
record.setMsg(response.getMsg());
|
||||||
|
record.setData(response.getData());
|
||||||
|
record.setTraceId(response.getTraceId());
|
||||||
|
dao.insert(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
package com.budwk.app.base.portal.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.budwk.app.base.portal.PortalTodoService;
|
||||||
|
import com.budwk.app.base.portal.PortalTodoLogService;
|
||||||
|
import com.budwk.app.base.portal.model.*;
|
||||||
|
import com.budwk.app.web.commons.base.Globals;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
|
import org.nutz.ioc.impl.PropertiesProxy;
|
||||||
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/** Basic 认证的门户 REST 客户端,所有操作共用环境拦截及结果处理。 */
|
||||||
|
@IocBean
|
||||||
|
@Slf4j
|
||||||
|
public class PortalTodoServiceImpl implements PortalTodoService {
|
||||||
|
private static final String LOG_PREFIX = "——————————————学校OA待办发送";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private PropertiesProxy conf;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private PortalTodoLogService portalTodoLogService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public PortalTodoResponse sendTodo(PortalTodoSendRequest request) {
|
||||||
|
return post("send", request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public PortalTodoResponse doneTodo(PortalTodoRequest request) {
|
||||||
|
return post("done", request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public PortalTodoResponse removeTodo(PortalTodoRequest request) {
|
||||||
|
return post("removeTodo", request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 先拦截环境与开关,再校验参数并请求;禁止自动重试不确定是否已送达的发送。 */
|
||||||
|
private PortalTodoResponse post(String operation, PortalTodoRequest request) {
|
||||||
|
PortalTodoResponse result = new PortalTodoResponse();
|
||||||
|
String requestId = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
long requestTime = System.currentTimeMillis();
|
||||||
|
long startedNanos = System.nanoTime();
|
||||||
|
String requestUrl = null;
|
||||||
|
String requestBody = null;
|
||||||
|
String responseBody = null;
|
||||||
|
String exceptionType = null;
|
||||||
|
HttpURLConnection connection = null;
|
||||||
|
try {
|
||||||
|
// 环境与功能开关均开启才调用门户,拦截早于参数及认证配置校验。
|
||||||
|
if (!Globals.sso || !Globals.MyConfig.getBoolean("AppPortalTodo", false)) {
|
||||||
|
result.setSkipped(true);
|
||||||
|
result.setCode("SKIPPED");
|
||||||
|
result.setMsg(!Globals.sso ? "开发模式不调用门户待办" : "门户待办开关未开启");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
JSONObject payload = payload(operation, request);
|
||||||
|
requestBody = payload.toString();
|
||||||
|
String baseUrl = required(conf.get("portal.todo.base-url"), "门户地址未配置");
|
||||||
|
String username = required(conf.get("portal.todo.username"), "门户认证用户名未配置");
|
||||||
|
String password = required(conf.get("portal.todo.password"), "门户认证密码未配置");
|
||||||
|
requestUrl = StrUtil.removeSuffix(baseUrl, "/")
|
||||||
|
+ "/openapi/sys-notifybus/sysNotifyComponent/" + operation;
|
||||||
|
// 打印实际发送的同一份JSON,不输出Basic用户名、密码或Authorization头。
|
||||||
|
log.info("{}【请求开始】调用编号={},操作={},地址={},发送内容={}",
|
||||||
|
LOG_PREFIX, requestId, operation, requestUrl, requestBody);
|
||||||
|
connection = (HttpURLConnection) URI.create(requestUrl).toURL().openConnection();
|
||||||
|
connection.setInstanceFollowRedirects(false);
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setUseCaches(false);
|
||||||
|
connection.setConnectTimeout(conf.getInt("portal.todo.connect-timeout", 5000));
|
||||||
|
connection.setReadTimeout(conf.getInt("portal.todo.read-timeout", 10000));
|
||||||
|
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
|
||||||
|
connection.setRequestProperty("Authorization", "Basic " + Base64.getEncoder()
|
||||||
|
.encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)));
|
||||||
|
try (OutputStream out = connection.getOutputStream()) {
|
||||||
|
out.write(requestBody.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
int status = connection.getResponseCode();
|
||||||
|
result.setHttpStatus(status);
|
||||||
|
try (InputStream in = status >= 400 ? connection.getErrorStream() : connection.getInputStream()) {
|
||||||
|
// 先保留原始响应,非JSON、解析失败等情况也能在控制台排查。
|
||||||
|
responseBody = in == null ? "" : new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
JSONObject json = JSONUtil.parseObj(responseBody);
|
||||||
|
result.setCode(json.getStr("code"));
|
||||||
|
result.setMsg(json.getStr("msg"));
|
||||||
|
result.setData(json.getStr("data"));
|
||||||
|
result.setTraceId(json.getStr("traceId"));
|
||||||
|
result.setSuccess(status >= 200 && status < 300 && Boolean.TRUE.equals(json.getBool("success")));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.setSuccess(false);
|
||||||
|
result.setCode("CLIENT_ERROR");
|
||||||
|
exceptionType = e.getClass().getSimpleName();
|
||||||
|
// 异常仅记录类型,不打印可能携带认证信息的异常原文。
|
||||||
|
result.setMsg(e instanceof IllegalArgumentException ? "门户参数、配置或响应格式无效" : "门户请求失败或超时");
|
||||||
|
} finally {
|
||||||
|
long elapsedMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startedNanos);
|
||||||
|
// 将多行响应编码为单行JSON字符串,确保每条日志均可用统一前缀定位。
|
||||||
|
String outcome = result.isSkipped() ? "已跳过" : result.isSuccess() ? "成功" : "失败";
|
||||||
|
String format = "{}【请求结束-{}】调用编号={},操作={},业务ID={},HTTP状态={},耗时={}ms,code={},msg={},traceId={},异常类型={},返回内容={}";
|
||||||
|
Object[] values = {LOG_PREFIX, outcome, requestId, operation,
|
||||||
|
request == null ? null : request.getEntityId(), result.getHttpStatus(), elapsedMillis,
|
||||||
|
result.getCode(), JSONUtil.toJsonStr(result.getMsg()), result.getTraceId(), exceptionType,
|
||||||
|
JSONUtil.toJsonStr(responseBody)};
|
||||||
|
if (result.isSuccess() || result.isSkipped()) log.info(format, values);
|
||||||
|
else log.error(format, values);
|
||||||
|
if (connection != null) connection.disconnect();
|
||||||
|
|
||||||
|
// 仅send成功落库。日志保存异常不能改写门户成功结果,也不能触发再次发送。
|
||||||
|
if ("send".equals(operation) && result.isSuccess() && !result.isSkipped()) {
|
||||||
|
try {
|
||||||
|
portalTodoLogService.recordSuccess(requestId, requestUrl, requestBody, responseBody,
|
||||||
|
result, requestTime, elapsedMillis);
|
||||||
|
log.info("{}【记录已写入当前事务】调用编号={},traceId={}", LOG_PREFIX, requestId, result.getTraceId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("{}【发送成功但记录入库失败】调用编号={},业务ID={},traceId={},异常类型={}",
|
||||||
|
LOG_PREFIX, requestId, request.getEntityId(), result.getTraceId(), e.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 只发送接口使用的字段;删除和已办不得混入标题、正文等发送参数。 */
|
||||||
|
private JSONObject payload(String operation, PortalTodoRequest request) {
|
||||||
|
if (request == null) throw new IllegalArgumentException("请求不能为空");
|
||||||
|
if (request.getTargets() == null || request.getTargets().isEmpty()
|
||||||
|
|| request.getTargets().stream().anyMatch(StrUtil::isBlank)) {
|
||||||
|
throw new IllegalArgumentException("接收人账号不能为空");
|
||||||
|
}
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
json.set("appName", "智慧工会平台");
|
||||||
|
json.set("moduleName", "智慧工会平台");
|
||||||
|
json.set("entityName", required(request.getEntityName(), "业务名称不能为空"));
|
||||||
|
json.set("entityId", required(request.getEntityId(), "业务ID不能为空"));
|
||||||
|
json.set("entityNumber", required(request.getEntityNumber(), "单据编码不能为空"));
|
||||||
|
json.set("targets", request.getTargets().stream().distinct().toList());
|
||||||
|
json.set("notifyType", "todo");
|
||||||
|
json.set("orgProperty", "fdLoginName");
|
||||||
|
json.set("todoType", 1);
|
||||||
|
json.set("async", false);
|
||||||
|
if ("send".equals(operation)) {
|
||||||
|
PortalTodoSendRequest send = (PortalTodoSendRequest) request;
|
||||||
|
json.set("subject", required(send.getSubject(), "标题不能为空"));
|
||||||
|
json.set("content", required(send.getContent(), "正文不能为空"));
|
||||||
|
String link = required(send.getLink(), "PC链接不能为空");
|
||||||
|
URI uri = URI.create(link);
|
||||||
|
if (!("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme()))
|
||||||
|
|| uri.getHost() == null) throw new IllegalArgumentException("PC链接必须为完整HTTP地址");
|
||||||
|
json.set("link", link);
|
||||||
|
// 独立调用可传完整H5地址;未提供时使用手机首页,避免门户打开空白页。
|
||||||
|
String mobileLink = send.getMobileLink();
|
||||||
|
if (StrUtil.isBlank(mobileLink)) {
|
||||||
|
mobileLink = URI.create(StrUtil.appendIfMissing(Globals.AppDomain, "/"))
|
||||||
|
.resolve("/platform/h5").toString();
|
||||||
|
}
|
||||||
|
URI mobileUri = URI.create(mobileLink);
|
||||||
|
if (!("http".equalsIgnoreCase(mobileUri.getScheme()) || "https".equalsIgnoreCase(mobileUri.getScheme()))
|
||||||
|
|| mobileUri.getHost() == null) throw new IllegalArgumentException("手机链接必须为完整HTTP地址");
|
||||||
|
json.set("mobileLink", mobileLink);
|
||||||
|
if (StrUtil.isNotBlank(send.getCreator())) json.set("creator", send.getCreator());
|
||||||
|
if (StrUtil.isNotBlank(send.getCreatorName())) json.set("creatorName", send.getCreatorName());
|
||||||
|
json.set("queryType", 1);
|
||||||
|
json.set("todoLevel", 3);
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String required(String value, String message) {
|
||||||
|
if (StrUtil.isBlank(value)) throw new IllegalArgumentException(message);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.budwk.app.base.portal.model;
|
||||||
|
|
||||||
|
import com.budwk.app.base.model.BaseModel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.nutz.dao.entity.annotation.*;
|
||||||
|
|
||||||
|
/** 每次发送成功的请求保存一条快照,多个接收人仍归属于同一次请求。 */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Table("portal_todo_log")
|
||||||
|
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||||
|
@Comment("学校OA待办成功发送记录")
|
||||||
|
public class PortalTodoLog extends BaseModel {
|
||||||
|
@Name
|
||||||
|
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||||
|
@Comment("请求关联编号,与控制台调用编号一致")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
@Comment("来源系统")
|
||||||
|
private String appName;
|
||||||
|
@Column
|
||||||
|
@Comment("来源模块")
|
||||||
|
private String moduleName;
|
||||||
|
@Column
|
||||||
|
@Comment("来源业务名称")
|
||||||
|
private String entityName;
|
||||||
|
@Column
|
||||||
|
@Comment("业务记录或wf任务ID")
|
||||||
|
private String entityId;
|
||||||
|
@Column
|
||||||
|
@Comment("业务单据编码")
|
||||||
|
private String entityNumber;
|
||||||
|
@Column
|
||||||
|
@ColDefine(type = ColType.TEXT)
|
||||||
|
@Comment("待办标题")
|
||||||
|
private String subject;
|
||||||
|
@Column
|
||||||
|
@ColDefine(customType = "MEDIUMTEXT")
|
||||||
|
@Comment("待办正文")
|
||||||
|
private String content;
|
||||||
|
@Column
|
||||||
|
@Comment("发送人登录账号")
|
||||||
|
private String creator;
|
||||||
|
@Column
|
||||||
|
@Comment("发送人姓名")
|
||||||
|
private String creatorName;
|
||||||
|
@Column
|
||||||
|
@ColDefine(customType = "MEDIUMTEXT")
|
||||||
|
@Comment("接收人登录账号JSON数组,保留工号前导零")
|
||||||
|
private String targets;
|
||||||
|
@Column
|
||||||
|
@ColDefine(type = ColType.TEXT)
|
||||||
|
@Comment("PC跳转地址")
|
||||||
|
private String link;
|
||||||
|
@Column
|
||||||
|
@ColDefine(type = ColType.TEXT)
|
||||||
|
@Comment("移动端跳转地址")
|
||||||
|
private String mobileLink;
|
||||||
|
@Column
|
||||||
|
@ColDefine(type = ColType.TEXT)
|
||||||
|
@Comment("发送接口地址,不含认证信息")
|
||||||
|
private String requestUrl;
|
||||||
|
@Column
|
||||||
|
@ColDefine(customType = "MEDIUMTEXT")
|
||||||
|
@Comment("实际发送的完整JSON报文")
|
||||||
|
private String requestBody;
|
||||||
|
@Column
|
||||||
|
@ColDefine(customType = "MEDIUMTEXT")
|
||||||
|
@Comment("门户完整响应正文")
|
||||||
|
private String responseBody;
|
||||||
|
@Column
|
||||||
|
@Comment("请求开始时间,毫秒时间戳")
|
||||||
|
private Long requestTime;
|
||||||
|
@Column
|
||||||
|
@Comment("接口调用耗时,毫秒,不含日志入库")
|
||||||
|
private Long elapsedMillis;
|
||||||
|
@Column
|
||||||
|
@Comment("HTTP响应状态码")
|
||||||
|
private Integer httpStatus;
|
||||||
|
@Column
|
||||||
|
@Comment("门户是否返回成功,不代表接收人已查看")
|
||||||
|
private Boolean success;
|
||||||
|
@Column
|
||||||
|
@Comment("门户业务状态码")
|
||||||
|
private String code;
|
||||||
|
@Column
|
||||||
|
@ColDefine(type = ColType.TEXT)
|
||||||
|
@Comment("门户返回提示")
|
||||||
|
private String msg;
|
||||||
|
@Column
|
||||||
|
@ColDefine(customType = "MEDIUMTEXT")
|
||||||
|
@Comment("门户原样返回的数据,不推断为notifyId")
|
||||||
|
private String data;
|
||||||
|
@Column
|
||||||
|
@Comment("门户链路追踪编号")
|
||||||
|
private String traceId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.budwk.app.base.portal.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 门户待办定位参数;发送、已办和删除时必须使用同一组业务标识及人员账号。 */
|
||||||
|
@Data
|
||||||
|
public class PortalTodoRequest {
|
||||||
|
/** 来源业务名称,同一待办生命周期内不得更改。 */
|
||||||
|
private String entityName;
|
||||||
|
/** 业务记录或任务的唯一 ID,不能仅使用可重复的节点名称。 */
|
||||||
|
private String entityId;
|
||||||
|
/** 所属业务单据编码。 */
|
||||||
|
private String entityNumber;
|
||||||
|
/** 门户登录账号数组,保留工号前导零,不传本系统人员 ID。 */
|
||||||
|
private List<String> targets;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.budwk.app.base.portal.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/** 门户调用结果;跳过不表示发送成功,网络异常时 httpStatus 为空。 */
|
||||||
|
@Data
|
||||||
|
public class PortalTodoResponse {
|
||||||
|
private boolean success;
|
||||||
|
private boolean skipped;
|
||||||
|
private Integer httpStatus;
|
||||||
|
/** 门户业务错误码,或本地 SKIPPED / CLIENT_ERROR。 */
|
||||||
|
private String code;
|
||||||
|
private String msg;
|
||||||
|
/** 门户原样返回的数据,不推断为 notifyId。 */
|
||||||
|
private String data;
|
||||||
|
/** 提供给门户管理员排查的链路标识。 */
|
||||||
|
private String traceId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.budwk.app.base.portal.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/** 发送待办参数;继承的定位参数需由业务方保留,供后续已办、删除使用。 */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class PortalTodoSendRequest extends PortalTodoRequest {
|
||||||
|
/** 待办标题。 */
|
||||||
|
private String subject;
|
||||||
|
/** 待办正文。 */
|
||||||
|
private String content;
|
||||||
|
/** 发送人门户登录账号,可不填。 */
|
||||||
|
private String creator;
|
||||||
|
/** 发送人姓名,可不填。 */
|
||||||
|
private String creatorName;
|
||||||
|
/** 可访问的 PC 业务页面完整地址,必填。 */
|
||||||
|
private String link;
|
||||||
|
/** 可访问的H5业务页面完整HTTP(S)地址;未传时回退到项目手机首页。 */
|
||||||
|
private String mobileLink;
|
||||||
|
}
|
||||||
@@ -93,6 +93,15 @@ public class FlowTodoCenterController {
|
|||||||
return Result.success(pagination);
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询当前用户的已办任务,仅返回列表展示及详情跳转所需的信息。
|
||||||
|
*
|
||||||
|
* @param pageNumber 查询页码
|
||||||
|
* @param pageSize 每页记录数
|
||||||
|
* @param searchKeyword 流程名称查询关键字
|
||||||
|
* @param category 流程分类
|
||||||
|
* @return 已办任务分页结果,不包含完整流程变量和任务表单数据
|
||||||
|
*/
|
||||||
@At
|
@At
|
||||||
@SaCheckLogin
|
@SaCheckLogin
|
||||||
public Result done(Integer pageNumber, Integer pageSize, String searchKeyword, String category) {
|
public Result done(Integer pageNumber, Integer pageSize, String searchKeyword, String category) {
|
||||||
@@ -106,8 +115,13 @@ public class FlowTodoCenterController {
|
|||||||
t.h5FormKey,
|
t.h5FormKey,
|
||||||
t.createdAt,
|
t.createdAt,
|
||||||
t.finishTime,
|
t.finishTime,
|
||||||
t.variable AS taskVariable,
|
-- 保留任务变量字段类型,但已办列表不返回其中的业务表单数据。
|
||||||
ins.variable,
|
'{}' AS taskVariable,
|
||||||
|
-- 列表仅需流程名称和申请人,避免完整变量中的身份证等信息泄露。
|
||||||
|
JSON_OBJECT(
|
||||||
|
'instanceName', ins.variable -> '$.instanceName',
|
||||||
|
'initiatorName', ins.variable -> '$.initiatorName'
|
||||||
|
) AS variable,
|
||||||
ins.state AS instanceState,
|
ins.state AS instanceState,
|
||||||
ins.id AS instanceId,
|
ins.id AS instanceId,
|
||||||
ins.businessNo,
|
ins.businessNo,
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.budwk.app.flow.listenter;
|
||||||
|
|
||||||
|
import com.budwk.app.flow.engine.event.ProcessEvent;
|
||||||
|
import com.budwk.app.flow.engine.event.ProcessEventListener;
|
||||||
|
import com.budwk.app.flow.entity.ProcessTask;
|
||||||
|
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
|
||||||
|
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||||
|
import com.budwk.app.flow.service.FlowPortalTodoService;
|
||||||
|
import com.budwk.app.web.commons.base.Globals;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
|
||||||
|
/** 门户专用监听器;发送从实际参与人新增入口触发,不重复消费任务开始事件。 */
|
||||||
|
@IocBean
|
||||||
|
@Slf4j
|
||||||
|
public class ProcessPortalTodoEventListener implements ProcessEventListener {
|
||||||
|
private static final String LOG_PREFIX = "——————————————学校OA待办发送";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
@Inject
|
||||||
|
private FlowPortalTodoService flowPortalTodoService;
|
||||||
|
|
||||||
|
/** @param event 任务结束/撤销或实例结束事件;按真实状态区分已办和移除,异常不阻断流程。 */
|
||||||
|
@Override
|
||||||
|
public void onEvent(ProcessEvent event) {
|
||||||
|
log.info("{}【WF事件入口】事件={},来源ID={}", LOG_PREFIX, event.getEventType(), event.getSourceId());
|
||||||
|
// 未启用门户同步时停止消费事件,保留日志便于定位跳过原因。
|
||||||
|
if (!Globals.sso || !Globals.MyConfig.getBoolean("AppPortalTodo", false)) {
|
||||||
|
log.info("{}【WF事件跳过】事件={},来源ID={},原因={},sso={},AppPortalTodo={}",
|
||||||
|
LOG_PREFIX, event.getEventType(), event.getSourceId(),
|
||||||
|
!Globals.sso ? "开发模式" : "门户待办开关未开启", Globals.sso,
|
||||||
|
Globals.MyConfig.getBoolean("AppPortalTodo", false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (event.getEventType() == ProcessEventTypeEnum.PROCESS_TASK_END) {
|
||||||
|
ProcessTask task = dao.fetch(ProcessTask.class, event.getSourceId());
|
||||||
|
if (task == null) {
|
||||||
|
log.warn("{}【WF事件跳过】来源ID={},原因=任务不存在", LOG_PREFIX, event.getSourceId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ProcessTaskStateEnum.FINISHED.getCode().equals(task.getTaskState())) {
|
||||||
|
flowPortalTodoService.doneTask(task.getId());
|
||||||
|
} else if (ProcessTaskStateEnum.ABANDON.getCode().equals(task.getTaskState())) {
|
||||||
|
flowPortalTodoService.removeTask(task.getId(), null);
|
||||||
|
} else {
|
||||||
|
log.info("{}【WF事件跳过】任务ID={},原因=结束事件的任务状态无需同步,任务状态={}",
|
||||||
|
LOG_PREFIX, task.getId(), task.getTaskState());
|
||||||
|
}
|
||||||
|
} else if (event.getEventType() == ProcessEventTypeEnum.PROCESS_TASK_REVOKE) {
|
||||||
|
flowPortalTodoService.removeTask(event.getSourceId(), null);
|
||||||
|
} else if (event.getEventType() == ProcessEventTypeEnum.PROCESS_INSTANCE_END) {
|
||||||
|
flowPortalTodoService.removeInstance(event.getSourceId());
|
||||||
|
} else {
|
||||||
|
// 开始事件不再次发送,发送已由新增参与人入口负责。
|
||||||
|
log.info("{}【WF事件跳过】事件={},来源ID={},原因=该事件无需本监听器同步,发送由新增参与人入口负责",
|
||||||
|
LOG_PREFIX, event.getEventType(), event.getSourceId());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("{}【WF事件同步失败】来源ID={},异常类型={}", LOG_PREFIX, event.getSourceId(), e.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,6 +35,9 @@ public class FlowCommonService {
|
|||||||
@Inject
|
@Inject
|
||||||
private Dao dao;
|
private Dao dao;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FlowPortalTodoService flowPortalTodoService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行任务
|
* 执行任务
|
||||||
* @param args
|
* @param args
|
||||||
@@ -111,9 +114,12 @@ public class FlowCommonService {
|
|||||||
|
|
||||||
// 撤销任务
|
// 撤销任务
|
||||||
List<ProcessTask> taskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskParentId, "=", taskId));
|
List<ProcessTask> taskList = dao.query(ProcessTask.class, Cnd.where(ProcessTask::getTaskParentId, "=", taskId));
|
||||||
|
// 收集全部被撤回的任务,包含并行会签的非直接子任务,避免门户残留待办。
|
||||||
|
java.util.Set<Long> revokedTaskIds = new java.util.LinkedHashSet<>();
|
||||||
for (ProcessTask task : taskList) {
|
for (ProcessTask task : taskList) {
|
||||||
task.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
|
task.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
|
||||||
dao.update(task);
|
dao.update(task);
|
||||||
|
revokedTaskIds.add(task.getId());
|
||||||
}
|
}
|
||||||
// 会签并行任务 撤销后续任务
|
// 会签并行任务 撤销后续任务
|
||||||
if (selfTask.getPerformType().equals(ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode())) {
|
if (selfTask.getPerformType().equals(ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode())) {
|
||||||
@@ -122,6 +128,7 @@ public class FlowCommonService {
|
|||||||
for (ProcessTask doingTask : doingTasks) {
|
for (ProcessTask doingTask : doingTasks) {
|
||||||
doingTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
|
doingTask.setTaskState(ProcessTaskStateEnum.WITHDRAW.getCode());
|
||||||
dao.update(doingTask);
|
dao.update(doingTask);
|
||||||
|
revokedTaskIds.add(doingTask.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +143,12 @@ public class FlowCommonService {
|
|||||||
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_REVOKE).sourceId(task.getId()).build());
|
ProcessPublisher.notify(ProcessEvent.builder().eventType(ProcessEventTypeEnum.PROCESS_TASK_REVOKE).sourceId(task.getId()).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 直接子任务由撤回事件同步;额外并行任务在此补齐,不重复删除同一任务。
|
||||||
|
taskList.forEach(task -> revokedTaskIds.remove(task.getId()));
|
||||||
|
revokedTaskIds.forEach(id -> flowPortalTodoService.removeTask(id, null));
|
||||||
|
// 原任务重新进入办理状态,需要恢复它在门户中的待办。
|
||||||
|
flowPortalTodoService.sendTask(selfTask.getId(), null);
|
||||||
|
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package com.budwk.app.flow.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.budwk.app.base.portal.PortalTodoService;
|
||||||
|
import com.budwk.app.base.portal.model.PortalTodoSendRequest;
|
||||||
|
import com.budwk.app.flow.constant.FlowConst;
|
||||||
|
import com.budwk.app.flow.entity.ProcessInstance;
|
||||||
|
import com.budwk.app.flow.entity.ProcessTask;
|
||||||
|
import com.budwk.app.flow.entity.ProcessTaskActor;
|
||||||
|
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||||
|
import com.budwk.app.sys.views.View_user;
|
||||||
|
import com.budwk.app.web.commons.base.Globals;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.ioc.aop.Aop;
|
||||||
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wf 门户适配,所有标识由任务持久化数据重建,不依赖当前登录请求。
|
||||||
|
*/
|
||||||
|
@IocBean
|
||||||
|
@Slf4j
|
||||||
|
public class FlowPortalTodoService {
|
||||||
|
private static final String LOG_PREFIX = "——————————————学校OA待办发送";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
@Inject
|
||||||
|
private PortalTodoService portalTodoService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param taskId wf 任务 ID
|
||||||
|
* @param actors 本次新增的参与人,null 表示当前任务全部参与人;空列表不发送
|
||||||
|
* @return 无返回值;门户异常只记录,不中断工作流
|
||||||
|
*/
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void sendTask(Long taskId, List<ProcessTaskActor> actors) {
|
||||||
|
sync(taskId, actors, "send");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param taskId 已完成的 wf 任务 ID;参与人账号沿用原任务,异常只记录。
|
||||||
|
*/
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void doneTask(Long taskId) {
|
||||||
|
sync(taskId, null, "done");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param taskId 原 wf 任务 ID,必须在删除任务记录之前调用
|
||||||
|
* @param actors 被移除的参与人快照;null 表示原任务全部参与人
|
||||||
|
* @return 无返回值;不改变 wf 数据,门户失败仅记录
|
||||||
|
*/
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void removeTask(Long taskId, List<ProcessTaskActor> actors) {
|
||||||
|
sync(taskId, actors, "removeTodo");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param instanceId wf 实例 ID;实例结束或删除前清理非已完成任务的门户待办。
|
||||||
|
*/
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
|
public void removeInstance(Long instanceId) {
|
||||||
|
log.info("{}【WF实例清理入口】实例ID={}", LOG_PREFIX, instanceId);
|
||||||
|
if (!enabled()) {
|
||||||
|
log.info("{}【WF跳过】实例ID={},原因={},sso={},AppPortalTodo={}", LOG_PREFIX, instanceId,
|
||||||
|
!Globals.sso ? "开发模式" : "门户待办开关未开启", Globals.sso,
|
||||||
|
Globals.MyConfig.getBoolean("AppPortalTodo", false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<ProcessTask> tasks = dao.query(ProcessTask.class,
|
||||||
|
Cnd.where(ProcessTask::getProcessInstanceId, "=", instanceId)
|
||||||
|
.and(ProcessTask::getTaskState, "!=", ProcessTaskStateEnum.FINISHED.getCode()));
|
||||||
|
if (tasks.isEmpty()) {
|
||||||
|
log.info("{}【WF跳过】实例ID={},原因=没有需要清理的任务", LOG_PREFIX, instanceId);
|
||||||
|
}
|
||||||
|
for (ProcessTask task : tasks) sync(task.getId(), null, "removeTodo");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("{}【WF实例清理失败】实例ID={},异常类型={}", LOG_PREFIX, instanceId, e.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean enabled() {
|
||||||
|
// WF同步与独立门户调用使用相同的环境及功能开关。
|
||||||
|
return Globals.sso && Globals.MyConfig.getBoolean("AppPortalTodo", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 包括数据读取和参数组装在内的异常均隔离,避免影响已授权的 wf 业务操作。
|
||||||
|
*/
|
||||||
|
private void sync(Long taskId, List<ProcessTaskActor> actors, String operation) {
|
||||||
|
// 在开关判断之前记录入口,使未进入HTTP客户端的调用也能明确定位跳过原因。
|
||||||
|
log.info("{}【WF同步入口】任务ID={},操作={}", LOG_PREFIX, taskId, operation);
|
||||||
|
if (!enabled()) {
|
||||||
|
log.info("{}【WF跳过】任务ID={},操作={},原因={},sso={},AppPortalTodo={}",
|
||||||
|
LOG_PREFIX, taskId, operation, !Globals.sso ? "开发模式" : "门户待办开关未开启",
|
||||||
|
Globals.sso, Globals.MyConfig.getBoolean("AppPortalTodo", false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ProcessTask task = dao.fetch(ProcessTask.class, taskId);
|
||||||
|
if (task == null) {
|
||||||
|
log.warn("{}【WF跳过】任务ID={},操作={},原因=任务不存在", LOG_PREFIX, taskId, operation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 首次申请节点由发起人直接提交,无需发送瞬时待办;退回申请节点仍需发送。
|
||||||
|
if (Long.valueOf(0).equals(task.getTaskParentId()) && "startTask".equals(task.getTaskName())) {
|
||||||
|
log.info("{}【WF跳过】任务ID={},操作={},原因=首次申请节点无需发送待办", LOG_PREFIX, taskId, operation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ("send".equals(operation) && !ProcessTaskStateEnum.DOING.getCode().equals(task.getTaskState())) {
|
||||||
|
log.info("{}【WF跳过】任务ID={},操作={},原因=任务非办理状态,任务状态={}",
|
||||||
|
LOG_PREFIX, taskId, operation, task.getTaskState());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ProcessInstance instance = dao.fetch(ProcessInstance.class, task.getProcessInstanceId());
|
||||||
|
if (instance == null) {
|
||||||
|
log.warn("{}【WF跳过】任务ID={},操作={},实例ID={},原因=流程实例不存在",
|
||||||
|
LOG_PREFIX, taskId, operation, task.getProcessInstanceId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<ProcessTaskActor> recipients = actors == null ? dao.query(ProcessTaskActor.class,
|
||||||
|
Cnd.where(ProcessTaskActor::getProcessTaskId, "=", taskId)) : actors;
|
||||||
|
if (recipients.isEmpty()) {
|
||||||
|
log.info("{}【WF跳过】任务ID={},操作={},原因=接收人列表为空", LOG_PREFIX, taskId, operation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (recipients.stream().anyMatch(actor -> StrUtil.isBlank(actor.getActorAccount()))) {
|
||||||
|
log.warn("{}【WF账号缺失】任务ID={},操作={},部分参与人没有登录账号,将过滤这些人员",
|
||||||
|
LOG_PREFIX, taskId, operation);
|
||||||
|
}
|
||||||
|
List<String> accounts = recipients.stream().map(ProcessTaskActor::getActorAccount)
|
||||||
|
.filter(StrUtil::isNotBlank).distinct().toList();
|
||||||
|
if (accounts.isEmpty()) {
|
||||||
|
log.info("{}【WF跳过】任务ID={},操作={},原因=接收人均缺少有效登录账号", LOG_PREFIX, taskId, operation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PortalTodoSendRequest request = new PortalTodoSendRequest();
|
||||||
|
request.setEntityName("wf");
|
||||||
|
request.setEntityId(String.valueOf(taskId));
|
||||||
|
request.setEntityNumber(StrUtil.blankToDefault(instance.getBusinessNo(), String.valueOf(instance.getId())));
|
||||||
|
request.setTargets(accounts);
|
||||||
|
if ("send".equals(operation)) {
|
||||||
|
String name = StrUtil.isBlank(instance.getVariable()) ? null : JSONUtil.parseObj(instance.getVariable())
|
||||||
|
.getStr(FlowConst.PROCESS_INSTANCE_NAME);
|
||||||
|
name = StrUtil.blankToDefault(name, request.getEntityNumber());
|
||||||
|
String node = StrUtil.blankToDefault(task.getDisplayName(), task.getTaskName());
|
||||||
|
request.setSubject(name + "-" + node);
|
||||||
|
request.setContent("您有一条待办任务,实例:" + name + ",处理环节:" + node);
|
||||||
|
request.setLink(buildLink(task, instance));
|
||||||
|
// H5使用任务自身的手机表单,不使用PC表单替代;缺失配置时明确记录回退原因。
|
||||||
|
if (StrUtil.isBlank(task.getH5FormKey())) {
|
||||||
|
log.warn("{}【H5入口回退】任务ID={},原因=未配置h5FormKey,将跳转手机首页", LOG_PREFIX, taskId);
|
||||||
|
}
|
||||||
|
request.setMobileLink(buildFormLink(task, instance, task.getH5FormKey(), "/platform/h5"));
|
||||||
|
if (StrUtil.isNotBlank(instance.getOperator())) {
|
||||||
|
View_user creator = dao.fetch(View_user.class, Cnd.where("id", "=", instance.getOperator()));
|
||||||
|
if (creator != null) {
|
||||||
|
request.setCreator(creator.getLoginname());
|
||||||
|
request.setCreatorName(creator.getUsername());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
portalTodoService.sendTodo(request);
|
||||||
|
} else if ("done".equals(operation)) {
|
||||||
|
portalTodoService.doneTodo(request);
|
||||||
|
} else {
|
||||||
|
portalTodoService.removeTodo(request);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("{}【WF同步失败】任务ID={},操作={},异常类型={}", LOG_PREFIX, taskId, operation, e.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复用 wf 表单入口参数,保留原链接查询和片段,避免工号或业务编号中的特殊字符破坏地址。
|
||||||
|
*/
|
||||||
|
private String buildLink(ProcessTask task, ProcessInstance instance) {
|
||||||
|
return buildFormLink(task, instance, task.getFormKey(), "/flow/todoCenter");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param task 提供taskId、taskKey的WF任务
|
||||||
|
* @param instance 提供bizId(businessNo)的流程实例
|
||||||
|
* @param formKey 对应端的业务表单地址,支持相对路径及完整HTTP(S)地址
|
||||||
|
* @param fallback 表单未配置时的入口路径;回退入口不附加业务参数
|
||||||
|
* @return 补齐项目域名并编码任务参数的地址,保留已有查询参数和锚点
|
||||||
|
*/
|
||||||
|
private String buildFormLink(ProcessTask task, ProcessInstance instance, String formKey, String fallback) {
|
||||||
|
String form = StrUtil.blankToDefault(formKey, fallback);
|
||||||
|
String link = URI.create(StrUtil.appendIfMissing(Globals.AppDomain, "/")).resolve(form).toString();
|
||||||
|
if (StrUtil.isBlank(formKey)) return link;
|
||||||
|
int fragmentAt = link.indexOf('#');
|
||||||
|
String fragment = fragmentAt < 0 ? "" : link.substring(fragmentAt);
|
||||||
|
if (fragmentAt >= 0) link = link.substring(0, fragmentAt);
|
||||||
|
return link + (link.contains("?") ? "&" : "?") + "taskId=" + task.getId()
|
||||||
|
+ "&bizId=" + encode(StrUtil.blankToDefault(instance.getBusinessNo(), ""))
|
||||||
|
+ "&taskKey=" + encode(task.getTaskName()) + fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encode(String value) {
|
||||||
|
return URLEncoder.encode(StrUtil.nullToEmpty(value), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
|||||||
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
|
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
|
||||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||||
import com.budwk.app.flow.service.ProcessDefineService;
|
import com.budwk.app.flow.service.ProcessDefineService;
|
||||||
|
import com.budwk.app.flow.service.FlowPortalTodoService;
|
||||||
import com.budwk.app.flow.service.ProcessInstanceService;
|
import com.budwk.app.flow.service.ProcessInstanceService;
|
||||||
import com.budwk.app.flow.service.ProcessTaskService;
|
import com.budwk.app.flow.service.ProcessTaskService;
|
||||||
import com.budwk.app.flow.vo.HighLightVO;
|
import com.budwk.app.flow.vo.HighLightVO;
|
||||||
@@ -57,6 +58,9 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
@Inject
|
@Inject
|
||||||
private ProcessDefineService processDefineService;
|
private ProcessDefineService processDefineService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FlowPortalTodoService flowPortalTodoService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public boolean save(ProcessInstance param) {
|
public boolean save(ProcessInstance param) {
|
||||||
@@ -198,10 +202,16 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
processInstance.setState(ProcessInstanceStateEnum.INTERRUPT.getCode());
|
processInstance.setState(ProcessInstanceStateEnum.INTERRUPT.getCode());
|
||||||
dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
|
dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
|
||||||
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
|
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
|
||||||
|
// 终止后任务不可办理,同步清理门户待办。
|
||||||
|
flowPortalTodoService.removeInstance(processInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void resume(Long processInstanceId, String operator) {
|
public void resume(Long processInstanceId, String operator) {
|
||||||
|
List<ProcessTask> restoredTasks = dao().query(ProcessTask.class,
|
||||||
|
Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
|
||||||
|
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
ProcessInstance processInstance = fetch(processInstanceId);
|
ProcessInstance processInstance = fetch(processInstanceId);
|
||||||
// 1. 更新流程实例状态为进行中
|
// 1. 更新流程实例状态为进行中
|
||||||
@@ -212,9 +222,12 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
|
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
|
||||||
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
|
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
|
||||||
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
|
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||||
|
// 仅恢复本次由终止状态重新进入办理的任务,避免重复发送已有待办。
|
||||||
|
restoredTasks.forEach(task -> flowPortalTodoService.sendTask(task.getId(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void pending(Long processInstanceId, String operator) {
|
public void pending(Long processInstanceId, String operator) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
// 1. 将该流程实例产生的任务状态修改为挂起
|
// 1. 将该流程实例产生的任务状态修改为挂起
|
||||||
@@ -227,10 +240,17 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
processInstance.setState(ProcessInstanceStateEnum.PENDING.getCode());
|
processInstance.setState(ProcessInstanceStateEnum.PENDING.getCode());
|
||||||
dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
|
dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
|
||||||
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
|
.and(ProcessInstance::getState, "=", ProcessInstanceStateEnum.DOING.getCode()));
|
||||||
|
// 挂起期间不应继续展示可办理的门户待办。
|
||||||
|
flowPortalTodoService.removeInstance(processInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void activate(Long processInstanceId, String operator) {
|
public void activate(Long processInstanceId, String operator) {
|
||||||
|
// 沿用当前 activate 的状态选择规则,只同步实际被恢复的任务。
|
||||||
|
List<ProcessTask> restoredTasks = dao().query(ProcessTask.class,
|
||||||
|
Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
|
||||||
|
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||||
ProcessInstance processInstance = fetch(processInstanceId);
|
ProcessInstance processInstance = fetch(processInstanceId);
|
||||||
// 1. 更新流程实例状态为进行中
|
// 1. 更新流程实例状态为进行中
|
||||||
processInstance.setState(ProcessInstanceStateEnum.DOING.getCode());
|
processInstance.setState(ProcessInstanceStateEnum.DOING.getCode());
|
||||||
@@ -240,6 +260,7 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
|
processTask.setTaskState(ProcessTaskStateEnum.DOING.getCode());
|
||||||
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
|
dao().update(ProcessTask.class, Chain.from(processTask), Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId)
|
||||||
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
|
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||||
|
restoredTasks.forEach(task -> flowPortalTodoService.sendTask(task.getId(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -323,6 +344,7 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void withdraw(Long processInstanceId, String operator) {
|
public void withdraw(Long processInstanceId, String operator) {
|
||||||
// Date now = new Date();
|
// Date now = new Date();
|
||||||
// 1. 将该流程实例状态修改为撤回
|
// 1. 将该流程实例状态修改为撤回
|
||||||
@@ -342,6 +364,8 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
processTask.setUpdatedBy(operator);
|
processTask.setUpdatedBy(operator);
|
||||||
}
|
}
|
||||||
dao().update(taskList, "taskState|updateAt|updateBy");
|
dao().update(taskList, "taskState|updateAt|updateBy");
|
||||||
|
// 实例撤回没有任务撤回事件,在此同步删除已不可办理的门户待办。
|
||||||
|
flowPortalTodoService.removeInstance(processInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// int update = dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
|
// int update = dao().update(ProcessInstance.class, Chain.from(processInstance), Cnd.where(ProcessInstance::getId, "=", processInstanceId)
|
||||||
@@ -555,6 +579,8 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
|
|||||||
@Override
|
@Override
|
||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void deleteProcessInstanceById(Long processInstanceId) {
|
public void deleteProcessInstanceById(Long processInstanceId) {
|
||||||
|
// 在实例、任务和参与人删除前清理门户,否则无法重建原待办定位参数。
|
||||||
|
flowPortalTodoService.removeInstance(processInstanceId);
|
||||||
List<ProcessTask> taskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId));
|
List<ProcessTask> taskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId));
|
||||||
List<Long> taskIds = taskList.stream().map(ProcessTask::getId).toList();
|
List<Long> taskIds = taskList.stream().map(ProcessTask::getId).toList();
|
||||||
// 任务参与人依赖流程任务,删除流程时先清理参与人,避免遗留无法回查所属流程的孤立记录。
|
// 任务参与人依赖流程任务,删除流程时先清理参与人,避免遗留无法回查所属流程的孤立记录。
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.budwk.app.flow.entity.ProcessTask;
|
|||||||
import com.budwk.app.flow.entity.ProcessTaskActor;
|
import com.budwk.app.flow.entity.ProcessTaskActor;
|
||||||
import com.budwk.app.flow.enums.*;
|
import com.budwk.app.flow.enums.*;
|
||||||
import com.budwk.app.flow.service.ProcessDefineService;
|
import com.budwk.app.flow.service.ProcessDefineService;
|
||||||
|
import com.budwk.app.flow.service.FlowPortalTodoService;
|
||||||
import com.budwk.app.flow.service.ProcessInstanceService;
|
import com.budwk.app.flow.service.ProcessInstanceService;
|
||||||
import com.budwk.app.flow.service.ProcessTaskService;
|
import com.budwk.app.flow.service.ProcessTaskService;
|
||||||
import com.budwk.app.flow.vo.ProcessTaskVO;
|
import com.budwk.app.flow.vo.ProcessTaskVO;
|
||||||
@@ -56,6 +57,9 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
|||||||
@Inject
|
@Inject
|
||||||
private ProcessDefineService processDefineService;
|
private ProcessDefineService processDefineService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FlowPortalTodoService flowPortalTodoService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean save(ProcessTask param) {
|
public boolean save(ProcessTask param) {
|
||||||
param.setId(null);
|
param.setId(null);
|
||||||
@@ -125,6 +129,7 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void finishProcessTask(Long processTaskId, String operator, Dict args) {
|
public void finishProcessTask(Long processTaskId, String operator, Dict args) {
|
||||||
ProcessTask his = fetch(processTaskId);
|
ProcessTask his = fetch(processTaskId);
|
||||||
ProcessTask processTask = new ProcessTask();
|
ProcessTask processTask = new ProcessTask();
|
||||||
@@ -149,6 +154,7 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void abandonProcessTask(Long processTaskId, String operator, Dict args) {
|
public void abandonProcessTask(Long processTaskId, String operator, Dict args) {
|
||||||
ProcessTask his = fetch(processTaskId);
|
ProcessTask his = fetch(processTaskId);
|
||||||
ProcessTask processTask = new ProcessTask();
|
ProcessTask processTask = new ProcessTask();
|
||||||
@@ -228,10 +234,13 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
|||||||
if (CollectionUtil.isEmpty(actors)) return;
|
if (CollectionUtil.isEmpty(actors)) return;
|
||||||
List<String> dbActors = getTaskActors(processTaskId);
|
List<String> dbActors = getTaskActors(processTaskId);
|
||||||
List<String> newActors = actors.stream().filter(actor -> !dbActors.contains(actor)).toList();
|
List<String> newActors = actors.stream().filter(actor -> !dbActors.contains(actor)).toList();
|
||||||
|
if (newActors.isEmpty()) return;
|
||||||
Sql sql = Sqls.create("select id,username,loginname,unitName,unitId from vw_user where id in (@ids)");
|
Sql sql = Sqls.create("select id,username,loginname,unitName,unitId from vw_user where id in (@ids)");
|
||||||
sql.setParam("ids", newActors);
|
sql.setParam("ids", newActors);
|
||||||
List<NutMap> list = listMap(sql);
|
List<NutMap> list = listMap(sql);
|
||||||
|
|
||||||
|
List<ProcessTaskActor> addedActors = new ArrayList<>();
|
||||||
|
|
||||||
for (NutMap actor : list) {
|
for (NutMap actor : list) {
|
||||||
ProcessTaskActor processTaskActor = new ProcessTaskActor();
|
ProcessTaskActor processTaskActor = new ProcessTaskActor();
|
||||||
processTaskActor.setProcessTaskId(processTaskId);
|
processTaskActor.setProcessTaskId(processTaskId);
|
||||||
@@ -243,7 +252,10 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
|||||||
processTaskActor.setCreatedAt(System.currentTimeMillis());
|
processTaskActor.setCreatedAt(System.currentTimeMillis());
|
||||||
System.out.println(StrUtil.format("给任务:{},添加参与者:{}", processTaskId, actor.toString()));
|
System.out.println(StrUtil.format("给任务:{},添加参与者:{}", processTaskId, actor.toString()));
|
||||||
insert(processTaskActor);
|
insert(processTaskActor);
|
||||||
|
addedActors.add(processTaskActor);
|
||||||
}
|
}
|
||||||
|
// 只为实际新增的账号发送,覆盖普通任务、串并行会签、转办和加签,避免事件重复发送。
|
||||||
|
flowPortalTodoService.sendTask(processTaskId, addedActors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -290,7 +302,14 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void removeTaskActor(Long processTaskId, List<String> actors) {
|
public void removeTaskActor(Long processTaskId, List<String> actors) {
|
||||||
|
if (CollectionUtil.isEmpty(actors)) return;
|
||||||
|
// 先保留被移除人员的门户账号,参与人记录清理后无法再据此定位待办。
|
||||||
|
List<ProcessTaskActor> removedActors = dao().query(ProcessTaskActor.class,
|
||||||
|
Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTaskId)
|
||||||
|
.and(ProcessTaskActor::getActorId, "in", actors));
|
||||||
|
flowPortalTodoService.removeTask(processTaskId, removedActors);
|
||||||
dao().clear(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTaskId).and(ProcessTaskActor::getActorId, "in", actors));
|
dao().clear(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", processTaskId).and(ProcessTaskActor::getActorId, "in", actors));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +500,9 @@ public class ProcessTaskServiceImpl extends BaseServiceImpl<ProcessTask> impleme
|
|||||||
task.setVariable(JSONUtil.toJsonStr(variable));
|
task.setVariable(JSONUtil.toJsonStr(variable));
|
||||||
updateIgnoreNull(task);
|
updateIgnoreNull(task);
|
||||||
|
|
||||||
|
// 转办后原参与人不再办理,先清理原任务门户待办;新任务在添加参与人时发送。
|
||||||
|
flowPortalTodoService.removeTask(taskId, null);
|
||||||
|
|
||||||
// 创建转办任务
|
// 创建转办任务
|
||||||
ProcessTask newTask = BeanUtil.copyProperties(task, ProcessTask.class, "id");
|
ProcessTask newTask = BeanUtil.copyProperties(task, ProcessTask.class, "id");
|
||||||
newTask.setTaskParentId(taskId);
|
newTask.setTaskParentId(taskId);
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ import org.nutz.mvc.upload.TempFile;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -43,6 +47,9 @@ import java.nio.file.StandardOpenOption;
|
|||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -54,6 +61,9 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
static String IMG_BASE64_PATTERN = "<img\\s+[^>]*src\\s*=\\s*['\"](data:image/[^'\"]+;base64,[^'\"]+)['\"][^>]*>";
|
static String IMG_BASE64_PATTERN = "<img\\s+[^>]*src\\s*=\\s*['\"](data:image/[^'\"]+;base64,[^'\"]+)['\"][^>]*>";
|
||||||
private static final int DOWNLOAD_FILE_CACHE_LIMIT = 2000;
|
private static final int DOWNLOAD_FILE_CACHE_LIMIT = 2000;
|
||||||
private final Map<String, Sys_file> downloadFileCache = new ConcurrentHashMap<>();
|
private final Map<String, Sys_file> downloadFileCache = new ConcurrentHashMap<>();
|
||||||
|
private static final Set<String> UPLOAD_SUFFIXES = Set.of("gif", "jpg", "jpeg", "png", "doc", "docx",
|
||||||
|
"xls", "xlsx", "rar", "zip", "7z", "txt", "pdf", "pptx", "ppt", "mp4");
|
||||||
|
private static final Set<String> ACTIVE_SUFFIXES = Set.of("svg", "svgz", "html", "htm", "xhtml", "xml", "js", "mjs", "swf");
|
||||||
|
|
||||||
public SysFileServiceImpl(Dao dao) {
|
public SysFileServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
@@ -86,11 +96,13 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void download(String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
public void download(String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
if (handleCachedFileRequest(id, request, response)) {
|
response.setHeader("X-Content-Type-Options", "nosniff");
|
||||||
return;
|
response.setHeader("Cache-Control", "private, no-store");
|
||||||
}
|
|
||||||
Sys_file sys_file = fetchDownloadFile(id);
|
Sys_file sys_file = fetchDownloadFile(id);
|
||||||
if (!ObjectUtil.isEmpty(sys_file) && handleCachedImageRequest(sys_file, request, response)) {
|
// 历史主动内容也必须在缓存协商前拦截,避免旧 SVG 继续在业务域下执行。
|
||||||
|
if (sys_file != null && (ACTIVE_SUFFIXES.contains(StrUtil.blankToDefault(sys_file.getSuffix(), "").toLowerCase(Locale.ROOT))
|
||||||
|
|| ACTIVE_SUFFIXES.contains(FileUtil.extName(sys_file.getName()).toLowerCase(Locale.ROOT)))) {
|
||||||
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "不允许访问此类型的附件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ObjectUtil.isEmpty(sys_file)) {
|
if (ObjectUtil.isEmpty(sys_file)) {
|
||||||
@@ -117,16 +129,6 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleCachedFileRequest(String id, HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
String etag = buildFileEtag(id);
|
|
||||||
if (etag.equals(request.getHeader("If-None-Match"))) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
|
||||||
setImageCacheHeaders(response, etag);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Sys_file fetchDownloadFile(String id) {
|
private Sys_file fetchDownloadFile(String id) {
|
||||||
if (StrUtil.isBlank(id)) {
|
if (StrUtil.isBlank(id)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -145,23 +147,15 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
return sysFile;
|
return sysFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleCachedImageRequest(Sys_file sysFile, HttpServletRequest request, HttpServletResponse response) {
|
|
||||||
if (!isImage(sysFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String etag = buildFileEtag(sysFile);
|
|
||||||
if (etag.equals(request.getHeader("If-None-Match"))) {
|
|
||||||
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
|
||||||
setImageCacheHeaders(response, etag);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean writeImageResponse(Sys_file sysFile, byte[] bytes, HttpServletResponse response) throws IOException {
|
private boolean writeImageResponse(Sys_file sysFile, byte[] bytes, HttpServletResponse response) throws IOException {
|
||||||
if (!isImage(sysFile)) {
|
if (!isImage(sysFile)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
String suffix = StrUtil.blankToDefault(sysFile.getSuffix(), FileUtil.extName(sysFile.getName())).toLowerCase(Locale.ROOT);
|
||||||
|
if (!isValidImage(bytes, suffix)) {
|
||||||
|
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "图片内容与文件类型不符");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
setImageCacheHeaders(response, buildFileEtag(sysFile));
|
setImageCacheHeaders(response, buildFileEtag(sysFile));
|
||||||
response.setHeader("Content-Disposition", "inline;filename=" + java.net.URLEncoder.encode(sysFile.getName(), java.nio.charset.StandardCharsets.UTF_8));
|
response.setHeader("Content-Disposition", "inline;filename=" + java.net.URLEncoder.encode(sysFile.getName(), java.nio.charset.StandardCharsets.UTF_8));
|
||||||
response.setHeader("Content-Length", String.valueOf(bytes.length));
|
response.setHeader("Content-Length", String.valueOf(bytes.length));
|
||||||
@@ -171,9 +165,9 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setImageCacheHeaders(HttpServletResponse response, String etag) {
|
private void setImageCacheHeaders(HttpServletResponse response, String etag) {
|
||||||
response.setHeader("Cache-Control", "public, max-age=604800, immutable");
|
response.setHeader("Cache-Control", "private, no-cache");
|
||||||
response.setHeader("ETag", etag);
|
response.setHeader("ETag", etag);
|
||||||
response.setDateHeader("Expires", System.currentTimeMillis() + 604800000L);
|
response.setDateHeader("Expires", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildFileEtag(Sys_file sysFile) {
|
private String buildFileEtag(Sys_file sysFile) {
|
||||||
@@ -181,12 +175,12 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildFileEtag(String id) {
|
private String buildFileEtag(String id) {
|
||||||
return "\"" + id + "\"";
|
return "\"safe-" + id + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isImage(Sys_file sysFile) {
|
private boolean isImage(Sys_file sysFile) {
|
||||||
String suffix = StrUtil.blankToDefault(sysFile.getSuffix(), FileUtil.extName(sysFile.getName())).toLowerCase();
|
String suffix = StrUtil.blankToDefault(sysFile.getSuffix(), FileUtil.extName(sysFile.getName())).toLowerCase();
|
||||||
return "jpg".equals(suffix) || "jpeg".equals(suffix) || "png".equals(suffix) || "gif".equals(suffix) || "webp".equals(suffix) || "bmp".equals(suffix) || "svg".equals(suffix);
|
return "jpg".equals(suffix) || "jpeg".equals(suffix) || "png".equals(suffix) || "gif".equals(suffix) || "webp".equals(suffix) || "bmp".equals(suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getImageContentType(Sys_file sysFile) {
|
private String getImageContentType(Sys_file sysFile) {
|
||||||
@@ -197,7 +191,6 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
case "gif" -> "image/gif";
|
case "gif" -> "image/gif";
|
||||||
case "webp" -> "image/webp";
|
case "webp" -> "image/webp";
|
||||||
case "bmp" -> "image/bmp";
|
case "bmp" -> "image/bmp";
|
||||||
case "svg" -> "image/svg+xml";
|
|
||||||
default -> "application/octet-stream";
|
default -> "application/octet-stream";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -310,6 +303,7 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
* 存储文件
|
* 存储文件
|
||||||
**/
|
**/
|
||||||
private String storageFile(String engine, TempFile file, boolean returnFileId) {
|
private String storageFile(String engine, TempFile file, boolean returnFileId) {
|
||||||
|
validateUpload(file);
|
||||||
// 如果引擎为空,默认使用本地
|
// 如果引擎为空,默认使用本地
|
||||||
if (ObjectUtil.isEmpty(engine)) {
|
if (ObjectUtil.isEmpty(engine)) {
|
||||||
engine = SysFileEngineTypeEnum.LOCAL.getValue();
|
engine = SysFileEngineTypeEnum.LOCAL.getValue();
|
||||||
@@ -383,6 +377,53 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 所有业务上传共用校验,客户端声明的 MIME 类型不能作为图片真实性依据。 */
|
||||||
|
private void validateUpload(TempFile file) {
|
||||||
|
if (file == null || StrUtil.isBlank(file.getSubmittedFileName())) {
|
||||||
|
throw new BaseException("请选择要上传的文件");
|
||||||
|
}
|
||||||
|
String suffix = FileUtil.extName(file.getSubmittedFileName()).toLowerCase(Locale.ROOT);
|
||||||
|
if (!UPLOAD_SUFFIXES.contains(suffix)) {
|
||||||
|
throw new BaseException("不允许上传此类型的文件");
|
||||||
|
}
|
||||||
|
if (Set.of("gif", "jpg", "jpeg", "png").contains(suffix)) {
|
||||||
|
try {
|
||||||
|
if (!isValidImage(Files.readAllBytes(file.getFile().toPath()), suffix)) {
|
||||||
|
throw new BaseException("图片内容与文件类型不符");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new BaseException("图片读取失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解码真实图片并限制像素数量,阻止伪装为图片的 SVG 和异常大尺寸图片。 */
|
||||||
|
private boolean isValidImage(byte[] bytes, String suffix) {
|
||||||
|
try (ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(bytes))) {
|
||||||
|
if (input == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||||
|
if (!readers.hasNext()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ImageReader reader = readers.next();
|
||||||
|
try {
|
||||||
|
reader.setInput(input, true, true);
|
||||||
|
String expected = "jpg".equals(suffix) ? "jpeg" : suffix;
|
||||||
|
if (!expected.equalsIgnoreCase(reader.getFormatName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long pixels = (long) reader.getWidth(0) * reader.getHeight(0);
|
||||||
|
return pixels > 0 && pixels <= 40000000 && reader.read(0) != null;
|
||||||
|
} finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
} catch (IOException | RuntimeException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 存储文件
|
* 存储文件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import cn.dev33.satoken.servlet.model.SaStorageForServlet;
|
|||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
import org.nutz.mvc.Mvcs;
|
import org.nutz.mvc.Mvcs;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletResponseWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author wizzer@qq.com
|
* @author wizzer@qq.com
|
||||||
*/
|
*/
|
||||||
@@ -22,7 +25,20 @@ public class SaTokenContextImpl implements SaTokenContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SaResponse getResponse() {
|
public SaResponse getResponse() {
|
||||||
return new SaResponseForServlet(Mvcs.getResp());
|
// 当前 Sa-Token 版本没有 Cookie 安全属性配置,在写入登录 Cookie 时统一补齐。
|
||||||
|
return new SaResponseForServlet(new HttpServletResponseWrapper(Mvcs.getResp()) {
|
||||||
|
/**
|
||||||
|
* 限制登录 Cookie 的脚本访问,并在 HTTPS 请求中仅允许安全传输。
|
||||||
|
*
|
||||||
|
* @param cookie Sa-Token 写入或清除的 Cookie
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addCookie(Cookie cookie) {
|
||||||
|
cookie.setHttpOnly(true);
|
||||||
|
cookie.setSecure(cookie.getSecure() || Mvcs.getReq().isSecure());
|
||||||
|
super.addCookie(cookie);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.budwk.app.web.commons.ext.handler;
|
package com.budwk.app.web.commons.ext.handler;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.nutz.boot.starter.WebServletFace;
|
||||||
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
|
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
import org.nutz.json.Json;
|
import org.nutz.json.Json;
|
||||||
import org.nutz.lang.util.NutMap;
|
import org.nutz.lang.util.NutMap;
|
||||||
@@ -9,28 +8,63 @@ import org.nutz.log.Log;
|
|||||||
import org.nutz.log.Logs;
|
import org.nutz.log.Logs;
|
||||||
|
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
import javax.servlet.Servlet;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误页拦截器,登陆后台显示友好提示
|
* 错误页拦截器,登陆后台显示友好提示
|
||||||
*/
|
*/
|
||||||
@IocBean
|
@IocBean
|
||||||
public class WkErrorPageHandler extends ErrorPageErrorHandler {
|
public class WkErrorPageHandler extends HttpServlet implements WebServletFace {
|
||||||
private static final Log log = Logs.get();
|
private static final Log log = Logs.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回容器错误处理 Servlet 的注册名称。
|
||||||
|
* @return 独立于业务路由的 Servlet 名称
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
|
public String getName() {
|
||||||
|
return "wkErrorPage";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回容器错误页的内部派发地址。
|
||||||
|
* @return 错误页映射路径
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getPathSpec() {
|
||||||
|
return "/__container_error";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将当前错误处理实例交给 NutzBoot 注册。
|
||||||
|
* @return 当前 Servlet 实例
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Servlet getServlet() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
|
||||||
|
if (request.getDispatcherType() != DispatcherType.ERROR) {
|
||||||
|
response.sendError(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (response.getStatus() == 403 || response.getStatus() == 404 || response.getStatus() == 500) {
|
if (response.getStatus() == 403 || response.getStatus() == 404 || response.getStatus() == 500) {
|
||||||
try {
|
try {
|
||||||
if (isAjax(request)) {
|
if (isAjax(request)) {
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
response.getWriter().write(Json.toJson(new NutMap("code", "-1").setv("msg", response.getStatus() + " error")));
|
response.getWriter().write(Json.toJson(new NutMap("code", "-1").setv("msg", response.getStatus() + " error")));
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
request.setAttribute("original_request_uri", request.getRequestURI());
|
request.setAttribute("original_request_uri", request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI));
|
||||||
RequestDispatcher rd = request.getRequestDispatcher("/platform/home/" + response.getStatus());
|
RequestDispatcher rd = request.getRequestDispatcher("/platform/home/" + response.getStatus());
|
||||||
rd.forward(request, response);
|
rd.forward(request, response);
|
||||||
return;
|
return;
|
||||||
@@ -39,7 +73,7 @@ public class WkErrorPageHandler extends ErrorPageErrorHandler {
|
|||||||
log.error(e);
|
log.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.handle(target, baseRequest, request, response);
|
// 不回落到容器默认错误页,避免泄露异常堆栈和容器信息。
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAjax(ServletRequest req) {
|
private boolean isAjax(ServletRequest req) {
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.budwk.app.web.commons.ext.handler;
|
||||||
|
|
||||||
|
import com.budwk.app.web.commons.ext.websocket.WkWebSocket;
|
||||||
|
import org.apache.catalina.core.StandardContext;
|
||||||
|
import org.apache.catalina.Executor;
|
||||||
|
import org.apache.catalina.core.StandardThreadExecutor;
|
||||||
|
import org.apache.catalina.startup.Tomcat;
|
||||||
|
import org.apache.catalina.valves.ErrorReportValve;
|
||||||
|
import org.apache.tomcat.websocket.server.WsSci;
|
||||||
|
import org.apache.tomcat.util.descriptor.web.FilterMap;
|
||||||
|
import org.nutz.boot.NbApp;
|
||||||
|
import org.nutz.boot.starter.tomcat.TomcatStarter;
|
||||||
|
import org.nutz.boot.tools.NbAppEventListener;
|
||||||
|
import org.nutz.ioc.impl.PropertiesProxy;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import javax.servlet.ServletContextListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在容器启动前补齐 NutzBoot Tomcat 的 WebSocket 注册和线程池绑定。
|
||||||
|
*/
|
||||||
|
public class WkTomcatConfiguration implements NbAppEventListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置官方嵌入式 Tomcat,保留项目的监听地址与消息推送能力。
|
||||||
|
*
|
||||||
|
* @param app 当前 NutzBoot 应用
|
||||||
|
* @param eventType 只在服务器启动前配置,避免重复注册端点
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void whenStartServers(NbApp app, EventType eventType) {
|
||||||
|
if (eventType != EventType.before) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TomcatStarter starter = app.getAppContext().getIoc().get(TomcatStarter.class);
|
||||||
|
Tomcat tomcat = starter.getServer();
|
||||||
|
PropertiesProxy conf = app.getAppContext().getConf();
|
||||||
|
tomcat.getConnector().setProperty("address", starter.getHost());
|
||||||
|
|
||||||
|
// Starter 创建了共享线程池,但尚未把它交给 Connector 使用。
|
||||||
|
for (Executor executor : tomcat.getService().findExecutors()) {
|
||||||
|
if (executor instanceof StandardThreadExecutor) {
|
||||||
|
StandardThreadExecutor threads = (StandardThreadExecutor) executor;
|
||||||
|
threads.setMinSpareThreads(conf.getInt("tomcat.executor.minSpareThreads", 10));
|
||||||
|
threads.setMaxIdleTime(conf.getInt("tomcat.executor.maxIdleTime", 60000));
|
||||||
|
tomcat.getConnector().getProtocolHandler().setExecutor(threads);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NutzBoot 使用编程式 Context,必须显式运行官方 WebSocket 初始化器。
|
||||||
|
StandardContext context = (StandardContext) tomcat.getHost().findChild(starter.getContextPath());
|
||||||
|
context.setUseHttpOnly(conf.getBoolean("tomcat.session.cookie.httponly", true));
|
||||||
|
context.addServletContainerInitializer(new WsSci(), Set.of(WkWebSocket.class));
|
||||||
|
// Tomcat 的错误页再次 forward 时仍保持 ERROR 派发;让 MVC 渲染原业务错误页。
|
||||||
|
// Nutz 的过滤器在前一个监听器中注册,须等其完成后才能添加映射。
|
||||||
|
context.addApplicationLifecycleListener(new ServletContextListener() {
|
||||||
|
/**
|
||||||
|
* 在 Nutz 注册完成后补充业务错误页的 ERROR 派发映射。
|
||||||
|
* @param event 当前 Servlet 上下文初始化事件
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void contextInitialized(ServletContextEvent event) {
|
||||||
|
FilterMap errorPages = new FilterMap();
|
||||||
|
errorPages.setFilterName("nutz");
|
||||||
|
errorPages.setDispatcher("ERROR");
|
||||||
|
// ERROR 再转发时 URL 匹配路径不变,按目标 Servlet 匹配才能进入 MVC。
|
||||||
|
errorPages.addServletName("default");
|
||||||
|
context.addFilterMap(errorPages);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Host 的兜底错误页也不能重新暴露此前已关闭的版本和堆栈。
|
||||||
|
ErrorReportValve errorReport = null;
|
||||||
|
for (org.apache.catalina.Valve valve : tomcat.getHost().getPipeline().getValves()) {
|
||||||
|
if (valve instanceof ErrorReportValve) {
|
||||||
|
errorReport = (ErrorReportValve) valve;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (errorReport == null) {
|
||||||
|
errorReport = new ErrorReportValve();
|
||||||
|
tomcat.getHost().getPipeline().addValve(errorReport);
|
||||||
|
}
|
||||||
|
errorReport.setShowReport(false);
|
||||||
|
errorReport.setShowServerInfo(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -54,7 +54,7 @@ import java.util.List;
|
|||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/unionReimburse/apply")
|
@At("/platform/unionReimburse/apply")
|
||||||
@Api("工会报销申请")
|
@Api("工会报销申请")
|
||||||
@Ok("json:full")
|
@Ok("json:{locked:'condolenceIdCard|condolenceBirthday|condolenceMobile',ignoreNull:false}")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class UnionReimburseApplyController {
|
public class UnionReimburseApplyController {
|
||||||
|
|
||||||
@@ -151,14 +151,11 @@ public class UnionReimburseApplyController {
|
|||||||
username as userName,
|
username as userName,
|
||||||
loginname as loginName,
|
loginname as loginName,
|
||||||
sex,
|
sex,
|
||||||
mobile,
|
|
||||||
technicalTitle,
|
technicalTitle,
|
||||||
IFNULL(unitname, '暂无') as unitName,
|
IFNULL(unitname, '暂无') as unitName,
|
||||||
unitid as unitId,
|
unitid as unitId,
|
||||||
unionid as unionId,
|
unionid as unionId,
|
||||||
unionname as unionName,
|
unionname as unionName,
|
||||||
DATE(birthday) AS birthday,
|
|
||||||
idCard,
|
|
||||||
unionCode
|
unionCode
|
||||||
from
|
from
|
||||||
vw_user
|
vw_user
|
||||||
@@ -173,6 +170,9 @@ public class UnionReimburseApplyController {
|
|||||||
}
|
}
|
||||||
cnd.and(View_user::getId, "!=", SecurityUtil.getUserId());
|
cnd.and(View_user::getId, "!=", SecurityUtil.getUserId());
|
||||||
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
if (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||||
|
if (StrUtil.isBlank(SecurityUtil.getUnionId())) {
|
||||||
|
return Result.success(java.util.Collections.emptyList());
|
||||||
|
}
|
||||||
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
|
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
|
||||||
}
|
}
|
||||||
sql.setCondition(cnd);
|
sql.setCondition(cnd);
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/unionReimburse/certifierUserSign")
|
@At("/platform/unionReimburse/certifierUserSign")
|
||||||
@Ok("json:full")
|
@Ok("json:{locked:'condolenceIdCard|condolenceBirthday|condolenceMobile',ignoreNull:false}")
|
||||||
@Api("证明人签字")
|
@Api("证明人签字")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class UnionReimburseCertifierUserSignController {
|
public class UnionReimburseCertifierUserSignController {
|
||||||
|
|||||||
+1
-1
@@ -68,7 +68,7 @@ import java.util.HashMap;
|
|||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/unionReimburse/mine")
|
@At("/platform/unionReimburse/mine")
|
||||||
@Api("工会报销我的")
|
@Api("工会报销我的")
|
||||||
@Ok("json:full")
|
@Ok("json:{locked:'condolenceIdCard|condolenceBirthday|condolenceMobile',ignoreNull:false}")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class UnionReimburseMineController {
|
public class UnionReimburseMineController {
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/unionReimburse/review")
|
@At("/platform/unionReimburse/review")
|
||||||
@Ok("json:full")
|
@Ok("json:{locked:'condolenceIdCard|condolenceBirthday|condolenceMobile',ignoreNull:false}")
|
||||||
@Api("工会报销审核")
|
@Api("工会报销审核")
|
||||||
public class UnionReimburseReviewController {
|
public class UnionReimburseReviewController {
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/unionReimburse/statistics")
|
@At("/platform/unionReimburse/statistics")
|
||||||
@Ok("json:full")
|
@Ok("json:{locked:'condolenceIdCard|condolenceBirthday|condolenceMobile',ignoreNull:false}")
|
||||||
@Api("慰问统计")
|
@Api("慰问统计")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class UnionReimburseStatisticsController {
|
public class UnionReimburseStatisticsController {
|
||||||
|
|||||||
+123
-3
@@ -4,10 +4,12 @@ import cn.hutool.core.date.DateUtil;
|
|||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.budwk.app.base.constant.RoleConstant;
|
import com.budwk.app.base.constant.RoleConstant;
|
||||||
|
import com.budwk.app.base.exception.BaseException;
|
||||||
import com.budwk.app.base.page.Pagination;
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
import com.budwk.app.sys.models.Sys_file;
|
import com.budwk.app.sys.models.Sys_file;
|
||||||
|
import com.budwk.app.sys.views.View_user;
|
||||||
import com.budwk.app.sys.services.SysFileService;
|
import com.budwk.app.sys.services.SysFileService;
|
||||||
import com.budwk.app.zhgh.club.model.ClubUser;
|
import com.budwk.app.zhgh.club.model.ClubUser;
|
||||||
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub;
|
import com.budwk.app.zhgh.dayofficework.outlay.outlayManage.club.model.OutlayManageClub;
|
||||||
@@ -125,6 +127,37 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
|||||||
if (oldRecord == null) {
|
if (oldRecord == null) {
|
||||||
return Result.error("未找到对应的报销记录");
|
return Result.error("未找到对应的报销记录");
|
||||||
}
|
}
|
||||||
|
if (!isAdmin() && !Objects.equals(oldRecord.getUserId(), SecurityUtil.getUserId())) {
|
||||||
|
return Result.error("无权修改此报销申请");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 人员身份和敏感资料由服务端补齐,避免前端删减字段导致丢失或被篡改。
|
||||||
|
if (oldRecord == null) {
|
||||||
|
View_user handler = dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
|
||||||
|
if (handler == null) {
|
||||||
|
return Result.error("经办人信息不存在");
|
||||||
|
}
|
||||||
|
unionReimburse.setUserId(handler.getId());
|
||||||
|
unionReimburse.setUserName(handler.getUsername());
|
||||||
|
unionReimburse.setLoginName(handler.getLoginname());
|
||||||
|
unionReimburse.setUnitId(handler.getUnitId());
|
||||||
|
unionReimburse.setUnitName(handler.getUnitName());
|
||||||
|
unionReimburse.setUnionId(handler.getUnionId());
|
||||||
|
unionReimburse.setUnionName(handler.getUnionName());
|
||||||
|
} else {
|
||||||
|
// 修改历史单据时保留原经办组织,避免人员调动改变经费归属。
|
||||||
|
unionReimburse.setUserId(oldRecord.getUserId());
|
||||||
|
unionReimburse.setUserName(oldRecord.getUserName());
|
||||||
|
unionReimburse.setLoginName(oldRecord.getLoginName());
|
||||||
|
unionReimburse.setUnitId(oldRecord.getUnitId());
|
||||||
|
unionReimburse.setUnitName(oldRecord.getUnitName());
|
||||||
|
unionReimburse.setUnionId(oldRecord.getUnionId());
|
||||||
|
unionReimburse.setUnionName(oldRecord.getUnionName());
|
||||||
|
}
|
||||||
|
Result recipientResult = fillCondolencePersonalData(unionReimburse, oldRecord);
|
||||||
|
if (recipientResult != null) {
|
||||||
|
return recipientResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeInvoiceDetails(unionReimburse);
|
normalizeInvoiceDetails(unionReimburse);
|
||||||
@@ -179,14 +212,104 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
|||||||
if (unionReimburse == null) {
|
if (unionReimburse == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!canReadApply(unionReimburse)) {
|
||||||
|
throw new BaseException("无权查看此报销申请");
|
||||||
|
}
|
||||||
List<UnionReimburseInvoiceDetail> invoiceDetails = this.dao().query(
|
List<UnionReimburseInvoiceDetail> invoiceDetails = this.dao().query(
|
||||||
UnionReimburseInvoiceDetail.class,
|
UnionReimburseInvoiceDetail.class,
|
||||||
Cnd.where("reimburseId", "=", id).asc("createdAt")
|
Cnd.where("reimburseId", "=", id).asc("createdAt")
|
||||||
);
|
);
|
||||||
unionReimburse.setInvoiceDetails(invoiceDetails == null ? new ArrayList<>() : invoiceDetails);
|
unionReimburse.setInvoiceDetails(invoiceDetails == null ? new ArrayList<>() : invoiceDetails);
|
||||||
|
// 申请页和详情页共用此接口,原始身份资料仅保留在服务端业务记录中。
|
||||||
|
unionReimburse.setCondolenceIdCard(null);
|
||||||
|
unionReimburse.setCondolenceBirthday(null);
|
||||||
|
unionReimburse.setCondolenceMobile(null);
|
||||||
return unionReimburse;
|
return unionReimburse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 保留历史单据的身份资料快照,新选人员须符合人员查询的数据范围。 */
|
||||||
|
private Result fillCondolencePersonalData(UnionReimburse form, UnionReimburse oldRecord) {
|
||||||
|
form.setCondolenceIdCard(null);
|
||||||
|
form.setCondolenceBirthday(null);
|
||||||
|
form.setCondolenceMobile(null);
|
||||||
|
if (!"UNION_REIMBURSE_PROJECT_1".equals(form.getReimburseProject())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(form.getCondolenceUserId())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (oldRecord != null && Objects.equals(oldRecord.getCondolenceUserId(), form.getCondolenceUserId())) {
|
||||||
|
form.setCondolenceIdCard(oldRecord.getCondolenceIdCard());
|
||||||
|
form.setCondolenceBirthday(oldRecord.getCondolenceBirthday());
|
||||||
|
form.setCondolenceMobile(oldRecord.getCondolenceMobile());
|
||||||
|
form.setCondolenceUnionId(oldRecord.getCondolenceUnionId());
|
||||||
|
form.setCondolenceUnitId(oldRecord.getCondolenceUnitId());
|
||||||
|
form.setCondolenceUserName(oldRecord.getCondolenceUserName());
|
||||||
|
form.setCondolenceLoginName(oldRecord.getCondolenceLoginName());
|
||||||
|
form.setCondolenceUnitName(oldRecord.getCondolenceUnitName());
|
||||||
|
form.setCondolenceUnionName(oldRecord.getCondolenceUnionName());
|
||||||
|
form.setCondolenceUnionCode(oldRecord.getCondolenceUnionCode());
|
||||||
|
form.setCondolenceSex(oldRecord.getCondolenceSex());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
View_user user = dao().fetch(View_user.class, Cnd.where("id", "=", form.getCondolenceUserId()));
|
||||||
|
if (user == null || Objects.equals(user.getId(), SecurityUtil.getUserId())
|
||||||
|
|| (!isAdmin() && (StrUtil.isBlank(SecurityUtil.getUnionId())
|
||||||
|
|| !Objects.equals(SecurityUtil.getUnionId(), user.getUnionId())))) {
|
||||||
|
return Result.error("无权选择此慰问对象");
|
||||||
|
}
|
||||||
|
form.setCondolenceUserName(user.getUsername());
|
||||||
|
form.setCondolenceLoginName(user.getLoginname());
|
||||||
|
form.setCondolenceUnitId(user.getUnitId());
|
||||||
|
form.setCondolenceUnitName(user.getUnitName());
|
||||||
|
form.setCondolenceUnionId(user.getUnionId());
|
||||||
|
form.setCondolenceUnionName(user.getUnionName());
|
||||||
|
form.setCondolenceUnionCode(user.getUnionCode());
|
||||||
|
form.setCondolenceSex(user.getSex());
|
||||||
|
form.setCondolenceIdCard(user.getIdCard());
|
||||||
|
form.setCondolenceBirthday(user.getBirthday() == null ? null : DateUtil.formatDate(user.getBirthday()));
|
||||||
|
form.setCondolenceMobile(user.getMobile());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 详情访问沿用经办、审核、统计和流程参与范围,不能仅凭单据 ID 查询。 */
|
||||||
|
private boolean canReadApply(UnionReimburse form) {
|
||||||
|
String userId = SecurityUtil.getUserId();
|
||||||
|
if (StrUtil.isBlank(userId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isAdmin() || userId.equals(form.getUserId())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (form.getStateId() != 1 && (AuthUtil.hasPermission("unionReimburse.review")
|
||||||
|
|| AuthUtil.hasPermission("h5.unionReimburse.review"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((form.getStateId() == 2 || form.getStateId() == 3)
|
||||||
|
&& StrUtil.isNotBlank(SecurityUtil.getUnionId())
|
||||||
|
&& Objects.equals(SecurityUtil.getUnionId(), form.getCondolenceUnionId())
|
||||||
|
&& (AuthUtil.hasPermission("unionReimburse.statistics")
|
||||||
|
|| AuthUtil.hasPermission("h5.unionReimburse.statistics"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 旧流程单据允许任务参与人和抄送人回看,关系必须由数据库确认。
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT COUNT(*) FROM wf_process_instance i
|
||||||
|
WHERE i.businessNo = @id AND (
|
||||||
|
EXISTS (SELECT 1 FROM wf_process_task t
|
||||||
|
INNER JOIN wf_process_task_actor a ON a.processTaskId = t.id
|
||||||
|
WHERE t.processInstanceId = i.id AND a.actorId = @userId)
|
||||||
|
OR EXISTS (SELECT 1 FROM wf_process_cc_instance c
|
||||||
|
WHERE c.processInstanceId = i.id AND c.actorId = @userId)
|
||||||
|
)
|
||||||
|
""");
|
||||||
|
sql.setParam("id", form.getId());
|
||||||
|
sql.setParam("userId", userId);
|
||||||
|
sql.setCallback(Sqls.callback.integer());
|
||||||
|
dao().execute(sql);
|
||||||
|
return sql.getInt() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteApply(String id) {
|
public void deleteApply(String id) {
|
||||||
if (StrUtil.isBlank(id)) {
|
if (StrUtil.isBlank(id)) {
|
||||||
@@ -242,14 +365,11 @@ public class UnionReimburseServiceImpl extends BaseServiceImpl<UnionReimburse> i
|
|||||||
u.username AS userName,
|
u.username AS userName,
|
||||||
u.loginname AS loginName,
|
u.loginname AS loginName,
|
||||||
u.sex,
|
u.sex,
|
||||||
u.mobile,
|
|
||||||
u.technicalTitle,
|
u.technicalTitle,
|
||||||
IFNULL(u.unitname, '暂无') AS unitName,
|
IFNULL(u.unitname, '暂无') AS unitName,
|
||||||
u.unitid AS unitId,
|
u.unitid AS unitId,
|
||||||
u.unionid AS unionId,
|
u.unionid AS unionId,
|
||||||
u.unionname AS unionName,
|
u.unionname AS unionName,
|
||||||
DATE(u.birthday) AS birthday,
|
|
||||||
u.idCard,
|
|
||||||
u.unionCode
|
u.unionCode
|
||||||
FROM
|
FROM
|
||||||
vw_user u
|
vw_user u
|
||||||
|
|||||||
+177
-17
@@ -1,23 +1,66 @@
|
|||||||
package com.budwk.app.zhgh.democratic.proposal.controller.dashboard;
|
package com.budwk.app.zhgh.democratic.proposal.controller.dashboard;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.budwk.app.base.page.Pagination;
|
||||||
import com.budwk.app.base.param.PageForm;
|
import com.budwk.app.base.param.PageForm;
|
||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
|
import com.budwk.app.flow.engine.model.ProcessModel;
|
||||||
|
import com.budwk.app.flow.engine.model.TaskModel;
|
||||||
|
import com.budwk.app.flow.entity.ProcessDefine;
|
||||||
|
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||||
|
import com.budwk.app.flow.service.ProcessDefineService;
|
||||||
|
import com.budwk.app.sys.models.Sys_dict;
|
||||||
|
import com.budwk.app.sys.services.SysDictService;
|
||||||
|
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
|
||||||
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.dao.Sqls;
|
||||||
|
import org.nutz.dao.sql.Sql;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
import org.nutz.mvc.annotation.Param;
|
import org.nutz.mvc.annotation.Param;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/platform/proposal/dashboard")
|
@At("/platform/proposal/dashboard")
|
||||||
@Api("提案数据看板")
|
@Api("提案数据看板")
|
||||||
@Ok("json:full")
|
@Ok("json:full")
|
||||||
public class ProposalDashboardController {
|
public class ProposalDashboardController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工作台表格允许排序的字段映射,值为查询中的真实字段或安全别名。
|
||||||
|
*/
|
||||||
|
private static final Map<String, String> DASHBOARD_ORDER_COLUMNS = Map.ofEntries(
|
||||||
|
Map.entry("code", "info.code"),
|
||||||
|
Map.entry("name", "info.name"),
|
||||||
|
Map.entry("createUserName", "info.createUserName"),
|
||||||
|
Map.entry("typeName", "typeName"),
|
||||||
|
Map.entry("delegationName", "delegationName"),
|
||||||
|
Map.entry("caseFilingResult", "info.caseFilingResult"),
|
||||||
|
Map.entry("isConsolidation", "info.isConsolidation"),
|
||||||
|
Map.entry("masterUnitName", "masterUnitName"),
|
||||||
|
Map.entry("slaveUnitNames", "slaveUnitNames"),
|
||||||
|
Map.entry("curTaskName", "curTaskName"),
|
||||||
|
Map.entry("instanceState", "instanceState")
|
||||||
|
);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ProposalCommonService proposalCommonService;
|
private ProposalCommonService proposalCommonService;
|
||||||
|
@Inject
|
||||||
|
private ProcessDefineService processDefineService;
|
||||||
|
@Inject
|
||||||
|
private SysDictService sysDictService;
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
|
||||||
@At("")
|
@At("")
|
||||||
@Ok("beetl:/platform/zhgh/democratic/proposal/dashboard/index.html")
|
@Ok("beetl:/platform/zhgh/democratic/proposal/dashboard/index.html")
|
||||||
@@ -25,30 +68,147 @@ public class ProposalDashboardController {
|
|||||||
public void index() {
|
public void index() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 接收届次ID查询卡片;Result.data 为卡片列表,含 count 或 todoCount/doneCount。
|
|
||||||
* @param sessionId 教代会届次ID;为空时卡片数量为零
|
|
||||||
* @return 统一结果对象,data 为各节点和立案结果统计
|
|
||||||
*/
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("proposal.dashboard")
|
@SaCheckPermission("proposal.dashboard")
|
||||||
public Result listNode(@Param("sessionId") String sessionId) {
|
public Result listNode(@Param("sessionId") String sessionId) {
|
||||||
return Result.success(proposalCommonService.listDashboardNodes(sessionId));
|
List<NutMap> nodes = new ArrayList<>();
|
||||||
|
|
||||||
|
// 提案总数
|
||||||
|
nodes.add(NutMap.NEW().addv("name", "提案总数").addv("id", "total").addv("type", "total").addv("count", 0));
|
||||||
|
|
||||||
|
// 节点
|
||||||
|
ProcessDefine define = processDefineService.getLastByName("JDHTA_NC");
|
||||||
|
ProcessModel processModel = processDefineService.processDefineToModel(define);
|
||||||
|
// 按流程图的连线关系遍历任务节点,保证看板展示顺序与流程图一致。
|
||||||
|
List<TaskModel> taskModels = processModel.getModels(TaskModel.class);
|
||||||
|
|
||||||
|
List<String> excludeNodes = new ArrayList<>(3);
|
||||||
|
excludeNodes.add("撰写提案");
|
||||||
|
excludeNodes.add("邀请附议人");
|
||||||
|
excludeNodes.add("提案附议");
|
||||||
|
|
||||||
|
// opinion_unit_reply、two_unit_reply 为临时屏蔽的承办单位答复节点,不在看板展示。
|
||||||
|
List<String> hiddenNodeIds = List.of("opinion_unit_reply", "two_unit_reply");
|
||||||
|
List<NutMap> taskNodes = new ArrayList<>(taskModels.stream()
|
||||||
|
.filter(taskModel -> !excludeNodes.contains(taskModel.getDisplayName()))
|
||||||
|
.filter(taskModel -> !hiddenNodeIds.contains(taskModel.getName()))
|
||||||
|
.map(taskModel -> NutMap.NEW()
|
||||||
|
.addv("name", taskModel.getDisplayName())
|
||||||
|
.addv("id", taskModel.getName())
|
||||||
|
.addv("type", "task")
|
||||||
|
.addv("count", 0)
|
||||||
|
.addv("todoCount", 0)
|
||||||
|
.addv("doneCount", 0)
|
||||||
|
).toList());
|
||||||
|
|
||||||
|
// 立案结果
|
||||||
|
List<Sys_dict> caseFilingResult = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT");
|
||||||
|
List<NutMap> caseFilingResultNodes = new ArrayList<>();
|
||||||
|
for (Sys_dict dict : caseFilingResult) {
|
||||||
|
caseFilingResultNodes.add(NutMap.NEW()
|
||||||
|
.addv("name", dict.getName())
|
||||||
|
.addv("id", dict.getCode())
|
||||||
|
.addv("type", "caseFilingResult")
|
||||||
|
.addv("count", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立案结果需展示在委员会确认承办单位之前,反馈评价统一固定到看板末尾。
|
||||||
|
List<NutMap> feedbackNodes = taskNodes.stream()
|
||||||
|
.filter(taskNode -> "feedback".equals(taskNode.getString("id")))
|
||||||
|
.toList();
|
||||||
|
taskNodes.removeIf(taskNode -> "feedback".equals(taskNode.getString("id")));
|
||||||
|
int committeeFilingUnitIndex = taskNodes.size();
|
||||||
|
for (int i = 0; i < taskNodes.size(); i++) {
|
||||||
|
if ("committeeFilingUnit".equals(taskNodes.get(i).getString("id"))) {
|
||||||
|
committeeFilingUnitIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
taskNodes.addAll(committeeFilingUnitIndex, caseFilingResultNodes);
|
||||||
|
nodes.addAll(taskNodes);
|
||||||
|
nodes.addAll(feedbackNodes);
|
||||||
|
|
||||||
|
Map<String, NutMap> taskProposalCounts = proposalCommonService.countDashboardTaskProposals(sessionId);
|
||||||
|
|
||||||
|
for (NutMap node : nodes) {
|
||||||
|
if (node.getString("type").equals("task")) {
|
||||||
|
NutMap taskCount = taskProposalCounts.get(node.getString("id"));
|
||||||
|
node.put("todoCount", taskCount == null ? 0 : taskCount.getLong("todoCount", 0L));
|
||||||
|
node.put("doneCount", taskCount == null ? 0 : taskCount.getLong("doneCount", 0L));
|
||||||
|
} else if (node.getString("type").equals("total")) {
|
||||||
|
int count = dao.count(ProposalInfo.class, Cnd.where(ProposalInfo::getSessionId, "=", sessionId));
|
||||||
|
node.put("count", count);
|
||||||
|
} else if (node.getString("type").equals("caseFilingResult")) {
|
||||||
|
int count = dao.count(ProposalInfo.class, Cnd.where(ProposalInfo::getSessionId, "=", sessionId).and(ProposalInfo::getCaseFilingResult, "=", node.getString("id")));
|
||||||
|
node.put("count", count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 接收看板筛选参数,返回与卡片统计范围一致的提案分页数据。
|
|
||||||
* @param pageForm pageNumber/pageSize 为页码和每页条数,pageOrderName/pageOrderBy 为排序参数
|
|
||||||
* @param sessionId 教代会届次ID;为空时返回空列表
|
|
||||||
* @param selectNodeId task 节点编码或立案结果字典编码
|
|
||||||
* @param selectNodeType task、total 或 caseFilingResult;为空时按总数查询
|
|
||||||
* @param selectNodeMode done 表示已办,其余值表示待办
|
|
||||||
* @return Result.data.list 为提案列表,Result.data.totalCount 为筛选后的总数
|
|
||||||
*/
|
|
||||||
@At
|
@At
|
||||||
@SaCheckPermission("proposal.dashboard")
|
@SaCheckPermission("proposal.dashboard")
|
||||||
public Result pageData(PageForm pageForm, String sessionId, String selectNodeId, String selectNodeType, String selectNodeMode) {
|
public Result pageData(PageForm pageForm, String sessionId, String selectNodeId, String selectNodeType, String selectNodeMode) {
|
||||||
return Result.success(proposalCommonService.pageDashboardData(pageForm, sessionId,
|
Sql sql = Sqls.create("""
|
||||||
selectNodeId, selectNodeType, selectNodeMode));
|
SELECT
|
||||||
|
info.*,
|
||||||
|
type.name AS typeName,
|
||||||
|
tcs.fullName AS sessionName,
|
||||||
|
tcd.`name` AS delegationName,
|
||||||
|
ins.id AS instanceId,
|
||||||
|
ins.businessNo,
|
||||||
|
ins.state instanceState,
|
||||||
|
ins.variable instanceVariable,
|
||||||
|
ins.processDefineId instanceProcessDefineId,
|
||||||
|
t.displayName curTaskName,
|
||||||
|
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
|
||||||
|
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
|
||||||
|
FROM
|
||||||
|
proposal_info info
|
||||||
|
LEFT JOIN proposal_type type on type.id = info.typeId
|
||||||
|
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
|
||||||
|
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
|
||||||
|
LEFT JOIN teacher_congress_delegate tcde on tcde.loginName = info.createUserLoginName
|
||||||
|
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
||||||
|
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
|
||||||
|
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = @taskState
|
||||||
|
$condition
|
||||||
|
""");
|
||||||
|
Cnd cnd = Cnd.NEW();
|
||||||
|
cnd.and("info.sessionId", "=", sessionId);
|
||||||
|
cnd.groupBy("info.id");
|
||||||
|
|
||||||
|
// 默认显示进行中的任务
|
||||||
|
sql.setParam("taskState", ProcessTaskStateEnum.DOING.getCode());
|
||||||
|
|
||||||
|
if (StrUtil.isNotBlank(selectNodeType) && StrUtil.isNotBlank(selectNodeId)) {
|
||||||
|
switch (selectNodeType) {
|
||||||
|
case "task":
|
||||||
|
cnd.and("t.taskName", "=", selectNodeId);
|
||||||
|
|
||||||
|
if (selectNodeMode.equals("todo")) {
|
||||||
|
sql.setParam("taskState", ProcessTaskStateEnum.DOING.getCode());
|
||||||
|
} else {
|
||||||
|
sql.setParam("taskState", ProcessTaskStateEnum.FINISHED.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "total":
|
||||||
|
break;
|
||||||
|
case "caseFilingResult":
|
||||||
|
cnd.and("info.caseFilingResult", "=", selectNodeId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据工作台字段白名单追加排序,未选择排序时保持原有查询顺序。
|
||||||
|
proposalCommonService.applySafePageOrder(cnd, pageForm, DASHBOARD_ORDER_COLUMNS);
|
||||||
|
sql.setCondition(cnd);
|
||||||
|
Pagination pagination = proposalCommonService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||||
|
return Result.success(pagination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
-18
@@ -288,22 +288,4 @@ public interface ProposalCommonService extends BaseService<ProposalInfo> {
|
|||||||
* @return 键为流程节点编码、值为待办和已办数量的统计结果
|
* @return 键为流程节点编码、值为待办和已办数量的统计结果
|
||||||
*/
|
*/
|
||||||
Map<String, NutMap> countDashboardTaskProposals(String sessionId);
|
Map<String, NutMap> countDashboardTaskProposals(String sessionId);
|
||||||
/**
|
|
||||||
* 查询看板卡片,统计范围与列表一致。
|
|
||||||
* @param sessionId 届次ID;为空时数量为零
|
|
||||||
* @return 包含节点编码、类型、名称和统计数量的卡片列表
|
|
||||||
*/
|
|
||||||
List<NutMap> listDashboardNodes(String sessionId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询看板提案分页数据。
|
|
||||||
* @param pageForm 分页及排序参数
|
|
||||||
* @param sessionId 届次ID;为空时返回空列表
|
|
||||||
* @param selectNodeId 流程节点编码或立案结果编码
|
|
||||||
* @param selectNodeType task、total 或 caseFilingResult
|
|
||||||
* @param selectNodeMode done 表示已办,其余值表示待办
|
|
||||||
* @return 分页对象,list 为提案记录,totalCount 为相同条件下的提案总数
|
|
||||||
*/
|
|
||||||
Pagination pageDashboardData(PageForm pageForm, String sessionId, String selectNodeId,
|
|
||||||
String selectNodeType, String selectNodeMode);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-238
@@ -23,10 +23,6 @@ import com.budwk.app.base.utils.PageUtil;
|
|||||||
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
|
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
|
||||||
import com.budwk.app.flow.constant.FlowConst;
|
import com.budwk.app.flow.constant.FlowConst;
|
||||||
import com.budwk.app.flow.engine.FlowEngine;
|
import com.budwk.app.flow.engine.FlowEngine;
|
||||||
import com.budwk.app.flow.engine.model.ProcessModel;
|
|
||||||
import com.budwk.app.flow.engine.model.TaskModel;
|
|
||||||
import com.budwk.app.flow.entity.ProcessDefine;
|
|
||||||
import com.budwk.app.flow.service.ProcessDefineService;
|
|
||||||
import com.budwk.app.flow.engine.util.FlowUtil;
|
import com.budwk.app.flow.engine.util.FlowUtil;
|
||||||
import com.budwk.app.flow.entity.ProcessInstance;
|
import com.budwk.app.flow.entity.ProcessInstance;
|
||||||
import com.budwk.app.flow.entity.ProcessTask;
|
import com.budwk.app.flow.entity.ProcessTask;
|
||||||
@@ -86,24 +82,6 @@ import java.util.stream.Collectors;
|
|||||||
@IocBean(args = {"refer:dao"})
|
@IocBean(args = {"refer:dao"})
|
||||||
public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalCommonService {
|
public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalCommonService {
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作台表格允许排序的字段映射,值为查询中的真实字段或安全别名。
|
|
||||||
*/
|
|
||||||
private static final Map<String, String> DASHBOARD_ORDER_COLUMNS = Map.ofEntries(
|
|
||||||
Map.entry("code", "info.code"),
|
|
||||||
Map.entry("name", "info.name"),
|
|
||||||
Map.entry("createUserName", "info.createUserName"),
|
|
||||||
Map.entry("typeName", "typeName"),
|
|
||||||
Map.entry("delegationName", "delegationName"),
|
|
||||||
Map.entry("caseFilingResult", "info.caseFilingResult"),
|
|
||||||
Map.entry("caseFilingType", "info.caseFilingType"),
|
|
||||||
Map.entry("isConsolidation", "info.isConsolidation"),
|
|
||||||
Map.entry("masterUnitName", "masterUnitName"),
|
|
||||||
Map.entry("slaveUnitNames", "slaveUnitNames"),
|
|
||||||
Map.entry("curTaskName", "curTaskName"),
|
|
||||||
Map.entry("instanceState", "instanceState")
|
|
||||||
);
|
|
||||||
|
|
||||||
/** 确定立案提案的 Word 模板编码。 */
|
/** 确定立案提案的 Word 模板编码。 */
|
||||||
private static final String PROPOSAL_CONFIRM_FILING_TEMPLATE_CODE = "proposal_confirm_filing";
|
private static final String PROPOSAL_CONFIRM_FILING_TEMPLATE_CODE = "proposal_confirm_filing";
|
||||||
|
|
||||||
@@ -209,8 +187,6 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
|||||||
@Inject
|
@Inject
|
||||||
private SysDictService sysDictService;
|
private SysDictService sysDictService;
|
||||||
@Inject
|
@Inject
|
||||||
private ProcessDefineService processDefineService;
|
|
||||||
@Inject
|
|
||||||
private SysUserService sysUserService;
|
private SysUserService sysUserService;
|
||||||
@Inject
|
@Inject
|
||||||
private TeacherCongressDelegateService teacherCongressDelegateService;
|
private TeacherCongressDelegateService teacherCongressDelegateService;
|
||||||
@@ -530,216 +506,6 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
|||||||
return sql;
|
return sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取提案看板卡片,沿用现有节点顺序,数量与下方列表共用同一数据范围。
|
|
||||||
* @param sessionId 教代会届次ID;为空时所有卡片数量为零
|
|
||||||
* @return 卡片列表;任务卡片含 todoCount/doneCount,其他卡片含 count
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<NutMap> listDashboardNodes(String sessionId) {
|
|
||||||
List<NutMap> nodes = new ArrayList<>();
|
|
||||||
|
|
||||||
// 提案总数
|
|
||||||
nodes.add(NutMap.NEW().addv("name", "提案总数").addv("id", "total").addv("type", "total").addv("count", 0));
|
|
||||||
|
|
||||||
// 节点
|
|
||||||
ProcessDefine define = processDefineService.getLastByName("JDHTA_NC");
|
|
||||||
ProcessModel processModel = processDefineService.processDefineToModel(define);
|
|
||||||
// 按流程图的连线关系遍历任务节点,保证看板展示顺序与流程图一致。
|
|
||||||
List<TaskModel> taskModels = processModel.getModels(TaskModel.class);
|
|
||||||
|
|
||||||
List<String> excludeNodes = new ArrayList<>(3);
|
|
||||||
excludeNodes.add("撰写提案");
|
|
||||||
excludeNodes.add("邀请附议人");
|
|
||||||
excludeNodes.add("提案附议");
|
|
||||||
|
|
||||||
// opinion_unit_reply、two_unit_reply 为临时屏蔽的承办单位答复节点,不在看板展示。
|
|
||||||
List<String> hiddenNodeIds = List.of("opinion_unit_reply", "two_unit_reply");
|
|
||||||
List<NutMap> taskNodes = new ArrayList<>(taskModels.stream()
|
|
||||||
.filter(taskModel -> !excludeNodes.contains(taskModel.getDisplayName()))
|
|
||||||
.filter(taskModel -> !hiddenNodeIds.contains(taskModel.getName()))
|
|
||||||
.map(taskModel -> NutMap.NEW()
|
|
||||||
.addv("name", taskModel.getDisplayName())
|
|
||||||
.addv("id", taskModel.getName())
|
|
||||||
.addv("type", "task")
|
|
||||||
.addv("count", 0)
|
|
||||||
.addv("todoCount", 0)
|
|
||||||
.addv("doneCount", 0)
|
|
||||||
).toList());
|
|
||||||
|
|
||||||
// 立案结果
|
|
||||||
List<Sys_dict> caseFilingResult = sysDictService.getSubListByCode("PROPOSAL_CASE_FILING_RESULT");
|
|
||||||
List<NutMap> caseFilingResultNodes = new ArrayList<>();
|
|
||||||
for (Sys_dict dict : caseFilingResult) {
|
|
||||||
caseFilingResultNodes.add(NutMap.NEW()
|
|
||||||
.addv("name", dict.getName())
|
|
||||||
.addv("id", dict.getCode())
|
|
||||||
.addv("type", "caseFilingResult")
|
|
||||||
.addv("count", 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 立案结果需展示在委员会确认承办单位之前,反馈评价统一固定到看板末尾。
|
|
||||||
List<NutMap> feedbackNodes = taskNodes.stream()
|
|
||||||
.filter(taskNode -> "feedback".equals(taskNode.getString("id")))
|
|
||||||
.toList();
|
|
||||||
taskNodes.removeIf(taskNode -> "feedback".equals(taskNode.getString("id")));
|
|
||||||
int committeeFilingUnitIndex = taskNodes.size();
|
|
||||||
for (int i = 0; i < taskNodes.size(); i++) {
|
|
||||||
if ("committeeFilingUnit".equals(taskNodes.get(i).getString("id"))) {
|
|
||||||
committeeFilingUnitIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
taskNodes.addAll(committeeFilingUnitIndex, caseFilingResultNodes);
|
|
||||||
nodes.addAll(taskNodes);
|
|
||||||
nodes.addAll(feedbackNodes);
|
|
||||||
|
|
||||||
Map<String, Long> categoryCounts = countDashboardCategories(sessionId);
|
|
||||||
Map<String, NutMap> taskProposalCounts = countDashboardTaskProposals(sessionId);
|
|
||||||
|
|
||||||
for (NutMap node : nodes) {
|
|
||||||
if (node.getString("type").equals("task")) {
|
|
||||||
NutMap taskCount = taskProposalCounts.get(node.getString("id"));
|
|
||||||
node.put("todoCount", taskCount == null ? 0 : taskCount.getLong("todoCount", 0L));
|
|
||||||
node.put("doneCount", taskCount == null ? 0 : taskCount.getLong("doneCount", 0L));
|
|
||||||
} else if (node.getString("type").equals("total")) {
|
|
||||||
node.put("count", categoryCounts.getOrDefault("total", 0L));
|
|
||||||
} else if (node.getString("type").equals("caseFilingResult")) {
|
|
||||||
node.put("count", categoryCounts.getOrDefault(node.getString("id"), 0L));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询看板提案列表,统一使用附议完成后的提案范围,按提案ID去重。
|
|
||||||
* @param pageForm 分页和白名单排序参数
|
|
||||||
* @param sessionId 教代会届次ID;为空时返回空列表
|
|
||||||
* @param selectNodeId 节点编码或立案结果字典编码
|
|
||||||
* @param selectNodeType task、total、caseFilingResult;为空时查询总数列表
|
|
||||||
* @param selectNodeMode done 查询已办任务,其余值查询待办任务
|
|
||||||
* @return 含 list 和 totalCount 的分页结果
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Pagination pageDashboardData(PageForm pageForm, String sessionId, String selectNodeId,
|
|
||||||
String selectNodeType, String selectNodeMode) {
|
|
||||||
Sql sql = Sqls.create("""
|
|
||||||
SELECT
|
|
||||||
info.*,
|
|
||||||
type.name AS typeName,
|
|
||||||
tcs.fullName AS sessionName,
|
|
||||||
tcd.`name` AS delegationName,
|
|
||||||
ins.id AS instanceId,
|
|
||||||
ins.businessNo,
|
|
||||||
ins.state instanceState,
|
|
||||||
ins.variable instanceVariable,
|
|
||||||
ins.processDefineId instanceProcessDefineId,
|
|
||||||
t.displayName curTaskName,
|
|
||||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName,
|
|
||||||
GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames
|
|
||||||
FROM
|
|
||||||
proposal_info info
|
|
||||||
LEFT JOIN proposal_type type on type.id = info.typeId
|
|
||||||
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
|
|
||||||
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
|
|
||||||
LEFT JOIN teacher_congress_delegate tcde on tcde.loginName = info.createUserLoginName
|
|
||||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = info.id
|
|
||||||
LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id
|
|
||||||
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = @taskState
|
|
||||||
$condition
|
|
||||||
""");
|
|
||||||
Cnd cnd = buildDashboardCondition(sessionId);
|
|
||||||
cnd.groupBy("info.id");
|
|
||||||
|
|
||||||
// 默认显示进行中的任务
|
|
||||||
sql.setParam("taskState", ProcessTaskStateEnum.DOING.getCode());
|
|
||||||
|
|
||||||
if (StrUtil.isNotBlank(selectNodeType) && StrUtil.isNotBlank(selectNodeId)) {
|
|
||||||
switch (selectNodeType) {
|
|
||||||
case "task":
|
|
||||||
cnd.and("t.taskName", "=", selectNodeId);
|
|
||||||
|
|
||||||
if (!"done".equals(selectNodeMode)) {
|
|
||||||
sql.setParam("taskState", ProcessTaskStateEnum.DOING.getCode());
|
|
||||||
} else {
|
|
||||||
sql.setParam("taskState", ProcessTaskStateEnum.FINISHED.getCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "total":
|
|
||||||
break;
|
|
||||||
case "caseFilingResult":
|
|
||||||
cnd.and("info.caseFilingResult", "=", selectNodeId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只接受白名单排序字段,复用现有分页能力。
|
|
||||||
applySafePageOrder(cnd, pageForm, DASHBOARD_ORDER_COLUMNS);
|
|
||||||
sql.setCondition(cnd);
|
|
||||||
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 统计看板总数及立案结果;一次按结果分组后求和,确保卡片与列表口径一致。
|
|
||||||
* @param sessionId 教代会届次ID
|
|
||||||
* @return total 为提案总数,其他键为立案结果编码
|
|
||||||
*/
|
|
||||||
private Map<String, Long> countDashboardCategories(String sessionId) {
|
|
||||||
Sql sql = Sqls.create("SELECT info.caseFilingResult, COUNT(1) AS proposalCount FROM proposal_info info $condition");
|
|
||||||
Cnd cnd = buildDashboardCondition(sessionId);
|
|
||||||
cnd.groupBy("info.caseFilingResult");
|
|
||||||
sql.setCondition(cnd);
|
|
||||||
Map<String, Long> counts = new HashMap<>();
|
|
||||||
long total = 0L;
|
|
||||||
for (NutMap row : listMap(sql)) {
|
|
||||||
long count = row.getLong("proposalCount", 0L);
|
|
||||||
total += count;
|
|
||||||
if (StrUtil.isNotBlank(row.getString("caseFilingResult"))) {
|
|
||||||
counts.put(row.getString("caseFilingResult"), count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
counts.put("total", total);
|
|
||||||
return counts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建看板统一范围:迁入提案按参考业务阶段排除草稿、附议阶段;新提案按实际WF任务判断。
|
|
||||||
* 历史迁入实例可能缺少当前任务,不能仅因缺任务而漏算已进入审核的历史提案。
|
|
||||||
* 新WF提案的旧 stateCode 不随每个节点更新,必须读取有效审核任务,并排除已退回撰写/附议的实例。
|
|
||||||
* @param sessionId 届次ID;清空选择时返回空范围,避免跨届次查询
|
|
||||||
* @return 仅包含筛选条件、不包含排序和分组的 Cnd
|
|
||||||
*/
|
|
||||||
private Cnd buildDashboardCondition(String sessionId) {
|
|
||||||
Cnd cnd = Cnd.where("info.sessionId", "=", StrUtil.blankToDefault(sessionId, ""));
|
|
||||||
cnd.and(new Static("""
|
|
||||||
EXISTS (
|
|
||||||
SELECT 1 FROM wf_process_instance scopeIns
|
|
||||||
WHERE scopeIns.businessNo = info.id
|
|
||||||
AND (
|
|
||||||
(JSON_UNQUOTE(JSON_EXTRACT(scopeIns.variable, '$.legacyV3')) = 'true' AND info.stateCode >= 200)
|
|
||||||
OR (
|
|
||||||
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(scopeIns.variable, '$.legacyV3')), 'false') <> 'true'
|
|
||||||
AND EXISTS (
|
|
||||||
SELECT 1 FROM wf_process_task scopeTask
|
|
||||||
WHERE scopeTask.processInstanceId = scopeIns.id
|
|
||||||
AND scopeTask.taskName NOT IN ('startTask', 'invite', 'second')
|
|
||||||
AND scopeTask.taskState IN (10, 20)
|
|
||||||
)
|
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM wf_process_task earlyTask
|
|
||||||
WHERE earlyTask.processInstanceId = scopeIns.id
|
|
||||||
AND earlyTask.taskName IN ('startTask', 'invite', 'second')
|
|
||||||
AND earlyTask.taskState = 10
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"""));
|
|
||||||
return cnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按提案ID去重统计数据看板的流程节点数量。
|
* 按提案ID去重统计数据看板的流程节点数量。
|
||||||
* 同一提案因撤回、退回等再次经过同一节点时,任务表会保留多条已办记录;
|
* 同一提案因撤回、退回等再次经过同一节点时,任务表会保留多条已办记录;
|
||||||
@@ -758,11 +524,10 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
|
|||||||
FROM wf_process_task t
|
FROM wf_process_task t
|
||||||
INNER JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
INNER JOIN wf_process_instance ins ON ins.id = t.processInstanceId
|
||||||
INNER JOIN proposal_info info ON info.id = ins.businessNo
|
INNER JOIN proposal_info info ON info.id = ins.businessNo
|
||||||
$condition
|
WHERE info.sessionId = @sessionId
|
||||||
|
GROUP BY t.taskName
|
||||||
""");
|
""");
|
||||||
Cnd cnd = buildDashboardCondition(sessionId);
|
sql.setParam("sessionId", sessionId);
|
||||||
cnd.groupBy("t.taskName");
|
|
||||||
sql.setCondition(cnd);
|
|
||||||
sql.setParam("doingState", ProcessTaskStateEnum.DOING.getCode());
|
sql.setParam("doingState", ProcessTaskStateEnum.DOING.getCode());
|
||||||
sql.setParam("finishedState", ProcessTaskStateEnum.FINISHED.getCode());
|
sql.setParam("finishedState", ProcessTaskStateEnum.FINISHED.getCode());
|
||||||
List<NutMap> taskCounts = listMap(sql);
|
List<NutMap> taskCounts = listMap(sql);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
|
||||||
import org.nutz.dao.entity.annotation.*;
|
import org.nutz.dao.entity.annotation.*;
|
||||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||||
|
|
||||||
@@ -161,13 +160,13 @@ public class MallGoods extends BaseModel {
|
|||||||
//循环sku,判定sku是否有效
|
//循环sku,判定sku是否有效
|
||||||
for (Map<String, Object> sku : goodsOperationDTO.getSkuList()) {
|
for (Map<String, Object> sku : goodsOperationDTO.getSkuList()) {
|
||||||
//判定参数不能为空
|
//判定参数不能为空
|
||||||
if ((!sku.containsKey("price") || StringUtil.isEmpty(sku.get("price").toString()) || Convert.toDouble(sku.get("price")) <= 0)) {
|
if ((!sku.containsKey("price") || CharSequenceUtil.isEmpty(sku.get("price").toString()) || Convert.toDouble(sku.get("price")) <= 0)) {
|
||||||
throw new BaseException("商品SKU价格不能小于等于0");
|
throw new BaseException("商品SKU价格不能小于等于0");
|
||||||
}
|
}
|
||||||
// if ((!sku.containsKey("cost") || StringUtil.isEmpty(sku.get("cost").toString()) || Convert.toDouble(sku.get("cost")) <= 0)) {
|
// if ((!sku.containsKey("cost") || StringUtil.isEmpty(sku.get("cost").toString()) || Convert.toDouble(sku.get("cost")) <= 0)) {
|
||||||
// throw new BaseException("商品SKU成本价不能小于等于0");
|
// throw new BaseException("商品SKU成本价不能小于等于0");
|
||||||
// }
|
// }
|
||||||
if (!sku.containsKey("quantity") || StringUtil.isEmpty(sku.get("quantity").toString()) || Convert.toInt(sku.get("quantity").toString()) < 0) {
|
if (!sku.containsKey("quantity") || CharSequenceUtil.isEmpty(sku.get("quantity").toString()) || Convert.toInt(sku.get("quantity").toString()) < 0) {
|
||||||
throw new BaseException("商品库存数量不能为负数");
|
throw new BaseException("商品库存数量不能为负数");
|
||||||
}
|
}
|
||||||
sku.values().forEach(i -> {
|
sku.values().forEach(i -> {
|
||||||
|
|||||||
+3
-1
@@ -127,7 +127,6 @@ public class CondolenceApplyController {
|
|||||||
username as userName,
|
username as userName,
|
||||||
loginname as loginName,
|
loginname as loginName,
|
||||||
sex,
|
sex,
|
||||||
mobile,
|
|
||||||
technicalTitle,
|
technicalTitle,
|
||||||
IFNULL(unitname, '暂无') as unitName,
|
IFNULL(unitname, '暂无') as unitName,
|
||||||
unitid as unitId,
|
unitid as unitId,
|
||||||
@@ -146,6 +145,9 @@ public class CondolenceApplyController {
|
|||||||
}
|
}
|
||||||
if(!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
if(!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) {
|
||||||
if(AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
|
if(AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_ADMIN.name(), RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
|
||||||
|
if (StrUtil.isBlank(SecurityUtil.getUnionId())) {
|
||||||
|
return Result.success(java.util.Collections.emptyList());
|
||||||
|
}
|
||||||
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
|
cnd.and(View_user::getUnionId, "=", SecurityUtil.getUnionId());
|
||||||
} else {
|
} else {
|
||||||
cnd.and(View_user::getId, "=", SecurityUtil.getUserId());
|
cnd.and(View_user::getId, "=", SecurityUtil.getUserId());
|
||||||
|
|||||||
@@ -16,16 +16,20 @@ sm4cbc:
|
|||||||
keyHex: 00112233445566778899aabbccddeeff
|
keyHex: 00112233445566778899aabbccddeeff
|
||||||
ivHex: 0102030405060708090a0b0c0d0e0f10
|
ivHex: 0102030405060708090a0b0c0d0e0f10
|
||||||
|
|
||||||
jetty:
|
tomcat:
|
||||||
contextPath: /
|
contextPath: ""
|
||||||
threadpool:
|
# 容器错误响应不披露版本;Session Cookie 保持 HttpOnly。
|
||||||
idleTimeout: 60000
|
session:
|
||||||
minThreads: 10
|
cookie:
|
||||||
|
httponly: true
|
||||||
|
executor:
|
||||||
|
maxIdleTime: 60000
|
||||||
|
minSpareThreads: 10
|
||||||
maxThreads: 500
|
maxThreads: 500
|
||||||
page:
|
page:
|
||||||
403: /error/403.html
|
403: /__container_error
|
||||||
404: /error/404.html
|
404: /__container_error
|
||||||
500: /error/500.html
|
500: /__container_error
|
||||||
#结合ftp使用,或用nginx代理ftp路径
|
#结合ftp使用,或用nginx代理ftp路径
|
||||||
#staticPath: /Users/wizzer/temp/files
|
#staticPath: /Users/wizzer/temp/files
|
||||||
#开发模式静态资源
|
#开发模式静态资源
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ var ioc={
|
|||||||
// 单个文件最大尺寸(大约的值,单位为字节,即 2097152 为 2M)
|
// 单个文件最大尺寸(大约的值,单位为字节,即 2097152 为 2M)
|
||||||
maxFileSize : 2097152,
|
maxFileSize : 2097152,
|
||||||
// 正则表达式匹配可以支持的文件名
|
// 正则表达式匹配可以支持的文件名
|
||||||
nameFilter : '^(.+[.])(gif|jpg|png|svg)$'
|
nameFilter : '(?i)^(.+[.])(gif|jpg|jpeg|png)$'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
imageUpload : {
|
imageUpload : {
|
||||||
@@ -32,7 +32,7 @@ var ioc={
|
|||||||
// 单个文件最大尺寸(大约的值,单位为字节,即 209715200 为 200M)
|
// 单个文件最大尺寸(大约的值,单位为字节,即 209715200 为 200M)
|
||||||
maxFileSize : 209715200,
|
maxFileSize : 209715200,
|
||||||
// 正则表达式匹配可以支持的文件名
|
// 正则表达式匹配可以支持的文件名
|
||||||
nameFilter : '^(.+[.])(gif||svg|jpg|png|jpeg|doc|docx|xls|xlsx|rar|zip|7z|txt|pdf|pptx|ppt|mp4)$'
|
nameFilter : '(?i)^(.+[.])(gif|jpg|png|jpeg|doc|docx|xls|xlsx|rar|zip|7z|txt|pdf|pptx|ppt|mp4)$'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fileUpload : {
|
fileUpload : {
|
||||||
|
|||||||
+1
-33
@@ -187,31 +187,6 @@ layout("/layouts/platform.html"){
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item label="生日"
|
|
||||||
v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
|
||||||
<el-form-item prop="condolenceBirthday" label="生日">
|
|
||||||
<el-input
|
|
||||||
:value="formData.condolenceBirthday && $moment(formData.condolenceBirthday).isValid() ?$moment(formData.condolenceBirthday).format('YYYY-MM-DD') : ''"
|
|
||||||
show-word-limit placeholder="请输入生日" disabled></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="身份证号"
|
|
||||||
v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
|
||||||
<el-form-item prop="condolenceIdCard" label="身份证号">
|
|
||||||
<el-input v-model="formData.condolenceIdCard" show-word-limit
|
|
||||||
placeholder="请输入身份证号" disabled></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="联系方式"
|
|
||||||
v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
|
||||||
<el-form-item prop="condolenceMobile" label="联系方式">
|
|
||||||
<el-input v-model="formData.condolenceMobile" show-word-limit
|
|
||||||
placeholder="请输入联系方式" disabled></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="慰问类型"
|
<el-descriptions-item label="慰问类型"
|
||||||
v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
v-if="formData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
||||||
<el-form-item label="慰问类型" prop="condolenceTypeId">
|
<el-form-item label="慰问类型" prop="condolenceTypeId">
|
||||||
@@ -816,7 +791,6 @@ layout("/layouts/platform.html"){
|
|||||||
trigger: ["change", "blur"]
|
trigger: ["change", "blur"]
|
||||||
}, {pattern: /^([1-9]{1})(\d{15}|\d{18})$/, message: "请输入正确的银行卡号", trigger: "blur"}],
|
}, {pattern: /^([1-9]{1})(\d{15}|\d{18})$/, message: "请输入正确的银行卡号", trigger: "blur"}],
|
||||||
bankOfDeposit: [{required: true, message: "请填写开户行", trigger: ["change", "blur"]}],
|
bankOfDeposit: [{required: true, message: "请填写开户行", trigger: ["change", "blur"]}],
|
||||||
condolenceMobile: [{pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur"}],
|
|
||||||
condolenceTime: [{required: true, message: "请选择慰问时间", trigger: ["change", "blur"]}],
|
condolenceTime: [{required: true, message: "请选择慰问时间", trigger: ["change", "blur"]}],
|
||||||
// invoiceNumber: [{required: true, message: "请填写发票张数", trigger: ["change", "blur"]}],
|
// invoiceNumber: [{required: true, message: "请填写发票张数", trigger: ["change", "blur"]}],
|
||||||
// invoice: [{required: true, message: "请填写发票号码", trigger: ["change", "blur"]}],
|
// invoice: [{required: true, message: "请填写发票号码", trigger: ["change", "blur"]}],
|
||||||
@@ -1187,10 +1161,7 @@ layout("/layouts/platform.html"){
|
|||||||
unionId,
|
unionId,
|
||||||
unionName,
|
unionName,
|
||||||
unionCode,
|
unionCode,
|
||||||
sex,
|
sex
|
||||||
birthday,
|
|
||||||
idCard,
|
|
||||||
mobile
|
|
||||||
} = user
|
} = user
|
||||||
this.$set(this.formData, "condolenceUserName", userName)
|
this.$set(this.formData, "condolenceUserName", userName)
|
||||||
this.$set(this.formData, "condolenceLoginName", loginName)
|
this.$set(this.formData, "condolenceLoginName", loginName)
|
||||||
@@ -1200,9 +1171,6 @@ layout("/layouts/platform.html"){
|
|||||||
this.$set(this.formData, "condolenceUnionName", unionName)
|
this.$set(this.formData, "condolenceUnionName", unionName)
|
||||||
this.$set(this.formData, "condolenceUnionCode", unionCode)
|
this.$set(this.formData, "condolenceUnionCode", unionCode)
|
||||||
this.$set(this.formData, "condolenceSex", sex)
|
this.$set(this.formData, "condolenceSex", sex)
|
||||||
this.$set(this.formData, "condolenceBirthday", birthday)
|
|
||||||
this.$set(this.formData, "condolenceIdCard", idCard)
|
|
||||||
this.$set(this.formData, "condolenceMobile", mobile)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
typeChange(val) {
|
typeChange(val) {
|
||||||
|
|||||||
@@ -86,18 +86,6 @@ const unionReimburseInfo = {
|
|||||||
<span>{{ viewData.condolenceSex }}</span>
|
<span>{{ viewData.condolenceSex }}</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item label="生日" v-if="viewData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
|
||||||
<span>{{ viewData.condolenceBirthday && $moment(viewData.condolenceBirthday).isValid() ? $moment(viewData.condolenceBirthday).format('YYYY-MM-DD') : '' }}</span>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="身份证号" v-if="viewData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
|
||||||
<span>{{ viewData.condolenceIdCard }}</span>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="联系方式" v-if="viewData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
|
||||||
<span>{{ viewData.condolenceMobile }}</span>
|
|
||||||
</el-descriptions-item>
|
|
||||||
|
|
||||||
<el-descriptions-item label="慰问类型" v-if="viewData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
<el-descriptions-item label="慰问类型" v-if="viewData.reimburseProject === 'UNION_REIMBURSE_PROJECT_1'">
|
||||||
<span>{{ viewData.typeName }}</span>
|
<span>{{ viewData.typeName }}</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|||||||
@@ -206,15 +206,6 @@ layout("/layouts/platform.html"){
|
|||||||
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
|
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" :value="row.caseFilingResult"></dict-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<!-- 是否重点复用立案类型字段;未立案与类型未填写分开展示,避免空值被误认为普通提案。 -->
|
|
||||||
<el-table-column label="是否重点" prop="caseFilingType" sortable="custom" width="110">
|
|
||||||
<template slot-scope="{row}">
|
|
||||||
<dict-tag v-if="row.caseFilingResult === 'CONFIRM_FILING' && row.caseFilingType"
|
|
||||||
:options="dict.type.PROPOSAL_CASE_FILING_TYPE" :value="row.caseFilingType"></dict-tag>
|
|
||||||
<span v-else-if="!row.caseFilingResult || row.caseFilingResult === 'CONFIRM_FILING'">未确定</span>
|
|
||||||
<span v-else>否</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
|
<el-table-column label="是否并案" prop="isConsolidation" sortable="custom" width="100">
|
||||||
<template scope="{row}">
|
<template scope="{row}">
|
||||||
<el-tag size="mini" v-if="row.isConsolidation" type="success">是</el-tag>
|
<el-tag size="mini" v-if="row.isConsolidation" type="success">是</el-tag>
|
||||||
@@ -250,7 +241,7 @@ layout("/layouts/platform.html"){
|
|||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
dicts: ["PROPOSAL_TYPE", "PROPOSAL_CASE_FILING_RESULT", "PROPOSAL_CASE_FILING_TYPE"],
|
dicts: ["PROPOSAL_TYPE", "PROPOSAL_CASE_FILING_RESULT"],
|
||||||
components: {
|
components: {
|
||||||
"proposal-info": PROPOSAL_INFO
|
"proposal-info": PROPOSAL_INFO
|
||||||
},
|
},
|
||||||
@@ -261,8 +252,6 @@ layout("/layouts/platform.html"){
|
|||||||
nodes: [],
|
nodes: [],
|
||||||
nodeDisplayModes: {}, // 存储每个节点的显示模式
|
nodeDisplayModes: {}, // 存储每个节点的显示模式
|
||||||
pageForm: {
|
pageForm: {
|
||||||
sessionId: null,
|
|
||||||
selectNodeMode: "todo",
|
|
||||||
selectNodeId: null,
|
selectNodeId: null,
|
||||||
selectNodeType: null,
|
selectNodeType: null,
|
||||||
pageSize: 5
|
pageSize: 5
|
||||||
@@ -279,10 +268,6 @@ layout("/layouts/platform.html"){
|
|||||||
|
|
||||||
// 届次改变
|
// 届次改变
|
||||||
sessionChange() {
|
sessionChange() {
|
||||||
// 切换届次后回到总数卡片,避免沿用上一届次的分类筛选。
|
|
||||||
this.$set(this.pageForm, "selectNodeId", null)
|
|
||||||
this.$set(this.pageForm, "selectNodeType", null)
|
|
||||||
this.$set(this.pageForm, "selectNodeMode", "todo")
|
|
||||||
this.getNodes()
|
this.getNodes()
|
||||||
this.doSearch()
|
this.doSearch()
|
||||||
},
|
},
|
||||||
@@ -291,8 +276,8 @@ layout("/layouts/platform.html"){
|
|||||||
listSession() {
|
listSession() {
|
||||||
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
this.$set(this, "sessionOptions", res.data || [])
|
this.sessionOptions = res.data
|
||||||
if (this.sessionOptions.length) {
|
if (this.sessionOptions) {
|
||||||
this.$set(this.pageForm, "sessionId", this.sessionOptions[0].id)
|
this.$set(this.pageForm, "sessionId", this.sessionOptions[0].id)
|
||||||
this.getNodes()
|
this.getNodes()
|
||||||
this.doSearch()
|
this.doSearch()
|
||||||
@@ -305,7 +290,7 @@ layout("/layouts/platform.html"){
|
|||||||
getNodes() {
|
getNodes() {
|
||||||
this.$axios.post('/platform/proposal/dashboard/listNode', {sessionId: this.pageForm.sessionId}).then(res => {
|
this.$axios.post('/platform/proposal/dashboard/listNode', {sessionId: this.pageForm.sessionId}).then(res => {
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
this.$set(this, "nodes", res.data || [])
|
this.nodes = res.data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -318,9 +303,9 @@ layout("/layouts/platform.html"){
|
|||||||
|
|
||||||
// 节点选择
|
// 节点选择
|
||||||
selectNode(node, index) {
|
selectNode(node, index) {
|
||||||
this.$set(this.pageForm, "selectNodeId", node.id)
|
this.pageForm.selectNodeId = node.id
|
||||||
this.$set(this.pageForm, "selectNodeType", node.type)
|
this.pageForm.selectNodeType = node.type
|
||||||
this.$set(this.pageForm, "selectNodeMode", this.getNodeDisplayMode(node.id))
|
this.pageForm.selectNodeMode = this.getNodeDisplayMode(node.id)
|
||||||
this.doSearch()
|
this.doSearch()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -297,11 +297,10 @@ layout("/layouts/platform.html"){
|
|||||||
{{memberNumberData.memberNum}}
|
{{memberNumberData.memberNum}}
|
||||||
<span style="font-size: 14px"> 人</span>
|
<span style="font-size: 14px"> 人</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-meta" style="text-align: right" v-loading="loading.memberNumber">
|
<!-- <div class="stat-meta" v-loading="loading.memberNumber">-->
|
||||||
年同比 {{memberNumberData.ratio}} 
|
<!-- 年同比 {{memberNumberData.ratio}} -->
|
||||||
<i v-if="memberNumberData.diff > 0" class="el-icon-top trend-up"></i>
|
<!-- <i :class="memberNumberData.diff >= 0 ? 'el-icon-top trend-up' : 'el-icon-bottom trend-down'"></i>-->
|
||||||
<i v-else-if="memberNumberData.diff < 0" class="el-icon-bottom trend-down"></i>
|
<!-- </div>-->
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card stats-card">
|
<div class="card stats-card">
|
||||||
@@ -394,8 +393,8 @@ layout("/layouts/platform.html"){
|
|||||||
memberNumberData: {
|
memberNumberData: {
|
||||||
memberNum: "--",
|
memberNum: "--",
|
||||||
lastYearMemberNum: "--",
|
lastYearMemberNum: "--",
|
||||||
ratio: "—",
|
ratio: "0.0%",
|
||||||
diff: null
|
diff: 0
|
||||||
},
|
},
|
||||||
memberPercentage: "--",
|
memberPercentage: "--",
|
||||||
unionMember: []
|
unionMember: []
|
||||||
@@ -404,39 +403,23 @@ layout("/layouts/platform.html"){
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* 无需传参;接口返回 memberNum 当前会员数和 lastYearMemberNum 去年归档会员数。
|
* 会员数
|
||||||
* @returns {Promise<void>} 更新会员数、同比及涨跌方向,请求结束后关闭加载状态。
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
getMemberNumber() {
|
async getMemberNumber() {
|
||||||
this.$set(this.loading, "memberNumber", true)
|
this.loading.memberNumber = true
|
||||||
return this.$axios.post("/platform/member/info/board/memberNumber").then((resp) => {
|
this.$axios.post("/platform/member/info/board/memberNumber").then((resp) => {
|
||||||
if (resp.code === 0) {
|
if (resp.code === 0) {
|
||||||
const data = resp.data || {}
|
const {
|
||||||
// 兼容数字字符串,但不把空值、布尔值或异常人数转换成有效的零人数。
|
data: { memberNum, lastYearMemberNum }
|
||||||
const memberNum = typeof data.memberNum === "number" || (typeof data.memberNum === "string" && data.memberNum.trim() !== "") ? Number(data.memberNum) : NaN
|
} = resp
|
||||||
const lastYearMemberNum = typeof data.lastYearMemberNum === "number" || (typeof data.lastYearMemberNum === "string" && data.lastYearMemberNum.trim() !== "") ? Number(data.lastYearMemberNum) : NaN
|
const diff = memberNum - lastYearMemberNum
|
||||||
const validMemberNum = Number.isSafeInteger(memberNum) && memberNum >= 0
|
const ratio = (lastYearMemberNum ? (Math.abs(diff) / lastYearMemberNum) * 100 : (diff / 1) * 10).toFixed(2) + " %"
|
||||||
const validLastYearMemberNum = Number.isSafeInteger(lastYearMemberNum) && lastYearMemberNum >= 0
|
this.memberNumberData = { memberNum, lastYearMemberNum, diff, ratio }
|
||||||
let diff = null
|
this.loading.memberNumber = false
|
||||||
let ratio = "—"
|
|
||||||
// 去年为零或没有归档人数时无法计算同比;仅在基数有效且大于零时显示涨跌。
|
|
||||||
if (validMemberNum && validLastYearMemberNum && lastYearMemberNum > 0) {
|
|
||||||
diff = memberNum - lastYearMemberNum
|
|
||||||
// 先按万分比对涨跌幅绝对值四舍五入,再恢复负号,避免 4.525% 被直接格式化成 4.52%。
|
|
||||||
const changePercent = Math.round(Math.abs(diff) * 10000 / lastYearMemberNum) / 100
|
|
||||||
ratio = diff === 0 ? "持平" : (diff < 0 && changePercent > 0 ? "-" : "") + changePercent.toFixed(2) + " %"
|
|
||||||
}
|
|
||||||
this.$set(this, "memberNumberData", {
|
|
||||||
memberNum: validMemberNum ? memberNum : "--",
|
|
||||||
lastYearMemberNum: validLastYearMemberNum ? lastYearMemberNum : "--",
|
|
||||||
diff: diff,
|
|
||||||
ratio: ratio
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
this.$message.warning(resp.msg)
|
this.$message.warning(resp.msg)
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
|
||||||
this.$set(this.loading, "memberNumber", false)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -501,23 +484,18 @@ layout("/layouts/platform.html"){
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 无需传参;接口 data 为 0~1 的会员比例,兼容数值字符串后用于水波图。
|
* 会员占比
|
||||||
* @returns {Promise<void>} 完成比例校验和图表更新,并在请求结束时关闭加载状态。
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
getMemberPercentage() {
|
async getMemberPercentage() {
|
||||||
this.$set(this.loading, "memberPercentage", true)
|
this.loading.memberPercentage = true
|
||||||
return this.$axios.post("/platform/member/info/board/memberPercentage").then((resp) => {
|
this.$axios.post("/platform/member/info/board/memberPercentage").then((resp) => {
|
||||||
|
this.loading.memberPercentage = false
|
||||||
if (resp.code === 0) {
|
if (resp.code === 0) {
|
||||||
const { data } = resp
|
const { data } = resp
|
||||||
// 空值、非数值和超出比例范围的数据不能作为 0% 展示;合法的零值需要保留。
|
this.memberPercentage = data.toFixed(2)
|
||||||
const percent = typeof data === "number" || (typeof data === "string" && data.trim() !== "") ? Number(data) : NaN
|
|
||||||
if (!Number.isFinite(percent) || percent < 0 || percent > 1) {
|
|
||||||
this.$message.warning("会员占比数据异常,请稍后重试")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$set(this, "memberPercentage", percent.toFixed(2))
|
|
||||||
if (chart.memberPercentageChart) {
|
if (chart.memberPercentageChart) {
|
||||||
chart.memberPercentageChart.changeData(percent)
|
chart.memberPercentageChart.changeData(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chart.memberPercentageChart = new G2Plot.Liquid("memberPercentageChart", {
|
chart.memberPercentageChart = new G2Plot.Liquid("memberPercentageChart", {
|
||||||
@@ -529,17 +507,13 @@ layout("/layouts/platform.html"){
|
|||||||
length: 128
|
length: 128
|
||||||
},
|
},
|
||||||
autoFit: true,
|
autoFit: true,
|
||||||
percent: percent,
|
percent: data,
|
||||||
color: '#217050',
|
color: '#217050',
|
||||||
statistic: {
|
statistic: {
|
||||||
content: {
|
content: {
|
||||||
style: {
|
style: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
// 深色文字配浅色描边,确保低水位白底和高水位绿底上的百分比均可读。
|
fill: '#fff', // 白色文字
|
||||||
fill: '#1f2937',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
stroke: '#fff',
|
|
||||||
lineWidth: 0.5,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -548,8 +522,6 @@ layout("/layouts/platform.html"){
|
|||||||
} else {
|
} else {
|
||||||
this.$message.warning(resp.msg)
|
this.$message.warning(resp.msg)
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
|
||||||
this.$set(this.loading, "memberPercentage", false)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-8
@@ -579,9 +579,6 @@ layout("/layouts/platform_h5.html"){
|
|||||||
</van-action-sheet>
|
</van-action-sheet>
|
||||||
|
|
||||||
<van-field label="性别" v-model="formData.condolenceSex" readonly placeholder="自动带出"></van-field>
|
<van-field label="性别" v-model="formData.condolenceSex" readonly placeholder="自动带出"></van-field>
|
||||||
<van-field label="生日" :value="formatDateText(formData.condolenceBirthday)" readonly placeholder="自动带出"></van-field>
|
|
||||||
<van-field label="身份证号" v-model="formData.condolenceIdCard" readonly placeholder="自动带出"></van-field>
|
|
||||||
<van-field label="联系方式" v-model="formData.condolenceMobile" readonly placeholder="自动带出"></van-field>
|
|
||||||
|
|
||||||
<van-field
|
<van-field
|
||||||
v-model="formData.typeName"
|
v-model="formData.typeName"
|
||||||
@@ -1412,7 +1409,7 @@ layout("/layouts/platform_h5.html"){
|
|||||||
},
|
},
|
||||||
normalizeContactFields() {
|
normalizeContactFields() {
|
||||||
// 提交前裁剪联系方式空格,避免系统手机号带空格时后端格式校验失败。
|
// 提交前裁剪联系方式空格,避免系统手机号带空格时后端格式校验失败。
|
||||||
const fields = ["mobile", "condolenceMobile"]
|
const fields = ["mobile"]
|
||||||
fields.forEach((fieldName) => {
|
fields.forEach((fieldName) => {
|
||||||
if (this.formData[fieldName]) {
|
if (this.formData[fieldName]) {
|
||||||
this.$set(this.formData, fieldName, String(this.formData[fieldName]).trim())
|
this.$set(this.formData, fieldName, String(this.formData[fieldName]).trim())
|
||||||
@@ -2209,7 +2206,6 @@ layout("/layouts/platform_h5.html"){
|
|||||||
const selectedUser = this.condolenceOptions.find(user => user.id === o.value)
|
const selectedUser = this.condolenceOptions.find(user => user.id === o.value)
|
||||||
if (selectedUser) {
|
if (selectedUser) {
|
||||||
this.fillCondolenceUser(selectedUser)
|
this.fillCondolenceUser(selectedUser)
|
||||||
this.$set(this.formData, "condolenceMobile", selectedUser.mobile)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCondolenceTypeConfirm(o) {
|
onCondolenceTypeConfirm(o) {
|
||||||
@@ -2341,9 +2337,6 @@ layout("/layouts/platform_h5.html"){
|
|||||||
this.$set(this.formData, "condolenceUnionName", user.unionName)
|
this.$set(this.formData, "condolenceUnionName", user.unionName)
|
||||||
this.$set(this.formData, "condolenceUnionCode", user.unionCode)
|
this.$set(this.formData, "condolenceUnionCode", user.unionCode)
|
||||||
this.$set(this.formData, "condolenceSex", user.sex)
|
this.$set(this.formData, "condolenceSex", user.sex)
|
||||||
this.$set(this.formData, "condolenceBirthday", user.birthday)
|
|
||||||
this.$set(this.formData, "condolenceIdCard", user.idCard)
|
|
||||||
this.$set(this.formData, "condolenceMobile", user.mobile)
|
|
||||||
},
|
},
|
||||||
// 查询慰问类型
|
// 查询慰问类型
|
||||||
queryCondolenceType() {
|
queryCondolenceType() {
|
||||||
|
|||||||
@@ -100,18 +100,6 @@ const UNION_REIMBURSE_INFO = {
|
|||||||
<div class="union-info-label">性别</div>
|
<div class="union-info-label">性别</div>
|
||||||
<div class="union-info-value">{{ viewData.condolenceSex }}</div>
|
<div class="union-info-value">{{ viewData.condolenceSex }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="union-info-row">
|
|
||||||
<div class="union-info-label">生日</div>
|
|
||||||
<div class="union-info-value">{{ formatDateText(viewData.condolenceBirthday) }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="union-info-row">
|
|
||||||
<div class="union-info-label">身份证号</div>
|
|
||||||
<div class="union-info-value">{{ viewData.condolenceIdCard }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="union-info-row">
|
|
||||||
<div class="union-info-label">联系方式</div>
|
|
||||||
<div class="union-info-value">{{ viewData.condolenceMobile }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="union-info-row">
|
<div class="union-info-row">
|
||||||
<div class="union-info-label">慰问类型</div>
|
<div class="union-info-label">慰问类型</div>
|
||||||
<div class="union-info-value">{{ viewData.typeName || viewData.condolenceTypeName }}</div>
|
<div class="union-info-value">{{ viewData.typeName || viewData.condolenceTypeName }}</div>
|
||||||
|
|||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
887dae22390914b7be8401e8ce87571d8f776eb6
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
71eb5eaecf2b7fd91d9dd7409a56314b9f2f132b3dffbb9e13ee56d0999b8c59
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
5bc042d969b2a8fae96441b439096181eb11bbce
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
0d9b4629144a1fd15ed01362ce0dc2c9c5246e4f57250dd95fb52dbbbb75c532
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-project</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<name>Jetty :: Asynchronous HTTP Client</name>
|
||||||
|
<properties>
|
||||||
|
<bundle-symbolic-name>${project.groupId}.client</bundle-symbolic-name>
|
||||||
|
<jetty.test.policy.loc>target/test-policy</jetty.test.policy.loc>
|
||||||
|
<spotbugs.onlyAnalyze>org.eclipse.client.*</spotbugs.onlyAnalyze>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>unpack</id>
|
||||||
|
<phase>generate-test-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>unpack</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<artifactItems>
|
||||||
|
<artifactItem>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-policy</artifactId>
|
||||||
|
<version>${jetty-test-policy.version}</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<overWrite>true</overWrite>
|
||||||
|
<includes>**/*.keystore,**/*.pem</includes>
|
||||||
|
<outputDirectory>${jetty.test.policy.loc}</outputDirectory>
|
||||||
|
</artifactItem>
|
||||||
|
</artifactItems>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<minimizeJar>true</minimizeJar>
|
||||||
|
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||||
|
<shadedClassifierName>hybrid</shadedClassifierName>
|
||||||
|
<artifactSet>
|
||||||
|
<includes>
|
||||||
|
<include>org.eclipse.jetty:jetty-http</include>
|
||||||
|
<include>org.eclipse.jetty:jetty-io</include>
|
||||||
|
<include>org.eclipse.jetty:jetty-util</include>
|
||||||
|
</includes>
|
||||||
|
</artifactSet>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.eclipse.jetty.http</pattern>
|
||||||
|
<shadedPattern>org.eclipse.jetty.client.shaded.http</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.eclipse.jetty.io</pattern>
|
||||||
|
<shadedPattern>org.eclipse.jetty.client.shaded.io</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.eclipse.jetty.util</pattern>
|
||||||
|
<shadedPattern>org.eclipse.jetty.client.shaded.util</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-http</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-io</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-jmx</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-security</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.kerby</groupId>
|
||||||
|
<artifactId>kerb-simplekdc</artifactId>
|
||||||
|
<version>2.1.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.jline</groupId>
|
||||||
|
<artifactId>jline</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<!-- transitive dependency defined as a range and we don't want that -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.minidev</groupId>
|
||||||
|
<artifactId>json-smart</artifactId>
|
||||||
|
<version>2.5.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.awaitility</groupId>
|
||||||
|
<artifactId>awaitility</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
024e0627736731cbd441931d50fcb22bf17f4cc0
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
989a013455688dc528a988c41514fbfece0e3354a54398af5a404cda582c94bd
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
d551472980052c26105c45dc476bfe07cc23dfb5
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
dcfe694ef6f27d9d6268e2fb135a1c9a7aabdfa55e7d738fc8df79f573086027
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
086fa31420e9a5a623cac7967b0b6de0674d93af
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
90058e595b73d85b96e192b7e6806098f83467680a95466974f445d6706ed682
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>jetty-project</artifactId>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>jetty-http</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<name>Jetty :: Http Utility</name>
|
||||||
|
<properties>
|
||||||
|
<bundle-symbolic-name>${project.groupId}.http</bundle-symbolic-name>
|
||||||
|
<spotbugs.onlyAnalyze>org.eclipse.jetty.http.*</spotbugs.onlyAnalyze>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-util</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-io</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional</Require-Capability>
|
||||||
|
<Provide-Capability>osgi.serviceloader; osgi.serviceloader=org.eclipse.jetty.http.HttpFieldPreEncoder</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>test-jar</id>
|
||||||
|
<goals>
|
||||||
|
<goal>test-jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<finalName>${jmhjar.name}</finalName>
|
||||||
|
<shadeTestJar>true</shadeTestJar>
|
||||||
|
<artifactSet>
|
||||||
|
<includes>
|
||||||
|
<include>org.openjdk.jmh:jmh-core</include>
|
||||||
|
</includes>
|
||||||
|
</artifactSet>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>org.openjdk.jmh.Main</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
<filters>
|
||||||
|
<filter>
|
||||||
|
<artifact>org.openjdk.jmh:jmh-core</artifact>
|
||||||
|
<includes>
|
||||||
|
<include>**</include>
|
||||||
|
</includes>
|
||||||
|
</filter>
|
||||||
|
<filter>
|
||||||
|
<artifact>*:*</artifact>
|
||||||
|
<excludes>
|
||||||
|
<exclude>META-INF/*.SF</exclude>
|
||||||
|
<exclude>META-INF/*.DSA</exclude>
|
||||||
|
<exclude>META-INF/*.RSA</exclude>
|
||||||
|
</excludes>
|
||||||
|
</filter>
|
||||||
|
</filters>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
f27754642c5b5e3b2e570669b7a1ceef613fc589
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
967fff3457fb62eb7485fe303cc4162edc1c02b15498c2f8401fbca263649ab8
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
0f398cff4f7ab827fecb9d4f657685b973512e35
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
770e71875fa37effaab5c11e932b9a52c2e019236062575b0403760242b0bf08
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
b01b199c670073760b66dbd5f79bbf0c3639ffc5
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
d757e04e165d940c019e9346c36e9bf8d1a71c8b6b196519bda4200e2a16f5d4
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-project</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>jetty-jaspi</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<name>Jetty :: JASPI Security</name>
|
||||||
|
<description>Jetty security infrastructure</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<bundle-symbolic-name>${project.groupId}.security.jaspi</bundle-symbolic-name>
|
||||||
|
<spotbugs.onlyAnalyze>org.eclipse.jetty.jaspi.*</spotbugs.onlyAnalyze>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>manifest</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Require-Capability>osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"</Require-Capability>
|
||||||
|
<Provide-Capability>osgi.serviceloader;osgi.serviceloader=org.eclipse.jetty.security.Authenticator$Factory</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-security</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.orbit</groupId>
|
||||||
|
<artifactId>javax.security.auth.message</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
<version>2.3.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<!-- Must stay on 3.x as 4.x is for Java 11 -->
|
||||||
|
<version>3.0.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>javax.activation-api</artifactId>
|
||||||
|
<version>1.2.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.geronimo.components</groupId>
|
||||||
|
<artifactId>geronimo-jaspi</artifactId>
|
||||||
|
<version>2.0.0</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.apache.geronimo.specs</groupId>
|
||||||
|
<artifactId>geronimo-jaspic_1.0_spec</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
7be35862967482c8ebe9615f5d5cd47230f9f1ee
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
8e49cd7df44f050c96d896726129fa5c9d0ac5895261cda774c8c899e617e5a6
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
41dcd1e0dc8c03bf94f2419067f5330217a43468
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
b9ff8bf676cf5911cce7080c438d5cb96436b27d25462a671cc47b2eb6a5f322
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
56e0e92f7cefb02d7c5748b480f45c063c6163ae
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
a89e010985a3a347c700c32a535f92f89b6c2548cabcf5e434563b2a59c37b2d
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-project</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>jetty-security</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<name>Jetty :: Security</name>
|
||||||
|
<description>Jetty security infrastructure</description>
|
||||||
|
<properties>
|
||||||
|
<bundle-symbolic-name>${project.groupId}.security</bundle-symbolic-name>
|
||||||
|
<spotbugs.onlyAnalyze>org.eclipse.jetty.security.*</spotbugs.onlyAnalyze>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>manifest</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=org.eclipse.jetty.security.Authenticator$Factory)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)";resolution:=optional</Require-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-http</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<classifier>tests</classifier>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
4f296e131092ecc93d76c210fd18d8133d82af7f
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
c2697118caa818c3f6b17bce98e60f971a56905a96b69eef9b3f34d0bcd1e729
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
479360715d77a8473a1344047f56b2f5a00a80a4
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
e9f670a373dd91b8ce752d1cc601ffb4890a8cffe67a3fb3addca09c9b0d3405
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
6d3866a9a9ec769524b2ed4009cbde3497af6b71
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
d2a396bd5f8f30ed1b6664e4cc07ddd0df9e1ef4a25452291f24e475890ef037
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-project</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<name>Jetty :: Server Core</name>
|
||||||
|
<description>The core jetty server artifact.</description>
|
||||||
|
<properties>
|
||||||
|
<bundle-symbolic-name>${project.groupId}.server</bundle-symbolic-name>
|
||||||
|
<spotbugs.onlyAnalyze>org.eclipse.jetty.server.*</spotbugs.onlyAnalyze>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>test-jar</id>
|
||||||
|
<goals>
|
||||||
|
<goal>test-jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-resources-keystore</id>
|
||||||
|
<phase>process-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<outputDirectory>${project.build.directory}/jetty-config-files</outputDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/config</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/**keystore**</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-http</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-io</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-xml</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-jmx</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-http</artifactId>
|
||||||
|
<version>9.4.57.v20241219-cug-patch1</version>
|
||||||
|
<classifier>tests</classifier>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-util-ajax</artifactId>
|
||||||
|
<version>9.4.57.v20241219</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.awaitility</groupId>
|
||||||
|
<artifactId>awaitility</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
269449bfdbb79d75afe4c4d73973cc7f38152591
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
e1bf730db18ea8cd98c95bee03673b3601f34322e30e91d3cfd8210c0d7dd00c
|
||||||
Reference in New Issue
Block a user