15 私有链接
要活用 ... 展开运算符,关键是理解它在不同场景下的灵活应用。以下是一些常见的用法场景和技巧,帮助你更好地掌握它的使用。
当你需要更新或合并两个对象时,展开运算符非常方便。例如:
在对象中合并和更新数据
更新对象:
const user = { name: 'Alice', age: 25 };
const updatedUser = { ...user, age: 26 }; // 覆盖 age
console.log(updatedUser); // { name: 'Alice', age: 26 }
用展开运算符创建一个新对象,其中后面的值会覆盖前面的。
合并多个对象:
const address = { city: 'New York', zip: '10001' };
const profile = { name: 'Bob', age: 30, ...address };
console.log(profile); // { name: 'Bob', age: 30, city: 'New York', zip: '10001' }
合并多个对象时,先展开基础对象,然后可以覆盖或添加额外属性。
浅拷贝对象或数组
当你需要创建一个对象或数组的副本时,展开运算符能非常方便地实现浅拷贝:
对象浅拷贝:
const original = { a: 1, b: 2 };
const copy = { ...original };
console.log(copy); // { a: 1, b: 2 }
数组浅拷贝:
const numbers = [1, 2, 3];
const copyNumbers = [...numbers];
console.log(copyNumbers); // [1, 2, 3]
使用展开运算符进行浅拷贝,避免直接修改原对象或数组。
数组合并
展开运算符不仅用于对象,也可以用来将多个数组合并:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const mergedArray = [...arr1, ...arr2];
console.log(mergedArray); // [1, 2, 3, 4, 5, 6]
在处理多个数组时,用展开运算符可以简洁地合并它们,而不需要 concat 方法。
函数参数解构
你可以用展开运算符传递可变数量的参数给函数,或者将数组展开为独立参数:
将数组展开为独立参数:
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
当函数参数列表是固定的,但你有一个数组时,展开数组作为独立参数非常方便。
传递不定数量的参数(rest 参数):
function sumAll(...args) {
return args.reduce((acc, val) => acc + val, 0);
}
console.log(sumAll(1, 2, 3, 4)); // 10
使用 ...args(rest 参数)接收不定数量的参数,然后在函数内部处理它们。
去重数组元素
展开运算符可以结合 Set 对象,快速去重数组元素:
const nums = [1, 2, 2, 3, 4, 4];
const uniqueNums = [...new Set(nums)];
console.log(uniqueNums); // [1, 2, 3, 4]
用 Set 去重后,用展开运算符将结果转回数组。
条件更新或过滤属性
展开运算符还可以在有条件的情况下更新对象属性,或者从对象中过滤掉某些不需要的字段:
有条件地添加属性:
const obj = {
name: 'John',
...(true && { age: 30 }) // 根据条件动态添加 age 属性
};
console.log(obj); // { name: 'John', age: 30 }
从对象中过滤掉某些属性:
const { age, ...rest } = { name: 'Alice', age: 25, city: 'LA' };
console.log(rest); // { name: 'Alice', city: 'LA' }
用解构赋值结合展开运算符,可以从对象中移除不需要的属性。
嵌套解构和展开
当你处理嵌套对象或数组时,可以结合展开运算符和解构赋值,操作特定的嵌套结构:
对象嵌套解构与更新:
const user = {
name: 'Alice',
address: {
city: 'New York',
zip: '10001'
}
};
const updatedUser = {
...user,
address: {
...user.address,
zip: '10002' // 更新嵌套的 zip 属性
}
};
console.log(updatedUser);
// 输出: { name: 'Alice', address: { city: 'New York', zip: '10002' } }
在嵌套结构中,先展开外层对象,再进一步操作内层对象,保证不改变原始对象的深层次结构。
数组嵌套解构:
const arr = [[1, 2], [3, 4], [5, 6]];
const [first, ...rest] = arr;
console.log(first); // [1, 2]
console.log(rest); // [[3, 4], [5, 6]]
用解构和展开运算符提取嵌套数组中的一部分,特别适合需要处理矩阵或多维数组时。
重新排序或分组数组
你可以使用展开运算符来灵活重新排序或分组数组元素:
将第一个元素移到最后:
const numbers = [1, 2, 3, 4];
const reordered = [...numbers.slice(1), numbers[0]];
console.log(reordered); // [2, 3, 4, 1]
使用 slice 和展开运算符轻松调整数组顺序。
简化参数传递(函数柯里化)
当你需要逐步传递参数给函数时,可以利用展开运算符简化函数柯里化的过程:
参数累积:
function curry(func, ...args) {
return function(...moreArgs) {
return func(...args, ...moreArgs); // 累积所有参数并调用
};
}
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum, 1, 2);
console.log(curriedSum(3)); // 6
通过 ...args 和 ...moreArgs 轻松传递和累积函数的参数,使代码更简洁、灵活。
减少数据嵌套的层次
当你有嵌套的数组或对象结构时,展开运算符可以帮助你快速"展平"它们:
合并嵌套数组:
const nestedArr = [1, [2, 3], [4, 5]];
const flattened = [].concat(...nestedArr);
console.log(flattened); // [1, 2, 3, 4, 5]
展开嵌套数组中的每一个子数组,并将它们与父数组合并为一个单层数组。虽然不能完全代替 flat,但对于简单嵌套,展开运算符是一种方便的方式。
替代 Object.assign()
通常在对象合并时,会使用 Object.assign(),但展开运算符是更简洁的替代方案:
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2 }
展开运算符比 Object.assign() 更简洁,且更易读。特别是在多个对象合并时,展开运算符更灵活。
数组插入元素
用展开运算符可以轻松地在数组中间插入新元素,而不必破坏原有数组结构:
在第2个位置插入:
const arr = [1, 2, 4];
const newArr = [...arr.slice(0, 2), 3, ...arr.slice(2)];
console.log(newArr); // [1, 2, 3, 4]
利用 slice 提取出数组的前后部分,使用展开运算符插入新元素,保持代码简洁。
动态构建对象
当你需要动态构建对象时,可以用展开运算符避免重复的代码逻辑:
function createUser(name, age, city) {
return {
...(name && { name }),
...(age && { age }),
...(city && { city })
};
}
console.log(createUser('Alice', 25));
// { name: 'Alice', age: 25 }
根据传入参数的情况,动态生成对象属性,减少条件判断代码的冗余。
实现不可变更新(Immutable Updates)
在函数式编程或 React 中,数据的不可变性非常重要。展开运算符可以轻松实现不可变更新:
const state = { count: 1, user: { name: 'Alice' } };
const newState = { ...state, count: state.count + 1 };
console.log(newState); // { count: 2, user: { name: 'Alice' } }
当更新嵌套状态时,确保通过展开运算符复制并更新状态,避免直接修改原始数据。
深拷贝对象和数组(结合递归)
展开运算符只进行浅拷贝,当对象或数组是嵌套结构时,如果你要进行深层次拷贝,必须结合递归。
对象深拷贝:
function deepClone(obj) {
return Object.keys(obj).reduce((acc, key) => {
acc[key] = typeof obj[key] === 'object' && obj[key] !== null ? deepClone(obj[key]) : obj[key];
return acc;
}, {});
}
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
cloned.b.c = 3;
console.log(original.b.c); // 输出: 2 (原对象没有改变)
console.log(cloned.b.c); // 输出: 3
在嵌套对象或数组场景下,使用递归和展开运算符结合,生成真正的深拷贝,避免对嵌套结构的引用问题。
基于条件的动态参数传递
在某些场景下,你可能需要动态地控制传递给函数的参数数量和顺序,展开运算符可以帮助你灵活处理这一问题:
动态构建函数参数:
function buildQuery(...conditions) {
return conditions.filter(Boolean).join(' AND ');
}
const includeCondition1 = true;
const includeCondition2 = false;
const query = buildQuery(
...(includeCondition1 ? ['name = "Alice"'] : []),
...(includeCondition2 ? ['age > 20'] : []),
'status = "active"'
);
console.log(query); // 输出: "name = "Alice" AND status = "active""
根据条件动态构建参数数组,利用展开运算符简化逻辑,减少条件判断的冗余。
处理树形或递归数据结构
当你处理树状结构的数据(例如 DOM 树、文件目录树等)时,展开运算符可以帮助你简洁地遍历和合并数据。
遍历和合并树形数据:
const tree = {
value: 1,
children: [
{ value: 2, children: [{ value: 4 }, { value: 5 }] },
{ value: 3 }
]
};
function flattenTree(node) {
return [
node.value,
...node.children?.map(flattenTree).flat() ?? [] // 使用展开运算符和递归
];
}
console.log(flattenTree(tree)); // 输出: [1, 2, 4, 5, 3]
在处理树形结构的数据时,结合展开运算符和递归,你可以方便地对每个节点进行操作,并构建新的平坦结构。
性能优化:批量操作的分割和合并
当你需要在数据量较大的数组中进行批量操作时,展开运算符可以帮助你快速分割数据,进行局部操作再合并。
批量更新和合并:
function batchUpdate(arr, batchSize) {
for (let i = 0; i < arr.length; i += batchSize) {
const batch = arr.slice(i, i + batchSize);
console.log('Processing batch:', batch);
// 假设这里进行批量更新操作
}
}
const data = [...Array(100).keys()]; // [0, 1, 2, ..., 99]
batchUpdate(data, 20); // 每次处理20个数据块
使用 slice() 和展开运算符,可以将大数据分割成小批量进行处理,适合性能优化场景,如分页加载数据等。
惰性求值(Lazy Evaluation)结合生成器
展开运算符也能与生成器函数一起使用,结合惰性求值(只在需要时生成值),处理大量数据时更为高效。
使用生成器与展开运算符:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const sequence = [...generateSequence(1, 10)];
console.log(sequence); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
生成器和展开运算符的结合,适合处理大规模的数据流或按需生成的序列。
与 Proxy 结合进行拦截和修改
如果你在使用代理模式(Proxy)来监控对象操作,展开运算符可以帮助你拦截对象中的动态操作,并在应用层进行修改或添加逻辑。
代理对象操作
const target = { name: 'Alice', age: 25 };
const handler = {
get(obj, prop) {
console.log(`Getting property ${prop}`);
return prop in obj ? obj[prop] : 'Unknown';
},
set(obj, prop, value) {
console.log(`Setting property ${prop} to ${value}`);
obj[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
const updated = { ...proxy, city: 'New York' }; // 结合展开运算符进行拦截
console.log(updated); // 输出代理对象操作日志,同时返回合并后的新对象
将展开运算符与 Proxy 结合,可以在对象操作的过程中实现拦截,提供灵活的调试、数据验证或安全控制。
动态解构和映射复杂数据
在处理 REST API 响应或者一些需要动态映射的场景中,展开运算符能够结合解构赋值,动态解析和组合数据。
动态解构和重组:
const apiResponse = {
data: { id: 1, name: 'Alice', age: 25 },
meta: { timestamp: '2023-08-25T12:00:00Z' }
};
const { data: { id, ...restData }, meta: { timestamp } } = apiResponse;
const transformedResponse = { id, timestamp, ...restData };
console.log(transformedResponse);
// 输出: { id: 1, timestamp: '2023-08-25T12:00:00Z', name: 'Alice', age: 25 }
使用解构结合展开运算符,从复杂的数据结构中提取需要的信息并重组,特别适用于 API 数据解析和转换。
HLS 加密 (HTTP Live Streaming + AES-128) 确实是非常实用的,尤其在需要保护视频内容的 Web 媒体流播放中,它是一个常见且强大的解决方案。以下是对 HLS 加密工作原理、实施步骤以及一些实用示例的详细介绍。
什么是 HLS?
HLS(HTTP Live Streaming) 是 Apple 开发的一种基于 HTTP 的流媒体协议。它通过将视频文件切割成许多小的分段文件(segment),并通过 .m3u8 播放列表 文件来管理分段文件。HLS 可以动态调整视频的比特率,根据网络状况或设备能力提供不同质量的视频流。
HLS 加密(AES-128 加密)
HLS 协议支持 AES-128 对称加密,这种加密方式可以为每个 HLS 的分段文件提供安全保护。加密后的视频分段文件只有在具备相应的解密密钥时才能播放。
AES-128 是一种常见的对称加密算法,对称加密意味着同一个密钥用于加密和解密视频文件。使用 AES-128 加密的 HLS 方案通常用于视频保护,防止未经授权的用户下载或播放视频。
HLS 加密的工作原理
HLS 加密主要有以下几个步骤:
视频切片:FFmpeg 将视频文件切成多个分段(.ts 文件)。
生成密钥:每个视频分段文件都会使用 AES-128 加密算法进行加密,密钥保存在一个独立的文件中。
生成播放列表(.m3u8):播放列表文件中会包含每个分段的地址,以及用来解密分段的密钥 URI 地址。
视频播放:播放器通过访问 .m3u8 文件,获取分段和密钥 URI,解密分段并播放视频。
实现 HLS 加密的步骤
我们将使用 FFmpeg 来生成加密的 HLS 视频流。需要准备一个视频文件和 OpenSSL 用于生成密钥。
- 生成 AES-128 密钥
使用 OpenSSL 来生成 128 位的密钥文件。执行以下命令生成一个 16 字节(128 位)的随机密钥:
openssl rand 16 > enc.key
enc.key 是我们生成的加密密钥文件,实际用于加密 HLS 的分段。
- 创建密钥信息文件
我们需要创建一个密钥信息文件(key_info_file.txt),它包含 HLS 播放列表中如何引用和访问密钥的相关信息。
密钥信息文件格式如下:
key_uri
/path/to/enc.key
IV
key_uri:播放器访问密钥的 URL 地址。可以是服务器上的文件路径,例如 https://example.com/encryption_key。
/path/to/enc.key:密钥的本地文件路径。这个路径用于 FFmpeg 在加密时找到密钥文件。
IV(初始化向量):这是一个可选项,如果不指定,FFmpeg 会自动生成。它用于增强加密的安全性。
示例 key_info_file.txt:
https://example.com/encryption_key
/path/to/enc.key
00000000000000000000000000000000
- 使用 FFmpeg 进行 HLS 加密
现在,我们将使用 FFmpeg 来生成加密的 HLS 视频。使用以下命令:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict -2 -hls_time 10 -hls_key_info_file key_info_file.txt -hls_playlist_type vod -hls_segment_filename 'segment_%03d.ts' output.m3u8
参数说明:
-i input.mp4:输入文件。
-c
libx264:使用 H.264 编码器。
-c
aac:使用 AAC 音频编码器。
-strict -2:确保兼容 AAC 编码。
-hls_time 10:每个 HLS 分段的时长为 10 秒。
-hls_key_info_file key_info_file.txt:使用我们之前创建的密钥信息文件进行加密。
-hls_playlist_type vod:生成点播类型的播放列表。
-hls_segmentfilename 'segment%03d.ts':指定输出的 HLS 分段文件名模式,生成如 segment_001.ts, segment_002.ts 的分段文件。
output.m3u8:输出的 HLS 播放列表文件。
- HLS 文件结构
生成的文件结构通常如下:
output.m3u8:HLS 播放列表文件。
segment_001.ts, segment_002.ts, ...:加密的视频分段文件。
enc.key:加密用的密钥文件(服务器需要安全存储)。
- 播放加密的 HLS
播放器(如 VLC、Safari 或一些 HLS 支持的 HTML5 播放器)在播放时会自动读取 .m3u8 文件中的信息,访问指定的 key_uri 获取密钥并解密视频分段。
如果密钥服务器要求用户身份验证(如登录验证),可以进一步保护密钥文件。
- 解密 HLS 流
播放器使用 .m3u8 文件中的 URI 获取密钥并解密 HLS 流。如果你想手动解密某个 .ts 视频分段,可以使用 OpenSSL 解密:
openssl aes-128-cbc -d -in encrypted_segment.ts -out decrypted_segment.ts -K <hex_key> -iv <hex_iv>
-K <hex_key>:这是你的 AES 密钥(以十六进制表示)。
-iv <hex_iv>:初始化向量(IV),如果你没有指定,FFmpeg 会自动生成。
总结:
通过 HLS 和 AES-128 加密,你可以有效保护视频内容,防止未经授权的下载和播放。关键点在于:
生成密钥并确保密钥文件的安全。
创建密钥信息文件以供 FFmpeg 使用。
加密视频分段并生成 HLS 播放列表。
密钥分发与访问控制:通过安全的密钥 URI 和服务器验证,确保只有授权用户能够解密视频。
这个流程比较适合 Web 环境下的流媒体播放,广泛应用于需要保护内容的场景,比如在线教育、付费视频、流媒体平台等。
如果密钥文件 enc.key 被暴露并且可以直接访问,那么任何人都能下载这个密钥并用它解密加密的 HLS 分段文件。这确实会对安全性构成威胁。因此,密钥的保护至关重要。为了确保加密方案的安全性,有几个策略可以用来保护密钥,防止未经授权的访问。
- 密钥存储在安全的服务器上
密钥文件不应该直接与视频文件一起存储在同一目录下或同一公开服务器上。应该将密钥文件存储在受保护的服务器或需要身份验证的服务器上,只有经过身份验证的用户才能访问该密钥。
密钥 URI 应指向一个受保护的地址。只有当客户端(例如浏览器或播放器)通过身份验证后,才能获取该密钥并解密视频。
具体实施方法:
HTTPS:确保密钥传输过程使用 HTTPS 加密协议,这样在传输过程中,密钥不会被中途截获。
身份验证:设置服务端的身份验证机制,只有通过用户登录验证或访问令牌验证的用户才能访问密钥文件。
示例:
在 key_info_file.txt 中的密钥 URI 可以是这样:
https://secure.example.com/get_key?id=video123
/path/to/enc.key
00000000000000000000000000000000
而不是直接将 enc.key 文件暴露在一个公开的 URL 上。
当用户请求密钥时,服务器可以通过用户认证、令牌验证或其他机制来确保只有合法用户能够获取密钥。
- 使用动态密钥和短时有效的密钥
为了进一步增加安全性,可以使用动态密钥生成,并限制密钥的有效时间。这样每次视频播放时,密钥都会不同,减少了密钥被重复使用的风险。
如何实现:
短时有效密钥:每次视频播放或特定时间段内生成一个新的密钥。密钥的有效时间可以很短,例如 5-10 分钟,过了这个时间后密钥失效。下次播放时用户必须重新获取密钥。
动态密钥生成:通过服务器动态生成密钥,避免长时间存储在某个固定位置。用户每次播放视频时,都需要通过身份验证获取新的密钥。
- 结合身份验证系统
你可以将 HLS 密钥的访问控制与用户身份验证系统(如 OAuth 或 JWT 令牌)结合起来。这种方式确保只有经过授权的用户才能访问密钥:
OAuth 或 JWT 验证:在用户登录后,服务器为用户生成一个短时效的令牌。只有携带这个令牌的请求才能访问密钥文件。
服务器端验证:通过用户的身份验证系统来验证密钥的访问权限,确保只有付费用户、注册用户或特定权限的用户可以获取密钥。
示例:
用户请求播放视频时,播放器首先访问一个授权 API,获取视频播放的授权 token 和密钥 URI。例如:
https://secure.example.com/get_key?id=video123&token=abcdef123456
服务器根据用户的身份或权限生成一个带有时效限制的密钥,并将其提供给播放器。播放器可以通过这个密钥来解密视频。
- 防止密钥缓存
为了防止密钥被恶意缓存或窃取,你还可以通过以下方式进一步加强安全:
禁止 HTTP 缓存:确保在提供密钥时,服务器不允许客户端缓存密钥。可以通过在 HTTP 响应头中添加 Cache-Control: no-store 来确保密钥不会被缓存。
限制密钥使用次数:服务器可以为每个密钥设定使用次数限制,确保每个密钥只能使用一次或几次,之后即失效。
- 结合 DRM 解决方案
如果需要更高级别的安全性,许多专业的视频平台会使用 DRM(数字版权管理) 技术,如 Google Widevine 或 Microsoft PlayReady。这些 DRM 技术可以在客户端和服务器之间建立更强的加密和身份验证机制,防止密钥被泄露或滥用。
DRM 的特点是:
加密过程更加复杂,使用非对称密钥加密,客户端无法直接访问解密密钥。
需要支持 DRM 的播放器来播放内容。
对应的 DRM 密钥管理服务器(KMS)可以动态管理密钥和权限。
- 限制密钥的访问条件
可以通过一些额外的机制来限制密钥的访问,比如:
IP 地址限制:只允许特定 IP 地址访问密钥,比如仅允许来自某些 CDN 或服务器的请求访问密钥。
时效限制:密钥只能在特定时间内有效。
HTTPS 保护:密钥文件的传输必须使用 HTTPS 协议,确保传输过程的安全性。
总结
尽管 AES-128 HLS 加密本身很安全,但关键在于如何保护密钥文件。具体策略包括:
密钥存储在安全的服务器:通过 HTTPS 和身份验证保护密钥。
使用动态密钥和短时效密钥:减少密钥暴露的机会。
结合身份验证系统:确保只有经过验证的用户能够访问密钥。
防止密钥缓存和滥用:确保密钥不能被重复使用或缓存。
结合 DRM 解决方案:如果需要更高的安全性,可以使用专业的 DRM 保护方案。
通过这些策略,你可以有效保护视频的加密密钥,防止未经授权的用户访问和解密视频。
-
2K (2048x1080)
ffmpeg -i input.mp4 -vf scale=2048:-1 -c:v libvpx-vp9 -b:v 4M -crf 24 -row-mt 1 -threads 4 -pass 1 -an -f null /dev/null && ffmpeg -i input.mp4 -vf scale=2048:-1 -c:v libvpx-vp9 -b:v 4M -crf 24 -row-mt 1 -threads 4 -pass 2 -c:a libopus -b:a 128k output_2k.webm
-
1920x1080 (Full HD)
ffmpeg -i input.mp4 -vf scale=1920:-1 -c:v libvpx-vp9 -b:v 3M -crf 25 -row-mt 1 -threads 4 -pass 1 -an -f null /dev/null && ffmpeg -i input.mp4 -vf scale=1920:-1 -c:v libvpx-vp9 -b:v 3M -crf 25 -row-mt 1 -threads 4 -pass 2 -c:a libopus -b:a 128k output_1080p.webm
-
1280x720 (HD)
ffmpeg -i input.mp4 -vf scale=1280:-1 -c:v libvpx-vp9 -b:v 2M -crf 26 -row-mt 1 -threads 4 -pass 1 -an -f null /dev/null && ffmpeg -i input.mp4 -vf scale=1280:-1 -c:v libvpx-vp9 -b:v 2M -crf 26 -row-mt 1 -threads 4 -pass 2 -c:a libopus -b:a 96k output_720p.webm
-
800x600 (SVGA)
ffmpeg -i input.mp4 -vf scale=800:-1 -c:v libvpx-vp9 -b:v 1M -crf 28 -row-mt 1 -threads 4 -pass 1 -an -f null /dev/null && ffmpeg -i input.mp4 -vf scale=800:-1 -c:v libvpx-vp9 -b:v 1M -crf 28 -row-mt 1 -threads 4 -pass 2 -c:a libopus -b:a 64k output_600p.webm
-
480x320 (低分辨率)
ffmpeg -i input.mp4 -vf scale=480:-1 -c:v libvpx-vp9 -b:v 600k -crf 30 -row-mt 1 -threads 4 -pass 1 -an -f null /dev/null && ffmpeg -i input.mp4 -vf scale=480:-1 -c:v libvpx-vp9 -b:v 600k -crf 30 -row-mt 1 -threads 4 -pass 2 -c:a libopus -b:a 48k output_320p.webm
-
WebM 格式(使用 VP9)最快最小配置命令:
ffmpeg.exe -i input.mkv -vf "unsharp=3:3:0.6:3:3:0.6,eq=contrast=0.98,hue=h=0,hqdn3d,scale=w=480:h=-1" -threads 6 -c:v libvpx-vp9 -speed 4 -crf 45 -b:v 50k -r 18 -pass 1 -an -map_metadata -1 -fflags +genpts -y -f null NUL && ffmpeg.exe -i input.mkv -vf "unsharp=3:3:0.6:3:3:0.6,eq=contrast=0.98,hue=h=0,hqdn3d,scale=w=480:h=-1" -threads 6 -c:v libvpx-vp9 -speed 4 -crf 45 -b:v 50k -r 18 -pass 2 -c:a libopus -b:a 32k -map_metadata -1 -fflags +genpts -y -loglevel info output_320p_min_fast.webm
安装koa2-cors:
npm install koa2-cors
const Koa = require('koa');
const cors = require('koa2-cors');
const app = new Koa();
app.use(cors({
origin: (ctx) => {
// 限制特定域名或根据条件动态设置
if (ctx.request.header.origin === 'http://allowed-origin.com') {
return 'http://allowed-origin.com';
}
return '*'; // 允许所有域名
},
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'], // 限制允许的请求方法
allowHeaders: ['Content-Type', 'Authorization', 'Accept'], // 指定允许的请求头
credentials: true, // 是否允许发送cookie
}));
// 其他中间件
app.use(async ctx => {
ctx.body = 'CORS enabled!';
});
app.listen(3000);
高级注意事项:
安全性考虑:
生产环境中尽量避免使用'*'作为origin值,特别是如果credentials设置为true时,这会导致安全隐患,确保origin只允许可信域名。
动态控制:
根据不同请求动态返回CORS设置,特别是针对不同的API,可以使用自定义逻辑来动态决定是否允许跨域请求。
复杂请求:
某些带有自定义请求头或非简单方法(如PUT、DELETE)的请求是“预检请求”,确保在返回响应头时正确处理Access-Control-Allow-Headers和Access-Control-Allow-Methods。
这样就可以在Koa2中灵活地控制和管理CORS设置。
使用'*'作为origin值
const Koa = require('koa');
const cors = require('koa2-cors');
const app = new Koa();
app.use(cors({
origin: '*', // 允许所有域名
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'], // 允许的请求方法
allowHeaders: ['Content-Type', 'Authorization', 'Accept'], // 允许的请求头
credentials: false, // 开发环境下不需要cookie,设置为false
}));
app.use(async ctx => {
ctx.body = 'CORS enabled with * origin!';
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在 PostgreSQL 中,默认情况下,所有表都被创建在 public 模式(schema)下。不过,你可以指定一个不同的模式,或者在创建模型时选择使用自定义的模式。你也可以通过 Sequelize 的 schema 方法在指定的模式下创建表。
- 新建一个模式
首先,你需要确保在 PostgreSQL 数据库中创建了自定义模式。你可以通过以下 SQL 命令新建一个模式(假设模式名为 custom_schema):
CREATE SCHEMA custom_schema;、
- 使用 Sequelize 配置自定义模式
在 Sequelize 中,你可以通过 schema 方法为模型指定自定义的模式。你可以全局指定模式,或者为每个模型单独指定模式。
2.1 为所有模型设置默认模式
如果你希望所有模型都使用某个自定义的模式,可以在 define 配置中指定 schema 选项:
const { Sequelize } = require('sequelize');
// 初始化 Sequelize,连接到 PostgreSQL 数据库
const sequelize = new Sequelize('cadre_evaluation', 'postgres', '123456', {
host: 'localhost', // PostgreSQL 数据库所在的主机
dialect: 'postgres', // 使用 PostgreSQL
port: 5432, // 默认 PostgreSQL 端口号
logging: false, // 禁用日志
define: {
schema: 'custom_schema' // 这里指定全局模式
}
});
// 测试连接
sequelize.authenticate()
.then(() => {
console.log('已成功建立连接。');
sequelize.sync({ force: false }) // force: true 将强制重建表
.then(() => {
console.log('数据库和表已创建!');
});
})
.catch(err => {
console.error('无法连接到数据库:', err);
});
module.exports = sequelize;
这样,所有模型在被创建时都会使用 custom_schema 模式,而不是默认的 public 模式。
2.2 为特定模型设置自定义模式
如果你只想为某些模型指定模式,可以在定义模型时使用 schema 方法:
const User = sequelize.define('User', {
// 定义字段
firstName: {
type: Sequelize.STRING,
allowNull: false
},
lastName: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true
}
});
// 为该模型使用特定的 schema
User.sync({ schema: 'custom_schema' })
.then(() => {
console.log('User 表已在 custom_schema 模式下创建!');
})
.catch(err => {
console.error('无法创建表:', err);
});
- 在 PostgreSQL 中创建并授权模式
如果你使用了自定义模式,你需要确保 PostgreSQL 中创建了这个模式,并且适当的权限被授予给用户。例如,创建一个新的模式并授权 postgres 用户访问:
CREATE SCHEMA custom_schema;
GRANT ALL PRIVILEGES ON SCHEMA custom_schema TO postgres;
总结
你可以通过 define 选项全局指定默认的模式。
也可以在模型定义时通过 schema 方法为每个模型指定自定义的模式。
确保自定义模式已经在 PostgreSQL 中创建,并且适当的权限被授予使用该模式的用户。
Docker安装On-Prem-Server:
apk update
apk add docker
docker pull navicat/navicatonpremserver:1.2.0
docker run -d -p 3030:3030 --mac-address="92:20:de:b0:5b:51" -v navicat_onprem_server_var_data:/opt/navicatonpremserver/x86_64-linux-gnu/var -v docker run -d --restart always -p 3030:3030 --mac-address="92:20:de:b0:5b:51" -v navicat_onprem_server_var_data:/opt/navicatonpremserver/x86_64-linux-gnu/var -v navicat_onprem_server_cert_data:/opt/navicatonpremserver/x86_64-linux-gnu/cert navicat/navicatonpremserver:1.2.0
完全删除实例:
docker ps
docker stop <container_id_or_name>
docker rm <container_id_or_name>
docker volume rm navicat_onprem_server_var_data navicat_onprem_server_cert_data
docker run -d -p 3030:3030 --mac-address="92:20:de:b0:5b:51" -v navicat_onprem_server_var_data:/opt/navicatonpremserver/x86_64-linux-gnu/var -v docker run -d --restart always -p 3030:3030 --mac-address="92:20:de:b0:5b:51" -v navicat_onprem_server_var_data:/opt/navicatonpremserver/x86_64-linux-gnu/var -v navicat_onprem_server_cert_data:/opt/navicatonpremserver/x86_64-linux-gnu/cert navicat/navicatonpremserver:1.2.0
更新实例:
docker stop <container_id_or_name>
docker update --restart always <container_id_or_name>
docker start <container_id_or_name>
将容器打包为镜像:
docker commit <container_id_or_name> my_navicat_image:1.0
docker images
使用新镜像创建容器:
docker run -d --restart always -p 3030:3030 --mac-address="92:20:de:b0:5b:51" -v navicat_onprem_server_var_data:/opt/navicatonpremserver/x86_64-linux-gnu/var -v navicat_onprem_server_cert_data:/opt/navicatonpremserver/x86_64-linux-gnu/cert my_navicat_image:1.0
备份镜像:
docker save -o my_navicat_image.tar my_navicat_image:1.0
恢复镜像:
通过 docker load 命令恢复镜像:
docker load -i my_navicat_image.tar
certbot-nginx添加定时任务,每月一次:
vi /var/spool/cron/crontabs/root
0 0 1 * * root sleep 2976 && certbot renew -q
以下是根据学习Rust编程的难度,从简单到高级排列的推荐网站和资源:
初级(入门级):
https://www.rust-lang.org/
Rust官网:了解Rust语言的基本信息和生态系统。
https://doc.rust-lang.org/book/
The Rust Programming Language (The Book):官方权威入门书籍,适合从零开始学习。
https://github.com/rust-lang/rustlings
Rustlings:互动式练习项目,通过简单的练习题目帮助初学者掌握基础知识。
https://doc.rust-lang.org/rust-by-example/
Rust by Example:通过实例学习Rust基础概念,非常适合初学者。
中级(进阶级):
https://www.udemy.com/
Udemy:提供多个Rust编程的在线课程,适合有一定基础后系统化进阶学习。
https://www.coursera.org/learn/rust-programming
Coursera:提供结构化的Rust课程,帮助深入理解Rust编程。
https://www.youtube.com/results?search_query=rust+programming
YouTube上的Rust编程频道:观看进阶Rust编程的视频教程,适合以视觉学习为主的学习者。
高级(高级和实战):
https://rust-lang-nursery.github.io/rust-cookbook/
Rust Cookbook:提供各种实际应用的代码示例,帮助理解高级编程模式和技巧。
https://github.com/rust-unofficial/awesome-rust
Awesome Rust:收集了大量高级资源,包括库、工具和文章,适合深入研究和实战应用。
https://www.rust-lang.org/zh-CN/community
Rust用户论坛:参与社区讨论,解决具体问题,并与其他Rust编程者交流。
https://stackoverflow.com/questions/tagged/rust
Stack Overflow:查阅和提问Rust相关问题,解决编程中的具体挑战。
依照这个顺序进行学习,可以从基础入手,逐步深入理解Rust编程,并最终能够应用于实际项目和高级编程场景。
https://play.rust-lang.org/
在Rust Playground中尝试Rust在线,无需在计算机上安装任何内容。