您觉得本文档还缺少什么内容?可以自己补充~
自动回显
实现代码参考: bc-util-echo-starter 模块.
使用场景
用户表中存储了岗位id(岗位表的主键)、部门id、学历(字典表的code)、民族等字段, 但在 列表页、详情页回显时,需要显示 岗位名、部门名、创建人名、学历名称等。如下表:
用户表(c_user): (字段: id, name, station_id, org_id, nation, education, position_status )
ID | 名称 | 岗位 | 部门 | 民族 | 学历 | 在职状态 |
---|---|---|---|---|---|---|
1 | 测试 | 研发 | 研发部 | 汉族 | 本科 | 离职 |
传统的解决思路:
- 表关联查询
- 冗余字段
- 前端自己解决回显问题
- 先单表查询用户的数据,然后在java代码中,循环提取需要回显的org_id、station_id、字典code等, 在批量去查询这些信息后,在循环set到对应的字段中.
解决方案
本项目提供了bc-util-echo-starter模块来解决上面的第四个方案。
- pom中加入依赖
- yml 开启配置, 更多更详细的配置参考
EchoProperties
bc:
echo:
# 是否开启 EchoService 功能
enabled: true
# 是否开启EchoResult
aop-enabled: true
- User实体实现
EchoVO
接口,并在User中加入echoMap .
public class User extends Entity<Long> implements EchoVO {
@TableField(exist = false)
private Map<String, Object> echoMap = new HashMap<>();
// ...
}
- 在 station_id, org_id, nation, education, position_status 等需要回显的字段上标记注解: @Echo
@Echo(api = "orgServiceImpl", beanClass = Org.class)
private Long orgId;
@Echo(api = "stationServiceImpl")
private Long stationId;
@Echo(api = "dictionaryServiceImpl", dictType = DictionaryType.NATION)
private String nation;
@Echo(api = "dictionaryServiceImpl", dictType = DictionaryType.EDUCATION)
private String education;
@Echo(api = "dictionaryServiceImpl", dictType = DictionaryType.POSITION_STATUS)
private String positionStatus;
- 若User表所在的服务,跟Org、Station、Dictionary表不在同一个服务,请参考5.1,否则参考5.2 。 (由于本项目中,他们是在同一个authority-server服务,所以使用5.2的方式来实现)
5.1. 将上面
@Echo
注解的api字段,改成OpenFeign的全类名如:com.becypress.product.oauth.api.OrgApi
、com.becypress.product.oauth.api.StationApi
、com.becypress.product.oauth.api.DictionaryApi
,并将它继承LoadService接口。
@FeignClient(name="bc-authority-server",path="/dictionary")
public interface DictionaryApi extends LoadService {
@Override
@GetMapping("/findByIds")
Map<Serializable, Object> findByIds(@RequestParam(value = "ids") Set<Serializable> ids);
}
@FeignClient(name="bc-authority-server",path="/org")
public interface OrgApi extends LoadService {
@Override
@GetMapping("/findByIds")
Map<Serializable, Object> findByIds(@RequestParam(value = "ids") Set<Serializable> ids);
}
@FeignClient(name="bc-authority-server",path="/station")
public interface StationApi extends LoadService {
@Override
@GetMapping("/findByIds")
Map<Serializable, Object> findByIds(@RequestParam(value = "ids") Set<Serializable> ids);
}
然后在添加Controller,如(EchoController)。代码
public class EchoController {
@GetMapping("/org/findByIds")
public Map<Serializable, Object> findUserByIds(@RequestParam(value = "ids") Set<Serializable> ids) {
return orgService.findByIds(ids);
}
//....
}
5.2. 将上面@Echo
注解中的api字段,实现LoadService接口。
a. Service 继承LoadService
public interface OrgService extends SuperCacheService<Org>, LoadService {}
public interface StationService extends SuperCacheService<Station>, LoadService {}
public interface DictionaryService extends SuperService<Dictionary>, LoadService {}
b. ServiceImpl 实现
public class DictionaryServiceImpl extends SuperServiceImpl<DictionaryMapper, Dictionary> implements DictionaryService {
@Override
public Map<Serializable, Object> findByIds(Set<Serializable> types) {
if (types.isEmpty()) {
return Collections.emptyMap();
}
Map<Serializable, Object> codeValueMap = new HashMap<>();
types.forEach(type -> {
Function<CacheHashKey, Map<String, String>> fun = (ck) -> {
LbqWrapper<Dictionary> wrap = Wraps.<Dictionary>lbQ().eq(Dictionary::getType, type);
List<Dictionary> list = list(wrap);
return CollHelper.uniqueIndex(list, Dictionary::getCode, Dictionary::getName);
};
Map<String, String> map = cachePlusOps.hGetAll(new DictionaryTypeCacheKeyBuilder().hashKey(type), fun);
map.forEach((value, txt) -> codeValueMap.put(StrUtil.join(ips.getDictSeparator(), type, value), txt));
});
return codeValueMap;
}
}
// OrgServiceImpl 和 StationServiceImpl 的实现参考源码
- 然后在UserController的page方法中,调用echoService.action
public class UserController {
@Override
public void query(PageParams<UserPageQuery> params, IPage<User> page, Long defSize) {
// 先查数据库, 将page查出数据
// 手动调用此方法实现数据回显
echoService.action(page);
}
}
- 调用该接口后,会在User的echoMap中put需要显示的值。
原理
- 项目启动时,EchoService构造方法通过策略模式注入Map
strategyMap, 这个strategyMap的key就是 @Echo注解中的api 字段, strategyMap的value就是所有实现了LoadService接口的实现类 或 OpenFeign。
注意: 实现LoadService的类,必须被Spring管理!
项目运行时, 调用了echoService.action(obj) 方法后,实现echoMap的填充。
echoService.action
方法逻辑如下:
/**
* 回显数据的3个步骤:(出现回显失败时,认真debug该方法)
* <p>
* 1. parse: 通过反射将obj的字段上标记了 @Echo 注解的字段解析出来, 封装到typeMap中
* 2. load: 依次查询待回显的数据
* 3. write: 将查询出来的结果 反射或put 到obj的 字段或echoMap 中
* <p>
* 注意:若对象中需要回显的字段之间出现循环引用,很可能发生异常,所以请保证不要出现循环引用!!!
*
* @param obj 需要回显的参数,支持 自定义对象(User)、集合(List<User>、Set<User>)、IPage
* @param ignoreFields 忽略字段
*/
public void action(Object obj, String... ignoreFields) {//...}
- 最后要自己实现LoadService的findByIds方法
常见问题
- 跨服务回显时,@Echo中的api需要指定为 FeignClient 的全类名。
如:消息服务,需要回显权限服务的 用户名:@Echo(api="com.becypress.product.oauth.api.UserApi")
- 单个服务回显时,@Echo中的api需要指定为 ServiceImpl 的Spring id。
如:权限服务,需要回显权限服务的 用户名:@Echo(api="userServiceImpl")