BladeX 是由一个商业级项目升级优化而来的SpringCloud微服务架构,采用Java8 API重构了业务代码,完全遵循阿里巴巴编码规范。采用Spring Boot 2 、Spring Cloud Honxton 、Mybatis 等核心技术,同时提供基于React和Vue的两个前端框架用于快速搭建企业级的SaaS微服务系统平台。
鉴权,获得Token,接口请求时,请求头需携带Token进行认证;不管是客户端(PC或移动端)的请求,还是服务内部调用,请求都会经过Zuul网关,然后再由网关来实现鉴权等操作
具体调用方式请看:https://sns.bladex.vip/article-14982.html
导入json配置文件到postman
设置test、dev、pro的环境变量
修改账号、密码,在Tests里使用脚本将token赋值给环境变量
//获取返回值
var response =JSON.parse(responseBody);
//判断返回值是否有access_token参数
if (response.access_token) {
//如果有则此次验证通过
tests["first has access_token"] = true;
//获取需要的参数
var token = response.access_token;
//打印获取的参数
console.log("response.access_token-->" + token);
//将值写入当前选中的环境中 变成环境变量
pm.environment.set("token", "bearer "+token);
}
else {
//如果无则此次验证不通过
tests["first has access_token"] = false;
}
提示:使用 Redis可视化工具Redis Desktop Manager来实时查看redis的使用情况
调用接口并查看控制台,发现打印本条信息没有从缓存获取
@GetMapping("/infod")
@Cacheable(cacheNames = "test-infod", key = "#name")
public R<String> infod(String name) {
log.info("本条信息没有从缓存获取");
return R.data("Hello: " + name);
}
查看Redis Desktop Manager,发现多了一个键值,正是我们设置的
再次调用接口,发现控制台再无日志输出,说明第二次以后的请求,缓存都从Redis取了
调用接口,并查看返回结果为删除缓存成功
@GetMapping("remove-info")
@CacheEvict(cacheNames = "demo-info", key = "#name")
public R<String> removeInfo(String name) {
return R.success("删除缓存成功");
}
查看Redis Desktop Manager,发现缓存被移除
再次调用infod接口,发现又如一开始,控制台打印了日志,并且Redis增加了键值
BlogMapper继承BaseMapper类,继承BaseMapper接口,它帮我们做了CRUD操作,我们不用在自己写sql语句了
package org.springbalde.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.demo.entity.Blog;
public interface BlogMapper extends BaseMapper<Blog> {
}
BlogService继承IService类
package org.springbalde.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.demo.entity.Blog;
public interface BlogService extends IService<Blog> {
}
BlogServiceImpl继承ServiceImpl类,实现BlogService类
package org.springbalde.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springbalde.demo.mapper.BlogMapper;
import org.springbalde.demo.service.BlogService;
import org.springblade.demo.entity.Blog;
import org.springframework.stereotype.Service;
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements BlogService{
}
在application.yml文件中,进行mybatis-plus配置,mybatis-plus扫描bean-alias的配置
#mybatis-plus配置
mybatis-plus:
mapper-locations: classpath:com/springbalde/**/mapper/*Mapper.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.springbalde.**.entity
新建一个MybatisPlusConfiguration类,使用@MapperScan注解,配置包扫描的路径
package org.springblade.test.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springblade.test.props.TestProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableFeignClients({"org.springblade", "org.springblade"})
@MapperScan({"org.springblade.**.mapper.**", "org.springblade.**.mapper.**"})
@EnableConfigurationProperties(TestProperties.class)
public class MybatisPlusConfiguration {
}
新增API
/**
* 新增
*/
@PostMapping("/save")
public R save(@RequestBody Blog blog) {
return R.status(service.save(blog));
}
通过id修改
/**
* 通过id修改
*/
@PostMapping("/update")
public R update(@RequestBody Blog blog) {
return R.status(service.updateById(blog));
}
通过id逻辑删除,需在实体类上加上@TableLogic注解
@PostMapping("/remove")
public R remove(@RequestParam String id) {
return R.status(service.removeByIds(Func.toLongList(id)));
}
/**
* 是否已删除
*/
@TableField("isDeleted")
@TableLogic(value = "0", delval = "1")
private Integer isDeleted;
通过id查询详情
/**
* 通过id查询详情
*/
@GetMapping("/detail")
public R<Blog> detail(String id) {
Blog detail = service.getById(id);
return R.data(detail);
}
查询多条
/**
* 查询多条
*/
@GetMapping("/list")
public R<List<Blog>> list() {
List<Blog> list = service.list();
return R.data(list);
}
使用条件构造器Wrappers查询多条
/**
* 查询多条
*/
@GetMapping("/list2")
public R<List<Blog>> list2(Blog blog) {
List<Blog> list = service.list(Wrappers.query(blog));
return R.data(list);
}
查询多条
/**
* 查询多条
*/
@GetMapping("/list4")
public R<List<Blog>> list4(@RequestParam Map<String, Object> blog) {
List<Blog> list = service.list(Condition.getQueryWrapper(blog, Blog.class).lambda().orderByDesc(Blog::getBlogDate));
return R.data(list);
}
分页
/**
* 分页
*/
@GetMapping("/page")
public R<IPage<Blog>> page(@ApiIgnore @RequestParam Map<String, Object> blog, Query query) {
IPage<Blog> pages = service.page(Condition.getPage(query), Condition.getQueryWrapper(blog, Blog.class));
return R.data(pages);
}
在package,feign下创建一个接口类,命名为BlogClient
package org.springblade.demo.feign;
import org.springblade.common.constant.CommonConstant;
import org.springblade.core.tool.api.R;
import org.springblade.demo.entity.Blog;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(
//定义Feign指向的service-id
value = CommonConstant.APPLICATION_DEMO_NAME
)
public interface BlogClient{
/**
* 接口前缀
*/
String API_PREFIX = "/api/blog";
/**
* 获取详情
*
* @param id 主键
* @return
*/
@GetMapping(API_PREFIX + "/detail")
R<Blog> detail(@RequestParam("id") Integer id);
}
增加feign的实现类BlogClientImpl
package org.springblade.demo.feign;
import lombok.AllArgsConstructor;
import org.springblade.core.tool.api.R;
import org.springblade.demo.entity.Blog;
import org.springblade.demo.service.BlogService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@AllArgsConstructor
public class BlogClientImpl implements BlogClient {
private BlogService service;
@Override
@GetMapping(API_PREFIX + "/detail")
public R<Blog> detail(Integer id) {
return R.data(service.getById(id));
}
}
在另一个服务Controller类中添加方法接口,进行服务调用
private BlogClient client;
@GetMapping("/blog-detail")
public R<Blog> blogDetail(Integer id) {
R<Blog> result = client.detail(id);
return result;
}
新建Hystrix类,命名为BlogClientFallback,实现BlogClient接口
package org.springblade.demo.feign;
import org.springblade.core.tool.api.R;
import org.springblade.demo.entity.Blog;
import java.time.LocalDateTime;
public class BlogClientFallback implements BlogClient {
@Override
public R<Blog> detail(Integer id) {
Blog blog = new Blog();
blog.setBlogTitle("Hystrix");
blog.setBlogContent("FallBack Success");
blog.setBlogDate(LocalDateTime.now());
blog.setIsDeleted(0);
return R.data(blog);
}
}
修改BlogClient,增加Hystrix配置
@FeignClient(
//定义Feign指向的service-id
value = CommonConstant.APPLICATION_DEMO_NAME,
//定义hystrix配置类
fallback = BlogClientFallback.class
)
public interface BlogClient {
/**
* 接口前缀
*/
String API_PREFIX = "/api/blog";
/**
* 获取详情
*
* @param id 主键
* @return
*/
@GetMapping(API_PREFIX + "/detail")
R<Blog> detail(@RequestParam("id") Integer id);
}
在BlogClientFallback类上加@Component注解
@Component
public class BlogClientFallbackConfiguration {
@Bean(name = {"configure1"})
public BlogClientFallbackConfiguration blogClientFallback(){
return new BlogClientFallbackConfiguration();
}
}
在BlogClientImpl模拟异常
@RestController
@AllArgsConstructor
public class BlogClientImpl implements BlogClient {
private BlogService service;
@Override
@GetMapping(API_PREFIX + "/detail")
public R<Blog> detail(Integer id) {
int cnt = 100 / 0;
return R.data(service.getById(id));
}
}
最后,使用Postman调用API查看,发现Hystrix配置生效
需添加注解
@EnableCircuitBreaker
//服务全局降级
@DefaultProperties(defaultFallback = "GlobalError")
模拟Hystrix服务降级and服务全局降级
/**
* @Description: 模拟超时过程 Hystrix测试
* @Param:
* @Author: BWL
* @Date: 2021-11-09 10:48
*/
@GetMapping("/timeOut")
public String timeOut() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
return "模拟超时过程";
}
/**
* 模拟服务降级
*/
@GetMapping("/timeOuts")
//配置熔断器,fallbackMethod为降级调用方法,commandProperties为触发服务降级配置
@HystrixCommand(fallbackMethod = "errors", commandProperties = {
//条件设置为2s后未返回结果则发生降级
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
})
public String timeOuts() {
//模拟程序出错
int cnt = 100 / 0;
return "服务降级";
}
/**
* 模拟服务全局降级
*/
@GetMapping("/timeOutsqj")
@HystrixCommand
public String timeOutsqj() {
//模拟程序出错
int cnt = 100 / 0;
return "服务全局降级";
}
public String errors() {
return "这是服务出错/超时发生的降级处理方法";
}
public String GlobalError() {
return "这是全局指定服务出错/超时发生的降级处理方法";
}
BladeX提供的Excel工具基于阿里出品的EasyExcel封装而来
基于官方文档,我们编写一个EasyExcel格式的bean
@Data
@ColumnWidth(25)
@HeadRowHeight(20)
@ContentRowHeight(18)
public class BlogExcel {
/**
* 主键
*/
// @ExcelIgnore
@ColumnWidth(10)
@ExcelProperty("id")
private Long id;
/**
* 标题
*/
@ColumnWidth(20)
@ExcelProperty("标题")
private String blogTitle;
/**
* 内容
*/
@ColumnWidth(20)
@ExcelProperty("内容")
private String blogContent;
/**
* 时间
*/
@ColumnWidth(10)
@ExcelProperty("时间")
private Date blogDate;
/**
* 是否已删除
*/
// @ExcelIgnore
@ColumnWidth(10)
@ExcelProperty("删除标记")
private Integer isDeleted;
}
excel导出测试,有标题无数据
@GetMapping("export-notice")
public void exportNotice(HttpServletResponse response) {
List<NoticeExcel> list = new ArrayList<>();
ExcelUtil.export(response, "导出数据", "数据表", list, NoticeExcel.class);
}
excel导出数据测试,进行数据填充
@GetMapping("/export-blog2")
public void exportNotice2(HttpServletResponse response) {
List<BlogExcel> list = new ArrayList<>();
BlogExcel ne1 = new BlogExcel();
ne1.setBlogTitle("标题1");
ne1.setBlogContent("发布通知");
ne1.setBlogDate(DateUtil.now());
BlogExcel ne2 = new BlogExcel();
ne2.setBlogTitle("标题2");
ne2.setBlogContent("批转通知");
ne2.setBlogDate(DateUtil.now());
list.add(ne1);
list.add(ne2);
ExcelUtil.export(response, "test导出数据", "test数据表", list, BlogExcel.class);
}
读取excel文件并返回list数据
@PostMapping("read-blog")
public R<List<BlogExcel>> readNotice(MultipartFile file) {
List<BlogExcel> list = ExcelUtil.read(file, BlogExcel.class);
return R.data(list);
}