前后端分离项目集成单点登录项目CAS5.18

embedded/2025/1/6 7:55:38/

之前我在项目中使用过CAS作为单点登录服务,不过那些项目,不管是asp.net MVC项目,还是java的spring boot项目,是前后端不分的,只要使用CAS的客户端(对于asp.net mvc项目来说,cas的客户端就是DotNetCasClient.dll,而java项目,cas客户端就是一些java包),然后配置一下就好了。现在普遍前后端分离,该如何使用CAS呢?

一、工作原理及流程

简单而言,就是:前端的数据,和对数据的操作,来源于后端,前端不需要单点登录,真正使用单点登录的是后端。前端不需要考察登录状态,只需访问后端,后端自然会处理,仅仅只需要考察到后端返回202编码时跳转到单点登录页面。具体流程如下:

前端向后端请求或提交数据,后端发现前端尚未登录,于是返回一个202状态码;前端接收到此状态码后,转向单点登录;登录成功后,跳转到后端;后端之后再跳转到前端。

在这里插入图片描述
下面以实例做详细说明。

二、运行环境

我的前后端分离项目,后端是Spring Boot程序,以此为例做说明。

单点登录服务:CAS 5.18
CAS客户端:cas-client-core 3.3.2
后端框架:Spring Boot2
前端框架:VUE 3

三、后端

后端主要是设置一个过滤器,对来自前端的请求进行是否已登录检查。过滤器基本来自于CAS客户端。但这个客户端需要稍为改写。原因据说是CAS无法将来自前端的AJAX请求直接重定向到单点登录服务器,所以改为向前端返回202状态码,然后前端接收到此状态码后,自行重定向到单点登录。这个我目前还不是很理解,需要进一步了解的同学可自行查阅附录参考文章。
在这里插入图片描述

1、pom.xml

<!-- 单点登录相关 -->
<dependency><groupId>org.jasig.cas.client</groupId><artifactId>cas-client-core</artifactId><version>3.3.2</version>
</dependency>
<dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10.5</version>
</dependency>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-cas</artifactId>
</dependency>
<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-taglibs</artifactId>
</dependency>

2、application.yml

cas:casSigntouServerUrlPrefix: https://192.168.10.250:11443/cas250/logoutcasServerLoginUrl: https://192.168.10.250:11443/cas250/logincasValidationServerUrlPrefix: https://192.168.10.250:11443/cas250clientBackendUrl: http://192.168.10.8:8090 #后端地址clientWebUrl: http://192.168.10.8:8080/#/afterLogin #前端地址

3、过滤器1,禁用ssl认证

不知为啥要这么做,照抄参考文章。反正我的前端和后端都不支持https。

import javax.net.ssl.*;
import javax.servlet.*;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;public class IgnoreSSLValidateFilter implements Filter {static {//执行设置,禁用ssl认证try {TrustManager[] trustAllCerts = {new X509TrustManager() {@Overridepublic X509Certificate[] getAcceptedIssuers() {return null;}@Overridepublic void checkClientTrusted(X509Certificate[] arg0, String arg1)throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] arg0, String arg1)throws CertificateException {}}};SSLContext sc = SSLContext.getInstance("SSL");sc.init(null, trustAllCerts, new SecureRandom());HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());HostnameVerifier allHostsValid = new HostnameVerifier() {@Overridepublic boolean verify(String hostname, SSLSession session) {return true;}};HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (KeyManagementException e) {e.printStackTrace();}}@Overridepublic void init(javax.servlet.FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {chain.doFilter(request, response);}@Overridepublic void destroy() {}}

4、过滤器2,改写重定向为返回202状态码

其实就是String xRequested =request.getHeader(“x-requested-with”); if(“XMLHttpRequest”.equals(xRequested){…}这里做了更改。不明觉厉。

import org.jasig.cas.client.authentication.*;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.servlet.FilterConfig;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationFilter extends AbstractCasFilter {private String casServerLoginUrl;private boolean renew = false;private boolean gateway = false;private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null;private static final Map<String, Class<? extends UrlPatternMatcherStrategy>> PATTERN_MATCHER_TYPES = new HashMap();private static final Logger LOGGER = LoggerFactory.getLogger(MyAuthenticationFilter.class);public MyAuthenticationFilter() {}@Overrideprotected void initInternal(FilterConfig filterConfig) throws ServletException {if (!this.isIgnoreInitConfiguration()) {super.initInternal(filterConfig);this.setCasServerLoginUrl(this.getPropertyFromInitParams(filterConfig, "casServerLoginUrl", (String)null));LOGGER.trace("Loaded CasServerLoginUrl parameter: {}", this.casServerLoginUrl);this.setRenew(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "renew", "false")));LOGGER.trace("Loaded renew parameter: {}", this.renew);this.setGateway(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "gateway", "false")));LOGGER.trace("Loaded gateway parameter: {}", this.gateway);String ignorePattern = this.getPropertyFromInitParams(filterConfig, "ignorePattern", (String)null);LOGGER.trace("Loaded ignorePattern parameter: {}", ignorePattern);String ignoreUrlPatternType = this.getPropertyFromInitParams(filterConfig, "ignoreUrlPatternType", "REGEX");LOGGER.trace("Loaded ignoreUrlPatternType parameter: {}", ignoreUrlPatternType);if (ignorePattern != null) {Class<? extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = (Class)PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);if (ignoreUrlMatcherClass != null) {this.ignoreUrlPatternMatcherStrategyClass = (UrlPatternMatcherStrategy) ReflectUtils.newInstance(ignoreUrlMatcherClass.getName(), new Object[0]);} else {try {LOGGER.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);this.ignoreUrlPatternMatcherStrategyClass = (UrlPatternMatcherStrategy)ReflectUtils.newInstance(ignoreUrlPatternType, new Object[0]);} catch (IllegalArgumentException var6) {LOGGER.error("Could not instantiate class [{}]", ignoreUrlPatternType, var6);}}if (this.ignoreUrlPatternMatcherStrategyClass != null) {this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);}}String gatewayStorageClass = this.getPropertyFromInitParams(filterConfig, "gatewayStorageClass", (String)null);if (gatewayStorageClass != null) {this.gatewayStorage = (GatewayResolver)ReflectUtils.newInstance(gatewayStorageClass, new Object[0]);}String authenticationRedirectStrategyClass = this.getPropertyFromInitParams(filterConfig, "authenticationRedirectStrategyClass", (String)null);if (authenticationRedirectStrategyClass != null) {this.authenticationRedirectStrategy = (AuthenticationRedirectStrategy)ReflectUtils.newInstance(authenticationRedirectStrategyClass, new Object[0]);}}}@Overridepublic void init() {super.init();CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");}@Overridepublic final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;if (this.isRequestUrlExcluded(request)) {LOGGER.debug("Request is ignored.");filterChain.doFilter(request, response);} else {HttpSession session = request.getSession(false);Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;if (assertion != null) {filterChain.doFilter(request, response);} else {String serviceUrl = this.constructServiceUrl(request, response);String ticket = this.retrieveTicketFromRequest(request);boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {LOGGER.debug("no ticket and no assertion found");String modifiedServiceUrl;if (this.gateway) {LOGGER.debug("setting gateway attribute in session");modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);} else {modifiedServiceUrl = serviceUrl;}LOGGER.debug("Constructed service url: {}", modifiedServiceUrl);String xRequested =request.getHeader("x-requested-with");if("XMLHttpRequest".equals(xRequested)){response.getWriter().write("{\"code\":202, \"msg\":\"no ticket and no assertion found\"}");}else{String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);LOGGER.debug("redirecting to \"{}\"", urlToRedirectTo);this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);}} else {filterChain.doFilter(request, response);}}}}public final void setRenew(boolean renew) {this.renew = renew;}public final void setGateway(boolean gateway) {this.gateway = gateway;}public final void setCasServerLoginUrl(String casServerLoginUrl) {this.casServerLoginUrl = casServerLoginUrl;}public final void setGatewayStorage(GatewayResolver gatewayStorage) {this.gatewayStorage = gatewayStorage;}private boolean isRequestUrlExcluded(HttpServletRequest request) {if (this.ignoreUrlPatternMatcherStrategyClass == null) {return false;} else {StringBuffer urlBuffer = request.getRequestURL();if (request.getQueryString() != null) {urlBuffer.append("?").append(request.getQueryString());}String requestUri = urlBuffer.toString();return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);}}static {PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class);PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class);PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class);}
}

5、注册配置

import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.CharacterEncodingFilter;import javax.servlet.Filter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;@Configuration
public class SsoFilterConfig implements Serializable, InitializingBean {private static final Logger LOGGER = LoggerFactory.getLogger(SsoFilterConfig.class);public static final String CAS_SIGNOUT_FILTER_NAME = "CAS Single Sign Out Filter";public static final String CAS_AUTH_FILTER_NAME = "CAS Filter";public static final String CAS_IGNOREL_SSL_FILTER_NAME = "CAS Ignore SSL Filter";public static final String CAS_FILTER_NAME = "CAS Validation Filter";public static final String CAS_WRAPPER_NAME = "CAS HttpServletRequest Wrapper Filter";public static final String CAS_ASSERTION_NAME = "CAS Assertion Thread Local Filter";public static final String CHARACTER_ENCODING_NAME = "Character encoding Filter";//CAS服务器退出地址@Value("${cas.casSigntouServerUrlPrefix:https://127.0.0.1:8443/cas/logout}")String casSigntouServerUrlPrefix;//CAS服务器登录地址@Value("${cas.casServerLoginUrl:https://127.0.0.1:8443/cas/login}")String casServerLoginUrl;//CAS服务器地址@Value("${cas.casValidationServerUrlPrefix:https://127.0.0.1:8443/cas}")String casValidationServerUrlPrefix;//客户端地址(即本系统地址?)@Value("${cas.clientBackendUrl:http://127.0.0.1:1234}")String clientBackendUrl;public SsoFilterConfig() {}/*** 单点登出功能,放在其他filter之前* casSigntouServerUrlPrefix为登出前缀:https://123.207.122.156:8081/cas/logout** @return*/@Bean@Order(0)public FilterRegistrationBean getCasSignoutFilterRegistrationBean() {FilterRegistrationBean registration = new FilterRegistrationBean();registration.setFilter(getCasSignoutFilter());registration.addUrlPatterns("/*", "*.html");registration.addInitParameter("casServerUrlPrefix", casSigntouServerUrlPrefix);registration.setName(CAS_SIGNOUT_FILTER_NAME);registration.setEnabled(true);return registration;}@Bean(name = CAS_SIGNOUT_FILTER_NAME)public Filter getCasSignoutFilter() {return new SingleSignOutFilter();}/*** 忽略SSL认证** @return*/@Bean@Order(1)public FilterRegistrationBean getCasSkipSSLValidationFilterRegistrationBean() {FilterRegistrationBean registration = new FilterRegistrationBean();registration.setFilter(getCasSkipSSLValidationFilter());registration.addUrlPatterns("/*", "*.html");registration.setName(CAS_IGNOREL_SSL_FILTER_NAME);registration.setEnabled(true);return registration;}@Bean(name = CAS_IGNOREL_SSL_FILTER_NAME)public Filter getCasSkipSSLValidationFilter() {return new IgnoreSSLValidateFilter();}/*** 负责用户的认证* casServerLoginUrl:https://123.207.122.156:8081/cas/login* casServerName:https://123.207.122.156:8080/tdw/alerts/** @return*/@Bean@Order(2)public FilterRegistrationBean getCasAuthFilterRegistrationBean() {FilterRegistrationBean registration = new FilterRegistrationBean();final Filter casAuthFilter = getCasAuthFilter();registration.setFilter(casAuthFilter);registration.addUrlPatterns("/*", "*.html");registration.addInitParameter("casServerLoginUrl", casServerLoginUrl);registration.addInitParameter("serverName", clientBackendUrl);registration.setName(CAS_AUTH_FILTER_NAME);registration.setEnabled(true);return registration;}@Bean(name = CAS_AUTH_FILTER_NAME)public Filter getCasAuthFilter() {return new MyAuthenticationFilter();}/*** 对Ticket进行校验* casValidationServerUrlPrefix要用内网ip* casValidationServerUrlPrefix:https://123.207.122.156:8081/cas* casServerName:https://123.207.122.156:8080/tdw/alerts/** @return*/@Bean@Order(3)public FilterRegistrationBean getCasValidationFilterRegistrationBean() {FilterRegistrationBean registration = new FilterRegistrationBean();final Filter casValidationFilter = getCasValidationFilter();registration.setFilter(casValidationFilter);registration.addUrlPatterns("/*", "*.html");registration.addInitParameter("casServerUrlPrefix", casValidationServerUrlPrefix);registration.addInitParameter("serverName", clientBackendUrl);registration.setName(CAS_FILTER_NAME);registration.setEnabled(true);return registration;}@Bean(name = CAS_FILTER_NAME)public Filter getCasValidationFilter() {return new Cas20ProxyReceivingTicketValidationFilter();}/*** 设置response的默认编码方式:UTF-8。** @return*/@Bean@Order(4)public FilterRegistrationBean getCharacterEncodingFilterRegistrationBean() {FilterRegistrationBean registration = new FilterRegistrationBean();registration.setFilter(getCharacterEncodingFilter());registration.addUrlPatterns("/*", "*.html");registration.setName(CHARACTER_ENCODING_NAME);registration.setEnabled(true);return registration;}@Bean(name = CHARACTER_ENCODING_NAME)public Filter getCharacterEncodingFilter() {CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();characterEncodingFilter.setEncoding("UTF-8");return characterEncodingFilter;}@Beanpublic FilterRegistrationBean casHttpServletRequestWrapperFilter(){FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();authenticationFilter.setFilter(new HttpServletRequestWrapperFilter());authenticationFilter.setOrder(6);List<String> urlPatterns = new ArrayList<>();urlPatterns.add("/*");authenticationFilter.setUrlPatterns(urlPatterns);return authenticationFilter;}@Overridepublic void afterPropertiesSet() throws Exception {}
}

6、给前端的相关接口

//import 自定义的 巴拉巴拉.server.modules.utils.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Api(tags = "cas单点登录接口")
@Controller
@RequestMapping("/cas")
public class LoginController {@Value("${cas.clientWebUrl}")String webUrl;@ApiOperation(value = "测试接口")@GetMapping("/test")@ResponseBodypublic String login(HttpServletRequest request) {System.out.println(request.getRemoteUser().toString());return "success";}@ApiOperation(value = "票根验证", notes = "验证通过时转发到前端主页")@GetMapping("/checkTicket")public void index(HttpServletResponse response) throws IOException {// 前端页面地址response.sendRedirect(webUrl);}@ApiOperation(value = "获取系统当前登录的用户名与用户名白名单")@GetMapping("/getUsername")@ResponseBodypublic Result getUsername(HttpServletRequest request) {Map<String, String> map = new HashMap<>();map.put("currentUser", request.getRemoteUser());return Result.ok().put("data",map);}
}

四、前端

前端不需要进行登录状态检查。它只在收到后端返回的202状态码时才主动重定向到单点登录。除此之外,它提供了一个登录后返回页面,记录一下登录账号,并凭此到后端获取该账号的详细信息。当然专门设置一个返回页面不是必须的。可见,由始至终,前端并没有校验登录状态,也没有存储什么登录状态,它是否登录,以及是否需要登录,都是由后端控制的。在它向后端请求数据、或提交数据时,由后端的过滤器处理。

前端涉及到处理的模块主要有:

1)请求处理程序
2)路由卫士
3)登录后返回页面
在这里插入图片描述

1、请求处理程序request/index.js

import axios from "axios";let redirectFlag = false;
const onResponse = (response) => {const code = response.data.code;if (code === 202 && !redirectFlag) {//转向单点的登录redirectFlag = true;window.location.href = appConfig.cas.casRedirectUrl;} else {。。。}
};。。。const getService = (config) => {const service = axios.create(config);// 添加请求拦截器service.interceptors.request.use(onRequest, onRequestError);// 添加响应拦截器service.interceptors.response.use(onResponse, onResponseError);return service;
};const DEFAULTCONFIG = {baseURL: "/api", // 所有的请求地址前缀部分timeout: 60000, // 请求超时时间毫秒withCredentials: true, // 异步请求携带cookieheaders: {"X-Requested-With": "XMLHttpRequest",},
};const service = getService(DEFAULTCONFIG);export default service;

2、路由卫士router/index.js

import { createRouter, createWebHashHistory } from "vue-router";const router = createRouter({history: createWebHashHistory(),routes,//一些路由规则
});// 路由守卫
const normalRouter = (to, from, next) => {if (to.path === "/afterLogin") {//必须保证登录返回页面顺利加载next();} else {//这部分代码与结合单点登录无关,由前端项目按照自己的逻辑自行定义myRouterDo(to, from, next);}
};router.beforeEach(normalRouter);export default router;//自定义逻辑
function myRouterDo(to, from, next) {。。。
}

3、登录后返回页面

以下代码中,getUserNameCAS()向后端获取登录账号;而在loginDo方法中,我会凭登录后账号向后端请求该账号的详细信息,做一些。

1)页面afterLogin.vue

在这里插入图片描述

<template><div><h1>===== 欢迎使用VUE3 =====</h1></div><div v-if="state.userName === null"><span>未登录</span></div><div v-if="state.userName !== null"><div class="flex-box navis"><div>框架首页</div><div><el-link type="primary" underline="true" href="/#/business">业务首页</el-link></div></div><div class="flex-box user-info"><div><span>用户名:</span><span>{{ state.userName }}</span></div><div><el-button type="primary" @click="logout" class="green-button">登出</el-button></div></div></div>
</template><script setup>
import { reactive, onMounted, nextTick } from "vue";
import { getUserName as getUserNameApi } from "@/api/cas.js";
import { logout } from "@/utils/auth.js";const state = reactive({userName: null
});onMounted(() => {nextTick(() => {getUserInfo();});
});function getUserInfo() {getUserNameApi().then((res) => {state.userName = res.data.currentUser;});
}
</script><style scoped>
</style>

2)后端接口api/cas.js

import { request } from "@/request";const appConfig = require("/public/web-config.json");
const $cas = appConfig.cas;//目前没有用到
export const login = () => {return request({url: `/cas/checkTicket`,method: "get",});
};export const logout = () => {window.location.href = $cas.casLogout;
};export const getUserName = () => {return request({url: `/cas/getUsername`,method: "get",});
};

oAuth2_704">五、单点登录oAuth2

似乎单点登录oAuth2有许多相同之处。oAuth2,使用第三方系统作为身份识别和授权服务,主要是授权。比如,我们使用微信来授权登录。当用户访问我们系统A时,首先转到微信,微信会询问用户,你同意系统A获取你的微信身份信息吗?用户同意后,微信就将用户的一些身份信息返回到我们系统A,我们就拿到了用户的身份,然后可以认为他登录了。从无须打造自己的登录服务这点来看,单点登录oAuth2的效果是一样的。

但是,oAuth2并没有所谓一处登录,处处可使用的效果。每个使用微信的应用都是独立的,你登录了系统A,并不能直接访问系统B。同时,单点登录有单点登出,即从一个应用登出以后,其他的应用也都登出了;而oAuth2显然也没有这种功能。

我的感觉是,局域网应用,适合使用单点登录;互联网,适合使用oAuth2

六、小结

CAS作为单点登录服务,应该是很成熟的吧,用起来,只要配置得当,还是很丝滑的。不过,我印象中,CAS部署起来十分麻烦。调试也很麻烦,以前经常遇到重定向次数过多的问题。如果这次不是有现成的部署,我不一定会选用它来做单点服务器。

附录:参考资料

后端代码基本上抄自以下文章。

springboot+vue集成cas单点登录

SpringBoot+Vue+CAS 前后端分离实现单点登录方案


http://www.ppmy.cn/embedded/150575.html

相关文章

Vue 解决浏览器刷新路由参数丢失问题 全局统一配置无需修改组件

在路由跳转的时候,我们经常会传一些参数过去,然后通过传过来的参数调用接口获取相关数据,但是刷新浏览器的时候路由参数会丢失。此时页面报错误了,如何通过全局配置的方式,不需要修改任何组件 实现刷新浏览器保存参数? 实现方式如下: 首先在router/index.js里添加参数管…

Java中如何实现线程安全的单例模式?

目录 1、懒汉式&#xff08;线程安全&#xff09; 2、饿汉式&#xff08;线程安全&#xff09; 3、双重校验锁&#xff08;线程安全&#xff09; 4、静态内部类&#xff08;推荐&#xff09; 5、枚举&#xff08;最佳方法&#xff09; 6、总结 在Java中&#xff0c;实现线…

.net core 的函数实现

Python基础 Python是一种广泛使用的高级编程语言&#xff0c;以其简洁易读的语法和强大的功能而闻名。它被广泛应用于数据分析、人工智能、网站开发、自动化脚本及其他众多领域。本文将详细介绍Python的基础知识&#xff0c;包括其安装及环境配置、基本语法、数据类型、控制结…

【机器学习】穷理至极,观微知著:微积分的哲思之旅与算法之道

文章目录 微积分基础&#xff1a;理解变化与累积的数学前言一、多重积分的高级应用1.1 高维概率分布的期望值计算1.1.1 多维期望值的定义1.1.2 Python代码实现1.1.3 运行结果1.1.4 结果解读 1.2 特征空间的体积计算1.2.1 单位球体的体积计算1.2.2 Python代码实现1.2.3 运行结果…

高频生活场景带动低频金融服务,美团企业版点燃场景金融建设引擎

撰稿 | 多客 来源 | 贝多财经 近年来&#xff0c;金融与生活、生产场景深度融合衍生出的“场景金融”逐渐进入大众视野。其中&#xff0c;对公业务场景金融以其在服务实体经济、维护金融稳定等层面的前瞻性&#xff0c;成为银行完善场景体系、传递金融温度的“压舱石”。 上海…

uniapp 微信小程序开发使用高德地图、腾讯地图

一、高德地图 1.注册高德地图开放平台账号 &#xff08;1&#xff09;创建应用 这个key 第3步骤&#xff0c;配置到项目中locationGps.js 2.下载高德地图微信小程序插件 &#xff08;1&#xff09;下载地址 高德地图API | 微信小程序插件 &#xff08;2&#xff09;引入项目…

练习题:29

目录 Python题目 题目 题目分析 1. 知识点覆盖分析 2. 连接库选择与导入分析 3. 连接步骤与参数分析 4. 数据库操作与连接关闭分析 代码实现 代码解释 1. 导入必要的库 2. 配置数据库连接参数 3. 建立数据库连接 4. 创建游标对象 5. 编写并执行 SQL 查询语句 6.…

SpringCloudAlibaba 技术栈—Sentinel

1、什么是sentinel? Sentinel是一个用于微服务架构的流量管理和控制系统&#xff0c;它通过限制和控制进入系统的流量&#xff0c;来保护系统免受过载和故障的影响&#xff0c;确保服务的稳定性。简而言之&#xff0c;它就是一个帮助微服务在高负载情况下也能稳定运行的工具。…