MyBatis Plus 拦截器实现数据权限控制(完整版)

news2024/11/28 21:57:12

一、说明

变化:相比于之前写的数据权限拦截器,新增了白名单功能,通过注解的方式让哪些SQL不进行数据权限拦截,之前的文章地址

思路:通过MyBatisPlus的拦截器对每个要执行的SQL进行拦截,然后判断其是否为查询语句,如果是查询语句,则继续判断,其类或者方法上是否存在@DataScopeIgnore注解,如果存在则不做任何处理,反之对原始SQL进行解析,并获取该用户的角色,并获取其针对该接口设置的数据权限信息,改造原始的查询条件,以此来实现数据权限的控制

缺陷:目前@DataScopeIgnore注解只有作用于Mapper层才能生效,不过按理说应该是满足一般数据权限控制的要求

代码地址,其中包含一些暂时未使用的代码,是为了后续做单点登录准备的,所有涉及代码在下面已经全部提及

二、拦截器

package com.xx.permission.config;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.xx.permission.entity.result.DataPermission;
import com.xx.permission.utils.ExpressionUtils;
import com.xx.permission.utils.UserUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

/**
 * @author aqi
 * @describe 数据权限拦截器
 */
@Slf4j
@Component
public class DataScopeInterceptor extends JsqlParserSupport implements InnerInterceptor {

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();

        if (sct == SqlCommandType.SELECT) {
            if (this.judgementDataScopeIgnore(ms)) {
                return;
            }

            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(parserMulti(mpBs.sql(), null));
        }
    }

    /**
     * 查询
     */
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        SelectBody selectBody = select.getSelectBody();
        PlainSelect plainSelect = (PlainSelect) selectBody;

        // 获取表名/别名(如果是关联查询是取第一个join左侧的表名/别名)
        String tableName = ExpressionUtils.getTableName(plainSelect);

        // 构建用户权限控制条件
        this.buildUserPermissionSql(plainSelect, sql, tableName);
    }

    /**
     * 构建用户权限控制条件
     * 思路:
     *      1、获取当前用户信息,再通过用户信息获取到角色对应的数据权限集合
     *      2、通过获取到的该用户所有的数据权限集合过滤出本次请求的数据权限
     *      3、将本次请求的数据权限拼接到已有的SQL中
     * @param plainSelect 用于解析SQL的类
     * @param tableName 表名/别名(join查询左侧表名)
     */
    private void buildUserPermissionSql(PlainSelect plainSelect, String sql, String tableName) {
        // 获取当前用户接口数据权限(这里的数据都是模拟的,实际上可能得从缓存或者session中获取)
        Map<String, List<DataPermission>> userPermissionMap = UserUtils.getUserPermission();
        // 获取本次请求的uri
        String uri = UserUtils.getUri();
        // 获取本次请求uri对应的数据权限集合
        List<DataPermission> dataPermissions = userPermissionMap.get(uri);
        if (!CollectionUtils.isEmpty(dataPermissions)) {
            // 将多个条件合并在一起
            dataPermissions.forEach(permission -> {
                InExpression inExpression = ExpressionUtils.buildInSql(tableName, permission);
                ExpressionUtils.appendExpression(plainSelect, inExpression);
            });
        }
        log.info("[DataScopeInterceptor]请求uri:[{}],原始SQL:[{}]处理后SQL:[{}]", uri, sql, plainSelect);
    }

    /**
     * 判断类/方法上是否存在DataScopeIgnore注解
     * 存在的问题:这里只能获取到Mapper层中的一些东西,所以在别的地方加@DataScopeIgnore也不会生效
     * @param ms ms
     * @return 是否需要进行数据权限控制,true:不需要,false:需要
     */
    private boolean judgementDataScopeIgnore(MappedStatement ms) {
        try {
            String id = ms.getId();

            String classPath = id.substring(0, id.lastIndexOf("."));
            Class<?> aClass = Class.forName(classPath);

            // 判断该类上是否存在DataScopeIgnore注解
            DataScopeIgnore declaredClassAnnotation = aClass.getDeclaredAnnotation(DataScopeIgnore.class);

            if (declaredClassAnnotation != null) {
                return true;
            }

            // 判断该方法上是否存在DataScopeIgnore注解
            String methodName = id.substring(id.lastIndexOf(".") + 1);
            Method method = aClass.getMethod(methodName);
            DataScopeIgnore declaredMethodAnnotation = method.getDeclaredAnnotation(DataScopeIgnore.class);

            return declaredMethodAnnotation != null;
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            // 解析DataScopeIgnore出现异常,默认当作需要做数据权限控制
            return false;
        }
    }

}

三、将拦截器加入MyBatisPlus拦截器

package com.xx.permission.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * @author aqi
 * @since 2023/5/15 14:05
 */
@Slf4j
@Configuration
@Component
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 初始化Mybatis Plus拦截器
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 自定义数据拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        interceptor.addInnerInterceptor(new DataScopeInterceptor());
        return interceptor;
    }
}

四、创建自定义白名单注解

package com.xx.permission.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author aqi
 * @describe 不进行数据权限控制
 * @since 2023/6/8 14:44
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScopeIgnore {

}

五、JsqlParser工具类

package com.xx.permission.utils;

import com.xx.permission.entity.result.DataPermission;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.PlainSelect;

import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author aqi
 * @since 2023/5/17 10:16
 * @describe JSqlParser工具类,用于通过API的方式操作SQL语句
 */
public class ExpressionUtils {

    private final static String LONG_TYPE = "long";
    private final static String STRING_TYPE = "string";

    /**
     * 构建in sql
     * @param tableName 表名
     * @param permission 字段权限
     * @return InExpression
     */
    public static InExpression buildInSql(String tableName, DataPermission permission) {
        // 把集合转变为JSQLParser需要的元素列表
        ItemsList itemsList = ExpressionUtils.handleFieldType(permission);
        // 创建IN表达式对象,传入列名及IN范围列表
        return new InExpression(new Column(tableName + "." + permission.getField()), itemsList);
    }

    /**
     * 构建查询语句之前,判断字段类型
     * @param permission 字段权限
     */
    private static ItemsList handleFieldType(DataPermission permission) {
        String fieldType = permission.getFieldType();
        if (Objects.equals(fieldType, LONG_TYPE)) {
            return new ExpressionList(permission.getValue().stream().map(LongValue::new).collect(Collectors.toList()));
        } else {
            return new ExpressionList(permission.getValue().stream().map(StringValue::new).collect(Collectors.toList()));
        }
    }

    /**
     * 构建eq sql
     * @param columnName 字段名称
     * @param value 字段值
     * @return EqualsTo
     */
    public static EqualsTo buildEq(String columnName, String value) {
       return new EqualsTo(new Column(columnName), new StringValue(value));
    }

    /**
     * 获取表名/别名
     * @param plainSelect plainSelect
     * @return 表名/别名
     */
    public static String getTableName(PlainSelect plainSelect) {
        // 获取别名
        Table table= (Table) plainSelect.getFromItem();
        Alias alias = table.getAlias();
        return null == alias ? table.getName() : alias.getName();
    }

    /**
     * 将2个where条件拼接到一起
     * @param plainSelect plainSelect
     * @param appendExpression 待拼接条件
     */
    public static void appendExpression(PlainSelect plainSelect, Expression appendExpression) {
        Expression where = plainSelect.getWhere() == null ? appendExpression : new AndExpression(plainSelect.getWhere(), appendExpression);
        plainSelect.setWhere(where);
    }
}

六、用户信息工具类

目前是简单的用户工具类,按照实际情况需要调整

package com.xx.permission.utils;

import com.xx.permission.config.CacheData;
import com.xx.permission.entity.result.DataPermission;
import com.xx.permission.entity.result.RoleDataPermission;
import com.xx.permission.entity.result.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author aqi
 * @since 2023/5/17 14:20
 */
@Slf4j
public class UserUtils {

    public static UserDTO currentUser;

    /**
     * 获取当前用户信息
     * @return 用户信息
     */
    public static UserDTO getCurrentUserInfo() {
        return currentUser;
    }

    /**
     * 模拟登录之后往session或者redis中存储用户信息的过程
     * @param user 用户信息
     */
    public static void setCurrentUser(UserDTO user) {
        UserUtils.currentUser = user;
    }

    /**
     * 获取用户权限信息
     * @return 用户权限信息
     */
    public static Map<String, List<DataPermission>> getUserPermission() {
        // 获取当前用户信息
        UserDTO currentUserInfo = UserUtils.getCurrentUserInfo();
        // 获取当前用户角色下的所有接口数据权限集合
        List<RoleDataPermission> roleDataPermissionList = CacheData.ROLE_DATA_PERMISSION_MAP.get(currentUserInfo.getRoleId());
        // key:uri(接口地址),value:DataPermission(数据权限)
        return roleDataPermissionList.stream().collect(Collectors.toMap(RoleDataPermission::getUri, RoleDataPermission::getDataPermissionList));
    }

    /**
     * 获取本次请求的uri
     * @return uri
     */
    public static String getUri() {
        // 获取此次请求的uri
        String uri = "";
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (null != requestAttributes) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            uri = request.getRequestURI();
        }
        return uri;
    }
}

七、初始化用户权限缓存

缓存

package com.xx.permission.config;

import com.xx.permission.entity.result.RoleDataPermission;

import java.util.List;
import java.util.Map;

/**
 * @author xiaxing
 * @describe
 * @since 2023/5/18 15:06
 */
public class CacheData {

    /**
     * 角色权限信息
     */
    public static List<RoleDataPermission> ROLE_DATA_PERMISSION_LIST;

    /**
     * 角色权限信息(key:角色ID,value:角色权限信息)
     */
    public static Map<Long, List<RoleDataPermission>> ROLE_DATA_PERMISSION_MAP;

}

初始化初始化角色数据权限

package com.xx.permission.config;

import com.alibaba.fastjson.JSONArray;
import com.xx.permission.entity.result.DataPermission;
import com.xx.permission.entity.result.RoleDataPermission;
import com.xx.permission.service.SysRoleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author aqi
 * @since 2023/5/18 14:48
 * @describe 初始化配置数据
 */
@Slf4j
@Component
public class InitConfig {

    @Resource
    private SysRoleService sysRoleService;

    @PostConstruct
    public void init() {
        this.initRoleDataPermission();
    }

    /**
     * 初始化角色数据权限
     */
    private void initRoleDataPermission() {
        log.info("初始化角色数据权限...");
        List<RoleDataPermission> roleDataPermission = sysRoleService.getRoleDataPermission();
        for (RoleDataPermission dataPermission : roleDataPermission) {
            List<DataPermission> dataPermissions = JSONArray.parseArray(dataPermission.getDataPermission(), DataPermission.class);
            dataPermission.setDataPermissionList(dataPermissions);
        }
        CacheData.ROLE_DATA_PERMISSION_LIST = roleDataPermission;
        CacheData.ROLE_DATA_PERMISSION_MAP = CacheData.ROLE_DATA_PERMISSION_LIST.stream()
                .collect(Collectors.groupingBy(RoleDataPermission::getRoleId));
    }

}

八、其他代码生成器生成代码

8.1、角色权限

xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xx.permission.mapper.SysRoleMapper">

    <select id="getRoleDataPermission" resultType="com.xx.permission.entity.result.RoleDataPermission">
        SELECT
            sys_role.id roleId,
            sys_role.role_name roleName,
            sys_role.DESCRIBE roleDescribe,
            sys_role_api_permission.data_permission dataPermission,
            sys_api.uri
        FROM
            sys_role
                INNER JOIN sys_role_api_permission ON sys_role.id = sys_role_api_permission.role_id
                INNER JOIN sys_api ON sys_api.id = sys_role_api_permission.api_id
    </select>

</mapper>

这里在方法上加上了@DataScopeIgnore注解,避免获取角色权限时受到数据权限拦截的影响

package com.xx.permission.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xx.permission.config.DataScopeIgnore;
import com.xx.permission.entity.SysRole;
import com.xx.permission.entity.result.RoleDataPermission;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * <p>
 * 角色表 Mapper 接口
 * </p>
 *
 * @author aqi
 * @since 2023-05-18
 */
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRole> {

    @DataScopeIgnore
    List<RoleDataPermission> getRoleDataPermission();

}

实现类

package com.xx.permission.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xx.permission.entity.SysRole;
import com.xx.permission.entity.result.RoleDataPermission;
import com.xx.permission.mapper.SysRoleMapper;
import com.xx.permission.service.SysRoleService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 角色表 服务实现类
 * </p>
 *
 * @author aqi
 * @since 2023-05-18
 */
@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {

    @Resource
    SysRoleMapper sysRoleMapper;

    @Override
    public List<RoleDataPermission> getRoleDataPermission() {
        return sysRoleMapper.getRoleDataPermission();
    }
}

实体类

package com.xx.permission.entity.result;

import lombok.Data;

import java.util.List;

/**
 * @author xiaxing
 * @describe 角色权限
 * @since 2023/5/18 14:59
 */
@Data
public class RoleDataPermission {

    /**
     * 角色ID
     */
    private Long roleId;

    /**
     * 角色名称
     */
    private String roleName;

    /**
     * 角色描述
     */
    private String roleDescribe;

    private String dataPermission;

    /**
     * 角色权限集合
     */
    private List<DataPermission> dataPermissionList;

    /**
     * 接口地址
     */
    private String uri;
}

package com.xx.permission.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 * 角色表
 * </p>
 *
 * @author aqi
 * @since 2023-05-18
 */
@Data
@TableName("sys_role")
public class SysRole implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 角色ID
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 角色名称
     */
    private String roleName;

    /**
     * 角色说明
     */
    private String describe;
}

8.2、表结构

这里的表结构只有最基础的字段

8.2.1、角色表

在这里插入图片描述

在这里插入图片描述

8.2.1、接口表

在这里插入图片描述

在这里插入图片描述

8.2.1、角色接口权限关联表

这里的数据权限存在了JSON中,只是为了少建表,其中field表示字段名称,value表示字段的值,fieldType表示字段的数据类型(目前只写了Long和String的支持)

在这里插入图片描述

在这里插入图片描述

九、测试

package com.xx.permission.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xx.permission.entity.result.UserDTO;
import com.xx.permission.service.TOrderService;
import com.xx.permission.utils.UserUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * <p>
 * 订单表 前端控制器
 * </p>
 *
 * @author aqi
 * @since 2023-06-08
 */
@RestController
@RequestMapping("/order")
public class TOrderController {

    @Resource
    private TOrderService tOrderService;

    @GetMapping("/list")
    public void test() {
        UserDTO userDTO = new UserDTO();
        userDTO.setRoleId(2L);
        UserUtils.setCurrentUser(userDTO);
        tOrderService.page(new Page<>(1, 10));
    }

}

效果

2023-06-09 09:47:12.349  INFO 33248 --- [nio-8080-exec-2] c.x.p.config.DataScopeInterceptor        : [DataScopeInterceptor]请求uri:[/order/list],原始SQL:[SELECT COUNT(*) AS total FROM t_order]处理后SQL:[SELECT COUNT(*) AS total FROM t_order WHERE t_order.area IN (2) AND t_order.type IN ('1')]
2023-06-09 09:47:12.349 DEBUG 33248 --- [nio-8080-exec-2] c.x.p.m.TOrderMapper.selectPage_mpCount  : ==>  Preparing: SELECT COUNT(*) AS total FROM t_order WHERE t_order.area IN (2) AND t_order.type IN ('1')
2023-06-09 09:47:12.349 DEBUG 33248 --- [nio-8080-exec-2] c.x.p.m.TOrderMapper.selectPage_mpCount  : ==> Parameters: 
2023-06-09 09:47:12.356 DEBUG 33248 --- [nio-8080-exec-2] c.x.p.m.TOrderMapper.selectPage_mpCount  : <==      Total: 1
2023-06-09 09:47:12.364  INFO 33248 --- [nio-8080-exec-2] c.x.p.config.DataScopeInterceptor        : [DataScopeInterceptor]请求uri:[/order/list],原始SQL:[SELECT  id,number,type,area  FROM t_order LIMIT ?]处理后SQL:[SELECT id, number, type, area FROM t_order WHERE t_order.area IN (2) AND t_order.type IN ('1') LIMIT ?]
2023-06-09 09:47:12.364 DEBUG 33248 --- [nio-8080-exec-2] c.x.p.mapper.TOrderMapper.selectPage     : ==>  Preparing: SELECT id, number, type, area FROM t_order WHERE t_order.area IN (2) AND t_order.type IN ('1') LIMIT ?
2023-06-09 09:47:12.365 DEBUG 33248 --- [nio-8080-exec-2] c.x.p.mapper.TOrderMapper.selectPage     : ==> Parameters: 10(Long)
2023-06-09 09:47:12.377 DEBUG 33248 --- [nio-8080-exec-2] c.x.p.mapper.TOrderMapper.selectPage     : <==      Total: 1

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

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

相关文章

勒索病毒远程桌面——防御方案

一、适用目标&#xff08;校园网、企业网&#xff0c;windows系列的操作系统&#xff09;&#xff1a; 所有在局域网内运行windows系统的电脑&#xff0c;并非只感染服务器操作系统&#xff0c;单机照样感染。会将你电脑中的所有文件全部加密&#xff0c;部分已感染案例有2个共…

常见的存储类型:DAS vs SAN vs NAS

什么是存储 你有想过你在朋友圈分享的照片都存在哪里&#xff1f;你在视频网站上浏览的视频都存放在哪里&#xff1f;甚至&#xff0c;你在银行卡里的存款、房贷是如何随时查询、随时存取的&#xff1f; 没错&#xff0c;这些照片、视频&#xff0c;甚至你的存款、房贷的数值…

机器学习-6 支持向量机

支持向量机 算法概述算法流程线性分类线性可分性向量内积硬间隔分类软间隔SVM模型非线性支持向量机非线性的情况非线性支持向量机核函数 SVM优点 算法步骤线性可支持向量机的程序流程图SVM算法步骤 算法实例有关数据集利用Sklearn的datasets模块生成数据集其他生成数据集的方法…

BIM与点云:一种基于航空LiDAR点云的大规模建筑重建

文章&#xff1a;City3D: Large-Scale Building Reconstruction from Airborne LiDAR Point Clouds 作者&#xff1a;Jin Huang , Jantien Stoter , Ravi Peters and Liangliang Nan 编辑&#xff1a;点云PCL 来源&#xff1a;arXiv2023 欢迎各位加入知识星球&#xff0c;获取P…

GeoServer SQL注入漏洞复现(CVE-2023-25157)

0x01 产品简介 GeoServer是一款开源的地理数据服务器软件&#xff0c;主要用于发布、共享和处理各种地理空间数据。它支持众多的地图和空间数据标准&#xff0c;能够使各种设备通过网络来浏览和使用这些地理信息数据。 0x02 漏洞概述 GeoServer在预览图层的时候&#xff0c;可…

Ubuntu20.04平台下使用二进制包部署MongoDB-6.0.4单实例

文章目录 1.1 准备服务器的基本信息1.2 操作系统上创建其用户1.3 部署MongoDB服务端1.4 部署MongoDB客户端1.5 部署MongoDB 27017实例1.5.1 创建相关目录1.5.2 准备配置文件1.5.3 准备启停脚本1.5.4 进行启停测试1.5.5 加入开机自启动 1.6 创建超级管理员用户1.6.1 创建本地的超…

do..while、while、for循环反汇编剖析

1、循环语句重要特征提取 循环语句最重要的特点就是执行的过程中会往上跳&#xff01;&#xff01;&#xff01; 箭头往上跳的一般都是循环语句&#xff0c;比如下面的for循环&#xff1a; 2、do..while语句反汇编 #include<iostream> using namespace std; #pragma …

【SpinalHDL快速入门】2、新建SpinalHDL工程,通过计数器Demo快速上手

文章目录 新建工程各个工具版本build.sbt 示例build.properties 示例如何在IEDA中更新 SpinalVersion 并 Reload sbt Project SpinalHDL入门例子&#xff1a;计数器demo1demo2&#xff08;支持reset信号异步复位&#xff0c;低电平有效&#xff09;demo3&#xff08;一个文件&a…

Flume学习--1、Flume概述、Flume入门、

1、Flume概述 1.1 Flume定义 Flume是Cloudera提供的一个高可用&#xff0c;高可靠的&#xff0c;分布式的海量日志采集、聚合和传输的系统。Flume基于流式结构&#xff0c;灵活简单。 Flume最主要的作用就是实时读取服务器本地磁盘的数据&#xff0c;将数据写入到HDFS。 1.2…

C#winform多国语言应用实例

我们在开发项目中,一般需要软件支持多种语言,供不同客户使用。本文实例讲解实现办法。 1 窗体项目创建 添加控件MenuStrip、comboBox及Button,并修改对应显示文本,combobox编辑项输入英语 确定窗体的Localizable属性为true,自动创建Form1.resx,为False时,没有Form1.r…

基站机房:保障通信网络稳定,如何解决安全隐患?

基站机房作为无线通信网络的关键组成部分&#xff0c;承载着大量的网络设备和通信设施&#xff0c;对于运营商和通信服务提供商来说具有重要意义。 无论是大型运营商还是通信服务提供商&#xff0c;动环监控系统都将成为他们成功运营和管理通信网络的关键工具。 客户案例 案例…

vue使用高德地图--附带移动获取当前城市信息

高德地图 1.使用准备申请密钥vue使用 2.移动地图获取城市案例(注意事项)3.总结 1.使用准备 申请密钥 登录注册高德开放平台进入控制台 创建应用 申请key–生成key和安全密钥(2021之后key需要配合安全密钥使用) 注意&#xff1a;安全密钥需要在key之前 vue使用 首先在pubil…

一款功能强大的报表引擎-VeryReport报表引擎

在企业管理中&#xff0c;数据分析和决策制定是非常重要的环节。而报表则是这个过程中最常用的工具之一。但是&#xff0c;传统的报表设计与展现方式已经无法满足企业对于数据分析和报表展示的需求。为了解决这些问题&#xff0c;我们向大家推荐一款新一代Web报表软件——VeryR…

越是大型企业越需要企业内部知识库?

随着信息时代的到来&#xff0c;越来越多的企业开始注重知识管理。知识管理是一种通过有效地捕捉、共享和利用企业内部的知识资源&#xff0c;促进企业创新和发展的方法。而企业内部知识库作为知识管理的一种重要方式&#xff0c;对于大型企业来说尤为重要。 一、大型企业内部…

苹果相关网站和服务器状态

https://www.apple.com.cn/cn/support/systemstatus/

googlecloud谷歌云的初学体会(1)

googlecloud谷歌云入门&#xff08;1&#xff09; 一、纯小白自述二、云是个什么云三、装一个软件&#xff08;资源、服务&#xff09;四、服务器&#xff08;爷爷提供服务的电脑&#xff09;五、PGSQL的安装六、总结 一、纯小白自述 自己是个小白&#xff0c;仅仅懂得几句sql…

华为OD机试真题 Java 实现【寻找密码】【2023Q1 100分】,附详细解题思路

一、题目描述 小王在进行游戏大闯关,有一个关卡需要输入一个密码才能通过,密码获得的条件如下: 在一个密码本中,每一页都有一个由 26 个小写字母组成的若干位密码,从它的末尾开始依次去掉一位得到的新密码也在密码本中存在。 请输出符合要求的密码,如果由多个符合要求…

爬虫如何选择工具和编程语言

爬虫选择工具和编程语言需要根据具体的需求和技术水平来决定。以下是一些常用的工具和编程语言&#xff1a; 工具&#xff1a; Scrapy&#xff1a;一个基于Python的高级爬虫框架&#xff0c;可用于快速开发和部署爬虫。Beautiful Soup&#xff1a;一个Python库&#xff0c;用…

基于“三维六类”干扰分析模型进行FDD900干扰规避优化指导

1.概述 随着网络发展&#xff0c;鉴于900M覆盖上的优势&#xff0c;为增强深度覆盖及竞对提升&#xff0c;当前FDD 900M已在加快部署&#xff0c;但随之也带来了干扰问题。当前&#xff0c;干扰排查成为FDD 900M部署过程中大量存在的难题。由于干扰排查难度大&#xff0c;且排…

线程池和使用

tip: 作为程序员一定学习编程之道&#xff0c;一定要对代码的编写有追求&#xff0c;不能实现就完事了。我们应该让自己写的代码更加优雅&#xff0c;即使这会费时费力。 推荐&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; 文章目录 线程池的目的线程池的参…