MoreRSS

site iconGorpeln Chen修改

致力于成为一名架构师的的iOS工程师。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Gorpeln Chen的 RSS 预览

MathJax的基本使用

2025-02-02 11:45:23

一、引言

MathJax引擎是一个开源的JavaScript库,它允许Web开发者在网页中嵌入高质量的数学公式。通过利用Web的最新技术,MathJax引擎可以解析LaTeX、MathML和AsciiMath等数学标记语言,并将其渲染为可视化的数学公式,这些公式可以在各种浏览器和操作系统上流畅地显示。

使用MathJax引擎,网页作者可以轻松地编写包含数学内容的文档,而无需担心用户的浏览器或操作系统是否能够正确显示这些数学公式。因为MathJax引擎会自动处理这些兼容性问题,确保用户能够以清晰、准确的方式查看数学内容。

MathJax引擎的特点包括:

  • 跨平台兼容性:它支持多种浏览器和操作系统,确保数学公式能够在各种设备上正确显示。
  • 高质量的排版:MathJax引擎使用先进的排版算法,确保数学公式能够以清晰、准确的方式呈现。
  • 易于使用:通过简单的标记语言,如LaTeX,用户可以轻松地编写数学公式,并将其嵌入到网页中。
  • 高度可定制性:MathJax引擎提供了丰富的配置选项,允许用户根据自己的需求进行定制,以满足特定的排版和显示要求。

总的来说,MathJax引擎是一个功能强大、易于使用的工具,它为Web上的数学内容显示提供了高质量的解决方案。无论是科学论文、教育资料还是技术文档,都可以利用MathJax引擎来呈现复杂的数学公式,提升网页的可读性和专业性。

二、使用

1、安装

npm install mathjax@3

2、配置

MathJax = {
  tex: {
    packages: ['base'],        // 要使用的扩展
    inlineMath: [              // 行内数学公式的开始/结束定界符对
      ['$', '$'], 
      ['\\(', '\\)']
    ],
    displayMath: [             // 显示数学公式的开始/结束定界符对
      ['$$', '$$'], 
      ['\\[', '\\]']
    ],
    processEscapes: true,      // 使用 \$ 来产生一个字面意义上的美元符号
    processEnvironments: true, // 在数学模式之外处理 \begin{xxx}...\end{xxx}
    processRefs: true,         // 在数学模式之外处理 \ref{...}
    digits: /^(?:[0-9]+(?:\{,\}[0-9]{3})*(?:\.[0-9]*)?|\.[0-9]+)/, // 用于识别数字的模式
    tags: 'none',              // 标签类型,可选值为 'none'、'ams' 或 'all'
    tagSide: 'right',          // \tag 宏的位置
    tagIndent: '0.8em',        // 标签的缩进量
    useLabelIds: true,         // 使用标签名称而不是标签编号作为 ID
    maxMacros: 10000,          // 每个表达式允许的最大宏替换次数
    maxBuffer: 5 * 1024,       // 内部 TeX 字符串的最大大小(5K)
    baseURL: (document.getElementsByTagName('base').length === 0) ? '' : String(document.location).replace(/#.*$/, '')), // 当存在 <base> 标签时,用于标签链接的 URL
    formatError: (jax, err) => jax.formatError(err) // 当 TeX 语法错误发生时调用的函数
  },
  options: {
    skipHtmlTags: [ 'script', 'noscript', 'style', 'textarea', 'pre', 'code', 'annotation', 'annotation-xml' ], // 不会搜索数学公式的 HTML 标签
    includeHtmlTags: { br: '\n', wbr: '', '#comment': '' }, // 可以出现在数学公式内的 HTML 标签
    ignoreHtmlClass: 'tex2jax_ignore',    // 标记不进行搜索的标签的类名
    processHtmlClass: 'tex2jax_process',  // 标记应进行搜索的标签的类名
    compileError: function (doc, math, err) {doc.compileError(math, err)}, // 编译错误处理函数
    typesetError: function (doc, math, err) {doc.typesetError(math, err)}, // 排版错误处理函数
    renderActions: {...} // 渲染操作
  },
  startup: {
    elements: null,          // 要进行排版的元素(默认是文档主体)
    typeset: true,           // 是否执行初始排版
    ready: Startup.defaultReady.bind(Startup),          // 组件加载完成时调用
    pageReady: Startup.defaultPageReady.bind(Startup),  // MathJax 和页面准备好时调用
    document: document,      // 要处理的文档(或片段或字符串)
    invalidOption: 'warn',   // 无效选项是致命错误还是产生警告
    optionError: OPTIONS.optionError,  // 用于报告无效选项的函数
    input: [],               // 要使用的输入引擎的名称(从已加载的引擎中选择)
    output: null,            // 要使用的输出引擎的名称(从已加载的引擎中选择)
    handler: null,           // 要注册的处理程序的名称(从已加载的处理程序中选择)
    adaptor: null            // 要使用的 DOM 适配器的名称(从已加载的适配器中选择)
  },
  svg: {
    scale: 1,                      // 所有表达式的全局缩放因子
    minScale: .5,                  // 要使用的最小缩放因子
    mtextInheritFont: false,       // 使 mtext 元素使用周围字体
    merrorInheritFont: true,       // 使 merror 文本使用周围字体
    mathmlSpacing: false,          // 为 true 时使用 MathML 间距规则,为 false 时使用 TeX 规则
    skipAttributes: {},            // 不复制到输出的 RFDa 等属性
    exFactor: .5,                  // ex 单位的默认大小(以 em 为单位)
    displayAlign: 'center',        // 当 indentalign 设置为 'auto' 时的默认值
    displayIndent: '0',            // 当 indentshift 设置为 'auto' 时的默认值
    fontCache: 'local',            // 字体缓存设置,可选值为 'local'、'global' 或 'none'
    localID: null,                 // 用于本地字体缓存的 ID(用于单方程处理)
    internalSpeechTitles: true,    // 插入带有语音内容的 <title> 标签
    titleID: 0                     // 用于 aria-labeledby 标题的初始 ID 编号
  }
};

按需配置,如果你都不需要,你可以什么也不配置

3、复制

需要复制时,在对应公式部分点击 右键 -> Copy to Clipboard -> TeX Commands 即可完成复制
20250202114523962

三、例子

1、四则运算

1)加减
$a+b-c$

效果:$a+b-c$

2)乘法
$a\times b$ (注意\times和b之间要有空格)

效果:$a\times b$

3)分数
$\frac{a}{b}$ 

效果:$\frac{a}{b}$

4)括号
$(\frac{a}{b})$    
$\left( \frac{a}{b} \right)$    
$\left[ \frac{a}{b} \right]$  
  
# 当使用\left和\right时,它们会根据所包含内容的大小自动调整括号的大小,使得括号能够合适地包围住内容,看起来更加美观和协调。而普通的小括号()不会自动调整大小,在一些复杂的公式中,可能会出现括号与内容大小不匹配的情况 

效果:
$(\frac{a}{b})$
$\left( \frac{a}{b} \right)$
$\left[ \frac{a}{b} \right]$

5)四则组合
$\frac{(a+b)\times c}{(d-e)\times f}$

效果:$\frac{(a+b)\times c}{(d-e)\times f}$

2、比较运算

$a < b$   
$a > b$   
$a = b$   
$a \not= b$   
$a \leq b$   
$a \geq b$   
$a \equiv b$    

效果:
$a < b$
$a > b$
$a = b$
$a \not= b$
$a \leq b$
$a \geq b$
$a \equiv b$

3、上标与下标

1)上标
$x^a$, $x^{ab}$, $x^ab$ 
   
# 多个字符时,要用花括号代替,不过单个字符时有无花括号是等价的

效果:$x^a$, $x^{ab}$, $x^ab$

2)下标
$a_n$, $a_{nm}$, $a_nm$
   
# 花括号原理同上   

效果:$a_n$, $a_{nm}$, $a_nm$

3)次方根
$\sqrt a$, $\sqrt[n]a$, $\sqrt[nm]{ab}$

效果:$\sqrt a$, $\sqrt[n]a$, $\sqrt[nm]{ab}$

4)上下标组合
$a_i^2$, $C^2_{10}$

效果:$a_i^2$, $C^2_{10}$

4、集合间的运算

1)集合
$\left\\{
x_1,x_2
\right\\}$ 
  
# 不能直接打花括号,必须要有\left,\right
# 可以写成一行,这样仅为了方便查看

效果:$\left\{ x_1,x_2 \right\}$

2)元素与集合的关系
$A \in B$           // 元素A 属于 集合B    
$A \notin B$        // 元素A 不属于 集合B    

效果:
$A \in B$
$A \notin B$

3)集合与集合的关系
$A \subset B$       // 集合A是集合B的 真子集,意味着A的所有元素都在B中,但B中至少存在一个元素不在A中    
$A \subseteq B$     // 集合A是集合B的 子集,即A的所有元素都在B中,A和B有可能相等    
$A \supset B$       // 集合A 真包含 集合B,表明B的所有元素都在A中,但A中至少存在一个元素不在B中    
$A \supseteq B$     // 集合A 包含 集合B,即B的所有元素都在A中,A和B有可能相等    

效果:
$A \subset B$
$A \subseteq B$
$A \supset B$
$A \supseteq B$

4)集合的基本运算
$A \cap B$          // 集合A与集合B的 交集,由既属于A又属于B的所有元素共同组成    
$A \cup B$          // 集合A与集合B的 并集,由属于A或者属于B的所有元素合并组成    
$A \setminus B$     // 集合A与集合B的 差集,是由所有属于A但不属于B的元素构成的集合,也可表示为 A - B    
$\complement_U A$   // 集合A在全集U中的 补集,即全集中所有不属于A的元素所组成的集合       

效果:
$A \cap B$
$A \cup B$
$A \setminus B$ 或 $A - B$
$\complement_U A$

5)集合中的符号
$ \cdot $           // 一个点    
$ \cdots $          // 三个点    
$ \mid $            // 分隔符    

效果:
$ \cdot $
$ \cdots $
$ \mid $

6)集合组合示例
$A = \left\\{ x_1,x_2,\cdots,x_n \right\\}$ // 定义集合A,集合A由元素x_1, x_2, ..., x_n组成    
$A\cap \complement_{A\cup B}B=A - B$        // 集合A与集合B在A和B的并集中的补集的交集,等于集合A与集合B的差集   

效果:
$A = \left\{ x_1,x_2,\cdots,x_n \right\}$
$A\cap \complement_{A\cup B}B=A - B$

5、常用特殊函数

1)三角函数
$\sin x$, $\cos x$, $\tan x$, $\cot x$ 
   
# 注意中间的空格

效果:$\sin x$, $\cos x$, $\tan x$, $\cot x$

2)反三角函数
$\arcsin x$, $\arccos x$, $\arctan x$

效果:$\arcsin x$, $\arccos x$, $\arctan x$

3)双曲函数
$\sinh x$, $\cosh x$, $\tanh x$, $\coth x$

效果:$\sinh x$, $\cosh x$, $\tanh x$, $\coth x$

4)对数
$\log_ax$, $\ln x$, $\lg x$

效果:$\log_ax$, $\ln x$, $\lg x$

6、常用特殊符号

1)求和
$\sum$, $\sum_{i=1}^n$, $\sum_{i=1}^na_i$, $\sum_{i=1}^{na_i}$

效果:$\sum$, $\sum_{i=1}^n$, $\sum_{i=1}^na_i$, $\sum_{i=1}^{na_i}$

2)无穷
$\infty$, $+\infty$, $-\infty$

效果:$\infty$, $+\infty$, $-\infty$

3)箭头
$\rightarrow$, $\leftarrow$, $\uparrow$, $\downarrow$

效果:$\rightarrow$, $\leftarrow$, $\uparrow$, $\downarrow$

4)极限
$\lim$, $\lim_{x\rightarrow 0}$, $\lim_{x\rightarrow \infty}$

效果:$\lim$, $\lim_{x\rightarrow 0}$, $\lim_{x\rightarrow \infty}$

5)积分
$\int$, $\int_a^b$, \$int_{ab}^{cd}$   

$ \int_a^b \left( -\frac{1}{x^2} \right) dx = \frac{1}{x}|_a^b = \frac{1}{b} - \frac{1}{a} $

效果:
$\int$, $\int_a^b$, $int_{ab}^{cd}$
$ \int_a^b \left( -\frac{1}{x^2} \right) dx = \frac{1}{x}|_a^b = \frac{1}{b} - \frac{1}{a} $

6)二重积分
$\iint$, $\iint_D$

效果:$\iint$, $\iint_D$

7)三重积分
$\iiint$, $\iiint_D$
    
# 以此类推,超过四重的积分不可用

效果:$\iiint$, $\iiint_D$

8)偏导
$\partial$

效果:$\partial$

9)梯度
$\nabla$

效果:$\nabla$

10)其他
$ \star $ , $ \ast $, $ \oplus $, $ \circ $, $ \bullet $

效果:$ \star $ , $ \ast $, $ \oplus $, $ \circ $, $ \bullet $

7、常用希腊字母

$ \alpha $ // 阿尔法
$ \beta $ // 贝塔
$ \gamma $ // 伽马
$ \delta $ // 德尔塔,变化量
$ \epsilon $ // 伊普西龙
$ \zeta $ // 泽塔
$ \eta $ // 伊塔,机械效率
$ \lambda $ // 兰姆达,常数
$ \mu $ // 缪,摩擦系数
$ \pi $ // 派,圆周率
$ \rho $ // 柔,极径
$ \sigma $ // 西格马,方差
$ \tau $ // 陶,力矩
$ \phi $ // 斐,欧拉函数
$ \chi $ // 希,卡方分布
$ \omega $ // 欧米伽,电阻

效果:
$ \alpha $, $ \beta $, $ \gamma $, $ \Delta $, $ \epsilon $, $ \zeta $, $ \eta $, $ \lambda $, $ \mu $, $ \pi $, $ \rho $, $ \sigma $, $ \tau $, $ \phi $, $ \chi $, $ \omega $

更多希腊字母

8、多行公式

1)换行
$$
\begin{aligned}
a \\
b,c \\
d
\end{aligned}
$$  
   
# '\\'是换行

效果:
\(\begin{aligned} a \\ b,c \\ d \end{aligned}\)

2)空格
$a \ b $ 
  
# '\'表示一空格

效果:$a \ b $

3)对齐
$$\begin{aligned}
[(n+1)!+k]\operatorname{mod}k &=(n+1)!\operatorname{mod}k+k\operatorname{mod}k
\\ &=0+0
\\ &=0
\end{aligned}$$
   
# '&'是对齐

效果:
\(\begin{aligned} [(n+1)!+k]\operatorname{mod}k &=(n+1)!\operatorname{mod}k+k\operatorname{mod}k \\ &=0+0 \\ &=0 \end{aligned}\)

4)方程组
$$ \begin{cases}
x_1+x_2=2
\\ x_1-x_2=0
\end{cases} $$

效果:
\(\begin{cases} x_1+x_2=2 \\ x_1-x_2=0 \end{cases}\)

5)矩阵与行列式
$$ \left(
\begin{matrix}
a &b
\\c &d
\end{matrix}
\right)
,
\left|
\begin{matrix}
a &b
\\c &d
\end{matrix}
\right| $$

效果:
\(\left( \begin{matrix} a &b \\c &d \end{matrix} \right) , \left| \begin{matrix} a &b \\c &d \end{matrix} \right|\)

9、表格

$$\begin{array}{c|lcr} n & \text{左} & \text{中} & \text{右} \\ \hline 1 & 0.24 & 1 & 125 \\ 2 & -1 & 189 & -8 \\ 3 & -20 & 2000 & 1+10i \\ \end{array}$$

效果:
\(\begin{array}{c|lcr} n & \text{左} & \text{中} & \text{右} \\ \hline 1 & 0.24 & 1 & 125 \\ 2 & -1 & 189 & -8 \\ 3 & -20 & 2000 & 1+10i \\ \end{array}\)

Mac应用发布错误:ITMS-91109

2025-01-18 20:20:58

问题

今天进行 时光本 macOS端 更新的时候,上传安装包到 App Store Connect 成功后,但是在 App Store Connect 一直无法看到相应的构架包,查看邮件后发现了一个警告邮件:Action needed: The uploaded build for 时光本-日记本·笔记本·记事本·备忘录 has one or more issues.

App Store Connect

Hello,

We noticed one or more issues with a recent delivery for the following app:

时光本-日记本·笔记本·记事本·备忘录 Version 1.2.9 Build 129 Please correct the following issues and upload a new binary to App Store Connect.

ITMS-91109: Invalid package contents - The package contains one or more files with the com.apple.quarantine extended file attribute, such as “com.gorpeln.xxx.pkg/Payload/GPNotesForMac.app/Contents/Resources/zh-Hans.lproj/Main.strings”. This attribute isn’t permitted in macOS apps distributed on TestFlight or the App Store. Please remove the attribute from all files within your app and upload again.

Apple Developer Relations

在网上查找这个错误提示表明,提交到 App Store 的 macOS 应用程序包中包含一个或多个带有com.apple.quarantine扩展文件属性的文件,比如com.gorpeln.xxx.pkg/Payload/GPNotesForMac.app/Contents/Resources/zh-Hans.lproj/Main.strings,而这种属性是不被允许的。

com.apple.quarantine属性是 macOS 的一个安全特性,用于标记从不受信任的来源获取的文件,例如通过互联网下载或 AirDrop 接收的文件,以防止潜在的恶意软件。要解决这个问题,需要移除应用程序中所有文件的 com.apple.quarantine 属性。

看到网上的结果后知道是安装包中某些文件被添加了com.apple.quarantine 属性,具体可能导致相关错误的原因也很多:

  • 下载来源的影响
  • Xcode 构建过程问题
  • 系统安全机制
  • 第三方软件干扰

但是具体怎么被添加的就不得而知了,于是就进行了漫长的排查解决过程,最后结果让自己很无奈。

解决思路

步骤一: 解压安装包

首先导出安装包,按照提示路径,找到对应文件,使用 xattr 查找该文件是否有 com.apple.quarantine ,查找结果显示的确有,并且有标记是keka解压软件,因为我是用的keka进行的安装包解压,想着keka的标记应该是该流程添加,并不是原来 com.apple.quarantine 属性添加的直接原因,于是就尝试其他方法解决该问题。

步骤二:更新 Xcode

为什么要更新 Xcode 呢,因为在提交 macOS 版本前先提交了 iOS 版本,iOS 版本提交后,提交成功页面显示有警告内容,提示马上要强制使用 Xcode 16 进行开发发布了,所以就想着是不是当前 Xcode 版本太低了导致的呢,于是就进行了更新操作,经过漫长的下载安装,问题依然存在,使用新的 Mac 用户也不行,只能想其他方法了。

步骤三:操作导出的安装包

Xcode 更新不行,想着提示有具体的安装包错误内容路径,就操作删除相关内容的 com.apple.quarantine 属性,网上也有相应的操作流程xattr -rd com.apple.quarantine /path/to/YourApp.app,操作后继续提交,还是不行,还是同样的错误。

分析这个步骤只是进行简单的安装包操作,没有对安装包里面的内容遍历操作,于是想着进行一下遍历删除操作,但是该操作又无法直接遍历安装包内部内容,于是就进行相应的解压,遍历删除相关属性,操作还算顺利,并且重新打包上传,上传直接就报错了,提示证书问题,确实重新打包没有使用证书,重新查找资料,配置相关重新打包证书,完成打包成功上传,但是苹果接着发来邮件提示证书还是有问题。

Hello,

We noticed one or more issues with a recent delivery for the following app:

时光本-日记本·笔记本·记事本·备忘录 Version 1.3.4 Build 134 Please correct the following issues and upload a new binary to App Store Connect.

ITMS-90237: The product archive package’s signature is invalid. Ensure that it is signed with your ‘3rd Party Mac Developer Installer’ certificate.

Apple Developer Relations

查找 ITMS-90237 问题的解决方法,正确配置相关证书后依然不行,感觉这个方法不太靠谱,虽然能够成功删除 com.apple.quarantine 属性,但是重新将解压的安装包打包十分复杂,想着还是换个方法。

该步骤相关操作代码:

import os
import subprocess
import shutil


def remove_quarantine_attribute(pkg_path):
    extract_dir = os.path.expanduser('~/Desktop/unpacked_pkg')
    if os.path.exists(extract_dir):
        try:
            shutil.rmtree(extract_dir)
            print(f"已删除现有的 {extract_dir} 目录。")
        except Exception as e:
            print(f"删除 {extract_dir} 目录时出错: {e}")
            return

    try:
        os.makedirs(extract_dir)
        print(f"成功创建 {extract_dir} 目录。")
    except Exception as e:
        print(f"创建 {extract_dir} 目录时出错: {e}")
        return

    try:
        # 尝试使用 xar 解压
        print("尝试使用 xar 解压 pkg 文件...")
        result = subprocess.run(['xar', '-xf', pkg_path, '-C', extract_dir], capture_output=True, text=True)
        if result.returncode != 0:
            print(f"使用 xar 解压失败: {result.stderr}")
            return
        else:
            print("使用 xar 解压成功。")

        # 打印解压后的目录内容,便于检查
        print(f"解压后的目录 {extract_dir} 内容:")
        for item in os.listdir(extract_dir):
            print(item)

        pkg_inner_dir = os.path.join(extract_dir, 'com.gorpeln.xxx.pkg')
        payload_path = os.path.join(pkg_inner_dir, 'Payload')
        if os.path.exists(pkg_inner_dir) and os.path.exists(payload_path):
            # 进入 Payload 所在目录并进行进一步处理
            os.chdir(pkg_inner_dir)
            try:
                # 执行 cat Payload | gunzip -dc | cpio -i 操作
                print("正在对 Payload 进行进一步解压...")
                command = 'cat Payload | gunzip -dc | cpio -i'
                result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=60)
                if result.returncode == 0:
                    print("Payload 进一步解压成功。")
                else:
                    print(f"对 Payload 进一步解压时出错: {result.stderr}")
            except subprocess.TimeoutExpired:
                print("对 Payload 进一步解压时超时,请检查 Payload 文件是否正常。")
            except subprocess.CalledProcessError as e:
                print(f"对 Payload 进一步解压时出错: {e.stderr}")
            finally:
                # 切换回上级目录
                os.chdir('..')

        # 遍历所有文件并移除属性
        for root, dirs, files in os.walk(extract_dir):
            for file in files:
                file_path = os.path.join(root, file)
                try:
                    subprocess.run(['xattr', '-d', 'com.apple.quarantine', file_path], check=True)
                    print(f"成功移除 {file_path} 的 com.apple.quarantine 属性。")
                except subprocess.CalledProcessError:
                    # 如果文件没有该属性,忽略错误
                    pass

        # 找到 .app 文件
        app_path = None
        payload_extracted_dir = os.path.join(pkg_inner_dir)
        for item in os.listdir(payload_extracted_dir):
            if item.endswith('.app'):
                app_path = os.path.join(payload_extracted_dir, item)
                break

        if app_path is None:
            print("未找到有效的 .app 应用程序包。")
            return

        # 签名 .app 包内的所有文件
        certificate_name_app = "3rd Party Mac Developer Application: xxxxxxxx (xxxxxxxxx)"
        find_command = f'find "{app_path}" -type f -exec codesign --force --deep --sign "{certificate_name_app}" \{\{\}} \;'
        subprocess.run(find_command, shell=True)

        # 签名 .app 包本身
        codesign_command = f'codesign --force --deep --sign "{certificate_name_app}" "{app_path}"'
        subprocess.run(codesign_command, shell=True)

        # 重新打包成 pkg 文件
        new_pkg_path = pkg_path.replace('.pkg', '_cleaned.pkg')
        install_path = '/Applications'
        certificate_name_installer = "3rd Party Mac Developer Installer: xxxxxxxx (xxxxxxxxx)"
        productbuild_command = [
            'productbuild',
            '--component', app_path, install_path,
            '--sign', certificate_name_installer,
            new_pkg_path
        ]
        result = subprocess.run(productbuild_command, capture_output=True, text=True)
        if result.returncode != 0:
            print(f"重新打包并签名 pkg 文件时出错:{result.stderr}")
        else:
            print(f"处理完成,新的签名 pkg 文件已生成: {new_pkg_path}")

    except Exception as e:
        print(f"处理过程中出现错误: {e}")
    finally:
        if os.path.exists(extract_dir):
            try:
                shutil.rmtree(extract_dir)
                print(f"已成功删除临时目录 {extract_dir}。")
            except Exception as e:
                print(f"删除临时目录 {extract_dir} 时出错: {e}")


# 使用示例
pkg_path = '/Users/gorpeln/Desktop/123/GPNotesForMac.pkg'
remove_quarantine_attribute(pkg_path)
    

步骤四:删除 Mac 破解软件

怀疑是电脑上安装的某些破解软件导致的,于是进行删除操作,包括删除了keka解压软件,再次尝试打包,问题依然没有解决。想想上个版本到现在电脑上也没有新安装什么应用,应该不是这个原因,只能想其他方法。

步骤五:删除代码中的相关文件

尝试直接删除提示有问题的文件,该文件在项目中是语言适配文件,做本地化处理的,删除后虽然对应用体验有一定的影响,但是也没有好的办法,只能先删了提交后再慢慢看具体问题了。删除后提交,居然又发邮件提错误了。。。

App Store Connect

Hello,

We noticed one or more issues with a recent delivery for the following app:

时光本-日记本·笔记本·记事本·备忘录 Version 1.3.8 Build 138 Please correct the following issues and upload a new binary to App Store Connect.

ITMS-91109: Invalid package contents - The package contains one or more files with the com.apple.quarantine extended file attribute, such as “com.gorpeln.xxx.pkg/Payload/GPNotesForMac.app/Contents/Resources/MLHudAlertInfo.png”. This attribute isn’t permitted in macOS apps distributed on TestFlight or the App Store. Please remove the attribute from all files within your app and upload again.

Apple Developer Relations

看到这个错误后心凉半截,删除了一个文件,但是同样错误又显示了一个新的文件,肯定不能再删除了,因为还不知道到底有多少文件被标记了 com.apple.quarantine 属性,其中被标记的还可能是核心代码,删除后影响项目运行的。并且标记的这个文件应该是一个第三方库文件,想着应该是第三方库有问题,输入搜索后没有发现对应文件,猜想可能是pod导入导致的错误。

步骤五:固定和更新第三方库

查看 Podfile 文件中第三方库都没有固定版本,想着可能是第三方库更新到新版本后出现了相关问题,于是就设置了所有库的对应版本并进行更新,提交上传,问题依旧。迷茫了,不知道该怎么办了。

步骤六:降级 Xcode 版本

脑子一昏,想着是不是 Xcode 版本高的问题,用比较旧的版本会不会好呢,于是就下载安装了。显而易见,并不会。因为在app上个版本到现在我除了这次升级,并没有操作 Xcode 版本,正常不会是 Xcode 版本问题的。

步骤七:删除源码中的相关属性

不知不觉,这个问题搞了一天了。没想法了,想着还是直接删除提示的对应文件吧,再次搜索提示的MLHudAlertInfo.png,居然看到了这个相关文件,进入查看具体内容,查找在Finder中的内容,确实存在MLHudAlertInfo.png文件。那这就好办了呀,直接查看这个文件是否包含 com.apple.quarantine 属性就行了呀,使用 xattr 查看后,的确存在,我感觉有点无语,基本确认是源码问题,直接使用 xattr 遍历删除项目文件夹下所有文件,重新提交上传,问题完美解决了。

总结

具体该问题是怎么导致的呢?

  • 静下来想想,无轮提示的 Main.strings 还是 MLHudAlertInfo.png,都是最近甚至多个版本都没有修改过,甚至 MLHudAlert 是导入后只使用,完全没有修改过的,再根据过程中提示到的 keka 解压软件标记,我推测大概率文件相关 com.apple.quarantine 的属性在相关文件下载后使用 keka 解压的时候就已经被标记了,跟随项目多个版本,但由于苹果官方最近收紧对 com.apple.quarantine 属性的检测导致构架包一直无法通过。

总结这次问题,为什么耗费了这么长时间呢?

  • macOS 软件开发较少,可参考的资料也较少,只能自己摸索
  • 解决问题思路问题:一直想的是前面的版本没问题,这次不行,应该是这次上次到这次提交之间修改某些文件导致的,没想到被苹果背刺了。
  • 第一次查找MLHudAlertInfo时,可能是因为手动输入错误导致没有成功搜索到相关文件

最后,如果感兴趣可以去查看使用一下【时光本】应用,是一款专注效率与记录的笔记工具。可以帮助你整理各种信息,包括便签、清单、图片、纪念日、地址、链接、银行卡、名片、账号、密码等

春节灯笼

2024-12-21 15:56:22

想为你的博客增添浓浓的春节氛围吗?快来了解我们的独特功能!设定时间后,两个喜庆的灯笼将准时在博客右上角亮起,灯笼上 “新春快乐” 的祝福熠熠生辉,瞬间让你的博客充满节日的欢乐与温馨。这不仅是装饰,更是为访客送上真挚祝福的创意方式,快来探索如何让你的博客在春节别具一格吧。

效果
20241221151857323方法

<link rel="stylesheet" type="text/css" href="../assets/css/deng.css">

注意:将文件下载到自己博客使用
https://gorpeln.top/assets/css/deng.css

html代码

<!-- 春节灯笼 -->
<div style="display:none;" id="lanterns">
    <div class="deng-box">
        <div class="deng">
            <div class="xian"></div>
            <div class="deng-a">
                <div class="deng-b">
                    <div class="deng-t">快乐</div>
                </div>
            </div>
            <div class="shui shui-a">
                <div class="shui-c"></div>
                <div class="shui-b"></div>
            </div>
        </div>
    </div>
    <div class="deng-box1">
        <div class="deng">
            <div class="xian"></div>
            <div class="deng-a">
                <div class="deng-b">
                    <div class="deng-t">新春</div>
                </div>
            </div>
            <div class="shui shui-a">
                <div class="shui-c"></div>
                <div class="shui-b"></div>
            </div>
        </div>
    </div>
</div>
<!-- 春节灯笼 -->
<script>
document.addEventListener("DOMContentLoaded", function() {
    // 获取当前日期
    var currentDate = new Date();
    
    // 定义开始日期和结束日期
    var startDate = new Date('2025-01-22');//腊月廿三
    var endDate = new Date('2025-02-12');//正月十五

    // 获取 div 元素
    var specialDiv = document.getElementById('lanterns');

    // 检查当前日期是否在指定的日期范围内
    if (currentDate >= startDate && currentDate <= endDate) {
        // 如果在范围内,则显示 div
        specialDiv.style.display = 'block';
    } else {
        // 否则,隐藏 div
        specialDiv.style.display = 'none';
    }
});
</script>

彩带、樱花背景

2024-12-21 15:32:27

飘舞的彩带,似灵动的画笔,在空中勾勒出绚丽色彩,为世界添一抹活力。
而烂漫樱花,如粉白的云霞,纷纷扬扬洒落,诉说着春日的温柔。彩带与樱花,在此交织出满是故事的奇妙天地,等你踏入探寻。

效果
20241221151857322

方法

<!--  樱花背景 -->
<script type="text/javascript" src="/assets/js/sakura.js"></script>
<!--  丝带背景 -->
<script size="150" alpha="0.3" zindex="-2" src="/assets/js/ribbon.min.js"></script>

注意:将文件下载到自己博客使用
https://gorpeln.top/assets/js/sakura.js
https://gorpeln.top/assets/js/ribbon.min.js

节日倒计时与时间进度条

2024-12-21 15:18:57

不仅能够精准显示距离特定节日的剩余时间,满足用户对重要节庆的期待与关注,还能直观展示今日、本周、本月乃至本年已悄然流逝的时间比例,帮助用户实时洞察时间的流转,让每一刻都能在时间的刻度中清晰呈现,实现对时间的高效感知与管理。

效果
20241221151857321

方法
1、 countdown.html

<div class="countdownNav" id="countdownNav">
    <div class="card-widget card-countdown">
        <div class="item-headline"><i></i><span></span></div>
        <div class="item-content">
            <div class="cd-count-left">
                <span class="cd-text">距离</span>
                <span class="cd-name" id="eventName">节日</span>
                <span class="cd-time" id="daysUntil">000</span>
                <span class="cd-date" id="eventDate"></span>
            </div>
            <div id="countRight" class="cd-count-right">
                <div id="countRight" class="cd-count-right">
                    <div class="cd-count-item">
                        <div class="cd-item-name">今日</div>
                        <div class="cd-item-progress"></div>
                    </div>
                    <div class="cd-count-item">
                        <div class="cd-item-name">本周</div>
                        <div class="cd-item-progress"></div>
                    </div>
                    <div class="cd-count-item">
                        <div class="cd-item-name">本月</div>
                        <div class="cd-item-progress"></div>
                    </div>
                    <div class="cd-count-item">
                        <div class="cd-item-name">本年</div>
                        <div class="cd-item-progress"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

2、 countdown-1.js

const CountdownTimer = (() => {
    const config = {
        targetDate: "2025-10-01",
        targetName: "国庆",
        units: {
            day: { text: "今日", unit: "小时" },
            week: { text: "本周", unit: "天" },
            month: { text: "本月", unit: "天" },
            year: { text: "本年", unit: "天" }
        }
    };

    const calculators = {
        day: () => {
            const hours = new Date().getHours();
            return {
                remaining: 24 - hours,
                percentage: (hours / 24) * 100
            };
        },
        week: () => {
            const day = new Date().getDay();
            const passed = day === 0 ? 6 : day - 1;
            return {
                remaining: 6 - passed,
                percentage: ((passed + 1) / 7) * 100
            };
        },
        month: () => {
            const now = new Date();
            const total = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
            const passed = now.getDate() - 1;
            return {
                remaining: total - passed,
                percentage: (passed / total) * 100
            };
        },
        year: () => {
            const now = new Date();
            const start = new Date(now.getFullYear(), 0, 1);
            const total = 365 + (now.getFullYear() % 4 === 0 ? 1 : 0);
            const passed = Math.floor((now - start) / 86400000);
            return {
                remaining: total - passed,
                percentage: (passed / total) * 100
            };
        }
    };

    function updateCountdown() {
        const elements = ['eventName', 'eventDate', 'daysUntil', 'countRight']
           .map(id => document.getElementById(id));

        if (elements.some(el => !el)) return;

        const [eventName, eventDate, daysUntil, countRight] = elements;
        const now = new Date();
        const target = new Date(config.targetDate);

        eventName.textContent = config.targetName;
        eventDate.textContent = config.targetDate;
        daysUntil.textContent = Math.round((target - now.setHours(0, 0, 0, 0)) / 86400000);

        countRight.innerHTML = Object.entries(config.units)
           .map(([key, { text, unit }]) => {
                const { remaining, percentage } = calculators[key]();
                return `
                    <div class="cd-count-item">
                        <div class="cd-item-name">${text}</div>
                        <div class="cd-item-progress">
                            <div class="cd-progress-bar" style="width: ${percentage}%; opacity: ${percentage}"></div>
                            <span class="cd-percentage ${percentage >= 46 ? 'cd-many' : ''}">${percentage.toFixed(2)}%</span>
                            <span class="cd-remaining ${percentage >= 60 ? 'cd-many' : ''}">
                                <span class="cd-tip">还剩</span>${remaining}<span class="cd-tip">${unit}</span>
                            </span>
                        </div>
                    </div>
                `;
            }).join('');
    }

    let timer;
    const start = () => {
        updateCountdown();
        timer = setInterval(updateCountdown, 600000);
    };

    ['pjax:complete', 'DOMContentLoaded'].forEach(event => document.addEventListener(event, start));
    document.addEventListener('pjax:send', () => timer && clearInterval(timer));

    return { start, stop: () => timer && clearInterval(timer) };
})();

优化后代码,实现倒计时包含多个时间,自动识别最近的期望时间,如果现在时间超过了所有的期望时间,就以最后一个期望时间为准。两个js二选一即可。
countdown-2.js

const CountdownTimer = (() => {
    const config = {
        events: [
            { targetDate: "2025-04-04", targetName: "清明节" },
            { targetDate: "2025-05-01", targetName: "劳动节" },
            { targetDate: "2025-05-31", targetName: "端午节" },
            { targetDate: "2025-10-01", targetName: "国庆节" },    
            { targetDate: "2026-02-17", targetName: "春节" }            
        ],
        units: {
            day: { text: "今日", unit: "小时" },
            week: { text: "本周", unit: "天" },
            month: { text: "本月", unit: "天" },
            year: { text: "本年", unit: "天" }
        }
    };

    const calculators = {
        day: () => {
            const hours = new Date().getHours();
            return {
                remaining: 24 - hours,
                percentage: (hours / 24) * 100
            };
        },
        week: () => {
            const day = new Date().getDay();
            const passed = day === 0 ? 6 : day - 1;
            return {
                remaining: 6 - passed,
                percentage: ((passed + 1) / 7) * 100
            };
        },
        month: () => {
            const now = new Date();
            const total = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
            const passed = now.getDate() - 1;
            return {
                remaining: total - passed,
                percentage: (passed / total) * 100
            };
        },
        year: () => {
            const now = new Date();
            const start = new Date(now.getFullYear(), 0, 1);
            const total = 365 + (now.getFullYear() % 4 === 0 ? 1 : 0);
            const passed = Math.floor((now - start) / 86400000);
            return {
                remaining: total - passed,
                percentage: (passed / total) * 100
            };
        }
    };

    function getNextEvent() {
        const now = new Date();
        for (let i = 0; i < config.events.length; i++) {
            const eventDate = new Date(config.events[i].targetDate);
            if (eventDate > now) {
                return config.events[i];
            }
        }
        return config.events[config.events.length - 1];
    }

    function updateCountdown() {
        const elements = ['eventName', 'eventDate', 'daysUntil', 'countRight']
           .map(id => document.getElementById(id));

        if (elements.some(el => !el)) return;

        const [eventName, eventDate, daysUntil, countRight] = elements;
        const now = new Date();
        const nextEvent = getNextEvent();
        const target = new Date(nextEvent.targetDate);

        eventName.textContent = nextEvent.targetName;
        eventDate.textContent = nextEvent.targetDate;
        daysUntil.textContent = Math.round((target - now.setHours(0, 0, 0, 0)) / 86400000);

        countRight.innerHTML = Object.entries(config.units)
           .map(([key, { text, unit }]) => {
                const { remaining, percentage } = calculators[key]();
                return `
                    <div class="cd-count-item">
                        <div class="cd-item-name">${text}</div>
                        <div class="cd-item-progress">
                            <div class="cd-progress-bar" style="width: ${percentage}%; opacity: ${percentage}"></div>
                            <span class="cd-percentage ${percentage >= 46 ? 'cd-many' : ''}">${percentage.toFixed(2)}%</span>
                            <span class="cd-remaining ${percentage >= 60 ? 'cd-many' : ''}">
                                <span class="cd-tip">还剩</span>${remaining}<span class="cd-tip">${unit}</span>
                            </span>
                        </div>
                    </div>
                `;
            }).join('');
    }

    let timer;
    const start = () => {
        updateCountdown();
        timer = setInterval(updateCountdown, 600000);
    };

    ['pjax:complete', 'DOMContentLoaded'].forEach(event => document.addEventListener(event, start));
    document.addEventListener('pjax:send', () => timer && clearInterval(timer));

    return { start, stop: () => timer && clearInterval(timer) };
})();

3、 style.css

/*倒计时*/
.countdownNav {
    margin-top: 1em;
    background-color: var(--box-color-light);
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    overflow: hidden;
    width: 100%;
}
.card-countdown {
    margin: 0.75em 1.5em;
}

.card-countdown .item-content {
    display: flex;
}

.cd-count-left {
    position: relative;
    display: flex;
    flex-direction: column;
    margin-right: 0.8rem;
    line-height: 1.5;
    align-items: center;
    justify-content: center;
}

.cd-count-left .cd-text {
    font-size: 14px;
}

.cd-count-left .cd-name {
    font-weight: bold;
    font-size: 18px;
}

.cd-count-left .cd-time {
    font-size: 30px;
    font-weight: bold;
    color: #dad9e6;
}

.cd-count-left .cd-date {
    font-size: 12px;
    opacity: 0.6;
}

.cd-count-left::after {
    content: "";
    position: absolute;
    right: -0.8rem;
    width: 2px;
    height: 80%;
    background-color: #dad9e6;
    opacity: 0.5;
}

.cd-count-right {
    flex: 1;
    margin-left: .8rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
}

.cd-count-item {
    display: flex;
    flex-direction: row;
    align-items: center;
    height: 24px;
}

.cd-item-name {
    font-size: 14px;
    margin-right: 0.8rem;
    white-space: nowrap;
}

.cd-item-progress {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    height: 100%;
    width: 100%;
    border-radius: 8px;
    background-color: var(--background-color-light);
    overflow: hidden;
}

.cd-progress-bar {
    height: 100%;
    border-radius: 8px;
    background-color: #dad9e6;
}

.cd-percentage,
.cd-remaining {
    position: absolute;
    font-size: 12px;
    margin: 0 6px;
    transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
}

.cd-many {
    color: #fff;
}

.cd-remaining {
    opacity: 0;
    transform: translateX(10px);
}

.card-countdown .item-content:hover .cd-remaining {
    transform: translateX(0);
    opacity: 1;
}

.card-countdown .item-content:hover .cd-percentage {
    transform: translateX(-10px);
    opacity: 0;
}

友链自动监测

2024-11-08 16:03:43

前言

管理友链时采取手动点击检验的方式,随着时间的推移,友链数量逐渐增加,这一做法显然已不再高效。于是就需要写了一项类似API的功能,输出所有友链数据的可达性。

功能概览

  1. github action自动定时检测友链状态,结果输出到page分支下的result.json。
  2. 友链状态展示页面,可以部署到zeabur或者vercel,加速api访问速度。
  3. 为确保兼容性,实现了两种检测方案:
    • 非兼容:使用格式文件动态读取友链内容,实现功能,友链列表自动实时性更新。
    • 兼容:使用TXT存储所有友链信息,兼容性好,适合所有站点,但是添加友链后可能需要手动更新文件。
  4. API访问数据,api包含数据包括可达链接、不可达链接、可达链接数目、不可达链接数目、更新时间戳,其中链接中包含站点名称和地址,便于前端部署。
  5. 测试脚本使用python,使用Request包的get和head两种检测方式检测,尽可能减少误判概率。
  6. 前端采用本地缓存,减少api调用次数,缓存半个小时刷新,不影响实时性。

预览

博客中预览:https://friends.gorpeln.top/

20241108160343641

其他

部署教程:参考文章尾部的参考链接
友链相关的常见问题