Commit da011aca authored by Administrator's avatar Administrator
Browse files

enhanced to use Qcloud object storage service

parent 3297998c
......@@ -52,8 +52,8 @@ wwv_imp_workspace.create_flow(
,p_tokenize_row_search=>'N'
,p_substitution_string_01=>'APP_NAME'
,p_substitution_value_01=>'Study Performance Tracking App'
,p_last_updated_by=>'ETHAN'
,p_last_upd_yyyymmddhh24miss=>'20230818141726'
,p_last_updated_by=>'CLARK LIN'
,p_last_upd_yyyymmddhh24miss=>'20230904203543'
,p_file_prefix => nvl(wwv_flow_application_install.get_static_app_file_prefix,'')
,p_files_version=>67
,p_print_server_type=>'NATIVE'
......
This diff is collapsed.
......@@ -15,7 +15,7 @@ wwv_imp_workspace.create_flow(
p_id=>wwv_flow.g_flow_id
,p_owner=>nvl(wwv_flow_application_install.get_schema,'STUDENT')
,p_name=>nvl(wwv_flow_application_install.get_application_name,'Study Performance Tracking App')
,p_alias=>nvl(wwv_flow_application_install.get_application_alias,'A5553336413616536')
,p_alias=>nvl(wwv_flow_application_install.get_application_alias,'A10221465982826889')
,p_page_view_logging=>'YES'
,p_page_protection_enabled_y_n=>'Y'
,p_checksum_salt=>'C0A2B0CD98CE357A1E50247D6CB5810FFFF4B204B12C060DF1B06724A237CBCB'
......@@ -44,19 +44,23 @@ wwv_imp_workspace.create_flow(
,p_browser_cache=>'N'
,p_browser_frame=>'D'
,p_runtime_api_usage=>'T'
,p_rejoin_existing_sessions=>'N'
,p_rejoin_existing_sessions=>'P'
,p_csv_encoding=>'Y'
,p_auto_time_zone=>'N'
,p_tokenize_row_search=>'N'
,p_substitution_string_01=>'APP_NAME'
,p_substitution_value_01=>unistr('\5B66\751F\6210\7EE9\67E5\8BE2\5DE5\5177')
,p_last_updated_by=>'STUDENT_DEV'
,p_last_upd_yyyymmddhh24miss=>'20230817091145'
,p_last_upd_yyyymmddhh24miss=>'20230904203527'
,p_file_prefix => nvl(wwv_flow_application_install.get_static_app_file_prefix,'')
,p_files_version=>60
,p_files_version=>67
,p_print_server_type=>'NATIVE'
,p_is_pwa=>'Y'
,p_pwa_is_installable=>'N'
,p_pwa_is_installable=>'Y'
,p_pwa_manifest_display=>'fullscreen'
,p_pwa_manifest_orientation=>'any'
,p_pwa_apple_status_bar_style=>'default'
,p_pwa_is_push_enabled=>'N'
);
wwv_flow_imp.component_end;
end;
......
This diff is collapsed.
......@@ -16,6 +16,15 @@ wwv_flow_imp_shared.create_list(
,p_name=>'Navigation Bar'
,p_list_status=>'PUBLIC'
);
wwv_flow_imp_shared.create_list_item(
p_id=>wwv_flow_imp.id(6030572211677996.10002)
,p_list_item_display_sequence=>1
,p_list_item_link_text=>'Install App'
,p_list_item_link_target=>'#action$a-pwa-install'
,p_list_item_icon=>'fa-cloud-download'
,p_list_text_02=>'a-pwaInstall'
,p_list_item_current_type=>'NEVER'
);
wwv_flow_imp_shared.create_list_item(
p_id=>wwv_flow_imp.id(16223652249515627.10002)
,p_list_item_display_sequence=>10
......
prompt --application/shared_components/pwa/shortcuts/my_shortcut
begin
-- Manifest
-- PWA SHORTCUT: My Shortcut
-- Manifest End
wwv_flow_imp.component_begin (
p_version_yyyy_mm_dd=>'2023.04.28'
,p_release=>'23.1.0'
,p_default_workspace_id=>16017191443360494
,p_default_application_id=>10002
,p_default_id_offset=>0
,p_default_owner=>'STUDENT'
);
wwv_flow_imp_shared.create_pwa_shortcut(
p_id=>wwv_flow_imp.id(6027991458633061.10002)
,p_name=>'My Shortcut'
,p_display_sequence=>10
,p_description=>'This is home page'
,p_target_url=>'f?p=&APP_ID.:1:&SESSION.'
,p_icon_url=>'pwa/shortcut-icon-10.png'
);
wwv_flow_imp.component_end;
end;
/
......@@ -4,6 +4,7 @@ prompt --install
@@application/create_application.sql
@@application/user_interfaces.sql
@@workspace/credentials/apex_student_performance_tracking_app.sql
@@application/shared_components/pwa/shortcuts/my_shortcut.sql
@@application/shared_components/navigation/lists/navigation_menu.sql
@@application/shared_components/navigation/lists/navigation_bar.sql
@@application/shared_components/navigation/lists/administration.sql
......
create or replace FUNCTION func_get_oss_url(key VARCHAR2)
RETURN VARCHAR2
AS
-- Call File Upload Service
lv_method VARCHAR2(30) := 'POST';
lc_req_body CLOB;
lv_url VARCHAR(4000) := 'http://localhost:8000/get_download_url/';
lc_res_body CLOB;
lj_object JSON_OBJECT_T;
lv_result VARCHAR2(4000);
BEGIN
IF key IS NULL THEN
RETURN NULL;
END IF;
-- Call File Upload Service
apex_web_service.g_request_headers(1).name := 'Content-Type';
apex_web_service.g_request_headers(1).Value := 'application/json; charset=utf-8';
lc_req_body := '{
"key": "' || key || '"
}';
lc_res_body := APEX_WEB_SERVICE.make_rest_request(
p_url => lv_url,
p_http_method => lv_method,
p_body => lc_req_body
);
-- Parse Service Result
lj_object := JSON_OBJECT_T.parse(lc_res_body);
lv_result := lj_object.get_string('result');
lv_url := lj_object.get_string('download_url');
IF lv_result = '0' THEN
RETURN lv_url;
ELSE
RETURN null;
END IF;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END func_get_oss_url;
/
\ No newline at end of file
create or replace PROCEDURE PROC_BLOB_TO_FILE(
ov_retcode OUT VARCHAR2,
ov_errmsg OUT VARCHAR2,
ib_blob IN BLOB,
iv_dir IN VARCHAR2,
iv_file_name IN VARCHAR2)
AS
ln_blob_len NUMBER;
lf_file UTL_FILE.FILE_TYPE;
ln_pos INTEGER := 1;
ln_amount BINARY_INTEGER := 32767;
lr_buffer RAW(32767);
BEGIN
ov_retcode := '0';
ov_errmsg := NULL;
--
IF iv_file_name IS NULL THEN
RETURN;
END IF;
--
-- Get BLOB length and open file in write binary mode
ln_blob_len := DBMS_LOB.getlength(ib_blob);
lf_file := UTL_FILE.fopen(
iv_dir,
iv_file_name,
'wb',
32767
);
--
-- Read chunks of the BLOB and write them to the file
-- until complete.
WHILE (ln_pos <= ln_blob_len) LOOP
DBMS_LOB.read(ib_blob, ln_amount, ln_pos, lr_buffer);
UTL_FILE.put_raw(lf_file, lr_buffer, TRUE);
ln_pos := ln_pos + ln_amount;
END LOOP;
-- Close the file.
UTL_FILE.fclose(lf_file);
EXCEPTION
WHEN OTHERS THEN
-- Close the file if something goes wrong.
IF (UTL_FILE.is_open(lf_file)) THEN
UTL_FILE.fclose(lf_file);
END IF;
--
ov_retcode := '1';
ov_errmsg := SQLERRM || dbms_utility.format_error_backtrace;
--
apex_debug.enter(
'PROC_BLOB_TO_FILE',
'iv_dir' , iv_dir,
'iv_file_name' , iv_file_name,
'ov_errmsg' , ov_errmsg);
END PROC_BLOB_TO_FILE;
/
\ No newline at end of file
# Import FastAPI Libs
from fastapi import FastAPI
from pydantic import BaseModel
# Import Qcloud COS Service Libs
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
# Import Other Common Libs
import sys
import os
import logging
from datetime import datetime
import mimetypes
import json
# ------------------------------------------------
# Define constants
# ------------------------------------------------
c_ret_code_success = 0
c_ret_code_error = 1
region = 'ap-shanghai'
bucket = 'clark-apex-saz-1320304559'
user_name = 'oss-program'
credential_file = '/home/oracle/python/apex/credential/credential.json'
# ------------------------------------------------
# Get credentials
# ------------------------------------------------
def get_credentials(file: str, bucket_name: str, user_name: str) -> tuple:
try:
# Open and read the JSON file
with open(file, "r") as credential_file:
data = json.load(credential_file)
# Access data from the JSON
secret_id = data[bucket_name][user_name]['secret_id']
secret_key = data[bucket_name][user_name]['secret_key']
return secret_id, secret_key
except Exception as e:
print(e)
return None, None
# ------------------------------------------------
# Function of upload single file to Qcloud COS
# ------------------------------------------------
def upload_single_file(dir: str, file_name: str, rename_prefix: str, client: CosS3Client, bucket: str, index: int) -> tuple:
file_key = ""
file_etag = ""
ret_code = c_ret_code_success
err_msg = ""
try:
# Check if file is empty
if file_name == None or file_name == "":
file_key = ""
else:
# Check file existance
file_location = os.path.join(dir, file_name)
mime_type, _ = mimetypes.guess_type(file_location)
if os.path.exists(file_location):
file_key = rename_prefix + "_" + str(index) + "_" + file_name
# Call COS service to upload
with open(file_location, 'rb') as fp:
response = client.put_object(
Bucket=bucket, # Bucket 由 BucketName-APPID 组成
Body=fp,
Key=file_key,
StorageClass='STANDARD',
ContentType=mime_type
)
file_etag = response['ETag'].strip('"')
# delete file after upload
os.remove(file_location)
else:
file_key = ""
return ret_code, err_msg, file_key, file_etag
except Exception as e:
ret_code = c_ret_code_error
err_msg = str(e)
return ret_code, err_msg, file_key, file_etag
# ------------------------------------------------
# Define File Upload Service Request and Response
# ------------------------------------------------
class File_Upload_Req(BaseModel):
dir: str
file1_name: str
file2_name: str
file3_name: str
file4_name: str
file5_name: str
class File_Upload_Res(BaseModel):
result: int
result_message: str
file1_key: str
file1_etag: str
file2_key: str
file2_etag: str
file3_key: str
file3_etag: str
file4_key: str
file4_etag: str
file5_key: str
file5_etag: str
app = FastAPI()
# ------------------------------------------------
# Define File Upload Service Process Detail
# ------------------------------------------------
@app.post("/upload/")
async def upload(file_uplaod_req: File_Upload_Req):
secret_id, secret_key = get_credentials(credential_file, bucket, user_name)
#secret_id = # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
#secret_key = # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# ---------------------------------------------------
# 10. Set Logging Level
# ---------------------------------------------------
# 正常情况日志级别使用 INFO,需要定位时可以修改为 DEBUG,此时 SDK 会打印和服务端的通信信息
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
# ---------------------------------------------------
# 20. Set Credential
# ---------------------------------------------------
# 1. 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成
# secret_id = os.environ['COS_SECRET_ID'] # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# secret_key = os.environ['COS_SECRET_KEY'] # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# secret_id = 'AKIDQjrAHBsKctxoSzNYXSHtITVKH0Yv64tb' # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# secret_key = 'd2NZ1KXcfrriPwRnxvhKpFlHiy0w6CD2' # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# region = 'ap-shanghai' # 替换为用户的 region,已创建桶归属的 region 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket
# COS 支持的所有 region 列表参见 https://cloud.tencent.com/document/product/436/6224
token = None # 如果使用永久密钥不需要填入 token,如果使用临时密钥需要填入,临时密钥生成和使用指引参见 https://cloud.tencent.com/document/product/436/14048
scheme = 'https' # 指定使用 http/https 协议来访问 COS,默认为 https,可不填
# ---------------------------------------------------
# 30. Config Client
# ---------------------------------------------------
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme)
client = CosS3Client(config)
# ---------------------------------------------------
# 40. Compose File Keys
# ---------------------------------------------------
# Get current date yyyy/mm/dd
current_time = datetime.now()
formatted_date = current_time.strftime("%Y/%m/%d/%H%M%S")
# ---------------------------------------------------
# 50. Upload Files
# ---------------------------------------------------
result = c_ret_code_success
error_list = []
# File 1
ret_code, err_msg, file1_key, file1_etag = upload_single_file(file_uplaod_req.dir, file_uplaod_req.file1_name, formatted_date, client, bucket, 1)
if ret_code != c_ret_code_success:
result = c_ret_code_error
error_list.append("File 1 upload failed: " + err_msg)
# File 2
ret_code, err_msg, file2_key, file2_etag = upload_single_file(file_uplaod_req.dir, file_uplaod_req.file2_name, formatted_date, client, bucket, 2)
if ret_code != c_ret_code_success:
result = c_ret_code_error
error_list.append("File 2 upload failed: " + err_msg)
# File 3
ret_code, err_msg, file3_key, file3_etag = upload_single_file(file_uplaod_req.dir, file_uplaod_req.file3_name, formatted_date, client, bucket, 3)
if ret_code != c_ret_code_success:
result = c_ret_code_error
error_list.append("File 3 upload failed: " + err_msg)
# File 4
ret_code, err_msg, file4_key, file4_etag = upload_single_file(file_uplaod_req.dir, file_uplaod_req.file4_name, formatted_date, client, bucket, 4)
if ret_code != c_ret_code_success:
result = c_ret_code_error
error_list.append("File 4 upload failed: " + err_msg)
# File 5
ret_code, err_msg, file5_key, file5_etag = upload_single_file(file_uplaod_req.dir, file_uplaod_req.file5_name, formatted_date, client, bucket, 5)
if ret_code != c_ret_code_success:
result = c_ret_code_error
error_list.append("File 5 upload failed: " + err_msg)
file_upload_res = File_Upload_Res(
result = result,
result_message = ', '.join(error_list),
file1_key = file1_key,
file1_etag = file1_etag,
file2_key = file2_key,
file2_etag = file2_etag,
file3_key = file3_key,
file3_etag = file3_etag,
file4_key = file4_key,
file4_etag = file4_etag,
file5_key = file5_key,
file5_etag = file5_etag
)
return file_upload_res
# ------------------------------------------------
# Define File Download URL Service Request and Response
# ------------------------------------------------
class File_Download_Req(BaseModel):
key: str
class File_Download_Res(BaseModel):
result: int
result_message: str
download_url: str
# ------------------------------------------------
# Define File Download URL Service Process Detail
# ------------------------------------------------
@app.post("/get_download_url/")
async def get_download_url(file_download_req: File_Download_Req):
# Define constants
secret_id, secret_key = get_credentials(credential_file, bucket, user_name)
# ---------------------------------------------------
# 10. Set Logging Level
# ---------------------------------------------------
# 正常情况日志级别使用 INFO,需要定位时可以修改为 DEBUG,此时 SDK 会打印和服务端的通信信息
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
# ---------------------------------------------------
# 20. Set Credential
# ---------------------------------------------------
# 1. 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成
# secret_id = os.environ['COS_SECRET_ID'] # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# secret_key = os.environ['COS_SECRET_KEY'] # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# secret_id = 'AKIDQjrAHBsKctxoSzNYXSHtITVKH0Yv64tb' # 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# secret_key = 'd2NZ1KXcfrriPwRnxvhKpFlHiy0w6CD2' # 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
# region = 'ap-shanghai' # 替换为用户的 region,已创建桶归属的 region 可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket
# COS 支持的所有 region 列表参见 https://cloud.tencent.com/document/product/436/6224
token = None # 如果使用永久密钥不需要填入 token,如果使用临时密钥需要填入,临时密钥生成和使用指引参见 https://cloud.tencent.com/document/product/436/14048
scheme = 'https' # 指定使用 http/https 协议来访问 COS,默认为 https,可不填
# ---------------------------------------------------
# 30. Config Client
# ---------------------------------------------------
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme)
client = CosS3Client(config)
try:
# 生成下载 URL,未限制请求头部和请求参数
url = client.get_presigned_url(
Method='GET',
Bucket=bucket,
Key=file_download_req.key,
Expired=120 # 120秒后过期,过期时间请根据自身场景定义
)
ret_code = c_ret_code_success
err_msg = ""
except Exception as e:
ret_code = c_ret_code_error
err_msg = str(e)
file_download_res = File_Download_Res(
result = ret_code,
result_message = err_msg,
download_url = url
)
return file_download_res
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment