feat: 上传全部影像
This commit is contained in:
parent
43eb5e70af
commit
976d687b1b
|
@ -1,4 +1,4 @@
|
||||||
import { Apis } from "@@/infra/api";
|
import { Apis, ArchiveTaskCreateDto } from "@@/infra/api";
|
||||||
import { User } from "./entities/User";
|
import { User } from "./entities/User";
|
||||||
|
|
||||||
export class UserRepository {
|
export class UserRepository {
|
||||||
|
@ -20,4 +20,8 @@ export class UserRepository {
|
||||||
async getDmpAnnotators() {
|
async getDmpAnnotators() {
|
||||||
return await Apis.getDmpAnnotators();
|
return await Apis.getDmpAnnotators();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createArchiveTask(p: ArchiveTaskCreateDto) {
|
||||||
|
return await Apis.createArchiveTask(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,8 @@ export class UserService {
|
||||||
/**
|
/**
|
||||||
* 分配标注序列
|
* 分配标注序列
|
||||||
*/
|
*/
|
||||||
async assignLabelDicom(user: User, study: Study[]) {
|
async createArchiveTask(user: User, study: Study[]) {
|
||||||
console.log(user, study);
|
console.log(user, study);
|
||||||
|
return await this.userRepository.createArchiveTask({ user, study });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { User } from "@@/domain/User/entities/User";
|
import { User } from "@@/domain/User/entities/User";
|
||||||
import { Request } from "./Request";
|
import { Request } from "./Request";
|
||||||
|
import { Study } from "@/modules/Admin/Dicom/Upload/DicomUploader/util";
|
||||||
|
|
||||||
const PREFIX = "/api/dmp";
|
const PREFIX = "/api/dmp";
|
||||||
const PREFIX_CERT = "/cert";
|
const PREFIX_CERT = "/cert";
|
||||||
|
@ -25,6 +26,11 @@ export type ExistInPacsDTO = {
|
||||||
SeriesInstanceUID: string;
|
SeriesInstanceUID: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ArchiveTaskCreateDto = {
|
||||||
|
user: User;
|
||||||
|
study: Study[];
|
||||||
|
};
|
||||||
|
|
||||||
export const Apis = {
|
export const Apis = {
|
||||||
/**
|
/**
|
||||||
* 用户登录
|
* 用户登录
|
||||||
|
@ -59,5 +65,11 @@ export const Apis = {
|
||||||
* 获取标注人员信息
|
* 获取标注人员信息
|
||||||
*/
|
*/
|
||||||
getDmpAnnotators: (): ResponseType =>
|
getDmpAnnotators: (): ResponseType =>
|
||||||
Request.get(PREFIX + `/user/find/annotator`),
|
Request.get(PREFIX + `/admin/find/annotator`),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建标注任务
|
||||||
|
*/
|
||||||
|
createArchiveTask: (p: ArchiveTaskCreateDto): ResponseType =>
|
||||||
|
Request.post(PREFIX + "/admin/createArchiveTask", p),
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,14 +12,11 @@ export const columnsForStudy: TableColumnsType<Study> = [
|
||||||
{
|
{
|
||||||
title: "切片数量",
|
title: "切片数量",
|
||||||
key: "seriesNumber",
|
key: "seriesNumber",
|
||||||
render: (_: any, record: Study) => {
|
render: (_: any, record: Study) => (
|
||||||
console.log(record);
|
|
||||||
return (
|
|
||||||
<span>
|
<span>
|
||||||
{record.subs.map((s) => s.subs.length).reduce((p, n) => p + n)}
|
{record.subs.map((s) => s.subs.length).reduce((p, n) => p + n)}
|
||||||
</span>
|
</span>
|
||||||
);
|
),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{ title: "年龄", dataIndex: "patientAge", key: "patientAge" },
|
{ title: "年龄", dataIndex: "patientAge", key: "patientAge" },
|
||||||
{ title: "性别", dataIndex: "patientSex", key: "patientSex" },
|
{ title: "性别", dataIndex: "patientSex", key: "patientSex" },
|
||||||
|
|
|
@ -42,7 +42,6 @@ export const useDicomUploader = () => {
|
||||||
//TODO: 耗时
|
//TODO: 耗时
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const studys = await parseDcmFiles(dcmFiles);
|
const studys = await parseDcmFiles(dcmFiles);
|
||||||
console.log("studys", studys);
|
|
||||||
setStudys(studys);
|
setStudys(studys);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -67,9 +67,9 @@ export const parseDcmFiles = async (dcmFiles: File[]): Promise<Study[]> => {
|
||||||
};
|
};
|
||||||
studys.push(study);
|
studys.push(study);
|
||||||
}
|
}
|
||||||
// 序列级别
|
// 序列级别,可能会存在某个序列被复制一份的情况,也会计算在切片数量中
|
||||||
let series = study.subs.find(
|
let series = study.subs.find(
|
||||||
(s) => s.SeriesInstanceUID === SeriesInstanceUID && s.
|
(s) => s.SeriesInstanceUID === SeriesInstanceUID
|
||||||
);
|
);
|
||||||
if (!series) {
|
if (!series) {
|
||||||
series = {
|
series = {
|
||||||
|
|
|
@ -134,7 +134,7 @@ export const DicomUpload = (props: DicomUploadProps) => {
|
||||||
*/
|
*/
|
||||||
const onAssignConfirm = () => {
|
const onAssignConfirm = () => {
|
||||||
if (!selectAnnotator?.id) return;
|
if (!selectAnnotator?.id) return;
|
||||||
userDomainService.assignLabelDicom(selectAnnotator, selectRows);
|
userDomainService.createArchiveTask(selectAnnotator, selectRows);
|
||||||
setSelectAnnotator(undefined);
|
setSelectAnnotator(undefined);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
33
apps/services/dicom/src/retrieval/entity/archiveTask.ts
Normal file
33
apps/services/dicom/src/retrieval/entity/archiveTask.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Study } from './study';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class ArchiveTask {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
annotatorId: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
StudyInstanceUID;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
SeriesInstanceUID: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ type: 'timestamp' })
|
||||||
|
createTime: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ type: 'timestamp' })
|
||||||
|
updateTime: Date;
|
||||||
|
|
||||||
|
@OneToMany(() => Study, (study) => study.archiveTask)
|
||||||
|
studies: Study[];
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Study } from './study';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Series {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
SeriesInstanceUID: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ type: 'timestamp' })
|
||||||
|
createTime: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ type: 'timestamp' })
|
||||||
|
updateTime: Date;
|
||||||
|
|
||||||
|
@ManyToOne(() => Study, (study) => study.series)
|
||||||
|
@JoinColumn({
|
||||||
|
name: 'StudyInstanceUID',
|
||||||
|
referencedColumnName: 'StudyInstanceUID',
|
||||||
|
}) // 自定义连接列
|
||||||
|
study: Study;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { Series } from './series';
|
||||||
|
import { ArchiveTask } from './archiveTask';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Study {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
StudyInstanceUID: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
PatientID: string | number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
PatientName: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
PatientSex: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
PatientAge: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ type: 'timestamp' })
|
||||||
|
createTime: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ type: 'timestamp' })
|
||||||
|
updateTime: Date;
|
||||||
|
|
||||||
|
@OneToMany(() => Series, (series) => series.study)
|
||||||
|
series: Series[];
|
||||||
|
|
||||||
|
@ManyToOne(() => ArchiveTask, (archiveTask) => archiveTask.studies)
|
||||||
|
@JoinColumn({ name: 'ArchiveTaskId', referencedColumnName: 'id' }) // 自定义连接列
|
||||||
|
archiveTask: ArchiveTask;
|
||||||
|
}
|
|
@ -1,4 +1,17 @@
|
||||||
import { Controller } from '@nestjs/common';
|
import { Controller } from '@nestjs/common';
|
||||||
|
import { EventPattern } from '@nestjs/microservices';
|
||||||
|
import { RetrievalService } from './retrieval.service';
|
||||||
|
|
||||||
@Controller('retrieval')
|
@Controller()
|
||||||
export class RetrievalController {}
|
export class RetrievalController {
|
||||||
|
constructor(private readonly retrievalSerivce: RetrievalService) {}
|
||||||
|
@EventPattern({ cmd: 'dicom.retrieval.archivetask.create' })
|
||||||
|
async createArchiveTask(payload) {
|
||||||
|
const { user, study } = payload;
|
||||||
|
const { id: annotatorId } = user;
|
||||||
|
return await this.retrievalSerivce.createArchiveTask({
|
||||||
|
annotatorId,
|
||||||
|
study,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { Module } from '@nestjs/common';
|
||||||
import { RetrievalController } from './retrieval.controller';
|
import { RetrievalController } from './retrieval.controller';
|
||||||
import { RetrievalService } from './retrieval.service';
|
import { RetrievalService } from './retrieval.service';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ArchiveTask } from './entity/archiveTask';
|
||||||
|
import { Series } from './entity/series';
|
||||||
|
import { Study } from './entity/study';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -16,7 +19,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
timezone: 'Asia/Shanghai', // 这里设置了时区
|
timezone: 'Asia/Shanghai', // 这里设置了时区
|
||||||
}),
|
}),
|
||||||
TypeOrmModule.forFeature([]),
|
TypeOrmModule.forFeature([ArchiveTask, Series, Study]),
|
||||||
],
|
],
|
||||||
controllers: [RetrievalController],
|
controllers: [RetrievalController],
|
||||||
providers: [RetrievalService],
|
providers: [RetrievalService],
|
||||||
|
|
|
@ -1,4 +1,16 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { ArchiveTask } from './entity/archiveTask';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RetrievalService {}
|
export class RetrievalService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ArchiveTask)
|
||||||
|
private readonly archiveTaskRepository: Repository<ArchiveTask>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createArchiveTask({ annotatorId, study }) {
|
||||||
|
return this.archiveTaskRepository.save({ annotatorId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
8
apps/services/dmp/archive/.env.dev
Normal file
8
apps/services/dmp/archive/.env.dev
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
PORT=31541
|
||||||
|
# nacos中注册服务的名称
|
||||||
|
NACOS_SERVICE_NAME=archive
|
||||||
|
NACOS_ADDR=127.0.0.1:8848
|
||||||
|
NACOS_NAMESPACE=56a3b295-f319-4ced-82b5-0df2e98cc541
|
||||||
|
# nacos配置中心
|
||||||
|
NACOS_DATAID='test'
|
||||||
|
NACOS_GROUP='DEFAULT_GROUP'
|
25
apps/services/dmp/archive/.eslintrc.js
Normal file
25
apps/services/dmp/archive/.eslintrc.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
};
|
35
apps/services/dmp/archive/.gitignore
vendored
Normal file
35
apps/services/dmp/archive/.gitignore
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
/coverage
|
||||||
|
/.nyc_output
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
/.idea
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# IDE - VSCode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
4
apps/services/dmp/archive/.prettierrc
Normal file
4
apps/services/dmp/archive/.prettierrc
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
73
apps/services/dmp/archive/README.md
Normal file
73
apps/services/dmp/archive/README.md
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<p align="center">
|
||||||
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
|
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||||
|
|
||||||
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||||
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||||
|
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||||
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||||
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||||
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||||
|
</p>
|
||||||
|
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
|
||||||
|
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the app
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# development
|
||||||
|
$ pnpm run start
|
||||||
|
|
||||||
|
# watch mode
|
||||||
|
$ pnpm run start:dev
|
||||||
|
|
||||||
|
# production mode
|
||||||
|
$ pnpm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
$ pnpm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
$ pnpm run test:e2e
|
||||||
|
|
||||||
|
# test coverage
|
||||||
|
$ pnpm run test:cov
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||||
|
|
||||||
|
## Stay in touch
|
||||||
|
|
||||||
|
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
||||||
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nest is [MIT licensed](LICENSE).
|
8
apps/services/dmp/archive/nest-cli.json
Normal file
8
apps/services/dmp/archive/nest-cli.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
|
}
|
56
apps/services/dmp/archive/package.json
Normal file
56
apps/services/dmp/archive/package.json
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"name": "@tavi/dmp-archive",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "cross-env NODE_ENV=dev nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "cross-env NODE_ENV=dev node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/common": "10.1.0",
|
||||||
|
"@nestjs/core": "10.1.0",
|
||||||
|
"@nestjs/platform-express": "10.1.0",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"@nestjs/config": "3.0.0",
|
||||||
|
"rxjs": "7.8.1",
|
||||||
|
"nats": "2.15.1",
|
||||||
|
"@nestjs/microservices": "10.0.5",
|
||||||
|
"nacos": "2.5.1",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
|
"cookie-parser": "1.4.6",
|
||||||
|
"@casl/ability": "6.5.0",
|
||||||
|
"typeorm": "0.3.17",
|
||||||
|
"@nestjs/typeorm": "10.0.0",
|
||||||
|
"bcrypt": "5.1.0",
|
||||||
|
"minimatch": "9.0.3",
|
||||||
|
"dayjs": "1.11.9",
|
||||||
|
"flatted": "3.2.7",
|
||||||
|
"crypto-js": "4.1.1",
|
||||||
|
"@tavi/util": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^9.0.0",
|
||||||
|
"@nestjs/schematics": "^9.0.0",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/node": "18.16.12",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
|
"eslint": "^8.0.1",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"prettier": "^2.3.2",
|
||||||
|
"source-map-support": "^0.5.20",
|
||||||
|
"ts-loader": "^9.2.3",
|
||||||
|
"ts-node": "^10.0.0",
|
||||||
|
"tsconfig-paths": "4.2.0",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
15
apps/services/dmp/archive/src/app.controller.ts
Normal file
15
apps/services/dmp/archive/src/app.controller.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { Controller } from '@nestjs/common';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
import { EventPattern } from '@nestjs/microservices';
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class AppController {
|
||||||
|
constructor(private readonly appService: AppService) {}
|
||||||
|
|
||||||
|
@EventPattern({ cmd: 'archive.task.create' })
|
||||||
|
async createArchiveTask(payload) {
|
||||||
|
const { annotatorId, study } = payload;
|
||||||
|
console.log(study);
|
||||||
|
return 123;
|
||||||
|
}
|
||||||
|
}
|
33
apps/services/dmp/archive/src/app.module.ts
Normal file
33
apps/services/dmp/archive/src/app.module.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AppController } from './app.controller';
|
||||||
|
import { AppService } from './app.service';
|
||||||
|
import { NacosModule } from './nacos/nacos.module';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ArchiveTask } from './entity/archiveTask';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
envFilePath: `.env.${process.env.NODE_ENV}`,
|
||||||
|
}),
|
||||||
|
NacosModule,
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
type: 'mysql',
|
||||||
|
host: 'localhost',
|
||||||
|
port: 3306,
|
||||||
|
username: 'root',
|
||||||
|
password: 'root',
|
||||||
|
database: 'dicom',
|
||||||
|
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||||
|
synchronize: true,
|
||||||
|
timezone: 'Asia/Shanghai', // 这里设置了时区
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forFeature([ArchiveTask]),
|
||||||
|
],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
exports: [],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
4
apps/services/dmp/archive/src/app.service.ts
Normal file
4
apps/services/dmp/archive/src/app.service.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppService {}
|
21
apps/services/dmp/archive/src/app.util.ts
Normal file
21
apps/services/dmp/archive/src/app.util.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as CryptoJS from 'crypto-js';
|
||||||
|
import { parse } from 'flatted';
|
||||||
|
|
||||||
|
export class SymmetricCrypto {
|
||||||
|
private key: string;
|
||||||
|
|
||||||
|
constructor(key: string) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt(encryptedData: string): object | null {
|
||||||
|
try {
|
||||||
|
const bytes = CryptoJS.AES.decrypt(encryptedData, this.key);
|
||||||
|
const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
|
||||||
|
return parse(decryptedData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Decryption failed:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,9 +5,8 @@ import {
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Task {
|
export class ArchiveTask {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
19
apps/services/dmp/archive/src/main.ts
Normal file
19
apps/services/dmp/archive/src/main.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
|
||||||
|
AppModule,
|
||||||
|
{
|
||||||
|
transport: Transport.NATS,
|
||||||
|
options: {
|
||||||
|
servers: ['nats://localhost:4222'], // 可以指定链接到多个nats的消息队列
|
||||||
|
maxReconnectAttempts: 5,
|
||||||
|
reconnectTimeWait: 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await app.listen();
|
||||||
|
}
|
||||||
|
bootstrap();
|
8
apps/services/dmp/archive/src/nacos/nacos.module.ts
Normal file
8
apps/services/dmp/archive/src/nacos/nacos.module.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { NacosService } from './nacos.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [NacosService],
|
||||||
|
exports: [NacosService],
|
||||||
|
})
|
||||||
|
export class NacosModule {}
|
108
apps/services/dmp/archive/src/nacos/nacos.service.ts
Normal file
108
apps/services/dmp/archive/src/nacos/nacos.service.ts
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// nacos.service.ts
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
OnApplicationBootstrap,
|
||||||
|
OnApplicationShutdown,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { NacosConfigClient, NacosNamingClient } from 'nacos'; // ts
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NacosService
|
||||||
|
implements OnApplicationBootstrap, OnApplicationShutdown
|
||||||
|
{
|
||||||
|
private nacosNamingClient: NacosNamingClient;
|
||||||
|
private nacosConfigClient: NacosConfigClient;
|
||||||
|
serviceName: string;
|
||||||
|
instance: { ip: string; port: number };
|
||||||
|
group: string;
|
||||||
|
dataId: string;
|
||||||
|
constructor(private configService: ConfigService) {
|
||||||
|
this.nacosNamingClient = new NacosNamingClient({
|
||||||
|
logger: console,
|
||||||
|
serverList: configService.get('NACOS_ADDR'),
|
||||||
|
namespace: configService.get('NACOS_NAMESPACE'),
|
||||||
|
});
|
||||||
|
this.nacosConfigClient = new NacosConfigClient({
|
||||||
|
namespace: configService.get('NACOS_NAMESPACE'),
|
||||||
|
serverAddr: configService.get('NACOS_ADDR'),
|
||||||
|
});
|
||||||
|
this.serviceName = configService.get('NACOS_SERVICE_NAME');
|
||||||
|
this.dataId = configService.get('NACOS_DATAID');
|
||||||
|
this.group = configService.get('NACOS_GROUP');
|
||||||
|
this.instance = {
|
||||||
|
ip: this.getServerIP(),
|
||||||
|
port: configService.get('PORT'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nestjs应用被关闭前
|
||||||
|
* @param {string} signal 'SIGTERM' | 'SIGINT' | 'SIGHUP' | 'SIGBREAK'
|
||||||
|
*/
|
||||||
|
onApplicationShutdown(signal?: string) {
|
||||||
|
if (signal) {
|
||||||
|
const { serviceName, instance, group } = this;
|
||||||
|
this.nacosNamingClient.deregisterInstance(serviceName, instance, group);
|
||||||
|
this.nacosConfigClient.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用完全启动&微服务也被成功启动
|
||||||
|
*/
|
||||||
|
onApplicationBootstrap() {
|
||||||
|
const { serviceName, instance } = this;
|
||||||
|
this.nacosNamingClient.registerInstance(serviceName, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 先于 onApplicationBootstrap
|
||||||
|
*/
|
||||||
|
async onModuleInit() {
|
||||||
|
this.nacosNamingClient.ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从nacos获取最新的配置信息
|
||||||
|
*/
|
||||||
|
async getConfig() {
|
||||||
|
const { dataId, group } = this;
|
||||||
|
const configFromNacos = await this.nacosConfigClient.getConfig(
|
||||||
|
dataId,
|
||||||
|
group,
|
||||||
|
);
|
||||||
|
return configFromNacos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅配置中心,当远程修改nacos的配置时,触发
|
||||||
|
*/
|
||||||
|
async subscribeConfiguration() {
|
||||||
|
const { dataId, group } = this;
|
||||||
|
this.nacosConfigClient.subscribe(
|
||||||
|
{
|
||||||
|
dataId,
|
||||||
|
group,
|
||||||
|
},
|
||||||
|
(content) => console.log('content', content),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getServerIP(): string {
|
||||||
|
const networkInterfaces = os.networkInterfaces();
|
||||||
|
for (const name of Object.keys(networkInterfaces)) {
|
||||||
|
for (const iface of networkInterfaces[name]) {
|
||||||
|
// 跳过IPv6和内部地址
|
||||||
|
if ('IPv4' !== iface.family || iface.internal !== false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一个找到的IPv4地址
|
||||||
|
return iface.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 'localhost'; // 如果找不到外部IPv4地址,返回localhost
|
||||||
|
}
|
||||||
|
}
|
4
apps/services/dmp/archive/tsconfig.build.json
Normal file
4
apps/services/dmp/archive/tsconfig.build.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
|
}
|
21
apps/services/dmp/archive/tsconfig.json
Normal file
21
apps/services/dmp/archive/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "es2017",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"incremental": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"strictBindCallApply": false,
|
||||||
|
"forceConsistentCasingInFileNames": false,
|
||||||
|
"noFallthroughCasesInSwitch": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import { Controller, Get, Inject, Post } from '@nestjs/common';
|
import { Body, Controller, Get, Inject, Post } from '@nestjs/common';
|
||||||
import { ClientProxy } from '@nestjs/microservices';
|
import { ClientProxy } from '@nestjs/microservices';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
@Controller('user')
|
@Controller('admin')
|
||||||
export class UserController {
|
export class AdminController {
|
||||||
constructor(@Inject('Client') private readonly client: ClientProxy) {}
|
constructor(@Inject('Client') private readonly client: ClientProxy) {}
|
||||||
|
|
||||||
@Get('find/annotator')
|
@Get('find/annotator')
|
||||||
|
@ -14,10 +14,12 @@ export class UserController {
|
||||||
return { data, code: 0 };
|
return { data, code: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("assign")
|
@Post('createArchiveTask')
|
||||||
async assign(){
|
async createArchiveTask(@Body() body) {
|
||||||
|
const { user, study } = body;
|
||||||
|
const { annotatorId } = user;
|
||||||
const { data } = await firstValueFrom(
|
const { data } = await firstValueFrom(
|
||||||
this.client.send({ cmd: 'dicom.user.find.annotator' }, {}),
|
this.client.send({ cmd: 'archive.task.create' }, { annotatorId, study }),
|
||||||
);
|
);
|
||||||
return { data, code: 0 };
|
return { data, code: 0 };
|
||||||
}
|
}
|
0
apps/services/dmp/gateway/src/admin/admin.dto.ts
Normal file
0
apps/services/dmp/gateway/src/admin/admin.dto.ts
Normal file
|
@ -1,5 +1,5 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { UserController } from './user.controller';
|
import { AdminController } from './admin.controller';
|
||||||
import { ClientsModule, Transport } from '@nestjs/microservices';
|
import { ClientsModule, Transport } from '@nestjs/microservices';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
@ -16,7 +16,7 @@ import { ClientsModule, Transport } from '@nestjs/microservices';
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
controllers: [UserController],
|
controllers: [AdminController],
|
||||||
providers: [],
|
providers: [],
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class AdminModule {}
|
|
@ -7,7 +7,7 @@ import { APP_FILTER, APP_GUARD } from '@nestjs/core';
|
||||||
import { AuthGuard } from './guard/auth.guard';
|
import { AuthGuard } from './guard/auth.guard';
|
||||||
import { ForbiddenExceptionFilter } from './filter/forbid.filter';
|
import { ForbiddenExceptionFilter } from './filter/forbid.filter';
|
||||||
import { AuthController } from './auth/auth.controller';
|
import { AuthController } from './auth/auth.controller';
|
||||||
import { UserModule } from './user/user.module';
|
import { AdminModule } from './admin/admin.module';
|
||||||
import * as cookieParser from 'cookie-parser';
|
import * as cookieParser from 'cookie-parser';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
@ -27,7 +27,7 @@ import * as cookieParser from 'cookie-parser';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
UserModule,
|
AdminModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, AuthController],
|
controllers: [AppController, AuthController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"dev:logger": "pnpm run --filter @tavi/logger start:dev",
|
"dev:logger": "pnpm run --filter @tavi/logger start:dev",
|
||||||
"dev:dmp-web": "pnpm run --filter @tavi/dmp-web dev",
|
"dev:dmp-web": "pnpm run --filter @tavi/dmp-web dev",
|
||||||
"dev:dmp-gateway": "pnpm run --filter @tavi/dmp-gateway start:dev",
|
"dev:dmp-gateway": "pnpm run --filter @tavi/dmp-gateway start:dev",
|
||||||
|
"dev:dmp-archive": "pnpm run --filter @tavi/dmp-archive start:dev",
|
||||||
"dev:aorta": "pnpm run --filter @tavi/aorta dev",
|
"dev:aorta": "pnpm run --filter @tavi/aorta dev",
|
||||||
"dev:aorta-gateway": "pnpm run --filter @tavi/aorta-gateway start:dev",
|
"dev:aorta-gateway": "pnpm run --filter @tavi/aorta-gateway start:dev",
|
||||||
"build:aorta": "pnpm run --filter @tavi/aorta build",
|
"build:aorta": "pnpm run --filter @tavi/aorta build",
|
||||||
|
|
895
pnpm-lock.yaml
895
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user