feat(medications): 新增完整的药物管理和服药提醒功能

实现了包含药物信息管理、服药记录追踪、统计分析、自动状态更新和推送提醒的完整药物管理系统。

核心功能:
- 药物 CRUD 操作,支持多种剂型和自定义服药时间
- 惰性生成服药记录策略,查询时才生成当天记录
- 定时任务自动更新过期记录状态(每30分钟)
- 服药前15分钟自动推送提醒(每5分钟检查)
- 每日/范围/总体统计分析功能
- 完整的 API 文档和数据库建表脚本

技术实现:
- 使用 Sequelize ORM 管理 MySQL 数据表
- 集成 @nestjs/schedule 实现定时任务
- 复用现有推送通知系统发送提醒
- 采用软删除和权限验证保障数据安全
This commit is contained in:
richarjiang
2025-11-07 17:29:11 +08:00
parent 37cc2a729b
commit 188b4addca
27 changed files with 3464 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
import {
Injectable,
NotFoundException,
ForbiddenException,
Logger,
} from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Medication } from './models/medication.model';
import { CreateMedicationDto } from './dto/create-medication.dto';
import { UpdateMedicationDto } from './dto/update-medication.dto';
import { v4 as uuidv4 } from 'uuid';
/**
* 药物管理服务
*/
@Injectable()
export class MedicationsService {
private readonly logger = new Logger(MedicationsService.name);
constructor(
@InjectModel(Medication)
private readonly medicationModel: typeof Medication,
) {}
/**
* 创建药物
*/
async create(
userId: string,
createDto: CreateMedicationDto,
): Promise<Medication> {
this.logger.log(`用户 ${userId} 创建药物:${createDto.name}`);
const medication = await this.medicationModel.create({
id: uuidv4(),
userId,
name: createDto.name,
photoUrl: createDto.photoUrl,
form: createDto.form,
dosageValue: createDto.dosageValue,
dosageUnit: createDto.dosageUnit,
timesPerDay: createDto.timesPerDay,
medicationTimes: createDto.medicationTimes,
repeatPattern: createDto.repeatPattern,
startDate: new Date(createDto.startDate),
endDate: createDto.endDate ? new Date(createDto.endDate) : null,
note: createDto.note,
isActive: true,
deleted: false,
});
this.logger.log(`成功创建药物 ${medication.id}`);
return medication;
}
/**
* 获取用户的药物列表
*/
async findAll(
userId: string,
isActive?: boolean,
page: number = 1,
pageSize: number = 20,
): Promise<{ rows: Medication[]; total: number }> {
const where: any = {
userId,
deleted: false,
};
if (isActive !== undefined) {
where.isActive = isActive;
}
const { rows, count } = await this.medicationModel.findAndCountAll({
where,
limit: pageSize,
offset: (page - 1) * pageSize,
order: [['createdAt', 'DESC']],
});
return { rows, total: count };
}
/**
* 根据ID获取药物详情
*/
async findOne(id: string, userId: string): Promise<Medication> {
const medication = await this.medicationModel.findOne({
where: {
id,
deleted: false,
},
});
if (!medication) {
throw new NotFoundException('药物不存在');
}
// 验证所有权
if (medication.userId !== userId) {
throw new ForbiddenException('无权访问此药物');
}
return medication;
}
/**
* 更新药物信息
*/
async update(
id: string,
userId: string,
updateDto: UpdateMedicationDto,
): Promise<Medication> {
const medication = await this.findOne(id, userId);
// 更新字段
if (updateDto.name !== undefined) {
medication.name = updateDto.name;
}
if (updateDto.photoUrl !== undefined) {
medication.photoUrl = updateDto.photoUrl;
}
if (updateDto.form !== undefined) {
medication.form = updateDto.form;
}
if (updateDto.dosageValue !== undefined) {
medication.dosageValue = updateDto.dosageValue;
}
if (updateDto.dosageUnit !== undefined) {
medication.dosageUnit = updateDto.dosageUnit;
}
if (updateDto.timesPerDay !== undefined) {
medication.timesPerDay = updateDto.timesPerDay;
}
if (updateDto.medicationTimes !== undefined) {
medication.medicationTimes = updateDto.medicationTimes;
}
if (updateDto.repeatPattern !== undefined) {
medication.repeatPattern = updateDto.repeatPattern;
}
if (updateDto.startDate !== undefined) {
medication.startDate = new Date(updateDto.startDate);
}
if (updateDto.endDate !== undefined) {
medication.endDate = new Date(updateDto.endDate);
}
if (updateDto.note !== undefined) {
medication.note = updateDto.note;
}
await medication.save();
this.logger.log(`成功更新药物 ${id}`);
return medication;
}
/**
* 删除药物(软删除)
*/
async remove(id: string, userId: string): Promise<void> {
const medication = await this.findOne(id, userId);
medication.deleted = true;
await medication.save();
this.logger.log(`成功删除药物 ${id}`);
}
/**
* 停用药物
*/
async deactivate(id: string, userId: string): Promise<Medication> {
const medication = await this.findOne(id, userId);
medication.isActive = false;
await medication.save();
this.logger.log(`成功停用药物 ${id}`);
return medication;
}
/**
* 激活药物
*/
async activate(id: string, userId: string): Promise<Medication> {
const medication = await this.findOne(id, userId);
medication.isActive = true;
await medication.save();
this.logger.log(`成功激活药物 ${id}`);
return medication;
}
}