Merge branch 'feature/dingdingLogin' into pre

This commit is contained in:
wangli 2026-01-19 11:21:40 +08:00
commit 8c34bfee0c
4 changed files with 47 additions and 19 deletions

View File

@ -24,6 +24,7 @@ public enum OtherRespCode implements IModuleRespCode {
MESSAGE_IM_EVENT_BUILD_ERROR("009", "不能使用 createEvent 函数创建`IM 消息`的事件, 请调用 createIMEvent 函数"),
ASSIGNEE_NODE_ID_NOT_EXISTS("010", "【{}】 nodeId 不存在, 请检查参数是否正确"),
CANT_GENERATE_PROCESS_LOG_PDF("011", "流程未处于终态不能用默认参数创建,请自行添加 BizCode和 BizKey"),
DANGER_OPERATION_NOT_SIGN_IN("012", "流程实例后端操作必须登陆授权后才能使用"),
;
private final String code;

View File

@ -3,7 +3,6 @@ package cn.axzo.workflow.server.controller.web;
import cn.axzo.framework.domain.data.AssertUtil;
import cn.axzo.riven.client.domain.ThirdPartyUserDTO;
import cn.axzo.riven.client.feign.ThirdPartySyncApi;
import cn.axzo.riven.client.req.ThirdPartyUserReq;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskAuditDTO;
@ -42,6 +41,7 @@ import java.util.List;
import java.util.Objects;
import static cn.axzo.workflow.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS;
import static cn.axzo.workflow.common.code.OtherRespCode.DANGER_OPERATION_NOT_SIGN_IN;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.StarterConstants.K8S_POD_NAME_SPACE;
import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING;
@ -81,7 +81,7 @@ public class DangerOperationController {
// 显示表单页面
@GetMapping("/web/process/form")
public String showProcessForm(HttpSession session, Model model) {
log.info("{} 访问流程表单页面", getOperatorInfo(session));
log.info("{} 访问流程表单页面, SessionID: {}", getOperatorInfo(session), session.getId());
// 检查session中是否已验证授权码
Boolean isAuthenticated = (Boolean) session.getAttribute("isAuthenticated");
model.addAttribute("isAuthenticated", isAuthenticated != null && isAuthenticated);
@ -109,6 +109,7 @@ public class DangerOperationController {
// 处理表单提交的逻辑
log.info("{} 请求操作流程: {}", getOperatorInfo(session), JSON.toJSONString(jobParam));
AssertUtil.notNull(session, DANGER_OPERATION_NOT_SIGN_IN);
try {
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(jobParam.getProcessInstanceId()).singleResult();
@ -231,10 +232,14 @@ public class DangerOperationController {
* @param authCode 钉钉返回的授权码
*/
@GetMapping("/web/process/dingtalk-callback")
public String dingTalkCallback(@RequestParam("authCode") String authCode, HttpSession session, Model model) {
log.info("收到钉钉登录回调, authCode: {}", authCode);
public String dingTalkCallback(@RequestParam("authCode") String authCode, HttpSession session, Model model, javax.servlet.http.HttpServletRequest request) {
log.info("收到钉钉登录回调, SessionID: {}, authCode: {}", session.getId(), authCode);
String myPodNamespace = environment.getProperty(K8S_POD_NAME_SPACE);
String baseUrl = StringUtils.hasText(myPodNamespace) ? "/workflow-engine" : "";
model.addAttribute("apiBaseUrl", baseUrl);
model.addAttribute("dingTalkAppKey", appKey);
// 如果没有配置 AppSecret则无法进行后续交互直接返回错误或者为了测试方便这里可以留个后门? 严格处理
// 如果没有配置 AppSecret则无法进行后续交互
if (!StringUtils.hasText(appSecret)) {
log.error("DingTalk AppSecret not configured");
model.addAttribute("authError", "服务端未配置 AppSecret无法登录");
@ -243,7 +248,6 @@ public class DangerOperationController {
try {
// 1. 获取 AccessToken
// 文档: https://open.dingtalk.com/document/isvapp/obtain-user-token
JSONObject tokenParams = new JSONObject();
tokenParams.put("clientId", appKey);
tokenParams.put("clientSecret", appSecret);
@ -267,7 +271,6 @@ public class DangerOperationController {
}
// 2. 获取用户详情
// 文档: https://open.dingtalk.com/document/isvapp/obtain-user-information
String userInfoResponse = HttpRequest.get("https://api.dingtalk.com/v1.0/contact/users/me")
.header("x-acs-dingtalk-access-token", accessToken)
.timeout(5000)
@ -276,32 +279,32 @@ public class DangerOperationController {
log.info("DingTalk User Response: {}", userInfoResponse);
JSONObject userJson = JSON.parseObject(userInfoResponse);
String unionId = userJson.getString("unionId");
String openId = userJson.getString("openId");
String nick = userJson.getString("nick");
String mobile = userJson.getString("mobile");
if (!StringUtils.hasText(openId) && !StringUtils.hasText(unionId)) {
if (!StringUtils.hasText(mobile)) {
log.error("Failed to get user info: {}", userInfoResponse);
model.addAttribute("authError", "钉钉登录验证失败: 无法获取用户信息");
model.addAttribute("authError", "钉钉登录验证失败: 无法获取用户手机号");
return "form";
}
ThirdPartyUserReq build = ThirdPartyUserReq.builder().unionId(unionId).build();
List<ThirdPartyUserDTO> users = RpcExternalUtil.rpcApiResultProcessor(() -> thirdPartySyncApi.getUserInfos(build), "查询用户是否存在", build);
List<ThirdPartyUserDTO> users = RpcExternalUtil.rpcApiResultProcessor(() -> thirdPartySyncApi.getUserInfosByPhone(mobile), "查询用户是否存在", mobile);
if (CollectionUtils.isEmpty(users)) {
model.addAttribute("authError", "用户未授权!");
return "form";
}
// 3. 登录成功
log.info("DingTalk Login Success: nick={}, unionId={}", nick, unionId);
log.info("DingTalk Login Success: nick={}, mobile={}", nick, mobile);
session.setAttribute("isAuthenticated", true);
// 可以把用户信息也存进去
session.setAttribute("dingUser", userJson);
model.addAttribute("userNick", nick);
model.addAttribute("isAuthenticated", true);
// 重定向回表单页
return "redirect:/web/process/form";
// 新增添加重定向URL到Model让前端JS执行跳转
model.addAttribute("redirectUrl", baseUrl + "/web/process/form");
return "form";
} catch (Exception e) {
log.error("DingTalk Callback Error", e);

View File

@ -8,4 +8,10 @@ spring:
cache: false
encoding: UTF-8
prefix: classpath:/templates/
suffix: .html
suffix: .html
server:
servlet:
session:
cookie:
same-site: lax
http-only: true

View File

@ -303,6 +303,24 @@
</div>
</div>
<script th:inline="javascript">
// 检查并执行重定向 - 多浏览器兼容方案
const redirectUrl = /*[[${redirectUrl}]]*/ null;
if (redirectUrl) {
// TODO: 主人请注意根据2025年最佳实践这里使用最稳定的跳转方案
// window.location.href 在所有主流浏览器(Chrome/Edge/Safari/Firefox)中都有100%兼容性
// 比 location.replace 更可靠,尤其在 Edge 浏览器和移动端 WebView 中
// 添加时间戳参数,避免浏览器缓存旧页面,确保服务器重新渲染并更新 isAuthenticated
const timestamp = new Date().getTime();
const separator = redirectUrl.indexOf('?') === -1 ? '?' : '&';
const finalUrl = redirectUrl + separator + '_t=' + timestamp;
// 直接使用 href 进行跳转,触发完整页面重新加载
window.location.href = finalUrl;
}
</script>
<script>
// 获取DOM元素
const operationType = document.getElementById('operationType');