木字楠后台管理系统开发(4):SpringSecurity引入并编写登陆接口

news2024/11/20 11:43:09

在这里插入图片描述


🎶 文章简介:木字楠后台管理系统开发(4):SpringSecurity引入并编写登陆接口
💡 创作目的:为了带大家完整的体验木字楠后台管理系统模版的开发流程
☀️ 今日天气:冬天来啦!
📝 每日一言:用柔软的面点,对抗这个坚硬的世界吧


文章目录

  • 🧣1、SpringSecurity的认证流程
    • 🎒 1-1、什么是SpringSecurity
    • 🎈1-2、SpringSecurity的基本工作原理
  • 💒 2、引入SpringSecurity依赖
  • 🍠 3、SpringSecurity配置
    • 🎁 3-1、网络请求拦截配置
      • 🍟 3-1-1、自定义 登录/注销 操作
        • 🌭 3-1-1-1、登录成功处理器
        • 🍔 3-1-1-2、登录失败处理器
        • 🌄 3-1-1-3、注销成功处理器
      • 🌅 3-1-2、请求拦截
      • ❤️ 3-1-3、未登录/未授权处理
        • 🧡 3-1-3-1、未登录处理器
        • 🎟️ 3-1-3-2、未授权处理器
    • 🎢 3-2、静态资源拦截配置
    • 🎪 3-3、登录底层以及密码加密方式配置
      • 🎨 3-3-1、登录逻辑重写
      • 🎊 3-3-2、密码加密方式
  • 🎏 4、获取用户信息重写

🧣1、SpringSecurity的认证流程

在使用SpringSecurity之前我们需要了解:

  • 什么是SpringSecurity?
  • SpringSecurity的基本工作原理是什么?

🎒 1-1、什么是SpringSecurity

  1. springsecurity是一个功能强大且高度可定制的身份验证和访问控制框架。
  2. springsecurity是一个专注于为Java应用程序提供身份验证授权的框架。
  3. 与所有Spring项目一样,Spring安全性的真正威力在于它可以很容易地扩展以满足定制需求。

Spring Security可以在 Controller层、 Service层、Mapper层等以加注解的方式来保护应用程序的安全。 Spring Security提供了细粒度的权限控制,可以精细到每一个API接口、每一个业务的方法,或者每一个操作数据库的Mapper层的方法。 Spring Security提供的是应用程序层的安全解决方案,一个系统的安全还需要考虑传输层和系统层的安全,例如采用Htps协议、服务器部署防火墙等。

🎈1-2、SpringSecurity的基本工作原理

Spring Security对Web资源的保护是靠过滤器链(FilterChain)实现的。
当我们发送网络请求至后端服务时,该网络请求会经过一系列的过滤器的过滤,直至通过所有过滤器才可以访问到服务器API。
而我们也可以对这一些列过滤器进行重写,按照我们自己的逻辑来进行过滤,由于SpringSecurity已经帮我们定制的大部分的过滤器,我们仅需要修改少部分过滤器即可完成权限管理。

在这里插入图片描述

💒 2、引入SpringSecurity依赖

本项目中的权限管理使用的是SpringSecurity + Jwt来进行实现权限控制。

    <!--==============  项目版本号规定 ===============-->
    <properties>
        <!--==============  工具依赖  ==================-->
        <userAgent.version>1.21</userAgent.version>
    </properties>
    
    <dependencies>
        <!--==============  SpringBoot相关依赖 ===============-->
        <!-- security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!--================== 工具依赖 =======================-->
        <!-- Token生成与解析-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>
    </dependencies>

引入依赖成功之后,我们重新启动项目。
我们会发现控制台中多了一串 字符串

在这里插入图片描述

而当我们访问接口的时候会发现界面变为了一个登陆界面,而非我们的接口返回值。
这是SpringSecurity的登陆拦截,当我们匿名访问接口的时候就会被拦截提示需要登陆,SpringSecurity默认的登陆Usernameuser ,而Password则是控制台内打印的 字符串

在这里插入图片描述

我们登陆之后发现接口可以正常访问
但是我们项目中肯定不会使用SpringSecurity的登陆方式,所以我们需要对SpringSecurity进行配置,来以api接口的方式进行登录。

在这里插入图片描述

🍠 3、SpringSecurity配置

我们在config包内新建一个配置类,继承WebSecurityConfigurerAdapter 重写其中的三个config方法

  • @Slf4j用于日志记录
  • @RequiredArgsConstructor 用于构造器注入bean(本项目中不使用@Autowired注入bean)
  • WebSecurityConfigurerAdapter SpringSecurity安全配置类 (Spring Security 5.7.0-M2已经弃用)

在这里插入图片描述

@Slf4j
@Configuration
@RequiredArgsConstructor
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }
}

🎁 3-1、网络请求拦截配置

我们对 protected void configure(HttpSecurity http) 进行中的内容进行配置,此方法主要是对于网络请求进行拦截过滤处理。

在这里插入图片描述

🍟 3-1-1、自定义 登录/注销 操作

springsecurity默认的登录界面并不是我们所需要的,我们需要的是一个登录接口。
这里我们配置/user/login为登录接口的接口,/user/logout为注销登录的接口(虽然我们可以修改SpringSecurity的登录方式,修改登录接口。但是由于SpringSecurity底层对登录接口进行了实现,所所以我们只需要去重写其底层实现即可完成自定义登录)

在这里插入图片描述

🌭 3-1-1-1、登录成功处理器

我们查看登录成功处理器发现需要一个 AuthenticationSuccessHandler 类型的接口,我们可以对接口进行实现,自定义一个登录成功处理器。

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

@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(ResponseResult.success(HttpCodeEnum.USER_LOGIN_SUCCESS)));
    }
}

🍔 3-1-1-2、登录失败处理器

我们查看登录失败处理器发现需要一个 AuthenticationFailureHandler 类型的接口,我们可以对接口进行实现,自定义一个登录失败处理器。但是引起登录失败的原因有很多,所以这里我们需要根据异常进行返回提示

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

@Slf4j
@Component
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        log.info("登录失败 =>" + exception.getMessage());
        response.getWriter().write(JSON.toJSONString(ResponseResult.fail(exception.getMessage())));
    }
}

🌄 3-1-1-3、注销成功处理器

我们查看注销成功处理器发现需要一个 LogoutSuccessHandler 类型的接口,我们可以对接口进行实现,自定义一个注销成功处理器,与登录成功的处理器功能相同。

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

@Component
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(ResponseResult.success(HttpCodeEnum.USER_LOGOUT_SUCCESS)));
    }
}

在配置类中进行引入

在这里插入图片描述

@Slf4j
@Configuration
@RequiredArgsConstructor
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final AuthenticationSuccessHandlerImpl authenticationSuccessHandler;
    private final AuthenticationFailureHandlerImpl authenticationFailureHandler;
    private final LogoutSuccessHandlerImpl logoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //region 自定义 登录/注销 处理器
        http.formLogin()
                .loginProcessingUrl("/user/login")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .and()
                .logout()
                .logoutUrl("/user/logout")
                .logoutSuccessHandler(logoutSuccessHandler);
        //endregion
    }
}

🌅 3-1-2、请求拦截

本项目中默认会拦截所有请求,但是仍有少部分请求时允许匿名访问的,所以这里我们通过使用自定义注解标识的方法来放行允许匿名访问的接口。

  • 匿名访问实现原理:我们在项目启动时获取标记有 @AnonymousAccess 注解的所有方法的请求路径添加入集合中,最终进行统一放行即可。

在这里插入图片描述

新建一个自定义注解,被本注解修饰的方法将会被允许匿名访问。(该注解仅仅时一个标记作用)

在这里插入图片描述

获取被 @AnonymousAccess 注解标记的 RequestMapping的参数,并且统计为一个Set集合。

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

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {

}

@Slf4j
@Configuration
@RequiredArgsConstructor
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final AuthenticationSuccessHandlerImpl authenticationSuccessHandler;
    private final AuthenticationFailureHandlerImpl authenticationFailureHandler;
    private final LogoutSuccessHandlerImpl logoutSuccessHandler;
    private final ApplicationContext applicationContext;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //region 自定义 登录/注销 处理器
        http.formLogin()
                .loginProcessingUrl("/user/login")
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .and()
                .logout()
                .logoutUrl("/user/logout")
                .logoutSuccessHandler(logoutSuccessHandler);
        //endregion

        //region 请求拦截
        http.authorizeRequests()
                .antMatchers(listAnonymous().toArray(new String[0])).permitAll()
                .anyRequest().authenticated();
        //endregion
    }

    /**
     * 查找可以匿名访问的接口
     *
     * @return 匿名访问接口集合
     */
    private Set<String> listAnonymous() {
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        Set<String> anonymousUrls = new HashSet<>();
        anonymousUrls.add("/user/login");
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            if (anonymousAccess != null) {
                assert infoEntry.getKey().getPatternsCondition() != null;
                anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
            }
        }
        log.info("可以匿名访问的url:{}", anonymousUrls);
        return anonymousUrls;
    }
}

❤️ 3-1-3、未登录/未授权处理

上面我们对于网络请求是否允许匿名访问进行了处理,接下来我们对未登录/未授权进行处理。
未登录 <=> 匿名 未授权 <=> 无权限

在这里插入图片描述

🧡 3-1-3-1、未登录处理器

我们查看未登录处理器发现需要一个 AuthenticationSuccessHandler 类型的接口,我们可以对接口进行实现,自定义一个未登录处理器。(注意:未登录处理器 与 登录失败处理器 不冲突)

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

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(ResponseResult.fail(HttpCodeEnum.USER_NOT_LOGIN)));
    }
}

🎟️ 3-1-3-2、未授权处理器

我们查看未授权处理器发现需要一个 AuthenticationSuccessHandler 类型的接口,我们可以对接口进行实现,自定义一个未授权处理器。

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

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(ResponseResult.fail(HttpCodeEnum.PERMISSION_NOT_DEFINED)));
    }
}

在这里插入图片描述

🎢 3-2、静态资源拦截配置

我们对 protected void configure(WebSecurity web) 进行中的内容进行配置,此方法主要是对于静态资源拦截过滤处理。我们只需要放行需要放行的静态资源即可。

在这里插入图片描述

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers(
                "/",
                "/js/**",
                "/css/**",
                "/img/**",
                "/fonts/**",
                "/index.html",
                "/favicon.ico",
                "/doc.html",
                "/swagger-ui.html",
                "/webjars/**",
                "/swagger-resources/**",
                "/v3/**",
                "/store/**"
        );
    }

🎪 3-3、登录底层以及密码加密方式配置

我们对 protected void configure(AuthenticationManagerBuilder auth) 进行中的内容进行配置,此方法主要是对于登录实现类的配置以及密码加密方式的配置。

在这里插入图片描述

🎨 3-3-1、登录逻辑重写

SpringSecurity默认的登录逻辑是由接口userDetailsService的实现类执行的,这里我们需要对具体的登录进行重写,所以我们新建一个具体的实现类。(先进行配置,不做具体实现

在这里插入图片描述

🎊 3-3-2、密码加密方式

我们这里选用无法被反向破解的密码加密方式BCrypt加密方式。我们直接将加密方式使用@Bean进行注入。

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

🎏 4、获取用户信息重写

  • 我们需要使用到UserAgent依赖来对请求进行解析,获取请求来源的ip地址、ip来源等信息…

  • loadUserByUsername()方法的实际作用是 根据用户名 来获取用户信息 如果获取到则返回用户实体类,若不存在则直接抛出异常。

  • 若我们在loadUserByUsername()方法中获取到实体类则会对方法进行层层封装,最终的密码校验在 AbstractUserDetailsAuthenticationProvider 中执行。若校验成功,则会去执行登录成功处理器中的内容。若校验失败,则会去执行登录失败处理器中的内容。

在这里插入图片描述

    <!--==============  项目版本号规定 ===============-->
    <properties>
        <!--==============  工具依赖  ==================-->
        <userAgent.version>1.21</userAgent.version>
    </properties>

    <dependencies>
        <!-- 解析客户端操作系统、浏览器等 -->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>${userAgent.version}</version>
        </dependency>
    </dependencies>

具体的登录逻辑重写如下:

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final HttpServletRequest request;
    private final UserAuthService userAuthService;
    private final UserInfoService userInfoService;
    private final RoleService roleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserAuth userAuth = getUserAuthInfo(username);
        UserInfo userInfo = getUserBasicInfo(userAuth.getUserInfoId());
        return convertToUser(request, userAuth, userInfo);
    }


    /**
     * 用户数据转换
     *
     * @param request  request请求
     * @param userAuth 用户权限信息
     * @param userInfo 用户基础信息
     * @return {@link User} 封装后的用户信息
     */
    private User convertToUser(HttpServletRequest request, UserAuth userAuth, UserInfo userInfo) {

        //region 获取请求用户ip地址相关信息
        String ipAddress = UserAgentUtil.getIpAddress(request);
        String ipSource = UserAgentUtil.getIpSource(ipAddress);
        UserAgent userAgent = UserAgentUtil.getUserAgent(request);
        String browser = userAgent.getBrowser().getName();
        String os = userAgent.getOperatingSystem().getName();
        //endregion

        //region 查询用户角色信息并查询对应权限列表
        Role role = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getId, userAuth.getUserRoleId()));
        Optional.ofNullable(role).orElseThrow(() -> new BaseException(HttpCodeEnum.USER_IDENTITY_LOAD_FAIL));
        Set<String> permissionList = roleService.listRolePermission(role);
        //endregion

        return new User() {{
            setId(userAuth.getId());
            setUsername(userAuth.getUsername());
            setPassword(userAuth.getPassword());
            setNickname(userInfo.getNickname());
            setRole(role);
            setLoginType(userAuth.getLoginType());
            setAvatar(userInfo.getAvatar());
            setGender(userInfo.getGender());
            setPersonIntro(userInfo.getPersonIntro());
            setIpAddress(ipAddress);
            setIpSource(ipSource);
            setGmtCreate(userInfo.getGmtCreate());
            setGmtUpdate(userInfo.getGmtUpdate());
            setLastLoginTime(userInfo.getLastLoginTime());
            setEmailLogin(userAuth.getEmailLogin());
            setIsDisabled(userAuth.getIsDisabled());
            setBrowser(browser);
            setOs(os);
            setPermissionList(permissionList);
        }};
    }


    /**
     * 根据用户名查询用户权限信息
     *
     * @param username 用户名
     * @return {@link  UserAuth} 用户权限信息
     */
    private UserAuth getUserAuthInfo(String username) {
        // 用户名非空校验
        if (StringUtils.isEmpty(username)) {
            throw new BaseException(HttpCodeEnum.USERNAME_OR_PASSWORD_ERROR);
        }

        //region 用户权限信息查询并校验
        UserAuth userAuth = userAuthService.getOne(new LambdaQueryWrapper<UserAuth>()
                .eq(UserAuth::getUsername, username)
                .eq(UserAuth::getIsDeleted, 0));
        Optional.ofNullable(userAuth).orElseThrow(() -> new BaseException(HttpCodeEnum.USERNAME_OR_PASSWORD_ERROR));
        if (userAuth.getIsDisabled()) {
            throw new BaseException(HttpCodeEnum.ACCOUNT_IS_DISABLED);
        }
        //endregion

        return userAuth;
    }


    /**
     * 根据用户信息Id查询用户权限信息
     *
     * @param userInfoId 用户信息Id
     * @return {@link  UserInfo} 用户信息
     */
    private UserInfo getUserBasicInfo(Long userInfoId) {
        //region 用户基础信息查询并校验
        UserInfo userInfo = userInfoService.getOne(new LambdaQueryWrapper<UserInfo>()
                .eq(UserInfo::getId, userInfoId));
        Optional.ofNullable(userInfo).orElseThrow(() -> new BaseException(HttpCodeEnum.USER_INFO_LOAD_FAIL));
        //endregion

        return userInfo;
    }
}

RoleServiceImpl

    @Override
    public Set<String> listRolePermission(Role role) {
        Set<String> permissionList = new HashSet<>();

        //region 通过校验则为超级管理员
        if ("super_admin".equals(role.getRoleLabel())) {
            permissionList.add("**:**:**");
            return permissionList;
        }
        //endregion

        return roleMapper.listPermissionByRoleId(role.getId());
    }

RoleMapper.xml

    <select id="listPermissionByRoleId" resultType="java.lang.String">
        select m.perm
        from role
                 left join role_menu rm on role.id = rm.role_id
                 left join menu m on rm.menu_id = m.id
        where role_id = #{roleId}
    </select>

在这里插入图片描述

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

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

相关文章

在ubuntu上部署gitlab详细步骤

一、Ubuntu安装gitlab步骤&#xff1a; 安装依赖 通过快捷键ctrlaltT打开命令行窗口&#xff0c;然后运行下面两行命令 sudo apt update sudo apt-get upgrade sudo apt-get install curl openssh-server ca-certificates postfix 如果这一步遇到下面提示界面&#xff0c…

BUUCTF Web2

[HCTF 2018]admin flask session的伪造 改密码的页面源码有提示&#xff0c;得到秘钥ckj123 自己的session .eJw9kEGLwjAUhP_KkrOHJm09CB5cbKULeaHwanm5iKu1adK4UBVpxP--XRc8zGmGj5l5sN1paC6GLa7DrZmxXXdkiwf7-GYLptCl2uoOcHWXmDu1kYnGLIFNdQdsBYmtkbb3YI89YDXKUHKNTkCg8S9PliJ…

Kotlin 开发Android app(二十二):Retrofit和简单的mvp框架

到这一节&#xff0c;基本上把大部分kotlin和android的开发都已经介绍完成了&#xff0c;通过了前面和这一章的框架结构&#xff0c;基本上能解决开发中的很多问题&#xff0c;并且能够知道android的主要的技术&#xff0c;并进行独立开发了。对于传统的开发的话&#xff0c;还…

一些可以显著提高大型 Java 项目启动速度的尝试

我们线上的业务 jar 包基本上普遍比较庞大&#xff0c;动不动一个 jar 包上百 M&#xff0c;启动时间在分钟级&#xff0c;拖慢了我们在故障时快速扩容的响应。于是做了一些分析&#xff0c;看看 Java 程序启动慢到底慢在哪里&#xff0c;如何去优化&#xff0c;目前的效果是大…

SpringSecurity安全框架

目录 一、Spring Security介绍 1、框架介绍 2、认证与授权实现思路 二、整合Spring Security 1、在common下创建spring_security模块 2、在spring_security引入相关依赖 3.代码结构说明&#xff1a; 4、创建spring security核心配置类 5、创建认证授权相关的工具类 &a…

Roson的Qt之旅 #139 Qt读写Excel

1.使用QAxObject读写Excel QAxObject类提供了一个包裹COM对象的QObject。 QAxObject可以被实例化为一个空的对象&#xff0c;用它应该包裹的COM对象的名字&#xff0c;或者用一个指向代表现有COM对象的IUnknown的指针。如果COM对象实现了IDispatch接口&#xff0c;该对象的属性…

【Python 身份证JSON数据读取】——身份证前六位地区码对照表文件(最全版-JSON文件)

点个赞留个关注吧&#xff01;&#xff01; 1、生成身份证前六位地区码对照表JSON文件 2、python 读取JSON文件 提取码【1234】 json文件下载 废话不多说&#xff0c;先上效果图 一、生成身份证json数据文件 先去百度搜索地区身份证号码前6位查询 ,然后进入网站控制台界面&…

经常会采坑的javascript原型应试题

一&#xff0e; 前言 原型和原型链在面试中历来备受重视&#xff0c;经常被提及。说难可能也不太难&#xff0c;但要真正完全理解&#xff0c;吃透它&#xff0c;还是要多下功夫的。 下面为大家简单阐述我对原型和原型链的理解&#xff0c;若是觉得有说的不对的地方&#xff…

必备技能,MySQL 查找并删除重复行

本文讲述如何查找数据库里重复的行。这是初学者十分普遍遇到的问题。方法也很简单。这个问题还可以有其他演变&#xff0c;例如&#xff0c;如何查找“两字段重复的行”&#xff08;#mysql IRC 频道问到的问题&#xff09; 如何查找重复行 第一步是定义什么样的行才是重复行。…

碳酸钾碱性溶液除钙镁软化树脂

碳酸钾是重要的基本无机化工、医药、轻工原料之一&#xff0c;主要用于光学玻璃、电焊条、电子管、电视显像管、灯泡、印染、染料、油墨、照相药品、泡花碱、聚酯、炸药、电镀、制革、陶瓷、建材、水晶、钾肥皂及药物的生产。用作气体吸附剂&#xff0c;干粉灭火剂&#xff0c;…

Spring Boot 整合 Groovy 脚本,实现动态编程

Groovy简介 Groovy 是增强 Java 平台的唯一的脚本语言。它提供了类似于 Java 的语法&#xff0c;内置映射&#xff08;Map&#xff09;、列表&#xff08;List&#xff09;、方法、类、闭包&#xff08;closure&#xff09;以及生成器。脚本语言不会替代系统编程语言&#xff…

「Redis数据结构」哈希对象(Hash)

「Redis数据结构」哈希对象&#xff08;Hash&#xff09; 文章目录「Redis数据结构」哈希对象&#xff08;Hash&#xff09;一、概述二、编码ZipListHashTable三、编码转换一、概述 Redis中hash对象是一个string类型的field和value的映射表&#xff0c;hash特别适合用于存储对…

RabbitMQ:消息模型

RabbitMQ 提供了 6 种消息模型&#xff0c;分别为&#xff1a;单生产单消费模型&#xff08;Hello World&#xff09;、消息分发模型&#xff08;Work queues&#xff09;、Fanout 消息订阅模式&#xff08;Publish/Subscribe&#xff09;、Direct 路由模式&#xff08;Routing…

基于JSP的手工艺品在线网站

摘 要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括手工艺品在线网站的网络应用&#xff0c;在外国手工艺品已经是很普遍的方式&#xff0c;不过国内的手工艺品可能还处于起步阶段。手工艺品在线网站具有在线下单功能。手…

详解vue中watch的用法

前言 说到 vue 中的 watch 方法&#xff0c;大家可能首先想到&#xff0c;它是用来监听数据的变化&#xff0c;一旦数据发生变化可以执行一些其他的操作。但是 watch 的操作可不止如此&#xff0c;本章就带大家一起深剖细析 vue 中的 watch 方法。 watch&#xff1f; 因为 vue…

DocuWare平台——用于文档管理和工作流程自动化的内容服务平台详细介绍(上)

DocuWare平台——用于文档管理和工作流程自动化的内容服务平台 成功实现办公自动化所需的一切 DocuWare 是一个先进的平台&#xff0c;可让您集中、快速、有效地管理、处理和利用业务信息。 我们的文档管理和工作流程解决方案的各项功能可以集成到任何 IT 系统中&#xff0c;…

源码解析:从 kubelet、容器运行时看 CNI 的使用

这是 Kubernetes 网络学习的第三篇笔记。 深入探索 Kubernetes 网络模型和网络通信认识一下容器网络接口 CNI&#xff08;本篇&#xff09;源码分析&#xff1a;从 kubelet、容器运行时看 CNI 的使用从 Flannel 学习 Kubernetes VXLAN 网络Cilium CNI 与 eBPF... 在上一篇中&…

web前端期末大作业 基于HTML+CSS+JavaScript程序员个人博客模板(web学生作业源码)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

翻译: ChatGPT 的激发敬畏、恐惧、特技和试图绕过其护栏的尝试

来自 OpenAI 的新聊天机器人正在激发敬畏、恐惧、特技和试图绕过其护栏的尝试。 以下是 DALL-E 2 在给出提示时生成的内容&#xff0c;“采用 AI 聊天机器人形式的分布式语言超级大脑。” “A distributed linguistic superbrain that takes the form of an A.I. chatbot.” 信…

WPS文件转Excel文件怎么转?建议看看这些方法

小伙伴们平时在接收文件的时候&#xff0c;有没有发现有些文件是以WPS格式进行保存的。这种格式的文件&#xff0c;如果没有使用相关的软件是没办法直接打开的。这种时候&#xff0c;其实我们可以将WPS转成其它office格式就可以打开它&#xff0c;进行编辑了。那你们知道WPS转E…