高效稳定 · 简洁易用 · 灵活扩展

项目地址:lemon-echo微服务架构

1、服务采用rest + json返回数据,正常数据直接返回数据对象,提示码/异常统一封装ResultError返回,正常状况下controller只需返回正常数据对象就可以,service业务异常抛出由框架DefaultGlobalExceptionHandler统一处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
带结果返回实例
@GetMapping("/menus")
public List<Menu> getMenus(Long projectId) {
List<Menu> list = menuService.getMenus(projectId);
return list;
}

无结果返回实例:
@PostMapping("/code")
public void mobileCode(AuthType authType, String mobile) {
mobileCodeService.sendMobileCode(authType, mobile);
}

业务代码实例:
public void sendMobileCode(final AuthType authType, final Long userId) {
String mobile = userAuthService.getMobileByUserId(userId);
if (StringUtils.isEmpty(mobile)) {
throw new BizServiceException(F11615);
}
this.sendMobileCode(authType, mobile);
}

异常码返回Json(http状态码大于300):
{
code: 11615,
msg: "未绑定手机号码"
}

2、服务功能配置采用预配置方式,需继承基础功能包内的配置类并注解@Configuration

1
2
3
4
@Configuration
public class MybatisConfig extends MybatisConfigurator {

}

3、服务引入auth-rpc进行权限校验,在WebMvcConfig配置相应的权限拦截器即可实现,需要控制权限的接口仅需加上注解@RequestPermissions({xxx})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
配置权限拦截:
@Configuration
public class WebMvcConfig extends WebMvcConfigurator {

@Bean
public PermissionInterceptor permissionInterceptor() {
return new PermissionInterceptor();
}
/**加入拦截器 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(permissionInterceptor())
.addPathPatterns("/portal/**");
}
}

接口权限配置
@RequestPermissions({"user_freeze"})
@PostMapping("/portal/user/freeze")
public void freeze(Long userId, FreezeTime freezeTime, String reason) {
userFreezeService.freeze(userId, freezeTime, reason);
}

4、rabbit消息队列在实际开发中,绝大部分情况下仅需使用topic模式和延时模式,框架封装并简化了rabbit的topic模式和延时模式功能,生产和消费时的方法采用相同的方法,降低多种方法的配置和学习成本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
消费端声明消息队列
@Configuration
public class RabbitConfig extends RabbitConfigurator {

@Override
public Collection<QueueBinding> getQueueBinding() {
List<QueueBinding> queueBindings = new ArrayList<>();
queueBindings.add(new QueueBinding(USER_UNFREEZE, "user.topic", "user.unfreeze"));
return queueBindings;
}
}

消费端消费队列消息
@RabbitListener(queues = {USER_UNFREEZE})
public void unfreezeUser(Long userId) {
userAuthService.unfreeze(userId);
}

生产端生产队列消息
public void unfreezeUser(Long userId) {
rabbitTopicSender.send(USER_UNFREEZE, userId);
}

注:

  1. RoutingKey命名规则:生产服务名称.业务功能
    如:USER_UNFREEZE = “user.unfreeze”;
  2. Queue命名规则:消费服务名称_RoutingKey
    如:USER_UNFREEZE = “auth_user.unfreeze”;

5、分布式锁采用redis特性作为锁凭证中心,封装了@DistributionLock和@LockKey降低对系统业务的侵入性,同时利用LockKey参数细分控制每个锁元素,增大服务的吞吐量。

1
2
3
4
5
帐户入账时锁定对应帐户类型+用户ID
@DistributionLock
public void increase(@LockKey AccountType accountType, @LockKey Long userId, AccountTradePo accountTrade) {

}

6、利用@cache进行数据缓存,可选择使用本地caffeine缓存器和远程redis缓存器,根据业务灵活使用缓存可以保障业务对高效稳定。

1
2
3
4
5
6
7
8
9
@Cacheable(value="userCaches", key="'user_' + #userId", cacheManager="caffeineCacheManager")
public UserPo getUserInfoById(Long userId) {
return userPo;
}

@Cacheable(value="userCaches", key="'user_' + #userId", cacheManager="redisCacheManager")
public UserPo getUserInfoById(Long userId) {
return userPo;
}

7、定时任务采用xxl-job分布式调度中心,利用统一调度策略(单点执行/分片执行),完成定时业务需求。

1
2
3
4
5
@XxlJob("pendUnFreezeUserHandler")
public ReturnT<String> pendUnFreezeUserHandler(String param) throws Exception {
XxlJobLogger.log("job exec unfreeze user.");
return ReturnT.SUCCESS;
}

8、项目pom引入auth-rpc后,在WebMvcConfig使用拦截器设置需要的权限规则

1
2
3
4
5
6
7
8
9
10
11
@Bean
public PermissionInterceptor permissionInterceptor() {
return new PermissionInterceptor();
}

/**加入拦截器 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(permissionInterceptor()).addPathPatterns("/portal/**");
}

9、接口请求时经过授权检查会解析x-auth-token,在请求的header参数增加用户ID参数x-user-id,业务参数使用时可以直接获取改值。

1
2
3
4
@PostMapping("/api/withdraw/apply")
public void applyWithdraw(@RequestHeader(value = AUTH_USER_ID) Long userId,
Long bankCardId, BigDecimal coins) {
}

10、接口请求数据均需要携带header参数:

1
2
3
4
5
6
7
8
9
10
AUTH_SCOPE = "x-auth-scope";  //授权作用域
AUTH_TOKEN = "x-auth-token"; //访问授权码
CLIENT_NAME = "x-client-name"; //客户端名称
CLIENT_IMEI = "x-client-imei"; //客户端设备号
CLIENT_TYPE = "x-client-type"; //客户端类型
CLIENT_VERSION = "x-client-version"; //客户端版本
APP_VERSION = "x-app-version"; //客户端应用版本
APP_CHANNEL = "x-app-channel"; //客户端应用渠道
BUNDLE_ID = "x-bundle-id"; //app包名(IOS: bundle id, 安卓: package name)
TIMESTAMP = "x-timestamp"; //时间戳