编写完管理员设置邮件服务器的功能后,实际上现在只剩两个配置还留在application.properties
中了,就是项目的路径以及重置密码链接过期的小时数。这两个东西我们也搬到option
中,不过那个链接比较特别,需要从用户初始化的时候获取并写入数据库。
配置过期时间
整体的思路如下:
- 项目启动的时候往数据库中写入一条6小时的过期时间配置
- 修改使用到
EmailProperties
的业务层,将其修改为从数据库中取数 - 修改前端控制器和页面
项目启动的时候写入过期时间
这个很简单,继续在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
,加上用户清单,整个界面显得更丰满了些。
最后照例放一下现在的系统配置界面: