accurateMatching.service.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { Client } from '@elastic/elasticsearch';
  2. import { Config, Init, Inject, Provide } from '@midwayjs/core';
  3. import { Context } from '@midwayjs/koa';
  4. import { floor, get, head } from 'lodash';
  5. @Provide()
  6. export class AccurateMatchingService {
  7. @Config('elasticsearch')
  8. esConfig: object;
  9. /**es连接实例 */
  10. esClient: Client;
  11. @Inject()
  12. ctx: Context;
  13. @Init()
  14. async initClient() {
  15. const esClient = new Client(this.esConfig);
  16. this.esClient = esClient;
  17. }
  18. async demand(keyword: string, skip: number = 0, limit: number = 10) {
  19. const config = { index: 'demand', fields: ['name', 'tags', 'area', 'brief'] };
  20. return this.search(keyword, config, skip, limit);
  21. }
  22. async supply(keyword: string, skip: number = 0, limit: number = 10) {
  23. const config = { index: 'supply', fields: ['name', 'tags', 'area', 'brief'] };
  24. return this.search(keyword, config, skip, limit);
  25. }
  26. async achievement(keyword: string, skip: number = 0, limit: number = 10) {
  27. const config = { index: 'achievement', fields: ['name', 'tags', 'area', 'brief'] };
  28. return this.search(keyword, config, skip, limit);
  29. }
  30. //由供给信息查询
  31. async forDemandSearch(keyword: string, skip = 0, limit = 10, id) {
  32. const fields = ['name', 'tags', 'brief'];
  33. const config = { index: ['demand', 'achievement'], fields };
  34. const origin_index = 'supply';
  35. return await this.manyIndexsSearch(keyword, config, skip, limit, origin_index, id);
  36. }
  37. // 由需求信息查询
  38. async forSupplySearch(keyword: string, skip = 0, limit = 10, id) {
  39. const fields = ['name', 'tags', 'brief'];
  40. const config = { index: ['supply', 'achievement'], fields };
  41. const origin_index = 'demand';
  42. return await this.manyIndexsSearch(keyword, config, skip, limit, origin_index, id);
  43. }
  44. /**精准匹配统一查询,使用简单的form+size分页模式.如果数据量大于1w,此处的分页模式应该修改,不过就会影响资源的占用及数据的实时性 */
  45. async search(keyword: string, config: object, skip: number, limit: number) {
  46. const index = get(config, 'index');
  47. const fields = get(config, 'fields', []);
  48. const result = await this.esClient.search({
  49. index,
  50. query: {
  51. bool: {
  52. must: [
  53. {
  54. multi_match: {
  55. query: keyword,
  56. fields,
  57. },
  58. },
  59. ],
  60. },
  61. },
  62. size: limit,
  63. from: skip,
  64. });
  65. const returnData = this.dealResponses(result);
  66. return returnData;
  67. }
  68. async manyIndexsSearch(keyword: string, config: object, skip: number, limit: number, origin_index: string, id?: number) {
  69. const index = get(config, 'index');
  70. // const fields = get(config, 'fields', []);
  71. const user_id = get(this.ctx, 'user.id');
  72. // const must: any = [
  73. // {
  74. // match: {
  75. // name: {
  76. // query: keyword,
  77. // },
  78. // },
  79. // },
  80. // ];
  81. const must_not = [
  82. {
  83. match: {
  84. user: user_id,
  85. },
  86. },
  87. ];
  88. const should: any = [
  89. {
  90. constant_score: {
  91. filter: {
  92. bool: {
  93. must: {
  94. match: { name: keyword },
  95. },
  96. },
  97. },
  98. boost: 3,
  99. },
  100. },
  101. ];
  102. const queries = [];
  103. queries.push({ bool: { must_not, should: { match: { name: keyword } } } });
  104. let fieldValue, industryValue;
  105. if (id) {
  106. const res = await this.esClient.search({
  107. index: origin_index,
  108. query: { bool: { must: { match: { id } } } },
  109. });
  110. const originResult = this.dealResponses(res);
  111. const industry = get(head(get(originResult, 'data')), 'industry');
  112. // 产业
  113. if (industry && industry !== '') {
  114. industryValue = industry;
  115. should.push({
  116. constant_score: {
  117. filter: {
  118. bool: {
  119. must: {
  120. term: { industry },
  121. },
  122. },
  123. },
  124. boost: 1,
  125. },
  126. });
  127. }
  128. // 技术领域
  129. const field = get(head(get(originResult, 'data')), 'field');
  130. if (field && field !== '') {
  131. fieldValue = field;
  132. should.push({
  133. constant_score: {
  134. filter: {
  135. bool: {
  136. must: {
  137. match: { field },
  138. },
  139. },
  140. },
  141. boost: 1,
  142. },
  143. });
  144. }
  145. }
  146. if (fieldValue) {
  147. queries.push({ match: { field: fieldValue } });
  148. }
  149. if (industryValue) {
  150. queries.push({ term: { industry: industryValue } });
  151. }
  152. const result = await this.esClient.search({
  153. index,
  154. // constant_score方式
  155. // query: {
  156. // bool: {
  157. // must_not,
  158. // should,
  159. // minimum_should_match: 1,
  160. // },
  161. // },
  162. // dis_max方式
  163. query: {
  164. dis_max: {
  165. queries,
  166. tie_breaker: 0.3,
  167. boost: 0.7,
  168. },
  169. },
  170. // min_score: 3,
  171. size: limit,
  172. from: skip,
  173. explain: true,
  174. });
  175. const returnData = this.dealResponses(result);
  176. return returnData;
  177. }
  178. dealResponses(result) {
  179. const total = get(result, 'hits.total.value', 0);
  180. const hits = get(result, 'hits.hits', []);
  181. const list = [];
  182. for (const ol of hits) {
  183. const _score = get(ol, '_score', 0);
  184. const _source = get(ol, '_index');
  185. const data = get(ol, '_source', {});
  186. let recommend = 0;
  187. /**推荐星数计算:超过5,就都是5颗星.未超过5的都向下取整 */
  188. if (_score >= 5) recommend = 5;
  189. else recommend = floor(_score);
  190. data._recommend = recommend;
  191. data._source = _source;
  192. list.push(data);
  193. }
  194. return { total, data: list };
  195. }
  196. }