当前位置: 首页 > news >正文

5_springboot_shiro_jwt_多端认证鉴权_禁用Cookie

1. Cookie是什么

​ Cookie是一种在客户端(通常是用户的Web浏览器)和服务器之间进行状态管理的技术。当用户访问Web服务器时,服务器可以向用户的浏览器发送一个名为Cookie的小数据块。浏览器会将这个Cookie存储在客户端,为这个Cookie设置了时间后,这个Cookie就会存储到客户端端的磁盘上。如果没有设置时间,它就会存放在浏览器所开启的进程内存中。在随后对该服务器的请求中,每次都会自动附带上这个Cookie。

​ Cookie通常包含一些基本信息,如唯一标识符、用户偏好设置、会话状态等。服务器通过解析这些Cookie来识别用户的身份、维持会话状态、个性化用户体验,甚至追踪用户行为。

每个Cookie都有特定的属性,如名称(name)、值(value)、过期时间(expiration date)、域(domain)、路径(path)等。服务器可以根据这些属性来确定何时发送或接收Cookie,以及如何处理它们。

​ 简而言之,Cookie是Web应用中实现用户状态保持和个性化服务的重要手段,但它同时也涉及到用户隐私问题,因为它们可以被用于追踪用户在互联网上的活动。出于隐私保护原因,现代浏览器都提供了对Cookie的管理和控制功能,允许用户禁用、删除或限制特定网站的Cookie使用。

​ 上一章讨论了Shiro如何保存会话,每个会话都会有一个SessionID。当一个会话被创建后,默认情况下这个SessionID作为Key被保存起来,上章保存到了Redis中。同时会被放入到Cookie中响应给浏览器,这样浏览器以后每次访问服务端,都会携带这个SessionID,服务端收到这个SessionID后,到Redis中获取Session数据,这样就能够识别到这个用户了。这就是保持会话的原理

2. 禁用Cookie

浏览器是可以禁用Cookie的,一旦浏览器禁用了Cookie后,传递SessionID没法传递到服务端,那就没法实现会话保持了。有一种办法是在所有请求的URL上添加一个请求参数如:JSESSIONID=2e8e4189-9254-4651-a77b-151f504efc3d, 这个请求参数就是SessionID,服务端读取这个参数来获取SessionID从而实现保持会话,这种办法被称为 URL重写(URL rewrite)。但是这种方法相对比较麻烦,业内采用这种方式的不多。

2.1 为什么要禁用Cookie

有一些场景比如 服务端要为小程序提供API服务,需要与小程序应用之间保持会话,还有原生的手机应用客户端程序,这些都没有使用浏览器,没法使用Cookie。

而我们的服务端需要做到为多端提供服务,不同渠道的客户端与服务端之间保持会话如何进行统一?我们的思路是禁用掉Cookie,禁用后,服务端就不会再向客户端响应Cookie数据了,取而代之的是将SessionID 作为响应数据返回给客户端,客户端收到响应后,将这个SessionID保存起来,后面每次发出请求的时候,取出这个SessionID,放入到请求头中,服务端收到请求之后,从请求头中取出SessionID,从而实现会话跟踪。

其实就是把浏览器自动发送Cookie变成了客户端程序发送SessionID,只不过方式是放在请求头中的。但是Shiro默认是开启Cookie的,即使我们禁用了Cookie,Shiro也不会到请求头中去取,这些都需要我们自己写代码去进行改造。

2.2 服务端禁用Cookie

在前一章节中,自己配置了SessionManager, 配置的是DefaultWebSessionManager 我们可以直接在服务端配置,让服务端不产生Cookie。为了方便比较,这里把禁用前和禁用后的相应信息截图出来。

现在不禁用Cookie, 用 Api fox 工具发起一次正确的登录,看看请求和响应报文分别是什么:( Api fox 工具的实际请求=>请求代码=>HTTP 可以看到请求报文)

  • 请求报文

    POST /login HTTP/1.1
    Host: 127.0.0.1:8080
    User-Agent: Apifox/1.0.0 (https://apifox.com)
    Accept: */*
    Host: 127.0.0.1:8080
    Connection: keep-alive
    Content-Type: application/x-www-form-urlencodedusername=administrator&password=admin
    
  • 响应报文
    请添加图片描述
    可以看到,服务器端产生了Cookie,并返回给了客户端。

现在在服务端禁用Cookie,代码如下(第13,15行):

package com.qinyeit.shirojwt.demos.configuration;
@Configuration
@Slf4j
public class ShiroConfiguration {// sessionManager配置@Beanpublic SessionManager sessionManager(SessionFactory sessionFactory,SessionDAO sessionDAO) {DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();// 禁用CookiewebSessionManager.setSessionIdCookieEnabled(false);// 禁用URL重写webSessionManager.setSessionIdUrlRewritingEnabled(false);// 既然cookie都禁用了,就没有必要设置它了// webSessionManager.setSessionIdCookie(cookieTemplate);// 自动配置中已经配置了sessionFactory 直接注入进来webSessionManager.setSessionFactory(sessionFactory);// 使用自定义的ShiroRedisSessionDAOwebSessionManager.setSessionDAO(sessionDAO);// 清理无效的sessionwebSessionManager.setDeleteInvalidSessions(true);// 开启session定时检查webSessionManager.setSessionValidationSchedulerEnabled(true);webSessionManager.setSessionValidationScheduler(new ExecutorServiceSessionValidationScheduler());return webSessionManager;}...
}

修改完毕,重启服务后,将redis中保存的会话数据全部清除掉,然后再执行登录:

  • 请求报文:

    POST /login HTTP/1.1
    Host: 127.0.0.1:8080
    User-Agent: Apifox/1.0.0 (https://apifox.com)
    Accept: */*
    Host: 127.0.0.1:8080
    Connection: keep-alive
    Content-Type: application/x-www-form-urlencodedusername=administrator&password=admin
    
  • 响应报文:
    请添加图片描述

可以看到,服务端不会再将SessionID响应回给客户端了。(不必关注那个 Cookie 1,那是工具里遗留的上一次的数据,没有清理掉)

现在在请求Home,返回的就是:

{"code": 401,"msg": "未登录或登录已过期"
}

因为保持会话的 SessionID丢了。也就是说虽然登录成功了,但是后面再请求服务器的时候,服务器依然不认识这个请求。

3. 保持会话的办法

引用Cookie后,无法保持会话。我们可以将会话ID作为数据响应给客户端程序,客户端程序将这个会话ID临时存储起来,下次发起请求的时候,将它放入到请求头中。(JavaScript 中使用 axios 库,在拦截器中很方便将数据放入到请求头中)。

这里做一个约定: SessionID在服务端依然叫 SessionID, 返回到客户端后,换一个叫法叫做 Access-Token , 请求头的名字也叫 Access-Token ,其实它就是SessionID

如果请求头中加入了自定义的头,这里会引发跨域问题。根据CORS(Cross-Origin Resource Sharing,跨源资源共享)规范,当发起跨域请求时,如果请求包含了自定义请求头(即非简单请求头),浏览器会先发送一个预检(OPTIONS)请求到服务器,询问服务器是否允许实际的请求发生。

这个预检请求会携带Access-Control-Request-Headers头部,列出实际请求打算发送的自定义请求头。服务器需要在响应中通过Access-Control-Allow-Headers头部告知浏览器哪些自定义请求头是可以接受的。只有当服务器确认允许这些自定义请求头之后,浏览器才会发送真实的POST、PUT、DELETE等请求。

简单请求指的是那些方法为GET、HEAD、POST,且满足以下条件之一的请求:

  • Content-Type 是 application/x-www-form-urlencoded、multipart/form-data 或 text/plain。
  • 请求头仅包含Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(满足上述简单请求条件),以及其他若干标准请求头。

只要超出简单请求的范畴,浏览器都会执行预检请求以确保跨域请求的安全性。

不过不用担心,如果我们能确保请求是在同源策略(协议、域名、端口号均相同)下进行的。如果是同源请求,则无论是否有自定义请求头,浏览器都不会发送OPTIONS预检请求。

4.改造DefaultWebSessionManager

DefaultWebSessionManager 默认从Cookie中获取。我们从SessionManager接口开始跟踪,看看它到底是如何获取sessionID的。

先看看类继承图:
在这里插入图片描述
脉络很清晰: DefaultWebSessionManager->DefaultSessionManager->AbstractValidatingSessionManager->AbstractNativeSessionManager->AbstractSessionManager->SessionManger

4.1 找改造点

而SessionManger中哪个方法是获取SessionID的呢?

public interface SessionManager {Session start(SessionContext context);Session getSession(SessionKey key) throws SessionException;
}

从第一个实现类开始,一次向下查找源代码:

  • AbstractSessionManager : 抽象类,并没有实现SessionManager接口。这个类中定义了默认失效时间为 30分钟,也可以在外部调用 setGlobalSessionTimeout(long globalSessionTimeout) 来设置失效时间

  • AbstractNativeSessionManager 抽象类,它实现了 getSession方法,在这个方法中又调用了 本类中的 lookupSession方法, lookupSession方法调用了本类中的抽象方法 doGetsession。 因为是抽象方法,所以子类中一定会实现这个doGetSession方法。

    这个类中可以set 一个 SessionListener 集合进来,这样就可以监听Session的 onStart, onStop,onExpiration

  • AbstractValidatingSessionManager 抽象类,它实现了 doGetSession方法,在doGetSession方法中,调用了 retrieveSession方法, 而retrieveSession方法又是本类中的一个抽象方法, 继续在子类中找retrieveSession 抽象方法的实现

    这个类中可以设置是否开启 用于定期验证会话的调度,和 调度器对象(SessionValidationScheduler)

  • DefaultSessionManager 类,它实现了 retrieveSession 方法,代码片段如下:

    在 retrieveSession 主要调用了本类中的getSessionId 和 retrieveSessionFromDataSource 两个方法。 getSessionId 方法被子类DefaultWebSessionManager 重写了。

    非Web应用的SessionManager 使用的就是这个类。

    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {// 可以看到 从SessionKey 中获取sessionID是有可能为null的// getSessionId 被 子类 DefaultWebSessionManager重写了Serializable sessionId = getSessionId(sessionKey);if (sessionId == null) {LOGGER.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a "+ "session could not be found.", sessionKey);return null;}Session s = retrieveSessionFromDataSource(sessionId);if (s == null) {//session ID was provided, meaning one is expected to be found, but we couldn't find one:String msg = "Could not find session with ID [" + sessionId + "]";throw new UnknownSessionException(msg);}return s;
    }
    // 从sessionKey中获取sessionID, 这个方法是 protected的,子类可以重写
    protected Serializable getSessionId(SessionKey sessionKey) {return sessionKey.getSessionId();
    }
    // 从dao中获取sessionID关联的session对象
    protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {return sessionDAO.readSession(sessionId);
    }
    

    这个类中,可以set进来 sessionDAO, cacheManager和 sessionFactory

  • DefaultWebSessionManager 类,看名字就知道它与Web会话管理有关系,有与cookie,url重写相关的属性。来看看它重写的 getSessionId方法:

    // 重写父类的方法
    @Override
    public Serializable getSessionId(SessionKey key) {Serializable id = super.getSessionId(key);// 父类方法中没有获取到SessionID,而且是 一个 web key (WebSessionKey类)if (id == null && WebUtils.isWeb(key)) {ServletRequest request = WebUtils.getRequest(key);ServletResponse response = WebUtils.getResponse(key);// 调用了下面的方法,最终调用 getReferencedSessionId 方法从cookie中获取sessionIDid = getSessionId(request, response);}return id;
    }protected Serializable getSessionId(ServletRequest request, ServletResponse response) {return getReferencedSessionId(request, response);
    }
    // 省略的代码就是在从Cookie中获取 SessionID
    private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {// 省略代码....return id;
    }
    

    代码跟踪到这里,就知道了我们该如何做了。

    1. 继承DefaultWebSessionManager

    2. 重写 protected Serializable getSessionId(ServletRequest request, ServletResponse response) 这个方法,在这个方法中从请求头中获取sessionID

    3. 在DefaultWebSessionManager 中,看到了一个方法 private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) 它是私有方法,被 protected void onStart(Session session, SessionContext context) 调用了,这个私有方法主要是创建cookie,并将cookie写回到浏览器。

      所以可以根据自己的需要,如果需要将SessionID响应到客户端,比如写入到响应头上,就可以重写onStart方法,如果没有这个需求则不用管这个方法

4.2 扩展DefaultWebSessionManager

下面我们写一个 AccessTokenWebSessionManager类,继承 DefaultWebSessionManager,并重写getSessionId 方法,从请求头上获取SessionID

package com.qinyeit.shirojwt.demos.shiro.session;
...
@Slf4j
public class AccessTokenWebSessionManager extends DefaultWebSessionManager {public AccessTokenWebSessionManager() {// 禁用Cookiesuper.setSessionIdCookieEnabled(false);// 禁用URL重写super.setSessionIdUrlRewritingEnabled(false);// 因为已经禁用了cookie,所以没有必要有这个配置了.// super.setSessionIdCookie(cookieTemplate);// 清理无效的sessionsuper.setDeleteInvalidSessions(true);// 开启session定时检查super.setSessionValidationSchedulerEnabled(true);super.setSessionValidationScheduler(new ExecutorServiceSessionValidationScheduler());}//从请求头 X-Access-Token 获取SessionIDprotected Serializable getSessionId(ServletRequest request, ServletResponse response) {String sessionId = WebUtils.toHttp(request).getHeader("X-Access-Token");if (sessionId != null) {return sessionId;}return super.getSessionId(request, response);}
}

4.3 改写Controller登录方法

原来登录成功后,是将sessionID,放入了cookie中响应给浏览器,浏览器下次请求的时候,只要cookie没有过期就会自动发送包含了sessionID的cookie。

现在cookie被禁用了,我们需要在登录成功后,将sessionID作为数据返回给客户端,客户端收到后存储起来,下次发送请求的时候,将它放入到请求头X-Access-Token上 .

package com.qinyeit.shirojwt.demos.controller;
@RestController
@Slf4j
public class AuthenticateController {...@PostMapping("/login")public Map<String, String> login(HttpServletRequest req) {Subject             subject = SecurityUtils.getSubject();Map<String, String> map     = new HashMap<>();if (subject.isAuthenticated()) {// 主体的标识,可以有多个,但是需要具备唯一性。比如:用户名,手机号,邮箱等。PrincipalCollection principalCollection = subject.getPrincipals();log.info("是否认证:{},当前登录用户主体信息:{}", subject.isAuthenticated(), principalCollection.getPrimaryPrincipal());map.put("name", principalCollection.getPrimaryPrincipal().toString());// 将sessionID作为数据返回给客户端。map.put("accessToken", subject.getSession().getId().toString());map.put("message", "登录成功");} else {...}return map;}...
}

4.3 配置SessionManager

自定义的AccessTokenWebSessionManager 需要配置成SpringBean:

@Configuration
@Slf4j
public class ShiroConfiguration {...// sessionManager配置 AccessTokenWebSessionManager@Beanpublic SessionManager sessionManager(SessionFactory sessionFactory,SessionDAO sessionDAO) {AccessTokenWebSessionManager webSessionManager = new AccessTokenWebSessionManager();// 自动配置中已经配置了sessionFactory 直接注入进来webSessionManager.setSessionFactory(sessionFactory);// 使用自定义的ShiroRedisSessionDAOwebSessionManager.setSessionDAO(sessionDAO);return webSessionManager;}...
}

5. 测试

程序启动后,先登录:

请求报文:

POST /login HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Apifox/1.0.0 (https://apifox.com)
Accept: */*
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Type: application/x-www-form-urlencodedusername=administrator&password=admin

响应结果:

{"name": "SystemAccount(account=administrator, pwdEncrypt=0b188436fd5c434e3b8ed05cfe7c107250c1ff0ac034fad089db0f017ac3cacb, salt=55ae2b2c63ddd6d4763e0c57bda9078e)","accessToken": "eb6490d0-7562-457c-b98a-69af27b8d6bc","message": "登录成功"
}

可以看到,sessionID作为数据响应回来了。这里没有客户端程序(JavaScript) ,就手动在Api fox 中添加请求头 X-Access-Token, 将 accessToken设置到工具中,然后发送请求到 home
在这里插入图片描述
请求报文:

GET / HTTP/1.1
Host: 127.0.0.1:8080
X-Access-Token: 312cc7ce-38e5-4f89-914d-4d452bb130e5
User-Agent: Apifox/1.0.0 (https://apifox.com)
Accept: */*
Host: 127.0.0.1:8080
Connection: keep-alive

响应结果:

{"sessionKeys": "[org.apache.shiro.subject.support.DefaultSubjectContext_AUTHENTICATED_SESSION_KEY, org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY]","name": "SystemAccount(account=administrator, pwdEncrypt=0b188436fd5c434e3b8ed05cfe7c107250c1ff0ac034fad089db0f017ac3cacb, salt=55ae2b2c63ddd6d4763e0c57bda9078e)"
}

6. 总结

  1. 可以在服务端应用中禁用Cookie,配置SessionManager中的 sessionIdCookieEnabled和sessionIdUrlRewritingEnabled
  2. Cookie一旦被禁用,SessionID无法传递,无法保持会话。我们可以在每个请求发出前,在请求报文中加入自定义请求头如: X-Access-Token ,将SessionID放入到这个头上
  3. 自定义一个SessionManager,继承DefaultWebSessionManager,并重写getSessionId 方法从请求头中获取SessionID

代码仓库 https://github.com/kaiwill/shiro-jwt , 本节代码在 5_springboot_shiro_jwt_多端认证鉴权_禁用Cookie 分支上.

相关文章:

5_springboot_shiro_jwt_多端认证鉴权_禁用Cookie

1. Cookie是什么 ​ Cookie是一种在客户端&#xff08;通常是用户的Web浏览器&#xff09;和服务器之间进行状态管理的技术。当用户访问Web服务器时&#xff0c;服务器可以向用户的浏览器发送一个名为Cookie的小数据块。浏览器会将这个Cookie存储在客户端&#xff0c;为这个Co…...

条形码申请指南:外地人如何成功注册香港条形码

香港条形码是打造的通行证&#xff0c;消费者对香港条码有一定的认知&#xff0c;拥有香港条形码就获得消费者对产品的认可&#xff0c;香港条形码是全球条码中具有防伪功能的条形码&#xff0c;化妆品、护肤品、保健品、包装食品等行业的产品认证&#xff0c;就有必要申请香港…...

Covalent Network借助大规模的历史Web3数据集,推动人工智能发展

人工智能在众多领域中增强了区块链的实用性&#xff0c;反之亦然&#xff0c;区块链确保了 AI 模型所使用的数据的来源和质量。人工智能带来的生产力提升&#xff0c;将与区块链系统固有的安全性和透明度融合。 Covalent Network&#xff08;CQT&#xff09;正位于这两项互补技…...

test测试类-变量学习

test测试类 作用&#xff1a;标记到类上成为测试类&#xff0c;标记到方法上成为测试方法 变量&#xff1a;测试类的变量&#xff0c;在测试类括号中应用 1、invocationCount变量 意思是这个方法应该被调用的次数。 在测试框架中&#xff0c;特别是当使用参数化测试或数据驱动…...

【DL经典回顾】激活函数大汇总(二十七)(Bent Identity附代码和详细公式)

激活函数大汇总&#xff08;二十七&#xff09;&#xff08;Bent Identity附代码和详细公式&#xff09; 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里&#xff0c;激活函数扮演着不可或…...

element-plus el-table表格默认选中某一行

需求&#xff1a;进入页面时默认选中表格第一行 <el-tableref"singleTableRef":data"tableData"highlight-current-rowrow-click"handleCurrentChange" ><el-table-column property"date" label"日期" /><…...

Vue+SpringBoot打造民宿预定管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色2.2.2 房主角色2.2.3 系统管理员角色 三、系统展示四、核心代码4.1 查询民宿4.2 新增民宿4.3 新增民宿评价4.4 查询留言4.5 新增民宿订单 五、免责说明 一、摘要 1.1 项目介绍 基于…...

基于单片机的模糊PID炉温控制系统设计

摘 要 电热炉是在工业热处理的生产中广泛使用的一种设备&#xff0c;电热炉的温度控制系统存在时变性&#xff0c;非线性&#xff0c;滞后性等特征&#xff0c;难以用常规PID的控制器对系统达到很好的控制效果。当控温精度的要求高时&#xff0c;使用传统的控制理论方法难以达…...

深入浅出落地应用分析:AI数字人「微软小冰」

hi,各位,今天要聊的是AI小冰,机缘巧合,投递了这家公司的产品,正好最近在看数字人相关的,就详细剖析下这款产品! 前言 小冰,全称为北京红棉小冰科技有限公司,前身为微软(亚洲)互联网工程院人工智能小冰团队,是微软全球最大的人工智能独立产品研发团队。作为微软全…...

【早鸟优惠|高录用|EI稳定检索】2024年虚拟现实、图像和信号处理国际学术会议(ICVISP 2024)诚邀投稿/参会!

【早鸟优惠|高录用|EI稳定检索】 2024年虚拟现实、图像和信号处理国际学术会议&#xff08;ICVISP 2024&#xff09;诚邀投稿/参会&#xff01; # 早鸟优惠 # 先投稿先送审 # #投稿免费参会、口头汇报及海报展示# 2024年虚拟现实、图像和信号处理国际学术会议&#xff08;I…...

CPU设计实战—异常处理指令

异常类型以及精确异常的处理 异常有点像中断&#xff0c;处理完还要回到原来的状态&#xff0c;所以需要对之前的状态进行保存。本CPU主要实现对以下异常的处理&#xff1a; 1.外部硬件中断 2.复位异常 3.系统调用异常&#xff08;发生在译码阶段&#xff09; 4.溢出异常&…...

Elasticsearch(13) match_phrase的使用

elasticsearch version&#xff1a; 7.10.1 match_phrase 语法 POST <index>/_search {"query": {"match_phrase": {"<field_name>": {"query": "<your_search_phrase>","slop": <max_dis…...

通过路由器监控,优化网络效率

路由器是网络的基本连接组件&#xff0c;路由器监控涉及将路由器网络作为一个整体进行管理&#xff0c;其中持续监控路由器的性能、运行状况、安全性和可用性&#xff0c;以确保更好的操作和最短的停机时间&#xff0c;因此监控路由器至关重要。 为什么路由器监控对组织很重要…...

使用canvas实现图纸标记及回显

图纸 图纸标记后的效果图 最近做的一个qms项目里面&#xff0c;需要前端在图纸上实现标记及标记后的内容还要能够回显&#xff0c;然后后端通过标记的点&#xff0c;去读取标记图纸的内容&#xff0c;如一些公式、数据之类的&#xff0c;目前实现的功能有 在图纸上面进行矩形…...

鸿蒙-自定义组件的生命周期

目录 自定义组件的生命周期 1.aboutToAppear 2.aboutToDisappear 3.onPageShow 4.onPageHide 5.onBackPress 日志输出 1.显示页面 2.页面点击返回按钮 3.页面跳转 4.页面返回 自定义组件的生命周期 先来一段列子 import router from ohos.router Entry Component…...

【Linux】自动化构建工具-make/Makefile

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 认识make/Makefile3. 了解make/Makefile原理3.1 依赖关系和依赖方法3.2 make检测的顺序3.3 PHONY:XXX 4. makefile内置符号 1. 前言 在上一篇中已经了解了【Linux】编译器-gcc/g使用&#xff0c;这次来一起…...

week07day03(power bi dax公式 零售数据业务分析)

一. 切片器(筛选)相关的三个函数 1.all &#xff08;all后面的数据意思是 不受其影响&#xff09; #ALL 筛选的是 筛选器 或 切片器#计算 销售金额 &#xff0c;并且 不受到 门店ID 控制 计算金额 CALCULATE(SUM(销售表[金额]),ALL(销售表[门店ID]))#计算 销售金额 &#x…...

rembg报错onnxruntime_providers_tensorrt.dll

报错&#xff1a; 2024-03-16 04:16:59.4413827 [E:onnxruntime:Default, provider_bridge_ort.cc:1534 onnxruntime::TryGetProviderInfo_TensorRT] D:\a_work\1\s\onnxruntime\core\session\provider_bridge_ort.cc:1209 onnxruntime::ProviderLibrary::Get [ONNXRuntimeErro…...

精酿啤酒:一口啤酒,一份享受

在繁华的都市生活中&#xff0c;我们总是匆匆忙忙&#xff0c;追求着各种目标和成就。然而&#xff0c;在这个过程中&#xff0c;我们往往忽略了生活的本质&#xff0c;那就是享受。而Fendi Club 啤酒&#xff0c;正是为那些追求品质生活的都市精英们量身打造的。 Fendi Club啤…...

git报: “fatal: detected dubious ownership in repository“

“fatal: detected dubious ownership in repository”的中文翻译是&#xff1a;“致命错误&#xff1a;检测到仓库中存在可疑的所有权问题”。 这句话意味着 Git 在检查代码仓库时发现所有权存在问题&#xff0c;可能是由于文件或目录的所有权与 Git 仓库预期的所有权不匹配。…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...