写在前面:第一次接触springboot的案例,的确比ssm少了太多的配置,参考尚硅谷+狂神说。员工管理系统导入资源后,前面的准备工作基本上是照着视频敲的,目的为了入门springboot项目的搭建。员工的CRUD操作是自己基于学过的SSM框架写的,虽然bug百出,但最后还是把功能都实现了,留下两个待解决的问题,再接再励~。
1. 准备工作
1.1 新建springboot项目
1.2 编写pom.xml
<!--thymeleaf模板引擎-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<!--lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
1.3 导入静态资源文件
1.4 准备pojo实体
Department 类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
Employee 类:
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Department department;
private Date birthday;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
this.birthday = new Date();
}
}
添加注解@Data @AllArgsConstructor @NoArgsConstructor
按住ALT+7 可以查看到类的结构,以Department 为例
**注意:**如果遇到没有上述结构,需要按住IDEA的Lombok插件,setting中安装并重启IDEA
1.5 编写dao层
当我们没有数据库文件时,如何模拟一个数据库?
DepartmentDao :
//使用类来模拟数据库
@Repository
public class DepartmentDao {
//模拟数据库中的数据
private static Map<Integer, Department> departments = null;
//赋值数据
static {
departments = new HashMap<Integer, Department>();
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"文体部"));
departments.put(103,new Department(103,"财务部"));
departments.put(104,new Department(104,"后勤部"));
}
//获取所有部门的信息
public Collection<Department> getDepartments(){
return departments.values();
}
//通过id获取部门信息
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
EmployeeDao :
//使用类来模拟数据库
public class EmployeeDao {
//模拟数据库中的数据
private static Map<Integer, Employee> employees = null;
//员工所属的部门
@Autowired
private static DepartmentDao departmentDao;
static {
employees = new HashMap<Integer, Employee>();
employees.put(101,
new Employee(101,"AA","A515801535@qq.com",0,
new Department(103,"财务部")));
employees.put(102,
new Employee(102,"BB","B515801535@qq.com",1,
new Department(104,"后勤部")));
employees.put(103,
new Employee(103,"CC","C515801535@qq.com",0,
new Department(101,"教学部")));
}
//设置主键自增
private static Integer initId = 1004;
//增加一个员工
public void save(Employee employee){
if (employee.getId() == null){
employee.setId(initId++);
}
//分配部门
employee.setDepartment(departmentDao.getDepartmentById(employee.getId()));
employees.put(employee.getId(),employee);
}
//查询所有的员工信息
public Collection<Employee> getAll(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
//通过id查询员工
public void delete(Integer id){
employees.remove(id);
}
}
2. 首页实现
2.1 IndexController.java编写
@Controller
public class IndexController {
@RequestMapping({
"/","index"})
public String index(){
return "index";
}
}
首页这种跳转我们更倾向于使用springMVC扩展方法
2.2 springMVC扩展
@Configuration
//@EnableWebMvc
public class ExtendsMvc implements WebMvcConfigurer {
//方法的作用是添加视图的控制跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/").setViewName("index");
}
}
使用地址:
127.0.0.1:8080
127.0.0.1:8080/
127.0.0.1:8080/index
127.0.0.1:8080/index.html
均可以访问
当此时需要注意的是:@EnableWebMvc不能使用,因为使用后springboot对springmvc的默认配置失效,我们没有去配置springmvc的静态资源路径,静态资源的请求立即失效。
3. 国际化
所谓国际化,指的是网页可以支持多语言的切换,例如:
所以我们要在项目的首页也实现此功能,点击进行中英切换
3.1 分析国际化原理
- 进入WebMvcAutoConfiguration搜索AcceptHeaderLocaleResolver
- 进入AcceptHeaderLocaleResolver类
可见请求头的地区解析器实现了一个地区解析器 - 继续往下看,发现实现地区解析器的方法
3.2 国际化的实现
-
国际化配置文件
注意命名:
en_US:表示英文
zh_CN:表示中文
login.properties:login.btn=登录 login.password=密码 login.remember=请记住我 login.tip=请登录 login.username=用户名
login_en_US.properties
login.btn=Sign login.password=password login.remember=Remember me login.tip=Please sign in login.username=username
login_zh_CN.properties:
login.btn=登录 login.password=密码 login.remember=请记住我 login.tip=请登录 login.username=用户名
-
前端展示
-
依葫芦画瓢,我们也根据请求头地区解析器的实现原理,自定义一个地区解析器,在config包下
public class MyLocaleResolver implements LocaleResolver { //解析请求 @Override public Locale resolveLocale(HttpServletRequest httpServletRequest) { //获取请求中的语言参数 String language = httpServletRequest.getParameter("language"); Locale defaLocale = Locale.getDefault();//如果没有就使用默认的 //如果请求参数携带了国际化的参数 if(StringUtils.isEmpty(language)){ //国家,地区 String[] split = language.split("_"); defaLocale = new Locale(split[0], split[1]); } return defaLocale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } }
-
将地区的解析装配到容器中
@Configuration //@EnableWebMvc public class ExtendsMvc implements WebMvcConfigurer { //方法的作用是添加视图的控制跳转 @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index").setViewName("index"); registry.addViewController("/index.html").setViewName("index"); registry.addViewController("/").setViewName("index"); } //自定义的国际化组件就生效了 @Bean public LocaleResolver setMyLocaleResolver(){ return new MyLocaleResolver(); } }
3.3 国际化遇到的问题
按照如上的配置完之后发现一个问题:国际化不生效,切换并不会生效。**
3.4 解决方案
方法其实很很搞笑,就是我们直接把配置文件下的setMyLocaleResolver方法名改成localeResolver 即可。
原因
:为什么会这样呢?
因为我们这个方法
//自定义的国际化组件就生效了
@Bean
public LocaleResolver setMyLocaleResolver(){
return new MyLocaleResolver();
}
的作用是在组件中添加一个bean,它的id为localeResolver
。
在springboot启动时,如果没有自定义配置文件时,会去加载这个默认的方法添加地区解析器的bean对象到容器中,@ConditionalOnMissingBean
的作用就是容器中没有id = localeResolver
这个bean时会执行这个默认的方法添加bean到容器中。
经过上述的分析,得出结论:
没有生效的原因就是我们没有把自定义的地区解析器加到容器中,我们使用的还是默认地区解析器。
那为什么改个方法名就可以了?bean的默认id不是类名的小写字母开头吗?
<mark>其实不然,</mark>
在基于JavaConfiguration下的,也就是@Cofiguration下的类中使用@Bean,当然这个通常作用于方法上,默认id是方法名
,而不是类名的小写字母开头。
在基于组件扫描的情况下,即@Component,@Sevice,@Controller,@Respositry
下创建的bean的对象默认id为类名的小写字母开头。
故我们刚刚只是类型为LocaleResolver,id = setMyLocaleResolver
的一个对象添加容器中了,容器中还有默LocaleResolver,id = localeResolver
的对象存在,当然只会去调用默认的自动配置(因为@ConditionalOnMissingBean
),故国际化是不会生效的!!
从bean在不同情况下的默认id入手排查,参考:https://www.cnblogs.com/1540340840qls/p/6962777.html
4. 登录功能实现
4.1 表单
<form class="form-signin" th:action="@{/user/login}">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<p th:text="${msg}" style="color: red" th:if="${not #strings.isEmpty(msg)}"></p>
<label class="sr-only">Username</label>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only">Password</label>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{
login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
4.2 前端控制器
@Controller
@RequestMapping("user")
public class LoginController {
@RequestMapping("login")
public String login(@RequestParam("username") String username,
@RequestParam("password")String password,
Model model){
//具体业务
if(!StringUtils.isEmpty(username) && "123".equals(password)){
return "redirect:/main.html";
}else{
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
}
main.html主要用来作为中间视图,过滤掉请求参数显示在地址栏。
5. 登录拦截器
设置拦截器的作用:可以拦截掉非法登录后台的操作,必须是用户登录了才能访问dashboard.html。
5.1自定义拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
//session中没有数据,说明还没有登录,拦截
if (null == loginUser){
request.setAttribute("msg","你们有权限登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false; //拦截
}else{
return true; //放行
}
}
}
5.2 mvc扩展
//自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"). //拦截所有
excludePathPatterns("/index.html","/","/css/**","/js/**","/img/**","/user/login"); //放行
}
🐖:需要在登录控制器中添加登录成功LoginController
session.setAttribute("loginUser",username);
两个作用:
- loginUser不为空时代表已登录
- 将username携带至dashboard.html
6. 展示员工列表
6.1 抽取公共部分代码
进入main.html
可见左边导航栏和上方导航栏均属属于固定的不变的,变得只是点击左边导航栏不同选项时的高亮显示。
<mark>删除一些项目中暂不需要的代码后重新展示。</mark>
- 新建重复代码块
common.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--头部导航栏-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="upbar">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${
session.loginUser}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
</li>
</ul>
</nav>
<!--左侧导航栏-->
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active th:href="@{
/main.html}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
首页 <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link active th:href="@{
/emps/list}">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
员工管理
</a>
</li>
</ul>
</div>
</nav>
</html>
- dashboard.html替换的部分使用嵌入的方法替换
<div th:replace="~{/commons/common::upbar}"></div>
<div th:replace="~{/commons/common::sidebar}"></div>
6.2 员工列表Controller
新建EmployeeController类:
@Controller
@RequestMapping("emps")
public class EmployeeController {
//注入员工Dao
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("list")
public String list(Model model){
Collection<Employee> emps = employeeDao.getAll();
model.addAttribute("emps",emps);
return "emp/list";
}
}
6.3前端展示员工列表
- 将list.html存放在新建的emp文件夹中
- 同样类似于dashboard.html嵌入公共代码部分
<div th:replace="~{/commons/common::upbar}"></div>
<div th:replace="~{/commons/common::sidebar}"></div>
- table中接收服务器的数据
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>birthday</th>
<th>department</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td>[[${
emp.id}]]</td>
<td >[[${
emp.lastName}]]</td>
<td>[[${
emp.email}]]</td>
<td th:text="${emp.gender}==0?'女':'男'"></td>
<td th:text="${#dates.format(emp.birthday,'yyyy-mm-dd')}"></td>
<td>[[${
emp.department.departmentName}]]</td>
<td>
<button class="btn-sm btn-primary">编辑</button>
<button class="btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
6.4 点击高亮显示
需求是当我们点击左侧导航栏可以高亮显示当前界面:
- 首页高亮
dashboard.html首页中发送参数?active=main.html
<div th:replace="~{/commons/common::sidebar(active='main.html')}"></div>
- common.html中用三目运算符接收
<a th:class="${active =='main.html'}? 'nav-link active':'nav-link'" th:href="@{/main.html}">
- 同理,员工管理选项
list.html:
<div th:replace="commons/common::sidebar(active='list.html')"></div>
common.html:
<a th:class="${active =='list.html'}? 'nav-link active':'nav-link'" th:href="@{/emps/list}">
7. 增加员工实现
7.1 增加员工按钮
<a th:href="@{/emps/add}" class="btn-sm btn-success">添加员工</a>
页面展示:
7.2 增加员工页面
- 在resources下的emp新建add.html
- 增加表单:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emps/addEmploy}" method="post">
<div class="form-group">
<label for="lastname">姓名:</label>
<input type="text" class="form-control" id="lastname" name="lastName" placeholder="lastName">
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" class="form-control" id="email" name="email" placeholder="email">
</div>
<label class="radio-inline">
<input type="radio" name="gender" value="1"> 男
<input type="radio" name="gender" value="0"> 女
</label>
<div class="form-group">
<label>部门:</label>
<select class="form-control" name="department.id">
<option th:each="department:${departments}" th:value="${department.id}"
th:text="${department.departmentName}"></option>
</select>
</div>
<div class="form-group">
<label for="birthday">生日:</label>
<input type="date" class="form-control" id="birthday" name="birthday" placeholder="birthday">
</div>
<input type="submit" class="btn-sm btn-success" >新增</input>
</form>
</main>
7.3 跳转增加员工页面controller
@RequestMapping("add")
public String add(Model model){
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments); //为了展示下拉框
return "emp/add";
}
7.4 增加员工controller
保存员工应当修改一处:
分配部门应当在controller处进行,这样是为了避免随着自增的员工id,导致没有部门可选(一个员工对于一个部门的)
@RequestMapping("addEmploy")
public String addEmploy(Employee employee, HttpServletRequest request){
//分配部门
employee.setDepartment(departmentDao.
getDepartmentById(Integer.parseInt(request.getParameter("department.id"))));
employeeDao.save(employee);
return "redirect:/emps/list";
}
8. 修改员工实现
8.1 编辑员工页面跳转
<td>
<a th:href="@{/emps/showUpdate/{id}(id=${emp.id})}" class="btn-sm btn-primary">编辑</a>
<a class="btn-sm btn-danger">删除</a>
</td>
需要注意的是thymeleaf传值id的时候,写法千万不能写错,如果写错则进不去Controller,直接报出404
8.2 update.html
<mark>与add.html界面一样</mark>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emps/addEmploy}" method="post">
<div class="form-group">
<label for="lastname">姓名:</label>
<input type="text" class="form-control" id="lastname" name="lastName" placeholder="lastName">
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" class="form-control" id="email" name="email" placeholder="email">
</div>
<label class="radio-inline">
<input type="radio" name="gender" value="1"> 男
<input type="radio" name="gender" value="0"> 女
</label>
<div class="form-group">
<label>部门:</label>
<select class="form-control" name="department.id">
<option th:each="department:${departments}" th:value="${department.id}"
th:text="${department.departmentName}"></option>
</select>
</div>
<div class="form-group">
<label for="birthday">生日:</label>
<input type="date" class="form-control" id="birthday" name="birthday" placeholder="birthday">
</div>
<input type="submit" class="btn-sm btn-success" >新增</input>
</form>
</main>
8.3 员工信息的回显
- 控制器的跳转
@RequestMapping("showUpdate/{id}")
public String showUpdate(Model model,@PathVariable("id") Integer id){
Employee employee = employeeDao.getEmployeeById(id);
Department department = employee.getDepartment();
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments); //为了展示下拉框
model.addAttribute("employee",employee);
return "emp/update";
}
- 前端的展示
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emps/update/{id}(id=${employee.id})}" method="post">
<div class="form-group">
<label>姓名:</label>
<input type="text" class="form-control" th:name="lastName" th:value="${employee.lastName}">
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" class="form-control" id="email" th:name="email" th:value="${employee.email}">
</div>
<label class="radio-inline">
<input type="radio" name="gender" value="1" th:checked="${employee.gender}==1"> 男
<input type="radio" name="gender" value="0" th:checked="${employee.gender}==0"> 女
</label>
<div class="form-group">
<label>部门:</label>
<select class="form-control" th:name="department.id">
<option th:selected="${employee.department.id == dept.id}"
th:text="${dept.departmentName}"
th:value="${dept.id}" th:each="dept:${departments}"
></option>
</select>
</div>
<div class="form-group">
<label>生日:</label>
<input type="date" th:value="${employee!=null}?${#dates.format(employee.birthday,'yyyy-MM-dd')}" class="form-control" th:name="birthday">
</div>
<input type="submit" class="btn-sm btn-success" value="修改"></input>
</form>
</main>
8.4 修改员工实现
- EmployeeDao新增方法
//修改员工
public void updateEmployee(Employee employee){
employees.put(employee.getId(),employee);
}
Map结构会覆盖key值相同的value
- 控制器的编写:
@PostMapping("update/{id}")
public String update(Employee employee,@PathVariable("id") String id,Model model){
employee.setId(Integer.parseInt(id)); //为employee设置id
Department department = departmentDao.getDepartmentById(employee.getDepartment().getId());
employee.setDepartment(department); //为employee设置部门
employeeDao.updateEmployee(employee); //修改
Collection<Employee> emps = employeeDao.getAll();
model.addAttribute("emps",emps);
return "forward:/emps/list"; //转发至/emps/list
}
9. 删除员工实现
- 删除的按钮跳转
<td>
<a th:href="@{/emps/showUpdate/{id}(id=${emp.id})}" class="btn-sm btn-primary">编辑</a>
<a class="btn-sm btn-danger" th:href="@{/emps/delete/{id}(id=${emp.id})}" >删除</a>
</td>
- 删除控制器的编写
@GetMapping("delete/{id}")
public String delete(@PathVariable("id") Integer id,Model model){
employeeDao.delete(id);
Collection<Employee> emps = employeeDao.getAll();
model.addAttribute("emps",emps);
return "forward:/emps/list"; //转发至/emps/list
}
10. 404界面处理与
基于约定 > 配置的springboot我们在resources中新建error,将404页面移动进去
访问错误路径:
11.注销用户
- 注销按钮(common.html)
<a class="nav-link" th:href="@{user/out}">注销</a>
- 用户的控制器LoginController
@GetMapping("out")
public String out(HttpSession session){
session.invalidate(); //销毁session
return "redirect:index";
}
12.遇到未能解决的问题
- 公共代码为什么不能放public中?<mark>6.1处</mark>
- <mark>8.3处员工信息的回显</mark>