天天看點

檔案管理體系優化,更新檔案管理功能

作者:三牛愛程式設計

實作效果視訊如下

視訊加載中...

此前,我已在架構内對檔案做了簡要的管理。在日常的使用中,我發現此前的設計還是沒有達到想要的效果,

故,欲對檔案管理體系做一次調整,進而達到配置化檔案功能與業務關聯。

此前,我已設計過檔案管理的表結構,此下,簡述一下,此前設計思路。

檔案管理體系優化,更新檔案管理功能

可見,以上存儲的字段,基本都是檔案常見字段。

除此之外,在其新增了biz_type、biz_key兩個字段,此兩個字段,用于檔案和業務之前的關聯使用。

進而,可以使檔案體系,脫離業務,又與業務完全綁定在一起。

本次調整,意在修改檔案上傳元件,使其徹底和業務脫離關聯。

開發思路

後端需要提供兩個接口,上傳和擷取檔案清單接口。

上傳接口需要支援多檔案傳輸,并接入bizKey和bieType。

不同的檔案元件可以通過bizKey和bieType擷取業務對應的檔案資料。

後端還需内置定義兩個參數,saveFileIds,deleteFileIds用于對業務與檔案之間的動态綁定。

我準備在新增和更新時,在架構内部進行自動綁定檔案主鍵,故此下,分兩種邏輯梳理開發思路。

新增

對于此種邏輯,直接綁定主鍵即可。但,我欲在架構内部自動綁定業務主鍵(bizKey)。

當此種情況下,有種情況不可避免,當一個處理邏輯内部有多個新增邏輯時,即會産生多個新增業務主鍵,那麼,以那個業務主鍵為準,又該綁定那個新增業務主鍵?

若,通過業務主鍵編碼進行記錄的話,勢必需要再次記錄一個編碼字段,反而造成了複雜性。

故,我欲取第一個新增主鍵為主,其他主鍵不做操作。在此基礎上,新增擴充函數進行攔截處理,相容特殊業務邏輯即可。

更新

對于此種邏輯,我需相容兩種處理邏輯。

1、已傳入bizKey,直接綁定更新檔案資料。

2、未傳入bizKey時,我會在檔案元件中定義bizKeyCode(業務編碼),進行自動綁定值。

參數傳遞

由于該邏輯需要統一處理,故需定義統一參數傳遞邏輯。

我計劃在header中新增key:值為fileContent,傳遞儲存、删除等檔案資料資訊,同時通過加密為字元串傳遞,後端在需要的地方統一處理功能邏輯。

擷取檔案清單

此前,也提過徹底拆解與業務之間的關聯。故,需封裝擷取檔案清單接口,用來滿足業務元件内容的資料回顯,與檔案編輯功能。

寫止于此,開發思路業已明晰,此下,将正式實作具體功能。為保證代碼清晰,特将前後端分開進行表述。

後端

1、新增業務主鍵入線程同步功能

由于,需要徹底脫離具體的業務邏輯,我欲在此前實作的線程同步中,存儲“新增業務主鍵”進行資料同步。因,功能與api請求均需要用到此邏輯,我計劃在“RequestAttributes”中新增屬性“insertBizKey”用來存儲新增後産生的業務主鍵,後期若有其他業務需求,再做更多設計。

/**
 * 插入腳本業務主鍵
 */
private String insertBizKey;           

2、統一插入入口設定業務主鍵入線程同步

此上,已定義了臨時屬性,此下将在“ExecuteSqlFactory”執行請求工廠中,對資料進行臨時綁定。

/**
 * 執行新增Sql語句
 *
 * @param requestInfo
 * @param requestParam
 * @param <T>
 * @return
 */
private <T extends Object> T execInsertSql(ISqlExecutor executor, ISqlRequestMessage requestInfo, RequestParam requestParam) throws Exception {
    Object result = null;
    if (requestParam != null && requestParam.isEmpty()) {
        // 轉換插入sql
        SqlResultInfo resultInfo = SqlParseUtil.convertInsertSql(requestInfo, requestParam);
        if (resultInfo != null) {
            GeneratedKeyInfo keyInfo = executor.executeId(builderSql(resultInfo.getSql()), resultInfo.getValues());
            // 判斷是否有主鍵Key值
            if (StringUtils.isNotEmpty(resultInfo.getPrimaryKeyValue())) {
                result = resultInfo.getPrimaryKeyValue();
            } else {
                result = keyInfo.getResult();
            }
        } else {
            throw new ResponseException(ResponseResult.NOT_INSERT_DATA_EXECUTOR);
        }
    }
    // 将主鍵存入線程同步中
    if (result != null) {
        String insertBizKey = RequestContextHolder.getAttributes().getInsertBizKey();
        if (StringUtils.isEmpty(insertBizKey)) {
            RequestContextHolder.getAttributes().setInsertBizKey(insertBizKey);
        }
    }
    return (T) result;
}           

3、前後端資料傳輸格式與對象

為相容多個元件的情況,我将未加密的傳輸資料定義為以下格式。

[
    {
        "bizKey": "",
        "bizKeyCode": "",
        "saveFileIds": "",
        "deleteFileIds": ""
    }
]           
package com.threeox.biz.file.entity;

import com.threeox.drivenlibrary.entity.base.BaseObject;

/**
 * 檔案請求參數
 *
 * @author 趙屈犇
 * @version 1.0
 * @date 建立時間: 2022/9/23 20:31
 * @Copyright(C): 2022 by 趙屈犇
 */
public class FileParamInfo extends BaseObject {

    /**
     * 業務主鍵
     */
    private String bizKey;
    /**
     * 業務主鍵編碼
     */
    private String bizKeyCode;
    /**
     * 儲存檔案ids,以,号分割
     */
    private String saveFileIds;
    /**
     * 删除檔案ids,以,号分割
     */
    private String deleteFileIds;

    public String getBizKey() {
        return bizKey;
    }

    public void setBizKey(String bizKey) {
        this.bizKey = bizKey;
    }

    public String getBizKeyCode() {
        return bizKeyCode;
    }

    public void setBizKeyCode(String bizKeyCode) {
        this.bizKeyCode = bizKeyCode;
    }

    public String getSaveFileIds() {
        return saveFileIds;
    }

    public void setSaveFileIds(String saveFileIds) {
        this.saveFileIds = saveFileIds;
    }

    public String getDeleteFileIds() {
        return deleteFileIds;
    }

    public void setDeleteFileIds(String deleteFileIds) {
        this.deleteFileIds = deleteFileIds;
    }
}           

4、處理新增、更新業務主鍵綁定

/**
 * 更新檔案業務資料
 *
 * @return a
 * @author 趙屈犇
 * date 建立時間: 2022/9/23 20:25
 * @version 1.0
 */
public void updateFileBiz() throws Exception {
    List<FileParamInfo> paramInfos = getParamInfo();
    if (ArrayUtils.isNotEmpty(paramInfos)) {
        UpdateBuilder updateBuilder = null;
        try {
            // 初始化執行器并開啟事務 防止執行器不包含檔案資料庫
            updateBuilder = UpdateBuilder.builder().executor(DrivenManageSqlExecutor.newInstance())
                .beginTransaction();
            for (FileParamInfo paramInfo: paramInfos) {
                updateFileBiz(paramInfo, updateBuilder);
            }
        } catch (Exception e) {
            if (updateBuilder != null) {
                updateBuilder.rollback();
            }
            throw e;
        } finally {
            if (updateBuilder != null) {
                updateBuilder.commit();
            }
        }
    }
}

/**
 * 更新檔案業務
 *
 * @param paramInfo
 * @param updateBuilder
 * @return a
 * @author 趙屈犇
 * @date 建立時間: 2022/9/23 21:57
 * @version 1.0
 */
 private void updateFileBiz(FileParamInfo paramInfo, UpdateBuilder updateBuilder) throws Exception {
     String bizKey = paramInfo.getBizKey();
     if (StringUtils.isEmpty(bizKey)) {
         // 擷取主鍵編碼
         String bizKeyCode = paramInfo.getBizKeyCode();
         RequestAttributes attributes = RequestContextHolder.getAttributes();
         if (StringUtils.isNotEmpty(bizKeyCode)) {
             if (EmptyUtils.isKeyNotEmpty(attributes.getParamsJSON(), bizKeyCode)) {
                 bizKey = attributes.getParamsJSON().getString(bizKeyCode);
             }
         }
         // 如果為空,指派插入業務主鍵
         if (StringUtils.isEmpty(bizKey)) {
             bizKey = attributes.getInsertBizKey();
         }
     }
     // 不為空時,處理更新邏輯
     if (StringUtils.isNotEmpty(bizKey)) {
         // 處理新增檔案
         String saveFileIds = paramInfo.getSaveFileIds();
         if (StringUtils.isNotEmpty(saveFileIds)) {
             updateBuilder.table("ox_Sys_File").set("biz_key", bizKey).set(DefaultFieldConstants.DATA_STATE,
                     DataState.NORMAL.getValue())
                 .and("file_id", QueryType.IN, saveFileIds.split(",")).execute();
         }
         // 處理删除檔案
         String deleteFileIds = paramInfo.getDeleteFileIds();
         if (StringUtils.isNotEmpty(deleteFileIds)) {
             updateBuilder.table("ox_Sys_File").set("biz_key", bizKey).set(DefaultFieldConstants.DATA_STATE,
                     DataState.DELETE.getValue())
                 .and("file_id", QueryType.IN, deleteFileIds.split(",")).execute();
         }
     }
 }

/**
 * 擷取檔案請求參數對象
 *
 * @return a
 * @author 趙屈犇
 * @date 建立時間: 2022/9/23 20:58
 * @version 1.0
 */
private List<FileParamInfo> getParamInfo() {
    try {
        // 擷取檔案請求參數
        String fileContent = RequestContextHolder.getRequestHeader("fileContent");
        if (StringUtils.isNotEmpty(fileContent)) {
            fileContent = EncryptFactory.getInstance().decrypt(fileContent);
            if (StringUtils.isNotEmpty(fileContent)) {
                return JSON.parseArray(fileContent, FileParamInfo.class);
            }
        }
    } catch (Exception e) {
        loggerFactory.error("擷取檔案請求對象報錯了", e);
    }
    return null;
}           

5、提供查詢檔案清單接口

此接口,由于是給元件提供使用,我需要定義必須傳入bizKey、bizType字段,然後,通過bizKey、bizType查詢檔案清單資料并傳回。

package com.threeox.biz.file.api;

import com.alibaba.fastjson.JSONObject;
import com.threeox.dblibrary.annotation.create.Column;
import com.threeox.dblibrary.enums.ValueType;
import com.threeox.dblibrary.enums.WhereAndOr;
import com.threeox.drivenlibrary.engine.annotation.api.Api;
import com.threeox.drivenlibrary.engine.annotation.api.ApiVerifyConfig;
import com.threeox.drivenlibrary.engine.annotation.request.RequestConfig;
import com.threeox.drivenlibrary.engine.annotation.request.SqlRequestConfig;
import com.threeox.drivenlibrary.engine.config.constants.dictionary.ConfigDictionaryConstants;
import com.threeox.drivenlibrary.engine.constants.DefaultFieldConstants;
import com.threeox.drivenlibrary.engine.function.impl.AbstractApiExtend;
import com.threeox.drivenlibrary.manage.entity.FileMessage;

/**
 * 擷取檔案清單接口
 *
 * @author 趙屈犇
 * @version 1.0
 * @date 建立時間: 2022/9/23 22:00
 * @Copyright(C): 2022 by 趙屈犇
 */
@Api(apiUrl = "list", apiName = "擷取檔案清單接口", verifyConfigs = {
        @ApiVerifyConfig(paramCode = "bizType", emptyHint = "請傳入業務類型!"),
        @ApiVerifyConfig(paramCode = "bizKey", emptyHint = "請傳入業務編碼!"),
}, isVerifyLogin = false, isVerifyToken = false, moduleUrl = "file/get", requestConfigs = {
        @RequestConfig(requestName = "擷取檔案清單接口", requestCode = "getFileListByBiz", sqlConfig = @SqlRequestConfig(includeEntitys = FileMessage.class,
                columns = {

                        @Column(columnName = "biz_type", whereValue = "bizType", isSelectWhereUse = true, isSelectUse = false, rightAndOr = WhereAndOr.AND),
                        @Column(columnName = "biz_key", whereValue = "bizKey", isSelectWhereUse = true, isSelectUse = false, rightAndOr = WhereAndOr.AND),
                        @Column(columnName = DefaultFieldConstants.DATA_STATE, whereValue = ConfigDictionaryConstants.DataStateDict.NORMAL, whereValueType = ValueType.CUSTOM_VALUE, isSelectWhereUse = true, isSelectUse = false)
                }
        ), isTreeResult = true, seedKey = "value", parentKey = "pValue", childrenKey = "children", parameterClass = JSONObject.class)
})
public class GetFileListExtend extends AbstractApiExtend {

}           

至此後端部分功能就開發完成了,接下來,就對前端功能進行改造。

前端

1、檔案工廠

在與後端進行互動過程中,勢必要對所需傳遞的參數進行綁定。

為避免代碼備援,故有此封裝。此工廠,需實作對新增、删除檔案主鍵記錄,并在接口需要傳遞時,傳回加密處理後的資料。

/**
 * 檔案工廠類
 *
 * @constructor
 */
let FileFactory = function (engine) {

    let self = this;
    // 臨時存儲檔案内容
    let tempFileContent = null;

    /**
     * 添加儲存檔案主鍵
     *
     * @param bizKeyCode
     * @param bizKey
     * @param saveFileId
     */
    self.addSaveFileIds = function (bizKeyCode, bizKey, fileInfos) {
        if (engineCommon.isListNotEmpty(fileInfos)) {
            for (let i = 0, length = fileInfos.length; i < length; i++) {
                self.addFileId(bizKeyCode, bizKey, fileInfos[0], 'saveFileIds');
            }
        }
    }

    /**
     * 添加删除檔案主鍵
     *
     * @param bizKeyCode
     * @param bizKey
     * @param fileInfo
     */
    self.addDeleteFileIds = function (bizKeyCode, bizKey, fileInfos) {
        if (engineCommon.isListNotEmpty(fileInfos)) {
            for (let i = 0, length = fileInfos.length; i < length; i++) {
                self.addFileId(bizKeyCode, bizKey, fileInfos[0], 'deleteFileIds')
            }
        };
    }

    /**
     * 添加檔案主鍵
     *
     * @param bizType
     * @param fileInfo
     * @param fileIdKey
     */
    self.addFileId = function (bizKeyCode, bizKey, fileInfo, fileIdKey) {
        if (fileInfo) {
            // 初始化臨時檔案内容變量
            if (self.tempFileContent == null) {
                self.tempFileContent = new Object();
            }
            // 根據bizType擷取配置
            let bizContent = self.tempFileContent[bizKeyCode];
            if (bizContent == null) {
                bizContent = new Object();
            }
            // 設定bizKey
            if (engineCommon.isNotEmpty(bizKey)) {
                bizContent['bizKey'] = bizKey;
            }
            let fileIds = bizContent[fileIdKey];
            if (fileIds == null) {
                fileIds = new Array();
            }
            fileIds.push(fileInfo['fileId']);
            bizContent[fileIdKey] = fileIds;
            self.tempFileContent[bizKeyCode] = bizContent;
        }
    }

    /**
     * 擷取檔案内容配置
     */
    self.getFileContent = function () {
        if (self.tempFileContent) {
            let fileParams = [];
            for (let bizKeyCode in self.tempFileContent) {
                let fileParam = {
                    "bizKeyCode": bizKeyCode
                };
                let bizContent = self.tempFileContent[bizKeyCode];
                if (bizContent) {
                    if (engineCommon.isNotEmpty(bizContent['bizKey'])) {
                        fileParam['bizKey'] = bizContent['bizKey'];
                    }
                    let savFileIds = bizContent['saveFileIds'];
                    if (engineCommon.isListNotEmpty(savFileIds)) {
                        fileParam['saveFileIds'] = savFileIds.join(',');
                    }
                    let deleteFileIds = bizContent['deleteFileIds'];
                    if (engineCommon.isListNotEmpty(deleteFileIds)) {
                        fileParam['deleteFileIds'] = deleteFileIds.join(',');
                    }
                }
                fileParams.push(fileParam);
            }
            return EncryptUtils.encrypt(JSON.stringify(fileParams));
        }
        return null;
    }
}           

2、元素實作動态綁定

本篇,将完善我此前定義的拖拽上傳元素。

let upload = layFactory.getLayOption("upload");
// 初始化上傳對象
let render = new Object();
render.elem = self.factory.getElementId('${fieldCode}_upload_element');
render.url = RequestConstants.ENGINE_UPLOAD_FILES_PATH;
// 定義檔案對象
let fileInfos = new Array();
// 擷取業務主鍵
let bizKey = self.factory.getValueByKey('${fileBizKeyCode}');
if (engineCommon.isNotEmpty(bizKey)) {
    // 擷取檔案清單資料
    ApiRequest.getFileList({
        params: {
            bizKey: bizKey,
            bizType: "${fileBizType}"
        },
        success: function (data, message, result) {
            // 擷取上傳後的檔案對象
            fileInfos = data;
            if (engineCommon.isListNotEmpty(data)) {
                self.factory.byId('${fieldCode}_upload_show_view').removeClass('layui-hide').find('img').attr('src', fileInfos[0].accessUrl);
                self.factory.byId("${fieldCode}").val(fileInfos[0].fileAccessPath);
            }
        }
    });
}
render.before = function (obj) {
    // 攔截layui上傳,自己實作上傳功能
    let files = $('#${fieldCode}_ROOT_NODE .layui-upload-file').prop('files');
    FileRequest.upload({
        "files": files,
        "bizType": "${fileBizType}",
        success: function (data, message, result) {
            // 儲存曆史的删除檔案
            self.factory.fileFactory.addDeleteFileIds('${fileBizKeyCode}', bizKey, fileInfos);
            // 替換已删除的檔案對象
            fileInfos = data;
            if (engineCommon.isListNotEmpty(fileInfos)) {
                self.factory.byId('${fieldCode}_upload_show_view').removeClass('layui-hide').find('img').attr('src', fileInfos[0].accessUrl);
                self.factory.byId("${fieldCode}").val(fileInfos[0].fileAccessPath);
            }
            self.factory.fileFactory.addSaveFileIds('${fileBizKeyCode}', bizKey, data);
        }
    });
    return false;
}
if (engineCommon.isNotEmpty('${fileSize}')) {
    render.size = '${fileSize}';
}
if (engineCommon.isNotEmpty('${fileSuffixes}')) {
    render.exts = '${fileSuffixes}';
}
if (engineCommon.isNotEmpty('${fileType}')) {
    render.accept = '${fileType}';
}
if ('${isShowFileInput}' === 'true') {
    self.factory.byId("${fieldCode}").show();
} else {
    self.factory.byId("${fieldCode}").hide();
}
upload.render(render);           
至此,業已實作檔案動态綁定、拖拽上傳元素等功能。此後,隻需對各個所需的上傳功能做前端元素封裝即可。

繼續閱讀