Fms-Java版开发实录:17 全部自定义配置都搬到数据库中

Fms-Java版开发实录:17 全部自定义配置都搬到数据库中

所有配置现在都搬到数据库中了,方便管理员进行配置。

编写完管理员设置邮件服务器的功能后,实际上现在只剩两个配置还留在application.properties中了,就是项目的路径以及重置密码链接过期的小时数。这两个东西我们也搬到option中,不过那个链接比较特别,需要从用户初始化的时候获取并写入数据库。

配置过期时间

整体的思路如下:

  1. 项目启动的时候往数据库中写入一条6小时的过期时间配置
  2. 修改使用到EmailProperties的业务层,将其修改为从数据库中取数
  3. 修改前端控制器和页面

项目启动的时候写入过期时间

这个很简单,继续在dataInitialize.sql中添加一行:

INSERT INTO  options(create_time, option_key, option_value, update_time)
VALUES (CURRENT_TIMESTAMP, 'userPwdResetExpireHour', '6', CURRENT_TIMESTAMP)
ON CONFLICT (option_key) DO NOTHING;

然后就是修改用到这个配置的地方,有两处,一个是在EmailSendingService的发送邮件功能中,另外一个是在生成利用这个过期时间写入数据库中。

修改EmailSendingService

这个很简单,就改动一个地方:

.replace("{{interval}}", optionDao.findByOptionKey("userPwdResetExpireHour").getOptionValue())

修改PasswordResetService

用到这个属性的地方在savePath方法中,需要修改成如下:

.plusHours(Long.parseLong(optionDao.findByOptionKey("userPwdResetExpireHour").getOptionValue()))

修改完之后,EmailProperties这个类对于PasswordResetService没有用处了,很快我们要彻底告别EmailProperties了。

控制器与页面

我打算把剩下这些配置都叫做系统配置,路径就是/admin/config。先来编写一个简单的控制器:

// 系统配置页面
@GetMapping("/config")
public String systemConfig(Model model) {
    User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    model.addAttribute("isAdmin", user.hasRole("ADMIN"));
    Option expireTime = optionService.getOptionByKey("userPwdResetExpireHour");
    model.addAttribute("expireTime", expireTime);
    model.addAttribute("updated", false);
    return "/admin/config";
}

这个页面老套路,接下来编写页面:

<main class="container">
    <h2 class="pb-2 py-3">系统设置</h2>
    <hr>
    <div class="row">
        <div class="col-12 col-sm-8 col-md-6 col-lg-4">
            <div th:if="${updated}" class="alert alert-success" role="alert">
                已更新系统配置
            </div>
            <form action="/admin/config" th:action="@{/admin/config}" method="post">
                <label for="expireTime" class="form-label">重置链接有效时间(小时)</label>
                <div class="input-group mb-3">
                    <span class="input-group-text" id="expireTime-addon">1-12小时</span>
                    <input type="number" min="1" max="12" step="1" class="form-control" id="expireTime" name="expireTime"
                           th:value="${expireTime.optionValue}" th:classappend="${timeError}? 'is-invalid'" aria-describedby="expireTime-addon" required>
                    <button class="btn btn-primary" type="submit" id="button-addon2">修改</button>
                    <div id="emailUsernameError" th:if="${timeError}" class="invalid-feedback">时间超出范围</div>
                </div>
            </form>
        </div>
        <div class="col"></div>
    </div>
</main>

页面的套路也是比较简单,根据是否成功来渲染,如果出错就显示错误。现在系统配置不多,如果以后多了,就要搞一个系统配置对象来对应这个界面。

之后是接受POST请求的控制器,如下:

// 更改系统配置
@PostMapping("/config")
public String updateConfig(@RequestParam("expireTime") int expireTime, Model model) {
    User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    model.addAttribute("isAdmin", user.hasRole("ADMIN"));
    Option expireHour = optionService.getOptionByKey("userPwdResetExpireHour");
    if (expireTime < 1 || expireTime > 12) {
        model.addAttribute("timeError", true);
    } else {
        expireHour.setOptionValue(String.valueOf(expireTime));
        optionService.saveOrUpdate(expireHour);
        model.addAttribute("updated", true);
    }
    model.addAttribute("expireTime", expireHour);
    return "/admin/config";
}

控制器依然是先获取用户,然后读取当前配置,之后根据传入参数正确与否,决定显示错误页面还是更新配置,之后再将配置传入页面。这样如果输入错误,页面显示未更新的结果,成功修改则显示修改后的结果。

配置服务器名称和端口

这个路径由于在项目启动的时候无法知道,必须在第一次访问的时候才知道项目的路径,像一些项目都是要让用户输入才可以。
这里我打算在用户初始化的时候,利用HttpServletRequest对象来获取,然后写入到数据库中,不知道行不行,试试吧。

修改初始化管理员的代码

修改写入新建管理员所在的方法adminRegister的代码,在最后添加:

//写入服务器名字和端口号到配置中
Option serverName = new Option();
serverName.setOptionKey("serverName");
serverName.setOptionValue("http://" + request.getServerName());
Option serverPort = new Option();
serverPort.setOptionKey("serverPort");
serverPort.setOptionValue(String.valueOf(request.getServerPort()));
optionService.saveOrUpdate(serverName);
optionService.saveOrUpdate(serverPort);

这实际上是写入了类似于http://xx.xx的地址和端口号到数据库中。

之后在EmailSendingService中修改发送邮件的方法,拼合路径的时候,从数据库中读出上述两个内容,然后进行拼合:

String assembledPath = optionDao.findByOptionKey("serverName").getOptionValue() +
        ':' + optionDao.findByOptionKey("serverPort").getOptionValue() + "/account/reset/" + path;
String message = template
        .replace("{{username}}", username)
        .replace("{{link}}", assembledPath)
        .replace("{{interval}}", optionDao.findByOptionKey("userPwdResetExpireHour").getOptionValue())
        .replace("{{time}}", simpleDateFormat.format(new Date()));

之后在EmailSendingService中,也不再需要EmailProperties了,可以将其删除,至此就彻底告别了EmailProperties类,可以将其删除,application.properties中的那几个配置也都可以删除了。

这样就把所有配置都移动到了数据库中。

修改控制器 - GET请求

控制器继续沿用/admin/config这个控制器,修改的套路与刚才的过期时间一样,只不过我这里每一行都用了一个表单,所以POST控制器会稍微复杂点,GET的比较简单,取出三个配置都渲染到页面里:

// 系统配置页面
@GetMapping("/config")
public String systemConfig(Model model) {
    User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    model.addAttribute("isAdmin", user.hasRole("ADMIN"));
    Option expireTime = optionService.getOptionByKey("userPwdResetExpireHour");
    Option serverName = optionService.getOptionByKey("serverName");
    Option serverPort = optionService.getOptionByKey("serverPort");

    model.addAttribute("expireTime", expireTime);
    model.addAttribute("serverName", serverName);
    model.addAttribute("serverPort", serverPort);
    model.addAttribute("updated", false);
    return "/admin/config";
}

修改控制器 - POST请求

由于使用了三个表单,因此要面对一些属性为null的情况,注意这里要使用包装类,好来判断是不是等于null。本质上也就是依次判断各个参数是否传入即可,反正每次只会传入一个配置属性:

// 更改系统配置
@PostMapping("/config")
public String updateConfig(
        @RequestParam(name = "expireTime",required = false) Integer expireTime,
        @RequestParam(name = "serverName",required = false) String serverName,
        @RequestParam(name = "serverPort",required = false) Integer serverPort,
        Model model) {
    User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    model.addAttribute("isAdmin", user.hasRole("ADMIN"));

    Option expireHour = optionService.getOptionByKey("userPwdResetExpireHour");
    model.addAttribute("expireTime", expireHour);

    Option serverNameFromDatabase = optionService.getOptionByKey("serverName");
    model.addAttribute("serverName", serverNameFromDatabase);

    Option serverPortFromDatabase = optionService.getOptionByKey("serverPort");
    model.addAttribute("serverPort", serverPortFromDatabase);

    if (expireTime != null) {
        if (expireTime < 1 || expireTime > 12) {
            model.addAttribute("timeError", true);
        } else {
            expireHour.setOptionValue(String.valueOf(expireTime));
            optionService.saveOrUpdate(expireHour);
            model.addAttribute("updated", true);
        }
        model.addAttribute("expireTime", expireHour);
        return "/admin/config";
    }

    if (serverName != null) {
        serverNameFromDatabase.setOptionValue(serverName);
        optionService.saveOrUpdate(serverNameFromDatabase);
        model.addAttribute("updated", true);
        model.addAttribute("serverName", serverNameFromDatabase);
        return "/admin/config";
    }

    if (serverPort != null) {
        if (serverPort < 0 || serverPort > 65535) {
            model.addAttribute("serverPortError", true);
        } else {
            serverPortFromDatabase.setOptionValue(String.valueOf(serverPort));
            optionService.saveOrUpdate(serverPortFromDatabase);
            model.addAttribute("updated", true);
            model.addAttribute("serverPort", serverPortFromDatabase);
        }
        return "/admin/config";
    }
    return "redirect:/admin/config";
}

config.html页面

最后是代码多了一点,但复杂度丝毫没有增加的页面:

<main class="container">
    <h2 class="pb-2 py-3">系统设置</h2>
    <hr>
    <div class="row">
        <div class="col-12 col-sm-8 col-md-6 col-lg-4">
            <div th:if="${updated}" class="alert alert-success" role="alert">
                已更新系统配置
            </div>
            <form action="/admin/config" th:action="@{/admin/config}" method="post">
                <label for="expireTime" class="form-label">重置链接有效时间(小时)</label>
                <div class="input-group mb-3">
                    <span class="input-group-text" id="expireTime-addon">1-12小时</span>
                    <input type="number" min="1" max="12" step="1" class="form-control" id="expireTime" name="expireTime"
                           th:value="${expireTime.optionValue}" th:classappend="${timeError}? 'is-invalid'" aria-describedby="expireTime-addon" required>
                    <button class="btn btn-primary" type="submit">修改</button>
                    <div id="emailUsernameError" th:if="${timeError}" class="invalid-feedback">时间超出范围</div>
                </div>
            </form>
            <form action="/admin/config" th:action="@{/admin/config}" method="post">
                <label for="serverName" class="form-label">服务器域名</label>
                <div class="input-group mb-3">
                    <span class="input-group-text" id="serverName-addon">@</span>
                    <input type="text" class="form-control" id="serverName" name="serverName"
                           th:value="${serverName.optionValue}" aria-describedby="serverName-addon" required>
                    <button class="btn btn-primary" type="submit">修改</button>
                </div>
            </form>
            <form action="/admin/config" th:action="@{/admin/config}" method="post">
                <label for="serverPort" class="form-label">服务器端口</label>
                <div class="input-group mb-3">
                    <span class="input-group-text" id="serverPort-addon">1-65535</span>
                    <input type="number" min="1"  step="1" class="form-control" id="serverPort" name="serverPort"
                           th:value="${serverPort.optionValue}" th:classappend="${serverPortError}? 'is-invalid'" aria-describedby="serverPort-addon" required>
                    <button class="btn btn-primary" type="submit">修改</button>
                    <div id="serverPortError" th:if="${serverPortError}" class="invalid-feedback">端口不正确</div>
                </div>
            </form>

        </div>
        <div class="col"></div>
    </div>
</main>

至此就可以在系统设置中配置服务器域名和服务器端口了,配置的内容用来发送邮件,实验了一下没什么问题。这里顺便再修改一下navbar.html,加上用户清单,整个界面显得更丰满了些。

最后照例放一下现在的系统配置界面:
fms-config

LICENSED UNDER CC BY-NC-SA 4.0
Comment