学习SpringBoot+Vue前后端分离项目,原项目GitHub地址,项目作者江雨一点雨博客。
后端项目用idea中的spring initializer插件创建,若没有,则在打开设置-插件-市场中搜索sping initializer下载安装并应用。
创建时,勾选Spring Web、Sprig Securit、MySQL以及MyBatis,Spring Boot的版本为2.1.8
将github上的原项目克隆到本地仓库,直接用Git拉的话实在太慢(吐糟GitHub),建议先用码云克隆Github上的项目,再通过码云把项目克隆到本地仓库,速度非常快。
克隆完成之后,在本地仓库打开微人事项目,将其中的SQL脚本vhr.sql导入到MySQL数据库中,方法有很多,这里我采用SQLyog这个图形化软件来完成导入并且后面的数据库操作都在这个软件中进行。
打开SQLyog,连接到数据库后,创建一个与要导入数据库相同名字的数据库,这里我们就叫它vhr,然后执行下图操作
选择刚才在本地仓库的SQL脚本,点击执行,导入成功。
导入成功之后,回到项目中来,处理一下依赖。
首先锁定一下mysql的版本5.1.27,然后添加druid,版本为1.1.10
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
接下来我们使用mybatis逆向工程来生成model和mapper,关于逆向工程有机会我好好研究一下,单独抽出来写一篇文章。这里我直接拿松哥提供的逆向工程生成了model和mapper,将其放入项目中,并在VhrApplication(SpringBoot启动类,根据项目名称变化)中写上@MapperScan,扫描你的mapper包
@SpringBootApplication
@MapperScan(basePackages = "org.javaboy.vhr.mapper")
public class VhrApplication {
public static void main(String[] args) {
SpringApplication.run(VhrApplication.class, args);
}
}
然后我们还要在依赖中添加resource,避免配置文件被过滤掉。
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml
org.springframework.boot
spring-boot-maven-plugin
接下来配置一下数据库,打开resources目录下的application.properties,添加如下代码 说明一下 最后一行不是注释 就是有3个’/’,这里是写文章时代码块转换的问题。
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql:///vhr?useUnicode=true&characterEncoding=UTF-8
首先找到model下的Hr,实现UserDetails接口
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired(); //账户是否没有过期
boolean isAccountNonLocked();//账户是否没有被锁定
boolean isCredentialsNonExpired();//密码是否没有过期
boolean isEnabled();//是否启用
}
生成getter和setter,注意修改上图四个属性的值(默认为false),并将生成的getEnabled删除掉,因为isEnabled就相当于getEnabled,不需要多余的一个。
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
接下来创建src/sevice包,以后service都写在这个包里面,创建HrService并实现UserDetailsService
自动导入HrMapper,在HrMapper里面创建Hr loadUserByUsername(String username)方法
@Service //不要忘记添加注解!!!
public class HrService implements UserDetailsService {
@Autowired
HrMapper hrMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Hr hr=hrMapper.loadUserByUsername(username);
//判断用户是否存在
if (hr==null){
throw new UsernameNotFoundException("用户名不存在");
}
return hr;
}
有两点需要注意,第一个就是不要忘记在service上面添加@Service注解,第二个就是idea会提示你自动注入的hrMapper错误,这个其实不会影响项目运行,因为我们在mapper.xml文件中已经配置了Mapper类。如果看的不舒服的话,可以在对应的Mapper类上添加@Repository注解
@Repository
public interface HrMapper
在HrMapper.xml文件里加上
<select id="loadUserByUsername" resultMap="BaseResultMap">
select * from hr where username=#{username}
</select>
这些由逆向生成的model和xml文件,有些地方没有遵循驼峰式命名,需要手动修改,一般idea都会用绿色下划波浪线提示你,我们只需要找到这些地方,ctrl+r开启查找替换
如图所示,namezh不符合,需要改成nameZh,注意勾选后面的两项。这里显示红色说明在你查找的该文件下没有这项单词,这里我已经改过了,所以没有显示。如果有的话,会直接高亮提示你单词所在位置,然后在下面的输入框内输入你想要改成的单词 ,确认无误后点击Replace all全部替换。
在src下创建config包,然后在config包内创建SecurityConfig类
@Configuration //重要!!!不要忘记添加
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
HrService hrService;
@Bean //重要!!!不要忘记添加
//密码编码工具,给明文密码加密加盐
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(hrService);
}
创建src/controller/HelloController来测试
@RestController
public class HelloController{
@GetMapping("/")
public String hello(){
retuen "hello";
}
}
启动项目,访问localhost:8080/hello,跳转到localhost:8080/login登录页面,
输入用户名admin,密码123,登录成功后跳转到localhost:8080/hello,显示hello
测试成功。
由于微人事项目是一个前后端分离的项目,所以后端不需要进行页面跳转的操作,只需要给前端提供JSON数据,让前端自己进行页面跳转。
在SecurityConfig里面写如下代码
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//开启登录配置
.anyRequest().authenticated()//登录之后就可以访问的接口
.and()
.formLogin()
//定义登录时,用户名的key,默认为username
.usernameParameter("username")
//定义登录时,密码的key,默认为password
.passwordParameter("password")
//登录处理接口
.loginProcessingUrl("/doLogin")
//定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,
//会自动跳转到该页面
.loginPage("/login")
//登陆成功的处理器
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
Hr hr = (Hr) authentication.getPrincipal();
//返回的JSON中不带有password
hr.setPassword(null);
RespBean ok = RespBean.ok("登陆成功", hr);
String s = new ObjectMapper().writeValueAsString(ok);
out.write(s);
out.flush();
out.close();
}
})
//登陆失败的处理器
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error("登陆失败");
if (e instanceof LockedException){
respBean.setMsg("账户被锁定,请联系管理员");
}else if (e instanceof CredentialsExpiredException){
respBean.setMsg("密码过期,请联系管理员");
}else if (e instanceof AccountExpiredException){
respBean.setMsg("账户过期,请联系管理员");
}else if (e instanceof DisabledException){
respBean.setMsg("账户被禁用,请联系管理员");
}else if (e instanceof BadCredentialsException){
respBean.setMsg("用户名或密码输入错误,请重新输入");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
})
.permitAll()
.and()
.logout()
//注销成功的处理器
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out=resp.getWriter();
out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功")));
out.flush();
out.close();
}
})
.permitAll()
.and()
.csrf().disable()
}
}
在model中创建返回的实体类RespBean
public class RespBean {
private Integer status;
private String msg;
private Object obj;
public static RespBean ok(String msg){
return new RespBean(200,msg,null);
}
public static RespBean ok(String msg,Object obj){
return new RespBean(200,msg,obj);
}
public static RespBean error(String msg){
return new RespBean(500,msg,null);
}
public static RespBean error(String msg,Object obj){
return new RespBean(500,msg,obj);
}
private RespBean() {
}
private RespBean(Integer status, String msg, Object obj) {
this.status = status;
this.msg = msg;
this.obj = obj;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
创建LoginController
@RestController //重要!!!
public class LoginController {
@GetMapping("/login")
public RespBean login(){
return RespBean.error("尚未登录,请登录");
}
}
启动项目,用Postman来测试一下,访问login页面,应该提示我们需要登录。
同样的,在未登录情况下,访问hello页面,也提示我们需要登录。
然后我们将get请求换成post请求,并添加上默认的用户名和密码,即admin和123,访问doLogin页面,实现登录。
然后我们再访问hello,就可以正确显示hello了。注意请求是get。
访问注销页面,注销成功。然后再去访问hello,提示需要登录,测试成功。
最后讲一下关于JSON中不需要携带password的问题,这个项目里面采用的是在传hr对象之前把password设置为null,还有一种办法就是在Hr中在getPassword的方法上添加@JsonIgnore注解,就可以在生成JSON字符串时忽略该字段。
@JsonIgnore
public String getPassword() {
return password;
}
但是这种方法的坏处就是无论在Hr对象变为JSON字符串还是JSON生成Hr对象时,该字段都会被忽略,然而后一种情况我们还是需要该字段的。