浅谈qiankun微前端

qiankun是single-spa二开;使用场景:不同技术栈,不同团队,独立开发部署、增量升级;总结:解耦;

    

 

 

 

 

 

 

 

 

主应用: 具有整合-输入子应用的html入口;

子应用  与single-spa基本一致,导出了三个生命周期函数 (bootstrap mount unmout)

js沙箱: 三个沙箱(快照沙箱、支持单应用的代理沙箱、多应用代理沙箱

  Snapshot Sandbox 源码解析:里面的iter方法,将遍历window属性的代码抽离出来了,调用这个工具方法后,我们只需要专注于迭代到相应属性时候需要进行的处理。

import type { SandBox } from '../interfaces';
import { SandBoxType } from '../interfaces';
function iter(obj: typeof window, callbackFn: (prop: any) => void) {// eslint-disable-next-line guard-for-in, no-restricted-syntaxfor (const prop in obj) {// patch for clearInterval for compatible reason, see #1490if (obj.hasOwnProperty(prop) || prop === 'clearInterval') {callbackFn(prop);}}
}
/*** 基于 diff 方式实现的沙箱,用于不支持 Proxy 的低版本浏览器*/
export default class SnapshotSandbox implements SandBox {proxy: WindowProxy;name: string;type: SandBoxType;sandboxRunning = true;private windowSnapshot!: Window;private modifyPropsMap: Record<any, any> = {};constructor(name: string) {this.name = name;this.proxy = window;this.type = SandBoxType.Snapshot;}active() {// 记录当前快照this.windowSnapshot = {} as Window;iter(window, (prop) => {this.windowSnapshot[prop] = window[prop];});// 恢复之前的变更Object.keys(this.modifyPropsMap).forEach((p: any) => {window[p] = this.modifyPropsMap[p];});this.sandboxRunning = true;}inactive() {this.modifyPropsMap = {};iter(window, (prop) => {if (window[prop] !== this.windowSnapshot[prop]) {// 记录变更,恢复环境this.modifyPropsMap[prop] = window[prop];window[prop] = this.windowSnapshot[prop];}});if (process.env.NODE_ENV === 'development') {console.info(`[qiankun:sandbox] ${this.name} origin window restore...`, Object.keys(this.modifyPropsMap));}this.sandboxRunning = false;}
}

LegacySandbox  过三个变量来记住沙箱激活后window发生变化过的所有属性,这样在后续的状态还原时候就不再需要遍历window的所有属性来进行对比,提升了程序运行的性能。但是这仍然改变不了这种机制仍然污染了window的状态的事实,因此也就无法承担起同时支持多个微应用运行的任务。

import type { SandBox } from '../../interfaces';
import { SandBoxType } from '../../interfaces';
import { getTargetValue } from '../common';function isPropConfigurable(target: WindowProxy, prop: PropertyKey) {const descriptor = Object.getOwnPropertyDescriptor(target, prop);return descriptor ? descriptor.configurable : true;
}/*** 基于 Proxy 实现的沙箱* TODO: 为了兼容性 singular 模式下依旧使用该沙箱,等新沙箱稳定之后再切换*/
export default class LegacySandbox implements SandBox {/** 沙箱期间新增的全局变量 */private addedPropsMapInSandbox = new Map<PropertyKey, any>();/** 沙箱期间更新的全局变量 */private modifiedPropsOriginalValueMapInSandbox = new Map<PropertyKey, any>();/** 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻做 snapshot */private currentUpdatedPropsValueMap = new Map<PropertyKey, any>();name: string;proxy: WindowProxy;globalContext: typeof window;type: SandBoxType;sandboxRunning = true;latestSetProp: PropertyKey | null = null;private setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) {if (value === undefined && toDelete) {// eslint-disable-next-line no-param-reassigndelete (this.globalContext as any)[prop];} else if (isPropConfigurable(this.globalContext, prop) && typeof prop !== 'symbol') {Object.defineProperty(this.globalContext, prop, { writable: true, configurable: true });// eslint-disable-next-line no-param-reassign(this.globalContext as any)[prop] = value;}}active() {if (!this.sandboxRunning) {this.currentUpdatedPropsValueMap.forEach((v, p) => this.setWindowProp(p, v));}this.sandboxRunning = true;}inactive() {if (process.env.NODE_ENV === 'development') {console.info(`[qiankun:sandbox] ${this.name} modified global properties restore...`, [...this.addedPropsMapInSandbox.keys(),...this.modifiedPropsOriginalValueMapInSandbox.keys(),]);}// renderSandboxSnapshot = snapshot(currentUpdatedPropsValueMapForSnapshot);// restore global props to initial snapshotthis.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) => this.setWindowProp(p, v));this.addedPropsMapInSandbox.forEach((_, p) => this.setWindowProp(p, undefined, true));this.sandboxRunning = false;}constructor(name: string, globalContext = window) {this.name = name;this.globalContext = globalContext;this.type = SandBoxType.LegacyProxy;const { addedPropsMapInSandbox, modifiedPropsOriginalValueMapInSandbox, currentUpdatedPropsValueMap } = this;const rawWindow = globalContext;const fakeWindow = Object.create(null) as Window;const setTrap = (p: PropertyKey, value: any, originalValue: any, sync2Window = true) => {if (this.sandboxRunning) {if (!rawWindow.hasOwnProperty(p)) {addedPropsMapInSandbox.set(p, value);} else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {// 如果当前 window 对象存在该属性,且 record map 中未记录过,则记录该属性初始值
          modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);}currentUpdatedPropsValueMap.set(p, value);if (sync2Window) {// 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据(rawWindow as any)[p] = value;}this.latestSetProp = p;return true;}if (process.env.NODE_ENV === 'development') {console.warn(`[qiankun] Set window.${p.toString()} while sandbox destroyed or inactive in ${name}!`);}// 在 strict-mode 下,Proxy 的 handler.set 返回 false 会抛出 TypeError,在沙箱卸载的情况下应该忽略错误return true;};const proxy = new Proxy(fakeWindow, {set: (_: Window, p: PropertyKey, value: any): boolean => {const originalValue = (rawWindow as any)[p];return setTrap(p, value, originalValue, true);},get(_: Window, p: PropertyKey): any {// avoid who using window.window or window.self to escape the sandbox environment to touch the really window// or use window.top to check if an iframe context// see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13if (p === 'top' || p === 'parent' || p === 'window' || p === 'self') {return proxy;}const value = (rawWindow as any)[p];return getTargetValue(rawWindow, value);},// trap in operator// see https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constants.js#L12has(_: Window, p: string | number | symbol): boolean {return p in rawWindow;},getOwnPropertyDescriptor(_: Window, p: PropertyKey): PropertyDescriptor | undefined {const descriptor = Object.getOwnPropertyDescriptor(rawWindow, p);// A property cannot be reported as non-configurable, if it does not exists as an own property of the target objectif (descriptor && !descriptor.configurable) {descriptor.configurable = true;}return descriptor;},defineProperty(_: Window, p: string | symbol, attributes: PropertyDescriptor): boolean {const originalValue = (rawWindow as any)[p];const done = Reflect.defineProperty(rawWindow, p, attributes);const value = (rawWindow as any)[p];setTrap(p, value, originalValue, false);return done;},});this.proxy = proxy;}
}

ProxySandbox 源码解析:它将window上的所有属性遍历拷贝生成一个新的fakeWindow对象,紧接着使用proxy代理这个fakeWindow,用户对window操作全部被拦截下来,只作用于在这个fakeWindow之上

import type { SandBox } from '../interfaces';
import { SandBoxType } from '../interfaces';
import { nativeGlobal, nextTask } from '../utils';
import { getTargetValue, setCurrentRunningApp, getCurrentRunningApp } from './common';type SymbolTarget = 'target' | 'globalContext';type FakeWindow = Window & Record<PropertyKey, any>;/*** fastest(at most time) unique array method* @see https://jsperf.com/array-filter-unique/30*/
function uniq(array: Array<string | symbol>) {return array.filter(function filter(this: PropertyKey[], element) {return element in this ? false : ((this as any)[element] = true);}, Object.create(null));
}// zone.js will overwrite Object.defineProperty
const rawObjectDefineProperty = Object.defineProperty;const variableWhiteListInDev =process.env.NODE_ENV === 'development' || window.__QIANKUN_DEVELOPMENT__? [// for react hot reload// see https://github.com/facebook/create-react-app/blob/66bf7dfc43350249e2f09d138a20840dae8a0a4a/packages/react-error-overlay/src/index.js#L180'__REACT_ERROR_OVERLAY_GLOBAL_HOOK__',]: [];
// who could escape the sandbox
const variableWhiteList: PropertyKey[] = [// FIXME System.js used a indirect call with eval, which would make it scope escape to global// To make System.js works well, we write it back to global window temporary// see https://github.com/systemjs/systemjs/blob/457f5b7e8af6bd120a279540477552a07d5de086/src/evaluate.js#L106'System',// see https://github.com/systemjs/systemjs/blob/457f5b7e8af6bd120a279540477552a07d5de086/src/instantiate.js#L357'__cjsWrapper',...variableWhiteListInDev,
];/*variables who are impossible to be overwrite need to be escaped from proxy sandbox for performance reasons*/
const unscopables = {undefined: true,Array: true,Object: true,String: true,Boolean: true,Math: true,Number: true,Symbol: true,parseFloat: true,Float32Array: true,isNaN: true,Infinity: true,Reflect: true,Float64Array: true,Function: true,Map: true,NaN: true,Promise: true,Proxy: true,Set: true,parseInt: true,requestAnimationFrame: true,
};const useNativeWindowForBindingsProps = new Map<PropertyKey, boolean>([['fetch', true],['mockDomAPIInBlackList', process.env.NODE_ENV === 'test'],
]);function createFakeWindow(globalContext: Window) {// map always has the fastest performance in has check scenario// see https://jsperf.com/array-indexof-vs-set-has/23const propertiesWithGetter = new Map<PropertyKey, boolean>();const fakeWindow = {} as FakeWindow;/*copy the non-configurable property of global to fakeWindowsee https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor> A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object.*/Object.getOwnPropertyNames(globalContext).filter((p) => {const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);return !descriptor?.configurable;}).forEach((p) => {const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);if (descriptor) {const hasGetter = Object.prototype.hasOwnProperty.call(descriptor, 'get');/*make top/self/window property configurable and writable, otherwise it will cause TypeError while get trap return.see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get> The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable data property.*/if (p === 'top' ||p === 'parent' ||p === 'self' ||p === 'window' ||(process.env.NODE_ENV === 'test' && (p === 'mockTop' || p === 'mockSafariTop'))) {descriptor.configurable = true;/*The descriptor of window.window/window.top/window.self in Safari/FF are accessor descriptors, we need to avoid adding a data descriptor while it wasExample:Safari/FF: Object.getOwnPropertyDescriptor(window, 'top') -> {get: function, set: undefined, enumerable: true, configurable: false}Chrome: Object.getOwnPropertyDescriptor(window, 'top') -> {value: Window, writable: false, enumerable: true, configurable: false}*/if (!hasGetter) {descriptor.writable = true;}}if (hasGetter) propertiesWithGetter.set(p, true);// freeze the descriptor to avoid being modified by zone.js// see https://github.com/angular/zone.js/blob/a5fe09b0fac27ac5df1fa746042f96f05ccb6a00/lib/browser/define-property.ts#L71
        rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor));}});return {fakeWindow,propertiesWithGetter,};
}let activeSandboxCount = 0;/*** 基于 Proxy 实现的沙箱*/
export default class ProxySandbox implements SandBox {/** window 值变更记录 */private updatedValueSet = new Set<PropertyKey>();name: string;type: SandBoxType;proxy: WindowProxy;globalContext: typeof window;sandboxRunning = true;latestSetProp: PropertyKey | null = null;private registerRunningApp(name: string, proxy: Window) {if (this.sandboxRunning) {const currentRunningApp = getCurrentRunningApp();if (!currentRunningApp || currentRunningApp.name !== name) {setCurrentRunningApp({ name, window: proxy });}// FIXME if you have any other good ideas// remove the mark in next tick, thus we can identify whether it in micro app or not// this approach is just a workaround, it could not cover all complex cases, such as the micro app runs in the same task context with master in some casenextTask(() => {setCurrentRunningApp(null);});}}active() {if (!this.sandboxRunning) activeSandboxCount++;this.sandboxRunning = true;}inactive() {if (process.env.NODE_ENV === 'development') {console.info(`[qiankun:sandbox] ${this.name} modified global properties restore...`, [...this.updatedValueSet.keys(),]);}if (--activeSandboxCount === 0) {variableWhiteList.forEach((p) => {if (this.proxy.hasOwnProperty(p)) {// @ts-ignoredelete this.globalContext[p];}});}this.sandboxRunning = false;}constructor(name: string, globalContext = window) {this.name = name;this.globalContext = globalContext;this.type = SandBoxType.Proxy;const { updatedValueSet } = this;const { fakeWindow, propertiesWithGetter } = createFakeWindow(globalContext);const descriptorTargetMap = new Map<PropertyKey, SymbolTarget>();const hasOwnProperty = (key: PropertyKey) => fakeWindow.hasOwnProperty(key) || globalContext.hasOwnProperty(key);const proxy = new Proxy(fakeWindow, {set: (target: FakeWindow, p: PropertyKey, value: any): boolean => {if (this.sandboxRunning) {this.registerRunningApp(name, proxy);// We must kept its description while the property existed in globalContext beforeif (!target.hasOwnProperty(p) && globalContext.hasOwnProperty(p)) {const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);const { writable, configurable, enumerable } = descriptor!;if (writable) {Object.defineProperty(target, p, {configurable,enumerable,writable,value,});}} else {// @ts-ignoretarget[p] = value;}if (variableWhiteList.indexOf(p) !== -1) {// @ts-ignoreglobalContext[p] = value;}updatedValueSet.add(p);this.latestSetProp = p;return true;}if (process.env.NODE_ENV === 'development') {console.warn(`[qiankun] Set window.${p.toString()} while sandbox destroyed or inactive in ${name}!`);}// 在 strict-mode 下,Proxy 的 handler.set 返回 false 会抛出 TypeError,在沙箱卸载的情况下应该忽略错误return true;},get: (target: FakeWindow, p: PropertyKey): any => {this.registerRunningApp(name, proxy);if (p === Symbol.unscopables) return unscopables;// avoid who using window.window or window.self to escape the sandbox environment to touch the really window// see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13if (p === 'window' || p === 'self') {return proxy;}// hijack globalWindow accessing with globalThis keywordif (p === 'globalThis') {return proxy;}if (p === 'top' ||p === 'parent' ||(process.env.NODE_ENV === 'test' && (p === 'mockTop' || p === 'mockSafariTop'))) {// if your master app in an iframe context, allow these props escape the sandboxif (globalContext === globalContext.parent) {return proxy;}return (globalContext as any)[p];}// proxy.hasOwnProperty would invoke getter firstly, then its value represented as globalContext.hasOwnPropertyif (p === 'hasOwnProperty') {return hasOwnProperty;}if (p === 'document') {return document;}if (p === 'eval') {return eval;}const value = propertiesWithGetter.has(p)? (globalContext as any)[p]: p in target? (target as any)[p]: (globalContext as any)[p];/* Some dom api must be bound to native window, otherwise it would cause exception like 'TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation'See this code:const proxy = new Proxy(window, {});const proxyFetch = fetch.bind(proxy);proxyFetch('https://qiankun.com');*/const boundTarget = useNativeWindowForBindingsProps.get(p) ? nativeGlobal : globalContext;return getTargetValue(boundTarget, value);},// trap in operator// see https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constants.js#L12has(target: FakeWindow, p: string | number | symbol): boolean {return p in unscopables || p in target || p in globalContext;},getOwnPropertyDescriptor(target: FakeWindow, p: string | number | symbol): PropertyDescriptor | undefined {/*as the descriptor of top/self/window/mockTop in raw window are configurable but not in proxy target, we need to get it from target to avoid TypeErrorsee https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/getOwnPropertyDescriptor> A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object.*/if (target.hasOwnProperty(p)) {const descriptor = Object.getOwnPropertyDescriptor(target, p);descriptorTargetMap.set(p, 'target');return descriptor;}if (globalContext.hasOwnProperty(p)) {const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);descriptorTargetMap.set(p, 'globalContext');// A property cannot be reported as non-configurable, if it does not exists as an own property of the target objectif (descriptor && !descriptor.configurable) {descriptor.configurable = true;}return descriptor;}return undefined;},// trap to support iterator with sandboxownKeys(target: FakeWindow): ArrayLike<string | symbol> {return uniq(Reflect.ownKeys(globalContext).concat(Reflect.ownKeys(target)));},defineProperty(target: Window, p: PropertyKey, attributes: PropertyDescriptor): boolean {const from = descriptorTargetMap.get(p);/*Descriptor must be defined to native window while it comes from native window via Object.getOwnPropertyDescriptor(window, p),otherwise it would cause a TypeError with illegal invocation.*/switch (from) {case 'globalContext':return Reflect.defineProperty(globalContext, p, attributes);default:return Reflect.defineProperty(target, p, attributes);}},deleteProperty: (target: FakeWindow, p: string | number | symbol): boolean => {this.registerRunningApp(name, proxy);if (target.hasOwnProperty(p)) {// @ts-ignoredelete target[p];updatedValueSet.delete(p);return true;}return true;},// makes sure `window instanceof Window` returns truthy in micro app
      getPrototypeOf() {return Reflect.getPrototypeOf(globalContext);},});this.proxy = proxy;activeSandboxCount++;}
}

css隔离 有2种方案,一种是基于shadowDom;(缺陷:样式弹窗脱离shadow tree会污染)   一种类似vue 的scoped给每一个子应用加一个约束;

预加载

  html entry 通过http请求加载制定地址的首屏内容html,解析-template script entry  styles  暴漏一个promise对象

{// template 是link替换为style之后的template
    template: embedHTML,// 静态资源文件
    assetPublicPath,// 获取外部脚本getExternalScripts: () => getExternalScripts(scripts, fetch),// 获取外部样式文件getExternalStyleSheets: () => getExternalStylesSheets(styles, fetch),// 脚本执行器, 让js代码在指定上下文中运行execScripts: (proxy, strictGlobal) => {if(!scripts.length){return Promise.resolve()}return execScripts(entry, scripts, proxy, {fetch, strictGlobal})}
}

 

  js entry 加载的不是应用本身,是js导出文件,并且含有生命周期;

应用通讯

  主基座创建全局的globalState对象,修改值变化方法:setGlobalState  监听值变化:onGlobalStateChange

// ------------------主应用------------------
import { initGlobalState, MicroAppStateActions } from 'qiankun';// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);// 在当前应用监听全局状态,有变更触发 callback
actions.onGlobalStateChange((state, prev) => {// state: 变更后的状态; prev 变更前的状态
  console.log(state, prev);
});
// 按一级属性设置全局状态,微应用中只能修改已存在的一级属性
actions.setGlobalState(state);
// 移除当前应用的状态监听,微应用 umount 时会默认调用
actions.offGlobalStateChange();// ------------------子应用------------------
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {props.onGlobalStateChange((state, prev) => {// state: 变更后的状态; prev 变更前的状态
    console.log(state, prev);});props.setGlobalState(state);
}

源码解析:initGlobalState函数的执行中完成了一个发布订阅模式的创建工作,并返回了相关的订阅/发布/注销方法

import { cloneDeep } from 'lodash';
import type { OnGlobalStateChangeCallback, MicroAppStateActions } from './interfaces';
// 全局状态
let globalState: Record<string, any> = {};
// 缓存相关的订阅者
const deps: Record<string, OnGlobalStateChangeCallback> = {};// 触发全局监听
function emitGlobal(state: Record<string, any>, prevState: Record<string, any>) {Object.keys(deps).forEach((id: string) => {if (deps[id] instanceof Function) {// 依次通知订阅者
      deps[id](cloneDeep(state), cloneDeep(prevState));}});
}
// 初始化
export function initGlobalState(state: Record<string, any> = {}) {if (state === globalState) {console.warn('[qiankun] state has not changed!');} else {const prevGlobalState = cloneDeep(globalState);globalState = cloneDeep(state);emitGlobal(globalState, prevGlobalState);}// 返回相关方法,形成闭包存储相关状态return getMicroAppStateActions(`global-${+new Date()}`, true);
}export function getMicroAppStateActions(id: string, isMaster?: boolean): MicroAppStateActions {return {/*** onGlobalStateChange 全局依赖监听** 收集 setState 时所需要触发的依赖** 限制条件:每个子应用只有一个激活状态的全局监听,新监听覆盖旧监听,若只是监听部分属性,请使用 onGlobalStateChange** 这么设计是为了减少全局监听滥用导致的内存爆炸** 依赖数据结构为:* {*   {id}: callback* }** @param callback* @param fireImmediately 是否立即执行callback*/onGlobalStateChange(callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) {if (!(callback instanceof Function)) {console.error('[qiankun] callback must be function!');return;}if (deps[id]) {console.warn(`[qiankun] '${id}' global listener already exists before this, new listener will overwrite it.`);}/ 注册订阅deps[id] = callback;if (fireImmediately) {const cloneState = cloneDeep(globalState);callback(cloneState, cloneState);}},/*** setGlobalState 更新 store 数据** 1. 对输入 state 的第一层属性做校验,只有初始化时声明过的第一层(bucket)属性才会被更改* 2. 修改 store 并触发全局监听** @param state*/setGlobalState(state: Record<string, any> = {}) {if (state === globalState) {console.warn('[qiankun] state has not changed!');return false;}const changeKeys: string[] = [];const prevGlobalState = cloneDeep(globalState);globalState = cloneDeep(Object.keys(state).reduce((_globalState, changeKey) => {if (isMaster || _globalState.hasOwnProperty(changeKey)) {changeKeys.push(changeKey);return Object.assign(_globalState, { [changeKey]: state[changeKey] });}console.warn(`[qiankun] '${changeKey}' not declared when init state!`);return _globalState;}, globalState),);if (changeKeys.length === 0) {console.warn('[qiankun] state has not changed!');return false;}// 触发全局监听
      emitGlobal(globalState, prevGlobalState);return true;},// 注销该应用下的依赖
    offGlobalStateChange() {delete deps[id];return true;},};
}

iframe那不行?

1.url不同步,浏览器刷新iframe url 状态丢失、后退前进按钮无法使用;

2.全文上下文完全隔离,内存变量不共享;iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。

3.慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。

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

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

相关文章

Linux捣鼓记录:快速搭建alist+aria2+qbittorrent

简介:使用docker-compose创建alist aria2 qbittorrent服务,前置条件安装docker及docker-compose插件,docker镜像仓库访问不了,建议配置代理用来拉取镜像。 一、确认路径,确认UID GID,确认端口 路径 alist挂载路径: - /home/dalong/app/alist:/opt/alist/data - /home/d…

php webman使用fileboy热加载

1.下载fileboy文件下载地址:https://gitee.com/dengsgo/fileboy/releases 2.在工作目录创建一个文件夹,把下载的exr文件复制一份到文件夹,重命名为‘fileboy.exe’,添加系统变量PATH: 3.打开cmd命令窗口执行 fileboy 命令,出现以下图说明配置成功 4.切换到项目根目录,执…

统计学入门:时间序列分析基础知识详解

时间序列分析中包含了许多复杂的数学公式,它们往往难以留存于记忆之中。为了更好地掌握这些内容,本文将整理并总结时间序列分析中的一些核心概念,如自协方差、自相关和平稳性等,并通过Python实现和图形化展示这些概念,使其更加直观易懂。希望通过这篇文章帮助大家更清楚地…

组合API-ref函数

当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可其他情况使用ref<template><div class="container"><div>{{name}}</div><div>{{age}}</div><button @click="updateName">修改数据</butt…

重磅来袭!MoneyPrinterPlus一键发布短视频到视频号,抖音,快手,小红书上线了

一键发布短视频到视频号,抖音,快手,小红书,MoneyPrinterPlus解放你的双手。MoneyPrinterPlus开源有一段时间了,已经实现了批量短视频混剪,一键生成短视频等功能。 有些小伙伴说了,我批量生成的短视频能不能一键上传到视频号,抖音,快手,小红书这些视频平台呢?答案是必须可以…

OTA自动化测试解决方案——实车级OTA测试系统PAVELINK.OTABOX

引言往期内容里为大家介绍了OTA技术、OTA后续的发展趋势预测及OTA自动化测试解决方案。本文是OTA系列的第三篇文章,今天主要向大家介绍实车级OTA自动化测试的实现手段,并简单介绍北汇信息的实车级OTA自动化测试解决方案——PAVELINK.OTABOX。实车级OTA自动化系统目前,OTA自动…

设置DepthBufferBits和设置DepthStencilFormat的区别

1)设置DepthBufferBits和设置DepthStencilFormat的区别2)Unity打包exe后,游戏内拉不起Steam的内购3)Unity 2022以上Profiler.FlushMemoryCounters耗时要怎么关掉4)用GoodSky资产包如何实现昼夜播发不同音乐功能这是第394篇UWA技术知识分享的推送,精选了UWA社区的热门话题…

深度学习第二课 Practical Aspect of Deep learning

Practical Aspect of Deep learning week1 深度学习的实用层面 1.1 训练/开发/测试集在机器学习发展的小数据量时代,常见做法是将所有数据三七分,就是人们常说的70%验证集,30%测试集,如果没有明确设置验证集,也可以按照60%训练,20%验证和20%测试集来划分。这是前几年机器…

winform窗体DataGridView合并单元格处理

文本是使用SunnyUI的UIDataGridView控件进行演示的,同样适用于System.Windows.Forms.DataGridView控件 具体需求如下,下表是个成绩表,其中姓名、总分、平均分这三列信息重复,需要对数据表进行合并单元格处理。 实现该需求需要两个步骤: 1.给表格添加单元格重绘事件 在方法…

您的AI英语搭子!

本文由 ChatMoney团队出品 人工智能的发展,掀起了一波又一波AI浪潮,适合英语老师的AI软件也不断问世,老师们可以借助AI技术辅助自己的教学、帮助学生学习。你是否苦于想学习英语却没有语言环境,写英语内容时不知道语法和拼写是否正确,不知道表达方式是否足够的“Native”?…

Rocky Linux 9.4安装MySQL:使用RPM安装包方式

Rocky Linux 9.4安装MySQL:使用RPM安装包方式 一、安装环境安装环境如下:服务器:Rocky Linux 9.4安装版本:MySQL 8.0.38 二、安装过程和细节 1、在官网下载 RPM 安装包官网下载地址如下,这个地址里有各个版本的安装包,根据自己的版本选择,下载对应系统的安装包 https://…

Windows10设置任务栏时间显示秒数

Windows10设置任务栏时间显示秒数 操作步骤: 1、打开注册表 【Windows + R】键打开运行窗口,输入【regedit】,回车打开注册表编辑器。 2、新建注册表项 1)在注册表编辑器地址栏输入【计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanc…