CANCERYS\kw093 il y a 1 semaine
Parent
révision
83f3e6601c
24 fichiers modifiés avec 1374 ajouts et 168 suppressions
  1. BIN
      .gradle/8.8/fileHashes/fileHashes.bin
  2. BIN
      .gradle/8.8/fileHashes/fileHashes.lock
  3. BIN
      .gradle/buildOutputCleanup/buildOutputCleanup.lock
  4. +2
    -2
      .gradle/buildOutputCleanup/cache.properties
  5. BIN
      .gradle/nb-cache/subprojects.ser
  6. +26
    -0
      bin/main/db/changelog/changes/enson_change/01_create_embedding.sql
  7. +52
    -0
      src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embedding.java
  8. +6
    -0
      src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embeddingRepository.java
  9. +47
    -0
      src/main/java/com/ffii/fhsmsc/modules/embedding/req/embeddingReq.java
  10. +107
    -0
      src/main/java/com/ffii/fhsmsc/modules/embedding/service/embeddingService.java
  11. +56
    -0
      src/main/java/com/ffii/fhsmsc/modules/embedding/web/embeddingController.java
  12. +1
    -16
      src/main/java/com/ffii/fhsmsc/modules/food_nutrients/entity/food_nutrients.java
  13. +72
    -0
      src/main/java/com/ffii/fhsmsc/modules/food_nutrients/req/food_nutrientsReq.java
  14. +423
    -112
      src/main/java/com/ffii/fhsmsc/modules/food_nutrients/service/food_nutrientsService.java
  15. +13
    -1
      src/main/java/com/ffii/fhsmsc/modules/food_nutrients/web/food_nutrientsController.java
  16. +63
    -0
      src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18n.java
  17. +7
    -0
      src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18nRepository.java
  18. +59
    -0
      src/main/java/com/ffii/fhsmsc/modules/i18n/req/i18nReq.java
  19. +115
    -0
      src/main/java/com/ffii/fhsmsc/modules/i18n/service/i18nService.java
  20. +55
    -0
      src/main/java/com/ffii/fhsmsc/modules/i18n/web/i18nController.java
  21. +99
    -0
      src/main/java/com/ffii/fhsmsc/modules/pet_information/req/pet_informationReq.java
  22. +141
    -36
      src/main/java/com/ffii/fhsmsc/modules/pet_information/service/pet_informationService.java
  23. +4
    -1
      src/main/java/com/ffii/fhsmsc/modules/pet_information/web/pet_informationController.java
  24. +26
    -0
      src/main/resources/db/changelog/changes/enson_change/01_create_embedding.sql

BIN
.gradle/8.8/fileHashes/fileHashes.bin Voir le fichier


BIN
.gradle/8.8/fileHashes/fileHashes.lock Voir le fichier


BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock Voir le fichier


+ 2
- 2
.gradle/buildOutputCleanup/cache.properties Voir le fichier

@@ -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

BIN
.gradle/nb-cache/subprojects.ser Voir le fichier


+ 26
- 0
bin/main/db/changelog/changes/enson_change/01_create_embedding.sql Voir le fichier

@@ -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
);

+ 52
- 0
src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embedding.java Voir le fichier

@@ -0,0 +1,52 @@
package com.ffii.fhsmsc.modules.embedding.entity;

import com.ffii.core.entity.BaseEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import java.util.Date;
@Entity
@Table(name = "embedding")
public class embedding extends BaseEntity<Long> {
@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;
}
}

+ 6
- 0
src/main/java/com/ffii/fhsmsc/modules/embedding/entity/embeddingRepository.java Voir le fichier

@@ -0,0 +1,6 @@
package com.ffii.fhsmsc.modules.embedding.entity;

import com.ffii.core.support.AbstractRepository;

public interface embeddingRepository extends AbstractRepository<embedding, Long> {
}

+ 47
- 0
src/main/java/com/ffii/fhsmsc/modules/embedding/req/embeddingReq.java Voir le fichier

@@ -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;
}

}

+ 107
- 0
src/main/java/com/ffii/fhsmsc/modules/embedding/service/embeddingService.java Voir le fichier

@@ -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<embedding, Long, embeddingRepository>{
private static final Logger logger = LoggerFactory.getLogger(embeddingService.class);
public embeddingService(JdbcDao jdbcDao, embeddingRepository repository) {
super(jdbcDao, repository);
}


public List<Map<String, Object>> search(Map<String, Object> 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;
}

}

+ 56
- 0
src/main/java/com/ffii/fhsmsc/modules/embedding/web/embeddingController.java Voir le fichier

@@ -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<Map<String, Object>> listJson(HttpServletRequest request) throws ServletRequestBindingException {
logger.info("Received list request with parameters: {}", request.getParameterMap());
Map<String, Object> 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());
}

}

+ 1
- 16
src/main/java/com/ffii/fhsmsc/modules/food_nutrients/entity/food_nutrients.java Voir le fichier

@@ -55,10 +55,6 @@ public class food_nutrients extends BaseEntity<Long> {
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<Long> {
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;
}


+ 72
- 0
src/main/java/com/ffii/fhsmsc/modules/food_nutrients/req/food_nutrientsReq.java Voir le fichier

@@ -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;
}
}

+ 423
- 112
src/main/java/com/ffii/fhsmsc/modules/food_nutrients/service/food_nutrientsService.java Voir le fichier

@@ -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<food_nutrients, Long, food_nutrientsRepository>{
public class food_nutrientsService extends AbstractBaseEntityService<food_nutrients, Long, food_nutrientsRepository> {
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<Map<String, Object>> search(Map<String, Object> 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<food_nutrie
+ " pi.pet,"
+ " pi.description");
}
sql.append(" FROM food_nutrients pi")
.append(" WHERE pi.deleted = 0"); // 使用 0,因为是 tinyint(1)

sql.append(" FROM food_nutrients pi");
if (useEmbeddingTable) {
sql.append(" LEFT JOIN embedding e ON e.table_name = 'food_nutrients' AND e.record_id = pi.id AND e.deleted = 0");
if (args != null && args.containsKey("language")) {
sql.append(" AND e.language = :language");
}
}
sql.append(" WHERE pi.deleted = 0");

if (args != null) {
if (args.containsKey("food_name")) {
sql.append(" AND pi.food_name = :food_name");
@@ -78,109 +99,399 @@ public class food_nutrientsService extends AbstractBaseEntityService<food_nutrie
logger.info("Added id filter: {}", args.get("id"));
}
}
sql.append(" ORDER BY pi.id");
logger.info("Final SQL query: {}", sql.toString());
List<Map<String, Object>> 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<String, Object> 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<String, Object> 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<Map<String, Object>> 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<String, Object> eArgs = new HashMap<>();
eArgs.put("table_name", "food_nutrients");
eArgs.put("record_id", recordId);
eArgs.put("language", language);
List<Map<String, Object>> 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<String, Object> i18nArgs = new HashMap<>();
i18nArgs.put("table_name", "food_nutrients");
i18nArgs.put("field_name", "brand");
i18nArgs.put("record_id", recordId);
i18nArgs.put("language", language);
List<Map<String, Object>> 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<String, Object> findArgs = new HashMap<>();
findArgs.put("table_name", tableName);
findArgs.put("field_name", fieldName);
findArgs.put("record_id", recordId);
findArgs.put("language", language);

List<Map<String, Object>> 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<Map<String, Object>> checkEmbeddingStatus(Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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;
}
}

+ 13
- 1
src/main/java/com/ffii/fhsmsc/modules/food_nutrients/web/food_nutrientsController.java Voir le fichier

@@ -37,9 +37,12 @@ public class food_nutrientsController {
logger.info("Received list request with parameters: {}", request.getParameterMap());
Map<String, Object> 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<Map<String, Object>> checkEmbeddingStatus(HttpServletRequest request) throws ServletRequestBindingException {
logger.info("Received embedding status request with parameters: {}", request.getParameterMap());
Map<String, Object> args = CriteriaArgsBuilder.withRequest(request)
.addString("language")
.addInteger("id")
.build();
logger.info("Built args: {}", args);
return new RecordsRes<>(food_nutrientsService.checkEmbeddingStatus(args));
}
}

+ 63
- 0
src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18n.java Voir le fichier

@@ -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<Long> {
@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;
}

}

+ 7
- 0
src/main/java/com/ffii/fhsmsc/modules/i18n/enity/i18nRepository.java Voir le fichier

@@ -0,0 +1,7 @@
package com.ffii.fhsmsc.modules.i18n.enity;

import com.ffii.core.support.AbstractRepository;

public interface i18nRepository extends AbstractRepository<i18n, Long> {
}

+ 59
- 0
src/main/java/com/ffii/fhsmsc/modules/i18n/req/i18nReq.java Voir le fichier

@@ -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;
}
}

+ 115
- 0
src/main/java/com/ffii/fhsmsc/modules/i18n/service/i18nService.java Voir le fichier

@@ -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<i18n, Long, i18nRepository>{
private static final Logger logger = LoggerFactory.getLogger(i18nService.class);
public i18nService(JdbcDao jdbcDao, i18nRepository repository) {
super(jdbcDao, repository);
}

public List<Map<String, Object>> search(Map<String, Object> 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<Map<String, Object>> 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;
}
}


+ 55
- 0
src/main/java/com/ffii/fhsmsc/modules/i18n/web/i18nController.java Voir le fichier

@@ -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<Map<String, Object>> listJson(HttpServletRequest request) throws ServletRequestBindingException {
logger.info("Received list request with parameters: {}", request.getParameterMap());
Map<String, Object> 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());
}

}

+ 99
- 0
src/main/java/com/ffii/fhsmsc/modules/pet_information/req/pet_informationReq.java Voir le fichier

@@ -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;
}
}

+ 141
- 36
src/main/java/com/ffii/fhsmsc/modules/pet_information/service/pet_informationService.java Voir le fichier

@@ -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<pet_information, Long, pet_informationRepository>{
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<Map<String, Object>> search(Map<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> 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<String, Object> 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<Map<String, Object>> 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<pet_inform
}
saveAndFlush(instance);
Long recordId = instance.getId();
final String table = "pet_information";

// 英文(原始字段 -> 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<String, Object> findArgs = new HashMap<>();
findArgs.put("table_name", tableName);
findArgs.put("field_name", fieldName);
findArgs.put("record_id", recordId);
findArgs.put("language", language);
List<Map<String, Object>> 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);
}
}

+ 4
- 1
src/main/java/com/ffii/fhsmsc/modules/pet_information/web/pet_informationController.java Voir le fichier

@@ -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<String, Object> 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());
}

}

+ 26
- 0
src/main/resources/db/changelog/changes/enson_change/01_create_embedding.sql Voir le fichier

@@ -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
);

Chargement…
Annuler
Enregistrer