zoukankan      html  css  js  c++  java
  • 远程文件管理系统(SpringBoot + Vue)

    一、简介


    可以实现对本地文件的 增、删、改、重命名等操作的监控,通过登录远程文件监控系统,获取一段时间内本地文件的变化情况。

    系统功能图如下:
    image.png
    流程图如下:
    网安流程图.png

    二、本地文件监控程序的实现(C++)


    调用 windows api 监控本地文件操作,将对应的文件操作上传到远程数据库端。

    #include <Windows.h>
    #include <string>
    #include <iostream>
    #include <iomanip>
    #include <tchar.h>
    #include <winsock.h>
    #include "include/mysql.h"
    #include <ctime>
    #include <thread>
    
    #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, &notify, 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

    文件信息类:

    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;
        }
    }
    
    

    数据库操作接口:

    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<FileInfo> findAllFile();
    
        //分页获取
        @Select("SELECT * FROM file_info LIMIT #{start},#{end}")
        List<FileInfo> 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);
    }
    

    控制类

    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<FileInfo> data=fileMapper.findFile(start,end);
            apiJson.setCode(0);
            apiJson.setCount(100);
            apiJson.setMsg("test");
            apiJson.setData(data);
            return apiJson;
        }
    
        //统计
        @GetMapping("/count")
        public List<Integer> 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<Integer> res=new ArrayList<Integer>();
            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<FileInfo> getAllFileInfo(){
            return fileMapper.findAllFile();
        }
    }
    
    

    若前端使用 layui 框架,需要 json 格式的数据,所以利用该类生成 json 数据

    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<FileInfo> data;
    
        public ApiJson(int code,String msg,int count,List<FileInfo> data){
            this.code=code;
            this.msg=msg;
            this.count=count;
            this.data=data;
        }
    
    }
    
    

    四、前端实现(layui)


    借助 ajax 与后端进行数据交换
    例如:

        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 表格

    借助 Echarts 实现统计图的生成    echarts

    详情见 github 项目:(还没上传)

    image.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

    5.2 FileMon.vue

    FileMon 中划分为三大部分:头部(导航栏NavMenu)、侧边栏(图Echart)、main (表格)。

    <template>
      <el-container>
        <el-header>
          <NavMenu></NavMenu>
        </el-header>
    
        <el-container>
          <el-aside width="500px">
            <Echart ref="ac_e"></Echart>
          </el-aside>
          <el-container>
            <el-main>
              <Table ref="ac_t" @exchange="exchange()"></Table>
            </el-main>
          </el-container>
        </el-container>
    
      </el-container>
    </template>
    
    <script>
      import NavMenu from './common/NavMenu.vue'
      import Table from './common/Table.vue'
      import Echart from './common/Echart.vue'
    
      export default {
        name: 'FileMon',
        components:{ NavMenu,Table,Echart },
        methods: {
          exchange: function() {
            this.$refs.ac_e.$data.ac = this.$refs.ac_t.$data.ac
            this.$refs.ac_e.loads()
          }
        }
      }
    </script>
    
    <style>
    </style>
    

    5.3 NavMenu

    <template>
    
      <el-menu
        :default-active="activeIndex2"
        class="el-menu-demo"
        mode="horizontal"
        background-color="#545c64"
        text-color="#fff"
        active-text-color="#ffd04b">
    
        <el-menu-item index="1"></el-menu-item>
        <div style="right: 650px;position: fixed; color: #EEEEEE;top: 4px;">
          <h2>远程文件监控系统</h2>
        </div>
      </el-menu>
    
    </template>
    
    <script>
      export default {
        name: 'NavMenu'
      }
    </script>
    
    <style>
    </style>
    

    5.4 表格

    <template>
        <el-table
          height="500"
          :data="tableData.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase()))"
          style=" 100%">
          <el-table-column
            label="ID"
            prop="id">
          </el-table-column>
          <el-table-column
            label="操作类型"
            prop="action">
          </el-table-column>
          <el-table-column
            label="文件名"
            prop="name">
          </el-table-column>
          <el-table-column
            label="日期"
            prop="time">
          </el-table-column>
          <el-table-column
            align="right">
            <template slot="header" slot-scope="scope">
              <el-input
                v-model="search"
                size="mini"
                placeholder="输入关键字搜索"/>
            </template>
            <template slot-scope="scope">
              <el-button
                size="mini"
                @click="handleEdit(scope.$index, scope.row)">Edit</el-button>
              <el-button
                size="mini"
                type="danger"
                @click="handleDelete(scope.$index, scope.row)">Delete</el-button>
            </template>
          </el-table-column>
        </el-table>
    </template>
    
    <script>
      export default {
        name: 'Table',
        data: function() {
          return {
            tableData: [],
            search: '',
            ac: [0,0,0,0]   //统计文件各类操作数量,供绘制饼图
          }
        },
        methods: {
          handleEdit(index, row) {
            console.log(index, row)
          },
          handleDelete(index, row) {
            this.$axios.get('delete?id='+row.id).then(resp =>{
              console.log(resp.data)
              if( resp.data != 0){
                this.$alert('删除成功')
                this.loads()    //每次删除后更新一下表格中的数据
              }
            })
          },
          loads(){
            this.$axios.get('/all').then(resp =>{
              if( resp){
                this.tableData = resp.data
                this.counts()     //每次更新表格数据后,统计各类操作数量
                this.$emit('exchange')
              }
            })
          },
          counts(){
            var i
            for( i in this.tableData){   //这个 for 循环 i 是列表tableData的索引
              if(this.tableData[i].action == 'File Added') {this.ac[0] = this.ac[0]+1}
              else if(this.tableData[i].action == 'File Deleted') {this.ac[1] = this.ac[1]+1}
              else if(this.tableData[i].action == 'File Modified') {this.ac[2] = this.ac[2]+1}
              else  {this.ac[3] = this.ac[3]+1}
            }
          }
        },
        mounted:function(){
          this.loads()
        }
      }
    </script>
    
    <style>
    </style>
    
    

    5.5 饼图

    <template>
          <div>
          <!--卡片视图区域-->
          <el-card>
              <!-- 2、为ECharts准备一个具备大小(宽高)的Dom -->
              <div id="main" style=" 600px;height:500px;"></div>
          </el-card>
          </div>
    </template>
    <script>
    import * as echarts from 'echarts'   //引入 echarts
    
    export default {
      name: 'Echart',
      data: function () {
          return {
            ac: [0,0,0,0]
          }
        },
      methods:{
        loads: function() {
          // 3、基于准备好的dom,初始化echarts实例
          var myChart = echarts.init(document.getElementById('main'))
          // 4、准备数据和配置项
          // 指定图表的配置项和数据
          var option = {
                  title: {
                      text: '文件监控信息',
                      left: 'center'
                  },
                  tooltip: {
                      trigger: 'item'
                  },
                  legend: {
                      orient: 'vertical',
                      left: 'left',
                  },
                  series: [
                      {
                          name: '访问来源',
                          type: 'pie',
                          radius: '50%',
                          data: [
                              {value: this.ac[0], name: '添加文件'},
                              {value: this.ac[1], name: '删除文件'},
                              {value: this.ac[2], name: '修改文件'},
                              {value: this.ac[3], name: '重命名'},
                          ],
                          emphasis: {
                              itemStyle: {
                                  shadowBlur: 10,
                                  shadowOffsetX: 0,
                                  shadowColor: 'rgba(0, 0, 0, 0.5)'
                              }
                          }
                      }
                  ]
          }
          // 5、展示数据
          // 使用刚指定的配置项和数据显示图表。
          myChart.setOption(option);
        }
      },
      mounted: function() {
        this.loads()
      }
    }
    </script>
    
    

    5.6 FileMon--表格--饼图 之间的关系

    Table.vue 中的 loads 函数,每次执行时 counts() 函数更新 ac 变量的值,并定义触发 exchange 事件。

          loads(){
            this.$axios.get('/all').then(resp =>{
              if( resp){
                this.tableData = resp.data
                this.counts()
                this.$emit('exchange')
              }
            })
          },
    

    FileMon.vue 监听 exchange 事件,触发时执行 exchange 函数

        <el-container>
          <el-aside width="500px">
            <Echart ref="ac_e"></Echart>
          </el-aside>
          <el-container>
            <el-main>
              <Table ref="ac_t" @exchange="exchange()"></Table>
            </el-main>
          </el-container>
        </el-container>
    

    exchange 函数 取 Echart.vue 中的 ac 变量 赋值为 Table.vue 中的 ac 变量,调用 Echart.vue 变量的 load 方法。

        methods: {
          exchange: function() {
            this.$refs.ac_e.$data.ac = this.$refs.ac_t.$data.ac
            this.$refs.ac_e.loads()
          }
        }
      }
    

    5.7 main.js

    // 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: '<App/>'
    })
    
    

    5.8 路由

    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
        }
      ]
    })
    
  • 相关阅读:
    Nginx rewrite模块深入浅出详解
    一个ip对应多个域名多个ssl证书配置-Nginx实现多域名证书HTTPS
    nginx: [emerg] getpwnam(“www”) failed错误
    mysql5.7 启动报发生系统错误2
    obv15 实例6:如果K线柱过多,ZIG将发生变动,导致明显的OBV15指标被隐藏!
    obv15 案例4,待日后分析
    稳定
    教你识别指标骗局:以某家捕捞季节和主力追踪为例讲解
    C++ 语句
    C++ 表达式
  • 原文地址:https://www.cnblogs.com/WTa0/p/14533165.html
Copyright © 2011-2022 走看看