All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dromara.jpom.controller.system.NginxController Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Code Technology Studio
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.dromara.jpom.controller.system;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharPool;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.github.odiszapc.nginxparser.NgxBlock;
import com.github.odiszapc.nginxparser.NgxConfig;
import com.github.odiszapc.nginxparser.NgxEntry;
import com.github.odiszapc.nginxparser.NgxParam;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.commander.AbstractSystemCommander;
import org.dromara.jpom.common.validator.ValidatorItem;
import org.dromara.jpom.common.validator.ValidatorRule;
import org.dromara.jpom.model.data.AgentWhitelist;
import org.dromara.jpom.service.WhitelistDirectoryService;
import org.dromara.jpom.service.system.NginxService;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.StringUtil;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;

/**
 * nginx 列表
 *
 * @author bwcx_jzy
 * @since 2019/4/17
 */
@RestController
@RequestMapping("/system/nginx")
@Slf4j
public class NginxController extends BaseAgentController {

    private final NginxService nginxService;
    private final WhitelistDirectoryService whitelistDirectoryService;

    public NginxController(NginxService nginxService,
                           WhitelistDirectoryService whitelistDirectoryService) {
        this.nginxService = nginxService;
        this.whitelistDirectoryService = whitelistDirectoryService;
    }

    /**
     * 配置列表
     *
     * @return json
     */
    @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage> list(String whitePath, String name, String showAll) {
        boolean checkNgxDirectory = whitelistDirectoryService.checkNgxDirectory(whitePath);
        Assert.state(checkNgxDirectory, "文件路径错误,非白名单路径");
        if (StrUtil.isEmpty(name)) {
            name = StrUtil.SLASH;
        }
        List array = nginxService.list(whitePath, name, showAll);
        return JsonMessage.success("", array);
    }

    /**
     * nginx列表
     */
    @RequestMapping(value = "tree.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage tree() {
        JSONArray array = nginxService.tree();
        return JsonMessage.success("", array);
    }

    /**
     * 获取配置文件信息页面
     *
     * @param path 白名单路径
     * @param name 名称
     * @return 页面
     */
    @RequestMapping(value = "item_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage itemData(String path, String name) {
        boolean ngxDirectory = whitelistDirectoryService.checkNgxDirectory(path);
        Assert.state(ngxDirectory, "文件路径错误,非白名单路径");
        String realPath = AgentWhitelist.convertRealPath(path);
        File file = FileUtil.file(realPath, name);
        Assert.state(FileUtil.isFile(file), "对应对配置文件不存在");
        JSONObject jsonObject = new JSONObject();
        String string = FileUtil.readUtf8String(file);
        jsonObject.put("context", string);
        String rName = StringUtil.delStartPath(file, realPath, true);
        // nginxService.paresName(path, file.getAbsolutePath())
        jsonObject.put("name", rName);
        jsonObject.put("whitePath", path);
        return JsonMessage.success("", jsonObject);
//            setAttribute("data", jsonObject);
    }

    private void checkName(String name) {
        Assert.hasText(name, "请填写文件名");
        Assert.state(name.endsWith(".conf"), "文件后缀必须为\".conf\"");
    }

    /**
     * 新增或修改配置
     *
     * @param name      文件名
     * @param whitePath 白名单路径
     * @param genre     操作类型
     * @return json
     */
    @RequestMapping(value = "updateNgx", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage updateNgx(String name, String whitePath, String genre, @ValidatorItem(msg = "请填写配置信息") String context) {
        this.checkName(name);
        //
        boolean ngxDirectory = whitelistDirectoryService.checkNgxDirectory(whitePath);
        Assert.state(ngxDirectory, "请选择正确的白名单");
        whitePath = AgentWhitelist.convertRealPath(whitePath);
        //nginx文件
        File file = FileUtil.file(whitePath, name);
        if ("add".equals(genre) && file.exists()) {
            return new JsonMessage<>(400, "该文件已存在");
        }

        InputStream inputStream = new ByteArrayInputStream(context.getBytes());
        try {
            NgxConfig conf = NgxConfig.read(inputStream);
            List list = conf.findAll(NgxBlock.class, "server");
            // 取消 nginx 内容必须检测 @jzy 2021-09-11
            //            if (list == null || list.size() <= 0) {
            //                return JsonMessage.getString(404, "内容解析为空");
            //            }
            for (NgxEntry ngxEntry : list) {
                NgxBlock ngxBlock = (NgxBlock) ngxEntry;
                // 检查日志路径
                NgxParam accessLog = ngxBlock.findParam("access_log");
                if (accessLog != null) {
                    FileUtil.mkParentDirs(accessLog.getValue());
                }
                accessLog = ngxBlock.findParam("error_log");
                if (accessLog != null) {
                    FileUtil.mkParentDirs(accessLog.getValue());
                }
                // 检查证书文件
                NgxParam sslCertificate = ngxBlock.findParam("ssl_certificate");
                if (sslCertificate != null && !FileUtil.exist(sslCertificate.getValue())) {
                    return new JsonMessage<>(404, "证书文件ssl_certificate,不存在");
                }
                NgxParam sslCertificateKey = ngxBlock.findParam("ssl_certificate_key");
                if (sslCertificateKey != null && !FileUtil.exist(sslCertificateKey.getValue())) {
                    return new JsonMessage<>(404, "证书文件ssl_certificate_key,不存在");
                }
                if (!checkRootRole(ngxBlock)) {
                    return new JsonMessage<>(405, "非系统管理员,不能配置静态资源代理");
                }
            }
        } catch (IOException e) {
            log.error("解析失败", e);
            return new JsonMessage<>(500, "解析失败");
        }

        FileUtil.writeString(context, file, CharsetUtil.UTF_8);

        String msg = this.reloadNginx();
        return JsonMessage.success("提交成功" + msg);
    }

    private String reloadNginx() {
        String serviceName = nginxService.getServiceName();
        try {
            String format = StrUtil.format("{} -s reload", serviceName);
            String nginxPath = this.getNginxPath();
            String msg = StrUtil.isEmpty(nginxPath) ? CommandUtil.execSystemCommand(format) : CommandUtil.execSystemCommand(format, FileUtil.file(nginxPath));
            if (StrUtil.isNotEmpty(msg)) {
                log.info(msg);
                return "(" + msg + ")";
            }
        } catch (Exception e) {
            log.error("reload nginx error", e);
        }
        return StrUtil.EMPTY;
    }

    /**
     * 权限检查 防止非系统管理员配置静态资源访问
     *
     * @param ngxBlock 代码片段
     * @return false 不正确
     */
    private boolean checkRootRole(NgxBlock ngxBlock) {
//        UserModel userModel = getUser();
        //        List locationAll = ngxBlock.findAll(NgxBlock.class, "location");
        //        if (locationAll != null) {
        //            for (NgxEntry ngxEntry1 : locationAll) {
        //                NgxBlock ngxBlock1 = (NgxBlock) ngxEntry1;
        //                NgxParam locationMain = ngxBlock1.findParam("root");
        //                if (locationMain == null) {
        //                    locationMain = ngxBlock1.findParam("alias");
        //                }
        //
        //            }
        //        }
        return true;
    }

    /**
     * 删除配置
     *
     * @param path 文件路径
     * @param name 文件名
     * @return json
     */
    @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage delete(String path, String name, String type, String from) {
        if (!whitelistDirectoryService.checkNgxDirectory(path)) {
            return new JsonMessage<>(400, "非法操作");
        }
        Assert.hasText(name, "请填写文件名");
        if (StrUtil.equals(from, "back")) {
            Assert.state(name.endsWith(".conf_back"), "不能操作此文件");
        } else {
            Assert.state(name.endsWith(".conf"), "文件后缀必须为\".conf\"");
        }
        path = AgentWhitelist.convertRealPath(path);
        File file = FileUtil.file(path, name);
        if (StrUtil.equals(type, "real")) {
            // 真删除
            FileUtil.del(file);
        } else {
            try {
                if (StrUtil.equals(from, "back")) {
                    // 恢复  可能出现 所以 copy Read-only file system
                    File back = FileUtil.file(path, StrUtil.removeSuffix(name, "_back"));
                    FileUtil.copy(file, back, true);
                    FileUtil.del(file);
                } else {
                    // 假删除
                    FileUtil.rename(file, file.getName() + "_back", false, true);
                }
            } catch (Exception e) {
                log.error("删除nginx", e);
                return new JsonMessage<>(400, "操作失败:" + e.getMessage());
            }
        }
        String msg = this.reloadNginx();
        return JsonMessage.success("删除成功" + msg);
    }

    /**
     * 获取nginx状态
     *
     * @return json
     */
    @RequestMapping(value = "status", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage status() {
        String name = nginxService.getServiceName();
        if (StrUtil.isEmpty(name)) {
            return new JsonMessage<>(500, "服务名错误");
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", name);
        boolean serviceStatus = AbstractSystemCommander.getInstance().getServiceStatus(name);
        jsonObject.put("status", serviceStatus);
        return JsonMessage.success("", jsonObject);
    }

    /**
     * 修改nginx配置
     *
     * @param name 服务名
     * @return json
     */
    @RequestMapping(value = "updateConf", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage updateConf(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "服务名称错误") String name) {
        JSONObject ngxConf = nginxService.getNgxConf();
        ngxConf.put("name", name);
        nginxService.save(ngxConf);
        return JsonMessage.success("修改成功");
    }

    /**
     * 获取配置信息
     *
     * @return json
     */
    @RequestMapping(value = "config", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage config() {
        JSONObject ngxConf = nginxService.getNgxConf();
        return JsonMessage.success("", ngxConf);
    }

    /**
     * 启动nginx
     *
     * @return json
     */
    @RequestMapping(value = "open", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage open() {
        String name = nginxService.getServiceName();
        String result = AbstractSystemCommander.getInstance().startService(name);
        return JsonMessage.success("nginx服务已启动 " + result);
    }

    /**
     * 关闭nginx
     *
     * @return json
     */
    @RequestMapping(value = "close", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage close() {
        String name = nginxService.getServiceName();
        String result = AbstractSystemCommander.getInstance().stopService(name);
        return JsonMessage.success("nginx服务已停止 " + result);
    }

    private String findServerPath(String name) {
        String checkResult = CommandUtil.execSystemCommand("sc qc " + name);
        List strings = StrSplitter.splitTrim(checkResult, "\n", true);
        //服务路径
        for (String str : strings) {
            str = str.toUpperCase().trim();
            if (str.startsWith("BINARY_PATH_NAME")) {
                String path = str.substring(str.indexOf(CharPool.COLON) + 1).replace("\"", "").trim();
                //file = FileUtil.file(path).getParentFile();
                if (FileUtil.exist(path)) {
                    return path;
                }
            }
        }
        return null;
    }

    private String getNginxPath() {
        AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
        return Optional.ofNullable(whitelist)
            .map(AgentWhitelist::getNginxPath)
            .orElseGet(() -> {
                if (SystemUtil.getOsInfo().isWindows()) {
                    String name = nginxService.getServiceName();
                    String path = this.findServerPath(name);
                    if (path != null) {
                        return FileUtil.file(path).getParentFile().getAbsolutePath();
                    }
                }
                return null;
            });
    }

    private String checkNginx() {
        String nginxPath = this.getNginxPath();
        String name = nginxService.getServiceName();
        String format = StrUtil.format("{} -t", name);
        return StrUtil.isEmpty(nginxPath) ? CommandUtil.execSystemCommand(format) : CommandUtil.execSystemCommand(format, FileUtil.file(nginxPath));
    }

    /**
     * 重新加载
     *
     * @return json
     */
    @RequestMapping(value = "reload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
    public IJsonMessage reload() {
        String checkResult = this.checkNginx();
        if (StrUtil.isNotEmpty(checkResult) && !StrUtil.containsIgnoreCase(checkResult, "successful")) {
            return new JsonMessage<>(400, checkResult);
        }
        String reloadMsg = this.reloadNginx();
        return JsonMessage.success("重新加载成功:" + reloadMsg, checkResult);
    }
}