refactor: 优化图片处理与数据库连接配置
This commit is contained in:
@@ -6,6 +6,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
|||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableAsync
|
@EnableAsync
|
||||||
@@ -14,10 +15,20 @@ public class AsyncConfig {
|
|||||||
@Bean("imageNameSyncExecutor")
|
@Bean("imageNameSyncExecutor")
|
||||||
public Executor imageNameSyncExecutor() {
|
public Executor imageNameSyncExecutor() {
|
||||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
executor.setCorePoolSize(5);
|
// 核心线程数
|
||||||
executor.setMaxPoolSize(10);
|
executor.setCorePoolSize(2);
|
||||||
|
// 最大线程数
|
||||||
|
executor.setMaxPoolSize(5);
|
||||||
|
// 队列容量
|
||||||
executor.setQueueCapacity(100);
|
executor.setQueueCapacity(100);
|
||||||
|
// 线程名前缀
|
||||||
executor.setThreadNamePrefix("imageNameSync-");
|
executor.setThreadNamePrefix("imageNameSync-");
|
||||||
|
// 拒绝策略:由调用线程处理
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||||
|
// 等待所有任务完成后再关闭线程池
|
||||||
|
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||||
|
// 等待时间(秒)
|
||||||
|
executor.setAwaitTerminationSeconds(60);
|
||||||
executor.initialize();
|
executor.initialize();
|
||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import java.util.Date;
|
|||||||
public class Grouping implements Serializable {
|
public class Grouping implements Serializable {
|
||||||
@Schema(description = "分组id",implementation = Long.class)
|
@Schema(description = "分组id",implementation = Long.class)
|
||||||
@TableId(type = IdType.ASSIGN_ID)
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING) // 仅作用于此字段
|
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||||
@TableField("id")
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description ="上级id",implementation = Long.class)
|
@Schema(description ="上级id",implementation = Long.class)
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import java.util.Date;
|
|||||||
public class Image {
|
public class Image {
|
||||||
@Schema(description = "图片id",implementation = Long.class)
|
@Schema(description = "图片id",implementation = Long.class)
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING) // 仅作用于此字段
|
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||||
@TableField("id")
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = " 外键,关联Markdown文件ID,标识图片所属文档",implementation = Long.class )
|
@Schema(description = " 外键,关联Markdown文件ID,标识图片所属文档",implementation = Long.class )
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ public class ImageName {
|
|||||||
@Schema(description = "图片名称id", implementation = Long.class)
|
@Schema(description = "图片名称id", implementation = Long.class)
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||||
@TableField("id")
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "关联的Markdown文件ID", implementation = Long.class)
|
@Schema(description = "关联的Markdown文件ID", implementation = Long.class)
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import java.util.Date;
|
|||||||
public class MarkdownFile implements Serializable {
|
public class MarkdownFile implements Serializable {
|
||||||
@Schema(description = "文本id",implementation = Long.class)
|
@Schema(description = "文本id",implementation = Long.class)
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING) // 仅作用于此字段
|
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||||
@TableField("id")
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "分组表id",implementation = Long.class)
|
@Schema(description = "分组表id",implementation = Long.class)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.test.bijihoudaun.entity;
|
package com.test.bijihoudaun.entity;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@@ -11,5 +12,6 @@ public class MarkdownFileVO extends MarkdownFile {
|
|||||||
private String groupingName;
|
private String groupingName;
|
||||||
|
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
|
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||||
private Long groupingId;
|
private Long groupingId;
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,6 @@ public class RegistrationCode implements Serializable {
|
|||||||
|
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
@Schema(description = "主键ID", name = "id")
|
@Schema(description = "主键ID", name = "id")
|
||||||
@TableField("id")
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "注册码", name = "code")
|
@Schema(description = "注册码", name = "code")
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ public class SystemSetting implements Serializable {
|
|||||||
|
|
||||||
@TableId
|
@TableId
|
||||||
@Schema(description = "设置键", name = "settingKey")
|
@Schema(description = "设置键", name = "settingKey")
|
||||||
@TableField("`setting_key`")
|
|
||||||
private String settingKey;
|
private String settingKey;
|
||||||
|
|
||||||
@Schema(description = "设置值", name = "settingValue")
|
@Schema(description = "设置值", name = "settingValue")
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ import java.util.Date;
|
|||||||
public class User {
|
public class User {
|
||||||
@Schema(description = "用户id",implementation = Long.class)
|
@Schema(description = "用户id",implementation = Long.class)
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING) // 仅作用于此字段
|
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||||
@TableField("id")
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "用户名",implementation = String.class)
|
@Schema(description = "用户名",implementation = String.class)
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ import org.apache.ibatis.annotations.Update;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface MarkdownFileMapper extends BaseMapper<MarkdownFile> {
|
public interface MarkdownFileMapper extends BaseMapper<MarkdownFile> {
|
||||||
|
|
||||||
@Select("SELECT mf.id, mf.grouping_id as groupingId, mf.`title`, mf.file_name, mf.`content`, mf.created_at, mf.updated_at, mf.is_deleted, mf.deleted_at, mf.deleted_by, mf.is_private, g.`grouping` as groupingName " +
|
/**
|
||||||
|
* 查询最近更新的笔记列表(不包含content大字段,提高性能)
|
||||||
|
*/
|
||||||
|
@Select("SELECT mf.id, mf.grouping_id as groupingId, mf.`title`, mf.file_name, null as `content`, mf.created_at, mf.updated_at, mf.is_deleted, mf.deleted_at, mf.deleted_by, mf.is_private, g.`grouping` as groupingName " +
|
||||||
"FROM `markdown_file` mf " +
|
"FROM `markdown_file` mf " +
|
||||||
"LEFT JOIN `grouping` g ON mf.grouping_id = g.id " +
|
"LEFT JOIN `grouping` g ON mf.grouping_id = g.id " +
|
||||||
"WHERE mf.is_deleted = 0 " +
|
"WHERE mf.is_deleted = 0 " +
|
||||||
@@ -23,14 +26,20 @@ public interface MarkdownFileMapper extends BaseMapper<MarkdownFile> {
|
|||||||
"LIMIT #{limit}")
|
"LIMIT #{limit}")
|
||||||
List<MarkdownFileVO> selectRecentWithGrouping(@Param("limit") int limit);
|
List<MarkdownFileVO> selectRecentWithGrouping(@Param("limit") int limit);
|
||||||
|
|
||||||
@Select("SELECT mf.id, mf.grouping_id as groupingId, mf.`title`, mf.file_name, mf.`content`, mf.created_at, mf.updated_at, mf.is_deleted, mf.deleted_at, mf.deleted_by, mf.is_private, g.`grouping` as groupingName " +
|
/**
|
||||||
|
* 根据分类ID查询笔记列表(不包含content大字段,提高性能)
|
||||||
|
*/
|
||||||
|
@Select("SELECT mf.id, mf.grouping_id as groupingId, mf.`title`, mf.file_name, null as `content`, mf.created_at, mf.updated_at, mf.is_deleted, mf.deleted_at, mf.deleted_by, mf.is_private, g.`grouping` as groupingName " +
|
||||||
"FROM `markdown_file` mf " +
|
"FROM `markdown_file` mf " +
|
||||||
"LEFT JOIN `grouping` g ON mf.grouping_id = g.id " +
|
"LEFT JOIN `grouping` g ON mf.grouping_id = g.id " +
|
||||||
"WHERE mf.grouping_id = #{groupingId} AND mf.is_deleted = 0 " +
|
"WHERE mf.grouping_id = #{groupingId} AND mf.is_deleted = 0 " +
|
||||||
"ORDER BY mf.updated_at DESC")
|
"ORDER BY mf.updated_at DESC")
|
||||||
List<MarkdownFileVO> selectByGroupingIdWithGrouping(@Param("groupingId") String groupingId);
|
List<MarkdownFileVO> selectByGroupingIdWithGrouping(@Param("groupingId") String groupingId);
|
||||||
|
|
||||||
@Select("SELECT id, grouping_id, `title`, file_name, `content`, created_at, updated_at, is_deleted, deleted_at, deleted_by, is_private FROM `markdown_file` WHERE is_deleted = 1")
|
/**
|
||||||
|
* 查询已删除的笔记(不包含content大字段)
|
||||||
|
*/
|
||||||
|
@Select("SELECT id, grouping_id, `title`, file_name, null as `content`, created_at, updated_at, is_deleted, deleted_at, deleted_by, is_private FROM `markdown_file` WHERE is_deleted = 1")
|
||||||
List<MarkdownFile> selectDeleted();
|
List<MarkdownFile> selectDeleted();
|
||||||
|
|
||||||
@Delete("DELETE FROM `markdown_file` WHERE id = #{id}")
|
@Delete("DELETE FROM `markdown_file` WHERE id = #{id}")
|
||||||
@@ -48,8 +57,15 @@ public interface MarkdownFileMapper extends BaseMapper<MarkdownFile> {
|
|||||||
* 获取所有笔记ID
|
* 获取所有笔记ID
|
||||||
* @return 所有笔记ID列表
|
* @return 所有笔记ID列表
|
||||||
*/
|
*/
|
||||||
@Select("SELECT id, grouping_id, `title`, file_name, `content`, created_at, updated_at, is_deleted, deleted_at, deleted_by, is_private FROM `markdown_file` WHERE is_deleted = 0")
|
@Select("SELECT id FROM `markdown_file` WHERE is_deleted = 0")
|
||||||
List<Integer> findAllIds();
|
List<Long> findAllIds();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有笔记的ID和content(用于图片清理任务)
|
||||||
|
* @return 所有笔记的ID和content
|
||||||
|
*/
|
||||||
|
@Select("SELECT id, `content` FROM `markdown_file` WHERE is_deleted = 0")
|
||||||
|
List<MarkdownFile> selectAllIdsAndContent();
|
||||||
|
|
||||||
@Select("SELECT mf.id, mf.grouping_id as groupingId, mf.`title`, mf.file_name, mf.`content`, mf.created_at, mf.updated_at, mf.is_deleted, mf.deleted_at, mf.deleted_by, mf.is_private, g.`grouping` as groupingName " +
|
@Select("SELECT mf.id, mf.grouping_id as groupingId, mf.`title`, mf.file_name, mf.`content`, mf.created_at, mf.updated_at, mf.is_deleted, mf.deleted_at, mf.deleted_by, mf.is_private, g.`grouping` as groupingName " +
|
||||||
"FROM `markdown_file` mf " +
|
"FROM `markdown_file` mf " +
|
||||||
|
|||||||
@@ -39,17 +39,15 @@ public class ImageCleanupService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public int cleanupRedundantImages() {
|
public int cleanupRedundantImages() {
|
||||||
// 获取所有笔记ID
|
// 批量获取所有笔记的ID和content(避免N+1查询)
|
||||||
List<Integer> allMarkdownIds = markdownFileMapper.findAllIds();
|
List<MarkdownFile> allMarkdownFiles = markdownFileMapper.selectAllIdsAndContent();
|
||||||
|
|
||||||
// 用于存储所有被引用的图片文件名
|
// 用于存储所有被引用的图片文件名
|
||||||
Set<String> referencedImageNames = new HashSet<>();
|
Set<String> referencedImageNames = new HashSet<>();
|
||||||
|
|
||||||
// 遍历所有笔记,收集被引用的图片
|
// 遍历所有笔记,收集被引用的图片
|
||||||
for (Integer markdownId : allMarkdownIds) {
|
for (MarkdownFile markdownFile : allMarkdownFiles) {
|
||||||
MarkdownFile markdownFile = markdownFileMapper.selectById(markdownId);
|
|
||||||
String content = (markdownFile != null) ? markdownFile.getContent() : "";
|
String content = (markdownFile != null) ? markdownFile.getContent() : "";
|
||||||
|
|
||||||
List<String> imageNames = MarkdownImageExtractor.extractImageFilenames(content);
|
List<String> imageNames = MarkdownImageExtractor.extractImageFilenames(content);
|
||||||
referencedImageNames.addAll(imageNames);
|
referencedImageNames.addAll(imageNames);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,12 +132,25 @@ public class ImageServiceImpl
|
|||||||
if (CollUtil.isEmpty(urls)) {
|
if (CollUtil.isEmpty(urls)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (String url : urls) {
|
// 批量查询,避免N+1问题
|
||||||
Image image = imageMapper.selectOne(new QueryWrapper<Image>().eq("url", url));
|
List<Image> images = imageMapper.selectList(new QueryWrapper<Image>().in("url", urls));
|
||||||
if (ObjectUtil.isNotNull(image)) {
|
if (CollUtil.isEmpty(images)) {
|
||||||
this.deleteImageByUrl(url);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除文件和数据库记录
|
||||||
|
for (Image image : images) {
|
||||||
|
try {
|
||||||
|
Path filePath = Paths.get(uploadDir, image.getStoredName());
|
||||||
|
Files.deleteIfExists(filePath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("删除图片文件失败: " + image.getStoredName(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量删除数据库记录
|
||||||
|
List<Long> ids = images.stream().map(Image::getId).toList();
|
||||||
|
this.removeByIds(ids);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,8 +165,8 @@ public class MarkdownFileServiceImpl
|
|||||||
imageName.setMarkdownId(markdownId);
|
imageName.setMarkdownId(markdownId);
|
||||||
return imageName;
|
return imageName;
|
||||||
}).toList();
|
}).toList();
|
||||||
// 批量插入新的文件名
|
// 批量插入新的文件名(使用MyBatis Plus批量插入)
|
||||||
list.forEach(imageName -> imageNameMapper.insert(imageName));
|
imageNameMapper.insert(list);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 数据库中已有记录,需要对比处理
|
// 数据库中已有记录,需要对比处理
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
# driver-class-name: org.sqlite.JDBC
|
|
||||||
# url: jdbc:sqlite:C:\it\houtaigunli\biji\mydatabase.db
|
|
||||||
# jpa:
|
|
||||||
# hibernate:
|
|
||||||
# ddl-auto: none
|
|
||||||
# show-sql: true
|
|
||||||
# properties:
|
|
||||||
# hibernate:
|
|
||||||
# format_sql: true
|
|
||||||
# dialect: org.hibernate.dialect.SQLiteDialect
|
|
||||||
#
|
|
||||||
|
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
url: jdbc:mysql://hdy-hk-8-8.311169.xyz:3306/biji_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
|
url: jdbc:mysql://hdy-hk-8-8.311169.xyz:3306/biji_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
|
||||||
username: biji_user
|
username: biji_user
|
||||||
password: Ll12331100
|
password: Ll12331100
|
||||||
|
# HikariCP连接池优化配置
|
||||||
|
hikari:
|
||||||
|
# 连接池名称
|
||||||
|
pool-name: BijiHikariPool
|
||||||
|
# 最小空闲连接数
|
||||||
|
minimum-idle: 5
|
||||||
|
# 最大连接数
|
||||||
|
maximum-pool-size: 20
|
||||||
|
# 连接空闲超时时间(毫秒)
|
||||||
|
idle-timeout: 300000
|
||||||
|
# 连接最大存活时间(毫秒)
|
||||||
|
max-lifetime: 1200000
|
||||||
|
# 连接超时时间(毫秒)
|
||||||
|
connection-timeout: 20000
|
||||||
|
# 测试连接是否可用的SQL
|
||||||
|
connection-test-query: SELECT 1
|
||||||
|
# 自动提交
|
||||||
|
auto-commit: true
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: update
|
ddl-auto: update
|
||||||
@@ -32,8 +38,12 @@ mybatis-plus:
|
|||||||
map-underscore-to-camel-case: true
|
map-underscore-to-camel-case: true
|
||||||
# 启用安全模式,防止SQL注入
|
# 启用安全模式,防止SQL注入
|
||||||
safe-mode: true
|
safe-mode: true
|
||||||
|
# 配置日志输出
|
||||||
|
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
|
||||||
global-config:
|
global-config:
|
||||||
db-config:
|
db-config:
|
||||||
logic-delete-field: isDeleted # 全局逻辑删除的实体字段名
|
logic-delete-field: isDeleted # 全局逻辑删除的实体字段名
|
||||||
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
|
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
|
||||||
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
||||||
|
# 全局表前缀(如果有的话)
|
||||||
|
# table-prefix: t_
|
||||||
@@ -1,38 +1,43 @@
|
|||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
# driver-class-name: org.sqlite.JDBC
|
|
||||||
# url: jdbc:sqlite:/data/mydatabase.db
|
|
||||||
# jpa:
|
|
||||||
# hibernate:
|
|
||||||
# ddl-auto: none
|
|
||||||
# show-sql: false
|
|
||||||
# properties:
|
|
||||||
# hibernate:
|
|
||||||
# format_sql: false
|
|
||||||
# dialect: org.hibernate.dialect.SQLiteDialect
|
|
||||||
|
|
||||||
# 上面是 默认配置,数据库为sqlite,下面是 配置mysql。从环境 变量中获取
|
|
||||||
driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver}
|
driver-class-name: ${DB_DRIVER:com.mysql.cj.jdbc.Driver}
|
||||||
url: ${DB_URL}
|
url: ${DB_URL}
|
||||||
username: ${DB_USERNAME}
|
username: ${DB_USERNAME}
|
||||||
password: ${DB_PASSWORD}
|
password: ${DB_PASSWORD}
|
||||||
|
# HikariCP连接池优化配置(生产环境)
|
||||||
|
hikari:
|
||||||
|
# 连接池名称
|
||||||
|
pool-name: BijiHikariPool
|
||||||
|
# 最小空闲连接数
|
||||||
|
minimum-idle: 10
|
||||||
|
# 最大连接数
|
||||||
|
maximum-pool-size: 50
|
||||||
|
# 连接空闲超时时间(毫秒)
|
||||||
|
idle-timeout: 600000
|
||||||
|
# 连接最大存活时间(毫秒)
|
||||||
|
max-lifetime: 1800000
|
||||||
|
# 连接超时时间(毫秒)
|
||||||
|
connection-timeout: 30000
|
||||||
|
# 测试连接是否可用的SQL
|
||||||
|
connection-test-query: SELECT 1
|
||||||
|
# 自动提交
|
||||||
|
auto-commit: true
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: update
|
ddl-auto: update
|
||||||
show-sql: true
|
show-sql: false
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
format_sql: true
|
format_sql: false
|
||||||
dialect: org.hibernate.dialect.MySQLDialect
|
dialect: org.hibernate.dialect.MySQLDialect
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# MyBatis-Plus配置
|
# MyBatis-Plus配置
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/*.xml
|
||||||
configuration:
|
configuration:
|
||||||
map-underscore-to-camel-case: true
|
map-underscore-to-camel-case: true
|
||||||
|
# 生产环境关闭SQL日志
|
||||||
|
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||||
global-config:
|
global-config:
|
||||||
db-config:
|
db-config:
|
||||||
logic-delete-field: isDeleted # 全局逻辑删除的实体字段名
|
logic-delete-field: isDeleted # 全局逻辑删除的实体字段名
|
||||||
|
|||||||
@@ -4,19 +4,21 @@
|
|||||||
<el-header class="header" v-if="!isMobile">
|
<el-header class="header" v-if="!isMobile">
|
||||||
<h1 @click="$emit('reset-view')" style="cursor: pointer; flex-grow: 1;">我的笔记</h1>
|
<h1 @click="$emit('reset-view')" style="cursor: pointer; flex-grow: 1;">我的笔记</h1>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<el-input
|
<div class="search-box">
|
||||||
:model-value="searchKeyword"
|
<el-input
|
||||||
@update:model-value="$emit('update:searchKeyword', $event)"
|
:model-value="searchKeyword"
|
||||||
placeholder="搜索笔记标题"
|
@update:model-value="handleSearchInput"
|
||||||
class="search-input"
|
placeholder="搜索笔记标题"
|
||||||
@keyup.enter="$emit('search')"
|
class="search-input"
|
||||||
>
|
@keyup.enter="$emit('search')"
|
||||||
<template #append>
|
clearable
|
||||||
<el-button @click="$emit('search')">
|
@clear="handleClear"
|
||||||
<el-icon><Search /></el-icon>
|
>
|
||||||
</el-button>
|
<template #suffix>
|
||||||
</template>
|
<el-icon class="search-icon" @click="$emit('search')"><Search /></el-icon>
|
||||||
</el-input>
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
<div v-if="userStore.isLoggedIn" class="user-actions">
|
<div v-if="userStore.isLoggedIn" class="user-actions">
|
||||||
<span class="welcome-text">欢迎, {{ userStore.userInfo?.username }}</span>
|
<span class="welcome-text">欢迎, {{ userStore.userInfo?.username }}</span>
|
||||||
<el-button type="danger" @click="$emit('logout')">退出</el-button>
|
<el-button type="danger" @click="$emit('logout')">退出</el-button>
|
||||||
@@ -58,14 +60,16 @@
|
|||||||
<div v-if="isMobile" class="mobile-search-container">
|
<div v-if="isMobile" class="mobile-search-container">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="searchKeyword"
|
:model-value="searchKeyword"
|
||||||
@update:model-value="$emit('update:searchKeyword', $event)"
|
@update:model-value="handleSearchInput"
|
||||||
placeholder="搜索笔记标题"
|
placeholder="搜索笔记标题"
|
||||||
class="mobile-search-input"
|
class="mobile-search-input"
|
||||||
@keyup.enter="$emit('search')"
|
@keyup.enter="$emit('search')"
|
||||||
|
@clear="handleClear"
|
||||||
|
clearable
|
||||||
size="large"
|
size="large"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<el-icon><Search /></el-icon>
|
<el-icon class="mobile-search-icon"><Search /></el-icon>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,6 +108,21 @@ const handleUpload = (file) => {
|
|||||||
emit('upload-markdown', file);
|
emit('upload-markdown', file);
|
||||||
return false; // Prevent el-upload's default behavior
|
return false; // Prevent el-upload's default behavior
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理搜索输入 - 当输入为空时自动回到首页
|
||||||
|
const handleSearchInput = (value) => {
|
||||||
|
emit('update:searchKeyword', value);
|
||||||
|
// 当清空输入时,自动触发回到首页
|
||||||
|
if (!value || value.trim() === '') {
|
||||||
|
emit('reset-view');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理清空按钮点击
|
||||||
|
const handleClear = () => {
|
||||||
|
emit('update:searchKeyword', '');
|
||||||
|
emit('reset-view');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -130,8 +149,42 @@ const handleUpload = (file) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.search-input .el-input__wrapper) {
|
:deep(.search-input .el-input__wrapper) {
|
||||||
border-radius: var(--border-radius) !important;
|
border-radius: 20px !important;
|
||||||
|
padding-right: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-input .el-input__wrapper:hover) {
|
||||||
|
box-shadow: 0 0 0 1px var(--el-color-primary) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.search-input .el-input__wrapper.is-focus) {
|
||||||
|
box-shadow: 0 0 0 1px var(--el-color-primary) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--el-text-color-placeholder);
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
background-color: var(--el-color-primary-light-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-search-icon {
|
||||||
|
color: var(--el-text-color-placeholder);
|
||||||
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-actions, .guest-actions {
|
.user-actions, .guest-actions {
|
||||||
|
|||||||
Reference in New Issue
Block a user