小天管理 发表的所有内容
-
最近提了离职,因为工资和工作环境问题,刚才和老板谈工资清算没谈拢… 入职说好的 5%的项目提成,我这边的工作已经做完了,但是现在项目还没有验收,所以貌似公司不打算给我….这样可以吗? 我提 N+1 的时候又说要满一年才有,但是劳动法规定的 6-12 个月是按一年算的吧? 如果谈不拢有必要走劳动仲裁吗? 还是我有问题?
-
我的域名是很久以前通过 cf partner 面板接入的 cname 记录。仅接入了子域名 cdn.a.com(源站 b.com),没有接入 a.com 和 www.a.com。接入时的设置是通过 http 续签证书。也就是在根目录下设置 http://cdn.a.com/.well-known/pki-validation/caxxxxx.txt 文件,cf 安排续签证书时校验这个文件验证所有权。 前天突然发现无法通过 https 访问 cdn.a.com。http 访问则没有问题。直接 https 访问源站 b.com 也没有问题。 通过 openssl s_client -connect cdn.a.com:443 -showcerts 检查发现没有证书: CONNECTED(00000003) 140170526717840:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:769: no peer certificate available 登录 cf 后台,在 [网站] [ SSL/TLS ] [边缘证书] 页面中,边缘证书列表显示为无证书: 我已检查过: [ SSL/TLS ] 被设置为完整(严格)。 没有开启 [始终使用 HTTPS ] 。确保续签时可通过 http 访问校验文件。 [安全性] [ WAF ] 中已设置规则,放行对校验文件的访问。 外部 DNS 解析服务中 cdn.a.com 通过 cname 指向官方指定的 cdn.a.com.cdn.cloudflare.net。 curl http://cdn.a.com/.well-known/pki-validation/caxxxxx.txt 可以得到预设的值。 我已试过如下方法但都没有效果: cf 后台暂停该域名,一小时后重启。 [ SSL/TLS ] 调整为关闭,一小时后恢复为完整(严格)。 [ SSL/TLS ] 调整为关闭,一小时后恢复为完整。 [边缘证书] 中关闭 [通用 SSL ] ,一小时后启用。 按下图提示,在外部 DNS 解析服务中设置 _acme-challenge.cdn.a.com cname 指向 cdn.a.com.uuid.dcv.cloudflare.com,然后关闭 [ SSL\TLS ] 和 [通用 SSL ] 十几分钟后恢复。
-
![1000013040.webp]( https://j.teno.cx/root/2024/09/01/66d47e29cf906.webp) 虽然之前有人发了,但实在太神奇所以再补充下,原理可以看参考资料:,https://www.v2ex.com/t/1014864 方法就是打开 prefix hint 功能, ![1000013041.webp]( https://j.teno.cx/root/2024/09/01/66d47f8d48c53.webp) 输入当前 IP 前缀或者按照 bras 的前缀写全尝试也可以,也就是可以调靓号,也可以全 0 补全,(如图上就是 0 补全)如果报错就是 bras 拒绝了手动的前缀 实测本地移动全 0 最短前缀可以,/48 ~/60 的子网部分 随便改靓号,电信如果改的太短或者太靓号会强制修改几个位,没法全 0 最短前缀,但只要分配给我了就可以一直用 # 此方法修改后重拨号前缀固定,理论上只要 bras 方这段前缀不过期就可以一直用,算静态 ipv6 体验卡 关于如何 win 系统用短 ipv6 , win 系统要把 ipv6 临时地址和 ra 通告给禁用了,适配器里面再改成手动获取 ipv6 地址 实现 12 位超短 ipv6 地址 linux 系统更简单,直接配置静态 ipv6 即可, 安卓只支持 slaac 短不了一点,iOS MacOS 似乎没法静态 ipv6 ,用有状态 dhcpv6 可以分固定 v6 ,(但 ROS 不支持有状态)
-
之前 pt 一直是直接使用群晖的 ds ,前一阵子看到 mp 感觉可以整自动化一些。但发现 mp 需要特定网站认证,而我只有 mt ,无法认证。 故求下列网站的一个要求 iyuu/hhclub/audiences/hddolby/zmpt/freefarm/hdfans/wintersakura/leaves/ptba /icc2022/xingtan/ptvicomo/agsvpt/hdkyl/qingwa/discfan/haidan/rousi 保证完成新手任务。 这是我 mt 的数据 上傳量: 4.06 TB 下載量: 3.13 TB 做種/下載時間比率: 19.59 邮箱地址:bGltY2hpaGlAZ21haWwuY29t ( base64 )
-
朋友有一台 MacBook Air 2018 ,可能升级 Sonoma14.6.1 时候进度条卡住了一个小时,被强制关机了。 结果,无法正常开机,Apple 进度条走完之后,就被要求输入密码来解锁。然后输入 Mac 密码后又无限循环 logo 进度条输密码… 1 、首先尝试 Mac 自带恢复模式进行升级操作,尴尬的是磁盘空间不足差了 3GB ,没辙。 2 、于是尝试用制作 U 盘启动器的方式来升级,但是选择升级过程中报错「这个“安装 macOS Sonoma”应用程序副本已损坏,不能用来安装 macOS 」,终端设置 date 时间无效。 3 、重置 PRAM/NVRAM 无效。 4 、shift 按住进入安全模式无效。 5 、无时间机器备份 目前是不是除了抹掉磁盘以外没招了?
-
1 、如在 PCB 板和原理图中用了( XH2.5-2A 直针 2PIN 针座),要找适配它的母头元件准备加入到清单,怎么找最便捷、准确? 这个自己瞎摸索过,是不是除了历史以来熟悉搭配型号,只能手选,别无它法? 2 、在自动布线的时候,限定某些区域不走线,不产生走线过孔,比较灵巧方式是如何做? 是不是用个什么容易放置、删除的东西“占位”,自动补完线后删除占位内容? 3 、以前看别人做电子制作,在一整块 PCB 板上画出一部分区域(连板?),可以像梳打饼干那样折断这部分区域,成为单独的 PCB 板块,这种划开区域的做法,在立创 EDA 里怎么做? 1.6MM 厚的板子。。。
-
如题,坐标广州,开的电信,应该是 500Mb 的带宽,不管是有线连接的台式机+PS5 ,还是我的 wifi 连接的笔记本,网速都很快,下载也从来没问题,但是手机网络就经常出问题,包括但不限于微信、飞书图片半天刷不出来(我的使用体验),微博、小红书的图片和视频也加载特别慢(我对象的使用体验)等。很奇怪啊,有大佬了解吗? 路由器是 RT-AX88U Pro 提前拜谢大佬!
-
在甲骨文免费的机器上用 docker 运行着一个 vaultwarden 服务,用来管理自己的所有密码。 因为是免费的 vps ,为了避免机器被忽然回收导致数据丢失,因此需要定期备份 vaultwarden 的数据库。 首先 vaultwarden 使用了 docker compose 部署,部署文件如下: services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: always environment: SIGNUPS_ALLOWED: false # Deactivate this with "false" after you have created your account so that no strangers can register volumes: - ./vw-data:/data # the path before the : can be changed ports: - 11001:80 # you can replace the 11001 with your preferred port 因此在 vw-data 下面,保存着所有密码数据,只要备份这个目录,就可以还原。 备份工具使用 restic ,它支持将数据直接备份到远程,下面步骤主要是设置好备份地址,然后定期执行的操作细节: 一: 首先安装 restic sudo apt update sudo apt install restic 二: 初始化 restic 仓库 先设置好环境变量: export RESTIC_REPOSITORY=s3:https://<minio-host>:<minio-port>/<bucket-name>/<subpath> export AWS_ACCESS_KEY_ID=<your-access-key> export AWS_SECRET_ACCESS_KEY=<your-secret-key> export RESTIC_PASSWORD=<your-password> 然后执行: restic init 执行成功后,会在远程 S3 目录 https://<minio-host>:<minio-port>/<bucket-name>/<subpath> 看到一些初始化 meta 数据。 三: 创建备份并清理旧数据的脚本 backup_and_prune.sh ,内容如下: #!/bin/bash # 设置环境变量 export RESTIC_REPOSITORY=s3:https://<minio-host>:<minio-port>/<bucket-name>/<subpath> export AWS_ACCESS_KEY_ID=<your-access-key> export AWS_SECRET_ACCESS_KEY=<your-secret-key> export RESTIC_PASSWORD=<your-password> # 备份目录 BACKUP_SOURCE="/home/ubuntu/vaultwarden/vwdata" # 日志文件 LOG_FILE="/home/ubuntu/back.log" # 执行备份并记录日志 restic backup $BACKUP_SOURCE >> $LOG_FILE 2>&1 # 删除超过 7 天的旧备份并记录日志 restic forget --keep-daily 7 --prune >> $LOG_FILE 2>&1 # 记录快照信息 restic snapshots >> $LOG_FILE 2>&1 请自行将里面的变量和路径替换为实际值。 四: 设置定期执行: 使用 cron 来设置每天定期执行备份任务。 crontab -e 添加以下行来设置每天凌晨 2 点执行备份脚本: 0 2 * * * /home/ubuntu/backup_and_prune.sh 确保你的脚本有执行权限: chmod +x /home/ubuntu/backup_and_prune.sh 这样你的备份脚本就会每天凌晨 2 点执行一次了。 五: 验证备份 验证数据是否正常的最好办法是让 vaultwarden 加载备份的数据,然后在网页查看数据是否是最新的。因此接下来的操作是将远程的备份下载到本地,然后在本地执行的具体操作。 首先设置环境变量 export RESTIC_REPOSITORY=s3:https://<minio-host>:<minio-port>/<bucket-name>/<subpath> export AWS_ACCESS_KEY_ID=<your-access-key> export AWS_SECRET_ACCESS_KEY=<your-secret-key> export RESTIC_PASSWORD=<your-password> 列出所有快照: restic snapshots 恢复某个快照到指定目录: restic restore <snapshot-id> --target ~/Downloads/ 根据本文的例子,最终的文件还原到了 ~/Downloads/vw-data 下面。 本地临时运行一个 docker compose 文件: services: vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: always environment: SIGNUPS_ALLOWED: false # Deactivate this with "false" after you have created your account so that no strangers can register volumes: - ~/Downloads/vw-data:/data # the path before the : can be changed ports: - 11001:80 # you can replace the 11001 with your preferred port docker compose up 启动后,访问 localhost:11001 查看数据正常就表示备份没有问题了。 本文首发于: https://blog.tomyail.com/backup-vaultwarden-data-using-restic/ 转载请注明出处
-
坐标成都。今天同事说网站打不开了,看不到文档。 请问是遇到什么变故了吗?临时的还是永久的?
-
前言 最近更新了我的系列文章,其中有一部分是关于 JavaScript 语言中“点表示法”的使用。 文章中有 5 个工具函数是纯JavaScript的,我觉得不仅仅是小程序项目用得上,其他前端 JS 项目也应该用得上。 我把文章中的这一部分单独整理出来,给需要写前端代码的朋友参考参考。 你可以直接在 Github 项目 中的前端 utils.js 文件中找到这 5 个工具函数。 下面是文章节选,完整的文章列表可以在 Github 项目 中查看。 点表示法a.b.c 用点表示法给对象赋值:putValue函数 想象一下,如果a是一个对象,你要给a.b.c.d赋值为 1 ,你会这样写? let a // 通过某种方式获得的对象 if (!a.b) { a.b = {} } if (!a.b.c) { a.b.c = {} } a.b.c.d = 1 拜托,大学生才这样写。为此,我们引入了putValue函数: utils.putValue(a, 'b.c.d', 1) putValue函数会自动创建a.b、a.b.c中间对象,它的声明如下: /** * 向对象中按照路径赋值,如果路径上的中间对象不存在,则自动创建。 * @param {Object} obj - 目标对象。 * @param {string} key - 属性路径,支持'a.b.c'形式。 * @param {*} value - 要设置的值。 * @param {Object} [options={}] - 可选参数。 * - {boolean} remove_undefined - 如果为 true 且 value 为 undefined ,则删除该属性。 * @throws {Error} 如果 obj 为 null 或 undefined ,或路径不合法(如中间非对象)则抛出异常。 */ putValue(obj, key, value, {remove_undefined = true} = {}){ // ... } 使用这个函数有两个地方要注意,一个是如果路径上的中间对象不是对象,会抛出异常。例如a.b=2,这里b不是对象,此时会抛出异常。 另一个是如果value是undefined,则会删除该属性。例如下面的代码: utils.putValue(a, 'b.c.d', undefined) console.log(a) // {b: {c: {}}},putValue 会自动创建中间对象,但不会自动删除空对象 但若你真想赋值为undefined,可设置remove_undefined参数为false。 读取对象属性值:pickValue函数 对应的,如果要获取a.b.c.d的值,可以使用pickValue函数: utils.putValue(a, 'b.c.d', 1) const value = utils.pickValue(a, 'b.c.d') console.log(value) // 1 当然你也可以直接使用javascript的原生语法: const value = a.b?.c?.d 这两种写法,当中间路径不存在时,均会返回undefined。 但a.b?.c?.d这种写法是硬编码,而在实际开发中路径可能是动态的。例如用户要修改一个配置项,这个配置可能是user_config.font.size也可能是user_config.page.color.background,如果使用硬编码的方式,可能会写出这样的代码: if (key === 'font.size') { user_config.font.size = value } else if (key === 'page.color.background') { user_config.page.color.background = value } // 更多的 if 语句... 这样写显然不够优雅,看看putValue与pickValue的组合用法。 // 写入用户配置: utils.putValue(user_config, key, value) // 读取用户配置 const value = utils.pickValue(user_config, key) 不管key怎么变,一句话搞定,感觉一下子和大学生拉开差距了是吧? 向数组末尾添加元素:pushValue函数 在实际开发中,常有向数组末尾添加数据的需求。例如记录用户最近的评论,此时你可以使用pushValue函数: const user_data = {} // 用户数据 let comment = {content: '顶'} // 用户的评论 utils.pushValue(user_data, `articles.recent_comments`, comment) console.log(user_data) // {articles: {recent_comments: [{content: '顶'}]}} 在上面代码中,pushValue函数先是自动创建了user_data.articles.recent_comments数组,然后把comment添加到数组末尾。pushValue函数的声明如下: /** * 将值推入对象指定路径的数组中,若路径或数组不存在则自动创建。 * @param {Object} obj - 目标对象。 * @param {string} key - 数组属性的路径,支持'a.b.c'形式。 * @param {*} value - 要推入的值。 * @throws {Error} 如果路径不是数组,则抛出异常。 */ pushValue(obj, key, value){ // ... } 再次调用此函数,数组中就会有两个评论: comment = {content: '再顶'} utils.pushValue(user_data, `articles.recent_comments`, comment) console.log(user_data) // {articles: {recent_comments: [{content: '顶'}, {content: '再顶'}]}} 向对象中添加多个属性:putObj函数 前面我们使用putValue函数向obj对象写入了一个属性值,但如果你要写入很多个(例如 100 个)属性值,你可能会使用for循环: let user_config = {} let new_config_keys // 100 个新的配置项(数组) let new_config_values // 对应的 100 个值(数组) for (let i = 0; i < new_config_keys.length; i++) { utils.putValue(user_config, new_config_keys[i], new_config_values[i]) } 这样写没问题,但在实战中,你拿到的用户配置往往不是数组的形式,而很可能是一个对象,例如: let new_config = { font: { size: 16, 'family.first': 'Arial', 'family.second': 'sans-serif', }, 'page.color.background': '#fff', // ... } // 你对拿到的配置数据又进一步处理 new_config.update_time = new Date() 这种情况下你可以使用putObj函数一次性写入多个属性值: utils.putObj(user_config, new_config) console.log(user_config) /* 输出如下: { font: { size: 16, family: { first: 'Arial', second: 'sans-serif' } }, page: { color: { background: '#fff' } }, update_time: '...', } */ 注意putObj会自动处理上面new_config变量中各种路径的写法。putObj函数的声明如下: /** * 将一个对象的所有属性按路径添加到另一个对象中。 * @param {Object} obj - 目标对象。 * @param {Object} obj_value - 要添加的属性对象,键支持'a.b.c'形式的路径。 * @param {Object} [options={}] - 可选参数。 * - {boolean} remove_undefined - 如果为 true 且 value 为 undefined ,则删除该属性。 * @throws {Error} 如果 obj 为 null 或 undefined ,或路径不合法(如中间非对象)则抛出异常。 * * 注意 * 若 obj_value 中出现重复路径,则后者会覆盖前者。 * 如 obj_value = {a: {b: 1}, 'a.b': 2},则结果为 {a: {b: 2}} */ putObj(obj, obj_value, { remove_undefined = true} = {}) { // ... } 从对象中获取多个属性:pickObj函数 同样的,我们可以一次性读取多个对象的属性值。例如虽然小程序中的用户配置非常复杂,但当前页面仅关注背景颜色、字体大小等少量配置项,你可以这样使用pickObj函数: let user_config // 某个用户的所有配置 // 本页面需要关注的配置 const keys = ['page.color.background', 'font.size', 'font.family'] // 获取当前页面需要的配置 const curr_config = utils.pickObj(user_config, keys) console.log(curr_config) /* 输出如下: { 'page.color.background': '#fff', 'font.size': 16, 'font.family': { first: 'Arial', second: 'sans-serif' } } */ console.log(curr_config.font) // undefined 注意,传给pickObj函数的第二个参数是一个字符串数组,而不是对象。并且,pickObj返回的对象中,属性值不是以curr_config.font.size这样的形式返回,而是返回curr_config['font.size']。 当然,如果你想要curr_config.font.size这样的形式,可用putObj转换一下: let obj_config = utils.putObj({}, curr_config) console.log(obj_config.font.size) // 16 点表示法在微信小程序中实战演示 为什么要设计这几个函数?为什么要支持config.a.b.c与config['a.b.c']两种写法混用?为什么传给putObj的第二个参数是对象,而传给pickObj的第二个参数是字符串数组?为什么pickObj返回的对象属性值不是config.a.b.c这样的形式,而是config['a.b.c']? 因为这样设计符合实战需求,一句话解释就是:“这样好用”。 下面我们通过几个案例来演示这些函数在实战中的应用。 在 js 中设置用户配置 假设用户首次打开小程序,你需要设置用户默认字体大小为 16 ,背景颜色为白色。可以这样写: let user_config = {} utils.putValue(user_config, 'font.size', 16) utils.putValue(user_config, 'page.color.background', '#fff') 使用putValue设置后,你想修改字体大小和背景颜色?可以这样写: user_config.font.size = 18 user_config.page.color.background = '#000' 在 wxml 中实现修改用户配置 你可能会在 wxml 页面中实现多个配置项的修改,并且使用同一个函数来处理。这时你可以这样写: <button bind:tap="changeConfig" data-key="font.size" value="16" > <button bind:tap="changeConfig" data-key="page.color.background" value="#fff"> changeConfig(e){ const { user_config } = this.data const { key, value } = e.currentTarget.dataset // 从 wxml 中获得点表示法的 key 字符串,直接调用 putValue 函数 utils.putValue(user_config, key, value) // 修改背景色时顺便改一下字体颜色(两种写法混用) if (key === 'page.color.background' && value === '#fff') { user_config.page.color.font_color = '#000' } // 记录最近修改时间 user_config.update_time = new Date() } 在 wxml 中使用用户配置 要在页面中使用page.color.background与page.color.font_color的值,实现根据用户配置显示不同的颜色,可以这样写: <view style="background-color: {{color.background}}"> <text style="color: {{color.font_color}}"> Hello, WxMpCloudBooster! </text> </view> onLoad(){ const { user_config } = this.data const color = utils.pickValue(user_config, 'page.color') this.setData({color}) } 你看,我们传递给pickValue的key根据实际需求可长可短。 初始化默认的用户配置 你希望为每个用户设置一个默认的用户配置,并且你想用常规方式写(不使用点表示法)。可以这样: // 默认配置 const DEFAULT_CONFIG = { font: { size: 16, }, page: { color: { background: '#fff', font_color: '#000' } }, // 这里也可以使用点表示法 a.b.c ,但你不想这样写... } App({ initConfig(){ let { user_config } = this.data utils.putObj(user_config, DEFAULT_CONFIG) // user_config 的其他值会被保留 // 保存用户配置... } }) 注意,上面代码中user_config可能会有其他没有出现在DEFAULT_CONFIG中的配置项,这些配置项会被保留。 记录用户最近发表的内容 假如你已经实现了“记录用户最近发布的评论”功能,代码如下: <button bind:tap="append" data-key="articles.recent_comments" data-prop="comment" > 当用户点击这个按钮时,假设this.data中已经有一个comment对象,你可以这样添加评论: append(e){ const { user_data } = this.data const { key, prop } = e.currentTarget.dataset const value = this.data[prop] // prop === "comment" utils.pushValue(user_data, key, value) // 注意这里用的是 push } 上面这个append函数会把this.data.comment对象添加到user_data.articles.recent_comments数组的末尾。 然后,此时你希望再增加一个按钮,可以把最近的点赞数据this.data.like添加到user_data.articles.recent_likes数组的末尾,那么只需一句: <button bind:tap="append" data-key="articles.recent_likes" data-prop="like" > 完成了,你不需要修改append函数,只需要给data-key和data-prop属性设置不同的值即可。 可见,点表示法很大的目的是为了在wxml中可以方便地指定路径,并在js中方便地处理这些路径。 在页面中修改多个配置项 假设你有一个修改用户配置项的页面,wxml代码如下: <!-- 注意这里有一个 for 循环 --> <view wx:for="{{configs}}"> 配置名称:{{item.title}} 当前值:{{item.value}} 输入新值:<input type="text" /> 点击修改:<button bind:tap="changeConfig"/> </view> 上面代码使用了for循环,configs变量中有多少个值,就会显示多少个配置项。 为了实现在用户打开页面时显示的是用户的当前值(而不是默认值),你还需要从user_config中读取当前用户的配置值。 代码样例如下: // 代码中写死了需要修改的配置项以及默认值 configs = [ {title: '字体大小', key: 'font.size', value: 16}, {title: '背景颜色', key: 'page.color.background', value: '#fff'}, {title: '字体颜色', key: 'page.color.font_color', value: '#000'}, ] // 读取当前用户的配置值 const uc_obj = utils.pickObj(user_config, configs.map(item => item.key)) // 注意,这里的 uc_obj 是 uc_obj['font.size'] 这样的形式,而不是 uc_obj.font.size // 用户当前值覆盖默认值 configs.forEach(item => { item.value = uc_obj[item.key] }) this.setData({configs}) // 传给 wxml 页面显示 这样你就实现了修改多个配置项的页面,用户打开页面时显示的是用户当前的配置值。 提问:假设我们坚决不使用点表示法,且要实现上面这些功能,你要如何设计才能如此简单、高效? 让你的函数也支持点表示法 好了,目前我们花了不少篇幅介绍点表示法,这是因为后面我们会介绍更多的工具函数,而这些工具函数都支持点表示法的调用方式。 当你编写自己的工具函数时,你可以调用putValue、pickValue、pushValue、putObj、pickObj这 5 个函数,轻松地让你的工具函数也支持点表示法。如果你不知道如何实现,可以参考utils.js中其他函数的代码。 (文章节选完,如果你感兴趣的话可以看看 Github 项目)
-
有注册成功电商平台 faire 吗 或者有美国身份,对电商感兴趣 有赚钱的机会 欢迎私聊
-
不同版本的 gradle 指令改来改去,就算这是两年一改也很是折腾,另外,为什么包管理器一定要绑定 jdk 版本呢 python 同样一个包,兼容 3.8 而与 3.12 冲突? semVer 说好的小版本兼容呢?另外,项目 a 依赖的 b 跟 c 的上层依赖互相冲突,只好分两个 project 搞
-
Spring RestTemplate 拦截器修改请求体导致的诡异问题 最近在工作中发现了 Spring 的一个"特性"(也许可以叫 Bug ?),反正我已经给 Spring 提了 PR ,等着看能不能合进去。 问题背景 最近在调用第三方 API 时,遇到了一个有意思的场景。整个调用流程大概是这样的: 先调用 /login 接口,发送 username 和 password ,对方服务返回一个 JWT 。 之后的每个请求接口都是标准格式,需要把 JWT 和请求参数放到一个 JSON 中,类似这样: { "token": "JWT-TOKENxxxxxx", "data": { "key1": "value1", "key2": "value2" } } 发送请求,然后拿到响应报文。 解决方案 为了避免在每个接口都重复封装 token ,我想到了用 org.springframework.http.client.ClientHttpRequestInterceptor 来拦截请求,统一修改请求体。 代码大概长这样: this.restTemplate = new RestTemplateBuilder() .requestFactory(() -> new ReactorNettyClientRequestFactory()) .interceptors((request, body, execution) -> { byte[] newBody = addToken(body); // 调用登陆获取 token ,修改入参 body ,添加 token return execution.execute(request, newBody); }) .build(); 诡异的问题 修改完成后,进入测试阶段,奇怪的事情就发生了:token 能正确获取,body 也修改成功了,但对方的接口一直报 400 ,Invalid JSON 。更奇葩的是,我把 newBody 整个复制出来,用独立的 Main 代码发送请求,居然一次就成功了。 深入源码 不服气的我只能往源码里找原因。从RestTemplate一路 Debug 到org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute,发现了这么一段代码: @Override public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { if (this.iterator.hasNext()) { //这里是在执行 interceptor 链,我的登陆和修改 body 接口就在这里执行 ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); return nextInterceptor.intercept(request, body, this); } else { // 上面的 interceptor 链执行完后,下面就是真实执行发送请求逻辑 HttpMethod method = request.getMethod(); ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method); request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value)); if (body.length > 0) { if (delegate instanceof StreamingHttpOutputMessage streamingOutputMessage) { streamingOutputMessage.setBody(new StreamingHttpOutputMessage.Body() { @Override public void writeTo(OutputStream outputStream) throws IOException { StreamUtils.copy(body, outputStream); } @Override public boolean repeatable() { return true; } }); } else { StreamUtils.copy(body, delegate.getBody()); } } return delegate.execute(); } } 在 Debug 到request.getHeaders().forEach这里时,我突然发现 request 里的Content-Length居然和body.length(被修改后的请求体)不一样。 问题根源 继续往上追溯,在org.springframework.http.client.AbstractBufferingClientHttpRequest中找到了这段代码: @Override protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { byte[] bytes = this.bufferedOutput.toByteArrayUnsafe(); if (headers.getContentLength() < 0) { headers.setContentLength(bytes.length); } ClientHttpResponse result = executeInternal(headers, bytes); this.bufferedOutput.reset(); return result; } 原来Content-Length在执行拦截器之前就已经被设置了。但我们在拦截器里修改了body,导致对方接收到的 JSON 格式总是不对,因为Content-Length和实际的请求体长度不匹配。 解决问题 这时候为了先解决问题,就先在interceptor中重新赋值了Content-Length this.restTemplate = new RestTemplateBuilder() .requestFactory(() -> new ReactorNettyClientRequestFactory()) .interceptors((request, body, execution) -> { byte[] newBody = addToken(body); // 调用登陆获取 token ,修改入参 body ,添加 token request.getHeaders().setContentLength(body.length); // 重新设置 Content-Length return execution.execute(request, newBody); }) .build(); 测试后,问题解决了。 反思和改进 问题虽然解决了,但我琢磨了一下,虽然是我在拦截器中修改了 body ,但这个地方 Spring 应该还是有责任把错误的Content-Length修正的。 第一,Spring 的文档中没有明确写这里应该由谁来负责,是个灰色地带。 第二,我们用RestTemplate谁会自己设置Content-Length啊,不都是框架设置的吗,所以这里不也应该由框架来负责嘛。 思考完,周末找了个时间给 Spring 提了个 PR ,有兴趣的同学可以到这里看看。Update Content-Length when body changed by Interceptor 有一说一,虽然不是第一次提 PR ,但是还是感觉挺爽的,记录一下。 写的挺乱的,技术一般,大佬轻喷。
-
本人不太熟悉运维和安全方面的东西,然后现在买了一台 2c2g 的阿里云的 ecs 想要在这台服务器上部署自己写的一些页面然后还有一些对外的 node 服务 但是又考虑到服务器的安全性,还有部署时候的一系列繁琐的操作,就像找个工具统一弄一下 在网上看大家对 1Panel 和 雷池 的评价比较高 不过看了一下对硬件的要求,我的服务器如果同时安装两个,可能内存就撑满了(现在服务器用 docker 装了一个 1panel 和 mysql 就已经占一半了) 现在想问一下各位大佬,装哪一款比较好,或者说这两个根本就是不同纬度的东西,没有可比性 我目前能想到的需求主要是集中在下面两点: https 证书的更新啥的 怕服务器被攻击
-
打电话对方听不清,要开免提,天才吧说底部麦克风损坏(当时那 sb 还说的是扬声器),只能自己掏钱整机更换,更换之后保修期只有 90 天 私人维修店说 120 换个“送话器”就能搞定,球问各位靠谱吗 顺便吐槽下 apple store 服务够 low 的,那态度拽的跟你欠了它八百万似的
-
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int g=100; int main() { static int x=666; pid_t pid = fork(); if (pid==-1){ printf("创建进程失败\r\n"); } if (pid==0){ printf("pid=%d g=%d,x=%d,g=%p,x=%p\r\n",pid,g,x,&g,&x); g=111; x=222; printf("我子进程 pid=%d,g=%d,x=%d,g=%p,x=%p\r\n",pid,g,x,&g,&x); }else{ printf("pid=%d,g=%d,x=%d,g=%p,x=%p\r\n",pid,g,x,&g,&x); g=666; x=777; printf("我是父进程 pid=%d,g=%d,x=%d,g=%p,x=%p\r\n",pid,g,x,&g,&x); } return 0; } 输出 pid=4502,g=100,x=666,g=0x601044,x=0x601048 我是父进程 pid=4502,g=666,x=777,g=0x601044,x=0x601048 pid=0 g=100,x=666,g=0x601044,x=0x601048 我子进程 pid=0,g=111,x=222,g=0x601044,x=0x601048 我的问题是: 子进程修改变量后,会重新开启一块新内存,再我重新修改变量值后,为什么在打印变量的地址还是相同的?
-
很久没更新翻译软件了,前段时间偶然知道 deepseek ,试用了下,添加到沉浸式翻译,翻译结果非常准确,读起来通顺,以前用过百度,通义千问,字节,腾讯,的 API 感觉都不是太完美,微软,Google 也差点,,,以前一直用的微软,,腾讯偶尔,,其他翻译都差点意思 还没试用过 openai 的翻译,,主要 api 并发问题不好解决,开翻译会员有太贵,, 请问网上那些卖中转 api 的,兼容 openai ,key 的是用 chat2api 项目开发的吗??他们那种怎么那么多账号,撸来的做负载?,,那种中转 api 根 CNY 一比一,不知道放到翻译 api 效果如何,,,
-
在阅读 ptrace 相关代码时,看到为了修改 openat 系统调用的 path 参数,在小于 sp 指针(或者叫高于栈顶位置)保存了新的 path 路径,然后将传递参数的寄存器指向了这个新的 path ,这样做为什么是没有问题的?一般文档上说,sp 指向栈顶,那么如果变量使用了没有被 sp 涵盖的内存空间(字符数组指向了小于 sp 指针的位置),这不会报错吗?
-
周围同事认为事假会“扣薪”,所以不到万不得已不会轻易请事假。但在本人的认知里,本来人就没去上班,没有当天的薪资很正常,故即使年假用光了也会经常以小时为单位请事假。(被调侃了下“没有生活压力~”) 关于这点认知的差异我稍感兴趣。可能是我一直以来都是按时薪去计算薪资,导致观念里认为自己的工作是计时的;但其他人或许不这么认为,他们大概更倾向于固定薪资(?),所以才会有因事假失去了当天的薪资属于被“扣薪”的观念。 综上,发帖调查统计下其他人的观念。
-
原脚本没有对剪贴板的内容进行 urlencode 处理,稍作修改了一下 https://gist.github.com/codexss/03ca2290a89d8becf6c39286b0af537c
-
今天想安装个新 Docker ,发现怎么都拉取不到镜像。然后想起来 Dockerhub 被墙了... 于是我把 NAS 的网关和 DNS 地址指向了旁路由网关,旁路由开了梯子。 但是还是不行,折腾了一圈后我把 NAS 的 ipv6 关了,然后就好了。 NAS 是威联通,旁路由是 openwrt+passwall 想请教一下各位,应该怎么设置调整才能 ipv6 和科学共存。因为需要 ipv6 访问 NAS
-
Hee Labs 是一家美国硅谷 AI 教育公司。我们的旗舰产品 Heeyo 是儿童教育 AI 老师产品。Heeyo 目前已获 OpenAI 等 350 万美元投资,最新估值 2000 万美元。 职位描述: 这是一份全职远程的 AI 工程师岗位。AI 工程师将负责模式识别、计算机科学、神经网络、软件开发和自然语言处理( NLP )等任务。他们将致力于开发和实施 AI 算法,设计和培训神经网络,并创建软件解决方案。AI 工程师将与跨职能团队合作,将 AI 技术整合到各种应用和系统中。 工作要求: 跟踪并探索前沿 AIGC/Chat-GPT/LLM 算法和技术,基于 AIGC 算法技术进行教育产品探索; 负责教育领域 AI 技术的研发和落地应用,进行 AIGC 方向的架构设计,研发,维护及优化; 与产品,教研部门紧密合作,确保产品的交付质量和速度; 保持对生成式 AI 领域最新发展的了解,并将其应用于我们的产品 资格条件: Base 新加坡(必要条件) 全栈工程师,有很强的动手和工程实践能力,能够快速进行 demo 实验落地; 熟悉 clip 、扩散模型、NeRF 、MVSNet 、Differentiable Rendering 等深度学习算法,并熟练使用 PyTorch/TensorFlow 等框架,以及图像开源工具库等; 精通 Python 善于沟通,良好的团队合作精神,良好的责任感和进取精神; 有独立分析解决问题能力,良好的文档编写和团队沟通能力。 加分项: 开源项目爱好者,有 github 上开源项目维护经历 创业经历,有过 0-1 初创团队经历 较强编程能力,曾获 ACM-ICPC/CCPC 区域赛金牌以及以上 请提交简历至微信 cocoonbabe