Merge branch 'feature/dingdingLogin' into pre
This commit is contained in:
commit
8c34bfee0c
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
@ -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');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user