微服务的编程测评系统6-管理员登录前端-前端路由优化

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. 管理员登录前端
    • 1.1 测试
    • 1.2 同源策略
    • 1.3 修改前端端口号
    • 1.4 跨域问题
    • 1.5 接收响应数据
    • 1.6 js-cookie
    • 1.7 错误消息提示
    • 1.8 优化
    • 1.9 响应拦截器
    • 1.10 @用法
  • 2. 后台管理-布局
    • 2.1 点击跳转不同页面
  • 3. 获取当前用户信息
    • 3.1 数据库修改
    • 3.2 设计
    • 3.3 开发后端
    • 3.4 开发前端
  • 4. 退出登录
    • 4.1 业务分析
    • 4.2 后端开发
    • 4.3 前端开发
  • 5. 前端路由优化
    • 5.1 重定向
    • 5.2 全局前置守卫
    • 5.3 token过期处理
  • 总结


前言

1. 管理员登录前端

1.1 测试

在这里插入图片描述
测试一下发现报了这个错
这个主要是因为我们配置的前置url没有含有http协议,所以浏览器就会自动加上静态资源的url

const service = axios.create({baseURL: "http://127.0.0.1:19090/system",timeout: 1000,
})

这样就Ok了
在这里插入图片描述
但是又出了一个新的问题
这个就是跨域问题

1.2 同源策略

在这里插入图片描述

1.3 修改前端端口号

在vite.config.js里面添加

  server: {port: 5555,}

这样就可以了

在这里插入图片描述

1.4 跨域问题

我们可以用一个代理服务器来处理
浏览器前端先请求同源的代理服务器,然后代理服务器把请求转发到后端

因为浏览器有同源策略的约束
但是代理服务器是没有同源策略的约束的

  server: {proxy: {"/dev-api": {target: "http://127.0.0.1:19090/system",rewrite: (p) => p.replace(/^\/dev-api/, ""),},},},

还是在vite.config.js里面,这样设置就可以了
这个就是对代理规则的配置

然后修改request.js里面的前置url

const service = axios.create({baseURL: "/dev-api",timeout: 1000,
})

如果没有加协议的话,浏览器会把前端的url拼接到baseURL上
所以请求地址为
http://localhost:5173/dev-api/sysUser/login
这个不会发生跨域问题
这个是同源的
会报404,找不到吗
不会
因为配置了代理规则
代理规则就是前缀包含/dev-api的时候,就会把请求转发到http://127.0.0.1:19090/system

  rewrite: (p) => p.replace(/^\/dev-api/, ""),

这个就是把/dev-api变为空的字符串
所以http://localhost:5173/dev-api/sysUser/login就会变为
http://127.0.0.1:19090/system/sysUser/login
先把http://localhost:5173替换为http://127.0.0.1:19090/system,然后去掉/dev-api,就OK了

在这里插入图片描述
补充一下,axios可以自动转换JSON数据,所以那样写没有问题

测试一下也是没有问题的

1.5 接收响应数据

但是axios这个调用接口的过程是一个异步的过程
往往不那么好拿到返回结果
所以要用await去获取异步操作结果
但是对应的调用他的函数也要为async

async function  loginFun() {const res = await loginService(userAccount.value, password.value);if(res.data.code === 1000){console.log("登录成功:" , res.data);}else{console.log(res.data.msg)}
}

async 表示这个函数要使用await,await表示调用这个方法接口的时候用异步的方式
console.log(“登录成功:” , res.data);这里,如果是 console.log(“登录成功:” +res.data);

那么res.data打印出的内容是object
如果要打印出类的详细数据的话,还是得用逗号隔开
在这里插入图片描述
登录成功要跳转页面
我们用router的push方法就可以了

在这里插入图片描述

    {path: '/oj/system',name: 'system',component: () => import('../views/System.vue')}

记得还要配置路由

import router from '@/router'
router.push("/oj/system")

这样就可以跳转了

1.6 js-cookie

登录成功以后要存储token
怎么存储呢
存储方式有很多种
比如cookie和local-storige
我们这里使用cookie存储
js-cookie就是来操作cookie的

npm install js-cookie

在utils下创建cookie.js

import Cookies from "js-cookie";
const TokenKey = "Admin-Oj-b-Token";
export function getToken() {return Cookies.get(TokenKey);
}
export function setToken(token) {return Cookies.set(TokenKey, token);
}
export function removeToken() {return Cookies.remove(TokenKey);
}

这样就可以了

import { setToken } from '@/utils/cookie'
setToken(res.data.data)

在这里插入图片描述
在appication.cookies那里就可以看到我们设置的cookie了

1.7 错误消息提示

import { ElMessage } from 'element-plus'
ElMessage.error(res.data.msg)

1.8 优化

          <el-input v-model="password"  type="password" show-password placeholder="请输入密码" />

给按钮加上 type=“password”
就可以把密码隐藏起来了
show-password就是显示是不是显示小眼睛

1.9 响应拦截器

我们在request.js里面设置

service.interceptors.response.use((res) => {// 未设置状态码则默认成功状态const code = res.data.code;const msg = res.data.msg;if (code !== 1000) {ElMessage.error(msg);return Promise.reject(new Error(msg));} else {return Promise.resolve(res.data);}},(error) => {return Promise.reject(error);}
);

为什么要用响应拦截器呢
因为我们可以用响应拦截器对响应进行拦截,,可以直接返回后端返回的result数据
现在我们就在登录成功和失败的情况下分别测试一下

  const res = await loginService(userAccount.value, password.value);console.log("登录成功:" , res.data);

在这里插入图片描述
在这里插入图片描述
我们可以看出登录成功返回的数据就是后端返回的result,没有进行分装了
这个就是Promise.resolve(res.data)的作用
而Promise.reject(new Error(msg))就是相当于返回一个异常了
直接报错了

    (error) => {return Promise.reject(error);}

这里是属于错误返回,其他的都是正常返回
我们控制台肯定不能这样打印的,因为这样打印就是相当于出错了
所以我们还要捕获异常
就和后端捕获异常是一样的写法

async function  loginFun() {try{const res = await loginService(userAccount.value, password.value);setToken(res.data.data)router.push("/oj/system")console.log("登录成功:" , res.data);}catch(err){console.log("登录失败:" , err);}
}

这样就可以了

1.10 @用法

import { setToken } from '@/utils/cookie'

这里的@是什么意思呢

  resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))},},

其实在vite.config.js里面就配置过了
@就是./src,所以很方便使用
在这里插入图片描述
相应的router这里的两点我们也可以改为@

在这里插入图片描述

2. 后台管理-布局

创建一个布局的文件
Layout.vue

<template><el-container class="layout-container"><el-header class="el-header"><el-dropdown><span class="el-dropdown__box"><div><strong>当前用户:</strong>超级管理员</div><el-icon><ArrowDownBold /></el-icon><!-- <el-icon><Lock /></el-icon> --></span><template #dropdown><el-dropdown-menu><el-dropdown-item @click="logout" :icon="SwitchButton">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></el-header><el-main class="layout-bottom-box"><div class="left"><el-aside width="200px" class="el-aside"><el-menu class="el-menu" router><el-menu-item index="/oj/layout/cuser"><el-icon><Management /></el-icon><span>用户管理</span></el-menu-item><el-menu-item index="/oj/layout/question"><el-icon><Management /></el-icon><span>题目管理</span></el-menu-item><el-menu-item index="/oj/layout/exam"><el-icon><Management /></el-icon><span>竞赛管理</span></el-menu-item></el-menu></el-aside></div><div class="right"><RouterView /></div></el-main></el-container>
</template><script setup>
import {Management,ArrowDownBold,Lock,SwitchButton
} from '@element-plus/icons-vue'
</script><style lang="scss" scoped>
.layout-container {height: 100vh;background: #f7f7f7;.layout-bottom-box {display: flex;justify-content: space-between;height: calc(100vh - 100px);overflow: hidden;.left {margin-right: 20px;background: #fff;display: flex;:deep(.el-menu) {flex: 1;.el-menu-item.is-active {color: #32c5ff;}.el-menu-item:hover {background: #fff;color: #32c5ff;}}}.right {flex: 1;overflow-y: auto;background: #fff;padding: 20px;}}.el-aside {background-color: #fff;&__logo {height: 120px;// background: url('@/assets/logo.png') no-repeat center / 120px auto;}.el-menu {border-right: none;}}.el-header {background-color: #fff;display: flex;align-items: center;justify-content: flex-end;height: 40px;.el-dropdown__box {display: flex;align-items: center;.el-icon {color: #4c4141;margin-left: 20px;}&:active,&:focus {outline: none;}}}.el-footer {display: flex;align-items: center;justify-content: center;font-size: 14px;color: #666;}
}
</style>

然后配置路由

    {path: '/oj/layout',name: 'layout',component: () => import('@/views/Layout.vue')}

在这里插入图片描述
这样就可以了
现在分析一下
分成了三个部分
在这里插入图片描述
我们用的是是这个container布局容器

  <el-container class="layout-container"><el-header class="el-header"></el-header><el-main class="layout-bottom-box"><div class="left"><el-aside width="200px" class="el-aside"></el-aside></div><div class="right"><RouterView /></div></el-main></el-container>

这个就是布局
整体结构

dropdown是一个下拉菜单

在这里插入图片描述
在这里插入图片描述

ArrowDownBold是图标
在这里插入图片描述
在这里插入图片描述

import {Management,ArrowDownBold,Lock,SwitchButton
} from '@element-plus/icons-vue'

但是使用图标要在js里面import

SwitchButton也是图标

el-aside我们用的是el-menu来写的

2.1 点击跳转不同页面

      <div class="right"><RouterView /></div>

这里就是根据不同url,渲染不同页面,就是主要的页面内容
先创建对应的vue文件

在这里插入图片描述
然后是配置router
在这里插入图片描述
怎么实现点击切换不url呢

官网的menu组件有一个router属性
在这里插入图片描述
这样就可以实现点击切换url了

首先先加上属性router
因为默认为false,不启用,加上就启动了

          <el-menu class="el-menu" router>

在el-menu这里加上router属性,表示启动router
然后还不行,因为点击要跳转到哪里呢,
这下就要设置index了

在这里插入图片描述
所以index设置为路径就可以了

            <el-menu-item index="/oj/question">

这样设置就可以了
在这里插入图片描述
但是点击跳转直接跳转到新的页面了,而不是在那个主页面展示
为什么会这样呢
因为我们配置的路径是/oj/question,与/oj/layout是同一级的
这个路由发生改变之后
会触发routerview,但是这个触发的routerview是app.vue那里的

    {path: '/oj/layout',name: 'layout',component: () => import('@/views/Layout.vue')},{path: '/oj/cuser',name: 'cuser',component: () => import('@/views/Cuser.vue')},

这里的配置路径就是同一级的,所以改变路径的时候就是触发的同一个routerview,因为这两个的路径的配置是类似的,所以layout能触发那个routerview,为什么cuser不行呢
所以cuser也是用的app.vue的routerview
所以我们需要把cuser的路由配置到layout里面去
因为cuser是在layout内部进行的页面渲染
因为渲染的顺序就是先渲染layout,然后是点击在layout里面渲染cuser
所以路径的配置,就必须在layout里面进行配置
所以cuser的路由就是layout下的路由
路由提供了一个child的属性就可以配置了,这个是数组

    {path: '/oj/layout',name: 'layout',component: () => import('@/views/Layout.vue'),children: [{path: '/cuser',name: 'cuser',component: () => import('@/views/Cuser.vue')},{path: '/exam',name: 'exam',component: () => import('@/views/Exam.vue')},{path: '/question',name: 'question',component: () => import('@/views/Question.vue')},]},

这样就可以了
其中cuser的路径就是/oj/layout/cuser
会自动加上父组件的路径的

      router.push("/oj/layout")

然后登录成功的跳转也要改了

 <el-menu-item index="/oj/layout/cuser">

然后这里也要改
在这里插入图片描述

但是还是不行

      <div class="right"><RouterView /></div>

这里的routerview是二级目录
这里跳转显示的是二级路由,而app.vue里面显示跳转的是一级路由
这里的情况是在一级小的页面里面有二级页面,所以对应也要在app.vuede的routerview里面在嵌套一个routerview
因为页面有嵌套关系
所以routerview也要有嵌套关系,路由配置也要有嵌套关系

    {path: '/oj/layout',name: 'layout',component: () => import('@/views/Layout.vue'),children: [{path: 'cuser',name: 'cuser',component: () => import('@/views/Cuser.vue')},{path: 'exam',name: 'exam',component: () => import('@/views/Exam.vue')},{path: 'question',name: 'question',component: () => import('@/views/Question.vue')},]},

注意这里的cuser的路径前面就不要加上/了,因为这样可能表示是以/cser开头的,是绝对路径,如果是二级路径,就最前面不要加/了
子路由 path 不加 /:路径会自动拼接父路由路径,保持嵌套关系(正确用法)。
子路由 path 加 /:路径被视为绝对路径,脱离父路由,成为独立的一级路由(不符合二级路由的设计意图)。

3. 获取当前用户信息

3.1 数据库修改

给数据库添加用户昵称字段

    nick_name varchar(20) not null comment '昵称',

要么重新创建数据库
要么用alter

alter table  tb_sys_user add nick_name varchar(20)  null  after user_account ;
update tb_sys_user set nick_name = '超级管理员' where user_account = 'aaa'
[HY000][1366] Incorrect string value: '\xE8\xB6\x85\xE7\xBA\xA7...' for column 'nick_name' at row 1

在update的时候报错了,这个是因为不支持中文的原因
就是编码出问题
改一下配置文件就可以了

在这里插入图片描述
找到etc/my.cnf
加上配置

character-set-server=utf8mb4
collation-server = utf8mb4_general_ci

保存一下,然后重启容器生效

但是就算这样修改了执行还是不行,因为这个表提前就创建好了,编码已经确定了
所以不行
所以我们要重新创建一个表
但是就算创建一个新的表还是有编码问题
怎么回事呢
因为数据库的编码没有变
得创建一个新的库才可以
我们先用root用户创建新的库
所以说改了配置文件以后,删除以前的库才可以,或者创建新的库才可以生效
右键表的数据,然后生成sql,生成insertSQL就可以保存数据了
在这里插入图片描述
这样就成功了

3.2 设计

在这里插入图片描述

3.3 开发后端

    @GetMapping("/info")public R<LoginUserVO> info(@RequestHeader(HttpConstants.AUTHENTICATIO) String token){return sysUserService.info(token);}

我们的token直接从header里面获取就可以了,用的是RequestHeader注解

@Data
public class LoginUserVO {private String nickName;
}
@Data
public class LoginUser {//存储在redis中的用户信息private Integer identity;private String nickName;
}

这里也要完善一下,存储到redis中的基本数据,还有数据库对应的类也要增加字段,记得还要修改对应的代码,登录存储基本用户数据的时候记得修改代码,存储昵称
在这里插入图片描述

tokenService里面分装方法

    public LoginUser getLoginUser(String token, String secret   ) {String userKey = getUserKey(token, secret);if(userKey == null){return null;}String tokenKey = getTokenKey(userKey);return redisService.getCacheObject(tokenKey, LoginUser.class);}private String getTokenKey(String userKey) {return CacheConstants.LOGIN_TOKEN_KEY + userKey;}private String getUserKey(String token, String secret) {Claims claims;try {claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息if (claims == null) {log.error("令牌已过期或验证不正确!");return null;}} catch (Exception e) {log.error("令牌已过期或验证不正确!e:",e);return null;}return JwtUtils.getUserKey(claims); //获取jwt中的key}
    @Overridepublic R<LoginUserVO> info(String token) {if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);}LoginUser loginUser = tokenService.getLoginUser(token,secret);if(loginUser == null){return R.fail();}LoginUserVO loginUserVO = new LoginUserVO();loginUserVO.setNickName(loginUser.getNickName());return R.ok(loginUserVO);}

然后测试一下

del+key可以删除redis数据

在这里插入图片描述
在这里插入图片描述
这样就成功了
这个是根据登录设置的header自动进行查询的,不用传json
然后测试一下延长redis时间的接口也是没有问题的

3.4 开发前端

export function getUserInfoService(){return service({url: '/sysUser/info',method: 'get'})
}
import { reactive } from 'vue';
import { getUserInfoService } from '@/apis/suser';const loginUser = reactive({nickName: ''
})async function getUserInfo(){const userInfo = await getUserInfoService();loginUser.nickName = userInfo.data.nickName;
}await getUserInfo();

然后再request.js里面定义请求拦截器

//请求拦截器
service.interceptors.request.use((config) => {if (getToken()) {config.headers["Authorization"] = "Bearer " + getToken();}return config;},(error) => {console.log(error)Promise.reject(error);}
);

这个请求拦截器就是拦截每个给后端发起的请求,然后判断是否有token,有的话,就在请求头中加上token

          <div><strong>当前用户:</strong>{{loginUser.nickName}}</div>

注意登录的前端接口写错了
改一下为这个样子

async function  loginFun() {try{const res = await loginService(userAccount.value, password.value);setToken(res.data)router.push("/oj/layout")console.log("登录成功:" , res.data);}catch(err){console.log("登录失败:" , err);}
}

这样就可以了
在这里插入图片描述

4. 退出登录

4.1 业务分析

就是让token不为空,然后解析一下,解析出来不能执行正常业务了。是可以进行解析的
所以点击退出登录,让redis中的数据不存在就可以了
在这里插入图片描述
这样就可以避免多次登录,redis中的数据增多了
后端返回请求以后,如果是成功的,前端就清楚存储的token
所以就是后端清楚redis,前端清楚token
在这里插入图片描述

4.2 后端开发

    @DeleteMapping("/logout")@Operation(summary = "退出登录", description = "退出登录")public R<Void> logout(@RequestHeader(HttpConstants.AUTHENTICATION) String token){log.info("退出登录...,token:{}", token);return toR(sysUserService.logout(token));}

因为退出登录后端会删除redis所以是DeleteMapping

    @Overridepublic boolean logout(String token) {if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);}return tokenService.deleteLoginUser(token,secret);}
    public boolean deleteLoginUser(String token, String secret) {String userKey = getUserKey(token, secret);if(userKey == null){return false;}String tokenKey = getTokenKey(userKey);return redisService.deleteObject(tokenKey);}

然后测试一下

4.3 前端开发

点击退出登录的时候会有一个消息弹窗
我们用的就是elementplus的消息弹窗
在这里插入图片描述
在这里插入图片描述
观察一下我们可以发现
ElMessageBox.confirm 方法返回一个 Promise 对象。当用户点击确认按钮时,Promise 会进入 resolved 状态,此时会执行 .then() 中的回调函数;而当用户点击取消按钮或者关闭对话框时,Promise 会进入 rejected 状态,这时就会执行 .catch() 中的回调函数。
所以说用户点击取消按钮或者关闭对话框时,就是相当于抛出了一个异常,所以弹窗如果后面还有代码就不会执行了
而点击了确定按钮的话(其实点击什么都是返回Promise ),会返回一个 Promise 对象,返回这个对象是一个异步的过程,所以要await,不然异步的话,就去判断Promise 对象,可能会判断失误
所以说点击了确定按钮的话,就会执行弹窗后面的代码了

export function logoutService(){return service({url: '/sysUser/logout',method: 'delete'})
}

如果是函数抛出异常,也会结束后面代码执行

async function logout(){await ElMessageBox.confirm('退出登录','温馨提示',{confirmButtonText: '确认',cancelButtonText: '取消',type: 'warning',})await logoutService();removeToken();router.push('/oj/login');
}

因为logoutService抛出的异常我们可以直接输出错误,对于异常的情况没有什么好处理的,就什么都不干就可以了,所以我们不用try和catch

5. 前端路由优化

5.1 重定向

这个的问题是什么呢
在这里插入图片描述
就是我们点击这个地址不用直接到login,而是要手动输入地址才可以了
什么做到点击这个http://localhost:5173/,就可以自动跳转到login呢,这个就要使用重定向了

    {path: '/',redirect: '/oj/login'},

这样配置就可以了

5.2 全局前置守卫

我们要求
未登录要求不管点击哪个页面都要跳回登录页面
登录过后,未过期,点回login自动跳转功能页面
登录过后,点击login,就直接不用登录就可以使用功能了

就是要在路由跳转之前进行判断处理
谁来进行页面跳转呢,就是router,router在哪里呢,就是在index.js里面配置的,所以对router进行配置即可,就是对页面跳转之前进行的配置

router.beforeEach((to, from, next) => {if (getToken()) {  //已经登陆过/* has token*/if (to.path === '/oj/login') {next({ path: '/oj/layout/question' })} else {next()}} else {if (to.path !== '/oj/login') {next({path:'/oj/login'})} else {next()}}
})

这样就可以了
to是目的路由
from是源路由
next是真正的目的路由是去哪里
next()就是和to一样的

这样我们在没有登录的情况下输入http://localhost:5173/oj/layout
就会自动跳转为http://localhost:5173/oj/login
登录下输入http://localhost:5173/oj/login
就会自动变为http://localhost:5173/oj/layout/question
但是如果登录状态过期呢
这个也是和没有登录是一样的,怎么判断呢,前端是无法判断token是否过期的

5.3 token过期处理

        boolean isLogin = redisService.hasKey(getTokenKey(userKey));if (!isLogin) {return unauthorizedResponse(exchange, "登录状态已过期");}
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, Stringmsg) {log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());return webFluxResponseWriter(exchange.getResponse(), msg,ResultCode.FAILED_UNAUTHORIZED.getCode());}
    FAILED_UNAUTHORIZED (3001, "未授权"),

我们可以这样处理
因为后端对于token过期会报错的,就在网关中,会报登录状态已过期,会报3001的错误,,,而且我们还有响应拦截器,所以就可以在响应拦截器中进行处理了,如果过期了就自动跳转到login

所以说登录状态由浏览器的token和token是否过期一起决定
我们可以去redis中删除数据,手动弄为过期
因为过期了,redis就会自动删除数据,所以我们删除它,就是模仿的过期

service.interceptors.response.use((res) => {// 未设置状态码则默认成功状态const code = res.data.code;const msg = res.data.msg;if(code === 3001){ElMessage.error(msg);router.push('/oj/login')removeToken();return Promise.reject(new Error(msg));}else if (code !== 1000) {ElMessage.error(msg);return Promise.reject(new Error(msg));} else {return Promise.resolve(res.data);}},(error) => {return Promise.reject(error);}
);

为什么要removeToken呢,因为已经过期了,没用了,但是不删掉的话,就可以一直去layout页面
注意如果是刷新的话,在全局前置守卫中,to就是自身url,而from则是根路径

这样就OK了
调试也没有错误

总结

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

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

相关文章

南京银行提前批金融科技面试记录

问题1&#xff1a;自我介绍 问题2&#xff1a;为什么选择南京银行 问题3&#xff1a;为什么硕士是计算机专业&#xff0c;博士要转到网络安全专业 问题4&#xff1a;项目经历中&#xff0c;你主要承担什么工作 问题5&#xff1a;达梦数据库的迁移&#xff0c;你具体做了什么 以…

STM32-第九节-ADC模数转换

一、ADC简介&#xff1a;1.名称&#xff1a;ADC&#xff0c;Analog-Digital Converter&#xff0c;模拟数字转换器2.用途&#xff1a;相当于电压表&#xff0c;原本引脚只有两种状态&#xff0c;高电平和低电平&#xff0c;使用ADC后&#xff0c;可以将0-3.3V间的任一引脚电压&…

nuxt更改页面渲染的html,去除自定义属性、

nuxt2 nuxt.config.js module.exports {// ...hooks: {render:route: (url, result) > {// 去除nuxt自定义属性result.html result.html.replace(/\sdata-n-head".*?"/gi,).replace(/\sdata-hid".*?"/gi, ).replace(/<a(.*?)href"\//gi,…

如何将iPad中的视频传输到电脑(6种简单方法)

iPad是一款功能强大的平板电脑&#xff0c;不仅用于娱乐和工作&#xff0c;还可以用于拍摄和保存珍贵的视频。然而&#xff0c;iPad的存储容量是有限的&#xff0c;这意味着你可能会遇到需要将视频从iPad传输到电脑的情况。无论你是想为iPad腾出空间&#xff0c;还是想在更大的…

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

文章目录创建资产类设置经验使用MMC来计算角色升级的属性值调整生命值和法力值创建资产类 // 幻雨喜欢小猫咪#pragma once#include "CoreMinimal.h" #include "Abilities/GameplayAbility.h" #include "Engine/DataAsset.h" #include "PDA_…

隧道代理的动态IP切换机制与实现原理

目录 一、动态IP切换的底层逻辑 1. 统一入口与动态出口的魔法 2. 云端IP池的智能调度 二、协议层的技术突破 1. 传输层隧道&#xff1a;IPsec与WireGuard的较量 2. 应用层隧道&#xff1a;HTTP/SOCKS5的进化 三、动态切换的触发机制 1. 被动触发&#xff1a;封禁检测与应…

时序数据库主流产品概览

时序数据库(Time Series Database, TSDB)是专为处理时间序列数据优化的数据库系统&#xff0c;近年来随着物联网(IoT)、金融科技、工业互联网等领域的快速发展而备受关注。本文将介绍当前主流的时序数据库产品。一、时序数据库概述时序数据是带时间戳记录的数据点序列&#xff…

图机器学习(17)——基于文档语料库构建知识图谱

图机器学习&#xff08;17&#xff09;——基于文档语料库构建知识图谱0. 前言1. 基于文档语料库构建知识图谱2. 知识图谱3. 文档-实体二分图0. 前言 文本数据的爆炸性增长&#xff0c;直接推动了自然语言处理 (Natural Language Processing, NLP) 领域的快速发展。在本节中&a…

【实时Linux实战系列】实时文件系统的特性与优化

在实时系统中&#xff0c;文件系统的性能和可靠性对于系统的整体表现至关重要。实时文件系统需要在严格的时间约束内完成文件的读写操作&#xff0c;以确保系统的实时性。本文将介绍实时文件系统的基本特性和应用场景&#xff0c;并提供相关的实施和优化建议&#xff0c;以满足…

Clickhouse源码分析-副本数据同步

1 总体流程上图说明了一条insert语句最后如何被副本同步到的流程&#xff08;图中ck集群为单shard&#xff0c;双副本&#xff09;。&#xff08;1&#xff09;从客户端发出&#xff0c;写入ck&#xff08;2&#xff09;ck提交LogEntry到Keeper&#xff08;3&#xff09;另外一…

Spring AI 系列之二十四 - ModerationModel

之前做个几个大模型的应用&#xff0c;都是使用Python语言&#xff0c;后来有一个项目使用了Java&#xff0c;并使用了Spring AI框架。随着Spring AI不断地完善&#xff0c;最近它发布了1.0正式版&#xff0c;意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说…

在 macOS 上 安装最新 Python 和 pip

文章目录方法一&#xff1a;使用 Homebrew&#xff08;推荐&#xff09;方法二&#xff1a;使用 pyenv&#xff08;管理多个 Python 版本&#xff09;方法三&#xff1a;从官网下载安装包升级 pip验证安装方法一&#xff1a;使用 Homebrew&#xff08;推荐&#xff09; 1. 安装…

新能源电池厂自动化应用:Modbus TCP转DeviceNet实践

一、项目背景在新能源电池厂的生产过程中&#xff0c;提升自动化水平对提高生产效率和产品质量至关重要。我们的生产线上&#xff0c;施耐德PLC负责整体的生产流程控制&#xff0c;采用Modbus TCP协议进行数据传输&#xff0c;它基于以太网&#xff0c;传输速度快、稳定性高&am…

Java进阶3:Java集合框架、ArrayList、LinkedList、HashSet、HashMap和他们的迭代器

Java集合框架 集合框架被设计成的目标&#xff1a;高性能、高效 允许不同类型的结合&#xff0c;以类似的方式进行工作&#xff0c;有高度的互操作性 对一个集合的扩展和适应必须是简单的两种容器&#xff1a;集合Collection、图Map 集合接口被分为了三种子类型&#xff1a;Lis…

笔记/使用Excel进行财务预测

文章目录金融预测的决策与数据收集决定财务问题收集财务数据清理与合并财务数据解释与应用预测结果使用excel进行财务回归分析回归预测的步骤解释回归结果在 Excel 中执行预测财务分析指标财务分析常用指标一览表财务指标的相关性对竞争对手进行基准测试财务指标的趋势分析持续…

力扣1287:有序数组中出现次数超过25%的元素

力扣1287:有序数组中出现次数超过25%的元素题目思路代码题目 给你一个非递减的 有序 整数数组&#xff0c;已知这个数组中恰好有一个整数&#xff0c;它的出现次数超过数组元素总数的 25%。 请你找到并返回这个整数 思路 哈希表秒了 代码 class Solution { public:int fi…

如何用 Z.ai 生成PPT,一句话生成整套演示文档

大家好&#xff0c;这里是K姐。 一个帮你追踪最新AI应用的女子。 最近朋友给我分享了一个好玩的页面截图。 一眼看过去&#xff0c;就感觉这PPT的文字排版很有人工味。 我立马就去试了一下&#xff0c;才发现它根本不是传统的 PPT&#xff0c;而是一种网页式的 Slides 。 做…

C/C++ 编程:掌握静态库与动态库的编译

在 C/C 项目开发中&#xff0c;理解并掌握如何编译和使用库文件是至关重要的一环。库允许你将常用的函数和代码模块化&#xff0c;从而提高代码重用性、简化项目管理并缩短编译时间。最常见的两种库类型是静态库 (.a) 和动态库 (.so)。它们各有优缺点&#xff0c;适用于不同的开…

汽车安全 | 汽车安全入门

引言 汽车安全不仅仅是对汽车/车辆进行物理入侵。这只是很小且简单的一部分。当你以攻击者/对手的思维去看待一辆联网汽车时&#xff0c;你关注的是整个车辆生态系统。这不仅包括它如何与外部实体通信&#xff0c;也包括它在车内如何运作。 汽车是主要的交通工具&#xff0c;…

CLIP与SIGLIP对比浅析

CLIP 和 SIGLIP 的核心区别在于损失函数的设计&#xff1a;CLIP 使用基于 softmax 的对比损失&#xff08;InfoNCE&#xff09;&#xff0c;强制正样本在全局对比中压倒所有负样本&#xff0c;计算成本高且受限于负样本数量&#xff1b;SIGLIP 改用基于 sigmoid 的二元分类损失…