谷粒学院——Day17【数据同步工具、SpringCloud【GateWay网关】、权限管理功能(接口)】

news2024/11/25 20:16:11

❤ 作者主页:Java技术一点通的博客
❀ 个人介绍:大家好,我是Java技术一点通!( ̄▽ ̄)~*
🍊 记得关注、点赞、收藏、评论⭐️⭐️⭐️
📣 认真学习,共同进步!!!🎉🎉

Canal数据同步工具

一、Canal介绍

1. 应用场景

在前面的统计分析功能中,我们采取了服务调用获取统计数据,这样耦合度高,效率相对较低。
目前我采取另一种实现方式,通过实时同步数据库表的方式实现,例如我们要统计每天注册与登录人数,我们只需把会员表同步到统计库中,实现本地统计就可以了,这样效率更高,耦合度更低,Canal就是一个很好的数据库同步工具
canal是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL。

2. Canal环境搭建

canal的原理是基于 mysql binlog 技术,所以这里一定需要开启mysql的binlog写入功能。

  1. 检查binlog功能是否有开启

    mysql> show variables like 'log_bin';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | log_bin       | OFF    |
    +---------------+-------+
    1 row in set (0.00 sec)
    

    在这里插入图片描述
     

  2. 如果显示状态为OFF表示该功能未开启,开启binlog功能

    1、修改 mysql 的配置文件 my.cnf
    vi /etc/my.cnf 
    追加内容:
    log-bin=mysql-bin     #binlog文件名
    binlog_format=ROW     #选择row模式
    server_id=1           #mysql实例id,不能和canal的slaveId重复
    
    2、重启 mysql:
    service mysql restart   
    
    3、登录 mysql 客户端,查看 log_bin 变量
    mysql> show variables like 'log_bin';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | log_bin       | ON|
    +---------------+-------+
    1 row in set (0.00 sec)
    ————————————————
    如果显示状态为ON表示该功能已开启
    
  3. 在mysql里面添加以下的相关用户和权限

    CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';
    GRANT SHOW VIEW, SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
    FLUSH PRIVILEGES;
    

    在这里插入图片描述
     

3. 下载安装Canal服务

下载地址:https://github.com/alibaba/canal/releases/download/canal-1.1.4/canal.deployer-1.1.4.tar.gz

  1. 下载之后,放到目录中,解压文件

    cd /usr/local/canal
    canal.deployer-1.1.4.tar.gz
    tar zxvf canal.deployer-1.1.4.tar.gz
    
  2. 修改配置文件

    vi conf/example/instance.properties
    
    #需要改成自己的数据库信息
    canal.instance.master.address=192.168.44.132:3306
    
    #需要改成自己的数据库用户名与密码
    
    canal.instance.dbUsername=canal
    
    canal.instance.dbPassword=canal
    
    #需要改成同步的数据库表规则,例如只是同步一下表
    #canal.instance.filter.regex=.*\\..*
    canal.instance.filter.regex=guli_ucenter.ucenter_member
    

    注意:

    mysql 数据解析关注的表,Perl正则表达式.
    多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\) 
    常见例子:
    1.  所有表:.*   or  .*\\..*
    2.  canal schema下所有表: canal\\..*
    3.  canal下的以canal打头的表:canal\\.canal.*
    4.  canal schema下的一张表:canal.test1
    5.  多个规则组合使用:canal\\..*,mysql.test1,mysql.test2 (逗号分隔)
    注意:此过滤条件只针对row模式的数据有效(ps. mixed/statement因为不解析sql,所以无法准确提取tableName进行过滤)
    
  3. 进入bin目录下启动

    sh bin/startup.sh
    

二、创建canal_clientedu模块

1. 在guli_parent下创建canal_clientedu模块

在这里插入图片描述

 

2. 引入相关的依赖

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-dbutils</groupId>
        <artifactId>commons-dbutils</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.otter</groupId>
        <artifactId>canal.client</artifactId>
    </dependency>

</dependencies>

3. 创建application.properties配置文件

# 服务端口号
server.port=10000

# 服务名
spring.application.name=canal-client

# 环境设置:dev,test,prod
spring.profiles.active=dev

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

4. 编写canal客户端类

package com.atguigu.canal.client;

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

@Component
public class CanalClient {

    //sql队列
    private Queue<String> SQL_QUEUE = new ConcurrentLinkedQueue<>();

    @Resource
    private DataSource dataSource;

    /**
     * canal入库方法
     */
    public void run() {

        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress("192.168.44.132",
                11111), "example", "", "");
        int batchSize = 1000;
        try {
            connector.connect();
            connector.subscribe(".*\\..*");
            connector.rollback();
            try {
                while (true) {
                    //尝试从master那边拉去数据batchSize条记录,有多少取多少
                    Message message = connector.getWithoutAck(batchSize);
                    long batchId = message.getId();
                    int size = message.getEntries().size();
                    if (batchId == -1 || size == 0) {
                        Thread.sleep(1000);
                    } else {
                        dataHandle(message.getEntries());
                    }
                    connector.ack(batchId);

                    //当队列里面堆积的sql大于一定数值的时候就模拟执行
                    if (SQL_QUEUE.size() >= 1) {
                        executeQueueSql();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (InvalidProtocolBufferException e) {
                e.printStackTrace();
            }
        } finally {
            connector.disconnect();
        }
    }

    /**
     * 模拟执行队列里面的sql语句
     */
    public void executeQueueSql() {
        int size = SQL_QUEUE.size();
        for (int i = 0; i < size; i++) {
            String sql = SQL_QUEUE.poll();
            System.out.println("[sql]----> " + sql);

            this.execute(sql.toString());
        }
    }

    /**
     * 数据处理
     *
     * @param entrys
     */
    private void dataHandle(List<Entry> entrys) throws InvalidProtocolBufferException {
        for (Entry entry : entrys) {
            if (EntryType.ROWDATA == entry.getEntryType()) {
                RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
                EventType eventType = rowChange.getEventType();
                if (eventType == EventType.DELETE) {
                    saveDeleteSql(entry);
                } else if (eventType == EventType.UPDATE) {
                    saveUpdateSql(entry);
                } else if (eventType == EventType.INSERT) {
                    saveInsertSql(entry);
                }
            }
        }
    }

    /**
     * 保存更新语句
     *
     * @param entry
     */
    private void saveUpdateSql(Entry entry) {
        try {
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
                List<Column> newColumnList = rowData.getAfterColumnsList();
                StringBuffer sql = new StringBuffer("update " + entry.getHeader().getTableName() + " set ");
                for (int i = 0; i < newColumnList.size(); i++) {
                    sql.append(" " + newColumnList.get(i).getName()
                            + " = '" + newColumnList.get(i).getValue() + "'");
                    if (i != newColumnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(" where ");
                List<Column> oldColumnList = rowData.getBeforeColumnsList();
                for (Column column : oldColumnList) {
                    if (column.getIsKey()) {
                        //暂时只支持单一主键
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }

    /**
     * 保存删除语句
     *
     * @param entry
     */
    private void saveDeleteSql(Entry entry) {
        try {
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
                List<Column> columnList = rowData.getBeforeColumnsList();
                StringBuffer sql = new StringBuffer("delete from " + entry.getHeader().getTableName() + " where ");
                for (Column column : columnList) {
                    if (column.getIsKey()) {
                        //暂时只支持单一主键
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }

    /**
     * 保存插入语句
     *
     * @param entry
     */
    private void saveInsertSql(Entry entry) {
        try {
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
                List<Column> columnList = rowData.getAfterColumnsList();
                StringBuffer sql = new StringBuffer("insert into " + entry.getHeader().getTableName() + " (");
                for (int i = 0; i < columnList.size(); i++) {
                    sql.append(columnList.get(i).getName());
                    if (i != columnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(") VALUES (");
                for (int i = 0; i < columnList.size(); i++) {
                    sql.append("'" + columnList.get(i).getValue() + "'");
                    if (i != columnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(")");
                SQL_QUEUE.add(sql.toString());
            }
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }

    /**
     * 入库
     * @param sql
     */
    public void execute(String sql) {
        Connection con = null;
        try {
            if(null == sql) return;
            con = dataSource.getConnection();
            QueryRunner qr = new QueryRunner();
            int row = qr.execute(con, sql);
            System.out.println("update: "+ row);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeQuietly(con);
        }
    }
}

5. 创建启动类

@SpringBootApplication
public class CanalApplication implements CommandLineRunner {
    @Resource
    private CanalClient canalClient;

    public static void main(String[] args) {
        SpringApplication.run(CanalApplication.class, args);
    }

    @Override
    public void run(String... strings) throws Exception {
        //项目启动,执行canal客户端监听
        canalClient.run();
    }
}

6. 测试

启动 canalcanal_clientedu 模块:
在这里插入图片描述
 
在这里插入图片描述


SpringCloud【GateWay网关】

一、网关基本概念

1. API网关介绍

API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
(1)客户端会多次请求不同的微服务,增加了客户端的复杂性。

(2)存在跨域请求,在一定场景下处理相对复杂。

(3)认证复杂,每个服务都需要独立认证。

(4)难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。

(5)某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。

以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性。

2. Spring Cloud Gateway

Spring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式。

Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。

在这里插入图片描述
 

3. Spring Cloud Gateway核心概念

网关提供API全托管服务,丰富的API管理功能,辅助企业管理大规模的API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等贡呢。

一般来说网关对外暴露的URL或者接口信息,我们统称为路由信息。如果研发过网关中间件或者使用过Zuul的人,会知道网关的核心是Filter以及Filter Chain(Filter责任链)。

Sprig Cloud Gateway也具有路由和Filter的概念。下面介绍一下Spring Cloud Gateway中几个重要的概念。
(1)路由。路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配。

(2)断言。Java8中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。

(3)过滤器。一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。

在这里插入图片描述
 
如上图所示,Spring cloud Gateway发出请求。然后再由Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway web handler。Handler再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑,然后返回。
 


二、创建api-gateway模块(网关服务)

1. 在infrastructure模块下创建api_gateway模块

在这里插入图片描述
 

2. 引入相关依赖

<dependencies>
        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>common_utils</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        
        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>
        
        <!--服务调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

3. 编写application.properties配置文件

# 服务端口
server.port=8222

# 服务名
spring.application.name=service-gateway

# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848


#使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
#服务路由名小写
#spring.cloud.gateway.discovery.locator.lower-case-service-id=true


#设置路由id
spring.cloud.gateway.routes[0].id=service-acl
#设置路由的uri
spring.cloud.gateway.routes[0].uri=lb://service-acl
#设置路由断言,代理servicerId为auth-service的/auth/路径
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**

#配置service-edu服务
spring.cloud.gateway.routes[1].id=service-edu
spring.cloud.gateway.routes[1].uri=lb://service-edu
spring.cloud.gateway.routes[1].predicates= Path=/eduservice/**

#配置service-ucenter服务
spring.cloud.gateway.routes[2].id=service-ucenter
spring.cloud.gateway.routes[2].uri=lb://service-ucenter
spring.cloud.gateway.routes[2].predicates= Path=/ucenterservice/**

#配置service-cms服务
spring.cloud.gateway.routes[3].id=service-cms
spring.cloud.gateway.routes[3].uri=lb://service-cms
spring.cloud.gateway.routes[3].predicates= Path=/cmsservice/**

#配置service-msm务
spring.cloud.gateway.routes[4].id=service-msm
spring.cloud.gateway.routes[4].uri=lb://service-msm
spring.cloud.gateway.routes[4].predicates= Path=/edumsm/**

#配置service-order服务
spring.cloud.gateway.routes[5].id=service-order
spring.cloud.gateway.routes[5].uri=lb://service-order
spring.cloud.gateway.routes[5].predicates= Path=/orderservice/**

#配置service-order服务
spring.cloud.gateway.routes[6].id=service-order
spring.cloud.gateway.routes[6].uri=lb://service-order
spring.cloud.gateway.routes[6].predicates= Path=/orderservice/**

#配置service-oss服务
spring.cloud.gateway.routes[7].id=service-oss
spring.cloud.gateway.routes[7].uri=lb://service-oss
spring.cloud.gateway.routes[7].predicates= Path=/eduoss/**

#配置service-statistic服务
spring.cloud.gateway.routes[8].id=service-statistic
spring.cloud.gateway.routes[8].uri=lb://service-statistic
spring.cloud.gateway.routes[8].predicates= Path=/staservice/**

#配置service-vod服务
spring.cloud.gateway.routes[9].id=service-vod
spring.cloud.gateway.routes[9].uri=lb://service-vod
spring.cloud.gateway.routes[9].predicates= Path=/eduvod/**

#配置service-user服务
spring.cloud.gateway.routes[10].id=service-user
spring.cloud.gateway.routes[10].uri=lb://service-user
spring.cloud.gateway.routes[10].predicates= Path=/eduuser/**

yml文件:
在这里插入图片描述
 

4. 编写启动类

@SpringBootApplication
@EnableFeignClients
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
}

5. 测试

同时启动 api-gatewayedu-service 模块。
在这里插入图片描述
 
在这里插入图片描述
 
在这里插入图片描述
 


二、网关相关配置

1. 网关解决跨域问题

创建 config 包,在此包下创建 CorsConfig 类:

// 如果加上此配置类,其他模块的controller不可以再加@CrossOrigin注解,否则会报错
@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

2. 全局Filter,统一处理会员登录与外部不允许访问的服务

创建 filter 包,此包下创建 AuthGlobalFilter 类:

package com.atguigu.gateway.filter;

import com.google.gson.JsonObject;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * <p>
 * 全局Filter,统一处理会员登录与外部不允许访问的服务
 * </p>
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        //谷粒学院api接口,校验用户必须登录
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            List<String> tokenList = request.getHeaders().get("token");
            if(null == tokenList) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            } else {
//                Boolean isCheck = JwtUtils.checkToken(tokenList.get(0));
//                if(!isCheck) {
                    ServerHttpResponse response = exchange.getResponse();
                    return out(response);
//                }
            }
        }
        //内部服务接口,不允许外部访问
        if(antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private Mono<Void> out(ServerHttpResponse response) {
        JsonObject message = new JsonObject();
        message.addProperty("success", false);
        message.addProperty("code", 28004);
        message.addProperty("data", "鉴权失败");
        byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

3. 自定义异常处理

服务网关调用服务时可能会有一些异常或服务不可用,它返回错误信息不友好,需要我们覆盖处理。

创建 handle 包,在此包下创建 ErrorHandlerConfig类 和 JsonExceptionHandler类:

package com.atguigu.gateway.handler;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;

/**
 * 覆盖默认的异常处理
 */
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfig(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                        ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

package com.atguigu.gateway.handler;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;

import java.util.HashMap;
import java.util.Map;

/**
 * 自定义异常处理
 *
 * <p>异常时用JSON代替HTML异常信息<p>
 *
 */
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * 获取异常属性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> map = new HashMap<>();
        map.put("success", false);
        map.put("code", 20005);
        map.put("message", "网关失败");
        map.put("data", null);
        return map;
    }

    /**
     * 指定响应处理方法为JSON处理的方法
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    /**
     * 根据code获取对应的HttpStatus
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return 200;
    }
}

权限管理需求描述

不同角色的用户登录后台管理系统拥有不同的菜单权限与功能权限。

权限管理包含三个功能模块:菜单管理角色管理用户管理

一、 菜单管理

1. 菜单列表:使用树形结构显示菜单列表

在这里插入图片描述
 

2. 添加菜单:点击添加菜单,弹框进行添加

在这里插入图片描述
 

3. 修改菜单

在这里插入图片描述
 

4. 删除菜单

在这里插入图片描述
 


二、角色管理

1. 角色列表:实现角色的条件查询带分页功能

在这里插入图片描述
 

2. 角色添加

在这里插入图片描述
 

3. 角色修改

在这里插入图片描述
在这里插入图片描述

 

4. 角色删除

  • 普通删除
    **加粗样式**

  • 批量删除
    在这里插入图片描述
     

5. 角色分配菜单

  • 点击分配按钮
    在这里插入图片描述
  • 给角色分配菜单
    在这里插入图片描述

 


三、用户管理

1. 用户列表

在这里插入图片描述
 

2. 用户添加

在这里插入图片描述
 

3. 用户修改

在这里插入图片描述
 

4. 用户删除

在这里插入图片描述
 

5. 用户分配角色

在这里插入图片描述
 


开发权限管理接口

一、创建权限管理服务

1. 在service模块下创建子模块service-acl

在这里插入图片描述
 

2. 在service_acl模块中引入依赖

<dependencies>
        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>spring_security</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

3. 创建权限管理相关的表

权限管理最少要5张表以上:用户表角色表资源表,以及它们的两个关系表,用户角色表角色资源表

在这里插入图片描述
 

  • acl_permission【权限表】

    CREATE TABLE `acl_permission` (
      `id` char(19) NOT NULL DEFAULT '' COMMENT '编号',
      `pid` char(19) NOT NULL DEFAULT '' COMMENT '所属上级',
      `name` varchar(20) NOT NULL DEFAULT '' COMMENT '名称',
      `type` tinyint(3) NOT NULL DEFAULT '0' COMMENT '类型(1:菜单,2:按钮)',
      `permission_value` varchar(50) DEFAULT NULL COMMENT '权限值',
      `path` varchar(100) DEFAULT NULL COMMENT '访问路径',
      `component` varchar(100) DEFAULT NULL COMMENT '组件路径',
      `icon` varchar(50) DEFAULT NULL COMMENT '图标',
      `status` tinyint(4) DEFAULT NULL COMMENT '状态(0:禁止,1:正常)',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
      `gmt_modified` datetime DEFAULT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `idx_pid` (`pid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限';
    
    
  • acl_role【角色表】

    CREATE TABLE `acl_role` (
      `id` char(19) NOT NULL DEFAULT '' COMMENT '角色id',
      `role_name` varchar(20) NOT NULL DEFAULT '' COMMENT '角色名称',
      `role_code` varchar(20) DEFAULT NULL COMMENT '角色编码',
      `remark` varchar(255) DEFAULT NULL COMMENT '备注',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
  • acl_role_permission【权限角色关联表】

    CREATE TABLE `acl_role_permission` (
      `id` char(19) NOT NULL DEFAULT '',
      `role_id` char(19) NOT NULL DEFAULT '',
      `permission_id` char(19) NOT NULL DEFAULT '',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `idx_role_id` (`role_id`),
      KEY `idx_permission_id` (`permission_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限';
    
    
  • acl_user【用户表】

    CREATE TABLE `acl_user` (
      `id` char(19) NOT NULL COMMENT '会员id',
      `username` varchar(20) NOT NULL DEFAULT '' COMMENT '微信openid',
      `password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',
      `nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
      `salt` varchar(255) DEFAULT NULL COMMENT '用户头像',
      `token` varchar(100) DEFAULT NULL COMMENT '用户签名',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uk_username` (`username`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
    
    
  • acl_user_role【用户角色表关联表】

    CREATE TABLE `acl_user_role` (
      `id` char(19) NOT NULL DEFAULT '' COMMENT '主键id',
      `role_id` char(19) NOT NULL DEFAULT '0' COMMENT '角色id',
      `user_id` char(19) NOT NULL DEFAULT '0' COMMENT '用户id',
      `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除',
      `gmt_create` datetime NOT NULL COMMENT '创建时间',
      `gmt_modified` datetime NOT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`),
      KEY `idx_role_id` (`role_id`),
      KEY `idx_user_id` (`user_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    

以上5张数据表之间的对应关系:
在这里插入图片描述
 

4. 复制权限管理接口代码

在这里插入图片描述
 

5. 编写application.properties配置文件

# 服务端口
server.port=8009

# 服务名
spring.application.name=service-acl

# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

spring.redis.host=192.168.44.132
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000

spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1

#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

#最小空闲

#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:com/atguigu/aclservice/mapper/xml/*.xml


#指定注册中心地址
spring.cloud.nacos.discovery.server-addr=localhost:8848


#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

6. 复制整合Spring Security代码

在common模块下创建子模块spring_security
在这里插入图片描述
 

7. spring_security模块中引入依赖

<dependencies>
        <dependency>
            <groupId>com.atguigu</groupId>
            <artifactId>common_utils</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Security依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
    </dependencies>

8. 在 common_utils 模块中添加工具类

package com.atguigu.commonutils;

import com.atguigu.commonutils.R;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ResponseUtil {

    public static void out(HttpServletResponse response, R r) {
        ObjectMapper mapper = new ObjectMapper();
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            mapper.writeValue(response.getWriter(), r);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 


二、开发权限管理接口

1. 获取所有菜单

  1. 在Permission实体类添加属性

    	@ApiModelProperty(value = "层级")
        @TableField(exist = false)
        private Integer level;
    
        @ApiModelProperty(value = "下级")
        @TableField(exist = false)
        private List<Permission> children;
    
        @ApiModelProperty(value = "是否选中")
        @TableField(exist = false)
        private boolean isSelect;
    
  2. controller层
    PermissionController

    //获取全部菜单
        @ApiOperation(value = "查询所有菜单")
        @GetMapping
        public R indexAllPermission() {
            List<Permission> list =  permissionService.queryAllMenuGuli();
            return R.ok().data("children",list);
        }
    
  3. service层
    PermissionService

      //获取全部菜单
        List<Permission> queryAllMenu();
    

    PermissionServiceImpl

     //获取全部菜单
        @Override
        public List<Permission> queryAllMenuGuli() {
            //1 查询菜单表所有数据
            QueryWrapper<Permission> wrapper = new QueryWrapper<>();
            wrapper.orderByDesc("id");
            List<Permission> permissionList = baseMapper.selectList(wrapper);
            //2 把查询所有菜单list集合按照要求进行封装
            List<Permission> resultList = bulidPermission(permissionList);
            return resultList;
        }
    
        //把返回所有菜单list集合进行封装的方法
        public static List<Permission> bulidPermission(List<Permission> permissionList) {
    
            //创建list集合,用于数据最终封装
            List<Permission> finalNode = new ArrayList<>();
            //把所有菜单list集合遍历,得到顶层菜单 pid=0菜单,设置level是1
            for(Permission permissionNode : permissionList) {
                //得到顶层菜单 pid=0菜单
                if("0".equals(permissionNode.getPid())) {
                    //设置顶层菜单的level是1
                    permissionNode.setLevel(1);
                    //根据顶层菜单,向里面进行查询子菜单,封装到finalNode里面
                    finalNode.add(selectChildren(permissionNode,permissionList));
                }
            }
            return finalNode;
        }
    
        private static Permission selectChildren(Permission permissionNode, List<Permission> permissionList) {
            //1 因为向一层菜单里面放二层菜单,二层里面还要放三层,把对象初始化
            permissionNode.setChildren(new ArrayList<Permission>());
    
            //2 遍历所有菜单list集合,进行判断比较,比较id和pid值是否相同
            for(Permission it : permissionList) {
                //判断 id和pid值是否相同
                if(permissionNode.getId().equals(it.getPid())) {
                    //把父菜单的level值+1
                    int level = permissionNode.getLevel()+1;
                    it.setLevel(level);
                    //如果children为空,进行初始化操作
                    if(permissionNode.getChildren() == null) {
                        permissionNode.setChildren(new ArrayList<Permission>());
                    }
                    //把查询出来的子菜单放到父菜单里面
                    permissionNode.getChildren().add(selectChildren(it,permissionList));
                }
            }
            return permissionNode;
        }
    
  4. Swagger测试

    {
      "success": true,
      "code": 20000,
      "message": "成功",
      "data": {
        "children": [
          {
            "id": "1",
            "pid": "0",
            "name": "全部数据",
            "type": 0,
            "permissionValue": null,
            "path": null,
            "component": null,
            "icon": null,
            "status": null,
            "level": 1,
            "children": [
              {
                "id": "1195354076890370050",
                "pid": "1",
                "name": "订单管理",
                "type": 1,
                "permissionValue": null,
                "path": "/order",
                "component": "Layout",
                "icon": null,
                "status": null,
                "level": 2,
                "children": [
                  {
                    "id": "1195354153482555393",
                    "pid": "1195354076890370050",
                    "name": "订单列表",
                    "type": 1,
                    "permissionValue": null,
                    "path": "list",
                    "component": "/order/list",
                    "icon": null,
                    "status": null,
                    "level": 3,
                    "children": [
                      {
                        "id": "1195354315093282817",
                        "pid": "1195354153482555393",
                        "name": "查看",
                        "type": 2,
                        "permissionValue": "order.list",
                        "path": "",
                        "component": "",
                        "icon": null,
                        "status": null,
                        "level": 4,
                        "children": [],
                        "isDeleted": false,
                        "gmtCreate": "2019-11-15 22:54:12",
                        "gmtModified": "2019-11-15 22:54:12",
                        "select": false
                      }
                    ],
                    "isDeleted": false,
                    "gmtCreate": "2019-11-15 22:53:33",
                    "gmtModified": "2019-11-15 22:53:58",
                    "select": false
                  }
                ],
                "isDeleted": false,
                "gmtCreate": "2019-11-15 22:53:15",
                "gmtModified": "2019-11-15 22:53:15",
                "select": false
              },
              {
                "id": "1195352547621965825",
                "pid": "1",
                "name": "CMS管理",
                "type": 1,
                "permissionValue": null,
                "path": "/cms",
                "component": "Layout",
                "icon": null,
                "status": null,
                "level": 2,
                "children": [
                  {
                    "id": "1195353513549205505",
                    "pid": "1195352547621965825",
                    "name": "Bander列表",
                    "type": 1,
                    "permissionValue": null,
                    "path": "banner/list",
                    "component": "/cms/banner/list",
                    "icon": null,
                    "status": null,
                    "level": 3,
                    "children": [
                      {
                        "id": "1195353672110673921",
                        "pid": "1195353513549205505",
                        "name": "删除",
                        "type": 2,
                        "permissionValue": "banner.remove",
                        "path": "",
                        "component": "",
                        "icon": null,
                        "status": null,
                        "level": 4,
                        "children": [],
                        "isDeleted": false,
                        "gmtCreate": "2019-11-15 22:51:39",
                        "gmtModified": "2019-11-15 22:51:39",
                        "select": false
                      },
                      {
                        "id": "1195353051395624961",
                        "pid": "1195353513549205505",
                        "name": "修改",
                        "type": 2,
                        "permissionValue": "banner.update",
                        "path": "banner/update/:id",
                        "component": "/cms/banner/form",
                        "icon": null,
                        "status": null,
                        "level": 4,
                        "children": [],
                        "isDeleted": false,
                        "gmtCreate": "2019-11-15 22:49:11",
                        "gmtModified": "2019-11-18 10:52:05",
                        "select": false
                      },
                      {
                        "id": "1195352909401657346",
                        "pid": "1195353513549205505",
                        "name": "添加",
                        "type": 2,
                        "permissionValue": "banner.add",
                        "path": "banner/add",
                        "component": "/cms/banner/form",
                        "icon": null,
                        "status": null,
                        "level": 4,
                        "children": [],
                        "isDeleted": false,
                        "gmtCreate": "2019-11-15 22:48:37",
                        "gmtModified": "2019-11-18 10:52:10",
                        "select": false
                      },
                      {
                        "id": "1195352856645701633",
                        "pid": "1195353513549205505",
                        "name": "查看",
                        "type": 2,
                        "permissionValue": "banner.list",
                        "path": "",
                        "component": null,
                        "icon": null,
                        "status": null,
                        "level": 4,
                        "children": [],
                        "isDeleted": false,
                        "gmtCreate": "2019-11-15 22:48:24",
                        "gmtModified": "2019-11-15 22:48:24",
                        "select": false
                      }
                    ],
                    "isDeleted": false,
                    "gmtCreate": "2019-11-15 22:51:01",
                    "gmtModified": "2019-11-18 10:51:29",
                    "select": false
                  }
                ],
                "isDeleted": false,
                "gmtCreate": "2019-11-15 22:47:11",
                "gmtModified": "2019-11-18 10:51:46",
                "select": false
              },
              {
                "id": "1195351862889254913",
                "pid": "1",
                "name": "统计分析",
                "type": 1,
                "permissionValue": null,
                "path": "/statistics/daily",
                "component": "Layout",
                "icon": null,
                "status": null,
                "level": 2,
                "children": [
                  {
                    "id": "1195352054917074946",
                    "pid": "1195351862889254913",
                    "name": "统计图表",
                    "type": 1,
                    "permissionValue": null,
                    "path": "chart",
                    "component": "/statistics/daily/chart",
                    "icon": null,
                    "status": null,
                    "level": 3,
                    "children": [
                      {
                        "id": "1195352127734386690",
                        "pid": "1195352054917074946",
                        "name": "查看",
                        "type": 2,
                        "permissionValue": "daily.list",
                        "path": "",
                        "component": "",
                        "icon": null,
                        "status": null,
                        "level": 4,
                        "children": [],
                        "isDeleted": false,
                        "gmtCreate": "2019-11-15 22:45:30",
                        "gmtModified": "2019-11-15 22:45:30",
                        "select": false
                      }
                    ],
                    "isDeleted": false,
                    "gmtCreate": "2019-11-15 22:45:13",
                    "gmtModified": "2019-11-15 22:45:13",
                    "select": false
                  },
            }
    }
    

2. 递归删除菜单

  1. controller层

    @ApiOperation(value = "递归删除菜单")
        @DeleteMapping("remove/{id}")
        public R remove(@PathVariable String id) {
            permissionService.removeChildByIdGuli(id);
            return R.ok();
        }
    
  2. service层

    @Override
        public void removeChildByIdGuli(String id) {
            //1 创建list集合,用于封装所有删除菜单id值
            List<String> idList = new ArrayList<>();
            //2 向idList集合设置删除菜单id
            this.selectPermissionChildById(id,idList);
            //把当前id封装到list里面
            idList.add(id);
            baseMapper.deleteBatchIds(idList);
        }
    
        //2 根据当前菜单id,查询菜单里面子菜单id,封装到list集合
        private void selectPermissionChildById(String id, List<String> idList) {
            //查询菜单里面子菜单id
            QueryWrapper<Permission>  wrapper = new QueryWrapper<>();
            wrapper.eq("pid",id);
            wrapper.select("id");
            List<Permission> childIdList = baseMapper.selectList(wrapper);
            //把childIdList里面菜单id值获取出来,封装idList里面,做递归查询
            childIdList.stream().forEach(item -> {
                //封装idList里面
                idList.add(item.getId());
                //递归查询
                this.selectPermissionChildById(item.getId(),idList);
            });
        }
    

3. 给角色分配权限

  1. controller层

    @ApiOperation(value = "给角色分配权限")
        @PostMapping("/doAssign")
        public R doAssign(String roleId,String[] permissionId) {
            permissionService.saveRolePermissionRealtionShipGuli(roleId,permissionId);
            return R.ok();
        }
    
  2. service层

    @Override
        public void saveRolePermissionRealtionShipGuli(String roleId, String[] permissionIds) {
            //roleId角色id
            //permissionId菜单id 数组形式
            //1 创建list集合,用于封装添加数据
            List<RolePermission> rolePermissionList = new ArrayList<>();
            //遍历所有菜单数组
            for(String perId : permissionIds) {
                //RolePermission对象
                RolePermission rolePermission = new RolePermission();
                rolePermission.setRoleId(roleId);
                rolePermission.setPermissionId(perId);
                //封装到list集合
                rolePermissionList.add(rolePermission);
            }
            //添加到角色菜单关系表
            rolePermissionService.saveBatch(rolePermissionList);
        }
    

创作不易,如果有帮助到你,请给文章点个赞和收藏,让更多的人看到!!!
关注博主不迷路,内容持续更新中。

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

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

相关文章

sql调优

一、MySQL架构总览&#xff1a; 三、SQL解析顺序 SELECT DISTINCT< select_list > FROM< left_table > < join_type > JOIN < right_table > ON < join_condition > WHERE< where_condition > GROUP BY< group_by_list > HAVING<…

振弦采集模块的频率值与温度值的修正

振弦采集模块的频率值与温度值的修正 此功能在 SF3.51 版本时增加。 固件版本 V3.51 修改固件版本号为 V3.51_2200827。 增加了频率和温度的多项式修正参数和对应指令。 $STFP、 $GTFP、 $STTP、 $GTTP 增加了 FFT 频幅数据输出功能。设置 ATSD_SEL.[5]为 1。 修正了 VM608 采集…

java实现的非关系型数据库:nosqldb

nosqldb一、nosqldb介绍二、nosqldb功能介绍三、数据存储结构介绍1. 数据文件存储结构(data.nosqldb)2.索引文件存储结构(index.mbdb)三、优化点1. 不支持连表查询2. 不支持分片存储3. 碎片整理一、nosqldb介绍 github地址 https://github.com/MaBo2420935619/nosqldb nosqld…

2022年终总结2023年计划

目录 前言&#xff1a; 2022年总结&#xff1a; 工作上&#xff1a; 生活上&#xff1a; 2023年规划&#xff1a; 工作上&#xff1a; 生活上&#xff1a; 前言&#xff1a; 嗨&#xff0c;不知不觉一年又过去了&#xff0c;2022已经结束了&#xff0c;我们迎来了2023。…

c++ -- STL容器--list容器

7. list容器7.1 简介① 功能&#xff1a;将数据进行链式存储。② 链表(list)是一种物理存储单元上非连续的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接实现的。③ 链表的组成&#xff1a;链表由一系列结点组成。④ 结点的组成&#xff1a;一个是存储数据元素…

serverless论文总结

1.Benchmarking, Analysis, and Optimization of Serverless Function Snapshots https://zhuanlan.zhihu.com/p/572288442 这项工作引入了vHive&#xff0c;一个针对无服务器实验的开源框架&#xff0c;它使系统研究人员能够在整个无服务器堆栈中进行创新。vHive集成了来自领…

ITSM | 权威指南发布,高速IT服务管理团队是什么样子的?

当Netflix正在打造流媒体平台时&#xff0c;还有人在营业厅里为一张网卡而烦恼。当Craig Newmark创建免费分类广告网站时&#xff0c;报社的网络管理员最关心的还是重启电子邮件服务器。当数字化转型成为一道必答题&#xff0c;谁能率先给出解题之法&#xff1f; 阅读本篇文章&…

Input子系统

文章目录前言Input子系统简介Input子系统代码实现框架Linux Input子系统支持的数据类型input核心层设备驱动层input_allocate_device 与 函数input_set_capabilityinput_register_device 函数input_unregister_device 与 input_free_device 函数事件处理层input_attach_handler…

A. Divide and Conquer

An array bb is good if the sum of elements of bb is even. You are given an array aa consisting of nn positive integers. In one operation, you can select an index ii and change ai:⌊ai2⌋ai:⌊ai2⌋. †† Find the minimum number of operations (possibly 00)…

ArcGIS 切片问题小结

1. 如果发布的切片缓存服务没有自动启动怎么办&#xff1f; 在进行切片时偶然情况下可能会遇到&#xff0c;你在切片时已经设置了server自动进行切片处理&#xff0c;但是在服务发布后&#xff0c;服务发布成功&#xff0c;但是服务没有成功启动&#xff0c;导致服务器没有自动…

免费数据恢复方法有哪些?分享这几种简单又实用的恢复方法(2023年最新)

很多时候&#xff0c;我们使用电脑总是容易误删一些数据。比如使用电脑&#xff0c;误删了办公资料&#xff1b;使用SD卡&#xff0c;出现照片没有办法打开的情况&#xff1b;使用移动硬盘&#xff0c;出现文档误格式化等问题。 不必要的数据被删除那就没问题&#xff0c;如果…

Java反序列化—Fastjson基础

0x01 前言 最近摆烂了很久&#xff0c;来学习一下fastjson 0x02 Fastjson 简介 Fastjson 是 Alibaba 开发的 Java 语言编写的高性能 JSON 库&#xff0c;用于将数据在 JSON 和 Java Object 之间互相转换。 提供两个主要接口来分别实现序列化和反序列化操作。 JSON.toJSONStr…

socket应用之从电脑发送图片到手机(1)之通信过程建立

本人曾经做了一个基于MPVd的C#开发的播放器&#xff0c;用于自娱自乐&#xff0c;后来又用websocket 写了个简单的远程控制器。由于websocket 要依赖于浏览器&#xff0c;因此有诸多不便&#xff0c;后来又用flutter写了一个&#xff0c;方便多了。 下面介绍具体实现。 1、通信…

RabbitMQ 消息持久化

RabbitMQ 消息持久化 持久化是为提高rabbitmq消息的可靠性&#xff0c;防止在异常情况(重启&#xff0c;关闭&#xff0c;宕机)下数据的丢失。设置完队列和消息的持久化&#xff0c;并不能完全保证消息不会丢失。尽管它告诉 RabbitMQ 将消息保存到磁盘&#xff0c;但当 Rabbit…

mapbox-gl添加threejs飞线

文章目录前言飞线实现1 初始化地图并加载three图层2 绘制飞线几何体将几何体正确定位在mapbox上正确操作BufferGeometry几何体3 tween实现动画全部代码总结待改进之处参考前言 mapbox-gl是一个基于webgl开发的三维地图渲染引擎&#xff0c;但是很多三维特效只用mapbox并不容易…

【CSDN 年终总结】CSDN的进阶之路—— “1+1=王”的2022总结

正文之前打个广告&#xff0c;我正在参加年度博客之星评选&#xff0c;请大家帮我投票打分&#xff0c;您的每一分都是对我的支持与鼓励。⭐ ⭐ ⭐ ⭐ ⭐https://bbs.csdn.net/topics/611386885?spm1001.2014.3001.6953 2022我在CSDN 2022 在CSDN是持续输出&#xff0c;持续…

TinyPng图片压缩的正确打开方式

https://tinypng.com/ TinyPNG使用智能的「有损压缩技术」来减少WEBP、JPEG和PNG文件的文件大小。通过选择性地减少图像中的「颜色数量」&#xff0c;使用更少的字节来存储数据。这种效果几乎是看不见的&#xff0c;但在文件大小上有非常大的差别。 使用过TinyPNG的都知道&…

MyBatis 万字长文:从入门到动态SQL超详细

文章目录1. 前言2. 创建项目3. 添加框架支持4. 建库5. 配置数据库连接信息和 XML 文件路径5.1 创建 Java 类5.2 Java 接口5.3 XML 文件6. 查询6.1 不带参数的查询6.2 单元测试6.3 带参数的查询7. 修改8. 增加8.1 将对象插入表中8.2 获取自增主键9. 删除10. 数据库字段和类属性名…

Video2StyleGAN: Disentangling Local and Global Variations in a Video翻译

点击下载论文 代码地址 摘要 使用预训练的StyleGAN生成器进行图像编辑已成为面部编辑的强大范例&#xff0c;它提供了对年龄、表情、照明度等的解纠缠控制。然而&#xff0c;该方法不能直接用于视频操作。我们认为主要因素是缺乏对面部位置、面部姿势和局部面部表情的精细和清…

腾讯云-云服务器购买流程-Java项目部署(详细的一批)

文章目录云服务器购买云服务搭建部署环境宝塔面板使用&#xff08;安装所需环境&#xff09;部署SpringBoot项目出现Error: Unable to access jarfile /www/wwwroot/xxxx.jar--server.port6066 问题解决腾讯云COS有什么用&#xff1f;如果感觉有用就一键三连吧&#xff0c;创作…