4.Swagger自动生成API文档
下面我们简单学习一下如何使用Swagger自动生成API文档,这里只做简单的使用,并没有详细的教学。如果要看更详细的教程,请移步其他Swagger的入门教程。而且现在国内也有其他比Swagger好用的接口框架,不一定要用Swagger。
我先引入Swagger的相关依赖:
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
然后,要在MallApplication这个启动类的类名上面添加一个@EnableSwagger2注解和@EnableWebMvc注解。
之后,我们要对其进行简单的配置,我们新建一个包,叫做config,这个包专门用来存储我们的配置文件的。
在里面创建一个SpringFoxConfig类:
package com.haiexijun.mall.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; @Configuration public class SpringFoxConfig { //访问http://localhost:8083/swagger-ui.html可以看到API文档 @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("慕慕生鲜") .description("") .termsOfServiceUrl("") .build(); } }
之后还要写一个配置类:
package com.haiexijun.mall.config; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * 描述: 配置地址映射的类 */ @Configuration public class MallWebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("swagger-ui.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations( "classpath:/META-INF/resources/webjars/"); } }
配置类这里已经写好了。我们要给controller里面哪个接口生成,就在接口的方法上添加@ApiOperation(“接口描述”)注解就行了,如@ApiOperation(“后台新增商品分类目录”)。
之后我们运行项目,然后访问http://localhost:8083/swagger-ui.html就可以查看到我们编写的接口文档了。我们还可以在里面输入参数调试。
5.后台更新分类目录接口的开发
Controller的方法:
@ApiOperation("后台更新分类目录") @PostMapping("/admin/category/update") @ResponseBody public ApiRestResponse updateCategory(@Valid @RequestBody UpdateCategoryReq updateCategoryReq,HttpSession session) throws MallException { User currentUser=(User) session.getAttribute(Constant.MALL_USER); if (currentUser==null){ return ApiRestResponse.error(MallExceptionEnum.NEED_LOGIN); } boolean adminRole=userService.checkAdminRole(currentUser); if (adminRole){ //是管理员 Category category=new Category(); BeanUtils.copyProperties(updateCategoryReq,category); cateGoryService.update(category); return ApiRestResponse.success(); }else { //不是管理员 return ApiRestResponse.error(MallExceptionEnum.NEED_ADMIN); } }
对应的Service方法:
@Override public void update(Category updateCategory) throws MallException { //校验分类目录的名字是否有冲突 if(updateCategory.getName()!=null){ Category categoryOld=categoryMapper.selectByName(updateCategory.getName()); if (categoryOld !=null && !categoryOld.getId().equals(updateCategory.getId())){ //如果通过传入的信息查到的id存在,则抛出显示不允许重名 throw new MallException(MallExceptionEnum.NAME_EXISTED); } } int count=categoryMapper.updateByPrimaryKeySelective(updateCategory); if (count==0){ throw new MallException(MallExceptionEnum.UPDATE_FAILED); } }
6. 统一校验管理员身份
之前每一个接口都要写相同的代码来验证管理员,如果有非常多的接口要实现的话,那就会有很多的重复代码。这一节优化一下之前的验证管理员的代码。
我们要新建一个过滤器AdminFilter,编写如下的代码
package com.haiexijun.mall.filter; import com.haiexijun.mall.common.ApiRestResponse; import com.haiexijun.mall.common.Constant; import com.haiexijun.mall.exception.MallExceptionEnum; import com.haiexijun.mall.model.pojo.User; import com.haiexijun.mall.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.PrintWriter; /** * 描述: 管理员校验的过滤器 */ public class AdminFilter implements Filter { @Autowired UserService userService; @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request= (HttpServletRequest) servletRequest; HttpSession session=request.getSession(); User currentUser=(User) session.getAttribute(Constant.MALL_USER); if (currentUser==null){ PrintWriter out =new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter(); out.write("{\n" + " \"status\": 10008,\n" + " \"msg\": \"用户未登录\",\n" + " \"data\": null\n" + "}"); out.flush(); out.close(); return; } boolean adminRole=userService.checkAdminRole(currentUser); if (adminRole){ //如果是管理员登录,就放行 filterChain.doFilter(servletRequest,servletResponse); }else { PrintWriter out =new HttpServletResponseWrapper((HttpServletResponse) servletResponse).getWriter(); out.write("{\n" + " \"status\": 10010,\n" + " \"msg\": \"无管理员权限\",\n" + " \"data\": null\n" + "}"); out.flush(); out.close(); return; } } @Override public void destroy() { Filter.super.destroy(); } }
写好filter后,下一步就是配置使用这个filter,我们创建一个AdminFilterConfig.
package com.haiexijun.mall.config; import com.haiexijun.mall.filter.AdminFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 描述:AdminFilter的配置 */ @Configuration public class AdminFilterConfig { @Bean public AdminFilter adminFilter(){ return new AdminFilter(); } @Bean(name = "adminFilterConf") public FilterRegistrationBean adminFilterConfig(){ FilterRegistrationBean filterRegistrationBean=new FilterRegistrationBean(); filterRegistrationBean.setFilter(adminFilter()); filterRegistrationBean.addUrlPatterns("/admin/category/*"); filterRegistrationBean.addUrlPatterns("/admin/product/*"); filterRegistrationBean.addUrlPatterns("/admin/order/*"); filterRegistrationBean.setName("adminFilterConfig"); return filterRegistrationBean; } }
7.删除分类目录接口
server新增加一个delete方法:
@Override public void delete(Integer id) throws MallException { Category categoryOld=categoryMapper.selectByPrimaryKey(id); //如果没有查到记录 if (categoryOld==null){ throw new MallException(MallExceptionEnum.DELETE_FAILED); } int count=categoryMapper.deleteByPrimaryKey(id); if (count==0){ throw new MallException(MallExceptionEnum.DELETE_FAILED); } }
然后编写controller:
@ApiOperation("后台删除目录") @PostMapping("/admin/category/delete") @ResponseBody public ApiRestResponse deleteCategory(@RequestParam("id") Integer id) throws MallException { cateGoryService.delete(id); return ApiRestResponse.success(); }
8.查询用户分类列表接口的开发
我们先来开发一下后台的查询用户分类列表接口:
分页先要引入pagehelper分页插件:
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency>
然后再application.properties文件中添加一行配置:
spring.main.allow-circular-references=true
之后就是编写相关代码就行了:
CategoryMapper新增一个selectList方法,然后给这个方法编写对应的xml映射:
<select id="selectList" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from mall_category </select>
之后就是要新建一个vo包,里面新建一个CategoryVO类,类里面只不过是比Category类多了一个List类型的属性:
package com.haiexijun.mall.vo; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 描述: */ public class CategoryVO { private Integer id; private String name; private Integer type; private Integer parentId; private Integer orderNum; private Date createTime; private Date updateTime; private List<CategoryVO> childCategory = new ArrayList<>(); public List<CategoryVO> getChildCategory() { return childCategory; } public void setChildCategory(List<CategoryVO> childCategory) { this.childCategory = childCategory; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Integer getType() { return type; } public void setType(Integer type) { this.type = type; } public Integer getParentId() { return parentId; } public void setParentId(Integer parentId) { this.parentId = parentId; } public Integer getOrderNum() { return orderNum; } public void setOrderNum(Integer orderNum) { this.orderNum = orderNum; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } }
之后就是Service里面的方法:
@Override public PageInfo listForAdmin(Integer pageNum,Integer pageSize){ PageHelper.startPage(pageNum,pageSize,"type,order_num"); List<Category> categoryList=categoryMapper.selectList(); PageInfo pageInfo=new PageInfo(categoryList); return pageInfo; }
controller的方法:
@ApiOperation("查询后台目录列表") @PostMapping("/admin/category/list") @ResponseBody public ApiRestResponse listCategoryForAdmin(@RequestParam Integer pageNum,@RequestParam Integer pageSize){ PageInfo pageInfo =cateGoryService.listForAdmin(pageNum,pageSize); return ApiRestResponse.success(pageInfo); }
这就编写好后台的目录分页查询的接口了。
下面要来编写用于前台展示的目录分页的接口:
要用到递归查询,下面是代码:
CategoryMapper新增一个selectCategoriesByParentId方法,并编写对应的xml映射:
<select id="selectCategoriesByParentId" parameterType="int" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from mall_category where parent_id=#{parentId} </select>
然后就编写service类的两个方法:
@Override public List<CategoryVO> listCategoryForCustomer(){ ArrayList<CategoryVO> categoryVOList = new ArrayList<CategoryVO>(); recursivelyFindCategories(categoryVOList,0); return categoryVOList; } private void recursivelyFindCategories(List<CategoryVO> categoryVOList,Integer parentId){ // 递归获取所有的子类别,并组合成为一个目录树 List<Category> categoryList = categoryMapper.selectCategoriesByParentId(parentId); if (!CollectionUtils.isEmpty(categoryList)){ for (int i=0;i<categoryList.size();i++){ Category category=categoryList.get(i); CategoryVO categoryVO=new CategoryVO(); BeanUtils.copyProperties(category,categoryVO); categoryVOList.add(categoryVO); recursivelyFindCategories(categoryVO.getChildCategory(),categoryVO.getId()); } } }
最后controller的方法:
@ApiOperation("查询前台目录列表") @PostMapping("/category/list") @ResponseBody public ApiRestResponse listCategoryForCustomer(){ List<CategoryVO> categoryVOS =cateGoryService.listCategoryForCustomer(); return ApiRestResponse.success(categoryVOS); }
9.利用Redis缓存加速
考虑到目录变化其实不是很频繁,所以使用Redis缓存来提高我们整体的效率。这一节主要学习用Spring-boot来集成redis。
先引入两个我们要用到的依赖:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
在引入好依赖之后,我们还要在application.properties文件中对redis进行配置,添加如下的配置:
spring.redis.host=192.168.172.129 spring.redis.port=6379 spring.redis.password=zc20020106
之后要在springboot的启动类的类名上添加如下的注解:
@EnableCaching
之后还要在我们希望用缓存的那个serviceImpl类的方法上面加上一个注解:
@Override @Cacheable(value = "listCategoryForCustomer") public List<CategoryVO> listCategoryForCustomer(){ ArrayList<CategoryVO> categoryVOList = new ArrayList<CategoryVO>(); recursivelyFindCategories(categoryVOList,0); return categoryVOList; }
之后还要建一个redis的配置类CachingConfig:
package com.haiexijun.mall.config; import java.time.Duration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.RedisConnectionFactory; /** * 描述: 缓存的配置类 */ @Configuration @EnableCaching public class CachingConfig { @Bean public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) { RedisCacheWriter redisCacheWriter = RedisCacheWriter .lockingRedisCacheWriter(connectionFactory); RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); cacheConfiguration = cacheConfiguration.entryTtl(Duration.ofSeconds(30)); RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter, cacheConfiguration); return redisCacheManager; } }
还要给之前的CategoryVO类实现Serializable接口:
public class CategoryVO implements Serializable {················}