应用系统基于CAS实现单点登录的解决方案

news2024/11/16 19:46:55

单点登录 (SingleSign-On,SSO) ,是一种帮助用户快捷访问网络中多个站点的安全通信技术。单点登录系统基于一种安全的通信协议,该协议通过多个系统之间的用户身份信息的交换来实现单点登录。使用单点登录系统时,用户只需要登录一次,就可以访问多个系统,不需要记忆多个口令密码。

1、CAS总体架构

CAS(Central Authentication Service)是 Yale大学发起的一个企业级的、开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法。CAS的目标是允许用户访问多个应用程序只提供一次用户凭据(如用户名和密码)。

CAS 体系包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。

CAS 具有以下特点:

一个开放的,文档齐全的协议。

开源的JAVA服务器组件。

CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

文档社区化和实现的支持。

具有广泛的客户群的支持。

CAS官方文档:https://apereo.github.io/cas/5.3.x/index.html#

2、CAS单点原理

在 CAS 的整个登录过程中,有三个重要的概念。

TGT:TGT 全称叫做 Ticket Granting Ticket,这个相当于我们平时所见到的 HttpSession 的作用,用户登录成功后,用户的基本信息,如用户名、登录有效期等信息,都将存储在此。

TGC:TGC 全称叫做 Ticket Granting Cookie,TGC 以 Cookie 的形式保存在浏览器中,根据 TGC 可以帮助用户找到对应的 TGT,所以这个 TGC 有点类似与会话 ID。

ST:ST 全称是 Service Ticket,这是 CAS Sever 通过 TGT 给用户发放的一张票据,用户在访问其他服务时,发现没有 Cookie 或者 ST ,那么就会 302 到 CAS Server 获取 ST,然后会携带着 ST 302 回来,CAS Client 则通过 ST 去 CAS Server 上获取用户的登录状态。

CAS的单点登录SSO流程如下, 应用系统要做单点登录,需要跟CAS服务进行集成,首先要理解CAS集成流程和原理。

  1. 用户通过浏览器访问应用1,应用1 发现用户没有登录,于是返回 302,并且携带上一个 service 参数,让用户去 CAS Server 上登录。
  2. 浏览器自动重定向到 CAS Server 上,CAS Server 获取用户 Cookie 中携带的 TGC,去校验用户是否已经登录,如果已经登录,则完成身份校验(此时 CAS Server 可以根据用户的 TGC 找到 TGT,进而获取用户的信息);如果未登录,则重定向到 CAS Server 的登录页面,用户输入用户名/密码,CAS Server 会生成 TGT,并且根据 TGT 签发一个 ST,再将 TGC 放在用户的 Cookie 中,完成身份校验。
  3. CAS Server 完成身份校验之后,会将 ST 拼接在 service 中,返回 302,浏览器将首先将 TGC 存在 Cookie 中,然后根据 302 的指示,携带上 ST 重定向到应用1。
  4. 应用1 收到浏览器传来的 ST 之后,拿去 CAS Server 上校验,去判断用户的登录状态,如果用户登录合法,CAS Server 就会返回用户信息给 应用1。
  5. 浏览器再去访问应用2,应用2 发现用户未登录,重定向到 CAS Server。
  6. CAS Server 发现此时用户实际上已经登录了,于是又重定向回应用2,同时携带上 ST。
  7. 应用2 拿着 ST 去 CAS Server 上校验,获取用户的登录信息。
  8. 在整个登录过程中,浏览器分别和 CAS Server、应用1、应用2 建立了会话,其中,和 CAS Server 建立的会话称之为全局会话,和应用1、应用2 建立的会话称之为局部会话;一旦局部会话成功建立,以后用户再去访问应用1、应用2 就不会经过 CAS Server 了。

3、组织用户初始化

集成cas之前需要先初始化组织用户数据,各业务系统要保持统一用户信息。

3.1、启动平台

搭建平台环境,启动平台。环境搭建参考官网在线文档。

基于非源码搭建开发环境:

https://yunchengxc.yuque.com/staff-kxgs7i/public/vk24t6

基于源码搭建开发环境:

https://yunchengxc.yuque.com/staff-kxgs7i/public/dzou8y

基于Doctor部署环境:

https://yunchengxc.yuque.com/staff-kxgs7i/public/ofbgh0

3.2、录入或导入数据

用管理员账号登录平台后台,在菜单组织用户下的组织管理、用户管理、职务管理、岗位管理录入或导入组织用户数据。

4、部署CAS服务

平台对cas 5.3进行了改造,适配平台的用户表和加密策略,需要使用平台提供的cas.war。运行CAS之前需要在数据库先执行平台的脚本,CAS获取用户信息访问平台的SYS_USER表。

4.1、修改数据库连接

打开 cas\WEB-INF\classes\application.properties

修改如下配置:

#数据库配置

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/yuncheng2021?characterEncoding=UTF-8&useUnicode=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

4.2、修改单点登录用户标识类型

单点登录身份标识类型,配置通过哪个属性标识唯一用户,对应单点登录界面的登录名,默认为username。

可根据项目实际需求修改为通过phone(手机号)或email(邮箱)登录,单点登录校验后返回的用户对象中principal也会对应修改。

#单点登录身份标识类型,配置通过哪个属性标识唯一用户,对应单点登录界面的登录名,默认为username
#从用户表查询用户和单点登录返回的用户名都使用该属性
# 值范围: username(sys_user表username字段):登录名 phone(sys_user表phone字段):手机号 email(sys_user表email字段):邮箱
#yuncheng.loginNameType=username

4.3 、CAS登录登出页定制

Cas服务登录页面位置:cas\WEB-INF\classes\templates\casLoginView.html

Cas服务等出页面位置:cas\WEB-INF\classes\templates\casLogoutView.html

4.4、启动CAS服务

需要准备tomcat,把cas包放到tomcat/webapps目录下,在tomcat/bin目录下执行startup.bat(windows)或startup.sh(linux)。

启动成功后访问cas地址,界面如下图所示:

5、平台集成CAS

5.1、后端CAS配置

在application-dev(prod).yml 中配置单点登录身份标识类型(跟cas服务配置保持一致)和cas服务地址,如下所示:

yuncheng:
#单点登录身份标识类型,配置通过哪个属性标识唯一用户,对应单点登录界面的登录名,默认为username
# 从用户表查询用户和单点登录返回的用户名都使用该属性
#值范围: username(sys_user表username字段):登录名 phone(sys_user表phone字段):手机号 email(sys_user表email字段):邮箱
#loginNameType: username
#cas单点登录cas:
cas:
prefixUrl: http://cas.example.org:8443/cas

5.2、前端CAS配置

修改public/config/bootConfig.js

VUE_APP_SSO设置为cas

VUE_APP_CAS_BASE_URL配置cas服务地址

//配置即代表开启SSO单点登录,类型值包括 cas oauth2
VUE_APP_SSO:"cas",
//单点登录地址
VUE_APP_CAS_BASE_URL:"http://cas.example.org:8443/cas"

6、业务系统集成CAS

6.1、业务系统同步组织用户

业务系统用户表需要有跟单点用户表保持一致的登录标识字段,如果没有对应的用户标识字段需要添加字段。

单点登录支持三种类型的登录用户标识:username(登录名)、phone(手机号)、email(邮箱),各业务系统要保持统一的登录用户标识,并在cas服务application.properties中配置如下属性:

yuncheng.loginNameType=username(phone/ email)

6.1.1、从门户同步

1、获取所有平台用户

对应接口:List<UserActorImpl> getAllUserList()

接口地址:http://127.0.0.1:30001/api/system/sysOrgConver/getAllUserList (注:接口地址路径以实际为准)

请求类型:get

参数:无

返回值:HttpResult< List<UserActorImpl> >用户对象集合

返回值示例:

{
"code": 200,
"message": "操作成功",
"success": true,
"timestamp": 1630752126366,
"result": [{
"id": "1373536011387523073",
"loginName": "admin",
"name": "管理员",
"createTime": 1577808000000,
"deptId": "1373536559281065985",
"deptCode": " A01A06",
"orgCode": "A01A06",
" deptName": "研发部",
" email": "aaa@163.com",
"phone": "13801066662",
" weixin": "xxxxxx"
},
{
"id": "1373536011387523073",
"loginName": "admin",
"name": "管理员",
"createTime": 1577808000000,
"deptId": "1373536559281065985",
"deptCode": " A01A06",
"orgCode": "A01A06",
" deptName": "研发部",
" email": "aaa@163.com",
"phone": "13801066662",
" weixin": "xxxxxx"
}]
}

后台rest参考

@RequestMapping(value = "/getAllUserList", 请求类型 = Request请求类型.GET)
public HttpResult<?> getAllUserList() { 
return HttpResult.ok(sysOrgConverService.getAllUserList());
}

2、通过用户id获取用户信息

对应接口:UserActorImpl getUserById(String userId)

接口地址:http://127.0.0.1:30001/api/system/sysOrgConver/getUserById?userId=xxxxx (注:接口地址路径以实际为准)

请求类型:get

参数:userId

返回值:HttpResult<UserActorImpl>(用户对象)

返回值示例:

{
"code": 200,
"message": "操作成功",
"success": true,
"timestamp": 1630752126366,
"result": {
"id": "1373536011387523073",
"loginName": "admin",
"name": "管理员",
"createTime": 1577808000000,
"deptId": "1373536559281065985",
"deptCode": " A01A06",
"orgCode": "A01A06",
" deptName": "研发部",
" email": "aaa@163.com",
"phone": "13801066662",
" weixin": "xxxxxx"
} 
}

后台rest参考

@RequestMapping(value = "/getUserById", 请求类型 = Request请求类型.GET)
public HttpResult<?> getUserById(@RequestParam("userId") String userId) {
return HttpResult.ok(sysOrgConverService.getUserById(userId));
}

6.1.2、从钉钉同步

参考在线文档

https://yunchengxc.yuque.com/staff-kxgs7i/public/ir11upm4igg0egr1#OLjQt

6.2、J2ee工程集成CAS

为便于理解,本章以一个springmvc框架的demo工程为示例讲解j2ee工程如何集成cas,该demo工程目录结构如下:

注:该示例截图开发工具使用idea。

6.2.1、引入CAS客户端包

从平台提供的cas.war cas\WEB-INF\lib下拷贝cas-client-core-3.5.1.jar

到web工程WEB-INF/lib目录下

本例使用idea编译运行该工程,需要打开File->Project structure..

在Libraries中导入cas-client-core-3.5.1.jar,如下图所示:

以上配置针对非maven工程,如果业务系统是maven项目需要在pom.xml中加入如下依赖:

<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.5.1</version>
</dependency>

6.2.2、配置CAS过滤器

web.xml文件中配置1个监听器,5个过滤器(其中2个可选),顺序需要固定,cas的过滤器配置在项目其他过滤器之前。

Cas过滤器配置顺序如下:

1、SingleSignOutHttpSessionListener和SingleSignOutFilter

这两个组合用于单点登录客户端统一登出处理。

2、 AuthenticationFilter

拦截请求,校验用户是否登录cas,如果没有登录重定向到cas服务登录界面,登录成功后携带ticket回调应用地址.

3、 Cas10TicketValidationFilter

拦截请求,判断请求中有ticket参数时调用cas校验服务校验ticket有效性,校验通过返回用户信息,并将用户信息保存到session中(key: _const_cas_assertion_, 用户org.jasig.cas.client.validation.Assertion).

4、HttpServletRequestWrapperFilter

可选过滤器.

这个过滤器用于将每一个请求对应的HttpServletRequest封装为其内部定义的CasHttpServletRequestWrapper,该封装类将利用之前保存在Session或request中的Assertion对象重写HttpServletRequest的getUserPrincipal()、getRemoteUser()和isUserInRole()方法。这样在我们的应用中就可以非常方便的从HttpServletRequest中获取到用户的相关信息。

5、AssertionThreadLocalFilter

可选过滤器.

这个过滤器会把Assertion对象存放到当前的线程变量中,我们在程序的任何地方都可以从线程变量中获取当前Assertion,就不需要再从Session或request中进行解析了。这个线程变量是由AssertionHolder持有的,我们在获取当前的Assertion时也只需要通过AssertionHolder的getAssertion()方法获取即可,如:Assertion assertion = AssertionHolder.getAssertion();

web.xml 配置示例如下:

<!-- 单点注销监听器 -->
<listener>
  <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 单点注销过滤器 -->
<filter>
  <filter-name>caslogoutFilter</filter-name>
  <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
  <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>http://192.168.3.111:9080/cas</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>caslogoutFilter</filter-name>
  <url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>caslogoutFilter</filter-name>
  <url-pattern>*.jsp</url-pattern>
</filter-mapping>
<!—cas登录校验 -->
<filter>
  <filter-name>casAuthenticationFilter</filter-name>
  <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
  <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>http://192.168.3.111:9080/cas/login</param-value>
  </init-param>
  <init-param>
    <param-name>serverName</param-name>
    <param-value>http://192.168.3.240:9080</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>casAuthenticationFilter</filter-name>
  <url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>casAuthenticationFilter</filter-name>
  <url-pattern>*.jsp</url-pattern>
</filter-mapping>

<filter>
  <filter-name>casTicketValidationFilter</filter-name>
  <filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class>
  <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>http://192.168.3.111:9080/cas</param-value>
  </init-param>
  <init-param>
    <param-name>serverName</param-name>
    <param-value>http://192.168.3.240:9080</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>casTicketValidationFilter</filter-name>
  <url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>casTicketValidationFilter</filter-name>
  <url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter>
  <filter-name>casHttpServletRequestWrapperFilter</filter-name>
  <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>casHttpServletRequestWrapperFilter</filter-name>
  <url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
  <filter-name>casHttpServletRequestWrapperFilter</filter-name>
  <url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter>
  <filter-name>casAssertionThreadLocalFilter</filter-name>
  <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>casAssertionThreadLocalFilter</filter-name>
  <url-pattern>*.action</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>casAssertionThreadLocalFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>

1、参数说明:

casServerUrlPrefix:cas服务地址(ip+端口+项目名)

casServerLoginUrl:cas服务登录地址(cas服务地址+/login)

serverName:cas客户端项目地址(ip+端口)

2、filter-mapping该项目拦截的jsp和action,根据实际项目情况进行调整。

如果原有系统有自己校验登录的全局过滤器或拦截器,原有登录校验失败后再判断cas是否登录。

如原系统过滤器或拦截器从session中获取用户信息,如果能获取到说明登录过,可以继续执行,如果没有则跳转到登录页面. 修改为如果没有获取到用户信息则继续获取cas登录信息,如果能获取到则说明cas登录成功,根据cas登录信息查询原系统用户对象放到session中.

cas服务application.properties配置文件中通过配置如下属性设置cas登录身份标识类型,默认为username,值范围为:username、phone、email,如果配置为phone则需要通过手机号查询用户。

yuncheng.loginNameType=username

登录校验修改示例:

本demo工程中使用自定义拦截器校验session中是否有登录用户。

该拦截器原始代码如下:

package com.itheima.core.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.itheima.core.po.User;
/**
 * 登录拦截器
 */
public class LoginInterceptor implements HandlerInterceptor {
   @Override
   public boolean preHandle(HttpServletRequest request, 
         HttpServletResponse response, Object handler)
         throws Exception {
      // 获取请求的URL
      String url = request.getRequestURI();
      // URL:除了登录请求外,其他的URL都进行拦截控制
      if (url.indexOf("/login.action") >= 0) {
         return true;
      }
      // 获取Session
      HttpSession session = request.getSession();
      User user = (User) session.getAttribute("USER_SESSION");
      // 判断Session中是否有用户数据,如果有,则返回true,继续向下执行
      if (user != null) {
         return true;
      }
      // 不符合条件的给出提示信息,并转发到登录页面
      request.setAttribute("msg", "您还没有登录,请先登录!");
      request.getRequestDispatcher("/WEB-INF/jsp/login.jsp")
                                              .forward(request, response);
      return false;
   }
   @Override
   public void postHandle(HttpServletRequest request, 
         HttpServletResponse response, Object handler,
         ModelAndView modelAndView) throws Exception {
   }
   @Override
   public void afterCompletion(HttpServletRequest request, 
         HttpServletResponse response, Object handler, Exception ex)
         throws Exception {
   }
}

修改后代码如下:

package com.itheima.core.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.itheima.core.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.itheima.core.po.User;
/**
 * 登录拦截器
 */
public class LoginInterceptor implements HandlerInterceptor {

   // 依赖注入
   @Autowired
   private UserService userService;

   @Override
   public boolean preHandle(HttpServletRequest request, 
         HttpServletResponse response, Object handler)
         throws Exception {
      // 获取请求的URL
      String url = request.getRequestURI();
      // URL:除了登录请求外,其他的URL都进行拦截控制
      if (url.indexOf("/login.action") >= 0) {
         return true;
      }
      // 获取Session
      HttpSession session = request.getSession();
      User user = (User) session.getAttribute("USER_SESSION");
      // 判断Session中是否有用户数据,如果有,则返回true,继续向下执行
      if (user != null) {
         return true;
      }else{
         String remoteUser = request.getRemoteUser();
         if(StringUtils.isNotEmpty(remoteUser)){
            user = userService.findUserByUserCode(remoteUser);
            if(user!=null){
               session.setAttribute("USER_SESSION", user);
               return true;
            }
         }
      }
      // 不符合条件的给出提示信息,并转发到登录页面
      request.setAttribute("msg", "您还没有登录,请先登录!");
      request.getRequestDispatcher("/WEB-INF/jsp/login.jsp")
                                              .forward(request, response);
      return false;
   }
   @Override
   public void postHandle(HttpServletRequest request, 
         HttpServletResponse response, Object handler,
         ModelAndView modelAndView) throws Exception {
   }
   @Override
   public void afterCompletion(HttpServletRequest request, 
         HttpServletResponse response, Object handler, Exception ex)
         throws Exception {
   }
}

修改内容:

判断session中没有用户信息则获取remoteUser(remoteUser为单点校验成功后由org.jasig.cas.client.util.HttpServletRequestWrapperFilter过滤器将登录用户放入request中),根据remoteUser查询用户表获取用户对象放入session中,并返回ture。

6.2.3、修改项目退出逻辑

在原有系统用户注销逻辑上增加cas注销逻辑,将项目原来退出后跳转到项目登录页面改为重定向到cas退出页面。

示例:

本demo工程退出代码在com.itheima.core.web.controller.UserController.java中,退出原始代码如下:

/**
 * 退出登录
 */
@RequestMapping(value = "/logout.action")
public String logout(HttpSession session) {
    // 清除Session
    session.invalidate();
    // 重定向到登录页面的跳转方法
    return "redirect:login.action";
}

修改后代码:

/**
 * 退出登录
 */
@RequestMapping(value = "/logout.action")
public void logout(HttpSession session, HttpServletResponse response) throws IOException {
    // 清除Session
    session.invalidate();

       String service = "http://192.168.3.240:9080/boot_crm" ;
       String casLogoutUrl = "http://192.168.3.111:9080/cas/logout?service=" + service;
       // 去退出页面
       response.sendRedirect(casLogoutUrl);
    // 重定向到登录页面的跳转方法
    //return "redirect:login.action";
}

发布于 2023-01-29 21:28・IP 属地北京

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

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

相关文章

JAVA的PDF Viewer:Big Faceless PDF Viewer Crack

PDF Viewer是一个可以显示PDF文档的Swing组件。针对不需要完整 API 的客户&#xff0c;Viewer 可以作为 Applet、应用程序或通过 Java Web Start 安装&#xff0c;或嵌入到 Swing 应用程序中。 产品概览 打印、保存、文本搜索、表单、数字签名和注释是众多可用功能中的一部分 -…

IB 课程的挑战(二)

以下我想换个角度用自身的经历以及多年来教学的经验去分享修读 IB课程的挑战。 &#xff08;一&#xff09;必修文学科2022尽管我于会考中文获得「A」级成绩&#xff0c;但面对IB课程的中国文学 (IB Chinese A Literature)时&#xff0c;却仍感到十分吃力。我之前从未接触过中国…

网络IO模型

1 介绍一提到网络IO&#xff0c;甚至一些网络框架&#xff0c;就无法避免遇到阻塞、非阻塞、同步、异步的概念&#xff0c;要理解这些概念&#xff0c;先要清楚网络IO是什么&#xff0c;以及网络IO如何工作。网络IO本质上也是IO的一种&#xff0c;就是数据的输入输出&#xff0…

护网行动(防守方)linux服务器通用安全加固指南(1)

实验所属系列&#xff1a;Linux服务器搭建/Linux服务安全 Linux 是一个开放式系统&#xff0c;可以在网络上找到许多现成的程序和工具&#xff0c;这既方便了用户&#xff0c;也方便了黑客&#xff0c;因为他们也能很容易地找到程序和工具来潜入 Linux 系统&#xff0c;或者盗…

CnOpenData中国观鸟记录数据

一、数据简介 观鸟&#xff0c;是指在自然环境中利用望远镜等观测记录设备在不影响野生鸟类正常生活的前提下观察鸟类的一种科学性质的户外活动。 鸟类&#xff0c;是生物多样性中最具指示性的类群。由全国各地观鸟爱好者们共同记录的观鸟信息&#xff0c;能够展示中国各地区的…

cesium +vue3 +vite 实现点线面功能

第一步&#xff1a;搭建vue3项目 1、npm在相应文件夹下创建 npm create vitelatest2、选择项目名称 3、选择vue 4、选择是否使用typescript 或者JavaScript 5、记得在终端npm install一下就可以启动项目了 第二步&#xff1a;引入cesiumJS 1、npm引入 npm i cesium vite…

海康Visionmaster-VM2D,VM3D,VM深度学习对电脑配置要求

为确保VM算法平台能正常安装及运行&#xff0c;对PC配置有所要求。 第一&#xff1a;推荐配置&#xff1a; 操作系统&#xff1a;Windows7/10&#xff08;64位中文操作系统&#xff09; CPU&#xff1a;Intel Core i7-6700 3.4GHz 或以上&#xff08;如需使用CPU相关深度学习…

32岁面试字节软件测试岗,想不到居然这么简单......

前段时间有个在小公司干了七八年的朋友离职了&#xff0c;想要拼一拼大厂&#xff0c;于是他选择了字节跳动。面试总共花费了 20 天左右&#xff0c;包含了 4 轮电话面试、1 轮笔试、1 轮主管视频面试、1 轮hr视频面试。 据他了解到的信息&#xff0c;其实去字节面试的人很多&…

爬虫(一)爬虫基本概念

爬虫概念网络爬虫 (网页蜘蛛, 网络机器人)爬虫就是模拟客户端发送网络请求, 接受请求对应的响应, 一种按照一定的规则,自动地抓取互联网信息的程序。理论上来说, 只要是用户通过客户端(浏览器)能够做到事情, 爬虫都能够去做。爬虫: 模拟客户端访问, 抓取数据。反爬: 保护重要数…

3、边界值分析

如何选定边界值 很难提供一份如何进行边界值分析的“详细说明”&#xff0c;因为这种方法需要一定程度的创造性&#xff0c;以及对问题采取一定程度的特殊处理办法。 但是可以提供一些通用指南&#xff1a; 如果输入条件规定了一个输入值范围&#xff0c;那么应针对范围的边…

WY49 数对

【答案解析】&#xff1a; 暴力破解&#xff1a;将 x 和 y 分别遍历 [1, n] &#xff0c;进行判断当 x % y > k 时统计计数 count 即可,但是这样的话当 n 的值非常大 的时候循环次数将非常恐怖&#xff0c;需要循环 n^2 次。 更优解法&#xff1a; 假设输入 n10 , k3 &#…

QT/C++——对话框

一、标准对话框 #include "widget.h" #include <QVBoxLayout> #include <QHBoxLayout>Widget::Widget(QWidget *parent): QWidget(parent) {btcolor new QPushButton("setcolor");bterrm new QPushButton("errmsg");btfile new …

检索业务:构建结果数据与分析

明确返回的结果对象数据 结果对象 Data public class SearchResult {/*** 查到的所商品信息*/private List<SkuEsModel> products;private Integer pageNum;//当前页面private Long total;//总记录数private Integer totalPages;//总页码private List<CatalogVo&g…

linux安装python3.10.9

Linux系统安装python3.10.9Linux系统安装python3.10.9查看系统自带python信息python官网下载安装包解压安装包上传安装包到服务器解压安装包进入解压后的目录安装python使用的依赖安装python使用的依赖完成编译安装python设置软连接设置python软连接设置pip软连接配置环境变量使…

【Linux】线程概念 | 同步

除了线程互斥&#xff0c;我们还有线程同步&#xff0c;来康康吧 文章目录1.为什么需要同步2.生产消费模型2.1 生产者和消费者的关系2.2 以简单代码为例2.3 并发3.条件变量接口3.1 init/destroy3.2 pthread_cond_wait3.3 pthrea_cond_signal/broadcast3.4 代码示例3.4.1 小bug3…

Python数据可视化(三)绘制统计图形大全

3.1 柱状图以 Python 代码的形式讲解柱状图的绘制原理&#xff0c;这里重点讲解 bar()函数的使用方法。代码&#xff1a;import matplotlib as mpl import matplotlib.pyplot as plt mpl.rcParams["font.sans-serif"]["SimHei"] mpl.rcParams["axes.u…

JavaScript Hashmap散列算法

文章目录前言一、什么是散列表二、为何使用散列算法三、实现散列算法1.字典结构2.散列函数3.put 设置/更新4. 获取值四、使用HashMap处理冲突1.分离链接2.线性探查总结前言 一、什么是散列表 散列表是字典(Dictionary)的一种实现. 集合以[值, 值]形式存储, 字典则以[键, 值]对…

不让袁树雄上春晚,导演于蕾是真英明

自从央视兔年春晚结束后&#xff0c;互联网上面就出现各种吐槽声音&#xff0c;尤其是关于热歌《早安隆回》的话题。说起歌曲《早安隆回》&#xff0c;这是2022年最火的歌曲之一&#xff0c;民间的呼声也一直很高&#xff0c;都希望能够登上央视春晚舞台。 不过随着央视春晚的结…

在甲骨文云容器实例(Container Instances)上部署Alist

在甲骨文云容器实例上部署Oracle Linux 8 Desktop加强版创建容器实例查看容器实例的公共 IP 地址查看密码使用Alist甲骨文云推出了容器实例&#xff0c;这是一项无服务器计算服务&#xff0c;可以即时运行容器&#xff0c;而无需管理任何服务器。今天我们尝试一下通过容器实例部…

数据结构与算法(一)(Python版)

python基础知识整理二 文章目录算法分析运行时间检测大O表示法“变位词”判断问题解法一: O(logn)解法二&#xff1a;暴力法 O(n!)解法三 O(n)Python数据类型的性能示例判断是否是素数求素数个数基本结构——线性结构栈抽象数据类型以及Python实现用Python实现抽象数据结构栈栈…