1. 程式人生 > >遠端檔案管理系統(SpringBoot + Vue)

遠端檔案管理系統(SpringBoot + Vue)

## 一、簡介 --- 可以實現對本地檔案的 增、刪、改、重新命名等操作的監控,通過登入遠端檔案監控系統,獲取一段時間內本地檔案的變化情況。 系統功能圖如下: ![image.png](https://img2020.cnblogs.com/blog/1859858/202103/1859858-20210314170832646-727086109.png) 流程圖如下: ![網安流程圖.png](https://img2020.cnblogs.com/blog/1859858/202103/1859858-20210314170832820-2081372481.png) ## 二、本地檔案監控程式的實現(C++) --- 呼叫 windows api 監控本地檔案操作,將對應的檔案操作上傳到遠端資料庫端。 ```cpp #include #include #include #include #include #include #include "include/mysql.h" #include #include #pragma comment(lib,"lib/libmysql.lib") #pragma comment(lib,"lib/mysqlclient.lib") using namespace std; /* 通過CreateFile函式開啟監控目錄,獲取監控目錄的控制代碼 API函式ReadDirecotryChangesW,實現檔案監控操作 */ //字串替換(全部) string replace(string& base, string src, string dst) { int pos = 0, srclen = src.size(), dstlen = dst.size(); while ((pos = base.find(src, pos)) != string::npos) { base.replace(pos, srclen, dst); pos += dstlen; } return base; } //void DirectoryMonitoring(); void DirectoryMonitoring(const TCHAR * disk,MYSQL &mysql) { //cout << __FUNCTION__ << " is called." << endl; //__FUNCTION__,當前被呼叫的函式名 string sql; ///mysql下面 DWORD cbBytes; //Double Word Windows.h中 char file_Name[MAX_PATH]; //設定檔名 char file_Name2[MAX_PATH]; //設定檔案重新命名後的名字 char notify[1024]; int count = 0; //檔案操作次數 TCHAR *dir =(TCHAR *) _T(disk); //_T 確保編碼的相容性,磁碟名 //呼叫CreateFile(Win Api)來獲得指向一個物理硬碟的控制代碼,CreateFile函式開啟監控目錄,獲取監控目錄的控制代碼。 HANDLE dirHandle = CreateFile(dir, GENERIC_READ | GENERIC_WRITE | FILE_LIST_DIRECTORY, //訪問模式對裝置可以讀寫資料 FILE_SHARE_READ | FILE_SHARE_WRITE, //共享模式可讀可寫 NULL, //檔案的安全特性,無 OPEN_EXISTING, //檔案必須已經存在,若不存在函式返回失敗 FILE_FLAG_BACKUP_SEMANTICS, //檔案屬性 NULL); //用於複製檔案控制代碼 if (dirHandle == INVALID_HANDLE_VALUE) //是否成功 { cout << "error" + GetLastError() << endl; } memset(notify, 0, strlen(notify)); //給notify賦值為0,即清空陣列 FILE_NOTIFY_INFORMATION *pnotify = (FILE_NOTIFY_INFORMATION*)notify; //結構體FILE_NOTIFY_INFORMATION,儲存檔案操作資訊其action屬性 cout << "正在監視檔案" << endl; while (true) { if (ReadDirectoryChangesW(dirHandle, ¬ify, 1024, true, //對目錄進行監視的控制代碼;一個指向FILE_NOTIFY_INFORMATION結構體的緩衝區,其中可以將獲取的資料結果將其返回;lpBuffer的緩衝區的大小值,以位元組為單位;是否監視子目錄. FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE, //對檔案過濾的方式和標準 &cbBytes, NULL, NULL)) //將接收的位元組數轉入lpBuffer引數 { //寬位元組轉換為多位元組 if (pnotify->
FileName) { memset(file_Name, 0, strlen(file_Name)); WideCharToMultiByte(CP_ACP, 0, pnotify->FileName, pnotify->FileNameLength / 2, file_Name, 99, NULL, NULL); } //重新命名的檔名 if (pnotify->NextEntryOffset != 0 && (pnotify->FileNameLength > 0 && pnotify->FileNameLength < MAX_PATH)) { PFILE_NOTIFY_INFORMATION p = (PFILE_NOTIFY_INFORMATION)((char*)pnotify + pnotify->NextEntryOffset); memset(file_Name2, 0, sizeof(file_Name2)); WideCharToMultiByte(CP_ACP, 0, p->FileName, p->FileNameLength / 2, file_Name2, 99, NULL, NULL); } string str=file_Name; str = replace(str, "\\", "/"); str = dir+str; string str2=file_Name2; str2 = replace(str2, "\\", "/"); str2 = dir+str2; string link = "-->"; //設定型別過濾器,監聽檔案建立、更改、刪除、重新命名等 switch (pnotify->Action) { case FILE_ACTION_ADDED: //新增檔案 count++; cout << count << setw(5) << "File Add:" << setw(5) << file_Name << endl; sql = "begin;"; mysql_query(&mysql, sql.c_str()); sql = "insert into file_info(action,name)\ values (\"File Added\",\""+str+"\");"; if (mysql_query(&mysql, sql.c_str())) { cout << "line: " << __LINE__ << ";" << mysql_error(&mysql) << mysql_errno(&mysql) << endl; } sql = "commit;"; mysql_query(&mysql, sql.c_str()); break; case FILE_ACTION_MODIFIED: //修改檔案 cout << "File Modified:" << setw(5) << file_Name << endl; sql = "begin;"; mysql_query(&mysql, sql.c_str()); sql = "insert into file_info(action,name)\ values (\"File Modified\",\"" + str + "\");"; if (mysql_query(&mysql, sql.c_str())) { cout << "line: " << __LINE__ << ";" << mysql_error(&mysql) << mysql_errno(&mysql) << endl; } sql = "commit;"; mysql_query(&mysql, sql.c_str()); break; case FILE_ACTION_REMOVED: //刪除檔案 count++; cout << count << setw(5) << "File Removed:" << setw(5) << file_Name << endl; sql = "begin;"; mysql_query(&mysql, sql.c_str()); sql = "insert into file_info(action,name)\ values (\"File Deleted\",\"" + str + "\");"; if (mysql_query(&mysql, sql.c_str())) { cout << "line: " << __LINE__ << ";" << mysql_error(&mysql) << mysql_errno(&mysql) << endl; } sql = "commit;"; mysql_query(&mysql, sql.c_str()); break; case FILE_ACTION_RENAMED_OLD_NAME: //重新命名 cout << "File Renamed:" << setw(5) << file_Name << "->" << file_Name2 << endl; sql = "begin;"; mysql_query(&mysql, sql.c_str()); sql = "insert into file_info(action,name)\ values (\"File Renamed\",\"" + str+link+str2 + "\");"; if (mysql_query(&mysql, sql.c_str())) { cout << "line: " << __LINE__ << ";" << mysql_error(&mysql) << mysql_errno(&mysql) << endl; } sql = "commit;"; mysql_query(&mysql, sql.c_str()); break; default: cout << "未知命令" << endl; } } } CloseHandle(dirHandle); } int _tmain(int argc, _TCHAR* argv[]) { const TCHAR * disk1 = _T("C://"); const TCHAR * disk2 = _T("D://"); const TCHAR * disk3 = _T("E://"); MYSQL mysql; mysql_init(&mysql); // 連線遠端資料庫 if (NULL == mysql_real_connect(&mysql, "database_host", "username", "password", "mysql", 3306, NULL, 0)) { cout << __LINE__ << mysql_error(&mysql) << mysql_errno(&mysql) << endl; throw - 1; } //進入資料庫hr_1 string sql = "use hr_1;"; if (mysql_query(&mysql, sql.c_str())) { cout << "line: " << __LINE__ << ";" << mysql_error(&mysql) << mysql_errno(&mysql) << endl; throw - 1; } mysql_query(&mysql, "SET NAMES GBK"); //資料庫編碼格式 thread t1(DirectoryMonitoring, disk1,ref(mysql)); thread t2(DirectoryMonitoring, disk2,ref(mysql)); thread t3(DirectoryMonitoring, disk3,ref(mysql)); t1.join(); t2.join(); t3.join(); return 0; } ``` ## 三、後端的實現(Java) --- 後端框架:SpringBoot    依賴:mybatis、lombok 檔案資訊類: ```java package com.example.file_monitor; import lombok.Data; import lombok.NoArgsConstructor; /* 檔案資訊實體物件 */ @Data @NoArgsConstructor public class FileInfo { private int id; private String action; private String name; private String time; public FileInfo(int id,String action,String name,String time){ this.id=id; this.action=action; this.name=name; this.time=time; } } ``` 資料庫操作介面: ```java import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; /* 資料庫操作介面 */ @Mapper public interface FileMapper { //獲取檔案監控資訊列表 @Select("SELECT * FROM file_info") List findAllFile(); //分頁獲取 @Select("SELECT * FROM file_info LIMIT #{start},#{end}") List findFile(@Param("start") int start,@Param("end") int end); //刪除 @Delete("DELETE FROM file_info WHERE id= #{id}") int deleteFile(@Param("id") int id); //統計各種操作 @Select("SELECT COUNT(*) FROM file_info WHERE action= #{a}") int getCount(@Param("a") String action); } ``` 控制類 ```java package com.example.file_monitor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping(value = "api") public class FileController { @Autowired private FileMapper fileMapper; @Autowired private ApiJson apiJson; //分頁 @GetMapping("/data") public ApiJson getFileList(@RequestParam("curr") int page,@RequestParam("nums") int limit){ int start=(page-1)*limit; int end= limit; List data=fileMapper.findFile(start,end); apiJson.setCode(0); apiJson.setCount(100); apiJson.setMsg("test"); apiJson.setData(data); return apiJson; } //統計 @GetMapping("/count") public List getCount(){ int mod=fileMapper.getCount("File Modified"); int add=fileMapper.getCount("File Added"); int dele=fileMapper.getCount("File Deleted"); int reName=fileMapper.getCount("File Renamed"); List res=new ArrayList(); res.add(mod); res.add(add); res.add(dele); res.add(reName); return res; } //刪除 @CrossOrigin @GetMapping("/delete") public int delFile(@RequestParam("id") int id){ return fileMapper.deleteFile(id); } //獲取所有資訊 @CrossOrigin @GetMapping("/all") public List getAllFileInfo(){ return fileMapper.findAllFile(); } } ``` 若前端使用 layui 框架,需要 json 格式的資料,所以利用該類生成 json 資料 ```java package com.example.file_monitor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; import java.util.List; @Data @NoArgsConstructor @Component public class ApiJson { private int code; private String msg; private int count; private List data; public ApiJson(int code,String msg,int count,List data){ this.code=code; this.msg=msg; this.count=count; this.data=data; } } ``` ## 四、前端實現(layui) --- 藉助 ajax 與後端進行資料交換 例如: ```javascript function sendAjaxGet() { $.ajax({ type: "GET", url: "/api/count", success: function(data){ Chart(data[0],data[1],data[2],data[3]); }, error: function (message) { } }); } sendAjaxGet(); ``` 藉助 layui table 實現表格的生成 [layui 表格](https://www.layui.com/doc/modules/table.html#use) 藉助 Echarts 實現統計圖的生成    [echarts](https://echarts.apache.org/zh/builder.html) 詳情見 github 專案:(還沒上傳) ![image.png](https://img2020.cnblogs.com/blog/1859858/202103/1859858-20210314170833086-2096794346.png) ## 五、前端實現(Vue) --- #### 5.1 簡介 之前使用的是 layui 搭建前端,最近在學 Vue,所以打算利用 Vue 前後端分離重寫一下前端。 目錄結構: ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1239731/1614932934224-303a7cab-e33d-40f2-a050-09f95c293bdf.png#align=left&display=inline&height=254 &originHeight=344&originWidth=361&size=17161&status=done&style=none&width=267)![遠端檔案監控系統初稿.png](https://img2020.cnblogs.com/blog/1859858/202103/1859858-20210314170833314-1536116927.png) #### 5.2 FileMon.vue FileMon 中劃分為三大部分:頭部(導航欄NavMenu)、側邊欄(圖Echart)、main (表格)。 ```javascript ``` #### 5.3 NavMenu ```javascript ``` #### 5.4 表格 ```javascript ``` #### 5.5 餅圖 ```javascript ``` #### 5.6 FileMon--表格--餅圖 之間的關係 Table.vue 中的 loads 函式,每次執行時 counts() 函式更新 ac 變數的值,並定義觸發 exchange 事件。 ```javascript loads(){ this.$axios.get('/all').then(resp =>{ if( resp){ this.tableData = resp.data this.counts() this.$emit('exchange') } }) }, ``` FileMon.vue 監聽 exchange 事件,觸發時執行 exchange 函式 ```javascript
``` exchange 函式 取 Echart.vue 中的 ac 變數 賦值為 Table.vue 中的 ac 變數,呼叫 Echart.vue 變數的 load 方法。 ```javascript methods: { exchange: function() { this.$refs.ac_e.$data.ac = this.$refs.ac_t.$data.ac this.$refs.ac_e.loads() } } } ``` #### 5.7 main.js ```javascript // The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import axios from 'axios' //引入axios //設定代理 axios.defaults.baseURL = 'http://localhost:8443/api' //註冊全域性 Vue.prototype.$axios = axios import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.config.productionTip = false Vue.use(ElementUI); /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '' }) ``` #### 5.8 路由 ```javascript import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import FileMon from '@/components/FileMon' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { path: '/fm', name: 'FileMon', component: FileMon } ]