loader.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. 'use strict';
  2. const path = require('path');
  3. const sleep = require('mz-modules/sleep');
  4. const AUTH_RETRIES = Symbol('authenticateRetries');
  5. module.exports = app => {
  6. const defaultConfig = {
  7. delegate: 'model',
  8. baseDir: 'model',
  9. logging(...args) {
  10. // if benchmark enabled, log used
  11. const used = typeof args[1] === 'number' ? `(${args[1]}ms)` : '';
  12. app.logger.info('[egg-sequelize]%s %s', used, args[0]);
  13. },
  14. host: 'localhost',
  15. port: 3306,
  16. username: 'root',
  17. benchmark: true,
  18. define: {
  19. freezeTableName: false,
  20. underscored: true,
  21. },
  22. };
  23. const config = app.config.sequelize;
  24. // support customize sequelize
  25. app.Sequelize = config.Sequelize || require('sequelize');
  26. const databases = [];
  27. if (!config.datasources) {
  28. databases.push(loadDatabase(Object.assign({}, defaultConfig, config)));
  29. } else {
  30. config.datasources.forEach(datasource => {
  31. databases.push(loadDatabase(Object.assign({}, defaultConfig, datasource)));
  32. });
  33. }
  34. app.beforeStart(async () => {
  35. await Promise.all(databases.map(database => authenticate(database)));
  36. });
  37. /**
  38. * load database to app[config.delegate]
  39. * @param {Object} config config for load
  40. * - delegate: load model to app[delegate]
  41. * - baseDir: where model located
  42. * - other sequelize configures(database username, password, etc...)
  43. * @return {Object} sequelize instance
  44. */
  45. function loadDatabase(config = {}) {
  46. if (typeof config.ignore === 'string' || Array.isArray(config.ignore)) {
  47. app.deprecate(`[egg-sequelize] if you want to exclude ${config.ignore} when load models, please set to config.sequelize.exclude instead of config.sequelize.ignore`);
  48. config.exclude = config.ignore;
  49. delete config.ignore;
  50. }
  51. const sequelize = config.connectionUri ?
  52. new app.Sequelize(config.connectionUri, config) :
  53. new app.Sequelize(config.database, config.username, config.password, config);
  54. const delegate = config.delegate.split('.');
  55. const len = delegate.length;
  56. let model = app;
  57. let context = app.context;
  58. for (let i = 0; i < len - 1; i++) {
  59. model = model[delegate[i]] = model[delegate[i]] || {};
  60. context = context[delegate[i]] = context[delegate[i]] || {};
  61. }
  62. if (model[delegate[len - 1]]) {
  63. throw new Error(`[egg-sequelize] app[${config.delegate}] is already defined`);
  64. }
  65. Object.defineProperty(model, delegate[len - 1], {
  66. value: sequelize,
  67. writable: false,
  68. configurable: true,
  69. });
  70. const DELEGATE = Symbol(`context#sequelize_${config.delegate}`);
  71. Object.defineProperty(context, delegate[len - 1], {
  72. get() {
  73. // context.model is different with app.model
  74. // so we can change the properties of ctx.model.xxx
  75. if (!this[DELEGATE]) {
  76. this[DELEGATE] = Object.create(model[delegate[len - 1]]);
  77. this[DELEGATE].ctx = this;
  78. }
  79. return this[DELEGATE];
  80. },
  81. configurable: true,
  82. });
  83. const modelDir = path.join(app.baseDir, 'app', config.baseDir);
  84. const models = [];
  85. const target = Symbol(config.delegate);
  86. app.loader.loadToApp(modelDir, target, {
  87. caseStyle: 'upper',
  88. ignore: config.exclude,
  89. filter(model) {
  90. if (!model || !model.sequelize) return false;
  91. models.push(model);
  92. return true;
  93. },
  94. initializer(factory) {
  95. if (typeof factory === 'function') {
  96. return factory(app, sequelize);
  97. }
  98. },
  99. });
  100. Object.assign(model[delegate[len - 1]], app[target]);
  101. models.forEach(model => {
  102. typeof model.associate === 'function' && model.associate();
  103. });
  104. return model[delegate[len - 1]];
  105. }
  106. /**
  107. * Authenticate to test Database connection.
  108. *
  109. * This method will retry 3 times when database connect fail in temporary, to avoid Egg start failed.
  110. * @param {Application} database instance of sequelize
  111. */
  112. async function authenticate(database) {
  113. database[AUTH_RETRIES] = database[AUTH_RETRIES] || 0;
  114. try {
  115. await database.authenticate();
  116. } catch (e) {
  117. if (database[AUTH_RETRIES] >= 3) throw e;
  118. // sleep 1s to retry, max 3 times
  119. database[AUTH_RETRIES] += 1;
  120. app.logger.warn(`Sequelize Error: ${e.message}, sleep 1 seconds to retry...`);
  121. await sleep(1000);
  122. await authenticate(database);
  123. }
  124. }
  125. };