From fb6a80e3069f18219c9251973cade96c413872be Mon Sep 17 00:00:00 2001 From: whyour Date: Mon, 9 May 2022 15:31:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9B=B4=E6=8D=A2=E5=A4=B4?= =?UTF-8?q?=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/api/user.ts | 36 ++++++++++++++++++++++++++--- back/config/index.ts | 2 ++ back/loaders/express.ts | 3 ++- back/loaders/initFile.ts | 6 +++++ back/services/sshKey.ts | 2 +- back/services/user.ts | 6 +++++ package.json | 9 +++++++- shell/check.sh | 10 -------- shell/share.sh | 16 +++++++++++++ shell/update.sh | 10 -------- src/layouts/index.tsx | 14 ++++++++++-- src/pages/setting/security.tsx | 42 +++++++++++++++++++++++++++++++++- 12 files changed, 127 insertions(+), 29 deletions(-) diff --git a/back/api/user.ts b/back/api/user.ts index 9cfd47a8..8940557d 100644 --- a/back/api/user.ts +++ b/back/api/user.ts @@ -1,13 +1,26 @@ import { Router, Request, Response, NextFunction } from 'express'; import { Container } from 'typedi'; import { Logger } from 'winston'; -import * as fs from 'fs'; -import config from '../config'; import UserService from '../services/user'; import { celebrate, Joi } from 'celebrate'; -import { getFileContentByName } from '../config/util'; +import multer from 'multer'; +import path from 'path'; +import { v4 as uuidV4 } from 'uuid'; +import config from '../config'; const route = Router(); +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, config.uploadPath); + }, + filename: function (req, file, cb) { + const ext = path.parse(file.originalname).ext; + const key = uuidV4(); + cb(null, key + ext); + }, +}); +const upload = multer({ storage: storage }); + export default (app: Router) => { app.use('/user', route); @@ -77,6 +90,7 @@ export default (app: Router) => { code: 200, data: { username: authInfo.username, + avatar: authInfo.avatar, twoFactorActivated: authInfo.twoFactorActivated, }, }); @@ -238,4 +252,20 @@ export default (app: Router) => { } }, ); + + route.put( + '/avatar', + upload.single('avatar'), + async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); + try { + const userService = Container.get(UserService); + const result = await userService.updateAvatar(req.file!.filename); + res.send(result); + } catch (e) { + logger.error('🔥 error: %o', e); + return next(e); + } + }, + ); }; diff --git a/back/config/index.ts b/back/config/index.ts index 76f4aa51..5d62d027 100644 --- a/back/config/index.ts +++ b/back/config/index.ts @@ -15,6 +15,7 @@ const scriptPath = path.join(dataPath, 'scripts/'); const bakPath = path.join(dataPath, 'bak/'); const logPath = path.join(dataPath, 'log/'); const dbPath = path.join(dataPath, 'db/'); +const uploadPath = path.join(dataPath, 'upload/'); const envFile = path.join(configPath, 'env.sh'); const confFile = path.join(configPath, 'config.sh'); @@ -58,6 +59,7 @@ export default { confFile, envFile, dbPath, + uploadPath, configPath, scriptPath, samplePath, diff --git a/back/loaders/express.ts b/back/loaders/express.ts index 8482de51..a216a909 100644 --- a/back/loaders/express.ts +++ b/back/loaders/express.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction, Application } from 'express'; +import express, { Request, Response, NextFunction, Application } from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; import routes from '../api'; @@ -17,6 +17,7 @@ import { EnvModel } from '../data/env'; export default ({ app }: { app: Application }) => { app.enable('trust proxy'); app.use(cors()); + app.use(`${config.api.prefix}/static`, express.static(config.uploadPath)); app.use((req, res, next) => { if (req.path.startsWith('/api') || req.path.startsWith('/open')) { diff --git a/back/loaders/initFile.ts b/back/loaders/initFile.ts index 5b37de87..aa6f1338 100644 --- a/back/loaders/initFile.ts +++ b/back/loaders/initFile.ts @@ -9,6 +9,7 @@ const dataPath = path.join(rootPath, 'data/'); const configPath = path.join(dataPath, 'config/'); const scriptPath = path.join(dataPath, 'scripts/'); const logPath = path.join(dataPath, 'log/'); +const uploadPath = path.join(dataPath, 'upload/'); const samplePath = path.join(rootPath, 'sample/'); const confFile = path.join(configPath, 'config.sh'); const authConfigFile = path.join(configPath, 'auth.json'); @@ -21,6 +22,7 @@ export default async () => { const scriptDirExist = await fileExist(scriptPath); const logDirExist = await fileExist(logPath); const configDirExist = await fileExist(configPath); + const uploadDirExist = await fileExist(uploadPath); if (!configDirExist) { fs.mkdirSync(configPath); @@ -42,6 +44,10 @@ export default async () => { fs.mkdirSync(logPath); } + if (!uploadDirExist) { + fs.mkdirSync(uploadPath); + } + dotenv.config({ path: confFile }); Logger.info('✌️ Init file down'); diff --git a/back/services/sshKey.ts b/back/services/sshKey.ts index aeea7afd..91c01c82 100644 --- a/back/services/sshKey.ts +++ b/back/services/sshKey.ts @@ -8,7 +8,7 @@ import path from 'path'; export default class SshKeyService { private homedir = os.homedir(); private sshPath = path.resolve(this.homedir, '.ssh'); - private sshConfigFilePath = path.resolve(this.homedir, '.ssh/config'); + private sshConfigFilePath = path.resolve(this.sshPath, 'config'); constructor(@Inject('logger') private logger: winston.Logger) {} diff --git a/back/services/user.ts b/back/services/user.ts index 68dd3347..db862690 100644 --- a/back/services/user.ts +++ b/back/services/user.ts @@ -221,6 +221,12 @@ export default class UserService { return { code: 200, message: '更新成功' }; } + public async updateAvatar(avatar: string) { + const authInfo = this.getAuthInfo(); + this.updateAuthInfo(authInfo, { avatar }); + return { code: 200, data: avatar, message: '更新成功' }; + } + public getUserInfo(): Promise { return new Promise((resolve) => { fs.readFile(config.authConfigFile, 'utf8', (err, data) => { diff --git a/package.json b/package.json index c1c73048..aa6631f3 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,10 @@ "react": "17", "react-dom": "17" }, - "ignoreMissing": ["monaco-editor", "react-router"] + "ignoreMissing": [ + "monaco-editor", + "react-router" + ] } }, "dependencies": { @@ -53,6 +56,7 @@ "iconv-lite": "^0.6.3", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.21", + "multer": "^1.4.4", "nedb": "^1.8.0", "node-fetch": "^3.2.1", "node-schedule": "^2.1.0", @@ -74,11 +78,13 @@ "@ant-design/pro-layout": "^6.33.1", "@monaco-editor/react": "^4.3.1", "@sentry/react": "^6.18.1", + "@types/body-parser": "^1.19.2", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/express-jwt": "^6.0.4", "@types/jsonwebtoken": "^8.5.8", "@types/lodash": "^4.14.179", + "@types/multer": "^1.4.7", "@types/nedb": "^1.8.12", "@types/node": "^17.0.21", "@types/node-fetch": "^2.6.1", @@ -96,6 +102,7 @@ "@umijs/test": "^3.5.21", "ansi-to-react": "^6.1.6", "antd": "^4.18.9", + "antd-img-crop": "^4.2.3", "codemirror": "^5.65.2", "compression-webpack-plugin": "9.2.0", "concurrently": "^7.0.0", diff --git a/shell/check.sh b/shell/check.sh index c777054c..cfab99d4 100644 --- a/shell/check.sh +++ b/shell/check.sh @@ -34,16 +34,6 @@ copy_dep() { echo -e "---> 配置文件复制完成\n" } -reload_pm2() { - pm2 l &>/dev/null - - pm2 delete panel --source-map-support --time &>/dev/null - pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null - - pm2 delete schedule --source-map-support --time &>/dev/null - pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null -} - pm2_log() { echo -e "---> pm2日志" local panelOut="/root/.pm2/logs/panel-out.log" diff --git a/shell/share.sh b/shell/share.sh index decf3bba..d77dac76 100755 --- a/shell/share.sh +++ b/shell/share.sh @@ -364,6 +364,22 @@ random_range() { echo $((RANDOM % ($end - $beg) + $beg)) } +reload_pm2() { + pm2 l &>/dev/null + + echo -e "启动面板服务\n" + pm2 delete panel --source-map-support --time &>/dev/null + pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null + + echo -e "启动定时任务服务\n" + pm2 delete schedule --source-map-support --time &>/dev/null + pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null + + echo -e "启动公开服务\n" + pm2 delete public --source-map-support --time &>/dev/null + pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null +} + init_env detect_termux detect_macos diff --git a/shell/update.sh b/shell/update.sh index 3cd5e163..6b8e8afd 100755 --- a/shell/update.sh +++ b/shell/update.sh @@ -333,16 +333,6 @@ patch_version() { } -reload_pm2() { - pm2 l &>/dev/null - - pm2 delete panel --source-map-support --time &>/dev/null - pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null - - pm2 delete schedule --source-map-support --time &>/dev/null - pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null -} - ## 对比脚本 diff_scripts() { local dir_current=$(pwd) diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index a2d0ea8a..7cf3108c 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -274,7 +274,12 @@ export default function (props: any) { ctx.isPhone && ( - } /> + } + src={`/api/static/${user.avatar}`} + /> {user.username} @@ -291,7 +296,12 @@ export default function (props: any) { {!collapsed && !ctx.isPhone && ( - } /> + } + src={`/api/static/${user.avatar}`} + /> {user.username} diff --git a/src/pages/setting/security.tsx b/src/pages/setting/security.tsx index a89c4d9e..f66eb5ed 100644 --- a/src/pages/setting/security.tsx +++ b/src/pages/setting/security.tsx @@ -1,10 +1,13 @@ import React, { useEffect, useState } from 'react'; -import { Typography, Input, Form, Button, message } from 'antd'; +import { Typography, Input, Form, Button, message, Avatar, Upload } from 'antd'; import { request } from '@/utils/http'; import config from '@/utils/config'; import { history } from 'umi'; import QRCode from 'qrcode.react'; import { PageLoading } from '@ant-design/pro-layout'; +import { UploadOutlined, UserOutlined } from '@ant-design/icons'; +import ImgCrop from 'antd-img-crop'; +import 'antd/es/slider/style'; const { Title, Link } = Typography; @@ -14,6 +17,7 @@ const SecuritySettings = ({ user, userChange }: any) => { const [twoFactoring, setTwoFactoring] = useState(false); const [twoFactorInfo, setTwoFactorInfo] = useState(); const [code, setCode] = useState(); + const [avatar, setAvatar] = useState(); const handleOk = (values: any) => { request @@ -86,6 +90,12 @@ const SecuritySettings = ({ user, userChange }: any) => { }); }; + const onChange = (e) => { + if (e.file && e.file.response) { + setAvatar(`/api/static/${e.file.response.data}`); + } + }; + useEffect(() => { setTwoFactorActivated(user && user.twoFactorActivated); }, [user]); @@ -210,6 +220,36 @@ const SecuritySettings = ({ user, userChange }: any) => { > {twoFactorActivated ? '禁用' : '启用'} + +
+ 头像 +
+ } src={avatar} /> + + + + + ); };