Commit d0537104 authored by Clark Lin's avatar Clark Lin
Browse files

Merge branch 'development' into 'main'

enhanced to use Qcloud object storage service

See merge request !2
parents e728b6e1 da011aca
...@@ -52,8 +52,8 @@ wwv_imp_workspace.create_flow( ...@@ -52,8 +52,8 @@ wwv_imp_workspace.create_flow(
,p_tokenize_row_search=>'N' ,p_tokenize_row_search=>'N'
,p_substitution_string_01=>'APP_NAME' ,p_substitution_string_01=>'APP_NAME'
,p_substitution_value_01=>'Study Performance Tracking App' ,p_substitution_value_01=>'Study Performance Tracking App'
,p_last_updated_by=>'ETHAN' ,p_last_updated_by=>'CLARK LIN'
,p_last_upd_yyyymmddhh24miss=>'20230818141726' ,p_last_upd_yyyymmddhh24miss=>'20230904203543'
,p_file_prefix => nvl(wwv_flow_application_install.get_static_app_file_prefix,'') ,p_file_prefix => nvl(wwv_flow_application_install.get_static_app_file_prefix,'')
,p_files_version=>67 ,p_files_version=>67
,p_print_server_type=>'NATIVE' ,p_print_server_type=>'NATIVE'
......
This diff is collapsed.
...@@ -15,7 +15,7 @@ wwv_imp_workspace.create_flow( ...@@ -15,7 +15,7 @@ wwv_imp_workspace.create_flow(
p_id=>wwv_flow.g_flow_id p_id=>wwv_flow.g_flow_id
,p_owner=>nvl(wwv_flow_application_install.get_schema,'STUDENT') ,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_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_view_logging=>'YES'
,p_page_protection_enabled_y_n=>'Y' ,p_page_protection_enabled_y_n=>'Y'
,p_checksum_salt=>'C0A2B0CD98CE357A1E50247D6CB5810FFFF4B204B12C060DF1B06724A237CBCB' ,p_checksum_salt=>'C0A2B0CD98CE357A1E50247D6CB5810FFFF4B204B12C060DF1B06724A237CBCB'
...@@ -44,19 +44,23 @@ wwv_imp_workspace.create_flow( ...@@ -44,19 +44,23 @@ wwv_imp_workspace.create_flow(
,p_browser_cache=>'N' ,p_browser_cache=>'N'
,p_browser_frame=>'D' ,p_browser_frame=>'D'
,p_runtime_api_usage=>'T' ,p_runtime_api_usage=>'T'
,p_rejoin_existing_sessions=>'N' ,p_rejoin_existing_sessions=>'P'
,p_csv_encoding=>'Y' ,p_csv_encoding=>'Y'
,p_auto_time_zone=>'N' ,p_auto_time_zone=>'N'
,p_tokenize_row_search=>'N' ,p_tokenize_row_search=>'N'
,p_substitution_string_01=>'APP_NAME' ,p_substitution_string_01=>'APP_NAME'
,p_substitution_value_01=>unistr('\5B66\751F\6210\7EE9\67E5\8BE2\5DE5\5177') ,p_substitution_value_01=>unistr('\5B66\751F\6210\7EE9\67E5\8BE2\5DE5\5177')
,p_last_updated_by=>'STUDENT_DEV' ,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_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_print_server_type=>'NATIVE'
,p_is_pwa=>'Y' ,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; wwv_flow_imp.component_end;
end; end;
......
This diff is collapsed.
...@@ -16,6 +16,15 @@ wwv_flow_imp_shared.create_list( ...@@ -16,6 +16,15 @@ wwv_flow_imp_shared.create_list(
,p_name=>'Navigation Bar' ,p_name=>'Navigation Bar'
,p_list_status=>'PUBLIC' ,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( wwv_flow_imp_shared.create_list_item(
p_id=>wwv_flow_imp.id(16223652249515627.10002) p_id=>wwv_flow_imp.id(16223652249515627.10002)
,p_list_item_display_sequence=>10 ,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 ...@@ -4,6 +4,7 @@ prompt --install
@@application/create_application.sql @@application/create_application.sql
@@application/user_interfaces.sql @@application/user_interfaces.sql
@@workspace/credentials/apex_student_performance_tracking_app.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_menu.sql
@@application/shared_components/navigation/lists/navigation_bar.sql @@application/shared_components/navigation/lists/navigation_bar.sql
@@application/shared_components/navigation/lists/administration.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