增加管理员模块~
This commit is contained in:
parent
e431530107
commit
09004dc000
|
@ -0,0 +1,114 @@
|
|||
<?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>admin</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>admin-application</artifactId>
|
||||
|
||||
<properties>
|
||||
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>admin-service-impl</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.boot</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
<version>0.2.1.RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>2.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 提供给 mapstruct 使用 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source> <!-- or higher, depending on your project -->
|
||||
<target>1.8</target> <!-- or higher, depending on your project -->
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,13 @@
|
|||
package cn.iocoder.mall.admin;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"cn.iocoder.mall.admin"})
|
||||
public class AdminApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AdminApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package cn.iocoder.mall.admin.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@EnableWebMvc
|
||||
@Configuration
|
||||
//@Import(value = {GlobalExceptionHandler.class, // 统一全局返回
|
||||
// ) // TODO 安全拦截器,实现认证和授权功能。
|
||||
public class MVCConfiguration implements WebMvcConfigurer {
|
||||
|
||||
// @Autowired
|
||||
// private UserSecurityInterceptor securityInterceptor;
|
||||
//
|
||||
// @Override
|
||||
// public void addInterceptors(InterceptorRegistry registry) {
|
||||
// registry.addInterceptor(securityInterceptor).addPathPatterns("/user/**", "/admin/**"); // 只拦截我们定义的接口
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// 解决 swagger-ui.html 的访问,参考自 https://stackoverflow.com/questions/43545540/swagger-ui-no-mapping-found-for-http-request 解决
|
||||
registry.addResourceHandler("swagger-ui.html**").addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
|
||||
registry.addResourceHandler("webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package cn.iocoder.mall.admin.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("cn.iocoder.mall.admin.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("管理员子系统")
|
||||
.description("管理员子系统")
|
||||
.termsOfServiceUrl("http://www.iocoder.cn")
|
||||
.version("1.0.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package cn.iocoder.mall.admin.controller;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("admin/admin")
|
||||
@Api("管理员模块")
|
||||
public class AdminController {
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package cn.iocoder.mall.admin.controller;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.admin.api.OAuth2Service;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
|
||||
import cn.iocoder.mall.admin.convert.PassportConvert;
|
||||
import cn.iocoder.mall.admin.vo.PassportLoginVO;
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("admin/passport")
|
||||
@Api("Admin Passport 模块")
|
||||
public class PassportController {
|
||||
|
||||
@Reference
|
||||
private OAuth2Service oauth2Service;
|
||||
|
||||
@PostMapping("/login")
|
||||
@ApiOperation(value = "手机号 + 验证码登陆(注册)", notes = "如果手机对应的账号不存在,则会自动创建")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "username", value = "账号", required = true, example = "15601691300"),
|
||||
@ApiImplicitParam(name = "password", value = "密码", required = true, example = "future")
|
||||
})
|
||||
public CommonResult<PassportLoginVO> login(@RequestParam("username") String username,
|
||||
@RequestParam("password") String password) {
|
||||
CommonResult<OAuth2AccessTokenBO> result = oauth2Service.getAccessToken(username, password);
|
||||
return PassportConvert.INSTANCE.convert(result);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package cn.iocoder.mall.admin.convert;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
|
||||
import cn.iocoder.mall.admin.vo.PassportLoginVO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PassportConvert {
|
||||
|
||||
PassportConvert INSTANCE = Mappers.getMapper(PassportConvert.class);
|
||||
|
||||
@Mappings({})
|
||||
PassportLoginVO convert(OAuth2AccessTokenBO oauth2AccessTokenBO);
|
||||
|
||||
@Mappings({})
|
||||
CommonResult<PassportLoginVO> convert(CommonResult<OAuth2AccessTokenBO> oauth2AccessTokenBO);
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package cn.iocoder.mall.admin.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
|
||||
@ApiModel("登陆结果 VO")
|
||||
public class PassportLoginVO {
|
||||
|
||||
@ApiModelProperty(value = "访问令牌", required = true, example = "2e3d7635c15e47e997611707a237859f")
|
||||
private String accessToken;
|
||||
@ApiModelProperty(value = "刷新令牌", required = true, example = "d091e7c35bbb4313b0f557a6ef23d033")
|
||||
private String refreshToken;
|
||||
@ApiModelProperty(value = "过期时间,单位:秒", required = true, example = "2879")
|
||||
private Integer expiresIn;
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public PassportLoginVO setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public PassportLoginVO setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public PassportLoginVO setExpiresIn(Integer expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
spring:
|
||||
application:
|
||||
name: admin-application
|
||||
|
||||
# server
|
||||
server:
|
||||
port: 8083
|
|
@ -0,0 +1,53 @@
|
|||
<?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>admin</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>application-sdk</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>5.1.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>5.1.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>admin-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,26 @@
|
|||
package cn.iocoder.mall.admin.sdk.context;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Security 上下文
|
||||
*/
|
||||
public class AdminSecurityContext {
|
||||
|
||||
private final Integer adminId;
|
||||
private final Set<Integer> roleIds;
|
||||
|
||||
public AdminSecurityContext(Integer adminId, Set<Integer> roleIds) {
|
||||
this.adminId = adminId;
|
||||
this.roleIds = roleIds;
|
||||
}
|
||||
|
||||
public Integer getAdminId() {
|
||||
return adminId;
|
||||
}
|
||||
|
||||
public Set<Integer> getRoleIds() {
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.mall.admin.sdk.context;
|
||||
|
||||
/**
|
||||
* {@link AdminSecurityContext} Holder
|
||||
*
|
||||
* 参考 spring security 的 ThreadLocalSecurityContextHolderStrategy 类,简单实现。
|
||||
*/
|
||||
public class AdminSecurityContextHolder {
|
||||
|
||||
private static final ThreadLocal<AdminSecurityContext> securityContext = new ThreadLocal<AdminSecurityContext>();
|
||||
|
||||
public static void setContext(AdminSecurityContext context) {
|
||||
securityContext.set(context);
|
||||
}
|
||||
|
||||
public static AdminSecurityContext getContext() {
|
||||
AdminSecurityContext ctx = securityContext.get();
|
||||
// 为空时,设置一个空的进去
|
||||
if (ctx == null) {
|
||||
ctx = new AdminSecurityContext(null, roleIds);
|
||||
securityContext.set(ctx);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
securityContext.remove();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package cn.iocoder.mall.admin.sdk.interceptor;
|
||||
|
||||
import cn.iocoder.common.framework.exception.ServiceException;
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.admin.api.OAuth2Service;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO;
|
||||
import cn.iocoder.mall.admin.sdk.context.AdminSecurityContext;
|
||||
import cn.iocoder.mall.admin.sdk.context.AdminSecurityContextHolder;
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
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.Set;
|
||||
|
||||
/**
|
||||
* 安全拦截器
|
||||
*/
|
||||
@Component
|
||||
public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
@Reference
|
||||
private OAuth2Service oauth2Service;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 校验访问令牌是否正确。若正确,返回授权信息
|
||||
String accessToken = HttpUtil.obtainAccess(request);
|
||||
OAuth2AuthenticationBO authentication = null;
|
||||
if (accessToken != null) {
|
||||
CommonResult<OAuth2AuthenticationBO> result = oauth2Service.checkToken(accessToken);
|
||||
if (result.isError()) { // TODO 芋艿,如果访问的地址无需登录,这里也不用抛异常
|
||||
throw new ServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
authentication = result.getData();
|
||||
// 添加到 SecurityContext
|
||||
AdminSecurityContext context = new AdminSecurityContext(authentication.getAdminId(), authentication.getRoleIds());
|
||||
AdminSecurityContextHolder.setContext(context);
|
||||
}
|
||||
// 校验是否需要已授权
|
||||
checkPermission(request, authentication);
|
||||
// 返回成功
|
||||
return super.preHandle(request, response, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 清空 SecurityContext
|
||||
AdminSecurityContextHolder.clear();
|
||||
}
|
||||
|
||||
private void checkPermission(HttpServletRequest request, OAuth2AuthenticationBO authentication) {
|
||||
Integer adminId = authentication != null ? authentication.getAdminId() : null;
|
||||
Set<Integer> roleIds = authentication != null ? authentication.getRoleIds() : null;
|
||||
String url = request.getRequestURI();
|
||||
CommonResult<Boolean> result = oauth2Service.checkPermission(adminId, roleIds, url);
|
||||
if (result.isError()) {
|
||||
throw new ServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* 提供 SDK 给其它服务,使用如下功能:
|
||||
*
|
||||
* 1. 通过 {@link cn.iocoder.mall.admin.sdk.interceptor.UserSecurityInterceptor} 拦截器,实现需要登陆 URL 的鉴权
|
||||
*/
|
||||
package cn.iocoder.mall.admin.sdk;
|
|
@ -0,0 +1,27 @@
|
|||
<?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>admin</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>admin-service-api</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>admin-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,16 @@
|
|||
package cn.iocoder.mall.admin.api;
|
||||
|
||||
public interface AdminService {
|
||||
|
||||
// /**
|
||||
// * 创建用户。一般在用户注册时,调用该方法
|
||||
// *
|
||||
// * TODO 芋艿,此处要传递一些用户注册时的相关信息,例如说 ip、ua、客户端来源等等。用于数据分析、风控等等。
|
||||
// *
|
||||
// * @param mobile 手机号
|
||||
// * @param code 手机验证码
|
||||
// * @return 用户
|
||||
// */
|
||||
// UserBO createUser(String mobile, String code) throws ServiceException;
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package cn.iocoder.mall.admin.api;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface OAuth2Service {
|
||||
|
||||
CommonResult<OAuth2AccessTokenBO> getAccessToken(String username, String password);
|
||||
|
||||
/**
|
||||
* 校验访问令牌,获取身份信息( 不包括 accessToken 等等 )
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @return 授权信息
|
||||
*/
|
||||
CommonResult<OAuth2AuthenticationBO> checkToken(String accessToken);
|
||||
|
||||
/**
|
||||
* TODO 校验权限
|
||||
*
|
||||
* @param adminId 管理员编号
|
||||
* @param roleIds 管理员拥有的角色编号的集合
|
||||
* @param url 指定 URL
|
||||
* @return 是否有权限
|
||||
*/
|
||||
CommonResult<Boolean> checkPermission(Integer adminId, Set<Integer> roleIds, String url);
|
||||
|
||||
// TODO @see 刷新 token
|
||||
|
||||
// TODO @see 移除 token
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package cn.iocoder.mall.admin.api;
|
||||
|
||||
public interface RoleService {
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package cn.iocoder.mall.admin.api.bo;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class OAuth2AccessTokenBO implements Serializable {
|
||||
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String accessToken;
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
private String refreshToken;
|
||||
/**
|
||||
* 过期时间,单位:秒。
|
||||
*/
|
||||
private Integer expiresIn;
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenBO setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenBO setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenBO setExpiresIn(Integer expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package cn.iocoder.mall.admin.api.bo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
public class OAuth2AuthenticationBO implements Serializable {
|
||||
|
||||
/**
|
||||
* 管理员编号
|
||||
*/
|
||||
private Integer adminId;
|
||||
/**
|
||||
* 角色编号数组
|
||||
*/
|
||||
private Set<Integer> roleIds;
|
||||
|
||||
public Integer getAdminId() {
|
||||
return adminId;
|
||||
}
|
||||
|
||||
public OAuth2AuthenticationBO setAdminId(Integer adminId) {
|
||||
this.adminId = adminId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<Integer> getRoleIds() {
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
public OAuth2AuthenticationBO setRoleIds(Set<Integer> roleIds) {
|
||||
this.roleIds = roleIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package cn.iocoder.mall.admin.api.constant;
|
||||
|
||||
/**
|
||||
* 错误码枚举类
|
||||
*
|
||||
* 用户中心,使用 1-002-000-000 段
|
||||
*/
|
||||
public enum AdminErrorCodeEnum {
|
||||
|
||||
// ========== OAUTH2 模块 ==========
|
||||
OAUTH2_UNKNOWN(1002001000, "未知错误"), // 预留
|
||||
// OAUTH2_INVALID_GRANT_BAD_CREDENTIALS(1001001001, "密码不正确"), // 暂时没用到
|
||||
// OAUTH2_INVALID_GRANT_USERNAME_NOT_FOUND(1001001002, "账号不存在"), // 暂时没用到
|
||||
// OAUTH2_INVALID_GRANT(1001001010, ""), // 预留
|
||||
OAUTH_INVALID_TOKEN_NOT_FOUND(1002001011, "访问令牌不存在"),
|
||||
OAUTH_INVALID_TOKEN_EXPIRED(1002001012, "访问令牌已过期"),
|
||||
OAUTH_INVALID_TOKEN_INVALID(1002001013, "访问令牌已失效"),
|
||||
OAUTH_INVALID_PERMISSION(1002001014, "没有该操作权限"), // TODO 芋艿,临时放在 OAUTH2 模块,理论来说,OAUTH2 只做认证,不做鉴权。
|
||||
|
||||
OAUTH_INVALID_TOKEN(1002001020, ""), // 预留
|
||||
|
||||
// ========== 管理员模块 ==========
|
||||
ADMIN_USERNAME_NOT_REGISTERED(1002002000, "账号不存在"),
|
||||
ADMIN_PASSWORD_ERROR(1002002001, "密码不正确"),
|
||||
ADMIN_IS_DISABLE(1002002002, "账号被禁用");
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
AdminErrorCodeEnum(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?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>admin</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>admin-service-impl</artifactId>
|
||||
|
||||
<properties>
|
||||
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.5</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>admin-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 提供给 mapstruct 使用 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.5.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source> <!-- or higher, depending on your project -->
|
||||
<target>1.8</target> <!-- or higher, depending on your project -->
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,14 @@
|
|||
package cn.iocoder.mall.admin.config;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
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 {
|
||||
|
||||
// 数据源,使用 HikariCP
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cn.iocoder.mall.admin.config;
|
||||
|
||||
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.mall.admin.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() {
|
||||
// 从 service_exception_message.properties 加载错误码的方案
|
||||
// Properties properties;
|
||||
// try {
|
||||
// properties = PropertiesLoaderUtils.loadAllProperties("classpath:service_exception_message.properties");
|
||||
// } catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
// }
|
||||
for (AdminErrorCodeEnum item : AdminErrorCodeEnum.values()) {
|
||||
ServiceExceptionUtil.put(item.getCode(), item.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package cn.iocoder.mall.admin.convert;
|
||||
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO;
|
||||
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
|
||||
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OAuth2Convert {
|
||||
|
||||
OAuth2Convert INSTANCE = Mappers.getMapper(OAuth2Convert.class);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "id", target = "accessToken")
|
||||
})
|
||||
OAuth2AccessTokenBO convertToAccessToken(OAuth2AccessTokenDO oauth2AccessTokenDO);
|
||||
|
||||
default OAuth2AccessTokenBO convertToAccessTokenWithExpiresIn(OAuth2AccessTokenDO oauth2AccessTokenDO) {
|
||||
return this.convertToAccessToken(oauth2AccessTokenDO)
|
||||
.setExpiresIn(Math.max((int) ((oauth2AccessTokenDO.getExpiresTime().getTime() - System.currentTimeMillis()) / 1000), 0));
|
||||
}
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "oauth2AccessTokenDO.id", target = "accessToken"),
|
||||
@Mapping(source = "adminRoleDOs.roleId", target = "roleIds")
|
||||
})
|
||||
OAuth2AuthenticationBO convertToAuthentication(OAuth2AccessTokenDO oauth2AccessTokenDO, List<AdminRoleDO> adminRoleDOs);
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package cn.iocoder.mall.admin.dao;
|
||||
|
||||
import cn.iocoder.mall.admin.dataobject.AdminDO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface AdminMapper {
|
||||
|
||||
AdminDO selectByUsername(@Param("username") String username);
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package cn.iocoder.mall.admin.dao;
|
||||
|
||||
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface AdminRoleMapper {
|
||||
|
||||
List<AdminRoleDO> selectByAdminId(@Param("adminId") Integer adminId);
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package cn.iocoder.mall.admin.dao;
|
||||
|
||||
import cn.iocoder.mall.admin.dataobject.OAuth2AccessTokenDO;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface OAuth2AccessTokenMapper {
|
||||
|
||||
void insert(OAuth2AccessTokenDO entity);
|
||||
|
||||
OAuth2AccessTokenDO selectByTokenId(String tokenId);
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package cn.iocoder.mall.admin.dao;
|
||||
|
||||
import cn.iocoder.mall.admin.dataobject.OAuth2RefreshTokenDO;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface OAuth2RefreshTokenMapper {
|
||||
|
||||
void insert(OAuth2RefreshTokenDO entity);
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package cn.iocoder.mall.admin.dao;
|
||||
|
||||
import cn.iocoder.mall.admin.dataobject.RoleResourceDO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface RoleResourceMapper {
|
||||
|
||||
List<RoleResourceDO> selectByResourceHandler(@Param("resourceHandler") String resourceHandler);
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package cn.iocoder.mall.admin.dataobject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 管理员实体
|
||||
*/
|
||||
public class AdminDO {
|
||||
|
||||
/**
|
||||
* 账号状态 - 开启
|
||||
*/
|
||||
public static final Integer STATUS_ENABLE = 1;
|
||||
/**
|
||||
* 账号状态 - 禁用
|
||||
*/
|
||||
public static final Integer STATUS_DISABLE = 2;
|
||||
|
||||
/**
|
||||
* 管理员编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 登陆账号
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickname;
|
||||
/**
|
||||
* 密码
|
||||
*
|
||||
* TODO 芋艿 暂时最简单的 MD5
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
/**
|
||||
* 账号状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public AdminDO setId(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public AdminDO setUsername(String username) {
|
||||
this.username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public AdminDO setNickname(String nickname) {
|
||||
this.nickname = nickname;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public AdminDO setPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public AdminDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public AdminDO setStatus(Integer status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package cn.iocoder.mall.admin.dataobject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* {@link AdminDO} 和 {@link RoleDO} 的关联表
|
||||
*/
|
||||
public class AdminRoleDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 管理员编号(外键:{@link AdminDO}
|
||||
*/
|
||||
private Integer adminId;
|
||||
/**
|
||||
* 角色编号(外键:{@link RoleDO}
|
||||
*/
|
||||
private Integer roleId;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
// TODO 芋艿 删除状态
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public AdminRoleDO setId(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getAdminId() {
|
||||
return adminId;
|
||||
}
|
||||
|
||||
public AdminRoleDO setAdminId(Integer adminId) {
|
||||
this.adminId = adminId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public AdminRoleDO setRoleId(Integer roleId) {
|
||||
this.roleId = roleId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public AdminRoleDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package cn.iocoder.mall.admin.dataobject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class OAuth2AccessTokenDO {
|
||||
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
private String refreshToken;
|
||||
/**
|
||||
* 管理员比那好
|
||||
*/
|
||||
private Integer adminId;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Date expiresTime;
|
||||
/**
|
||||
* 是否有效
|
||||
*/
|
||||
private Boolean valid;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenDO setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenDO setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getAdminId() {
|
||||
return adminId;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenDO setAdminId(Integer adminId) {
|
||||
this.adminId = adminId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getExpiresTime() {
|
||||
return expiresTime;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenDO setExpiresTime(Date expiresTime) {
|
||||
this.expiresTime = expiresTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenDO setValid(Boolean valid) {
|
||||
this.valid = valid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package cn.iocoder.mall.admin.dataobject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*
|
||||
* idx_uid
|
||||
*/
|
||||
public class OAuth2RefreshTokenDO {
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Integer adminId;
|
||||
/**
|
||||
* 是否有效
|
||||
*/
|
||||
private Boolean valid;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private Date expiresTime;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public OAuth2RefreshTokenDO setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getAdminId() {
|
||||
return adminId;
|
||||
}
|
||||
|
||||
public OAuth2RefreshTokenDO setAdminId(Integer adminId) {
|
||||
this.adminId = adminId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public OAuth2RefreshTokenDO setValid(Boolean valid) {
|
||||
this.valid = valid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getExpiresTime() {
|
||||
return expiresTime;
|
||||
}
|
||||
|
||||
public OAuth2RefreshTokenDO setExpiresTime(Date expiresTime) {
|
||||
this.expiresTime = expiresTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public OAuth2RefreshTokenDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package cn.iocoder.mall.admin.dataobject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 资源实体
|
||||
*/
|
||||
public class ResourceDO {
|
||||
|
||||
/**
|
||||
* 资源类型 - 菜单
|
||||
*/
|
||||
public static final Integer TYPE_MENU = 1;
|
||||
/**
|
||||
* 资源类型 - 操作
|
||||
*
|
||||
* 例如,按钮。
|
||||
*/
|
||||
public static final Integer TYPE_OPERATION = 2;
|
||||
|
||||
/**
|
||||
* 资源编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 资源名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 资源类型
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
/**
|
||||
* 展示名
|
||||
*/
|
||||
private String displayName;
|
||||
/**
|
||||
* 添加时间
|
||||
*/
|
||||
private Date createTime;
|
||||
/**
|
||||
* 父级资源编号(外键:{@link ResourceDO#id})
|
||||
*/
|
||||
private Integer pid;
|
||||
/**
|
||||
* 操作
|
||||
*
|
||||
* 当资源类型为【菜单】时,handler 配置为界面 URL ,或者前端组件名
|
||||
* 当资源类型为【操作】时,handler 配置为后端 URL 。举个例子,如果有一个「创建管理员」的表单,那么前端界面上的按钮可以根据这个 url 判断是否展示,后端接收到该 url 的请求时会判断是否有权限。
|
||||
*/
|
||||
private String handler;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public ResourceDO setId(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ResourceDO setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public ResourceDO setType(Integer type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
public ResourceDO setSort(Integer sort) {
|
||||
this.sort = sort;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public ResourceDO setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public ResourceDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getPid() {
|
||||
return pid;
|
||||
}
|
||||
|
||||
public ResourceDO setPid(Integer pid) {
|
||||
this.pid = pid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public ResourceDO setHandler(String handler) {
|
||||
this.handler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package cn.iocoder.mall.admin.dataobject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 角色实体
|
||||
*/
|
||||
public class RoleDO {
|
||||
|
||||
/**
|
||||
* 账号状态 - 开启
|
||||
*/
|
||||
public static final Integer STATUS_ENABLE = 1;
|
||||
/**
|
||||
* 账号状态 - 禁用
|
||||
*/
|
||||
public static final Integer STATUS_DISABLE = 2;
|
||||
|
||||
/**
|
||||
* 角色编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 角色名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public RoleDO setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public RoleDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public RoleDO setStatus(Integer status) {
|
||||
this.status = status;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package cn.iocoder.mall.admin.dataobject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* {@link RoleDO} 和 {@link ResourceDO} 的关联表
|
||||
*/
|
||||
public class RoleResourceDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 角色编号(外键:{@link RoleDO}
|
||||
*/
|
||||
private Integer roleId;
|
||||
/**
|
||||
* 资源比那好(外键:{@link ResourceDO}
|
||||
*/
|
||||
private Integer resourceId;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
// TODO 芋艿 删除状态
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public RoleResourceDO setId(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public RoleResourceDO setRoleId(Integer roleId) {
|
||||
this.roleId = roleId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public RoleResourceDO setCreateTime(Date createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Integer getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public RoleResourceDO setResourceId(Integer resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package cn.iocoder.mall.admin;
|
|
@ -0,0 +1,50 @@
|
|||
package cn.iocoder.mall.admin.service;
|
||||
|
||||
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.admin.api.AdminService;
|
||||
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
|
||||
import cn.iocoder.mall.admin.dao.AdminMapper;
|
||||
import cn.iocoder.mall.admin.dao.AdminRoleMapper;
|
||||
import cn.iocoder.mall.admin.dataobject.AdminDO;
|
||||
import cn.iocoder.mall.admin.dataobject.AdminRoleDO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@com.alibaba.dubbo.config.annotation.Service
|
||||
public class AdminServiceImpl implements AdminService {
|
||||
|
||||
@Autowired
|
||||
private AdminMapper adminMapper;
|
||||
@Autowired
|
||||
private AdminRoleMapper adminRoleMapper;
|
||||
|
||||
public CommonResult<AdminDO> validAdmin(String username, String password) {
|
||||
AdminDO admin = adminMapper.selectByUsername(username);
|
||||
// 账号不存在
|
||||
if (admin == null) {
|
||||
return ServiceExceptionUtil.error(AdminErrorCodeEnum.ADMIN_USERNAME_NOT_REGISTERED.getCode());
|
||||
}
|
||||
// 密码不正确
|
||||
if (DigestUtils.md5DigestAsHex(password.getBytes()).equals(admin.getPassword())) {
|
||||
return ServiceExceptionUtil.error(AdminErrorCodeEnum.ADMIN_PASSWORD_ERROR.getCode());
|
||||
}
|
||||
// 账号被禁用
|
||||
if (AdminDO.STATUS_DISABLE.equals(admin.getStatus())) {
|
||||
return ServiceExceptionUtil.error(AdminErrorCodeEnum.ADMIN_IS_DISABLE.getCode());
|
||||
}
|
||||
// 校验成功,返回管理员。并且,去掉一些非关键字段,考虑安全性。
|
||||
admin.setPassword(null);
|
||||
admin.setStatus(null);
|
||||
return CommonResult.success(admin);
|
||||
}
|
||||
|
||||
public List<AdminRoleDO> getAdminRoles(Integer adminId) {
|
||||
return adminRoleMapper.selectByAdminId(adminId);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package cn.iocoder.mall.admin.service;
|
||||
|
||||
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.admin.api.OAuth2Service;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AccessTokenBO;
|
||||
import cn.iocoder.mall.admin.api.bo.OAuth2AuthenticationBO;
|
||||
import cn.iocoder.mall.admin.api.constant.AdminErrorCodeEnum;
|
||||
import cn.iocoder.mall.admin.convert.OAuth2Convert;
|
||||
import cn.iocoder.mall.admin.dao.OAuth2AccessTokenMapper;
|
||||
import cn.iocoder.mall.admin.dao.OAuth2RefreshTokenMapper;
|
||||
import cn.iocoder.mall.admin.dataobject.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@com.alibaba.dubbo.config.annotation.Service
|
||||
public class OAuth2ServiceImpl implements OAuth2Service {
|
||||
|
||||
/**
|
||||
* 访问令牌过期时间,单位:毫秒
|
||||
*/
|
||||
@Value("${modules.oauth2-code-service.access-token-expire-time-millis}")
|
||||
private int accessTokenExpireTimeMillis;
|
||||
/**
|
||||
* 刷新令牌过期时间,单位:毫秒
|
||||
*/
|
||||
@Value("${modules.oauth2-code-service.refresh-token-expire-time-millis}")
|
||||
private int refreshTokenExpireTimeMillis;
|
||||
|
||||
@Autowired
|
||||
private AdminServiceImpl adminService;
|
||||
@Autowired
|
||||
private OAuth2AccessTokenMapper oauth2AccessTokenMapper;
|
||||
@Autowired
|
||||
private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;
|
||||
@Autowired
|
||||
private RoleServiceImpl roleService;
|
||||
|
||||
@Override
|
||||
public CommonResult<OAuth2AccessTokenBO> getAccessToken(String username, String password) {
|
||||
CommonResult<AdminDO> adminResult = adminService.validAdmin(username, password);
|
||||
// 校验失败,返回错误结果
|
||||
if (adminResult.isError()) {
|
||||
return CommonResult.error(adminResult);
|
||||
}
|
||||
AdminDO admin = adminResult.getData();
|
||||
// 创建刷新令牌
|
||||
OAuth2RefreshTokenDO oauth2RefreshTokenDO = createOAuth2RefreshToken(admin.getId());
|
||||
// 创建访问令牌
|
||||
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(admin.getId(), oauth2RefreshTokenDO.getId());
|
||||
// 转换返回
|
||||
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<OAuth2AuthenticationBO> checkToken(String accessToken) {
|
||||
OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByTokenId(accessToken);
|
||||
if (accessTokenDO == null) { // 不存在
|
||||
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_TOKEN_NOT_FOUND.getCode());
|
||||
}
|
||||
if (accessTokenDO.getExpiresTime().getTime() < System.currentTimeMillis()) { // 已过期
|
||||
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_TOKEN_EXPIRED.getCode());
|
||||
}
|
||||
if (!accessTokenDO.getValid()) { // 无效
|
||||
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_TOKEN_INVALID.getCode());
|
||||
}
|
||||
// 获得管理员拥有的角色
|
||||
List<AdminRoleDO> adminRoleDOs = adminService.getAdminRoles(accessTokenDO.getAdminId());
|
||||
return CommonResult.success(OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO, adminRoleDOs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> checkPermission(Integer adminId, Set<Integer> roleIds, String url) {
|
||||
// 避免传入的是空集合
|
||||
if (roleIds == null) {
|
||||
roleIds = Collections.emptySet();
|
||||
}
|
||||
// 校验权限
|
||||
List<RoleResourceDO> roleResourceDOs = roleService.getRoleByResourceHandler(url);
|
||||
if (roleResourceDOs.isEmpty()) { // 任何角色,都可以访问
|
||||
return CommonResult.success(true);
|
||||
}
|
||||
for (RoleResourceDO roleResourceDO : roleResourceDOs) {
|
||||
if (roleIds.contains(roleResourceDO.getId())) {
|
||||
return CommonResult.success(true);
|
||||
}
|
||||
}
|
||||
// 没有权限,返回错误
|
||||
return ServiceExceptionUtil.error(AdminErrorCodeEnum.OAUTH_INVALID_PERMISSION.getCode());
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenDO createOAuth2AccessToken(Integer adminId, String refreshToken) {
|
||||
OAuth2AccessTokenDO accessToken = new OAuth2AccessTokenDO().setId(generateAccessToken())
|
||||
.setRefreshToken(refreshToken)
|
||||
.setAdminId(adminId)
|
||||
.setExpiresTime(new Date(System.currentTimeMillis() + accessTokenExpireTimeMillis))
|
||||
.setValid(true);
|
||||
oauth2AccessTokenMapper.insert(accessToken);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
private OAuth2RefreshTokenDO createOAuth2RefreshToken(Integer adminId) {
|
||||
OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setId(generateRefreshToken())
|
||||
.setAdminId(adminId)
|
||||
.setExpiresTime(new Date(System.currentTimeMillis() + refreshTokenExpireTimeMillis))
|
||||
.setValid(true);
|
||||
oauth2RefreshTokenMapper.insert(refreshToken);
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
private String generateAccessToken() {
|
||||
return UUID.randomUUID().toString().replaceAll("-", "");
|
||||
}
|
||||
|
||||
private String generateRefreshToken() {
|
||||
return UUID.randomUUID().toString().replaceAll("-", "");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package cn.iocoder.mall.admin.service;
|
||||
|
||||
import cn.iocoder.mall.admin.api.RoleService;
|
||||
import cn.iocoder.mall.admin.dao.RoleResourceMapper;
|
||||
import cn.iocoder.mall.admin.dataobject.RoleResourceDO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@com.alibaba.dubbo.config.annotation.Service
|
||||
public class RoleServiceImpl implements RoleService {
|
||||
|
||||
@Autowired
|
||||
private RoleResourceMapper roleResourceMapper;
|
||||
|
||||
public List<RoleResourceDO> getRoleByResourceHandler(String resourceHandler) {
|
||||
return roleResourceMapper.selectByResourceHandler(resourceHandler);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
##################### 业务模块 #####################
|
||||
## OAuth2CodeService
|
||||
modules.oauth2-code-service.access-token-expire-time-millis = 2880000
|
||||
modules.oauth2-code-service.refresh-token-expire-time-millis = 43200000
|
|
@ -0,0 +1,32 @@
|
|||
spring:
|
||||
# datasource
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:33061/mall_admin?useSSL=false
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: 123456
|
||||
|
||||
# server
|
||||
server:
|
||||
port: 8083
|
||||
|
||||
# mybatis
|
||||
mybatis:
|
||||
config-location: classpath:mybatis-config.xml
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
type-aliases-package: cn.iocoder.mall.admin.dataobject
|
||||
|
||||
# dubbo
|
||||
dubbo:
|
||||
application:
|
||||
name: admin-service
|
||||
registry:
|
||||
address: zookeeper://127.0.0.1:2181
|
||||
protocol:
|
||||
port: -1
|
||||
name: dubbo
|
||||
scan:
|
||||
base-packages: cn.iocoder.mall.admin.service
|
||||
demo:
|
||||
service:
|
||||
version: 1.0.0
|
|
@ -0,0 +1,20 @@
|
|||
<?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.AdminMapper">
|
||||
|
||||
<!--<insert id="insert" parameterType="UserDO" useGeneratedKeys="true" keyProperty="id">-->
|
||||
<!--INSERT INTO users (-->
|
||||
<!--id, mobile, create_time-->
|
||||
<!--) VALUES (-->
|
||||
<!--#{id}, #{mobile}, #{createTime}-->
|
||||
<!--)-->
|
||||
<!--</insert>-->
|
||||
|
||||
<select id="selectByUsername" parameterType="String" resultType="AdminDO">
|
||||
SELECT
|
||||
id, username, nickname, password, status
|
||||
FROM admin
|
||||
WHERE username = #{username}
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -0,0 +1,21 @@
|
|||
<?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="insert" parameterType="UserDO" useGeneratedKeys="true" keyProperty="id">-->
|
||||
<!--INSERT INTO users (-->
|
||||
<!--id, mobile, create_time-->
|
||||
<!--) VALUES (-->
|
||||
<!--#{id}, #{mobile}, #{createTime}-->
|
||||
<!--)-->
|
||||
<!--</insert>-->
|
||||
|
||||
<select id="selectByAdminId" parameterType="Integer" resultType="AdminRoleDO">
|
||||
SELECT
|
||||
ar.id, ar.admin_id, ar.role_id
|
||||
FROM admin a, admin_role ar
|
||||
WHERE a.id = #{adminId}
|
||||
AND a.id = ar.admin_id
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -0,0 +1,22 @@
|
|||
<?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.OAuth2AccessTokenMapper">
|
||||
|
||||
<insert id="insert" parameterType="OAuth2AccessTokenDO">
|
||||
INSERT INTO oauth2_access_token (
|
||||
id, refresh_token, admin_id, valid, expires_time,
|
||||
create_time
|
||||
) VALUES (
|
||||
#{id}, #{refreshToken}, #{adminId}, #{valid}, #{expiresTime},
|
||||
#{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="selectByTokenId" parameterType="String" resultType="OAuth2AccessTokenDO">
|
||||
SELECT
|
||||
id, admin_id, valid, expires_time
|
||||
FROM oauth2_access_token
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -0,0 +1,13 @@
|
|||
<?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.OAuth2RefreshTokenMapper">
|
||||
|
||||
<insert id="insert" parameterType="OAuth2RefreshTokenDO">
|
||||
INSERT INTO oauth2_refresh_token (
|
||||
id, admin_id, valid, expires_time, create_time
|
||||
) VALUES (
|
||||
#{id}, #{adminId}, #{valid}, #{expiresTime}, #{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
</mapper>
|
|
@ -0,0 +1,21 @@
|
|||
<?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.RoleResourceMapper">
|
||||
|
||||
<!--<insert id="insert" parameterType="UserDO" useGeneratedKeys="true" keyProperty="id">-->
|
||||
<!--INSERT INTO users (-->
|
||||
<!--id, mobile, create_time-->
|
||||
<!--) VALUES (-->
|
||||
<!--#{id}, #{mobile}, #{createTime}-->
|
||||
<!--)-->
|
||||
<!--</insert>-->
|
||||
|
||||
<select id="selectByResourceHandler" parameterType="String" resultType="RoleResourceDO">
|
||||
SELECT
|
||||
rr.id, rr.role_id, rr.resouce_id
|
||||
FROM resouce r, role_resource rr
|
||||
WHERE r.handler = #{resourceHandler}
|
||||
AND r.id = rr.resource_id
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
<configuration>
|
||||
|
||||
<settings>
|
||||
<!-- 使用驼峰命名法转换字段。 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
</settings>
|
||||
|
||||
<typeAliases>
|
||||
<typeAlias alias="Integer" type="java.lang.Integer"/>
|
||||
<typeAlias alias="Long" type="java.lang.Long"/>
|
||||
<typeAlias alias="HashMap" type="java.util.HashMap"/>
|
||||
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
|
||||
<typeAlias alias="ArrayList" type="java.util.ArrayList"/>
|
||||
<typeAlias alias="LinkedList" type="java.util.LinkedList"/>
|
||||
</typeAliases>
|
||||
|
||||
</configuration>
|
|
@ -0,0 +1,22 @@
|
|||
<?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>mall-parent</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>admin</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>admin-application</module>
|
||||
<module>admin-sdk</module>
|
||||
<module>admin-service-api</module>
|
||||
<module>admin-service-impl</module>
|
||||
</modules>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,21 @@
|
|||
package cn.iocoder.common.framework.util;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class HttpUtil {
|
||||
|
||||
public static String obtainAccess(HttpServletRequest request) {
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (!StringUtils.hasText(authorization)) {
|
||||
return null;
|
||||
}
|
||||
int index = authorization.indexOf("Bearer ");
|
||||
if (index == -1) { // 未找到
|
||||
return null;
|
||||
}
|
||||
return authorization.substring(index + 7).trim();
|
||||
}
|
||||
|
||||
}
|
1
pom.xml
1
pom.xml
|
@ -18,6 +18,7 @@
|
|||
<module>order</module>
|
||||
<module>user</module>
|
||||
<module>common</module>
|
||||
<module>admin</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.mall.user.config;
|
||||
|
||||
import cn.iocoder.common.framework.config.GlobalExceptionHandler;
|
||||
import cn.iocoder.mall.user.sdk.interceptor.SecurityInterceptor;
|
||||
import cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
@ -13,11 +13,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||
@EnableWebMvc
|
||||
@Configuration
|
||||
@Import(value = {GlobalExceptionHandler.class, // 统一全局返回
|
||||
SecurityInterceptor.class}) // 安全拦截器,实现认证和授权功能。
|
||||
UserSecurityInterceptor.class}) // 安全拦截器,实现认证和授权功能。
|
||||
public class MVCConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private SecurityInterceptor securityInterceptor;
|
||||
private UserSecurityInterceptor securityInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
|
|
|
@ -46,7 +46,7 @@ public class PassportController {
|
|||
})
|
||||
public CommonResult<MobileRegisterVO> mobileRegister(@RequestParam("mobile") String mobile,
|
||||
@RequestParam("code") String code) {
|
||||
CommonResult<OAuth2AccessTokenBO> result = oauth2Service.getAccessToken2(mobile, code);
|
||||
CommonResult<OAuth2AccessTokenBO> result = oauth2Service.getAccessToken(mobile, code);
|
||||
return PassportConvert.INSTANCE.convert(result);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.mall.user.controller;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.user.sdk.context.SecurityContextHolder;
|
||||
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
|
||||
import cn.iocoder.mall.user.vo.UserInfoVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
@ -18,7 +18,7 @@ public class UserController {
|
|||
@ApiOperation(value = "用户信息")
|
||||
public CommonResult<UserInfoVO> info() {
|
||||
// TODO 芋艿,正在实现中
|
||||
UserInfoVO user = new UserInfoVO().setId(SecurityContextHolder.getContext().getUid());
|
||||
UserInfoVO user = new UserInfoVO().setId(UserSecurityContextHolder.getContext().getUid());
|
||||
return CommonResult.success(user);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package cn.iocoder.mall.user.sdk.context;
|
||||
|
||||
/**
|
||||
* {@link SecurityContext} Holder
|
||||
*
|
||||
* 参考 spring security 的 ThreadLocalSecurityContextHolderStrategy 类,简单实现。
|
||||
*/
|
||||
public class SecurityContextHolder {
|
||||
|
||||
private static final ThreadLocal<SecurityContext> securityContext = new ThreadLocal<SecurityContext>();
|
||||
|
||||
public static void setContext(SecurityContext context) {
|
||||
securityContext.set(context);
|
||||
}
|
||||
|
||||
public static SecurityContext getContext() {
|
||||
SecurityContext ctx = securityContext.get();
|
||||
// 为空时,设置一个空的进去
|
||||
if (ctx == null) {
|
||||
ctx = new SecurityContext(null);
|
||||
securityContext.set(ctx);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
securityContext.remove();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
package cn.iocoder.mall.user.sdk.context;
|
||||
|
||||
/**
|
||||
* Security 上下文
|
||||
* User Security 上下文
|
||||
*/
|
||||
public class SecurityContext {
|
||||
public class UserSecurityContext {
|
||||
|
||||
private final Long uid;
|
||||
|
||||
public SecurityContext(Long uid) {
|
||||
public UserSecurityContext(Long uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.mall.user.sdk.context;
|
||||
|
||||
/**
|
||||
* {@link UserSecurityContext} Holder
|
||||
*
|
||||
* 参考 spring security 的 ThreadLocalSecurityContextHolderStrategy 类,简单实现。
|
||||
*/
|
||||
public class UserSecurityContextHolder {
|
||||
|
||||
private static final ThreadLocal<UserSecurityContext> securityContext = new ThreadLocal<UserSecurityContext>();
|
||||
|
||||
public static void setContext(UserSecurityContext context) {
|
||||
securityContext.set(context);
|
||||
}
|
||||
|
||||
public static UserSecurityContext getContext() {
|
||||
UserSecurityContext ctx = securityContext.get();
|
||||
// 为空时,设置一个空的进去
|
||||
if (ctx == null) {
|
||||
ctx = new UserSecurityContext(null);
|
||||
securityContext.set(ctx);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
securityContext.remove();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
package cn.iocoder.mall.user.sdk.interceptor;
|
||||
|
||||
import cn.iocoder.common.framework.exception.ServiceException;
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.user.sdk.annotation.PermitAll;
|
||||
import cn.iocoder.mall.user.sdk.context.SecurityContext;
|
||||
import cn.iocoder.mall.user.sdk.context.SecurityContextHolder;
|
||||
import cn.iocoder.mall.user.sdk.context.UserSecurityContext;
|
||||
import cn.iocoder.mall.user.sdk.context.UserSecurityContextHolder;
|
||||
import cn.iocoder.mall.user.service.api.OAuth2Service;
|
||||
import cn.iocoder.mall.user.service.api.bo.OAuth2AuthenticationBO;
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
|
@ -21,7 +21,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
* 安全拦截器
|
||||
*/
|
||||
@Component
|
||||
public class SecurityInterceptor extends HandlerInterceptorAdapter {
|
||||
public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
|
||||
|
||||
@Reference
|
||||
private OAuth2Service oauth2Service;
|
||||
|
@ -29,7 +29,7 @@ public class SecurityInterceptor extends HandlerInterceptorAdapter {
|
|||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 校验访问令牌是否正确。若正确,返回授权信息
|
||||
String accessToken = obtainAccess(request);
|
||||
String accessToken = HttpUtil.obtainAccess(request);
|
||||
OAuth2AuthenticationBO authentication = null;
|
||||
if (accessToken != null) {
|
||||
CommonResult<OAuth2AuthenticationBO> result = oauth2Service.checkToken(accessToken);
|
||||
|
@ -38,8 +38,8 @@ public class SecurityInterceptor extends HandlerInterceptorAdapter {
|
|||
}
|
||||
authentication = result.getData();
|
||||
// 添加到 SecurityContext
|
||||
SecurityContext context = new SecurityContext(authentication.getUid());
|
||||
SecurityContextHolder.setContext(context);
|
||||
UserSecurityContext context = new UserSecurityContext(authentication.getUid());
|
||||
UserSecurityContextHolder.setContext(context);
|
||||
}
|
||||
// 校验是否需要已授权
|
||||
HandlerMethod method = (HandlerMethod) handler;
|
||||
|
@ -53,19 +53,7 @@ public class SecurityInterceptor extends HandlerInterceptorAdapter {
|
|||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 清空 SecurityContext
|
||||
SecurityContextHolder.clear();
|
||||
}
|
||||
|
||||
private String obtainAccess(HttpServletRequest request) {
|
||||
String authorization = request.getHeader("Authorization");
|
||||
if (!StringUtils.hasText(authorization)) {
|
||||
return null;
|
||||
}
|
||||
int index = authorization.indexOf("Bearer ");
|
||||
if (index == -1) { // 未找到
|
||||
return null;
|
||||
}
|
||||
return authorization.substring(index + 7).trim();
|
||||
UserSecurityContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* 提供 SDK 给其它服务,使用如下功能:
|
||||
*
|
||||
* 1. 通过 {@link } 拦截器,
|
||||
* 1. 通过 {@link cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor} 拦截器,实现需要登陆 URL 的鉴权
|
||||
*/
|
||||
package cn.iocoder.mall.user.sdk;
|
|
@ -1,27 +1,13 @@
|
|||
package cn.iocoder.mall.user.service.api;
|
||||
|
||||
|
||||
import cn.iocoder.common.framework.exception.ServiceException;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.user.service.api.bo.OAuth2AccessTokenBO;
|
||||
import cn.iocoder.mall.user.service.api.bo.OAuth2AuthenticationBO;
|
||||
|
||||
public interface OAuth2Service {
|
||||
|
||||
/**
|
||||
* 使用手机号 + 验证码,获取访问令牌等信息
|
||||
*
|
||||
* 如果手机未注册,并且验证码正确,进行自动注册。
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param code 验证码
|
||||
* @return 授权信息
|
||||
*/
|
||||
@Deprecated
|
||||
OAuth2AccessTokenBO getAccessToken(String mobile, String code)
|
||||
throws ServiceException;
|
||||
|
||||
CommonResult<OAuth2AccessTokenBO> getAccessToken2(String mobile, String code);
|
||||
CommonResult<OAuth2AccessTokenBO> getAccessToken(String mobile, String code);
|
||||
|
||||
/**
|
||||
* 校验访问令牌,获取身份信息( 不包括 accessToken 等等 )
|
||||
|
|
|
@ -47,31 +47,7 @@ public class MobileCodeServiceImpl implements MobileCodeService {
|
|||
* @param code 验证码
|
||||
* @return 手机验证码信息
|
||||
*/
|
||||
public MobileCodeDO validLastMobileCode(String mobile, String code) {
|
||||
MobileCodeDO mobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile);
|
||||
if (mobileCodePO == null) { // 若验证码不存在,抛出异常
|
||||
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_NOT_FOUND.getCode());
|
||||
}
|
||||
if (System.currentTimeMillis() - mobileCodePO.getCreateTime().getTime() >= codeExpireTimes) { // 验证码已过期
|
||||
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_EXPIRED.getCode());
|
||||
}
|
||||
if (mobileCodePO.getUsed()) { // 验证码已使用
|
||||
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_USED.getCode());
|
||||
}
|
||||
if (!mobileCodePO.getCode().equals(code)) {
|
||||
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.MOBILE_CODE_NOT_CORRECT.getCode());
|
||||
}
|
||||
return mobileCodePO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验手机号的最后一个手机验证码是否有效
|
||||
*
|
||||
* @param mobile 手机号
|
||||
* @param code 验证码
|
||||
* @return 手机验证码信息
|
||||
*/
|
||||
public CommonResult<MobileCodeDO> validLastMobileCode2(String mobile, String code) {
|
||||
public CommonResult<MobileCodeDO> validLastMobileCode(String mobile, String code) {
|
||||
MobileCodeDO mobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile);
|
||||
if (mobileCodePO == null) { // 若验证码不存在,抛出异常
|
||||
return ServiceExceptionUtil.error(UserErrorCodeEnum.MOBILE_CODE_NOT_FOUND.getCode());
|
||||
|
|
|
@ -52,29 +52,9 @@ public class OAuth2ServiceImpl implements OAuth2Service {
|
|||
|
||||
@Override
|
||||
@Transactional
|
||||
public OAuth2AccessTokenBO getAccessToken(String mobile, String code) {
|
||||
// 校验手机号的最后一个手机验证码是否有效
|
||||
MobileCodeDO mobileCodeDO = mobileCodeService.validLastMobileCode(mobile, code);
|
||||
// 获取用户
|
||||
UserDO userDO = userService.getUser(mobile);
|
||||
if (userDO == null) { // 用户不存在
|
||||
throw ServiceExceptionUtil.exception(UserErrorCodeEnum.USER_MOBILE_NOT_REGISTERED.getCode());
|
||||
}
|
||||
// 创建刷新令牌
|
||||
OAuth2RefreshTokenDO oauth2RefreshTokenDO = createOAuth2RefreshToken(userDO.getId());
|
||||
// 创建访问令牌
|
||||
OAuth2AccessTokenDO oauth2AccessTokenDO = createOAuth2AccessToken(userDO.getId(), oauth2RefreshTokenDO.getId());
|
||||
// 标记已使用
|
||||
mobileCodeService.useMobileCode(mobileCodeDO.getId(), userDO.getId());
|
||||
// 转换返回
|
||||
return OAuth2Convert.INSTANCE.convertToAccessTokenWithExpiresIn(oauth2AccessTokenDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommonResult<OAuth2AccessTokenBO> getAccessToken2(String mobile, String code) {
|
||||
public CommonResult<OAuth2AccessTokenBO> getAccessToken(String mobile, String code) {
|
||||
// 校验传入的 mobile 和 code 是否合法
|
||||
CommonResult<MobileCodeDO> result = mobileCodeService.validLastMobileCode2(mobile, code);
|
||||
CommonResult<MobileCodeDO> result = mobileCodeService.validLastMobileCode(mobile, code);
|
||||
if (result.isError()) {
|
||||
return CommonResult.error(result);
|
||||
}
|
||||
|
|
|
@ -4,17 +4,17 @@
|
|||
|
||||
<insert id="insert" parameterType="OAuth2AccessTokenDO">
|
||||
INSERT INTO oauth2_access_token (
|
||||
id, refresh_token, uid, valid, expires_time,
|
||||
id, refresh_token, adminId, valid, expires_time,
|
||||
create_time
|
||||
) VALUES (
|
||||
#{id}, #{refreshToken}, #{uid}, #{valid}, #{expiresTime},
|
||||
#{id}, #{refreshToken}, #{adminId}, #{valid}, #{expiresTime},
|
||||
#{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="selectByTokenId" parameterType="String" resultType="OAuth2AccessTokenDO">
|
||||
SELECT
|
||||
id, uid, valid, expires_time
|
||||
id, adminId, valid, expires_time
|
||||
FROM oauth2_access_token
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
<insert id="insert" parameterType="OAuth2RefreshTokenDO">
|
||||
INSERT INTO oauth2_refresh_token (
|
||||
id, uid, valid, expires_time, create_time
|
||||
id, adminId, valid, expires_time, create_time
|
||||
) VALUES (
|
||||
#{id}, #{uid}, #{valid}, #{expiresTime}, #{createTime}
|
||||
#{id}, #{adminId}, #{valid}, #{expiresTime}, #{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
|
|
Loading…
Reference in New Issue