lrf 2 лет назад
Сommit
51643cb455

+ 29 - 0
.autod.conf.js

@@ -0,0 +1,29 @@
+'use strict';
+
+module.exports = {
+  write: true,
+  prefix: '^',
+  plugin: 'autod-egg',
+  test: [
+    'test',
+    'benchmark',
+  ],
+  dep: [
+    'egg',
+    'egg-scripts',
+  ],
+  devdep: [
+    'egg-ci',
+    'egg-bin',
+    'egg-mock',
+    'autod',
+    'autod-egg',
+    'eslint',
+    'eslint-config-egg',
+  ],
+  exclude: [
+    './test/fixtures',
+    './dist',
+  ],
+};
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+coverage

+ 4 - 0
.eslintrc

@@ -0,0 +1,4 @@
+{
+  "extends": "eslint-config-egg",
+  "root": true
+}

+ 46 - 0
.github/workflows/nodejs.yml

@@ -0,0 +1,46 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Node.js CI
+
+on:
+  push:
+    branches:
+      - main
+      - master
+  pull_request:
+    branches:
+      - main
+      - master
+  schedule:
+    - cron: '0 2 * * *'
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        node-version: [16]
+        os: [ubuntu-latest, windows-latest, macos-latest]
+
+    steps:
+    - name: Checkout Git Source
+      uses: actions/checkout@v2
+
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+
+    - name: Install Dependencies
+      run: npm i
+
+    - name: Continuous Integration
+      run: npm run ci
+
+    - name: Code Coverage
+      uses: codecov/codecov-action@v1
+      with:
+        token: ${{ secrets.CODECOV_TOKEN }}

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+typings/
+.nyc_output/

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# dick-scan
+
+
+
+## QuickStart
+
+<!-- add docs here for user -->
+
+see [egg docs][egg] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+- Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail.
+
+
+[egg]: https://eggjs.org

+ 28 - 0
app/controller/home.js

@@ -0,0 +1,28 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+const os = require('os');
+
+class HomeController extends Controller {
+  async index() {
+    const { ctx } = this;
+    ctx.body = 'hi';
+    await this.ctx.service.disk.sendWarningEmail();
+  }
+
+  async diskInfo() {
+    const res = await this.ctx.service.disk.diskInfo();
+    this.ctx.ok({ data: res });
+  }
+
+  async toDir() {
+    const res = await this.ctx.service.disk.toDir(this.ctx.request.body);
+    this.ctx.ok({ data: res });
+  }
+  async delFile() {
+    const res = await this.ctx.service.disk.delFile(this.ctx.request.body);
+    this.ctx.ok({ data: res });
+  }
+}
+
+module.exports = HomeController;

+ 29 - 0
app/router.js

@@ -0,0 +1,29 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+const os = require('os');
+function getIPAdress() {
+  const interfaces = os.networkInterfaces();
+  for (const devName in interfaces) {
+    const iface = interfaces[devName];
+    for (let i = 0; i < iface.length; i++) {
+      const alias = iface[i];
+      if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
+        return alias.address;
+      }
+    }
+  }
+}
+module.exports = app => {
+  const { router, controller } = app;
+  const { routePrefix, cluster } = app.config;
+  const ipAddress = getIPAdress();
+  console.log(`前缀:http://${ipAddress}:${cluster.listen.port}${routePrefix}`);
+  console.log();
+  router.get(routePrefix, controller.home.index);
+  router.get(`${routePrefix}/diskInfo`, controller.home.diskInfo);
+  router.post(`${routePrefix}/dir`, controller.home.toDir);
+  router.post(`${routePrefix}/delFile`, controller.home.delFile);
+};

+ 21 - 0
app/schedule/task.js

@@ -0,0 +1,21 @@
+'use strict';
+module.exports = {
+  schedule: {
+    cron: '0 26 9 * * *',
+    // interval: '10s', // 时间间隔
+    type: 'all', // 指定所有的 worker 都需要执行
+  },
+  async task(ctx) {
+    const { redLine, checkDrive } = ctx.app.config;
+    if (!checkDrive) return;
+    if (!redLine) return;
+    const diskList = await ctx.service.disk.diskInfo();
+    const drive = diskList.find(f => f.mounted === checkDrive);
+    if (!drive) return;
+    const { capacity } = drive;
+    const number = parseInt(capacity.replace('%', ''));
+    if (number && number >= redLine) {
+      await ctx.service.disk.sendWarningEmail();
+    }
+  },
+};

+ 120 - 0
app/service/disk.js

@@ -0,0 +1,120 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const d = require('diskinfo');
+const fs = require('fs');
+const path = require('path');
+const moment = require('moment');
+const nodemailer = require('nodemailer');
+//
+class DiskService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'disk');
+  }
+
+  async diskInfo() {
+    return new Promise(resolve => {
+      d.getDrives(function(err, aDrives) {
+        aDrives = aDrives.map(i => _.omit(i, [ 'filesystem' ]));
+        resolve(aDrives);
+      });
+    });
+  }
+  async toDir({ route = [ '' ] } = {}) {
+    const root = this.app.config.dirRoot;
+    const p = path.resolve(root, ...route);
+    const dir = fs.readdirSync(p);
+    const arr = [];
+    for (const file of dir) {
+      const fp = path.resolve(p, file);
+      const fi = fs.statSync(fp);
+      const obj = { name: file };
+      if (fi.isDirectory()) obj.type = 'dir';
+      else if (fi.isFile()) obj.type = 'file';
+      else obj.type = undefined;
+      // 文件大小, kb
+      obj.size = fi.size / 100;
+      // 创建时间
+      obj.create_time = moment(fi.ctime).format('YYYY-MM-DD HH:mm:ss');
+      arr.push(obj);
+    }
+    return arr;
+  }
+
+  async delFile({ route, target, times }) {
+    if (!_.isArray(route)) throw new BusinessError(ErrorCode.BADPARAM, '路径错误');
+    const root = this.app.config.dirRoot;
+    const p = path.resolve(root, ...route, target || '');
+    if (fs.statSync(p).isFile()) {
+      if (_.get(times, 'start_time') && _.get(times, 'end_time')) {
+        const r = this.inRange(moment(fs.statSync(p).ctime).format('YYYY-MM-DD HH:mm:ss'), times);
+        if (!r) return;
+      }
+      fs.unlinkSync(p);
+    } else await this.dirDelete(p, times);
+  }
+  /**
+   * 删除路径下的所有内容
+   * @param {String} filePath 路径
+   * @param {Object} times 里面有开始时间和结束时间: start_time;end_time
+   */
+  async dirDelete(filePath, times) {
+    if (fs.existsSync(filePath)) {
+      const files = fs.readdirSync(filePath);
+      for (const file of files) {
+        const curPath = path.resolve(filePath, file);
+        const fi = fs.statSync(curPath);
+        if (fi.isDirectory()) {
+          // recurse
+          this.dirDelete(curPath);
+        } else {
+          // delete file
+          if (_.get(times, 'start_time') && _.get(times, 'end_time')) {
+            const r = this.inRange(moment(fi.ctime).format('YYYY-MM-DD HH:mm:ss'), times);
+            if (!r) continue;
+          }
+          fs.unlinkSync(curPath);
+        }
+      }
+      if (!(_.get(times, 'start_time') && _.get(times, 'end_time'))) fs.rmdirSync(filePath);
+    }
+  }
+
+  /**
+   * 判断时间是否在范围内
+   * @param {String} create_time 文件的时间范围
+   * @param {Object} times 里面有开始时间和结束时间: start_time;end_time
+   */
+  inRange(create_time, times) {
+    console.log(moment(create_time).isBetween(times.start_time, times.end_time, null, '[]'));
+    return moment(create_time).isBetween(times.start_time, times.end_time, null, '[]');
+  }
+
+  async sendWarningEmail() {
+    const { receiver, sender } = this.app.config;
+    if (!receiver) return;
+    if (!sender) return;
+    const config = {
+      host: 'smtp.163.com',
+      port: 465,
+      secure: true,
+      auth: sender,
+    };
+    const mailOptions = {
+      from: `"free服务器" <${sender.user}>`, // 邮件来源
+      to: receiver, // 邮件发送到哪里,多个邮箱使用逗号隔开
+      subject: 'free服务器提示', // 邮件主题
+      text: '项目空间不足', // 存文本类型的邮件正文
+      html: '<b>清理科企项目空间啦!!</b>', // html类型的邮件正文
+    };
+    const transporter = nodemailer.createTransport(config);
+    transporter.sendMail(mailOptions, (error, info) => {
+      if (error) console.log(error);
+      else console.log(info);
+    });
+
+  }
+}
+
+module.exports = DiskService;

+ 51 - 0
config/config.default.js

@@ -0,0 +1,51 @@
+/* eslint valid-jsdoc: "off" */
+
+'use strict';
+
+/**
+ * @param {Egg.EggAppInfo} appInfo app info
+ */
+module.exports = appInfo => {
+  /**
+   * built-in config
+   * @type {Egg.EggAppConfig}
+   **/
+  const config = (exports = {});
+
+  // use for cookie sign key, should change to your own and keep security
+  config.keys = appInfo.name + '_1663725200927_6862';
+
+  // add your middleware config here
+  config.middleware = [];
+
+  // add your user config here
+  const userConfig = {
+    // myAppName: 'egg',
+  };
+  // 进程设置
+  config.cluster = {
+    listen: {
+      port: 11011,
+    },
+  };
+  // 路由设置
+  config.routePrefix = '/disk/api';
+  config.dirRoot = 'D:\\temp\\temp';
+  // 检查的盘符
+  config.checkDrive = 'D:';
+  // 占用空间警告线
+  config.redLine = 80; // 百分制
+  // 邮件发送人
+  config.sender = {
+    user: 'myhope1977@163.com', // 邮箱账号
+    pass: 'RZGYKLOQUTRCNLEO', // 邮箱stmp授权码
+  };
+  // 邮件接收人,多个人用 "," 分隔
+  config.receiver = '402788946@qq.com';
+
+
+  return {
+    ...config,
+    ...userConfig,
+  };
+};

+ 14 - 0
config/config.prod.js

@@ -0,0 +1,14 @@
+'use strict';
+
+module.exports = () => {
+  const config = (exports = {});
+  // 可操作范围的根目录
+  config.dirRoot = 'D:\\free';
+  // 检查的盘符
+  config.checkDrive = 'D:';
+  // 占用空间警告线
+  config.redLine = 80; // 百分制
+  // 邮件接收人
+  config.receiver = 'guhongwei0324@163.com';
+  return config;
+};

+ 9 - 0
config/plugin.js

@@ -0,0 +1,9 @@
+'use strict';
+
+/** @type Egg.EggPlugin */
+module.exports = {
+  // had enabled by egg
+  // static: {
+  //   enable: true,
+  // }
+};

+ 53 - 0
package.json

@@ -0,0 +1,53 @@
+{
+  "name": "disk-scan",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "egg": {
+    "framework": "naf-framework-mongoose-free"
+  },
+  "dependencies": {
+    "diskinfo": "^0.0.3",
+    "egg": "^2",
+    "egg-scripts": "^2",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.1",
+    "naf-framework-mongoose-free": "^0.0.36",
+    "nodemailer": "^6.7.8"
+  },
+  "devDependencies": {
+    "autod": "^3",
+    "autod-egg": "^1",
+    "egg-bin": "^4",
+    "egg-ci": "^2",
+    "egg-mock": "^4",
+    "eslint": "^8",
+    "eslint-config-egg": "^12",
+    "jsonwebtoken": "^8.5.1"
+  },
+  "engines": {
+    "node": ">=16.0.0"
+  },
+  "scripts": {
+    "start": "egg-scripts start --daemon --title=egg-server-dick-scan",
+    "stop": "egg-scripts stop --title=egg-server-dick-scan",
+    "dev": "egg-bin dev",
+    "debug": "egg-bin debug",
+    "test": "npm run lint -- --fix && npm run test-local",
+    "test-local": "egg-bin test",
+    "cov": "egg-bin cov",
+    "lint": "eslint .",
+    "ci": "npm run lint && npm run cov",
+    "autod": "autod"
+  },
+  "ci": {
+    "version": "16",
+    "type": "github"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "",
+  "license": "MIT"
+}

+ 20 - 0
test/app/controller/home.test.js

@@ -0,0 +1,20 @@
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/controller/home.test.js', () => {
+  it('should assert', async () => {
+    const pkg = require('../../../package.json');
+    assert(app.config.keys.startsWith(pkg.name));
+
+    // const ctx = app.mockContext({});
+    // yield ctx.service.xx();
+  });
+
+  it('should GET /', async () => {
+    return app.httpRequest()
+      .get('/')
+      .expect('hi, egg')
+      .expect(200);
+  });
+});