1. 完成 auth 鉴权逻辑

2. 完成 admin 获取 Admin 上下文
3. 完成 user 获取 User 上下文
This commit is contained in:
YunaiV 2020-04-23 21:18:48 +08:00
parent a545d673ab
commit eb86ae7cbc
104 changed files with 815 additions and 1256 deletions

View File

@ -28,12 +28,17 @@ public class CollectionUtil {
return from.stream().map(func).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMap(List<T> from, Function<T, K> keyFunc) {
return from.stream().collect(Collectors.toMap(keyFunc, item -> item));
}
public static <T, K, V> Map<K, V> convertMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc));
}
public static <T, K> Map<K, T> convertMap(List<T> from, Function<T, K> keyFunc) {
return from.stream().collect(Collectors.toMap(keyFunc, item -> item));
public static <T, K, V> Map<K, List<V>> convertMultiMap(List<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
return from.stream().collect(Collectors.groupingBy(keyFunc,
Collectors.mapping(valueFunc, Collectors.toList())));
}
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {

View File

@ -1,6 +1,9 @@
package cn.iocoder.mall.security.config;
import cn.iocoder.mall.security.core.interceptor.AccountAuthInterceptor;
import cn.iocoder.mall.security.core.interceptor.AdminDemoInterceptor;
import cn.iocoder.mall.security.core.interceptor.AdminSecurityInterceptor;
import cn.iocoder.mall.security.core.interceptor.UserSecurityInterceptor;
import cn.iocoder.mall.web.config.CommonWebAutoConfiguration;
import cn.iocoder.mall.web.core.constant.CommonMallConstants;
import org.slf4j.Logger;
@ -30,6 +33,21 @@ public class CommonSecurityAutoConfiguration implements WebMvcConfigurer {
return new AccountAuthInterceptor(false);
}
@Bean
public AdminSecurityInterceptor adminSecurityInterceptor() {
return new AdminSecurityInterceptor();
}
@Bean
public UserSecurityInterceptor userSecurityInterceptor() {
return new UserSecurityInterceptor();
}
@Bean
public AdminDemoInterceptor adminDemoInterceptor() {
return new AdminDemoInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// AccountAuthInterceptor 拦截器
@ -38,6 +56,18 @@ public class CommonSecurityAutoConfiguration implements WebMvcConfigurer {
registry.addInterceptor(this.adminAccountAuthInterceptor())
.addPathPatterns(CommonMallConstants.ROOT_PATH_ADMIN + "/**");
logger.info("[addInterceptors][加载 AccountAuthInterceptor 拦截器完成]");
// AdminSecurityInterceptor 拦截器
registry.addInterceptor(this.adminSecurityInterceptor())
.addPathPatterns(CommonMallConstants.ROOT_PATH_ADMIN + "/**");
logger.info("[addInterceptors][加载 AdminSecurityInterceptor 拦截器完成]");
// UserSecurityInterceptor 拦截器
registry.addInterceptor(this.userAccountAuthInterceptor())
.addPathPatterns(CommonMallConstants.ROOT_PATH_USER + "/**");
logger.info("[addInterceptors][加载 UserSecurityInterceptor 拦截器完成]");
// AdminDemoInterceptor 拦截器
registry.addInterceptor(this.adminDemoInterceptor())
.addPathPatterns(CommonMallConstants.ROOT_PATH_ADMIN + "/**");
logger.info("[addInterceptors][加载 AdminDemoInterceptor 拦截器完成]");
}
}

View File

@ -19,6 +19,6 @@ public @interface RequiresPermissions {
*
* @return 权限标识数组
*/
String[] value();
String[] value() default {};
}

View File

@ -14,9 +14,5 @@ public class AdminSecurityContext {
* 管理员编号
*/
private Integer adminId;
/**
* 管理员账号
*/
private String username;
}

View File

@ -8,7 +8,9 @@ import cn.iocoder.mall.security.core.annotation.RequiresAuthenticate;
import cn.iocoder.mall.security.core.annotation.RequiresNone;
import cn.iocoder.mall.security.core.annotation.RequiresPermissions;
import cn.iocoder.mall.system.biz.enums.SystemErrorCodeEnum;
import cn.iocoder.mall.system.rpc.api.authorization.AuthorizationRPC;
import cn.iocoder.mall.system.rpc.api.oauth2.OAuth2RPC;
import cn.iocoder.mall.system.rpc.request.authorization.AuthorizationCheckPermissionsRequest;
import cn.iocoder.mall.system.rpc.request.oauth2.OAuth2AccessTokenAuthenticateRequest;
import cn.iocoder.mall.system.rpc.response.oauth2.OAuth2AccessTokenResponse;
import cn.iocoder.mall.web.core.util.CommonWebUtil;
@ -21,6 +23,7 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
public class AccountAuthInterceptor extends HandlerInterceptorAdapter {
@ -28,7 +31,8 @@ public class AccountAuthInterceptor extends HandlerInterceptorAdapter {
@Reference(validation = "true", version = "${dubbo.consumer.OAuth2RPC.version}")
private OAuth2RPC oauth2RPC;
@Reference(validation = "true", version = "${dubbo.consumer.AuthorizationRPC.version}")
private AuthorizationRPC authorizationRPC;
/**
* 是否默认要求认证
@ -51,7 +55,7 @@ public class AccountAuthInterceptor extends HandlerInterceptorAdapter {
// 判断是否需要认证
this.checkAuthenticate(handlerMethod, accountId);
// 判断是否需要权限
this.checkPermission(handlerMethod, accountId);
return true;
}
@ -63,12 +67,12 @@ public class AccountAuthInterceptor extends HandlerInterceptorAdapter {
// 执行认证
OAuth2AccessTokenAuthenticateRequest oauth2AccessTokenAuthenticateRequest = new OAuth2AccessTokenAuthenticateRequest()
.setAccessToken(accessToken).setIp(HttpUtil.getIp(request));
CommonResult<OAuth2AccessTokenResponse> oauth2AccessTokenResponseResult = oauth2RPC.authenticate(oauth2AccessTokenAuthenticateRequest);
if (oauth2AccessTokenResponseResult.isError()) { // TODO 有一个问题点假设 token 认证失败但是该 url 是无需认证的是不是一样能够执行过去
throw ServiceExceptionUtil.exception(oauth2AccessTokenResponseResult);
CommonResult<OAuth2AccessTokenResponse> oauth2AccessTokenResult = oauth2RPC.authenticate(oauth2AccessTokenAuthenticateRequest);
if (oauth2AccessTokenResult.isError()) { // TODO 有一个问题点假设 token 认证失败但是该 url 是无需认证的是不是一样能够执行过去
throw ServiceExceptionUtil.exception(oauth2AccessTokenResult);
}
// 设置账号编号
Integer accountId = oauth2AccessTokenResponseResult.getData().getAccountId();
Integer accountId = oauth2AccessTokenResult.getData().getAccountId();
CommonWebUtil.setAccountId(request, accountId);
return accountId;
}
@ -96,7 +100,12 @@ public class AccountAuthInterceptor extends HandlerInterceptorAdapter {
return;
}
// 权限验证
AuthorizationCheckPermissionsRequest authorizationCheckPermissionsRequest = new AuthorizationCheckPermissionsRequest()
.setAccountId(accountId).setPermissions(Arrays.asList(permissions));
CommonResult<Boolean> authorizationCheckPermissionsResult = authorizationRPC.checkPermissions(authorizationCheckPermissionsRequest);
if (authorizationCheckPermissionsResult.isError()) { // TODO 有一个问题点假设 token 认证失败但是该 url 是无需认证的是不是一样能够执行过去
throw ServiceExceptionUtil.exception(authorizationCheckPermissionsResult);
}
}
}

View File

@ -1,29 +1,28 @@
package cn.iocoder.mall.system.sdk.interceptor;
package cn.iocoder.mall.security.core.interceptor;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.system.api.constant.AdminConstants;
import cn.iocoder.mall.system.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.system.sdk.context.AdminSecurityContextHolder;
import cn.iocoder.mall.security.core.context.AdminSecurityContextHolder;
import cn.iocoder.mall.system.biz.enums.SystemErrorCodeEnum;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
/**
* Admin 演示拦截器
*
* 这是个比较奇怪的拦截器用于演示的管理员账号禁止使用 POST 请求从而实现即达到阉割版的演示的效果又避免影响了数据
*/
@Component
public class AdminDemoInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (AdminConstants.USERNAME_DEMO.equals(AdminSecurityContextHolder.getContext().getUsername())
// Admin 编号等于 0 约定为演示账号
if (Objects.equals(AdminSecurityContextHolder.getContext().getAdminId(), 0)
&& request.getMethod().equalsIgnoreCase(HttpMethod.POST.toString())) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_DEMO_CAN_NOT_WRITE.getCode());
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.AUTHORIZATION_DEMO_PERMISSION_DENY.getCode());
}
return true;
}

View File

@ -1,5 +1,12 @@
package cn.iocoder.mall.security.core.interceptor;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.security.core.context.AdminSecurityContext;
import cn.iocoder.mall.security.core.context.AdminSecurityContextHolder;
import cn.iocoder.mall.system.rpc.api.admin.AdminRPC;
import cn.iocoder.mall.system.rpc.response.admin.AdminResponse;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
@ -7,16 +14,30 @@ import javax.servlet.http.HttpServletResponse;
public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获得 Admin 信息
@Reference(validation = "true", version = "${dubbo.consumer.AdminRPC.version}")
private AdminRPC adminRPC;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
Integer accountId = AdminSecurityContextHolder.getContext().getAdminId();
if (accountId != null) {
// 获得 Admin 信息
CommonResult<AdminResponse> adminResult = adminRPC.getAdminByAccountId(accountId);
if (adminResult.isError()) {
throw ServiceExceptionUtil.exception(adminResult);
}
// 设置到 SecurityContext
AdminResponse adminResponse = adminResult.getData();
AdminSecurityContext context = new AdminSecurityContext().setAdminId(adminResponse.getId());
AdminSecurityContextHolder.setContext(context);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 清空 SecurityContext
AdminSecurityContextHolder.clear();
}
}

View File

@ -1,5 +1,13 @@
package cn.iocoder.mall.security.core.interceptor;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.security.core.context.AdminSecurityContextHolder;
import cn.iocoder.mall.security.core.context.UserSecurityContext;
import cn.iocoder.mall.security.core.context.UserSecurityContextHolder;
import cn.iocoder.mall.system.rpc.api.user.UserRPC;
import cn.iocoder.mall.system.rpc.response.user.UserResponse;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
@ -7,15 +15,30 @@ import javax.servlet.http.HttpServletResponse;
public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
@Reference(validation = "true", version = "${dubbo.consumer.UserRPC.version}")
private UserRPC userRPC;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获得用户信息
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
Integer accountId = AdminSecurityContextHolder.getContext().getAdminId();
if (accountId != null) {
// 获得 Admin 信息
CommonResult<UserResponse> userResult = userRPC.getUserByAccountId(accountId);
if (userResult.isError()) {
throw ServiceExceptionUtil.exception(userResult);
}
// 设置到 SecurityContext
UserResponse userResponse = userResult.getData();
UserSecurityContext context = new UserSecurityContext().setUserId(userResponse.getId());
UserSecurityContextHolder.setContext(context);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 清空 SecurityContext
UserSecurityContextHolder.clear();
}
}

View File

@ -18,18 +18,6 @@
<artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>system-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>user-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<!-- Spring 核心 -->
<dependency>
@ -49,17 +37,6 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<optional>true</optional>
</dependency>
<!-- 监控相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -1,49 +0,0 @@
package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.servlet.CorsFilter;
import cn.iocoder.mall.system.sdk.interceptor.AdminDemoInterceptor;
import cn.iocoder.mall.spring.boot.web.interceptor.AccessLogInterceptor;
import cn.iocoder.mall.system.sdk.interceptor.AdminSecurityInterceptor;
import cn.iocoder.mall.spring.boot.web.handler.GlobalExceptionHandler;
import cn.iocoder.mall.spring.boot.web.handler.GlobalResponseBodyHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿未来可能考虑 REACTIVE
@ConditionalOnClass({
DispatcherServlet.class,
WebMvcConfigurer.class, // Spring MVC 容器
AdminSecurityInterceptor.class,
AccessLogInterceptor.class
}) // 有引入 system-sdk
public class AdminMVCAutoConfiguration implements WebMvcConfigurer {
@Bean
@ConditionalOnMissingBean(AdminSecurityInterceptor.class)
public AdminSecurityInterceptor adminSecurityInterceptor() {
return new AdminSecurityInterceptor();
}
@Bean
@ConditionalOnMissingBean(AdminDemoInterceptor.class)
public AdminDemoInterceptor adminDemoInterceptor() {
return new AdminDemoInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
registry.addInterceptor(adminSecurityInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
registry.addInterceptor(adminDemoInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_ADMIN + "/**");
}
}

View File

@ -1,58 +0,0 @@
package cn.iocoder.mall.spring.boot.web;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.servlet.CorsFilter;
import cn.iocoder.mall.spring.boot.web.interceptor.AccessLogInterceptor;
import cn.iocoder.mall.spring.boot.web.handler.GlobalExceptionHandler;
import cn.iocoder.mall.spring.boot.web.handler.GlobalResponseBodyHandler;
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) // TODO 芋艿未来可能考虑 REACTIVE
@ConditionalOnClass({DispatcherServlet.class, WebMvcConfigurer.class, // Spring MVC 容器
UserSecurityInterceptor.class, // 有引入 user-sdk
AccessLogInterceptor.class}) // 有引入 system-sdk
public class UserMVCAutoConfiguration implements WebMvcConfigurer {
@Bean
// @ConditionalOnMissingBean(AccessLogInterceptor.class)
public AccessLogInterceptor userAccessLogInterceptor() {
return new AccessLogInterceptor();
}
@Bean
@ConditionalOnMissingBean(UserSecurityInterceptor.class)
public UserSecurityInterceptor userSecurityInterceptor() {
return new UserSecurityInterceptor();
}
@Bean
@ConditionalOnMissingBean(GlobalResponseBodyHandler.class)
public GlobalResponseBodyHandler globalReturnValueHandler() {
return new GlobalResponseBodyHandler();
}
@Bean
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userAccessLogInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_USER + "/**");
registry.addInterceptor(userSecurityInterceptor()).addPathPatterns(MallConstants.ROOT_PATH_USER + "/**");
}
}

View File

@ -1,4 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.mall.spring.boot.web.AdminMVCAutoConfiguration, \
cn.iocoder.mall.spring.boot.web.UserMVCAutoConfiguration, \
cn.iocoder.mall.spring.boot.metrics.MetricsAutoConfiguration

View File

@ -15,7 +15,7 @@
<modules>
<module>system-application</module>
<module>system-sdk</module>
<!-- <module>system-sdk</module>-->
<!-- <module>system-service-api</module>-->
<!-- <module>system-service-impl</module>-->
<module>system-rpc-api</module>

View File

@ -38,9 +38,7 @@ public enum SystemErrorCodeEnum implements ServiceExceptionUtil.Enumerable {
// ADMIN_DELETE_ONLY_DISABLE(1002002004, "只有关闭的账号才可以删除"),
// ADMIN_ADMIN_STATUS_CAN_NOT_UPDATE(1002002005, "管理员的账号状态不允许变更"),
// ADMIN_ASSIGN_ROLE_NOT_EXISTS(1002002006, "分配员工角色时,有角色不存在"),
// ADMIN_INVALID_PERMISSION(1002002007, "没有该操作权限"),
// ADMIN_ADMIN_CAN_NOT_UPDATE(1002002008, "管理员的账号不允许变更"),
// ADMIN_DEMO_CAN_NOT_WRITE(1002002009, "演示账号暂不允许写操作。欢迎加入我们的交流群http://t.cn/EKEr5WE"),
// ========== 资源模块 1002003000 ==========
// RESOURCE_NAME_DUPLICATE(1002003000, "已经存在该名字的资源"),
@ -72,8 +70,15 @@ public enum SystemErrorCodeEnum implements ServiceExceptionUtil.Enumerable {
// DEPT_NOT_EXITS(1002007003, "当前部门不存在"),
// DEPT_EXITS_CHILDREN(1002007004, "当前部门存在子部门"),
// DEPT_PARENT_NOT_LEGAL(1002007005, "父级部门不合法"),
// ========== 授权模块 1002008000 ==========
AUTHORIZATION_PERMISSION_DENY(1002008001, "没有该操作权限"),
AUTHORIZATION_DEMO_PERMISSION_DENY(1002008002, "演示账号暂不允许写操作。欢迎加入我们的交流群http://t.cn/EKEr5WE"),
;
private final int code;
private final String message;

View File

@ -0,0 +1,52 @@
package cn.iocoder.mall.system.biz.bo.authorization;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 授权模块 - 资源信息 BO
*/
@Data
@Accessors(chain = true)
public class ResourceBO {
/**
* 资源编号
*/
private Integer id;
/**
* 菜单名
*/
private String name;
/**
* 权限标识
*/
private String permission;
/**
* 资源类型
*/
private Integer type;
/**
* 排序
*/
private Integer sort;
/**
* 父级资源编号
*/
private Integer pid;
/**
* 前端路由
*/
private String route;
/**
* 菜单图标
*/
private String icon;
/**
* 创建时间
*/
private Date createTime;
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.mall.system.biz.bo.authorization;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 授权模块 - 角色信息 BO
*/
@Data
@Accessors(chain = true)
public class RoleBO {
/**
* 角色编号
*/
private Integer id;
/**
* 角色名字
*/
private String name;
/**
* 角色编码
*/
private String code;
/**
* 添加时间
*/
private Date createTime;
}

View File

@ -4,7 +4,7 @@ import lombok.Data;
import lombok.experimental.Accessors;
/**
* TODO 注释
* User 模块 - User 信息 BO
*/
@Data
@Accessors(chain = true)

View File

@ -21,9 +21,9 @@ public interface SmsSignConvert {
SmsSignConvert INSTANCE = Mappers.getMapper(SmsSignConvert.class);
@Mappings({})
SmsSignBO convert(SmsSignDO smsSignDO);
SmsSignBO convert(SmsSignDO bean);
@Mappings({})
List<ListSmsSignBO> convert(List<SmsSignDO> smsSignDOList);
List<ListSmsSignBO> convert(List<SmsSignDO> beans);
}

View File

@ -11,8 +11,8 @@ public interface AccountConvert {
AccountConvert INSTANCE = Mappers.getMapper(AccountConvert.class);
AccountBO convert(AccountDO accountDO);
AccountBO convert(AccountDO bean);
AccountDO convert(AccountCreateDTO accountCreateDTO);
AccountDO convert(AccountCreateDTO bean);
}

View File

@ -10,6 +10,6 @@ public interface AdminConvert {
AdminConvert INSTANCE = Mappers.getMapper(AdminConvert.class);
AdminBO convert(AdminDO adminDO);
AdminBO convert(AdminDO bean);
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.mall.system.biz.convert.authorization;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.dataobject.authorization.ResourceDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface ResourceConvert {
ResourceConvert INSTANCE = Mappers.getMapper(ResourceConvert.class);
ResourceBO convert(ResourceDO bean);
List<ResourceBO> convertList(List<ResourceDO> beans);
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.mall.system.biz.convert.authorization;
import cn.iocoder.mall.system.biz.bo.authorization.RoleBO;
import cn.iocoder.mall.system.biz.dataobject.authorization.RoleDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface RoleConvert {
RoleConvert INSTANCE = Mappers.getMapper(RoleConvert.class);
RoleBO convert(RoleDO bean);
List<RoleBO> convertList(List<RoleDO> beans);
}

View File

@ -10,6 +10,6 @@ public interface OAuth2Convert {
OAuth2Convert INSTANCE = Mappers.getMapper(OAuth2Convert.class);
OAuth2AccessTokenBO convert(OAuth2AccessTokenDO accessTokenDO);
OAuth2AccessTokenBO convert(OAuth2AccessTokenDO bean);
}

View File

@ -12,8 +12,8 @@ public interface SystemLogConvert {
SystemLogConvert INSTANCE = Mappers.getMapper(SystemLogConvert.class);
AccessLogDO convert(AccessLogAddDTO accessLogAddDTO);
AccessLogDO convert(AccessLogAddDTO bean);
ExceptionLogDO convert(ExceptionLogAddDTO exceptionLogAddDTO);
ExceptionLogDO convert(ExceptionLogAddDTO bean);
}

View File

@ -17,6 +17,6 @@ public interface UserConvert {
@Mapping(source = "accessTokenBO", target = "token")
UserAuthenticateBO convert(UserBO userBO, OAuth2AccessTokenBO accessTokenBO);
UserBO convert(UserDO userDO);
UserBO convert(UserDO bean);
}

View File

@ -1,10 +1,17 @@
package cn.iocoder.mall.system.biz.dao.admin;
import cn.iocoder.mall.system.biz.dataobject.admin.AdminDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface AdminMapper extends BaseMapper<AdminDO> {
default AdminDO selectByAccountId(Integer accountId) {
return selectOne(new QueryWrapper<AdminDO>()
.eq("account_id", accountId)
);
}
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.mall.system.biz.dao.authorization;
import cn.iocoder.mall.system.biz.dataobject.authorization.AccountRoleDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
@Repository
public interface AccountRoleMapper extends BaseMapper<AccountRoleDO> {
default List<AccountRoleDO> selectByAccountId( Integer accountId) {
return selectList(new QueryWrapper<AccountRoleDO>().eq("account_id", accountId));
}
default List<AccountRoleDO> selectListByAccountIds(Collection<Integer> accountIds) {
return selectList(new QueryWrapper<AccountRoleDO>().in("account_id", accountIds));
}
default int deleteByAccountId(Integer accountId) {
return delete(new QueryWrapper<AccountRoleDO>().eq("account_id", accountId));
}
default int deleteByRoleId(Integer roleId) {
return delete(new QueryWrapper<AccountRoleDO>().eq("role_id", roleId));
}
/**
* 批量插入因为 MyBaits Plus 的批量插入是基于 Service 实现所以只好写 XML
*
* @param accountRoleDOs 数组
*/
int insertList(@Param("accountRoleDOs") List<AccountRoleDO> accountRoleDOs);
}

View File

@ -1,12 +1,13 @@
package cn.iocoder.mall.admin.dao;
package cn.iocoder.mall.system.biz.dao.authorization;
import cn.iocoder.common.framework.mybatis.QueryWrapperX;
import cn.iocoder.mall.admin.dataobject.ResourceDO;
import cn.iocoder.mall.system.biz.dataobject.authorization.ResourceDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@ -17,18 +18,18 @@ public interface ResourceMapper extends BaseMapper<ResourceDO> {
List<ResourceDO> selectListByTypeAndRoleIds(@Param("type") Integer type,
@Param("roleIds") Set<Integer> roleIds);
default List<ResourceDO> selectListByPermission(String permission) {
return selectList(new QueryWrapperX<ResourceDO>().like("permissions", permission));
default ResourceDO selectByPermission(String permission) {
return selectOne(new QueryWrapper<ResourceDO>().eq("permission", permission));
}
default List<ResourceDO> selectListByPermissions(Collection<String> permissions) {
return selectList(new QueryWrapper<ResourceDO>().in("permission", permissions));
}
default List<ResourceDO> selectListByType(Integer type) {
return selectList(new QueryWrapperX<ResourceDO>().eqIfPresent("type", type));
}
default List<ResourceDO> selectListByIds(Set<Integer> ids) {
return selectList(new QueryWrapper<ResourceDO>().in("id", ids));
}
default int selectCountByPid(Integer pid) {
return selectCount(new QueryWrapper<ResourceDO>().eq("pid", pid));
}

View File

@ -1,8 +1,8 @@
package cn.iocoder.mall.admin.dao;
package cn.iocoder.mall.system.biz.dao.authorization;
import cn.iocoder.common.framework.mybatis.QueryWrapperX;
import cn.iocoder.mall.system.api.dto.role.RolePageDTO;
import cn.iocoder.mall.admin.dataobject.RoleDO;
import cn.iocoder.mall.system.biz.dataobject.authorization.RoleDO;
import cn.iocoder.mall.system.biz.dto.authorization.RolePageDTO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;

View File

@ -1,6 +1,6 @@
package cn.iocoder.mall.admin.dao;
package cn.iocoder.mall.system.biz.dao.authorization;
import cn.iocoder.mall.admin.dataobject.RoleResourceDO;
import cn.iocoder.mall.system.biz.dataobject.authorization.RoleResourceDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
@ -23,7 +23,7 @@ public interface RoleResourceMapper extends BaseMapper<RoleResourceDO> {
return selectList(new QueryWrapper<RoleResourceDO>().eq("resource_id", resourceId));
}
default List<RoleResourceDO> selectListByResourceId(Collection<Integer> resourceIds) {
default List<RoleResourceDO> selectListByResourceIds(Collection<Integer> resourceIds) {
return selectList(new QueryWrapper<RoleResourceDO>().in("resource_id", resourceIds));
}

View File

@ -4,14 +4,16 @@ import cn.iocoder.common.framework.dataobject.DeletableDO;
import cn.iocoder.mall.system.biz.dataobject.account.AccountDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* {@link AccountDO} {@link RoleDO} 的关联表
*/
@TableName("admin_role")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("account_role")
public class AccountRoleDO extends DeletableDO {
/**

View File

@ -1,6 +1,7 @@
package cn.iocoder.mall.system.biz.dataobject.authorization;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import cn.iocoder.mall.system.biz.enums.authorization.ResourceTypeEnum;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -37,7 +38,7 @@ public class ResourceDO extends DeletableDO {
/**
* 资源类型
*
* 关联 {@link Resource}
* 关联 {@link ResourceTypeEnum}
*/
private Integer type;
/**
@ -51,23 +52,16 @@ public class ResourceDO extends DeletableDO {
*/
private Integer pid;
/**
* 前端路由
*
*
* 目前当且仅当资源类型为菜单才会生效 handler 配置为界面 URL 或者前端组件名或者前端的路由
* 目前当且仅当资源类型为 {@link ResourceTypeEnum#MENU} 才会生效
*/
private String handler;
private String route;
/**
* 图标
* 菜单图标
*
* 目前当且仅当资源类型为菜单才会生效
* 目前当且仅当资源类型为 {@link ResourceTypeEnum#MENU} 才会生效
*/
private String icon;
/**
* 权限标识数组使用逗号分隔
*
* 例如system:admin:add
* 推荐格式为 ${系统}:${模块}:${操作}
*/
private String permissions;
}

View File

@ -3,14 +3,16 @@ package cn.iocoder.mall.system.biz.dataobject.authorization;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 角色实体
*/
@TableName("role")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("role")
public class RoleDO extends DeletableDO {
/**
@ -21,5 +23,9 @@ public class RoleDO extends DeletableDO {
* 角色名
*/
private String name;
/**
* 角色编码
*/
private String code;
}

View File

@ -3,14 +3,16 @@ package cn.iocoder.mall.system.biz.dataobject.authorization;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* {@link RoleDO} {@link ResourceDO} 的关联表
*/
@TableName("role_resource")
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("role_resource")
public class RoleResourceDO extends DeletableDO {
/**

View File

@ -1,6 +1,6 @@
package cn.iocoder.mall.system.biz.dataobject.user;
import cn.iocoder.common.framework.dataobject.BaseDO;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import cn.iocoder.mall.system.biz.dataobject.account.AccountDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@ -14,7 +14,7 @@ import lombok.experimental.Accessors;
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
public class UserDO extends BaseDO {
public class UserDO extends DeletableDO {
/**
* 用户编号

View File

@ -4,17 +4,18 @@ import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
import java.util.Collection;
/**
* OAuth2 模块 - 访问令牌认证 Request
* 授权模块 - 校验账号是否有权限 DTO
*/
@Data
@Accessors(chain = true)
public class AuthorizationCheckPermissionsDTO {
@NotNull(message = "访问令牌不能为空")
private String accessToken;
@NotNull(message = "IP 不能为空")
private String ip;
@NotNull(message = "账号编号不能为空")
private Integer accountId;
@NotNull(message = "权限不能为空")
private Collection<String> permissions;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.mall.system.api.dto.role;
package cn.iocoder.mall.system.biz.dto.authorization;
import cn.iocoder.common.framework.vo.PageParam;
import io.swagger.annotations.ApiModel;

View File

@ -0,0 +1,21 @@
package cn.iocoder.mall.system.biz.enums.authorization;
public enum RoleCodeEnum {
SUPER_ADMIN("SUPER_ADMIN"), // 超级管理员
;
/**
* 角色编码
*/
private final String code;
RoleCodeEnum(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}

View File

@ -7,6 +7,8 @@ import cn.iocoder.mall.system.biz.bo.admin.AdminBO;
*/
public interface AdminService {
AdminBO get(Integer id);
AdminBO getAdmin(Integer id);
AdminBO getAdminByAccountId(Integer accountId);
}

View File

@ -4,7 +4,6 @@ import cn.iocoder.mall.system.biz.bo.admin.AdminBO;
import cn.iocoder.mall.system.biz.convert.admin.AdminConvert;
import cn.iocoder.mall.system.biz.dao.admin.AdminMapper;
import cn.iocoder.mall.system.biz.dataobject.admin.AdminDO;
import cn.iocoder.mall.system.biz.service.admin.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -15,9 +14,15 @@ public class AdminServiceImpl implements AdminService {
private AdminMapper adminMapper;
@Override
public AdminBO get(Integer id) {
public AdminBO getAdmin(Integer id) {
AdminDO adminDO = adminMapper.selectById(id);
return AdminConvert.INSTANCE.convert(adminDO);
}
@Override
public AdminBO getAdminByAccountId(Integer accountId) {
AdminDO adminDO = adminMapper.selectByAccountId(accountId);
return AdminConvert.INSTANCE.convert(adminDO);
}
}

View File

@ -1,10 +0,0 @@
package cn.iocoder.mall.system.biz.service.admin;
/**
* 授权 Service 接口
*/
public class AuthorizationService {
}

View File

@ -1,14 +1,68 @@
package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.common.framework.util.CollectionUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.dao.authorization.AccountRoleMapper;
import cn.iocoder.mall.system.biz.dao.authorization.RoleResourceMapper;
import cn.iocoder.mall.system.biz.dataobject.authorization.AccountRoleDO;
import cn.iocoder.mall.system.biz.dataobject.authorization.RoleResourceDO;
import cn.iocoder.mall.system.biz.dto.authorization.AuthorizationCheckPermissionsDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.mall.system.biz.enums.SystemErrorCodeEnum.AUTHORIZATION_PERMISSION_DENY;
@Service
@Slf4j
public class AuthorizationServiceImpl implements AuthorizationService {
@Autowired
private AccountRoleMapper accountRoleMapper;
@Autowired
private RoleResourceMapper roleResourceMapper;
@Autowired
private RoleService roleService;
@Autowired
private ResourceService resourceService;
@Override
public void checkPermissions(AuthorizationCheckPermissionsDTO checkPermissionsDTO) {
// 查询管理员拥有的角色关联数据
List<AccountRoleDO> accountRoleDOs = accountRoleMapper.selectByAccountId(checkPermissionsDTO.getAccountId());
if (CollectionUtil.isEmpty(accountRoleDOs)) { // 如果没有角色默认无法访问
throw ServiceExceptionUtil.exception(AUTHORIZATION_PERMISSION_DENY);
}
Set<Integer> roleIds = CollectionUtil.convertSet(accountRoleDOs, AccountRoleDO::getRoleId);
// 判断是否为超管若是超管默认有所有权限
if (roleService.hasSuperAdmin(roleIds)) {
return;
}
// 查询权限对应资源
List<ResourceBO> resourceBOs = resourceService.getListByPermissions(checkPermissionsDTO.getPermissions());
if (CollectionUtil.isEmpty(resourceBOs)) { // 无对应资源则认为无需权限验证
log.warn("[checkPermissions][permission({}) 未配置对应资源]", checkPermissionsDTO.getPermissions());
return;
}
Set<Integer> permissionIds = CollectionUtil.convertSet(resourceBOs, ResourceBO::getId);
// 权限验证
List<RoleResourceDO> roleResourceDOs = roleResourceMapper.selectListByResourceIds(permissionIds);
if (CollectionUtil.isEmpty(roleResourceDOs)) { // 资源未授予任何角色必然权限验证不通过
throw ServiceExceptionUtil.exception(AUTHORIZATION_PERMISSION_DENY);
}
Map<Integer, List<Integer>> resourceRoleMap = CollectionUtil.convertMultiMap(roleResourceDOs,
RoleResourceDO::getResourceId, RoleResourceDO::getRoleId);
for (Map.Entry<Integer, List<Integer>> entry : resourceRoleMap.entrySet()) {
if (!CollectionUtil.containsAny(roleIds, entry.getValue())) { // 所以有任一不满足就验证失败抛出异常
throw ServiceExceptionUtil.exception(AUTHORIZATION_PERMISSION_DENY);
}
}
}
}

View File

@ -1,4 +1,12 @@
package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import java.util.Collection;
import java.util.List;
public interface ResourceService {
List<ResourceBO> getListByPermissions(Collection<String> permissions);
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.mall.system.biz.bo.authorization.ResourceBO;
import cn.iocoder.mall.system.biz.convert.authorization.ResourceConvert;
import cn.iocoder.mall.system.biz.dao.authorization.ResourceMapper;
import cn.iocoder.mall.system.biz.dataobject.authorization.ResourceDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
@Service
public class ResourceServiceImpl implements ResourceService {
@Autowired
private ResourceMapper resourceMapper;
@Override
public List<ResourceBO> getListByPermissions(Collection<String> permissions) {
List<ResourceDO> resourceDOs = resourceMapper.selectListByPermissions(permissions);
return ResourceConvert.INSTANCE.convertList(resourceDOs);
}
}

View File

@ -1,4 +1,20 @@
package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.mall.system.biz.bo.authorization.RoleBO;
import java.util.Collection;
import java.util.List;
public interface RoleService {
List<RoleBO> getRoleList(Collection<Integer> ids);
/**
* 判断指定角色是否包含超级管理员角色
*
* @param ids 角色编号数组
* @return 是否有超级管理员角色
*/
boolean hasSuperAdmin(Collection<Integer> ids);
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.mall.system.biz.service.authorization;
import cn.iocoder.mall.system.biz.bo.authorization.RoleBO;
import cn.iocoder.mall.system.biz.convert.authorization.RoleConvert;
import cn.iocoder.mall.system.biz.dao.authorization.RoleMapper;
import cn.iocoder.mall.system.biz.dataobject.authorization.RoleDO;
import cn.iocoder.mall.system.biz.enums.authorization.RoleCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
@Service
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public List<RoleBO> getRoleList(Collection<Integer> ids) {
List<RoleDO> roleDOs = roleMapper.selectBatchIds(ids);
return RoleConvert.INSTANCE.convertList(roleDOs);
}
@Override
public boolean hasSuperAdmin(Collection<Integer> ids) {
List<RoleDO> roleDOs = roleMapper.selectBatchIds(ids);
for (RoleDO roleDO : roleDOs) {
if (RoleCodeEnum.SUPER_ADMIN.getCode().equals(roleDO.getCode())) {
return true;
}
}
return false;
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.mall.system.biz.service.user;
import cn.iocoder.mall.system.biz.bo.user.UserAuthenticateBO;
import cn.iocoder.mall.system.biz.bo.user.UserBO;
import cn.iocoder.mall.system.biz.dto.oatuh2.OAuth2MobileCodeAuthenticateDTO;
/**
@ -10,4 +11,6 @@ public interface UserService {
UserAuthenticateBO authenticate(OAuth2MobileCodeAuthenticateDTO authenticateDTO);
UserBO getUserByAccountId(Integer accountId);
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.mall.system.biz.service.user;
import cn.iocoder.common.framework.constant.DeletedStatusEnum;
import cn.iocoder.mall.system.biz.bo.ouath2.OAuth2AccessTokenBO;
import cn.iocoder.mall.system.biz.bo.user.UserAuthenticateBO;
import cn.iocoder.mall.system.biz.bo.user.UserBO;
@ -36,9 +37,16 @@ public class UserServiceImpl implements UserService {
return UserConvert.INSTANCE.convert(userBO, accessTokenBO);
}
@Override
public UserBO getUserByAccountId(Integer accountId) {
UserDO userDO = userMapper.selectById(accountId);
return UserConvert.INSTANCE.convert(userDO);
}
private UserDO creatUser(Integer accountId) {
UserDO user = new UserDO();
user.setAccountId(accountId);
user.setDeleted(DeletedStatusEnum.DELETED_NO.getValue());
userMapper.insert(user);
return user;
}

View File

@ -17,4 +17,3 @@ mybatis-plus:
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: cn.iocoder.mall.system.biz.dataobject

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.system.biz.dao.authorization.AccountRoleMapper">
<insert id="insertList">
INSERT INTO account_role (
account_id, role_id, create_time, deleted
) VALUES
<foreach collection="accountRoleDOs" item="accountRole" separator=",">
(#{accountRole.accountId}, #{accountRole.roleId}, #{accountRole.createTime}, #{accountRole.deleted})
</foreach>
</insert>
</mapper>

View File

@ -0,0 +1,74 @@
package cn.iocoder.mall.system.rest.controller.datadict;
import cn.iocoder.common.framework.constant.MallConstants;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.security.core.annotation.RequiresPermissions;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(MallConstants.ROOT_PATH_ADMIN + "/data-dict")
@Api(tags = "管理员 - 数据字典 API")
public class AdminsDataDictController {
// @Reference(validation = "true", version = "${dubbo.provider.DataDictService.version}")
// private DataDictService dataDictService;
@GetMapping("/demo")
@ApiOperation(value = "数据字典全列表")
@RequiresPermissions("system.dataDict.list")
public CommonResult<Boolean> list() {
return CommonResult.success(true);
}
// @GetMapping("/list")
// @ApiOperation(value = "数据字典全列表")
// @RequiresPermissions("system.dataDict.list")
// public CommonResult<List<DataDictBO>> list() {
// return success( dataDictService.selectDataDictList());
// }
//
// @GetMapping("/tree")
// @RequiresPermissions // 因为是通用的接口所以无需权限标识
// @ApiOperation(value = "数据字典树结构", notes = "该接口返回的信息更为精简。一般用于前端缓存数据字典到本地。")
// public CommonResult<List<DataDictEnumVO>> tree() {
// // 查询数据字典全列表
// List<DataDictBO> dataDicts = dataDictService.selectDataDictList();
// // 构建基于 enumValue 聚合的 Multimap
// ImmutableListMultimap<String, DataDictBO> dataDictMap = Multimaps.index(dataDicts, DataDictBO::getEnumValue); // KEY enumValue VALUE DataDictBO 数组
// // 构建返回结果
// List<DataDictEnumVO> dataDictEnumVOs = new ArrayList<>(dataDictMap.size());
// dataDictMap.keys().forEach(enumValue -> {
// DataDictEnumVO dataDictEnumVO = new DataDictEnumVO().setEnumValue(enumValue)
// .setValues(DataDictConvert.INSTANCE.convert2(dataDictMap.get(enumValue)));
// dataDictEnumVOs.add(dataDictEnumVO);
// });
// return success(dataDictEnumVOs);
// }
//
// @PostMapping("/add")
// @RequiresPermissions("system.dataDict.add")
// @ApiOperation(value = "创建数据字典")
// public CommonResult<DataDictBO> add(DataDictAddDTO dataDictAddDTO) {
// return success(dataDictService.addDataDict(AdminSecurityContextHolder.getContext().getAdminId(), dataDictAddDTO));
// }
//
// @PostMapping("/update")
// @RequiresPermissions("system.dataDict.update")
// @ApiOperation(value = "更新数据字典")
// public CommonResult<Boolean> update(DataDictUpdateDTO dataDictUpdateDTO) {
// return success(dataDictService.updateDataDict(AdminSecurityContextHolder.getContext().getAdminId(), dataDictUpdateDTO));
// }
//
// @PostMapping("/delete")
// @RequiresPermissions("system.dataDict.delete")
// @ApiOperation(value = "删除数据字典")
// @ApiImplicitParam(name = "id", value = "编号", required = true, example = "100")
// public CommonResult<Boolean> delete(@RequestParam("id") Integer id) {
// return success(dataDictService.deleteDataDict(AdminSecurityContextHolder.getContext().getAdminId(), id));
// }
}

View File

@ -39,7 +39,7 @@ public class AdminsOAuth2Controller {
OAuth2UsernameAuthenticateDTO authenticateDTO = AdminsOAuth2Convert.INSTANCE.convert(request);
OAuth2AccessTokenBO accessTokenBO = oauth2Service.authenticate(authenticateDTO);
// 获得 Admin 信息
AdminBO adminBO = adminService.get(accessTokenBO.getAccountId());
AdminBO adminBO = adminService.getAdmin(accessTokenBO.getAccountId());
if (adminBO == null) {
throw ServiceExceptionUtil.exception(ADMIN_NOT_FOUND);
}

View File

@ -10,6 +10,6 @@ public interface AdminsAdminConvert {
AdminsAdminConvert INSTANCE = Mappers.getMapper(AdminsAdminConvert.class);
AccountUsernameAuthorizeBO convert(AdminsOAuth2UsernameAuthenticateRequest request);
AccountUsernameAuthorizeBO convert(AdminsOAuth2UsernameAuthenticateRequest bean);
}

View File

@ -14,7 +14,7 @@ public interface AdminsOAuth2Convert {
AdminsOAuth2Convert INSTANCE = Mappers.getMapper(AdminsOAuth2Convert.class);
OAuth2UsernameAuthenticateDTO convert(AdminsOAuth2UsernameAuthenticateRequest request);
OAuth2UsernameAuthenticateDTO convert(AdminsOAuth2UsernameAuthenticateRequest bean);
@Mapping(source = "adminBO", target = "admin")
@Mapping(source = "accessTokenBO.id", target = "token.accessToken")

View File

@ -13,9 +13,9 @@ public interface UsersOAuth2Convert {
UsersOAuth2Convert INSTANCE = Mappers.getMapper(UsersOAuth2Convert.class);
OAuth2MobileCodeAuthenticateDTO convert(UsersOAuth2MobileCodeAuthenticateRequest request);
OAuth2MobileCodeAuthenticateDTO convert(UsersOAuth2MobileCodeAuthenticateRequest bean);
@Mapping(source = "token.id", target = "token.accessToken")
UsersOAuth2AuthenticateResponse convert(UserAuthenticateBO userAuthenticateBO);
UsersOAuth2AuthenticateResponse convert(UserAuthenticateBO bean);
}

View File

@ -22,11 +22,11 @@ public interface AdminsSmsConvert {
AdminsSmsConvert INSTANCE = Mappers.getMapper(AdminsSmsConvert.class);
@Mappings({})
AddSignDTO convert(AddSignRequest addSignRequest);
AddSignDTO convert(AddSignRequest bean);
@Mappings({})
UpdateSignDTO convert(UpdateSignRequest updateSignRequest);
UpdateSignDTO convert(UpdateSignRequest bean);
@Mappings({})
ListSmsTemplateDTO convert(ListSmsTemplateRequest listSmsTemplateRequest);
ListSmsTemplateDTO convert(ListSmsTemplateRequest bean);
}

View File

@ -1,7 +1,15 @@
package cn.iocoder.mall.system.rpc.api.admin;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.rpc.response.admin.AdminResponse;
/**
* Admin RPC 接口
*/
public interface AdminRPC {
CommonResult<AdminResponse> getAdmin(Integer id);
CommonResult<AdminResponse> getAdminByAccountId(Integer accountId);
}

View File

@ -0,0 +1,10 @@
package cn.iocoder.mall.system.rpc.api.authorization;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.rpc.request.authorization.AuthorizationCheckPermissionsRequest;
public interface AuthorizationRPC {
CommonResult<Boolean> checkPermissions(AuthorizationCheckPermissionsRequest checkPermissionsRequest);
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.mall.system.rpc.api.user;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.rpc.response.user.UserResponse;
/**
* User RPC 接口
*/
public interface UserRPC {
// CommonResult<UserResponse> getUser(Integer id);
CommonResult<UserResponse> getUserByAccountId(Integer accountId);
}

View File

@ -7,7 +7,7 @@ import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 权模块 - 校验账号是否有权限 Request
* 权模块 - 校验账号是否有权限 Request
*/
@Data
@Accessors(chain = true)

View File

@ -4,7 +4,7 @@ import lombok.Data;
import lombok.experimental.Accessors;
/**
* Admin 信息 Response
* Admin 模块 - Admin 信息 Response
*/
@Data
@Accessors(chain = true)
@ -14,6 +14,5 @@ public class AdminResponse {
* 管理员编号
*/
private Integer id;
// private String
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.mall.system.rpc.response.user;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* User 模块 - User 信息 Response
*/
@Data
@Accessors(chain = true)
public class UserResponse {
/**
* 用户编号
*/
private Integer id;
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.mall.system.rpc.convert.admn;
import cn.iocoder.mall.system.biz.bo.admin.AdminBO;
import cn.iocoder.mall.system.rpc.response.admin.AdminResponse;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AdminCovert {
AdminCovert INSTANCE = Mappers.getMapper(AdminCovert.class);
AdminResponse convert(AdminBO bean);
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.mall.system.rpc.convert.authorization;
import cn.iocoder.mall.system.biz.dto.authorization.AuthorizationCheckPermissionsDTO;
import cn.iocoder.mall.system.rpc.request.authorization.AuthorizationCheckPermissionsRequest;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface AuthorizationConvert {
AuthorizationConvert INSTANCE = Mappers.getMapper(AuthorizationConvert.class);
AuthorizationCheckPermissionsDTO convert(AuthorizationCheckPermissionsRequest bean);
}

View File

@ -14,6 +14,6 @@ public interface OAuth2Convert {
OAuth2AccessTokenAuthenticateDTO convert(OAuth2AccessTokenAuthenticateRequest authenticateRequest);
OAuth2AccessTokenResponse convert(OAuth2AccessTokenBO accessTokenBO);
OAuth2AccessTokenResponse convert(OAuth2AccessTokenBO bean);
}

View File

@ -12,8 +12,8 @@ public interface SystemLogConvert {
SystemLogConvert INSTANCE = Mappers.getMapper(SystemLogConvert.class);
AccessLogAddDTO convert(AccessLogAddRequest accessLogAddRequest);
AccessLogAddDTO convert(AccessLogAddRequest bean);
ExceptionLogAddDTO convert(ExceptionLogAddRequest exceptionLogAddRequest);
ExceptionLogAddDTO convert(ExceptionLogAddRequest bean);
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.mall.system.rpc.convert.user;
import cn.iocoder.mall.system.biz.bo.user.UserBO;
import cn.iocoder.mall.system.rpc.response.user.UserResponse;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
UserResponse convert(UserBO bean);
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.mall.system.rpc.rpc.admin;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.biz.bo.admin.AdminBO;
import cn.iocoder.mall.system.biz.service.admin.AdminService;
import cn.iocoder.mall.system.rpc.api.admin.AdminRPC;
import cn.iocoder.mall.system.rpc.convert.admn.AdminCovert;
import cn.iocoder.mall.system.rpc.response.admin.AdminResponse;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service(version = "${dubbo.provider.AdminRPC.version}", validation = "true")
public class AdminRPCImpl implements AdminRPC {
@Autowired
private AdminService adminService;
@Override
public CommonResult<AdminResponse> getAdmin(Integer id) {
AdminBO adminBO = adminService.getAdmin(id);
return CommonResult.success(AdminCovert.INSTANCE.convert(adminBO));
}
@Override
public CommonResult<AdminResponse> getAdminByAccountId(Integer accountId) {
AdminBO adminBO = adminService.getAdminByAccountId(accountId);
return CommonResult.success(AdminCovert.INSTANCE.convert(adminBO));
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.mall.system.rpc.rpc.authorization;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.biz.dto.authorization.AuthorizationCheckPermissionsDTO;
import cn.iocoder.mall.system.biz.service.authorization.AuthorizationService;
import cn.iocoder.mall.system.rpc.api.authorization.AuthorizationRPC;
import cn.iocoder.mall.system.rpc.convert.authorization.AuthorizationConvert;
import cn.iocoder.mall.system.rpc.request.authorization.AuthorizationCheckPermissionsRequest;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service(version = "${dubbo.provider.AuthorizationRPC.version}", validation = "true")
public class AuthorizationRPCImpl implements AuthorizationRPC {
@Autowired
private AuthorizationService authorizationService;
@Override
public CommonResult<Boolean> checkPermissions(AuthorizationCheckPermissionsRequest checkPermissionsRequest) {
AuthorizationCheckPermissionsDTO checkPermissionsDTO = AuthorizationConvert.INSTANCE.convert(checkPermissionsRequest);
authorizationService.checkPermissions(checkPermissionsDTO);
return CommonResult.success(true);
}
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.mall.system.rpc.rpc.user;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.system.biz.bo.user.UserBO;
import cn.iocoder.mall.system.biz.service.user.UserService;
import cn.iocoder.mall.system.rpc.api.user.UserRPC;
import cn.iocoder.mall.system.rpc.convert.user.UserConvert;
import cn.iocoder.mall.system.rpc.response.user.UserResponse;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service(version = "${dubbo.provider.UserRPC.version}", validation = "true")
public class UserRPCImpl implements UserRPC {
@Autowired
private UserService userService;
@Override
public CommonResult<UserResponse> getUserByAccountId(Integer accountId) {
UserBO userBO = userService.getUserByAccountId(accountId);
return CommonResult.success(UserConvert.INSTANCE.convert(userBO));
}
}

View File

@ -17,9 +17,21 @@ dubbo:
version: 1.0.0
OAuth2RPC:
version: 1.0.0
AuthorizationRPC:
version: 1.0.0
AdminRPC:
version: 1.0.0
UserRPC:
version: 1.0.0
# Dubbo 服务消费者的配置
consumer:
SystemLogRPC: # 用于 AccessLogInterceptor 等拦截器,记录 HTTP API 请求的访问日志
version: 1.0.0
OAuth2RPC:
OAuth2RPC: # 用于 AccountAuthInterceptor 拦截器,执行认证
version: 1.0.0
AuthorizationRPC: # 用于 AccountAuthInterceptor 拦截器,执行鉴权(权限验证)
version: 1.0.0
AdminRPC:
version: 1.0.0
UserRPC:
version: 1.0.0

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>system</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>system-sdk</artifactId>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>system-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- RPC 相关 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,22 +0,0 @@
package cn.iocoder.mall.system.sdk.annotation;
import java.lang.annotation.*;
/**
* 参考 Shiro @RequiresPermissions 设计 http://shiro.apache.org/static/1.3.2/apidocs/org/apache/shiro/authz/annotation/RequiresPermissions.html
*
* 通过将该注解添加到 Controller 的方法上进行授权鉴定
*/
@Documented
@Target({ElementType.METHOD}) // 暂时不支持 ElementType.TYPE 因为没有场景
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
/**
* 当有多个标识时必须全部拥有权限才可以操作
*
* @return 权限标识数组
*/
String[] value();
}

View File

@ -1,17 +0,0 @@
package cn.iocoder.mall.system.sdk.constant;
/**
* 逻辑类型枚举
*/
public enum LogicalEnum {
/**
* 并且
*/
AND,
/**
* 或者
*/
OR,
}

View File

@ -1,28 +0,0 @@
package cn.iocoder.mall.system.sdk.context;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Set;
/**
* Security 上下文
*/
@Data
@Accessors(chain = true)
public class AdminSecurityContext {
/**
* 管理员编号
*/
private Integer adminId;
/**
* 管理员账号
*/
private String username;
/**
* 拥有的角色编号
*/
private Set<Integer> roleIds;
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.mall.system.sdk.context;
/**
* {@link AdminSecurityContext} Holder
*
* 参考 spring security ThreadLocalSecurityContextHolderStrategy 简单实现
*/
public class AdminSecurityContextHolder {
private static final ThreadLocal<AdminSecurityContext> SECURITY_CONTEXT = new ThreadLocal<>();
public static void setContext(AdminSecurityContext context) {
SECURITY_CONTEXT.set(context);
}
public static AdminSecurityContext getContext() {
AdminSecurityContext ctx = SECURITY_CONTEXT.get();
// 为空时设置一个空的进去
if (ctx == null) {
ctx = new AdminSecurityContext();
SECURITY_CONTEXT.set(ctx);
}
return ctx;
}
public static void clear() {
SECURITY_CONTEXT.remove();
}
}

View File

@ -1,117 +0,0 @@
package cn.iocoder.mall.system.sdk.interceptor;
import cn.iocoder.common.framework.constant.UserTypeEnum;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.MallUtil;
import cn.iocoder.common.framework.util.StringUtil;
import cn.iocoder.mall.system.api.AdminService;
import cn.iocoder.mall.system.api.OAuth2Service;
import cn.iocoder.mall.system.api.bo.admin.AdminAuthorizationBO;
import cn.iocoder.mall.system.api.bo.oauth2.OAuth2AuthenticationBO;
import cn.iocoder.mall.system.api.constant.AdminErrorCodeEnum;
import cn.iocoder.mall.system.api.dto.oauth2.OAuth2GetTokenDTO;
import cn.iocoder.mall.system.sdk.annotation.RequiresPermissions;
import cn.iocoder.mall.system.sdk.context.AdminSecurityContext;
import cn.iocoder.mall.system.sdk.context.AdminSecurityContextHolder;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Set;
/**
* Admin 安全拦截器
*/
@Component
public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
@Reference(validation = "true", version = "${dubbo.consumer.OAuth2Service.version:1.0.0}")
private OAuth2Service oauth2Service;
@Reference(validation = "true", version = "${dubbo.consumer.AdminService.version:1.0.0}")
private AdminService adminService;
/**
* 忽略的 URL 集合即无需经过认证
*
* 对于 Admin 的系统默认所有接口都需要进行认证
*/
@Value("${admins.security.ignore_urls:#{null}}")
private Set<String> ignoreUrls;
public AdminSecurityInterceptor setIgnoreUrls(Set<String> ignoreUrls) {
this.ignoreUrls = ignoreUrls;
return this;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 设置当前访问的用户类型注意即使未登陆我们也认为是管理员
MallUtil.setUserType(request, UserTypeEnum.ADMIN.getValue());
// 根据 accessToken 获得认证信息判断是谁
String accessToken = HttpUtil.obtainAuthorization(request);
OAuth2AuthenticationBO authentication = null;
ServiceException serviceException = null;
if (StringUtil.hasText(accessToken)) {
try {
authentication = oauth2Service.getAuthentication(new OAuth2GetTokenDTO().setAccessToken(accessToken)
.setUserType(UserTypeEnum.ADMIN.getValue()));
} catch (ServiceException e) {
serviceException = e;
}
}
// 进行鉴权
String url = request.getRequestURI();
boolean needAuthentication = ignoreUrls == null || !ignoreUrls.contains(url);
AdminAuthorizationBO authorization = null;
if (needAuthentication) {
if (serviceException != null) { // 认证失败抛出上面认证失败的 ServiceException 异常
throw serviceException;
}
if (authentication == null) { // 无认证信息抛出未登陆 ServiceException 异常
throw new ServiceException(AdminErrorCodeEnum.OAUTH2_NOT_LOGIN.getCode(), AdminErrorCodeEnum.OAUTH2_NOT_LOGIN.getMessage());
}
authorization = checkPermission(handler, authentication);
}
// 鉴权完成初始化 AdminSecurityContext 上下文
AdminSecurityContext context = new AdminSecurityContext();
AdminSecurityContextHolder.setContext(context);
if (authentication != null) {
context.setAdminId(authentication.getUserId());
MallUtil.setUserId(request, authentication.getUserId()); // 记录到 request 避免 AdminSecurityContext 后续清理掉后其它地方需要用到 userId
if (authorization != null) {
context.setUsername(authorization.getUsername());
context.setRoleIds(authorization.getRoleIds());
}
}
// 返回成功
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 清空 SecurityContext
AdminSecurityContextHolder.clear();
}
private AdminAuthorizationBO checkPermission(Object handler, OAuth2AuthenticationBO authentication) {
// 获得 @RequiresPermissions 注解
Assert.isTrue(handler instanceof HandlerMethod, "handler 必须是 HandlerMethod 类型");
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequiresPermissions requiresPermissions = handlerMethod.getMethodAnnotation(RequiresPermissions.class);
// 执行校验
return adminService.checkPermissions(authentication.getUserId(),
requiresPermissions != null ? Arrays.asList(requiresPermissions.value()) : null);
}
}

View File

@ -1,6 +0,0 @@
/**
* 提供 SDK 给其它服务使用如下功能
*
* 1. 通过 {@link cn.iocoder.mall.system.sdk.interceptor.AdminSecurityInterceptor} 拦截器实现需要登陆 URL 的鉴权
*/
package cn.iocoder.mall.system.sdk;

View File

@ -19,8 +19,6 @@ public interface RoleService {
*/
List<RoleBO> getRoleList();
List<RoleBO> getRoleList(Collection<Integer> ids);
RoleBO addRole(Integer adminId, RoleAddDTO roleAddDTO);
Boolean updateRole(Integer adminId, RoleUpdateDTO roleUpdateDTO);

View File

@ -1,28 +0,0 @@
package cn.iocoder.mall.admin.config;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@MapperScan("cn.iocoder.mall.admin.dao") // 扫描对应的 Mapper 接口
@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理为什么使用 proxyTargetClass 参数参见 https://blog.csdn.net/huang_550/article/details/76492600
public class DatabaseConfiguration {
// 数据库连接池 Druid
@Bean
public ISqlInjector sqlInjector() {
return new DefaultSqlInjector(); // MyBatis Plus 逻辑删除
}
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor(); // MyBatis Plus 分页插件
}
}

View File

@ -1,19 +0,0 @@
package cn.iocoder.mall.admin.config;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.mall.system.api.constant.AdminErrorCodeEnum;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
@Configuration
public class ServiceExceptionConfiguration {
@EventListener(ApplicationReadyEvent.class) // 可参考 https://www.cnblogs.com/ssslinppp/p/7607509.html
public void initMessages() {
for (AdminErrorCodeEnum item : AdminErrorCodeEnum.values()) {
ServiceExceptionUtil.put(item.getCode(), item.getMessage());
}
}
}

View File

@ -1,38 +0,0 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
@Repository
public interface AdminRoleMapper extends BaseMapper<AdminRoleDO> {
default List<AdminRoleDO> selectByAdminId( Integer adminId) {
return selectList(new QueryWrapper<AdminRoleDO>().eq("admin_id", adminId));
}
default List<AdminRoleDO> selectListByAdminIds(Collection<Integer> adminIds) {
return selectList(new QueryWrapper<AdminRoleDO>().in("admin_id", adminIds));
}
default int deleteByAdminId(Integer adminId) {
return delete(new QueryWrapper<AdminRoleDO>().eq("admin_id", adminId));
}
default int deleteByRoleId(Integer roleId) {
return delete(new QueryWrapper<AdminRoleDO>().eq("role_id", roleId));
}
/**
* 批量插入因为 MyBaits Plus 的批量插入是基于 Service 实现所以只好写 XML
*
* @param adminRoleDOs 数组
*/
int insertList(@Param("adminRoleDOs") List<AdminRoleDO> adminRoleDOs);
}

View File

@ -1,9 +0,0 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.ExceptionLogDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface ExceptionLogMapper extends BaseMapper<ExceptionLogDO> {
}

View File

@ -1,24 +0,0 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2AccessTokenMapper extends BaseMapper<OAuth2AccessTokenDO> {
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2AccessTokenDO> query = new QueryWrapper<OAuth2AccessTokenDO>()
.eq("user_id", userId).eq("user_type", userType)
.eq("valid", true);
return update(new OAuth2AccessTokenDO().setValid(false), query);
}
default int updateToInvalidByRefreshToken(String refreshToken) {
QueryWrapper<OAuth2AccessTokenDO> query = new QueryWrapper<OAuth2AccessTokenDO>()
.eq("refresh_token", refreshToken).eq("valid", true);
return update(new OAuth2AccessTokenDO().setValid(false), query);
}
}

View File

@ -1,18 +0,0 @@
package cn.iocoder.mall.admin.dao;
import cn.iocoder.mall.admin.dataobject.OAuth2RefreshTokenDO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
@Repository
public interface OAuth2RefreshTokenMapper extends BaseMapper<OAuth2RefreshTokenDO> {
default int updateToInvalid(Integer userId, Integer userType) {
QueryWrapper<OAuth2RefreshTokenDO> query = new QueryWrapper<OAuth2RefreshTokenDO>()
.eq("user_id", userId).eq("user_type", userType)
.eq("valid", true);
return update(new OAuth2RefreshTokenDO().setValid(false), query);
}
}

View File

@ -1,29 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* {@link AdminDO} {@link RoleDO} 的关联表
*/
@TableName("admin_role")
@Data
@Accessors(chain = true)
public class AdminRoleDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 管理员编号(外键{@link AdminDO}
*/
private Integer adminId;
/**
* 角色编号(外键{@link RoleDO}
*/
private Integer roleId;
}

View File

@ -1,118 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.BaseDO;
import cn.iocoder.mall.system.api.dto.systemlog.AccessLogAddDTO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 异常日志 DO
*/
@Data
@Accessors(chain = true)
@TableName("exception_log")
public class ExceptionLogDO extends BaseDO {
/**
* 编号
*/
private Integer id;
/**
* 链路追踪编号
*
* 一般来说通过链路追踪编号可以将访问日志错误日志链路追踪日志logger 打印日志等结合在一起从而进行排错
*/
private String traceId;
/**
* 用户编号.
*
* 当管理员为空时该值为 {@link AccessLogAddDTO#USER_ID_NULL}
*/
private Integer userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 应用名
*
* 目前读取 spring.application.name
*/
private String applicationName;
/**
* 访问地址
*/
private String uri;
/**
* 参数
*/
private String queryString;
/**
* http 方法
*/
private String method;
/**
* userAgent
*/
private String userAgent;
/**
* ip
*/
private String ip;
/**
* 异常发生时间
*/
private Date exceptionTime;
/**
* 异常名
*
* {@link Throwable#getClass()} 的类全名
*/
private String exceptionName;
/**
* 异常导致的消息
*
* {@link cn.iocoder.common.framework.util.ExceptionUtil#getMessage(Throwable)}
*/
private String exceptionMessage;
/**
* 异常导致的根消息
*
* {@link cn.iocoder.common.framework.util.ExceptionUtil#getRootCauseMessage(Throwable)}
*/
private String exceptionRootCauseMessage;
/**
* 异常的栈轨迹
*
* {@link cn.iocoder.common.framework.util.ExceptionUtil#getServiceException(Exception)}
*/
private String exceptionStackTrace;
/**
* 异常发生的类全名
*
* {@link StackTraceElement#getClassName()}
*/
private String exceptionClassName;
/**
* 异常发生的类文件
*
* {@link StackTraceElement#getFileName()}
*/
private String exceptionFileName;
/**
* 异常发生的方法名
*
* {@link StackTraceElement#getMethodName()}
*/
private String exceptionMethodName;
/**
* 异常发生的方法所在行
*
* {@link StackTraceElement#getLineNumber()}
*/
private Integer exceptionLineNumber;
}

View File

@ -1,46 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* OAUTH2 AccessToken
*/
@TableName("oauth2_access_token")
@Data
@Accessors(chain = true)
public class OAuth2AccessTokenDO extends BaseDO {
/**
* 访问令牌
*/
@TableId(type = IdType.INPUT)
private String id;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 用户编号
*/
private Integer userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 过期时间
*/
private Date expiresTime;
/**
* 是否有效
*/
private Boolean valid;
}

View File

@ -1,44 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
/**
* 刷新令牌
*
* idx_uid
*/
@TableName("oauth2_refresh_token")
@Data
@Accessors(chain = true)
public class OAuth2RefreshTokenDO extends BaseDO {
/**
* 刷新令牌
*/
@TableId(type = IdType.INPUT)
private String id;
/**
* 用户编号
*/
private Integer userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 是否有效
*/
private Boolean valid;
/**
* 过期时间
*/
private Date expiresTime;
}

View File

@ -1,56 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 资源实体
*/
@TableName(value = "resource")
@Data
@Accessors(chain = true)
public class ResourceDO extends DeletableDO {
/**
* 资源编号
*/
private Integer id;
/**
* 资源类型
*/
private Integer type;
/**
* 排序
*/
private Integer sort;
/**
* 展示名
*/
private String displayName;
/**
* 父级资源编号(外键{@link ResourceDO#id})
*/
private Integer pid;
/**
* 操作
*
* 目前当且仅当资源类型为菜单才会生效 handler 配置为界面 URL 或者前端组件名或者前端的路由
*/
private String handler;
/**
* 图表
*
* 目前当且仅当资源类型为菜单才会生效
*/
private String icon;
/**
* 权限标识数组使用逗号分隔
*
* 例如system.admin.add
* 推荐格式为 ${系统}.${模块}.${操作}
*/
private String permissions;
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 角色实体
*/
@TableName("role")
@Data
@Accessors(chain = true)
public class RoleDO extends DeletableDO {
/**
* 角色编号
*/
private Integer id;
/**
* 角色名
*/
private String name;
}

View File

@ -1,29 +0,0 @@
package cn.iocoder.mall.admin.dataobject;
import cn.iocoder.common.framework.dataobject.DeletableDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* {@link RoleDO} {@link ResourceDO} 的关联表
*/
@TableName("role_resource")
@Data
@Accessors(chain = true)
public class RoleResourceDO extends DeletableDO {
/**
* 编号
*/
private Integer id;
/**
* 角色编号(外键{@link RoleDO}
*/
private Integer roleId;
/**
* 资源编号(外键{@link ResourceDO}
*/
private Integer resourceId;
}

View File

@ -48,28 +48,6 @@ public class AdminServiceImpl implements AdminService {
@Autowired
private RoleServiceImpl roleService;
@Override
public AdminAuthenticationBO authentication(AdminAuthenticationDTO adminAuthenticationDTO) {
AdminDO admin = adminMapper.selectByUsername(adminAuthenticationDTO.getUsername());
// 账号不存在
if (admin == null) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_USERNAME_NOT_REGISTERED.getCode());
}
// 密码不正确
if (!encodePassword(adminAuthenticationDTO.getPassword()).equals(admin.getPassword())) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_PASSWORD_ERROR.getCode());
}
// 账号被禁用
if (CommonStatusEnum.DISABLE.getValue().equals(admin.getStatus())) {
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_IS_DISABLE.getCode());
}
// 创建 accessToken
OAuth2AccessTokenBO accessTokenBO = oauth2Service.createToken(new OAuth2CreateTokenDTO().setUserId(admin.getId())
.setUserType(UserTypeEnum.ADMIN.getValue()));
// 转换返回
return AdminConvert.INSTANCE.convert2(admin).setToken(accessTokenBO);
}
@Override
public PageResult<AdminBO> getAdminPage(AdminPageDTO adminPageDTO) {
IPage<AdminDO> page = adminMapper.selectPage(adminPageDTO);
@ -227,29 +205,4 @@ public class AdminServiceImpl implements AdminService {
return true;
}
@Override
public AdminAuthorizationBO checkPermissions(Integer adminId, List<String> permissions) {
// 查询管理员拥有的角色关联数据
List<AdminRoleDO> adminRoleList = adminRoleMapper.selectByAdminId(adminId);
Set<Integer> adminRoleIds = CollectionUtil.convertSet(adminRoleList, AdminRoleDO::getRoleId);
// 授权校验
if (!CollectionUtil.isEmpty(permissions)) {
Map<String, List<Integer>> permissionRoleMap = roleService.getPermissionRoleMap(permissions);
for (Map.Entry<String, List<Integer>> entry : permissionRoleMap.entrySet()) {
if (!CollectionUtil.containsAny(entry.getValue(), adminRoleIds)) { // 所以有任一不满足就验证失败抛出异常
throw ServiceExceptionUtil.exception(AdminErrorCodeEnum.ADMIN_INVALID_PERMISSION.getCode());
}
}
}
// 获得用户
AdminDO admin = adminMapper.selectById(adminId);
// 返回成功
return new AdminAuthorizationBO().setId(adminId).setUsername(admin.getUsername())
.setRoleIds(adminRoleIds);
}
private String encodePassword(String password) {
return DigestUtils.md5DigestAsHex(password.getBytes());
}
}

View File

@ -1,4 +0,0 @@
##################### 业务模块 #####################
## OAuth2CodeService
modules.oauth2-code-service.access-token-expire-time-millis = 2880000
modules.oauth2-code-service.refresh-token-expire-time-millis = 43200000

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.mall.admin.dao.AdminRoleMapper">
<insert id="insertList">
INSERT INTO admin_role (
admin_id, role_id, create_time, deleted
) VALUES
<foreach collection="adminRoleDOs" item="adminRole" separator=",">
(#{adminRole.adminId}, #{adminRole.roleId}, #{adminRole.createTime}, #{adminRole.deleted})
</foreach>
</insert>
</mapper>

View File

@ -14,7 +14,6 @@
<modules>
<!-- <module>user-application</module>-->
<!-- <module>user-service-api</module>-->
<!-- <module>user-sdk</module>-->
<!-- <module>user-service-impl</module>-->
<module>user-application</module>
<module>user-rest</module>

View File

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>user</artifactId>
<groupId>cn.iocoder.mall</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-sdk</artifactId>
<dependencies>
<!-- Mall 相关 -->
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>common-framework</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.iocoder.mall</groupId>
<artifactId>user-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<!-- RPC 相关 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
</dependencies>
</project>

Some files were not shown because too many files have changed in this diff Show More