学习前后端分离有一阵子了,到了疲倦期,是时候整理整理这两个月学到的东西了。这是后端篇,这几天期末考试,看情况再整理前端篇。
前后端分离,顾名思义就是前端一套,后端一套,只用http请求,连接两端,前端负责显示渲染数据,后端负责操作数据。
后端开发:开发工具:idea,技术栈:springboot,jpa,shiro。
在web里选spring web,在SQL里选Spring Data JPA
首先想办法让idea连接到本地的mysql上去,这里的几个坑:
连上数据库了开始编写pojo实体类:在类名上一行写注解:@Entity(“表名”),主键上写注解:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = “字段名”)
其他字段上写注解:@Column(name = “字段名”)
类中的属性名,与数据库表中的字段名一一对应,采用驼峰命名法(作为初学者的体验:一开始尽量别考虑命名规范,不要用下划线,不要用大写字母,全用小写字母,jpa那个东西玄学的很,命名一不小心就报错了,还是不知道怎么改的那种错)get,set方法,tostring方法不要忘了。如果有一对多关系(比如一个用户拥有很多角色,为了方便写代码,直接就在用户类中,加一个属性:角色id,这个属性在数据库表里是没有的,需要加@Transient注解)
编写接口,extends JpaRepository<实体类类名,Integer>,接着就在里面写各种接口,大概有getBy字段,save,findAllBy字段,deleteAllBy字段这些方法,特别注意在delete方法上最好加上注解:
@Modifying
@Transactional
虽然不大明白这两个注解干嘛用的,到后面删除一个用户,然后涉及到要删其他很多表的时候,没有这个注解可能会报错(可以先不加,到时候报错了再加上去试试)。
在service类名上加上注解:@Service。用注解:@Autowired,注入dao类或者其他service类,这里可以实现多表查询,多表删除巴拉巴拉的各种操作,看具体需要啦,基础的增删改查的话也就调用一下dao提供的findAllby方法就可以了。
在controller类名上加上注解:@Controller。用@Autowired注入service类,调用service的接口,在controller层基本就是调用接口,不进行复杂的数据处理了,在各个方法上加上注解:
@CrossOrigin
@PostMapping(path = “http请求的路径”)
@ResponseBody
到这里后端开发接口的大致步骤已经走完了,接下来是一些配置上的总结。
这个东西只可意会不可言传:这里面有个从前端传来的图片处理,配置了把图片下载到本地的路径。还有个解决跨域的配置。
package com.example.demo.config;
import com.example.demo.interceptor.LoginInterceptor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootConfiguration
public class WebConfigurer implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/api/file/**").addResourceLocations("file:"+"D:/pic/");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedOrigins("http://127.0.0.1")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
private CorsConfiguration corsConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
/* 请求常用的三种配置,*代表允许所有,当时你也可以自定义属性(比如header只能带什么,只能是post方式等等)
*/
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setMaxAge(3600L);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig());
return new CorsFilter(source);
}
}
首先编写自定义Realm类,继承AuthorizingRealm,重写doGetAuthenticationInfo方法,大致代码如下:
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String name = authenticationToken.getPrincipal().toString();
User user = userService.getByName(name);
String passwordInDB = user.getPassword();
String salt = user.getSalt();
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(name,passwordInDB, ByteSource.Util.bytes(salt),getName());
return authenticationInfo;
}
设置了用户名,密码,盐这些信息。
接着编写shiro配置类:ShiroConfiguration,写一下getRealm方法,写一下securityManager方法……
接着就可以在controller层使用subject了
登录代码:
@GetMapping(path = "/login")
@ResponseBody
public Result login(User u){
String name = u.getName();
String password = u.getPassword();
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(name,password);
usernamePasswordToken.setRememberMe(true);
try {
subject.login(usernamePasswordToken);
System.out.println("login in success");
System.out.println("isRemembered:"+subject.isRemembered());
System.out.println("isAuthenticated:"+subject.isAuthenticated());
return ResultFactory.buildSuccessResult(name);
}catch (AuthenticationException e){
String message="账号密码错误";
return ResultFactory.buildFailResult(message);
}
}
登出代码:
@CrossOrigin
@GetMapping(path = "/logout")
@ResponseBody
public Result logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
String message="成功登出";
System.out.println(message);
return ResultFactory.buildSuccessResult(message);
}
这部分算是我学到现在,收获最多的部分,拦截请求?查询当前角色权限?设置接口访问权限?乡下人哪见过这种高科技……
首先说一下 @RequiresPermissions(“路径名”),这个与@PostMapping,@GetMapping差不多理解,都是接口的路径,不同的是 @RequiresPermissions是给shiro看的,比如:
@RequiresPermissions("/api/admin/user/all")
@GetMapping(path = "/all")
@ResponseBody
这里接口/all,传到后端shiro看这个接口的路径是/api/admin/user/all
接着说一下自定义过滤器,他继承PathMatchingFilter,重写onPreHandle函数,这个函数大概就是在接受到前端请求后,触发的,这个东西逻辑需要自己写,大概目标就是把请求接口路径与数据库里面存放着的所需验证权限的接口路径对比,如果需要验证权限,就去找当前用户的权限。这里面一堆自己前面实现的类,姑且放个代码作为参考。
package com.example.demo.filter;
import com.example.demo.pojo.AdminPermission;
import com.example.demo.service.AdminPermissionService;
import com.example.demo.utils.SpringContextUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Set;
import lombok.extern.log4j.Log4j2;
//@Log4j2
public class URLPathMatchingFilter extends PathMatchingFilter {
@Autowired
AdminPermissionService adminPermissionService;
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
System.out.println("URLPathMatchingFilter:");
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//放行option
if (HttpMethod.OPTIONS.toString().equals((httpServletRequest).getMethod())) {
httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
return true;
}
if (null == adminPermissionService) {
adminPermissionService = SpringContextUtils.getContext().getBean(AdminPermissionService.class);
}
String requestAPI = getPathWithinApplication(request);
System.out.println("访问接口:" + requestAPI);
Subject subject = SecurityUtils.getSubject();
if (!subject.isAuthenticated()) {
System.out.println("需要登录");
return false;
}
// 判断访问接口是否需要过滤(数据库中是否有对应信息)
boolean needFilter = adminPermissionService.needFilter(requestAPI);
if (!needFilter) {
System.out.println("接口:" + requestAPI + "无需权限");
return true;
} else {
System.out.println("验证访问权限:" + requestAPI);
// 判断当前用户是否有相应权限
boolean hasPermission = false;
String username = subject.getPrincipal().toString();
Set<String> permissionAPIs = adminPermissionService.listPermissionURLsByUser(username);
for (String api : permissionAPIs) {
if (api.equals(requestAPI)) {
hasPermission = true;
break;
}
}
if (hasPermission) {
System.out.println("访问权限:" + requestAPI + "验证成功");
return true;
} else {
System.out.println("当前用户没有访问接口" + requestAPI + "的权限");
return false;
}
}
//return super.onPreHandle(request, response, mappedValue);
}
}
有了过滤器就要使用,需要在shiro配置类里配置……
public URLPathMatchingFilter getURLPathMatchingFilter() {
return new URLPathMatchingFilter();
}
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String > filterChainDefinitionMap = new LinkedHashMap<String, String>();
Map<String, Filter> customizedFilter = new HashMap<>();
System.out.println("shiroFilter:");
// 设置自定义过滤器名称为 url
customizedFilter.put("url", getURLPathMatchingFilter());
System.out.println(getURLPathMatchingFilter());
// 对管理接口的访问启用自定义拦截(url 规则),即执行 URLPathMatchingFilter 中定义的过滤方法
filterChainDefinitionMap.put("/api/admin/**", "url");
// 启用自定义过滤器
shiroFilterFactoryBean.setFilters(customizedFilter);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}