Browse Source

update 用户一键登录

ct 2 days ago
parent
commit
1af4052a74

+ 3 - 3
fs-service/src/main/resources/application-druid-jnmy-test.yml

@@ -41,7 +41,7 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://120.46.174.121:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    url: jdbc:mysql://1.94.236.76:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                     username: root
                     password: Ylrztek250218!3@.
                 # 从库数据源
@@ -96,7 +96,7 @@ spring:
             druid:
                 # 主库数据源
                 master:
-                    url: jdbc:mysql://120.46.174.121:2345/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    url: jdbc:mysql://1.94.236.76:2345/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                     username: root
                     password: Ylrztek250218!3@.
                 # 初始连接数
@@ -145,7 +145,7 @@ spring:
                 # 主库数据源
                 type: com.alibaba.druid.pool.DruidDataSource
                 driverClassName: com.mysql.cj.jdbc.Driver
-                url: jdbc:mysql://120.46.174.121:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                url: jdbc:mysql://1.94.236.76:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                 username: root
                 password: Ylrztek250218!3@.
                 # 初始连接数

+ 347 - 0
fs-user-app/src/main/java/com/fs/app/controller/AppLoginController.java

@@ -6,11 +6,15 @@ import cn.hutool.core.date.DateTime;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.annotation.Login;
 import com.fs.app.param.*;
+import com.fs.app.utils.UniVerifyUtil;
 import com.fs.app.utils.WxUtil;
 import com.fs.common.VerifyCodeUtil;
+import com.fs.common.annotation.RateLimiter;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.enums.LimitType;
+import com.fs.common.exception.CustomException;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.service.ISmsService;
 import com.fs.common.utils.sign.Md5Utils;
@@ -71,6 +75,9 @@ public class AppLoginController extends AppBaseController{
     @Autowired
     private OpenIMService openIMService;
 
+    @Autowired
+    private UniVerifyUtil uniVerifyUtil;
+
 //    @ApiOperation("注册app用户")
 //    @PostMapping("/register")
 //    @RepeatSubmit
@@ -127,6 +134,37 @@ public class AppLoginController extends AppBaseController{
         }
     }
 
+    @ApiOperation("UniApp一键登录(安全)")
+    @PostMapping("/loginByUniverify")
+    @Transactional
+    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+    public R loginByUniverify(@RequestBody FsUserLoginParam param) {
+        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
+            return R.error("一键登录凭证不完整");
+        }
+//        String tokenCacheKey = "univerify:used:" + param.getAccessToken();
+//        if (redisCache.getCacheObject(tokenCacheKey) != null) {
+//            return R.error("登录凭证已失效,请重新授权");
+//        }
+        String phone;
+        try {
+            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
+        } catch (CustomException e) {
+            return R.error(e.getMessage());
+        } catch (Exception e) {
+            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
+            return R.error("获取手机号失败,请重试");
+        }
+        if (StringUtils.isEmpty(phone)) {
+            return R.error("获取手机号失败");
+        }
+//        redisCache.setCacheObject(tokenCacheKey, "1", 1, TimeUnit.MINUTES);
+        param.setPhone(phone);
+        return handleSecurePhoneLogin(param);
+    }
+
+
+
     @PostMapping("/registerSendCode")
     public R registerSendCode(@RequestBody Map<String, String> body){
         String phone = body.get("phone");
@@ -458,6 +496,163 @@ public class AppLoginController extends AppBaseController{
         return userService.updatePasswordByPhone(password,encryptPhone);
     }
 
+    @ApiOperation("绑定手机号")
+    @PostMapping("/setIPhoneNumberOne")
+    public R setIPhoneNumberOne(@Validated @RequestBody FsUserEditPhoneParam param) {
+        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
+            return R.error("一键登录凭证不完整");
+        }
+        String phone;
+        try {
+            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
+        } catch (CustomException e) {
+            return R.error(e.getMessage());
+        } catch (Exception e) {
+            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
+            return R.error("获取手机号失败,请重试");
+        }
+        if (StringUtils.isEmpty(phone)) {
+            return R.error("获取手机号失败");
+        }
+        param.setPhone(phone);
+
+        FsUser userMap = findUserByPhone(param.getPhone());
+        if (userMap != null) {
+            if (StringUtils.isNotEmpty(userMap.getAppleKey()) && !param.getAppleKey().equals(userMap.getAppleKey())) {
+                return R.error("该手机号已绑定其他账号");
+            }
+            if (param.getSimExist() == 0 && !Md5Utils.hash(param.getPassword()).equals(userMap.getPassword())) {
+                return R.error("密码不正确");
+            }
+        } else {
+            userMap = createNewAppleUser(param, param.getPhone());
+        }
+
+        userMap.setLoginDevice(param.getLoginDevice());
+        userMap.setHistoryApp("app");
+//        if (userMap.getFirstLoginTime() == null) {
+//            userMap.setFirstLoginTime(new Date());
+//        }
+        userMap.setSource(param.getSource());
+        if (userMap.getNickName().equals("匿名用户**")) {
+            userMap.setNickName("苹果用户" + param.getPhone().substring(param.getPhone().length() - 4));
+        }
+        userMap.setAppleKey(param.getAppleKey());
+//        addUserPortrait(userMap);
+        if (userService.updateFsUser(userMap)>0){
+            return generateTokenAndReturn(userMap);
+        }
+        return R.error("绑定手机号失败");
+    }
+
+
+    @ApiOperation("绑定手机号")
+    @PostMapping("/setPhoneOne")
+    public R setPhoneOne(@Validated @RequestBody FsUserEditPhoneParam param) {
+        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
+            return R.error("一键登录凭证不完整");
+        }
+        String phone;
+        try {
+            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
+        } catch (CustomException e) {
+            return R.error(e.getMessage());
+        } catch (Exception e) {
+            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
+            return R.error("获取手机号失败,请重试");
+        }
+        if (StringUtils.isEmpty(phone)) {
+            return R.error("获取手机号失败");
+        }
+
+        param.setPhone(phone);
+
+        FsUser user = userService.selectFsUserByUnionid(param.getUnionId());
+        if (user == null) {
+            return R.error("用户数据不存在");
+        }
+        //验证码绑定,需要校验验证码
+        if (param.getBindType()!=null){
+            String encryptPhone = encryptPhone(param.getPhone());
+            String redisCode = redisCache.getCacheObject("sms:code:" + encryptPhone);
+            if (StringUtils.isEmpty(redisCode)){
+                return R.error("验证码已过期,请重新发送");
+            }
+            if (!redisCode.equals(param.getCode())) {
+                return R.error("验证码错误");
+            }
+            redisCache.deleteObject("sms:code:" + encryptPhone);
+        }
+        FsUser userMap = findUserByPhone(param.getPhone());
+        //绑定的手机号已存在用户的情况,将微信登录的时候创建的新号的UnionId移动到老号中,删除新号(将两个号合并)
+        if (userMap!=null){
+            if (userMap.getUserId().equals(user.getUserId())) {
+                user.setPhone(param.getPhone());
+                user.setLoginDevice(param.getLoginDevice());
+                user.setHistoryApp("app");
+//                if (user.getFirstLoginTime() == null) {
+//                    user.setFirstLoginTime(new Date());
+//                }
+                user.setSource(param.getSource());
+                userService.updateFsUser(user);
+//                addUserPortrait(user);
+                return generateTokenAndReturn(user);
+            }
+            if (StringUtils.isNotEmpty(userMap.getUnionId())&&!userMap.getUnionId().equals(user.getUnionId())){
+                return R.error("该手机号已绑定其他微信");
+            }
+            //如果存在手机号也有用户,微信也有用户,保留创建时间比较久的用户
+            FsUser keepUser;
+            FsUser deleteUser;
+            if (userMap.getCreateTime().before(user.getCreateTime())){
+                keepUser = userMap;
+                deleteUser = user;
+            }else {
+                keepUser = user;
+                deleteUser = userMap;
+            }
+            keepUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);
+            keepUser.setSource(param.getSource());
+            keepUser.setHistoryApp("app");
+//            if (keepUser.getFirstLoginTime() == null) {
+//                keepUser.setFirstLoginTime(new Date());
+//            }
+            keepUser.setUnionId(user.getUnionId());
+            keepUser.setPhone(param.getPhone());
+            keepUser.setIntegral(keepUser.getIntegral()+deleteUser.getIntegral());
+            if (userService.updateFsUser(keepUser)>0){
+//                userService.realDeleteFsUserByUserId(deleteUser.getUserId());
+                userService.deleteFsUserByUserId(deleteUser.getUserId());
+                return generateTokenAndReturn(keepUser);
+            }
+            return R.error("绑定手机号失败");
+        }
+        user.setPhone(param.getPhone());
+        user.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null );
+        user.setHistoryApp("app");
+//        if (user.getFirstLoginTime() == null) {
+//            user.setFirstLoginTime(new Date());
+//        }
+        user.setSource(param.getSource()!= null ? param.getSource() : null);
+        userMap = new FsUser();
+        userMap.setLoginDevice(param.getLoginDevice());
+        userMap.setHistoryApp("app");
+//        if (userMap.getFirstLoginTime() == null) {
+//            userMap.setFirstLoginTime(new Date());
+//        }
+        userMap.setSource(param.getSource());
+        if (user.getNickName().equals("匿名用户**")) {
+            userMap.setNickName("匿名用户" + param.getPhone().substring(param.getPhone().length() - 4));
+        }
+        userMap.setUserId(user.getUserId());
+        userMap.setPhone(param.getPhone());
+//        addUserPortrait(user);
+        if (userService.updateFsUser(userMap)>0){
+            return generateTokenAndReturn(user);
+        }
+        return R.error("绑定手机号失败");
+    }
+
     @ApiOperation("绑定手机号")
     @PostMapping("/setPhone")
     @Transactional(rollbackFor = Exception.class)
@@ -870,6 +1065,158 @@ public class AppLoginController extends AppBaseController{
         return generateTokenAndReturn(user);
     }
 
+    @ApiOperation("UniApp一键登录选择账号(安全)")
+    @PostMapping("/loginByUserIdUniverify")
+    @Transactional
+    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+    public R loginByUserIdUniverify(@RequestBody FsUserLoginParam param) {
+        if (StringUtils.isEmpty(param.getAccessToken()) || StringUtils.isEmpty(param.getOpenId())) {
+            return R.error("一键登录凭证不完整");
+        }
+//        String tokenCacheKey = "univerify:used:" + param.getAccessToken();
+//        if (redisCache.getCacheObject(tokenCacheKey) != null) {
+//            return R.error("登录凭证已失效,请重新授权");
+//        }
+        String phone;
+        try {
+            phone = uniVerifyUtil.getPhoneNumber(param.getAccessToken(), param.getOpenId());
+        } catch (CustomException e) {
+            return R.error(e.getMessage());
+        } catch (Exception e) {
+            logger.error("UniVerify获取手机号失败: {}", e.getMessage());
+            return R.error("获取手机号失败,请重试");
+        }
+        if (StringUtils.isEmpty(phone)) {
+            return R.error("获取手机号失败");
+        }
+//        redisCache.setCacheObject(tokenCacheKey, "1", 1, TimeUnit.MINUTES);
+        param.setPhone(phone);
+        return handleSecurePhoneLoginByUserId(param);
+    }
+
+
+    /**
+     * UniApp 一键登录(安全):手机号已由 uniCloud 校验,不信任客户端传入。
+     */
+    private R handleSecurePhoneLogin(FsUserLoginParam param) {
+        FsUser user = null;
+        String encryptPhone = encryptPhone(param.getPhone());
+        List<FsUser> usersByPhone = findUsersByPhone(encryptPhone);
+        if (CollectionUtil.isEmpty(usersByPhone)) {
+            R newUserResponse = buildNewAppUserLoginResponse(param, encryptPhone);
+            if (newUserResponse != null) {
+                return newUserResponse;
+            }
+            usersByPhone = findUsersByPhone(encryptPhone);
+            if (CollectionUtil.isEmpty(usersByPhone)) {
+                return R.error("注册失败,请重试");
+            }
+        }
+        if (usersByPhone.size() == 1) {
+            user = usersByPhone.get(0);
+            if (user.getUnionId() == null) {
+                if (user.getPhone().length() <= 11) {
+                    FsUser fsUser = new FsUser();
+                    fsUser.setUserId(user.getUserId());
+                    fsUser.setPhone(encryptPhone);
+                    userMapper.updateFsUser(fsUser);
+                }
+                return R.ok().put("isNew", true).put("phone", encryptPhone);
+            }
+            if (StringUtils.isNotEmpty(param.getJpushId())) {
+                updateExistingUserJpushId(user, param.getJpushId());
+//                try {
+//                    fsUserCouponService.sendRegisterCoupon(user);
+//                } catch (Exception e) {
+//                    logger.error("发送注册优惠券失败:{}", e.getMessage());
+//                }
+            }
+        } else {
+            for (FsUser fsUser : usersByPhone) {
+                fsUser.setPhone("***********");
+            }
+            return R.ok().put("users", usersByPhone);
+        }
+        user.setPhone("***********");
+        return generateTokenAndReturn(user);
+    }
+
+    /**
+     * 多账号场景下按 userId 登录,手机号已由 uniCloud 校验。
+     */
+    private R handleSecurePhoneLoginByUserId(FsUserLoginParam param) {
+        List<FsUser> usersByPhone = findUsersByPhone(param.getPhone());
+        if (CollectionUtil.isEmpty(usersByPhone)){
+            FsUser newUser = createNewUser(param);
+            if (StringUtils.isNotEmpty(newUser.getJpushId())) {
+//                try {
+//                    //发送注册优惠券
+//                    fsUserCouponService.sendRegisterCoupon(newUser);
+//                } catch (Exception e) {
+//                    logger.error("发送注册优惠券失败:{}",e.getMessage());
+//                }
+            }
+            return R.ok().put("isNew",true).put("phone",encryptPhone(param.getPhone()));
+        }
+        if (usersByPhone.size() == 1) {
+            FsUser user = usersByPhone.get(0);
+            if (StringUtils.isNotEmpty(param.getJpushId())) {
+                updateExistingUserJpushId(user, param.getJpushId());
+            }
+            return generateTokenAndReturn(user);
+        }
+        if (param.getUserId() == null) {
+            return R.error("请选择登录账号");
+        }
+        for (FsUser fsUser : usersByPhone) {
+            if (fsUser.getUserId().equals(param.getUserId())) {
+                if (StringUtils.isNotEmpty(param.getJpushId())) {
+                    updateExistingUserJpushId(fsUser, param.getJpushId());
+                }
+                return generateTokenAndReturn(fsUser);
+            }
+        }
+        return R.error("账号不匹配,请检查账号");
+    }
+
+    private R buildNewAppUserLoginResponse(FsUserLoginParam param, String encryptedPhone) {
+        FsUser newUser = createNewUser(param);
+        if (newUser.getUserId() == null) {
+            return null;
+        }
+//        if (StringUtils.isNotEmpty(newUser.getJpushId())) {
+//            try {
+//                fsUserCouponService.sendRegisterCoupon(newUser);
+//            } catch (Exception e) {
+//                logger.error("发送注册优惠券失败:{}", e.getMessage());
+//            }
+//        }
+
+//        String json = configService.selectConfigByKey("his.integral");
+//        IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+//        FsCourseReward rewardConfig = courseRewardMapper.selectFsCourseRewardById(config.getRewardId());
+//        if (Objects.isNull(rewardConfig)) {
+//            throw new CustomException("销售公司未配置" + ",请选择其他奖励类型");
+//        }
+//        if (com.fs.common.utils.StringUtils.isBlank(rewardConfig.getActualRewards())) {
+//            throw new CustomException("配置错误,请联系管理员#1");
+//        }
+//        JSONArray jsonArray;
+//        try {
+////            JSONArray jsonArrayOld = JSON.parseArray(rewardConfig.getActualRewards());
+//            jsonArray = buildGrandGiftArrayLls(jsonArrayOld, newUser.getUserId());
+//        } catch (Exception e) {
+//            logger.error("解析{}配置JSON失败", e);
+//            throw new CustomException("配置错误,请联系管理员#2");
+//        }
+//        if (jsonArray == null || jsonArray.isEmpty()) {
+//            throw new CustomException("配置错误,请联系管理员#3");
+//        }
+        return R.ok().put("isNew", true).put("phone", encryptedPhone);
+    }
+
+
+
     private FsUser createNewUser(FsUserLoginParam param) {
         FsUser newUser = new FsUser();
         newUser.setLoginDevice(param.getLoginDevice() != null ? param.getLoginDevice() : null);

+ 10 - 3
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -197,16 +197,23 @@ public class UserController extends  AppBaseController {
     @ApiOperation("检测是否登录")
     @GetMapping("/checkLogin")
     public R checkLogin(HttpServletRequest request){
-        if(StringUtils.isEmpty(getUserId())){
+        String userId = getUserId();
+        if(StringUtils.isEmpty(userId)){
             //未登录
             return R.error("未登录");
         }
         else{
             //登录
-            String token = jwtUtils.generateToken(Long.parseLong(getUserId()));
+            long userid = Long.parseLong(userId);
+            //2.1查询是否拉黑
+            FsUser fsUser = fsUserService.selectFsUserByUserId(userid);
+            if (fsUser.getStatus() == null || fsUser.getStatus() != 1){
+                return R.error("用户异常");
+            }
+            String token = jwtUtils.generateToken(userid);
             Map<String,Object> map=new HashMap<>();
             map.put("token",token);
-            return R.ok("认证成功").put("userId",getUserId()).put("token",token);
+            return R.ok("认证成功").put("userId", userId).put("token",token);
         }
     }
 

+ 4 - 0
fs-user-app/src/main/java/com/fs/app/param/FsUserEditPhoneParam.java

@@ -34,4 +34,8 @@ public class FsUserEditPhoneParam implements Serializable {
     private Integer bindType;
     //验证码
     private String code;
+
+    private String openId;
+
+    private String accessToken;
 }

+ 4 - 0
fs-user-app/src/main/java/com/fs/app/param/FsUserLoginParam.java

@@ -20,7 +20,11 @@ public class FsUserLoginParam implements Serializable {
     private String jpushId;
 
     private String openId;
+    /** UniApp 一键登录 access_token(univerify) */
+    private String accessToken;
 
     private String loginDevice;
     private String source;
+    private Long userId;
+
 }

+ 80 - 0
fs-user-app/src/main/java/com/fs/app/utils/UniVerifyUtil.java

@@ -0,0 +1,80 @@
+package com.fs.app.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.http.HttpUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * UniApp 一键登录(univerify)工具类,通过 uniCloud 云函数 URL 化接口换取手机号。
+ */
+@Component
+public class UniVerifyUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(UniVerifyUtil.class);
+
+    @Value("${univerify.cloud-function-url:https://fc-mp-f9cb4d3a-682d-4e2a-93ff-627e36b762b5.next.bspapp.com/getLoginMobile}")
+    private String cloudFunctionUrl;
+
+    /**
+     * 使用 access_token 和 openid 向 uniCloud 换取真实手机号。
+     */
+    public String getPhoneNumber(String accessToken, String openId) {
+        if (StringUtils.isBlank(cloudFunctionUrl)) {
+            throw new CustomException("一键登录服务未配置,请联系管理员");
+        }
+        if (StringUtils.isAnyBlank(accessToken, openId)) {
+            throw new CustomException("一键登录凭证不完整");
+        }
+
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("access_token", accessToken);
+        requestBody.put("openid", openId);
+
+        String response = HttpUtils.doPost(cloudFunctionUrl, requestBody.toJSONString());
+        log.info("UniVerify 换号响应: {}", response);
+        if (StringUtils.isBlank(response)) {
+            throw new CustomException("获取手机号失败");
+        }
+
+        JSONObject json = JSON.parseObject(response);
+        if (Boolean.FALSE.equals(json.getBoolean("success"))) {
+            JSONObject error = json.getJSONObject("error");
+            if (error != null) {
+                throw new CustomException(StringUtils.defaultIfBlank(error.getString("message"), "获取手机号失败"));
+            }
+            throw new CustomException("获取手机号失败");
+        }
+
+        Integer code = json.getInteger("code");
+        if (code != null && code != 0) {
+            String message = json.getString("message");
+            if (StringUtils.isBlank(message)) {
+                message = json.getString("errMsg");
+            }
+            throw new CustomException(StringUtils.defaultIfBlank(message, "获取手机号失败"));
+        }
+
+        String phoneNumber = json.getString("phoneNumber");
+        if (StringUtils.isBlank(phoneNumber) && json.containsKey("data")) {
+            Object dataObj = json.get("data");
+            if (dataObj instanceof JSONObject) {
+                phoneNumber = ((JSONObject) dataObj).getString("phoneNumber");
+            } else if (dataObj instanceof String) {
+                JSONObject data = JSON.parseObject((String) dataObj);
+                if (data != null) {
+                    phoneNumber = data.getString("phoneNumber");
+                }
+            }
+        }
+        if (StringUtils.isBlank(phoneNumber)) {
+            throw new CustomException("获取手机号失败");
+        }
+        return phoneNumber;
+    }
+}

+ 1 - 1
fs-user-app/src/main/resources/application.yml

@@ -7,7 +7,7 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-myhk-test
+    active: druid-jnmy-test
 #    active: dev
 #    active: druid-jzzx
 #    active: druid-yzt