feat(模板): 添加模板管理功能及相关服务

实现模板的增删改查功能,包括模板实体、DTO、服务和控制器
添加模板渲染服务用于动态生成产品SKU
在product服务中集成模板服务用于SKU生成
添加模板数据填充器初始化默认模板
This commit is contained in:
tikkhun 2025-11-27 19:04:03 +08:00
parent 0809840507
commit 3545633f9e
9 changed files with 405 additions and 6 deletions

View File

@ -33,6 +33,7 @@ import { Subscription } from '../entity/subscription.entity';
import { Site } from '../entity/site.entity'; import { Site } from '../entity/site.entity';
import { Dict } from '../entity/dict.entity'; import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity'; import { DictItem } from '../entity/dict_item.entity';
import { Template } from '../entity/template.entity';
import DictSeeder from '../db/seeds/dict.seeder'; import DictSeeder from '../db/seeds/dict.seeder';
export default { export default {
@ -75,6 +76,7 @@ export default {
Site, Site,
Dict, Dict,
DictItem, DictItem,
Template
], ],
synchronize: true, synchronize: true,
logging: false, logging: false,

View File

@ -0,0 +1,36 @@
import { Controller, Get, Inject, Param } from '@midwayjs/core';
import { DictService } from '../service/dict.service';
/**
*
*/
@Controller('/locales')
export class LocaleController {
@Inject()
dictService: DictService;
/**
*
* @param lang zh-CN, en-US
* @returns JSON
*/
@Get('/:lang')
async getLocale(@Param('lang') lang: string) {
// 根据语言代码查找对应的字典
const dict = await this.dictService.getDict({ name: lang }, ['items']);
// 如果字典不存在,则返回空对象
if (!dict) {
return {};
}
// 将字典项转换为 key-value 对象
const locale = dict.items.reduce((acc, item) => {
acc[item.name] = item.title;
return acc;
}, {});
return locale;
}
}

View File

@ -0,0 +1,115 @@
import { Inject, Controller, Get, Post, Put, Del, Body, Param } from '@midwayjs/core';
import { TemplateService } from '../service/template.service';
import { successResponse, errorResponse } from '../utils/response.util';
import { CreateTemplateDTO, UpdateTemplateDTO } from '../dto/template.dto';
import { ApiOkResponse, ApiTags } from '@midwayjs/swagger';
import { Template } from '../entity/template.entity';
import { BooleanRes } from '../dto/reponse.dto';
/**
* @controller TemplateController
*/
@ApiTags('Template')
@Controller('/template')
export class TemplateController {
@Inject()
templateService: TemplateService;
/**
* @summary
* @description
*/
@ApiOkResponse({ type: [Template], description: '成功获取模板列表' })
@Get('/')
async getTemplateList() {
try {
// 调用服务层获取列表
const data = await this.templateService.getTemplateList();
// 返回成功响应
return successResponse(data);
} catch (error) {
// 返回错误响应
return errorResponse(error.message);
}
}
/**
* @summary
* @description
* @param name
*/
@ApiOkResponse({ type: Template, description: '成功获取模板' })
@Get('/:name')
async getTemplateByName(@Param('name') name: string) {
try {
// 调用服务层获取单个模板
const data = await this.templateService.getTemplateByName(name);
// 返回成功响应
return successResponse(data);
} catch (error) {
// 返回错误响应
return errorResponse(error.message);
}
}
/**
* @summary
* @description
* @param templateData
*/
@ApiOkResponse({ type: Template, description: '成功创建模板' })
@Post('/')
async createTemplate(@Body() templateData: CreateTemplateDTO) {
try {
// 调用服务层创建模板
const data = await this.templateService.createTemplate(templateData);
// 返回成功响应
return successResponse(data);
} catch (error) {
// 返回错误响应
return errorResponse(error.message);
}
}
/**
* @summary
* @description ID
* @param id ID
* @param templateData
*/
@ApiOkResponse({ type: Template, description: '成功更新模板' })
@Put('/:id')
async updateTemplate(
@Param('id') id: number,
@Body() templateData: UpdateTemplateDTO
) {
try {
// 调用服务层更新模板
const data = await this.templateService.updateTemplate(id, templateData);
// 返回成功响应
return successResponse(data);
} catch (error) {
// 返回错误响应
return errorResponse(error.message);
}
}
/**
* @summary
* @description ID
* @param id ID
*/
@ApiOkResponse({ type: BooleanRes, description: '成功删除模板' })
@Del('/:id')
async deleteTemplate(@Param('id') id: number) {
try {
// 调用服务层删除模板
const data = await this.templateService.deleteTemplate(id);
// 返回成功响应
return successResponse(data);
} catch (error) {
// 返回错误响应
return errorResponse(error.message);
}
}
}

View File

@ -0,0 +1,36 @@
import { Seeder, SeederFactoryManager } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { Template } from '../../entity/template.entity';
/**
* @class TemplateSeeder
* @description
*/
export default class TemplateSeeder implements Seeder {
/**
* @method run
* @description product_sku
* @param {DataSource} dataSource - repository
* @param {SeederFactoryManager} factoryManager - Seeder
*/
public async run(
dataSource: DataSource,
factoryManager: SeederFactoryManager
): Promise<any> {
// 获取 Template 实体的 repository
const templateRepository = dataSource.getRepository(Template);
// 检查名为 'product_sku' 的模板是否已存在
const existingTemplate = await templateRepository.findOne({
where: { name: 'product_sku' },
});
// 如果模板不存在,则创建并保存
if (!existingTemplate) {
const template = new Template();
template.name = 'product_sku';
template.value = '{{brand}}-{{flavor}}-{{strength}}-{{humidity}}';
await templateRepository.save(template);
}
}
}

View File

@ -27,16 +27,35 @@ export class CreateProductDTO {
@Rule(RuleType.string()) @Rule(RuleType.string())
description: string; description: string;
@ApiProperty({ description: '产品 SKU', required: false })
@Rule(RuleType.string())
sku?: string;
@ApiProperty({ description: '品牌', type: DictItemDTO }) @ApiProperty({ description: '品牌', type: DictItemDTO })
@Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() })) @Rule(
RuleType.object().keys({
title: RuleType.string().required(),
name: RuleType.string(),
})
)
brand: DictItemDTO; brand: DictItemDTO;
@ApiProperty({ description: '规格', type: DictItemDTO }) @ApiProperty({ description: '规格', type: DictItemDTO })
@Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() })) @Rule(
RuleType.object().keys({
title: RuleType.string().required(),
name: RuleType.string(),
})
)
strength: DictItemDTO; strength: DictItemDTO;
@ApiProperty({ description: '口味', type: DictItemDTO }) @ApiProperty({ description: '口味', type: DictItemDTO })
@Rule(RuleType.object().keys({ title: RuleType.string().required(), name: RuleType.string() })) @Rule(
RuleType.object().keys({
title: RuleType.string().required(),
name: RuleType.string(),
})
)
flavor: DictItemDTO; flavor: DictItemDTO;
@ApiProperty() @ApiProperty()

22
src/dto/template.dto.ts Normal file
View File

@ -0,0 +1,22 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
export class CreateTemplateDTO {
@ApiProperty({ description: '模板名称', required: true })
@Rule(RuleType.string().required())
name: string;
@ApiProperty({ description: '模板内容', required: true })
@Rule(RuleType.string().required())
value: string;
}
export class UpdateTemplateDTO {
@ApiProperty({ description: '模板名称', required: true })
@Rule(RuleType.string().required())
name: string;
@ApiProperty({ description: '模板内容', required: true })
@Rule(RuleType.string().required())
value: string;
}

View File

@ -0,0 +1,43 @@
import { ApiProperty } from '@midwayjs/swagger';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('template')
export class Template {
@ApiProperty({ type: 'number' })
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ type: 'string' })
@Column({ unique: true })
name: string;
@ApiProperty({ type: 'string' })
@Column('text')
value: string;
@ApiProperty({ nullable: true ,name:"描述"})
@Column('text',{nullable: true,comment: "描述"})
description?: string;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -25,12 +25,16 @@ import { Variation } from '../entity/variation.entity';
import { Dict } from '../entity/dict.entity'; import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity'; import { DictItem } from '../entity/dict_item.entity';
import { Context } from '@midwayjs/koa'; import { Context } from '@midwayjs/koa';
import { TemplateService } from './template.service';
@Provide() @Provide()
export class ProductService { export class ProductService {
@Inject() @Inject()
ctx: Context; ctx: Context;
@Inject()
templateService: TemplateService;
@InjectEntityModel(Product) @InjectEntityModel(Product)
productModel: Repository<Product>; productModel: Repository<Product>;
@ -209,7 +213,7 @@ export class ProductService {
} }
async createProduct(createProductDTO: CreateProductDTO): Promise<Product> { async createProduct(createProductDTO: CreateProductDTO): Promise<Product> {
const { name, description, brand, flavor, strength, humidity } = const { name, description, brand, flavor, strength, humidity, sku } =
createProductDTO; createProductDTO;
// 获取或创建品牌、口味、规格 // 获取或创建品牌、口味、规格
@ -251,8 +255,18 @@ export class ProductService {
product.description = description; product.description = description;
product.attributes = attributesToMatch; product.attributes = attributesToMatch;
// 生成 SKU // 如果用户提供了 sku则直接使用否则通过模板引擎生成
product.sku = `${brandItem.name}-${flavorItem.name}-${strengthItem.name}-${humidityItem.name}`; if (sku) {
product.sku = sku;
} else {
// 生成 SKU
product.sku = await this.templateService.render('product_sku', {
brand: brandItem.name,
flavor: flavorItem.name,
strength: strengthItem.name,
humidity: humidityItem.name,
});
}
// 保存产品 // 保存产品
return await this.productModel.save(product); return await this.productModel.save(product);

View File

@ -0,0 +1,112 @@
import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { Template } from '../entity/template.entity';
import { CreateTemplateDTO, UpdateTemplateDTO } from '../dto/template.dto';
/**
* @service TemplateService
*/
@Provide()
export class TemplateService {
// 注入 Template 实体模型
@InjectEntityModel(Template)
templateModel: Repository<Template>;
/**
*
* @returns {Promise<Template[]>}
*/
async getTemplateList(): Promise<Template[]> {
// 使用 find 方法查询所有模板
return this.templateModel.find();
}
/**
*
* @param {string} name -
* @returns {Promise<Template>}
*/
async getTemplateByName(name: string): Promise<Template> {
// 使用 findOne 方法按名称查询模板
return this.templateModel.findOne({ where: { name } });
}
/**
*
* @param {CreateTemplateDTO} templateData -
* @returns {Promise<Template>}
*/
async createTemplate(templateData: CreateTemplateDTO): Promise<Template> {
// 创建一个新的模板实例
const template = new Template();
// 设置模板的名称和值
template.name = templateData.name;
template.value = templateData.value;
// 保存新模板到数据库
return this.templateModel.save(template);
}
/**
* ID
* @param {number} id - ID
* @param {UpdateTemplateDTO} templateData -
* @returns {Promise<Template>}
*/
async updateTemplate(
id: number,
templateData: UpdateTemplateDTO
): Promise<Template> {
// 首先根据 ID 查找模板
const template = await this.templateModel.findOneBy({ id });
// 如果模板不存在,则抛出错误
if (!template) {
throw new Error(`模板 ID ${id} 不存在`);
}
// 更新模板的名称和值
template.name = templateData.name;
template.value = templateData.value;
// 保存更新后的模板到数据库
return this.templateModel.save(template);
}
/**
* ID
* @param {number} id - ID
* @returns {Promise<boolean>} true false
*/
async deleteTemplate(id: number): Promise<boolean> {
// 执行删除操作
const result = await this.templateModel.delete(id);
// 如果影响的行数大于 0则表示删除成功
return result.affected > 0;
}
/**
*
* @param {string} name -
* @param {Record<string, any>} data -
* @returns {Promise<string>}
*/
async render(name: string, data: Record<string, any>): Promise<string> {
// 根据名称获取模板
const template = await this.getTemplateByName(name);
// 如果模板不存在,则抛出错误
if (!template) {
throw new Error(`模板 '${name}' 不存在`);
}
// 获取模板的原始内容
let rendered = template.value;
// 遍历数据对象,替换模板中的占位符
for (const key in data) {
// 创建一个正则表达式来匹配 {{key}}
const regex = new RegExp(`{{${key}}}`, 'g');
// 执行替换操作
rendered = rendered.replace(regex, data[key]);
}
// 返回渲染后的字符串
return rendered;
}
}