Skip to content

Commit

Permalink
[add] 更新了相片后删去旧相片的原图(同样,进行更新操作时也是要这么处理)
Browse files Browse the repository at this point in the history
  • Loading branch information
VecHK committed Jan 7, 2024
1 parent 31460a6 commit 7a64639
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 69 deletions.
10 changes: 5 additions & 5 deletions server/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,25 @@ module.exports = app => {

{
const { create, remove, get, show, edit, removeMemberGalleryVote } = controller.admin.member;
setAdminRouter('post', 'member', create);
setAdminRouter('delete', 'member/:id', remove);
setAdminRouter('get', 'member/:id', get);
setAdminRouter('get', 'member', show);
setAdminRouter('get', 'member/:id', get);
setAdminRouter('post', 'member', create);
setAdminRouter('patch', 'member/:id', edit);
setAdminRouter('delete', 'member/:id', remove);

setAdminRouter('delete', 'member/:id/gallery/:gallery_id/vote', removeMemberGalleryVote);
}

{
const { create, remove, show, get, showPhotoVote, showMemberVote, sortByVoteCount, edit } = controller.admin.photo;
setAdminRouter('get', 'photo/:id', get);
setAdminRouter('post', 'photo', create);
setAdminRouter('patch', 'photo/:id', edit);
setAdminRouter('delete', 'photo/:id', remove);
setAdminRouter('get', 'photo/:id', get);
setAdminRouter('get', 'gallery/:gallery_id/photo', show);
setAdminRouter('get', 'gallery/:gallery_id/photo_vote', showPhotoVote);
setAdminRouter('get', 'gallery/:gallery_id/member_vote', showMemberVote);
setAdminRouter('put', 'gallery/:gallery_id/photo/sortByVoteCount', sortByVoteCount);
setAdminRouter('patch', 'photo/:id', edit);
}

setRouter('get', 'gallery/:gallery_id/submission/:qq_num', controller.gallery.submission);
Expand Down
34 changes: 22 additions & 12 deletions server/app/service/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,40 @@
const path = require('path');
const fs = require('fs');
const sharp = require('sharp');
const { Service } = require('egg');
const fileExists = require('../utils/file-exists');

function urlPathnameJoin(url, append_path) {
const u = new URL(url);
u.pathname = path.join(u.pathname, append_path);
return u.toString();
}

async function fileExists(filename) {
try {
await fs.promises.access(filename);
return true;
} catch (err) {
if (err.code === 'ENOENT') {
return false;
} else {
throw err;
}
}
// ref: https://sharp.pixelplumbing.com/api-input#metadata
function getNormalSize({ width, height, orientation }) {
return (orientation || 0) >= 5
? { orientation, width: height, height: width }
: { orientation, width, height };
}

const { Service } = require('egg');
async function getImageSize(filePath) {
return getNormalSize(await sharp(filePath).metadata());
}

module.exports = app =>
class ImageService extends Service {
static async getImageDimensions(src) {
const srcPath = app.serviceClasses.image.toSrcSavePath(src);

if (!fs.existsSync(srcPath)) {
throw new this.app.WarningError('src不存在', 404);
}

const { width, height } = await getImageSize(srcPath);

return { width, height };
}

static allFilename(src) {
const src_without_ext = path.parse(src).name;
return app.config.supported_formats.map(format => {
Expand Down
22 changes: 18 additions & 4 deletions server/app/service/member.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ module.exports = app =>

async edit(id, data) {
return this.app.model.transaction(async transaction => {
const member = await this.findById(id, { transaction });
const transaction_opts = { transaction, lock: transaction.LOCK.UPDATE };

const member = await this.findById(id, transaction_opts);

if (data.hasOwnProperty('qq_num') && (member.qq_num !== data.qq_num)) {
// 检查所修改的QQ号是否被占用
Expand All @@ -56,22 +58,34 @@ module.exports = app =>
qq_num: data.qq_num,
},

transaction,
lock: transaction.LOCK.UPDATE,
...transaction_opts,
});

if (sameQQNumMember) {
throw new this.ctx.app.WarningError('无法更新,所修改的QQ号已被占用', 409);
}
}

const old_avatar_src = member.avatar_src;

this.editableProperty.forEach(key => {
if (data.hasOwnProperty(key)) {
member[key] = data[key];
}
});

return await member.save({ transaction });
// await app.serviceClasses.image.removeSrc(member.avatar_src);
if (member.avatar_src !== old_avatar_src) {
await app.serviceClasses.image.getImageDimensions(member.avatar_src);

const result = await member.save(transaction_opts);

await app.serviceClasses.image.removeSrc(old_avatar_src);

return result;
} else {
return await member.save(transaction_opts);
}
});
}

Expand Down
56 changes: 18 additions & 38 deletions server/app/service/photo.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
'use strict';

const fs = require('fs');
const sharp = require('sharp');
const CommonService = require('./common');

async function getImageSize(filePath) {
return getNormalSize(await sharp(filePath).metadata());
}
// ref: https://sharp.pixelplumbing.com/api-input#metadata
function getNormalSize({ width, height, orientation }) {
return (orientation || 0) >= 5
? { orientation, width: height, height: width }
: { orientation, width, height };
}

module.exports = app =>
class PhotoService extends CommonService {
get OBJECT_NAME() {
Expand All @@ -24,18 +12,6 @@ module.exports = app =>
return this.app.model.Photo;
}

async getImageDimensions(src) {
const srcPath = app.serviceClasses.image.toSrcSavePath(src);

if (!fs.existsSync(srcPath)) {
throw new this.app.WarningError('src不存在', 404);
}

const { width, height } = await getImageSize(srcPath);

return { width, height };
}

async getMemberSubmissionByQQNum(gallery_id, qq_num) {
const galleryP = this.service.gallery.findById(parseInt(gallery_id));
const memberP = this.service.member.findOneByOptions({
Expand Down Expand Up @@ -95,6 +71,7 @@ module.exports = app =>
);
update_data.src = created_file.src;
}

return this.edit(photo.id, {
...update_data,
});
Expand Down Expand Up @@ -141,7 +118,7 @@ module.exports = app =>
if (exists) {
throw new this.ctx.app.WarningError('该成员已经投稿过了,不能重复投稿', 409);
} else {
const { width, height } = await this.getImageDimensions(src);
const { width, height } = await app.serviceClasses.image.getImageDimensions(src);
return await this.Model.create({
member_id: parseInt(member_id),
gallery_id: parseInt(gallery_id),
Expand All @@ -160,36 +137,39 @@ module.exports = app =>

async edit(id, data) {
return this.app.model.transaction(async transaction => {
const photo = await this.findById(id, { transaction, lock: transaction.LOCK.UPDATE });
const transaction_opts = { transaction, lock: transaction.LOCK.UPDATE };
const photo = await this.findById(id, transaction_opts);

if (data.hasOwnProperty('member_id')) {
// 检查成员是否存在
await this.service.member.detectExistsById(data.member_id, {
transaction,
lock: transaction.LOCK.UPDATE,
});
await this.service.member.detectExistsById(data.member_id, transaction_opts);
}

if (data.hasOwnProperty('gallery_id')) {
// 检查相册是否存在
await this.service.gallery.detectExistsById(data.gallery_id, {
transaction,
lock: transaction.LOCK.UPDATE,
});
await this.service.gallery.detectExistsById(data.gallery_id, transaction_opts);
}

const old_src = photo.src;

this.editableProperty.forEach(key => {
if (data.hasOwnProperty(key)) {
photo[key] = data[key];
}
});

if (data.src) {
const { width, height } = await this.getImageDimensions(data.src);
if (photo.src !== old_src) {
const { width, height } = await app.serviceClasses.image.getImageDimensions(photo.src);
Object.assign(photo, { width, height });
}

return await photo.save({ transaction });
const result = await photo.save(transaction_opts);

await app.serviceClasses.image.removeSrc(old_src);

return result;
} else {
return await photo.save(transaction_opts);
}
});
}

Expand Down
2 changes: 2 additions & 0 deletions server/config/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ module.exports = appInfo => {
const imagePath = path.join(config.static.prefix, 'src');
const imageThumbPath = path.join(config.static.prefix, 'thumb');

const convert_formats = Object.freeze('jpg avif webp'.split(' '));
const popularly_formats = Object.freeze([ 'jpg', 'jpeg', 'png', 'gif' ]);
const next_gen_formats = Object.freeze([ 'avif', 'webp' ]);
const supported_formats = Object.freeze([ ...popularly_formats, ...next_gen_formats ]);
Expand Down Expand Up @@ -79,6 +80,7 @@ module.exports = appInfo => {
popularly_formats,
next_gen_formats,
supported_formats,
convert_formats,

imageThumbSavePath,
imageSavePath,
Expand Down
58 changes: 57 additions & 1 deletion server/test/admin-member.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const assert = require('assert');
const mock = require('egg-mock');
const { getToken, createMember, getMemberById, removeMemberById, createApp, createPhoto, commonCreateGallery, getPhotoById, removePhotoById, constructEnvironment, prepareData, submitVote } = require('./common');
const path = require('path')
const fileExists = require('../app/utils/file-exists');
const { getToken, createMember, getMemberById, removeMemberById, createApp, createPhoto, commonCreateGallery, getPhotoById, removePhotoById, constructEnvironment, prepareData, submitVote, adminUdateMember, default_upload_image_path, uploadImage } = require('./common');

describe('controller/admin/member', () => {
let app
Expand Down Expand Up @@ -68,6 +70,60 @@ describe('controller/admin/member', () => {
await removeMemberById(token, app, memberA.id, 200)
})

it('should successfully update a member', async () => {
const old_member = await createMember(token, app, { qq_num: 2120 })
const member_id = old_member.id

{
const updated_member = await adminUdateMember(token, app, member_id, {
name: 'ion',
avatar_src: old_member.avatar_src
}, 200)
assert(updated_member.name !== old_member.name)
assert(updated_member.name === 'ion')
assert(updated_member.avatar_src === old_member.avatar_src)

for (const format of app.config.convert_formats) {
const exists = await fileExists(
path.join(
app.config.imageSavePath,
`${path.parse(updated_member.avatar_src).name}.${format}`
)
)
assert(exists === true)
}
}

{
const img = await uploadImage(token, app, default_upload_image_path)
const updated_member = await adminUdateMember(token, app, member_id, {
name: 'ion',
avatar_src: img.src
}, 200)
assert(updated_member.avatar_src !== old_member.avatar_src)

for (const format of app.config.convert_formats) {
const exists = await fileExists(
path.join(
app.config.imageSavePath,
`${path.parse(updated_member.avatar_src).name}.${format}`
)
)
assert(exists === true)
}

for (const format of app.config.convert_formats) {
const exists = await fileExists(
path.join(
app.config.imageSavePath,
`${path.parse(old_member.avatar_src).name}.${format}`
)
)
assert(exists === false)
}
}
})

it('should prevent remove member that member has a submission', async () => {
const member = await createMember(token, app, { name: 'get member', qq_num: 1141 })

Expand Down
Loading

0 comments on commit 7a64639

Please sign in to comment.