阿里云迁移AWS视频点播技术攻坚

文章目录

  • 🐷 背景
  • 🦥 简述
  • 🐥 Aws服务
  • 🦜 AWS CloudFormation
  • 🐞 问题
  • 🐉 落地方案
  • 🦉 Aws vs Aliyun
  • 🍄 避坑指南

🐷 背景

由于AWS整体成本略低于阿里云,公司决定将文件存储模块的业务迁移至AWS,迁移过程中发现,关于视频播放,转码之前阿里云已自动集成相关功能,但Aws并没有原生支持,为此踩了很多坑!

🦥 简述

本次主要是完成视频上传Aws并完成转码播放功能,基础功能已实现,但速度方面略低于阿里云,后续还需要优化改进。

Aws指南

🐥 Aws服务

  • Amazon Elemental MediaConvert 将媒体文件从源格式转码为可在智能手机、平板电 脑、PC 和其他设备播放的流媒体格式。
  • Amazon CloudFront 加速将您的视频内容分发给最终用户。
  • Amazon Step Functions 讲执行不同任务的离散函数整合为工作流。
  • Amazon Lambda 让您无需预置或管理服务器即可运行代码。
  • Amazon Simple Storage Service (Amazon S3) 用于对象存储。
  • Amazon DynamoDB 用于追踪源和目标文件的元数据以及工作流程的进度。
  • Amazon CloudWatch 用于追踪转码任务。
  • Amazon Simple Queue Service (Amazon SQS) 用于捕获工作流的输出。
  • Amazon Simple Notification Service (Amazon SNS) 用于发送发布、编码和错误的通知信息。

按照aws解决方案库提供的方案,部署大概需要20分钟,部署完成后怎么使用,部署过程都做了哪些事情,整体流程是怎么样的,从哪些环节可以接入自定义的逻辑和程序。
除了以上部署方案提到的服务,本身该部署方案也是一个服务AWS CloudFormation。
根据本次部署和开发经历,建议需要使用该解决方案的小伙伴不要着急部署,先通过各种学习途径了解以上服务,尤其是标红的服务是做什么的,什么应用场景下使用。

B站学习资料:AWS认证解决方案全栈架构师 中文课程

🦜 AWS CloudFormation


CloudFormation是一个模板,该模板包含一套解决方案所需的所有资源,将一个通用解决方案配置成一个模板,需要再次部署相同解决方案则使用模板即可快速实现复制。启动堆栈可对模板进行部署实施。

堆栈部署完成,删除堆栈可将堆栈相关的所有资源进行一并删除,想部署或尝试的可以放心实施,不用删除即可!

Aws点播使用的模板


整体转码流程

官方服务说明


堆栈部署产生的aws服务说明


mediaconvert作业模板


Lambda

Lambda很重要!Lambda很重要!Lambda很重要!


IAM Role

IAM很重要!IAM很重要!IAM很重要!

SQS

SQS消息队列,转码完成后会向消息队列写入消息,业务侧可读取消息实现转码完成事件通知。

🐞 问题

使用Workflow trigger选项VideoFile的话,部署的方案会默认转码成m3u8文件;会默认使用3个模板进行转码(MediaConvert_Template_2160p、MediaConvert_Template_1080p、MediaConvert_Template_720p)于现有阿里云转码成单个mp4文件不一致,现有业务对多种品质输出没有要求,需要能够定制转码输出格式。

备注:如要实现类似点播清晰度选择的话可考虑多个质量转码模板。
解决方案在github上

🐉 落地方案

调整堆栈参数

Workflow trigger选择MetadataFile ,工作流触发参数由VideoFile调整为Metadata,堆栈会进行自动调整。
调整后s3桶事件通知中由之前的视频文件触发工作流,变更为由metadata触发,metadata就是.json的一个文件,具体内容参考github

Metadata文件创建

堆栈调整后,上传视频文件不再触发工作流,此时需要对上传的视频文件生成对应的metadata文件(.json文件),可以由业务侧进行两次上传,先上传视频文件,再上传metadata文件,进而触发工作流。考虑存在失败的可能以及业务侧需要实现额外的逻辑,因此使用了Lambda创建Metadata文件,文件上传完成触发Lambda,由Lambda实现生成Metadata文件。

const aws = require('aws-sdk');const s3 = new aws.S3({ apiVersion: '2006-03-01' });exports.handler = async (event, context) => {// Get the object from the event and show its content typeconst bucket = event.Records[0].s3.bucket.name;const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));if(key.endsWith('.json')){console.log('json file');return;}const params = {Bucket: bucket,Key: key,}; console.log(params);const metadata={"srcVideo": key,"ArchiveSource": true,"FrameCapture":true,"JobTemplate":"jiaoyubaoCompanyVideo_trans_template"};const metadataKey=key.substring(0,key.lastIndexOf('.'))+'.json';console.log('metadatakey:',metadataKey);try {//const { ContentType } = await s3.headObject(params).promise();//console.log('CONTENT TYPE:', ContentType);//upload metadataconst uploadParams = {Bucket: bucket, Key: metadataKey, Body: JSON.stringify(metadata)};console.log(uploadParams);// call S3 to retrieve upload file to specified bucketconst res=await s3.upload (uploadParams).promise();return res;} catch (err) {console.log(err);throw err;}
};

自定义转码模板

默认模板转码为m3u8格式文件,需要实现mp4格式转码,自定义转码模板jiaoyubaoCompanyVideo_trans_template

质量优化级别建议使用单一传递 HQ,太低转码文件质量较低,但文件大小会降低明显,考虑效果,采用中间质量。
最大比特率(位/秒)设置为了2000000,该值会影响转码视频大小,实际测试2000000转码视频较源视频大小略有压缩,固采用该值,如需高质量可参考该值设置更大的值,如需更多的压缩,可降低该值。

完成以上三部,即可通过aws控制台上传视频,触发转码工作流,输出转码视频了

业务侧上传视频

机构后台限制了视频大小50M,考虑到后台上传会导致占用更多的流量,和服务器资源,因此使用客户端直传方案。
使用jssdk实现web直传。

客户端直连S3实现分片续传思路与实践

https://aws.amazon.com/cn/blogs/china/s3-multipul-upload-practice/

授权方案

方案一:STS临时授权

由于客户端直传,使用长久AK(AwsAccessKeyId、AwsSecretAccessKey)的话会造成敏感信息泄漏,存在安全隐患。
临时授权需要一个Role(角色)用于创建一个临时的凭证,前端使用临时凭证上传s3。
为了安全考虑,该角色拥有可用的最小权限,角色绑定策略实现权限限制。因此需要先创建一个策略,然后绑定到该角色。

策略:ClientUploadVideoS3Policy

{"Version": "2012-10-17","Statement": [{"Sid": "VisualEditor0","Effect": "Allow","Action": ["s3:PutObject","s3:GetObject"],"Resource": "arn:aws-cn:s3:::videoondemand-company-source234242b7/*"}]
}

角色:ClientVideoUploadAssumeRole


AssumeRole

private Amazon.SecurityToken.Model.AssumeRoleResponse GetOriginalAwsAssumeRole(int durationSeconds=3600)
{//Amazon Resource Namevar roleArnToAssume = "arn:aws-cn:iam::77480923422862:role/ClientVideoUploadAssumeRole";var client = new Amazon.SecurityToken.AmazonSecurityTokenServiceClient(AWSCredentialsConfig.AwsAccessKeyId,AWSCredentialsConfig.AwsSecretAccessKey,Amazon.RegionEndpoint.CNNorthWest1);// Create the request to use with the AssumeRoleAsync call.var assumeRoleReq = new Amazon.SecurityToken.Model.AssumeRoleRequest(){DurationSeconds = durationSeconds,RoleSessionName = "AwsUpload",RoleArn = roleArnToAssume};var assumeRoleRes =  client.AssumeRole(assumeRoleReq);return assumeRoleRes;
}
{"AssumedRoleUser": {"Arn": "arn:aws-cn:sts::77421422862:assumed-role/ClientVideoUploadAssumeRole/AwsUpload","AssumedRoleId": "AROA3IZSCNY432ZRBYTQU5:AwsUpload"},"Credentials": {"AccessKeyId": "ASIA3234fsdfwe42Q3UK6","Expiration": "2022-12-30T15:24:37+08:00","SecretAccessKey": "khoROTW8vyDSD7tcaDmvMZg7S0Ujte6TiRP+ql2b","SessionToken": "IQoJb3JpZ2luX2VjEGEaDmNuLW5vcnRod2VzdC0xIkYwRAIgUZ7b3+OGVn+agrp3IJ+JRQDjTTgKcGW+F92eOi0CsMICICqooZD6FJoCnOdBNaAPfrvzB5iG4fxzSQL234234uEKp8CCI///wEQABoMNzc0ODA5NzQyODYyIgw+fkUE58kq72ukAWkq8wEbHoeO5inZTKxAr/kafYBQ5RFOyYx8EJjrYd/uCReS6Te/Ix64D9/aqSM/BimyACmnjM3BmeDuys12zYDdHLfOAhuOUR4dyjKEXPWTwV6usxVb+5EYw4T9Z47dX9gV8EoI0n342342jzKnY8iQIikpidsYFjo6g27Q/dddCUXGqhIUB0QtzAhoMRAM9ndVl9w2VH7CBUsFPFXPhUaxhhIol59p8L2342342BKkFFpY/Zf8wSMLBYLLO3p9/uCBA4D06chibtYVoiIPLoMvPDlOEU6iAk+LtKTOtdmZ5/J8+WTGeZB8Y6R7II14wpe++rAY6ngGPSJw/q0JoPm8WMMH84c3Lv47V9n9sDTSX8WHlASPvgySRiuP+DvSd3RqMDootuk3awg1x3hvzU438xJ+BCo5qAWFIJE2niwyslWh3zrxiGADBAqqg/XT2P4yQg8wiscQh38KxSOlj+LxgvTcdVfe4/Yy4uWtudvWR4EkcVvpWWIw+sxDkX0LlYvfHz3yhAq209Gt8E5zcX4NWQ8mnug=="},"PackedPolicySize": 0,"SourceIdentity": null,"ResponseMetadata": {"RequestId": "15c423624-923f4-452b-902e-1e8561175523","Metadata": {},"ChecksumAlgorithm": 0,"ChecksumValidationStatus": 0},"ContentLength": 1463,"HttpStatusCode": 200
}

方案二:使用预签名 URL(未验证)

https://docs.amazonaws.cn/AmazonS3/latest/userguide/using-presigned-url.html

jssdk直传

<script src="/js/aws-sdk-2.45.0.min.js"></script><script>var accessKeyId = "{$:token.Credentials.AccessKeyId}";var accessKeySecret = "{$:token.Credentials.SecretAccessKey}";var sessionToken = "{$:token.Credentials.SessionToken}";var endpoint = `${location.protocol}//s3.cn-northwest-1.amazonaws.com.cn`;var s3 = new AWS.S3({ accessKeyId: accessKeyId, secretAccessKey: accessKeySecret, sessionToken: sessionToken, region: 'cn-northwest-1', endpoint: endpoint, signatureVersion: 'v4' });var filePicker = document.getElementById("videoPicker");var progress = document.getElementById("progress");filePicker.addEventListener('change', function (event) {console.log(event.target.files[0]);if (event.target.files[0].size / 1024 / 1024 > 50) {alert("文件超出大小限制,不得超过50M");document.getElementById("videoPicker").value = null;return;}});function start() {if (filePicker.files.length <= 0) {alert("请先选择视频");return;}var file = filePicker.files[0];var ext = file.name.substring(file.name.lastIndexOf('.'));$("#progressInfo").css({ width: '100%' });filePicker.setAttribute("disabled", "disabled");var params = {Bucket: 'videoondemand-company-source-gbwnqmc885b7',Key: `{$:companyInfo.comid}_${Date.now()}${ext}`,Body:file};var upload = s3.upload(params, {queueSize: 1,//分片上传并发队列,代表了能同时上传的分片数量connectTimeout: 60 * 1000 * 10,httpOptions: {timeout: 60 * 1000 * 10}}).on('httpUploadProgress', function (e) {var percent = parseInt(e.loaded, 10) / parseInt(e.total, 10);percent = percent.toFixed(2) * 100;setTimeout(function () {var p = percent + "%";$(".progress span").css({ width: p });});});upload.send(function (err, data) {console.log(err);console.log(data);if (err) {alert("上传失败,请稍后重试");$("#progressInfo").css({ width: '0' });}//上传成功$.ajax({url: '/CompanyHandler.ashx',type: 'post',dataType: 'json',data: { action: 'saveVideo', object: data.Key, rand: Math.random() },success: function (result) {if (result.result == 1) {//关闭弹窗,刷新列表页parent.location.reload();}else {alert(result.description);}},error: function () {alert("发生异常,请稍后重试或联系客服反馈");}});filePicker.removeAttribute("disabled");});}</script>

S3 CORS


[{"AllowedHeaders": ["*"],"AllowedMethods": ["PUT","POST","DELETE"],"AllowedOrigins": ["https://*.cn"],"ExposeHeaders": ["ETag"]},{"AllowedHeaders": ["*"],"AllowedMethods": ["PUT","POST","DELETE"],"AllowedOrigins": ["http://*.jiaoyubao.cn"],"ExposeHeaders": ["ETag"]},{"AllowedHeaders": ["*"],"AllowedMethods": ["PUT","POST","DELETE"],"AllowedOrigins": ["http://localhost:53762"],"ExposeHeaders": ["ETag"]}
]

SQS异步通知机制

转码完成后会将完成消息写入SQS,业务侧可以通过读取SQS拿到消息,进行业务侧数据和逻辑完善。可以实现一个服务轮询SQS那消息,但由于SQS按照接口调用次数收费以及可以预测的大量的空队列读取情况,空队列读取依旧计入接口调用次数(aws有100万次的接口调用免费额度)。
基于以上考虑,使用SQSLambda 触发器实现推送机制,有消息到达SQS则触发Lambda函数,将消息推送给业务侧,需要自行编写Lambda函数。

VideoonDemand-Company-Notify

const http = require('http')exports.handler = async (event) => {const data = JSON.stringify(event);console.log(data)//其他业务复用当前部署堆栈的话,异步通知需要差异化的话,可以考虑通过视频前缀约束在此处进行差异化处理。const options = {hostname: 'vip3.shhddg.cn',port: 80,path: '/awsnotifications.aspx',method: 'POST',headers: {'Content-Type': 'application/json','Content-Length': data.length}}await new Promise(function (resolve, reject) {const req = http.request(options, res => {console.log(`状态码: ${res.statusCode}`)res.on('data', d => {})});req.on('error', error => {console.error(error)});req.write(data);req.end();                         });return event;
};

VideoonDemand-Company-Notify 要能够被SQS触发,需要配置对应的权限,创建的Lambda的执行角色需要加入AWSLambdaSQSExecutionRole 权限策略



域名解析到S3

将域名解析到S3 转码结果目标桶,并配置S3公开访问,从而可以使用域名访问S3资源。域名需要进行解析,注意需要去掉Host转发


CloudFormation部署的堆栈,默认会在当前账户创建CloudFront用于域名解析,仅做Dns解析调整即可。堆栈默认对S3桶权限做了配置。但目前的CloudFront统一在另外账户,不在当前部署堆栈的账户,因此,需要将域名直接解析到S3桶,并对S3桶设置了公开访问和策略调整,Principal设置了所有,默认是堆栈创建的对应域名的CloudFront用户允许。

🦉 Aws vs Aliyun

机构后台使用STS临时授权方案上传S3,对比阿里云接口调用和方式,Aws和阿里云从风格到接口基本一致。

阿里云

private AssumeRoleResponse GetStsToken()
{//获取sts凭证const string REGIONID = "cn-hangzhou";const string ENDPOINT = "sts.cn-yuenan.aliyuncs.com";// 构建一个 Aliyun Client, 用于发起请求// 构建Aliyun Client时需要设置AccessKeyId和AccessKeySevcretDefaultProfile.AddEndpoint(REGIONID, REGIONID, "Sts", ENDPOINT);IClientProfile profile = DefaultProfile.GetProfile(REGIONID, AppCode.ConstSetting.AccessKeyId, AppCode.ConstSetting.AccessKeySecret);DefaultAcsClient client = new DefaultAcsClient(profile);// 构造AssumeRole请求AssumeRoleRequest request = new AssumeRoleRequest();request.AcceptFormat = FormatType.JSON;// 指定角色Arnrequest.RoleArn = "acs:ram::14898694342448426:role/jiaoyubaovideorole";request.RoleSessionName = "upload";// 可以设置Token有效期,可选参数,默认3600秒;// request.DurationSeconds = 3600;// 可以设置Token的附加Policy,可以在获取Token时,通过额外设置一个Policy进一步减小Token的权限;// request.Policy="<policy-content>"try{AssumeRoleResponse response = client.GetAcsResponse(request);//Token过期时间;服务器返回UTC时间,这里转换成北京时间显示;return response;}catch (Exception ex){//Console.Write(ex.ToString());return null;}
}

Aws

private Amazon.SecurityToken.Model.AssumeRoleResponse GetOriginalAwsAssumeRole(int durationSeconds=3600)
{var roleArnToAssume = "arn:aws-cn:iam::7743242742862:role/ClientVideoUploadAssumeRole";var client = new Amazon.SecurityToken.AmazonSecurityTokenServiceClient(AWSCredentialsConfig.AwsAccessKeyId,AWSCredentialsConfig.AwsSecretAccessKey,Amazon.RegionEndpoint.CNNorthWest1);// Create the request to use with the AssumeRoleAsync call.var assumeRoleReq = new Amazon.SecurityToken.Model.AssumeRoleRequest(){DurationSeconds = durationSeconds,RoleSessionName = "AwsUpload",RoleArn = roleArnToAssume};var assumeRoleRes =  client.AssumeRole(assumeRoleReq);return assumeRoleRes;
}

🍄 避坑指南

坑1:文档版本不一致,不好找

服务商给到的视频点播文档中的方案链接https://www.amazonaws.cn/solutions/video-on-demand/?nc1=h_ls

搜索引擎或官方给到的视频点播文档链接https://aws.amazon.com/cn/solutions/implementations/video-on-demand-on-aws/

这两个链接的github源码不一致,而实现自定义定制功能的解决方案,在第一个链接中可以找到,第二个链接找不到。

坑2:SQS Lambda 触发器需要额外的权限

The provided execution role does not have permissions to call ReceiveMessage
Lambda函数 配置-权限-执行角色 的权限策略需要添加AWSLambdaSQSExecutionRole 策略

坑3:S3 CORS

分片上传ETag问题,使用分片上传的话ExposeHeaders务必设置ETag,否则会存在跨域问题,可参考以下文档
https://www.cnblogs.com/duhuo/p/14828021.html

[{"AllowedHeaders": ["*"],"AllowedMethods": ["PUT","POST","DELETE"],"AllowedOrigins": ["https://*.hfdhsa.cn"],"ExposeHeaders": ["ETag" //坑]},{"AllowedHeaders": ["*"],"AllowedMethods": ["PUT","POST","DELETE"],"AllowedOrigins": ["http://*.geertfd.cn"],"ExposeHeaders": ["ETag"]},{"AllowedHeaders": ["*"],"AllowedMethods": ["PUT","POST","DELETE"],"AllowedOrigins": ["http://localhost:53762" //坑,本地测试,多端口尚未解决],"ExposeHeaders": ["ETag"]}]

坑4:jssdk

var accessKeyId = "{$:token.Credentials.AccessKeyId}";
var accessKeySecret = "{$:token.Credentials.SecretAccessKey}";
var sessionToken = "{$:token.Credentials.SessionToken}";//坑,要传
//坑,要传,特别是协议cors
var endpoint = `${location.protocol}//s3.cn-northwest-1.amazonaws.com.cn`;
var s3 = new AWS.S3({ accessKeyId: accessKeyId, secretAccessKey: accessKeySecret, sessionToken: sessionToken, region: 'cn-northwest-1', endpoint: endpoint, signatureVersion: 'v4' });//坑 signatureVersion不指定报错

坑5:Lambda

需要重点了解和会编写脚本,比如Nodejs phython等解释性语言。如果使用Java或.NET需要编译的语言,需要按照官方文档实现开发,和打包成.gz文件上传到Aws,Aws提供了编译语言对应的运行环境,需要注意环境支持的版本等信息。建议使用解释性语言,好处是只要Aws提供了对应的解释器,就可以在线编写代码进行测试,缺点是无法本地调试或逐语句调试。即使使用解释性语言,复杂业务逻辑或引用第三方包,还是需要本地开发并打包成.gz文件上传到Aws。
Nodejs 支持异步的话,务必使用await,否则异步操作不会执行,比如:

 await new Promise(function (resolve, reject) {const req = http.request(options, res => {console.log(`状态码: ${res.statusCode}`)res.on('data', d => {})});req.on('error', error => {console.error(error)});req.write(data);req.end();                         });

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/325543.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用C语言实现完全平方数计算【一题一策】第三期

题目&#xff1a;一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上 168 又是一个完全平方数&#xff0c;请问该数是多少&#xff1f; 一、题目分析 首先假设该数为x&#xff0c;则x100y?&#xff0c;y为完全平方数。 然后加上168又是一个完全平方数&…

密码学(一)

文章目录 前言一、Cryptographic Primitives二、Cryptographic Keys2.1 Symmetric key cryptography2.2 asymmetric key cryptography 三、Confidentiality3.1 Symmetric key encryption algorithms3.2 asymmetric key block ciphers3.3 其他 四、Integrity4.1 symmetric key s…

选择排序!!!基础排序详解 C语言版

目录 1.什么是选择排序 2.选择排序源代码 3.优化代码 1.什么是选择排序 这是一个选择排序的流程图&#xff0c;其实很简单&#xff0c;就是每次挑选数字中最小的作为第一个 &#xff0c;直到整个数据有序就结束了 顾名思义&#xff0c;选择&#xff0c;那就是选取&#xff0c…

数据库期末重点

第一章&#xff1a; 1.数据库发展的三个阶段 第一代数据库系统、第二代数据库系统、新一代数据库系统 2.数据库系统发展的三个里程碑 IMS系统、DBTG报告、关系数据库系统 3.数据管理技术三个阶段 人工管理阶段(40年代中-50年代中) 文件系统阶段(50年代末-60年代中) 数据…

XD6500S一款串口SiP模块 射频LoRa芯片 内置sx1262

1.1产品介绍 XD6500S是一款集射频前端和LoRa射频于一体的LoRa SIP模块系列收发器SX1262 senies&#xff0c;支持LoRa⑧和FSK调制。LoRa技术是一种扩频协议优化低数据速率&#xff0c;超长距离和超低功耗用于LPWAN应用的通信。 XD6500S设计具有4.2 mA的有效接收电流消耗&#…

C++《异常》

前言&#xff1a;C有一套独立的异常处理机制,今天就来做详细的介绍try,catch这两个词等 在C语言中处理错误的方式和缺陷有&#xff1a; 返回错误码。 缺陷&#xff1a; 1.错误码不好设置&#xff0c;比如&#xff1a;除0操作&#xff0c;就不好返回错误码。如果返回一个数字&…

面试题:说一下Java开启异步线程的几种方法?

文章目录 整体描述实现方法一、注解Async1. 添加注解2. 创建异步方法Service和实现类3. 调用异步方法 二、AsyncManager1. 创建AsyncManager类2. 创建一个耗时的操作类3. 执行异步操作 三、线程池1. 创建线程池2. 创建一个耗时的操作类3. 执行线程池 总结 整体描述 在java中异…

算法每日一题:在链表中插入最大公约数 | 链表 | 最大公约数

hello&#xff0c;大家好&#xff0c;我是星恒 今天的题目是有关链表和最大公约数的题目&#xff0c;比较简单&#xff0c;核心在于求解最大公约数&#xff0c;我们题解中使用辗转相除法来求解&#xff0c;然后我们会在最后给大家拓展一下求解最大公约数的四个方法&#xff0c;…

技术学习|CDA level I 业务分析方法

业务分析方法有三个主要构成部分&#xff1a;业务指标分析、业务模型分析及业务分析方法。 业务指标分析是发现业务问题的核心方法&#xff1a;用于通用指标和场景指标的计算及分析方法&#xff0c;以及指标体系的设计与应用方法。业务模型是从一系列业务行为中抽象出来的信息…

箭头函数 - JavaScript的新宠儿

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; CSS专栏&#xff1a;想学CSS的&#xff0c;冲这里 &#x1f4…

企业老旧档案怎么处理?

不管选择何种处理方式处理企业老旧档案&#xff0c;都要先制定一份详细的档案处理计划&#xff0c;明确处理的目标、方式和时间&#xff0c;并确保有足够的人力和物力资源来完成处理工作。 一般来说&#xff0c;常用的企业老旧档案有以下几种方法&#xff1a; 1. 整理归档&…

13. 强化学习编程实验1-在格子世界中寻宝(1)

文章目录 1.实验目的2.任务描述3.任务分析3.1 待求问题是多步决策问题否3.2 问题求解过程是一个马尔科夫决策过程3.3 状态空间S的确定3.4 动作空间A的确定3.5 状态转移概率P的确定3.6 立即回报R的确定3.7 折扣 γ \gamma γ的确定 4. 编程架构4.1 程序中有哪些对象和类4.2 环境…