¥
立即购买

Java正则表达式开发专家

10 浏览
1 试用
0 购买
Dec 6, 2025更新

本提示词专为Java开发场景设计,能够根据具体任务需求生成精准、高效的正则表达式解决方案。提示词采用结构化工作流程,从需求分析到代码实现层层递进,确保生成的正则表达式既符合Java语法规范又满足实际业务需求。特别适用于文本处理、数据验证、模式匹配等开发场景,提供技术解释和最佳实践建议,帮助开发者理解实现原理并应用于实际项目。

需求分析

  • 目标:从 Nginx 访问日志中提取“真实客户端 IPv4”
  • 规则:
    1. 若存在 X-Forwarded-For,则优先选取其中“首个公网 IPv4”
    2. 仅匹配公网 IPv4,跳过以下网段:127.0.0.0/8(回环)、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16(私有)、169.254.0.0/16(链路本地,附加排除,常见于真实环境)
    3. 若 X-Forwarded-For 不存在或其中没有公网 IPv4,则回退到日志行首的客户端 IP(同样仅限公网)
  • 日志示例(需命中):
    • 198.51.100.23 - - [2025-06-01T12:41:22+08:00] "GET /api" 200
    • 203.0.113.5 - - [2025-06-01] "GET /" 200 "X-Forwarded-For: 10.0.0.2, 203.0.113.5"

正则表达式

  • 设计思路:

    • 定义 IPv4 合法八位组
    • 定义私有/保留(需跳过)网段
    • 定义“公网 IPv4”= 非上述网段 且 符合 IPv4 取值范围
    • X-Forwarded-For 中通过“重复跳过若干私网 IP + 捕获首个公网 IP”的结构,确保拿到第一个公网 IP
    • 若 XFF 不满足,则回退开头的公网 IP
  • 组成片段(Java 字符串常量形式):

    • 八位组(0-255):
      • (?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)
    • IPv4:
      • OCTET(?:.OCTET){3}
    • 需跳过网段(私有/回环/链路本地):
      • 10.OCTET.OCTET.OCTET
      • 192.168.OCTET.OCTET
      • 172.(16-31).OCTET.OCTET
      • 127.OCTET.OCTET.OCTET
      • 169.254.OCTET.OCTET
    • 公网 IPv4(用否定前瞻剔除上述前缀):
      • (?!(?:10|127).|192.168.|169.254.|172.(?:1[6-9]|2\d|3[0-1]).)IPv4
  • 完整模式(带命名捕获组,大小写不敏感,通过 Pattern.CASE_INSENSITIVE 设置):

    • 第一分支:X-Forwarded-For 中的首个公网 IP 捕获为组 xff
      • \bX-Forwarded-For\s*:\s*(?:\s*(?:PRIVATE_IP)\s*,)\s(?PUBLIC_IP)\b
    • 第二分支:行首的公网 IP 捕获为组 remote
      • ^\s*(?PUBLIC_IP)\b

最终组合(展示为 Java 中的字符串):

  • (?:\bX-Forwarded-For\s*:\s*(?:\s*(?:PRIVATE_IP)\s*,)\s(?PUBLIC_IP)\b)|(?:^\s*(?PUBLIC_IP)\b)

捕获组说明:

  • 命名组 xff:当存在 X-Forwarded-For 且其中包含公网 IPv4 时,捕获该列表里“首个公网” IP
  • 命名组 remote:当未从 X-Forwarded-For 取到公网 IP 时,回退捕获行首的公网 IP
  • 使用逻辑:优先取 group("xff"),若为空再取 group("remote")

Java代码实现

import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NginxClientIpExtractor {

    // 八位组 0-255
    private static final String OCTET = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)";
    // 通用 IPv4
    private static final String IPV4 = OCTET + "(?:\\." + OCTET + "){3}";

    // 需跳过的私有/回环/链路本地网段
    private static final String PRIVATE_IP =
            "(?:"
                    + "10\\." + OCTET + "\\." + OCTET + "\\." + OCTET
                    + "|192\\.168\\." + OCTET + "\\." + OCTET
                    + "|172\\.(?:1[6-9]|2\\d|3[0-1])\\." + OCTET + "\\." + OCTET
                    + "|127\\." + OCTET + "\\." + OCTET + "\\." + OCTET
                    + "|169\\.254\\." + OCTET + "\\." + OCTET
                    + ")";

    // 公网 IPv4(用前缀否定前瞻排除上述网段)
    private static final String PUBLIC_IPV4 =
            "(?!(?:10|127)\\.|192\\.168\\.|169\\.254\\.|172\\.(?:1[6-9]|2\\d|3[0-1])\\.)" + IPV4;

    // 完整提取模式:
    // 1) XFF: 跳过若干私网IP + 捕获首个公网IP 到命名组 xff
    // 2) 行首: 捕获公网IP 到命名组 remote
    private static final String REGEX =
            "(?:\\bX-Forwarded-For\\s*:\\s*(?:\\s*(?:" + PRIVATE_IP + ")\\s*,)*\\s*(?<xff>" + PUBLIC_IPV4 + ")\\b)"
          + "|(?:^\\s*(?<remote>" + PUBLIC_IPV4 + ")\\b)";

    // 编译 Pattern(大小写不敏感即可匹配 X-Forwarded-For 大小写变体)
    private static final Pattern PATTERN = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE);

    /**
     * 从单行 Nginx 访问日志中提取真实客户端公网 IPv4。
     * 优先 X-Forwarded-For 中首个公网 IP;否则回退到行首公网 IP;都没有则返回 Optional.empty()。
     */
    public static Optional<String> extractClientIp(String logLine) {
        if (logLine == null || logLine.isEmpty()) {
            return Optional.empty();
        }
        Matcher m = PATTERN.matcher(logLine);
        if (!m.find()) {
            return Optional.empty();
        }
        String ip = m.group("xff");
        if (ip == null) {
            ip = m.group("remote");
        }
        return Optional.ofNullable(ip);
    }

    // 示例演示
    public static void main(String[] args) {
        String l1 = "198.51.100.23 - - [2025-06-01T12:41:22+08:00] \"GET /api\" 200";
        String l2 = "203.0.113.5 - - [2025-06-01] \"GET /\" 200 \"X-Forwarded-For: 10.0.0.2, 203.0.113.5\"";
        System.out.println(extractClientIp(l1).orElse("<none>")); // 198.51.100.23
        System.out.println(extractClientIp(l2).orElse("<none>")); // 203.0.113.5
    }
}

使用说明

  • 将 NginxClientIpExtractor 集成到日志处理流水线中,对每一条日志调用 extractClientIp(line)
  • 返回值为 Optional,存在时即为“真实客户端公网 IPv4”
  • 已内建优先级:X-Forwarded-For 首个公网 IP > 行首公网 IP
  • 性能建议:保持 PATTERN 单例复用;避免在高频路径重复编译
  • 适用范围:IPv4;若需要 IPv6 或 Forwarded 规范头(for=...),需扩展

测试示例 使用 JUnit 5 的单元测试,覆盖边界与典型场景。

import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

public class NginxClientIpExtractorTest {

    @Test
    void test_NoXff_UseRemoteAddr_Public() {
        String line = "198.51.100.23 - - [2025-06-01T12:41:22+08:00] \"GET /api\" 200";
        assertEquals(Optional.of("198.51.100.23"), NginxClientIpExtractor.extractClientIp(line));
    }

    @Test
    void test_Xff_FirstPublicChosen() {
        String line = "203.0.113.5 - - [2025-06-01] \"GET /\" 200 \"X-Forwarded-For: 10.0.0.2, 203.0.113.5\"";
        assertEquals(Optional.of("203.0.113.5"), NginxClientIpExtractor.extractClientIp(line));
    }

    @Test
    void test_Xff_AllPrivate_FallbackToRemotePublic() {
        String line = "203.0.113.9 - - [2025-06-01] \"GET /\" 200 \"X-Forwarded-For: 10.0.0.1, 172.31.2.3, 192.168.1.9\"";
        assertEquals(Optional.of("203.0.113.9"), NginxClientIpExtractor.extractClientIp(line));
    }

    @Test
    void test_NoXff_RemoteIsPrivate_NoResult() {
        String line = "10.1.2.3 - - [2025-06-01] \"GET /\" 200";
        assertTrue(NginxClientIpExtractor.extractClientIp(line).isEmpty());
    }

    @Test
    void test_Xff_SkipLoopback_ThenPublic() {
        String line = "10.0.0.9 - - [2025-06-01] \"GET /\" 200 \"X-Forwarded-For: 127.0.0.1, 198.51.100.77\"";
        assertEquals(Optional.of("198.51.100.77"), NginxClientIpExtractor.extractClientIp(line));
    }

    @Test
    void test_Xff_SpacesAndMixed() {
        String line = "203.0.113.10 - - [2025-06-01] \"GET /\" 200 \"X-Forwarded-For:   10.0.0.1 ,   203.0.113.88 , 192.168.1.1\"";
        assertEquals(Optional.of("203.0.113.88"), NginxClientIpExtractor.extractClientIp(line));
    }

    @Test
    void test_172_15_IsPublic_NotExcluded() {
        String line = "172.15.1.2 - - [2025-06-01] \"GET /\" 200";
        assertEquals(Optional.of("172.15.1.2"), NginxClientIpExtractor.extractClientIp(line));
    }

    @Test
    void test_169_254_Excluded() {
        String line = "169.254.1.2 - - [2025-06-01] \"GET /\" 200";
        assertTrue(NginxClientIpExtractor.extractClientIp(line).isEmpty());
    }

    @Test
    void test_MalformedOrEmptyLine() {
        assertTrue(NginxClientIpExtractor.extractClientIp("").isEmpty());
        assertTrue(NginxClientIpExtractor.extractClientIp(null).isEmpty());
    }
}

预期结果(部分):

  • 示例1返回 198.51.100.23
  • 示例2返回 203.0.113.5
  • 所有 private/loopback/link-local-only 情况返回 empty
  • 172.15.x.x 作为公网返回该地址

最佳实践

  • 预编译并复用 Pattern,避免每行重复编译造成 CPU 消耗
  • X-Forwarded-For 中仅解析第一个公网地址,避免“任意一个公网地址”误判(遵循标准代理链语义)
  • 若业务还需识别 CGNAT(100.64.0.0/10)或保留地址(0.0.0.0/8、240.0.0.0/4 等),可在 PRIVATE_IP 与 PUBLIC_IPV4 的否定前瞻中补充排除前缀
  • 真实生产中可能存在 IPv6 或 Forwarded 标准头(Forwarded: for=...);如需支持,请并行增加 IPv6 模式与对应解析逻辑
  • 记录无法解析或无公网 IP 的行,便于上游网关/代理配置核查

需求分析

  • 订单号固定格式:ORD-YYYYMMDD-XXXXXX
  • 约束:
    • 前缀固定为 "ORD"
    • 日期为8位数字(仅格式校验,不在正则内做“真实日历”合法性判断)
    • 尾段为6位仅包含大写字母与数字,且至少包含2个大写字母
  • 需要分组提取:日期(YYYYMMDD)与尾段(XXXXXX)
  • 提供:正则、示例校验方法、边界与批量测试样例
  • 匹配目标(格式上均应匹配):
    • ORD-20251206-AB12X9
    • ORD-20241130-1A2B3C
    • ORD-20240230-ABC123(注意:日期格式正确,但不是实际存在的日期,需在业务层面做额外的日历校验)

正则表达式

  • Java 正则(含命名分组,便于可读性与维护):
^ORD-(?<date>[0-9]{8})-(?<tail>(?=(?:.*[A-Z]){2,})[A-Z0-9]{6})$
  • 解释:
    • ^ORD-:固定前缀
    • (?[0-9]{8}):命名分组 date,8位数字日期(仅格式)
    • -:连字符
    • (?...): 命名分组 tail,尾段6位
      • (?=(?:.*[A-Z]){2,}):正向前瞻,确保接下来的6位中至少出现2个大写字母
      • [A-Z0-9]{6}:6位仅限大写字母或数字
    • $:整串匹配结束
  • 选择该写法的原因:
    • 用前瞻在“保持精确长度为6”的同时检查“至少两个大写字母”,避免把长度判断写复杂
    • 全串锚定(^...$)避免子串误匹配
    • 命名分组提升可读性(Java可通过 group("date")/group("tail") 获取)

Java代码实现

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class OrderIdValidator {
    // 编译后的正则,建议长期复用,避免重复编译带来的开销
    private static final Pattern ORDER_ID_PATTERN = Pattern.compile(
            "^ORD-(?<date>[0-9]{8})-(?<tail>(?=(?:.*[A-Z]){2,})[A-Z0-9]{6})$"
    );

    // 严格日期解析器:用于“业务层面”的真实日期合法性校验(非正则内完成)
    private static final DateTimeFormatter DATE_FMT_STRICT =
            DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(ResolverStyle.STRICT);

    private OrderIdValidator() {}

    // 仅做格式校验(正则),不验证日期真伪
    public static boolean isFormatValid(String orderId) {
        if (orderId == null) return false;
        return ORDER_ID_PATTERN.matcher(orderId).matches();
    }

    // 同时做格式校验与真实日期校验(严格)
    public static boolean isStrictValid(String orderId) {
        Optional<OrderParts> partsOpt = parse(orderId);
        if (!partsOpt.isPresent()) return false;
        return isRealDate(partsOpt.get().getDate());
    }

    // 分组提取(成功则返回日期与尾段);仅在格式匹配时返回
    public static Optional<OrderParts> parse(String orderId) {
        if (orderId == null) return Optional.empty();
        Matcher m = ORDER_ID_PATTERN.matcher(orderId);
        if (!m.matches()) return Optional.empty();

        String date = m.group("date");
        String tail = m.group("tail");
        return Optional.of(new OrderParts(orderId, date, tail));
    }

    // 业务层面:严格日期校验(yyyyMMdd必须为真实日期)
    public static boolean isRealDate(String yyyyMMdd) {
        try {
            LocalDate.parse(yyyyMMdd, DATE_FMT_STRICT);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    // 示例:批量校验并打印结果
    public static void main(String[] args) {
        List<String> samples = new ArrayList<>();
        // 题中样例(格式应匹配)
        samples.add("ORD-20251206-AB12X9");
        samples.add("ORD-20241130-1A2B3C");
        samples.add("ORD-20240230-ABC123"); // 非真实日期

        // 边界/对照样例
        samples.add("ORD-20251206-A12345"); // 尾段仅1个字母 -> 格式应失败
        samples.add("ORD-20251206-123456"); // 尾段0个字母 -> 格式应失败
        samples.add("ORD-20251206-AB12x9"); // 含小写x -> 格式应失败
        samples.add("ORD-20251206-AB12X9X"); // 尾段长度7 -> 格式应失败
        samples.add("ORD-2025120-AB12X9");  // 日期7位 -> 格式应失败
        samples.add("ORD-20251306-AB12X9"); // 13月(格式匹配,但真实日期失败)
        samples.add("XXX-20251206-AB12X9"); // 前缀错误 -> 格式应失败
        samples.add("ORD-abcdefgh-ABC123"); // 日期非数字 -> 格式应失败
        samples.add("ORD-20251206-AB12X@"); // 非法字符@ -> 格式应失败
        samples.add("ORD-20251206-AAAAAA"); // 6字母 -> 格式应通过,真实日期也通过

        for (String s : samples) {
            boolean formatOk = isFormatValid(s);
            Optional<OrderParts> parts = parse(s);
            boolean realDateOk = parts.map(p -> isRealDate(p.getDate())).orElse(false);

            System.out.printf(
                "%-25s | format=%-5s | date=%-8s | tail=%-6s | realDate=%-5s%n",
                s,
                formatOk,
                parts.map(OrderParts::getDate).orElse("-"),
                parts.map(OrderParts::getTail).orElse("-"),
                realDateOk
            );
        }
    }

    // 简单数据承载类
    public static final class OrderParts {
        private final String original;
        private final String date; // yyyyMMdd
        private final String tail; // 6位大写字母/数字(至少2个字母)

        public OrderParts(String original, String date, String tail) {
            this.original = original;
            this.date = date;
            this.tail = tail;
        }
        public String getOriginal() { return original; }
        public String getDate() { return date; }
        public String getTail() { return tail; }
        @Override public String toString() {
            return "OrderParts{date=" + date + ", tail=" + tail + "}";
        }
    }
}

使用说明

  • 仅需格式校验时(不关心日期真伪):调用 isFormatValid(orderId) 或 parse(orderId) 提取分组。
  • 需要严格校验真实日期时:调用 isStrictValid(orderId)(内部先匹配正则,再用 LocalDate 严格解析)。
  • 获取分组:
    • 命名分组 date:YYYYMMDD(字符串)
    • 命名分组 tail:尾段6位(大写字母与数字,至少2字母)
  • 性能建议:将 Pattern 静态编译并复用;避免每次用 Pattern.matches 触发隐式重编译。

测试示例(期望结论)

  • 应匹配(格式):
    • ORD-20251206-AB12X9 → format=true,realDate=true
    • ORD-20241130-1A2B3C → format=true,realDate=true
    • ORD-20240230-ABC123 → format=true,realDate=false(2月30日不存在)
  • 不应匹配(格式):
    • ORD-20251206-A12345 → 尾段字母仅1个
    • ORD-20251206-123456 → 尾段无字母
    • ORD-20251206-AB12x9 → 含小写
    • ORD-20251206-AB12X9X → 尾段长度错误
    • ORD-2025120-AB12X9 → 日期长度错误
    • XXX-20251206-AB12X9 → 前缀错误
    • ORD-abcdefgh-ABC123 → 日期非数字
    • ORD-20251206-AB12X@ → 非法字符
  • 格式匹配但真实日期失败(若启用严格校验):
    • ORD-20240230-ABC123
    • ORD-20251306-AB12X9

最佳实践

  • 使用全串锚定 ^...$,避免子串误匹配。
  • 采用命名分组(? ...)、(? ...)提高可读性。
  • 将“真实日期合法性”从正则中剥离,使用 LocalDate 严格解析,既保证表达式简洁,又提升可维护性。
  • 复用已编译的 Pattern,避免重复编译带来的性能损耗。
  • 明确大小写规则:尾段仅允许 [A-Z0-9],不要使用 CASE_INSENSITIVE 以免误接纳小写。
  • 输入为外部来源时,注意做空值检查与长度上限限制(本例已通过正则与 null 判断覆盖)。

需求分析

  • 目标:从 Markdown 文本中提取普通超链接的文本与 URL,忽略图片语法 ![...](...) 与反引号包裹的代码片段(包括行内 ... 与围栏 ...)。
  • 约束:
    • 仅匹配 http|https|ftp 三类 URL。
    • 链接文本允许包含空格与转义字符(如 \])。
    • 返回两分组:(text, url)。
    • 忽略代码中的“伪链接”,也忽略图片语法。
  • 难点与边界:
    • Markdown 链接形式:text,需仅捕获 url,不包含可选的 title。
    • URL 可能包含成对圆括号,如 .../a(b)c,需避免在第一个 ) 处提早结束。
    • 图片 ![...] 与普通链接 [...] 的区分。
    • 反引号代码片段的屏蔽(不参与匹配)。

为保证可维护性与性能,采用“两阶段策略”:

  1. 先用正则屏蔽代码片段(替换为空格,不改变索引长度)。
  2. 再用专门的链接正则提取匹配。

正则表达式

主匹配(提取普通超链接,忽略图片):

  • Java 字符串常量形式(用于 Pattern.compile):
(?<!!)\[((?:\\.|[^\[\]\n])+?)\]\(\s*((?:https?|ftp)://[^\s)]+(?:\([^ \s)]*\)[^\s)]*)*)\s*(?:\s+(?:"[^"]*"|'[^']*'|\([^)]*\)))?\s*\)
  • 各部分说明:
    • (?<!!):负向前瞻,确保 [ 前一字符不是 !(排除图片 ![...](...))。
    • \[((?:\\.|[^\[\]\n])+?)\]:捕获链接文本(第1组)。
      • (?:\\.|[^\[\]\n])+? 允许转义字符(如 \])与除 [] 和换行外的任意字符,惰性匹配。
    • \(\):包裹 URL(及可选 title)。
    • \s*((?:https?|ftp)://[^\s)]+(?:\([^ \s)]*\)[^\s)]*)*)\s*:捕获 URL(第2组)。
      • 仅允许 http|https|ftp
      • [^ \s)]+(?:\([^ \s)]*\)[^\s)]*)* 允许 URL 中出现成对的圆括号,不被外层 ) 提前截断。
    • (?:\s+(?:"[^"]*"|'[^']*'|\([^)]*\)))?:可选的 title(不计入 URL 捕获)。
    • 结尾 \s*\):关闭链接的 )

代码片段屏蔽(两类):

  • 围栏代码块:``` ... ```(跨行)
    • `(?s)```.*?````
  • 行内代码:`[^`]*`

Java代码实现

import java.util.*;
import java.util.regex.*;

/**
 * 从 Markdown 文本中提取普通超链接 (text, url),忽略图片和反引号代码片段。
 */
public class MarkdownLinkExtractor {

    // 1) 屏蔽代码片段的正则
    private static final Pattern CODE_FENCE = Pattern.compile("(?s)```.*?```"); // 跨行围栏代码块
    private static final Pattern CODE_INLINE = Pattern.compile("`[^`]*`");     // 行内代码

    // 2) 提取普通链接的正则(两组:1=text, 2=url)
    private static final String LINK_REGEX =
            "(?<!!)\\[((?:\\\\.|[^\\[\\]\\n])+?)\\]\\(\\s*((?:https?|ftp)://[^\\s)]+(?:\\([^\\s)]*\\)[^\\s)]*)*)\\s*(?:\\s+(?:\"[^\"]*\"|'[^']*'|\\([^)]*\\)))?\\s*\\)";
    private static final Pattern LINK_PATTERN = Pattern.compile(LINK_REGEX);

    // 3) 反转义链接文本中的常见转义(如 \], \[, \(, \), \\)
    private static final Pattern LINK_TEXT_ESCAPES = Pattern.compile("\\\\([\\\\\\[\\]\\(\\)])");

    public static class Link {
        public final String text;
        public final String url;
        public Link(String text, String url) { this.text = text; this.url = url; }
        @Override public String toString() { return "(" + text + ", " + url + ")"; }
    }

    /**
     * 提取 Markdown 普通链接 (text, url),忽略图片与反引号代码。
     */
    public static List<Link> extractMarkdownLinks(String markdown) {
        if (markdown == null || markdown.isEmpty()) return Collections.emptyList();

        // 第一步:屏蔽代码片段(替换为空格,保持长度不变,避免打乱后续匹配的索引)
        String masked = maskBySpaces(markdown, CODE_FENCE);
        masked = maskBySpaces(masked, CODE_INLINE);

        // 第二步:在屏蔽后的文本中查找普通链接
        List<Link> result = new ArrayList<>();
        Matcher m = LINK_PATTERN.matcher(masked);
        while (m.find()) {
            String rawText = m.group(1);
            String url = m.group(2);

            // 反转义链接文本中的常见转义序列
            String text = unescapeLinkText(rawText);

            result.add(new Link(text, url));
        }
        return result;
    }

    // 用空格覆盖匹配到的区间(不改变字符串长度)
    private static String maskBySpaces(String input, Pattern p) {
        Matcher m = p.matcher(input);
        StringBuilder sb = new StringBuilder(input);
        while (m.find()) {
            for (int i = m.start(); i < m.end(); i++) {
                sb.setCharAt(i, ' ');
            }
        }
        return sb.toString();
    }

    // 将 \[, \], \(, \), \\ 反转义为本体
    private static String unescapeLinkText(String s) {
        return LINK_TEXT_ESCAPES.matcher(s).replaceAll("$1");
    }

    // Demo
    public static void main(String[] args) {
        String md = ""
                + "这是正文 [接口文档](https://docs.example.org/v1/api) 与 [下载](ftp://files.example.org/a.zip)。\n"
                + "![图](https://img.example.org/i.png) `代码含 [假链](http://x)`\n";

        List<Link> links = extractMarkdownLinks(md);
        for (Link link : links) {
            System.out.println(link);
        }
    }
}

使用说明

  • 将上述类保存并运行,输出为所提取的 (text, url) 列表。
  • 适用于一般 Markdown 文本:
    • 自动屏蔽行内反引号代码 ... 与围栏代码 ...
    • 忽略图片 ![alt](url)
    • 仅提取 http|https|ftp 链接
    • 支持 URL 中的成对括号
    • 支持链接文本中的常见反斜杠转义
  • 注意:
    • 该实现不尝试完整解析 Markdown 所有边界(如极端嵌套或罕见语法组合)。如果需要 100% 完整规范支持,建议使用专业 Markdown 解析器后再筛选链接节点。

测试示例

输入(包含题目给定用例):

  1. 基本与忽略规则
  1. 含 title 的链接(title 不应进入 url)
  1. URL 含括号
  1. 行内代码忽略
  1. 围栏代码忽略
  1. 链接文本转义
  1. 非允许协议不匹配
  • 文本:邮件
  • 预期:无匹配

最佳实践

  • 性能与可维护性:
    • 先屏蔽代码片段,再匹配链接,避免复杂且脆弱的“单正则”解决方案,降低回溯风险。
    • 将 Pattern 预编译为静态常量,避免重复编译开销。
    • URL 子模式采用“允许括号配对的常见写法”:
      • [^\\s)]+(?:\\([^\\s)]*\\)[^\\s)]*)*,在大多数实际 URL 中表现稳定。
  • 可靠性:
    • 如需更完整的 Markdown 支持(嵌套、中括号内复杂结构、引用式链接等),建议用专业 Markdown 解析库(如 flexmark-java)构建 AST 再抽取节点。
  • 国际化与编码:
    • Java 字符串与 Pattern 默认支持 Unicode;如涉及更复杂的 Unicode 分类,可启用相应的 Unicode 特性或在字符类中显式列举。
  • 安全:
    • 在使用提取到的 URL 前,应按业务需求进行协议白名单验证(此处已限定为 http/https/ftp),并做好后续网络访问的安全校验。

示例详情

解决的问题

打造一套“即插即用”的智能提示词,让AI以资深Java正则专家的身份,快速把零散需求转化为可直接落地的方案:从需求拆解到模式设计,再到可复制的Java示例、测试用例与优化建议,一次性生成。通过标准化输出与清晰解释,帮助个人与团队在文本处理、数据校验、日志解析、数据提取等高频场景中,显著降低试错成本、提升交付速度与代码质量,并沉淀可复用的工程化资产,最终实现更高的效率与更稳定的产出。

适用用户

Java后端工程师

快速产出注册登录、订单号、邮件手机号等校验规则;一键生成可运行示例与测试用例,直接接入服务;减少边界漏判与性能问题,缩短上线周期。

测试工程师

为接口与日志断言生成稳定的匹配表达式;批量构造边界用例,自动校正误报漏报;将回归校验固化为可复用模板。

数据工程师与爬虫开发

从网页与文本中提取价格、时间、地址等关键信息;一键适配多地区格式差异;借助示例代码快速嵌入到数据清洗流程。

特征总结

一键从业务需求生成匹配方案与Java示例,省去查资料与试错,立即落地到代码
内置关键边界与异常场景提示,自动优化正则结构,降低误匹配与性能隐患
支持文本提取、数据校验、日志解析等高频场景,轻松覆盖注册表单到运维监控
可按复杂度要求给出不同实现方案,附测试用例与预期结果,开箱即用可快速验证
提供命名规范与注释范式,自动生成可读性强的代码片段,便于团队协作与维护
内置性能优化建议与替代写法,避免灾难性回溯,让线上流量高峰依旧稳定
参数化模板支持多项目复用,一次配置即可批量生成各类校验与提取规则
提供错误示例与修复思路,快速定位边界问题,新手也能写出可上线的正则
支持中英文与多地区格式差异处理,手机号、时间、货币等一键适配本地规则
从安全合规角度把关输入过滤,内置黑白名单思路,降低注入与越权风险

如何使用购买的提示词模板

1. 直接在外部 Chat 应用中使用

将模板生成的提示词复制粘贴到您常用的 Chat 应用(如 ChatGPT、Claude 等),即可直接对话使用,无需额外开发。适合个人快速体验和轻量使用场景。

2. 发布为 API 接口调用

把提示词模板转化为 API,您的程序可任意修改模板参数,通过接口直接调用,轻松实现自动化与批量处理。适合开发者集成与业务系统嵌入。

3. 在 MCP Client 中配置使用

在 MCP client 中配置对应的 server 地址,让您的 AI 应用自动调用提示词模板。适合高级用户和团队协作,让提示词在不同 AI 工具间无缝衔接。

AI 提示词价格
¥20.00元
先用后买,用好了再付款,超安全!

您购买后可以获得什么

获得完整提示词模板
- 共 623 tokens
- 3 个可调节参数
{ 任务描述 } { 匹配目标 } { 复杂度要求 }
获得社区贡献内容的使用权
- 精选社区优质案例,助您快速上手提示词
使用提示词兑换券,低至 ¥ 9.9
了解兑换券 →
限时半价

不要错过!

半价获取高级提示词-优惠即将到期

17
:
23
小时
:
59
分钟
:
59