SpringBoot使用用户输入的自定义数据源启动【附源码】

news2024/12/28 4:12:14

一、项目背景

不知道小伙伴们有没有遇到过这样的需求,就是一个项目启动时不知道数据源,需要项目无数据源启动后,用户在画面自定义录入数据源信息,然后项目再初始化数据库链接,初始化管理员用户。最后项目进入正常使用。

正常情况下,应该不会遇到这种需求吧,我们都是把数据库链接放在配置文件,然后启动项目,简简单单,轻轻松松。但是当整个项目交给用户使用时,谁使用都不知道情况下,算了,只能让他们自己输入数据源了。。。

本文就是针对这个问题,简单的介绍一下我实现的思路吧,本文只放核心代码,源码放在文章最后了。

二、涉及到技术栈

由于前端技术受限(画页面影响我输出的速度),这里就不画页面了,通过接口方式来展示。

  • Spring Boot version: 2.7.12
  • Mysql version: 8.0.29
  • Mybatis-plus version: 3.3.2
  • Mybatis动态数据源

demo中引入的依赖

		<!-- 引入web相关 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--使用Mysql数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>

        <!-- mybatis-plus的依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!--动态数据源-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>

        <!--Lombok管理Getter/Setter/log等-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
            <version>1.18.24</version>
        </dependency>

三、功能实现

本文只是为了演示实现思想,源码只是一个实现的小demo,具体使用还是需要结合自己项目。

实现思想

  1. 首先,项目启动后不加载数据源
  2. 然后通过拦截器检验,是否连接数据库
  3. 如果没有连接数据库,则去配置文件的地址找配置文件,如果存在,则加载数据库配置
  4. 如果不存在数据库文件,则抛出异常,让用户输入数据库链接
  5. 用户输入数据库链接后,进行校验连接
  6. 如果连接通过,生成配置文件,保存在指定目录,供后续重启加载
  7. 同时,装载Hikari连接池,利用Mybatis plus动态切换数据源的功能,将此连接池切为master,至此,数据库启动成功。

数据库表

CREATE TABLE `maple_user`
(
    `id`        BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
    `user_name` VARCHAR(64) NOT NULL COMMENT '登录账号',
    `password`  VARCHAR(64) NOT NULL COMMENT '登录密码',
    PRIMARY KEY (`id`) USING BTREE
) COMMENT='用户信息' COLLATE='utf8_general_ci' ENGINE=InnoDB;

创建项目

这个很简单,就不多说了(说多了就是废话😂)

image-20230525143334803

配置文件

这里配置文件简单配置了,mybatis-plus的配置和文件存储的路径(init.config)

server:
  port: 8080

mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

init:
  config:
    filePath: /srv/apps/config/
    dbFileName: db.properfies
    userFileName: user.properfies

调用接口

这里提供了四个接口,分别是

  • 初始化数据库配置
  • 校验数据库是否链接
  • 重置数据库配置,并断开链接
  • 获取用户信息,如果不存在,初始化

接口简单的controller贴上,非核心实现就不贴了,需要的朋友可以去看源码

package com.maple.inputdb.controller;

import com.maple.inputdb.bean.InitModel;
import com.maple.inputdb.config.InitDataConfig;
import com.maple.inputdb.entity.MapleUser;
import com.maple.inputdb.service.IMapleUserService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 笑小枫 <https://xiaoxiaofeng.com/>
 * @date 2023/3/9
 */
@RestController
@RequestMapping("/init")
@AllArgsConstructor
public class InitController {

    private final InitDataConfig initDataConfig;

    private final IMapleUserService userService;

    /**
     * 初始化数据库
     *
     * @param initModel 数据库配置
     */
    @PostMapping("/initData")
    public void initData(@RequestBody InitModel initModel) {
        initDataConfig.initData(initModel);
    }

    /**
     * 校验数据库是否链接
     *
     * @return 配置完成
     */
    @PostMapping("/check")
    public String check() {
        return "系统配置完成";
    }

    /**
     * 重置连接数据
     */
    @PostMapping("/resetData")
    public void resetData() {
        initDataConfig.deleteFile();
    }

    /**
     * 获取用户信息,如果不存在,初始化
     *
     * @param userName 用户账号
     * @return 用户信息
     */
    @PostMapping("/getUser")
    public MapleUser getUser(String userName) {
        return userService.getUser(userName);
    }
}

核心工具类

  • 数据库是否链接全部变量,使用单例模式,初始化是否连接数据库的状态,放全局变量
package com.maple.inputdb.config;

/**
 * 数据库是否链接全部变量
 *
 * @author 笑小枫 <https://xiaoxiaofeng.com/>
 * @date 2023/3/23
 */
public class DbStatusSingleton {
    
    /**
     * false:未连接数据库  true:已连接数据库
     */
    private boolean dbStatus = false;

    private static final DbStatusSingleton DB_STATUS_SINGLETON = new DbStatusSingleton();

    private DbStatusSingleton() {
    }

    public static DbStatusSingleton getInstance() {
        return DB_STATUS_SINGLETON;
    }

    public boolean getDbStatus() {
        return dbStatus;
    }

    public void setDbStatus(boolean dbStatus) {
        this.dbStatus = dbStatus;
    }

}
  • 创建拦截器,请求进来之前先判断是否初始化配置,如果没有则报错,这里可以指定错误码,前端可以统一拦截错误码,然后跳转初始化配置页面。注意:需要在启动类上添加@ServletComponentScan注解
package com.maple.inputdb.filter;

import com.maple.inputdb.config.DbStatusSingleton;
import com.maple.inputdb.config.DynamicDatasourceConfig;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @author 笑小枫 <https://xiaoxiaofeng.com/>
 * @date 2023/3/23
 */
@WebFilter(filterName = "dbFilter", urlPatterns = "/*")
@Order(1)
@Slf4j
@AllArgsConstructor
public class DbFilter implements Filter {

    private final DynamicDatasourceConfig datasourceConfig;

    private final List<String> excludedUrlList;

    @Override
    public void init(FilterConfig filterConfig) {
        excludedUrlList.addAll(Collections.singletonList(
                "/init/initData"
        ));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        String url = ((HttpServletRequest) request).getRequestURI();
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        boolean isMatch = false;
        for (String excludedUrl : excludedUrlList) {
            if (Pattern.matches(excludedUrl.replace("*", ".*"), url)) {
                isMatch = true;
                break;
            }
        }
        if (isMatch) {
            chain.doFilter(request, response);
        } else {
            boolean isOk = DbStatusSingleton.getInstance().getDbStatus() || datasourceConfig.checkDataSource();
            if (isOk) {
                chain.doFilter(request, response);
            } else {
                log.error("初始化系统失败,请先进行系统配置");
                writeRsp(httpServletResponse);
            }
        }
    }

    private void writeRsp(HttpServletResponse response) {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.setHeader("content-type", "application/json;charset=UTF-8");
        try {
            response.getWriter().println("初始化系统失败,请先进行系统配置");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 校验数据库配置,储存初始化配置
package com.maple.inputdb.config;

import com.maple.inputdb.bean.InitModel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author 笑小枫 <https://xiaoxiaofeng.com/>
 * @date 2023/3/10
 */
@Slf4j
@Component
@AllArgsConstructor
public class InitDataConfig {

    private final DynamicDatasourceConfig dynamicDatasourceConfig;

    private final InitConfigProperties initConfigProperties;

    public void initData(InitModel initModel) {

        if (DbStatusSingleton.getInstance().getDbStatus()
                || Boolean.TRUE.equals(dynamicDatasourceConfig.checkDataSource())) {
            throw new RuntimeException("数据已完成初始化,请勿重复操作");
        }

        // 检查数据库连接是否正确
        checkConnection(initModel);

        if (!new File(initConfigProperties.getInitFilePath() + initConfigProperties.getInitUserName()).exists()) {
            File file = createFile(initConfigProperties.getInitFilePath(), initConfigProperties.getInitUserName());
            try (FileWriter out = new FileWriter(file);
                 BufferedWriter bw = new BufferedWriter(out)) {
                bw.write("userName=" + initModel.getSysUserName());
                bw.newLine();
                bw.write("password=" + initModel.getSysPassword());
                bw.flush();
            } catch (IOException e) {
                log.info("写入管理员信息文件失败", e);
                throw new RuntimeException("写入管理员信息文件失败,请重试");
            }
        }

        if (!new File(initConfigProperties.getInitFilePath() + initConfigProperties.getInitDbName()).exists()) {
            File file = createFile(initConfigProperties.getInitFilePath(), initConfigProperties.getInitDbName());
            try (FileWriter out = new FileWriter(file);
                 BufferedWriter bw = new BufferedWriter(out)) {

                bw.write(String.format("jdbcUrl=jdbc:mysql://%s:%s/%s?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=UTF-8",
                        initModel.getDatabaseHost(), initModel.getDatabasePort(), initModel.getDatabaseName()));
                bw.newLine();
                bw.write("username=" + initModel.getUser());
                bw.newLine();
                bw.write("password=" + initModel.getPassword());
                bw.newLine();
                bw.write("driverClassName=com.mysql.cj.jdbc.Driver");
                bw.flush();

            } catch (IOException e) {
                log.info("写入数据库文件失败", e);
                throw new RuntimeException("写入数据库文件失败,请重试");
            }
        }

        boolean isOk = dynamicDatasourceConfig.checkDataSource();
        if (!isOk) {
            throw new RuntimeException("初始化数据库信息失败,请检查配置是否正确");
        }
    }

    /**
     * 检查数据库连接是否正确
     *
     * @param initModel 连接信息
     */
    private void checkConnection(InitModel initModel) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection conn = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/%s",
                    initModel.getDatabaseHost(), initModel.getDatabasePort(), initModel.getDatabaseName()), initModel.getUser(), initModel.getPassword());
            log.info("校验数据库连接成功,开始进行数据库配置" + conn.getCatalog());
            conn.close();
        } catch (SQLException | ClassNotFoundException e) {
            log.info("校验数据库连接失败", e);
            throw new RuntimeException("初始化数据库信息失败,请检查配置是否正确:" + e.getMessage());
        }
    }

    private File createFile(String path, String fileName) {
        File pathFile = new File(path);
        if (pathFile.mkdirs()) {
            log.info(path + " is not exist, this is auto created.");
        }
        File file = new File(path + File.separator + fileName);
        try {
            if (!file.createNewFile()) {
                throw new RuntimeException(String.format("创建%s文件失败,请重试", fileName));
            }
        } catch (IOException e) {
            log.error(String.format("创建%s文件失败", fileName), e);
            throw new RuntimeException(String.format("创建%s文件失败,请重试", fileName));
        }
        return file;
    }

    public void deleteFile() {
        File sqlFile = new File(initConfigProperties.getInitFilePath() + initConfigProperties.getInitDbName());
        if (sqlFile.exists()) {
            log.info(sqlFile.getName() + " --- delete sql file result:" + sqlFile.delete());
        }

        File userFile = new File(initConfigProperties.getInitFilePath() + initConfigProperties.getInitUserName());
        if (userFile.exists()) {
            log.info(userFile.getName() + " --- delete user file result:" + userFile.delete());
        }

        dynamicDatasourceConfig.stopDataSource();

        // 数据初始化状态设为false
        DbStatusSingleton.getInstance().setDbStatus(false);
        log.info("初始化数据重置完成");
    }
}
  • 使用Mybatis plus动态数据源功能,进行切换数据源,完成数据库连接启动和关闭功能
package com.maple.inputdb.config;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

/**
 * @author 笑小枫 <https://xiaoxiaofeng.com/>
 * @date 2023/3/9
 */
@Slf4j
@Component
public class DynamicDatasourceConfig {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private InitConfigProperties initConfigProperties;

    public Boolean checkDataSource() {
        try {
            Connection connection = dataSource.getConnection();
            connection.close();
            DbStatusSingleton.getInstance().setDbStatus(true);
            return true;
        } catch (Exception e) {
            log.info("获取数据库连接失败,即将重新连接数据库...");
            return addDataSource();
        }
    }

    public Boolean addDataSource() {
        File file = new File(initConfigProperties.getInitFilePath() + initConfigProperties.getInitDbName());
        if (!file.exists()) {
            log.error("连接数据库失败:没有找到db.properties文件");
            return false;
        }
        try (InputStream rs = new FileInputStream(file)) {
            Properties properties = new Properties();
            properties.load(rs);
            HikariConfig config = new HikariConfig(properties);
            config.setPassword(config.getPassword());
            DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
            ds.addDataSource("master", new HikariDataSource(config));
            ds.setPrimary("master");
            DbStatusSingleton.getInstance().setDbStatus(true);
            log.info("连接数据库成功");
            return true;
        } catch (Exception e) {
            log.error("连接数据库失败:" + e);
            return false;
        }
    }

    /**
     * 关闭数据库连接
     */
    public void stopDataSource() {
        try {
            Connection connection = dataSource.getConnection();
            connection.close();
        } catch (Exception e) {
            log.info("数据库连接已关闭,无需重复关闭...");
            return;
        }
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        HikariDataSource hds = (HikariDataSource) ds.getDataSource("master");
        try {
            if (hds.isRunning()) {
                hds.close();
                log.info("数据库连接已关闭");
            }
            ds.setPrimary("null");
            ds.removeDataSource("master");
        } catch (Exception e) {
            log.error("关闭数据库连接失败:", e);
            e.printStackTrace();
        }
    }
}
  • 初始化数据的Model类
package com.maple.inputdb.bean;

import lombok.Data;

/**
 * @author 笑小枫 <https://xiaoxiaofeng.com/>
 * @date 2023/3/10
 */
@Data
public class InitModel {
    /**
     * 数据库相关字段
     */
    private String databaseHost;
    private String databasePort;
    private String databaseName;
    private String user;
    private String password;


    /**
     * 初始化用户
     */
    private String sysUserName;
    private String sysPassword;

}
  • 初始化数据的配置文件类
package com.maple.inputdb.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @author 笑小枫 <https://xiaoxiaofeng.com/>
 * @date 2023/3/10
 */
@Data
@Configuration
public class InitConfigProperties {

    @Value("${init.config.filePath}")
    private String initFilePath;

    @Value("${init.config.dbFileName}")
    private String initDbName;

    @Value("${init.config.userFileName}")
    private String initUserName;

}

还有一些getUser接口牵扯到的文件crud类,这里就不一一去贴了。

四、功能测试

  • check连接:首先启动项目,调用/init/check接口,这是还没有初始化数据库配置,在拦截器拦截校验时报错了,如下图所示:

image-20230526111351407

  • 初始化连接:调用/init/initData接口初始化数据
{
  "sysUserName": "admin",
  "sysPassword": "123456",
  "databaseHost": "127.0.0.1",
  "databasePort": "3306",
  "databaseName": "maple",
  "user": "root",
  "password": "123456"
}

初始化时,会先校验是否初始化过配置,如果没有才会进行,保存配置,并连接数据库,如下图所示:

image-20230526111702732

这是看我们存放配置的文件路径里面已经出现了我们的文件。

image-20230526112003126

打开db.properfies文件,可以看到下面内容,后续项目重新启动,会先来此目录判断文件是否存在,如果存在,则会自动加载文件内容,去连接数据库。image-20230526112028170

  • check连接:可以看到此时数据库已经连接成功

image-20230526111853516

  • 重置数据库:调用/init/resetData接口,这里会报一个错,可以忽略,想了解详情的,可以去看源码,然后会无了一个大语…

image-20230526112301509

  • 再次check连接:可以看到,在拦截器拦截校验时,数据库已经断开,配置文件也已经删除,需要重新配置了

image-20230526112454084

五、功能总结

本文主要利用Mybatis Plus的动态切换数据源的功能,间接实现了无数据源启动,用户自定义数据源的功能。只是一种实现思路,肯定还会有更优的实现方案,暂时还没有找到,如找到,会继续出文介绍。

配合本文的还有数据库版本管理,连接数据库后,可以初始化数据库表结构,然后再初始化管理员信息,后续迭代升级时,sql变更,在项目启动时自动加载,维护数据库表版本,可以去看后续的文章,通过flywaydb实现。

本文到此就结束了,如果帮助到你了,帮忙点个赞👍

本文源码:https://github.com/hack-feng/maple-product/tree/main/maple-input-db

SpringBoot使用flywaydb实现数据库版本管理【附源码】

我是笑小枫,全网皆可搜的【笑小枫】

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

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

相关文章

cuda2 向量加法

向量加法 向量加法程序解读 #include<stdio.h> #include<cuda.h>typedef float FLOAT; #define USE_UNIX 1 区别不同系统 get thread id 1D block and 2D grid #define get_tid() (block)get block id&#xff0c; 2D gridwarm up 可选的&#xff0c;让gpu先运作…

2023年湖北住建厅八大员怎么考取施工员质量员资料员等岗位???

2023年湖北住建厅八大员怎么考取施工员质量员资料员等岗位&#xff1f;&#xff1f;&#xff1f; 2023年湖北住建厅八大员具体包含哪些岗位呢&#xff0c;可以选择的有施工员&#xff0c;质量员&#xff0c;资料员&#xff0c;材料员&#xff0c;机械员&#xff0c;标准员&…

用python进行办公自动化都需要学习什么知识呢?

本文先来分享Python实现自动化办公需要学什么&#xff0c;从哪里学&#xff01;以及自动化办公技巧的资源整理… 很多非IT职场人&#xff0c;想要把Python用到工作中&#xff0c;却不知道如何下手。其实自动化办公无非就是Excel、PPT、Word、邮件、文件处理、数据分析处理、爬虫…

chatgpt赋能python:Python写模拟器脚本

Python写模拟器脚本 Python是一种强大的编程语言&#xff0c;适用于各种任务&#xff0c;包括模拟器编写。模拟器是一种软件程序&#xff0c;能够模拟硬件或软件系统的行为。这篇文章将介绍Python编写模拟器脚本时需要关注的一些关键点。 为什么选择Python编写模拟器脚本 Py…

Vue+springboot个人博客网站系统的设计与实现3virm

本课题采用Java Web技术来设计开发一个可以发表文章、浏览文章的博客系统。课题主要包括前台博客系统以及后台管理系统&#xff1a;前台博客系统应该具备浏览文章&#xff08;能够实现分类查找、关键字查找、首页推荐等&#xff09;、评论文章&#xff08;用户能够对自己喜爱的…

chatgpt赋能python:Python的几次幂

Python的几次幂 Python是一种适用于多种任务的高级编程语言&#xff0c;可以用于网站开发&#xff0c;数据分析&#xff0c;机器学习以及人工智能等。其优越的设计和灵活的语法使其成为程序员众所周知和喜爱的语言。其中&#xff0c;Python中的乘方运算是其中一个非常常用的算…

OA系统开发设计

项目介绍 基于开源流程引擎camunda开发的办公自动化系统。采用前后端分离架构&#xff0c;基于可视化的表单建模、流程建模工具&#xff0c;零代码快速构建业务OA应用。 项目演示 演示地址请私信作者。 技术栈 后端&#xff1a;SpringBootJWTShiromybatis-plus 流程引擎&a…

mysql多级分类设计

简介 在数据库设计中&#xff0c;经常会遇到需要存储多级分类信息的情况&#xff0c;如商品分类、地区分类等。本文将详细介绍如何在MySQL中设计和管理多级分类数据 解决方案 一. 层级字段&#xff08;Hierarchy Field&#xff09;方法 层级字段方法是最常见和简单的多级分…

用redis的消息订阅功能更新应用内的caffeine本地缓存

1、为什么要更新caffeine缓存&#xff1f; 1.1&#xff0c;caffeine缓存的优点和缺点 生产环境中&#xff0c;caffeine缓存是我们在应用中使用的本地缓存&#xff0c; 它的优势在于存在于应用内&#xff0c;访问速度最快&#xff0c;通常都不到1ms就能做出响应&#xff0c; 缺…

Gitlab数据自动备

【场景】&#xff1a;将Gitlab服务器定时备份到Gitlab备份服务器 1.设置Gitlab服务器以及Gitlab备份服务器时间 1.1查看系统时间&#xff1a; date 1.2修改具体时间&#xff1a; date -s "2023-06-02 15:15:00" 1.3把时间写入CMOS&#xff1a; clock -w 1.4把…

深入了解Altium Designer 2023的规则设置

在PCB设计中&#xff0c;规则设置是确保PCB设计符合标准和规范的关键步骤&#xff0c;Altium Designer 2023作为一款强大的PCB设计软件&#xff0c;提供了丰富的规则设置功能&#xff0c;可帮助电子工程师实现高效准确的设计。下面将详细介绍AD 2023中的规则设置功能&#xff0…

【OpenMMLab AI实战营第二期笔记】人体关键点检测与MMPose

人体关键点检测与MMPose 介绍 人体姿态估计&#xff08;Human Pose Estimation&#xff09;是计算机视觉领域中的一个重要研究方向&#xff0c;也是计算机理解人类动作、行为必不可少的一步&#xff0c;人体姿态估计是指通过计算机算法在图像或视频中定位人体关键点&#xff…

TDEngine3.0环境搭建总结

TDEngine3.0环境搭建总结 一、TDengine 介绍二、TDengine的下载三、TDengine Server安装及配置3.1 安装3.2 taos的参数配置3.3 启动3.4 taosAdapter 四、TDengine Client 安装4.1 linux客户端安装4.2 windows客户端安装 一、TDengine 介绍 TDengine 官网 TDengine的介绍   T…

算法工程师的岗位职责(合集)

算法工程师的岗位职责1 职责&#xff1a; 1、负责运动控制的数据采集、信号处理、仪器控制等模块研发和维护,包括关键技术方案设计/详细设计/调试/验证/测试/现场调试 2、编写软件使用说明书等相关技术性文件 3、完成项目中有关机器人轨迹设计、分析、控制的需求分析(7轴机械手…

Maven依赖传递

Maven 依赖传递是 Maven 的核心机制之一&#xff0c;它能够一定程度上简化 Maven 的依赖配置。本节我们将详细介绍依赖传递及其相关概念。 依赖传递 如下图所示&#xff0c;项目 A 依赖于项目 B&#xff0c;B 又依赖于项目 C&#xff0c;此时 B 是 A 的直接依赖&#xff0c;C…

java爬虫详解及简单实例

java爬虫是一种自动化程序&#xff0c;可以模拟人类在互联网上的行为&#xff0c;从网站上抓取数据并进行处理。下面是Java爬虫的详细解释&#xff1a; 1、爬虫的基本原理 Java爬虫的基本原理是通过HTTP协议模拟浏览器发送请求&#xff0c;获取网页的HTML代码&#xff0c;然后…

PS2024后期调色滤镜插件Alien Skin Exposure7

Exposure是一款常见的ps调色滤镜插件&#xff0c;相信许多朋友都曾经用过它。一张普通的图片经过后期调色处理后&#xff0c;可以得到更加靓丽的效果。因此选择一款专业性强、操作简单的后期调色软件很重要。那么&#xff0c;我们应该如何选择后期调色软件呢&#xff1f;下面给…

第三大章docker的部署

1. 红为写的命令 systemctl stop firewalld.service setenforce 0 #安装依赖包yum install -y yum-utils device-mapper-persistent-data lvm2 -------------------------------------------------------------------------------------------- yum-utils&#xff1a;提供了…

揭秘虚拟直播:3D场景与2D背景的区别

虚拟直播是指通过技术手段创造出虚拟场景&#xff0c;将主播或演员放置其中进行实时直播的一种形式。这种直播方式结合了虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;和实时渲染等技术&#xff0c;近年来&#xff0c;随着VR和AR技术的不断成熟和普…

Flink第八章:FlinkSQL

系列文章目录 Flink第一章:环境搭建 Flink第二章:基本操作. Flink第三章:基本操作(二) Flink第四章:水位线和窗口 Flink第五章:处理函数 Flink第六章:多流操作 Flink第七章:状态编程 Flink第八章:FlinkSQL 文章目录 系列文章目录前言一、常用函数1.快速上手案例2.连接外部数据…