博客地址
博客源码链接: https://github.com/linhaojun857/aurora
前端
后台前端的部分目前知识粗略看了,首先在启动页面app.vue中,会先进入generaMenu菜单生成这个函数中,其中会访问后端的/api/admin/user/menus接口,这时如果后端没有用户信息security会拒绝访问返回错误的flag,前端判断flag,如果是错误的值那么就跳转到login页面中(login.vue是唯一一个显式加入router路由的),如果后端存有用户信息,那么就会将前端组件的路由、组件位置和组件图标返回给前端,前端根据这个动态的生成路由。这样做能够控制用户能够看到的页面,方便权限控制。
在app.vue中,每生成一次页面都会调用一次report函数,这个函数会访问后端的report接口,上报访客信息。
剩下的就是页面切换、页面设计和常规的访问接口设计了。
在部署到服务器时,使用npm run buid命令,构建项目,在docker中下载nginx作为前端的服务器,更改nginx的默认访问页面为打包后得到的index.html页面位置,然后设置请求转发给运行了后端项目的容器。这里注意的是不同容器之间的通信是要设置网络模式的,我使用的是link模式,link模式的大致方法是在配置文件中将ip地址设置为目标容器的名称,运行容器的时候使用link指定该目标容器即可。
后端
后端的部分,真的是感慨作者的开发功底,使用了springboot, mybatis plus作为主要的框架,然后其中还使用了spring security权限校验,消息队列,定时任务,多方式登录,拦截器等功能。在项目中代码严谨,耦合度低,设计规范。其中还有很多地方,我看到就会觉得 哇 还能这样搞的!一时半会想不起来,边写边想把。
mybatis plus
在项目中首先完成基本的框架,其它的额外功能先不搞,先引入mybatis plus的依赖,这个时候一个很坑的地方就来了,依赖中的mybatis plus的版本要和spingboot的版本相对应,否则启动时就会出现无法创建sqlsession的错误,没法正常启动。引入依赖之后在项目的yml中配置相关数据,mybatis plus的本质上还是使用jdbc连接的(对jdbc的另一层封装?目前的理解,上次搜狐的面试官也问过我这个问题,后面看一下)。先在spring datasource中配置数据库的信息包括jdbc、数据库地址、数据库连接池等(这个使用的应该就是SPI服务者使用的接口功能吧,框架规定了如何调用这个接口,接口的具体功能,然后实现交给各种厂商实现,实现后得到的jar作为依赖引入,springboot再在配置中指定这个依赖)。在配置mybatis-plus中要配置mapper.xml的地址和日志格式。在启动类上配置mapperscan注解,这样会把每个mapper交给springboot管理,或者在每个mapper上加上mapper注解也可以,但是不使用@Repository注解,idea会报警告,提示找不到这个bean,直接忽略即可。该注解的作用不只是将类识别为Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。
mybatis plus有代码生成器的功能,在pom中导入代码生成器的依赖,然后根据模板按照自己的需求编写代码生成器实现类。(这里要注意的是mybatis plus的代码生成器依赖中包括mybatis plus extension的依赖,而mybatis plus starter中也包括这个依赖,两者可能会发生冲突导致starter的依赖无法使用,这样就无法配置Mybatis plus的一些扩展功能 如 分页功能)。
编写redisconfig配置类设置分页插件的功能。
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
swagger
在项目的构件中对于接口的测试十分重要的。使用集成了swagger的knif4j,引入依赖,然后编写配置类,启动swagger配置
@Configuration
@EnableSwagger2WebMvc
public class Knif4jConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// .protocols(Collections.singleton("https"))
// .host("https://www.linhaojun.top")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.wzh.myaurora.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("aurora文档")
.description("aurora")
.contact(new Contact("wzh", "", ""))
.version("1.0")
.build();
}
}
然后在需要扫描的接口上加入api注解即可。
redis
使用redis缓存能够减少对数据库的访问压力,加快访问的相应速度。老规矩Pom中引入依赖,在yml中配置redis的连接地址、密码和连接池等信息。在服务器上要下载redis启动。如果自己需要对redis中的一些配置做更改可以编写配置类返回满足自己要求的redistemplate对象。一般来说redis默认的对象存储序列化格式会导致无法查看的问题,编写配置类指定更容易查看的序列化方式。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
另外一般会编写redisservice类包装redistemplate中对redis数据库的操作功能,使得使用方法更加的便捷。
webmvc拦截器
在项目复现的过程中,设置了一个拦截器用来拦截请求中是否包含currentPage,pageSize这两个参数,如果包含这两个参数就用其初始化一个page对象,然后调用pageUtil类将page保存到threadlocal本地变量中,后续对请求的处理中如果需要有需要这个page对象,就使用pageutil从threadlocal变量中取出。当这个请求处理结束之后将threadlocal清空。另外需要编写配置类将该拦截器加入到spring中。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private PaginationInterceptor paginationInterceptor;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedOrigins("*")
.allowedMethods("*");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 这里将定义的paginationInterceptor加入到拦截器中
// 拦截器是定义在spring ioc容器中的,能够使用其中的组件,位于filter过滤器后面
// 这个拦截器的作用是将请求中包含的当前页码,每页的页数得到赋值给pageutil中的page,以供后续的使用
registry.addInterceptor(paginationInterceptor);
}
}
关于为什么要使用这个拦截器,本质上是为什么要使用本地变量保存这样一个固定的page对象呢?我现在个人理解是如果不使用threadlocal保存page对象的话,在这次请求中所涉及到的地方都需要new一个page对象来进行操作,这样会造成资源浪费,而且每一个请求都是一个线程,多个请求并发过来page是一个共享变量会导致处理结果错误。
知识点
复现的过程中自己从头建立了一个新的项目去除spring security、消息队列、定时任务功能,只实现基本的请求处理功能。目前是是把article相关的内容做好了。其中感叹于自己对于Java认识的浅薄和对springboot工程的完整度的强大的感叹。
重要的一点,构建自己的返回对象 resultVO,这样能够让前后端都遵守一个规范,方便开发。
针对前端传来的参数设计相应的vo value object来接受处理,针对前端需要的数据设计相应的dto data trans object对象来包含数据
项目中经常使用到的常数,将其总结为constant接口来保存,这个接口只是保存常数
如果有需要可以定义自己的异常类来处理特殊的情况
在处理请求时,如果涉及到多个查询之类的可以使用CompletableFuture.supplyAsync方法传入一个实现类thread接口的匿名类来得到一个future对象进行异步调用,加快处理速度。
队列转为stream流使用map对其中元素进行处理,然后重新转为队列
在article中有一个文章上传功能,这个功能实现设计的我觉得很巧妙。通过定义的articleImportStrategyContext类,里面有一个map数据类型,key是上传策略,value是对应的实现类(使用@autowired注解map,会自动将value的实现类注入到其中,其实现类的@Service(“normalArticleImportStrategyImpl”)的名称就是key)。前端传来文件流和上传策略,通过上传策略articleImportStrategyContext找到该策略的实现类实现上传。
@Controller注解也是Spring MVC中的注解,它将类标记为一个控制器,用于处理HTTP请求,但是返回的结果是一个视图(View),通常用于构建传统的Web应用。@RestController注解是Spring MVC中的注解,它将类标记为一个控制器,用于处理HTTP请求,并将返回的结果直接写入到HTTP响应体中,通常用于构建RESTful API。如果在controller类中使用的是@Controller,但是没有返回一个视图而只是一个字符串之类的,就会导致如下错误。在springmvc中我们是使用viewResolver,通过在controller中return的前缀来决定跳转到相应的视图,如果返回的字符串没有对应的视图,viewResolver就找不到也就会报错。
Circular view path [login]: would dispatch back to the current handler URL [/ssmtest1/login] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
如果想要用视图去展示,应该要设置好视图展示页面,比如说用一个模板语言来接收返回的数据(thymeleaf或者freemarker等),也可以用jsp接收,但是SpringBoot官方是不推荐用jsp的,而是建议使用thymeleaf作为模板语言,这里我以thymeleaf为例。引入thymeleaf的依赖,然后在application.properties中配置thymeleaf,配置完之后在/src/main/resources/template中创建你要返回的视图模板,如果你的controller中返回的值为index,那模板创建的名称就为index.html,最后在ctroller中设置返回值为index,就能够返回这个视图了。
在传统的Web应用中访问ctroller层是会给你返回一个视图的(也就是页面),那时的Web应用也还不是前后端分离的,后端访问数据库,执行逻辑,得到结果后创建视图返回给浏览器显示。
mybatis中,可以使用collection和association来处理一对多和多对一的关系
其中每个标签中有2中种使用方式,一种是嵌套查询,一种是嵌套结果
嵌套查询会在主查询中,去调用子查询
嵌套结果只需要查询一次,将结果进行封装
https://blog.csdn.net/zifengye520/article/details/121922749
@Transactional(rollbackFor = Exception.class)
Springboot的事务管理,使用注解,然后就会为该方法创建一个代理帮助方法中的内容开启事务,方法执行完后关闭事务,事务的默认传播级别是REQUIRED,当使用了该注解的内部调用了另一个也开启了事物的方法之后,该方法就会检测当前线程之后是否存在事务(调用它的方法是否开启了事务),如果有那么就加入这个事务,否则创建一个新的事务。