1. 项目概述
最近在对接泛微E9系统时,我发现很多开发者对Token认证和流程创建的完整实现存在困惑。本文将分享一个经过生产验证的Spring Boot集成方案,涵盖从认证到流程创建的全链路实现。
这个方案已经在我们公司的多个业务系统中稳定运行超过一年,日均调用量在5000次左右。相比网上零散的代码片段,我会重点讲解那些官方文档没有明确说明的细节问题,比如Token刷新机制、附件处理技巧等实际开发中必然会遇到的坑。
2. 环境准备与基础配置
2.1 依赖配置
首先在pom.xml中添加必要依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency>注意:泛微E9的API对JSON解析较为敏感,实测Fastjson的兼容性最好。如果使用Jackson可能会出现字段映射问题。
2.2 配置文件
application.yml中配置泛微服务地址:
weaver: e9: base-url: http://e9-server:8080 auth: username: api_user password: api_password123 grant_type: password workflow: create-path: /api/workflow/create3. Token认证实现
3.1 认证接口分析
泛微E9采用OAuth2密码模式认证,需要特别注意以下几点:
- Token有效期为2小时
- 每次请求都需要在Header中添加Token
- 响应中的expires_in单位是秒
3.2 认证服务实现
创建AuthService处理认证逻辑:
@Service public class AuthService { @Value("${weaver.e9.base-url}") private String baseUrl; @Value("${weaver.e9.auth.username}") private String username; @Value("${weaver.e9.auth.password}") private String password; private String accessToken; private long expireTime; public synchronized String getToken() { if (System.currentTimeMillis() < expireTime - 60000) { return accessToken; } // 调用认证接口 String authUrl = baseUrl + "/api/oauth2/token"; HttpPost request = new HttpPost(authUrl); List<NameValuePair> params = new ArrayList<>(); params.add(new BasicNameValuePair("username", username)); params.add(new BasicNameValuePair("password", password)); params.add(new BasicNameValuePair("grant_type", "password")); try { request.setEntity(new UrlEncodedFormEntity(params)); HttpResponse response = HttpClients.createDefault().execute(request); String result = EntityUtils.toString(response.getEntity()); JSONObject json = JSONObject.parseObject(result); this.accessToken = json.getString("access_token"); this.expireTime = System.currentTimeMillis() + json.getLong("expires_in") * 1000; return accessToken; } catch (Exception e) { throw new RuntimeException("获取Token失败", e); } } }关键点:这里实现了Token的缓存和自动刷新机制,在Token到期前1分钟会自动获取新Token,避免在业务高峰期集中刷新导致性能问题。
4. 流程创建实现
4.1 流程接口分析
创建流程的主要参数包括:
- workflowid:流程模板ID
- requestid:可选,用于关联业务系统ID
- formmode:表单模式(1-正常,2-只读)
- isupate:是否更新已有流程(0-否,1-是)
4.2 流程创建服务
创建WorkflowService处理流程创建:
@Service public class WorkflowService { @Autowired private AuthService authService; @Value("${weaver.e9.base-url}") private String baseUrl; @Value("${weaver.e9.workflow.create-path}") private String createPath; public String createWorkflow(WorkflowRequest request) { String token = authService.getToken(); String url = baseUrl + createPath; HttpPost httpPost = new HttpPost(url); httpPost.setHeader("Authorization", "Bearer " + token); try { // 构建请求体 JSONObject params = new JSONObject(); params.put("workflowid", request.getWorkflowId()); params.put("requestid", request.getRequestId()); params.put("formmode", 1); params.put("isupdate", 0); // 添加表单数据 JSONObject formData = new JSONObject(); for (Map.Entry<String, Object> entry : request.getFormData().entrySet()) { formData.put(entry.getKey(), entry.getValue()); } params.put("formdata", formData); // 处理附件 if (!CollectionUtils.isEmpty(request.getFiles())) { JSONArray fileList = new JSONArray(); for (WorkflowFile file : request.getFiles()) { JSONObject fileJson = new JSONObject(); fileJson.put("filename", file.getFilename()); fileJson.put("filecontent", Base64.getEncoder() .encodeToString(file.getContent())); fileList.add(fileJson); } params.put("filelist", fileList); } httpPost.setEntity(new StringEntity(params.toJSONString(), ContentType.APPLICATION_JSON)); HttpResponse response = HttpClients.createDefault().execute(httpPost); String result = EntityUtils.toString(response.getEntity()); return JSONObject.parseObject(result).getString("requestid"); } catch (Exception e) { throw new RuntimeException("创建流程失败", e); } } }5. 常见问题与解决方案
5.1 Token失效问题
现象:偶尔会出现Token无效的错误(错误码401)
解决方案:
- 实现Token自动刷新机制(如我们前面代码所示)
- 增加重试逻辑,当遇到401时自动刷新Token并重试一次
// 在WorkflowService中增加重试逻辑 public String createWorkflowWithRetry(WorkflowRequest request) { try { return createWorkflow(request); } catch (RuntimeException e) { if (e.getMessage().contains("401")) { // 强制刷新Token authService.forceRefreshToken(); return createWorkflow(request); } throw e; } }5.2 附件上传问题
现象:大文件上传失败或超时
优化方案:
- 分块上传大文件
- 设置合理的超时时间
// 修改HttpClient配置 RequestConfig config = RequestConfig.custom() .setConnectTimeout(30000) .setSocketTimeout(60000) .build(); CloseableHttpClient httpClient = HttpClientBuilder.create() .setDefaultRequestConfig(config) .build();5.3 性能优化建议
- 使用连接池管理HTTP连接
- 对频繁调用的接口添加本地缓存
- 异步处理非关键路径操作
// 使用连接池 PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(); connManager.setMaxTotal(200); connManager.setDefaultMaxPerRoute(50); CloseableHttpClient httpClient = HttpClientBuilder.create() .setConnectionManager(connManager) .build();6. 完整调用示例
6.1 控制器层实现
@RestController @RequestMapping("/api/workflow") public class WorkflowController { @Autowired private WorkflowService workflowService; @PostMapping("/create") public ResponseEntity<?> create(@RequestBody WorkflowCreateDTO dto) { try { WorkflowRequest request = new WorkflowRequest(); request.setWorkflowId(dto.getWorkflowId()); request.setRequestId(UUID.randomUUID().toString()); Map<String, Object> formData = new HashMap<>(); formData.put("field1", dto.getField1()); formData.put("field2", dto.getField2()); request.setFormData(formData); if (dto.getFile() != null) { List<WorkflowFile> files = new ArrayList<>(); files.add(new WorkflowFile(dto.getFile().getOriginalFilename(), dto.getFile().getBytes())); request.setFiles(files); } String requestId = workflowService.createWorkflowWithRetry(request); return ResponseEntity.ok(requestId); } catch (Exception e) { return ResponseEntity.status(500).body(e.getMessage()); } } }6.2 测试用例
@SpringBootTest class WorkflowServiceTest { @Autowired private WorkflowService workflowService; @Test void testCreateWorkflow() { WorkflowRequest request = new WorkflowRequest(); request.setWorkflowId("123"); request.setRequestId("TEST-" + System.currentTimeMillis()); Map<String, Object> formData = new HashMap<>(); formData.put("title", "测试流程"); formData.put("amount", 1000); request.setFormData(formData); String requestId = workflowService.createWorkflow(request); assertNotNull(requestId); System.out.println("创建的流程ID:" + requestId); } }7. 生产环境注意事项
- 监控与告警:建议对Token获取失败、流程创建失败等情况设置监控告警
- 日志记录:详细记录请求和响应数据,方便问题排查
- 限流措施:对高频接口添加限流保护,避免对泛微服务器造成过大压力
- 数据校验:在调用API前做好数据校验,避免无效请求
// 示例:使用Guava RateLimiter做限流 private RateLimiter rateLimiter = RateLimiter.create(50); // 每秒50个请求 public String createWorkflowWithRateLimit(WorkflowRequest request) { if (!rateLimiter.tryAcquire()) { throw new RuntimeException("操作过于频繁,请稍后再试"); } return createWorkflow(request); }8. 扩展优化方向
- 异步处理:对于非实时要求的流程,可以引入消息队列异步处理
- 结果回调:配置流程审批结果回调通知
- 模板管理:动态管理流程模板,避免硬编码
- 性能监控:添加接口耗时监控,及时发现性能瓶颈
// 异步处理示例 @Async public void asyncCreateWorkflow(WorkflowRequest request) { try { createWorkflow(request); } catch (Exception e) { // 记录失败日志 log.error("异步创建流程失败", e); } }在实际项目中,我们还将常用流程模板ID配置在了数据库中,通过后台管理界面可以动态调整,避免了每次修改模板都需要重新发布代码的问题。同时建议为每个业务系统分配独立的API账号,方便后续的权限管理和操作审计。