説明なし
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

get_field_matcher.go 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. package tools
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "sync"
  6. "time"
  7. "git.x2erp.com/qdy/go-svc-mcp/internal/mcp"
  8. )
  9. // 数据库字典表结构
  10. type FieldDictionary struct {
  11. TableNameCN string `json:"table_name_cn"`
  12. TableNameEN string `json:"table_name_en"`
  13. FieldNameCN string `json:"field_name_cn"`
  14. FieldNameEN string `json:"field_name_en"`
  15. FieldType string `json:"field_type"`
  16. StandardName string `json:"standard_name"`
  17. Description string `json:"description"`
  18. Category string `json:"category"`
  19. IsCalculated bool `json:"is_calculated"`
  20. Aliases string `json:"aliases"`
  21. }
  22. // 缓存结构,添加过期时间和最后访问时间
  23. type fieldDictionaryCache struct {
  24. data []FieldDictionary
  25. expireTime time.Time // 缓存过期时间
  26. lastAccessTime time.Time // 最后访问时间
  27. expireDuration time.Duration // 过期时长
  28. mutex sync.RWMutex // 读写锁
  29. }
  30. // 全局缓存实例
  31. var cache *fieldDictionaryCache
  32. // 初始化缓存
  33. func initCache() {
  34. if cache == nil {
  35. cache = &fieldDictionaryCache{
  36. expireDuration: 20 * time.Minute, // 20分钟过期
  37. data: make([]FieldDictionary, 0),
  38. }
  39. }
  40. }
  41. // 获取缓存数据(会更新最后访问时间)
  42. func getCache() ([]FieldDictionary, bool) {
  43. initCache()
  44. cache.mutex.RLock()
  45. defer cache.mutex.RUnlock()
  46. // 检查缓存是否有效
  47. if len(cache.data) == 0 {
  48. return nil, false
  49. }
  50. // 检查是否过期(20分钟没有被访问)
  51. if time.Since(cache.lastAccessTime) > cache.expireDuration {
  52. return nil, false
  53. }
  54. // 检查是否到达过期时间
  55. if time.Now().After(cache.expireTime) {
  56. return nil, false
  57. }
  58. return cache.data, true
  59. }
  60. // 设置缓存数据
  61. func setCache(data []FieldDictionary) {
  62. initCache()
  63. cache.mutex.Lock()
  64. defer cache.mutex.Unlock()
  65. cache.data = data
  66. cache.lastAccessTime = time.Now()
  67. cache.expireTime = time.Now().Add(cache.expireDuration)
  68. }
  69. // 清除缓存
  70. func clearCache() {
  71. initCache()
  72. cache.mutex.Lock()
  73. defer cache.mutex.Unlock()
  74. cache.data = make([]FieldDictionary, 0)
  75. cache.lastAccessTime = time.Time{}
  76. cache.expireTime = time.Time{}
  77. }
  78. // 更新最后访问时间
  79. func updateLastAccessTime() {
  80. initCache()
  81. cache.mutex.Lock()
  82. defer cache.mutex.Unlock()
  83. cache.lastAccessTime = time.Now()
  84. }
  85. // 从数据库读取字段字典的方法
  86. func getFieldDictionaryFromDB() ([]FieldDictionary, error) {
  87. // TODO: 你来实现这个数据库查询
  88. // 这里返回示例数据用于测试
  89. // 示例查询SQL(根据你的实际表结构调整):
  90. /*
  91. SELECT
  92. table_name_cn,
  93. table_name_en,
  94. field_name_cn,
  95. field_name_en,
  96. field_type,
  97. standard_name,
  98. description,
  99. category,
  100. is_calculated,
  101. aliases
  102. FROM system_field_dictionary
  103. WHERE tenant_id = ? AND is_active = true
  104. ORDER BY category, table_name_cn, field_name_cn
  105. */
  106. return []FieldDictionary{
  107. // 销售相关字段
  108. {
  109. TableNameCN: "销售订单",
  110. TableNameEN: "sales_order",
  111. FieldNameCN: "销售数量",
  112. FieldNameEN: "sales_quantity",
  113. FieldType: "decimal(10,2)",
  114. StandardName: "销售数量",
  115. Description: "销售订单中的商品数量",
  116. Category: "销售",
  117. IsCalculated: false,
  118. Aliases: "销量,销售数",
  119. },
  120. {
  121. TableNameCN: "销售订单",
  122. TableNameEN: "sales_order",
  123. FieldNameCN: "结算单价",
  124. FieldNameEN: "settlement_price",
  125. FieldType: "decimal(10,2)",
  126. StandardName: "结算单价",
  127. Description: "销售结算时的单价",
  128. Category: "销售",
  129. IsCalculated: false,
  130. Aliases: "单价,销售单价",
  131. },
  132. {
  133. TableNameCN: "销售订单",
  134. TableNameEN: "sales_order",
  135. FieldNameCN: "销售金额",
  136. FieldNameEN: "sales_amount",
  137. FieldType: "decimal(10,2)",
  138. StandardName: "销售金额",
  139. Description: "销售总金额(计算字段:销售数量 × 结算单价)",
  140. Category: "销售",
  141. IsCalculated: true,
  142. Aliases: "销售额,销售总额",
  143. },
  144. // 采购相关字段
  145. {
  146. TableNameCN: "采购订单",
  147. TableNameEN: "purchase_order",
  148. FieldNameCN: "采购数量",
  149. FieldNameEN: "purchase_quantity",
  150. FieldType: "decimal(10,2)",
  151. StandardName: "采购数量",
  152. Description: "采购订单中的商品数量",
  153. Category: "采购",
  154. IsCalculated: false,
  155. Aliases: "进货数量,采购数",
  156. },
  157. {
  158. TableNameCN: "采购订单",
  159. TableNameEN: "purchase_order",
  160. FieldNameCN: "采购单价",
  161. FieldNameEN: "purchase_price",
  162. FieldType: "decimal(10,2)",
  163. StandardName: "采购单价",
  164. Description: "采购商品单价",
  165. Category: "采购",
  166. IsCalculated: false,
  167. Aliases: "进价,采购价",
  168. },
  169. // 库存相关字段
  170. {
  171. TableNameCN: "库存表",
  172. TableNameEN: "inventory",
  173. FieldNameCN: "库存数量",
  174. FieldNameEN: "inventory_quantity",
  175. FieldType: "decimal(10,2)",
  176. StandardName: "库存数量",
  177. Description: "当前库存数量",
  178. Category: "库存",
  179. IsCalculated: false,
  180. Aliases: "库存,现存量",
  181. },
  182. }, nil
  183. }
  184. // 加载字段字典(带缓存)
  185. func loadFieldDictionary(refresh bool) ([]FieldDictionary, error) {
  186. // 如果强制刷新,先清除缓存
  187. if refresh {
  188. clearCache()
  189. }
  190. // 尝试从缓存获取
  191. if data, ok := getCache(); ok {
  192. return data, nil
  193. }
  194. // 从数据库加载
  195. dict, err := getFieldDictionaryFromDB()
  196. if err != nil {
  197. return nil, fmt.Errorf("加载字段字典失败: %v", err)
  198. }
  199. // 设置缓存
  200. setCache(dict)
  201. return dict, nil
  202. }
  203. // 查找字段匹配
  204. func findFieldMatch(fieldCN string, dictionary []FieldDictionary) (bool, *FieldDictionary, []string) {
  205. // 精确匹配
  206. for _, dict := range dictionary {
  207. if dict.FieldNameCN == fieldCN {
  208. return true, &dict, nil
  209. }
  210. }
  211. // 别名匹配
  212. var matchedDict *FieldDictionary
  213. for _, dict := range dictionary {
  214. if dict.Aliases != "" {
  215. // 简单的别名匹配(实际使用时你可能需要解析逗号分隔的字符串)
  216. if dict.Aliases == fieldCN {
  217. matchedDict = &dict
  218. break
  219. }
  220. }
  221. }
  222. if matchedDict != nil {
  223. return true, matchedDict, nil
  224. }
  225. // 收集所有字段名作为建议
  226. var suggestions []string
  227. for _, dict := range dictionary {
  228. suggestions = append(suggestions, dict.FieldNameCN)
  229. }
  230. return false, nil, suggestions
  231. }
  232. func init() {
  233. mcp.Register("field_matcher", "根据中文字段名称匹配数据库字段信息,返回英文字段名、表名、类型等详细信息",
  234. map[string]interface{}{
  235. "type": "object",
  236. "properties": map[string]interface{}{
  237. "fields": map[string]interface{}{
  238. "type": "array",
  239. "items": map[string]interface{}{
  240. "type": "string",
  241. },
  242. "description": "要匹配的中文字段名称数组",
  243. "minItems": 1,
  244. },
  245. "refresh_cache": map[string]interface{}{
  246. "type": "boolean",
  247. "description": "是否刷新字段字典缓存",
  248. "default": false,
  249. },
  250. },
  251. "required": []string{"fields"},
  252. },
  253. func(input json.RawMessage, deps *mcp.ToolDependencies) (interface{}, error) {
  254. var params struct {
  255. Fields []string `json:"fields"`
  256. RefreshCache bool `json:"refresh_cache"`
  257. }
  258. if len(input) > 0 {
  259. if err := json.Unmarshal(input, &params); err != nil {
  260. return nil, err
  261. }
  262. }
  263. if len(params.Fields) == 0 {
  264. return nil, fmt.Errorf("fields 参数不能为空")
  265. }
  266. // 加载字段字典(带缓存)
  267. startTime := time.Now()
  268. dictionary, err := loadFieldDictionary(params.RefreshCache)
  269. if err != nil {
  270. return nil, err
  271. }
  272. loadTime := time.Since(startTime)
  273. // 更新缓存最后访问时间
  274. updateLastAccessTime()
  275. // 处理每个字段的匹配结果
  276. matches := make([]map[string]interface{}, 0, len(params.Fields))
  277. foundCount := 0
  278. for _, fieldCN := range params.Fields {
  279. isFound, fieldInfo, suggestions := findFieldMatch(fieldCN, dictionary)
  280. matchResult := map[string]interface{}{
  281. "input_field_cn": fieldCN,
  282. "is_found": isFound,
  283. }
  284. if isFound && fieldInfo != nil {
  285. foundCount++
  286. matchResult["match_info"] = map[string]interface{}{
  287. "table_name_cn": fieldInfo.TableNameCN,
  288. "table_name_en": fieldInfo.TableNameEN,
  289. "field_name_en": fieldInfo.FieldNameEN,
  290. "field_type": fieldInfo.FieldType,
  291. "standard_name": fieldInfo.StandardName,
  292. "description": fieldInfo.Description,
  293. "category": fieldInfo.Category,
  294. "is_calculated": fieldInfo.IsCalculated,
  295. }
  296. } else {
  297. matchResult["suggestions"] = suggestions
  298. matchResult["note"] = "未找到匹配字段,请尝试其他名称或拆解字段"
  299. }
  300. matches = append(matches, matchResult)
  301. }
  302. // 获取缓存信息
  303. cacheData, cacheValid := getCache()
  304. cacheInfo := map[string]interface{}{
  305. "is_valid": cacheValid,
  306. "total_fields": len(cacheData),
  307. }
  308. if cacheValid {
  309. cacheInfo["last_access_time"] = cache.lastAccessTime.Format(time.RFC3339)
  310. cacheInfo["expire_time"] = cache.expireTime.Format(time.RFC3339)
  311. cacheInfo["will_expire_in"] = time.Until(cache.expireTime).String()
  312. }
  313. // 按照示例的返回格式
  314. return map[string]interface{}{
  315. "tenant_id": deps.ReqCtx.TenantID,
  316. "user_id": deps.ReqCtx.UserID,
  317. "input_fields": params.Fields,
  318. "matches": matches,
  319. "summary": map[string]interface{}{
  320. "total_fields": len(params.Fields),
  321. "found_count": foundCount,
  322. "not_found_count": len(params.Fields) - foundCount,
  323. "success_rate": fmt.Sprintf("%.1f%%", float64(foundCount)/float64(len(params.Fields))*100),
  324. },
  325. "cache_info": cacheInfo,
  326. "load_time": loadTime.String(),
  327. "timestamp": time.Now().Format(time.RFC3339),
  328. "suggestion": "如果未找到匹配字段,请尝试将复合字段拆解为ERP常用基本字段再次查询",
  329. }, nil
  330. },
  331. )
  332. }