SpringBoot整合JWT+Spring Security+Redis实现登录拦截(二)权限认证

news2025/2/7 0:15:49

上篇博文中我们已经实现了登录拦截,接下来我们继续补充代码,实现权限的认证

一、RBAC权限模型

        什么事RBAC权限模型?

        RBAC权限模型(Role-Based Access Control)即:基于角色的权限访问控制。在RBAC中,权限与角色关联,用户通过赋予角色而获得相应角色的权限。故我们需要如下几张表:

        用户表:系统接口及访问的操作者

        权限表:能够访问某接口或者做某操作的授权资格

        角色表:具有一类相同操作权限的用户的总称

        用户角色表:用户角色相关联的表

        角色权限表:角色与权限相关联的表

二、创建对应的数据库表

        可根据项目需要增加或删减、修改对应的表字段

        sys_user

DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'ID',
  `nick_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `gender` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',
  `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码',
  `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色ID',
  `is_admin` bit(1) NULL DEFAULT b'0' COMMENT '是否为admin账号',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `enabled` bit(1) NULL DEFAULT NULL COMMENT '是否启用',
  `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
  `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统用户' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

 sys_role

DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'ID',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
  `level` int(0) NULL DEFAULT NULL COMMENT '角色级别',
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
  `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

sys_menu

DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'ID',
  `pid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上级菜单ID',
  `sub_count` int(0) NULL DEFAULT 0 COMMENT '子菜单数目',
  `type` int(0) NULL DEFAULT NULL COMMENT '菜单类型',
  `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单标题',
  `menu_sort` int(0) NULL DEFAULT NULL COMMENT '排序',
  `icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图标',
  `path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '链接地址',
  `cache` bit(1) NULL DEFAULT b'0' COMMENT '缓存',
  `hidden` bit(1) NULL DEFAULT b'0' COMMENT '隐藏',
  `permission` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限',
  `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
  `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 118 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统菜单' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

sys_user_role

DROP TABLE IF EXISTS `sys_users_roles`;
CREATE TABLE `sys_users_roles`  (
  `user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户ID',
  `role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE,
  INDEX `user_id`(`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色关联' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

sys_role_menu

DROP TABLE IF EXISTS `sys_roles_menus`;
CREATE TABLE `sys_roles_menus`  (
  `role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
  `menu_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单ID',
  PRIMARY KEY (`role_id`, `menu_id`) USING BTREE,
  INDEX `FKcngg2qadojhi3a651a5adkvbq`(`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色菜单关联' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

三、生成5张基础表对应的Java代码

        可自行书写或使用mybatis工具进行生成,也可参考之前的博文

mybatis-plus自动生成代码(整理版)_mybatisplus代码自动生成-CSDN博客文章浏览阅读111次。整理版,添加了注释模版,常用基础方法。也可直接替换成公共的或自己代码中自定义的。仅提供基础方法,可根据具体需求自行改造。仅提供基础方法,可根据具体需求自行改造。仅提供基础方法,可根据具体需求自行改造。_mybatisplus代码自动生成https://blog.csdn.net/shaogaiyue9745602/article/details/134525030?spm=1001.2014.3001.5502

四、鉴权实现

  1.创建PermissionMapper.xml

           变现sql根据用户ID查询角色对应的权限

<?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.hng.mapper.PermissionMapper">
    <select id="selectPermsByUserId" resultType="java.lang.String">
        SELECT
            DISTINCT m.permission
        FROM
            sys_users_roles ur
            LEFT JOIN sys_role r ON ur.role_id = r.id
            LEFT JOIN sys_roles_menus	rm ON ur.role_id = rm.role_id
            LEFT JOIN sys_menu m ON m.id = rm.menu_id
        WHERE
            user_id = #{userId}
    </select>
</mapper>

2.创建PermissionMapper

package com.hng.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hng.entity.SysMenu;

import java.util.List;

/**
 * <p>
 * 系统菜单 Mapper 接口
 * </p>
 *
 * @author 郝南过
 * @since 2023-12-19
 */
public interface PermissionMapper extends BaseMapper<SysMenu> {

    List<String> selectPermsByUserId(String userId);
}

3.改造SecurityUser

    (1)改造构造函数增加权限集合

    private List<String> permissions;

    public SecurityUser(SysUser user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    (2)改造getAuthorities

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //将permissions中的String类型的权限信息封装成SimpleGrantedAuthority对象
        if(authorities!=null){
            return authorities;
        }
        authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

 4.改造UserDetailsServiceImpl

        修改loadUserByUsername方法,使用Permission增加权限查询

package com.hng.config.security;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hng.config.exception.customize.EntityNotFoundException;
import com.hng.modules.system.entity.SysUser;
import com.hng.modules.system.mapper.PermissionMapper;
import com.hng.modules.system.mapper.SysMenuMapper;
import com.hng.modules.system.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @Author: 郝南过
 * @Description: 将Security拦截的用户名密码改为数据库中已有的用户名密码,Security自己校验密码,默认使用PasswordEncoder,格式为{id}password(id代表加密方式),
 * 一般不采用此方式,SpringSecurity提供了BcryptPasswordEncoder,只需将此注入到spring容器中。SpringSecurity就会使用它进行替换校验
 * @Date: 2023/12/11 11:29
 * @Version: 1.0
 */
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private SysUserService sysUserService;

    @Resource
    private PermissionMapper permissionMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(SysUser::getUserName,username);
        SysUser user = sysUserService.getOne(queryWrapper);
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        //查询授权信息
        List<String> permissions = permissionMapper.selectPermsByUserId(user.getId());
        //判断用户是否是管理员,如果是管理员添加admin权限
        if(user.getIsAdmin()){
            permissions.add("admin");
        }
        return new SecurityUser(user,permissions);
    }
}

5.自定义权限检测

      可直接使用已有的权限检测@PreAuthorize(“hasAuthority('自定义字符串')”),hasAuthority是系统提供的,可直接使用,也可以自定义。以下为自定义,为方便直接判断admin用户,并给admin用户授予所有权限。

PermissionConfig
package com.hng.config.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 自定义权限检测
 */
@Component(value = "ex")
public class PermissionConfig {

    public Boolean check(String... permissions) {
        // 获取当前用户的所有权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        List<String> exPermissions = securityUser.getPermissions();
        // 判断当前用户的所有权限是否包含接口上定义的权限
        return exPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(exPermissions::contains);
    }
}

6.在SecurityConfig中添加注解

@EnableGlobalMethodSecurity(prePostEnabled = true)//开启权限权限认证

7.在Cotroller中添加对应的权限字符

        如果未自定义权限检测使用注解@PreAuthorize(“hasAuthority('自定义字符串')”),本篇博文中的例子已经自定义为@PreAuthorize("@ex.check('自定义字符串')")

  SysUserController 示例

package com.hng.controller;

import lombok.RequiredArgsConstructor;
import io.swagger.annotations.ApiOperation;
import com.hng.config.response.ResponseResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import io.swagger.annotations.ApiParam;

import com.hng.entity.SysUser;
import com.hng.service.SysUserService;


/**
 * <p>
 * 系统用户 前端控制器
 * </p>
 */
@Slf4j
@Api(value = "SysUser", tags = "SysUser")
@RestController
@RequiredArgsConstructor //Lombok的一个注解,简化@Autowired
@RequestMapping("/sys-user")
public class SysUserController {

    private final SysUserService sysUserService;
    
    @PostMapping("/getList")
    @ApiOperation("sysUser列表查询")
    @PreAuthorize("@ex.check('SysUser:list')")
    public ResponseResult queryAllSysUser(@Validated @RequestBody SysUser sysUser){
        List<SysUser> list = sysUserService.queryAll(sysUser);
        return ResponseResult.success(list);
    }

    @PostMapping("/add")
    @ApiOperation("新增SysUser")
    @PreAuthorize("@ex.check('SysUser:add')")
    public ResponseResult addSysUser(@Validated @RequestBody SysUser sysUser){
        sysUserService.addSysUser(sysUser);
        return ResponseResult.success();
    }

    /**
     * 根据ID查询数据
     * @param id ID
     */
    @GetMapping("getById/{id}")
    @ApiOperation("sysUser根据Id查询")
    @ResponseBody
    @PreAuthorize("@ex.check('SysUser:getById')")
    public ResponseResult getById(@PathVariable Integer id) {
        return ResponseResult.success(sysUserService.getSysUserById(id));
    }

    /**
     * 更新数据
     * @param sysUser 实体对象
     */
    @PutMapping("update")
    @ApiOperation("sysUser更新")
    @ResponseBody
    @PreAuthorize("@ex.check('SysUser:edit')")
    public ResponseResult update(@Validated @RequestBody SysUser sysUser) {
        sysUserService.updateSysUser(sysUser);
        return ResponseResult.success();
    }

    /**
     * 删除数据
     * @param id ID
     */
    @DeleteMapping("delete/{id}")
    @ApiOperation("sysUser根据Id删除")
    @ResponseBody
    @PreAuthorize("@ex.check('SysUser:del')")
    public ResponseResult delete(@PathVariable Integer id) {
        sysUserService.deleteSysUser(id);
        return ResponseResult.success();
    }

}

五.数据库中添加数据

        根据自己定义的权限字符串与用户进行数据添加

六.测试

        使用pomstman进行测试,注此时可以测试上篇博文中的权限认证异常处理器

 【demo示例代码】

https://download.csdn.net/download/shaogaiyue9745602/88661442icon-default.png?t=N7T8https://download.csdn.net/download/shaogaiyue9745602/88661442

 

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

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

相关文章

黑豹程序员-平方根倒数速算法

程序员约翰卡马克&#xff08;John Carmack&#xff09;在《雷神之锤 III 竞技场》源代码中的平方根倒数速算法&#xff08;Fast Inverse Square Root&#xff0c;Fast InvSqrt()&#xff09;&#xff0c;看过之后大为惊奇。 该算法的意义在于减少了求平方根倒数时浮点运算操作…

第一个程序:HelloWorld——IDEA 使用

IDEA创建是&#xff1a;项目(projefct)、模块(module)、包(package)、类(class) 1.双击打开IDEA&#xff0c;勾选Do not import settings点击OK。 2.选择New Project这里选择创建一个空的项目名为helloworld2023&#xff0c;选择项目创建路径&#xff0c;最后点击创建即可。 3.…

OPNET Modeler帮助文档的打开方式

前面有篇文章修改OPNET帮助文档的默认打开浏览器 & 给Edge浏览器配置IE Tab插件已经提到了打开OPNET Modeler打开帮助文档的方法&#xff0c;有时候打开时会显示如下。 界面中没有什么内容加载出来&#xff01;我是在Google浏览器中打开的&#xff0c;其他的浏览器也是一样…

关于个人Git学习记录及相关

前言 可以看一下猴子都能懂的git入门&#xff0c;图文并茂不枯燥 猴子都能懂的git入门 学习东西还是建议尽可能的去看官方文档 权威且详细 官方文档 强烈建议看一下GitHub漫游指南及开源指北&#xff0c;可以对开源深入了解一下&#xff0c;打开新世界的大门&#xff01; …

使用YT Config Tools工具导出引脚配置清单至Excel文件

使用YT Config Tool工具导出引脚配置清单至Excel文件 文章目录 使用YT Config Tool工具导出引脚配置清单至Excel文件IntroductionOperations在YTC中导入hello_world样例工程在Pinout Configuration标签页中配置引脚保存源码工程导出Excel文件 Conclusion Introduction YT Conf…

如何进行实例管理

目录 修改实例规格 修改网络带宽 网站的访问量每天都比较高&#xff0c;网站明显变慢了&#xff0c;这是怎么回事&#xff1f; 这说明你的网站的并发访问能力已经不足了&#xff0c;并发访问是指同一时间&#xff0c;多个用户请求访问同一个域名下的资源或服务&#xff0c;请…

postgresql|数据库|LVM快照热备冷恢复数据库的思考

一&#xff0c; LVM快照备份的意义 数据库备份一直是数据库运维工作中的重点&#xff0c;一个完备的备份不仅仅是仅有后悔药的功能&#xff0c;还可能有迁移数据库的作用。 那么&#xff0c;数据库备份系统我们需要的&#xff0c;也就是看重的是四个点&#xff0c;甚至更多的…

c语言:计算阶乘的和|练习题

一、题目 输入一个数n&#xff0c;计算1&#xff01;2&#xff01;……n&#xff01;的和 如图&#xff1a; 二、思路分析 设置两个函数 1、一个函数求阶乘 2、一个函数求多个数相加的总和 3、把求阶乘的函数&#xff0c;嵌套在求相加总和的函数里面 三、代码截图【带注释】 四…

关于OpenCV中 CV_Assert() 的使用引起程序中止/崩溃问题

CV_Assert() 的作用是&#xff1a;若括号中的表达式值为 false &#xff0c;则返回一个错误信息&#xff0c;并终止程序执行。 但是 CV_Assert() 与 assert 不同&#xff0c;CV_Assert() 会通过异常抛出&#xff0c;所以如果使用 CV_Assert()&#xff0c;可以通过捕获异常而不是…

【微服务面试题(三十三道)】

文章目录 微服务面试题&#xff08;三十三道&#xff09;概述1.什么是微服务&#xff1f;2.微服务带来了哪些挑战&#xff1f;3.现在有哪些流行的微服务解决方案&#xff1f;这三种方案有什么区别吗&#xff1f; 4.说下微服务有哪些组件&#xff1f; 注册中心5.注册中心是用来干…

odoo17核心概念view5——ir_ui_view.py

这是view系列的第5篇文章&#xff0c;介绍一下view对应的后端文件ir_ui_view.py&#xff0c;它是base模块下的一个文件 位置&#xff1a;odoo\addons\base\models\ir_ui_view.py 该文件一共定义了三个模型 1.1 ir.ui.view.custom 查询数据库这个表是空的&#xff0c;从名字看…

基于ssm学生考勤管理系统的设计与实现论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

每日一练2023.12.25——验证身份【PTA】

题目链接 &#xff1a;验证身份 题目要求&#xff1a; 一个合法的身份证号码由17位地区、日期编号和顺序编号加1位校验码组成。校验码的计算规则如下&#xff1a; 首先对前17位数字加权求和&#xff0c;权重分配为&#xff1a;{7&#xff0c;9&#xff0c;10&#xff0c;5&a…

C语言操作符详情

C语言操作符详情 是否控制求值顺序中 只有“&&”“&#xff1f;&#xff01;”“&#xff0c;”“||”为“是” 其余均为“否”

AI+CRM能做什么?人工智能在客户管理系统中的应用

Hello&#xff0c;大家好&#xff0c;今天小编和大家分享AI人工智能在CRM系统中的应用。运用AI的场景包括&#xff1a;赋能内容生产、客户服务支持、赋能品牌推广、自动化业务流程、数据分析、辅助科学决策、给出最佳客户联系时间。合理运用CRM系统中AI人工智能助手可以让团队工…

RetinaNet:Focal Loss for Dense Object Detection(CVPR2018)

文章目录 Abstract北京发现问题并给出方法成果 IntroductionRelated WorkRobust 评估 Focal LossBalanced Cross EntropyFocal Loss DefinitionClass Imbalance and Model InitializationClass Imbalance and Two-stage Detectors RetinaNet DetectorExperimentsConclusion hh …

BFS解决多源最短路相关leetcode算法题

文章目录 1.01矩阵2.飞地的数量3.地图中的最高点4.地图分析 1.01矩阵 01矩阵 class Solution {int dx[4] {0,0,1,-1};int dy[4] {1,-1,0,0}; public:vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {//正难则反&#xff0c;找0…

基于机器学习算法的数据分析师薪资预测模型优化研究(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【WordPress插件】热门关键词推荐v1.3.0 Pro开心版

介绍&#xff1a; WordPress插件-WBOLT热门关键词推荐插件&#xff08;Smart Keywords Tool&#xff09;是一款集即时关键词推荐、关键词选词工具及文章智能标签功能于一体的WordPress网站SEO优化插件。 智能推荐&#xff1a; 热门关键词推荐引擎-支持360搜索、Bing、谷歌&a…

【快刊录用】Springer旗下2区,1个21天录用,12天见刊!

网络安全类SCIE ☑️期刊简介&#xff1a;IF&#xff1a;4.0-5.0&#xff0c;JCR2区&#xff0c;中科院3区 ☑️检索情况&#xff1a;SCIE 在检&#xff0c;正刊 ☑️征稿领域&#xff1a;提高安全性和隐私性的边缘/云的智能方法的研究&#xff0c;如数字孪生等 ☑️录用案…