Commit 0a8ef507 authored by nanahira's avatar nanahira

add lock to image resize

parent ce97de60
Pipeline #14449 passed with stages
in 5 minutes and 7 seconds
This diff is collapsed.
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@nestjs-modules/ioredis": "^1.0.1",
"@nestjs/axios": "^0.0.4", "@nestjs/axios": "^0.0.4",
"@nestjs/common": "^8.0.0", "@nestjs/common": "^8.0.0",
"@nestjs/config": "^1.1.6", "@nestjs/config": "^1.1.6",
...@@ -31,9 +32,11 @@ ...@@ -31,9 +32,11 @@
"@xzeldon/imagemagick-native": "^1.9.5", "@xzeldon/imagemagick-native": "^1.9.5",
"class-transformer": "^0.4.0", "class-transformer": "^0.4.0",
"class-validator": "^0.13.2", "class-validator": "^0.13.2",
"ioredis": "^4.28.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"pg": "^8.7.1", "pg": "^8.7.1",
"pg-native": "^3.0.0", "pg-native": "^3.0.0",
"redlock": "^5.0.0-beta.2",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^7.2.0", "rxjs": "^7.2.0",
......
...@@ -7,6 +7,8 @@ import { HttpModule } from '@nestjs/axios'; ...@@ -7,6 +7,8 @@ import { HttpModule } from '@nestjs/axios';
import { AvatarApiService } from './avatar-api/avatar-api.service'; import { AvatarApiService } from './avatar-api/avatar-api.service';
import { ImageService } from './image/image.service'; import { ImageService } from './image/image.service';
import { Avatar } from './entities/Avatar.entity'; import { Avatar } from './entities/Avatar.entity';
import { LockService } from './lock/lock.service';
import { RedisModule } from '@nestjs-modules/ioredis';
@Module({ @Module({
imports: [ imports: [
...@@ -31,8 +33,16 @@ import { Avatar } from './entities/Avatar.entity'; ...@@ -31,8 +33,16 @@ import { Avatar } from './entities/Avatar.entity';
}; };
}, },
}), }),
RedisModule.forRootAsync({
inject: [ConfigService],
useFactory: async (config: ConfigService) => ({
config: {
url: config.get('REDIS_URL'),
},
}),
}),
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService, AvatarApiService, ImageService], providers: [AppService, AvatarApiService, ImageService, LockService],
}) })
export class AppModule {} export class AppModule {}
...@@ -4,6 +4,7 @@ import { Injectable, ConsoleLogger } from '@nestjs/common'; ...@@ -4,6 +4,7 @@ import { Injectable, ConsoleLogger } from '@nestjs/common';
import { ImageService } from './image/image.service'; import { ImageService } from './image/image.service';
import { AvatarApiService } from './avatar-api/avatar-api.service'; import { AvatarApiService } from './avatar-api/avatar-api.service';
import { Avatar } from './entities/Avatar.entity'; import { Avatar } from './entities/Avatar.entity';
import { LockService } from './lock/lock.service';
@Injectable() @Injectable()
export class AppService extends ConsoleLogger { export class AppService extends ConsoleLogger {
...@@ -12,6 +13,7 @@ export class AppService extends ConsoleLogger { ...@@ -12,6 +13,7 @@ export class AppService extends ConsoleLogger {
private db: Connection, private db: Connection,
private image: ImageService, private image: ImageService,
private avatarApi: AvatarApiService, private avatarApi: AvatarApiService,
private lockService: LockService,
) { ) {
super('app'); super('app');
} }
...@@ -37,30 +39,39 @@ export class AppService extends ConsoleLogger { ...@@ -37,30 +39,39 @@ export class AppService extends ConsoleLogger {
return existingAvatar.toBuffer(); return existingAvatar.toBuffer();
} }
this.log(`Resizing ${url} to ${size}`); this.log(`Resizing ${url} to ${size}`);
let originalAvatarBuffer: Buffer; return this.lockService.using(
const originalAvatar = await this.db.getRepository(Avatar).findOne({ [`avatar_${url}_${size}`],
where: { 10000,
url, async () => {
size: 0, let originalAvatarBuffer: Buffer;
const originalAvatar = await this.db.getRepository(Avatar).findOne({
where: {
url,
size: 0,
},
select: ['content'],
});
if (originalAvatar) {
originalAvatarBuffer = originalAvatar.toBuffer();
} else {
originalAvatarBuffer = await this.avatarApi.downloadAvatar(url);
try {
await this.saveAvatar(url, 0, originalAvatarBuffer);
} catch (e) {
this.error(`Fail to save original avatar: ${e.message}`);
}
}
const avatarBuffer = await this.image.resize(
originalAvatarBuffer,
size,
);
try {
await this.saveAvatar(url, size, avatarBuffer);
} catch (e) {
this.error(`Fail to save avatar: ${e.message}`);
}
return avatarBuffer;
}, },
select: ['content'], );
});
if (originalAvatar) {
originalAvatarBuffer = originalAvatar.toBuffer();
} else {
originalAvatarBuffer = await this.avatarApi.downloadAvatar(url);
try {
await this.saveAvatar(url, 0, originalAvatarBuffer);
} catch (e) {
this.error(`Fail to save original avatar: ${e.message}`);
}
}
const avatarBuffer = await this.image.resize(originalAvatarBuffer, size);
try {
await this.saveAvatar(url, size, avatarBuffer);
} catch (e) {
this.error(`Fail to save avatar: ${e.message}`);
}
return avatarBuffer;
} }
} }
import { Test, TestingModule } from '@nestjs/testing';
import { LockService } from './lock.service';
describe('LockService', () => {
let service: LockService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [LockService],
}).compile();
service = module.get<LockService>(LockService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { InjectRedis, Redis } from '@nestjs-modules/ioredis';
import { Injectable } from '@nestjs/common';
import Redlock from 'redlock';
@Injectable()
export class LockService extends Redlock {
constructor(@InjectRedis() private readonly redis: Redis) {
super([redis]);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment