遠端檔案管理系統(SpringBoot + Vue)
阿新 • • 發佈:2021-03-14
## 一、簡介
---
可以實現對本地檔案的 增、刪、改、重新命名等操作的監控,通過登入遠端檔案監控系統,獲取一段時間內本地檔案的變化情況。
系統功能圖如下:
![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
Edit
Delete
```
#### 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
}
]