UrlParamUtil.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package com.kingdee.eas.custom.perfweb.utils;
  2. import com.kingdee.eas.custom.integration.base.config.AppConfig;
  3. import com.kingdee.eas.custom.integration.base.config.SpringContextHolder;
  4. import com.kingdee.eas.custom.integration.base.service.AppConfigService;
  5. import com.kingdee.eas.custom.integration.base.service.StateValidator;
  6. import org.apache.commons.lang3.StringUtils;
  7. import org.apache.log4j.Logger;
  8. import org.springframework.web.util.UriComponentsBuilder;
  9. import java.io.UnsupportedEncodingException;
  10. import java.net.URLEncoder;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. /**
  14. *类名称: UrlParamUtil
  15. *功能描述: 飞书待办跳转地址工具类
  16. *负责拼接并编码 pcurl / appurl 参数,集中管理域名、入口路径和各业务页面路径
  17. *创建日期: 2026-06-12
  18. *作 者: 青梧
  19. *版 本: 1.0
  20. */
  21. public class UrlParamUtil {
  22. private static final Logger logger = Logger.getLogger(UrlParamUtil.class);
  23. /** integration 飞书单点入口(相对 SHR 域名) */
  24. private static final String FEISHU_AUTH_PATH = "/qy/auth/feishu";
  25. //===== 基础域名(切换环境时改 BASE_HOST / SHR_HOST 等当前生效变量)=====
  26. /** OA 跳转网关前缀 - 正式环境(oAToSHR) */
  27. public static String BASE_HOST_PROD = "http://ehr.shichuangyi.cn:8080/shr/api/oAToSHR?redirect=";
  28. /** OA 跳转网关前缀 - 测试环境(oAToSHR) */
  29. public static String BASE_HOST_TEST = "http://10.0.3.24:8080/shr/api/oAToSHR?redirect=";
  30. /** 当前生效的 OA 待办网关 */
  31. public static String BASE_HOST = BASE_HOST_PROD;
  32. /** SHR 系统地址 - 正式环境 */
  33. public static String SHR_HOST_PROD = "http://ehr.shichuangyi.cn:8080";
  34. /** SHR 系统地址 - 测试环境 */
  35. public static String SHR_HOST_TEST = "http://10.0.3.24:8080";
  36. /** 当前生效的 SHR 地址(dynamic.do 等业务页) */
  37. public static String SHR_HOST = SHR_HOST_PROD;
  38. //正式环境
  39. public static String PRODURL = "https://freport.shichuangyi.cn/oa-ofs/prod-api/approve/rest/ofs/ReceiveRequestInfoByJson";
  40. //测试环境
  41. public static String TESTURL = "https://mtest.shichuangyi.cn/prod-approval-api/approve/rest/ofs/ReceiveRequestInfoByJson";
  42. //绩效工作台 - 正式环境
  43. public static String JXGZT_PC_PROD = "http://ehr.shichuangyi.cn:8080/shr/dynamic.do?inFrame=true&fromHeader=true&uipk=com.kingdee.shr.perfweb.app.perfportal.core&serviceId=fUjSqQK1RouZaEJUI2jH5vI9KRA%3D";
  44. public static String JXGZT_BOS_PROD = "http://ehr.shichuangyi.cn:8080/shr/dynamic.do?inFrame=true&fromHeader=true&uipk=com.kingdee.shr.perfweb.app.perfportal.core&serviceId=fUjSqQK1RouZaEJUI2jH5vI9KRA%3D";
  45. //绩效工作台 - 测试环境
  46. public static String JXGZT_PC_TEST = "http://10.0.3.24:8080/shr/dynamic.do?inFrame=true&fromHeader=true&uipk=com.kingdee.shr.perfweb.app.perfportal.core&serviceId=fUjSqQK1RouZaEJUI2jH5vI9KRA%3D";
  47. public static String JXGZT_BOS_TEST = "http://10.0.3.24:8080/shr/dynamic.do?inFrame=true&fromHeader=true&uipk=com.kingdee.shr.perfweb.app.perfportal.core&serviceId=fUjSqQK1RouZaEJUI2jH5vI9KRA%3D";
  48. public static String JXGZT_PC = JXGZT_PC_PROD;
  49. public static String JXGZT_BOS = JXGZT_BOS_PROD;
  50. //默认组织ID(考核结果汇总列表 hrOrgId)
  51. public static String DEFAULT_HR_ORG_ID = "00000000-0000-0000-0000-000000000000CCE7AED4";
  52. /**
  53. *考核结果汇总列表页 query 模板。
  54. *动态参数使用 {@code {period}}、{@code {perfPlanId}}、{@code {hrOrgId}} 占位,拼接时需对值做 URL 编码。
  55. */
  56. private static final String EVA_RESULT_MANAGER_LIST_QUERY =
  57. "uipk=com.kingdee.shr.perfweb.app.base.evalplan.evaResultManager.list"
  58. + "&serviceId=nQafn+gKQ+CngP3xudsk5PI9KRA="
  59. + "&isOrg=0"
  60. + "&period={period}"
  61. + "&perfPlanId={perfPlanId}"
  62. + "&hrOrgId={hrOrgId}";
  63. /**
  64. * 绩效考核结果报表列表页 query 模板(飞书待办跳转)。
  65. * periodId 为对应待办的考核周期 ID。
  66. */
  67. private static final String CENTER_PERF_STAT_REPORT_LIST_QUERY =
  68. "uipk=com.kingdee.eas.custom.perfweb.handler.CenterPerfStatReport.list"
  69. + "&serviceId=eF6u2TdvSbScepkXUeRU1vI9KRA%3D"
  70. + "&inFrame=true"
  71. + "&periodId={periodId}";
  72. private static final String DEFAULT_CHARSET = "UTF-8";
  73. /**
  74. *对字符串进行 URL 编码(UTF-8),用于 query 参数值。
  75. *例如:{@code urlEncode("a=b")} 返回 {@code "a%3Db"}
  76. *
  77. *@param value 待编码字符串,null 或空串返回空串
  78. *@return 编码后的字符串
  79. */
  80. public static String urlEncode(String value) {
  81. if (value == null || value.isEmpty()) {
  82. return "";
  83. }
  84. try {
  85. return URLEncoder.encode(value, DEFAULT_CHARSET);
  86. } catch (UnsupportedEncodingException e) {
  87. throw new RuntimeException("URL encode failed: " + value, e);
  88. }
  89. }
  90. /**
  91. *替换模板中的 {@code {key}} 占位符。
  92. *
  93. *@param template 含占位符的模板,如 {@code "...&period={period}&perfPlanId={perfPlanId}"}
  94. *@param params key 对应占位符名称(不含花括号),value 为替换值(调用方自行决定是否已编码)
  95. *@return 替换后的字符串
  96. */
  97. public static String fillUrlTemplate(String template, Map<String, String> params) {
  98. if (template == null) {
  99. return null;
  100. }
  101. if (params == null || params.isEmpty()) {
  102. return template;
  103. }
  104. String result = template;
  105. for (Map.Entry<String, String> entry : params.entrySet()) {
  106. String replacement = entry.getValue() != null ? entry.getValue() : "";
  107. result = result.replace("{" + entry.getKey() + "}", replacement);
  108. }
  109. return result;
  110. }
  111. /**
  112. *替换模板占位符,并对每个参数值自动做 URL 编码(传入 BOS 原始 ID 即可)。
  113. */
  114. public static String fillUrlTemplateEncoded(String template, Map<String, String> rawParams) {
  115. Map<String, String> encodedParams = new HashMap<>();
  116. if (rawParams != null) {
  117. for (Map.Entry<String, String> entry : rawParams.entrySet()) {
  118. encodedParams.put(entry.getKey(), urlEncode(entry.getValue()));
  119. }
  120. }
  121. return fillUrlTemplate(template, encodedParams);
  122. }
  123. /**
  124. *构建考核结果汇总列表页地址(dynamic.do)。
  125. *
  126. *@param periodId 考核周期 ID(summaryInfo.getPeriod().getId())
  127. *@param perfPlanId 考核方案 ID(summaryInfo.getEvaPlan().getId())
  128. *@param hrOrgId 组织 ID,为空时使用 {@link #DEFAULT_HR_ORG_ID}
  129. *@return 完整 SHR 页面 URL,未套 OA redirect
  130. */
  131. public static String buildEvaResultManagerListUrl(String periodId, String perfPlanId, String hrOrgId) {
  132. Map<String, String> params = new HashMap<>();
  133. params.put("period", periodId);
  134. params.put("perfPlanId", perfPlanId);
  135. params.put("hrOrgId", resolveHrOrgId(hrOrgId));
  136. String query = fillUrlTemplateEncoded(EVA_RESULT_MANAGER_LIST_QUERY, params);
  137. return SHR_HOST + "/shr/dynamic.do?" + query;
  138. }
  139. /**
  140. *构建考核结果汇总列表页地址,hrOrgId 使用 {@link #DEFAULT_HR_ORG_ID}。
  141. */
  142. public static String buildEvaResultManagerListUrl(String periodId, String perfPlanId) {
  143. return buildEvaResultManagerListUrl(periodId, perfPlanId, DEFAULT_HR_ORG_ID);
  144. }
  145. /**
  146. * 构建考核结果汇总列表页的 OA 待办跳转地址(oAToSHR,非 Bot 消息)。
  147. */
  148. public static String buildEvaResultManagerListOaUrl(String periodId, String perfPlanId) {
  149. return buildOaRedirectUrl(buildEvaResultManagerListUrl(periodId, perfPlanId));
  150. }
  151. /**
  152. * 构建考核结果汇总列表页的 OA 待办跳转地址(oAToSHR,非 Bot 消息)。
  153. */
  154. public static String buildEvaResultManagerListOaUrl(String periodId, String perfPlanId, String hrOrgId) {
  155. return buildOaRedirectUrl(buildEvaResultManagerListUrl(periodId, perfPlanId, hrOrgId));
  156. }
  157. /**
  158. * 构建绩效考核结果报表列表页地址(dynamic.do)。
  159. *
  160. * @param periodId 考核周期 ID
  161. */
  162. public static String buildCenterPerfStatReportListUrl(String periodId) {
  163. Map<String, String> params = new HashMap<>();
  164. params.put("periodId", periodId != null ? periodId : "");
  165. String query = fillUrlTemplateEncoded(CENTER_PERF_STAT_REPORT_LIST_QUERY, params);
  166. return SHR_HOST + "/shr/dynamic.do?" + query;
  167. }
  168. /**
  169. * 构建绩效考核结果报表列表页的 OA 待办跳转地址(oAToSHR,非 Bot 消息)。
  170. */
  171. public static String buildCenterPerfStatReportListOaUrl(String periodId) {
  172. return buildOaRedirectUrl(buildCenterPerfStatReportListUrl(periodId));
  173. }
  174. /**
  175. * 绩效工作台 Bot 消息跳转地址(integration 单点 + 目标页)。
  176. * 用于:启动考核、单人/批量评分、考核结果公示。
  177. */
  178. public static String buildPerfPortalFeishuMessageUrl() {
  179. return buildFeishuMessageUrl(JXGZT_PC);
  180. }
  181. /**
  182. * @deprecated 待办请用 {@link #buildOaRedirectUrl(String)};Bot 消息请用 {@link #buildPerfPortalFeishuMessageUrl()}
  183. */
  184. public static String buildPerfPortalOaUrl() {
  185. return buildPerfPortalFeishuMessageUrl();
  186. }
  187. /**
  188. * Bot 消息跳转地址:integration {@code /qy/auth/feishu?state=Base64(redirect|appType)}。
  189. * 仅用于启动考核、单人/批量评分、考核结果公示;OA 待办请用 {@link #buildOaRedirectUrl(String)}。
  190. *
  191. * @param targetUrl 登录成功后打开的 HR 业务页完整 URL(未编码,写入 state)
  192. */
  193. public static String buildFeishuMessageUrl(String targetUrl) {
  194. if (StringUtils.isBlank(targetUrl)) {
  195. return "";
  196. }
  197. try {
  198. StateValidator stateValidator = SpringContextHolder.getBean(StateValidator.class);
  199. AppConfigService appConfigService = SpringContextHolder.getBean(AppConfigService.class);
  200. AppConfig appConfig = appConfigService.getPrimaryApp("feishu");
  201. if (appConfig == null || StringUtils.isBlank(appConfig.getAppType())) {
  202. throw new IllegalStateException("oauth-config 未配置 feishu 主应用 appType");
  203. }
  204. String state = stateValidator.generate(targetUrl, appConfig.getAppType());
  205. String url = UriComponentsBuilder.fromUriString(appConfigService.getShrServerName() + FEISHU_AUTH_PATH)
  206. .queryParam("state", state)
  207. .build(true)
  208. .toUriString();
  209. logger.error("integration 单点地址:" +url);
  210. return url;
  211. } catch (Exception e) {
  212. logger.error("integration 单点地址构建失败,回退 oAToSHR,targetUrl=" + targetUrl, e);
  213. return buildOaRedirectUrl(targetUrl);
  214. }
  215. }
  216. /**
  217. * OA 待办跳转地址:{@code BASE_HOST + urlEncode(targetUrl)}(oAToSHR)。
  218. */
  219. public static String buildOaRedirectUrl(String targetUrl) {
  220. if (StringUtils.isBlank(targetUrl)) {
  221. return "";
  222. }
  223. return BASE_HOST + urlEncode(targetUrl);
  224. }
  225. private static String resolveHrOrgId(String hrOrgId) {
  226. if (hrOrgId == null || hrOrgId.trim().isEmpty()) {
  227. return DEFAULT_HR_ORG_ID;
  228. }
  229. return hrOrgId.trim();
  230. }
  231. }