问题偏多建议大家看完文章后再开始实现
OAuth鉴权
https://www.coze.cn/open/docs/developer_guides/preparation
https://www.coze.cn/open/docs/developer_guides/oauth_apps
OAuth 授权码鉴权
https://www.coze.cn/open/docs/developer_guides/oauth_code
创建OAuth应用
https://www.coze.cn/open/oauth/apps
获取访问令牌
https://www.coze.cn/api/permission/oauth2/authorize?response_type=code&client_id=22***替换为自己的.app.coze&redirect_uri=http://localhost:48080/admin-api/ai/coze/oauth&state=
最后的state=
无论使用与否都需要携带
这里如果本地的接口写好的话,他会直接请求接口做后续逻辑,我这里测试就一切从简
存好url中的code= 后面的授权码
获取 OAuth Access Token
postman访问
https://api.coze.cn/api/permission/oauth2/token
Authorization Bearer
{
"grant_type":"authorization_code",
"code":"code_",
"redirect_uri":"http://localhost:48080/admin-api/ai/coze/oauth"
,"client_id":""}
以上报错是code有问题,重新授权拿新的授权码再试就成功了
coze配置文件
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;/*** @author YXY* @date 2025-03-24* @description coze配置文件*/
@Data
@RefreshScope
public class CozeProperties {@Value("${coze.clientSecret}")private String clientSecret;@Value("${coze.grantType}")private String grantType;@Value("${coze.code}")private String code;@Value("${coze.clientId}")private String clientId;@Value("${coze.redirectUri}")private String redirectUri;@Value("${coze.oAuthAccessTokenUri}")private String oAuthAccessTokenUri;}
配置文件
coze:clientSecret: 创建 OAuth 应用时获取的客户端密钥grantType: authorization_codecode: 授权码clientId: 创建 OAuth 应用时获取的客户端 ID。 redirectUri: 创建 OAuth 应用时指定的重定向 URL。 oAuthAccessTokenUri: https://api.coze.cn/api/permission/oauth2/token
import com.alibaba.fastjson.JSONObject;
import com.goodsoft.shrk.module.ai.controller.admin.coze.vo.CozeAuthRespVo;
import com.goodsoft.shrk.module.ai.enums.coze.CozeEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.concurrent.TimeUnit;
/*** @author YXY* @date 2025-03-24* @description coze API* */
@Component
@Slf4j
public class CozeApiClient {private static final String MEDIA_TYPE_JSON = "application/json; charset=utf-8";private final static OkHttpClient client = new OkHttpClient().newBuilder().connectionPool(new ConnectionPool(100, 5, TimeUnit.MINUTES)).readTimeout(60 * 10, TimeUnit.SECONDS).build();private static final String ERROR_MESSAGE = "Unexpected code: ";@Resourceprivate CozeProperties cozeProperties;/*** 获取token* @return*/public CozeAuthRespVo getAccessToken() {try {JSONObject jsonObject = new JSONObject();jsonObject.put(CozeEnum.GRANT_TYPE.getName(), cozeProperties.getGrantType());jsonObject.put(CozeEnum.CODE.getName(), cozeProperties.getCode());jsonObject.put(CozeEnum.CLIENT_ID.getName(), cozeProperties.getClientId());jsonObject.put(CozeEnum.REDIRECT_URI.getName(), cozeProperties.getRedirectUri());RequestBody body = RequestBody.create(jsonObject.toString(), MediaType.get(MEDIA_TYPE_JSON));Request request = buildAuthRequest(cozeProperties.getOAuthAccessTokenUri(), body);String res = executeRequest(request);CozeAuthRespVo cozeAuthRespVo = JSONObject.parseObject(res, CozeAuthRespVo.class);return cozeAuthRespVo;} catch (IOException e) {e.printStackTrace();throw new RuntimeException(e);}}/*** 构建获取鉴权请求* @param url* @param body* @return*/private Request buildAuthRequest(String url, RequestBody body) {return new Request.Builder().url(url).addHeader(CozeEnum.AUTHORIZATION.getName(), CozeEnum.BEARER+ cozeProperties.getClientSecret()).post(body).build();}/*** 发送请求* @param request* @return* @throws IOException*/private String executeRequest(Request request) throws IOException {try (Response response = client.newCall(request).execute()) {if (!response.isSuccessful()) {System.out.println(response.body().string());throw new IOException(ERROR_MESSAGE + response);}String res = response.body().string();return res;}}
}
以上获取 Access Token 成功了,但是根据官网调用 刷新OAuth Access Token 一直失败
所以打算换种方式实现
采用官网提供的SDK进行实现
官方github的demo地址:https://github.com/coze-dev/coze-java/tree/main/example/src/main/java/example
pom中引入
因为我的项目中有使用 okhttp3 版本冲突了,所以这里处理了下
<dependency><groupId>com.coze</groupId><artifactId>coze-api</artifactId><version>LATEST</version><exclusions><exclusion><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId></exclusion></exclusions></dependency>
CozeProperties 配置文件
package com.goodsoft.shrk.module.ai.config.coze;import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;/*** @author YXY* @date 2025-03-24* @description coze鉴权配置文件*/
@Data
@RefreshScope
public class CozeProperties {@Value("${coze.clientSecret}")private String clientSecret;@Value("${coze.code}")private String code;@Value("${coze.clientId}")private String clientId;@Value("${coze.redirectUri}")private String redirectUri;
}
import com.coze.openapi.client.auth.OAuthToken;
import com.coze.openapi.service.auth.WebOAuthClient;
import com.coze.openapi.service.config.Consts;
import com.goodsoft.shrk.framework.redis.config.CommonCache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/*** @author YXY* @date 2025-03-24* @description coze API*/
@Component
@Slf4j
public class CozeApiClient {@Resourceprivate CozeProperties cozeProperties;@Resourceprivate CommonCache commonCache;@Autowiredprivate RedissonClient redissonClient;private static final String LOCK_KEY = "COZE_ACCESS_TOKEN_LOCK";private static final String COZE_ACCESS_TOKEN= "coze_access_token:";/*** 获取token* @return*/public OAuthToken getAccessToken() {if (commonCache.hasKey(COZE_ACCESS_TOKEN)){return commonCache.getVal(COZE_ACCESS_TOKEN);}boolean lockAcquired = false;int maxRetries = 5; // 最大重试次数int retries = 0; // 当前重试次数RLock lock = redissonClient.getLock(LOCK_KEY);if (!commonCache.hasKey(COZE_ACCESS_TOKEN)){while (!lockAcquired && retries < maxRetries) {try {// 尝试获取锁,最多等待 5 秒钟lockAcquired = lock.tryLock(5, TimeUnit.SECONDS);if (lockAcquired) {try {WebOAuthClient oauth =new WebOAuthClient.WebOAuthBuilder().clientID(cozeProperties.getClientId()).clientSecret(cozeProperties.getClientSecret()).baseURL(Consts.COZE_CN_BASE_URL).build();OAuthToken resp = oauth.getAccessToken(cozeProperties.getCode(), cozeProperties.getRedirectUri());log.info( "获取CozeApi-token成功:{}", resp);resp = oauth.refreshToken(resp.getRefreshToken());log.info( "刷新CozeApi-token成功:{}", resp);commonCache.set(COZE_ACCESS_TOKEN,resp,resp.getExpiresIn()-60,TimeUnit.SECONDS);} finally {lock.unlock(); // 释放锁}} else {// 未能获取锁,重试retries++;Thread.sleep(100); // 重试间隔}} catch (InterruptedException e) {// 处理中断异常e.printStackTrace();// 尝试重新标记中断状态,并继续重试Thread.currentThread().interrupt();}}}return commonCache.getVal(COZE_ACCESS_TOKEN);}
}
测试鉴权成功,进行下一步
执行工作流
找到工作流的 workflow_id
demo
public void reqWorkflows(){OAuthToken accessToken = getAccessToken();TokenAuth authCli = new TokenAuth(accessToken.getAccessToken());CozeAPI coze =new CozeAPI.Builder().baseURL(Consts.COZE_CN_BASE_URL).auth(authCli).readTimeout(10000).build();HashMap<String, Object> map = new HashMap<>();map.put("input", "生成一个设计方案");map.put("fileUrl", "https://***.pdf");map.put("title", "标题");RunWorkflowResp workflowResp = coze.workflows().runs().create(RunWorkflowReq.builder().workflowID("******工作流ID").parameters(map).build());System.out.println(workflowResp);}
做到这里我发现这个授权码code用一次就失效了,每次都得点击页面授权,这个需要前端打开授权页,然后用户点击授权成功回调接口执行后续逻辑,而我们需要的业务没有页面授权这一操作,
应该采用OAuth JWT 授权
OAuth JWT 授权
https://www.coze.cn/open/docs/developer_guides/oauth_jwt
重新创建JWT应用
点击创建Key 自动下载私钥 复制公钥备用
将私钥 private_key.pem
文件放到项目的resources
下
CozeProperties配置文件
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;/*** @author YXY* @date 2025-03-24* @description coze鉴权配置文件*/
@Data
@RefreshScope
public class CozeProperties {@Value("${coze.clientId}")private String clientId;@Value("${coze.jwt.publicKey}")private String publicKey;public String getPrivateKey() {try (InputStream inputStream = CozeApiClient.class.getClassLoader().getResourceAsStream("private_key.pem")) {if (inputStream == null) {throw new RuntimeException("私钥文件未找到");}return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);} catch (IOException e) {throw new RuntimeException("读取私钥文件失败", e);}}
}
根据官网提供方式进行测试:
public static void main(String[] args) {String jwtOauthClientID = "应用ID";String jwtOauthPrivateKey = getPrivateKey();String jwtOauthPublicKeyID = "公钥";JWTOAuthClient oauth = null;try {oauth =new JWTOAuthClient.JWTOAuthBuilder().clientID(jwtOauthClientID).privateKey(jwtOauthPrivateKey).publicKey(jwtOauthPublicKeyID).baseURL(Consts.COZE_CN_BASE_URL).build();} catch (Exception e) {e.printStackTrace();return;}try {OAuthToken resp = oauth.getAccessToken();System.out.println(resp);} catch (Exception e) {e.printStackTrace();}}public static String getPrivateKey() {try (InputStream inputStream = CozeApiClient.class.getClassLoader().getResourceAsStream("private_key.pem")) {if (inputStream == null) {throw new RuntimeException("私钥文件未找到");}return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);} catch (IOException e) {throw new RuntimeException("读取私钥文件失败", e);}}
运行报错
找到原因:https://github.com/coze-dev/coze-java?tab=readme-ov-file#jwt-oauth-app
需要自己实现com.coze.openapi.service.auth.JWTBuilder这个接口
https://github.com/coze-dev/coze-java/blob/main/example/src/main/java/example/auth/ExampleJWTBuilder.java
需要实现自己的 JWTBuilder
import com.coze.openapi.service.auth.JWTBuilder;
import com.coze.openapi.service.auth.JWTPayload;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.NoArgsConstructor;import java.security.PrivateKey;
import java.util.Map;/*** @author YXY* @date 2025-03-24* @description*/
@NoArgsConstructor
public class ExampleJWTBuilder implements JWTBuilder {@Overridepublic String generateJWT(PrivateKey privateKey, Map<String, Object> header, JWTPayload payload) {try {JwtBuilder jwtBuilder =Jwts.builder().setHeader(header).setIssuer(payload.getIss()).setAudience(payload.getAud()).setIssuedAt(payload.getIat()).setExpiration(payload.getExp()).setId(payload.getJti()).signWith(privateKey, SignatureAlgorithm.RS256);if (payload.getSessionName() != null) {jwtBuilder.claim("session_name", payload.getSessionName());}return jwtBuilder.compact();} catch (Exception e) {throw new RuntimeException("Failed to generate JWT", e);}}
}
实现后进行使用
成功
package com.goodsoft.shrk.module.ai.config.coze;import com.coze.openapi.client.auth.OAuthToken;
import com.coze.openapi.service.auth.JWTOAuthClient;
import com.coze.openapi.service.config.Consts;
import com.goodsoft.shrk.framework.redis.config.CommonCache;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;import static com.goodsoft.shrk.module.system.dal.redis.RedisKeyConstants.COZE_ACCESS_TOKEN;/*** @author YXY* @date 2025-03-24* @description coze鉴权配置文件*/
@Component
@Slf4j
public class CozeApiClient {@Resourceprivate CozeProperties cozeProperties;@Resourceprivate CommonCache commonCache;@Autowiredprivate RedissonClient redissonClient;private static final String LOCK_KEY = "COZE_ACCESS_TOKEN_LOCK";/*** 获取token* @return*/public OAuthToken getAccessToken() {if (commonCache.hasKey(COZE_ACCESS_TOKEN)){return commonCache.getVal(COZE_ACCESS_TOKEN);}boolean lockAcquired = false;int maxRetries = 5; // 最大重试次数int retries = 0; // 当前重试次数RLock lock = redissonClient.getLock(LOCK_KEY);if (!commonCache.hasKey(COZE_ACCESS_TOKEN)){while (!lockAcquired && retries < maxRetries) {try {// 尝试获取锁,最多等待 5 秒钟lockAcquired = lock.tryLock(5, TimeUnit.SECONDS);if (lockAcquired) {try {OAuthToken oAuthToken = getOAuthToken();commonCache.set(COZE_ACCESS_TOKEN,oAuthToken,oAuthToken.getExpiresIn()-60,TimeUnit.SECONDS);} finally {lock.unlock(); // 释放锁}} else {// 未能获取锁,重试retries++;Thread.sleep(100); // 重试间隔}} catch (InterruptedException e) {// 处理中断异常e.printStackTrace();// 尝试重新标记中断状态,并继续重试Thread.currentThread().interrupt();}}}return commonCache.getVal(COZE_ACCESS_TOKEN);}public OAuthToken getOAuthToken() {String jwtOauthClientID = cozeProperties.getClientId();String jwtOauthPrivateKey = cozeProperties.getPrivateKey();String jwtOauthPublicKeyID = cozeProperties.getPublicKey();OAuthToken resp = null;try {JWTOAuthClient oauth =new JWTOAuthClient.JWTOAuthBuilder().clientID(jwtOauthClientID).privateKey(jwtOauthPrivateKey).publicKey(jwtOauthPublicKeyID).baseURL(Consts.COZE_CN_BASE_URL).jwtBuilder(new ExampleJWTBuilder()).build();resp = oauth.getAccessToken();System.out.println(resp);} catch (Exception e) {e.printStackTrace();}return resp;}}
鉴权搞定之后进行调用工作流
工作流入参实体,工作流id 和 工作流入参数据
package com.goodsoft.shrk.module.ai.controller.admin.coze.vo;import lombok.Data;import java.util.HashMap;/*** @author YXY* @date 2025-03-24* @description*/
@Data
public class WorkflowsReqVo {private String workflowID;private HashMap<String, Object> parameters;
}
CozeApiClient
这里的 COZE_ACCESS_TOKEN = "coze_access_token:";
提取到redis常量池中了
import com.coze.openapi.client.auth.OAuthToken;
import com.coze.openapi.client.workflows.run.RunWorkflowReq;
import com.coze.openapi.client.workflows.run.RunWorkflowResp;
import com.coze.openapi.service.auth.JWTOAuthClient;
import com.coze.openapi.service.auth.TokenAuth;
import com.coze.openapi.service.config.Consts;
import com.coze.openapi.service.service.CozeAPI;
import com.goodsoft.shrk.framework.redis.config.CommonCache;
import com.goodsoft.shrk.module.ai.controller.admin.coze.vo.WorkflowsReqVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;import static com.goodsoft.shrk.module.system.dal.redis.RedisKeyConstants.COZE_ACCESS_TOKEN;/*** @author YXY* @date 2025-03-24* @description coze鉴权配置文件*/
@Component
@Slf4j
public class CozeApiClient {@Resourceprivate CozeProperties cozeProperties;@Resourceprivate CommonCache commonCache;@Autowiredprivate RedissonClient redissonClient;private static final String LOCK_KEY = "COZE_ACCESS_TOKEN_LOCK";/*** 获取token* @return*/public OAuthToken getAccessToken() {if (commonCache.hasKey(COZE_ACCESS_TOKEN)){return commonCache.getVal(COZE_ACCESS_TOKEN);}boolean lockAcquired = false;int maxRetries = 5; // 最大重试次数int retries = 0; // 当前重试次数RLock lock = redissonClient.getLock(LOCK_KEY);if (!commonCache.hasKey(COZE_ACCESS_TOKEN)){while (!lockAcquired && retries < maxRetries) {try {// 尝试获取锁,最多等待 5 秒钟lockAcquired = lock.tryLock(5, TimeUnit.SECONDS);if (lockAcquired) {try {OAuthToken oAuthToken = getOAuthToken();commonCache.set(COZE_ACCESS_TOKEN,oAuthToken,oAuthToken.getExpiresIn()-60,TimeUnit.SECONDS);} finally {lock.unlock(); // 释放锁}} else {// 未能获取锁,重试retries++;Thread.sleep(100); // 重试间隔}} catch (InterruptedException e) {// 处理中断异常e.printStackTrace();// 尝试重新标记中断状态,并继续重试Thread.currentThread().interrupt();}}}return commonCache.getVal(COZE_ACCESS_TOKEN);}public OAuthToken getOAuthToken() {String jwtOauthClientID = cozeProperties.getClientId();String jwtOauthPrivateKey = cozeProperties.getPrivateKey();String jwtOauthPublicKeyID = cozeProperties.getPublicKey();OAuthToken resp = null;try {JWTOAuthClient oauth =new JWTOAuthClient.JWTOAuthBuilder().clientID(jwtOauthClientID).privateKey(jwtOauthPrivateKey).publicKey(jwtOauthPublicKeyID).baseURL(Consts.COZE_CN_BASE_URL).jwtBuilder(new ExampleJWTBuilder()).build();resp = oauth.getAccessToken();System.out.println(resp);} catch (Exception e) {e.printStackTrace();}return resp;}/*** 调用工作流*/public void reqWorkflows(WorkflowsReqVo req){OAuthToken accessToken = getAccessToken();TokenAuth authCli = new TokenAuth(accessToken.getAccessToken());CozeAPI coze =new CozeAPI.Builder().baseURL(Consts.COZE_CN_BASE_URL).auth(authCli).readTimeout(10000).build();RunWorkflowResp workflowResp = coze.workflows().runs().create(RunWorkflowReq.builder().workflowID(req.getWorkflowID()).parameters(req.getParameters()).isAsync(true).build());System.out.println(workflowResp);}}
因为工作流执行时间较长,我的工作流配置了执行完成后 http请求我的接口进行后续逻辑处理,所以这里开启了异步执行
isAsync(true)
代表异步调用工作流
coze工作流发送http请求:https://blog.csdn.net/YXWik/article/details/146398637
成功