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

aws(学习笔记第四十课) image-content-search

aws(学习笔记第四十课) image-content-search

  • 使用SQS + Lambda集成
  • 数据库(Aurora Serverless
  • Cognito(用户管理)
  • rekognition(图像解析)

学习内容:

  • 使用SQS + Lambda+ Aurora Serverless + Cognito + rekognition

1. 整体架构

1.1 代码链接

  • 代码链接(image-content-search)

1.2 关键架构流程

  • 用户上传图像 → S3触发Lambda → 图像分析 → 结果存入数据库
  • 前端通过API Gateway查询数据库(需Cognito认证)
  • 事件总线协调异步任务(如分析完成后触发存储操作)。

1.3 upload图像文件的动作

在这里插入图片描述

1.4 search图像文件的动作

在这里插入图片描述

2. 代码解析

2.1 yml文件配置详细设定

2.1.1 yml文件

这里配置了EnvironmentAuthorRegion等配置,比起写入cdk.pypython代码中,这里将配置数据写入yml文件中会更加清晰。

Environment: Development
Author: Mohsen
Region: eu-central-1
ProjectName: ImageContentSearchDeadLetterQueue:MaxReceiveCount: 3Cognito:SelfSignUp: TrueDomainPrefix: image-content-searchAllowedOAuthScopes:- phone- email- openid- profileDatabase:Name: images_labelsDeletionProtection: FalseScaling:AutoPause: TrueMin: 2Max: 8SecondsToAutoPause: 1800Functions:DefaultSignedUrlExpirySeconds: "3600"DefaultMaxApiCallAttempts: "5"
2.1.2 yml文件文件解析
 with open("stack/config.yml", 'r') as stream:configs = yaml.safe_load(stream)# for example, use configs in image_data_functionimage_data_function = Function(self, "ICS_IMAGE_DATA",function_name="ICS_IMAGE_DATA",runtime=Runtime.PYTHON_3_7,timeout=Duration.seconds(5),role=image_data_function_role,environment={"DEFAULT_MAX_CALL_ATTEMPTS": configs["Functions"]["DefaultMaxApiCallAttempts"],"CLUSTER_ARN": database_cluster_arn,"CREDENTIALS_ARN": database_secret.secret_arn,"DB_NAME": database.database_name,"REGION": Aws.REGION},handler="main.handler",code=Code.from_asset("./src/imageData"))

2.2 创建s3 bucket

        ### S3 coreimages_S3_bucket = _s3.Bucket(self, "ICS_IMAGES")images_S3_bucket.add_cors_rule(allowed_methods=[_s3.HttpMethods.POST],allowed_origins=["*"] # add API gateway web resource URL)

这里,需要从API gatewaydomain进行跨域访问S3 bucketAWSurl,因此需要CORS Cross-Origin Resource Share,是浏览器的一种安全机制,用于控制不同源(协议+域名+端口)之间的资源访问。
在之前的文章中介绍过。spring boot(学习笔记第五课) 自定义错误页,CORS(跨域支持)

2.3 创建API Gateway

 ### api gateway coreapi_gateway = RestApi(self, 'ICS_API_GATEWAY', rest_api_name='ImageContentSearchApiGateway')api_gateway_resource = api_gateway.root.add_resource(configs["ProjectName"])api_gateway_landing_page_resource = api_gateway_resource.add_resource('web')api_gateway_get_signedurl_resource = api_gateway_resource.add_resource('signedUrl')api_gateway_image_search_resource = api_gateway_resource.add_resource('search')
  • api_gateway_resource作为父resouce
  • api_gateway_landing_page_resource作为子resource,作为文件上传的表示页面。
  • api_gateway_landing_page_resource作为子resource,作为文件上传S3 bucket的请求url
  • api_gateway_image_search_resource作为子resource,作为文件分析结果的页面。

2.4 创建文件上传的表示页面

2.4.1 创建文件上传的api_gateway_resourcelambda函数
 ### landing page functionget_landing_page_function = Function(self, "ICS_GET_LANDING_PAGE",function_name="ICS_GET_LANDING_PAGE",runtime=Runtime.PYTHON_3_7,handler="main.handler",code=Code.from_asset("./src/landingPage"))
2.4.2 配置文件上传的LambdaIntegration
get_landing_page_integration = LambdaIntegration(get_landing_page_function,proxy=True,integration_responses=[IntegrationResponse(status_code='200',response_parameters={'method.response.header.Access-Control-Allow-Origin': "'*'"})])

注意,这里配置method.response.header.Access-Control-Allow-Origin以便允许其他domain过来的跨域的访问。但是,如果是生产环境,需要将*换成特定的domain

2.4.3 配置文件上传的API Gateway Method
api_gateway_landing_page_resource.add_method('GET', get_landing_page_integration,method_responses=[MethodResponse(status_code='200',response_parameters={'method.response.header.Access-Control-Allow-Origin': True})])
2.4.4 配置文件上传的页面代码

在这里插入图片描述

2.4.4.1 pythonlambda handler

\src\landingPage\main.py

# this function
# gets the simple html page
# updates the login page and logout page address
# returns the contentdef handler(event, context):login_page = event["headers"]["Referer"]return {'statusCode': 200,'headers': {'Content-Type': 'text/html'},'body': file_get_contents("index.html").replace('###loginPage###', login_page)}def file_get_contents(filename):with open(filename) as f:return f.read()

这里,直接将html的文件打开,进行返回

2.4.4.2 html的页面代码

\src\landingPage\index.html

<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1"><style>body{font-family:Arial}.tab{overflow:hidden;border:1px solid #ccc;background-color:#f1f1f1}.tab button{background-color:inherit;float:left;border:none;outline:0;cursor:pointer;padding:14px 16px;transition:.3s;font-size:17px}.tab button:hover{background-color:#ddd}.tab button.active{background-color:#ccc}.tabcontent{display:none;padding:6px 12px;-webkit-animation:fadeEffect 1s;animation:fadeEffect 1s}@-webkit-keyframes fadeEffect{from{opacity:0}to{opacity:1}}@keyframes fadeEffect{from{opacity:0}to{opacity:1}}input[type=text],select{width:30%;padding:12px 20px;margin:8px 0;display:inline-block;border:1px solid #ccc;border-radius:4px;box-sizing:border-box}.submit[type=submit]{width:20%;background-color:#4caf50;color:#fff;padding:14px 20px;margin:8px 0;border:none;border-radius:4px;cursor:pointer}input[type=submit]:hover{background-color:#45a049}.div{border-radius:5px;background-color:#f2f2f2;padding:20px}table{border-collapse:collapse;table-layout: fixed;width:100%}td,th{text-align:left;padding:8px;word-wrap:break-word;}tr:nth-child(even){background-color:#f2f2f2}</style><script src="https://code.jquery.com/jquery-3.6.4.min.js"></script><script>var authData = window.location.hash.substring(1);var idToken = authData.split('&').find((rec) => rec.split('=')[0] == 'id_token').split('=')[1]var getSignedUrlEndpoint = window.location.href.split('#')[0].replace('web', 'signedUrl')var searchImageEndpoint = window.location.href.split('#')[0].replace('web', 'search')var loginPage = '###loginPage###';var logoutPage = loginPage.replace('login', 'logout');function logout() {window.location.replace(logoutPage);}function getSignedUrl() {$.ajax({url: getSignedUrlEndpoint,headers: { 'Authorization': idToken },type: "GET",contentType: 'application/json; charset=utf-8',success: function (result) {console.log(result);$("#upload_image_form").attr('action', result.url);$('input[name="key"]').val(result.fields.key);$('input[name="X-Amz-Credential"]').val(result.fields['x-amz-credential']);$('input[name="X-Amz-Algorithm"]').val(result.fields['x-amz-algorithm']);$('input[name="X-Amz-Date"]').val(result.fields['x-amz-date']);$('input[name="x-amz-security-token"]').val(result.fields['x-amz-security-token']);$('input[name="Policy"]').val(result.fields.policy);$('input[name="X-Amz-Signature"]').val(result.fields['x-amz-signature']);},error: function (error) {console.log(error);if (error.status == 401) {logout();}}});}function listImagesByLabel(outputTab, label, language, country) {console.log('Finding images with label: ' + label);var formData = language ? {label, language, country} : {label}$.ajax({url: searchImageEndpoint,headers: { 'Authorization': idToken },type: "POST",data: {...formData, 'source': 'API'},contentType: 'application/json; charset=utf-8',success: function (results) {console.log(results);$(outputTab + " tr").remove();$(outputTab + " th").remove();if (results) {$(outputTab).append( '<tr><th>Image ID</th></tr>' );results.forEach(item => {$(outputTab).append( '<tr><td>' + item.id + '</th></tr>' );});}},error: function (error) {console.log(error.responseText, error.status);if (error.status == 401) {logout();}}});}$(document).ready(function () {if (window.location.hash) {// getSignedUrl();} else {console.log('Authorization information from cognito is not found!');}});function submitSearchQuery() {event.preventDefault();var language = $('#language').val();var country = $('#country').val();var label = $('input[name=label]').val();listImagesByLabel('#search_image_result', label, language, country);}function openTab(evt, tabName) {$("#upload_result").text('');$("#upload_result").css("color", "black");$("#file_select").val('');if (tabName == 'upload') {getSignedUrl();}if (tabName == 'report') {listImagesByLabel('#report_image_result', 'offensive');}var i, tabcontent, tablinks;tabcontent = document.getElementsByClassName("tabcontent");for (i = 0; i < tabcontent.length; i++) {tabcontent[i].style.display = "none";}tablinks = document.getElementsByClassName("tablinks");for (i = 0; i < tablinks.length; i++) {tablinks[i].className = tablinks[i].className.replace(" active", "");}document.getElementById(tabName).style.display = "block";evt.currentTarget.className += " active";}function submitFileUpload() {event.preventDefault();var formData = new FormData();var selectedFile = $('input[name="file"]')[0]$('#upload_image_form *').filter(':input').filter(":hidden").each(function(k, v){formData.append(v.name, v.defaultValue);});formData.append("file", selectedFile.files[0]);$.ajax({url: $("#upload_image_form").attr('action'),type: 'POST',data: formData,success: function (data) {$("#upload_result").text('The file has been successfully uploaded!');$("#upload_result").css("color", "green");getSignedUrl();},error: function(xhr, textStatus, errorThrown){$("#upload_result").text('The file upload failed!');$("#upload_result").css("color", "red");console.log(textStatus);console.log(errorThrown);},cache: false,contentType: false,processData: false});};</script>
</head><body><div style="width: 50%; margin-left: 25%;"><div class="tab" style="margin-top: 10px;"><button class="tablinks" onclick="openTab(event, 'upload')" id="default_tab">Upload</button><button class="tablinks" onclick="openTab(event, 'search')">Search</button><button class="tablinks" onclick="openTab(event, 'report')">Report</button><button class="tablinks" onclick="logout()" style="float: right;">Logout</button></div><div id="upload" class="tabcontent"><h3>Upload Image</h3><p>Select image to upload:</p><form id="upload_image_form" method="post" enctype="multipart/form-data"><input type="hidden" name="key"/><br /><input type="hidden" name="X-Amz-Credential"/><input type="hidden" name="X-Amz-Algorithm"/><input type="hidden" name="X-Amz-Date"/><input type="hidden" name="x-amz-security-token"/><input type="hidden" name="Policy"/><input type="hidden" name="X-Amz-Signature"/><input type="file" id="file_select" name="file"/> <br /><input type="submit" class="submit" value="Upload" onclick="submitFileUpload()"/></form><p id="upload_result"></p></div><div id="search" class="tabcontent"><h3>Search Labels</h3><form id="search_image_form" method="post"><label >Language:</label><select name="language" id="language"><option value="en">English</option><option value="tr">Turkish</option><option value="nl">Dutch</option></select><br /><label >Country:</label><select name="country" id="country"><option value="nl">Netherlands</option></select><br /><label >Label to search:</label><input type="text" name="label"/><br /><input class="submit" type="submit" value="Search" onclick="submitSearchQuery()"/></form><table id="search_image_result"></table></div><div id="report" class="tabcontent"><h3>Report of offensive photos</h3><table id="report_image_result"></table></div></div><script>document.getElementById("default_tab").click();</script></body></html>

2.5 配置cognito安全认证

 ### cognitorequired_attribute = _cognito.StandardAttribute(required=True)users_pool = _cognito.UserPool(self, "ICS_USERS_POOL",auto_verify=_cognito.AutoVerifiedAttrs(email=True), #required for self sign-upstandard_attributes=_cognito.StandardAttributes(email=required_attribute), #required for self sign-upself_sign_up_enabled=configs["Cognito"]["SelfSignUp"])user_pool_app_client = _cognito.CfnUserPoolClient(self, "ICS_USERS_POOL_APP_CLIENT",supported_identity_providers=["COGNITO"],allowed_o_auth_flows=["implicit"],allowed_o_auth_scopes=configs["Cognito"]["AllowedOAuthScopes"],user_pool_id=users_pool.user_pool_id,callback_urls=[api_gateway.url_for_path('/web')],allowed_o_auth_flows_user_pool_client=True,explicit_auth_flows=["ALLOW_REFRESH_TOKEN_AUTH"])

这里表示/web需要认证,并且认证之后url将重定向到/web

2.6 创建signedURLAPI Gateway

2.6.1 创建signedURLAPI Gateway
        ### get signed URL functionget_signedurl_function = Function(self, "ICS_GET_SIGNED_URL",function_name="ICS_GET_SIGNED_URL",environment={"ICS_IMAGES_BUCKET": images_S3_bucket.bucket_name,"DEFAULT_SIGNEDURL_EXPIRY_SECONDS": configs["Functions"]["DefaultSignedUrlExpirySeconds"]},runtime=Runtime.PYTHON_3_7,handler="main.handler",code=Code.from_asset("./src/getSignedUrl"))get_signedurl_integration = LambdaIntegration(get_signedurl_function,proxy=True,integration_responses=[IntegrationResponse(status_code='200',response_parameters={'method.response.header.Access-Control-Allow-Origin': "'*'",})])api_gateway_get_signedurl_authorizer = CfnAuthorizer(self, "ICS_API_GATEWAY_GET_SIGNED_URL_AUTHORIZER",rest_api_id=api_gateway_get_signedurl_resource.api.rest_api_id,name="ICS_API_GATEWAY_GET_SIGNED_URL_AUTHORIZER",type="COGNITO_USER_POOLS",identity_source="method.request.header.Authorization",provider_arns=[users_pool.user_pool_arn])get_signedurl_method = api_gateway_get_signedurl_resource.add_method('GET', get_signedurl_integration,authorization_type=AuthorizationType.COGNITO,method_responses=[MethodResponse(status_code='200',response_parameters={'method.response.header.Access-Control-Allow-Origin': True,})])signedurl_custom_resource = typing.cast("aws_cloudformation.CfnCustomResource", get_signedurl_method.node.find_child('Resource'))signedurl_custom_resource.add_property_override('AuthorizerId', api_gateway_get_signedurl_authorizer.ref)images_S3_bucket.grant_put(get_signedurl_function, objects_key_pattern="new/*")
2.6.2 创建signedURLAPI Gateway处理的lambda

这里,从前端传递来的文件被putS3 bucket

import json
import boto3
import logging
import os
import time
import hashlibfrom botocore.exceptions import ClientError
images_bucket = os.environ['ICS_IMAGES_BUCKET']
default_signedurl_expiry_seconds = os.environ['DEFAULT_SIGNEDURL_EXPIRY_SECONDS']# this function
# creates a pre-sighned URL for uploading image to S3 and returns itdef handler(event, context):uniquehash = hashlib.sha1("{}".format(time.time_ns()).encode('utf-8')).hexdigest()result = create_presigned_post(images_bucket, "new/{}/{}".format(uniquehash[:2],uniquehash))return {'statusCode': 200,'headers': {'Content-Type': 'application/json; charset=UTF-8'},'body': json.dumps(result)}def create_presigned_post(bucket_name, object_name, fields=None, conditions=None, expiration=default_signedurl_expiry_seconds):s3_client = boto3.client('s3')try:response = s3_client.generate_presigned_post(bucket_name,object_name,Fields=fields,Conditions=conditions,ExpiresIn=int(expiration))except ClientError as e:logging.error(e)return Nonereturn response

2.7 监视S3 bucketlambda

2.7.1 监视架构

在这里插入图片描述

2.7.2 创建lmabda
### image massage functionimage_massage_function = Function(self, "ICS_IMAGE_MASSAGE",function_name="ICS_IMAGE_MASSAGE",timeout=Duration.seconds(6),runtime=Runtime.PYTHON_3_7,environment={"ICS_IMAGE_MASSAGE": image_queue.queue_name},handler="main.handler",code=Code.from_asset("./src/imageMassage"))images_S3_bucket.grant_write(image_massage_function, "processed/*")images_S3_bucket.grant_delete(image_massage_function, "new/*")images_S3_bucket.grant_read(image_massage_function, "new/*")new_image_added_notification = _s3notification.LambdaDestination(image_massage_function)images_S3_bucket.add_event_notification(_s3.EventType.OBJECT_CREATED,new_image_added_notification,_s3.NotificationKeyFilter(prefix="new/"))image_queue.grant_send_messages(image_massage_function)
2.7.2 S3 bucket监视的lmabda代码
def handler(event, context):s3 = boto3.resource('s3')for record in event['Records']:newKey = record['s3']['object']['key']bucket = record['s3']['bucket']['name']name = bucket.split("/")[-1]localfile = "/tmp/{}".format(name)# download the filenew_key_obj = s3.Object(bucket, newKey)new_key_obj.download_file(localfile)# calc hashimage_SHA1 = getSha1(localfile)# check if not existprocessed_key = "processed/{}/{}".format(image_SHA1[:2], image_SHA1)key_is_processed = isS3ObjectExist(bucket, processed_key)if key_is_processed: continue# add to the queuemessage = json.dumps({"image": processed_key,"original_key": newKey,"original_last_modified": new_key_obj.last_modified,"etag": new_key_obj.e_tag}, default=str)queue = sqs.get_queue_by_name(QueueName=queue_name)response = queue.send_message(MessageBody=message)logger.info("Message {} has been sent.".format(response.get('MessageId')))#move the images3.Object(bucket, processed_key).copy_from(CopySource="{}/{}".format(bucket,newKey))new_key_obj.delete()# delete local fileos.remove(localfile)return Truedef isS3ObjectExist(bucket, key):s3 = boto3.resource('s3')try:s3.Object(bucket,key)return Falseexcept botocore.exceptions.ClientError as e:if e.response['Error']['Code'] == "404":return Trueelse:raise edef getSha1(filepath):sha1 = hashlib.sha1()with open(filepath, 'rb') as f:while True:data = f.read(65536) # read in 64kb chunksif not data: breaksha1.update(data)return sha1.hexdigest()

2.8 创建lambda对图像进行分析

2.8.1 图像分析架构

在这里插入图片描述

2.8.1 图像分析lambda函数
def handler(event, context):for record in event['Records']:# receiptHandle = record['receiptHandle']body = record['body']message = json.loads(body)bucket = os.environ['ICS_IMAGES_BUCKET']key = message['image']# original_key = message['original_key']# original_last_modified = message['original_last_modified']# etag = message['etag']logger.info('Processing {}.'.format(key))detected_labels = rekognition_client.detect_labels(Image={'S3Object': {'Bucket': bucket, 'Name': key}},MaxLabels=20,MinConfidence=85)detected_unsafe_contents = rekognition_client.detect_moderation_labels(Image={'S3Object': {'Bucket': bucket, 'Name': key}})object_labels = []for l in detected_labels['Labels']:object_labels.append(l['Name'].lower()) # add objects in imagefor l in detected_unsafe_contents['ModerationLabels']:if ('offensive' not in object_labels): object_labels.append("offensive") #label image as offensiveobject_labels.append(l['Name'].lower())image_id = key.split("/")[-1]response = events_client.put_events(Entries=[{'Source': "EventBridge",'Resources': [context.invoked_function_arn,],'DetailType': 'images_labels','Detail': json.dumps({"labels": object_labels, "image_id": image_id}),'EventBusName': event_bus_name},])if response["FailedEntryCount"] == 1:raise Exception(f'Failed entry observed. Count: {response["Entries"]}')

2.9 创建图像分析数据保存的数据库

2.9.1 创建数据库的密码
### databasedatabase_secret = _secrets_manager.Secret(self, "ICS_DATABASE_SECRET",secret_name="rds-db-credentials/image-content-search-rds-secret",generate_secret_string=_secrets_manager.SecretStringGenerator(generate_string_key='password',secret_string_template='{"username": "dba"}',exclude_punctuation=True,exclude_characters='/@\" \\\'',require_each_included_type=True))
2.9.2 创建数据库
database = _rds.CfnDBCluster(self, "ICS_DATABASE",engine=_rds.DatabaseClusterEngine.aurora_mysql(version=_rds.AuroraMysqlEngineVersion.VER_5_7_12).engine_type,engine_mode="serverless",database_name=configs["Database"]["Name"],enable_http_endpoint=True,deletion_protection=configs["Database"]["DeletionProtection"],master_username=database_secret.secret_value_from_json("username").to_string(),master_user_password=database_secret.secret_value_from_json("password").to_string(),scaling_configuration=_rds.CfnDBCluster.ScalingConfigurationProperty(auto_pause=configs["Database"]["Scaling"]["AutoPause"],min_capacity=configs["Database"]["Scaling"]["Min"],max_capacity=configs["Database"]["Scaling"]["Max"],seconds_until_auto_pause=configs["Database"]["Scaling"]["SecondsToAutoPause"]),)
2.9.3 将数据库密码和数据库绑定attachment
        database_cluster_arn = "arn:aws:rds:{}:{}:cluster:{}".format(Aws.REGION, Aws.ACCOUNT_ID, database.ref)secret_target = _secrets_manager.CfnSecretTargetAttachment(self,"ICS_DATABASE_SECRET_TARGET",target_type="AWS::RDS::DBCluster",target_id=database.ref,secret_id=database_secret.secret_arn)secret_target.node.add_dependency(database)

2.10 创建数据库的lambda function

2.10.1 创建数据库访问role
        ### database functionimage_data_function_role = _iam.Role(self, "ICS_IMAGE_DATA_FUNCTION_ROLE",role_name="ICS_IMAGE_DATA_FUNCTION_ROLE",assumed_by=_iam.ServicePrincipal("lambda.amazonaws.com"),managed_policies=[_iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaVPCAccessExecutionRole"),_iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole"),_iam.ManagedPolicy.from_aws_managed_policy_name("AmazonRDSDataFullAccess")])
2.10.2 创建image_data_function

在这里插入图片描述

       image_data_function = Function(self, "ICS_IMAGE_DATA",function_name="ICS_IMAGE_DATA",runtime=Runtime.PYTHON_3_7,timeout=Duration.seconds(5),role=image_data_function_role,environment={"DEFAULT_MAX_CALL_ATTEMPTS": configs["Functions"]["DefaultMaxApiCallAttempts"],"CLUSTER_ARN": database_cluster_arn,"CREDENTIALS_ARN": database_secret.secret_arn,"DB_NAME": database.database_name,"REGION": Aws.REGION},handler="main.handler",code=Code.from_asset("./src/imageData"))
2.10.3 创建image search function

在这里插入图片描述

        image_search_integration = LambdaIntegration(image_data_function,proxy=True,integration_responses=[IntegrationResponse(status_code='200',response_parameters={'method.response.header.Access-Control-Allow-Origin': "'*'",})])api_gateway_image_search_authorizer = CfnAuthorizer(self, "ICS_API_GATEWAY_IMAGE_SEARCH_AUTHORIZER",rest_api_id=api_gateway_image_search_resource.api.rest_api_id,name="ICS_API_GATEWAY_IMAGE_SEARCH_AUTHORIZER",type="COGNITO_USER_POOLS",identity_source="method.request.header.Authorization",provider_arns=[users_pool.user_pool_arn])search_integration_method = api_gateway_image_search_resource.add_method('POST', image_search_integration,authorization_type=AuthorizationType.COGNITO,method_responses=[MethodResponse(status_code='200',response_parameters={'method.response.header.Access-Control-Allow-Origin': True,})])search_integration_custom_resource = typing.cast("aws_cloudformation.CfnCustomResource", search_integration_method.node.find_child('Resource'))search_integration_custom_resource.add_property_override('AuthorizerId', api_gateway_image_search_authorizer.ref)

在这里插入图片描述

2.10.4 创建image dbschema
        ### custom resourcelambda_provider = Provider(self, 'ICS_IMAGE_DATA_PROVIDER',on_event_handler=image_data_function)CustomResource(self, 'ICS_IMAGE_DATA_RESOURCE',service_token=lambda_provider.service_token,pascal_case_properties=False,resource_type="Custom::SchemaCreation",properties={"source": "Cloudformation"})

在这里插入图片描述

2.10.5 创建image db的保存lambda

在这里插入图片描述
image_analyzer_function保存分析结果到event_bus之中,这里继续将event_rule.add_target(_event_targets.LambdaFunction(image_data_function)),之后image_data_function会将数据保存到数据库。

        ### event bridgeevent_bus = _events.EventBus(self, "ICS_IMAGE_CONTENT_BUS", event_bus_name="ImageContentBus")event_rule = _events.Rule(self, "ICS_IMAGE_CONTENT_RULE",rule_name="ICS_IMAGE_CONTENT_RULE",description="The event from image analyzer to store the data",event_bus=event_bus,event_pattern=_events.EventPattern(resources=[image_analyzer_function.function_arn]),)event_rule.add_target(_event_targets.LambdaFunction(image_data_function))

在这里插入图片描述

3 执行cdk

TODO

相关文章:

aws(学习笔记第四十课) image-content-search

aws(学习笔记第四十课) image-content-search 使用SQS Lambda集成 数据库&#xff08;Aurora Serverless&#xff09; Cognito&#xff08;用户管理&#xff09; rekognition&#xff08;图像解析&#xff09; 学习内容&#xff1a; 使用SQS Lambda Aurora Serverless Cog…...

GPT-4o 图像生成与八个示例指南

什么是GPT-4o图像生成&#xff1f; 简单来说&#xff0c;GPT-4o图像生成是集成在ChatGPT内部的一项功能。用户可以直接在对话中&#xff0c;通过文本描述&#xff08;Prompt&#xff09;来创建、编辑和调整图像。这与之前的图像生成工具相比&#xff0c;体验更流畅、交互性更强…...

PostgreSQL 查看表膨胀情况的方法

PostgreSQL 查看表膨胀情况的方法 表膨胀(Table Bloat)是PostgreSQL中由于MVCC机制导致的一种常见现象&#xff0c;当大量数据被更新或删除后&#xff0c;表中会积累"死元组"(dead tuples)&#xff0c;这些死元组占据空间但不可见&#xff0c;导致表实际占用的磁盘空…...

从 0 到 1!深度剖析项目实施流程,开启项目管理新视野

一、项目准备 / 前期准备 &#xff08;一&#xff09;跟销售进行项目交接 对接人&#xff1a;销售人员交接会议内容&#xff1a; 了解项目背景、客户基本信息、项目版本、具备二次开发功能、接口、了解合同信息等。明确项目情况、客户基本情况、使用软件&#xff08;版本&…...

书生实战营之沐曦专场

一&#xff1a;实验环境进入和启动实验容器(D.run平台) 1.1首先进入平台进行注册 D.run平台https://console.d.run/ 注册和登录环节就跳过了。 1.2 启动实验容器--详细步骤如下 1.2.1选择容器的名称、区域、镜像&#xff08;注意镜像必须选择Dlinfer&#xff09; 1.2.2可以选…...

在运行 Hadoop 作业时,遇到“No such file or directory”,如何在windows里打包在虚拟机里运行

最近在学习Hadoop集群map reduce分布运算过程中&#xff0c;经多方面排查可能是电脑本身配置的原因导致每次运行都会报“No such file or directory”的错误&#xff0c;最后我是通过打包文件到虚拟机里运行得到结果&#xff0c;具体步骤如下&#xff1a; 前提是要保证maven已经…...

基于YOLOV5的目标检测识别

基于YOLOV5的目标检测识别 舰船目标检测口罩目标检测飞机目标检测 舰船目标检测 口罩目标检测 飞机目标检测...

第4篇:服务层抽象与复用逻辑

在业务系统复杂度指数级增长的今天&#xff0c;服务层&#xff08;Service Layer&#xff09;的合理设计直接影响着系统的可维护性和扩展性。本文将深入剖析 Egg.js 框架中的服务层架构设计&#xff0c;从基础实现到高级封装&#xff0c;全方位讲解企业级应用的开发实践。 一、…...

多模态大语言模型arxiv论文略读(五十四)

RoboMP 2 ^2 2: A Robotic Multimodal Perception-Planning Framework with Multimodal Large Language Models ➡️ 论文标题&#xff1a;RoboMP 2 ^2 2: A Robotic Multimodal Perception-Planning Framework with Multimodal Large Language Models ➡️ 论文作者&#xff…...

中小企业MES系统详细设计

版本&#xff1a;V1.1 日期&#xff1a;2025年5月2日 一、设备协议兼容性设计 1.1 设备接入框架 #mermaid-svg-PkwqEMRIIlIBPP58 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-PkwqEMRIIlIBPP58 .error-icon{fill…...

第二十周:项目开发中遇到的相关问题(一)

自十九周开始&#xff0c;我们便开始着手写项目&#xff08;关于新闻资讯类的Web项目&#xff09;&#xff0c;当然&#xff0c;在这之中我们也学到了很多高效且有用的好技术&#xff0c;在接下来的内容中将去具体的描述这些好技术&#xff0c;介绍它们的具体用法和应用场景。本…...

WebRtc10: 端对端1v1传输基本流程

媒体能力协商过程 RTCPeerConnection&#xff08;核心类&#xff09; 基本格式 pc new RTCPeerConnection([configiration]); RTCPeerConnection方法分类 媒体协商Stream/Track传输相关方法统计相关方法 媒体协商过程 协商状态变化 媒体协商方法 createOffercreateAnswe…...

【云备份】配置文件加载模块

目录 一.为什么要配置文件 二.配置文件的实现 三.单例文件配置类设计 四.源码 一.为什么要配置文件 我们将服务端程序运行中用到的一些关键信息保存到配置文件中&#xff0c;这样可以使程序的运行更加灵活。 这样做的好处是&#xff0c;未来如果我们想要修改一些关键信息&…...

重构之道:识别并替换不合适使用的箭头函数

1、引言 JavaScript 自 ES6 引入了箭头函数(Arrow Function)后,因其简洁的语法和对 this 的词法绑定机制,迅速成为开发者喜爱的写法之一。然而,并不是所有场景都适合使用箭头函数。 在实际开发中,我们常常会因为追求代码简洁而忽视其潜在问题,例如: this 指向错误不适…...

git问题记录-如何切换历史提交分支,且保留本地修改

问题记录 我在本地编写了代码&#xff0c;突然想查看之前提交的代码&#xff0c;并且想保留当前所在分支所做的修改 通过git stash对本地的代码进行暂存 使用git checkout <commit-hash>切换到之前的提交记录。 查看完之后我想切换回来&#xff0c;恢复暂存的本地代码…...

【MySQL】事务管理

事务管理 一. 事务的概念二. 事务的特征三. 事务的版本支持四. 事务的提交方式五. 事务的常见操作六. 事务的隔离级别1. 查看与设置隔离级别2. 读未提交 (Read Uncommitted)3. 读提交 (Read Committed)4. 可重复读 (Repeatable Read)5. 串行化 (Serializable)6. 隔离级别的总结…...

【点对点协议(PPP)全解析】从原理到工程实践

目录 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心配置实现案例1&#xff1a;基础PPP链路建立案例2&#xff1a;CHAP认证配置 运行结果验证 三、性能对比测试…...

环境搭建:开启 Django 开发之旅

一、环境搭建&#xff1a;开启 Django 开发之旅 &#xff08;一&#xff09;安装 Python 先确保电脑上装有 Python 3.6 及以上版本&#xff0c;Django 5.1 的话&#xff0c;至少得 Python 3.8 哦。 安装前&#xff0c;先查下有没有装过 Python &#xff0c;终端&#xff08;Wi…...

如何配置NGINX作为反向代理服务器来缓存后端服务的响应?

大家好&#xff0c;我是锋哥。今天分享关于【如何配置NGINX作为反向代理服务器来缓存后端服务的响应&#xff1f;】面试题。希望对大家有帮助&#xff1b; 如何配置NGINX作为反向代理服务器来缓存后端服务的响应&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源…...

【Java IO流】File类基础详解

参考笔记&#xff1a;java File类基础 万字详解&#xff08;通俗易懂&#xff09;-CSDN博客 目录 1.前言 2. File类介绍 3. File类构造方法 4.File类常用的方法案例演示 4.1 创建文件/文件夹的方法 4.2 删除文件/文件夹的方法 4.3 判断文件/文件夹是否存在的方法 4.4 …...

《C#数据结构与算法》—201线性表

线性表的实现方式 顺序表 线性表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素&#xff0c;用这种方式存储的线性表叫顺序表。 特点&#xff1a;表中相邻的数据元素在内存中存储位置也相邻。 顺序表接口实现&#xff1a; 方法名参数返回值描述GetLen…...

MATLAB绘制局部放大图

今天&#xff0c;我将分享一段 MATLAB 代码&#xff0c;该代码生成了一个主副图结合的可视化展示&#xff0c;用于比较不同控制系统性能表现。 clc; clear; close all;% 生成时间向量 t 0:0.1:12;% 生成模拟数据 zero_feedback 0.5 * ones(size(t)); % 恒定…...

TS 常用类型

JS不会检查变量类型的变化 给变量规定特定的数据类型&#xff0c;错误赋值时会报错 优势&#xff1a;TS会标记出代码中的意外行为&#xff0c;尤其是typeerrors 具体实现&#xff1a;类型注解 JS和TS中数据类型的变化...

[Control-Chaos] Toxic Cascade(毒性級鏈)

信息 信息描述靶場名稱Toxic Cascade地址GitHub: Toxic Cascade難度中等人數推薦1人類型CTF、APT 攻擊模擬、故事解謎、化工工程與逆向工程描述Toxic Cascade 是一個結合 CTF、APT 攻擊模擬、故事解謎、化工工程與逆向工程的高度沉浸式靶場。該靶場具有獨特的情境背景與模擬真…...

纳米AI搜索体验:MCP工具的实际应用测试,撰写报告 / 爬虫小红书效果惊艳

1. 引言 近期测试了纳米AI搜索的MCP工具功能&#xff0c;重点体验了其智能体在报告生成和社交媒体数据分析方面的表现。平台整合了100多个MCP工具&#xff0c;通过本地化部署的方式&#xff0c;为用户提供了不同于云端方案的操作体验。本文将分享实际测试结果&#xff0c;包括智…...

React useMemo函数

第一个参数是回调函数&#xff0c;返回计算的结果&#xff0c;第二个参数是依赖项&#xff0c;该函数只监听count1变量的变化 import { useReducer, useState } from react; import ./App.css;// 定义一个Reducer函数 根据不同的action进行不同的状态修改 function reducer(st…...

第 1 篇:起点的选择:为何需要超越数组与链表?

大家好&#xff0c;欢迎来到“数据结构选型指南”系列&#xff01;在软件开发中&#xff0c;数据是核心&#xff0c;而如何高效地组织和访问这些数据&#xff0c;则是程序性能的关键。选择合适的数据结构&#xff0c;就像为你的 Java 应用选择最优的“引擎零件”。今天&#xf…...

MySQL 索引不生效的情况

MySQL 索引不生效的 SQL 查询需要避免的情况 索引是提高 MySQL 查询性能的关键&#xff0c;但某些 SQL 写法会导致索引失效&#xff0c;从而影响查询效率。以下是需要避免的常见情况&#xff1a; 1. 使用 NOT、! 或 <> 操作符 -- 索引可能失效 SELECT * FROM users WH…...

【阿里云大模型高级工程师ACP学习笔记】2.9 大模型应用生产实践 (上篇)

特别说明:由于这一章节是2025年3月官方重点更新的部分,新增内容非常多,因此我不得不整理成上、下两篇,方便大家参考。 学习目标 备考阿里云大模型高级工程师ACP认证,旨在全面掌握大模型应用生产实践的专业知识,提升在该领域的实操技能与理论水平,为职业发展增添助力。具…...

STM32 ZIBEE DL-20 无线串口模块

一.配置方法 二.串口中断 u8 i; u16 buf[20],res; u8 receiving_flag 0; // 新增一个标志&#xff0c;用于标记是否开始接收数组 void USART1_IRQHandler(void) {if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) //接收中断{res USART_ReceiveData(USART1);if(receiv…...

【算法基础】选择排序算法 - JAVA

一、算法基础 1.1 什么是选择排序 选择排序是一种简单直观的排序算法&#xff0c;它的工作原理是&#xff1a;首先在未排序序列中找到最小&#xff08;或最大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后再从剩余未排序元素中继续寻找最小&#xf…...

FastAPI 与数据库交互示例

目录 安装必要的包完整代码示例运行应用使用说明API 端点说明代码解析 下面将创建一个简单的 FastAPI 应用程序&#xff0c;演示如何与 SQLite 数据库进行交互。这个例子包括创建、读取、更新和删除&#xff08;CRUD&#xff09;操作。 安装必要的包 首先&#xff0c;需要安装…...

(六——下)RestAPI 毛子(Http resilience/Refit/游标分页)

文章目录 项目地址一、Refit1.1 安装需要的包1.2 创建接口IGitHubApi1.3 创建RefitGitHubService1. 实现接口2. 注册服务 1.4 修改使用方法 二、Http resilience2.1 安装所需要的包2.2 创建resilience pipeline简单版2.3 创建全局的resilience处理1. 创建清理全局ResilienceHan…...

Rust 学习笔记:关于枚举与模式匹配的练习题

Rust 学习笔记&#xff1a;关于枚举与模式匹配的练习题 Rust 学习笔记&#xff1a;关于枚举与模式匹配的练习题以下程序能否通过编译&#xff1f;若能&#xff0c;输出是什么&#xff1f;考虑这两种表示结果类型的方式&#xff0c;若计算成功&#xff0c;则包含值 T&#xff1b…...

父子组件双向绑定

v-model 语法糖实现 vue中我们在input中可以直接使用v-model来完成双向绑定,这个时候 v-model 通常会帮我们完成两件事: v-bind:value的数据绑定@input的事件监听如果我们现在封装了一个组件,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢? 当我…...

系统思考与第一性原理

最近一直有客户提到“第一性原理”&#xff0c;希望借此穿透纷繁复杂的现象&#xff0c;看清事情的本质。我第一反应是&#xff1a;这与系统思考中的冰山模型不谋而合。 冰山模型中提到&#xff1a;我们看到的只是表面事件&#xff0c;事件背后有趋势&#xff0c;趋势背后有结…...

基于Redis实现-UV统计

基于Redis实现-UV统计 本文将使用HyperLogLog来实现UV统计。 首先我们搞懂两个概念&#xff1a; UV&#xff1a;全称Unique Visitor&#xff0c;也叫独立访客量&#xff0c;是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站&#xff0c;只记录一次…...

【iOS】类与对象底层探索

类与对象底层探索 Clang探索对象本质objc_setProperty源码探索cls与类的关联原理isa的类型isa_t原理探索 类&类的结构什么是元类NSObject到底有几个isa走位&继承关系图objc_class&objc_object 类结构分析计算cache类中的内存大小获取bits属性列表&#xff08;prope…...

2025年- H18-Lc126-54.螺旋矩阵(矩阵)---java版

1.题目描述 2.思路* 思路1&#xff1a; 补充2&#xff1a; directions[1][0] // 表示“下”这个方向的行增量&#xff08;1&#xff09; directions[1][1] // 表示“下”这个方向的列增量&#xff08;0&#xff09; int[][] directions {{0, 1}, {1, 0}, {0, -1}, {-…...

Paddle Serving|部署一个自己的OCR识别服务器

前言 之前使用C部署了自己的OCR识别服务器&#xff0c;Socket网络传输部分是自己写的&#xff0c;回过头来一看&#xff0c;自己犯傻了&#xff0c;PaddleOCR本来就有自己的OCR服务器项目&#xff0c;叫PaddleServing&#xff0c;这里记录一下部署过程。 1 下载依赖环境 1.1 …...

yolov5 本地训练

YOLOv5 | Kaggle 直接gitclone他的源码用Vscode看&#xff08;也可以直接把jupyter下下来&#xff09; 他要1.8&#xff0c;我的是2.7&#xff0c;他这个代码可能有点年头了 两年前了 他的环境 我的环境 我就是不懂为什么清华源的torch windows默认下出来是cpu版本 . 在终端…...

同城跑腿小程序帮取帮送接单抢单预约取件智能派单同城配送全开源运营版源码优创

一、源码描述 这是一套同城跑腿小程序&#xff0c;基于FastadminUniapp框架&#xff0c;全开源无加密&#xff0c;可私有化部署&#xff0c;包含用户端、骑手端和运营端&#xff08;后端&#xff09;&#xff0c;支持帮取/帮送模式&#xff0c;支持一键接单/抢单&#xff0c;主…...

基于SpringBoot的药房药品销售管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

机器学习中的学习率及其衰减方法全面解析

摘要&#xff1a; 本文深入解析机器学习中的学习率及其衰减方法&#xff0c;涵盖学习率的作用、常用衰减参数及七种主流衰减策略&#xff08;分段常数、指数、自然指数、多项式、余弦、线性余弦、噪声线性余弦&#xff09;。通过公式推导与图示对比&#xff0c;揭示不同衰减方式…...

硬件性能与能效比竞赛:解码 PC 硬件的 “速度与激情”

引言&#xff1a;当性能遇见能效&#xff0c;一场永不停歇的算力革命 在数字内容爆炸式增长的时代&#xff0c;无论是 4K/8K 游戏的极致画质追求&#xff0c;还是 AI 大模型的本地化部署需求&#xff0c;亦或是内容创作者对实时渲染的效率渴求&#xff0c;都在推动 PC 硬件走向…...

大模型在终末期肾脏病风险预测与临床方案制定中的应用研究

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 1.3 研究方法与数据来源 二、终末期肾脏病概述 2.1 定义与诊断标准 2.2 发病机制与影响因素 2.3 现状与危害 三、大模型技术原理及应用现状 3.1 大模型基本原理 3.2 在医疗领域应用案例 3.3 在终末期肾脏病…...

【C++11】智能指针

&#x1f4dd;前言&#xff1a; 这篇文章我们来讲讲C11——智能指针&#xff1a; &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;C学习笔记 &#x1f380;CSDN主页 愚润求学 &#x1f304;其他专栏&#xff1a;C语言入门基础&#xff0c;pyt…...

华为云Astro轻应用利用自定义连接器调用第三方接口实际操作

样图 说明 华为云Astro轻应用通过自定义连接器调用第三方接口具有多方面的作用,主要体现在以下几点: 扩展功能与集成能力 调用第三方服务:通过配置自定义连接器,Astro轻应用可以调用第三方提供的Rest协议接口,实现第三方提供的业务功能,扩展应用的能力。 集成外部系统:…...

【中间件】brpc_基础_butex.h

butex.h 学习笔记 源码 1 概述 butex.h 提供了一种用户态同步原语 butex&#xff08;类似 Linux 的 futex&#xff09;&#xff0c;专为 bthread 设计&#xff0c;用于高效协调线程的阻塞与唤醒。其核心是通过原子操作结合等待队列管理&#xff0c;减少内核态切换开销&#…...

数字智慧方案5876丨智慧交通枢纽智能化系统建设方案(56页PPT)(文末有下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2301_78256053/89575493 资料解读&#xff1a;智慧交通枢纽智能化系统建设方案 详细资料请看本解读文章的最后内容。 随着城市化进程的加速&#xff0c;交…...