diff --git a/admin/admin-application/pom.xml b/admin/admin-application/pom.xml
new file mode 100644
index 000000000..c316c806d
--- /dev/null
+++ b/admin/admin-application/pom.xml
@@ -0,0 +1,114 @@
+
+
+
+ admin
+ cn.iocoder.mall
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ admin-application
+
+
+ 1.3.0.Final
+
+
+
+
+ cn.iocoder.mall
+ admin-service-impl
+ 1.0-SNAPSHOT
+
+
+
+ cn.iocoder.mall
+ common-framework
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ com.alibaba
+ dubbo
+ 2.6.5
+
+
+
+ com.alibaba.boot
+ dubbo-spring-boot-starter
+ 0.2.1.RELEASE
+
+
+
+ org.apache.curator
+ curator-framework
+ 2.12.0
+
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+ 1.8
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-application/src/main/java/cn/iocoder/mall/admin/AdminApplication.java b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/AdminApplication.java
new file mode 100644
index 000000000..46385f023
--- /dev/null
+++ b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/AdminApplication.java
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-application/src/main/java/cn/iocoder/mall/admin/config/MVCConfiguration.java b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/config/MVCConfiguration.java
new file mode 100644
index 000000000..70d181d5d
--- /dev/null
+++ b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/config/MVCConfiguration.java
@@ -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/");
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-application/src/main/java/cn/iocoder/mall/admin/config/SwaggerConfiguration.java b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/config/SwaggerConfiguration.java
new file mode 100644
index 000000000..a6b26cd2b
--- /dev/null
+++ b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/config/SwaggerConfiguration.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-application/src/main/java/cn/iocoder/mall/admin/controller/AdminController.java b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/controller/AdminController.java
new file mode 100644
index 000000000..33f556690
--- /dev/null
+++ b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/controller/AdminController.java
@@ -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 {
+
+
+
+}
\ No newline at end of file
diff --git a/admin/admin-application/src/main/java/cn/iocoder/mall/admin/controller/PassportController.java b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/controller/PassportController.java
new file mode 100644
index 000000000..4e5c5ef7f
--- /dev/null
+++ b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/controller/PassportController.java
@@ -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 login(@RequestParam("username") String username,
+ @RequestParam("password") String password) {
+ CommonResult result = oauth2Service.getAccessToken(username, password);
+ return PassportConvert.INSTANCE.convert(result);
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-application/src/main/java/cn/iocoder/mall/admin/convert/PassportConvert.java b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/convert/PassportConvert.java
new file mode 100644
index 000000000..1572dfbfd
--- /dev/null
+++ b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/convert/PassportConvert.java
@@ -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 convert(CommonResult oauth2AccessTokenBO);
+
+}
\ No newline at end of file
diff --git a/admin/admin-application/src/main/java/cn/iocoder/mall/admin/vo/PassportLoginVO.java b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/vo/PassportLoginVO.java
new file mode 100644
index 000000000..b1376bcde
--- /dev/null
+++ b/admin/admin-application/src/main/java/cn/iocoder/mall/admin/vo/PassportLoginVO.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-application/src/main/resources/application.yaml b/admin/admin-application/src/main/resources/application.yaml
new file mode 100644
index 000000000..e6d37fdf2
--- /dev/null
+++ b/admin/admin-application/src/main/resources/application.yaml
@@ -0,0 +1,7 @@
+spring:
+ application:
+ name: admin-application
+
+# server
+server:
+ port: 8083
\ No newline at end of file
diff --git a/admin/admin-sdk/pom.xml b/admin/admin-sdk/pom.xml
new file mode 100644
index 000000000..b83e37b75
--- /dev/null
+++ b/admin/admin-sdk/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ admin
+ cn.iocoder.mall
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ application-sdk
+
+
+ org.springframework
+ spring-context
+ 5.1.5.RELEASE
+ compile
+
+
+ org.springframework
+ spring-webmvc
+ 5.1.5.RELEASE
+ compile
+
+
+ com.alibaba
+ dubbo
+ 2.6.5
+ compile
+
+
+ javax.servlet
+ servlet-api
+ 2.5
+ compile
+
+
+ cn.iocoder.mall
+ common-framework
+ 1.0-SNAPSHOT
+ compile
+
+
+ cn.iocoder.mall
+ admin-service-api
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/context/AdminSecurityContext.java b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/context/AdminSecurityContext.java
new file mode 100644
index 000000000..a720ad09c
--- /dev/null
+++ b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/context/AdminSecurityContext.java
@@ -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 roleIds;
+
+ public AdminSecurityContext(Integer adminId, Set roleIds) {
+ this.adminId = adminId;
+ this.roleIds = roleIds;
+ }
+
+ public Integer getAdminId() {
+ return adminId;
+ }
+
+ public Set getRoleIds() {
+ return roleIds;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/context/AdminSecurityContextHolder.java b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/context/AdminSecurityContextHolder.java
new file mode 100644
index 000000000..9eca2c5bf
--- /dev/null
+++ b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/context/AdminSecurityContextHolder.java
@@ -0,0 +1,30 @@
+package cn.iocoder.mall.admin.sdk.context;
+
+/**
+ * {@link AdminSecurityContext} Holder
+ *
+ * 参考 spring security 的 ThreadLocalSecurityContextHolderStrategy 类,简单实现。
+ */
+public class AdminSecurityContextHolder {
+
+ private static final ThreadLocal securityContext = new ThreadLocal();
+
+ 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();
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/interceptor/AdminSecurityInterceptor.java b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/interceptor/AdminSecurityInterceptor.java
new file mode 100644
index 000000000..6ba914581
--- /dev/null
+++ b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/interceptor/AdminSecurityInterceptor.java
@@ -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 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 roleIds = authentication != null ? authentication.getRoleIds() : null;
+ String url = request.getRequestURI();
+ CommonResult result = oauth2Service.checkPermission(adminId, roleIds, url);
+ if (result.isError()) {
+ throw new ServiceException(result.getCode(), result.getMessage());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/package-info.java b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/package-info.java
new file mode 100644
index 000000000..57a5a7f74
--- /dev/null
+++ b/admin/admin-sdk/src/main/java/cn/iocoder/mall/admin/sdk/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 提供 SDK 给其它服务,使用如下功能:
+ *
+ * 1. 通过 {@link cn.iocoder.mall.admin.sdk.interceptor.UserSecurityInterceptor} 拦截器,实现需要登陆 URL 的鉴权
+ */
+package cn.iocoder.mall.admin.sdk;
\ No newline at end of file
diff --git a/admin/admin-service-api/pom.xml b/admin/admin-service-api/pom.xml
new file mode 100644
index 000000000..bb3089468
--- /dev/null
+++ b/admin/admin-service-api/pom.xml
@@ -0,0 +1,27 @@
+
+
+
+ admin
+ cn.iocoder.mall
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ admin-service-api
+
+
+ cn.iocoder.mall
+ common-framework
+ 1.0-SNAPSHOT
+
+
+ cn.iocoder.mall
+ admin-service-api
+ 1.0-SNAPSHOT
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/AdminService.java b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/AdminService.java
new file mode 100644
index 000000000..36079c724
--- /dev/null
+++ b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/AdminService.java
@@ -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;
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/OAuth2Service.java b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/OAuth2Service.java
new file mode 100644
index 000000000..cb62661dc
--- /dev/null
+++ b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/OAuth2Service.java
@@ -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 getAccessToken(String username, String password);
+
+ /**
+ * 校验访问令牌,获取身份信息( 不包括 accessToken 等等 )
+ *
+ * @param accessToken 访问令牌
+ * @return 授权信息
+ */
+ CommonResult checkToken(String accessToken);
+
+ /**
+ * TODO 校验权限
+ *
+ * @param adminId 管理员编号
+ * @param roleIds 管理员拥有的角色编号的集合
+ * @param url 指定 URL
+ * @return 是否有权限
+ */
+ CommonResult checkPermission(Integer adminId, Set roleIds, String url);
+
+ // TODO @see 刷新 token
+
+ // TODO @see 移除 token
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/RoleService.java b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/RoleService.java
new file mode 100644
index 000000000..b19deee5d
--- /dev/null
+++ b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/RoleService.java
@@ -0,0 +1,4 @@
+package cn.iocoder.mall.admin.api;
+
+public interface RoleService {
+}
diff --git a/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/bo/OAuth2AccessTokenBO.java b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/bo/OAuth2AccessTokenBO.java
new file mode 100644
index 000000000..3d66558d2
--- /dev/null
+++ b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/bo/OAuth2AccessTokenBO.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/bo/OAuth2AuthenticationBO.java b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/bo/OAuth2AuthenticationBO.java
new file mode 100644
index 000000000..f499e25ca
--- /dev/null
+++ b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/bo/OAuth2AuthenticationBO.java
@@ -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 roleIds;
+
+ public Integer getAdminId() {
+ return adminId;
+ }
+
+ public OAuth2AuthenticationBO setAdminId(Integer adminId) {
+ this.adminId = adminId;
+ return this;
+ }
+
+ public Set getRoleIds() {
+ return roleIds;
+ }
+
+ public OAuth2AuthenticationBO setRoleIds(Set roleIds) {
+ this.roleIds = roleIds;
+ return this;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/constant/AdminErrorCodeEnum.java b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/constant/AdminErrorCodeEnum.java
new file mode 100644
index 000000000..85d445641
--- /dev/null
+++ b/admin/admin-service-api/src/main/java/cn/iocoder/mall/admin/api/constant/AdminErrorCodeEnum.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/pom.xml b/admin/admin-service-impl/pom.xml
new file mode 100644
index 000000000..578ae0764
--- /dev/null
+++ b/admin/admin-service-impl/pom.xml
@@ -0,0 +1,77 @@
+
+
+
+ admin
+ cn.iocoder.mall
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ admin-service-impl
+
+
+ 1.3.0.Final
+
+
+
+
+ com.alibaba
+ dubbo
+ 2.6.5
+ compile
+
+
+ cn.iocoder.mall
+ admin-service-api
+ 1.0-SNAPSHOT
+ compile
+
+
+
+ mysql
+ mysql-connector-java
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.0.0
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+ 1.8
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/config/DatabaseConfiguration.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/config/DatabaseConfiguration.java
new file mode 100644
index 000000000..efa7bf84a
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/config/DatabaseConfiguration.java
@@ -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
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/config/ServiceExceptionConfiguration.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/config/ServiceExceptionConfiguration.java
new file mode 100644
index 000000000..de21043ac
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/config/ServiceExceptionConfiguration.java
@@ -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());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/convert/OAuth2Convert.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/convert/OAuth2Convert.java
new file mode 100644
index 000000000..57ccd8f8f
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/convert/OAuth2Convert.java
@@ -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 adminRoleDOs);
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/AdminMapper.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/AdminMapper.java
new file mode 100644
index 000000000..d27e96b02
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/AdminMapper.java
@@ -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);
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/AdminRoleMapper.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/AdminRoleMapper.java
new file mode 100644
index 000000000..3a0399e97
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/AdminRoleMapper.java
@@ -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 selectByAdminId(@Param("adminId") Integer adminId);
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/OAuth2AccessTokenMapper.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/OAuth2AccessTokenMapper.java
new file mode 100644
index 000000000..4e1a1399a
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/OAuth2AccessTokenMapper.java
@@ -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);
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/OAuth2RefreshTokenMapper.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/OAuth2RefreshTokenMapper.java
new file mode 100644
index 000000000..73659b94b
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/OAuth2RefreshTokenMapper.java
@@ -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);
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/RoleResourceMapper.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/RoleResourceMapper.java
new file mode 100644
index 000000000..92f882c06
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dao/RoleResourceMapper.java
@@ -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 selectByResourceHandler(@Param("resourceHandler") String resourceHandler);
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/AdminDO.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/AdminDO.java
new file mode 100644
index 000000000..1874a0ea6
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/AdminDO.java
@@ -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;
+ }
+
+}
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/AdminRoleDO.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/AdminRoleDO.java
new file mode 100644
index 000000000..048ac41e6
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/AdminRoleDO.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/OAuth2AccessTokenDO.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/OAuth2AccessTokenDO.java
new file mode 100644
index 000000000..373fad188
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/OAuth2AccessTokenDO.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/OAuth2RefreshTokenDO.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/OAuth2RefreshTokenDO.java
new file mode 100644
index 000000000..271d60cb5
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/OAuth2RefreshTokenDO.java
@@ -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;
+ }
+
+}
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/ResourceDO.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/ResourceDO.java
new file mode 100644
index 000000000..b8b278263
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/ResourceDO.java
@@ -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;
+ }
+
+}
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/RoleDO.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/RoleDO.java
new file mode 100644
index 000000000..e5e34b4c7
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/RoleDO.java
@@ -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;
+ }
+
+}
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/RoleResourceDO.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/RoleResourceDO.java
new file mode 100644
index 000000000..79ed702da
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/dataobject/RoleResourceDO.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/package-info.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/package-info.java
new file mode 100644
index 000000000..35990e238
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.mall.admin;
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/AdminServiceImpl.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/AdminServiceImpl.java
new file mode 100644
index 000000000..64033c463
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/AdminServiceImpl.java
@@ -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 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 getAdminRoles(Integer adminId) {
+ return adminRoleMapper.selectByAdminId(adminId);
+ }
+
+}
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/OAuth2ServiceImpl.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/OAuth2ServiceImpl.java
new file mode 100644
index 000000000..22e173568
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/OAuth2ServiceImpl.java
@@ -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 getAccessToken(String username, String password) {
+ CommonResult 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 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 adminRoleDOs = adminService.getAdminRoles(accessTokenDO.getAdminId());
+ return CommonResult.success(OAuth2Convert.INSTANCE.convertToAuthentication(accessTokenDO, adminRoleDOs));
+ }
+
+ @Override
+ public CommonResult checkPermission(Integer adminId, Set roleIds, String url) {
+ // 避免传入的是空集合
+ if (roleIds == null) {
+ roleIds = Collections.emptySet();
+ }
+ // 校验权限
+ List 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("-", "");
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/RoleServiceImpl.java b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/RoleServiceImpl.java
new file mode 100644
index 000000000..22aace4d9
--- /dev/null
+++ b/admin/admin-service-impl/src/main/java/cn/iocoder/mall/admin/service/RoleServiceImpl.java
@@ -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 getRoleByResourceHandler(String resourceHandler) {
+ return roleResourceMapper.selectByResourceHandler(resourceHandler);
+ }
+
+}
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/config/application.properties b/admin/admin-service-impl/src/main/resources/config/application.properties
new file mode 100644
index 000000000..2196c16be
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/config/application.properties
@@ -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
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/config/application.yaml b/admin/admin-service-impl/src/main/resources/config/application.yaml
new file mode 100644
index 000000000..d5fa428f0
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/config/application.yaml
@@ -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
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/mapper/AdminMapper.xml b/admin/admin-service-impl/src/main/resources/mapper/AdminMapper.xml
new file mode 100644
index 000000000..1855f9514
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/mapper/AdminMapper.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/mapper/AdminRoleMapper.xml b/admin/admin-service-impl/src/main/resources/mapper/AdminRoleMapper.xml
new file mode 100644
index 000000000..63a517de0
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/mapper/AdminRoleMapper.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/mapper/OAuth2AccessTokenMapper.xml b/admin/admin-service-impl/src/main/resources/mapper/OAuth2AccessTokenMapper.xml
new file mode 100644
index 000000000..47c1cf10c
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/mapper/OAuth2AccessTokenMapper.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ INSERT INTO oauth2_access_token (
+ id, refresh_token, admin_id, valid, expires_time,
+ create_time
+ ) VALUES (
+ #{id}, #{refreshToken}, #{adminId}, #{valid}, #{expiresTime},
+ #{createTime}
+ )
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/mapper/OAuth2RefreshTokenMapper.xml b/admin/admin-service-impl/src/main/resources/mapper/OAuth2RefreshTokenMapper.xml
new file mode 100644
index 000000000..04cefd1b7
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/mapper/OAuth2RefreshTokenMapper.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ INSERT INTO oauth2_refresh_token (
+ id, admin_id, valid, expires_time, create_time
+ ) VALUES (
+ #{id}, #{adminId}, #{valid}, #{expiresTime}, #{createTime}
+ )
+
+
+
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/mapper/RoleResourceMapper.xml b/admin/admin-service-impl/src/main/resources/mapper/RoleResourceMapper.xml
new file mode 100644
index 000000000..d3d57df15
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/mapper/RoleResourceMapper.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/admin-service-impl/src/main/resources/mybatis-config.xml b/admin/admin-service-impl/src/main/resources/mybatis-config.xml
new file mode 100644
index 000000000..7f604cc7e
--- /dev/null
+++ b/admin/admin-service-impl/src/main/resources/mybatis-config.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/admin/pom.xml b/admin/pom.xml
new file mode 100644
index 000000000..ea03dd1cf
--- /dev/null
+++ b/admin/pom.xml
@@ -0,0 +1,22 @@
+
+
+
+ mall-parent
+ cn.iocoder.mall
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ admin
+ pom
+
+ admin-application
+ admin-sdk
+ admin-service-api
+ admin-service-impl
+
+
+
+
\ No newline at end of file
diff --git a/common/common-framework/src/main/java/cn/iocoder/common/framework/util/HttpUtil.java b/common/common-framework/src/main/java/cn/iocoder/common/framework/util/HttpUtil.java
new file mode 100644
index 000000000..4cd649b6e
--- /dev/null
+++ b/common/common-framework/src/main/java/cn/iocoder/common/framework/util/HttpUtil.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index a76b6db58..2b71f2742 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
order
user
common
+ admin
pom
diff --git a/user/user-application/src/main/java/cn/iocoder/mall/user/config/MVCConfiguration.java b/user/user-application/src/main/java/cn/iocoder/mall/user/config/MVCConfiguration.java
index 2994a78e0..d3e420ffe 100644
--- a/user/user-application/src/main/java/cn/iocoder/mall/user/config/MVCConfiguration.java
+++ b/user/user-application/src/main/java/cn/iocoder/mall/user/config/MVCConfiguration.java
@@ -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) {
diff --git a/user/user-application/src/main/java/cn/iocoder/mall/user/controller/PassportController.java b/user/user-application/src/main/java/cn/iocoder/mall/user/controller/PassportController.java
index d68898914..71c9112b4 100644
--- a/user/user-application/src/main/java/cn/iocoder/mall/user/controller/PassportController.java
+++ b/user/user-application/src/main/java/cn/iocoder/mall/user/controller/PassportController.java
@@ -46,7 +46,7 @@ public class PassportController {
})
public CommonResult mobileRegister(@RequestParam("mobile") String mobile,
@RequestParam("code") String code) {
- CommonResult result = oauth2Service.getAccessToken2(mobile, code);
+ CommonResult result = oauth2Service.getAccessToken(mobile, code);
return PassportConvert.INSTANCE.convert(result);
}
diff --git a/user/user-application/src/main/java/cn/iocoder/mall/user/controller/UserController.java b/user/user-application/src/main/java/cn/iocoder/mall/user/controller/UserController.java
index d84c797a6..e12d96bbd 100644
--- a/user/user-application/src/main/java/cn/iocoder/mall/user/controller/UserController.java
+++ b/user/user-application/src/main/java/cn/iocoder/mall/user/controller/UserController.java
@@ -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 info() {
// TODO 芋艿,正在实现中
- UserInfoVO user = new UserInfoVO().setId(SecurityContextHolder.getContext().getUid());
+ UserInfoVO user = new UserInfoVO().setId(UserSecurityContextHolder.getContext().getUid());
return CommonResult.success(user);
}
diff --git a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/SecurityContext.java b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/UserSecurityContext.java
similarity index 61%
rename from user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/SecurityContext.java
rename to user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/UserSecurityContext.java
index 12f93bc69..cfadba524 100644
--- a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/SecurityContext.java
+++ b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/UserSecurityContext.java
@@ -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;
}
diff --git a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/SecurityContextHolder.java b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/UserSecurityContextHolder.java
similarity index 50%
rename from user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/SecurityContextHolder.java
rename to user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/UserSecurityContextHolder.java
index a618c3591..cb51cabeb 100644
--- a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/SecurityContextHolder.java
+++ b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/context/UserSecurityContextHolder.java
@@ -1,23 +1,23 @@
package cn.iocoder.mall.user.sdk.context;
/**
- * {@link SecurityContext} Holder
+ * {@link UserSecurityContext} Holder
*
* 参考 spring security 的 ThreadLocalSecurityContextHolderStrategy 类,简单实现。
*/
-public class SecurityContextHolder {
+public class UserSecurityContextHolder {
- private static final ThreadLocal securityContext = new ThreadLocal();
+ private static final ThreadLocal securityContext = new ThreadLocal();
- public static void setContext(SecurityContext context) {
+ public static void setContext(UserSecurityContext context) {
securityContext.set(context);
}
- public static SecurityContext getContext() {
- SecurityContext ctx = securityContext.get();
+ public static UserSecurityContext getContext() {
+ UserSecurityContext ctx = securityContext.get();
// 为空时,设置一个空的进去
if (ctx == null) {
- ctx = new SecurityContext(null);
+ ctx = new UserSecurityContext(null);
securityContext.set(ctx);
}
return ctx;
diff --git a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/SecurityInterceptor.java b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserSecurityInterceptor.java
similarity index 69%
rename from user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/SecurityInterceptor.java
rename to user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserSecurityInterceptor.java
index 511f18c5f..cb7a8d165 100644
--- a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/SecurityInterceptor.java
+++ b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/interceptor/UserSecurityInterceptor.java
@@ -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 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();
}
}
\ No newline at end of file
diff --git a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/package-info.java b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/package-info.java
index 656d91a8c..36a1a7694 100644
--- a/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/package-info.java
+++ b/user/user-sdk/src/main/java/cn/iocoder/mall/user/sdk/package-info.java
@@ -1,6 +1,6 @@
/**
* 提供 SDK 给其它服务,使用如下功能:
*
- * 1. 通过 {@link } 拦截器,
+ * 1. 通过 {@link cn.iocoder.mall.user.sdk.interceptor.UserSecurityInterceptor} 拦截器,实现需要登陆 URL 的鉴权
*/
package cn.iocoder.mall.user.sdk;
\ No newline at end of file
diff --git a/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/OAuth2Service.java b/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/OAuth2Service.java
index 1ac861335..5bc3224c7 100644
--- a/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/OAuth2Service.java
+++ b/user/user-service-api/src/main/java/cn/iocoder/mall/user/service/api/OAuth2Service.java
@@ -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 getAccessToken2(String mobile, String code);
+ CommonResult getAccessToken(String mobile, String code);
/**
* 校验访问令牌,获取身份信息( 不包括 accessToken 等等 )
diff --git a/user/user-application/src/main/java/cn/iocoder/mall/user/config/ServiceExceptionConfiguration.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/config/ServiceExceptionConfiguration.java
similarity index 100%
rename from user/user-application/src/main/java/cn/iocoder/mall/user/config/ServiceExceptionConfiguration.java
rename to user/user-service-impl/src/main/java/cn/iocoder/mall/user/config/ServiceExceptionConfiguration.java
diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/MobileCodeServiceImpl.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/MobileCodeServiceImpl.java
index 0615f4093..5df55576a 100644
--- a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/MobileCodeServiceImpl.java
+++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/MobileCodeServiceImpl.java
@@ -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 validLastMobileCode2(String mobile, String code) {
+ public CommonResult validLastMobileCode(String mobile, String code) {
MobileCodeDO mobileCodePO = mobileCodeMapper.selectLast1ByMobile(mobile);
if (mobileCodePO == null) { // 若验证码不存在,抛出异常
return ServiceExceptionUtil.error(UserErrorCodeEnum.MOBILE_CODE_NOT_FOUND.getCode());
diff --git a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/OAuth2ServiceImpl.java b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/OAuth2ServiceImpl.java
index c06960b2b..f17b54866 100644
--- a/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/OAuth2ServiceImpl.java
+++ b/user/user-service-impl/src/main/java/cn/iocoder/mall/user/service/OAuth2ServiceImpl.java
@@ -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 getAccessToken2(String mobile, String code) {
+ public CommonResult getAccessToken(String mobile, String code) {
// 校验传入的 mobile 和 code 是否合法
- CommonResult result = mobileCodeService.validLastMobileCode2(mobile, code);
+ CommonResult result = mobileCodeService.validLastMobileCode(mobile, code);
if (result.isError()) {
return CommonResult.error(result);
}
diff --git a/user/user-service-impl/src/main/resources/mapper/OAuth2AccessTokenMapper.xml b/user/user-service-impl/src/main/resources/mapper/OAuth2AccessTokenMapper.xml
index 763b77790..ee3b4dcaa 100644
--- a/user/user-service-impl/src/main/resources/mapper/OAuth2AccessTokenMapper.xml
+++ b/user/user-service-impl/src/main/resources/mapper/OAuth2AccessTokenMapper.xml
@@ -4,17 +4,17 @@
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}
)
diff --git a/user/user-service-impl/src/main/resources/mapper/OAuth2RefreshTokenMapper.xml b/user/user-service-impl/src/main/resources/mapper/OAuth2RefreshTokenMapper.xml
index 3d9277070..b4646aee4 100644
--- a/user/user-service-impl/src/main/resources/mapper/OAuth2RefreshTokenMapper.xml
+++ b/user/user-service-impl/src/main/resources/mapper/OAuth2RefreshTokenMapper.xml
@@ -4,9 +4,9 @@
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}
)