Posted in

Go语言高级编程PDF百度云资源正在被批量清洗:基于Go AST的自动化版权指纹识别系统已上线(附检测接口)

第一章:Go语言高级编程PDF百度云资源治理背景与现状

近年来,随着Go语言在云原生、微服务及基础设施领域的广泛应用,“Go语言高级编程”类学习资料需求激增。大量用户通过百度云盘分享、传播相关PDF电子书,但资源呈现高度碎片化、版本混杂、版权归属不清等特征。据不完全统计,百度网盘中以“Go语言高级编程”为关键词的公开分享链接超1200个,其中约67%未标注原始出版信息,41%包含被篡改的封面或缺失前言/版权页。

资源质量参差不齐

常见问题包括:扫描版OCR识别错误(如将defer误转为deferf)、代码块排版错乱、章节编号断裂、缺失配套示例源码。部分资源将《Go语言高级编程》(作者:柴树杉、曹春晖)与《Go语言编程》(许式伟)内容混编,导致读者混淆语言特性演进脉络。

版权与合规风险突出

该书由人民邮电出版社正式出版,ISBN 978-7-115-49827-9,受《中华人民共和国著作权法》保护。百度云公开分享未获授权的完整PDF,已触发多次平台下架动作。2023年Q4,出版社联合网信部门开展专项巡查,累计处置侵权链接327条。

技术治理实践建议

推荐采用以下方式验证资源可靠性:

  • 检查PDF元数据是否含合法出版信息:
    pdfinfo "Go语言高级编程.pdf" | grep -E "(Title|Author|Creator|Producer)"
    # 正常应输出:Title: Go语言高级编程;Author: 柴树杉, 曹春晖;Producer: Adobe Acrobat Pro DC
  • 核对目录结构是否与官方目录一致(共12章,含“CGO编程”“RPC实现”“Web框架构建”等特色章节);
  • 优先选用GitHub开源镜像站提供的勘误后版本(如 https://github.com/chai2010/advanced-go-programming-book 中的 docs/ 目录)。
评估维度 合规资源特征 风险资源典型表现
元数据完整性 包含ISBN、出版社、出版年份 Creator字段显示“PDFCreator 2.0”
代码可执行性 所有代码块经go run验证通过 存在import "unsafe"但无CGO注释
更新时效性 同步Go 1.21+新特性说明 仍使用已废弃的http.CloseNotifier

第二章:Go AST解析原理与版权指纹建模

2.1 Go抽象语法树(AST)结构深度剖析与遍历策略

Go 的 go/ast 包将源码映射为结构化节点树,每个节点实现 ast.Node 接口,具备 Pos()、End() 和 Type() 方法。

AST 核心节点类型

  • *ast.File:顶层编译单元,含 Name、Decls(函数/变量声明列表)
  • *ast.FuncDecl:函数声明,嵌套 *ast.FuncType(签名)与 *ast.BlockStmt(函数体)
  • *ast.BinaryExpr:二元运算,含 X(左操作数)、Op(操作符)、Y(右操作数)

遍历策略对比

策略 触发时机 适用场景
ast.Inspect 深度优先,可中断 动态条件过滤、早期退出
ast.Walk 严格遍历所有子节点 全量分析、无条件扫描
func inspectFuncNames(n ast.Node) bool {
    if fn, ok := n.(*ast.FuncDecl); ok {
        fmt.Printf("Found func: %s\n", fn.Name.Name) // fn.Name 是 *ast.Ident,Name 字段为标识符字符串
    }
    return true // 继续遍历
}
ast.Inspect(fset.File(0), inspectFuncNames)

该回调在每次进入节点时执行;fset 是 token.FileSet,用于定位源码位置,File(0) 获取首个文件节点。返回 true 表示继续下行,false 中断当前分支。

2.2 基于AST节点特征的代码指纹提取方法论与实现

代码指纹需捕捉语义稳定性而非表层文本差异。核心路径:源码 → 解析为AST → 提取关键节点特征 → 序列化哈希。

特征选取维度

  • 节点类型(FunctionDeclaration, BinaryExpression等)
  • 操作符/关键字(+, ===, return)
  • 标识符名抽象化(统一替换为 IDENTIFIER)
  • 字面量类型标记(NUMBER_LIT, STRING_LIT)

关键实现(Python + tree-sitter)

def extract_ast_features(node, features=None):
    if features is None:
        features = []
    # 忽略空格、注释等无关节点
    if node.type in {"comment", "string", "program"}:
        return features
    # 抽象标识符,保留结构语义
    if node.type == "identifier":
        features.append("IDENTIFIER")
    else:
        features.append(node.type)
    # 深度优先遍历
    for child in node.children:
        extract_ast_features(child, features)
    return features

逻辑分析:递归遍历AST,跳过语法噪声节点;对identifier做泛化处理,消除变量命名差异;node.type提供语法结构骨架,保障跨版本鲁棒性。

特征类别 示例值 是否参与哈希
节点类型 IfStatement ✅
抽象标识符 IDENTIFIER ✅
原始字面量值 "user_id" ❌(仅类型)
graph TD
    A[源代码] --> B[tree-sitter解析]
    B --> C[AST根节点]
    C --> D[递归遍历+特征抽象]
    D --> E[特征序列]
    E --> F[SHA-256哈希]

2.3 版权敏感模式识别:从函数签名到控制流图的多粒度匹配

版权敏感代码识别需跨越抽象层级,构建语义等价而非字面匹配的能力。

函数签名指纹提取

对目标函数生成标准化签名(忽略命名、空格、注释):

def compute_hash(func_ast):
    # 提取参数类型序列 + 返回类型 + 调用的敏感API集合
    return hashlib.sha256(
        f"{[t.name for t in func_ast.args]}.{func_ast.returns}.{sorted(func_ast.calls & SENSITIVE_APIS)}".encode()
    ).hexdigest()

逻辑说明:func_ast为AST解析结果;SENSITIVE_APIS = {"memcpy", "AES_encrypt", "RSA_sign"};哈希值作为粗粒度候选过滤器。

控制流图子图同构匹配

使用图编辑距离(GED)量化CFG相似性:

方法 时间复杂度 敏感度 适用场景
基于基本块哈希 O(n) 低 快速初筛
CFG子图同构 NP-hard 高 精确克隆检测

多粒度协同流程

graph TD
    A[源函数] --> B[签名哈希匹配]
    B --> C{候选集 < 5?}
    C -->|是| D[构建CFG]
    C -->|否| E[拒绝]
    D --> F[GED ≤ 阈值 3?]
    F -->|是| G[标记为高置信度复用]

2.4 指纹哈希压缩与抗混淆鲁棒性增强实践

为应对代码混淆(如变量重命名、控制流扁平化)导致的指纹失配问题,需在哈希前引入语义感知的压缩预处理。

核心压缩策略

  • 移除非语义符号(注释、空白符、字符串字面量)
  • 统一标识符为占位符(var_1, func_2)
  • 归一化控制流结构(如将 for/while 统一为 loop)

哈希算法选型对比

算法 抗混淆鲁棒性 压缩率 计算开销
SHA-256 中 低 高
SSDeep 高 高 中
TLSH 极高 极高 低
from tlsh import TLSH

def gen_robust_fingerprint(code: str) -> str:
    # 压缩:保留AST结构特征,剥离语法噪声
    normalized = normalize_ast(code)  # 实现见 utils.py
    # TLSH对字节序列敏感,需编码为字节数组
    return TLSH().from_bytes(normalized.encode()).hexdigest()

逻辑说明:normalize_ast() 提取抽象语法树的节点类型序列(如 [ASSIGN, CALL, RETURN]),再映射为紧凑字节流;TLSH 的局部敏感哈希特性使其对节点顺序微调(如混淆插入冗余if True:)保持距离

graph TD
    A[原始源码] --> B[AST解析]
    B --> C[节点类型序列化]
    C --> D[占位符归一化]
    D --> E[TLSH哈希]
    E --> F[64字符指纹]

2.5 大规模PDF源码还原与AST重建的工程化适配方案

面对千万级PDF文档的结构化还原需求,传统单进程解析易触发内存溢出与AST碎片化。核心挑战在于:语义断层修复、跨页上下文对齐与增量式AST合并。

数据同步机制

采用双缓冲队列协调解析器与重建器:

  • 前置缓冲区接收OCR文本块与布局坐标
  • 后置缓冲区按逻辑段(标题/列表/代码块)聚合AST节点
class ASTMerger:
    def __init__(self, merge_threshold=0.85):
        self.threshold = merge_threshold  # 相似度阈值,控制节点合并粒度
        self.pending_nodes = deque()      # 持久化待合并节点(支持崩溃恢复)

    def try_merge(self, new_node: ASTNode) -> bool:
        if not self.pending_nodes:
            self.pending_nodes.append(new_node)
            return False
        last = self.pending_nodes[-1]
        # 基于字体/缩进/语义标签计算结构相似度
        sim = structural_similarity(last, new_node) 
        if sim > self.threshold:
            last.merge_children(new_node)  # 深度合并子树而非浅拷贝
            return True
        self.pending_nodes.append(new_node)
        return False

该实现避免AST树深度爆炸,merge_threshold动态适配不同排版密度文档;structural_similarity融合位置偏移(px)、字体族哈希、语义标记权重(如<code>权重×1.5)。

关键参数对比

参数 默认值 影响维度 调优建议
batch_size 64 内存峰值/吞吐量 高DPI扫描件建议≤32
layout_tolerance 2.5px 表格线识别鲁棒性 学术论文宜设为1.2
ast_cache_ttl 300s 跨文档引用一致性 法规类文档需延长至1800s
graph TD
    A[PDF流分片] --> B{布局分析器}
    B --> C[文本块+坐标]
    C --> D[语义标注器]
    D --> E[AST片段]
    E --> F[双缓冲队列]
    F --> G[相似度匹配]
    G --> H{> threshold?}
    H -->|Yes| I[节点合并]
    H -->|No| J[新节点入队]
    I --> K[持久化AST根]

第三章:自动化检测系统架构与核心组件

3.1 分布式任务调度与PDF→Go源码逆向流水线设计

该流水线将扫描PDF文档中的伪代码/流程图,经OCR与语义解析后生成等效Go源码,全程由分布式调度器协调。

核心组件职责

  • PDF解析器:提取矢量图与文本层,保留结构化坐标信息
  • 语义对齐引擎:匹配LaTeX公式、UML活动图与Go控制流模式
  • 调度适配器:将任务切片为{page_id, region_bbox, target_type}单元,注册至Redis Stream队列

任务分发逻辑(Go)

func dispatchToWorker(task Task) error {
    // task.ID由Snowflake生成;task.Payload含base64 PDF片段与OCR置信度阈值
    return redisClient.XAdd(ctx, &redis.XAddArgs{
        Stream: "pdf2go:queue",
        Values: map[string]interface{}{
            "id":       task.ID,
            "payload":  task.Payload,
            "deadline": time.Now().Add(30 * time.Second).Unix(),
        },
    }).Err()
}

XAdd确保消息持久化且按时间序入队;deadline字段供worker超时熔断,避免长尾任务阻塞流水线。

流水线阶段映射表

阶段 负责服务 SLA 输出格式
OCR增强 tesseract-go JSON+SVG锚点
控制流还原 graph2go AST节点序列
类型推导 go-type-infer Go interface{}
graph TD
    A[PDF输入] --> B[分页切片]
    B --> C[OCR+结构识别]
    C --> D{置信度≥0.85?}
    D -->|是| E[生成AST]
    D -->|否| F[转人工校验队列]
    E --> G[Go源码生成]

3.2 指纹比对引擎:局部敏感哈希(LSH)与编辑距离融合算法实战

在高吞吐日志指纹聚类场景中,纯LSH易因哈希碰撞导致语义近似但结构差异大的样本误判;纯编辑距离则无法扩展至亿级样本。本方案采用两级协同策略:先用MinHash-LSH快速筛选候选集(Jaccard相似度 > 0.4),再对候选对执行加权编辑距离精排。

LSH候选生成(MinHash + Banding)

from datasketch import MinHashLSH, MinHash
def build_lsh_index(docs, num_perm=128, num_bands=8):
    lsh = MinHashLSH(threshold=0.4, num_perm=num_perm)
    for i, doc in enumerate(docs):
        m = MinHash(num_perm=num_perm)
        for word in doc.split(): m.update(word.encode('utf8'))
        lsh.insert(f'id_{i}', m)
    return lsh

num_bands=8 将128维签名划分为8组×16维,提升哈希桶敏感性;threshold=0.4 平衡召回率与计算开销。

融合打分公式

组件 权重 说明
LSH相似度 0.3 快速粗筛置信度
编辑距离归一值 0.5 1 - edit_dist/len(max)
长度一致性 0.2 1 - abs(len_a-len_b)/max_len
graph TD
    A[原始日志片段] --> B[MinHash签名]
    B --> C[LSH哈希桶检索]
    C --> D{候选集 size ≤ 50?}
    D -->|是| E[逐对加权编辑距离]
    D -->|否| F[Top-50 LSH相似度截断]
    E --> G[融合得分排序]

3.3 检测结果可信度评估模型与误报抑制机制

可信度评估采用多维置信加权融合策略,综合考虑检测置信度、上下文一致性、历史行为稳定性三类指标。

评估因子构成

  • 静态置信分:模型原始输出概率(经温度校准)
  • 动态上下文分:基于滑动窗口内相邻帧语义一致性(IoU ≥ 0.7 计为1分)
  • 时序稳定性分:过去5次检测结果的类别熵值倒数(熵越低,稳定性越高)

可信度计算公式

def compute_trust_score(raw_conf, iou_seq, class_entropy):
    # raw_conf: 校准后置信度 [0.0, 1.0]
    # iou_seq: 近3帧IoU序列,长度3,值域[0.0, 1.0]
    # class_entropy: 过去5次预测的类别分布熵 [0.0, log2(N)]
    context_score = np.mean([1.0 if iou >= 0.7 else 0.3 for iou in iou_seq])
    stability_score = max(0.2, 1.0 - min(0.8, class_entropy / 2.0))  # 归一化至[0.2,1.0]
    return 0.5 * raw_conf + 0.3 * context_score + 0.2 * stability_score

逻辑说明:权重分配体现“模型本征能力为主、上下文佐证为辅、时序验证兜底”的设计哲学;stability_score 设置下限0.2防止历史噪声导致可信度坍塌。

误报抑制决策流

graph TD
    A[原始检测框] --> B{可信度 ≥ 0.65?}
    B -->|是| C[进入最终输出队列]
    B -->|否| D{连续2帧低于0.4?}
    D -->|是| E[触发抑制:丢弃+标记异常]
    D -->|否| F[暂存缓冲区,等待下一帧校验]
误报类型 抑制响应延迟 触发条件
单帧孤立误报 即时 可信度
闪烁型误报 1帧缓冲 可信度在[0.4, 0.65)区间振荡
长期漂移误报 3帧累积 连续3帧稳定性分

第四章:API接口集成与生产环境部署指南

4.1 RESTful检测接口规范设计与gRPC双协议支持实现

为兼顾兼容性与高性能,系统采用双协议网关统一接入层:RESTful面向第三方系统集成,gRPC用于内部微服务间低延迟调用。

接口语义统一建模

所有检测能力抽象为 DetectRequest / DetectResponse 公共消息体,字段语义严格对齐:

字段 类型 说明
task_id string 全局唯一任务标识,用于幂等与追踪
image_data bytes Base64编码图像(REST)或原始二进制(gRPC)
confidence_threshold float32 置信度过滤阈值,默认0.5

gRPC服务定义节选

service DetectionService {
  // 同步检测(支持流式响应扩展)
  rpc Detect(DetectRequest) returns (DetectResponse);
}

逻辑分析:DetectRequest 复用 REST 的 JSON Schema 字段命名,避免协议转换歧义;bytes image_data 原生支持零拷贝传输,较 REST 的 base64 编码减少约33%带宽开销。

协议路由流程

graph TD
  A[HTTP/1.1] -->|Accept: application/json| B(REST Adapter)
  C[gRPC/HTTP2] --> D(gRPC Endpoint)
  B & D --> E[Core Detection Engine]

双协议适配关键点

  • REST 层自动处理 Content-Type 解析与错误码映射(如 400 → INVALID_ARGUMENT)
  • gRPC 层启用 grpc-gateway 生成反向 REST 代理,实现单套业务逻辑双协议暴露

4.2 百度云网盘文件钩子注入与实时扫描SDK接入实践

百度云网盘客户端支持通过 FileHookService 注册文件系统级钩子,拦截本地同步目录的增删改事件。

钩子注册流程

  • 调用 BaiduPCS.registerFileHook() 传入监听路径与回调接口
  • SDK 内部通过 inotify(Linux)/ ReadDirectoryChangesW(Windows)实现底层监听
  • 事件触发后,自动调用 onFileChanged(path, eventType, md5) 回调

实时扫描SDK集成示例

// 初始化扫描SDK(v3.2.1+)
RealTimeScanner scanner = RealTimeScanner.builder()
    .withPolicy(ScanPolicy.SCAN_ON_WRITE) // 仅写入时扫描
    .withEngine(EngineType.CLAMAV_LOCAL)   // 本地ClamAV引擎
    .build();
scanner.start(); // 启动后自动绑定已注册钩子

此代码启动轻量级扫描器,SCAN_ON_WRITE 策略避免重复扫描缓存文件;CLAMAV_LOCAL 引擎需提前部署 clamd 服务并配置 socket 路径。

支持的事件类型对照表

事件类型 触发条件 是否阻塞同步
CREATE 新建文件/目录 否
MODIFY 文件内容变更 是(默认)
DELETE 文件被移入回收站 否

数据流时序(mermaid)

graph TD
    A[用户保存文件] --> B[钩子捕获MODIFY事件]
    B --> C[SDK提取文件元数据与MD5]
    C --> D{是否命中白名单?}
    D -- 否 --> E[调用ClamAV异步扫描]
    D -- 是 --> F[跳过扫描,直通同步]
    E --> G[返回威胁结果]

4.3 批量清洗任务编排与状态可观测性建设(Prometheus+Grafana)

数据同步机制

清洗任务由 Airflow DAG 触发,每批次生成唯一 batch_id 并写入元数据表,作为后续指标打点的关联键。

指标埋点与暴露

清洗服务通过 Prometheus Client 暴露自定义指标:

from prometheus_client import Counter, Gauge

# 定义批处理核心指标
batch_success_total = Counter(
    'cleaning_batch_success_total', 
    'Total number of successful cleaning batches',
    ['pipeline_name', 'env']  # 多维标签,支持按流水线/环境切片
)
batch_duration_seconds = Gauge(
    'cleaning_batch_duration_seconds',
    'Duration of last completed batch in seconds',
    ['batch_id', 'status']
)

逻辑分析:Counter 累计成功批次数,Gauge 实时记录单批次耗时;['batch_id'] 标签使 Grafana 可下钻至具体批次,['status'] 支持失败归因。需在任务结束时调用 batch_duration_seconds.labels(...).set(elapsed)。

监控看板联动

Grafana 配置关键面板,聚合维度如下:

面板名称 查询表达式 用途
批次成功率趋势 rate(cleaning_batch_success_total[1h]) 检测周期性异常
TOP5 耗时批次 topk(5, cleaning_batch_duration_seconds) 定位性能瓶颈

任务状态流转

graph TD
    A[任务提交] --> B[Pre-check OK?]
    B -->|Yes| C[执行清洗]
    B -->|No| D[标记失败并告警]
    C --> E[写入结果表]
    C --> F[上报指标]
    E --> G[更新batch_status=success]

4.4 权限隔离、审计日志与GDPR合规性保障方案

核心策略三支柱

  • 最小权限动态授权:基于RBAC+ABAC混合模型,按角色、数据敏感等级、访问上下文实时决策
  • 全链路不可篡改日志:记录操作主体、时间、资源ID、原始请求载荷哈希(SHA-256)
  • GDPR关键能力支撑:支持「被遗忘权」自动级联擦除 + 「数据可携权」结构化导出(JSON Schema v1.1)

审计日志采样代码

# GDPR-compliant audit logger with PII masking
def log_access(user_id: str, resource_id: str, action: str):
    payload_hash = hashlib.sha256(request.body).hexdigest()[:16]
    # Masked PII: only retain last 4 digits of user_id if numeric
    masked_id = f"***{user_id[-4:]}" if user_id.isdigit() else user_id
    audit_entry = {
        "timestamp": datetime.utcnow().isoformat(),
        "subject": masked_id,
        "resource": f"urn:api:{resource_id}",
        "action": action,
        "payload_hash": payload_hash,
        "consent_id": get_active_consent(user_id)  # Required for Art.6 compliance
    }
    write_to_immutable_storage(audit_entry)

该函数确保日志满足GDPR第32条“安全性”要求:masked_id防止身份重识别;payload_hash提供完整性校验;consent_id绑定法律依据,支持监管审计溯源。

合规能力映射表

GDPR条款 技术实现 验证方式
第17条 自动触发外键级联删除 + S3对象版本标记清除 审计日志中erasure_job_id字段
第20条 /api/v1/users/{id}/export?format=json 返回含@context的Schema.org元数据
graph TD
    A[用户请求删除] --> B{验证合法基础}
    B -->|有效consent| C[启动级联擦除工作流]
    B -->|无consent| D[拒绝并记录audit]
    C --> E[数据库行删除]
    C --> F[S3加密对象标记为DELETED]
    C --> G[日志写入immutable ledger]

第五章:附录:检测接口调用示例与开源贡献说明

接口调用环境准备

在调用安全检测类 API 前,请确保已安装 requests(≥2.28.0)与 python-dotenv(用于密钥管理),并完成以下配置:

  • 将 API_KEY 和 BASE_URL 写入 .env 文件;
  • 使用 load_dotenv() 加载环境变量;
  • 所有请求需携带 Content-Type: application/json 与 Authorization: Bearer <token> 头。

Python 同步调用示例

以下为对 /v1/scan/url 接口的完整调用代码(含错误重试与响应解析):

import requests, os, time
from dotenv import load_dotenv
load_dotenv()

def scan_url(target_url: str) -> dict:
    url = f"{os.getenv('BASE_URL')}/v1/scan/url"
    headers = {
        "Authorization": f"Bearer {os.getenv('API_KEY')}",
        "Content-Type": "application/json"
    }
    payload = {"url": target_url, "scan_depth": 2}

    for attempt in range(3):
        try:
            resp = requests.post(url, json=payload, headers=headers, timeout=30)
            resp.raise_for_status()
            return resp.json()
        except requests.exceptions.RequestException as e:
            if attempt == 2:
                raise e
            time.sleep(1.5 ** attempt)
    return {}

cURL 快速验证命令

开发调试阶段可直接使用以下命令发起单次检测请求(替换 <YOUR_TOKEN> 和目标 URL):

curl -X POST 'https://api.secure.dev/v1/scan/url' \
  -H 'Authorization: Bearer <YOUR_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com","scan_depth":1}'

开源项目协作规范

本系列工具链所依赖的核心检测引擎已开源至 GitHub 仓库 secure-scan-core,欢迎参与贡献。关键协作路径如下:

贡献类型 提交方式 审核周期 CI 检查项
Bug 修复 PR 至 main 分支 ≤24h pytest + bandit + mypy
新检测规则 PR 至 rules/ 目录 ≤48h rule-validator + test coverage ≥95%
文档改进 编辑 docs/ 下 Markdown ≤12h markdownlint + dead-links check

贡献流程图

flowchart LR
    A[ Fork 仓库 ] --> B[ 创建特性分支 feature/xxx ]
    B --> C[ 编写代码/测试/文档 ]
    C --> D[ 本地运行 pre-commit hooks ]
    D --> E[ 提交 PR 并关联 Issue ]
    E --> F{ CI 全部通过? }
    F -->|是| G[Maintainer 人工评审]
    F -->|否| C
    G --> H[合并至 main 分支]

接口响应字段说明

成功响应中关键字段含义如下(以 scan_id, status, findings 为核心):

  • scan_id: UUIDv4 格式唯一标识,可用于轮询状态或获取报告;
  • status: 取值包括 "queued"、"running"、"completed"、"failed";
  • findings: 数组结构,每个元素含 severity(CRITICAL/HIGH/MEDIUM/LOW)、cwe_id、description、evidence_snippet;
  • report_url: 生成的 PDF 报告直链(有效期 7 天,需凭 token 访问)。

社区支持渠道

问题反馈与协作讨论请优先使用以下官方渠道:

  • GitHub Discussions(分类标签:question / enhancement / rule-request);
  • Slack 工作区 #security-scan-dev 频道(邀请链接见 README);
  • 每周三 16:00 UTC 的公开贡献者同步会议(Zoom 链接与议程提前 48 小时发布于 Discussions)。

专攻高并发场景,挑战百万连接与低延迟极限。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注