5 Commits

Author SHA1 Message Date
zc
5407bae132 优化 2026-06-15 18:10:42 +08:00
zc
e370b51c39 天海优化配置 2026-05-09 09:25:47 +08:00
zc
5cc05036da 天海优化数据库名 2026-05-08 16:48:26 +08:00
zc
e72867edbe 整箱导出优化 2026-05-07 16:22:54 +08:00
zc
470a2dbe6e 主从库双数据源 2026-05-07 15:58:13 +08:00
19 changed files with 393 additions and 41 deletions

View File

@@ -0,0 +1,18 @@
package top.wms.admin.common.config.mybatis;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import top.wms.admin.common.constant.DataSourceContextHolder;
/**
* 动态数据源路由
*
* @author Admin
* @since 2024/12/22
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}

View File

@@ -0,0 +1,40 @@
package top.wms.admin.common.constant;
/**
* 数据源上下文 Holder
*
* @author Admin
* @since 2024/12/22
*/
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
private DataSourceContextHolder() {
}
/**
* 设置数据源
*
* @param dataSource 数据源标识
*/
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
/**
* 获取数据源
*
* @return 数据源标识
*/
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
/**
* 清除数据源
*/
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}

View File

@@ -74,6 +74,11 @@ public class UserContext implements Serializable {
*/
private String clientId;
/**
* 数据源标识
*/
private String dataSource;
public UserContext(Set<String> permissions, Set<RoleContext> roles, Integer passwordExpirationDays) {
this.permissions = permissions;
this.setRoles(roles);

View File

@@ -0,0 +1,30 @@
package top.wms.admin.common.enums;
/**
* 数据源枚举
*
* @author Admin
* @since 2024/12/22
*/
public enum DataSourceEnum {
/**
* 主数据源
*/
WMS_TH("wms_th"),
/**
* 从数据源
*/
WMS_QL("wms_ql");
private final String value;
DataSourceEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

View File

@@ -8,6 +8,7 @@ import jakarta.servlet.http.HttpServletRequest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import top.wms.admin.auth.model.req.LoginReq;
import top.wms.admin.common.constant.DataSourceContextHolder;
import top.wms.admin.common.context.RoleContext;
import top.wms.admin.common.context.UserContext;
import top.wms.admin.common.context.UserContextHolder;
@@ -91,6 +92,14 @@ public abstract class AbstractLoginHandler<T extends LoginReq> implements LoginH
userContext.setClientType(client.getClientType());
model.setExtra(CLIENT_ID, client.getClientId());
userContext.setClientId(client.getClientId());
// 设置数据源(如果用户未配置,则使用默认主库)
String dataSource = user.getDataSource();
if (dataSource == null || dataSource.isEmpty()) {
dataSource = "wms_th"; // 默认主库
}
userContext.setDataSource(dataSource);
DataSourceContextHolder.setDataSource(dataSource);
// 登录并缓存用户信息
StpUtil.login(userContext.getId(), model.setExtraData(BeanUtil.beanToMap(new UserExtraContext(SpringWebUtils
.getRequest()))));

View File

@@ -10,6 +10,8 @@ import top.continew.starter.data.mp.base.BaseMapper;
import top.wms.admin.fullWorkOrder.model.entity.FullWorkOrderDO;
import top.wms.admin.fullWorkOrder.model.resp.FullWorkOrderResp;
import java.util.List;
/**
* 整箱领取记录 Mapper
*
@@ -21,4 +23,6 @@ public interface FullWorkOrderMapper extends BaseMapper<FullWorkOrderDO> {
IPage<FullWorkOrderResp> selectFullWorkOrderPage(@Param("page") Page<Object> objectPage,
@Param(Constants.WRAPPER) QueryWrapper<FullWorkOrderDO> queryWrapper);
List<FullWorkOrderResp> selectFullWorkOrderExport(@Param(Constants.WRAPPER) QueryWrapper<FullWorkOrderDO> queryWrapper);
}

View File

@@ -71,8 +71,16 @@ public class FullWorkOrderServiceImpl extends BaseServiceImpl<FullWorkOrderMappe
@Override
public void export(FullWorkOrderQuery query, SortQuery sortQuery, HttpServletResponse response) {
List<FullWorkOrderResp> list = super.list(query, sortQuery, this.getDetailClass());
list.forEach(super::fill);
QueryWrapper<FullWorkOrderDO> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StrUtil.isNotBlank(query.getMaterialCode()), "f.material_code", query.getMaterialCode());
queryWrapper.eq(StrUtil.isNotBlank(query.getOrderNo()), "f.order_no", query.getOrderNo());
queryWrapper.eq(StrUtil.isNotBlank(query.getBatch()), "f.batch", query.getBatch());
queryWrapper.eq(StrUtil.isNotBlank(query.getMaterialName()), "m.material_name", query.getMaterialName());
queryWrapper.between(CollUtil.isNotEmpty(query.getCreateTime()), "f.create_time", CollUtil.getFirst(query
.getCreateTime()), CollUtil.getLast(query.getCreateTime()));
List<FullWorkOrderResp> list = baseMapper.selectFullWorkOrderExport(queryWrapper);
ExcelUtils.export(list, "整箱领取导出记录", FullWorkOrderResp.class, response);
}

View File

@@ -92,4 +92,9 @@ public class UserDO extends BaseDO {
*/
@TableField(exist = false)
private Long equipmentId;
/**
* 数据源标识wms/wms_ql
*/
private String dataSource;
}

View File

@@ -93,6 +93,12 @@ public class UserReq implements Serializable {
@Schema(description = "状态", example = "1")
private DisEnableStatusEnum status;
/**
* 状态
*/
@Schema(description = "数据源")
private String dataSource;
/**
* 设备ID
*/

View File

@@ -116,6 +116,12 @@ public class UserDetailResp extends BaseDetailResp {
@Schema(description = "设备ID")
private Long equipmentId;
/**
* 数据源
*/
@Schema(description = "数据源")
private String dataSource;
@Override
public Boolean getDisabled() {
return this.getIsSystem() || Objects.equals(this.getId(), UserContextHolder.getUserId());

View File

@@ -81,6 +81,12 @@ public class UserResp extends BaseDetailResp {
@Schema(description = "描述", example = "张三描述信息")
private String description;
/**
* 状态
*/
@Schema(description = "数据源")
private String dataSource;
/**
* 角色名称列表
*/

View File

@@ -12,4 +12,15 @@
left join sys_user u on f.create_user = u.id
${ew.customSqlSegment}
</select>
<select id="selectFullWorkOrderExport" resultType="top.wms.admin.fullWorkOrder.model.resp.FullWorkOrderResp">
select
f.*,
m.material_name materialName,
u.username createUserString
from
sys_full_work_order f
left join sys_material_info m on f.material_code = m.encoding
left join sys_user u on f.create_user = u.id
${ew.customSqlSegment}
</select>
</mapper>

View File

@@ -22,7 +22,8 @@
t1.is_system,
t1.pwd_reset_time,
t1.dept_id,
t2.name AS deptName
t2.name AS deptName,
t1.data_source
FROM sys_user AS t1
LEFT JOIN sys_dept AS t2 ON t2.id = t1.dept_id
</sql>

View File

@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.x.file.storage.spring.EnableFileStorage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -28,7 +29,7 @@ import top.continew.starter.web.model.R;
@EnableGlobalResponse
@EnableCrudRestController
@RestController
@SpringBootApplication
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@RequiredArgsConstructor
@EnableScheduling
public class WmsAdminApplication {

View File

@@ -0,0 +1,110 @@
package top.wms.admin.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import top.wms.admin.common.config.mybatis.DynamicRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 动态数据源配置
*
* @author Admin
* @since 2024/12/22
*/
@Configuration
public class DynamicDataSourceConfig {
@Value("${spring.datasource.wms_th.url}")
private String wmsUrl;
@Value("${spring.datasource.wms_th.username}")
private String wmsUsername;
@Value("${spring.datasource.wms_th.password}")
private String wmsPassword;
@Value("${spring.datasource.wms_ql.url}")
private String wms_qlUrl;
@Value("${spring.datasource.wms_ql.username}")
private String wms_qlUsername;
@Value("${spring.datasource.wms_ql.password}")
private String wms_qlPassword;
/**
* 主数据源wms
*/
@Bean("wmsDataSource")
public DataSource wmsDataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName("com.p6spy.engine.spy.P6SpyDriver");
config.setJdbcUrl(wmsUrl);
config.setUsername(wmsUsername);
config.setPassword(wmsPassword);
config.setMaximumPoolSize(20);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setKeepaliveTime(30000);
config.setMaxLifetime(1800000);
return new HikariDataSource(config);
}
/**
* 从数据源wms_ql
*/
@Bean("wms_qlDataSource")
public DataSource wms_qlDataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName("com.p6spy.engine.spy.P6SpyDriver");
config.setJdbcUrl(wms_qlUrl);
config.setUsername(wms_qlUsername);
config.setPassword(wms_qlPassword);
config.setMaximumPoolSize(20);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setKeepaliveTime(30000);
config.setMaxLifetime(1800000);
return new HikariDataSource(config);
}
/**
* 动态数据源
*/
@Bean("dynamicDataSource")
@Primary
public DataSource dynamicDataSource(DataSource wmsDataSource, DataSource wms_qlDataSource) {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
// 设置默认数据源
dynamicRoutingDataSource.setDefaultTargetDataSource(wmsDataSource);
// 设置数据源映射
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("wms_th", wmsDataSource);
dataSourceMap.put("wms_ql", wms_qlDataSource);
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
// 必须调用此方法,否则首次获取数据源时会失败
dynamicRoutingDataSource.afterPropertiesSet();
return dynamicRoutingDataSource;
}
/**
* 事务管理器
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}

View File

@@ -0,0 +1,59 @@
package top.wms.admin.config;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import top.wms.admin.common.constant.DataSourceContextHolder;
import top.wms.admin.common.context.UserContext;
import top.wms.admin.common.context.UserContextHolder;
/**
* 动态数据源拦截器
* 在每次请求时根据当前登录用户切换数据源
*
* @author Admin
* @since 2024/12/22
*/
@Component
public class DynamicDataSourceInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String requestUri = request.getRequestURI();
// 认证相关接口始终使用主库wms确保用户验证和权限获取在主库进行
if (requestUri.contains("/auth/login") || requestUri.contains("/auth/user/info") || requestUri
.contains("/auth/user/route") || requestUri.contains("/auth/logout")) {
DataSourceContextHolder.setDataSource("wms_th");
return true;
}
// 如果用户已登录,从用户上下文获取数据源并设置
if (StpUtil.isLogin()) {
try {
UserContext userContext = UserContextHolder.getContext();
if (userContext != null && userContext.getDataSource() != null) {
DataSourceContextHolder.setDataSource(userContext.getDataSource());
}
} catch (Exception e) {
// 如果获取用户上下文失败,使用默认数据源
DataSourceContextHolder.clearDataSource();
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception ex) throws Exception {
// 请求结束后清除数据源上下文
DataSourceContextHolder.clearDataSource();
}
}

View File

@@ -0,0 +1,27 @@
package top.wms.admin.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* WebMvc 配置
*
* @author Admin
* @since 2024/12/22
*/
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final DynamicDataSourceInterceptor dynamicDataSourceInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册动态数据源拦截器,优先级高于其他拦截器
registry.addInterceptor(dynamicDataSourceInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/error", "/doc.html", "/webjars/**", "/swagger-ui/**", "/swagger-resources/**", "/*/api-docs/**", "/favicon.ico");
}
}

View File

@@ -3,7 +3,6 @@ package top.wms.admin.controller.common;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.anno.Cached;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
@@ -14,7 +13,6 @@ import org.dromara.x.file.storage.core.FileInfo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import top.wms.admin.common.constant.CacheConstants;
import top.wms.admin.common.util.PictureUtils;
import top.wms.admin.system.enums.OptionCategoryEnum;
import top.wms.admin.system.model.query.*;
@@ -145,7 +143,6 @@ public class CommonController {
@SaIgnore
@Operation(summary = "查询系统配置参数", description = "查询系统配置参数")
@GetMapping("/dict/option/site")
@Cached(key = "'SITE'", name = CacheConstants.OPTION_KEY_PREFIX)
public List<LabelValueResp<String>> listSiteOptionDict() {
OptionQuery optionQuery = new OptionQuery();
optionQuery.setCategory(OptionCategoryEnum.SITE.name());

View File

@@ -1,7 +1,7 @@
--- ### 项目配置
project:
# URL跨域配置默认放行此 URL第三方登录回调默认使用此 URL 为前缀,请注意更改为你实际的前端 URL
url: http://localhost:6609
url: http://localhost:80
--- ### 服务器配置
server:
@@ -16,32 +16,36 @@ spring:
--- ### 数据源配置
spring.datasource:
# 主数据源wms_th
wms_th:
type: com.zaxxer.hikari.HikariDataSource
# 请务必提前创建好名为 wms_admin 的数据库,如果使用其他数据库名请注意同步修改 DB_NAME 配置
url: jdbc:p6spy:mysql://127.0.0.1:3306/wms?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/wms_th?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
# password: root
password: test123$
# PostgreSQL 配置
# url: jdbc:p6spy:mysql://192.168.2.30:${DB_PORT:3306}/continew?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# username: ${DB_USER:root}
# password: ${DB_PWD:SQLsql123}
# url: jdbc:p6spy:mysql://81.68.71.142:${DB_PORT:3306}/continew?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# username: ${DB_USER:root}
# password: ${DB_PWD:MYsql12@}
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
password: SQLth7410!DD
# Hikari 连接池配置
hikari:
maximum-pool-size: 20
connection-timeout: 30000
idle-timeout: 600000
keepaliveTime: 30000
max-lifetime: 1800000
# 从数据源wms_ql
wms_ql:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/wms_th?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# url: jdbc:p6spy:mysql://127.0.0.1:3306/wms_ql?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&autoReconnect=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
# password: root
password: SQLth7410!DD
# Hikari 连接池配置
hikari:
# 最大连接数量(默认 10根据实际环境调整
# 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
maximum-pool-size: 20
# 获取连接超时时间(默认 30000 毫秒30 秒)
connection-timeout: 30000
# 空闲连接最大存活时间(默认 600000 毫秒10 分钟)
idle-timeout: 600000
# 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime默认 0禁用
keepaliveTime: 30000
# 连接最大生存时间(默认 1800000 毫秒30 分钟)
max-lifetime: 1800000
## Liquibase 配置
spring.liquibase:
@@ -60,7 +64,7 @@ spring.data:
# 端口(默认 6379
port: ${REDIS_PORT:6379}
# 密码(未设置密码时请注释掉)
# password: ${REDIS_PWD:redis2025}
password: ${REDIS_PWD:food}
# 数据库索引
database: ${REDIS_DB:0}
# 连接超时时间
@@ -153,7 +157,12 @@ logging:
continew-starter.web.cors:
enabled: true
# 配置允许跨域的域名
allowed-origins: '*'
allowed-origins:
- ${project.url}
- http://127.0.0.1:80
- http://192.168.40.56:80
- http://10.126.126.3:80
- http://localhost:5173
# 配置允许跨域的请求方式
allowed-methods: '*'
# 配置允许跨域的请求头