feat(novel-front): 增加评论用户地理位置显示功能

This commit is contained in:
xiongxiaoyang
2025-06-30 20:51:29 +08:00
parent efb136e3be
commit 4693c7ffae
16 changed files with 222 additions and 12 deletions

View File

@ -61,6 +61,12 @@
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
<build>

View File

@ -2,12 +2,14 @@ package com.java2nb.novel.controller;
import com.java2nb.novel.core.bean.UserDetails;
import com.java2nb.novel.core.enums.ResponseStatus;
import com.java2nb.novel.core.utils.IpUtil;
import com.java2nb.novel.entity.Book;
import com.java2nb.novel.entity.BookCategory;
import com.java2nb.novel.entity.BookComment;
import com.java2nb.novel.entity.BookIndex;
import com.java2nb.novel.service.BookContentService;
import com.java2nb.novel.service.BookService;
import com.java2nb.novel.service.IpLocationService;
import com.java2nb.novel.vo.BookCommentVO;
import com.java2nb.novel.vo.BookSettingVO;
import com.java2nb.novel.vo.BookSpVO;
@ -37,6 +39,8 @@ public class BookController extends BaseController {
private final Map<String, BookContentService> bookContentServiceMap;
private final IpLocationService ipLocationService;
/**
* 查询首页小说设置列表数据
*/
@ -158,6 +162,7 @@ public class BookController extends BaseController {
if (userDetails == null) {
return RestResult.fail(ResponseStatus.NO_LOGIN);
}
comment.setLocation(ipLocationService.getLocation(IpUtil.getRealIp(request)));
bookService.addBookComment(userDetails.getId(), comment);
return RestResult.ok();
}

View File

@ -0,0 +1,55 @@
package com.java2nb.novel.core.config;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* IP 地址定位配置类
*
* @author xiongxiaoyang
* @date 2025/6/30
*/
@Slf4j
@Configuration
public class IpLocationConfig {
/**
* 使用 {@link Searcher} 实现高效的本地 IP 查询服务, 创建基于内存的 IP 地址查询对象,支持并发访问且仅需初始化一次。
*
* <p>该方法会将 ip2region.xdb 数据库文件加载到内存中,
* 并构建一个线程安全的 {@link Searcher} 实例,可用于高效、并发的 IP 地址定位查询。</p>
*
* <p>{@link Searcher} 实例是线程安全的,可以作为全局单例在整个应用中跨线程使用。</p>
*
* <p>通过配置 destroyMethod="close",确保在 Spring 容器关闭时自动释放底层资源。</p>
*/
@Bean(destroyMethod = "close")
public Searcher searcher() throws IOException {
// 1、从 classpath 加载整个 xdb 到内存。
try (InputStream inputStream = new ClassPathResource("ip2region.xdb").getInputStream()) {
File tempDbFile = File.createTempFile("ip2region", ".xdb");
try (FileOutputStream outputStream = new FileOutputStream(tempDbFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
// 确保程序退出时删除临时文件
tempDbFile.deleteOnExit();
byte[] cBuff = Searcher.loadContentFromFile(tempDbFile.getPath());
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
return Searcher.newWithBuffer(cBuff);
}
}
}

View File

@ -0,0 +1,24 @@
package com.java2nb.novel.service;
/**
* IP 地址定位服务类
*
* <p>该服务用于实现 IP 地址到地理位置的查询功能,
* 包括国家、省份、城市等信息。</p>
*
* <p>此类设计为 Spring 管理的 Service Bean支持在 Controller 或其他 Service 中注入使用。</p>
*
* @author xiongxiaoyang
* @date 2025/6/30
*/
public interface IpLocationService {
/**
* 根据 IP 地址查询地理位置信息
*
* @param ip 待查询的 IP 地址IPv4
* @return 如果是中国 IP返回省份否则返回国家
*/
String getLocation(String ip);
}

View File

@ -0,0 +1,56 @@
package com.java2nb.novel.service.impl;
import com.java2nb.novel.core.utils.IpUtil;
import com.java2nb.novel.service.IpLocationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* IpLocationService 实现类
*
* @author xiongxiaoyang
* @date 2025/6/30
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class IpLocationServiceImpl implements IpLocationService {
private final Searcher searcher;
@Override
public String getLocation(String ip) {
try {
// 示例返回:"中国|0|湖北省|武汉市|电信"
String region = searcher.search(ip);
String[] regions = region.split("\\|");
if (regions.length > 0) {
// 国家
String country = regions[0];
if ("0".equals(country)) {
// 内网IP直接获取本机公网IP
String publicIp = IpUtil.getPublicIP();
log.info("内网IP{}本机公网IP{}", ip, publicIp);
if (StringUtils.hasText(publicIp)) {
return getLocation(publicIp);
}
} else if ("中国".equals(country)) {
// 是中国,则返回省份(第三个字段)
String province = regions.length > 2 ? regions[2] : "未知地区";
// 去掉最后一个“省”字
return province.endsWith("") ? province.substring(0, province.length() - 1) : province;
} else {
// 非中国,返回国家名
return country;
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return "未知地区";
}
}

Binary file not shown.

View File

@ -4,7 +4,7 @@
<mapper namespace="com.java2nb.novel.mapper.FrontBookCommentMapper">
<select id="listCommentByPage" resultType="com.java2nb.novel.vo.BookCommentVO">
select t1.id,t1.book_id,t1.comment_content,t1.reply_count,t1.create_time,t2.username create_user_name,t2.user_photo create_user_photo
select t1.id,t1.book_id,t1.comment_content,t1.location,t1.reply_count,t1.create_time,t2.username create_user_name,t2.user_photo create_user_photo
from book_comment t1 inner join user t2 on t1.create_user_id = t2.id
<trim>
<if test="bookId != null">

View File

@ -135,7 +135,7 @@
"<div class=\"user_heads fl\" vals=\"389\">" +
"<img src=\""+(comment.createUserPhoto ? comment.createUserPhoto : '/images/man.png')+"\" class=\"user_head\" alt=\"\">" +
"<span class=\"user_level1\" style=\"display: none;\">见习</span></div>" +
"<ul class=\"pl_bar fr\">\t\t\t<li class=\"name\">"+(comment.createUserName.substr(0, 4) + "****" + comment.createUserName.substr(comment.createUserName.length - 3, 3))+"</li><li class=\"dec\">" +
"<ul class=\"pl_bar fr\">\t\t\t<li class=\"name\">"+(comment.createUserName)+"<span style='padding-left: 10px' class=\"other\">"+(comment.location ? comment.location + "读者" : '')+"</span></li><li class=\"dec\">" +
comment.commentContent+
"</li><li class=\"other cf\">" +
"<span class=\"time fl\">"+comment.createTime+"</span>" +

View File

@ -95,7 +95,7 @@
<span class="block">暂无评论</span>
</div>
<div class="commentBar" id="commentPanel" th:style="${bookCommentPageBean.total == 0}? 'display:none'">
<div th:each="comment: ${bookCommentPageBean.list}" class="comment_list cf"><div class="user_heads fl" vals="389"><img th:src="${comment.createUserPhoto}?${comment.createUserPhoto}:'/images/man.png'" class="user_head" alt=""><span class="user_level1" style="display: none;">见习</span></div><ul class="pl_bar fr"> <li class="name" th:text="${#strings.substring(comment.createUserName,0,4)}+'****'+${#strings.substring(comment.createUserName,#strings.length(comment.createUserName)-3,#strings.length(comment.createUserName))}"></li><li class="dec" th:utext="${comment.commentContent}"></li><li class="other cf"><span class="time fl" th:text="${#calendars.format(comment.createTime, 'yyyy-MM-dd HH:mm:ss')}"></span><span class="fr"><a href="javascript:void(0);" onclick="javascript:BookDetail.AddAgreeTotal(77,this);" class="zan" style="display: none;">赞<i class="num">(0)</i></a></span></li> </ul> </div>
<div th:each="comment: ${bookCommentPageBean.list}" class="comment_list cf"><div class="user_heads fl" vals="389"><img th:src="${comment.createUserPhoto}?${comment.createUserPhoto}:'/images/man.png'" class="user_head" alt=""><span class="user_level1" style="display: none;">见习</span></div><ul class="pl_bar fr"> <li class="name"><span th:text="${#strings.substring(comment.createUserName,0,4)}+'****'+${#strings.substring(comment.createUserName,#strings.length(comment.createUserName)-3,#strings.length(comment.createUserName))}"></span><span style="padding-left: 10px" class="other" th:if="${comment.location}" th:text="${comment.location} + '读者'"></span></span></li><li class="dec" th:utext="${comment.commentContent}"></li><li class="other cf"><span class="time fl" th:text="${#calendars.format(comment.createTime, 'yyyy-MM-dd HH:mm:ss')}"></span><span class="fr"><a href="javascript:void(0);" onclick="javascript:BookDetail.AddAgreeTotal(77,this);" class="zan" style="display: none;">赞<i class="num">(0)</i></a></span></li> </ul> </div>
</div>
@ -319,7 +319,7 @@
"<div class=\"user_heads fl\" vals=\"389\">" +
"<img src=\""+(comment.createUserPhoto ? comment.createUserPhoto : '/images/man.png')+"\" class=\"user_head\" alt=\"\">" +
"<span class=\"user_level1\" style=\"display: none;\">见习</span></div>" +
"<ul class=\"pl_bar fr\">\t\t\t<li class=\"name\">"+(comment.createUserName)+"</li><li class=\"dec\">" +
"<ul class=\"pl_bar fr\">\t\t\t<li class=\"name\">"+(comment.createUserName)+"<span style='padding-left: 10px' class=\"other\">"+(comment.location ? comment.location + "读者" : '')+"</span></li><li class=\"dec\">" +
comment.commentContent+
"</li><li class=\"other cf\">" +
"<span class=\"time fl\">"+comment.createTime+"</span>" +