Pārlūkot izejas kodu

1、增加用户授权登录接口

rayson 1 gadu atpakaļ
vecāks
revīzija
27dbf8ee1d
26 mainītis faili ar 1137 papildinājumiem un 26 dzēšanām
  1. 35 5
      citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java
  2. 8 0
      menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/ErrorCodeConstants.java
  3. 50 0
      menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/sms/MdeSmsSceneEnum.java
  4. 6 0
      menduner/menduner-system-biz/pom.xml
  5. 127 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/MdeAuthController.java
  6. 41 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthCheckCodeReqVO.java
  7. 56 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthLoginReqVO.java
  8. 38 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthLoginRespVO.java
  9. 58 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSmsLoginReqVO.java
  10. 26 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSmsSendReqVO.java
  11. 35 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSmsValidateReqVO.java
  12. 34 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSocialLoginReqVO.java
  13. 30 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthWeixinMiniAppLoginReqVO.java
  14. 31 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeWeixinJsapiSignatureRespVO.java
  15. 7 7
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/permission/MdeRoleController.java
  16. 1 1
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/user/vo/MdeUserRespVO.java
  17. 1 1
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/user/vo/MdeUserSaveReqVO.java
  18. 31 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/convert/MdeAuthConvert.java
  19. 1 1
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/user/MdeUserDO.java
  20. 4 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/user/MdeUserMapper.java
  21. 13 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/framework/rpc/config/RpcConfiguration.java
  22. 4 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/framework/rpc/package-info.java
  23. 88 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/auth/MdeAuthService.java
  24. 284 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/auth/MdeAuthServiceImpl.java
  25. 51 0
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/user/MdeUserService.java
  26. 77 11
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/user/MdeUserServiceImpl.java

+ 35 - 5
citu-module-system/citu-module-system-biz/src/test/java/com/citu/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java

@@ -12,11 +12,13 @@ import com.citu.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;
 import com.citu.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
 import com.citu.module.system.framework.sms.core.property.SmsChannelProperties;
 import com.google.common.collect.Lists;
+import com.tencentcloudapi.common.AbstractModel;
+import com.tencentcloudapi.common.Credential;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.common.profile.ClientProfile;
+import com.tencentcloudapi.common.profile.HttpProfile;
 import com.tencentcloudapi.sms.v20210111.SmsClient;
-import com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse;
-import com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus;
-import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
-import com.tencentcloudapi.sms.v20210111.models.SendStatus;
+import com.tencentcloudapi.sms.v20210111.models.*;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -25,8 +27,10 @@ import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 
+import static cn.hutool.core.util.RandomUtil.randomLong;
 import static com.citu.framework.common.util.json.JsonUtils.toJsonString;
 import static com.citu.framework.test.core.util.RandomUtils.*;
+import static com.citu.framework.test.core.util.RandomUtils.randomString;
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.when;
@@ -43,6 +47,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
             .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
             .setSignature("芋道源码");
 
+
     @InjectMocks
     private TencentSmsClient smsClient = new TencentSmsClient(properties);
 
@@ -188,7 +193,7 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
     @Test
     public void testGetSmsTemplate() throws Throwable {
         // 准备参数
-        Long apiTemplateId = randomLongId();
+        Long apiTemplateId = randomLong();
         String requestId = randomString();
 
         // mock 方法
@@ -227,4 +232,29 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest {
                 "未知审核状态(3)");
     }
 
+    public static void main(String[] args) throws TencentCloudSDKException {
+        // 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
+        // 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
+        // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
+        Credential cred = new Credential("AKIDXtyoNfgRv96mCLEqBF5exzkERN2QXOzJ", "4sCF0UifMnysVrxmKbbLp55oGRLzLQEl");
+        // 实例化一个http选项,可选的,没有特殊需求可以跳过
+        HttpProfile httpProfile = new HttpProfile();
+        httpProfile.setEndpoint("sms.tencentcloudapi.com");
+        // 实例化一个client选项,可选的,没有特殊需求可以跳过
+        ClientProfile clientProfile = new ClientProfile();
+        clientProfile.setHttpProfile(httpProfile);
+        // 实例化要请求产品的client对象,clientProfile是可选的
+        SmsClient client = new SmsClient(cred, "ap-nanjing", clientProfile);
+        // 实例化一个请求对象,每个接口都会对应一个request对象
+        DescribeSmsTemplateListRequest req = new DescribeSmsTemplateListRequest();
+        req.setInternational(0L);
+        req.setTemplateIdSet(new Long[]{Long.parseLong("1106279")});
+        // 返回的resp是一个DescribeSmsTemplateListResponse的实例,与请求对象对应
+        DescribeSmsTemplateListResponse resp = client.DescribeSmsTemplateList(req);
+
+        // 输出json格式的字符串回包
+        System.out.println(AbstractModel.toJsonString(resp));
+    }
+
+
 }

+ 8 - 0
menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/ErrorCodeConstants.java

@@ -54,6 +54,14 @@ public interface ErrorCodeConstants {
 
     // ========== 门墩儿-用户登录 1_200_001_000 ==========
     ErrorCode MDE_USER_NOT_EXISTS = new ErrorCode(1_200_001_001, "门墩儿-用户登录不存在");
+    ErrorCode MDE_USER_MOBILE_NOT_EXISTS = new ErrorCode(1_200_001_002, "手机号未注册用户");
+    ErrorCode MDE_USER_MOBILE_USED = new ErrorCode(1_200_001_003, "修改手机失败,该手机号({})已经被使用");
+
+    // ========== AUTH 模块 1_200_002_000 ==========
+    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_200_002_001, "登录失败,账号密码不正确");
+    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_200_002_002, "登录失败,账号被禁用");
+    ErrorCode AUTH_SOCIAL_USER_NOT_FOUND = new ErrorCode(1_200_002_003, "登录失败,解析不到三方登录信息");
+    ErrorCode AUTH_MOBILE_USED = new ErrorCode(1_200_002_004, "手机号已经被使用");
 
     // ========== 角色模块 1_200_003_000 ==========
     ErrorCode MDE_ROLE_NOT_EXISTS = new ErrorCode(1_200_003_001, "角色不存在");

+ 50 - 0
menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/sms/MdeSmsSceneEnum.java

@@ -0,0 +1,50 @@
+package com.citu.module.menduner.system.enums.sms;
+
+import cn.hutool.core.util.ArrayUtil;
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 用户短信验证码发送场景的枚举
+ *
+ * @author rayson
+ */
+@Getter
+@AllArgsConstructor
+public enum MdeSmsSceneEnum implements IntArrayValuable {
+
+    MENDUNER_LOGIN(1, "menduner-user-sms-login", "手机号登陆"),
+    MENDUNER_UPDATE_MOBILE(2, "menduner-user-update-mobile", "求职者 - 修改手机"),
+    MENDUNER_UPDATE_PASSWORD(3, "menduner-user-update-password", "求职者 - 修改密码"),
+    MENDUNER_RESET_PASSWORD(4, "menduner-user-reset-password", "求职者 - 忘记密码");
+
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MdeSmsSceneEnum::getScene).toArray();
+
+    /**
+     * 验证场景的编号
+     */
+    private final Integer scene;
+    /**
+     * 模版编码
+     */
+    private final String templateCode;
+    /**
+     * 描述
+     */
+    private final String description;
+
+    @Override
+    public int[] array() {
+        return ARRAYS;
+    }
+
+    public static MdeSmsSceneEnum getCodeByScene(Integer scene) {
+        return ArrayUtil.firstMatch(sceneEnum -> sceneEnum.getScene().equals(scene),
+                values());
+    }
+
+}

+ 6 - 0
menduner/menduner-system-biz/pom.xml

@@ -35,6 +35,12 @@
             <artifactId>menduner-system-api</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>com.citu</groupId>
+            <artifactId>citu-module-system-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
 
         <!-- 业务组件 -->
         <dependency>

+ 127 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/MdeAuthController.java

@@ -0,0 +1,127 @@
+package com.citu.module.menduner.system.controller.admin.auth;
+
+import cn.hutool.core.util.StrUtil;
+import com.citu.framework.common.enums.UserTypeEnum;
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.security.config.SecurityProperties;
+import com.citu.framework.security.core.util.SecurityFrameworkUtils;
+import com.citu.module.menduner.system.controller.admin.auth.vo.*;
+import com.citu.module.menduner.system.convert.MdeAuthConvert;
+import com.citu.module.menduner.system.service.auth.MdeAuthService;
+import com.citu.module.system.api.social.SocialClientApi;
+import com.citu.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户登录")
+@RestController
+@RequestMapping("/menduner/system/auth")
+@Validated
+@Slf4j
+public class MdeAuthController {
+
+    @Resource
+    private MdeAuthService authService;
+
+    @Resource
+    private SocialClientApi socialClientApi;
+
+    @Resource
+    private SecurityProperties securityProperties;
+
+    @PostMapping("/login")
+    @Operation(summary = "使用手机 + 密码登录")
+    public CommonResult<MdeAuthLoginRespVO> login(@RequestBody @Valid MdeAuthLoginReqVO reqVO) {
+        return success(authService.login(reqVO));
+    }
+
+    @PostMapping("/logout")
+    @PermitAll
+    @Operation(summary = "登出系统")
+    public CommonResult<Boolean> logout(HttpServletRequest request) {
+        String token = SecurityFrameworkUtils.obtainAuthorization(request,
+                securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
+        if (StrUtil.isNotBlank(token)) {
+            authService.logout(token);
+        }
+        return success(true);
+    }
+
+    @PostMapping("/refresh-token")
+    @Operation(summary = "刷新令牌")
+    @Parameter(name = "refreshToken", description = "刷新令牌", required = true)
+    public CommonResult<MdeAuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
+        return success(authService.refreshToken(refreshToken));
+    }
+
+    // ========== 短信登录相关 ==========
+
+    @PostMapping("/sms-login")
+    @Operation(summary = "使用手机 + 验证码登录")
+    public CommonResult<MdeAuthLoginRespVO> smsLogin(@RequestBody @Valid MdeAuthSmsLoginReqVO reqVO) {
+        return success(authService.smsLogin(reqVO));
+    }
+
+    @PostMapping("/send-sms-code")
+    @Operation(summary = "发送手机验证码")
+    public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid MdeAuthSmsSendReqVO reqVO) {
+        authService.sendSmsCode(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    @PostMapping("/validate-sms-code")
+    @Operation(summary = "校验手机验证码")
+    public CommonResult<Boolean> validateSmsCode(@RequestBody @Valid MdeAuthSmsValidateReqVO reqVO) {
+        authService.validateSmsCode(getLoginUserId(), reqVO);
+        return success(true);
+    }
+
+    // ========== 社交登录相关 ==========
+
+    @GetMapping("/social-auth-redirect")
+    @Operation(summary = "社交授权的跳转")
+    @Parameters({
+            @Parameter(name = "type", description = "社交类型", required = true),
+            @Parameter(name = "redirectUri", description = "回调路径")
+    })
+    public CommonResult<String> socialAuthRedirect(@RequestParam("type") Integer type,
+                                                   @RequestParam("redirectUri") String redirectUri) {
+        return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri));
+    }
+
+    @PostMapping("/social-login")
+    @Operation(summary = "社交快捷登录,使用 code 授权码", description = "适合未登录的用户,但是社交账号已绑定用户")
+    public CommonResult<MdeAuthLoginRespVO> socialLogin(@RequestBody @Valid MdeAuthSocialLoginReqVO reqVO) {
+        return success(authService.socialLogin(reqVO));
+    }
+
+    @PostMapping("/weixin-mini-app-login")
+    @Operation(summary = "微信小程序的一键登录")
+    public CommonResult<MdeAuthLoginRespVO> weixinMiniAppLogin(@RequestBody @Valid MdeAuthWeixinMiniAppLoginReqVO reqVO) {
+        return success(authService.weixinMiniAppLogin(reqVO));
+    }
+
+    @PostMapping("/create-weixin-jsapi-signature")
+    @Operation(summary = "创建微信 JS SDK 初始化所需的签名",
+            description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
+    public CommonResult<SocialWxJsapiSignatureRespDTO> createWeixinMpJsapiSignature(@RequestParam("url") String url) {
+        SocialWxJsapiSignatureRespDTO signature = socialClientApi.createWxMpJsapiSignature(
+                UserTypeEnum.MEMBER.getValue(), url).getCheckedData();
+        return success(MdeAuthConvert.INSTANCE.convert(signature));
+    }
+
+
+}

+ 41 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthCheckCodeReqVO.java

@@ -0,0 +1,41 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import com.citu.framework.common.validation.InEnum;
+import com.citu.framework.common.validation.Mobile;
+import com.citu.module.system.enums.sms.SmsSceneEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+// TODO 芋艿:code review 相关逻辑
+@Schema(description = "menduner - 校验验证码 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MdeAuthCheckCodeReqVO {
+
+    @Schema(description = "手机号", example = "15601691234")
+    @NotBlank(message = "手机号不能为空")
+    @Mobile
+    private String mobile;
+
+    @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotBlank(message = "手机验证码不能为空")
+    @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+    @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+    private String code;
+
+    @Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
+    @NotNull(message = "发送场景不能为空")
+    @InEnum(SmsSceneEnum.class)
+    private Integer scene;
+
+}

+ 56 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthLoginReqVO.java

@@ -0,0 +1,56 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import cn.hutool.core.util.StrUtil;
+import com.citu.framework.common.validation.InEnum;
+import com.citu.framework.common.validation.Mobile;
+import com.citu.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "menduner - 手机 + 密码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MdeAuthLoginReqVO {
+
+    @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
+    @NotEmpty(message = "手机号不能为空")
+    @Mobile
+    private String mobile;
+
+    @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
+    @NotEmpty(message = "密码不能为空")
+    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
+    private String password;
+
+    // ========== 绑定社交登录时,需要传递如下参数 ==========
+
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @InEnum(SocialTypeEnum.class)
+    private Integer socialType;
+
+    @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String socialCode;
+
+    @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+    private String socialState;
+
+    @AssertTrue(message = "授权码不能为空")
+    public boolean isSocialCodeValid() {
+        return socialType == null || StrUtil.isNotEmpty(socialCode);
+    }
+
+    @AssertTrue(message = "授权 state 不能为空")
+    public boolean isSocialState() {
+        return socialType == null || StrUtil.isNotEmpty(socialState);
+    }
+
+}

+ 38 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthLoginRespVO.java

@@ -0,0 +1,38 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "menduner - 登录 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MdeAuthLoginRespVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long userId;
+
+    @Schema(description = "访问令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "happy")
+    private String accessToken;
+
+    @Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "nice")
+    private String refreshToken;
+
+    @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime expiresTime;
+
+    /**
+     * 仅社交登录、社交绑定时会返回
+     *
+     * 为什么需要返回?微信公众号、微信小程序支付需要传递 openid 给支付接口
+     */
+    @Schema(description = "社交用户 openid", example = "qq768")
+    private String openid;
+
+}

+ 58 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSmsLoginReqVO.java

@@ -0,0 +1,58 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import cn.hutool.core.util.StrUtil;
+import com.citu.framework.common.validation.InEnum;
+import com.citu.framework.common.validation.Mobile;
+import com.citu.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "menduner - 手机 + 验证码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MdeAuthSmsLoginReqVO {
+
+    @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
+    @NotEmpty(message = "手机号不能为空")
+    @Mobile
+    private String mobile;
+
+    @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "手机验证码不能为空")
+    @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+    @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+    private String code;
+
+    // ========== 绑定社交登录时,需要传递如下参数 ==========
+
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @InEnum(SocialTypeEnum.class)
+    private Integer socialType;
+
+    @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private String socialCode;
+
+    @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+    private String socialState;
+
+    @AssertTrue(message = "授权码不能为空")
+    public boolean isSocialCodeValid() {
+        return socialType == null || StrUtil.isNotEmpty(socialCode);
+    }
+
+    @AssertTrue(message = "授权 state 不能为空")
+    public boolean isSocialState() {
+        return socialType == null || StrUtil.isNotEmpty(socialState);
+    }
+
+}

+ 26 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSmsSendReqVO.java

@@ -0,0 +1,26 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import com.citu.framework.common.validation.InEnum;
+import com.citu.framework.common.validation.Mobile;
+import com.citu.module.system.enums.sms.SmsSceneEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "menduner - 发送手机验证码 Request VO")
+@Data
+@Accessors(chain = true)
+public class MdeAuthSmsSendReqVO {
+
+    @Schema(description = "手机号", example = "15601691234")
+    @Mobile
+    private String mobile;
+
+    @Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
+    @NotNull(message = "发送场景不能为空")
+    @InEnum(SmsSceneEnum.class)
+    private Integer scene;
+
+}

+ 35 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSmsValidateReqVO.java

@@ -0,0 +1,35 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import com.citu.framework.common.validation.InEnum;
+import com.citu.framework.common.validation.Mobile;
+import com.citu.module.system.enums.sms.SmsSceneEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "menduner - 校验手机验证码 Request VO")
+@Data
+@Accessors(chain = true)
+public class MdeAuthSmsValidateReqVO {
+
+    @Schema(description = "手机号", example = "15601691234")
+    @Mobile
+    private String mobile;
+
+    @Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
+    @NotNull(message = "发送场景不能为空")
+    @InEnum(SmsSceneEnum.class)
+    private Integer scene;
+
+    @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "手机验证码不能为空")
+    @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+    @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+    private String code;
+
+}

+ 34 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthSocialLoginReqVO.java

@@ -0,0 +1,34 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import com.citu.framework.common.validation.InEnum;
+import com.citu.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "menduner - 社交快捷登录 Request VO,使用 code 授权码")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MdeAuthSocialLoginReqVO {
+
+    @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    @InEnum(SocialTypeEnum.class)
+    @NotNull(message = "社交平台的类型不能为空")
+    private Integer type;
+
+    @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotEmpty(message = "授权码不能为空")
+    private String code;
+
+    @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+    @NotEmpty(message = "state 不能为空")
+    private String state;
+
+}

+ 30 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeAuthWeixinMiniAppLoginReqVO.java

@@ -0,0 +1,30 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "menduner - 微信小程序手机登录 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MdeAuthWeixinMiniAppLoginReqVO {
+
+    @Schema(description = "手机 code,小程序通过 wx.getPhoneNumber 方法获得", requiredMode = Schema.RequiredMode.REQUIRED, example = "hello")
+    @NotEmpty(message = "手机 code 不能为空")
+    private String phoneCode;
+
+    @Schema(description = "登录 code,小程序通过 wx.login 方法获得", requiredMode = Schema.RequiredMode.REQUIRED, example = "word")
+    @NotEmpty(message = "登录 code 不能为空")
+    private String loginCode;
+
+    @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+    @NotEmpty(message = "state 不能为空")
+    private String state;
+
+}

+ 31 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/auth/vo/MdeWeixinJsapiSignatureRespVO.java

@@ -0,0 +1,31 @@
+package com.citu.module.menduner.system.controller.admin.auth.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Schema(description = "menduner - 微信公众号 JSAPI 签名 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class MdeWeixinJsapiSignatureRespVO {
+
+    @Schema(description = "微信公众号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "hello")
+    private String appId;
+
+    @Schema(description = "匿名串", requiredMode = Schema.RequiredMode.REQUIRED, example = "world")
+    private String nonceStr;
+
+    @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long timestamp;
+
+    @Schema(description = "URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    private String url;
+
+    @Schema(description = "签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "阿巴阿巴")
+    private String signature;
+
+}

+ 7 - 7
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/permission/MdeRoleController.java

@@ -32,7 +32,7 @@ import static java.util.Collections.singleton;
 
 @Tag(name = "管理后台 - 角色")
 @RestController
-@RequestMapping("/menduner/system/role")
+@RequestMapping("/menduner/system/mde-role")
 @Validated
 public class MdeRoleController {
 
@@ -41,14 +41,14 @@ public class MdeRoleController {
 
     @PostMapping("/create")
     @Operation(summary = "创建角色")
-    @PreAuthorize("@ss.hasPermission('system:role:create')")
+    @PreAuthorize("@ss.hasPermission('system:mde-role:create')")
     public CommonResult<Long> createRole(@Valid @RequestBody MdeRoleSaveReqVO createReqVO) {
         return success(roleService.createRole(createReqVO, null));
     }
 
     @PutMapping("/update")
     @Operation(summary = "修改角色")
-    @PreAuthorize("@ss.hasPermission('system:role:update')")
+    @PreAuthorize("@ss.hasPermission('system:mde-role:update')")
     public CommonResult<Boolean> updateRole(@Valid @RequestBody MdeRoleSaveReqVO updateReqVO) {
         roleService.updateRole(updateReqVO);
         return success(true);
@@ -57,7 +57,7 @@ public class MdeRoleController {
     @DeleteMapping("/delete")
     @Operation(summary = "删除角色")
     @Parameter(name = "id", description = "角色编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('system:role:delete')")
+    @PreAuthorize("@ss.hasPermission('system:mde-role:delete')")
     public CommonResult<Boolean> deleteRole(@RequestParam("id") Long id) {
         roleService.deleteRole(id);
         return success(true);
@@ -65,7 +65,7 @@ public class MdeRoleController {
 
     @GetMapping("/get")
     @Operation(summary = "获得角色信息")
-    @PreAuthorize("@ss.hasPermission('system:role:query')")
+    @PreAuthorize("@ss.hasPermission('system:mde-role:query')")
     public CommonResult<MdeRoleRespVO> getRole(@RequestParam("id") Long id) {
         MdeRoleDO role = roleService.getRole(id);
         return success(BeanUtils.toBean(role, MdeRoleRespVO.class));
@@ -73,7 +73,7 @@ public class MdeRoleController {
 
     @GetMapping("/page")
     @Operation(summary = "获得角色分页")
-    @PreAuthorize("@ss.hasPermission('system:role:query')")
+    @PreAuthorize("@ss.hasPermission('system:mde-role:query')")
     public CommonResult<PageResult<MdeRoleRespVO>> getRolePage(MdeRolePageReqVO pageReqVO) {
         PageResult<MdeRoleDO> pageResult = roleService.getRolePage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, MdeRoleRespVO.class));
@@ -90,7 +90,7 @@ public class MdeRoleController {
     @GetMapping("/export-excel")
     @Operation(summary = "导出角色 Excel")
     @ApiAccessLog(operateType = EXPORT)
-    @PreAuthorize("@ss.hasPermission('system:role:export')")
+    @PreAuthorize("@ss.hasPermission('system:mde-role:export')")
     public void export(HttpServletResponse response, @Validated MdeRolePageReqVO exportReqVO) throws IOException {
         exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
         List<MdeRoleDO> list = roleService.getRolePage(exportReqVO).getList();

+ 1 - 1
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/user/vo/MdeUserRespVO.java

@@ -50,7 +50,7 @@ public class MdeUserRespVO {
 
     @Schema(description = "注册终端")
     @ExcelProperty("注册终端")
-    private String registerTerminal;
+    private Integer registerTerminal;
 
     @Schema(description = "最后登录IP")
     @ExcelProperty("最后登录IP")

+ 1 - 1
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/admin/user/vo/MdeUserSaveReqVO.java

@@ -40,7 +40,7 @@ public class MdeUserSaveReqVO {
     private String registerIp;
 
     @Schema(description = "注册终端")
-    private String registerTerminal;
+    private Integer registerTerminal;
 
     @Schema(description = "最后登录IP")
     private String loginIp;

+ 31 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/convert/MdeAuthConvert.java

@@ -0,0 +1,31 @@
+package com.citu.module.menduner.system.convert;
+
+
+import com.citu.module.menduner.system.controller.admin.auth.vo.*;
+import com.citu.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
+import com.citu.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
+import com.citu.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
+import com.citu.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
+import com.citu.module.system.api.social.dto.SocialUserBindReqDTO;
+import com.citu.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface MdeAuthConvert {
+
+    MdeAuthConvert INSTANCE = Mappers.getMapper(MdeAuthConvert.class);
+
+    SocialUserBindReqDTO convert(Long userId, Integer userType, MdeAuthSocialLoginReqVO reqVO);
+
+    SmsCodeSendReqDTO convert(MdeAuthSmsSendReqVO reqVO);
+
+    SmsCodeUseReqDTO convert(MdeAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
+
+    MdeAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean, String openid);
+
+    SmsCodeValidateReqDTO convert(MdeAuthSmsValidateReqVO bean);
+
+    SocialWxJsapiSignatureRespDTO convert(SocialWxJsapiSignatureRespDTO bean);
+
+}

+ 1 - 1
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/user/MdeUserDO.java

@@ -62,7 +62,7 @@ public class MdeUserDO extends BaseDO {
     /**
      * 注册终端
      */
-    private String registerTerminal;
+    private Integer registerTerminal;
     /**
      * 最后登录IP
      */

+ 4 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/user/MdeUserMapper.java

@@ -15,6 +15,10 @@ import org.apache.ibatis.annotations.Mapper;
 @Mapper
 public interface MdeUserMapper extends BaseMapperX<MdeUserDO> {
 
+    default MdeUserDO selectByPhone(String mobile) {
+        return selectOne(MdeUserDO::getPhone, mobile);
+    }
+
     default PageResult<MdeUserDO> selectPage(MdeUserPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<MdeUserDO>()
                 .likeIfPresent(MdeUserDO::getUsername, reqVO.getUsername())

+ 13 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/framework/rpc/config/RpcConfiguration.java

@@ -0,0 +1,13 @@
+package com.citu.module.menduner.system.framework.rpc.config;
+
+import com.citu.module.system.api.logger.LoginLogApi;
+import com.citu.module.system.api.sms.SmsCodeApi;
+import com.citu.module.system.api.social.SocialClientApi;
+import com.citu.module.system.api.social.SocialUserApi;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+@EnableFeignClients(clients = {SmsCodeApi.class, LoginLogApi.class, SocialUserApi.class, SocialClientApi.class})
+public class RpcConfiguration {
+}

+ 4 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/framework/rpc/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 占位
+ */
+package com.citu.module.menduner.system.framework.rpc;

+ 88 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/auth/MdeAuthService.java

@@ -0,0 +1,88 @@
+package com.citu.module.menduner.system.service.auth;
+
+
+import com.citu.module.menduner.system.controller.admin.auth.vo.*;
+
+import javax.validation.Valid;
+
+/**
+ * menduner的认证 Service 接口
+ * <p>
+ * 提供用户的账号密码登录、token 的校验等认证相关的功能
+ *
+ * @author rayson
+ */
+public interface MdeAuthService {
+
+    /**
+     * 手机 + 密码登录
+     *
+     * @param reqVO 登录信息
+     * @return 登录结果
+     */
+    MdeAuthLoginRespVO login(@Valid MdeAuthLoginReqVO reqVO);
+
+    /**
+     * 基于 token 退出登录
+     *
+     * @param token token
+     */
+    void logout(String token);
+
+    /**
+     * 手机 + 验证码登陆
+     *
+     * @param reqVO 登陆信息
+     * @return 登录结果
+     */
+    MdeAuthLoginRespVO smsLogin(@Valid MdeAuthSmsLoginReqVO reqVO);
+
+    /**
+     * 社交登录,使用 code 授权码
+     *
+     * @param reqVO 登录信息
+     * @return 登录结果
+     */
+    MdeAuthLoginRespVO socialLogin(@Valid MdeAuthSocialLoginReqVO reqVO);
+
+    /**
+     * 微信小程序的一键登录
+     *
+     * @param reqVO 登录信息
+     * @return 登录结果
+     */
+    MdeAuthLoginRespVO weixinMiniAppLogin(MdeAuthWeixinMiniAppLoginReqVO reqVO);
+
+    /**
+     * 获得社交认证 URL
+     *
+     * @param type        社交平台类型
+     * @param redirectUri 跳转地址
+     * @return 认证 URL
+     */
+    String getSocialAuthorizeUrl(Integer type, String redirectUri);
+
+    /**
+     * 给用户发送短信验证码
+     *
+     * @param userId 用户编号
+     * @param reqVO  发送信息
+     */
+    void sendSmsCode(Long userId, MdeAuthSmsSendReqVO reqVO);
+
+    /**
+     * 校验短信验证码是否正确
+     *
+     * @param userId 用户编号
+     * @param reqVO  校验信息
+     */
+    void validateSmsCode(Long userId, MdeAuthSmsValidateReqVO reqVO);
+
+    /**
+     * 刷新访问令牌
+     *
+     * @param refreshToken 刷新令牌
+     * @return 登录结果
+     */
+    MdeAuthLoginRespVO refreshToken(String refreshToken);
+}

+ 284 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/auth/MdeAuthServiceImpl.java

@@ -0,0 +1,284 @@
+package com.citu.module.menduner.system.service.auth;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
+import com.citu.framework.common.enums.CommonStatusEnum;
+import com.citu.framework.common.enums.TerminalEnum;
+import com.citu.framework.common.enums.UserTypeEnum;
+import com.citu.framework.common.util.monitor.TracerUtils;
+import com.citu.framework.common.util.servlet.ServletUtils;
+import com.citu.module.menduner.system.controller.admin.auth.vo.*;
+import com.citu.module.menduner.system.convert.MdeAuthConvert;
+import com.citu.module.menduner.system.dal.dataobject.user.MdeUserDO;
+import com.citu.module.menduner.system.enums.sms.MdeSmsSceneEnum;
+import com.citu.module.menduner.system.service.user.MdeUserService;
+import com.citu.module.system.api.logger.LoginLogApi;
+import com.citu.module.system.api.logger.dto.LoginLogCreateReqDTO;
+import com.citu.module.system.api.oauth2.OAuth2TokenApi;
+import com.citu.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
+import com.citu.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
+import com.citu.module.system.api.sms.SmsCodeApi;
+import com.citu.module.system.api.social.SocialClientApi;
+import com.citu.module.system.api.social.SocialUserApi;
+import com.citu.module.system.api.social.dto.SocialUserBindReqDTO;
+import com.citu.module.system.api.social.dto.SocialUserRespDTO;
+import com.citu.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
+import com.citu.module.system.enums.logger.LoginLogTypeEnum;
+import com.citu.module.system.enums.logger.LoginResultEnum;
+import com.citu.module.system.enums.oauth2.OAuth2ClientConstants;
+import com.citu.module.system.enums.sms.SmsSceneEnum;
+import com.citu.module.system.enums.social.SocialTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.citu.framework.common.util.servlet.ServletUtils.getClientIP;
+import static com.citu.framework.web.core.util.WebFrameworkUtils.getTerminal;
+import static com.citu.module.menduner.system.enums.ErrorCodeConstants.*;
+import static com.citu.module.menduner.system.enums.ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS;
+import static com.citu.module.menduner.system.enums.ErrorCodeConstants.AUTH_LOGIN_USER_DISABLED;
+import static com.citu.module.system.enums.ErrorCodeConstants.*;
+
+/**
+ * menduner的认证 Service 接口 实现
+ *
+ * @author rayson
+ **/
+@Service
+@Slf4j
+public class MdeAuthServiceImpl implements MdeAuthService {
+
+    @Resource
+    private MdeUserService userService;
+    @Resource
+    private SmsCodeApi smsCodeApi;
+    @Resource
+    private LoginLogApi loginLogApi;
+    @Resource
+    private SocialUserApi socialUserApi;
+    @Resource
+    private SocialClientApi socialClientApi;
+    @Resource
+    private OAuth2TokenApi oauth2TokenApi;
+
+    @Override
+    public MdeAuthLoginRespVO login(MdeAuthLoginReqVO reqVO) {
+        // 使用手机 + 密码,进行登录。
+        MdeUserDO user = check(reqVO.getMobile(), reqVO.getPassword());
+
+        // 如果 socialType 非空,说明需要绑定社交用户
+        String openid = null;
+        if (reqVO.getSocialType() != null) {
+            openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())).getCheckedData();
+        }
+
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, openid);
+    }
+
+    private MdeUserDO check(String mobile, String password) {
+        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;
+        // 校验账号是否存在
+        MdeUserDO user = userService.getUserByPhone(mobile);
+        if (user == null) {
+            createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
+            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
+        }
+        if (!userService.isPasswordMatch(password, user.getPassword())) {
+            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);
+            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
+        }
+        // 校验是否禁用
+        if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);
+            throw exception(AUTH_LOGIN_USER_DISABLED);
+        }
+        return user;
+    }
+
+    @Override
+    public void logout(String token) {
+        // 删除访问令牌
+        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token).getCheckedData();
+        if (accessTokenRespDTO == null) {
+            return;
+        }
+        // 删除成功,则记录登出日志
+        createLogoutLog(accessTokenRespDTO.getUserId());
+    }
+
+    private void createLogoutLog(Long userId) {
+        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
+        reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());
+        reqDTO.setTraceId(TracerUtils.getTraceId());
+        reqDTO.setUserId(userId);
+        reqDTO.setUserType(getUserType().getValue());
+        reqDTO.setUsername(getPhone(userId));
+        reqDTO.setUserAgent(ServletUtils.getUserAgent());
+        reqDTO.setUserIp(getClientIP());
+        reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());
+        loginLogApi.createLoginLog(reqDTO);
+    }
+
+    @Override
+    @Transactional
+    public MdeAuthLoginRespVO smsLogin(MdeAuthSmsLoginReqVO reqVO) {
+        // 校验验证码
+        String userIp = getClientIP();
+        smsCodeApi.useSmsCode(MdeAuthConvert.INSTANCE.convert(reqVO, MdeSmsSceneEnum.MENDUNER_LOGIN.getScene(), userIp)).getCheckedData();
+
+        // 获得获得注册用户
+        MdeUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp, getTerminal());
+        Assert.notNull(user, "获取用户失败,结果为空");
+
+        // 如果 socialType 非空,说明需要绑定社交用户
+        String openid = null;
+        if (reqVO.getSocialType() != null) {
+            openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())).getCheckedData();
+        }
+
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, openid);
+    }
+
+    @Override
+    @Transactional
+    public MdeAuthLoginRespVO socialLogin(MdeAuthSocialLoginReqVO reqVO) {
+        // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
+        SocialUserRespDTO socialUser = socialUserApi.getSocialUserByCode(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
+                reqVO.getCode(), reqVO.getState()).getCheckedData();
+        if (socialUser == null) {
+            throw exception(AUTH_SOCIAL_USER_NOT_FOUND);
+        }
+
+        // 情况一:已绑定,直接读取用户信息
+        MdeUserDO user;
+        if (socialUser.getUserId() != null) {
+            user = userService.getMdeUser(socialUser.getUserId());
+            // 情况二:未绑定,注册用户 + 绑定用户
+        } else {
+            user = userService.createUser(socialUser.getAvatar(), getClientIP(), getTerminal());
+            socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+                    reqVO.getType(), reqVO.getCode(), reqVO.getState()));
+        }
+        if (user == null) {
+            throw exception(USER_NOT_EXISTS);
+        }
+
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, user.getPhone(), LoginLogTypeEnum.LOGIN_SOCIAL, socialUser.getOpenid());
+    }
+
+    @Override
+    @Transactional
+    public MdeAuthLoginRespVO weixinMiniAppLogin(MdeAuthWeixinMiniAppLoginReqVO reqVO) {
+        // 获得对应的手机号信息
+        SocialWxPhoneNumberInfoRespDTO phoneNumberInfo = socialClientApi.getWxMaPhoneNumberInfo(
+                UserTypeEnum.MEMBER.getValue(), reqVO.getPhoneCode()).getCheckedData();
+        Assert.notNull(phoneNumberInfo, "获得手机信息失败,结果为空");
+
+        // 获得获得注册用户
+        MdeUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(),
+                getClientIP(), TerminalEnum.WECHAT_MINI_PROGRAM.getTerminal());
+        Assert.notNull(user, "获取用户失败,结果为空");
+
+        // 绑定社交用户
+        String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+                SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), reqVO.getState())).getCheckedData();
+
+        // 创建 Token 令牌,记录登录日志
+        return createTokenAfterLoginSuccess(user, user.getPhone(), LoginLogTypeEnum.LOGIN_SOCIAL, openid);
+    }
+
+    @Override
+    public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
+        return socialClientApi.getAuthorizeUrl(type, UserTypeEnum.MEMBER.getValue(), redirectUri).getCheckedData();
+    }
+
+    @Override
+    public void sendSmsCode(Long userId, MdeAuthSmsSendReqVO reqVO) {
+        // 情况 1:如果是修改手机场景,需要校验新手机号是否已经注册,说明不能使用该手机了
+        if (Objects.equals(reqVO.getScene(), SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene())) {
+            MdeUserDO user = userService.getUserByPhone(reqVO.getMobile());
+            if (user != null && !Objects.equals(user.getId(), userId)) {
+                throw exception(AUTH_MOBILE_USED);
+            }
+        }
+        // 情况 2:如果是重置密码场景,需要校验手机号是存在的
+        if (Objects.equals(reqVO.getScene(), SmsSceneEnum.MEMBER_RESET_PASSWORD.getScene())) {
+            MdeUserDO user = userService.getUserByPhone(reqVO.getMobile());
+            if (user == null) {
+                throw exception(MDE_USER_MOBILE_NOT_EXISTS);
+            }
+        }
+        // 情况 3:如果是修改密码场景,需要查询手机号,无需前端传递
+        if (Objects.equals(reqVO.getScene(), SmsSceneEnum.MEMBER_UPDATE_PASSWORD.getScene())) {
+            MdeUserDO user = userService.getMdeUser(userId);
+            // TODO 芋艿:后续 member user 手机非强绑定,这块需要做下调整;
+            reqVO.setMobile(user.getPhone());
+        }
+
+        // 执行发送
+        smsCodeApi.sendSmsCode(MdeAuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
+    }
+
+    @Override
+    public void validateSmsCode(Long userId, MdeAuthSmsValidateReqVO reqVO) {
+        smsCodeApi.validateSmsCode(MdeAuthConvert.INSTANCE.convert(reqVO));
+    }
+
+    @Override
+    public MdeAuthLoginRespVO refreshToken(String refreshToken) {
+        OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken,
+                OAuth2ClientConstants.CLIENT_ID_DEFAULT).getCheckedData();
+        return MdeAuthConvert.INSTANCE.convert(accessTokenDO, null);
+    }
+
+    private String getPhone(Long userId) {
+        if (userId == null) {
+            return null;
+        }
+        MdeUserDO user = userService.getMdeUser(userId);
+        return user != null ? user.getPhone() : null;
+    }
+
+    private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {
+        // 插入登录日志
+        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();
+        reqDTO.setLogType(logType.getType());
+        reqDTO.setTraceId(TracerUtils.getTraceId());
+        reqDTO.setUserId(userId);
+        reqDTO.setUserType(getUserType().getValue());
+        reqDTO.setUsername(mobile);
+        reqDTO.setUserAgent(ServletUtils.getUserAgent());
+        reqDTO.setUserIp(getClientIP());
+        reqDTO.setResult(loginResult.getResult());
+        loginLogApi.createLoginLog(reqDTO);
+        // 更新最后登录时间
+        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {
+            userService.updateUserLogin(userId, getClientIP());
+        }
+    }
+
+    private UserTypeEnum getUserType() {
+        return UserTypeEnum.MEMBER;
+    }
+
+    private MdeAuthLoginRespVO createTokenAfterLoginSuccess(MdeUserDO user, String phone,
+                                                            LoginLogTypeEnum logType, String openid) {
+        // 插入登陆日志
+        createLoginLog(user.getId(), phone, logType, LoginResultEnum.SUCCESS);
+        // 创建 Token 令牌
+        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
+                .setUserId(user.getId()).setUserType(getUserType().getValue())
+                .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT)).getCheckedData();
+        // 构建返回结果
+        return MdeAuthConvert.INSTANCE.convert(accessTokenRespDTO, openid);
+    }
+}

+ 51 - 0
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/user/MdeUserService.java

@@ -1,7 +1,9 @@
 package com.citu.module.menduner.system.service.user;
 
 
+import com.citu.framework.common.enums.TerminalEnum;
 import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.common.validation.Mobile;
 import com.citu.module.menduner.system.controller.admin.user.vo.MdeUserPageReqVO;
 import com.citu.module.menduner.system.controller.admin.user.vo.MdeUserSaveReqVO;
 import com.citu.module.menduner.system.dal.dataobject.user.MdeUserDO;
@@ -53,4 +55,53 @@ public interface MdeUserService {
      */
     PageResult<MdeUserDO> getMdeUserPage(MdeUserPageReqVO pageReqVO);
 
+    /**
+     * 更新用户的最后登陆信息
+     *
+     * @param id      用户编号
+     * @param loginIp 登陆 IP
+     */
+    void updateUserLogin(Long id, String loginIp);
+
+    /**
+     * 通过手机查询用户
+     *
+     * @param phone 手机
+     * @return 用户对象
+     */
+    MdeUserDO getUserByPhone(String phone);
+
+    /**
+     * 基于手机号创建用户。
+     * 如果用户已经存在,则直接进行返回
+     *
+     * @param phone     手机号
+     * @param registerIp 注册 IP
+     * @param terminal   终端 {@link TerminalEnum}
+     * @return 用户对象
+     */
+    MdeUserDO createUserIfAbsent(@Mobile String phone, String registerIp, Integer terminal);
+
+    /**
+     * 创建用户
+     * 目的:三方登录时,如果未绑定用户时,自动创建对应用户
+     *
+     * @param avtar      头像
+     * @param registerIp 注册 IP
+     * @param terminal   终端 {@link TerminalEnum}
+     * @return 用户对象
+     */
+    MdeUserDO createUser(String avtar, String registerIp, Integer terminal);
+
+
+    /**
+     * 判断密码是否匹配
+     *
+     * @param rawPassword     未加密的密码
+     * @param encodedPassword 加密后的密码
+     * @return 是否匹配
+     */
+    boolean isPasswordMatch(String rawPassword, String encodedPassword);
+
+
 }

+ 77 - 11
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/user/MdeUserServiceImpl.java

@@ -1,22 +1,24 @@
 package com.citu.module.menduner.system.service.user;
 
 
-import org.springframework.stereotype.Service;
-import javax.annotation.Resource;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.*;
-import com.citu.module.menduner.system.controller.admin.user.vo.*;
-import com.citu.module.menduner.system.dal.dataobject.user.MdeUserDO;
+import cn.hutool.core.util.IdUtil;
+import com.citu.framework.common.enums.CommonStatusEnum;
 import com.citu.framework.common.pojo.PageResult;
-import com.citu.framework.common.pojo.PageParam;
 import com.citu.framework.common.util.object.BeanUtils;
-
+import com.citu.module.menduner.system.controller.admin.user.vo.MdeUserPageReqVO;
+import com.citu.module.menduner.system.controller.admin.user.vo.MdeUserSaveReqVO;
+import com.citu.module.menduner.system.dal.dataobject.user.MdeUserDO;
 import com.citu.module.menduner.system.dal.mysql.user.MdeUserMapper;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
 
 import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static com.citu.module.menduner.system.enums.ErrorCodeConstants.*;
+import static com.citu.module.menduner.system.enums.ErrorCodeConstants.MDE_USER_NOT_EXISTS;
 
 /**
  * 门墩儿-用户登录 Service 实现类
@@ -30,6 +32,9 @@ public class MdeUserServiceImpl implements MdeUserService {
     @Resource
     private MdeUserMapper mdeUserMapper;
 
+    @Resource
+    private PasswordEncoder passwordEncoder;
+
     @Override
     public Long createMdeUser(MdeUserSaveReqVO createReqVO) {
         // 插入
@@ -72,4 +77,65 @@ public class MdeUserServiceImpl implements MdeUserService {
         return mdeUserMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public void updateUserLogin(Long id, String loginIp) {
+        mdeUserMapper.updateById(new MdeUserDO().setId(id)
+                .setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));
+    }
+
+    @Override
+    public MdeUserDO getUserByPhone(String phone) {
+        return mdeUserMapper.selectByPhone(phone);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MdeUserDO createUserIfAbsent(String phone, String registerIp, Integer terminal) {
+        // 用户已经存在
+        MdeUserDO user = mdeUserMapper.selectByPhone(phone);
+        if (user != null) {
+            return user;
+        }
+        // 用户不存在,则进行创建
+        return createUser(phone, null, registerIp, terminal);
+    }
+
+
+    @Override
+    public MdeUserDO createUser(String avtar, String registerIp, Integer terminal) {
+        return createUser(null, avtar, registerIp, terminal);
+    }
+
+
+    private MdeUserDO createUser(String phone, String avtar,
+                                 String registerIp, Integer terminal) {
+        // 生成密码
+        String password = IdUtil.fastSimpleUUID();
+        // 插入用户
+        MdeUserDO user = new MdeUserDO();
+        user.setPhone(phone);
+        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
+        user.setPassword(encodePassword(password)); // 加密密码
+        user.setRegisterIp(registerIp).setRegisterTerminal(terminal);
+        user.setAvatar(avtar); // 基础信息
+
+        mdeUserMapper.insert(user);
+
+        return user;
+    }
+
+    @Override
+    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
+        return passwordEncoder.matches(rawPassword, encodedPassword);
+    }
+
+    /**
+     * 对密码进行加密
+     *
+     * @param password 密码
+     * @return 加密后的密码
+     */
+    private String encodePassword(String password) {
+        return passwordEncoder.encode(password);
+    }
 }