【mybatis-plus】Springboot+AOP+自定义注解实现多数据源操作(数据源信息存在数据库)

news2024/11/16 11:59:17

背景

本文主要讲述的是如何实现动态切换数据源,数据源信息是存储在数据库表里,与在配置文件中写好数据库信息然后用@DS("XX")注解不同。

 

 

目录

 前言

一、准备工作

1.依赖

2.数据库表(脚本)

3.配置文件

4.自定义注解@ChangeDB

5.定义AOP切面类

6.DataSourceHelp类

二、开发代码

1.controller层

2.service层

3. dbService类

4.sysDataResourceMapper类

5.看下调用过程

 


 前言

本文是为了解决多数据源切换执行任务等,希望对大家有所帮助,有问题的可以在评论区留言,看到一定回复您!

一、准备工作

1.依赖

 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
     <version>3.4.0</version>
 </dependency>

2.数据库表(脚本)

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_data_resource
-- ----------------------------
DROP TABLE IF EXISTS `sys_data_resource`;
CREATE TABLE `sys_data_resource`  (
  `uuid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主键',
  `data_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据源别名',
  `ip_address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '服务器地址(支持ip和域名)',
  `port` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '端口号',
  `data_resource_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据库名称',
  `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `parent_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '父id,默认0,0:代表父级 用于树形结构',
  `data_resource_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'mysql' COMMENT '数据库类型,默认:mysql 用于区分不同数据库驱动',
  `create_user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建用户id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改用户id',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据库链接信息',
  `status` int(2) NULL DEFAULT 0 COMMENT '0:未删除,1:已删除',
  `data_desc` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据描述',
  PRIMARY KEY (`uuid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '动态数据源管理' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

3.配置文件

#配置数据源
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源,默认值为master
      strict: false
      datasource:
        master: # 数据源1配置
          url: jdbc:log4jdbc:mysql://192.168.1.202:3306/xxxxxx?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
          username: xxxxx
          password: xxx123!
          driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      druid:
        db-type: com.alibaba.druid.pool.DruidDataSource
        break-after-acquire-failure: true
        # 初始化配置
        initial-size: 3
        # 最小连接数
        min-idle: 3
        # 最大连接数
        max-active: 15
        # 获取连接超时时间
        max-wait: 5000
        # 连接有效性检测时间
        time-between-eviction-runs-millis: 90000
        # 最大空闲时间
        min-evictable-idle-time-millis: 1800000
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false

        validation-query: select 1
        # 配置监控统计拦截的filters
        filters: stat
        stat-view-servlet:
          url-pattern: /druid/*
          reset-enable: false

        web-stat-filter:
          url-pattern: /*
          exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
  redis:
    #数据库索引
    database: 0
    host: 192.168.1.202
    port: 6379
    password: xxxx23!

url和driver-class-name 大家可以自行换成mysql的驱动就行。

4.自定义注解@ChangeDB

当方法上使用@ChangeDB时,代表切换数据源,不加时,默认走主数据源。


import java.lang.annotation.*;

/**
 * 自定义多数据源切换注解
 * <p>
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 * 如果传递value,则切换到value对应的数据源
 *
 * @author 曹震
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ChangeDB {
    String value() default "";
}

5.定义AOP切面类


import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.hvit.data_governance.dataComparison.dbhelp.DataSourceHelp;
import com.hvit.data_governance.dataComparison.entity.SysDataResource;
import com.hvit.data_governance.dataComparison.service.SysDataResourceService;
import com.hvit.data_governance.utils.DBUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Set;

/**
 * 动态数据源切面拦截
 * 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
 *
 * @author 曹震
 * @version 1.0
 */
@Slf4j
@Aspect
@Component
@Order(1)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {
    @Autowired
    private DataSource dataSource;
    @Resource
    private DefaultDataSourceCreator defaultDataSourceCreator;
    @Autowired
    private SysDataResourceService sysDataResourceService;

    @Pointcut("@annotation(com.hvit.data_governance.dataComparison.dbhelp.ChangeDB) || @within(com.hvit.data_governance.dataComparison.dbhelp.ChangeDB)")
    public void dsPointCut() {

    }

    @Around("dsPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
        String dynamicDataSource = "";
        try {
            //获取参数,切换数据源参数固定都传poolName参数
            //get请求获取参数
            dynamicDataSource = request.getParameter(DBUtils.DATA_RESOURCE_NAME);
            if (StringUtils.isEmpty(dynamicDataSource)) {
                //如果get请求没获取到参数,就那用post请求body获取参数
                dynamicDataSource = getDataResourceName(request);
            }
            //如果get、post都没获取到,支持动态传参切换数据源
            if (StringUtils.isEmpty(dynamicDataSource)) {
                dynamicDataSource = (String) request.getAttribute("poolName");
            }
            if (StringUtils.isNotEmpty(dynamicDataSource)) {
                //先获取传入的数据源是不是存在数据池当中
                Set<String> sets = ds.getCurrentDataSources().keySet();
                boolean flag = false;
                for (String str : sets) {
                    //首先排除主数据源
                    if (!"master".equals(str)) {
                        if (str.equals(dynamicDataSource)) {
                            DynamicDataSourceContextHolder.push(dynamicDataSource);
                            flag = true;
                            break;
                        }
                    }
                }
                //如果数据池中没有找到当前数据源,那么开始进行新增数据源
                //poolName其实就是数据源id
                if (!flag) {
                    SysDataResource sysDataResource = sysDataResourceService.getById(dynamicDataSource);
                    if (sysDataResource != null) {
                        manualAddSource(sysDataResource);
                        DynamicDataSourceContextHolder.push(dynamicDataSource);
                    }
                }
                return joinPoint.proceed();
            }
            return joinPoint.proceed();
        } finally {
            //每次使用完成,清空数据源
            //DynamicDataSourceContextHolder.poll();
            if (StringUtils.isNotEmpty(dynamicDataSource)) {
                ds.removeDataSource(dynamicDataSource);
            }
        }
    }

    /***
     * post请求获取HttpServletRequest 内 poolName参数
     * @param request
     * @return
     */
    public String getDataResourceName(HttpServletRequest request) throws IOException {
        StringBuilder data = new StringBuilder();
        String line;
        BufferedReader reader;
        try {
            reader = request.getReader();
            while (null != (line = reader.readLine())) {
                data.append(line);
            }
        } catch (IOException e) {
            return null;
        }
        JSONObject jsonObject = JSONObject.parseObject(data.toString());
        if (jsonObject == null) {
            return "";
        }
        return jsonObject.getString(DBUtils.DATA_RESOURCE_NAME);
    }

    /***
     * 手动加入数据源池
     * @param sysDataResource
     */
    public void manualAddSource(SysDataResource sysDataResource) {
        DataSourceHelp dataSourceHelp = new DataSourceHelp();
        dataSourceHelp.setDriverClassName(DBUtils.getDriverClassName(sysDataResource.getDataResourceType()));
        dataSourceHelp.setPassword(sysDataResource.getPassword());
        dataSourceHelp.setPoolName(sysDataResource.getUuid());
        dataSourceHelp.setUsername(sysDataResource.getUserName());
        dataSourceHelp.setUrl(sysDataResource.getUrl());
        addSource(dataSourceHelp);
    }

    public Set<String> addSource(DataSourceHelp dataSourceHelp) {
        DataSourceProperty dataSourceProperty = new DataSourceProperty();
        BeanUtils.copyProperties(dataSourceHelp, dataSourceProperty);
        DynamicRoutingDataSource source = (DynamicRoutingDataSource) dataSource;
        DataSource dataSource = defaultDataSourceCreator.createDataSource(dataSourceProperty);
        source.addDataSource(dataSourceHelp.getPoolName(), dataSource);
        return source.getCurrentDataSources().keySet();
    }
}

 

 这里解释下,我们会先做页面中保存数据库链接信息,然后在树结构中点击查询时,会固定传一个参数poolName,我们传的是记录数据库信息的id,作为poolName,用于我们后续切换数据源的时候的一个标识。

注意: 切面中,我们会在get请求里拿到poolName进行数据源切换,但是在日常考虑中,有post请求也需要切换数据源,所以代码中也加入了从body里取得poolName进行切换参数。为了进一步实现自定义切换参数,我们也可以在  request.getAttribute("poolName");取得参数进行切换!

6.DataSourceHelp类

import lombok.Data;

@Data
public class DataSourceHelp {
    private String poolName;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
}

二、开发代码

1.controller层


import com.hvit.data_governance.dataComparison.request.*;
import com.hvit.data_governance.dataComparison.service.data.DBService;
import com.hvit.data_governance.dataComparison.service.data.SysDataResourceDataService;
import com.hvit.data_governance.utils.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.SQLException;

/**
 * <p>
 * 动态数据源管理 前端控制器
 * </p>
 *
 * @author 曹震
 * @since 2022-12-06
 */
@Api(tags = "动态数据源管理")
@RestController
@RequestMapping("/hvit/dataResource/")
public class SysDataResourceController {

    @Autowired
    private SysDataResourceDataService sysDataResourceDataService;
    @Autowired
    private DBService dbService;

    @ApiOperation("获取数据集的字段信息")
    @GetMapping("/getDataTableColumns")
    public ResponseEntity getDataTableColumns(String tableName, String poolName) {
        return ResponseEntity.ok(sysDataResourceDataService.getDataTableColumns(tableName, poolName));
    }
}

2.service层

@Slf4j
@Service
public class SysDataResourceDataService {
    
    @Autowired
    private SysDataResourceService sysDataResourceService;
    @Autowired
    private DBService dbService;

    public R getDataTableColumns(String tableName, String poolName) {
        if (StringUtils.isAnyEmpty(tableName, poolName)) {
            return R.error("缺少参数!");
        }
        getDataTableFramework(tableName, poolName);
        QueryWrapper<SysTableStructure> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
        queryWrapper.lambda().eq(SysTableStructure::getTableName, tableName);
        queryWrapper.lambda().orderByAsc(SysTableStructure::getSort);
        return R.ok().put("data", sysTableStructureService.list(queryWrapper));
    }

 /***
     * 注意点:当方法内调用其他需要数据源切换的方法时,需要将类本身注入进来,然后调用才能生效(数据源切换)
     * 处理字段入库
     * @param tableName
     * @param poolName
     */
    @Transactional
    public void getDataTableFramework(String tableName, String poolName) {
        //获取表字段结构信息
        List<TableFramework> tableFrameworks = dbService.getTableFramework(tableName);
        //获取不在表中的字段信息
        List<String> stringList = getDifferenceSet(tableFrameworks, tableName, poolName);
        //装备剔除没用的字段
        if (!CollectionUtils.isEmpty(stringList)) {
            QueryWrapper<SysTableStructure> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
            queryWrapper.lambda().eq(SysTableStructure::getTableName, tableName);
            queryWrapper.lambda().in(SysTableStructure::getField, stringList);
            sysTableStructureService.remove(queryWrapper);
        }
        AtomicInteger i = new AtomicInteger();
        if (!CollectionUtils.isEmpty(tableFrameworks)) {
            tableFrameworks.forEach(x -> {
                QueryWrapper<SysTableStructure> wrapper = new QueryWrapper<>();
                wrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
                wrapper.lambda().eq(SysTableStructure::getTableName, tableName);
                wrapper.lambda().eq(SysTableStructure::getField, x.getField());
                if (sysTableStructureService.count(wrapper) == 0) {
                    SysTableStructure sysTableStructure = new SysTableStructure();
                    sysTableStructure.setField(x.getField());
                    sysTableStructure.setTableName(tableName);
                    sysTableStructure.setHidden(1);
                    sysTableStructure.setType(x.getType());
                    sysTableStructure.setSort(i.get());
                    sysTableStructure.setLength(DBUtils.getContent(x.getType()));
                    sysTableStructure.setDataResourceUuid(poolName);
                    sysTableStructure.setPriKey(x.getKey());
                    sysTableStructureService.save(sysTableStructure);
                }
                i.getAndIncrement();
            });
        }
    }

/***
     * 智能匹配字段差集
     * 当表字段存在记录表中,而实际数据库表字段通过其他手动修改过时,智能剔除。
     * @param tableFrameworks
     * @param tableName
     * @return
     */
    public List<String> getDifferenceSet(List<TableFramework> tableFrameworks, String tableName, String poolName) {
        QueryWrapper<SysTableStructure> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(SysTableStructure::getTableName, tableName);
        queryWrapper.lambda().eq(SysTableStructure::getDataResourceUuid, poolName);
        List<SysTableStructure> structureList = sysTableStructureService.list(queryWrapper);
        List<String> retList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(structureList)) {
            List<String> strings = sysTableStructureService.list(queryWrapper).stream().map(SysTableStructure::getField).collect(Collectors.toList());
            List<String> stringList = tableFrameworks.stream().map(TableFramework::getField).collect(Collectors.toList());
            retList = strings.stream().filter(e -> {
                return !stringList.contains(e);
            }).collect(Collectors.toList());
        }
        return retList;
    }
}

3. dbService类

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.hvit.data_governance.dataComparison.dao.SysDataResourceMapper;
import com.hvit.data_governance.dataComparison.dbhelp.ChangeDB;
import com.hvit.data_governance.dataComparison.entity.SysDataResource;
import com.hvit.data_governance.dataComparison.entity.TableFramework;
import com.hvit.data_governance.dataComparison.request.HandleMianDataReq;
import com.hvit.data_governance.dataComparison.request.RowDataReq;
import com.hvit.data_governance.dataComparison.service.SysDataResourceService;
import com.hvit.data_governance.utils.DBUtils;
import com.hvit.data_governance.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@Service
public class DBService {
    @Resource
    private SysDataResourceMapper sysDataResourceMapper;
    @Autowired
    private SysDataResourceDataService sysDataResourceDataService;

   /***
     * 获取表结构信息
     * @param tableName
     * @return
     */
    @ChangeDB
    public List<TableFramework> getTableFramework(String tableName) {
        return sysDataResourceMapper.getTableFramework(tableName);
    }
}

4.sysDataResourceMapper类

import com.hvit.data_governance.dataComparison.entity.SysDataResource;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hvit.data_governance.dataComparison.entity.TableFramework;
import org.apache.ibatis.annotations.*;

import java.util.LinkedHashMap;
import java.util.List;

/**
 * <p>
 * 动态数据源管理 Mapper 接口
 * </p>
 *
 * @author 曹震
 * @since 2022-12-06
 */
@Mapper
public interface SysDataResourceMapper extends BaseMapper<SysDataResource> {

/***
     * 获取表结构信息
     * @param tableName
     * @return
     */
    @Select("DESC `${tableName}`")
    List<TableFramework> getTableFramework(@Param("tableName") String tableName);


}

5.看下调用过程

 

 我们可以看到日志输出中,已经进行的数据源的切换,在调用完后也及时的清除了切换的数据源。

 

可以看到表的结构信息已经获取到了。同样postman中也有,给大家看下。

那么到此处就已经做好了动态数据源的切换了。

如果大家喜欢的话,点个关注,点个赞,谢谢。

 

 

 

 

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

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

相关文章

【MySQL基础教程】约束的介绍与使用

前言 本文为 【MySQL基础教程】约束 相关知识&#xff0c;下边将对约束概述&#xff0c;约束演示&#xff0c;外键约束&#xff08;包括&#xff1a;外键约束介绍、外键约束语法&#xff09;等内容进行详尽介绍~ &#x1f4cc;博主主页&#xff1a;小新要变强 的主页 &#x1…

只懂黑盒测试也能学会的代码覆盖率及精准化测试

测试覆盖率是对测试完成程度的度量。它通常依据某种覆盖准则来对测试用例执行情况进行衡量&#xff0c;以判断测试执行得是否充分 。 ——出自《 计算机科学技术名词 》第三版 今天文章中我们给大家介绍覆盖率统计及覆盖率分析。在10月13日20&#xff1a;00&#xff0c;资深测…

Vue_cli中config文件配置详细注解

我在scr同级目录下创建了config文件夹&#xff0c;里边包含三个文件 它们3个有引用关系 dev引用prod prod.env.js文件 /* * process.argv.splice(2)[0]返回的是node.js的绝对路径 * /usr/local/bin/node */ let HOST process.argv.splice(2)[0] || prod module.exports {NO…

项目实战之旅游网(十)前台用户注册

目录 一.网站首页 二.编写注册界面 三.生成验证码 四.注册流程 五.编写注册方法 六.发送邮件配置 一.网站首页 static/frontdesk下存放前台静态资源&#xff0c;而templates/frontdesk是前台页面 二.编写注册界面 在上个界面点击注册就可以跳转到注册界面 在这里遇到一…

2022 年上海市大学生程序设计竞赛 M. My University Is Better Than Yours

大家总喜欢搞些什么排行榜。有一说一&#xff0c;排行榜通常不重要&#xff0c;除非——比如你老板要你做一下年终总 结。 为了实现建设世界一流大学和建设世界一流学科的目标&#xff0c;不少大学都用各种方式提升排名&#xff1a;发表论文、 申请基金、提升多样性. . . 不过…

Linux操作系统实验4——内存映射

实验要求&#xff1a; 1.在源码中查看file_operations和vm_operations_struct结构定义及其操作对象的方法&#xff0c;重点查看mmap方法fault方法的参选类型。 2.设备模块代码的编写和调试&#xff0c;重新编写file_operations结构中的mmap方法&#xff0c;和vm_operations_str…

美团一面:能不能通俗的解释下为什么要有意向锁这个东西?

面试真题&#xff0c;用通俗的例子解释清楚 MySQL 为什么有了表锁和行锁之后&#xff0c;还要引入意向锁 众所周知&#xff0c;InnoDB 中既有读锁也有写锁&#xff0c;也称为共享锁和排他锁&#xff0c;这两种锁既可以加在整张表上&#xff0c;也可以加在行上。 MySQL 自身就提…

【Apifox】设置apiFox自动获取token

文章目录问题描述解决方案注意事项参考文章问题描述 接口测试时&#xff0c;每次都需要手动登录获取token&#xff0c;先登录系统&#xff0c;从浏览器中复制token&#xff0c;再到apifox的接口上把token帖上去&#xff0c;然后才能去测试具体的接口&#xff1b;更麻烦的是&am…

【实时数仓】热度关键词接口、项目整体部署流程

文章目录一 热度关键词接口1 Sugar配置&#xff08;1&#xff09;图表配置&#xff08;2&#xff09;接口地址&#xff08;3&#xff09;数据格式&#xff08;4&#xff09;执行SQL2 数据接口实现&#xff08;1&#xff09;创建关键词统计实体类&#xff08;2&#xff09;Mappe…

小黑hbase终于勉强跑到了自己的m1 Macbook上啦,虽然终端用不了,但是能从happybase访问的日常积累:happybase简单使用

1.happybase连接 # 连接操作 import happybase# 建立连接 con happybase.Connection(localhost, 9090) con.open() # 输出所有表名称 print(con.tables()) # 关闭传输 con.close() con.open()2.创建表格 # 创建表格 con happybase.Connection(localhost, 9090) # 默认9090…

Android 图像混合技术

Android 图像混合技术 色彩知识 色彩 光学三原色 光学三原色由&#xff1a;红、绿、蓝组成。 色值分别是&#xff1a; 红&#xff08;red &#xff09;&#xff1a;#FF0000 RGB&#xff08;255&#xff0c;0&#xff0c;0&#xff09;绿&#xff08;green&#xff09;&am…

第十九讲:神州路由器基础知识

路由器简介路由器&#xff08;Router&#xff09;是连接Internet中多个网络或网段的网络设备&#xff0c;它能将不同网络或网段之间的数据信息进行“翻译”&#xff0c;以使它们能够相互“读”懂对方的数据&#xff0c;实现不同网络或网段的互联互通。此外&#xff0c;它会根据…

数据平台建设指南(上)

前言 年底了&#xff0c;整理了下过去做的一些项目&#xff0c;希望能够给大数据行业的同学提供些大数据平台建设的思路。内容大致分五部分&#xff1a;数据采集&#xff0c;数据存储、数据计算、基础平台以及数据治理篇。由于涉及到的内容较多&#xff0c;打算分成两篇文章&am…

PHY驱动注册部分

SOC可以对PHY 进行配置或者读取PHY 相关状态&#xff0c;这个就需要 PHY 内部寄存器去实现了。PHY 芯片寄存器地址空间为 5位&#xff08;支持访问32个寄存器).IEEE 定义了0~15这 16个寄存器的功能。而 16~31这16 个寄存器由厂商自行实现。 就是说不管你用的哪个厂家的 PHY 芯…

SAP ABAP——SAP简介(三)【S/4 HANA开发环境】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

linux常用命令(三)-目录操作

目录创建 - mkdir 我们可以使用mkdir&#xff08;make directory&#xff09;来创建目录。 示例 目录删除 - rm 可以使用rm&#xff08;remove&#xff09;来删除一个目录 语法 rm [-irf] name ...i&#xff1a;删除前逐一询问确认r&#xff1a;将目录及以下之档案亦逐一删…

转转AB平台的设计与实现

导读 在数据驱动时代&#xff0c;不管是在产品功能迭代还是策略决策时都需要数据的支撑。那么&#xff0c;当我们准备上线一个新功能或者策略时&#xff0c;如何评估新老版本优劣&#xff0c;即数据的可量化就成了问题。这个时候就需要引入 A/B Test 了。 一、A/B Test 是什么…

Tomcat基本用法

Tomcat基本用法一、Tomcat 是什么二、下载安装三、目录结构四、启动服务器五、部署静态页面一、Tomcat 是什么 汤姆猫&#xff1f; 事实上&#xff0c;Java 世界中的 “汤姆猫” 完全不是一回事&#xff0c;但是同样大名鼎鼎 ~ Tomcat 是一个 HTTP 服务器。 前面我们已经学…

公司如何做好舆情监控,舆情监控解决方案有哪些?

随着互联网快速发展&#xff0c;企业网络舆情动态成为决策发展的重要依据&#xff0c;所以做好网络舆情监控至关重要&#xff0c;接下来TOOM舆情监测小编带您简单了解公司如何做好舆情监控&#xff0c;舆情监控解决方案有哪些? 一、公司如何做好舆情监控 舆情监控是指通过不…

Spring Boot 使用 SpringDoc 库的 Swagger3.0

Swagger 定义 Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法&#xff0c;参数和模型紧密集成到服务器端的代码&#xff0c;允许API来始终保持同步…