From aeb0696f457ca85b62d15ab6586c5779cf5cd97b Mon Sep 17 00:00:00 2001
From: Administrator <15274802129@163.com>
Date: Fri, 31 Jul 2026 16:56:21 +0800
Subject: [PATCH] feat(firewall): 添加访问日志功能
---
src/main/java/cc/mrbird/febs/firewall/service/impl/FirewallRuleServiceImpl.java | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 323 insertions(+), 0 deletions(-)
diff --git a/src/main/java/cc/mrbird/febs/firewall/service/impl/FirewallRuleServiceImpl.java b/src/main/java/cc/mrbird/febs/firewall/service/impl/FirewallRuleServiceImpl.java
new file mode 100644
index 0000000..af37bcf
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/firewall/service/impl/FirewallRuleServiceImpl.java
@@ -0,0 +1,323 @@
+package cc.mrbird.febs.firewall.service.impl;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.entity.QueryRequest;
+import cc.mrbird.febs.common.utils.RedisUtils;
+import cc.mrbird.febs.firewall.dto.CountryVo;
+import cc.mrbird.febs.firewall.entity.FirewallAccessLog;
+import cc.mrbird.febs.firewall.entity.FirewallCountryRule;
+import cc.mrbird.febs.firewall.entity.FirewallIpWhite;
+import cc.mrbird.febs.firewall.entity.FirewallSite;
+import cc.mrbird.febs.firewall.mapper.FirewallAccessLogMapper;
+import cc.mrbird.febs.firewall.mapper.FirewallCountryRuleMapper;
+import cc.mrbird.febs.firewall.mapper.FirewallIpWhiteMapper;
+import cc.mrbird.febs.firewall.mapper.FirewallSiteMapper;
+import cc.mrbird.febs.firewall.nginx.IpWhitelistNginxConfigGenerator;
+import cc.mrbird.febs.firewall.nginx.NginxConfigGenerator;
+import cc.mrbird.febs.firewall.service.IFirewallRuleService;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 防火墙规则 Service 实现
+ *
+ * @author auto-generated
+ * @date 2026-07-31
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class FirewallRuleServiceImpl implements IFirewallRuleService {
+
+ private final FirewallCountryRuleMapper countryRuleMapper;
+ private final FirewallIpWhiteMapper ipWhiteMapper;
+ private final FirewallAccessLogMapper accessLogMapper;
+ private final FirewallSiteMapper siteMapper;
+ private final NginxConfigGenerator generator;
+ private final IpWhitelistNginxConfigGenerator ipWhitelistGenerator;
+ private final RedisUtils redisUtils;
+
+ /** Redis 中 Nginx reload 任务队列 key */
+ private static final String NGINX_RELOAD_QUEUE_KEY = "firewall:nginx_reload_queue";
+
+ // ==================== 站点管理 ====================
+
+ @Override
+ public IPage<FirewallSite> siteList(FirewallSite site, QueryRequest request) {
+ Page<FirewallSite> page = new Page<>(request.getPageNum(), request.getPageSize());
+ LambdaQueryWrapper<FirewallSite> wrapper = new LambdaQueryWrapper<FirewallSite>()
+ .like(StrUtil.isNotBlank(site.getSiteName()), FirewallSite::getSiteName, site.getSiteName())
+ .like(StrUtil.isNotBlank(site.getDomain()), FirewallSite::getDomain, site.getDomain())
+ .eq(site.getStatus() != null, FirewallSite::getStatus, site.getStatus())
+ .orderByDesc(FirewallSite::getCreatedTime);
+ return siteMapper.selectPage(page, wrapper);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public FebsResponse saveSite(FirewallSite site) {
+ if (site.getId() != null) {
+ // 更新
+ FirewallSite exist = siteMapper.selectById(site.getId());
+ if (exist == null) {
+ return new FebsResponse().fail().message("站点不存在");
+ }
+ siteMapper.updateById(site);
+ log.info("防火墙站点已更新: id={}, name={}", site.getId(), site.getSiteName());
+ } else {
+ // 新增
+ siteMapper.insert(site);
+ // 回填 nginxConf(如果没填则用默认值)
+ if (StrUtil.isBlank(site.getNginxConf())) {
+ site.setNginxConf("/etc/nginx/conf.d/firewall_country_" + site.getId() + ".conf");
+ siteMapper.updateById(site);
+ }
+ log.info("防火墙站点已新增: id={}, name={}", site.getId(), site.getSiteName());
+ }
+ return new FebsResponse().success().message("保存成功");
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public FebsResponse deleteSite(Long id) {
+ FirewallSite site = siteMapper.selectById(id);
+ if (site == null) {
+ return new FebsResponse().fail().message("站点不存在");
+ }
+ // 删除站点关联的国家规则
+ countryRuleMapper.delete(new LambdaQueryWrapper<FirewallCountryRule>()
+ .eq(FirewallCountryRule::getSiteId, id));
+ // 删除站点关联的IP白名单
+ ipWhiteMapper.delete(new LambdaQueryWrapper<FirewallIpWhite>()
+ .eq(FirewallIpWhite::getSiteId, id));
+ // 删除Nginx配置文件(国家黑名单 + IP白名单)
+ if (StrUtil.isNotBlank(site.getNginxConf())) {
+ cn.hutool.core.io.FileUtil.del(site.getNginxConf());
+ }
+ cn.hutool.core.io.FileUtil.del(IpWhitelistNginxConfigGenerator.getConfPath(id));
+ siteMapper.deleteById(id);
+ log.info("防火墙站点已删除: id={}, name={}", id, site.getSiteName());
+ return new FebsResponse().success().message("删除成功");
+ }
+
+ // ==================== 国家规则管理 ====================
+
+ @Override
+ public IPage<FirewallCountryRule> countryRuleList(FirewallCountryRule rule, QueryRequest request) {
+ Page<FirewallCountryRule> page = new Page<>(request.getPageNum(), request.getPageSize());
+ LambdaQueryWrapper<FirewallCountryRule> wrapper = new LambdaQueryWrapper<FirewallCountryRule>()
+ .eq(rule.getSiteId() != null, FirewallCountryRule::getSiteId, rule.getSiteId())
+ .eq(rule.getEnabled() != null, FirewallCountryRule::getEnabled, rule.getEnabled())
+ .like(StrUtil.isNotBlank(rule.getCountryName()), FirewallCountryRule::getCountryName, rule.getCountryName())
+ .orderByDesc(FirewallCountryRule::getCreatedTime);
+ return countryRuleMapper.selectPage(page, wrapper);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public FebsResponse saveCountryRuleSingle(FirewallCountryRule rule) {
+ if (rule.getSiteId() == null) {
+ return new FebsResponse().fail().message("请选择站点");
+ }
+ if (StrUtil.isBlank(rule.getCountryCode())) {
+ return new FebsResponse().fail().message("国家编码不能为空");
+ }
+ rule.setCountryCode(rule.getCountryCode().toUpperCase());
+
+ if (rule.getId() != null) {
+ countryRuleMapper.updateById(rule);
+ log.info("防火墙国家规则已更新: id={}, siteId={}, country={}", rule.getId(), rule.getSiteId(), rule.getCountryCode());
+ } else {
+ // 检查同站点下是否已存在相同国家编码
+ Integer count = countryRuleMapper.selectCount(new LambdaQueryWrapper<FirewallCountryRule>()
+ .eq(FirewallCountryRule::getSiteId, rule.getSiteId())
+ .eq(FirewallCountryRule::getCountryCode, rule.getCountryCode()));
+ if (count > 0) {
+ return new FebsResponse().fail().message("该站点下已存在相同国家编码: " + rule.getCountryCode());
+ }
+ countryRuleMapper.insert(rule);
+ log.info("防火墙国家规则已新增: siteId={}, country={}", rule.getSiteId(), rule.getCountryCode());
+ }
+
+ // 保存后自动生成Nginx配置并发送reload信号
+ regenerateNginxConfig(rule.getSiteId());
+ return new FebsResponse().success().message("保存成功,Nginx 配置已更新");
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public FebsResponse deleteCountryRule(Long id) {
+ FirewallCountryRule rule = countryRuleMapper.selectById(id);
+ if (rule == null) {
+ return new FebsResponse().fail().message("规则不存在");
+ }
+ Long siteId = rule.getSiteId();
+ countryRuleMapper.deleteById(id);
+ log.info("防火墙国家规则已删除: id={}, siteId={}, country={}", id, siteId, rule.getCountryCode());
+
+ // 删除后自动生成Nginx配置并发送reload信号
+ regenerateNginxConfig(siteId);
+ return new FebsResponse().success().message("删除成功,Nginx 配置已更新");
+ }
+
+ // ==================== 内部方法 ====================
+
+ /** 重新生成Nginx配置并发送reload信号 */
+ private void regenerateNginxConfig(Long siteId) {
+ try {
+ generator.generate(siteId);
+ ipWhitelistGenerator.generate(siteId);
+ redisUtils.lSet(NGINX_RELOAD_QUEUE_KEY, String.valueOf(siteId));
+ log.info("Nginx reload 信号已入队: siteId={}", siteId);
+ } catch (Exception e) {
+ log.error("Nginx 配置生成失败: siteId={}", siteId, e);
+ }
+ }
+
+ // ==================== 原有方法 ====================
+
+ @Override
+ public List<CountryVo> getCountries(Long siteId) {
+ List<FirewallCountryRule> rules = countryRuleMapper.selectList(
+ new LambdaQueryWrapper<FirewallCountryRule>()
+ .eq(FirewallCountryRule::getSiteId, siteId)
+ .eq(FirewallCountryRule::getEnabled, 1)
+ );
+
+ List<CountryVo> result = new ArrayList<>();
+ for (FirewallCountryRule rule : rules) {
+ CountryVo vo = new CountryVo();
+ vo.setCode(rule.getCountryCode());
+ vo.setName(rule.getCountryName());
+ result.add(vo);
+ }
+ return result;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void saveCountryRule(Long siteId, List<String> countries) {
+ // 1. 删除旧规则
+ countryRuleMapper.delete(new LambdaQueryWrapper<FirewallCountryRule>()
+ .eq(FirewallCountryRule::getSiteId, siteId));
+
+ // 2. 批量插入新规则
+ for (String country : countries) {
+ FirewallCountryRule rule = new FirewallCountryRule();
+ rule.setSiteId(siteId);
+ rule.setCountryCode(country.toUpperCase());
+ rule.setCountryName(country);
+ rule.setEnabled(1);
+ countryRuleMapper.insert(rule);
+ }
+
+ log.info("防火墙国家规则已更新: siteId={}, countries={}", siteId, countries);
+
+ // 3. 生成 Nginx 配置文件
+ generator.generate(siteId);
+ ipWhitelistGenerator.generate(siteId);
+
+ // 4. 发送 reload 信号
+ redisUtils.lSet(NGINX_RELOAD_QUEUE_KEY, String.valueOf(siteId));
+ log.info("Nginx reload 信号已入队: siteId={}", siteId);
+ }
+
+ @Override
+ public void triggerNginxReload(Long siteId) {
+ generator.generate(siteId);
+ ipWhitelistGenerator.generate(siteId);
+ redisUtils.lSet(NGINX_RELOAD_QUEUE_KEY, String.valueOf(siteId));
+ log.info("手动触发 Nginx reload: siteId={}", siteId);
+ }
+
+ // ==================== IP白名单管理 ====================
+
+ @Override
+ public IPage<FirewallIpWhite> ipWhiteList(FirewallIpWhite ipWhite, QueryRequest request) {
+ Page<FirewallIpWhite> page = new Page<>(request.getPageNum(), request.getPageSize());
+ LambdaQueryWrapper<FirewallIpWhite> wrapper = new LambdaQueryWrapper<FirewallIpWhite>()
+ .eq(ipWhite.getSiteId() != null, FirewallIpWhite::getSiteId, ipWhite.getSiteId())
+ .eq(ipWhite.getEnabled() != null, FirewallIpWhite::getEnabled, ipWhite.getEnabled())
+ .like(StrUtil.isNotBlank(ipWhite.getIp()), FirewallIpWhite::getIp, ipWhite.getIp())
+ .like(StrUtil.isNotBlank(ipWhite.getRemark()), FirewallIpWhite::getRemark, ipWhite.getRemark())
+ .orderByDesc(FirewallIpWhite::getCreatedTime);
+ return ipWhiteMapper.selectPage(page, wrapper);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public FebsResponse saveIpWhite(FirewallIpWhite ipWhite) {
+ if (ipWhite.getSiteId() == null) {
+ return new FebsResponse().fail().message("请选择站点");
+ }
+ if (StrUtil.isBlank(ipWhite.getIp())) {
+ return new FebsResponse().fail().message("IP地址不能为空");
+ }
+ ipWhite.setIp(ipWhite.getIp().trim());
+
+ if (ipWhite.getId() != null) {
+ ipWhiteMapper.updateById(ipWhite);
+ log.info("防火墙IP白名单已更新: id={}, siteId={}, ip={}", ipWhite.getId(), ipWhite.getSiteId(), ipWhite.getIp());
+ } else {
+ // 检查同站点下是否已存在相同IP
+ Integer count = ipWhiteMapper.selectCount(new LambdaQueryWrapper<FirewallIpWhite>()
+ .eq(FirewallIpWhite::getSiteId, ipWhite.getSiteId())
+ .eq(FirewallIpWhite::getIp, ipWhite.getIp()));
+ if (count > 0) {
+ return new FebsResponse().fail().message("该站点下已存在相同IP: " + ipWhite.getIp());
+ }
+ ipWhiteMapper.insert(ipWhite);
+ log.info("防火墙IP白名单已新增: siteId={}, ip={}", ipWhite.getSiteId(), ipWhite.getIp());
+ }
+
+ // 保存后自动生成Nginx配置并发送reload信号
+ ipWhitelistGenerator.generate(ipWhite.getSiteId());
+ redisUtils.lSet(NGINX_RELOAD_QUEUE_KEY, String.valueOf(ipWhite.getSiteId()));
+ return new FebsResponse().success().message("保存成功,Nginx 配置已更新");
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public FebsResponse deleteIpWhite(Long id) {
+ FirewallIpWhite ipWhite = ipWhiteMapper.selectById(id);
+ if (ipWhite == null) {
+ return new FebsResponse().fail().message("IP白名单不存在");
+ }
+ Long siteId = ipWhite.getSiteId();
+ ipWhiteMapper.deleteById(id);
+ log.info("防火墙IP白名单已删除: id={}, siteId={}, ip={}", id, siteId, ipWhite.getIp());
+
+ // 删除后自动生成Nginx配置并发送reload信号
+ ipWhitelistGenerator.generate(siteId);
+ redisUtils.lSet(NGINX_RELOAD_QUEUE_KEY, String.valueOf(siteId));
+ return new FebsResponse().success().message("删除成功,Nginx 配置已更新");
+ }
+
+ // ==================== 访问日志 ====================
+
+ @Override
+ public void recordAccess(FirewallAccessLog accessLog) {
+ accessLogMapper.insert(accessLog);
+ }
+
+ @Override
+ public IPage<FirewallAccessLog> accessLogList(FirewallAccessLog log, QueryRequest request) {
+ Page<FirewallAccessLog> page = new Page<>(request.getPageNum(), request.getPageSize());
+ LambdaQueryWrapper<FirewallAccessLog> wrapper = new LambdaQueryWrapper<FirewallAccessLog>()
+ .eq(log.getSiteId() != null, FirewallAccessLog::getSiteId, log.getSiteId())
+ .eq(log.getIsBlocked() != null, FirewallAccessLog::getIsBlocked, log.getIsBlocked())
+ .eq(StrUtil.isNotBlank(log.getCountryCode()), FirewallAccessLog::getCountryCode, log.getCountryCode())
+ .like(StrUtil.isNotBlank(log.getIp()), FirewallAccessLog::getIp, log.getIp())
+ .orderByDesc(FirewallAccessLog::getCreatedTime);
+ return accessLogMapper.selectPage(page, wrapper);
+ }
+}
--
Gitblit v1.9.1