Skip to content

Commit ce03bc7

Browse files
author
pedro
committed
feat:添加头像上传业务
1 parent d960f7f commit ce03bc7

12 files changed

Lines changed: 135 additions & 55 deletions

File tree

.eslintrc.js

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
module.exports = {
2-
"extends": "standard",
3-
"plugins": ["jest"],
4-
"rules": {
5-
"semi": ["warn", "always"],
6-
"quotes": ["warn", "single"],
7-
"eol-last": 0,
8-
"jest/no-disabled-tests": "warn",
9-
"jest/no-focused-tests": "error",
10-
"jest/no-identical-title": "error",
11-
"jest/prefer-to-have-length": "warn",
12-
"jest/valid-expect": "error"
2+
extends: 'standard',
3+
plugins: ['jest'],
4+
rules: {
5+
semi: ['warn', 'always'],
6+
quotes: ['warn', 'single'],
7+
'eol-last': 0,
8+
'jest/no-disabled-tests': 'warn',
9+
'jest/no-focused-tests': 'error',
10+
'jest/no-identical-title': 'error',
11+
'jest/prefer-to-have-length': 'warn',
12+
'jest/valid-expect': 'error'
1313
},
14-
"env": {
15-
"jest": true
14+
env: {
15+
jest: true
1616
}
17-
};
17+
};

app/api/cms/file.js

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
'use strict';
22

3-
const { LinRouter, NotFound } = require('lin-mizar');
4-
const { ratelimit } = require('lin-mizar/lin/limiter');
3+
const { LinRouter, ParametersException } = require('lin-mizar');
4+
// const { ratelimit } = require('lin-mizar/lin/limiter');
55
const { LocalUploader } = require('../../extensions/file/local-uploader');
6-
const redis = require('redis');
6+
// const redis = require('redis');
77

8-
const client = redis.createClient();
8+
// const client = redis.createClient();
99

1010
const file = new LinRouter({
1111
prefix: '/cms/file'
@@ -14,27 +14,27 @@ const file = new LinRouter({
1414
file.post('/', async ctx => {
1515
const files = await ctx.multipart();
1616
if (files.length < 1) {
17-
throw new NotFound({ msg: '未找到符合条件的文件资源' });
17+
throw new ParametersException({ msg: '未找到符合条件的文件资源' });
1818
}
1919
const uploader = new LocalUploader('app/assets');
20-
await uploader.upload(files);
21-
ctx.success();
20+
const arr = await uploader.upload(files);
21+
ctx.json(arr);
2222
});
2323

24-
file.get(
25-
'/',
26-
ratelimit({
27-
db: client,
28-
duration: 30 * 1000,
29-
max: 5,
30-
// throw: true,
31-
logging: true
32-
}),
33-
async ctx => {
34-
ctx.body = {
35-
msg: 'just a normal response.'
36-
};
37-
}
38-
);
24+
// file.get(
25+
// '/',
26+
// ratelimit({
27+
// db: client,
28+
// duration: 30 * 1000,
29+
// max: 5,
30+
// // throw: true,
31+
// logging: true
32+
// }),
33+
// async ctx => {
34+
// ctx.body = {
35+
// msg: 'just a normal response.'
36+
// };
37+
// }
38+
// );
3939

4040
module.exports = { file };

app/api/cms/user.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const {
1515
RegisterValidator,
1616
LoginValidator,
1717
UpdateInfoValidator,
18-
ChangePasswordValidator
18+
ChangePasswordValidator,
19+
AvatarUpdateValidator
1920
} = require('../../validators/user');
2021

2122
const { UserDao } = require('../../dao/user');
@@ -147,4 +148,13 @@ user.linGet(
147148
}
148149
);
149150

151+
user.put('/avatar', loginRequired, async ctx => {
152+
const v = await new AvatarUpdateValidator().validate(ctx);
153+
const avatar = v.get('body.avatar');
154+
let user = ctx.currentUser;
155+
user.avatar = avatar;
156+
await user.save();
157+
ctx.success({ msg: '更新头像成功' });
158+
});
159+
150160
module.exports = { user };

app/app.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const Koa = require('koa');
44
const KoaBodyParser = require('koa-bodyparser');
55
const cors = require('@koa/cors');
66
const { config } = require('lin-mizar/lin/config');
7+
const mount = require('koa-mount');
8+
const serve = require('koa-static');
79

810
function applyCors (app) {
911
// 跨域
@@ -15,6 +17,11 @@ function applyBodyParse (app) {
1517
app.use(KoaBodyParser());
1618
}
1719

20+
function applyStatic (app, prefix = '/assets') {
21+
const assetsDir = config.getItem('file.storeDir', 'app/static');
22+
app.use(mount(prefix, serve(assetsDir)));
23+
}
24+
1825
function indexPage (app) {
1926
app.context.manager.loader.mainRouter.get('/', async ctx => {
2027
ctx.type = 'html';
@@ -30,6 +37,7 @@ async function createApp () {
3037
const app = new Koa();
3138
applyBodyParse(app);
3239
applyCors(app);
40+
applyStatic(app);
3341
config.initApp(app);
3442
const { log, error, Lin, multipart } = require('lin-mizar');
3543
app.use(log);

app/config/setting.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use strict';
22

33
module.exports = {
4+
port: 5000,
5+
siteDomain: 'http://localhost:5000',
46
countDefault: 10,
57
pageDefault: 0,
68
apiDir: 'app/api',

app/extensions/file/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module.exports = {
44
file: {
5-
storeDir: 'assets',
5+
storeDir: 'app/assets',
66
singleLimit: 1024 * 1024 * 2,
77
totalLimit: 1024 * 1024 * 20,
88
nums: 10,

app/extensions/file/local-uploader.js

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,62 @@
11
const { Uploader } = require('lin-mizar/lin/file');
2+
const { File } = require('lin-mizar');
3+
const { config } = require('lin-mizar/lin/config');
24
const fs = require('fs');
5+
const path = require('path');
6+
const { cloneDeep } = require('lodash');
37

48
class LocalUploader extends Uploader {
59
/**
610
* 处理文件流
711
* @param {object[]} files 文件流数组
812
*/
913
async upload (files) {
14+
const arr = [];
1015
for (const stream of files) {
11-
const filepath = this.getStorePath(stream.filename);
12-
const target = fs.createWriteStream(filepath);
13-
await stream.pipe(target);
16+
// 由于stream的特性,当读取其中的数据时,它的buffer会被消费
17+
// 所以此处深拷贝一份计算md5值
18+
const tmpStream = cloneDeep(stream);
19+
const md5 = this.generateMd5(tmpStream);
20+
const siteDomain = config.getItem('siteDomain', 'http://localhost');
21+
// 检查md5存在
22+
const exist = await File.findOne({
23+
where: {
24+
md5: md5
25+
}
26+
});
27+
if (exist) {
28+
arr.push({
29+
key: stream.fieldname,
30+
id: exist.id,
31+
url: `${siteDomain}/assets/${exist.path}`
32+
});
33+
} else {
34+
const { absolutePath, relativePath, realName } = this.getStorePath(
35+
stream.filename
36+
);
37+
const target = fs.createWriteStream(absolutePath);
38+
await stream.pipe(target);
39+
const ext = path.extname(realName);
40+
// stream.filename tream.filedname stream.mimeType stream.readableLength
41+
const saved = await File.createRecord(
42+
{
43+
path: relativePath,
44+
// type: 1,
45+
name: realName,
46+
extension: ext,
47+
size: stream.readableLength,
48+
md5: md5
49+
},
50+
true
51+
);
52+
arr.push({
53+
key: stream.fieldname,
54+
id: saved.id,
55+
url: `${siteDomain}/assets/${saved.path}`
56+
});
57+
}
1458
}
59+
return arr;
1560
}
1661
}
1762

app/starter.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ function applyConfig () {
1010
for (const file of files) {
1111
config.getConfigFromFile(`app/config/${file}`);
1212
}
13+
// 加载其它配置文件
14+
config.getConfigFromFile('app/extensions/file/config.js');
1315
}
1416

1517
const run = async () => {
1618
applyConfig();
1719
const { createApp } = require('./app');
1820
const app = await createApp();
19-
app.listen(5000, () => {
20-
app.context.logger.start('listening at http://localhost:5000');
21+
const port = config.getItem('port');
22+
app.listen(port, () => {
23+
app.context.logger.start(`listening at http://localhost:${port}`);
2124
});
2225
};
2326
// 启动应用

app/validators/user.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,17 @@ class ChangePasswordValidator extends LinValidator {
8181
}
8282
}
8383

84+
class AvatarUpdateValidator extends LinValidator {
85+
constructor () {
86+
super();
87+
this.avatar = new Rule('isNotEmpty', '必须传入头像的url链接');
88+
}
89+
}
90+
8491
module.exports = {
8592
ChangePasswordValidator,
8693
UpdateInfoValidator,
8794
LoginValidator,
88-
RegisterValidator
95+
RegisterValidator,
96+
AvatarUpdateValidator
8997
};

jest.config.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
// https://jestjs.io/docs/en/configuration.html
33

44
module.exports = {
5-
coverageDirectory: "coverage",
6-
testEnvironment: "node",
7-
testPathIgnorePatterns: [
8-
"/node_modules/"
9-
]
10-
};
5+
coverageDirectory: 'coverage',
6+
testEnvironment: 'node',
7+
testPathIgnorePatterns: ['/node_modules/']
8+
};

0 commit comments

Comments
 (0)