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

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇:Apollo Client 配置与缓存

上一篇:GraphQL 入门篇:基础查询语法

依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在:

https://github.com/GoldenaArcher/graphql-by-example

异常处理

官方文档参考: Error Handling

graphql 处理默认异常是没什么问题的,比如说 id 没有提供的情况:

但是,再找不到数据的情况下,graphql 就会返回 null 而非报错:

诚然,使用 !Company 去强制执行非空也是一种解决方案:

type Query {job(id: ID!): Jobjobs: [Job]company(id: ID!): Company!
}

但是,这也会同样触发其他的问题,比如说下面这个情况,当所找的公司不存在,又如果使用非空操作就会跑出内部异常错误,同时 job 的 query 也会终止:

尽管这种调用方式比较少见,但是也是具有特定的业务场景的,因此返回值并不推荐使用强制非空操作,而是建议使用 custom error

custom errors - server

这里只需要更新 resolvers 即可:

import { GraphQLError } from "graphql";
import { getJobs, getJob, getJobsByCompany } from "./db/jobs.js";
import { getCompany } from "./db/companies.js";export const resolvers = {Query: {// new code here, can extract into functionjob: async (_root, { id }) => {const job = await getJob(id);if (!job) {throw new GraphQLError("Job not found: " + id, {extensions: {code: "NOT_FOUND",},});}return job;},jobs: async () => getJobs(),// new code herecompany: async (_root, { id }) => {const company = await getCompany(id);if (!company) {throw new GraphQLError("Company not found: " + id, {extensions: {code: "NOT_FOUND",},});}return company;},},
};

GraphQLError 我这里用了 class-based 的实现:

class GraphQLNotFound extends GraphQLError {constructor(message) {super(message, {extensions: {code: "NOT_FOUND",// this will change the entire graphql status code, be sure to discuss how to approach it firsthttp: {status: 404,},},});}
}

用方法跑出对应的 GraphQLError 也可以

customer errors - client

服务端这里,主要还是在 fetch 的时候进行对应的操作,和正常的 rest 操作也没什么区别:

import { useParams } from "react-router";
import { useEffect, useState } from "react";
import { getCompany } from "../lib/graphql/queries";
import JobList from "../components/JobList";function CompanyPage() {const { companyId } = useParams();const [state, setState] = useState({company: null,loading: true,hasErrors: false,});useEffect(() => {(async () => {try {const company = await getCompany(companyId);setState({ company, loading: false, hasErrors: false });} catch (e) {setState({company: null,loading: false,hasErrors: true,});}})();}, [companyId]);const { company, loading, hasErrors } = state;if (loading) {return <div>Loading...</div>;}if (hasErrors) {return <div>Something went wrong...</div>;}return (<div><h1 className="title">{company.name}</h1><div className="box">{company.description}</div><h2 className="title is-5">Jobs At {company.name}</h2><JobList jobs={company.jobs} /></div>);
}export default CompanyPage;

效果如下:

mutation - add

开始 CUD 操作,这三类都可以被归类到 Mutation 中

mutation - add, server

company 来说,只有 title 和 description 可以被更新,不过因为目前没有做 auth 部分的实现,所以还是会写在 signature 里——但是没用上

  • schema
    type Mutation {createJob(title: String!, description: String, companyId: ID!): Job
    }
    
  • resolver
      Mutation: {createJob: (_root, { title, description }) => {const companyId = "FjcJCHJALA4i"; // TODO - change it laterreturn createJob({ companyId, title, description });},},
    

sandbox 中验证更新:

mutation - add, client

  • queries
    query 这里的代码和 server 中差不多,不过返回数据只显示 id 去重新拉数据
    CreateJobInput 的实现看下一个 section
    export async function createJob({ title, description }) {const mutation = gql`mutation CreateJob($input: CreateJobInput!) {createJob(input: $input) {id}}`;const { createJob: job } = await client.request(mutation, {input: {title,description,},});return job;
    }
    
  • react
    import { useState } from "react";
    import { useNavigate } from "react-router-dom";
    import { createJob } from "../lib/graphql/queries";function CreateJobPage() {const navigate = useNavigate();const [title, setTitle] = useState("");const [description, setDescription] = useState("");const handleSubmit = async (event) => {event.preventDefault();const job = await createJob({ title, description });console.log(job);navigate(`/jobs/${job.id}`);};// 省略 JSX
    }export default CreateJobPage;
    

效果如下:

Input Type

在 graphql 里,input 和 output 是两种不同的类型。之前定义的是 output,下面会重新定义 input:

  • schema
    type Mutation {createJob(input: CreateJobInput!): Job
    }input CreateJobInput {title: String!description: String
    }
    

mutation - delete

这里只更新 server 端:

  • schema
    type Mutation {deleteJob(id: ID!): Job
    }
    
  • resolvers
      Mutation: {deleteJob: (_root, { id }) => {deleteJob(id);},},
    

mutation - update

这里只更新 server 端:

  • schema
    type Mutation {updateJob(input: UpdateJobInput!): Job
    }input UpdateJobInput {id: ID!title: Stringdescription: String
    }
    
  • resolvers
      Mutation: {updateJob: async (_root, { input: { id, title, description } }) => {await updateJob({ id, title, description });},},
    

认证(Authentication)

具体的认证流程就不说了,这里依旧用的是基础的 jwt 进行认证

登录时会将 jwt 保存到 local storage 中,这里就简单讲一下,怎么在 graphql 中传递和处理 jwt

目前这段处理服务端代码

  • server
    const getContext = ({ req }) => {return {auth: req.auth,};
    };app.use("/graphql", apolloMiddleware(aplloServer, { context: getContext }));
    
  • resolvers
        updateJob: async (_root, { input: { id, title, description } }, {auth}) => {if (!auth) {throw new GraphQLUnauthorized("Unauthorized");}await updateJob({ id, title, description });
    

效果展示:

用户 ↔ 公司 关联

前面是写死了公司 id,现在会用更符合现实逻辑的方式去实现:

  • server
    const getContext = async ({ req }) => {if (!req.auth) {return {};}const user = await getUser(req.auth.sub);return {user,};
    };
    
    sub 可以理解成用户 id:Asserts the identity of the user, called subject in OpenID (sub).
  • resolver
        createJob: (_root, { input: { title, description } }, context) => {if (!context.user) {throw new GraphQLUnauthorized("Unauthorized");}const companyId = context.user.companyId; // TODO - change it laterreturn createJob({ companyId, title, description });},
    

效果如下:

认证(Authorization)- 客户端

目前后端加了验证,前端如果不传 auth token 的话就无法实现增加的功能:

客户端的实现更多的和用的包有关,这部分会提一下 graqhql-request 的实现,后面替换成 apollo 的 client 也会完成对应的更换:

const client = new GraphQLClient("http://localhost:9000/graphql", {headers: () => {const accessToken = getAccessToken();if (accessToken) {return {Authorization: "Bearer " + accessToken,};}return {};},
});

效果如下:

认证(Authorization)- 删除

删除功能除了认证用户之外,还需要确认当前用户是否可以删除对应的记录,比如说现在的实现,A 公司的用户可以删除 B 公司的 post,从而开启一场朴实无华的商业战:

因此,在做认证的时候就需要多加一步:

deleteJob: async (_root, { id }, { user }) => {if (!user) {throw new GraphQLUnauthorized("Unauthorized");}const job = await deleteJob(id, user.companyId);if (!job) {throw new GraphQLNotFound("Job not found: " + id);}return job;
};

这里的 deleteJob 会通过 idcompanyId 去查找并返回对应的记录

设置 apollo-client

apollo-client 是官方提供的 client 和 server 一样,优势在于提供更可靠的 cache 机制,替换方式相对而言也比较简单——尤其是 graphql-request 和 apollo-client 的冲突不算特别多的情况下,完整的实现如下:

import {ApolloClient,ApolloLink,concat,createHttpLink,gql,InMemoryCache,
} from "@apollo/client";
import { getAccessToken } from "../auth";const httpLink = createHttpLink({uri: "http://localhost:9000/graphql",
});const authLink = new ApolloLink((operation, forward) => {const accessToken = getAccessToken();if (accessToken) {operation.setContext({headers: {authorization: `Bearer ${accessToken}`,},});}return forward(operation);
});const apolloClient = new ApolloClient({link: concat(authLink, httpLink),cache: new InMemoryCache(),
});export async function createJob({ title, description }) {const mutation = gql`mutation CreateJob($input: CreateJobInput!) {createJob(input: $input) {id}}`;const { data } = await apolloClient.mutate({mutation,variables: {input: {title,description,},},});return data.createJob;
}export async function getJobs() {const query = gql`query {jobs {iddatetitlecompany {idname}}}`;const { data } = await apolloClient.query({ query });return data.jobs;
}export async function getJob(id) {const query = gql`query ($id: ID!) {job(id: $id) {iddatetitledescriptioncompany {idname}}}`;const { data } = await apolloClient.query({query,variables: { id },});return data.job;
}export async function getCompany(id) {const query = gql`query Jobs($id: ID!) {company(id: $id) {idnamedescriptionjobs {iddatetitle}}}`;const { data } = await apolloClient.query({ query, variables: { id } });return data.company;
}

这里主要做了几个部分:

  • link
    类比的话,就是 apollo 的 middleware,官方的图解如下:

    目前常见的 Link 有:
    Link 类型用途
    HttpLink把请求发到 GraphQL 后端(最常用)
    ErrorLink捕获 GraphQL 错误 / 网络错误,打印或重定向
    AuthLink / ApolloLink自定义 headers,例如添加 token
    RetryLink请求失败时自动 retry(可设置 retry 策略)
    BatchHttpLink把多个 query 合并成一个 HTTP 请求
    这里只会用到 HttpLinkApolloLink
    需要注意 chaining 的顺序,HttpLink 必须是第一个调用的,按顺序必须在 concat 的最后一个
    对于非 HttpLink 来说,需要注意 forward(operation) 进行下一步操作
  • cache
    这个下一个 section 会讲的更多一些,这里开启了 InMemoryCache
  • 验证
    就是 ApolloLink 中实现的内容
    这里主要实现的内容就是绑定了 Authorization header,实现方式和 graphql-request 略有不同,本质上是一样的
  • query/mutation 的转换
    graphql-request 只有 request,apollo-client 则是像 server 一样分离了 query 和 mutation
    然后就是返回结果不一样
    这两点需要注意一下

cache

缓存只会在 client 做,server 是没有缓存机制的

这也是为啥课程换用 apollo-client 了吧……

Caching in Apollo Client

前面开启了默认的 InMemoryCache ,实现之后,当遇到数据已经获取的情况下,就不会重新获取数据。如下面这张图:

从主页面到子页面还会进行 fetch——因为缺乏必要的 description,但是当从子页面返回主页面时,数据在已经存在的情况下,就不会自动更新了

他们这个机制还是有点复杂的,Apollo Client 会以对象结构保存请求字段(不是 query 名称,而是字段结构 + 参数值)对应的数据。每次请求时,它根据:

  1. 查询字段结构是否一致(即你请求了哪些字段);
  2. 传入的变量(如 $id)是否一致;
  3. 缓存中是否已有这些字段对应的数据(使用 normalized cache,如 Job:123);

如果三者都满足,就会直接从缓存中读取,避免触发网络请求。

cache 机制

目前官方的 client 提供的机制有下面几个:

具体说明如下:

NameDescriptionDescription
cache-firstApollo Client first executes the query against the cache. If all requested data is present in the cache, that data is returned. Otherwise, Apollo Client executes the query against your GraphQL server and returns that data after caching it.Prioritizes minimizing the number of network requests sent by your application.This is the default fetch policy.缓存优先策略:优先从缓存中读取数据,若缓存不完整则发起网络请求,并将结果缓存。默认策略,适合数据变化不频繁的场景。
cache-onlyApollo Client executes the query only against the cache. It never queries your server in this case.A cache-only query throws an error if the cache does not contain data for all requested fields.仅使用缓存:完全依赖本地缓存,不发起任何网络请求。若缓存中无数据或不完整会抛出错误。适用于确保缓存已存在的场景。
cache-and-networkApollo Client executes the full query against both the cache and your GraphQL server. The query automatically updates if the result of the server-side query modifies cached fields.Provides a fast response while also helping to keep cached data consistent with server data.缓存和网络并用:先返回缓存数据以快速响应,再请求服务端数据并更新缓存。兼顾速度与一致性。
network-onlyApollo Client executes the full query against your GraphQL server, without first checking the cache. The query’s result is stored in the cache.Prioritizes consistency with server data, but can’t provide a near-instantaneous response when cached data is available.仅走网络,带缓存写入:跳过缓存,始终走网络请求,但结果会写入缓存。适合需要最新数据的情况
no-cacheSimilar to network-only, except the query’s result is not stored in the cache.不使用缓存:请求数据但不写入缓存。适用于一次性查询或敏感数据展示等场景
standbyUses the same logic as cache-first, except this query does not automatically update when underlying field values change. You can still manually update this query with refetch and updateQueries.待机模式:和 cache-first 类似,但不会自动响应数据变更,适合后台组件或非活跃页面手动更新数据的场景

GraphQL client 端自带的 cache 机制……我觉得应该是可以满足大多数的业务需求了

customized query

下面这个实现,在 createJob 里面写满了对应的数据——也就是 jobByIdQuery 中有的数据,就可以在创建数据后,不获取数据库最新的数据,而直接渲染:

const jobByIdQuery = gql`query ($id: ID!) {job(id: $id) {iddatetitlecompany {idname}}}
`;export async function createJob({ title, description }) {const mutation = gql`mutation CreateJob($input: CreateJobInput!) {createJob(input: $input) {id}}`;const { data } = await apolloClient.mutate({mutation,variables: {input: {title,description,},},update: (cache, { data }) => {cache.writeQuery({query: jobByIdQuery,variables: { id: data.createJob.id },data,});},});return data.createJob;
}export async function getJob(id) {const query = jobByIdQuery;const { data } = await apolloClient.query({query,variables: { id },});return data.job;
}

注意, writeQuery 是在 cache 之后手动重写进 cache 的方法

Fragment

上面的案例中已经可以看到, job 的定义已经被复用了好几次。graphql 本身也提供了一个 Fragment 的对象,可以用来改善代码复用的问题:

const jobDetailFragment = gql`fragment JobDetail on Job {iddatetitledescriptioncompany {idname}}
`;const jobByIdQuery = gql`query ($id: ID!) {job(id: $id) {...JobDetail}}${jobDetailFragment}
`;export async function createJob({ title, description }) {const mutation = gql`mutation CreateJob($input: CreateJobInput!) {createJob(input: $input) {...JobDetail}}${jobDetailFragment}`;const { data } = await apolloClient.mutate({mutation,variables: {input: {title,description,},},update: (cache, { data }) => {cache.writeQuery({query: jobByIdQuery,variables: { id: data.createJob.id },data,});},});return data.createJob;
}export async function getJobs() {const query = gql`query {jobs {...JobDetail}}${jobDetailFragment}`;const { data } = await apolloClient.query({ query });return data.jobs;
}export async function getJob(id) {const query = jobByIdQuery;const { data } = await apolloClient.query({query,variables: { id },});return data.job;
}

可以看到,本来需要重复声明的 Job 定义,这里可以用 JobDetail 去获取

这里还是建议类似的 Fragment 保存最低所需的数据,否则就失去 graphql 可以动态获取对应数据的优势了

相关文章:

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…...

微服务通信安全:深入解析mTLS的原理与实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言&#xff1a;微服务时代的通信安全挑战 随着云原生和微服务架构的普及&#xff0c;服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL

ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

Python实现简单音频数据压缩与解压算法

Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中&#xff0c;压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言&#xff0c;提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...

全面解析数据库:从基础概念到前沿应用​

在数字化时代&#xff0c;数据已成为企业和社会发展的核心资产&#xff0c;而数据库作为存储、管理和处理数据的关键工具&#xff0c;在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理&#xff0c;到社交网络的用户数据存储&#xff0c;再到金融行业的交易记录处理&a…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟

2025年4月29日&#xff0c;在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上&#xff0c;可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞&#xff0c;强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…...

ubuntu22.04 安装docker 和docker-compose

首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...

DiscuzX3.5发帖json api

参考文章&#xff1a;PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下&#xff0c;适配我自己的需求 有一个站点存在多个采集站&#xff0c;我想通过主站拿标题&#xff0c;采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...

第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)

第一篇&#xff1a;Liunx环境下搭建PaddlePaddle 3.0基础环境&#xff08;Liunx Centos8.5安装Python3.10pip3.10&#xff09; 一&#xff1a;前言二&#xff1a;安装编译依赖二&#xff1a;安装Python3.10三&#xff1a;安装PIP3.10四&#xff1a;安装Paddlepaddle基础框架4.1…...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

使用SSE解决获取状态不一致问题

使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件&#xff0c;这个上传文件是整体功能的一部分&#xff0c;文件在上传的过程中…...

【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权

摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题&#xff1a;安全。文章将详细阐述认证&#xff08;Authentication) 与授权&#xff08;Authorization的核心概念&#xff0c;对比传统 Session-Cookie 与现代 JWT&#xff08;JS…...

软件工程 期末复习

瀑布模型&#xff1a;计划 螺旋模型&#xff1a;风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合&#xff1a;模块内部功能紧密 模块之间依赖程度小 高内聚&#xff1a;指的是一个模块内部的功能应该紧密相关。换句话说&#xff0c;一个模块应当只实现单一的功能…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...

【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?

FTP&#xff08;File Transfer Protocol&#xff09;本身是一个基于 TCP 的协议&#xff0c;理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况&#xff0c;主要原因包括&#xff1a; ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

算法打卡第18天

从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…...

Visual Studio Code 扩展

Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后&#xff0c;命令 changeCase.commands 可预览转换效果 EmmyLua…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)

cd /home 进入home盘 安装虚拟环境&#xff1a; 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境&#xff1a; virtualenv myenv 3、激活虚拟环境&#xff08;激活环境可以在当前环境下安装包&#xff09; source myenv/bin/activate 此时&#xff0c;终端…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

xmind转换为markdown

文章目录 解锁思维导图新姿势&#xff1a;将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件&#xff08;ZIP处理&#xff09;2.解析JSON数据结构3&#xff1a;递归转换树形结构4&#xff1a;Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...

【WebSocket】SpringBoot项目中使用WebSocket

1. 导入坐标 如果springboot父工程没有加入websocket的起步依赖&#xff0c;添加它的坐标的时候需要带上版本号。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dep…...

恶补电源:1.电桥

一、元器件的选择 搜索并选择电桥&#xff0c;再multisim中选择FWB&#xff0c;就有各种型号的电桥: 电桥是用来干嘛的呢&#xff1f; 它是一个由四个二极管搭成的“桥梁”形状的电路&#xff0c;用来把交流电&#xff08;AC&#xff09;变成直流电&#xff08;DC&#xff09;。…...