security+JWT

news2024/11/19 16:45:57

security+JWT

  • 添加依赖
    • 准备工作
      • sql
      • UserInfo
      • UserMapper
      • UserService、UserServiceImpl
  • 创建JwtUtils工具类,做token的生成和校验
  • 进入Security
    • 创建AccountDetailsServiceImpl,并且实现UserDetailsService编写登录操作
  • 创建拦截器JWTAuthenticationFilter继承UsernamePasswordAuthenticationFilter
  • 创建拦截器JWTAuthorizationFilter
  • 创建WebSecurityConfig配置类,对springsecurity进行相关配置
    • 创建JWTAuthenticationEntryPoint
    • 测试
      • 访问/user/add
    • 登录/api/login
      • 由于使用的是调试工具测试的返回的token我也不知道在哪,可以通过代码输出进行获取
    • 在/user/add接口Hander中添加token
  • 再次访问/user/add,添加成功
  • 上面代码是使用的默认的/api/login的登录接口

添加依赖

<!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.8</version>
        </dependency>

        <!--lombok用来简化实体类-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

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


        <dependency>
            <groupId>com.fawkes</groupId>
            <artifactId>fawkes-core</artifactId>
            <version>2.0.0-RELEASE</version>
        </dependency>
 
        <!--        jwt增强-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <!-- alibaba json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        
		<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

准备工作

sql

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `salt` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

UserInfo

package com.wlj.entity;


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;


@Data
@TableName("user")
public class UserInfo implements Serializable {


    private static final long serialVersionUID = 1L;

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;

    private String username;

    private String password;

    private String salt;

}

UserMapper

package com.wlj.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wlj.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {
}

UserService、UserServiceImpl

package com.wlj.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.wlj.entity.UserInfo;

public interface UserService extends IService<UserInfo> {

    String login(UserInfo user);

    String add(UserInfo user);

    String findOne(UserInfo user);

    String update(UserInfo user);

}

package com.wlj.service;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wlj.entity.UserInfo;
import com.wlj.mapper.UserMapper;

import com.wlj.util.Constant;
import com.wlj.util.JwtTokenUtil;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;



@Service
public class UserServiceImpl   extends ServiceImpl<UserMapper, UserInfo>      implements  UserService {


    @Autowired
    UserMapper userMapper;
    @Override
    public String login(UserInfo user) {
        UserInfo sysUser = userMapper.selectOne(
                new QueryWrapper<UserInfo>().lambda().eq(UserInfo::getUsername, user.getUsername()));
        String token = JwtTokenUtil.createToken(sysUser.getUsername(), Constant.PRIORITY_FRONT_USER);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("Authorization", JwtTokenUtil.TOKEN_PREFIX + token);
        jsonObject.put("userName", sysUser.getUsername());

        return jsonObject.toString();

    }

    @Override
    public String add(UserInfo user) {

        int insert = userMapper.insert(user);
        return insert +"";
    }

    @Override
    public String findOne(UserInfo user) {
        UserInfo sysUser = userMapper.selectOne(
                new QueryWrapper<UserInfo>().lambda().eq(UserInfo::getUsername, user.getUsername()));
        return sysUser.getUsername();
    }

    @Override
    public String update(UserInfo user) {
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
       wrapper.eq("id", user.getId());
        int update = userMapper.update(user, wrapper);
        return "成功更新"+ update+ "条数据";
    }
}

创建JwtUtils工具类,做token的生成和校验

package com.wlj.util;



import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtTokenUtil {
    // Token请求头
    public static final String TOKEN_HEADER = "Authorization";
    // Token前缀
    public static final String TOKEN_PREFIX = "Bearer ";

    // 签名主题
    public static final String SUBJECT = "piconjo";
    // 过期时间
    public static final long EXPIRITION = 1000 * 24 * 60 * 60 * 7;
    //public static final long EXPIRITION = 1000 * 60;
    // 应用密钥
    public static final String APPSECRET_KEY = "Yik8MJeqpcO97HX1+WM1o38pG6ln0otV0O/WS6eVcF/uQNLUX9IJY6lRMnJh 9jdJjlBAmfhMPZ5GOyuji39RwWU7OQse/iEfY5fgE/eTZPCWqJpDykqtVYJ+ ZWV1Y6Tk+U8EilEuDCw4uAmYYGvH3oaM0scWzat9TJvvdFRt1E8=";
    // 角色权限声明
    private static final String ROLE_CLAIMS = "role";
    private static final String ISS = "paperpass";

//    @Autowired
//    private static RedisUtils redisUtils;

    /**
     * 生成Token
     */
    public static String createToken(String username, String role) {
        Map<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, "ROLE_" + role);
        String token = Jwts
                .builder()
                .setSubject(username)
                .setClaims(map)
                .claim("username", username)
                .setIssuer(ISS)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY)
                .compact();
        System.out.println("token失效时间:" + new Date(System.currentTimeMillis() + EXPIRITION));
        return token;
    }

    /**
     * 校验Token
     */
    public static Claims checkJWT(String token) {
        try {
            return Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 从Token中获取username
     */
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }

    /**
     * 从Token中获取用户角色
     */
    public static String getUserRole(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("role").toString();
    }

    /**
     * 校验Token是否过期
     */
    public static boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }

    public static void refreshTokenExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION));
        System.out.println("token失效时间:" + new Date(System.currentTimeMillis() + EXPIRITION));
    }
}

进入Security

创建AccountDetailsServiceImpl,并且实现UserDetailsService编写登录操作

package com.wlj.config;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wlj.entity.UserInfo;
import com.wlj.mapper.UserMapper;
import com.wlj.util.Constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

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

@Service
public class AccountDetailsServiceImpl implements UserDetailsService {

    @Resource
    private UserMapper userMapper;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        if (s == null || "".equals(s)) {
            throw new RuntimeException("用户不能为空");
        }
        // 调用方法查询用户
        UserInfo sysUser = userMapper.selectOne(
                new QueryWrapper<UserInfo>().lambda().eq(UserInfo::getUsername, s));

        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_" + Constant.PRIORITY_FRONT_USER));
		//密码加密格式 和前端的验证方法一致
        String encode = passwordEncoder.encode(sysUser.getPassword());
        
       return new User(sysUser.getUsername(),encode, authorities);
    }
}

UserDetails和UserDetailsService是什么?
两个接口
UserDetails:提供用户核心信息,登录验证完成之后,根据上述代码 return new User(sysUser.getUsername(),encode, authorities); security会保存用户的信息,通过 authResult.getPrincipal();可以获取到的对应用户信息和权限信息
UserDetailsService:用户自定义登录逻辑。需要设置使用默认的登录
接口

创建拦截器JWTAuthenticationFilter继承UsernamePasswordAuthenticationFilter

springsercurity对UserDetailsServiceImpl返回的user进行验证,验证成功则生成token,失败则返回失败信息
UsernamePasswordAuthenticationFilter 用户密码认证拦截器
如何获取用户输入的账号密码

UsernamePasswordAuthenticationFilter扩展AbstractAuthenticationProcessingFilter,因为需要从HTTP请求中从指定名称的参数获取用户名和密码,并且传递给验证核心;

package com.wlj.util;

import com.alibaba.fastjson.JSON;
import org.springframework.security.authentication.*;
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.UsernamePasswordAuthenticationFilter;

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;

/**
 * 验证用户名密码正确后 生成一个token并将token返回给客户端
 * <p>
 * 该拦截器用于获取用户登录的信息
 * 至于具体的验证 只需创建一个token并调用authenticationManager的authenticate()方法
 * 让Spring security验证即可 验证的事交给框架
 */
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        //配置登录验证的url 会通过AccountDetailsServiceImpl的loadUserByUsername继续验证,验证通过会进入successfulAuthentication方法 不通过会进入unsuccessfulAuthentication
        super.setFilterProcessesUrl("/api/login");
    }

    /**
     * 验证操作 接收并解析用户凭证
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 从输入流中获取到登录的信息
        // 创建一个token并调用authenticationManager.authenticate() 让Spring security进行验证
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
                request.getParameter("username"), request.getParameter("password")));
    }

    /**
     * 验证【成功】后调用的方法
     * 若验证成功 生成token并返回
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        User user = (User) authResult.getPrincipal();

        // 从User中获取权限信息
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        // 创建Token
        String token = JwtTokenUtil.createToken(user.getUsername(), authorities.toString());

        // 设置编码 防止乱码问题
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        // 在请求头里返回创建成功的token
        // 设置请求头为带有"Bearer "前缀的token字符串
        response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);

        // 处理编码方式 防止中文乱码
        response.setContentType("text/json;charset=utf-8");
        // 将反馈塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString("登录成功"));
    }

    /**
     * 验证【失败】调用的方法
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        String returnData = "";
        // 账号过期
        if (failed instanceof AccountExpiredException) {
            returnData = "账号过期";
        }
        // 密码错误
        else if (failed instanceof BadCredentialsException) {
            returnData = "密码错误";
        }
        // 密码过期
        else if (failed instanceof CredentialsExpiredException) {
            returnData = "密码过期";
        }
        // 账号不可用
        else if (failed instanceof DisabledException) {
            returnData = "账号不可用";
        }
        //账号锁定
        else if (failed instanceof LockedException) {
            returnData = "账号锁定";
        }
        // 用户不存在
        else if (failed instanceof InternalAuthenticationServiceException) {
            returnData = "用户不存在";
        }
        // 其他错误
        else {
            returnData = "未知异常";
        }

        // 处理编码方式 防止中文乱码
        response.setContentType("text/json;charset=utf-8");
        // 将反馈塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(returnData));
    }
}

创建拦截器JWTAuthorizationFilter

会对所有security没有放行的url进行拦截验证

package com.wlj.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

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.ArrayList;
import java.util.Collection;


/**
 * 登录成功后 走此类进行鉴权操作
 * <p>
 * 当访问需要权限校验的URL(当然 该URL也是需要经过配置的) 则会来到此拦截器 在该拦截器中对传来的Token进行校验
 * 只需告诉Spring security该用户是否已登录 并且是什么角色 拥有什么权限即可
 */
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

//    private RedisUtils redisUtils;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 在过滤之前和之后执行的事件
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String tokenHeader = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        super.doFilterInternal(request, response, chain);
    }

    /**
     * 从token中获取用户信息并新建一个token
     *
     * @param tokenHeader 字符串形式的Token请求头
     * @return 带用户名和密码以及权限的Authentication
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        // 去掉前缀 获取Token字符串
        String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX, "");
        // 从Token中解密获取用户名
        String username = JwtTokenUtil.getUsername(token);
        // 从Token中解密获取用户角色
        String role = JwtTokenUtil.getUserRole(token);
        // 将[ROLE_XXX,ROLE_YYY]格式的角色字符串转换为数组
        String[] roles = StringUtils.strip(role, "[]").split(", ");
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (String s : roles) {
            authorities.add(new SimpleGrantedAuthority(s));
        }
        if (username != null) {
            //这里向SpringSecurity声明用户角色,做相关操作放行
            return new UsernamePasswordAuthenticationToken( username , null, null);
        }
        return null;
    }
}

创建WebSecurityConfig配置类,对springsecurity进行相关配置

package com.wlj.config;

import com.wlj.util.JWTAuthenticationEntryPoint;
import com.wlj.util.JWTAuthenticationFilter;
import com.wlj.util.JWTAuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
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.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("accountDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    /**
     * 认证管理器配置方法
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    /**
     * 安全配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 跨域共享
        http.cors()
                .and()
                // 跨域伪造请求限制无效
                .csrf().disable()
                .authorizeRequests()
                // 访问需要拦截的url
                .antMatchers("/user/login").permitAll()
                .antMatchers("/api/login").permitAll()
 
                .antMatchers("/user/update").permitAll( ) //根据角色访问

                // 任何请求,访问的用户都需要经过认证
                .anyRequest().authenticated()
//                // 其余资源都可访问
//                .anyRequest().permitAll()
                .and()
                // 添加JWT登录拦截器
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                // 添加JWT鉴权拦截器
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .sessionManagement()
                // 设置Session的创建策略为:Spring Security永不创建HttpSession 不使用HttpSession来获取SecurityContext
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 异常处理
                .exceptionHandling()
                // 匿名用户访问无权限资源时的异常  JWTAuthenticationEntryPoint
                .authenticationEntryPoint(new JWTAuthenticationEntryPoint());
//        http.formLogin();
        http.headers().frameOptions().sameOrigin();
        http.csrf().disable();
    }

    /**
     * 跨域配置
     *
     * @return 基于URL的跨域配置信息
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 注册跨域配置
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

创建JWTAuthenticationEntryPoint

package com.wlj.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fawkes.core.base.api.ApiResponseBody;
import com.fawkes.core.enums.BizCodeMsgEnum;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

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

public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/javascript;charset=utf-8");
//        response.getWriter().print(JSONObject.toJSONString("您未登录,没有访问权限"));
        response.setStatus(HttpServletResponse.SC_OK);
        final ApiResponseBody error = ApiResponseBody.error(BizCodeMsgEnum.NO_TOKEN_AUTH);
        response.getWriter().write(new ObjectMapper().writeValueAsString(error));
    }
}

测试

访问/user/add

在这里插入图片描述

登录/api/login

注意 密码要和数据库的一致,加密方式也一样
密码可通过

 BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //密码加密
        String encode = bCryptPasswordEncoder.encode("123");

进行获取

在这里插入图片描述

由于使用的是调试工具测试的返回的token我也不知道在哪,可以通过代码输出进行获取

找到JWTAuthenticationFilter中的successfulAuthentication方法把token打印出来

 System.out.println(JwtTokenUtil.TOKEN_PREFIX + token + "生成的token");

再次尝试登录拿到token

在/user/add接口Hander中添加token

在这里插入图片描述

再次访问/user/add,添加成功

上面代码是使用的默认的/api/login的登录接口

如果使用自定义的登录接口,其实更简单只是放行/user/login,依次验证用户名密码没有问题之后,和api/login生成的token逻辑一致即可,访问时携带token,JWTAuthorizationFilter会根据security配置进行拦截验证token

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

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

相关文章

mac电影特效合成软件nuke15 完美激活版下载

Nuke 15是一款由英国The Foundry公司开发的专业的合成软件&#xff0c;被广泛用于电影、电视和广告制作中的后期合成和特效制作。 Mac软件下载&#xff1a;nuke15 完美激活版下载 Win软件下载&#xff1a;NUKE 13 中文激活版 Nuke 15拥有强大的功能和灵活性&#xff0c;可以帮助…

TartanVO: A Generalizable Learning-based VO 服务器复现(rtx3090 py3)

源码地址 代码地址&#xff1a;https://github.com/castacks/tartanvo/tree/python3 配环境 git clone https://github.com/castacks/tartanvo.git -b python3创建conda环境&#xff1a; conda create -n tartanvo python3.8安装pytorch conda install pytorch1.10.1 torc…

路由router

什么是路由? 一个路由就是一组映射关系&#xff08;key - value&#xff09;key 为路径&#xff0c;value 可能是 function 或 component 2、安装\引入\基础使用 只有vue-router3&#xff0c;才能应用于vue2&#xff1b;vue-router4可以应用于vue3中 这里我们安装vue-router3…

elementUI el-table+树形结构子节点选中后没有打勾?(element版本问题 已解决)

问题 1.不勾选父级CB111&#xff0c;直接去勾选子级&#xff08;ST2001…&#xff09;&#xff0c;子级选中后没有打勾显示 排查 一直以为是这个树形结构和表格不兼容产生的问题&#xff0c;到后来看官方demo都是可以勾选的&#xff0c;最后排查到了版本问题&#xff0c; 项…

电动滑板车UL2272认证测试项目和标准

平衡车ul2272认证标准于2016年2月正式公布&#xff0c;美国消费品安全协会(cpsc)宣布&#xff0c;所有平衡车(包括扭扭车)的制造商、进口商、经销商&#xff0c;其在美国本土生产、进口、销售的平衡车必须符合新的安全标准&#xff0c;包括ul2272平衡车电路系统认证标准。另外&…

flutter开发入门,windows环境安装,耗时一天解决各种bug,终于成功

首先说明要安装的环境&#xff1a;java8必须&#xff0c;android studio&#xff0c;chrome是开发安卓和web是必须的 java8的下载地址&#xff1a;https://www.java.com/en/download/、 java8蓝奏云下载地址&#xff1a;jre-8u381-windows-x64.exe - 蓝奏云 flutter国内环境…

lvgl 界面管理器

lv_scr_mgr lvgl 界面管理器 适配 lvgl 8.3 降低界面之间的耦合使用较小的内存&#xff0c;界面切换后会自动释放内存内存泄漏检测 使用方法 在lv_scr_mgr_port.h 中创建一个枚举&#xff0c;用于界面ID为每个界面创建一个页面管理器句柄将界面句柄添加到 lv_scr_mgr_por…

C++11新特性(lambda,可变参数模板,包装器,bind)

lambda表达式是什么&#xff1f;包装器又是什么&#xff1f;有什么作用&#xff1f;莫急&#xff0c;此篇文章将详细带你探讨它们的作用。很多同学在学习时害怕这些东西&#xff0c;其实都是方便使用的工具&#xff0c;很多情况下我们学这些新的东西觉得麻烦&#xff0c;累赘&a…

自学嵌入式多久才可以达到找工作的水平

自学嵌入式多久才可以达到找工作的水平 时间以及达到嵌入式工作水平所需的具体努力因人而异。但一般而言&#xff0c;自学嵌入式系统开发需要时间和毅力。以下是一些关键因素&#xff0c;影响着您能够在多久内达到找工作的水平&#xff1a;最近很多小伙伴找我&#xff0c;说想要…

YOLOv4 论文总结

贡献&#xff1a; 1.有效且强大的模型&#xff0c;常规GPU&#xff08;1080ti or 2080ti&#xff09;可得到实时、高质量的检测结果。 2.在训练中&#xff0c;验证 Bag-of-Freebies 和 Bag-of-Specials 方法 3.提出了两种数据增强手段&#xff0c;马赛克和自对抗训练&#x…

LeetCode【74】搜索二维矩阵

题目&#xff1a; 代码&#xff1a; public static boolean searchMatrix(int[][] matrix, int target) {int rows matrix.length;int columns matrix[0].length;// 先找到行&#xff0c;行为当前行第一列<target&#xff0c;当前行1行&#xff0c;第一列>targetfor…

详细教程:Postman 怎么调试 WebSocket

WebSocket 是一个支持双向通信的网络协议&#xff0c;它在实时性和效率方面具有很大的优势。Postman 是一个流行的 API 开发工具&#xff0c;它提供了许多功能来测试和调试 RESTful API 接口&#xff0c;最新的版本也支持 WebSocket 接口的调试。想要学习更多关于 Postman 的知…

Linux友人帐之环境变量

一、环境变量 1.1 环境变量的概念 1. 什么是环境变量&#xff1f; 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。 2. 为什么会有环境变量&#xff1f; 在Linux系统中&#xff0c;我们发现我们在执行一些指令时&#xff0c;比如…

10个打工人必备AI神器,升职加薪靠AI

HI&#xff0c;同学们&#xff0c;我是赤辰&#xff0c;本期是第18篇AI工具类教程&#xff0c;文章底部准备了粉丝福利&#xff0c;看完后可领取&#xff01;1. Runway&#xff08;文字转视频AI工具&#xff09; 只需要一句提示词就能精确生成你所想象的视频场景&#xff0c;还…

natapp内网穿透-将本地运行的程序/服务器通过公网IP供其它人访问

文章目录 1.几个基本概念1.1 局域网1.2 内网1.3 内网穿透1.4 Natapp 2.搭建内网穿透环境3.本地服务测试 1.几个基本概念 1.1 局域网 LAN&#xff08;Local Area Network&#xff0c;局域网&#xff09;是一个可连接住宅&#xff0c;学校&#xff0c;实验室&#xff0c;大学校…

百乐钢笔维修(官方售后,全流程)

文章目录 1 背景2 方法3 结果 1 背景 在给钢笔上墨的途中&#xff0c;不小心总成掉地上了&#xff0c;把笔尖摔弯了&#xff08;虽然还可以写字&#xff0c;但是非常的挂纸&#xff09;&#xff0c;笔身没有什么问题&#xff0c;就想着维修一下笔尖或者替换一下总成。 一般维…

Vulnhub系列靶机---Raven: 2

文章目录 信息收集主机发现端口扫描目录扫描用户枚举 漏洞发现漏洞利用UDF脚本MySQL提权SUID提权 靶机文档&#xff1a;Raven: 2 下载地址&#xff1a;Download (Mirror) 信息收集 靶机MAC地址&#xff1a;00:0C:29:15:7F:17 主机发现 sudo nmap -sn 192.168.8.0/24sudo arp…

操作系统中的(进程,线程)

操作系统是一个搞管理的软件&#xff0c;它对上给各个应用程序提供稳定的运行环境&#xff1b;对下管理各种硬件设备。 进程 一个操作系统由内核和配套的应用程序组成。而进程就是操作系统内核中众多关键概念中的一个。进程通俗一点来讲就是一个已经跑起来的程序。 每个进程…

【数据结构与算法】二叉树的镜像实现

需求分析&#xff1a; 将所有节点的左子树与右子树交换&#xff0c;以达到交换前与交换后成为镜像的效果。 如图&#xff1a; 实现代码&#xff1a; 先准备一个二叉树具有节点类&#xff0c;添加左右子节点的方法和层序遍历方法。 /*** author CC* version 1.0* since2023/10…

数学术语之源——“齐次(homogeneity)”的含义

1. “homogeneous”的词源 “homogeneous”源自1640年代&#xff0c;来自中古拉丁词“homogeneus”&#xff0c;这个词又源自古希腊词“homogenes”&#xff0c;词义为“of the same kind(关于同一种类的)”&#xff0c;由“homos”(词义“same(相同的)”&#xff0c;参见“ho…