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

【工作记录】前后端分离场景下CAS单点登录的集成思路与实践@20230926

背景及目的

之前做一个公司项目的时候甲方要求集成他们指定的CAS服务端实现登录,要求不影响原有业务。
CAS服务端提供的文档都是基于前后端不分离的应用,对前后端分离应用没有任何说明,找官方人问也是爱答不理的,近期正好有时间就想着研究下这个集成过程。
于是有了这篇文章,主要为了记录下集成过程和相关配置,方便后续类似的对接。
当然也希望能帮助到需要的朋友。

CAS涉及到的角色

  • 认证服务器
  • 客户端-API
  • 客户端-前台

整体流程梳理:

  • 客户端请求地址A
  • 发现用户没有token,跳转到指定登录页面,地址类似/cas/login?service=xxxxx
  • 请求成功后会回调callbackUrl同时拼接ticket参数,
  • 客户端发起ticket验证请求,v2: /serviceValidate v3: /p3/serviceValidate
  • 验证成功后会回调到callbackUrl中,同时在session中添加指定用户属性
  • 生成token并传递到前端或前端通过请求拿到token
    • 思路一: 客户端拿到用户属性后根据用户生成token返回给前端,可以通过指定url拼接参数给到前端,前端拦截后保存
    • 思路二: 客户端拿到用户属性后重定向到一个约定的前端地址,前端在路由守卫中拦截该路径并在拦截到后发起getToken请求,请求成功后
      保存token等信息到缓存或者cookie即可。

实现过程

前期准备

  1. cas认证服务端部署
  2. 注册CAS协议的应用,需要配置service/callbackUrl/clientName
  3. vue-admin-template模板项目
  4. 简单的springboot项目
  5. 配置几个用于跳转的路由,在路由文件中添加
  {path: '/callback',hidden: true},{path: '/tologin',hidden: true},

这里只是为了路由守卫的拦截,可以不写vue页面,只需要在路由列表中添加定义即可,亲测无误。

改造过程-前端

路由守卫文件 src/permission.js
import {getToken} from '@/utils/auth'; // get token from cookie
import getPageTitle from '@/utils/get-page-title'
import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css'; // progress bar style
import router from './router'
import store from './store'NProgress.configure({ showSpinner: false }) // NProgress Configurationconst whiteList = ['/login'] // no redirect whitelist//是否支持cas登录的开关
const enableCasLogin = truerouter.beforeEach(async(to, from, next) => {// start progress barNProgress.start()// set page titledocument.title = getPageTitle(to.meta.title)const hasToken = getToken()//判断是否是去login页面if(to.path === '/tologin') {if(enableCasLogin){//实际访问的cas登录地址window.location.href = `http://localhost:9009/cas/login?service=http://localhost:8989/test1/index&redirect=${to.params.redirect}`} else {next(`/login`)}NProgress.done()} else if (whiteList.indexOf(to.path) !== -1) {next();} else if (to.path === '/callback') {if(!hasToken) {await store.dispatch(`user/resetToken`)}next('/')NProgress.done()} else {if(hasToken) {next()} else {if(to.path === '/') {next(`/tologin`)} else {next(`/tologin?redirect=${to.path}`)}}NProgress.done()}
})router.afterEach(() => {// finish progress barNProgress.done()
})
接口配置 /api/user.js
import request from '@/utils/request'export function login(data) {return request({url: '/auth/login',method: 'post',data})
}export function getCaptcha() {return request({url: '/auth/captcha',method: 'get',})
}export function getCasToken() {return request({url: '/auth/getToken',method: 'get',})
}export function logout() {return request({url: '/vue-admin-template/user/logout',method: 'post'})
}
store配置 src/store/modules/user.js
import { getCasToken, login, logout } from '@/api/user'
import { resetRouter } from '@/router'
import { getToken, removeToken, setToken } from '@/utils/auth'const getDefaultState = () => {return {token: getToken(),name: '',avatar: ''}
}const state = getDefaultState()const mutations = {RESET_STATE: (state) => {Object.assign(state, getDefaultState())},SET_TOKEN: (state, token) => {state.token = token},SET_NAME: (state, name) => {state.name = name},SET_AVATAR: (state, avatar) => {state.avatar = avatar}
}const actions = {// user loginlogin({ commit }, userInfo) {return new Promise((resolve, reject) => {login(userInfo).then(response => {const { data } = responsecommit('SET_TOKEN', data.tokenType + " " + data.token)setToken(data.token)resolve()}).catch(error => {reject(error)})})},// user logoutlogout({ commit, state }) {return new Promise((resolve, reject) => {logout(state.token).then(() => {removeToken() // must remove  token  firstresetRouter()commit('RESET_STATE')resolve()}).catch(error => {reject(error)})})},// remove tokenresetToken({ commit }) {return new Promise(resolve => {removeToken() // must remove  token  firstcommit('RESET_STATE')getCasToken().then(response => {const { data } = responsecommit('SET_TOKEN', data.tokenType + " " + data.token)commit('SET_NAME', data.user.username)setToken(data.token)resolve()}).catch(error => {reject(error)})})}
}export default {namespaced: true,state,mutations,actions
}
配置下接口地址 src/vue.config.js
module.exports = {/*** You will need to set publicPath if you plan to deploy your site under a sub path,* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,* then publicPath should be set to "/bar/".* In most cases please use '/' !!!* Detail: https://cli.vuejs.org/config/#publicpath*/publicPath: '/',outputDir: 'dist',assetsDir: 'static',lintOnSave: process.env.NODE_ENV === 'development',productionSourceMap: false,devServer: {port: port,open: true,overlay: {warnings: false,errors: true},// before: require('./mock/mock-server.js')proxy: {'/dev-api': {target: 'http://localhost:8989',pathRewrite: { '^/dev-api': '' }}}},configureWebpack: {// provide the app's title in webpack's name field, so that// it can be accessed in index.html to inject the correct title.name: name,resolve: {alias: {'@': resolve('src')}}},chainWebpack(config) {// it can improve the speed of the first screen, it is recommended to turn on preloadconfig.plugin('preload').tap(() => [{rel: 'preload',// to ignore runtime.js// https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],include: 'initial'}])// when there are many pages, it will cause too many meaningless requestsconfig.plugins.delete('prefetch')// set svg-sprite-loaderconfig.module.rule('svg').exclude.add(resolve('src/icons')).end()config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()config.when(process.env.NODE_ENV !== 'development',config => {config.plugin('ScriptExtHtmlWebpackPlugin').after('html').use('script-ext-html-webpack-plugin', [{// `runtime` must same as runtimeChunk name. default is `runtime`inline: /runtime\..*\.js$/}]).end()config.optimization.splitChunks({chunks: 'all',cacheGroups: {libs: {name: 'chunk-libs',test: /[\\/]node_modules[\\/]/,priority: 10,chunks: 'initial' // only package third parties that are initially dependent},elementUI: {name: 'chunk-elementUI', // split elementUI into a single packagepriority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or apptest: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm},commons: {name: 'chunk-commons',test: resolve('src/components'), // can customize your rulesminChunks: 3, //  minimum common numberpriority: 5,reuseExistingChunk: true}}})// https:// webpack.js.org/configuration/optimization/#optimizationruntimechunkconfig.optimization.runtimeChunk('single')})}
}

改造过程-后端

添加依赖 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.13</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.zjtx.tech</groupId><artifactId>auth-cas-api</artifactId><version>0.0.1-SNAPSHOT</version><name>auth-cas-api</name><description>CAS API</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>net.unicon.cas</groupId><artifactId>cas-client-autoconfig-support</artifactId><version>2.3.0-GA</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

依赖中主要添加的是cas-client-autoconfig-support这个jar包,提供了cas需要的一些配置和拦截器

配置文件添加
server:port: 8989
cas:# 配置实际的cas地址server-url-prefix: http://localhost:9009/cas# 配置实际的cas登录地址server-login-url: http://localhost:9009/cas/loginclient-host-url: http://localhost:8989/# 这里可以选择cas 和 cas3 区别是请求的部分地址不一样,如ticket验证的接口validation-type: cas
#  拦截的URL地址authentication-url-patterns:- /*
spring:jackson:serialization:FAIL_ON_EMPTY_BEANS: false
获取token的controller

src/main/java/com/zjtx/tech/controller/AuthController.java

package com.zjtx.tech.contorller;import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;@RestController
@RequestMapping("/auth")
public class AuthController {@GetMapping("getToken")public ResultBean<TokenUser> getToken(HttpServletRequest request){Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);System.out.println("assertion = " + assertion.getPrincipal().getAttributes());String username = assertion.getPrincipal().getName();System.out.println(username);//这里仅为了演示直接new了一个简单对象返回给前端SimpleUserBean user = new SimpleUserBean("1", username, "123456", "456789");return new ResultBean<>(200, "success", new TokenUser(user, "123456", "Bearer"));}}
回调接口对应的controller

src/main/java/com/zjtx/tech/controller/TestController.java

package com.zjtx.tech.contorller;import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@RestController
public class TestController {@GetMapping("test1/index")public void index(HttpServletRequest request, HttpServletResponse resp) throws IOException {String token = request.getParameter("congress");System.out.println("congress : " + token);Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);System.out.println("assertion = " + assertion.getPrincipal().getAttributes());String username = assertion.getPrincipal().getName();System.out.println(username);resp.sendRedirect("http://localhost:9528/#/callback");}@GetMapping("test1/index1")public String index1(HttpServletRequest request) {String token = request.getParameter("token");System.out.println("token : " + token);Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);String username = assertion.getPrincipal().getName();System.out.println(username);return "test index cas拦截正常,登录账号:" + username;}/*** 不走cas认证,无法获取登录信息** @param request* @return*/@GetMapping("test1/index2")public String index2(HttpServletRequest request) {return "cas 未拦截";}
}

涉及到的简单bean就不在此列举了。

测试过程

  1. 启动前后端及认证服务器项目
  2. 访问前端地址 会跳转到认证服务器定义的loginUrl
  3. 登录完成后会调用http://localhost:9528/#/callback,被路由守卫拦截后进行token的获取和保存
  4. 保存完成后进入首页

总结

本文主要记录了前后端分离场景下集成CAS单点登录的基本流程。
作为记录的同时也希望能帮助到需要的朋友,有任何疑问欢迎留言评论。
创作不易,欢迎一键三连~

相关文章:

【工作记录】前后端分离场景下CAS单点登录的集成思路与实践@20230926

背景及目的 之前做一个公司项目的时候甲方要求集成他们指定的CAS服务端实现登录&#xff0c;要求不影响原有业务。 CAS服务端提供的文档都是基于前后端不分离的应用&#xff0c;对前后端分离应用没有任何说明&#xff0c;找官方人问也是爱答不理的&#xff0c;近期正好有时间就…...

阿里云数据库RDS有哪些?细数关系型数据库大全

阿里云RDS关系型数据库大全&#xff0c;关系型数据库包括MySQL版、PolarDB、PostgreSQL、SQL Server和MariaDB等&#xff0c;NoSQL数据库如Redis、Tair、Lindorm和MongoDB&#xff0c;阿里云百科分享阿里云RDS关系型数据库大全&#xff1a; 目录 阿里云RDS关系型数据库大全 …...

【计算机网络】因特网中的电子邮件

文章目录 简单邮件传送协议SMTP邮件访问协议POP3IMAPHTTP 参考资料 电子邮件为异步通信媒介 因特网电子邮件系统 电子邮件系统的三个构件&#xff1a;用户代理、邮件服务器、邮件发送和读取协议 用户代理 User Agent 即UA 电子邮件客户端软件&#xff0c;用户与电子邮件系统的接…...

【C++11】多线程

多线程创建线程thread提供的成员函数获取线程id的方式线程函数参数的问题线程join场景和detach 互斥量库&#xff08;mutex&#xff09;mutexrecursive_mutexlock_guard 和 unique_lock 原子性操作库&#xff08;atomic&#xff09;条件变量库&#xff08;condition_varuable&a…...

【vue3】shallowReactive与shallowRef;readonly与shallowReadonly;toRaw与markRaw

假期第六篇&#xff0c;对于基础的知识点&#xff0c;我感觉自己还是很薄弱的。 趁着假期&#xff0c;再去复习一遍 1、shallowReactive与shallowRef shallowReactive&#xff1a;只处理对象最外层属性的响应式&#xff08;浅响应式&#xff09; shallowRef&#xff1a;只处理…...

手机建模教程 | 如何从易模App中导出模型?有哪些格式?含贴图吗?

很多小伙伴使用易模App是为了能快速地将已有实物的物体“变成”三维模型后转到自己习惯的3D软件中去编辑&#xff0c;于是&#xff0c;大家都关心模型能否导出&#xff0c;以及导出格式有没有自己想要的&#xff1f; 博雅仔告诉大家&#xff0c;当然可以导出&#xff01; 在导出…...

数据分析技能点-机器学习优化思想

优化思想,这个听起来极其专业和高端的词汇,其实它无处不在,悄无声息地影响着我们的生活和决策。从寻找最快的上班路线,到决定如何配置投资组合,优化思想都是一个不可或缺的元素。而在机器学习领域,优化思想更是扮演着至关重要的角色。 文章目录 优化的基础优化问题与实际…...

应用架构的演进:亚马逊的微服务实践

当你在亚马逊上购物时,或许不会想到,你看到的这个购物网站,其背后技术架构经历了什么样的变迁与升级。 还记得上世纪 90 年代,那个只卖书的网上书店吗?那时的亚马逊,不过是一个架构简单的网站,所有的功能都堆积在一个庞大的软件堡垒里。随着更多业务的增加、更新和迭代,这个软…...

leetCode 55.跳跃游戏 贪心算法

给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入…...

CF505B Mr. Kitayuta‘s Colorful Graph

Mr. Kitayuta’s Colorful Graph 题面翻译 给出一个 n n n 个点&#xff0c; m m m 条边的无向图&#xff0c;每条边上是有颜色的。有 q q q 组询问 对于第 i i i 组询问&#xff0c;给出点对 u i , v i u_i,v_i ui​,vi​。求有多少种颜色 c c c 满足&#xff1a;有至…...

c#设计模式-结构型模式 之 组合模式

&#x1f680;简介 组合模式又名部分整体模式&#xff0c;是一种 结构型设计模式 &#xff0c;是用于把一组相似的对象当作一个 单一的对象 。组合模式 依据树形结构来组合对象 &#xff0c;用来表示部分以及整体层&#xff0c;它可以让你将对象组合成树形结构&#xff0c;并且…...

【Rust日报】2023-09-30 使用Rust做web抓取

CockroachDB 用rust重新实现 嘿&#xff0c;伙计们&#xff0c;我在 Rust 中实现了一个分布式 SQL 数据库。它就像 CockroachDB 和谷歌Google Spanner。告诉我你的想法。 注意: 这不是生产级别的数据库&#xff0c;这是一个以学习为目的的项目。有许多特性&#xff0c;但是缺少…...

【密评】商用密码应用安全性评估从业人员考核题库(三)

商用密码应用安全性评估从业人员考核题库&#xff08;三&#xff09; 国密局给的参考题库5000道只是基础题&#xff0c;后续更新完5000还会继续更其他高质量题库&#xff0c;持续学习&#xff0c;共同进步。 501 多项选择题 《个人信息保护法》要求个人信息处理者应当采取哪些…...

MySQL进阶_查询优化和索引优化

文章目录 第一节、索引失效案例1.1 数据准备1.2 全值匹配我最爱1.3 最佳左前缀法则 第一节、索引失效案例 可以从以下维度对数据库进行优化&#xff1a; 索引失效、没有充分利用到索引–索引建立关联查询太多JOIN (设计缺陷或不得已的需求)–SQL优化服务器调优及各个参数设置…...

Hadoop2复安装过程详细步骤

1、在vmware中更改了虚拟机的网络类型&#xff0c;--->NAT方式&#xff0c;&#xff08;虚拟交换机的ip可以从vmvare的edit-->vertual network editor看到&#xff09; 2、根据这个交换机&#xff08;网关&#xff09;的地址&#xff0c;来设置我们的客户端windows7的ip&…...

【Java-LangChain:面向开发者的提示工程-7】文本扩展

第七章 文本扩展 扩展是将短文本&#xff08;例如一组说明或主题列表&#xff09;输入到大型语言模型中&#xff0c;让模型生成更长的文本&#xff08;例如基于某个主题的电子邮件或论文&#xff09;。这种应用是一把双刃剑&#xff0c;好处例如将大型语言模型用作头脑风暴的伙…...

竞赛 基于设深度学习的人脸性别年龄识别系统

文章目录 0 前言1 课题描述2 实现效果3 算法实现原理3.1 数据集3.2 深度学习识别算法3.3 特征提取主干网络3.4 总体实现流程 4 具体实现4.1 预训练数据格式4.2 部分实现代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习机器视觉的…...

从技能需求到就业前景,了解前端和后端开发的优缺点和个人选择

文章目录 每日一句正能量一、引言前端开发后端开发 二、两者的对比分析三、技能转换和跨领域工作四&#xff1a;介绍全栈开发后记 每日一句正能量 命运决定的不是你的人生&#xff0c;能决定你人生的只有自己。 一、引言 前端和后端是Web开发中两个不可或缺的领域。前端开发主…...

Flutter笔记:AnimationMean、AnimationMax 和 AnimationMin 三个类的用法

Flutter笔记 AnimationMean、AnimationMax 和 AnimationMin三个类的用法 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/…...

华为云云耀云服务器L实例评测|云耀云服务器L实例部署Gogs服务器

华为云云耀云服务器L实例评测&#xff5c;云耀云服务器L实例部署Gogs服务器 一、云耀云服务器L实例介绍1.1 云耀云服务器L实例简介1.2 云耀云服务器L实例特点 二、Gogs介绍2.1 Gogs简介2.2 Gogs特点 三、本次实践介绍3.1 本次实践简介3.2 本次环境规划 四、远程登录华为云云耀云…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

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…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...