From 83f3e6601c740f93365fa4f3db76042b1e97e907 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 15 Aug 2025 15:27:58 +0800 Subject: [PATCH] gg --- .gradle/8.8/fileHashes/fileHashes.bin | Bin 36347 -> 36397 bytes .gradle/8.8/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .gradle/buildOutputCleanup/cache.properties | 4 +- .gradle/nb-cache/subprojects.ser | Bin 659 -> 659 bytes .../enson_change/01_create_embedding.sql | 26 + .../modules/embedding/entity/embedding.java | 52 ++ .../embedding/entity/embeddingRepository.java | 6 + .../modules/embedding/req/embeddingReq.java | 47 ++ .../embedding/service/embeddingService.java | 107 ++++ .../embedding/web/embeddingController.java | 56 ++ .../food_nutrients/entity/food_nutrients.java | 17 +- .../food_nutrients/req/food_nutrientsReq.java | 72 +++ .../service/food_nutrientsService.java | 535 ++++++++++++++---- .../web/food_nutrientsController.java | 14 +- .../ffii/fhsmsc/modules/i18n/enity/i18n.java | 63 +++ .../modules/i18n/enity/i18nRepository.java | 7 + .../ffii/fhsmsc/modules/i18n/req/i18nReq.java | 59 ++ .../modules/i18n/service/i18nService.java | 115 ++++ .../modules/i18n/web/i18nController.java | 55 ++ .../req/pet_informationReq.java | 99 ++++ .../service/pet_informationService.java | 177 ++++-- .../web/pet_informationController.java | 5 +- .../enson_change/01_create_embedding.sql | 26 + 24 files changed, 1374 insertions(+), 168 deletions(-) create mode 100644 bin/main/db/changelog/changes/enson_change/01_create_embedding.sql create mode 100644 src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embedding.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embeddingRepository.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/embedding/req/embeddingReq.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/embedding/service/embeddingService.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/embedding/web/embeddingController.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18n.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18nRepository.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/i18n/req/i18nReq.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/i18n/service/i18nService.java create mode 100644 src/main/java/com/ffii/fhsmsc/modules/i18n/web/i18nController.java create mode 100644 src/main/resources/db/changelog/changes/enson_change/01_create_embedding.sql diff --git a/.gradle/8.8/fileHashes/fileHashes.bin b/.gradle/8.8/fileHashes/fileHashes.bin index c1d94a3caa8503b941ec755dec3fef9502f3b7a6..7a0078478f0c634caaf7a72a9e4e410659791a7b 100644 GIT binary patch delta 69 zcmex8n`!MFrVS<%jA@%qB?@Kv8L;_2jk1~6cL0{}by1)u-` literal 17 VcmZQBtK4>L;_2jk1~6bg3;;XS1$O`d diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 408ec5d783b95297e23a26a51950220f7e5f18e7..e2b46248be80ca178ba80f03589a75a1ed9ea522 100644 GIT binary patch literal 17 VcmZP;&{XR?GD++T0~j#M0sti115W?| literal 17 VcmZP;&{XR?GD++T0~jz00RSY=14IA- diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties index 2206be0..45299b8 100644 --- a/.gradle/buildOutputCleanup/cache.properties +++ b/.gradle/buildOutputCleanup/cache.properties @@ -1,2 +1,2 @@ -#Wed Aug 06 17:08:34 HKT 2025 -gradle.version=8.9 +#Mon Aug 11 14:31:25 HKT 2025 +gradle.version=8.8 diff --git a/.gradle/nb-cache/subprojects.ser b/.gradle/nb-cache/subprojects.ser index 7385fda03bda3a80b0e8d4ab882e186e329ad462..fb70ee4ac93294a1a081de66a15ecd4d1ff38e69 100644 GIT binary patch delta 15 XcmbQtI+=CCT9( { + @Column(name = "table_name") + private String table_name; + @Column(name = "record_id") + private Long record_id; + @Column(name = "embedding_text") + private String embedding_text; + @Column(name = "embedding_vector") + private String embedding_vector; + @Column(name = "language") + private String language; + public String getTable_name() { + return table_name; + } + public void setTable_name(String table_name) { + this.table_name = table_name; + } + public Long getRecord_id() { + return record_id; + } + public void setRecord_id(Long record_id) { + this.record_id = record_id; + } + public String getEmbedding_text() { + return embedding_text; + } + public void setEmbedding_text(String embedding_text) { + this.embedding_text = embedding_text; + } + public String getEmbedding_vector() { + return embedding_vector; + } + public void setEmbedding_vector(String embedding_vector) { + this.embedding_vector = embedding_vector; + } + public String getLanguage() { + return language; + } + public void setLanguage(String language) { + this.language = language; + } +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embeddingRepository.java b/src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embeddingRepository.java new file mode 100644 index 0000000..4bf291f --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embeddingRepository.java @@ -0,0 +1,6 @@ +package com.ffii.fhsmsc.modules.embedding.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface embeddingRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/embedding/req/embeddingReq.java b/src/main/java/com/ffii/fhsmsc/modules/embedding/req/embeddingReq.java new file mode 100644 index 0000000..3770efb --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/embedding/req/embeddingReq.java @@ -0,0 +1,47 @@ +package com.ffii.fhsmsc.modules.embedding.req; + +public class embeddingReq { + private Long id; + private String table_name; + private Long record_id; + private String embedding_text; + private String embedding_vector; + private String language; + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public String getTable_name() { + return table_name; + } + public void setTable_name(String table_name) { + this.table_name = table_name; + } + public Long getRecord_id() { + return record_id; + } + public void setRecord_id(Long record_id) { + this.record_id = record_id; + } + public String getEmbedding_text() { + return embedding_text; + } + public void setEmbedding_text(String embedding_text) { + this.embedding_text = embedding_text; + } + public String getEmbedding_vector() { + return embedding_vector; + } + public void setEmbedding_vector(String embedding_vector) { + this.embedding_vector = embedding_vector; + } + public String getLanguage() { + return language; + } + public void setLanguage(String language) { + this.language = language; + } + +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/embedding/service/embeddingService.java b/src/main/java/com/ffii/fhsmsc/modules/embedding/service/embeddingService.java new file mode 100644 index 0000000..41dcf5e --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/embedding/service/embeddingService.java @@ -0,0 +1,107 @@ +package com.ffii.fhsmsc.modules.embedding.service; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ffii.core.exception.InternalServerErrorException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; +import com.ffii.fhsmsc.modules.embedding.entity.embedding; +import com.ffii.fhsmsc.modules.embedding.entity.embeddingRepository; +import com.ffii.fhsmsc.modules.embedding.req.embeddingReq; + +import jakarta.validation.Valid; + +@Service +public class embeddingService extends AbstractBaseEntityService{ + private static final Logger logger = LoggerFactory.getLogger(embeddingService.class); + public embeddingService(JdbcDao jdbcDao, embeddingRepository repository) { + super(jdbcDao, repository); + } + + + public List> search(Map args) { + logger.info("Search called with args: {}", args); + StringBuilder sql = new StringBuilder(); + + // Check if we need to fetch records for embedding processing + if (args != null && args.containsKey("embedding_text")) { + sql.append("SELECT pi.id,pi.record_id, pi.embedding_text"); + } else if (args != null && args.containsKey("embedding_vector")) { + sql.append("SELECT pi.id, pi.record_id, pi.embedding_vector"); + } else { + // Normal case - return all fields except embeddings + sql.append("SELECT" + + " pi.id," + + " pi.table_name," + + " pi.record_id," + + " pi.embedding_text," + + " pi.embedding_vector," + + " pi.language"); + } + + sql.append(" FROM embedding pi") + .append(" WHERE pi.deleted = 0"); // 使用 0,因为是 tinyint(1) + + if (args != null) { + if (args.containsKey("table_name")) { + sql.append(" AND pi.table_name = :table_name"); + logger.info("Added table_name filter: {}", args.get("table_name")); + } + if (args.containsKey("id")) { + sql.append(" AND pi.id = :id"); + logger.info("Added id filter: {}", args.get("id")); + } + if (args.containsKey("language")) { + sql.append(" AND pi.language = :language"); + logger.info("Added language filter: {}", args.get("language")); + } + } + + return jdbcDao.queryForList(sql.toString(), args); + } + @Transactional(rollbackFor = Exception.class) + public embedding saveOrUpdate(@Valid embeddingReq req) { + embedding instance; + + if (req.getId() != null && req.getId() > 0) { + // 更新现有记录 + instance = find(req.getId()).orElseThrow(InternalServerErrorException::new); + + // 只更新非空字段 + if (req.getTable_name() != null) { + instance.setTable_name(req.getTable_name()); + } + if (req.getRecord_id() != null) { + instance.setRecord_id(req.getRecord_id()); + } + if (req.getEmbedding_text() != null) { + instance.setEmbedding_text(req.getEmbedding_text()); + } + if (req.getEmbedding_vector() != null) { + instance.setEmbedding_vector(req.getEmbedding_vector()); + } + if (req.getLanguage() != null) { + instance.setLanguage(req.getLanguage()); + } + + + } else { + // 创建新记录 + instance = new embedding(); + BeanUtils.copyProperties(req, instance); + } + + saveAndFlush(instance); + return instance; + + } + + +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/embedding/web/embeddingController.java b/src/main/java/com/ffii/fhsmsc/modules/embedding/web/embeddingController.java new file mode 100644 index 0000000..58dc9f0 --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/embedding/web/embeddingController.java @@ -0,0 +1,56 @@ +package com.ffii.fhsmsc.modules.embedding.web; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.fhsmsc.modules.embedding.req.embeddingReq; +import com.ffii.fhsmsc.modules.embedding.service.embeddingService; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/embedding") +public class embeddingController { + private static final Logger logger = LoggerFactory.getLogger(embeddingController.class); + private embeddingService embeddingService; + + public embeddingController( + embeddingService embeddingService + ) { + this.embeddingService = embeddingService; + } + + @GetMapping("/list") + public RecordsRes> listJson(HttpServletRequest request) throws ServletRequestBindingException { + logger.info("Received list request with parameters: {}", request.getParameterMap()); + Map args = CriteriaArgsBuilder.withRequest(request) + .addString("table_name") + .addBoolean("embedding_text") // Add embedding_text parameter + .addBoolean("embedding_vector") + .addLong("record_id") + .addInteger("id") + .addString("language") + .build(); + logger.info("Built args: {}", args); + return new RecordsRes<>(embeddingService.search(args)); + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid embeddingReq req) { + return new IdRes(embeddingService.saveOrUpdate(req).getId()); + } + + +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/entity/food_nutrients.java b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/entity/food_nutrients.java index 3463fa8..8bba399 100644 --- a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/entity/food_nutrients.java +++ b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/entity/food_nutrients.java @@ -55,10 +55,6 @@ public class food_nutrients extends BaseEntity { private String target_age_category; @Column(name = "pet") private String pet; - @Column(name = "embedding_text") - private String embedding_text; - @Column(name = "embedding_vector") - private String embedding_vector; @Column(name = "description") private String description; @@ -200,18 +196,7 @@ public class food_nutrients extends BaseEntity { public void setPet(String pet) { this.pet = pet; } - public String getEmbedding_text() { - return embedding_text; - } - public void setEmbedding_text(String embedding_text) { - this.embedding_text = embedding_text; - } - public String getEmbedding_vector() { - return embedding_vector; - } - public void setEmbedding_vector(String embedding_vector) { - this.embedding_vector = embedding_vector; - } + public String getDescription() { return description; } diff --git a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/req/food_nutrientsReq.java b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/req/food_nutrientsReq.java index 554b9f8..f8221c2 100644 --- a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/req/food_nutrientsReq.java +++ b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/req/food_nutrientsReq.java @@ -6,8 +6,12 @@ public class food_nutrientsReq { private Long id; @Column(name = "food_name") private String food_name; + @Column + private String food_name_zh; @Column(name = "brand") private String brand; + @Column + private String brand_zh; @Column(name = "size_kg") private Double size_kg; @Column(name = "protein_percent") @@ -46,16 +50,30 @@ public class food_nutrientsReq { private Integer calories_kcal_kg; @Column(name = "food_type") private String food_type; + @Column(name = "food_type_zh") + private String food_type_zh; @Column(name = "target_age_category") private String target_age_category; + @Column(name = "target_age_category_zh") + private String target_age_category_zh; @Column(name = "pet") private String pet; + @Column(name = "pet_zh") + private String pet_zh; @Column(name = "embedding_text") private String embedding_text; @Column(name = "embedding_vector") private String embedding_vector; @Column(name = "description") private String description; + @Column(name = "description_zh") + private String description_zh; + @Column(name = "embedding_text_zh") + private String embedding_text_zh; + @Column(name = "embedding_vector_zh") + private String embedding_vector_zh; + @Column(name = "language") + private String language; public Long getId() { return id; @@ -219,4 +237,58 @@ public class food_nutrientsReq { public void setDescription(String description) { this.description = description; } + public String getFood_name_zh() { + return food_name_zh; + } + public void setFood_name_zh(String food_name_zh) { + this.food_name_zh = food_name_zh; + } + public String getBrand_zh() { + return brand_zh; + } + public void setBrand_zh(String brand_zh) { + this.brand_zh = brand_zh; + } + public String getFood_type_zh() { + return food_type_zh; + } + public void setFood_type_zh(String food_type_zh) { + this.food_type_zh = food_type_zh; + } + public String getTarget_age_category_zh() { + return target_age_category_zh; + } + public void setTarget_age_category_zh(String target_age_category_zh) { + this.target_age_category_zh = target_age_category_zh; + } + public String getPet_zh() { + return pet_zh; + } + public void setPet_zh(String pet_zh) { + this.pet_zh = pet_zh; + } + public String getDescription_zh() { + return description_zh; + } + public void setDescription_zh(String description_zh) { + this.description_zh = description_zh; + } + public String getEmbedding_text_zh() { + return embedding_text_zh; + } + public void setEmbedding_text_zh(String embedding_text_zh) { + this.embedding_text_zh = embedding_text_zh; + } + public String getEmbedding_vector_zh() { + return embedding_vector_zh; + } + public void setEmbedding_vector_zh(String embedding_vector_zh) { + this.embedding_vector_zh = embedding_vector_zh; + } + public String getLanguage() { + return language; + } + public void setLanguage(String language) { + this.language = language; + } } diff --git a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/service/food_nutrientsService.java b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/service/food_nutrientsService.java index cd033c3..8b67be5 100644 --- a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/service/food_nutrientsService.java +++ b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/service/food_nutrientsService.java @@ -1,5 +1,6 @@ package com.ffii.fhsmsc.modules.food_nutrients.service; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -12,31 +13,45 @@ import com.ffii.core.exception.InternalServerErrorException; import com.ffii.core.support.AbstractBaseEntityService; import com.ffii.core.support.JdbcDao; import com.ffii.core.utils.BeanUtils; +import com.ffii.fhsmsc.modules.embedding.req.embeddingReq; +import com.ffii.fhsmsc.modules.embedding.service.embeddingService; import com.ffii.fhsmsc.modules.food_nutrients.entity.food_nutrients; import com.ffii.fhsmsc.modules.food_nutrients.entity.food_nutrientsRepository; import com.ffii.fhsmsc.modules.food_nutrients.req.food_nutrientsReq; +import com.ffii.fhsmsc.modules.i18n.req.i18nReq; +import com.ffii.fhsmsc.modules.i18n.service.i18nService; import jakarta.validation.Valid; @Service -public class food_nutrientsService extends AbstractBaseEntityService{ +public class food_nutrientsService extends AbstractBaseEntityService { private static final Logger logger = LoggerFactory.getLogger(food_nutrientsService.class); - - public food_nutrientsService(JdbcDao jdbcDao, food_nutrientsRepository repository) { - super(jdbcDao, repository); - } + private final i18nService i18nService; + private final embeddingService embeddingService; + + public food_nutrientsService(JdbcDao jdbcDao, food_nutrientsRepository repository, i18nService i18nService, embeddingService embeddingService) { + super(jdbcDao, repository); + this.i18nService = i18nService; + this.embeddingService = embeddingService; + } public List> search(Map args) { logger.info("Search called with args: {}", args); StringBuilder sql = new StringBuilder(); - - // Check if we need to fetch records for embedding processing + boolean useEmbeddingTable = false; + + // Check if we need to fetch specific fields from the embedding table if (args != null && args.containsKey("embedding_text")) { - sql.append("SELECT pi.id, pi.embedding_text"); + sql.append("SELECT pi.id, e.embedding_text"); + useEmbeddingTable = true; } else if (args != null && args.containsKey("embedding_vector")) { - sql.append("SELECT pi.id, pi.embedding_vector"); + sql.append("SELECT pi.id, e.embedding_vector"); + useEmbeddingTable = true; + } else if (args != null && args.containsKey("embedding")) { + sql.append("SELECT pi.id, e.embedding_text, e.embedding_vector"); + useEmbeddingTable = true; } else { - // Normal case - return all fields except embeddings + // Normal case - return all fields from food_nutrients sql.append("SELECT" + " pi.id," + " pi.food_name," @@ -64,10 +79,16 @@ public class food_nutrientsService extends AbstractBaseEntityService> result = jdbcDao.queryForList(sql.toString(), args); logger.info("Query returned {} results", result.size()); + + // Apply i18n for food_nutrients fields if no embedding parameters are specified + if (args != null && args.containsKey("language") && args.get("language") != null && !useEmbeddingTable) { + String language = (String) args.get("language"); + logger.info("Adding i18n support for language: {}", language); + + String[] translatableFields = { + "food_name", "brand", "food_type", "target_age_category", "pet", "description" + }; + + for (Map record : result) { + Object idObj = record.get("id"); + Long id = null; + if (idObj instanceof Integer) { + id = ((Integer) idObj).longValue(); + } else if (idObj instanceof Long) { + id = (Long) idObj; + } + if (id != null) { + Map i18nArgs = new HashMap<>(); + i18nArgs.put("table_name", "food_nutrients"); + i18nArgs.put("record_id", id); + i18nArgs.put("language", language); + + for (String field : translatableFields) { + i18nArgs.put("field_name", field); + List> translations = i18nService.search(i18nArgs); + if (translations != null && !translations.isEmpty()) { + String translated = (String) translations.get(0).get("value"); + if (translated != null) { + record.put(field, translated); + } + } + } + } + } + } + return result; } @Transactional(rollbackFor = Exception.class) - public food_nutrients saveOrUpdate(@Valid food_nutrientsReq req) { - food_nutrients instance; - - if (req.getId() != null && req.getId() > 0) { - // 更新现有记录 - instance = find(req.getId()).orElseThrow(InternalServerErrorException::new); - - // 只更新非空字段 - if (req.getFood_name() != null) { - instance.setFood_name(req.getFood_name()); - } - if (req.getBrand() != null) { - instance.setBrand(req.getBrand()); - } - if (req.getSize_kg() != null) { - instance.setSize_kg(req.getSize_kg()); - } - if (req.getProtein_percent() != null) { - instance.setProtein_percent(req.getProtein_percent()); - } - if (req.getFat_percent() != null) { - instance.setFat_percent(req.getFat_percent()); - } - if (req.getFibre_percent() != null) { - instance.setFibre_percent(req.getFibre_percent()); - } - if (req.getAsh_percent() != null) { - instance.setAsh_percent(req.getAsh_percent()); - } - if (req.getTaurine_percent() != null) { - instance.setTaurine_percent(req.getTaurine_percent()); - } - if (req.getVitamin_a_iu_kg() != null) { - instance.setVitamin_a_iu_kg(req.getVitamin_a_iu_kg()); - } - if (req.getVitamin_d_iu_kg() != null) { - instance.setVitamin_d_iu_kg(req.getVitamin_d_iu_kg()); - } - if (req.getVitamin_e_mg_kg() != null) { - instance.setVitamin_e_mg_kg(req.getVitamin_e_mg_kg()); - } - if (req.getPhosphorus_percent() != null) { - instance.setPhosphorus_percent(req.getPhosphorus_percent()); - } - if (req.getCalcium_percent() != null) { - instance.setCalcium_percent(req.getCalcium_percent()); - } - if (req.getMagnesium_percent() != null) { - instance.setMagnesium_percent(req.getMagnesium_percent()); - } - if (req.getMethionine_percent() != null) { - instance.setMethionine_percent(req.getMethionine_percent()); - } - if (req.getMoisture_percent() != null) { - instance.setMoisture_percent(req.getMoisture_percent()); - } - if (req.getOmega_3_mg_kg() != null) { - instance.setOmega_3_mg_kg(req.getOmega_3_mg_kg()); - } - if (req.getOmega_6_mg_kg() != null) { - instance.setOmega_6_mg_kg(req.getOmega_6_mg_kg()); - } - if (req.getVitamin_c_mg_kg() != null) { - instance.setVitamin_c_mg_kg(req.getVitamin_c_mg_kg()); - } - if (req.getCalories_kcal_kg() != null) { - instance.setCalories_kcal_kg(req.getCalories_kcal_kg()); - } - if (req.getFood_type() != null) { - instance.setFood_type(req.getFood_type()); - } - if (req.getTarget_age_category() != null) { - instance.setTarget_age_category(req.getTarget_age_category()); - } - if (req.getPet() != null) { - instance.setPet(req.getPet()); - } - if (req.getEmbedding_text() != null) { - instance.setEmbedding_text(req.getEmbedding_text()); - } - if (req.getEmbedding_vector() != null) { - instance.setEmbedding_vector(req.getEmbedding_vector()); - } - if (req.getDescription() != null) { - instance.setDescription(req.getDescription()); - } - } else { - // 创建新记录 - instance = new food_nutrients(); - BeanUtils.copyProperties(req, instance); - } - - saveAndFlush(instance); - return instance; - } -} + public food_nutrients saveOrUpdate(@Valid food_nutrientsReq req) { + food_nutrients instance; + String targetLanguage = req.getLanguage(); // 新增:获取目标语言 + + if (req.getId() != null && req.getId() > 0) { + instance = find(req.getId()).orElseThrow(InternalServerErrorException::new); + + // 检查是否需要语言参数 + boolean needsLanguage = req.getFood_name() != null || req.getBrand() != null || + req.getFood_type() != null || req.getTarget_age_category() != null || + req.getPet() != null || req.getDescription() != null; + + if (needsLanguage && (targetLanguage == null || targetLanguage.trim().isEmpty())) { + throw new IllegalArgumentException("Language parameter is required when updating translatable fields (food_name, brand, food_type, target_age_category, pet, description)"); + } + + // 记录原始值用于检测变化 + String originalFoodName = instance.getFood_name(); + String originalBrand = instance.getBrand(); + + if (req.getFood_name() != null) { + instance.setFood_name(req.getFood_name()); + } + if (req.getBrand() != null) { + instance.setBrand(req.getBrand()); + } + if (req.getSize_kg() != null) { + instance.setSize_kg(req.getSize_kg()); + } + if (req.getProtein_percent() != null) { + instance.setProtein_percent(req.getProtein_percent()); + } + if (req.getFat_percent() != null) { + instance.setFat_percent(req.getFat_percent()); + } + if (req.getFibre_percent() != null) { + instance.setFibre_percent(req.getFibre_percent()); + } + if (req.getAsh_percent() != null) { + instance.setAsh_percent(req.getAsh_percent()); + } + if (req.getTaurine_percent() != null) { + instance.setTaurine_percent(req.getTaurine_percent()); + } + if (req.getVitamin_a_iu_kg() != null) { + instance.setVitamin_a_iu_kg(req.getVitamin_a_iu_kg()); + } + if (req.getVitamin_d_iu_kg() != null) { + instance.setVitamin_d_iu_kg(req.getVitamin_d_iu_kg()); + } + if (req.getVitamin_e_mg_kg() != null) { + instance.setVitamin_e_mg_kg(req.getVitamin_e_mg_kg()); + } + if (req.getPhosphorus_percent() != null) { + instance.setPhosphorus_percent(req.getPhosphorus_percent()); + } + if (req.getCalcium_percent() != null) { + instance.setCalcium_percent(req.getCalcium_percent()); + } + if (req.getMagnesium_percent() != null) { + instance.setMagnesium_percent(req.getMagnesium_percent()); + } + if (req.getMethionine_percent() != null) { + instance.setMethionine_percent(req.getMethionine_percent()); + } + if (req.getMoisture_percent() != null) { + instance.setMoisture_percent(req.getMoisture_percent()); + } + if (req.getOmega_3_mg_kg() != null) { + instance.setOmega_3_mg_kg(req.getOmega_3_mg_kg()); + } + if (req.getOmega_6_mg_kg() != null) { + instance.setOmega_6_mg_kg(req.getOmega_6_mg_kg()); + } + if (req.getVitamin_c_mg_kg() != null) { + instance.setVitamin_c_mg_kg(req.getVitamin_c_mg_kg()); + } + if (req.getCalories_kcal_kg() != null) { + instance.setCalories_kcal_kg(req.getCalories_kcal_kg()); + } + if (req.getFood_type() != null) { + instance.setFood_type(req.getFood_type()); + } + if (req.getTarget_age_category() != null) { + instance.setTarget_age_category(req.getTarget_age_category()); + } + if (req.getPet() != null) { + instance.setPet(req.getPet()); + } + if (req.getDescription() != null) { + instance.setDescription(req.getDescription()); + } + + saveAndFlush(instance); + + Long recordId = instance.getId(); + final String table = "food_nutrients"; + + // 更新i18n表 - 使用指定的语言 + if (targetLanguage != null) { + if (req.getFood_name() != null) upsertI18n(table, recordId, "food_name", targetLanguage, req.getFood_name()); + if (req.getBrand() != null) upsertI18n(table, recordId, "brand", targetLanguage, req.getBrand()); + if (req.getFood_type() != null) upsertI18n(table, recordId, "food_type", targetLanguage, req.getFood_type()); + if (req.getTarget_age_category() != null) upsertI18n(table, recordId, "target_age_category", targetLanguage, req.getTarget_age_category()); + if (req.getPet() != null) upsertI18n(table, recordId, "pet", targetLanguage, req.getPet()); + if (req.getDescription() != null) upsertI18n(table, recordId, "description", targetLanguage, req.getDescription()); + } + + // 当food_name或brand有更新时,更新embedding + if (req.getFood_name() != null || req.getBrand() != null) { + String currentFoodName = req.getFood_name() != null ? req.getFood_name() : originalFoodName; + String currentBrand = req.getBrand() != null ? req.getBrand() : originalBrand; + updateEmbeddingForFoodChange(recordId, currentFoodName, currentBrand, targetLanguage); + } + + } else { + // 新建记录 + if (targetLanguage == null || targetLanguage.trim().isEmpty()) { + throw new IllegalArgumentException("Language parameter is required when creating new food nutrient record"); + } + + instance = new food_nutrients(); + BeanUtils.copyProperties(req, instance); + saveAndFlush(instance); + + Long recordId = instance.getId(); + final String table = "food_nutrients"; + + // 保存i18n翻译 + if (req.getFood_name() != null) upsertI18n(table, recordId, "food_name", targetLanguage, req.getFood_name()); + if (req.getBrand() != null) upsertI18n(table, recordId, "brand", targetLanguage, req.getBrand()); + if (req.getFood_type() != null) upsertI18n(table, recordId, "food_type", targetLanguage, req.getFood_type()); + if (req.getTarget_age_category() != null) upsertI18n(table, recordId, "target_age_category", targetLanguage, req.getTarget_age_category()); + if (req.getPet() != null) upsertI18n(table, recordId, "pet", targetLanguage, req.getPet()); + if (req.getDescription() != null) upsertI18n(table, recordId, "description", targetLanguage, req.getDescription()); + + // 为新建记录创建embedding + if (req.getFood_name() != null || req.getBrand() != null) { + updateEmbeddingForFoodChange(recordId, req.getFood_name(), req.getBrand(), targetLanguage); + } + } + + return instance; + } + + /** + * 当food_name或brand发生变化时更新embedding表 + */ + private void updateEmbeddingForFoodChange(Long recordId, String foodName, String brand, String language) { + try { + logger.info("Starting updateEmbeddingForFoodChange - recordId: {}, foodName: {}, brand: {}, language: {}", + recordId, foodName, brand, language); + + // 构建embedding文本 + StringBuilder embeddingText = new StringBuilder(); + + // 添加food_name(如果传入的话) + if (foodName != null && !foodName.trim().isEmpty()) { + embeddingText.append(foodName); + logger.info("Added foodName to embedding text: '{}'", foodName); + } + + // 从i18n表获取对应语言的brand值 + String brandFromI18n = getBrandFromI18n(recordId, language); + logger.info("Retrieved brand from i18n: '{}'", brandFromI18n); + + if (brandFromI18n != null && !brandFromI18n.trim().isEmpty()) { + if (embeddingText.length() > 0) { + embeddingText.append(" "); + } + embeddingText.append(brandFromI18n); + logger.info("Added brandFromI18n to embedding text: '{}'", brandFromI18n); + } + + // 移除这部分:不再添加传入的brand,避免重复 + // if (brand != null && !brand.trim().isEmpty()) { + // if (embeddingText.length() > 0) { + // embeddingText.append(" "); + // } + // embeddingText.append(brand); + // logger.info("Added brand to embedding text: '{}'", brand); + // } + + logger.info("Final embedding text: '{}'", embeddingText.toString()); + + if (embeddingText.length() > 0) { + // 查找现有的embedding记录 + Map eArgs = new HashMap<>(); + eArgs.put("table_name", "food_nutrients"); + eArgs.put("record_id", recordId); + eArgs.put("language", language); + + List> existing = embeddingService.search(eArgs); + logger.info("Found {} existing embedding records", existing.size()); + + embeddingReq eReq = new embeddingReq(); + if (!existing.isEmpty()) { + Object idObj = existing.get(0).get("id"); + Long id = null; + if (idObj instanceof Integer) id = ((Integer) idObj).longValue(); + else if (idObj instanceof Long) id = (Long) idObj; + if (id != null) { + eReq.setId(id); + logger.info("Updating existing embedding record with ID: {}", id); + } + } else { + logger.info("Creating new embedding record"); + } + + eReq.setTable_name("food_nutrients"); + eReq.setRecord_id(recordId); + eReq.setEmbedding_text(embeddingText.toString()); + eReq.setLanguage(language); + + logger.info("Saving embedding with text: '{}'", eReq.getEmbedding_text()); + embeddingService.saveOrUpdate(eReq); + logger.info("Successfully updated embedding for record ID: {} with text: '{}' in language: {}", + recordId, embeddingText.toString(), language); + } else { + logger.warn("No embedding text generated for record ID: {}", recordId); + } + } catch (Exception e) { + logger.error("Error updating embedding for record ID: " + recordId, e); + // 不抛出异常,避免影响主流程 + } + } + + /** + * 从i18n表获取指定记录的brand翻译值 + */ + private String getBrandFromI18n(Long recordId, String language) { + try { + logger.info("Getting brand from i18n for recordId: {}, language: {}", recordId, language); + + Map i18nArgs = new HashMap<>(); + i18nArgs.put("table_name", "food_nutrients"); + i18nArgs.put("field_name", "brand"); + i18nArgs.put("record_id", recordId); + i18nArgs.put("language", language); + + List> translations = i18nService.search(i18nArgs); + logger.info("Found {} brand translations", translations.size()); + + if (translations != null && !translations.isEmpty()) { + String translated = (String) translations.get(0).get("value"); + logger.info("Brand translation value: '{}'", translated); + return translated; + } else { + logger.info("No brand translation found"); + } + } catch (Exception e) { + logger.error("Error getting brand from i18n for record ID: " + recordId + ", language: " + language, e); + } + return null; + } + + private void upsertI18n(String tableName, Long recordId, String fieldName, String language, String value) { + if (recordId == null || value == null) return; + + Map findArgs = new HashMap<>(); + findArgs.put("table_name", tableName); + findArgs.put("field_name", fieldName); + findArgs.put("record_id", recordId); + findArgs.put("language", language); + + List> rows = i18nService.search(findArgs); + + i18nReq ireq = new i18nReq(); + if (rows != null && !rows.isEmpty()) { + Object idObj = rows.get(0).get("id"); + Long id = null; + if (idObj instanceof Integer) id = ((Integer) idObj).longValue(); + else if (idObj instanceof Long) id = (Long) idObj; + if (id != null) ireq.setId(id); + } + ireq.setTable_name(tableName); + ireq.setField_name(fieldName); + ireq.setRecord_id(recordId); + ireq.setLanguage(language); + ireq.setValue(value); + + i18nService.saveOrUpdate(ireq); + } + + public List> checkEmbeddingStatus(Map args) { + logger.info("Check embedding status called with args: {}", args); + StringBuilder sql = new StringBuilder(); + + sql.append("SELECT" + + " fn.id," + + " e_en.embedding_text as embedding_text_en," + + " e_en.embedding_vector as embedding_vector_en," + + " e_zh.embedding_text as embedding_text_zh," + + " e_zh.embedding_vector as embedding_vector_zh"); + + sql.append(" FROM food_nutrients fn") + .append(" LEFT JOIN embedding e_en ON e_en.table_name = 'food_nutrients' AND e_en.record_id = fn.id AND e_en.language = 'en' AND e_en.deleted = 0") + .append(" LEFT JOIN embedding e_zh ON e_zh.table_name = 'food_nutrients' AND e_zh.record_id = fn.id AND e_zh.language = 'zh' AND e_zh.deleted = 0") + .append(" WHERE fn.deleted = 0"); + + if (args != null) { + if (args.containsKey("language")) { + String language = (String) args.get("language"); + if ("zh".equals(language)) { + sql = new StringBuilder(); + sql.append("SELECT" + + " fn.id," + + " e_zh.embedding_text as embedding_text_zh," + + " e_zh.embedding_vector as embedding_vector_zh"); + sql.append(" FROM food_nutrients fn") + .append(" LEFT JOIN embedding e_zh ON e_zh.table_name = 'food_nutrients' AND e_zh.record_id = fn.id AND e_zh.language = 'zh' AND e_zh.deleted = 0") + .append(" WHERE fn.deleted = 0"); + } else if ("en".equals(language)) { + sql = new StringBuilder(); + sql.append("SELECT" + + " fn.id," + + " e_en.embedding_text as embedding_text_en," + + " e_en.embedding_vector as embedding_vector_en"); + sql.append(" FROM food_nutrients fn") + .append(" LEFT JOIN embedding e_en ON e_en.table_name = 'food_nutrients' AND e_en.record_id = fn.id AND e_en.language = 'en' AND e_en.deleted = 0") + .append(" WHERE fn.deleted = 0"); + } + } + if (args.containsKey("id")) { + sql.append(" AND fn.id = :id"); + logger.info("Added id filter: {}", args.get("id")); + } + } + + sql.append(" ORDER BY fn.id"); + logger.info("Final SQL query: {}", sql.toString()); + + List> result = jdbcDao.queryForList(sql.toString(), args); + logger.info("Query returned {} results", result.size()); + + // Convert null values to false and non-null values to true + for (Map record : result) { + for (String key : record.keySet()) { + if (key.startsWith("embedding_")) { + Object value = record.get(key); + record.put(key, value != null && !value.toString().trim().isEmpty()); + } + } + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/web/food_nutrientsController.java b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/web/food_nutrientsController.java index 2f4bd6d..2c0cbcb 100644 --- a/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/web/food_nutrientsController.java +++ b/src/main/java/com/ffii/fhsmsc/modules/food_nutrients/web/food_nutrientsController.java @@ -37,9 +37,12 @@ public class food_nutrientsController { logger.info("Received list request with parameters: {}", request.getParameterMap()); Map args = CriteriaArgsBuilder.withRequest(request) .addString("food_name") + .addBoolean("embedding") .addBoolean("embedding_text") // Add embedding_text parameter .addBoolean("embedding_vector") .addInteger("id") + .addString("language") + .build(); logger.info("Built args: {}", args); return new RecordsRes<>(food_nutrientsService.search(args)); @@ -50,5 +53,14 @@ public class food_nutrientsController { return new IdRes(food_nutrientsService.saveOrUpdate(req).getId()); } - + @GetMapping("/embedding-status") + public RecordsRes> checkEmbeddingStatus(HttpServletRequest request) throws ServletRequestBindingException { + logger.info("Received embedding status request with parameters: {}", request.getParameterMap()); + Map args = CriteriaArgsBuilder.withRequest(request) + .addString("language") + .addInteger("id") + .build(); + logger.info("Built args: {}", args); + return new RecordsRes<>(food_nutrientsService.checkEmbeddingStatus(args)); + } } diff --git a/src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18n.java b/src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18n.java new file mode 100644 index 0000000..87b3c0d --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18n.java @@ -0,0 +1,63 @@ +package com.ffii.fhsmsc.modules.i18n.enity; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +@Entity +@Table(name = "i18n") +public class i18n extends BaseEntity { + @Column(name = "table_name") + private String table_name; + @Column(name = "field_name") + private String field_name; + @Column(name = "record_id") + private Long record_id; + @Column(name = "language") + private String language; + @Column(name = "value") + private String value; + + public String getTable_name() { + return table_name; + } + + public void setTable_name(String table_name) { + this.table_name = table_name; + } + + public String getField_name() { + return field_name; + } + + public void setField_name(String field_name) { + this.field_name = field_name; + } + + public Long getRecord_id() { + return record_id; + } + + public void setRecord_id(Long record_id) { + this.record_id = record_id; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18nRepository.java b/src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18nRepository.java new file mode 100644 index 0000000..cf4738c --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18nRepository.java @@ -0,0 +1,7 @@ +package com.ffii.fhsmsc.modules.i18n.enity; + +import com.ffii.core.support.AbstractRepository; + +public interface i18nRepository extends AbstractRepository { + +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/i18n/req/i18nReq.java b/src/main/java/com/ffii/fhsmsc/modules/i18n/req/i18nReq.java new file mode 100644 index 0000000..2856a83 --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/i18n/req/i18nReq.java @@ -0,0 +1,59 @@ +package com.ffii.fhsmsc.modules.i18n.req; + + +public class i18nReq { + private Long id; + private String table_name; + private String field_name; + private Long record_id; + private String language; + private String value; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTable_name() { + return table_name; + } + + public void setTable_name(String table_name) { + this.table_name = table_name; + } + + public String getField_name() { + return field_name; + } + + public void setField_name(String field_name) { + this.field_name = field_name; + } + + public Long getRecord_id() { + return record_id; + } + + public void setRecord_id(Long record_id) { + this.record_id = record_id; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/i18n/service/i18nService.java b/src/main/java/com/ffii/fhsmsc/modules/i18n/service/i18nService.java new file mode 100644 index 0000000..7a2d1ec --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/i18n/service/i18nService.java @@ -0,0 +1,115 @@ +package com.ffii.fhsmsc.modules.i18n.service; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ffii.core.exception.InternalServerErrorException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; +import com.ffii.fhsmsc.modules.i18n.enity.i18n; +import com.ffii.fhsmsc.modules.i18n.enity.i18nRepository; +import com.ffii.fhsmsc.modules.i18n.req.i18nReq; + +import jakarta.validation.Valid; + +@Service +public class i18nService extends AbstractBaseEntityService{ + private static final Logger logger = LoggerFactory.getLogger(i18nService.class); + + public i18nService(JdbcDao jdbcDao, i18nRepository repository) { + super(jdbcDao, repository); + } + + public List> search(Map args) { + logger.info("Search called with args: {}", args); + StringBuilder sql = new StringBuilder(); + + // Always select all fields needed for translation + sql.append("SELECT" + + " pi.id," + + " pi.table_name," + + " pi.field_name," + + " pi.record_id," + + " pi.language," + + " pi.value"); + + sql.append(" FROM i18n pi") + .append(" WHERE pi.deleted = 0"); // 使用 0,因为是 tinyint(1) + + if (args != null) { + if (args.containsKey("table_name")) { + sql.append(" AND pi.table_name = :table_name"); + logger.info("Added table_name filter: {}", args.get("table_name")); + } + if (args.containsKey("id")) { + sql.append(" AND pi.id = :id"); + logger.info("Added id filter: {}", args.get("id")); + } + if (args.containsKey("field_name")) { + sql.append(" AND pi.field_name = :field_name"); + logger.info("Added field_name filter: {}", args.get("field_name")); + } + if (args.containsKey("record_id")) { + sql.append(" AND pi.record_id = :record_id"); + logger.info("Added record_id filter: {}", args.get("record_id")); + } + if (args.containsKey("language")) { + sql.append(" AND pi.language = :language"); + logger.info("Added language filter: {}", args.get("language")); + } + if (args.containsKey("value")) { + sql.append(" AND pi.value = :value"); + logger.info("Added value filter: {}", args.get("value")); + } + } + + sql.append(" ORDER BY pi.id"); + logger.info("Final SQL query: {}", sql.toString()); + + List> result = jdbcDao.queryForList(sql.toString(), args); + logger.info("Query returned {} results", result.size()); + return result; + } + + @Transactional(rollbackFor = Exception.class) + public i18n saveOrUpdate(@Valid i18nReq req) { + i18n instance; + + if (req.getId() != null && req.getId() > 0) { + // 更新现有记录 + instance = find(req.getId()).orElseThrow(InternalServerErrorException::new); + + // 只更新非空字段 + if (req.getTable_name() != null) { + instance.setTable_name(req.getTable_name()); + } + if (req.getField_name() != null) { + instance.setField_name(req.getField_name()); + } + if (req.getRecord_id() != null) { + instance.setRecord_id(req.getRecord_id()); + } + if (req.getLanguage() != null) { + instance.setLanguage(req.getLanguage()); + } + if (req.getValue() != null) { + instance.setValue(req.getValue()); + } + + } else { + // 创建新记录 + instance = new i18n(); + BeanUtils.copyProperties(req, instance); + } + + saveAndFlush(instance); + return instance; + } +} + diff --git a/src/main/java/com/ffii/fhsmsc/modules/i18n/web/i18nController.java b/src/main/java/com/ffii/fhsmsc/modules/i18n/web/i18nController.java new file mode 100644 index 0000000..8f69ef0 --- /dev/null +++ b/src/main/java/com/ffii/fhsmsc/modules/i18n/web/i18nController.java @@ -0,0 +1,55 @@ +package com.ffii.fhsmsc.modules.i18n.web; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.fhsmsc.modules.i18n.req.i18nReq; +import com.ffii.fhsmsc.modules.i18n.service.i18nService; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/i18n") +public class i18nController { + private static final Logger logger = LoggerFactory.getLogger(i18nController.class); + private i18nService i18nService; + + public i18nController( + i18nService i18nService + ) { + this.i18nService = i18nService; + } + + @GetMapping("/list") + public RecordsRes> listJson(HttpServletRequest request) throws ServletRequestBindingException { + logger.info("Received list request with parameters: {}", request.getParameterMap()); + Map args = CriteriaArgsBuilder.withRequest(request) + .addString("table_name") + .addString("field_name") + .addLong("record_id") + .addString("language") + .addInteger("id") + .build(); + logger.info("Built args: {}", args); + return new RecordsRes<>(i18nService.search(args)); + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid i18nReq req) { + return new IdRes(i18nService.saveOrUpdate(req).getId()); + } + + +} diff --git a/src/main/java/com/ffii/fhsmsc/modules/pet_information/req/pet_informationReq.java b/src/main/java/com/ffii/fhsmsc/modules/pet_information/req/pet_informationReq.java index b6f599f..8ca14cc 100644 --- a/src/main/java/com/ffii/fhsmsc/modules/pet_information/req/pet_informationReq.java +++ b/src/main/java/com/ffii/fhsmsc/modules/pet_information/req/pet_informationReq.java @@ -37,6 +37,26 @@ public class pet_informationReq { private String health_goal; @Column(name = "type") private String type; + @Column(name = "pet_name_zh") + private String pet_name_zh; + @Column(name = "gender_zh") + private String gender_zh; + @Column(name = "age_category_zh") + private String age_category_zh; + @Column(name = "breed_zh") + private String breed_zh; + @Column(name = "sterilization_zh") + private String sterilization_zh; + @Column(name = "activity_level_zh") + private String activity_level_zh; + @Column(name = "health_issues_zh") + private String health_issues_zh; + @Column(name = "family_health_history_zh") + private String family_health_history_zh; + @Column(name = "health_goal_zh") + private String health_goal_zh; + @Column(name = "type_zh") + private String type_zh; public Long getId() { return id; @@ -150,4 +170,83 @@ public class pet_informationReq { this.type = type; } + public String getPet_name_zh() { + return pet_name_zh; + } + + public void setPet_name_zh(String pet_name_zh) { + this.pet_name_zh = pet_name_zh; + } + public String getGender_zh() { + return gender_zh; + } + + public void setGender_zh(String gender_zh) { + this.gender_zh = gender_zh; + } + + public String getAge_category_zh() { + return age_category_zh; + } + + public void setAge_category_zh(String age_category_zh) { + this.age_category_zh = age_category_zh; + } + + public String getBreed_zh() { + return breed_zh; + } + + public void setBreed_zh(String breed_zh) { + this.breed_zh = breed_zh; + } + + public String getSterilization_zh() { + return sterilization_zh; + } + + public void setSterilization_zh(String sterilization_zh) { + this.sterilization_zh = sterilization_zh; + } + + public String getActivity_level_zh() { + return activity_level_zh; + } + + public void setActivity_level_zh(String activity_level_zh) { + this.activity_level_zh = activity_level_zh; + } + + public String getHealth_issues_zh() { + return health_issues_zh; + } + + public void setHealth_issues_zh(String health_issues_zh) { + this.health_issues_zh = health_issues_zh; + } + + public String getFamily_health_history_zh() { + return family_health_history_zh; + } + + public void setFamily_health_history_zh(String family_health_history_zh) { + this.family_health_history_zh = family_health_history_zh; + } + + public String getHealth_goal_zh() { + return health_goal_zh; + } + + public void setHealth_goal_zh(String health_goal_zh) { + this.health_goal_zh = health_goal_zh; + } + + public String getType_zh() { + return type_zh; + } + + public void setType_zh(String type_zh) { + this.type_zh = type_zh; + } + } diff --git a/src/main/java/com/ffii/fhsmsc/modules/pet_information/service/pet_informationService.java b/src/main/java/com/ffii/fhsmsc/modules/pet_information/service/pet_informationService.java index 3308829..925e42d 100644 --- a/src/main/java/com/ffii/fhsmsc/modules/pet_information/service/pet_informationService.java +++ b/src/main/java/com/ffii/fhsmsc/modules/pet_information/service/pet_informationService.java @@ -15,51 +15,98 @@ import com.ffii.core.utils.BeanUtils; import com.ffii.fhsmsc.modules.pet_information.enity.pet_information; import com.ffii.fhsmsc.modules.pet_information.enity.pet_informationRepository; import com.ffii.fhsmsc.modules.pet_information.req.pet_informationReq; - +import com.ffii.fhsmsc.modules.i18n.service.i18nService; +import com.ffii.fhsmsc.modules.i18n.req.i18nReq; import jakarta.validation.Valid; +import java.util.HashMap; @Service public class pet_informationService extends AbstractBaseEntityService{ private static final Logger logger = LoggerFactory.getLogger(pet_informationService.class); + private final i18nService i18nService; - public pet_informationService(JdbcDao jdbcDao, pet_informationRepository repository) { + public pet_informationService(JdbcDao jdbcDao, pet_informationRepository repository, i18nService i18nService) { super(jdbcDao, repository); + this.i18nService = i18nService; } public List> search(Map args) { - logger.info("Search called with args: {}", args); - StringBuilder sql = new StringBuilder("SELECT" - + " pi.id," - + " pi.owner_id," - + " pi.pet_name," - + " pi.gender," - + " pi.age_category," - + " pi.birth_date," - + " pi.breed," - + " pi.weight_kg," - + " pi.sterilization," - + " pi.activity_level," - + " pi.health_issues," - + " pi.family_health_history," - + " pi.health_goal," - + " pi.type" - + " FROM pet_information pi" - + " WHERE pi.deleted = 0" // 使用 0,因为是 tinyint(1) - ); - if (args != null) { - if (args.containsKey("owner_id")) { - sql.append(" AND pi.owner_id = :owner_id"); - logger.info("Added owner_id filter: {}", args.get("owner_id")); - } - } - - sql.append(" ORDER BY pi.id"); - logger.info("Final SQL query: {}", sql.toString()); - - List> result = jdbcDao.queryForList(sql.toString(), args); - logger.info("Query returned {} results", result.size()); - return result; - } + logger.info("Search called with args: {}", args); + StringBuilder sql = new StringBuilder("SELECT" + + " pi.id," + + " pi.owner_id," + + " pi.pet_name," + + " pi.gender," + + " pi.age_category," + + " pi.birth_date," + + " pi.breed," + + " pi.weight_kg," + + " pi.sterilization," + + " pi.activity_level," + + " pi.health_issues," + + " pi.family_health_history," + + " pi.health_goal," + + " pi.type" + + " FROM pet_information pi" + + " WHERE pi.deleted = 0"); // 使用 0,因为是 tinyint(1) + + if (args != null) { + if (args.containsKey("owner_id")) { + sql.append(" AND pi.owner_id = :owner_id"); + logger.info("Added owner_id filter: {}", args.get("owner_id")); + } + } + + sql.append(" ORDER BY pi.id"); + logger.info("Final SQL query: {}", sql.toString()); + + List> result = jdbcDao.queryForList(sql.toString(), args); + logger.info("Query returned {} results", result.size()); + + // 如果指定了语言参数,则添加国际化支持 + if (args != null && args.containsKey("language") && args.get("language") != null) { + String language = (String) args.get("language"); + logger.info("Adding i18n support for language: {}", language); + + String[] translatableFields = { + "pet_name", "gender", "age_category", "breed", "sterilization", + "activity_level", "health_issues", "family_health_history", "health_goal", "type" + }; + + for (Map record : result) { + Object idObj = record.get("id"); + Long id = null; + if (idObj instanceof Integer) { + id = ((Integer) idObj).longValue(); + } else if (idObj instanceof Long) { + id = (Long) idObj; + } + if (id != null) { + // Get translations for each translatable field + Map i18nArgs = new HashMap<>(); + i18nArgs.put("table_name", "pet_information"); + i18nArgs.put("record_id", id); + i18nArgs.put("language", language); + + for (String field : translatableFields) { + i18nArgs.put("field_name", field); + logger.info("i18nArgs for {}: {}", field, i18nArgs); + + List> translations = i18nService.search(i18nArgs); + logger.info("{} translations: {}", field, translations); + if (!translations.isEmpty()) { + String translatedValue = (String) translations.get(0).get("value"); + if (translatedValue != null) { + record.put(field, translatedValue); + } + } + } + } + } + } + + return result; + } @Transactional(rollbackFor = Exception.class) public pet_information saveOrUpdate(@Valid pet_informationReq req) { @@ -126,6 +173,64 @@ public class pet_informationService extends AbstractBaseEntityService en) + if (req.getPet_name() != null) upsertI18n(table, recordId, "pet_name", "en", req.getPet_name()); + if (req.getGender() != null) upsertI18n(table, recordId, "gender", "en", req.getGender()); + if (req.getAge_category() != null) upsertI18n(table, recordId, "age_category", "en", req.getAge_category()); + if (req.getBirth_date() != null) upsertI18n(table, recordId, "birth_date", "en", String.valueOf(req.getBirth_date().getTime())); + if (req.getBreed() != null) upsertI18n(table, recordId, "breed", "en", req.getBreed()); + if (req.getWeight_kg() != null) upsertI18n(table, recordId, "weight_kg", "en", String.valueOf(req.getWeight_kg())); + if (req.getSterilization() != null) upsertI18n(table, recordId, "sterilization", "en", req.getSterilization()); + if (req.getActivity_level() != null) upsertI18n(table, recordId, "activity_level", "en", req.getActivity_level()); + if (req.getHealth_issues() != null) upsertI18n(table, recordId, "health_issues", "en", req.getHealth_issues()); + if (req.getFamily_health_history() != null) upsertI18n(table, recordId, "family_health_history", "en", req.getFamily_health_history()); + if (req.getHealth_goal() != null) upsertI18n(table, recordId, "health_goal", "en", req.getHealth_goal()); + if (req.getType() != null) upsertI18n(table, recordId, "type", "en", req.getType()); + + // 如需支持中文,请在 `pet_informationReq` 中新增同名 `_zh` 字段,并参照 food_nutrients 的写法 upsert 为语言 "zh" + if (req.getPet_name_zh() != null) upsertI18n(table, recordId, "pet_name", "zh", req.getPet_name_zh()); + if (req.getGender_zh() != null) upsertI18n(table, recordId, "gender", "zh", req.getGender_zh()); + if (req.getAge_category_zh() != null) upsertI18n(table, recordId, "age_category", "zh", req.getAge_category_zh()); + if (req.getBirth_date() != null) upsertI18n(table, recordId, "birth_date", "zh", String.valueOf(req.getBirth_date().getTime())); + if (req.getBreed_zh() != null) upsertI18n(table, recordId, "breed", "zh", req.getBreed_zh()); + if (req.getWeight_kg() != null) upsertI18n(table, recordId, "weight_kg", "zh", String.valueOf(req.getWeight_kg())); + if (req.getSterilization_zh() != null) upsertI18n(table, recordId, "sterilization", "zh", req.getSterilization_zh()); + if (req.getActivity_level_zh() != null) upsertI18n(table, recordId, "activity_level", "zh", req.getActivity_level_zh()); + if (req.getHealth_issues_zh() != null) upsertI18n(table, recordId, "health_issues", "zh", req.getHealth_issues_zh()); + if (req.getFamily_health_history_zh() != null) upsertI18n(table, recordId, "family_health_history", "zh", req.getFamily_health_history_zh()); + if (req.getHealth_goal_zh() != null) upsertI18n(table, recordId, "health_goal", "zh", req.getHealth_goal_zh()); + if (req.getType_zh() != null) upsertI18n(table, recordId, "type", "zh", req.getType_zh()); + return instance; } -} + private void upsertI18n(String tableName, Long recordId, String fieldName, String language, String value) { + if (recordId == null || value == null) return; + + Map findArgs = new HashMap<>(); + findArgs.put("table_name", tableName); + findArgs.put("field_name", fieldName); + findArgs.put("record_id", recordId); + findArgs.put("language", language); + + List> rows = i18nService.search(findArgs); + + i18nReq ireq = new i18nReq(); + if (rows != null && !rows.isEmpty()) { + Object idObj = rows.get(0).get("id"); + Long id = null; + if (idObj instanceof Integer) id = ((Integer) idObj).longValue(); + else if (idObj instanceof Long) id = (Long) idObj; + if (id != null) ireq.setId(id); + } + ireq.setTable_name(tableName); + ireq.setField_name(fieldName); + ireq.setRecord_id(recordId); + ireq.setLanguage(language); + ireq.setValue(value); + + i18nService.saveOrUpdate(ireq); + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fhsmsc/modules/pet_information/web/pet_informationController.java b/src/main/java/com/ffii/fhsmsc/modules/pet_information/web/pet_informationController.java index dc71da5..4f8f593 100644 --- a/src/main/java/com/ffii/fhsmsc/modules/pet_information/web/pet_informationController.java +++ b/src/main/java/com/ffii/fhsmsc/modules/pet_information/web/pet_informationController.java @@ -16,10 +16,10 @@ import com.ffii.core.response.RecordsRes; import com.ffii.core.utils.CriteriaArgsBuilder; import com.ffii.fhsmsc.modules.pet_information.req.pet_informationReq; import com.ffii.fhsmsc.modules.pet_information.service.pet_informationService; - import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; + @RestController @RequestMapping("/pet_information") public class pet_informationController { @@ -37,6 +37,7 @@ public class pet_informationController { logger.info("Received list request with parameters: {}", request.getParameterMap()); Map args = CriteriaArgsBuilder.withRequest(request) .addInteger("owner_id") + .addString("language") .build(); logger.info("Built args: {}", args); return new RecordsRes<>(pet_informationService.search(args)); @@ -46,4 +47,6 @@ public class pet_informationController { public IdRes saveOrUpdate(@RequestBody @Valid pet_informationReq req) { return new IdRes(pet_informationService.saveOrUpdate(req).getId()); } + + } diff --git a/src/main/resources/db/changelog/changes/enson_change/01_create_embedding.sql b/src/main/resources/db/changelog/changes/enson_change/01_create_embedding.sql new file mode 100644 index 0000000..c6c3222 --- /dev/null +++ b/src/main/resources/db/changelog/changes/enson_change/01_create_embedding.sql @@ -0,0 +1,26 @@ +--liquibase formatted sql +--changeset terence:77-create-embedding-table +--comment: Insert F&B related permissions and user authorities + +--liquibase formatted sql +--changeset yourname:001-create-menu_item-table + +--liquibase formatted sql +--changeset yourname:002-create-embedding-table +--comment: Create embedding table similar to i18n for multilingual vector storage + +CREATE TABLE embedding ( + id INT AUTO_INCREMENT PRIMARY KEY, + table_name VARCHAR(50), + field_name VARCHAR(50), + record_id INT, + embedding_text TEXT, + embedding_vector JSON, + language VARCHAR(10), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + modified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + created_by INT DEFAULT NULL, + modified_by INT DEFAULT NULL, + deleted BIT(1) DEFAULT b'0', + version INT DEFAULT 0 +); \ No newline at end of file