SpringSecurity登录认证流程及源码分析

目录

一 作用

二 流程及源码分析 


一 作用

spring security作为spring家族中的一员,它的主要作用有两个,分别是认证和授权。

我们以前在实现登录功能的时候,前端会传来用户名和密码,然后我们根据前端传来的数据从用户表中的数据进行比较,从而实现用户登录。

而springSecurity的功能也有登录认证,并且它在登录认证成功后,会生成一个认证对象,当登录成功后再发送其他请求,就会根据这个认证对象来判断当前用户是否已经登录。

二 流程及源码分析 

在使用springSecurity之前,你还需要导入它的依赖坐标,只要导入了依赖,它就会自动生效。

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

① 用户输入用户名和密码以及验证码访问登录接口:

登录其实也是一个请求,所以也会被springsecurity拦截,但是因为你没有登录,就没有他的认证对象,它就不会让你访问这个接口,所以开始之前还需要在spring security的配置类中放行登录请求。(配置类的代码最后会统一给)

放行登录亲请求:

② 调用逻辑业务service层,完成验证。

package com.fs.system.service.ipml;import cn.hutool.core.convert.Convert;
import com.fs.common.constant.CacheConstants;
import com.fs.common.constant.UserConstants;
import com.fs.common.core.pojo.SysUser;
import com.fs.common.core.vo.LoginUser;
import com.fs.common.enums.UserStatus;
import com.fs.common.exception.ServiceException;
import com.fs.common.exception.user.BlackListException;
import com.fs.common.exception.user.CaptchaException;
import com.fs.common.exception.user.UserNotExistsException;
import com.fs.common.exception.user.UserPasswordNotMatchException;
import com.fs.common.util.DateUtils;
import com.fs.common.util.RedisCache;
import com.fs.common.util.ip.IpUtils;
import com.fs.system.security.UserDetailsServiceImpl;
import com.fs.system.service.ISysConfigService;
import com.fs.system.service.ISysLoginService;
import com.fs.system.service.ISysUserService;
import com.mysql.cj.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;import java.util.Objects;@Service
public class ISysLoginServiceImpl implements ISysLoginService {@Autowiredprivate ISysUserService sysUserService ;  //进行用户信息校验,包括查询该用户是否存在@Autowiredprivate SysPasswordService passwordService ;  //密码校验,包括密码输入的正确性已经输入密码的次数@Autowiredprivate ISysConfigService sysConfigService ; //判断验证码功能有没有开启@Autowiredprivate RedisCache redisCache ; //用于操作缓存中的数据@Autowiredprivate TokenService tokenService ; //对token的操作@Autowiredprivate UserDetailsServiceImpl userDetailsService ; //用来获取LoginUser对象@Autowiredprivate AuthenticationManager authenticationManager ; //用于获取认证/** 登录验证 */@Overridepublic String login(String username, String password, String code, String uuid) {
//        1.验证码验证码validateCaptcha(username , code , uuid);System.out.println("验证码校验完成...");
//        2.参数校验loginCheck(username , password) ;3.根据用户名查询用户
//        SysUser sysUser = sysUserService.selectUserByUserName(username);//        检验密码
//        passwordService.validate(sysUser , password);  //通过Security帮我们进行处理,不需要自己校验了//        创建token
//        //先创建一个LoginUser对象,因为返回的的是LoginUser对象
//        LoginUser loginUser = new LoginUser();
//        loginUser.setUserId(sysUser.getUserId());
//        loginUser.setUser(sysUser);//        返回一个认证对象Authentication authentication = null;try{//创建一个Authentication认证对象,  属性: authenticated =false, 没有被认证UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);//返回一个认证对象:  被认证认证对象authentication = authenticationManager.authenticate(authRequest); //这个认证对象就是一个登录的认证器}catch (Exception e){throw new  ServiceException("用户不存在或者是密码错误");}//调用tokenService创建token//里面已经给LoginUser赋值token了,不过这里面的token是一个uuid,redis存的是loginUser,LoginUser里面的token又是一个uuid?String token = tokenService.createToken((LoginUser) userDetailsService.loadUserByUsername(username));
//        修改登陆时间和登录ipLoginUser loginUser= (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());
//        返回token给前端(token里面存放着登录用户的信息,)System.out.println("token:=========>"+token);return token;}/** 验证码验证码 */@Overridepublic void validateCaptcha(String username, String code, String uuid) {/*** code是前端传来的验证码答案,uuid是验证码缓存的key*/
//        先判断有没有开启验证码功能boolean captchaEnabled = sysConfigService.selectCaptchaEnabled();if (!captchaEnabled) throw new ServiceException("验证码功能没有开启");
//        然后根据key去查询缓存中的value,判断value和输入的答案是否正确String value = redisCache.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + uuid);
//        再将从缓存中拿到的value和code比对if (!value.equals(code)){throw new CaptchaException();}}/**  记录登录信息 :  */@Overridepublic void recordLoginInfo(Long userId) {SysUser sysUser = new SysUser();sysUser.setUserId(userId);sysUser.setLoginIp(IpUtils.getIpAddr());sysUser.setLoginDate(DateUtils.getNowDate());sysUserService.updateUserProfile(sysUser);}/**  登录前置校验(对请求参数的校验)  */private void loginCheck(String username , String password){
//        非空校验if (Objects.isNull(username)||Objects.isNull(password)){throw new UserNotExistsException() ;}
//        长度校验// 密码如果不在指定范围内 错误if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH){throw new UserPasswordNotMatchException();}//用户名如果不在指定范围内if (username.length()<UserConstants.USERNAME_MIN_LENGTH||username.length()>UserConstants.USERNAME_MAX_LENGTH){throw new UserPasswordNotMatchException() ;}// IP黑名单校验//todo  这个地方调用selectConfigByKey这个方法,但是这个数据库里面根本没有记录黑名单的列String blackStr = sysConfigService.selectConfigByKey("sys.login.blackIPList");if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())){throw new BlackListException();}}
}

 

③ 获取认证对象

我们需要调用spring security中的方法来获取一个认证对象,它会经过一系列的认证,只有最后用户的信息认证成功后才会生成一个认证对象,这说明用户认证成功,否则则说明这个用户认证失败。

 

④ 源码分析

那么我们就从这里作为入口,去看看springsecurity的源码是如何实现用户的账号信息以及密码校验的。

1.进入anthenticate方法后,我们发现这个是一个接口的抽象方法:

2.既然是接口,那我们就找到它的默认实现类ProviderManager,并且找到这个实现的方法:

3.那么我们继续看这个方法,这个方法的核心就是调用登录认证器的authenticate()方法:

 

4.我们现在知道这里主要就是调用了登录认证器的authenticate方法,那么我们就进这个方法看看里面实现了什么。最后发现这个方法也是一个接口的抽象方法:

5.然后我们找到它的实现类AbstractUserDetailsAuthenticationProvider,并且找到对应的方法:

6.我们找到实现方法authenticate后,发现它主要进行两步操作,先从缓存中获取到这个用户,如果是null,那么就调用etrieveUser()方法,而第一次登录里面肯什么都没有,所以是null,那么主要就是调用etrieveUser()方法:

7.那么不用多说,直接进入这个方法。但是发现它是一个抽象方法,那么我们肯定就是找到它的实现方法,去看里面的实现逻辑:

8.找到它的实现方法,这就已经到尾了,不过我们还记得一开时我们需要返回的值就是一个用户信息对象,所以这里也是找到了我们心心念念的 loadUserByUsername(username)方法:

9.那么我们肯定就进入这个方法去看看,发现它是接口 UserDetailsService的方法。

10.既然如此,我们只需要实现这个接口重写loadUserByUsername方法,然后从数据路根据用户名查询到用户信息,然后封装成一个loginUser对象,并且对这个用户的基本信息进行一个校验,没问题后就可以返回了,到这里根据用户名查找用户这个验证已经完成了。(不过需要注意,因为这个方法返回的是一个UserDetails,所以loginUser对象需要实现或者继承它才能作为该对象返回loginUser对象)

package com.fs.system.security;import cn.hutool.core.util.ObjectUtil;
import com.fs.common.core.pojo.SysUser;
import com.fs.common.core.vo.LoginUser;
import com.fs.common.enums.UserStatus;
import com.fs.common.exception.ServiceException;
import com.fs.common.exception.user.UserNotExistsException;
import com.fs.system.mapper.SysUserMapper;
import com.fs.system.service.ipml.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.Collections;
import java.util.Objects;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate SysUserMapper userMapper ;@Autowiredprivate TokenService tokenService ;@Overridepublic UserDetails  loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("username:"+username);
//        从数据库查询用户信息SysUser sysUser = userMapper.selectUserByUserName(username);System.out.println("查询到用户信息:"+sysUser);//验证用户是否存在if (Objects.isNull(sysUser)){System.out.println("用户不存在");throw new UserNotExistsException() ;}
//        是否禁用if (sysUser.getStatus().equals(UserStatus.DISABLE)){throw  new ServiceException("用户已封禁") ;}
//        是否删除if (sysUser.getDelFlag()== UserStatus.DELETED.getCode()){throw new ServiceException("账号已经被删除");}//        创建LoginUser对象LoginUser loginUser = creatLoginUser(sysUser);
//        创建tokentokenService.createToken(loginUser);return loginUser ;}public LoginUser creatLoginUser(SysUser sysUser){//先创建一个LoginUser对象,因为返回的的是LoginUser对象LoginUser loginUser = new LoginUser();loginUser.setUserId(sysUser.getUserId());loginUser.setUser(sysUser);System.out.println("成功查询user对象并且返回:"+loginUser);return loginUser ;}
}

上面只是完成了用户名查到用户的功能,既然已经查询到用户的信息了,说明也就知道了用户的真实密码,那么我接下来就需要把用户输入的密码和真实密码做对比,所以我们继续回到DaoAuthenticationProvider类中。

在这里的 createSuccessAuthentication()方法中,我们成功看见了密码比对的方法:

我们直接找到源头,发现这个加密方法是一个PasswordEncoder接口的抽象方法,里面主要两个方法,分别用于对前端输入的密码加密,还有把加密后的密码和用户信息中的密码进行比对。

所以,我们要实现自定义的加密以及对密码进行比对,我们只需要实现这个接口,同时完成这个两个方法的实现就可以了:

package com.fs.system.security;import cn.hutool.core.convert.Convert;
import com.fs.common.util.sign.PasswordUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;/*** @author suke* @version 1.0* @title MyPasswordEncoder* @description  自定义的密码编码* @create 2024/7/25 10:58*/
@Component
public class MyPasswordEncoder implements PasswordEncoder {@Overridepublic String encode(CharSequence rawPassword) {return PasswordUtils.generate(Convert.toStr(rawPassword));}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return PasswordUtils.verify(Convert.toStr(rawPassword),encodedPassword);}
}
PasswordUtils是我们用于密码的加密和比对的一个工具类,我们使用的是md5加密,工具类代码如下:
package com.fs.common.util.sign;import java.security.MessageDigest;
import java.util.Random;/*** MD5加盐加密*/    
public class PasswordUtils {/*** 生成含有 随机盐的密码*/public static String generate(String password) {Random r = new Random();StringBuilder sb = new StringBuilder(16);sb.append(r.nextInt(99999999)).append(r.nextInt(99999999));int len = sb.length();if (len < 16) {  //不够16位,前面补0for (int i = 0; i < 16 - len; i++) {sb.append("0");}}String salt = sb.toString();password = md5Hex(password + salt); //32位的16进制char[] cs = new char[48];for (int i = 0; i < 48; i += 3) {cs[i] = password.charAt(i / 3 * 2);char c = salt.charAt(i / 3);cs[i + 1] = c;cs[i + 2] = password.charAt(i / 3 * 2 + 1);}return new String(cs);}/*** 校验密码是否正确*/public static boolean verify(String password, String md5) {char[] cs1 = new char[32];char[] cs2 = new char[16];for (int i = 0; i < 48; i += 3) {cs1[i / 3 * 2] = md5.charAt(i);cs1[i / 3 * 2 + 1] = md5.charAt(i + 2);cs2[i / 3] = md5.charAt(i + 1);}String salt = new String(cs2);return md5Hex(password + salt).equals(new String(cs1));}/*** 获取十六进制字符串形式的MD5摘要*/public static String md5Hex(String src) {try {return Md5Utils.hash(src);} catch (Exception e) {return null;}}public static void main(String[] args) {System.out.println(generate("123456"));//System.out.println(verify("123456", "02dd65660724d38816641b3a98409fa2080401b489c9ff42"));}
}

最后在这里,这个校验过程就完成了,最后会成功创建一个认证对象。

随后,我们只需要创建一个token,返回给前端就欧克了。

这里附上token创建的代码:

package com.fs.system.service.ipml;import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fs.common.constant.CacheConstants;
import com.fs.common.constant.Constants;
import com.fs.common.core.vo.LoginUser;
import com.fs.common.util.RedisCache;
import com.fs.common.util.ServletUtils;
import com.fs.common.util.ip.AddressUtils;
import com.fs.common.util.ip.IpUtils;
import com.fs.system.config.TokenProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;/*** token验证处理*/
@Service
public class TokenService {@Autowiredprivate TokenProperties tokenProperties;protected static final long MILLIS_SECOND = 1000;protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;@Autowiredprivate RedisCache redisCache;/*** 获取用户身份信息** @return 用户信息*/public LoginUser getLoginUser(HttpServletRequest request){// 获取请求携带的令牌String token = getToken(request);if (StringUtils.isNotEmpty(token)){try{Claims claims = parseToken(token);// 解析对应的权限以及用户信息String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);String userKey = getTokenKey(uuid);LoginUser user = redisCache.getCacheObject(userKey);return user;}catch (Exception e){}}return null;}/*** 设置用户身份信息*/public void setLoginUser(LoginUser loginUser){if (Objects.nonNull(loginUser) && StrUtil.isNotEmpty(loginUser.getToken())){refreshToken(loginUser);}}/*** 删除用户身份信息*/public void delLoginUser(String token){if (StringUtils.isNotEmpty(token)){String userKey = getTokenKey(token);redisCache.deleteObject(userKey);}}/*** 创建令牌** @param loginUser 用户信息* @return 令牌*/public String createToken(LoginUser loginUser){String token = UUID.randomUUID(false).toString(true);loginUser.setToken(token);setUserAgent(loginUser);refreshToken(loginUser);Map<String, Object> claims = new HashMap<>();claims.put(Constants.LOGIN_USER_KEY, token);return createToken(claims);}/*** 验证令牌有效期,相差不足20分钟,自动刷新缓存** @param loginUser* @return 令牌*/public void verifyToken(LoginUser loginUser){long expireTime = loginUser.getExpireTime();long currentTime = System.currentTimeMillis();if (expireTime - currentTime <= MILLIS_MINUTE_TEN){refreshToken(loginUser);}}/*** 刷新令牌有效期** @param loginUser 登录信息*/public void refreshToken(LoginUser loginUser){loginUser.setLoginTime(System.currentTimeMillis());loginUser.setExpireTime(loginUser.getLoginTime() + tokenProperties.getExpireTime() * MILLIS_MINUTE);// 根据uuid将loginUser缓存String userKey = getTokenKey(loginUser.getToken());redisCache.setCacheObject(userKey, loginUser, tokenProperties.getExpireTime(), TimeUnit.MINUTES);}/*** 设置用户代理信息** @param loginUser 登录信息*/public void setUserAgent(LoginUser loginUser){UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));String ip = IpUtils.getIpAddr();loginUser.setIpaddr(ip);loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));loginUser.setBrowser(userAgent.getBrowser().getName());loginUser.setOs(userAgent.getOs().getName());}/*** 从数据声明生成令牌** @param claims 数据声明* @return 令牌*/private String createToken(Map<String, Object> claims){String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, tokenProperties.getSecret()).compact();return token;}/*** 从令牌中获取数据声明** @param token 令牌* @return 数据声明*/private Claims parseToken(String token){return Jwts.parser().setSigningKey(tokenProperties.getSecret()).parseClaimsJws(token).getBody();}/*** 从令牌中获取用户名** @param token 令牌* @return 用户名*/public String getUsernameFromToken(String token){Claims claims = parseToken(token);return claims.getSubject();}/*** 获取请求token** @param request* @return token*/private String getToken(HttpServletRequest request){String token = request.getHeader(tokenProperties.getHeader());if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)){token = token.replace(Constants.TOKEN_PREFIX, "");}return token;}private String getTokenKey(String uuid){return CacheConstants.LOGIN_TOKEN_KEY + uuid;}
}

 最后再附上spring security配置类的完成代码:

package com.fs.system.config;import com.fs.common.util.RedisCache;
import com.fs.system.security.JwtAuthenticationTokenFilter;
import com.fs.system.security.MyDaoAuthenticationProvider;
import com.fs.system.security.MyPasswordEncoder;
import com.fs.system.security.TokenAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;/*** @author suke* @version 1.0* @title SpringSecurityConfiguration* @description  springSecurity的自定义配置类* @create 2024/7/25 10:14*/
@Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowired
//    @Qualifier("userDetailsServiceImpl")private UserDetailsService userDetailsService;@Autowiredprivate RedisCache redisCache ;@Autowiredprivate SysPasswordProperties  passwordProperties ;@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter ;@Autowiredprivate CorsFilter corsFilter;@Autowiredprivate TokenAuthenticationEntryPoint tokenAuthenticationEntryPoint;//    @Autowired
//    private LogoutSuccessHandlerImpl logoutSuccessHandler;//对密码进行加密    密码校验@Beanpublic PasswordEncoder passwordEncoder(){return new MyPasswordEncoder() ;}//修改用户名,密码@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {System.out.println("进入配置类...");//使用MyDaoAuthenticationProviderauth.authenticationProvider(new MyDaoAuthenticationProvider(userDetailsService,redisCache,passwordEncoder(),passwordProperties));}
修改用户名,密码
//@Override
//protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//    //使用MyDaoAuthenticationProvider
//    auth.authenticationProvider(new MyDaoAuthenticationProvider());
//}@Bean(name = BeanIds.AUTHENTICATION_MANAGER)@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http// CSRF禁用,因为不使用session.csrf().disable()// 禁用HTTP响应标头.headers().cacheControl().disable().and()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 过滤请求.authorizeRequests()// 对于登录login 注册register 验证码captchaImage 允许匿名访问.antMatchers("/login","/getRouters", "/captchaImage").permitAll()// 静态资源,可匿名访问.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()// 除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated().and().headers().frameOptions().disable();//添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//添加跨域过滤器http.addFilterBefore(corsFilter,JwtAuthenticationTokenFilter.class);//配置认证失败的入口http.exceptionHandling().authenticationEntryPoint(tokenAuthenticationEntryPoint);
//
//        //配置注销
//        http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);}}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java高级Day18-集合

62.集合 之前保存多个数据元素使用数组&#xff0c;但数组有以下缺点&#xff1a; 长度开始必须指定&#xff0c;指定后不可修改 保存的必须为同一类型的元素 使用数组进行增加/删除元素的代码比较麻烦 集合 可以动态的保存任意多个对象 提供了一系列方便操作对象的方法 …

河南萌新联赛2024第(三)场:河南大学

传送门&#xff1a;河南萌新联赛2024第&#xff08;三&#xff09;场&#xff1a;河南大学_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ B 正则表达式 思路&#xff1a;模拟 代码&#xff1a; #include<bits/stdc.h> using namespace std; typedef long lo…

vue3+fetch请求+接收到流式的markdown数据+一边gpt打字机式输出内容,一边解析markdown语法+highlight.js实现代码高亮

这个问题终于解决了&#xff01;好开心。 先看最终效果&#xff1a; video_20240724_141543_edit 项目背景&#xff1a;vue3 场景&#xff1a;像gpt一样可以对话&#xff0c;当用户发送问题之后&#xff0c;ai回复&#xff0c;ai是一部分一部分回复&#xff0c;像打印机式输出…

Moving Object Segmentation: All You Need Is SAM(and Flow) 论文详解

系列文章目录 文章目录 系列文章目录前言摘要1 引言2 相关工作3 SAM Preliminaries4 帧级分割Ⅰ&#xff1a;以流作为输入5 帧级分割Ⅱ&#xff1a;以流为提示6 序列级掩膜关联7 实验7.1 数据集7.2 评价指标7 .3 实施细节7.4 消融实验7.5 定量结果7 .定性可视化 8 结论致谢附录…

Ollama0.3.0 + llama3.1本地部署

Ollama0.3.0 llama3.1本地部署 安装Ollama 官网下载安装包 https://ollama.com/download ​​ 根据系统选择对应版本 安装完成后&#xff0c;电脑右下角会出现ollama程序图标&#xff0c;表示ollama正在运行。 ​​ 打开cmd命令 下载Llama3.1 输入ollama&#xff0c…

从“线缆迷宫”到“数字通途”:一机一网助力成天泰园区网络升级

(文 林海宾/深圳速锦网络科技有限公司) 林海宾,现任深圳速锦网络科技有限公司(以下简称速锦网络)的项目总监,一个入行十年、经验老道的数字化升级”操盘手“。他曾经主导过中国农业银行深圳分行130多个网点以及美的珠海工厂等数字化建设升级项目。在2024年的五一,他帮助深圳市…

【MATLAB源码】机器视觉与图像识别技术(7)续---BP神经网络

系列文章目录在最后面&#xff0c;各位同仁感兴趣可以看看&#xff01; BP神经网络 第一节、BP网络定义第二节、BP网络结构及其特点第三节、信息传播方式 信息的正向传播&#xff1a;实质是计算网络的输出误差的反向传播&#xff1a;实质是学习过程第四节、 BP网络的算法流程…

视频怎么在尽量不损害画质的前提下压缩?试试这4款视频压缩神器

4个视频压缩神器&#xff0c;帮你在不损画质的前提下满足压缩需求&#xff1a; 1、嗨格式压缩大师 关键词&#xff1a;高效、批量 直达链接>>yasuo.hgs.cn 嗨格式压缩大师是一款免费的文件压缩工具&#xff0c;支持视频、图片、PDF、PPT等文件快速、批量压缩&#xff…

代码随想录 day 28 贪心

第八章 贪心算法 part02 贪心 局部最优解推出全局最优 &#xff0c;而且想不到反例&#xff0c;那么就试一试贪心 将问题分解为若干个子问题 找出适合的贪心策略 求解每一个子问题的最优解 将局部最优解堆叠成全局最优解 只要想清楚 局部最优 是什么&#xff0c;如果推导出全局…

XR-Frame 计算相机与场景物体的距离

如下哦 const cameraTransform this.scene.getElementById(camera).getComponent(transform)const modelTransform this.scene.getElementById(yourNodeId).getComponent("transform");if (cameraTransform.worldPosition.distanceTo(modelTransform.worldPosition…

pip install albumentations安装下载遇19kB/s超级慢细水管解决办法

albumentations 是一个用于图像增强的 Python 库&#xff0c;它提供了丰富的图像变换功能&#xff0c;可以用于数据增强&#xff0c;从而提高深度学习模型的泛化能力。 直接安装命令&#xff1a; pip install albumentations但是如果半夜遇到这种19kB/s的下载速度 为头发着想&…

【C++】C++11新增语法(右值引用、完美转法)

文章目录 1.C11新增常用语法1.1 统一的列表初始化1.2 initializer_list初始化1.3 声明相关1.4 继承与多态相关 2. 右值引用与移动语义2.1 左值引用与右值引用2.2 右值引用与移动语义的使用场景2.3 右值引用引用左值(move) 3. 完美转发4. 新的类功能4.1 新增两个默认成员函数4.2…

记录两道关于编码解码的问题

环境&#xff1a;php环境即可&#xff0c;也可使用phpstudy。 参考文章: 深入理解浏览器解析机制和XSS向量编码-CSDN博客(很重要) HTML 字符编码&#xff08;自我复习&#xff09;-CSDN博客 例题1&#xff1a; <?php header("X-XSS-Protection: 0"); $xss …

Jangow-1.0.1靶机漏洞复现(未完成)

首先&#xff0c;这个靶机只能使用VirtualBox打开&#xff0c;靶机下载地址为 https://download.vulnhub.com/jangow/jangow-01-1.0.1.ova 虚拟机软件下载地址为 Download_Old_Builds – Oracle VM VirtualBox 开启靶机后访问ip进入如下页面&#xff0c;点击site进入到一个…

昇思25天学习打卡营第16天|Diffusion扩散模型,DCGAN生成漫画头像

Diffusion扩散模型 关于扩散模型&#xff08;Diffusion Models&#xff09;有很多种理解&#xff0c;本文的介绍是基于denoising diffusion probabilistic model &#xff08;DDPM&#xff09;&#xff0c;DDPM已经在&#xff08;无&#xff09;条件图像/音频/视频生成领域取得…

MySQL:GROUP BY 分组查询

分组查询是SQL中一个非常强大的功能&#xff0c;它允许我们将数据按照一个或多个字段进行分组&#xff0c;并对每个分组进行聚合计算&#xff08;如求和、平均值、最大值、最小值等&#xff09;。在MySQL中&#xff0c;我们使用 GROUP BY 关键字来实现分组查询。 核心语法 SE…

Vue3自研开源Tree组件:人性化的拖拽API设计

针对Element Plus Tree组件拖拽功能API用的麻烦&#xff0c;小卷开发了一个API使用简单的JuanTree组件。拖拽功能用起来非常简单&#xff01; 文章目录 使用示例allowDragallowDrop支持节点勾选支持dirty检测后台API交互 源码实现 使用示例 组件的使用很简单&#xff1a; 通过…

4.1.2、操作系统-概述及进程管理-状态管理和前趋图

进出的组成和状态 进程是计算机中正在运行的程序的实例。它是操作系统进行资源分配和管理的基本单位,包括代码、数据和执行状态等信息。 进程的组成:进程控制块PCB(唯一标志)、程序(描述进程要做什么)、数据(存放进程执行时所需数据)。 我们电脑中的QQ影音和网易云音乐可以并…

小米手机怎么查看电池剩余容量

最近发现自己的小米11pro的待机时间越来越短了&#xff0c;怀疑是电池剩余容量太小了&#xff0c;希望测下电池剩余容量好打算是否要更换下电池。 1.抓取bug测试 首先打开拨号界面&#xff0c;输入*#*#284#*#*然后开始抓取日志。 等待bug报告生成完毕&#xff0c;然后点击就…

Git原理与用法系统总结

目录 Reference前言版本控制系统Git的诞生配置Git配置用户名和邮件配置颜色配置.gitignore文件 Git的基础用法初始化仓库克隆现有的仓库添加暂存文件提交变动到仓库比较变动查看日志Git回退Git重置暂存区 Git版本管理重新提交取消暂存撤销对文件的修改 Git分支Git分支的优势Git…