当前位置: 首页 > article >正文

java实现coze平台鉴权+工作流调用(踩坑记录)

问题偏多建议大家看完文章后再开始实现

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_code
  code: 授权码
  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: ";
    @Resource
    private 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 {

    @Resource
    private CozeProperties cozeProperties;

    @Resource
    private CommonCache commonCache;

    @Autowired
    private 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 {
    @Override
    public 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 {


    @Resource
    private CozeProperties cozeProperties;

    @Resource
    private CommonCache commonCache;

    @Autowired
    private 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 {


    @Resource
    private CozeProperties cozeProperties;

    @Resource
    private CommonCache commonCache;

    @Autowired
    private 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
在这里插入图片描述
成功

在这里插入图片描述


http://www.kler.cn/a/599631.html

相关文章:

  • Springboot的jak安装与配置教程
  • java版嘎嘎快充玉阳软件互联互通中电联云快充协议充电桩铁塔协议汽车单车一体充电系统源码uniapp
  • 0324-项目
  • 豆包AI插件:提升浏览器使用效率的智能助手
  • 10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?
  • 笔记:代码随想录算法训练营day60:并查集理论基础、寻找存在的路径
  • vue2中引入elementui
  • Qt在ARM中,如何使用drmModeObjectSetProperty 设置 Plane 的 zpos 值
  • 在 Kubernetes 中部署 Trivy 漏洞扫描服务
  • 地理信息系统(GIS)在智慧城市中的40个应用场景案例
  • BSides Vancouver 2018靶机通关教学
  • ROS2下MoveIt+Rviz+MuJoCo 三剑合璧!Panda 机械臂联动仿真!
  • Box-Cox变换:让数据服从正态分布的数学魔法
  • [unity 点击事件] 区域响应点击事件,排除子节点区域,Raycast Target 应用
  • 简单描述一下,大型语言模型简史
  • An Easy Problem(信息学奥赛一本通-1223)
  • 计算机是如何工作的
  • 【Ratis】SlideWindow滑动窗口机制
  • 在C++ Qt中集成Halcon窗口并实现跨平台兼容和大图加载
  • IIS漏洞再现