支持更换头像

This commit is contained in:
whyour 2022-05-09 15:31:41 +08:00
parent 2c9b283b75
commit fb6a80e306
12 changed files with 127 additions and 29 deletions

View File

@ -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);
}
},
);
};

View File

@ -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,

View File

@ -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')) {

View File

@ -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');

View File

@ -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) {}

View File

@ -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<any> {
return new Promise((resolve) => {
fs.readFile(config.authConfigFile, 'utf8', (err, data) => {

View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -274,7 +274,12 @@ export default function (props: any) {
ctx.isPhone && (
<Dropdown overlay={menu} placement="bottomRight" trigger={['click']}>
<span className="side-menu-user-wrapper">
<Avatar shape="square" size="small" icon={<UserOutlined />} />
<Avatar
shape="square"
size="small"
icon={<UserOutlined />}
src={`/api/static/${user.avatar}`}
/>
<span style={{ marginLeft: 5 }}>{user.username}</span>
</span>
</Dropdown>
@ -291,7 +296,12 @@ export default function (props: any) {
{!collapsed && !ctx.isPhone && (
<Dropdown overlay={menu} placement="topLeft" trigger={['hover']}>
<span className="side-menu-user-wrapper">
<Avatar shape="square" size="small" icon={<UserOutlined />} />
<Avatar
shape="square"
size="small"
icon={<UserOutlined />}
src={`/api/static/${user.avatar}`}
/>
<span style={{ marginLeft: 5 }}>{user.username}</span>
</span>
</Dropdown>

View File

@ -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<any>();
const [code, setCode] = useState<string>();
const [avatar, setAvatar] = useState<string>();
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 ? '禁用' : '启用'}
</Button>
<div
style={{
fontSize: 18,
borderBottom: '1px solid #f0f0f0',
marginBottom: 8,
paddingBottom: 4,
marginTop: 16,
}}
>
</div>
<Avatar size={128} shape="square" icon={<UserOutlined />} src={avatar} />
<ImgCrop rotate>
<Upload
method="put"
showUploadList={false}
maxCount={1}
action="/api/user/avatar"
onChange={onChange}
name="avatar"
headers={{
Authorization: `Bearer ${localStorage.getItem(config.authKey)}`,
}}
>
<Button icon={<UploadOutlined />} style={{ marginLeft: 8 }}>
</Button>
</Upload>
</ImgCrop>
</>
);
};