JWT、session、token区别和实现

JWT、session、token区别和实现

这里需要用到Redis和JWT。

springboot版本是3.2.1

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>
<!--redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.14.1</version>
</dependency>

<!--通用基础配置boottest/lombok/hutool-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.6</version>
</dependency>

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>1.7.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>
<!-- JWT需要的包 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.32</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.3.0</version>
</dependency>

Knief4j配置

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Knife4jConfig {
    // 创建 admin API分组
    @Bean
    public GroupedOpenApi sessionApi() {
        return GroupedOpenApi.builder()
                .group("session接口")
                .pathsToMatch("/session/**")
                .build();
    }

    // 创建 token API分组
    @Bean
    public GroupedOpenApi tokenApi() {
        return GroupedOpenApi.builder()
                .group("token接口")
                .pathsToMatch("/token/**")
                .build();
    }

    // 创建 jwt API分组
    @Bean
    public GroupedOpenApi JWTApi() {
        return GroupedOpenApi.builder()
                .group("jwt接口")
                .pathsToMatch("/jwt/**")
                .build();
    }

    @Bean
    public OpenAPI customerOpenApi() {
        return new OpenAPI().info(
                new Info()
                .title("接口文档")
                .version("1.0")
                .contact(new Contact()
                .name("bunny")));
    }
}

applicatuion.xml配置

spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

  data:
    redis:
      database: 0
      host: 192.168.31.140
      port: 6379

在这里插入图片描述

session

简介

session是浏览器存在内存中的一种验证方式,基于会话式的,如果服务器重启或者是浏览器关闭这一次会话结束才会重新生成新的session。

或者是设置session的过期时间,当到达过期时间时session也会失效

但是session好处在于,如果设定时间时半小时,如果这段时间用户一直在操作那么session会自动延长过期时间,当在这段时间内用户一直未操作那么才会过期。

优点
  1. 状态保持:会话允许服务器在多个页面请求之间保持用户的状态。这意味着用户在网站上浏览时,服务器可以跟踪他们的活动,包括登录状态、购物车内容等。
  2. 安全性:通过会话,服务器可以在不暴露用户信息的情况下跟踪用户的活动。相比于将用户信息直接存储在客户端(如cookie)中,使用会话可以提供更高的安全性。
  3. 用户体验:会话可以改善用户体验,因为它可以使用户在网站上进行交互时更加流畅,而不需要在每个页面都重新进行身份验证或者重新输入信息。
  4. 个性化服务:通过会话,网站可以根据用户的活动和偏好提供个性化的服务,比如推荐商品、定制内容等。
缺点
  1. 服务器负载:使用会话需要服务器在一段时间内保持用户状态,这可能会增加服务器的负载,尤其是在大量用户同时访问时。
  2. 存储开销:会话数据通常需要存储在服务器端,这可能会增加存储开销,尤其是对于大型网站来说。
  3. 跨设备问题:会话通常是与特定设备或浏览器相关联的,这可能导致在不同设备或浏览器上的一致性问题。
  4. 隐私问题:如果会话数据不正确处理或者未加密,可能会导致隐私问题,比如会话劫持或会话固定攻击。

session是比较安全的,缺点是不能跨设备,比如分布式中。我在和服务器A进行通信,这时候接口需要和服务器B进行通信时是不可能实现的因为是基于会话的,所以需要重新登录。

虽然说不可跨设备,在后面中我也会介绍session如何跨设备,这里需要使用到Redis,将信息存储在Redis中这样就可以做到跨设备了。

需重新登录
用户
服务器A
服务器B

关于session存储在cookies中不安全的问题?

这里的案例会在后面代码中写到。

打开控制台找到应用,存储在这里面的内容是基于domain存储的,其实就是按照域名访问的,不同域名之间是相互隔绝的,如果说存储在cookie中不安全其实存储在这里任何东西都不安全,照我来说的话。

只要代码写的没问题,不跨域之间都是相互隔绝的。只不过session会存在无法跨设备(可以自己手动解决下面会说到)。

在这里插入图片描述

具体实现

SessionController

其中需要用到HttpSessionpublic String getSessionInfo(HttpSession session),这样就可以操作session了。

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/session")
@Tag(name = "session请求相关", description = "session请求相关")
public class SessionController {
    private static final String SESSION_KEY = "session_key";

    /**
     * 判断消息是否为空
     *
     * @param message 消息
     * @return 是否为空,布尔值
     */
    private boolean isEmpty(String message) {
        return message == null || message.trim().isEmpty();
    }

    /**
     * @param session session
     * @param message 罅隙
     * @return 保存session信息成功消息
     */
    @RequestMapping("saveSessionInfo")
    @Operation(summary = "请求session")
    public String saveSessionInfo(HttpSession session, String message) {
        if (isEmpty(message)) {
            return "messages不能为空";
        }
        session.setAttribute(SESSION_KEY, message);
        return "保存session信息成功,sessionId:" + session.getId();
    }

    @RequestMapping("getSessionInfo")
    @Operation(summary = "获取session", description = "获取session")
    public String getSessionInfo(HttpSession session) {
        return "获取session消息为:" + session.getAttribute(SESSION_KEY);
    }
}
实际操作

打开浏览器,可以使用接口文档测试或者是浏览器进行测试都可以。

假设message中存储就是用户信息

在这里插入图片描述

当我们获取session时,浏览器会判断这次会话是否是上次的,如果是的就会获取用户信息,比如在用户登录时,登录完成之后前端不需要再传递userid也能获得用户的登录信息。

在这里插入图片描述

如何实现关闭或者重启服务器也可以获得session

如果说现在需要重启服务器或者关闭浏览器也能获得上次session,方法有多终,下面说的token就是一种将用户信息存储在Redis中模拟session的情况。

或者是重写HttpSession接口,因为HttpSession是使用HttpSession接口实现的,那么我们只需要重写其中的方法自定义去控制也可以完成session自己控制。

在这里插入图片描述

只要重写这三个就可以完成对session的设置、获取、移出。

在这里插入图片描述

  • 这种session有缺点,它是基于会话的,如果浏览器关闭那么session会重新登录
  • 或者服务器重启时也会重新登录因为会重新获取session
  • 又或者没有重启,没有关闭浏览器,如果是分布式的服务器。
  • 当访问服务器(server1)时,这个session是存在 server1中的
  • 如果这时请求接口是在另一个服务器上(server2)那么session会不存在。
  • 因为不是基于同一个会话,但是也有优点。
  • 假如设置session过期时间比如半小时,如果在29:59:59时这时又重新发起请求那么这个session会自动会续时长
  • 如果要想自己操作session,想让session自定义
  • 解决方案:
  • 可以重写session,因为session集成于接口 HttpSession;可以重写它可以实现自己操作session自定义
  • 或者将session放在redis中

token

目录结构见上面

Redis序列化配置RedisConfig

这个就不解释了,序列化Redis,学过Redis的都应该知道的。每个人配置方式不同,可以按照自己的方式来,这只是提供参考

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);

        // 设置key序列化为String
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 设置value序列化方式为JSON,使用GenericJackson2JsonRedisSerializer替换为默认序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        return redisTemplate;
    }
}

实际操作

先将代码粘上来,后面逐一说明其作用。

import com.alibaba.fastjson.JSON;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.http.HttpRequest;
import java.util.UUID;

@RestController
@RequestMapping("/token")
@Tag(name = "基于Redis存储session", description = "Redis存储token")
public class RedisSessionController {
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 判断消息是否为空
     *
     * @param message 消息
     * @return 是否为空,布尔值
     */
    private boolean isEmpty(String message) {
        return message == null || message.trim().isEmpty();
    }


    @RequestMapping("saveSessionByToken")
    @Operation(summary = "设置session使用token", description = "设置session使用token")
    public String saveSessionByToken(String message) {
        if (isEmpty(message)) {
            return "messages不为空";
        }

        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token, message);
        return "保存session信息成功,sessionId=" + token;
    }

    @RequestMapping("getSessionByToken")
    @Operation(summary = "获取token使用token", description = "获取token使用token")
    public String getSessionByToken(String token) {
        if (isEmpty(token)) {
            return "token不为空";
        }
        return "获取的token的信息为:" + redisTemplate.opsForValue().get(token);
    }

    @RequestMapping("getSessionByTokenWithCookie")
    @Operation(summary = "设置token使用cookies", description = "设置token使用cookies")
    public String getSessionByTokenWithCookie(HttpServletResponse response, String message) {
        if (isEmpty(message)) {
            return "message不能为空";
        }

        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token, message);
        Cookie cookie = new Cookie("token", token);
        cookie.setMaxAge(-1);// 设置有效期
        response.addCookie(cookie);
        return "保存token信息成功,token:" + token;
    }

    @RequestMapping("getByTokenWithCookie")
    @Operation(summary = "获取token使用cookies", description = "获取token使用cookies")
    public String getByTokenWithCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();// 因为cookie是一个数组,浏览器中有很多cookie
        String token = null;

        if (cookies == null) return "信息为空";

        // 当找到cookie时就跳出
        for (Cookie cookie : cookies) {
            if ("token".equals(cookie.getName())) {
                token = cookie.getValue();
                break;
            }
        }

        if (isEmpty(token)) return "token不能为空";
        Object message = redisTemplate.opsForValue().get(token);
        return "获取token使用cookies,token:" + token+";获取message使用cookies,message:" + message;// 这里不做转换JSON也可以
        // return "获取token使用cookies,token:" + token+";获取message使用cookies,message:" + JSON.toJSONString(message);// 这里不做转换JSON也可以
    }
}

设置token

将用户信息存储到Redis中,先设置唯一值将这个值放在Redis中,获取时因为这个值是唯一的所以可以在Redis中获取到。判断是否为空的函数isEmpty在上面全部代码中有的。

当然这里也需要讲你Redis服务器打开,否则也是没用的。

@RequestMapping("saveSessionByToken")
@Operation(summary = "设置session使用token", description = "设置session使用token")
public String saveSessionByToken(String message) {
    if (isEmpty(message)) {
        return "messages不为空";
    }

    String token = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set(token, message);
    return "保存session信息成功,sessionId=" + token;
}

在这里插入图片描述

可以发现,存储的值在Redis中了,那么下次就是用这个key去获取用户相关信息

获取token

请求接口时也可以正常的拿到所需要的值

@RequestMapping("getSessionByToken")
@Operation(summary = "获取token使用token", description = "获取token使用token")
public String getSessionByToken(String token) {
    if (isEmpty(token)) {
        return "token不为空";
    }
    return "获取的token的信息为:" + redisTemplate.opsForValue().get(token);
}

在这里插入图片描述

将token存储在cookie中

在cookie中可以设置setMaxAge来设置有效期

@RequestMapping("getSessionByTokenWithCookie")
@Operation(summary = "设置token使用cookies", description = "设置token使用cookies")
public String getSessionByTokenWithCookie(HttpServletResponse response, String message) {
    if (isEmpty(message)) {
        return "message不能为空";
    }

    String token = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set(token, message);
    Cookie cookie = new Cookie("token", token);
    cookie.setMaxAge(-1);// 设置有效期
    response.addCookie(cookie);
    return "保存token信息成功,token:" + token;
}

在这里插入图片描述

从cookie中获取token

这时候就不需要前端再传递当前登录时这个用户的id,直接请求这个接口就可以获取到用户登录的信息等。

因为存储在cookie中的,所以下次请求带上这个cookie就可以了。

@RequestMapping("getByTokenWithCookie")
@Operation(summary = "获取token使用cookies", description = "获取token使用cookies")
public String getByTokenWithCookie(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();// 因为cookie是一个数组,浏览器中有很多cookie
    String token = null;

    if (cookies == null) return "信息为空";

    // 当找到cookie时就跳出
    for (Cookie cookie : cookies) {
        if ("token".equals(cookie.getName())) {
            token = cookie.getValue();
            break;
        }
    }

    if (isEmpty(token)) return "token不能为空";
    Object message = redisTemplate.opsForValue().get(token);
    return "获取token使用cookies,token:" + token+";获取message使用cookies,message:" + message;// 这里不做转换JSON也可以
    // return "获取token使用cookies,token:" + token+";获取message使用cookies,message:" + JSON.toJSONString(message);// 这里不做转换JSON也可以
}

在这里插入图片描述

JWT

目录结构见最上面开头截图。

JWT这个就有点鸡肋了,感觉现在没有多少用JWT了,因为不安全,比如说使用session或者是Redis存储,举个例子;我想让这个用户下线,只需要在后面将这个用户剔除就行了。

但是JWT不行,只要她的信息没有过期而且可以被解析,她就认为这个服务器就是她主人。

而且她的信息是可以被解析的,因为信息是base64编码的,并不是加密的,你可以去网上找一个可以解析base64的,看看是否可以被解析。

当然我这里就不去网上找解析base64网站了,因为浏览器控制台为我们提供了这个函数叫atob,也是JavaScript中自带的,如果想把字符串弄成base64可以使用函数btoa()

JWT工具类JWTUtils

先把这个工具类放上,目录结构看上面的就可以了。

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONWriterUTF16JDK8UF;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.swagger.v3.core.util.Json;
import nonapi.io.github.classgraph.json.JSONUtils;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component("jwtUtils")
public class JWTUtils<T> {
    private static final String SECRET = "test123456";

    public String createToken(String key, T data, Integer expireSeconds) {
        String token = null;
        try {
            // 因为
            Date date = new Date(System.currentTimeMillis() + expireSeconds + 100000);
            token = JWT.create()
                    .withClaim(key, JSON.toJSONString(data))
                    .withExpiresAt(date)
                    .sign(Algorithm.HMAC256(SECRET));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return token;
    }

    public <T> T getTokenData(String key, String token, Class<T> tClass) {
        try {
            if (null == token || token.isEmpty()) {
                return null;
            }

            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            DecodedJWT jwt = verifier.verify(token);
            String jsonData = jwt.getClaim(key).asString();
            return JSON.parseObject(jsonData, tClass);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

实际操作

先把全部的代码都放上来。

import com.example.session.ulits.JWTUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@Tag(name = "jwt请求", description = "测试jwt请求")
@RequestMapping("/jwt")
public class JWTController {
    private static final String SESSION_KEY = "session_key";
    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private JWTUtils jwtUtils;

    /**
     * 判断消息是否为空
     *
     * @param message 消息
     * @return 是否为空,布尔值
     */
    private boolean isEmpty(String message) {
        return message == null || message.trim().isEmpty();
    }


    @RequestMapping("saveMyJWT")
    @Operation(summary = "保存JWT信息")
    public String saveMyJWT(HttpServletResponse response, String message) {
        if (isEmpty(message)) {
            return "message不能为空";
        }
        // 设置JWT的key值和消息,还有过期时间这里是10秒
        String token = jwtUtils.createToken(SESSION_KEY, message, 100);
        Cookie cookie = new Cookie("token", token);
        response.addCookie(cookie);// 将这个JWT的信息保存在cookie中
        return "保存信息token成功,token:" + token;
    }

    @RequestMapping("getByJwt")
    @Operation(summary = "获取JWT")
    public String getByJwt(String token) {
        if (isEmpty(token)) {
            return "token不能为空";
        }

        String message = (String) jwtUtils.getTokenData(SESSION_KEY, token, String.class);
        return "保存token信息成功,token的:" + message;
    }
}
保存JWT信息

这里的消息我就不写用户信息随便写一个message,看看中文如何被解析的

@RequestMapping("saveMyJWT")
@Operation(summary = "保存JWT信息")
public String saveMyJWT(HttpServletResponse response, String message) {
    if (isEmpty(message)) {
        return "message不能为空";
    }
    String token = jwtUtils.createToken(SESSION_KEY, message, 10);
    redisTemplate.opsForValue().set(token, message);
    Cookie cookie = new Cookie("token", token);
    response.addCookie(cookie);
    return "保存信息token成功,token:" + token;
}

附上JavaScript解析中文的代码,如果你的是英文的就不需要这样做了,这里只是做一个扩展。

function b64_to_utf8(str) {
  return decodeURIComponent(escape(atob(str)));
}

let base64String = "5L2g5aW977yM5LiW55WM77yB";
let chineseString = b64_to_utf8(base64String);
console.log(chineseString);

可以看到JWT的信息已经保存在cookie中了。

在这里插入图片描述

将上图中,中间的这串字符串传递到刚刚写的JavaScript代码中。可以看到信息已经被解析了。

它的字符串是用.来分割的,所以每个.后面的字符串都是base64,也就是说都可以被解析的。

function b64_to_utf8(str) {
  return decodeURIComponent(escape(atob(str)));
}

let base64String = "eyJzZXNzaW9uX2tleSI6Ilwi5oiR55qE5ZWK5a6e5omT5a6eXCIiLCJleHAiOjE3MDM0MTc5Njh9";
let chineseString = b64_to_utf8(base64String);
console.log(chineseString);

在这里插入图片描述

获取JWT信息

之后使用Java的代码获取JWT信息,这里就不使用cookie遍历了,简单的实现下。

@RequestMapping("getByJwt")
@Operation(summary = "获取JWT")
public String getByJwt(String token) {
    if (isEmpty(token)) {
        return "token不能为空";
    }

    String message = (String) jwtUtils.getTokenData(SESSION_KEY, token, String.class);
    return "保存token信息成功,token的:" + message;
}

的。

function b64_to_utf8(str) {
  return decodeURIComponent(escape(atob(str)));
}

let base64String = "eyJzZXNzaW9uX2tleSI6Ilwi5oiR55qE5ZWK5a6e5omT5a6eXCIiLCJleHAiOjE3MDM0MTc5Njh9";
let chineseString = b64_to_utf8(base64String);
console.log(chineseString);

[外链图片转存中…(img-K7dL767V-1703419158667)]

获取JWT信息

之后使用Java的代码获取JWT信息,这里就不使用cookie遍历了,简单的实现下。

@RequestMapping("getByJwt")
@Operation(summary = "获取JWT")
public String getByJwt(String token) {
    if (isEmpty(token)) {
        return "token不能为空";
    }

    String message = (String) jwtUtils.getTokenData(SESSION_KEY, token, String.class);
    return "保存token信息成功,token的:" + message;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】mysql执行查询超过30秒之后报错

出现场景&#xff1a; 用workbench&#xff0c;查本地数据库&#xff0c;执行查询超过30秒之后&#xff0c;会报错断开连接&#xff0c;报错信息&#xff1a;Error Code: 2013. Lost connection to MySQL server during query 解决办法&#xff1a; 修改配置设置&#xff0c…

数据可视化分析大屏,大数据统计UI页面源文件(信息分析平台免费PS资料)

数据可视化可以帮助数据分析者更好地理解数据&#xff0c;发现数据中的规律和趋势。通过图表和图形等可视化工具&#xff0c;数据分析者可以更快速地发现数据中的关系&#xff0c;比如相关性、趋势、异常值等。对于普通用户来说&#xff0c;理解复杂的数据可能会很困难。通过数…

【开源】基于JAVA语言的学校热点新闻推送系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 新闻类型模块2.2 新闻档案模块2.3 新闻留言模块2.4 新闻评论模块2.5 新闻收藏模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 新闻类型表3.2.2 新闻表3.2.3 新闻留言表3.2.4 新闻评论表3.2.5 新闻收藏表 四、系统展…

Anylogic Pro 8.8.x for Mac / for Linux Crack

Digital twins – a step towards a digital enterprise AnyLogic是唯一一个支持创建模拟模型的方法的模拟建模工具&#xff1a;面向过程&#xff08;离散事件&#xff09;、系统动态和代理&#xff0c;以及它们的任何组合。AnyLogic提供的建模语言的独特性、灵活性和强大性使…

上传文件到七牛云的相关代码(可直接用)

首先在七牛云注册&#xff0c;然后选择对象存储&#xff0c;按照以下配置&#xff1a; 我的存储地区是华南-广东&#xff0c;注意这个如果选择不一样&#xff0c;后面的代码调用会有一点区别&#xff0c;访问控制选择公开&#xff0c;不然回显的外链无法访问。 记住这个空间名称…

HarmonyOS构建第一个ArkTS应用(Stage模型)

构建第一个ArkTS应用&#xff08;Stage模型&#xff09; 创建ArkTS工程 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择File > New > Create Project来创建一个新工程。 选择Application应用开发…

深入了解 Android 中的应用程序签名

深入了解 Android 中的应用程序签名 一、应用程序签名介绍1.1 应用程序签名1.2 应用程序签名的意义1.3 应用程序签名的流程1.4 应用程序签名的方案1.5 签名的重要性和应用场景 二、AOSP 的应用签名2.1 AOSP的应用签名文件路径2.2 应用程序指定签名文件 三、Android Studio 的应…

10:IIC通信

1&#xff1a;IIC通信 I2C总线&#xff08;Inter IC BUS&#xff09; 是由Philips公司开发的一种通用数据总线&#xff0c;应用广泛&#xff0c;下面是一些指标参数&#xff1a; 两根通信线&#xff1a;SCL&#xff08;Serial Clock&#xff0c;串行时钟线&#xff09;、SDA&a…

物联网协议之COAP简介及Java实践

目录 前言 一、COAP简介 1、关于COAP 2、COAP特点 3、基于COAP的NB-IoT接入流程 二、CoAP协议JAVA实践 1、californium介绍 2、Java集成 3、Maven 资源引入 4、定义Server端 5、Client调用 6、运行测试 总结 前言 今天平安夜&#xff0c;祝大家圣诞快乐&#xff0c…

41 sysfs 文件系统

前言 在 linux 中常见的文件系统 有很多, 如下 基于磁盘的文件系统, ext2, ext3, ext4, xfs, btrfs, jfs, ntfs 内存文件系统, procfs, sysfs, tmpfs, squashfs, debugfs 闪存文件系统, ubifs, jffs2, yaffs 文件系统这一套体系在 linux 有一层 vfs 抽象, 用户程序不用…

前端---html 的介绍

1. 网页效果图 --CSDN 2. html的定义 HTML 的全称为&#xff1a;HyperText Mark-up Language, 指的是超文本标记语言。 标记&#xff1a;就是标签, <标签名称> </标签名称>, 比如: <html></html>、<h1></h1> 等&#xff0c;标签大多数都是…

一篇文章拿捏继承多态练习题

继承多态习题 选择题解释 简答题 选择题 下面哪种面向对象的方法可以让你变得富有( ) A: 继承 B: 封装 C: 多态 D: 抽象 ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关&#xff0c; 而对方法的调用则可以关联于具体的对象。 A: 继承 B: 模…

Linux系统编程(六):进程(下)

参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 进程与程序 1.1 main() 函数由谁调用&#xff1f; C 语言程序总是从 main 函数开始执行int main(void) int main(int argc, char *argv[]) // 如果需要向应用程序传参&#xff0c;则选择该种写法操作系…

TikTok真题第3天 | 856.括号的分数、2115. 从给定原材料中找到所有可以做出的菜、394.字符串解码

856.括号的分数 题目链接&#xff1a;856.score-of-parentheses 解法&#xff1a; leetcode官方的题解基本是每个字都认得&#xff0c;连起来就看不懂。 使用栈来解决&#xff0c;后进先出&#xff0c;后面加入的左括号&#xff0c;先弹出和右括号去匹配。定义一个记录分数…

部署LNMP动态网站

部署LNMP动态网站 安装LNMP平台相关软件1. 安装软件包2. 启动服务&#xff08;nginx、mariadb、php-fpm&#xff09;3. 修改Nginx配置文件&#xff0c;实现动静分离4. 配置数据库 上线wordpress代码 &#xff08;测试搭建的LNMP环境是否可以使用&#xff09;1. 上线php动态网站…

什么等等? I/O Wait ≠ I/O 瓶颈?

本文地址&#xff1a;什么等等&#xff1f; I/O Wait ≠ I/O 瓶颈&#xff1f; | 深入浅出 eBPF 1. I/O Wait 定义2. 测试验证3. 进一步明确磁盘吞吐和读写频繁进程4. 内核 CPU 统计实现分析5. 总结参考资料 1. I/O Wait 定义 I/O Wait 是针对单个 CPU 的性能指标&#xff0…

Bresenham 算法

1965 年&#xff0c;Bresenham 为数字绘图仪开发了一种绘制直线的算法&#xff0c;该算法同样使用于光栅扫描显示器&#xff0c;被称为 Bresenham 算法。 原理 算法的目标是选择表示直线的最佳光栅位置。Bresenhan 算法在主位移方向上每次递增一个单位。另一个方向的增量为 0…

【java爬虫】基于springboot+jdbcTemplate+sqlite+OkHttp获取个股的详细数据

注&#xff1a;本文所用技术栈为&#xff1a;springbootjdbcTemplatesqliteOkHttp 前面的文章我们获取过沪深300指数的成分股所属行业以及权重数据&#xff0c;本文我们来获取个股的详细数据。 我们的数据源是某狐财经&#xff0c;接口的详细信息在下面的文章中&#xff0c;本…

抖店对接厂家时,厂家不愿提供ERP打单如何解决?相关解答如下

我是王路飞。 现在的抖店已经不能拍单了&#xff0c;只能让厂家使用抖音电子面单发货。 关于这件事&#xff0c;我之前也说过&#xff0c;无货源商家太聪明了&#xff0c;所以平台一定会解决拍单问题的&#xff0c;无非是个时间问题罢了。 而且我认为这对我们商家来说也是个…

关于巴西网络犯罪分子使用LOLBaS和CMD脚本窃取银行账户的动态情报

一、基本内容 最近&#xff0c;一名未知身份的网络犯罪威胁行为者以使用西班牙语和葡萄牙语的用户为目标&#xff0c;破坏墨西哥、秘鲁和葡萄牙等地的网上银行账户。该攻击链主要利用社会工程学技术&#xff0c;利用葡萄牙和西班牙用户的电子邮件&#xff0c;发送带有欺骗性的…