guomengjiao 5 mēneši atpakaļ
vecāks
revīzija
18214dfef7
21 mainītis faili ar 835 papildinājumiem un 46 dzēšanām
  1. 16 1
      pom.xml
  2. 18 0
      ruoyi-admin/src/main/resources/application-dev.yml
  3. 5 0
      ruoyi-admin/src/main/resources/application.yml
  4. 68 18
      ruoyi-api/src/main/java/com/ruoyi/api/controller/user/ApiUserLoginController.java
  5. 12 0
      ruoyi-common/pom.xml
  6. 59 0
      ruoyi-common/src/main/java/com/ruoyi/common/properties/AlipayProperties.java
  7. 33 0
      ruoyi-common/src/main/java/com/ruoyi/common/properties/WeChatProperties.java
  8. 96 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/IOSToeknUtils.java
  9. 98 0
      ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsSend.java
  10. 1 1
      ruoyi-shop/src/main/java/com/ruoyi/shop/business/domain/BusinessSalesConfig.java
  11. 1 1
      ruoyi-shop/src/main/java/com/ruoyi/shop/business/domain/bo/BusinessSalesConfigBo.java
  12. 2 2
      ruoyi-shop/src/main/java/com/ruoyi/shop/business/domain/vo/BusinessSalesConfigVo.java
  13. 26 0
      ruoyi-user/src/main/java/com/ruoyi/user/domain/bo/ThirdLoginBo.java
  14. 40 0
      ruoyi-user/src/main/java/com/ruoyi/user/domain/bo/ThirdRegisterBo.java
  15. 25 0
      ruoyi-user/src/main/java/com/ruoyi/user/domain/bo/UserLoginBo.java
  16. 34 0
      ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/UserBindingVo.java
  17. 35 0
      ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/UserLoginVo.java
  18. 1 0
      ruoyi-user/src/main/java/com/ruoyi/user/enums/UserThirdType.java
  19. 26 2
      ruoyi-user/src/main/java/com/ruoyi/user/service/IUserService.java
  20. 235 20
      ruoyi-user/src/main/java/com/ruoyi/user/service/impl/UserServiceImpl.java
  21. 4 1
      ruoyi-weixin/ruoyi-weixin-miniapp/src/main/java/com/ruoyi/weixin/domain/WxUserDto.java

+ 16 - 1
pom.xml

@@ -65,7 +65,8 @@
         <docker.registry.host>http://${docker.registry.url}:2375</docker.registry.host>
         <docker.namespace>ruoyi</docker.namespace>
         <docker.plugin.version>1.2.2</docker.plugin.version>
-
+        <alipay.version>4.38.90.ALL</alipay.version>
+        <jwks-rsa.version>0.12.0</jwks-rsa.version>
     </properties>
 
     <!-- 依赖声明 -->
@@ -524,6 +525,20 @@
                 <artifactId>wechatpay-java</artifactId>
                 <version>${wechatpay-java.version}</version>
             </dependency>
+
+            <!-- aliPay sdk-->
+            <dependency>
+                <groupId>com.alipay.sdk</groupId>
+                <artifactId>alipay-sdk-java</artifactId>
+                <version>${alipay.version}</version>
+            </dependency>
+
+            <!-- 苹果授权 -->
+            <dependency>
+                <groupId>com.auth0</groupId>
+                <artifactId>jwks-rsa</artifactId>
+                <version>${jwks-rsa.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 18 - 0
ruoyi-admin/src/main/resources/application-dev.yml


+ 5 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -362,6 +362,11 @@ aliyunsample:
   templateCode: SMS_468305037
   templateModule: "验证码:${code},请尽快返回完成验证。如非本人使用,敬请忽略本信息。"
 
+# 短信
+sms:
+  redisKey: "vercode:key:"
+  verCodeTm: 60
+
 # 阿里云账号AK信息
 # 内容安全接口地址:https://green-cip.cn-shanghai.aliyuncs.com(47.116.84.226)
 # 视频点播接口地址:https://vod.cn-shanghai.aliyuncs.com(106.15.83.32)

+ 68 - 18
ruoyi-api/src/main/java/com/ruoyi/api/controller/user/ApiUserLoginController.java

@@ -5,13 +5,16 @@ import cn.hutool.core.util.ObjectUtil;
 import com.ruoyi.api.controller.common.AbstractApiController;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.validate.RegGroup;
-import com.ruoyi.common.core.validate.WxAuthGroup;
 import com.ruoyi.framework.web.service.ApiTokenService;
 import com.ruoyi.shop.config.domain.ShopSaleConfig;
 import com.ruoyi.shop.config.service.IShopSaleConfigService;
 import com.ruoyi.shop.coupon.service.ICouponReceiveRecordService;
 import com.ruoyi.user.domain.User;
+import com.ruoyi.user.domain.bo.ThirdLoginBo;
+import com.ruoyi.user.domain.bo.ThirdRegisterBo;
 import com.ruoyi.user.domain.bo.UserIntegralRecordBo;
+import com.ruoyi.user.domain.bo.UserLoginBo;
+import com.ruoyi.user.domain.vo.UserLoginVo;
 import com.ruoyi.user.enums.IntegralSourceType;
 import com.ruoyi.user.service.IUserIntegralRecordService;
 import com.ruoyi.user.service.IUserService;
@@ -20,10 +23,12 @@ import com.ruoyi.weixin.service.WxUserService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.RequiredArgsConstructor;
-import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -98,23 +103,68 @@ public class ApiUserLoginController extends AbstractApiController {
     @PostMapping("/user/authorization/register")
     public R<Map<String, Object>> registerAuthorization(@RequestBody @Validated(RegGroup.class) WxUserDto wxUserDto) {
         User user = userService.authorization(wxUserDto);
-        ShopSaleConfig shopSaleConfig = shopSaleConfigService.defaultShopSaleConfig();
-        if(ObjectUtil.isNotNull(shopSaleConfig) && shopSaleConfig.getNewUserGivePoint() > 0) {
-            userIntegralRecordService.inner(UserIntegralRecordBo.builder()
-                .integralNote("注册会员送积分")
-                .sourceType(IntegralSourceType.USER_REGISTER)
-                .userId(user.getId())
-                .value(shopSaleConfig.getNewUserGivePoint())
-                .remark("注册会员送积分")
-                .canExpire(false)
-                .build());
-        }
-        //送新人券
-        couponReceiveRecordService.newPersonCoupon(user.getId());
-        String token = apiTokenService.generateToken(user);
+
         HashMap<String, Object> result = new HashMap<>();
-        result.put("token", token);
+        result.put("token", loginSuccess(user, true));
         return R.ok(result);
     }
 
+    /**
+     * 验证码登录
+     */
+    @ApiOperation("验证码登录")
+    @PostMapping("/captcha")
+    public R<UserLoginVo> loginByCaptcha(@Validated @RequestBody UserLoginBo bo) {
+        UserLoginVo userLoginVo = userService.loginByCaptcha(bo);
+        userLoginVo.setToken(loginSuccess(userLoginVo.getUser(), userLoginVo.getIsReg()));
+        return R.ok(userLoginVo);
+    }
+
+    /**
+     * 第三方登录
+     */
+    @ApiOperation("第三方登录")
+    @PostMapping("/third")
+    public R<UserLoginVo> loginByThird(@Validated @RequestBody ThirdLoginBo bo) {
+        UserLoginVo userLoginVo = userService.loginByThird(bo);
+        userLoginVo.setToken(loginSuccess(userLoginVo.getUser(), false));
+        return R.ok(userLoginVo);
+    }
+
+    /**
+     * 第三方注册
+     */
+    @ApiOperation("第三方注册")
+    @PostMapping("/third/register")
+    public R<UserLoginVo> registerByThird(@Validated @RequestBody ThirdRegisterBo bo) {
+        return R.ok(userService.registerByThird(bo));
+    }
+
+    /**
+     * 一键登录
+     */
+    @ApiOperation("一键登录")
+    @PostMapping("/mobile")
+    public R<UserLoginVo> loginByMobile(@Validated @RequestBody UserLoginBo bo) {
+        return R.ok(userService.loginByMobile(bo));
+    }
+
+    private String loginSuccess(User user, Boolean isReg) {
+        if (isReg) {
+            ShopSaleConfig shopSaleConfig = shopSaleConfigService.defaultShopSaleConfig();
+            if (ObjectUtil.isNotNull(shopSaleConfig) && shopSaleConfig.getNewUserGivePoint() > 0) {
+                userIntegralRecordService.inner(UserIntegralRecordBo.builder()
+                    .integralNote("注册会员送积分")
+                    .sourceType(IntegralSourceType.USER_REGISTER)
+                    .userId(user.getId())
+                    .value(shopSaleConfig.getNewUserGivePoint())
+                    .remark("注册会员送积分")
+                    .canExpire(false)
+                    .build());
+            }
+            //送新人券
+            couponReceiveRecordService.newPersonCoupon(user.getId());
+        }
+        return apiTokenService.generateToken(user);
+    }
 }

+ 12 - 0
ruoyi-common/pom.xml

@@ -249,6 +249,18 @@
             <groupId>com.belerweb</groupId>
             <artifactId>pinyin4j</artifactId>
         </dependency>
+
+        <!-- aliPay sdk-->
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+        </dependency>
+
+        <!-- 苹果授权 -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>jwks-rsa</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 59 - 0
ruoyi-common/src/main/java/com/ruoyi/common/properties/AlipayProperties.java

@@ -0,0 +1,59 @@
+package com.ruoyi.common.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 支付宝 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "alipay")
+public class AlipayProperties {
+
+    /**
+     *
+     */
+    private String serverUrl;
+    /**
+     *
+     */
+    private String appId;
+    /**
+     *
+     */
+    private String appPrivateKey;
+    /**
+     *
+     */
+    private String charset;
+    /**
+     *
+     */
+    private String signType;
+    /**
+     *
+     */
+    private String format;
+    /**
+     *
+     */
+    private String alipayPublicKey;
+    /**
+     *
+     */
+    private String certPath;
+    /**
+     *
+     */
+    private String publicCertPath;
+    /**
+     *
+     */
+    private String rootCertPath;
+
+
+}

+ 33 - 0
ruoyi-common/src/main/java/com/ruoyi/common/properties/WeChatProperties.java

@@ -0,0 +1,33 @@
+package com.ruoyi.common.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 微信 配置属性
+ *
+ * @author Lion Li
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "wechat")
+public class WeChatProperties {
+
+    /**
+     *
+     */
+    private String accessTokenUrl;
+
+    /**
+     *
+     */
+    private String appId;
+
+    /**
+     *
+     */
+    private String appSecret;
+
+
+}

+ 96 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/IOSToeknUtils.java

@@ -0,0 +1,96 @@
+package com.ruoyi.common.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.auth0.jwk.Jwk;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwtParser;
+import io.jsonwebtoken.Jwts;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.charset.StandardCharsets;
+import java.security.PublicKey;
+import java.util.Base64;
+import java.util.Map;
+
+/**
+ * @Classname IOSToeknUtils
+ * @Description IOS token操作工具
+ * @Date 2023/03/23
+ * @Created by Goden
+ */
+public class IOSToeknUtils {
+
+    private final static String authUrl = "https://appleid.apple.com/auth/keys";
+
+    private final static String authIss = "https://appleid.apple.com";
+
+    /**
+     * 解码identityToken
+     *
+     * @param identityToken
+     * @return
+     */
+    public static JSONObject parserIdentityToken(String identityToken) {
+        String[] arr = identityToken.split("\\.");
+
+        String firstDate = new String(Base64.getDecoder().decode(arr[0]), StandardCharsets.UTF_8);
+        String decode = new String(Base64.getDecoder().decode(arr[1]), StandardCharsets.UTF_8);
+        JSONObject claimObj = JSON.parseObject(decode);
+        // 将第一部分获取到的kid放入消息体中,方便后续匹配对应的公钥使用
+        claimObj.put("kid", JSONObject.parseObject(firstDate).get("kid"));
+        return claimObj;
+    }
+
+    /**
+     * 根据kid获取对应的苹果公钥
+     *
+     * @param kid
+     * @return
+     */
+    public static PublicKey getPublicKey(String kid) {
+        try {
+            RestTemplate restTemplate = new RestTemplate();
+            JSONObject data = restTemplate.getForObject(authUrl, JSONObject.class);
+            assert data != null;
+            JSONArray jsonArray = data.getJSONArray("keys");
+            for (Object obj : jsonArray) {
+                Map json = ((Map) obj);
+                // 获取kid对应的公钥
+                if (json.get("kid").equals(kid)) {
+                    Jwk jwa = Jwk.fromValues(json);
+                    return jwa.getPublicKey();
+                }
+            }
+        } catch (Exception e) {
+
+        }
+        return null;
+    }
+
+    /**
+     * 对前端传来的identityToken进行验证
+     *
+     * @param identityToken
+     * @param jsonObject
+     * @return
+     * @throws Exception
+     */
+    public static Boolean verifyExc(String identityToken, JSONObject jsonObject) throws Exception {
+        String kid = (String) jsonObject.get("kid");
+        PublicKey publicKey = getPublicKey(kid);
+
+        JwtParser jwtParser = Jwts.parser().setSigningKey(publicKey);
+        jwtParser.requireIssuer(authIss);
+        jwtParser.requireAudience((String) jsonObject.get("aud"));
+        jwtParser.requireSubject((String) jsonObject.get("sub"));
+        try {
+            Jws<Claims> claim = jwtParser.parseClaimsJws(identityToken);
+            return claim != null && claim.getBody().containsKey("auth_time");
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

+ 98 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/SmsSend.java

@@ -0,0 +1,98 @@
+package com.ruoyi.common.utils;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.redis.RedisUtils;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 公共短信发送
+ */
+@Component
+public class SmsSend {
+
+    @Resource
+    private Sample sample;
+
+    @Value("${sms.redisKey}")
+    private String redisKey;  //验证码缓存KEY
+
+    @Value("${sms.verCodeTm}")
+    private int verCodeTm;  //验证码过期时间
+
+    /**
+     * 发送
+     *
+     * @param phone
+     */
+    public void send(String phone) {
+        if (openSmsCode()) {
+            //获取验证码
+            String code = RandomStringUtils.randomNumeric(4);
+            String mode = sendSmsCodeMode();
+            switch (mode) {
+                case "1":
+                    sample.sendCode(phone, code);
+                    break;
+                default:
+                    return;
+            }
+            //缓存
+            RedisUtils.setCacheObject(redisKey + phone, code, verCodeTm, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * 检验
+     *
+     * @param phone
+     * @param checkCode
+     */
+    public void check(String phone, String checkCode) {
+        if (openSmsCode()) {
+            String code = RedisUtils.getCacheObject(redisKey + phone);
+            if (StrUtil.isEmpty(code)) {
+                throw new ServiceException("手机验证码已过期");
+            } else {
+                if (!code.equals(checkCode)) {
+                    throw new ServiceException("手机验证码不正确");
+                }
+            }
+        }
+    }
+
+    /**
+     * 短信验证码开关
+     *
+     * @return
+     */
+    private Boolean openSmsCode() {
+        String open = RedisUtils.getCacheObject(Constants.SYS_CONFIG_KEY + "sms.code.open");
+        if (StringUtils.isEmpty(open)) {
+            return true;
+        }
+        return Convert.toBool(open);
+    }
+
+    /**
+     * 发送短信验证码的方式
+     * 0:feixinyun;
+     * 1: aliyun;
+     *
+     * @return
+     */
+    private String sendSmsCodeMode() {
+        String mode = RedisUtils.getCacheObject(Constants.SYS_CONFIG_KEY + "send:sms:code:mode");
+        if (StringUtils.isEmpty(mode)) {
+            return "1";
+        }
+        return mode;
+    }
+}

+ 1 - 1
ruoyi-shop/src/main/java/com/ruoyi/shop/business/domain/BusinessSalesConfig.java

@@ -31,7 +31,7 @@ public class BusinessSalesConfig extends BaseTimeEntity {
     /**
      * 多少积分可以兑换1元钱
      */
-    private BigDecimal pointsToCashRatio;
+    private Integer pointsToCashRatio;
     /**
      * 商品兑换积分比例
      */

+ 1 - 1
ruoyi-shop/src/main/java/com/ruoyi/shop/business/domain/bo/BusinessSalesConfigBo.java

@@ -41,7 +41,7 @@ public class BusinessSalesConfigBo extends BaseEntity {
      */
     @ApiModelProperty(value = "多少积分可以兑换1元钱", required = true)
     @NotNull(message = "多少积分可以兑换1元钱不能为空", groups = { AddGroup.class, EditGroup.class })
-    private BigDecimal pointsToCashRatio;
+    private Integer pointsToCashRatio;
 
     /**
      * 商品兑换积分比例

+ 2 - 2
ruoyi-shop/src/main/java/com/ruoyi/shop/business/domain/vo/BusinessSalesConfigVo.java

@@ -25,7 +25,7 @@ public class BusinessSalesConfigVo {
     private static final long serialVersionUID = 1L;
 
     /**
-     * 
+     *
      */
     @ExcelProperty(value = "")
     @ApiModelProperty("")
@@ -43,7 +43,7 @@ public class BusinessSalesConfigVo {
      */
     @ExcelProperty(value = "多少积分可以兑换1元钱")
     @ApiModelProperty("多少积分可以兑换1元钱")
-    private BigDecimal pointsToCashRatio;
+    private Integer pointsToCashRatio;
 
     /**
      * 商品兑换积分比例

+ 26 - 0
ruoyi-user/src/main/java/com/ruoyi/user/domain/bo/ThirdLoginBo.java

@@ -0,0 +1,26 @@
+package com.ruoyi.user.domain.bo;
+
+import com.ruoyi.user.enums.UserThirdType;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+public class ThirdLoginBo implements Serializable {
+    /**
+     * 第三方类型
+     */
+    @ApiModelProperty(value = "第三方类型", required = true)
+    @NotNull(message = "第三方类型不能为空")
+    private UserThirdType thirdType;
+
+    /**
+     * 第三方code
+     */
+    @ApiModelProperty(value = "第三方code", required = true)
+    @NotBlank(message = "第三方code不能为空")
+    private String code;
+}

+ 40 - 0
ruoyi-user/src/main/java/com/ruoyi/user/domain/bo/ThirdRegisterBo.java

@@ -0,0 +1,40 @@
+package com.ruoyi.user.domain.bo;
+
+import com.ruoyi.user.enums.UserThirdType;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Data
+public class ThirdRegisterBo implements Serializable {
+    /**
+     * 第三方类型
+     */
+    @ApiModelProperty(value = "第三方类型", required = true)
+    @NotNull(message = "第三方类型不能为空")
+    private UserThirdType thirdType;
+
+    /**
+     * 手机号
+     */
+    @ApiModelProperty(value = "手机号", required = true)
+    @NotBlank(message = "手机号不能为空")
+    private String mobile;
+
+    /**
+     * 验证码
+     */
+    @ApiModelProperty(value = "验证码", required = true)
+    @NotBlank(message = "验证码不能为空")
+    private String captcha;
+
+    /**
+     * 标识码
+     */
+    @ApiModelProperty(value = "标识码", required = true)
+    @NotBlank(message = "标识码不能为空")
+    private String identityCode;
+}

+ 25 - 0
ruoyi-user/src/main/java/com/ruoyi/user/domain/bo/UserLoginBo.java

@@ -0,0 +1,25 @@
+package com.ruoyi.user.domain.bo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+@Data
+public class UserLoginBo implements Serializable {
+    /**
+     * 手机号
+     */
+    @ApiModelProperty(value = "手机号", required = true)
+    @NotBlank(message = "手机号不能为空")
+    @Length(min = 11, max = 11, message = "手机号格式不正确")
+    private String mobile;
+
+    /**
+     * 验证码
+     */
+    @ApiModelProperty(value = "验证码")
+    private String captcha;
+}

+ 34 - 0
ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/UserBindingVo.java

@@ -0,0 +1,34 @@
+package com.ruoyi.user.domain.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class UserBindingVo implements Serializable {
+
+    /**
+     * 手机号
+     */
+    @ApiModelProperty(value = "手机号")
+    private String mobile;
+
+    /**
+     * 是否绑定微信
+     */
+    @ApiModelProperty(value = "是否绑定微信")
+    private boolean bindingWechat = false;
+
+    /**
+     * 是否绑定支付宝
+     */
+    @ApiModelProperty(value = "是否绑定支付宝")
+    private boolean bindingAlipay = false;
+
+    /**
+     * 是否绑定苹果
+     */
+    @ApiModelProperty(value = "是否绑定苹果")
+    private boolean bindingApple = false;
+}

+ 35 - 0
ruoyi-user/src/main/java/com/ruoyi/user/domain/vo/UserLoginVo.java

@@ -0,0 +1,35 @@
+package com.ruoyi.user.domain.vo;
+
+import com.ruoyi.user.domain.User;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class UserLoginVo implements Serializable {
+    /**
+     * 用户
+     */
+    @ApiModelProperty(value = "用户")
+    private User user;
+
+    /**
+     * token
+     */
+    @ApiModelProperty(value = "token")
+    private String token;
+
+    /**
+     * 是否注册
+     */
+    @ApiModelProperty(value = "是否注册")
+    private Boolean isReg;
+
+    /**
+     * 标识码
+     */
+    @ApiModelProperty(value = "标识码")
+    private String identityCode;
+}

+ 1 - 0
ruoyi-user/src/main/java/com/ruoyi/user/enums/UserThirdType.java

@@ -14,6 +14,7 @@ public enum UserThirdType implements IIntegerEnum {
 
     WX_MINI_PROGRAM(0, "微信小程序"),
     ALI_MINI_PROGRAM(1, "支付宝小程序"),
+    APPLE(3, "苹果"),
     ;
 
     @EnumValue

+ 26 - 2
ruoyi-user/src/main/java/com/ruoyi/user/service/IUserService.java

@@ -3,10 +3,12 @@ package com.ruoyi.user.service;
 import com.ruoyi.common.core.domain.PageQuery;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.user.domain.User;
-import com.ruoyi.user.domain.bo.PersonalDataBo;
-import com.ruoyi.user.domain.bo.UserBo;
+import com.ruoyi.user.domain.bo.*;
+import com.ruoyi.user.domain.vo.UserBindingVo;
+import com.ruoyi.user.domain.vo.UserLoginVo;
 import com.ruoyi.user.domain.vo.UserStatisticsVo;
 import com.ruoyi.user.domain.vo.UserVo;
+import com.ruoyi.user.enums.UserThirdType;
 import com.ruoyi.weixin.domain.WxUserDto;
 
 import java.math.BigDecimal;
@@ -149,4 +151,26 @@ public interface IUserService {
      */
     List<UserStatisticsVo> statisticsUserRegisterForHome();
 
+    UserLoginVo loginByCaptcha(UserLoginBo bo);
+
+    UserLoginVo loginByThird(ThirdLoginBo bo);
+
+    UserLoginVo registerByThird(ThirdRegisterBo bo);
+
+    UserLoginVo loginByMobile(UserLoginBo bo);
+
+    /**
+     * 第三方身份绑定信息
+     * @param userId
+     * @return
+     */
+    UserBindingVo thirdBindingInfo(Long userId);
+
+    /**
+     * 查找用户第三方身份
+     * @param thirdType
+     * @param code
+     * @return
+     */
+    String loadIdentityCodeByCode(UserThirdType thirdType, String code);
 }

+ 235 - 20
ruoyi-user/src/main/java/com/ruoyi/user/service/impl/UserServiceImpl.java

@@ -3,8 +3,17 @@ package com.ruoyi.user.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.request.AlipaySystemOauthTokenRequest;
+import com.alipay.api.request.AlipayUserInfoShareRequest;
+import com.alipay.api.response.AlipaySystemOauthTokenResponse;
+import com.alipay.api.response.AlipayUserInfoShareResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -13,14 +22,21 @@ import com.ruoyi.common.core.domain.PageQuery;
 import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.enums.ExceptionEnum;
 import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.properties.AlipayProperties;
+import com.ruoyi.common.properties.WeChatProperties;
+import com.ruoyi.common.utils.IOSToeknUtils;
 import com.ruoyi.common.utils.ShareCodeUtils;
+import com.ruoyi.common.utils.SmsSend;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.rest.RestUtil;
 import com.ruoyi.user.domain.User;
 import com.ruoyi.user.domain.UserThirdIdentity;
-import com.ruoyi.user.domain.bo.PersonalDataBo;
-import com.ruoyi.user.domain.bo.UserBo;
+import com.ruoyi.user.domain.bo.*;
+import com.ruoyi.user.domain.vo.UserBindingVo;
+import com.ruoyi.user.domain.vo.UserLoginVo;
 import com.ruoyi.user.domain.vo.UserStatisticsVo;
 import com.ruoyi.user.domain.vo.UserVo;
+import com.ruoyi.user.enums.UserThirdType;
 import com.ruoyi.user.exception.UserExceptionEnum;
 import com.ruoyi.user.mapper.UserMapper;
 import com.ruoyi.user.mapper.UserThirdIdentityMapper;
@@ -50,9 +66,13 @@ public class UserServiceImpl implements IUserService {
 
     private final UserThirdIdentityMapper userThirdIdentityMapper;
 
+    private final SmsSend smsSend;
 
+    private final RestUtil restUtil;
 
+    private final WeChatProperties wxPayConfig;
 
+    private final AlipayProperties aliPayConfig;
 
     /**
      * 查询小程序用户管理
@@ -224,15 +244,17 @@ public class UserServiceImpl implements IUserService {
         if (ObjectUtil.isNotNull(userThirdIdentity)) {
             return this.getById(userThirdIdentity.getUserId(), true);
         }
+        return registerOrLogin(wxUserDto, UserThirdType.WX_MINI_PROGRAM.getCode(), openId);
+    }
 
-
+    private User registerOrLogin(WxUserDto wxUserDto, Integer thirdType, String identityCode) {
         User user = this.loadByMobile(wxUserDto.getMobile(), false);
-        if(ObjectUtil.isNotNull(user))
-        {
-           return user;
+        if (ObjectUtil.isNotNull(user)) {
+            user.setLastLoginTime(new Date());
+            this.baseMapper.updateById(user);
+            return user;
         }
-
-
+        wxUserDto.setIsReg(true);
         //创建用户
         user = new User();
         user.setNickname(wxUserDto.getNickname());
@@ -249,12 +271,13 @@ public class UserServiceImpl implements IUserService {
         insertByBo(BeanUtil.toBean(user, UserBo.class));
 
         //保存第三方关系
-        userThirdIdentity = new UserThirdIdentity();
-        userThirdIdentity.setUserId(user.getId());
-        userThirdIdentity.setThirdType(0);
-        userThirdIdentity.setIdentityCode(openId);
-        userThirdIdentityMapper.insert(userThirdIdentity);
-
+        if (ObjectUtil.isNotNull(thirdType)) {
+            UserThirdIdentity userThirdIdentity = new UserThirdIdentity();
+            userThirdIdentity.setUserId(user.getId());
+            userThirdIdentity.setThirdType(thirdType);
+            userThirdIdentity.setIdentityCode(identityCode);
+            userThirdIdentityMapper.insert(userThirdIdentity);
+        }
         return user;
     }
 
@@ -343,12 +366,7 @@ public class UserServiceImpl implements IUserService {
     @Override
     public User loginAuthorization(String openId) {
         //查询此openid是否绑定过用户
-        UserThirdIdentity userThirdIdentity = userThirdIdentityMapper.selectOne(
-            new LambdaQueryWrapper<UserThirdIdentity>()
-                .eq(UserThirdIdentity::getThirdType, 0)
-                .eq(UserThirdIdentity::getIdentityCode, openId)
-                .last("limit 1")
-        );
+        UserThirdIdentity userThirdIdentity = getUserThirdIdentity(UserThirdType.WX_MINI_PROGRAM.getCode(), openId, null);
         if (ObjectUtil.isNull(userThirdIdentity)) {
             throw new ServiceException(UserExceptionEnum.USER_THIRD_ID_NOT_EXISTS);
         }
@@ -359,6 +377,16 @@ public class UserServiceImpl implements IUserService {
         return user;
     }
 
+    private UserThirdIdentity getUserThirdIdentity(Integer thirdType, String identityCode, Long userId) {
+        return userThirdIdentityMapper.selectOne(
+            new LambdaQueryWrapper<UserThirdIdentity>()
+                .eq(UserThirdIdentity::getThirdType, thirdType)
+                .eq(ObjectUtil.isNotNull(userId), UserThirdIdentity::getUserId, userId)
+                .eq(StringUtils.isNotEmpty(identityCode), UserThirdIdentity::getIdentityCode, identityCode)
+                .last("limit 1")
+        );
+    }
+
     @Override
     public void personalDataUpdate(PersonalDataBo bo, Long userId) {
         User user = this.getById(userId, true);
@@ -403,6 +431,193 @@ public class UserServiceImpl implements IUserService {
         return dataList;
     }
 
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public UserLoginVo loginByCaptcha(UserLoginBo bo) {
+        //验证码验证
+        smsSend.check(bo.getMobile(), bo.getCaptcha());
+        //登录或注册
+        WxUserDto wxUserDto = new WxUserDto();
+        wxUserDto.setMobile(bo.getMobile());
+        User user = registerOrLogin(wxUserDto, null, null);
+
+        UserLoginVo userLoginVo = new UserLoginVo();
+        userLoginVo.setUser(user);
+        userLoginVo.setIsReg(wxUserDto.getIsReg());
+        return userLoginVo;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public UserLoginVo loginByThird(ThirdLoginBo bo) {
+        //根据code获取唯一标识
+        String identityCode = loadIdentityCodeByCode(bo.getThirdType(), bo.getCode());
+        //查询用户身份
+        UserThirdIdentity userThirdIdentity = getUserThirdIdentity(bo.getThirdType().getCode(), identityCode, null);
+        UserLoginVo userLoginVo = new UserLoginVo();
+        if (ObjectUtil.isNull(userThirdIdentity)) {
+            //用户身份不存在,请先注册
+            userLoginVo.setIsReg(false);
+            userLoginVo.setIdentityCode(identityCode);
+            return userLoginVo;
+        }
+        //查询用户
+        User user = loadByIdForLock(userThirdIdentity.getUserId(), false);
+        if (ObjectUtil.isNull(user)) {
+            //如果用户不存在,则清理数据,重新注册
+            userThirdIdentityMapper.delete(new LambdaQueryWrapper<UserThirdIdentity>().eq(UserThirdIdentity::getUserId, userThirdIdentity.getUserId())
+                .eq(UserThirdIdentity::getThirdType, userThirdIdentity.getThirdType()));
+            userLoginVo.setIsReg(false);
+            userLoginVo.setIdentityCode(identityCode);
+            return userLoginVo;
+        }
+        user.setLastLoginTime(new Date());
+        this.baseMapper.updateById(user);
+        userLoginVo.setUser(user);
+        return userLoginVo;
+    }
+
+    @Override
+    public UserLoginVo registerByThird(ThirdRegisterBo bo) {
+        smsSend.check(bo.getMobile(), bo.getCaptcha());
+        return null;
+    }
+
+    @Override
+    public UserLoginVo loginByMobile(UserLoginBo bo) {
+        return null;
+    }
+
+    @Override
+    public UserBindingVo thirdBindingInfo(Long userId) {
+        User user = loadByIdForLock(userId, true);
+        UserBindingVo vo = new UserBindingVo();
+        vo.setMobile(user.getMobile());
+        //是否绑定微信
+        if (ObjectUtil.isNotNull(getUserThirdIdentity(UserThirdType.WX_MINI_PROGRAM.getCode(), null, userId))) {
+            vo.setBindingWechat(true);
+        }
+        //是否绑定支付宝
+        if (ObjectUtil.isNotNull(getUserThirdIdentity(UserThirdType.ALI_MINI_PROGRAM.getCode(), null, userId))) {
+            vo.setBindingAlipay(true);
+        }
+        //是否绑定苹果
+        if (ObjectUtil.isNotNull(getUserThirdIdentity(UserThirdType.APPLE.getCode(), null, userId))) {
+            vo.setBindingApple(true);
+        }
+        return vo;
+    }
+
+    @Override
+    public String loadIdentityCodeByCode(UserThirdType thirdType, String code) {
+        switch (thirdType) {
+            case WX_MINI_PROGRAM:
+                return getWeChatIdByCode(code);
+            case ALI_MINI_PROGRAM:
+                return getAlipayIdByCode(code);
+            case APPLE:
+                return getAppleIdByCode(code);
+            default:
+                throw new ServiceException("第三方身份错误");
+        }
+    }
+
+    /**
+     * 获取微信OpenId
+     *
+     * @param code
+     * @return
+     */
+    private String getWeChatIdByCode(String code) {
+        if (StringUtils.isBlank(code)) {
+            throw new ServiceException("请求参数code不能为空");
+        }
+        //微信开放平台
+        try {
+            String url = String.format(wxPayConfig.getAccessTokenUrl(), wxPayConfig.getAppId(), wxPayConfig.getAppSecret(), code);
+
+            Map result = restUtil.getForEntity(url, Map.class, null);
+            Integer errcode = MapUtil.getInt(result, "errcode");
+            if (ObjectUtil.isNotNull(errcode) && errcode == 40029) {
+                throw new ServiceException("请求参数code错误");
+            }
+            String openid = MapUtil.getStr(result, "openid").replaceAll("\"", "");
+            if (StringUtils.isBlank(openid)) {
+                throw new ServiceException("获取微信唯一标识异常");
+            }
+            return openid;
+        } catch (Exception ex) {
+            throw new ServiceException("获取微信唯一标识异常");
+        }
+    }
+
+    /**
+     * 获取支付宝ID
+     *
+     * @param code
+     * @return
+     */
+    private String getAlipayIdByCode(String code) {
+        //使用支付宝小程序的固定方法获取auth_code
+        if (StringUtils.isBlank(code)) {
+            throw new ServiceException("请求参数code不能为空");
+        } else {
+            AlipayClient alipayClient = new DefaultAlipayClient(aliPayConfig.getServerUrl(), // 支付宝网关(固定)
+                aliPayConfig.getAppId(), // APPID 即创建应用后生成
+                aliPayConfig.getAppPrivateKey(), // 开发者私钥,由开发者自己生成
+                aliPayConfig.getFormat(), // 参数返回格式,只支持json
+                aliPayConfig.getCharset(), // 编码集,支持GBK/UTF-8
+                aliPayConfig.getAlipayPublicKey(), // 支付宝公钥,由支付宝生成
+                aliPayConfig.getSignType()); // 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2
+
+            //获取accessToken
+            AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
+            request.setCode(code);
+            request.setGrantType("authorization_code");
+            try {
+                AlipaySystemOauthTokenResponse responseToken = alipayClient.execute(request);
+                if (responseToken.isSuccess()) {
+                    //获取用户信息
+                    AlipayUserInfoShareRequest alipayUserInfoShareRequest = new AlipayUserInfoShareRequest();
+                    AlipayUserInfoShareResponse response = alipayClient.execute(alipayUserInfoShareRequest, responseToken.getAccessToken());
+                    if (response.isSuccess()) {
+                        //新商户建议使用open_id替代该字段。对于新商户,user_id字段未来计划逐步回收,存量商户可继续使用。如使用open_id,请确认 应用-开发配置-openid配置管理 已启用。无该配置项
+                        return response.getOpenId();
+                    } else {
+                        throw new ServiceException("获取用户信息调用失败");
+                    }
+                } else {
+                    throw new ServiceException("获取accessToken调用失败");
+                }
+            } catch (AlipayApiException e) {
+                throw new ServiceException("获取支付宝唯一标识异常");
+            }
+        }
+    }
+
+    /**
+     * 获取苹果用户唯一标识
+     *
+     * @param identityToken
+     * @return
+     */
+    private String getAppleIdByCode(String identityToken) {
+        // 解码后的消息体
+        JSONObject playloadObj = IOSToeknUtils.parserIdentityToken(identityToken);
+        Boolean success;
+        try {
+            success = IOSToeknUtils.verifyExc(identityToken, playloadObj);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        if (!success) {
+            // TODO 校验token失败具体操作
+            throw new ServiceException("苹果验证失败");
+        }
+        return playloadObj.getString("sub");
+    }
+
     public static List<UserStatisticsVo> completionDate(String start, String end) {
         //日期格式化
         List<UserStatisticsVo> dateList = new ArrayList<>();

+ 4 - 1
ruoyi-weixin/ruoyi-weixin-miniapp/src/main/java/com/ruoyi/weixin/domain/WxUserDto.java

@@ -65,5 +65,8 @@ public class WxUserDto implements Serializable {
      */
     private String birthday;
 
-
+    /**
+     * 是否注册
+     */
+    private Boolean isReg = false;
 }