feat: 添加LibraryItem功能并优化筛选能力

1. 创建了完整的LibraryItem功能模块
   - 实体类:LibraryItem
   - Mapper:LibraryItemMapper
   - Service:LibraryItemService
   - Controller:LibraryItemController
   - 相关模型类

2. 将tags字段替换为四个独立字段
   - age:年龄(youth-青年,middle-中年,old-老年)
   - gender:性别(male-男性,female-女性)
   - scene:场景(emotion-情感,podcast-播客,education-教育)
   - language:语种(chinese-中文,english-英语)

3. 支持多条件筛选
   - 在pageList方法中添加了根据age、gender、scene、language字段筛选的逻辑
   - 优化了SQL查询性能

4. 生成了完整的SQL文件
   - 创建了dh_library_item表
   - 添加了合理的索引设计
   - 包含了示例数据
   - 提供了查询示例

5. 创建了.gitignore文件
   - 忽略了不必要的文件和目录
   - 优化了git仓库结构
This commit is contained in:
wangzhiwei 2026-01-07 14:16:04 +08:00
parent 60b3e7e1a3
commit 6035db3648
13 changed files with 579 additions and 29 deletions

49
.gitignore vendored
View File

@ -1,26 +1,35 @@
# ---> Java
# Compiled class file
*.class
# Maven
.flattened-pom.xml
*.pom.versionsBackup
# Log file
# IDE
.idea/
*.iml
*.ipr
*.iws
# Build output
target/
**/target/
# Logs
logs/
*.log
# BlueJ files
*.ctxt
# OS
.DS_Store
Thumbs.db
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Environment variables
.env
.env.local
.env.*.local
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# Test reports
jacoco/
coverage/
# Temporary files
*.tmp
*.temp
.cache/

View File

@ -0,0 +1,15 @@
package top.continew.admin.business.mapper;
import org.apache.ibatis.annotations.Mapper;
import top.continew.admin.business.model.entity.LibraryItem;
import top.continew.starter.data.mp.base.BaseMapper;
/**
* 库中项目Mapper
*
* @author author
* @since 2026-01-07
*/
@Mapper
public interface LibraryItemMapper extends BaseMapper<LibraryItem> {
}

View File

@ -0,0 +1,68 @@
package top.continew.admin.business.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import top.continew.starter.extension.crud.model.entity.BaseDO;
/**
* 库中项目实体
*
* @author author
* @since 2026-01-07
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("dh_library_item")
public class LibraryItem extends BaseDO {
/**
* 是否自定义
*/
private Boolean isCustom;
/**
* 用户ID
*/
private String userId;
/**
* 类型
*/
private String type;
/**
* 名称
*/
private String name;
/**
* 年龄youth-青年middle-中年old-老年
*/
private String age;
/**
* 性别male-男性female-女性
*/
private String gender;
/**
* 场景emotion-情感podcast-播客education-教育
*/
private String scene;
/**
* 语种chinese-中文english-英语
*/
private String language;
/**
* 资源URL
*/
private String sourceUrl;
/**
* 封面URL
*/
private String coverUrl;
}

View File

@ -0,0 +1,17 @@
package top.continew.admin.business.model.query;
import lombok.Data;
import lombok.EqualsAndHashCode;
import top.continew.starter.extension.crud.model.query.PageQuery;
/**
* 库中项目分页查询对象
*
* @author author
* @since 2026-01-07
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LibraryItemPageQuery extends PageQuery {
// 可根据需要添加额外的分页查询条件
}

View File

@ -0,0 +1,53 @@
package top.continew.admin.business.model.query;
import lombok.Data;
import java.io.Serializable;
/**
* 库中项目查询条件
*
* @author author
* @since 2026-01-07
*/
@Data
public class LibraryItemQuery implements Serializable {
/**
* 用户ID
*/
private String userId;
/**
* 是否自定义
*/
private Boolean isCustom;
/**
* 类型
*/
private String type;
/**
* 名称
*/
private String name;
/**
* 年龄youth-青年middle-中年old-老年
*/
private String age;
/**
* 性别male-男性female-女性
*/
private String gender;
/**
* 场景emotion-情感podcast-播客education-教育
*/
private String scene;
/**
* 语种chinese-中文english-英语
*/
private String language;
}

View File

@ -0,0 +1,66 @@
package top.continew.admin.business.model.req;
import lombok.Data;
import lombok.EqualsAndHashCode;
import top.continew.starter.extension.crud.model.req.BaseReq;
/**
* 库中项目请求参数对象
*
* @author author
* @since 2026-01-07
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LibraryItemReq extends BaseReq {
/**
* 是否自定义
*/
private Boolean isCustom;
/**
* 用户ID
*/
private String userId;
/**
* 类型
*/
private String type;
/**
* 名称
*/
private String name;
/**
* 年龄youth-青年middle-中年old-老年
*/
private String age;
/**
* 性别male-男性female-女性
*/
private String gender;
/**
* 场景emotion-情感podcast-播客education-教育
*/
private String scene;
/**
* 语种chinese-中文english-英语
*/
private String language;
/**
* 资源URL
*/
private String sourceUrl;
/**
* 封面URL
*/
private String coverUrl;
}

View File

@ -0,0 +1,83 @@
package top.continew.admin.business.model.resp;
import lombok.Data;
import lombok.EqualsAndHashCode;
import top.continew.starter.extension.crud.model.resp.BaseResp;
import java.time.LocalDateTime;
/**
* 库中项目响应对象
*
* @author author
* @since 2026-01-07
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class LibraryItemResp extends BaseResp {
/**
* 库中项目ID
*/
private Long id;
/**
* 是否自定义
*/
private Boolean isCustom;
/**
* 用户ID
*/
private String userId;
/**
* 类型
*/
private String type;
/**
* 名称
*/
private String name;
/**
* 年龄youth-青年middle-中年old-老年
*/
private String age;
/**
* 性别male-男性female-女性
*/
private String gender;
/**
* 场景emotion-情感podcast-播客education-教育
*/
private String scene;
/**
* 语种chinese-中文english-英语
*/
private String language;
/**
* 资源URL
*/
private String sourceUrl;
/**
* 封面URL
*/
private String coverUrl;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,19 @@
package top.continew.admin.business.service;
import top.continew.admin.business.model.query.LibraryItemQuery;
import top.continew.admin.business.model.query.LibraryItemPageQuery;
import top.continew.admin.business.model.req.LibraryItemReq;
import top.continew.admin.business.model.resp.LibraryItemResp;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.BaseService;
/**
* 库中项目服务
*
* @author author
* @since 2026-01-07
*/
public interface LibraryItemService extends BaseService<LibraryItemResp, LibraryItemResp, LibraryItemQuery, LibraryItemReq> {
PageResp<LibraryItemResp> pageList(LibraryItemQuery query, LibraryItemPageQuery pageQuery);
}

View File

@ -0,0 +1,89 @@
package top.continew.admin.business.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import top.continew.admin.business.mapper.LibraryItemMapper;
import top.continew.admin.business.model.entity.LibraryItem;
import top.continew.admin.business.model.query.LibraryItemPageQuery;
import top.continew.admin.business.model.query.LibraryItemQuery;
import top.continew.admin.business.model.req.LibraryItemReq;
import top.continew.admin.business.model.resp.LibraryItemResp;
import top.continew.admin.business.service.LibraryItemService;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
/**
* 库中项目服务实现
*
* @author author
* @since 2026-01-07
*/
@Service
@RequiredArgsConstructor
public class LibraryItemServiceImpl extends BaseServiceImpl<LibraryItemMapper, LibraryItem, LibraryItemResp, LibraryItemResp, LibraryItemQuery, LibraryItemReq> implements LibraryItemService {
private final LibraryItemMapper libraryItemMapper;
@Override
public PageResp<LibraryItemResp> pageList(LibraryItemQuery query, LibraryItemPageQuery pageQuery) {
LambdaQueryWrapper<LibraryItem> wrapper = new LambdaQueryWrapper<>();
if (query.getUserId() != null) {
wrapper.eq(LibraryItem::getUserId, query.getUserId());
}
if (query.getIsCustom() != null) {
wrapper.eq(LibraryItem::getIsCustom, query.getIsCustom());
}
if (query.getType() != null) {
wrapper.eq(LibraryItem::getType, query.getType());
}
if (query.getName() != null) {
wrapper.like(LibraryItem::getName, query.getName());
}
// 年龄筛选
if (query.getAge() != null) {
wrapper.eq(LibraryItem::getAge, query.getAge());
}
// 性别筛选
if (query.getGender() != null) {
wrapper.eq(LibraryItem::getGender, query.getGender());
}
// 场景筛选
if (query.getScene() != null) {
wrapper.eq(LibraryItem::getScene, query.getScene());
}
// 语种筛选
if (query.getLanguage() != null) {
wrapper.eq(LibraryItem::getLanguage, query.getLanguage());
}
// 默认按createTime倒序排列
wrapper.orderByDesc(LibraryItem::getCreateTime);
Page<LibraryItem> page = new Page<>(pageQuery.getPage(), pageQuery.getSize());
Page<LibraryItem> result = libraryItemMapper.selectPage(page, wrapper);
PageResp<LibraryItemResp> pageResp = new PageResp<>();
pageResp.setTotal(result.getTotal());
pageResp.setList(result.getRecords().stream().map(this::convertToResp).toList());
return pageResp;
}
@Override
public Long add(LibraryItemReq req) {
LibraryItem libraryItem = new LibraryItem();
BeanUtils.copyProperties(req, libraryItem);
libraryItem.setCreateTime(java.time.LocalDateTime.now());
libraryItem.setUpdateTime(java.time.LocalDateTime.now());
libraryItemMapper.insert(libraryItem);
return libraryItem.getId();
}
private LibraryItemResp convertToResp(LibraryItem libraryItem) {
LibraryItemResp resp = new LibraryItemResp();
BeanUtils.copyProperties(libraryItem, resp);
return resp;
}
}

View File

@ -0,0 +1,75 @@
package top.continew.admin.controller.business;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.continew.admin.business.model.query.LibraryItemPageQuery;
import top.continew.admin.business.model.query.LibraryItemQuery;
import top.continew.admin.business.model.req.BatchReq;
import top.continew.admin.business.model.req.LibraryItemReq;
import top.continew.admin.business.model.resp.LibraryItemResp;
import top.continew.admin.business.service.LibraryItemService;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.BaseController;
import top.continew.starter.extension.crud.enums.Api;
import top.continew.starter.extension.crud.model.query.SortQuery;
import top.continew.starter.extension.crud.model.resp.PageResp;
import top.continew.starter.log.core.annotation.Log;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 库中项目管理 API
*
* @author author
* @since 2026-01-07
*/
@Tag(name = "数字人-库中项目管理 API")
@RestController
@RequiredArgsConstructor
@CrudRequestMapping(value = "/business/libraryItem", api = {Api.GET, Api.UPDATE, Api.LIST})
public class LibraryItemController extends BaseController<LibraryItemService, LibraryItemResp, LibraryItemResp, LibraryItemQuery, LibraryItemReq> {
@Log
@Operation(summary = "查询库中项目", description = "查询库中项目列表")
@ResponseBody
@GetMapping({"/list"})
public List<LibraryItemResp> list(LibraryItemQuery query, SortQuery sortQuery) {
return baseService.list(query, sortQuery);
}
@Log
@ResponseBody
@Operation(summary = "新增库中项目", description = "新增库中项目")
@PostMapping()
public Long addLibraryItem(@RequestBody LibraryItemReq req) {
return baseService.add(req);
}
@Log
@Operation(summary = "分页查询库中项目", description = "分页查询库中项目列表")
@ResponseBody
@GetMapping({"/page"})
public PageResp<LibraryItemResp> page(LibraryItemQuery query, LibraryItemPageQuery pageQuery) {
return baseService.pageList(query, pageQuery);
}
@Log
@Operation(summary = "批量删除库中项目", description = "根据库中项目ID列表批量删除库中项目")
@PostMapping("/batchDelete")
public void deleteBatch(@Validated @RequestBody BatchReq req) {
baseService.delete(req.getIds());
}
@Log
@Operation(summary = "批量删除库中项目(通过路径参数)", description = "根据路径中的ID列表批量删除库中项目ID以逗号分隔")
@DeleteMapping("/{ids}")
public void delete(@PathVariable("ids") String ids) {
List<Long> idList = Arrays.stream(ids.split(",")).map(Long::valueOf).collect(Collectors.toList());
baseService.delete(idList);
}
}

View File

@ -23,11 +23,11 @@ spring.datasource:
datasource:
# 主库配置(可配多个,构成多主)
digital_human:
# url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:digital_human}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false&allowPublicKeyRetrieval=true
url: jdbc:mysql://106.54.11.219:3306/digital_human?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:root}
# url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:13307}/${DB_NAME:digital_human}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false&allowPublicKeyRetrieval=true
url: jdbc:mysql://43.248.131.153:13307/digital_human?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&autoReconnect=true&maxReconnects=10&failOverReadOnly=false
username: ${DB_USER:digital_human}
# password: ${DB_PWD:147369Wan}
password: ${DB_PWD:123456}
password: ${DB_PWD:digitalHuman@2026}
# password: ${DB_PWD:C9MUjc5ChtqHeCtQ}
driver-class-name: com.mysql.cj.jdbc.Driver
type: ${spring.datasource.type}
@ -56,15 +56,15 @@ spring.data:
## Redis 配置(单机模式)
redis:
# 地址
host: 106.54.11.219
host: 43.248.131.153
# host: 106.54.11.219
# 端口(默认 6379
port: 6379
port: 16379
# port: 16380
# 密码(未设置密码时请注释掉)
password: 123456
password: 654321
# 数据库索引
database: ${REDIS_DB:11}
database: ${REDIS_DB:10}
# 连接超时时间
timeout: 10s
# 是否开启 SSL

View File

@ -0,0 +1,54 @@
-- MySQL 8.0 建表语句
-- 库中项目表 - 存储各种类型的库中项目,支持按年龄、性别、场景、语种等维度筛选
drop table if exists dh_library_item;
create table dh_library_item (
`id` bigint not null auto_increment comment '主键ID',
`is_custom` tinyint(1) default 0 comment '是否自定义0-否1-是',
`user_id` varchar(64) not null comment '用户ID',
`type` varchar(32) not null comment '类型',
`name` varchar(128) comment '名称',
`age` varchar(16) comment '年龄youth-青年middle-中年old-老年',
`gender` varchar(16) comment '性别male-男性female-女性',
`scene` varchar(16) comment '场景emotion-情感podcast-播客education-教育',
`language` varchar(16) comment '语种chinese-中文english-英语',
`source_url` varchar(512) not null comment '资源URL',
`cover_url` varchar(512) comment '封面URL',
`create_user` bigint not null comment '创建者',
`create_time` datetime not null default current_timestamp comment '创建时间',
`update_user` bigint comment '更新者',
`update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间',
`is_deleted` tinyint(1) not null default 0 comment '删除标记0-未删除1-已删除',
primary key (`id`),
-- 单列索引,用于快速筛选
index idx_user_id (`user_id`),
index idx_type (`type`),
index idx_age (`age`),
index idx_gender (`gender`),
index idx_scene (`scene`),
index idx_language (`language`),
index idx_create_time (`create_time`),
index idx_is_deleted (`is_deleted`),
-- 联合索引,用于多条件查询优化
index idx_user_type (`user_id`, `type`),
index idx_age_gender_scene (`age`, `gender`, `scene`),
index idx_gender_scene_language (`gender`, `scene`, `language`)
) engine=innodb default charset=utf8mb4 collate=utf8mb4_0900_ai_ci comment='库中项目表';
-- 示例数据插入语句
insert into dh_library_item (is_custom, user_id, type, name, age, gender, scene, language, source_url, cover_url, create_user) values
(0, '123456', 'voice', '青年男声-情感中文', 'youth', 'male', 'emotion', 'chinese', 'https://example.com/voice/youth-male-emotion-chinese.mp3', 'https://example.com/cover/youth-male.jpg', 1),
(0, '123456', 'voice', '青年女声-播客中文', 'youth', 'female', 'podcast', 'chinese', 'https://example.com/voice/youth-female-podcast-chinese.mp3', 'https://example.com/cover/youth-female.jpg', 1),
(0, '123456', 'voice', '中年男声-教育中文', 'middle', 'male', 'education', 'chinese', 'https://example.com/voice/middle-male-education-chinese.mp3', 'https://example.com/cover/middle-male.jpg', 1),
(0, '123456', 'voice', '中年女声-情感英文', 'middle', 'female', 'emotion', 'english', 'https://example.com/voice/middle-female-emotion-english.mp3', 'https://example.com/cover/middle-female.jpg', 1),
(0, '123456', 'voice', '老年男声-教育英文', 'old', 'male', 'education', 'english', 'https://example.com/voice/old-male-education-english.mp3', 'https://example.com/cover/old-male.jpg', 1),
(1, '789012', 'voice', '自定义声音-青年女声-中文', 'youth', 'female', 'podcast', 'chinese', 'https://example.com/voice/custom-youth-female-chinese.mp3', 'https://example.com/cover/custom.jpg', 2);
-- 查询示例
-- 1. 查询青年男性的中文资源
-- select * from dh_library_item where age = 'youth' and gender = 'male' and language = 'chinese' and is_deleted = 0;
-- 2. 查询情感场景的女声资源
-- select * from dh_library_item where scene = 'emotion' and gender = 'female' and is_deleted = 0;
-- 3. 按创建时间倒序查询自定义资源
-- select * from dh_library_item where is_custom = 1 and is_deleted = 0 order by create_time desc;

View File

@ -23,4 +23,6 @@ CREATE TABLE IF NOT EXISTS `t_task_record_history` (
`type` tinyint DEFAULT NULL COMMENT '消费类型0充点1按次数2按点数',
`account_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '从哪个账户进行的扣费',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=791699284218093843 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='换哒-任务生成记录';
) ENGINE=InnoDB AUTO_INCREMENT=791699284218093843 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='换哒-任务生成记录';