Springboot使用Redis发布订阅自动更新缓存数据源

news2025/2/21 10:44:13

背景

当项目有很多数据源的时候,通常会在启动的时候就把数据源连接加载缓存上,当数据源进行变更后如何自动实时将缓存的数据源进行更新呢?如果是单个项目直接调接口方法就行了,但是涉及到分布式多个系统呢?

解决方案:

使用Redis轻量级消息队列,它可以实现实时通知,实时状态更新等功能,配合AOP实现自动更新数据源状态。

下面结合代码写一个使用示例:

1.首先创建数据源对象

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 *
 * @author ws
 * @since 2022-08-12
 */
@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("ed_datasource_info")
public class DatasourceInfo implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 数据源编码
     */
    @TableField("datasource_code")
    private String datasourceCode;

    /**
     * 数据源名称
     */
    @TableField("datasource_name")
    private String datasourceName;

    /**
     * 数据源类型
     */
    @TableField("datasource_type")
    private String datasourceType;

    /**
     * 类型 0:数据库 1:Rest-api
     */
    @TableField("type")
    private Integer type;

    /**
     * 创建人
     */
    @TableField("creator")
    private String creator;

    /**
     * 模式
     */
    @TableField("schema_name")
    private String schemaName;


    @TableField("create_time")
    private Date createTime;

    @TableField("update_time")
    private Date updateTime;

    /**
     * 数据源连接信息
     */
    @TableField("link_json")
    private String linkJson;
 
}

2.初始化启动加载数据源

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sztech.common.constant.DataSourceTypeEnum;
import com.sztech.entity.DatasourceInfo;
import com.sztech.service.DatasourceInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class DataSourceRecovery implements InitializingBean {

    @Resource
    private DatasourceInfoService datasourceInfoService;

    @Override
    public void afterPropertiesSet() throws Exception {
        refresh();
    }

    private void refresh() throws Exception{
        this.refresh(null);
    }

    public void refresh(String sourceCode){
        QueryWrapper<DatasourceInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", DataSourceTypeEnum.DB.getKey());
        if(StringUtils.isNotBlank(sourceCode)){
            queryWrapper.eq("datasource_code",sourceCode);
        }

        List<DatasourceInfo> list = datasourceInfoService.list(queryWrapper);
        if(CollectionUtils.isEmpty(list)){
            return;
        }

        CountDownLatch countDownLatch = new CountDownLatch(list.size());
        for(DatasourceInfo datasourceInfo : list){
            new Thread(new ReadloadThread(datasourceInfo, countDownLatch)).start();
        }

        try {
            countDownLatch.await(1,TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            log.error("数据源加载等待超时",e);
        }
    }

    /**
     * 多线程加载数据源,提高启动速度
     */
    static class ReadloadThread implements Runnable {

        private DatasourceInfo datasourceInfo;
        private CountDownLatch countDownLatch;

        public ReadloadThread() {
        }

        public ReadloadThread(DatasourceInfo datasourceInfo,CountDownLatch countDownLatch) {
            this.datasourceInfo = datasourceInfo;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                DataSourceContext.setClientMap(datasourceInfo);
                DataSourceContext.setConfigMap(datasourceInfo.getDatasourceCode(),datasourceInfo);
            }catch (Exception e){
                log.error("datasource:{},加载失败",datasourceInfo.getDatasourceCode(),e);
            }finally {
                countDownLatch.countDown();
            }
        }
    }
}

3.创建DataSourceContext,用于数据源缓存数据源连接

import com.sztech.core.tool.DBTool;
import com.sztech.entity.DatasourceInfo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * User: wangsheng
 * Date: 2022-02-11
 * Time: 14:05
 */
public class DataSourceContext {
    /**
     * 客户端缓存
     */
    private final static Map<String, IClient> clientMap = new ConcurrentHashMap<>();

    /**
     * 数据源配置缓存
     */
    private final static Map<String, DatasourceInfo> configMap = new ConcurrentHashMap<>();

    public static void setClientMap(DatasourceInfo datasourceInfo) {
        if(clientMap.containsKey(datasourceInfo.getDatasourceCode())){
            try {
                clientMap.get(datasourceInfo.getDatasourceCode()).close();
            }catch (Exception ignored){
            }
        }
        clientMap.put(datasourceInfo.getDatasourceCode(),
                DBTool.buildClient(datasourceInfo));
    }

    public static void setConfigMap(String key, DatasourceInfo datasourceInfo) {
        configMap.put(key, datasourceInfo);
    }

    public static void removeClientMap(String key) {
        if(clientMap.containsKey(key)){
            try {
                clientMap.get(key).close();
            }catch (Exception ignored){
            }
        }
        clientMap.remove(key);
    }

    public static void removeConfigMap(String key) {
        configMap.remove(key);
    }

    public static IClient getClientMap(String key) {
        IClient client = clientMap.get(key);
        if(null == client){
            throw new RuntimeException(String.format("数据源编码:[%s]不存在或被删除...", key));
        }
        return client;
    }

    public static DatasourceInfo getConfigMap(String key) {
        DatasourceInfo datasourceInfo = configMap.get(key);
        if(null == datasourceInfo){
            throw new RuntimeException(String.format("数据源编码:[%s]不存在或被删除...", key));
        }

        return datasourceInfo;
    }
}
package com.sztech.core.tool;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.odps.Instance;
import com.sztech.common.constant.ResultEnum;
import com.sztech.common.exception.BizException;
import com.sztech.common.utils.ReflectionUtils;
import com.sztech.common.utils.SpringUtils;
import com.sztech.common.utils.ThreadPoolUtil;
import com.sztech.core.datasource.DataSourceContext;
import com.sztech.core.datasource.IClient;
import com.sztech.core.datasource.rdbms.RdbmsConfig;
import com.sztech.entity.*;
import com.sztech.pojo.dto.ColumnDto;
import com.sztech.pojo.dto.QueryTableDto;
import com.sztech.pojo.dto.TableDto;
import com.sztech.pojo.node.PartitionColumn;
import com.sztech.pojo.vo.*;
import com.sztech.service.CreateTableLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Description:
 * User: wangsheng
 * Date: 2022-08-12
 * Time: 16:59
 */
@Slf4j
public class DBTool {

    /**
     * 建立客户端
     */
    public static IClient buildClient(DatasourceInfo datasourceInfo) {
        IClient client = ReflectionUtils.getInstanceFromCache(datasourceInfo.getDatasourceType(), "type", IClient.class);
        return client.open(datasourceInfo);
    }

    /**
     * 测试数据源
     *
     * @return
     */
    public static boolean testSource(DatasourceInfo datasourceInfo) {
        IClient client = ReflectionUtils.getInstanceFromCache(datasourceInfo.getDatasourceType(), "type", IClient.class);
        return client.testSource(datasourceInfo);
    }

    public static List<String> getSchemas(DatasourceInfo datasourceInfo) {
        List<String> schemas = new ArrayList<>();
        Connection conn = null;
        try {
            IClient client = ReflectionUtils.getInstanceFromCache(datasourceInfo.getDatasourceType(), "type", IClient.class);
            Class.forName(client.driverName());
            String linkJson = datasourceInfo.getLinkJson();
            RdbmsConfig rdbmsConfig = JSONObject.parseObject(linkJson).toJavaObject(RdbmsConfig.class);
            conn = DriverManager.getConnection(rdbmsConfig.getJdbcUrl(), rdbmsConfig.getUsername(), rdbmsConfig.getDecodePassword());
            DatabaseMetaData metadata = conn.getMetaData();
            try (ResultSet resultSet = metadata.getSchemas()) {
                while (resultSet.next()) {
                    String schemaName = resultSet.getString("TABLE_SCHEM");
                    schemas.add(schemaName);
                }
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        }
        return schemas;
    }

    /**
     * 获取驱动名称
     */
    public static String getDriverName(String datasourceType) {
        IClient client = ReflectionUtils.getInstanceFromCache(datasourceType, "type", IClient.class);
        return client.driverName();
    }

    /**
     * 获取表中列信息
     */
    public static List<ColumnDto> getColumns(String datasourceCode, String tableName) {
        return DataSourceContext.getClientMap(datasourceCode).getColumns(tableName);
    }

    /**
     * 获取表中分区列信息
     */
    public static List<String> getPartitionColumns(String datasourceCode, String tableName) {
        return DataSourceContext.getClientMap(datasourceCode).getPartitionColumns(tableName);
    }

    /**
     * 获取表信息
     */
    public static List<String> getTableNames(String datasourceCode, String tableNameLike) {
        return DataSourceContext.getClientMap(datasourceCode).getTableNames(tableNameLike);
    }

    /**
     * 获取表信息
     */
    public static List<TableDto> getTables(String datasourceCode) {
        return DataSourceContext.getClientMap(datasourceCode).getTables();
    }

    /**
     * 获取单个表信息
     */
    public static TableDto getTableByName(String datasourceCode, String tableName) {
        return DataSourceContext.getClientMap(datasourceCode).getTableByName(tableName);
    }

    /**
     * 获取单个表信息(创建时间,字段数)
     */
    public static TableDto getTableField(String datasourceCode, String tableName) {
        return DataSourceContext.getClientMap(datasourceCode).getTableField(tableName);
    }


    /**
     * 获取表信息(获取创建时间)
     *
     * @param dto
     * @return
     */
    public static TableInfoVo getTableData(QueryTableDto dto) {
        IClient client = DataSourceContext.getClientMap(dto.getDataSourceCode());
        return client.getTableInfo(dto.getTableName());
    }


    /**
     * 根据字段type建表
     */
    public static void createTableByColumns(List<ColumnDto> columnDtos, String tableName, String datasourceCode) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        List<String> sqls = client.buildTableSql(columnDtos, tableName, true);
        log.info("执行建表语句为:" + JSON.toJSONString(sqls));
        sqls.forEach(s -> client.executeCommandSyn(s, new HashMap<>()));
    }

    /**
     * 根据字段type建表
     */
    public static void createTableByNotTransformedColumns(List<ColumnDto> columnDtos, String tableName, String datasourceCode) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        List<String> sqls = client.buildTableSql(columnDtos, tableName, false);
        log.info("执行建表语句为:" + JSON.toJSONString(sqls));
        sqls.forEach(s -> client.executeCommandSyn(s, new HashMap<>()));
    }

    /**
     * 创建索引
     * 注: oracle 索引名在整个库里必须唯一 否则建立失败
     *
     * @param datasourceCode 数据源编码
     * @param tableName      表名
     * @param filedNames     filed1,filed2...
     * @param unique         唯一
     */
    public static void createIndex(String datasourceCode, String tableName, String filedNames, Boolean unique) {
        DataSourceContext.getClientMap(datasourceCode).createIndex(tableName, filedNames, unique);
    }

    /**
     * sql校验
     *
     * @param datasourceCode
     * @param sql
     * @param sourceType
     * @return
     */
    public static Map<String, Object> checkSql(String datasourceCode, String sql, String sourceType) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        return client.checkSql(sql, sourceType);
    }

    /**
     * 根据sql创建表
     *
     * @param datasourceCode
     * @param sql
     */
    public static void createTableWithSql(String datasourceCode, String sql) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        log.info("执行建表语句为:" + JSON.toJSONString(sql));
        client.executeCommandSyn(sql, new HashMap<>());
//        DataSourceContext.getClientMap(datasourceCode).createTableWithSql(sql);
    }

    /**
     * 删除表
     *
     * @param datasourceCode
     * @param tableName
     */
    public static void dropTable(String datasourceCode, String tableName) {
        DataSourceContext.getClientMap(datasourceCode).dropTable(tableName);
    }

    /**
     * 单表查询数据
     */
    public static List<Map<String, Object>> selectDataFromTable(String datasourceCode, List<DataTableColumn> columns, String tableName, String search, Integer limit) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql = client.getSelectSql(columns, tableName, search, limit);
        log.info("执行语句:" + selectSql);
        return client.selectDataFromTable(selectSql, null);
    }

    /**
     * 单表查询数据
     */
    public static List<Map<String, Object>> selectFromTable(String datasourceCode, List<FormColumn> columns, List<FormColumn> searchColumns, String tableName, String search, Integer pageNum, Integer pageSize, MapSqlParameterSource params) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql = client.getFormSelectSql(columns, searchColumns, tableName, search, pageNum, pageSize, params);
        log.info("执行语句:" + selectSql);
        return client.selectDataFromTable(selectSql, params);
    }

    /**
     * 单表查询数据
     */
    public static List<Map<String, Object>> selectFromForBackUp(String datasourceCode, List<FormColumn> columns, List<FormColumn> searchColumns, String tableName, String search, Integer pageNum, Integer pageSize, MapSqlParameterSource params) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql = client.selectFromForBackUp(columns, searchColumns, tableName, search, pageNum, pageSize, params);
        log.info("执行语句:" + selectSql);
        return client.selectDataFromTable(selectSql, params);
    }

    /**
     * 单表查询数据
     */
    public static List<Map<String, Object>> selectFromFile(String datasourceCode, List<FormColumn> columns, List<FormColumn> searchColumns, String tableName, String search, Integer pageNum, Integer pageSize, MapSqlParameterSource params) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql = client.getFormSelectSqlForFile(columns, searchColumns, tableName, search, pageNum, pageSize, params);
        log.info("执行语句:" + selectSql);
        return client.selectDataFromTable(selectSql, params);
    }
    /**
     * 查询单表是否存在文件名
     */
    public static List<Map<String, Object>> getExistOldName(String datasourceCode, String tableName, String search) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql = client.getExistOldName( tableName, search);
        log.info("执行语句:" + selectSql);
        return client.selectDataFromTable(selectSql, null);
    }
    /**
     * 单表查询数据(查询归集表专门使用)
     */
    public static List<Map<String, Object>> selectCollectTable(CollectConditionVo vo) {
        IClient client = DataSourceContext.getClientMap(vo.getDatasourceCode());
        // 获取查询语句
        String selectSql = client.getCollectTable(vo);
        log.info("执行语句:" + selectSql);
        return client.selectDataFromTable(selectSql, vo.getParams());
    }

    /**
     * 单表查询数据量
     */
    public static Map<String, Object> getFormCount(String datasourceCode, List<FormColumn> columns, List<FormColumn> searchColumns, String tableName, String search, MapSqlParameterSource params) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql = client.getCountSql(columns, searchColumns, tableName, search, params);
        log.info("执行语句:" + selectSql);
        return client.getCount(selectSql, params);
    }

    /**
     * 查询区县库表的数据量
     */
    public static Map<String, Object> getCountryCount(String datasourceCode, String tableName,  MapSqlParameterSource params) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql ="select count(1) as count from "+tableName;
        log.info("执行语句:" + selectSql);
        return client.getCount(selectSql, params);
    }

    public static Map<String, Object> getFormCountForFile(String datasourceCode, List<FormColumn> columns, List<FormColumn> searchColumns, String tableName, String search, MapSqlParameterSource params) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        // 获取查询语句
        String selectSql = client.getCountSqlForFile(columns, searchColumns, tableName, search, params);
        log.info("执行语句:" + selectSql);
        return client.getCount(selectSql, params);
    }

    /**
     * 查询表数据量
     */
    public static Long getTableRows(String datasourceCode, String tableName) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        return client.getTableRows(tableName);
    }

    /**
     * 查询表对应分区数据量
     */
    public static Long getTablePartitionRows(String datasourceCode, String tableName, List<PartitionColumn> partitionColumns) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        return client.getTablePartitionRows(tableName, partitionColumns);
    }

    /**
     * 查询表数据量
     */
    public static Integer getTablePhysicalSize(String datasourceCode, String tableName) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        return client.getPhysicalSize(tableName);
    }

    /**
     * 获取表最大值
     *
     * @param datasourceCode 数据源编码
     * @param tableName      表名
     * @param incColumnName  自增列名
     * @return {@link Integer}
     */
    public static Object getMaxValue(String datasourceCode, String tableName, String incColumnName, String condition) {
        return DataSourceContext.getClientMap(datasourceCode).getMaxValue(tableName, incColumnName, condition);
    }

    public static Object getMaxValue(String datasourceCode, String schema, String tableName, String incColumnName, String condition) {
        return DataSourceContext.getClientMap(datasourceCode).getMaxValue(schema, tableName, incColumnName, condition);
    }


    public static Object getMaxTime(String datasourceCode, String schema, String tableName, String incColumnName, String tongId,String condition) {
        return DataSourceContext.getClientMap(datasourceCode).getMaxTime(schema, tableName, incColumnName,tongId, condition);
    }


    /**
     * 字段存在
     *
     * @param datasourceCode 数据源编码
     * @param tableName      表名
     * @param fieldName      字段名
     * @return {@link Boolean}
     */
    public static Boolean fieldExist(String datasourceCode, String tableName, String fieldName) {
        List<ColumnDto> columns = getColumns(datasourceCode, tableName);
        return columns.stream().anyMatch(s -> s.getName().equalsIgnoreCase(fieldName));
    }

    /**
     * 数据预览 获取前十条
     *
     * @return
     */
    public static String dataView(String datasourceCode, String tableName, String condition) {
        return DataSourceContext.getClientMap(datasourceCode).dataView(tableName, condition);
    }

    /**
     * 创建分区临时表
     * odps适用
     */
    public static void createPartitionedTableByColumns(List<ColumnDto> columnDtos, String tableName, String tableComment, String partitionedField, String datasourceCode) {
        DataSourceContext.getClientMap(datasourceCode).createPartitionedTableByColumns(columnDtos, tableName, tableComment, partitionedField);
    }

    /**
     * 同步执行命令
     */
    public static void executeCommandSyn(String datasourceCode, String command, Map<String, Object> params) {
        DataSourceContext.getClientMap(datasourceCode).executeCommandSyn(command, params);
    }

    /**
     * 异步执行命令
     * odps适用
     */
    public static Instance executeCommandASyn(String datasourceCode, String command, Map<String, Object> params) {
        return DataSourceContext.getClientMap(datasourceCode).executeCommandASyn(command, params);
    }

    /**
     * 是否有导出权限
     * odps适用
     *
     * @param datasourceCode 数据源编码
     * @param tableName      表名
     * @return {@link Boolean}
     */
    public static Boolean exportEnable(String datasourceCode, String tableName) {
        return DataSourceContext.getClientMap(datasourceCode).exportEnable(tableName);
    }

    /**
     * 插入单条数据
     *
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer insert(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).insert(vo);
    }

    /**
     * 批量插入数据
     *
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer[] betchInsert(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).betchInsert(vo);
    }

    /**
     * 批量插入数据
     *
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer[] betchInsertByConnection(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).betchInsertByConnection(vo);
    }

    /**
     * 这个方法不需要分装参数,直接传字段名称list就好了
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer[] betchInsertForCommom(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).betchInsertForCommom(vo);
    }

    /**
     * 删除数据
     *
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer delete(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).delete(vo);
    }

    /**
     * 这个删除方法可以自定义条件服号
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer deleteForCommon(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).deleteForCommon(vo);
    }


    public static Integer deleteForFile(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).deleteForFile(vo);
    }

    public static String deleteForPre(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).deleteForPre(vo);
    }

    /**
     * 修改数据
     *
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer update(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).update(vo);
    }



    /**
     * 修改数据
     *
     * @param datasourceCode
     * @param vo
     * @return
     */
    public static Integer updateForFile(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).updateForFile(vo);
    }

    /**
     * 获取表单基本信息
     *
     * @param vo
     * @return
     */
    public static TableMetaDataVo getTableBasicInfo(String datasourceCode, FormTableVo vo) {
        return DataSourceContext.getClientMap(datasourceCode).getTableBasicInfo(vo);
    }

    /**
     * 根据字段type建表
     */
    public static void createCollectTable(List<CatalogColumnInfo> columnDtos, String tableName, String datasourceCode, String tableComment, Boolean ifPartition) {
        IClient client = DataSourceContext.getClientMap(datasourceCode);
        List<String> sqls = client.buildTableSqlForCollect(columnDtos, tableName, tableComment, ifPartition);
        log.info("执行建表语句为:" + JSON.toJSONString(sqls));

        try {
            sqls.forEach(s -> client.executeCommandSyn(s, new HashMap<>()));
        } catch (Exception e) {
            e.printStackTrace();
            String message = e.getMessage();
            if (e instanceof BizException) {
                BizException exception = (BizException) e;
                message = exception.getMsg();
            }

            log.error("建表错误=======================>{}:", message);
            ThreadPoolExecutor instance = ThreadPoolUtil.instance();
            String finalMessage = message;
            instance.submit(() -> {
                CreateTableLog createTableLog = new CreateTableLog();
                createTableLog.setErrorLog(finalMessage);
                createTableLog.setParams(JSON.toJSONString(sqls));
                createTableLog.setCode(tableName);
                CreateTableLogService createTableLogService = SpringUtils.getBean(CreateTableLogService.class);
                createTableLogService.save(createTableLog);
            });
            throw new BizException(ResultEnum.ERROR.getCode(), "建表失败请联系管理员");
        }

    }

    /**
     * 根据字段type建表
     */
    public static void updateCollectTable(CreateCollectVo vo) {
        IClient client = DataSourceContext.getClientMap(vo.getDatasourceCode());
        List<String> sqls = client.buildTableSqlForUpdate(vo);
        log.info("执行更新表语句为:" + JSON.toJSONString(sqls));

        try {
            sqls.forEach(s -> client.executeCommandSyn(s, new HashMap<>()));
        } catch (Exception e) {
            e.printStackTrace();
            String message = e.getMessage();
            if (e instanceof BizException) {
                BizException exception = (BizException) e;
                message = exception.getMsg();
            }
            log.error("建表错误=======================>{}:", message);
            ThreadPoolExecutor instance = ThreadPoolUtil.instance();
            String finalMessage = message;
            instance.submit(() -> {
                CreateTableLog createTableLog = new CreateTableLog();
                createTableLog.setErrorLog(finalMessage);
                createTableLog.setParams(JSON.toJSONString(sqls));
                createTableLog.setCode(vo.getTableName());
                CreateTableLogService createTableLogService = SpringUtils.getBean(CreateTableLogService.class);
                createTableLogService.save(createTableLog);
            });
            log.info("建表失败了开始准备抛出了-------------------------------------->");
            throw new BizException(ResultEnum.ERROR.getCode(), "建表失败请联系管理员");
        }
    }

    /**
     * 获取数据源下所有表信息(包括表名,表字段数,表创建时间)
     *
     * @param datasourceCode
     * @param tableNameLike
     * @return
     */
    public static List<TableDto> getTablesDetail(String datasourceCode, String tableNameLike, Integer start, Integer pageSize, String specifyTableName) {
        return DataSourceContext.getClientMap(datasourceCode).getTablesDetail(tableNameLike, start, pageSize, specifyTableName);
    }

    /**
     * 获取表数量
     * @param datasourceCode
     * @param tableName
     * @return
     */
    public static Long getTableCountSchema(String datasourceCode, String tableName) {
        return DataSourceContext.getClientMap(datasourceCode).getTableCountSchema(tableName);
    }

    public static Integer getTableColumnCount(String dataSourceCode, String tableName) {
        return DataSourceContext.getClientMap(dataSourceCode).getTableColumnCount(tableName);
    }

    public static Integer getPreTableColumnCount(String dataSourceCode, String tableName) {
        return DataSourceContext.getClientMap(dataSourceCode).getPreTableColumnCount(tableName);
    }

    /**
     * 获取符号
     * @return
     */
    public static String getSymbol(String datasourceCode) {
        return DataSourceContext.getClientMap(datasourceCode).getSymbol();
    }

}


import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ReflectionUtils {

    private static final Map<String, Set<?>> clazzMap = new ConcurrentHashMap<>();
    private static final ReentrantLock clazzLock = new ReentrantLock();

    /**
     * 通过反射获取接口/抽象类的所有实现类
     * 通过缓存类信息减少查找时间
     * 接口与抽象类必须放在实现类的同级目录或者父目录
     */
    @SuppressWarnings("unchecked")
    public static <T> Set<Class<? extends T>> getReflections(Class<T> clazz) {
        if (clazzMap.containsKey(clazz.getName())) {
            return (Set<Class<? extends T>>) clazzMap.get(clazz.getName());
        }

        try {
            clazzLock.lock();
            if (clazzMap.containsKey(clazz.getName())) {
                return (Set<Class<? extends T>>) clazzMap.get(clazz.getName());
            }

            Reflections reflections = new Reflections(clazz.getPackage().getName());
            Set<Class<? extends T>> subTypesOf = reflections.getSubTypesOf(clazz);
            clazzMap.put(clazz.getName(), subTypesOf);
            return subTypesOf;
        } catch (Exception e) {
            log.error("getReflections error", e);
        } finally {
            clazzLock.unlock();
        }

        return new HashSet<>();
    }


    /**
     * 通过反射获取新对象
     * @param type type
     * @param methodName methodName
     * @param clazz clazz
     * @return <T>
     */
    public static <T> T getInstance(String type, String methodName, Class<T> clazz) {
        Set<Class<? extends T>> set = getReflections(clazz);
        for (Class<? extends T> t : set) {
            try {
                //排除抽象类
                if (Modifier.isAbstract(t.getModifiers())) {
                    continue;
                }
                Object obj = t.getMethod(methodName).invoke(t.newInstance());
                if (type.equalsIgnoreCase(obj.toString())) {
                    return t.newInstance();
                }
            } catch (Exception e) {
                log.error("getInstance error", e);
            }
        }

        throw new RuntimeException("implement class not exist");
    }

    /**
     * 通过反射获取新对象
     * @param type type
     * @param methodName methodName
     * @param clazz clazz
     * @return <T>
     */
    public static <T> T getInstanceFromCache(String type, String methodName, Class<T> clazz) {
        return getInstance(type, methodName, clazz);
    }

}

 client客户接口端适配多种数据源


import com.ws.websocket.entity.DatasourceInfo;


/**
 * Description:
 * User: wangsheng
 * Date: 2022-12-30
 * Time: 10:31
 */
public interface IClient {
    /**
     * 连接数据源
     *
     * @param dataSourceInfo 数据源信息
     * @return {@link IClient}
     */
    IClient open(DatasourceInfo dataSourceInfo);
    /**
     * 关闭数据源
     */
    void close();
    /**
     * 驱动类型
     *
     * @return
     */
    String driverName();
    /**
     * 数据源类型
     *
     * @return {@link String}
     */
    String type();
    /**
     * 测试数据源
     *
     * @param datasourceInfo
     * @return
     */
    boolean testSource(DatasourceInfo datasourceInfo);

}


import com.ws.websocket.entity.DatasourceInfo;
//公共查询
public abstract class AbsClient implements IClient  {
    protected DatasourceInfo datasourceInfo;
}
package com.ws.websocket.util;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSONObject;
import com.ws.websocket.entity.DatasourceInfo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

@Slf4j
public abstract class AbsRdbmsClient extends AbsClient {

    protected DruidDataSource druidDataSource;
    @Override
    public IClient open(DatasourceInfo datasourceInfo) {
        RdbmsConfig rdbmsConfig = JSONObject.parseObject(datasourceInfo.getLinkJson()).toJavaObject(RdbmsConfig.class);
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setInitialSize(5);
        druidDataSource.setMinIdle(30);
        druidDataSource.setMaxActive(300);
        druidDataSource.setMaxWait(10000);

        druidDataSource.setBreakAfterAcquireFailure(true);// 跳出重试循环
        druidDataSource.setConnectionErrorRetryAttempts(3);// 重试三次
        druidDataSource.setTimeBetweenConnectErrorMillis(3000);
        druidDataSource.setLoginTimeout(3);

        druidDataSource.setUrl(rdbmsConfig.getJdbcUrl());
        druidDataSource.setDriverClassName(driverName());
        druidDataSource.setUsername(rdbmsConfig.getUsername());
        //解密
        //  druidDataSource.setPassword(RsaUtils.decode(rdbmsConfig.getPassword()));
        druidDataSource.setPassword(rdbmsConfig.getPassword());
        // 设置 MetaUtil 工具类所需参数
        Properties properties = new Properties();

        properties.put("remarks", "true");
        properties.put("useInformationSchema", "true");
        druidDataSource.setConnectProperties(properties);

        this.druidDataSource = druidDataSource;
        this.datasourceInfo = datasourceInfo;
        return this;
    }

    @Override
    public void close() {
        druidDataSource.close();
    }


    @Override
    public boolean testSource(DatasourceInfo datasourceInfo) {
        Connection connection = null;
        try {
            Class.forName(driverName());
            String linkJson = datasourceInfo.getLinkJson();
            RdbmsConfig rdbmsConfig = JSONObject.parseObject(linkJson).toJavaObject(RdbmsConfig.class);
            connection = DriverManager.getConnection(rdbmsConfig.getJdbcUrl(), rdbmsConfig.getUsername(), rdbmsConfig.getPassword());
            // 有效
            if (connection.isValid(3)) {
                return true;
            } else {
                return false;
            }
        } catch (SQLException e) {
            log.error("数据源测试失败", e);
            return false;
        } catch (ClassNotFoundException e) {
            log.error("未找到驱动信息:{}", driverName());
            return false;
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
    @Data
    class RdbmsConfig  {

        private String jdbcUrl;

        private String username;

        private String password;



        public void setSSL() {
            String lowerCase = this.jdbcUrl.toLowerCase();
            if (!lowerCase.contains("usessl")) {
                if (this.jdbcUrl.contains("?")) {
                    this.jdbcUrl = this.jdbcUrl + "&useSSL=false";
                } else {
                    this.jdbcUrl = this.jdbcUrl + "?useSSL=false";
                }
            }
        }
    }
}
import com.alibaba.fastjson.JSONObject;
import com.ws.websocket.entity.DatasourceInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

@Slf4j
public class DmClient extends AbsRdbmsClient {
    private String schema;
    @Override
    public String type() {
        return "DMDB";
    }

    @Override
    public String driverName() {
        return "dm.jdbc.driver.DmDriver";
    }
    @Override
    public IClient open(DatasourceInfo datasourceInfo) {
        RdbmsConfig commonLinkParams = JSONObject.parseObject(datasourceInfo.getLinkJson()).toJavaObject(RdbmsConfig.class);
        this.schema = StringUtils.isNotBlank(datasourceInfo.getSchemaName()) ? datasourceInfo.getSchemaName() : commonLinkParams.getUsername().toUpperCase();
        datasourceInfo.setSchemaName(schema);
        return super.open(datasourceInfo);
    }
    @Override
    public void close() {

    }

    @Override
    public boolean testSource(DatasourceInfo datasourceInfo) {
        return false;
    }
}

4.创建redis订阅数据源操作频道配置

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

/**
 * @Author: wangsheng
 * @Data: 2022/8/16 16:40
 */
@Slf4j
@Configuration
public class RedisListenerConfig {
    /**
     * 订阅数据源操作频道
     *
     * @param connectionFactory connectionFactory
     * @param dataSourceMonitor 数据源监视器
     * @return RedisMessageListenerContainer
     */
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            DataSourceMonitor dataSourceMonitor){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(dataSourceMonitor, new PatternTopic("DATASOURCE_CHANNEL"));
        log.info(dataSourceMonitor.getClass().getName() + " 订阅频道 {}", "DATASOURCE_CHANNEL");
        return container;
    }
}

5.redis监听数据源操作


import com.alibaba.fastjson.JSONObject;
import com.ws.websocket.entity.DatasourceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;

/**
 * Description: redis监听数据源操作
 * User: wangsheng
 * Date: 2022-08-12
 * Time: 17:07
 */
@Slf4j
@Component
public class DataSourceMonitor implements MessageListener {

    @Override
    public void onMessage(Message message, byte[] bytes) {
        JSONObject box = JSONObject.parseObject(new String(message.getBody(), StandardCharsets.UTF_8));
        String operation = box.getString("key");
        if ("SAVE_OR_UPDATE".equals(operation)) {
            // 更新 DataSourceContext
            DatasourceInfo datasourceInfo = box.getObject("value", DatasourceInfo.class);
            if (datasourceInfo.getType().equals(0)) {
                String datasourceCode = datasourceInfo.getDatasourceCode();
                DataSourceContext.setConfigMap(datasourceCode, datasourceInfo);
                DataSourceContext.setClientMap(datasourceInfo);
                log.info("redis 监听到数据源 {} 新增或更新,更新 DataSourceContext 完成", datasourceCode);
            }

        } else {
            String datasourceCode = box.getString("value");
            // 更新 DataSourceContext
            DataSourceContext.removeConfigMap(datasourceCode);
            DataSourceContext.removeClientMap(datasourceCode);
            log.info("redis 监听到数据源 {} 删除,更新 DataSourceContext 完成", datasourceCode);
        }

    }

}

6.创建AOP自动监听数据源变化


import com.alibaba.fastjson.JSONObject;
import com.ws.websocket.entity.DatasourceInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;


/**
 * @Author: wangsheng
 * @Data: 2022/8/15 16:37
 */
@Slf4j
@Aspect
@Component
public class DatasourceAspect {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    /**
     * 新增或编辑数据源时发布 Redis 消息
     */
    @AfterReturning(value = "execution(* com.ws.service.DatasourceInfoService.saveOrUpdateDatasourceInfo(..))", returning = "datasourceInfo")
    public void saveOrUpdate(JoinPoint joinPoint, DatasourceInfo datasourceInfo) {
        HashMap<String, Object> box = new HashMap<>(4);
        box.put("key", "SAVE_OR_UPDATE");
        box.put("value", datasourceInfo);
        // 发布 Redis 消息
        stringRedisTemplate.convertAndSend("DATASOURCE_CHANNEL",JSONObject.toJSONString(box));
        log.info("新增或更新数据源 {} 方法切面发布 Redis 消息完成", datasourceInfo.getDatasourceCode());
    }

    /**
     * 删除数据源时发布 Redis 消息
     */
    @AfterReturning(value = "execution(* com.ws.service.DatasourceInfoService.deleteDatasourceInfo(..))", returning = "datasourceCode")
    public void delete(JoinPoint joinPoint, String datasourceCode) {
        Map<String, Object> box = new HashMap<>(4);
        box.put("key", "DELETE");
        box.put("value", datasourceCode);
        // 发布 Redis 消息
        stringRedisTemplate.convertAndSend("DATASOURCE_CHANNEL", JSONObject.toJSONString(box));
        log.info("删除数据源 {} 方法切面发布Redis消息完成", datasourceCode);
    }
}

这样就解决了数据源连接信息自动加载更新同步的问题,但还是有个问题,当数据源重启后,缓存的连接信息会失效,且AOP无法监听到数据源重启变动,这个时候还需要一个定时任务对数据源进行连接测试,如果失效则重新连接缓存上。

7.创建定时任务

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ws.websocket.entity.DatasourceInfo;
import com.ws.websocket.service.DatasourceInfoService;
import com.ws.websocket.util.DBTool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;

@Component
@RequiredArgsConstructor
@Slf4j
public class DataSourceRetryConnectSchedule {
    @Resource
    private DatasourceInfoService datasourceInfoService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    //每2小时执行一次
    @Scheduled(cron = "0 0 */2 * * ?")
    public void RetryConnect() {
        log.info("开始监测数据源连接");
        QueryWrapper<DatasourceInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("type", 0);

        List<DatasourceInfo> list = datasourceInfoService.list(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        for (DatasourceInfo datasourceInfo : list) {
            Boolean bb = DBTool.testSource(datasourceInfo);
            if (!bb) {
                log.info("数据源重连{}"+datasourceInfo.getDatasourceName());
                HashMap<String, Object> box = new HashMap<>(4);
                box.put("key", "SAVE_OR_UPDATE");
                box.put("value", datasourceInfo);
                // 发布 Redis 消息
                stringRedisTemplate.convertAndSend("DATASOURCE_CHANNEL", JSONObject.toJSONString(box));
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2300902.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spring cloud gateway限流常见算法

目录 一、网关限流 1、限流的作用 1. 保护后端服务 2. 保证服务质量 (QoS) 3. 避免滥用和恶意攻击 4. 减少资源浪费 5. 提高系统可扩展性和稳定性 6. 控制不同用户的访问频率 7. 提升用户体验 8. 避免API滥用和负载过高 9. 监控与分析 10. 避免系统崩溃 2、网关限…

网络安全的态势如何以及如何解决?

大家好,我是AI拉呱,一个专注于人工智领域与网络安全方面的博主,现任资深算法研究员一职,兼职硕士研究生导师;热爱机器学习和深度学习算法应用,深耕大语言模型微调、量化、私域部署。曾获多次获得AI竞赛大奖,拥有多项发明专利和学术论文。对于AI算法有自己独特见解和经验…

2026考研趋势深度解析:政策变化+高效工具指南

2026考研深度解析&#xff1a;趋势洞察高效工具指南&#xff0c;助你科学备战上岸 从政策变化到工具实战&#xff0c;这份千字攻略解决99%考生的核心焦虑 【热点引入&#xff1a;考研赛道进入“高难度模式”】 2025年全国硕士研究生报名人数突破520万&#xff0c;报录比预计扩…

AI工具篇:利用DeepSeek+Kimi 辅助生成综述汇报PPT

随着科研和学术报告需求的增加&#xff0c;如何高效地准备一份结构清晰、内容充实的PPT已成为许多研究者的挑战。 传统的PPT制作过程繁琐&#xff0c;需要大量文献收集、数据分析和设计工作&#xff0c;而AI工具能够帮助提升效率&#xff0c;减少重复劳动。 本文将介绍如何使用…

【Linux系统】—— 调试器 gdb/cgdb的使用

【Linux系统】—— 调试器 gdb/cgdb的使用 1 前置准备2 快速认识 gdb3 cgdb/gdb 的使用3.1 简单认识 cgdb3.2 打断点 / 删断点3.3 逐过程 / 逐语句3.4 查看变量3.5 快速跳转 4 cgdb/gdb 调试技巧4.1 watch4.2 「set var」确定问题原因4.3 条件断点 5 概念理解6 gdb/cgdb 指令一…

Wireshark 输出 数据包列表本身的值

在 Wireshark 中&#xff0c;如果你想输出数据包列表本身的值&#xff08;例如&#xff0c;将数据包的摘要信息、时间戳、源地址、目的地址等导出为文本格式&#xff09;&#xff0c;可以使用 导出为纯文本文件 的功能。以下是详细步骤&#xff1a; 步骤 1&#xff1a;打开 Wir…

docker部署单机版doris,完整无坑

文章目录 一、部署1、修改内核参数2、下载Docker 开发环境镜像3、下载安装包4、启动镜像5、配置fe6、配置be7、远程连接 二、运维命令参考资料 一、部署 1、修改内核参数 在启动doris的be时&#xff0c;需要将 Linux 操作系统的内核参数设置为2000000&#xff0c;这里是Doris…

STM32 低功耗模式

目录 背景 低功耗模式 睡眠模式 进入睡眠模式 退出睡眠模式 停止模式 进入停止模式 退出停止模式 待机模式 进入待机模式 退出待机模式 程序 睡眠模式 休眠模式配置 进入休眠模式 退出睡眠模式 停止模式 停止模式配置 进入停止模式 退出停止模式 待机模式…

网络安全架构战略 网络安全体系结构

本节书摘来自异步社区《网络安全体系结构》一书中的第1章&#xff0c;第1.4节&#xff0c;作者【美】Sean Convery 1.4 一切皆为目标 网络安全体系结构 当前的大型网络存在着惊人的相互依赖性&#xff0c;作为一名网络安全设计师&#xff0c;对这一点必须心知肚明。Internet就…

【算法】回溯算法

回溯算法 什么是回溯 人生无时不在选择。在选择的路口&#xff0c;你该如何抉择 ..... 回溯&#xff1a; 是一种选优搜索法&#xff0c;又称为试探法&#xff0c;按选优条件向前搜索&#xff0c;以达到目标。但当探索到某一步时&#xff0c;发现原先选择并不优或达不到目标&am…

Centos安装php-8.0.24.tar

查看系统环境 cat /etc/redhat-release 预先安装必要的依赖 yum install -y \ wget \ gcc \ gcc-c \ autoconf \ automake \ libtool \ make \ libxml2 \ libxml2-devel \ openssl \ openssl-devel \ sqlite-devel yum update 1、下载解压 cd /data/ wget https:/…

机器学习(李宏毅)——RNN

一、前言 本文章作为学习2023年《李宏毅机器学习课程》的笔记&#xff0c;感谢台湾大学李宏毅教授的课程&#xff0c;respect&#xff01;&#xff01;&#xff01; 二、大纲 引例RNN历史基本思想RNN变形RNN训练 三、引例 学习RNN之前先看一个例子&#xff1a; 假设要做一…

Linux 文件系统inode软硬链接

目录 一、理解文件系统 1、前言 2、磁盘 二、inode 1、创建一个新文件的 4 个操作 三、软硬链接 1、软链接 2、硬链接 3、硬链接的应用 4、软链接的应用 一、理解文件系统 1、前言 在我们电脑文件里&#xff0c;分为打开的文件和未打开的文件&#xff0c;我们在上…

多目标粒子群优化算法-MOPSO-(机器人路径规划/多目标信号处理(图像/音频))

具体完整算法请跳转&#xff1a;多目标粒子群优化算法-MOPSO-&#xff08;机器人路径规划/多目标信号处理&#xff08;图像/音频&#xff09;&#xff09; 多目标粒子群优化算法&#xff08;Multi-Objective Particle Swarm Optimization&#xff0c;MOPSO&#xff09;是一种基…

Unity合批处理优化内存序列帧播放动画

Unity合批处理序列帧优化内存 介绍图片导入到Unity中的处理Unity中图片设置处理Unity中图片裁剪 创建序列帧动画总结 介绍 这里是针对Unity序列帧动画的优化内容&#xff0c;将多个图片合批处理然后为了降低Unity的内存占用&#xff0c;但是相对的质量也会稍微降低。可自行进行…

DAY07 Collection、Iterator、泛型、数据结构

学习目标 能够说出集合与数组的区别数组:1.是引用数据类型的一种2.可以存储多个元素3.数组的长度是固定的 int[] arr1 new int[10]; int[] arr2 {1,2,3};4.数组即可以存储基本类型的数据,又可以存储引用数据类型的数据int[],double[],String[],Student[]集合:1.是引用数据类…

k8s集群如何赋权普通用户仅管理指定命名空间资源

文章目录 1. 普通用户2. 创建私钥3. 创建 CertificateSigningRequest4. 批准 CertificateSigningRequest5. 创建 kubeconfig6. 创建角色和角色绑定7. 测试 1. 普通用户 创建用户demo useradd demo2. 创建私钥 下面的脚本展示了如何生成 PKI 私钥和 CSR。 设置 CSR 的 CN 和 …

DeepSeek与ChatGPT的全面对比

在人工智能&#xff08;AI&#xff09;领域&#xff0c;生成式预训练模型&#xff08;GPT&#xff09;已成为推动技术革新的核心力量。OpenAI的ChatGPT自发布以来&#xff0c;凭借其卓越的自然语言处理能力&#xff0c;迅速占据市场主导地位。然而&#xff0c;近期中国AI初创公…

超全Deepseek资料包,deepseek下载安装部署提示词及本地部署指南介绍

该资料包涵盖了DeepSeek模型的下载、安装、部署以及本地运行的详细指南&#xff0c;适合希望在本地环境中高效运行DeepSeek模型的用户。资料包不仅包括基础的安装步骤&#xff0c;还提供了68G多套独立部署视频教程教程&#xff0c;针对不同硬件配置的模型选择建议&#xff0c;以…

DeepSeek24小时写作机器人,持续创作高质量文案

内容创作已成为企业、自媒体和创作者的核心竞争力。面对海量的内容需求&#xff0c;人工创作效率低、成本高、质量参差不齐等问题日益凸显。如何在有限时间内产出高质量内容&#xff1f;DeepSeek写作机器人&#xff0c;一款24小时持续创作的智能工具&#xff0c;为企业和个人提…