@@ -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 |
@@ -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 | |||||
); |
@@ -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; | |||||
} | |||||
} |
@@ -0,0 +1,6 @@ | |||||
package com.ffii.fhsmsc.modules.embedding.entity; | |||||
import com.ffii.core.support.AbstractRepository; | |||||
public interface embeddingRepository extends AbstractRepository<embedding, Long> { | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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()); | |||||
} | |||||
} |
@@ -55,10 +55,6 @@ public class food_nutrients extends BaseEntity<Long> { | |||||
private String target_age_category; | private String target_age_category; | ||||
@Column(name = "pet") | @Column(name = "pet") | ||||
private String pet; | private String pet; | ||||
@Column(name = "embedding_text") | |||||
private String embedding_text; | |||||
@Column(name = "embedding_vector") | |||||
private String embedding_vector; | |||||
@Column(name = "description") | @Column(name = "description") | ||||
private String description; | private String description; | ||||
@@ -200,18 +196,7 @@ public class food_nutrients extends BaseEntity<Long> { | |||||
public void setPet(String pet) { | public void setPet(String pet) { | ||||
this.pet = 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() { | public String getDescription() { | ||||
return description; | return description; | ||||
} | } | ||||
@@ -6,8 +6,12 @@ public class food_nutrientsReq { | |||||
private Long id; | private Long id; | ||||
@Column(name = "food_name") | @Column(name = "food_name") | ||||
private String food_name; | private String food_name; | ||||
@Column | |||||
private String food_name_zh; | |||||
@Column(name = "brand") | @Column(name = "brand") | ||||
private String brand; | private String brand; | ||||
@Column | |||||
private String brand_zh; | |||||
@Column(name = "size_kg") | @Column(name = "size_kg") | ||||
private Double size_kg; | private Double size_kg; | ||||
@Column(name = "protein_percent") | @Column(name = "protein_percent") | ||||
@@ -46,16 +50,30 @@ public class food_nutrientsReq { | |||||
private Integer calories_kcal_kg; | private Integer calories_kcal_kg; | ||||
@Column(name = "food_type") | @Column(name = "food_type") | ||||
private String food_type; | private String food_type; | ||||
@Column(name = "food_type_zh") | |||||
private String food_type_zh; | |||||
@Column(name = "target_age_category") | @Column(name = "target_age_category") | ||||
private String target_age_category; | private String target_age_category; | ||||
@Column(name = "target_age_category_zh") | |||||
private String target_age_category_zh; | |||||
@Column(name = "pet") | @Column(name = "pet") | ||||
private String pet; | private String pet; | ||||
@Column(name = "pet_zh") | |||||
private String pet_zh; | |||||
@Column(name = "embedding_text") | @Column(name = "embedding_text") | ||||
private String embedding_text; | private String embedding_text; | ||||
@Column(name = "embedding_vector") | @Column(name = "embedding_vector") | ||||
private String embedding_vector; | private String embedding_vector; | ||||
@Column(name = "description") | @Column(name = "description") | ||||
private String 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() { | public Long getId() { | ||||
return id; | return id; | ||||
@@ -219,4 +237,58 @@ public class food_nutrientsReq { | |||||
public void setDescription(String description) { | public void setDescription(String description) { | ||||
this.description = 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; | |||||
} | |||||
} | } |
@@ -1,5 +1,6 @@ | |||||
package com.ffii.fhsmsc.modules.food_nutrients.service; | package com.ffii.fhsmsc.modules.food_nutrients.service; | ||||
import java.util.HashMap; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | 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.AbstractBaseEntityService; | ||||
import com.ffii.core.support.JdbcDao; | import com.ffii.core.support.JdbcDao; | ||||
import com.ffii.core.utils.BeanUtils; | 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_nutrients; | ||||
import com.ffii.fhsmsc.modules.food_nutrients.entity.food_nutrientsRepository; | 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.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; | import jakarta.validation.Valid; | ||||
@Service | @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); | 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) { | public List<Map<String, Object>> search(Map<String, Object> args) { | ||||
logger.info("Search called with args: {}", args); | logger.info("Search called with args: {}", args); | ||||
StringBuilder sql = new StringBuilder(); | 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")) { | 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")) { | } 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 { | } else { | ||||
// Normal case - return all fields except embeddings | |||||
// Normal case - return all fields from food_nutrients | |||||
sql.append("SELECT" | sql.append("SELECT" | ||||
+ " pi.id," | + " pi.id," | ||||
+ " pi.food_name," | + " pi.food_name," | ||||
@@ -64,10 +79,16 @@ public class food_nutrientsService extends AbstractBaseEntityService<food_nutrie | |||||
+ " pi.pet," | + " pi.pet," | ||||
+ " pi.description"); | + " 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 != null) { | ||||
if (args.containsKey("food_name")) { | if (args.containsKey("food_name")) { | ||||
sql.append(" AND pi.food_name = :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")); | logger.info("Added id filter: {}", args.get("id")); | ||||
} | } | ||||
} | } | ||||
sql.append(" ORDER BY pi.id"); | sql.append(" ORDER BY pi.id"); | ||||
logger.info("Final SQL query: {}", sql.toString()); | logger.info("Final SQL query: {}", sql.toString()); | ||||
List<Map<String, Object>> result = jdbcDao.queryForList(sql.toString(), args); | List<Map<String, Object>> result = jdbcDao.queryForList(sql.toString(), args); | ||||
logger.info("Query returned {} results", result.size()); | 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; | return result; | ||||
} | } | ||||
@Transactional(rollbackFor = Exception.class) | @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; | |||||
} | |||||
} |
@@ -37,9 +37,12 @@ public class food_nutrientsController { | |||||
logger.info("Received list request with parameters: {}", request.getParameterMap()); | logger.info("Received list request with parameters: {}", request.getParameterMap()); | ||||
Map<String, Object> args = CriteriaArgsBuilder.withRequest(request) | Map<String, Object> args = CriteriaArgsBuilder.withRequest(request) | ||||
.addString("food_name") | .addString("food_name") | ||||
.addBoolean("embedding") | |||||
.addBoolean("embedding_text") // Add embedding_text parameter | .addBoolean("embedding_text") // Add embedding_text parameter | ||||
.addBoolean("embedding_vector") | .addBoolean("embedding_vector") | ||||
.addInteger("id") | .addInteger("id") | ||||
.addString("language") | |||||
.build(); | .build(); | ||||
logger.info("Built args: {}", args); | logger.info("Built args: {}", args); | ||||
return new RecordsRes<>(food_nutrientsService.search(args)); | return new RecordsRes<>(food_nutrientsService.search(args)); | ||||
@@ -50,5 +53,14 @@ public class food_nutrientsController { | |||||
return new IdRes(food_nutrientsService.saveOrUpdate(req).getId()); | 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)); | |||||
} | |||||
} | } |
@@ -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; | |||||
} | |||||
} |
@@ -0,0 +1,7 @@ | |||||
package com.ffii.fhsmsc.modules.i18n.enity; | |||||
import com.ffii.core.support.AbstractRepository; | |||||
public interface i18nRepository extends AbstractRepository<i18n, Long> { | |||||
} |
@@ -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; | |||||
} | |||||
} |
@@ -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; | |||||
} | |||||
} | |||||
@@ -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()); | |||||
} | |||||
} |
@@ -37,6 +37,26 @@ public class pet_informationReq { | |||||
private String health_goal; | private String health_goal; | ||||
@Column(name = "type") | @Column(name = "type") | ||||
private String 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() { | public Long getId() { | ||||
return id; | return id; | ||||
@@ -150,4 +170,83 @@ public class pet_informationReq { | |||||
this.type = type; | 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; | |||||
} | |||||
} | } |
@@ -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_information; | ||||
import com.ffii.fhsmsc.modules.pet_information.enity.pet_informationRepository; | 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.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 jakarta.validation.Valid; | ||||
import java.util.HashMap; | |||||
@Service | @Service | ||||
public class pet_informationService extends AbstractBaseEntityService<pet_information, Long, pet_informationRepository>{ | public class pet_informationService extends AbstractBaseEntityService<pet_information, Long, pet_informationRepository>{ | ||||
private static final Logger logger = LoggerFactory.getLogger(pet_informationService.class); | 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); | super(jdbcDao, repository); | ||||
this.i18nService = i18nService; | |||||
} | } | ||||
public List<Map<String, Object>> search(Map<String, Object> args) { | 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) | @Transactional(rollbackFor = Exception.class) | ||||
public pet_information saveOrUpdate(@Valid pet_informationReq req) { | public pet_information saveOrUpdate(@Valid pet_informationReq req) { | ||||
@@ -126,6 +173,64 @@ public class pet_informationService extends AbstractBaseEntityService<pet_inform | |||||
} | } | ||||
saveAndFlush(instance); | 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; | 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); | |||||
} | |||||
} |
@@ -16,10 +16,10 @@ import com.ffii.core.response.RecordsRes; | |||||
import com.ffii.core.utils.CriteriaArgsBuilder; | import com.ffii.core.utils.CriteriaArgsBuilder; | ||||
import com.ffii.fhsmsc.modules.pet_information.req.pet_informationReq; | import com.ffii.fhsmsc.modules.pet_information.req.pet_informationReq; | ||||
import com.ffii.fhsmsc.modules.pet_information.service.pet_informationService; | import com.ffii.fhsmsc.modules.pet_information.service.pet_informationService; | ||||
import jakarta.servlet.http.HttpServletRequest; | import jakarta.servlet.http.HttpServletRequest; | ||||
import jakarta.validation.Valid; | import jakarta.validation.Valid; | ||||
@RestController | @RestController | ||||
@RequestMapping("/pet_information") | @RequestMapping("/pet_information") | ||||
public class pet_informationController { | public class pet_informationController { | ||||
@@ -37,6 +37,7 @@ public class pet_informationController { | |||||
logger.info("Received list request with parameters: {}", request.getParameterMap()); | logger.info("Received list request with parameters: {}", request.getParameterMap()); | ||||
Map<String, Object> args = CriteriaArgsBuilder.withRequest(request) | Map<String, Object> args = CriteriaArgsBuilder.withRequest(request) | ||||
.addInteger("owner_id") | .addInteger("owner_id") | ||||
.addString("language") | |||||
.build(); | .build(); | ||||
logger.info("Built args: {}", args); | logger.info("Built args: {}", args); | ||||
return new RecordsRes<>(pet_informationService.search(args)); | return new RecordsRes<>(pet_informationService.search(args)); | ||||
@@ -46,4 +47,6 @@ public class pet_informationController { | |||||
public IdRes saveOrUpdate(@RequestBody @Valid pet_informationReq req) { | public IdRes saveOrUpdate(@RequestBody @Valid pet_informationReq req) { | ||||
return new IdRes(pet_informationService.saveOrUpdate(req).getId()); | return new IdRes(pet_informationService.saveOrUpdate(req).getId()); | ||||
} | } | ||||
} | } |
@@ -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 | |||||
); |