system-config.service.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import { GenericCrudService } from '@d8d/shared-crud';
  2. import { DataSource, In } from 'typeorm';
  3. import { SystemConfig } from '../entities/system-config.entity';
  4. import { redisUtil } from '@d8d/shared-utils';
  5. export class SystemConfigService extends GenericCrudService<SystemConfig> {
  6. constructor(dataSource: DataSource) {
  7. super(dataSource, SystemConfig, {
  8. userTracking: {
  9. createdByField: 'createdBy',
  10. updatedByField: 'updatedBy'
  11. }
  12. });
  13. }
  14. /**
  15. * 根据配置键获取配置值(带缓存)
  16. */
  17. async getConfigByKey(configKey: string): Promise<string | null> {
  18. // 1. 先尝试从缓存获取
  19. const cachedValue = await redisUtil.getSystemConfig(configKey);
  20. // 2. 如果缓存命中且不是空值缓存
  21. if (cachedValue !== null && !redisUtil.isNullValue(cachedValue)) {
  22. return cachedValue;
  23. }
  24. // 3. 如果是空值缓存,直接返回null
  25. if (cachedValue !== null && redisUtil.isNullValue(cachedValue)) {
  26. return null;
  27. }
  28. // 4. 缓存未命中,查询数据库
  29. const config = await this.repository.findOne({
  30. where: { configKey }
  31. });
  32. const configValue = config?.configValue || null;
  33. // 5. 将结果写入缓存
  34. if (configValue !== null) {
  35. // 正常值,缓存1小时
  36. await redisUtil.setSystemConfig(configKey, configValue, 3600);
  37. } else {
  38. // 空值,缓存5分钟防止缓存穿透
  39. await redisUtil.setNullSystemConfig(configKey, 300);
  40. }
  41. return configValue;
  42. }
  43. /**
  44. * 根据配置键获取配置值,如果不存在则返回默认值
  45. */
  46. async getConfigByKeyWithDefault(configKey: string, defaultValue: string): Promise<string> {
  47. const value = await this.getConfigByKey(configKey);
  48. return value || defaultValue;
  49. }
  50. /**
  51. * 批量获取配置(带缓存)
  52. */
  53. async getConfigsByKeys(configKeys: string[]): Promise<Record<string, string>> {
  54. // 1. 先尝试从缓存批量获取
  55. const cachedValues = await redisUtil.getSystemConfigs(configKeys);
  56. const result: Record<string, string> = {};
  57. const missingKeys: string[] = [];
  58. // 2. 处理缓存命中的键
  59. configKeys.forEach(key => {
  60. const cachedValue = cachedValues[key];
  61. if (cachedValue === undefined) {
  62. // 空值缓存,跳过
  63. return;
  64. }
  65. if (cachedValue === null) {
  66. // 缓存未命中,需要查询数据库
  67. missingKeys.push(key);
  68. } else if (!redisUtil.isNullValue(cachedValue)) {
  69. // 有效缓存值
  70. result[key] = cachedValue;
  71. }
  72. // 如果是空值缓存(redisUtil.isNullValue返回true),不添加到结果中
  73. });
  74. // 3. 如果有未命中的键,查询数据库
  75. if (missingKeys.length > 0) {
  76. const configs = await this.repository.find({
  77. where: {
  78. configKey: In(missingKeys)
  79. }
  80. });
  81. const dbValues: Record<string, string> = {};
  82. configs.forEach(config => {
  83. if (config.configValue !== null) {
  84. dbValues[config.configKey] = config.configValue;
  85. }
  86. });
  87. // 4. 处理数据库查询结果并更新缓存
  88. const cachePromises: Promise<void>[] = [];
  89. missingKeys.forEach(key => {
  90. const dbValue = dbValues[key];
  91. if (dbValue !== undefined) {
  92. // 数据库中有值,添加到结果并缓存
  93. result[key] = dbValue;
  94. cachePromises.push(redisUtil.setSystemConfig(key, dbValue, 3600));
  95. } else {
  96. // 数据库中无值,设置空值缓存
  97. cachePromises.push(redisUtil.setNullSystemConfig(key, 300));
  98. }
  99. });
  100. // 等待所有缓存操作完成
  101. if (cachePromises.length > 0) {
  102. await Promise.all(cachePromises);
  103. }
  104. }
  105. return result;
  106. }
  107. /**
  108. * 设置配置值,如果配置键不存在则创建,存在则更新
  109. */
  110. async setConfig(configKey: string, configValue: string, description?: string): Promise<SystemConfig> {
  111. const existingConfig = await this.repository.findOne({
  112. where: { configKey }
  113. });
  114. if (existingConfig) {
  115. // 更新现有配置
  116. const updatedConfig = await this.update(existingConfig.id, {
  117. configValue,
  118. description: description || existingConfig.description
  119. });
  120. // 清除相关缓存
  121. await redisUtil.deleteSystemConfig(configKey);
  122. return updatedConfig!;
  123. } else {
  124. // 创建新配置
  125. const newConfig = await this.create({
  126. configKey,
  127. configValue,
  128. description
  129. } as SystemConfig);
  130. // 清除相关缓存(如果之前有空值缓存)
  131. await redisUtil.deleteSystemConfig(configKey);
  132. return newConfig!;
  133. }
  134. }
  135. /**
  136. * 获取所有配置
  137. */
  138. async getAllConfigs(): Promise<SystemConfig[]> {
  139. return await this.repository.find();
  140. }
  141. /**
  142. * 删除配置
  143. */
  144. async deleteConfig(configKey: string): Promise<boolean> {
  145. const config = await this.repository.findOne({
  146. where: { configKey }
  147. });
  148. if (!config) {
  149. return false;
  150. }
  151. await this.delete(config.id);
  152. // 清除相关缓存
  153. await redisUtil.deleteSystemConfig(configKey);
  154. return true;
  155. }
  156. /**
  157. * 重写update方法,在更新时清除缓存
  158. */
  159. override async update(id: number, data: Partial<SystemConfig>): Promise<SystemConfig | null> {
  160. const updatedConfig = await super.update(id, data);
  161. if (updatedConfig) {
  162. // 清除相关缓存
  163. await redisUtil.deleteSystemConfig(updatedConfig.configKey);
  164. }
  165. return updatedConfig;
  166. }
  167. /**
  168. * 重写delete方法,在删除时清除缓存
  169. */
  170. override async delete(id: number): Promise<boolean> {
  171. // 先获取配置信息,以便后续清除缓存
  172. const config = await this.getById(id);
  173. const result = await super.delete(id);
  174. if (result && config) {
  175. // 清除相关缓存
  176. await redisUtil.deleteSystemConfig(config.configKey);
  177. }
  178. return result;
  179. }
  180. /**
  181. * 缓存预热 - 预加载常用配置到缓存
  182. */
  183. async warmUpCache(configKeys?: string[]): Promise<void> {
  184. // 如果没有指定配置键,预加载所有配置
  185. const keysToWarm = configKeys || ['app.login.enabled', 'app.payment.enabled', 'app.notification.enabled'];
  186. // 批量获取配置并缓存
  187. await this.getConfigsByKeys(keysToWarm);
  188. }
  189. }