feat: 添加任务重新排序功能并增强任务实体属性。
This commit is contained in:
parent
1fe53ab9de
commit
7274c58329
|
|
@ -29,4 +29,4 @@ import { AuthController } from './auth.controller';
|
|||
},
|
||||
],
|
||||
})
|
||||
export class AuthModule { }
|
||||
export class AuthModule {}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
|
|||
const email = emails?.[0]?.value || null;
|
||||
|
||||
// 检查 GitHub 用户是否已存在
|
||||
let user = await this.userService.findUserByProvider('github', id.toString());
|
||||
let user = await this.userService.findUserByProvider(
|
||||
'github',
|
||||
id.toString(),
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
// 如果用户不存在,创建新的 OAuth 用户
|
||||
|
|
@ -28,7 +31,7 @@ export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
|
|||
'github',
|
||||
id.toString(),
|
||||
username,
|
||||
email || `${username}@github.local`
|
||||
email || `${username}@github.local`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,17 +20,19 @@ export class DeepseekService {
|
|||
{
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: "你是一个虚拟货币分析师,擅长分析虚拟货币的统计文件,并且给用户提供建议,应该做空还是做多以及具体的杠杆倍数,还有应该在什么点位入场以及具体的止盈止损点位"
|
||||
role: 'system',
|
||||
content:
|
||||
'你是一个虚拟货币分析师,擅长分析虚拟货币的统计文件,并且给用户提供建议,应该做空还是做多以及具体的杠杆倍数,还有应该在什么点位入场以及具体的止盈止损点位',
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "帮我分析下这份虚拟货币的统计文件,并且给我提供建议,应该做空还是做多以及具体的杠杆倍数,还有应该在什么点位入场以及具体的止盈止损点位"
|
||||
role: 'user',
|
||||
content:
|
||||
'帮我分析下这份虚拟货币的统计文件,并且给我提供建议,应该做空还是做多以及具体的杠杆倍数,还有应该在什么点位入场以及具体的止盈止损点位',
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: message
|
||||
}
|
||||
role: 'user',
|
||||
content: message,
|
||||
},
|
||||
],
|
||||
model,
|
||||
stream: false,
|
||||
|
|
|
|||
|
|
@ -3,12 +3,7 @@ import { GitHubService } from './github.service';
|
|||
import { UserService } from '../user/user.service';
|
||||
import { Response } from 'express';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiQuery,
|
||||
} from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Github 模块')
|
||||
@Controller('github')
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ import { UserModule } from '../user/user.module';
|
|||
providers: [GitHubService],
|
||||
exports: [GitHubService],
|
||||
})
|
||||
export class GitHubModule { }
|
||||
export class GitHubModule {}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,9 @@ export class GitHubService {
|
|||
|
||||
async getUserInfo(accessToken: string): Promise<any> {
|
||||
// 先检查缓存
|
||||
const cachedUserInfo = await this.cacheManager.get(`github_user_${accessToken}`);
|
||||
const cachedUserInfo = await this.cacheManager.get(
|
||||
`github_user_${accessToken}`,
|
||||
);
|
||||
if (cachedUserInfo) {
|
||||
return cachedUserInfo;
|
||||
}
|
||||
|
|
@ -76,17 +78,22 @@ export class GitHubService {
|
|||
});
|
||||
|
||||
// 获取用户邮箱信息
|
||||
const emailResponse = await axios.get('https://api.github.com/user/emails', {
|
||||
headers: {
|
||||
Authorization: `token ${accessToken}`,
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
const emailResponse = await axios.get(
|
||||
'https://api.github.com/user/emails',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `token ${accessToken}`,
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const userInfo = {
|
||||
...userResponse.data,
|
||||
emails: emailResponse.data,
|
||||
email: emailResponse.data.find((email: any) => email.primary)?.email || userResponse.data.email,
|
||||
email:
|
||||
emailResponse.data.find((email: any) => email.primary)?.email ||
|
||||
userResponse.data.email,
|
||||
};
|
||||
|
||||
// 缓存用户信息
|
||||
|
|
|
|||
|
|
@ -3,12 +3,7 @@ import { LarkService } from './lark.service';
|
|||
import { UserService } from '../user/user.service';
|
||||
import { Response } from 'express';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiQuery,
|
||||
ApiResponse,
|
||||
} from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiQuery, ApiResponse } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Lark 模块')
|
||||
@Controller('lark')
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ import { UserModule } from '../user/user.module';
|
|||
providers: [LarkService],
|
||||
exports: [LarkService],
|
||||
})
|
||||
export class LarkModule { }
|
||||
export class LarkModule {}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import axios from 'axios';
|
|||
|
||||
@Injectable()
|
||||
export class LarkService {
|
||||
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) { }
|
||||
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
|
||||
private readonly appId = process.env.FEISHU_APP_ID;
|
||||
private readonly appSecret = process.env.FEISHU_APP_SECRET;
|
||||
|
||||
|
|
@ -41,25 +41,34 @@ export class LarkService {
|
|||
|
||||
async getUserInfo(accessToken: string): Promise<any> {
|
||||
// 先检查缓存
|
||||
const cachedUserInfo = await this.cacheManager.get(`lark_user_${accessToken}`);
|
||||
const cachedUserInfo = await this.cacheManager.get(
|
||||
`lark_user_${accessToken}`,
|
||||
);
|
||||
if (cachedUserInfo) {
|
||||
return cachedUserInfo;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取用户基本信息
|
||||
const userResponse = await axios.get('https://open.feishu.cn/open-apis/authen/v1/user_info', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
const userResponse = await axios.get(
|
||||
'https://open.feishu.cn/open-apis/authen/v1/user_info',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const userInfo = userResponse.data;
|
||||
|
||||
if (userInfo && userInfo.code === 0) {
|
||||
// 缓存用户信息
|
||||
await this.cacheManager.set(`lark_user_${accessToken}`, userInfo.data, 3600); // 缓存1小时
|
||||
await this.cacheManager.set(
|
||||
`lark_user_${accessToken}`,
|
||||
userInfo.data,
|
||||
3600,
|
||||
); // 缓存1小时
|
||||
return userInfo.data;
|
||||
} else {
|
||||
throw new Error('获取飞书用户信息失败');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsBoolean, IsOptional } from 'class-validator';
|
||||
import {
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
} from 'class-validator';
|
||||
import { TaskPriority } from '../entities/task.entity';
|
||||
|
||||
export class CreateTaskDto {
|
||||
@ApiProperty({ description: '任务标题' })
|
||||
|
|
@ -16,4 +24,19 @@ export class CreateTaskDto {
|
|||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isCompleted?: boolean;
|
||||
|
||||
@ApiProperty({ description: '任务截止日期', required: false })
|
||||
@IsDateString()
|
||||
@IsOptional()
|
||||
dueDate?: Date;
|
||||
|
||||
@ApiProperty({
|
||||
description: '任务优先级',
|
||||
enum: TaskPriority,
|
||||
default: TaskPriority.MEDIUM,
|
||||
required: false,
|
||||
})
|
||||
@IsEnum(TaskPriority)
|
||||
@IsOptional()
|
||||
priority?: TaskPriority;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsNumber } from 'class-validator';
|
||||
|
||||
export class ReorderTasksDto {
|
||||
@ApiProperty({
|
||||
description: '按新顺序排列的任务ID数组',
|
||||
type: [Number],
|
||||
})
|
||||
@IsArray()
|
||||
@IsNumber({}, { each: true })
|
||||
taskIds: number[];
|
||||
}
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
export enum TaskPriority {
|
||||
LOW = 'low',
|
||||
MEDIUM = 'medium',
|
||||
HIGH = 'high',
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Task {
|
||||
@PrimaryGeneratedColumn()
|
||||
|
|
@ -11,11 +17,31 @@ export class Task {
|
|||
@ApiProperty({ description: '任务标题' })
|
||||
title: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Column({ type: 'text', nullable: true })
|
||||
@ApiProperty({ description: '任务描述', required: false })
|
||||
description?: string;
|
||||
|
||||
@Column({ default: false })
|
||||
@ApiProperty({ description: '是否完成', default: false })
|
||||
isCompleted: boolean;
|
||||
|
||||
@Column({ type: 'int' })
|
||||
@ApiProperty({ description: '任务排序位置' })
|
||||
position: number;
|
||||
|
||||
@Column({ type: 'datetime', nullable: true })
|
||||
@ApiProperty({ description: '任务截止日期', required: false })
|
||||
dueDate?: Date;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: TaskPriority,
|
||||
default: TaskPriority.MEDIUM,
|
||||
})
|
||||
@ApiProperty({
|
||||
description: '任务优先级',
|
||||
enum: TaskPriority,
|
||||
default: TaskPriority.MEDIUM,
|
||||
})
|
||||
priority: TaskPriority;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,8 @@ import {
|
|||
import { TaskService } from './task.service';
|
||||
import { CreateTaskDto } from './dto/create-task.dto';
|
||||
import { UpdateTaskDto } from './dto/update-task.dto';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiParam,
|
||||
} from '@nestjs/swagger';
|
||||
import { ReorderTasksDto } from './dto/reorder-tasks.dto';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
|
||||
import { Task } from './entities/task.entity';
|
||||
|
||||
@ApiTags('任务模块')
|
||||
|
|
@ -45,6 +41,13 @@ export class TaskController {
|
|||
return this.taskService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch('reorder')
|
||||
@ApiOperation({ summary: '对任务进行重新排序' })
|
||||
@ApiResponse({ status: 200, description: '排序成功' })
|
||||
reorder(@Body() reorderTasksDto: ReorderTasksDto) {
|
||||
return this.taskService.reorder(reorderTasksDto.taskIds);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@ApiOperation({ summary: '更新任务' })
|
||||
@ApiParam({ name: 'id', description: '任务ID' })
|
||||
|
|
|
|||
|
|
@ -12,13 +12,33 @@ export class TaskService {
|
|||
private taskRepository: Repository<Task>,
|
||||
) {}
|
||||
|
||||
create(createTaskDto: CreateTaskDto) {
|
||||
const task = this.taskRepository.create(createTaskDto);
|
||||
return this.taskRepository.save(task);
|
||||
async create(createTaskDto: CreateTaskDto) {
|
||||
return await this.taskRepository.manager.transaction(
|
||||
async (transactionalEntityManager) => {
|
||||
// 使用悲观写锁防止并发竞态条件
|
||||
const maxPosition = await transactionalEntityManager
|
||||
.createQueryBuilder(Task, 'task')
|
||||
.select('MAX(task.position)', 'max')
|
||||
.setLock('pessimistic_write')
|
||||
.getRawOne();
|
||||
|
||||
const position = (maxPosition.max ?? -1) + 1;
|
||||
|
||||
const task = transactionalEntityManager.create(Task, {
|
||||
...createTaskDto,
|
||||
position,
|
||||
});
|
||||
return await transactionalEntityManager.save(task);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.taskRepository.find();
|
||||
return this.taskRepository.find({
|
||||
order: {
|
||||
position: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
|
|
@ -34,4 +54,19 @@ export class TaskService {
|
|||
await this.taskRepository.delete(id);
|
||||
return { message: 'Deleted successfully' };
|
||||
}
|
||||
async reorder(taskIds: number[]) {
|
||||
await this.taskRepository.manager.transaction(
|
||||
async (transactionalEntityManager) => {
|
||||
for (let i = 0; i < taskIds.length; i++) {
|
||||
const taskId = taskIds[i];
|
||||
await transactionalEntityManager.update(
|
||||
Task,
|
||||
{ id: taskId },
|
||||
{ position: i },
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
return { message: 'Tasks reordered successfully' };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,7 @@ import {
|
|||
} from '@nestjs/common';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { Public } from '../auth/decorators/public.decorator';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiBody,
|
||||
} from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger';
|
||||
import { User } from './entities/user.entity';
|
||||
|
||||
@ApiTags('用户模块')
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export class UserService {
|
|||
@InjectRepository(User)
|
||||
private userRepository: Repository<User>,
|
||||
private jwtService: JwtService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
async createUser(username: string, password: string | null, email: string) {
|
||||
let hashedPassword = null;
|
||||
|
|
@ -35,9 +35,12 @@ export class UserService {
|
|||
return this.userRepository.findOne({ where: { username } });
|
||||
}
|
||||
|
||||
async findUserByProvider(provider: string, providerId: string): Promise<User | undefined> {
|
||||
async findUserByProvider(
|
||||
provider: string,
|
||||
providerId: string,
|
||||
): Promise<User | undefined> {
|
||||
return this.userRepository.findOne({
|
||||
where: { provider, providerId }
|
||||
where: { provider, providerId },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +48,7 @@ export class UserService {
|
|||
provider: string,
|
||||
providerId: string,
|
||||
providerUsername: string,
|
||||
email: string
|
||||
email: string,
|
||||
): Promise<User> {
|
||||
// 生成唯一的用户名:provider_providerUsername
|
||||
const username = `${provider}_${providerUsername}`;
|
||||
|
|
|
|||
Loading…
Reference in New Issue