当前位置: 首页 > 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 仓库预期的所有权不匹配。…...

使用VSCode开发Django指南

使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架&#xff0c;专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用&#xff0c;其中包含三个使用通用基本模板的页面。在此…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

C++.OpenGL (14/64)多光源(Multiple Lights)

多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...

基于PHP的连锁酒店管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...

Win系统权限提升篇UAC绕过DLL劫持未引号路径可控服务全检项目

应用场景&#xff1a; 1、常规某个机器被钓鱼后门攻击后&#xff0c;我们需要做更高权限操作或权限维持等。 2、内网域中某个机器被钓鱼后门攻击后&#xff0c;我们需要对后续内网域做安全测试。 #Win10&11-BypassUAC自动提权-MSF&UACME 为了远程执行目标的exe或者b…...

C#最佳实践:为何优先使用as或is而非强制转换

C#最佳实践&#xff1a;为何优先使用as或is而非强制转换 在 C# 的编程世界里&#xff0c;类型转换是我们经常会遇到的操作。就像在现实生活中&#xff0c;我们可能需要把不同形状的物品重新整理归类一样&#xff0c;在代码里&#xff0c;我们也常常需要将一个数据类型转换为另…...

leetcode238-除自身以外数组的乘积

leetcode 238 思路 可以在不使用除法的情况下&#xff0c;利用前缀积和后缀积来实现解答 前缀积&#xff1a;对每个位置&#xff0c;计算当前数字左侧的所有数字的乘积后缀积&#xff1a;对每个位置&#xff0c;计算当前数字右侧的所有数字的乘积 结合这两种思想&#xff0…...