2025.1 最近的一些事情,解决的一些问题

grtsinry431/22/202539 views0 likes0 comments
Original

简单说说 reactCompiler

Next.js 15,React.js 19 更新挺久了,随之一并带来的是 reactCompiler 等待了不知多久的 beta 版本,简单试了下 Next.js 集成,体验还不错,具体步骤就看 官方文档

简单来说装一个 babel 插件

SHELL
1pnpm install babel-plugin-react-compiler

然后开 experimental 功能就行了,在 next.config.ts

TYPESCRIPT
1import type { NextConfig } from 'next'
2 
3const nextConfig: NextConfig = {
4  experimental: {
5    reactCompiler: true,
6  },
7}
8 
9export default nextConfig

开了之后,它会自动优化我们的代码,比如 useMemo useCallback 这种缓存的使用,会尽量避免多次频繁重新渲染,组件频繁清空数据整个渲染和挂载,...emm 怎么说,感觉 React 也开始借鉴 Vue 了,变成自动档了,可以自己优化了

显示 ip 归属地的实现

最近用 maxmindgeoip2 库补全我 ipv6 归属地的显示,正好说一下这个的完整实现。

XML
1<!--这个仅支持ipv4-->
2<dependency>
3    <groupId>org.lionsoul</groupId>
4    <artifactId>ip2region</artifactId>
5     <version>2.7.0</version>
6</dependency>
7
8<!--支持ipv4 ipv6,但是感觉效果不是很好-->
9<dependency>
10    <groupId>com.maxmind.geoip2</groupId>
11    <artifactId>geoip2</artifactId>
12    <version>4.2.1</version>
13</dependency>

首先到其开源仓库下载数据集:ip2region

ip2region.xdb 放好在 src/main/resources

同样如法炮制,下载 maxmind 的数据集,由于其商业版本收费,我们只能选择:GeoLite.mmdb

拿到 GeoLite2-City.mmdbGeoLite2-ASN.mmdb

我们可以写一个 IPLocationUtil

JAVA
1package com.grtsinry43.grtblog.util;
2
3import com.maxmind.geoip2.DatabaseReader;
4import com.maxmind.geoip2.model.AsnResponse;
5import com.maxmind.geoip2.model.CityResponse;
6import jakarta.servlet.http.HttpServletRequest;
7import org.apache.commons.lang3.StringUtils;
8import org.lionsoul.ip2region.xdb.Searcher;
9import org.slf4j.Logger;
10import org.slf4j.LoggerFactory;
11import org.springframework.util.FileCopyUtils;
12
13import java.io.File;
14import java.io.IOException;
15import java.io.InputStream;
16import java.net.InetAddress;
17import java.net.UnknownHostException;
18
19/**
20 * Utility class for getting address by IP using GeoIP2 library.
21 *
22 * @date 2024/11/2 23:58
23 * @description 热爱可抵岁月漫长
24 */
25public class IPLocationUtil {
26
27    private static final Logger logger = LoggerFactory.getLogger(IPLocationUtil.class);
28    private static final String LOCAL_IP = "127.0.0.1";
29    private static final String LOCAL_IPV6 = "::1";
30
31    // 这里根据需要初始化数据库搜索实例
32    private static final Searcher searcher;
33    private static DatabaseReader dbReader;
34    private static DatabaseReader asnReader;
35
36    static {
37        try {
38            InputStream cityDbStream = IPLocationUtil.class.getResourceAsStream("/GeoLite2-City.mmdb");
39            InputStream asnDbStream = IPLocationUtil.class.getResourceAsStream("/GeoLite2-ASN.mmdb");
40            if (cityDbStream == null || asnDbStream == null) {
41                throw new IOException("GeoIP2 database files not found in classpath");
42            }
43            dbReader = new DatabaseReader.Builder(cityDbStream).build();
44            asnReader = new DatabaseReader.Builder(asnDbStream).build();
45            InputStream ris = IPLocationUtil.class.getResourceAsStream("/ip2region.xdb");
46            byte[] dbBinStr = FileCopyUtils.copyToByteArray(ris);
47            searcher = Searcher.newWithBuffer(dbBinStr);
48            logger.debug("ip2region 数据库加载成功");
49            logger.debug("GeoIP2 数据库加载成功");
50        } catch (IOException e) {
51            logger.error("无法创建数据库读取器: {}", e.getMessage());
52            throw new RuntimeException("无法加载 GeoIP2 数据库", e);
53        }
54    }
55
56    /**
57     * 获取用户真实 IP 地址
58     *
59     * @param request HttpServletRequest
60     * @return 用户真实 IP 地址
61     */
62    public static String getIp(HttpServletRequest request) {
63        String ipAddress = request.getHeader("X-Original-Forwarded-For");
64
65        if (StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
66            ipAddress = request.getHeader("X-Forwarded-For");
67        }
68        if (StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
69            ipAddress = request.getRemoteAddr();
70            if (LOCAL_IP.equals(ipAddress) || LOCAL_IPV6.equals(ipAddress)) {
71                try {
72                    InetAddress inet = InetAddress.getLocalHost();
73                    ipAddress = inet.getHostAddress();
74                } catch (UnknownHostException e) {
75                    logger.error("获取IP地址失败: {}", e.getMessage());
76                }
77            }
78        }
79
80        if (ipAddress != null && ipAddress.length() > 15) {
81            int commaIndex = ipAddress.indexOf(",");
82            if (commaIndex > 0) {
83                ipAddress = ipAddress.substring(0, commaIndex);
84            }
85        }
86        return LOCAL_IPV6.equals(ipAddress) ? LOCAL_IP : ipAddress;
87    }
88
89    /**
90     * 根据 IP 获取 ip2region 信息
91     *
92     * @param ip IP 地址
93     * @return 解析后的信息
94     */
95    public static String getIp2region(String ip) {
96        if (searcher == null) {
97            logger.error("搜索器未初始化");
98            return null;
99        }
100
101        try {
102            InetAddress ipAddress = InetAddress.getByName(ip);
103            // 判断是 ipv4 还是 ipv6
104            if (ipAddress instanceof java.net.Inet4Address) {
105                String ipInfo = searcher.search(ip);
106                if (!StringUtils.isEmpty(ipInfo)) {
107                    return ipInfo.replace("|0", "").replace("0|", "").replace("|", "");
108                }
109            } else if (ipAddress instanceof java.net.Inet6Address) {
110                CityResponse response = dbReader.city(ipAddress);
111                AsnResponse asnResponse = asnReader.asn(ipAddress);
112                String isp = asnResponse.getAutonomousSystemOrganization();
113                if (StringUtils.isBlank(isp)) {
114                    isp = "未知运营商";
115                } else if (isp.startsWith("China Telecom")) {
116                    isp = "电信";
117                } else if (isp.startsWith("China Unicom")) {
118                    isp = "联通";
119                } else if (isp.startsWith("China Mobile")) {
120                    isp = "移动";
121                } else if (isp.startsWith("CERNET2 IX")) {
122                    isp = "教育网";
123                }
124                return response.getCountry().getNames().get("zh-CN") + response.getMostSpecificSubdivision().getNames().get("zh-CN") +
125                        response.getCity().getNames().get("zh-CN") + isp;
126            }
127        } catch (Exception e) {
128            logger.error("解析IP信息失败: {}", e.getMessage());
129        }
130        return null;
131    }
132}

还有一件事情哦,如果你使用 nginx 或 docker 部署,很可能会出现拿不到 ip 的情况,比如 nginx 中,我们要转发好访客的 ip

CONF
1location /api/v1 {
2        rewrite ^/api/v1/(.*)$ /$1 break;
3        proxy_pass http://127.0.0.1:8080;
4        proxy_set_header Host $http_host;
5        proxy_set_header X-Real-IP $remote_addr;
6        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
7        proxy_set_header X-Forwarded-Proto $scheme;
8    }

评论效果

全站更新通知的实现

这个是我最近加的一点点小功能,希望给站点加一点活力吧,毕竟我这都没几个人看 (让我难受的很啊)

双向通知当然是 websocket 的能力,而我们加到 socket 连接之后,如何告诉其他组件呢?

对了!可以使用 MessageChannel,可以看下 MDN 的文档

这是浏览器的一个原生能力,我们在两个端口 port1 port2 之间可以实现消息的传递,通过在一个端口发送消息,一个端口监听消息,我们就可以实现很多功能啦(有点像 electron 的 ipc,node 的 emitter)

我们就先创建一个公共实例:

TYPESCRIPT
1// frontend/src/utils/channel.ts
2const channel = new MessageChannel();
3export default channel;

在socket收到事件时候:

TYPESCRIPT
1// 用于发送更新通知
2        newSocket.on("updateNotification", (content) => {
3            console.log(content);
4            channel.port2.postMessage({
5                content: content,
6                publishAt: new Date().toISOString(),
7            });
8        });

另一面便可以处理啦:

TYPESCRIPT
1useEffect(() => {
2    // 这里是为了消息定时关闭
3        let timer: NodeJS.Timeout;
4        channel.port1.onmessage = (event) => {
5            const res = event.data;
6            if (res) {
7                setNotification(res);
8                setShow(true);
9                timer = setTimeout(() => setShow(false), 10000);
10            }
11        };
12
13        return () => clearTimeout(timer);
14    }, []);

通知效果

上次更新于: 1/22/2025, 1:13:21 AM
COMMENT 7287508864545722368

发表评论

登录之后评论体验更好哦 ~
支持 Markdown 语法 0 / 3000

在风雨飘摇之中

本站已运行了

一路走来,感谢陪伴与支持

愿我们不负热爱,继续前行

全站通知
更新通知