JWT整合Gateway实现鉴权(RSA与公私密钥工具类)

news2024/10/5 21:19:27

一.业务流程

1.使用RSA生成公钥和私钥。私钥保存在授权中心,公钥保存在网关(gateway)和各个信任微服务中。
2.用户请求登录。
3.授权中心进行校验,通过后使用私钥对JWT进行签名加密。并将JWT返回给用户
4.用户携带JWT访问
5.gateway直接通过公钥解密JWT进行验证
 

二.RSA测试Demo

JWT包含三部分数据
header头部分-Payload载荷(包含用户信息身份)-Signature签名


一.工具类

用户基本信息

package entity;

/**
 * 载荷对象
 */
public class UserInfo {

    private Long id;

    private String username;

    public UserInfo() {
    }

    public UserInfo(Long id, String username) {
        this.id = id;
        this.username = username;
    }

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

公钥私钥生成读取类

package utils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RsaUtils {
    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

公钥私钥加解密类

package utils;


import entity.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;

public class JwtUtils {
    /**
     * 私钥加密token
     *
     * @param userInfo      载荷中的数据
     * @param privateKey    私钥
     * @param expireMinutes 过期时间,单位秒
     * @return
     * @throws Exception
     */
    public static String generateToken(UserInfo userInfo, PrivateKey privateKey, int expireMinutes) throws Exception {
        return Jwts.builder()
                .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
                .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }

    /**
     * 私钥加密token
     *
     * @param userInfo      载荷中的数据
     * @param privateKey    私钥字节数组
     * @param expireMinutes 过期时间,单位秒
     * @return
     * @throws Exception
     */
    public static String generateToken(UserInfo userInfo, byte[] privateKey, int expireMinutes) throws Exception {
        return Jwts.builder()
                .claim(JwtConstans.JWT_KEY_ID, userInfo.getId())
                .claim(JwtConstans.JWT_KEY_USER_NAME, userInfo.getUsername())
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey))
                .compact();
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥字节数组
     * @return
     * @throws Exception
     */
    private static Jws<Claims> parserToken(String token, byte[] publicKey) throws Exception {
        return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey))
                .parseClaimsJws(token);
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     * @throws Exception
     */
    public static UserInfo getInfoFromToken(String token, PublicKey publicKey) throws Exception {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        return new UserInfo(
                ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }

    /**
     * 获取token中的用户信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     * @throws Exception
     */
    public static UserInfo getInfoFromToken(String token, byte[] publicKey) throws Exception {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        return new UserInfo(
                ObjectUtils.toLong(body.get(JwtConstans.JWT_KEY_ID)),
                ObjectUtils.toString(body.get(JwtConstans.JWT_KEY_USER_NAME))
        );
    }
}

辅助类:
 

package utils;

import org.apache.commons.lang3.StringUtils;

/**
 * 从jwt解析得到的数据是Object类型,转换为具体类型可能出现空指针,
 * 这个工具类进行了一些转换
 */
public class ObjectUtils {

    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        return obj.toString();
    }

    public static Long toLong(Object obj) {
        if (obj == null) {
            return 0L;
        }
        if (obj instanceof Double || obj instanceof Float) {
            return Long.valueOf(StringUtils.substringBefore(obj.toString(), "."));
        }
        if (obj instanceof Number) {
            return Long.valueOf(obj.toString());
        }
        if (obj instanceof String) {
            return Long.valueOf(obj.toString());
        } else {
            return 0L;
        }
    }

    public static Integer toInt(Object obj) {
        return toLong(obj).intValue();
    }
}
package utils;

public abstract class JwtConstans {
    public static final String JWT_KEY_ID = "id";
    public static final String JWT_KEY_USER_NAME = "username";
}
二.测试代码
import entity.UserInfo;
import org.junit.Before;
import org.junit.Test;
import utils.JwtUtils;
import utils.RsaUtils;

import java.security.PrivateKey;
import java.security.PublicKey;

public class JwtTest {

    private static final String pubKeyPath = "D:\\tmp\\rsa\\rsa.pub";

    private static final String priKeyPath = "D:\\tmp\\rsa\\rsa.pri";

    private PublicKey publicKey;

    private PrivateKey privateKey;

    @Test
    public void testRsa() throws Exception {
        RsaUtils.generateKey(pubKeyPath, priKeyPath, "234");
    }

    //在运行生成token之前,获取公钥和私钥对象
    @Before
    public void testGetRsa() throws Exception {
        this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
        this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
    }

    //通过私钥加密
    @Test
    public void testGenerateToken() throws Exception {
        // 生成token
        String token = JwtUtils.generateToken(new UserInfo(20L, "jack"), privateKey, 5);
        System.out.println("token = " + token);
    }

    //通过公钥解析token
    @Test
    public void testParseToken() throws Exception {
        String token = "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MjsInVzZXJuYW1lIjoiamFjayIsImV4cCI6MTY3MDAzOTI0M30.Y6MstaAuWwgsNenMRYQSBeG-zx9kHmh75PJuGrJuyPPuetebwqS6Xl2NQGmMYx1mQII-Tq6M-vGGMvQJ8q2Dd8GXL1u-RMC9-e7SKkAgVFP0YzwY3YJRgw9q62snWZqZllmNIgp_jFu14HHHktCS49V-bd1HR9W2PMQoWOeWquI";

        // 解析token
        UserInfo user = JwtUtils.getInfoFromToken(token, publicKey);
        System.out.println("id: " + user.getId());
        System.out.println("userName: " + user.getUsername());
    }
}
三.测试流程


指定路径一定包括rsa







三.Spring Cloud+Gateway+RSA


一.gateway模块:
yml文件

定义白名单(不需要鉴权的路径),公钥地址(解密鉴权),以及cookieName(获取加密的token)

配置类:

获取公钥

package com.yigou.gw.config;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import utils.RsaUtils;

import javax.annotation.PostConstruct;
import java.security.PublicKey;

@ConfigurationProperties(prefix = "yigou.jwt")
@Data
@Slf4j
public class JwtProperties {

    private String pubKeyPath;// 公钥

    private PublicKey publicKey; // 公钥

    private String cookieName;

    //@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
    //执行顺序:Constructor >> @Autowired >> @PostConstruct
    @PostConstruct
    public void init(){
        try {
            // 获取公钥和私钥
            this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
        } catch (Exception e) {
            log.error("初始化公钥失败!", e);
            throw new RuntimeException();
        }
    }
}

获取定义的白名单对象

package com.yigou.gw.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@ConfigurationProperties(prefix = "yigou.filter")
@Data
public class FilterProperties {

    private List<String> allowPaths;
}
自定义拦截:
package com.yigou.gw.filter;

import com.yigou.gw.config.FilterProperties;
import com.yigou.gw.config.JwtProperties;
import entity.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import utils.JwtUtils;

import java.util.List;


@Component
@EnableConfigurationProperties({JwtProperties.class, FilterProperties.class})
@Slf4j
public class LoginFilter implements GlobalFilter, Ordered {

    @Autowired
    private JwtProperties prop;

    @Autowired
    private FilterProperties fprop;

    //核心过滤器方法
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //设置白名单,白名单里面的请求路径,直接放行
        String path = exchange.getRequest().getPath().toString();
        List<String> allowPaths = fprop.getAllowPaths();
        for (String allowPath : allowPaths) {
            //判断path是否以allowPath开头
            if (path.startsWith(allowPath)){
                //放行
                return chain.filter(exchange);
            }
        }
        //1.获取请求中的token
        HttpCookie cookie = exchange.getRequest().getCookies().getFirst(prop.getCookieName());
        String token=null;
        if (cookie!=null){
             token=cookie.getValue();
        }
        log.info("token:",token);
        try {
            //2.解析token
            UserInfo userInfo = JwtUtils.getInfoFromToken(token, prop.getPublicKey());
            //3.放行
            if (userInfo!=null){
               return chain.filter(exchange);
            }

        } catch (Exception e) {
            e.printStackTrace();
            //如果出现异常,设置异常状态
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        }
        return exchange.getResponse().setComplete();
    }

    //过滤器执行顺序
    @Override
    public int getOrder() {
        return 0;
    }
}
功能总结:

1.使用yml配置获取公钥存储路径以及白名单路径。
2.通过路径得到公钥,自定义拦截器先去对比是否为白名单路径,若为白名单路径直接放行。
3.若不为白名单  通过cookieName得到存储的token,并使用公钥解析是否可以获得userinfo对象来判断是否放行。

二.鉴权中心模板:
yml文件:
server:
  port: 8087
spring:
  application:
    name: auth-service
  cloud:
    nacos:
      discovery:
        server-addr: http://192.168.147.129:8848

yigou:
  jwt:
    secret: yigou@Login(Auth}*^31)&javaman% # 登录校验的密钥
    pubKeyPath: D:\\tmp\\rsa\\rsa.pub # 公钥地址
    priKeyPath: D:\\tmp\\rsa\\rsa.pri # 私钥地址
    expire: 1800 # 过期时间,单位秒
    cookieName: YG_TOKEN
    cookieMaxAge: 1800
配置文件:
package com.yigou.auth.config;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import utils.RsaUtils;

import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;

@ConfigurationProperties(prefix = "yigou.jwt")
@Data
@Slf4j
public class JwtProperties {

    private String secret; // 密钥

    private String pubKeyPath;// 公钥

    private String priKeyPath;// 私钥

    private int expire;// token过期时间

    private PublicKey publicKey; // 公钥

    private PrivateKey privateKey; // 私钥
    
    private String cookieName;

    private Integer cookieMaxAge;

    /**
     * @PostContruct:在构造方法执行之后执行该方法
     */
    @PostConstruct
    public void init(){
        try {
            File pubKey = new File(pubKeyPath);
            File priKey = new File(priKeyPath);
            if (!pubKey.exists() || !priKey.exists()) {
                // 生成公钥和私钥
                RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
            }
            // 获取公钥和私钥
            this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
            this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
        } catch (Exception e) {
            log.error("初始化公钥和私钥失败!", e);
            throw new RuntimeException();
        }
    }

    // getter setter ...
}
controller
package com.yigou.auth.controller;

import com.yigou.auth.config.JwtProperties;
import com.yigou.auth.service.AuthService;
import com.yigou.common.enums.ExceptionEnum;
import com.yigou.common.exception.YgException;
import com.yigou.common.utils.CookieUtils;
import com.yigou.user.entity.TbUser;
import entity.UserInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import utils.JwtUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
@EnableConfigurationProperties(JwtProperties.class)
public class AuthController {
    @Autowired
    private JwtProperties prop;

    @Autowired
    private AuthService authService;

    //接收用户名和密码,通过密钥加密生成JWT,写入到cookie中保存
    @PostMapping("/accredit")
    public ResponseEntity<Void> authentication(@RequestParam("username") String username,
                                               @RequestParam("password") String password,
                                               HttpServletResponse response,
                                               HttpServletRequest request){
        //1.验证之后,生成JWT-token
        String token=authService.authentication(username,password);
        //2.如果token是空的,验证失败
        if(StringUtils.isEmpty(token)){
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        //3.如果不为空,说明不为空,返回token存放到cookie中
        CookieUtils.
                setCookie(request,response,prop.getCookieName(),token,prop.getCookieMaxAge(),true);
        return ResponseEntity.ok().build();
    }

    //验证用户信息,如果用户信息验证成功,返回用户信息
    @GetMapping("verify")
    public ResponseEntity<UserInfo> verify(@CookieValue("YG_TOKEN") String token,
                                           HttpServletRequest request,
                                           HttpServletResponse response){
        //1.根据token解析获取到的cookie中的token
        try {
            UserInfo userInfo = JwtUtils.getInfoFromToken(token, prop.getPublicKey());
            //重新生成token
            String newToken = JwtUtils.generateToken(userInfo, prop.getPrivateKey(), prop.getExpire());
            //重新把Token设置到cookie中,覆盖原来的cookie
            CookieUtils.setCookie(request,response,prop.getCookieName(),newToken, prop.getCookieMaxAge());
            return ResponseEntity.ok(userInfo);
        } catch (Exception e) {
            throw new YgException(ExceptionEnum.USER_TOKEN_EXISTS_FALL);
        }
    }
}
service:
package com.yigou.auth.service.impI;

import com.yigou.auth.client.UserClient;
import com.yigou.auth.config.JwtProperties;
import com.yigou.auth.service.AuthService;
import com.yigou.common.enums.ExceptionEnum;
import com.yigou.common.exception.YgException;
import com.yigou.user.entity.TbUser;
import entity.UserInfo;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import utils.JwtUtils;

@Service
public class AuthServiceImpl implements AuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties prop;
    //验证用户名和密码之后,生成jwt-token
    @Override
    public String authentication(String username, String password) {
        //1.通过用户名,密码验证是否存在
        TbUser user = userClient.getUserInfo(username, password);
        if (user==null){
            return null;
        }
        //2.生成token
        UserInfo userInfo = new UserInfo();
        userInfo.setId(user.getId());
        userInfo.setUsername(user.getUsername());
        try {
            String token = JwtUtils.generateToken(userInfo, prop.getPrivateKey(), prop.getExpire());
            return token;
        } catch (Exception e) {
            throw new YgException(ExceptionEnum.JWT_TOKEN_CREATE_fALL);
        }
    }
}
调用接口
package com.yigou.auth.client;

import com.yigou.user.api.UserApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("user-service")
public interface UserClient extends UserApi {
}
三.注意点:
1.使用Feign调用其他模块方法:

将提供模块的对外接口包以及httpclient依赖到调用模块

 <!--httpclient支持-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

调用模块的接口类继承提供模块对外开放的接口类

注释@FeignClient的参数名和提供模块在注册中心的名相同。

2.提供模块建立对外接口包

对外接口类 所提供方法 要与controller方法行映射

3.调用模块主函数开启FeignClients
package com.yigou.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class    YgAuthServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(YgAuthServiceApplication.class, args);
    }

}
四.总结

1.登录请求通过网关(登录为白名单 因此不会拦截校验),网关转发到授权中心模块
2.授权中心模块通过前端发送的用户信息feign调用用户模块查询用户信息。根据是否有该用户来判断是否使用该用户信息以及拿到yml定义的私钥路径去生成token存入cookie中。


3.若登陆成功 那么随后每次请求都会携带cookie中的token 在网关进行拦截以及公钥解密。每次鉴权都会在鉴权模块将token重新刷新一遍。

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

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

相关文章

前端页面实现【矩阵表格与列表】

实现页面&#xff1a; 1.动态表绘制&#xff08;可用于矩阵构建&#xff09; <template><div><h4><b>基于层次分析法的权重计算</b></h4><table table-layout"fixed"><thead><tr><th v-for"(_, colI…

王思聪隐形女儿曝光

王思聪"隐形"女儿曝光&#xff01;黄一鸣独自面对怀孕风波&#xff0c;坚持生下爱情结晶近日&#xff0c;娱乐圈掀起了一场惊天波澜&#xff01;前王思聪绯闻女友黄一鸣在接受专访时&#xff0c;大胆揭露了她与王思聪之间的爱恨纠葛&#xff0c;并首度公开承认&#…

VBA技术资料MF161:按需要显示特定工作表

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

重生之 SpringBoot3 入门保姆级学习(24、场景整合 kafka 消息发送服务)

重生之 SpringBoot3 入门保姆级学习&#xff08;24、场景整合 kafka 消息发送服务&#xff09; 6.4 消息发送服务 6.4 消息发送服务 访问 kafka-ui &#xff08;注意这里需要换成你自己的服务器或者虚拟机的 IP 地址&#xff0c;虚拟机可以用局域网 192.168.xxx.xxx 的地址&…

05-对混合app应用中的元素进行定位

本文介绍对于混合app应用中的元素如何进行定位。 一、app的类型 1&#xff09;Native App&#xff08;原生应用&#xff09; 原生应用是指利用Android、IOS平台官方的开发语言、开发类库、工具等进行开发的app应用&#xff0c;在应用性能和交互体验上应该是最好的。 通俗点来…

富唯智能复合机器人

复合机器人&产品概述 富唯智能复合机器人集协作机器人、移动机器人和视觉引导技术于一体&#xff0c;搭载ICD系列核心控制器&#xff0c;一体化控制整个复合机器人系统&#xff0c;并且可以对接产线系统&#xff0c;搭配我司自研的2D/3D视觉平台&#xff0c;能够轻松实现工…

【Java】已解决Java中的java.util.NoSuchElementException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决Java中的java.util.NoSuchElementException异常 一、分析问题背景 java.util.NoSuchElementException是Java中常见的运行时异常&#xff0c;它通常发生在使用迭代器&#xf…

文献阅读:SpaceX:空间转录组学的基因共表达网络估计

文献介绍 「文献题目」 SpaceX: gene co-expression network estimation for spatial transcriptomics 「研究团队」 Veerabhadran Baladandayuthapani&#xff08;美国密歇根大学&#xff09;、Xiang Zhou&#xff08;美国密歇根大学&#xff09; 「发表时间」 2022-09-30 「…

鸿蒙HarmonyOS实战:渲染控制、路由案例

条件渲染 简单来说&#xff0c;就是动态控制组件的显示与隐藏&#xff0c;类似于vue中的v-if 但是这里写法就是用if、else、else if看起来更像是原生的感觉 效果 循环渲染 我们实际开发中&#xff0c;数据一般是后端返回来的对象格式&#xff0c;对此我们需要进行遍历&#…

【决战欧洲杯巅峰】欧洲杯含金量比世界杯高吗?有走地数据分析软件吗?

关于欧洲杯和世界杯的含金量对比&#xff0c;这是一个相当主观的问题&#xff0c;因为两者的价值和重要性很大程度上取决于个人的喜好和观点。但我可以从一些关键方面来为你提供比较的视角。 首先&#xff0c;从参赛队伍和竞技水平来看&#xff0c;世界杯无疑是全球范围内最具…

探索 JQuery EasyUI:构建简单易用的前端页面

介绍 当我们站在网页开发的浩瀚世界中&#xff0c;眼花缭乱的选择让我们难以抉择。而就在这纷繁复杂的技术海洋中&#xff0c;JQuery EasyUI 如一位指路明灯&#xff0c;为我们提供了一条清晰的航线。 1.1 什么是 JQuery EasyUI&#xff1f; JQuery EasyUI&#xff0c;简单来…

尚硅谷爬虫学习第一天(2) 爬虫案例

import urllib.request# 下载网页 url_page http://www.baidu.com # url 代表下载的路径&#xff0c;filename 代表文件的名字 # urllib.request.urlretrieve(url_page,baidu.html) # 在python中 可以写变量的名字&#xff0c;也可以直接写值,这不就是java吗# 下载图片 # url_…

关于app爬虫的环境准备

摘要 有些数据需要在手机应用中才能查看&#xff0c;没有网页版&#xff0c;所以学习移动端的爬虫是有必要的。 手机系统分为安卓和苹果两大系统&#xff0c;本次讲解主要以安卓手机为例 有安卓手机的可以使用手机&#xff0c;没有的可以使用模拟器&#xff0c;本次以夜神模…

为什么带上符号位计算,仍然可以算出正负数对应的补码

文章目录 &#x1f680;前言&#x1f680; 为什么要引入原反补码✈️ 利用加法表示减法✈️ 关于数字在计算机中的存储&#x1f680; 数学解释 &#x1f680; 为什么带上符号位计算&#xff0c;仍然可以算出正负数对应的补码 &#x1f680;前言 为应付期末&#xff0c;速成数电…

N7745A Keysight 是德 多端口光功率计 简述

N7745A光功率计专为表征多端口光器件而设计&#xff0c;适用于多路复用器、PON分路器、波长选择开关&#xff08;WSS&#xff09;和ROADM等多端口器件的测试。它可以节省通道空间&#xff0c;通过LAN或USB连接进行并行编程&#xff0c;集成多种设备到单一设置&#xff0c;提高了…

[Qt] Qt Creator中配置 Vs-Code 编码风格

新建vscode-onedark.xml文档 &#xff0c;将如下内容复制进去&#xff0c;并配置到Creator中&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <style-scheme version"1.0" name"One Dark"><style name"Tex…

打造一个属于你的桌面天气 超级有个性的天气桌面

打造一个属于你的桌面天气 超级有个性的天气桌面。大家好&#xff0c;今天我们带来一个非常有趣的桌面天气工具&#xff0c;喜欢桌面diy的你&#xff0c;快点用上它吧&#xff01; 桌面上的美化&#xff0c;是许多爱美用户的心血和热爱。每个地方的美化&#xff0c;都是自己亲…

信息论与大数据安全知识点

文章目录 第一章 绪论&#xfffc;大数据概述大数据安全与加密技术 安全存储与访问控制技术访问控制概念早期的四种访问控制模型局限性总结 大数据场景下的访问控制技术 安全检索技术密文检索基础 安全处理技术同态加密 隐私保护技术 第一章 绪论&#xfffc; 大数据概述 大数…

【QT5】<重点> QT多线程

文章目录 前言 一、QThread创建多线程 二、QMutex基于互斥量的同步 三、QReadWriteLock线程同步 四、QWaitCondition线程同步 五、QSemaphore基于信号量的同步 前言 本篇记录学习QT多线程的知识&#xff0c;参考视频13.1QThread创建多线程程序_哔哩哔哩。若涉及版权问题…

【C语言】C语言入门宝典:核心概念全解析

. C语言专栏 | C专栏 &#x1f449; 个人主页 &#x1f448; 前言 此篇文章我们主要是宏观的了解一下什么是C语言&#xff0c;C语言里面有那些知识点&#xff0c;所有的知识点我们此篇只是以入门为主&#xff0c;点到为止&#xff0c;简单易懂&#xff0c;后期的文章会一 一详…