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.FirewallCountryRule;
|
import cc.mrbird.febs.firewall.entity.FirewallSite;
|
import cc.mrbird.febs.firewall.mapper.FirewallCountryRuleMapper;
|
import cc.mrbird.febs.firewall.mapper.FirewallSiteMapper;
|
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 FirewallSiteMapper siteMapper;
|
private final NginxConfigGenerator generator;
|
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));
|
// 删除Nginx配置文件
|
if (StrUtil.isNotBlank(site.getNginxConf())) {
|
cn.hutool.core.io.FileUtil.del(site.getNginxConf());
|
}
|
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);
|
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);
|
|
// 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);
|
redisUtils.lSet(NGINX_RELOAD_QUEUE_KEY, String.valueOf(siteId));
|
log.info("手动触发 Nginx reload: siteId={}", siteId);
|
}
|
}
|