Spring Security自定义认证授权过滤器

news2024/11/17 3:53:40

自定义认证授权过滤器

  • 自定义认证授权过滤器
  • 1、SpringSecurity内置认证流程
  • 2、自定义Security认证过滤器
    • 2.1 自定义认证过滤器
    • 2.2 定义获取用户详情服务bean
    • 2.3 定义SecurityConfig类
    • 2.4 自定义认证流程测试
  • 3、 基于JWT实现无状态认证
    • 3.1 认证成功响应JWT实现
    • 3.2 SpringSecurity基于Jwt实现认证小结
  • 4、自定义Security授权过滤
    • 4.1 授权流程说明
    • 4.2 授权实现流程
    • 4.3 配置自定义授权过滤器
  • 5、自定义权限拒绝处理
    • 5.1 自定义认证用户权限拒绝处理器
    • 5.2 自定义匿名用户拒绝处理器
  • 6、自定义认证授权整体流程小结

自定义认证授权过滤器

如果对 Spring Security 了解不够请看这篇。 https://blog.csdn.net/m0_62943934/article/details/134815311

需求:

SpringSecurity 内置的认证只能接收 form 表单提交的账户、密码信息。并且内部是使用 HttpServletRequest 的 getParameter 方法获取账户信息的,同时如果需要对需求进行扩展,内置的授权方式就不够看了。

@Nullable
protected String obtainUsername(HttpServletRequest request) {
    return request.getParameter(this.usernameParameter);
}

但是,我们需要清楚现在的大部分项目都是基于前后端分离的架构来进行实现的。而这种情况下前段基本都会使用类似于axios这种发送异步请求,而这时账户信息就不能使用 getParameter 方法获取了。需要使用获取流数据的方式,此时就需要我们自己定义认证授权过滤器

1、SpringSecurity内置认证流程

通过研究SpringSecurity内置基于form表单认证的UsernamePasswordAuthenticationFilter过滤器,我们可以仿照自定义认证过滤器:

在这里插入图片描述

内置认证过滤器的核心流程:

在这里插入图片描述

核心流程梳理如下:

  • 认证过滤器(UsernamePasswordAuthentionFilter)接收 form 表单提交的账户、密码信息,并封装成UsernamePasswordAuthenticationToken认证凭对象;
  • 认证过滤器调用认证管理器AuthenticationManager进行认证处理;
  • 认证管理器通过调用用户详情服务获取用户详情UserDetails;
  • 认证管理器通过密码匹配器PasswordEncoder进行匹配,如果密码一致,则将用户相关的权限信息一并封装到Authentication认证对象中;
  • 认证过滤器将Authentication认证过滤器放到认证上下文,方便请求从上下文获取认证信息;

2、自定义Security认证过滤器

SpringSecurity 内置的认证过滤器是基于post请求且为form表单的方式获取认证数据的,那如何接收前端Json异步提交的数据据实现认证操作呢?

显然,我们可仿照UsernamePasswordAuthentionFilter类自定义一个过滤器并实现认证过滤逻辑;

2.1 自定义认证过滤器

UsernamePasswordAuthentionFilter过滤器继承了模板认证过滤器AbstractAuthenticationProcessingFilter抽象类,我们也可仿照实现:

package com.shen.security.security_test.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author shenyang
 * @version 1.0
 * @Date 2024/3/11 16:29
 * 认证过滤器
 *
 */
public class MyUserNamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public static final String USERNAME_KEY = "username";
    public static final String PASSWORD_KEY = "password";
    /**
     * 自定义构造器,传入登录认证的url地址
     *
     * @param defaultFilterProcessesUrl
     */
    public MyUserNamePasswordAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    /**
     * 尝试去认证的方法
     *
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        //请求必须是post请求 并且请求类型必须是json
        if (!request.getMethod().equalsIgnoreCase("POST")//忽略大小写
                &
                ! MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //获取post请求ajax的数据流 并且将其数据流中的数据反序列化成Map
        HashMap<String,String> userInfo = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
        String username = userInfo.get(USERNAME_KEY);
        username = (username != null) ? username : "";
        username = username.trim();
        String password = userInfo.get(PASSWORD_KEY);
        password = password != null ? password : "";
        //组装认证的票据对象 将用户名和密码信息封装到认证票据对象下
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        // Allow subclasses to set the "details" property
		//setDetails(request, authRequest);
        //调用认证管理器认证指定的票据对象   交给认证管理器认证票据对象
        return this.getAuthenticationManager().authenticate(authRequest);
    }


    public MyUserNamePasswordAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
        super(requiresAuthenticationRequestMatcher);
    }

    /**
     * 认证成功后执行的方法
     * @param request
     * @param response
     * @param chain 过滤器链
     * @param authResult 认证对象信息
     * @throws IOException
     * @throws ServletException
     */

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        User principal = (User) authResult.getPrincipal();
        String username = principal.getUsername();
        Collection<GrantedAuthority> authorities = principal.getAuthorities();
        String password = principal.getPassword();
        //响应数据格式json
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //编码格式
        response.setCharacterEncoding("UTF-8");
        Map<String,String> infos = new HashMap<>();
        infos.put("msg","登录成功");
        infos.put("data","");
        infos.put("code","1");
        //响应
        response.getWriter().write(new ObjectMapper().writeValueAsString(infos));
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        //响应数据格式json
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //编码格式
        request.setCharacterEncoding("UTF-8");
        Map<String,String> infos = new HashMap<>();
        infos.put("msg","登录失败");
        infos.put("data","");
        infos.put("code","1");
        //响应
        response.getWriter().write(new ObjectMapper().writeValueAsString(infos));
    }
}

2.2 定义获取用户详情服务bean

package com.shen.service;

import com.shen.entity.TbUser;
import com.shen.mapper.TbUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
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 java.util.List;

/**
 * @author by shen
 * @Date 2024/1/23
 * @Description
 */
@Service("userDetailsService")
public class MyUserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private TbUserMapper tbUserMapper;
    /**
     * 使用security当用户认证时,会自动将用户的名称注入到该方法中
     * 然后我们可以自己写逻辑取加载用户的信息,然后组装成UserDetails认证对象
     * @param userName
     * @return 用户的基础信息,包含密码和权限集合,security底层会自动比对前端输入的明文密码
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //1.根据用户名称获取用户的账户信息
        TbUser dbUser=tbUserMapper.findUserInfoByName(userName);
        //判断该用户是否存在
        if (dbUser==null) {
            throw new UsernameNotFoundException("用户名输入错误!");
        }
        //2.组装UserDetails对象
        //获取当前用户对应的权限集合(自动将以逗号间隔的权限字符串封装到权限集合中)
        List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(dbUser.getRoles());
        /*
            参数1:账户
            参数2:密码
            参数3:权限集合
         */
        User user = new User(dbUser.getUsername(), dbUser.getPassword(), list);
        return user;
    }
}

2.3 定义SecurityConfig类

配置默认认证过滤器,保证自定义的认证过滤器要在默认的认证过滤器之前;

package com.shen.security.config;

import com.shen.security.filter.MyUserNamePasswordAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity//开启web安全设置生效
//开启SpringSecurity相关注解支持
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 配置授权策略
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();//禁用跨站请求伪造
  		http.authorizeRequests()//对资源进行认证处理
    	.antMatchers("/authentication/form").permitAll()//登录路径无需拦截
    	.anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问
     	http
          		//坑-过滤器要添加在默认过滤器之前,否则,登录失效
                .addFilterBefore(myUserNamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public MyUserNamePasswordAuthenticationFilter myUserNamePasswordAuthenticationFilter() throws Exception {
      	构造认证过滤器对象,并设置默认登录路径
        MyUserNamePasswordAuthenticationFilter myUserNamePasswordAuthenticationFilter =
                new MyUserNamePasswordAuthenticationFilter("/authentication/form");
        myUserNamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
        return myUserNamePasswordAuthenticationFilter;
    }
}

2.4 自定义认证流程测试

在这里插入图片描述

3、 基于JWT实现无状态认证

JWT是无状态的,所以在服务器端无需存储和维护认证信息,这样会大大减轻服务器的压力,所以我们可在自定义的认证过滤器认证成功后通过successfulAuthentication方法向前端颁发token认证字符串;

3.1 认证成功响应JWT实现

测试工程导入Jwt工具类,集成流程如下:

    /**
     * 认证工程处理方法
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        //认证主体信息,此时以填充用户权限信息
        UserDetails principal = (UserDetails) authResult.getPrincipal();
        //组装响应前端的信息
        String username = principal.getUsername();
        Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();
        //构建JwtToken 加入权限信息是为了将来访问时,jwt解析获取当前用户对应的权限,做授权的过滤
      	//权限字符串格式:[P5, ROLE_ADMIN]
        String token = JwtTokenUtil.createToken(username, authorities.toString());
        HashMap<String, String> info = new HashMap<>();
        info.put("name",username);
        info.put("token",token);
        //设置响应格式
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        //内容编码格式
        response.setCharacterEncoding("utf-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(info));
    }

测试获取认证Token

3.2 SpringSecurity基于Jwt实现认证小结

在这里插入图片描述

4、自定义Security授权过滤

上一小结认证成功后向请求方响应了token信息,那么请求方访问其它系统资源时,就需要带着这个token到后台,后台需要一个授权过滤器获取token信息,并解析用户权限信息,将信息封装到UsernamePasswordAuthentionToken对象存入安全上下文,方便请求时安全过滤处理;

4.1 授权流程说明

在这里插入图片描述

4.2 授权实现流程

定义授权过滤器:

package com.shen.security.config;

import com.google.gson.Gson;
import com.shen.security.utils.JwtTokenUtil;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;

/**
 * @author by shen
 * @Date 2024/1/23
 * @Description 权限认证filter
 */
public class AuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //1.获取http请求头中携带的jwt票据字符串(注意:如果用户尚未认证,则jwt票据字符串不存在)
        String jwtToken = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
        //2.判断请求中的票据是否存在
        if (StringUtils.isBlank(jwtToken)) {
            //如果票据为空,可能用户准备取认证,所以不做拦截,但是此时UsernamePasswordAuthenticationToken对象未生成,那么即使放行本次请求
            //后续的过滤器链中也会校验认证票据对象
            filterChain.doFilter(request,response);
            return;
        }
        //3.校验票据
        Claims claims = JwtTokenUtil.checkJWT(jwtToken);
        //票据失效
        if (claims==null) {
            //票据失效则提示前端票据已经失效,需要重新认证
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setContentType("utf-8");
            response.getWriter().write("jwt token failure!!");
            return;
        }
        //4.从合法的票据中获取用户名和权限信息
        //用户名
        String username = JwtTokenUtil.getUsername(jwtToken);
        //权限信息 [P5, ROLE_ADMIN]
        String roles = JwtTokenUtil.getUserRole(jwtToken);
        //将数组格式的字符串转化成权限对象集合
        String comStr = StringUtils.strip(roles, "[]");
        List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(comStr);
        //5.组装认证成功的票据对象(认证成功时,密码位置null)
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, authorityList);
        //6.将认证成功的票据对象保存到安全上下文中,方便后续的过滤器直接获取权限信息
        SecurityContextHolder.getContext().setAuthentication(token);
        //7.发行过滤器
        filterChain.doFilter(request,response);
    }
}

4.3 配置自定义授权过滤器

/**
  * 给访问的资源配置权限过滤
  * @param http
  * @throws Exception
  */
@Override
protected void configure(HttpSecurity http) throws Exception {
  http.csrf().disable();//禁用跨站请求伪造
  http.authorizeRequests()//对资源进行认证处理
    .antMatchers("/myLogin").permitAll()//登录路径无需拦截
    .anyRequest().authenticated();  //除了上述资源外,其它资源,只有 认证通过后,才能有权访问
  //添加自定义的认证过滤器:UsernamePasswordAuthenticationFilter是默认的登录认证过滤器,而我们重写了该过滤器,所以访问时,应该先走我们
  //自定义的过滤器
  http.addFilterBefore(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  //配置授权过滤器,过滤一切资源
  http.addFilterBefore( authenticationFilter(),MyUsernamePasswordAuthenticationFilter.class);
}

/**
  * 自定义授权过滤器
  * 过滤一切被访问的资源
  * @return
  */
@Bean
public AuthenticationFilter authenticationFilter(){
  AuthenticationFilter filter = new AuthenticationFilter();
  return filter;
}

访问测试:

​ 略

5、自定义权限拒绝处理

上一小结当用户未认证访问资源或者认证成功后访问没有权限的资源时,响应给前端的信息不友好,我们可通过自定义权限访问拒绝的处理器来友好提醒用户;

5.1 自定义认证用户权限拒绝处理器

通过实现AccessDeniedHandler接口实现:

    /**
     * 自定义登录认证策略配置授权策略 -1
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
		//......
        http.exceptionHandling()
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest request,
                                       HttpServletResponse response,
                                       AccessDeniedException e) throws IOException, ServletException {
                        //认证用户访问资源时权限拒绝处理策略
                        response.getWriter().write("no permission......reject....");
                    }
                });
    }

效果:

在这里插入图片描述

5.2 自定义匿名用户拒绝处理器

同样通过实现AuthenticationEntryPoint接口实现:

    /**
     * 自定义登录认证策略配置授权策略 -1
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
		//......
				http.exceptionHandling()
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest request,
                                       HttpServletResponse response,
                                       AccessDeniedException e) throws IOException, ServletException {
                        //认证用户访问资源时权限拒绝处理策略
                        response.getWriter().write("no permission......reject....");
                    }
                })
          		.authenticationEntryPoint(new AuthenticationEntryPoint(){
                    @Override
    				public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        				 response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        				 response.setCharacterEncoding("UTF-8");
        				 response.getWriter().write("匿名用户无权访问");
          });
    }
}

6、自定义认证授权整体流程小结

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

Kafka MQ 生产者和消费者

Kafka MQ 生产者和消费者 Kafka 的客户端就是 Kafka 系统的用户&#xff0c;它们被分为两种基本类型:生产者和消费者。除 此之外&#xff0c;还有其他高级客户端 API——用于数据集成的 Kafka Connect API 和用于流式处理 的 Kafka Streams。这些高级客户端 API 使用生产者和消…

如何保证消息的可靠传输

数据的丢失问题&#xff0c;可能出现在生产者、MQ、消费者中 生产者丢失&#xff1a; 生产者将数据发送到 RabbitMQ 的时候&#xff0c;可能数据就在半路给搞丢了&#xff0c;因为网络问题啥的&#xff0c;都有可能。此时可以选择用 RabbitMQ 提供的事务功能&#xff0c;就是生…

脚手架cli快速创建Vue2/Vue3项目

前言&#xff1a; 本文的nodejs版本是14.21.3 第一步 进入cmd窗口 1、全局安装webpack npm install webpack-g&#xff0c; npm install webpack-g 第二步 2、全局安装vue脚手架 npm install -g vue/cli 第三步 3、初始化vue项目 &#xff08;vue脚手架使用webpack模…

资料下载-嵌入式 Linux 入门

学习的第一步是去下载资料。 1. 有哪些资料 所有资料分 4 类&#xff1a; ① 开发板配套资料(原理图、虚拟机的映像文件、烧写工具等)&#xff0c;放在百度网盘 ② 录制视频过程中编写的文档、源码、图片&#xff0c;放在 GIT 仓库 ③ u-boot、linux 内核、buildroot 等比较大…

STM32CubeMX学习笔记18——FSMC(TFT-LCD屏触摸)

1.触摸屏简介 目前最常用的触摸屏有两种&#xff1a;电阻式触摸屏和电容式触摸屏 1.1 电阻式触摸屏 电阻式的触摸屏结构如下图示&#xff0c;它主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成&#xff0c;这些结构层都是透明的&#xff0c;整个触摸屏覆盖在液晶面板上…

45、C++/基础练习20240311

一、提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 要求 使用C风格字符串完成。 代码&#xff1a; #include <iostream>using namespace std;int main() {string buf;//定义字符串类型变量bufcout << &…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Stack容器组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Stack容器组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Stack容器组件 堆叠容器&#xff0c;子组件按照顺序依次入栈&#xff0c;后一…

软件测试需要学什么?学多久?软件测试技术进阶路线图

很多新手&#xff0c;不知道软件测试学习该如何开始&#xff0c;软件测试需要掌握哪些知识。下面是根据本人的理解&#xff0c;粗略整理的一个学习大纲&#xff0c;基本上涵盖了软件测试工程师需要掌握的全部技能&#xff0c;希望对刚入行或者准备学习测试的朋友提供一点指引。…

哈希表|1.两数之和

力扣题目链接 /*** Note: The returned array must be malloced, assume caller calls free().*/// leetcode 支持 ut_hash 函式庫typedef struct {int key;int value;UT_hash_handle hh; // make this structure hashable} map;map* hashMap NULL;void hashMapAdd(int key, i…

Qt教程 — 1.1 Linux下安装Qt

目录 1 下载Qt 1.1 官方下载 1.2 百度网盘下载 1.3 Linux虚拟机终端下载 2 Qt安装 3 安装相关依赖 4 测试安装 1 下载Qt 1.1 官方下载 通过官网下载对应版本&#xff0c;本文选择的版本为qt-opensource-linux-x64-5.12.12&#xff0c;Qt官方下载链接&#xff1a;htt…

【C++庖丁解牛】模拟实现STL的string容器(最后附源码)

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 1.vs和g下string结构…

P5266 【深基17.例6】学籍管理题解

题目 您要设计一个学籍管理系统&#xff0c;最开始学籍数据是空的&#xff0c;然后该系统能够支持下面的操作&#xff08;不超过条&#xff09;&#xff1a; 插入与修改&#xff0c;格式1 NAME SCORE&#xff1a;在系统中插入姓名为NAME(由字母和数字组成不超过20个字符的字符…

如何使用群晖NAS结合cpolar内网穿透实现公网访问本地Office文件

文章目录 本教程解决的问题是&#xff1a;1. 本地环境配置2. 制作本地分享链接3. 制作公网访问链接4. 公网ip地址访问您的分享相册5. 制作固定公网访问链接 本教程解决的问题是&#xff1a; 1.Word&#xff0c;PPT&#xff0c;Excel等重要文件存在本地环境&#xff0c;如何在编…

智能硬件 | AI手机是营销噱头吗?对哪些行业利好?

趁着AI浪潮&#xff0c;手机厂商争先抢滩AI手机领域&#xff0c;智能手机开始步入AI新时代。AI手机不需要借助第三方App&#xff0c;而是通过手机自身的算力&#xff0c;直接成为用户的智能助手。但手机的AI应用能有多少&#xff0c;是否只是手机厂商为促进销量的营销噱头&…

【框架设计】MVC和MVVM对比图

1. MVC&#xff08;Model-View-Controller&#xff09; 单向通信View和Model通过Controller承上启下 2. MVVM&#xff08;Model-View-ViewModel&#xff09; 数据绑定M -> VM -> V DOM事件监听 V -> VM -> M 1. MVC是单向的&#xff0c;MVVM是双向的&#xff0c;…

CUDA入门之统一内存

原文来自CUDA 编程入门之统一内存 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质…

Java——正则表达式详解

目录 Java正则表达式1、正则表达式语法1.1、基本的元字符1.2、数量元字符1.3、位置元字符1.4、特殊字符元字符1.5、回溯引用和前后查找1.6、大小写转换1.7、匹配模式 2、Java中的正则表达式2.1、概述2.2、获取匹配位置2.3、捕获组 3、匹配单个字符3.1、匹配纯文本3.2、匹配任意…

小程序对于人力资源行业的创新与变革

随着移动互联网的快速发展&#xff0c;小程序成为了各行各业推广和服务的新利器。对于人力资源行业来说&#xff0c;开发一款定制化的小程序不仅可以提升服务效率&#xff0c;还可以增强品牌形象和用户粘性。那么&#xff0c;如何定制开发人力资源类的小程序呢&#xff1f;下面…

嵌入式3-11

1、整理思维导图 2、尝试学生结构体数组&#xff0c;完成成员的输入&#xff0c;以成绩为条件完成对学生成员的冒泡排序并输出&#xff08;全部在主函数内完成&#xff09; #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.…