|
|
@@ -1,26 +1,41 @@
|
|
|
package com.fs.framework.service;
|
|
|
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.http.HttpUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
import com.fs.common.constant.Constants;
|
|
|
import com.fs.common.core.redis.RedisCache;
|
|
|
import com.fs.common.exception.ServiceException;
|
|
|
import com.fs.common.exception.user.CaptchaException;
|
|
|
import com.fs.common.exception.user.CaptchaExpireException;
|
|
|
import com.fs.common.exception.user.UserPasswordNotMatchException;
|
|
|
+import com.fs.common.service.WechatLoginService;
|
|
|
import com.fs.common.utils.MessageUtils;
|
|
|
+import com.fs.common.utils.ServletUtils;
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
+import com.fs.common.utils.ip.IpUtils;
|
|
|
+import com.fs.company.domain.CompanyUser;
|
|
|
+import com.fs.company.service.ICompanyUserService;
|
|
|
import com.fs.framework.manager.AsyncManager;
|
|
|
import com.fs.framework.manager.factory.AsyncFactory;
|
|
|
import com.fs.framework.security.LoginUser;
|
|
|
import com.fs.his.domain.StoreLoginUser;
|
|
|
import com.fs.system.service.ISysConfigService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.security.authentication.AuthenticationManager;
|
|
|
import org.springframework.security.authentication.BadCredentialsException;
|
|
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
|
import org.springframework.security.core.Authentication;
|
|
|
+import org.springframework.security.core.userdetails.UserDetailsService;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
+import java.util.*;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 登录校验方法
|
|
|
@@ -28,6 +43,7 @@ import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
*/
|
|
|
@Component
|
|
|
+@Slf4j
|
|
|
public class CompanyLoginService
|
|
|
{
|
|
|
@Autowired
|
|
|
@@ -39,6 +55,24 @@ public class CompanyLoginService
|
|
|
@Autowired
|
|
|
private RedisCache redisCache;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private ICompanyUserService companyUserService;
|
|
|
+
|
|
|
+ @Value("${wechat.company.appid}")
|
|
|
+ private String appId;
|
|
|
+ @Value("${wechat.company.secret}")
|
|
|
+ private String secret;
|
|
|
+ @Value("${wechat.company.redirectUri}")
|
|
|
+ private String redirectUri;
|
|
|
+ @Value("${wechat.isNeedScan}")
|
|
|
+ private Boolean isNeedScan;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private WechatLoginService wechatLoginService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private UserDetailsService userDetailsService;
|
|
|
+
|
|
|
/**
|
|
|
* 登录验证
|
|
|
*
|
|
|
@@ -91,4 +125,227 @@ public class CompanyLoginService
|
|
|
return tokenService.createToken(loginUser);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ public boolean checkIsNeedCheck(String username, String password, String code, String uuid)
|
|
|
+ {
|
|
|
+ String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
|
|
|
+ String captcha = redisCache.getCacheObject(verifyKey);
|
|
|
+ //redisCache.deleteObject(verifyKey);
|
|
|
+ if (captcha == null)
|
|
|
+ {
|
|
|
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
|
|
|
+ throw new CaptchaExpireException();
|
|
|
+ }
|
|
|
+ if (!code.equalsIgnoreCase(captcha))
|
|
|
+ {
|
|
|
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
|
|
|
+ throw new CaptchaException();
|
|
|
+ }
|
|
|
+ // 用户验证
|
|
|
+ Authentication authentication = null;
|
|
|
+ try
|
|
|
+ {
|
|
|
+ // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
|
|
|
+ authentication = authenticationManager
|
|
|
+ .authenticate(new UsernamePasswordAuthenticationToken(username, password));
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ if (e instanceof BadCredentialsException)
|
|
|
+ {
|
|
|
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
|
|
+ throw new UserPasswordNotMatchException();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, e.getMessage()));
|
|
|
+ throw new ServiceException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
|
|
+ //查询当前登录用户信息
|
|
|
+ CompanyUser companyUser = companyUserService.selectCompanyUserById(loginUser.getUser().getUserId());
|
|
|
+
|
|
|
+ Long[] userIds = new Long[]{2020L};
|
|
|
+ for (Long userId : userIds) {
|
|
|
+ if (userId.equals(companyUser.getUserId())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否开启了扫码配置
|
|
|
+ if (ObjectUtil.isEmpty(isNeedScan) || !isNeedScan){
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ //true → 要发短信验证码再登录
|
|
|
+ //false → 直接登录
|
|
|
+ return needCheck(companyUser);
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean needCheck(CompanyUser companyUser) {
|
|
|
+ // 1. 校验 IP
|
|
|
+ if (!checkIp(companyUser)) {
|
|
|
+ // IP 不一致
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 校验是否首次登录
|
|
|
+ if (checkIsFirstLogin(companyUser)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 校验上次登录时间是否在五天前
|
|
|
+ if (checkIsLoginTime(companyUser)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 检查是否在设置的某一天
|
|
|
+ /*if (checkIsSpecialDay(new Date())) {
|
|
|
+ return true;
|
|
|
+ }*/
|
|
|
+ if (haveUnionId(companyUser)){
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean checkIp(CompanyUser companyUser) {
|
|
|
+ // 获取当前 IP
|
|
|
+ String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()).split(",")[0].trim();
|
|
|
+
|
|
|
+ // 获取已记录的登录 IP
|
|
|
+ String lastLoginIp = companyUser.getLoginIp();
|
|
|
+
|
|
|
+ if (StringUtils.isNotEmpty(lastLoginIp)) {
|
|
|
+ List<String> ipList = Arrays.stream(lastLoginIp.split(","))
|
|
|
+ .map(String::trim)
|
|
|
+ .filter(s -> !s.isEmpty())
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ return ipList.contains(ipAddr);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ //检查是否第一次登录
|
|
|
+ public boolean checkIsFirstLogin(CompanyUser companyUser){
|
|
|
+ // 获取上次登录 IP
|
|
|
+ String lastLoginIp = companyUser.getLoginIp();
|
|
|
+ if (StringUtils.isEmpty(lastLoginIp)||companyUser.getLoginDate()==null){
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ public boolean checkIsLoginTime(CompanyUser companyUser) {
|
|
|
+ // 获取上次登录时间
|
|
|
+ Date loginDate = companyUser.getLoginDate();
|
|
|
+ if (loginDate == null) {
|
|
|
+ // 没有登录记录,直接返回 true(需要处理)
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 当前时间
|
|
|
+ Date now = new Date();
|
|
|
+
|
|
|
+ // 计算两个时间的毫秒差
|
|
|
+ long diff = now.getTime() - loginDate.getTime();
|
|
|
+
|
|
|
+ // 5天 = 5 * 24 * 60 * 60 * 1000 毫秒
|
|
|
+ long fiveDays = 5L * 24 * 60 * 60 * 1000;
|
|
|
+
|
|
|
+ return diff >= fiveDays;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean haveUnionId( CompanyUser companyUser){
|
|
|
+ if (StringUtils.isEmpty(companyUser.getUnionId())){
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取微信登录二维码参数
|
|
|
+ * @param username 当前登录用户名
|
|
|
+ * @return 二维码参数
|
|
|
+ */
|
|
|
+ public Map<String, String> getWechatQrCode(String username) throws Exception {
|
|
|
+ // 生成 loginTicket
|
|
|
+ String ticket = UUID.randomUUID().toString();
|
|
|
+ redisCache.setCacheObject("login:ticket:" + ticket, username, 60, TimeUnit.SECONDS);
|
|
|
+
|
|
|
+ return wechatLoginService.getQrCode(ticket,appId,secret,redirectUri); // 返回二维码参数
|
|
|
+ }
|
|
|
+
|
|
|
+ public String checkWechatScan(String ticket) {
|
|
|
+ String status = redisCache.getCacheObject("wechat:scan:" + ticket);
|
|
|
+ if ("ok".equals(status)) {
|
|
|
+ String username = redisCache.getCacheObject("login:ticket:" + ticket);
|
|
|
+ redisCache.deleteObject("login:ticket:" + ticket);
|
|
|
+ redisCache.deleteObject("wechat:scan:" + ticket);
|
|
|
+ CompanyUser companyUser = companyUserService.selectUserByUserName(username);
|
|
|
+
|
|
|
+ // 调 UserDetailsServiceImpl.loadUserByUsername 获取完整 LoginUser
|
|
|
+ LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
|
|
|
+ companyUser.setUserId(loginUser.getUser().getUserId());
|
|
|
+ String loginIp = companyUser.getLoginIp();
|
|
|
+ String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()).split(",")[0].trim();
|
|
|
+ log.info("销售用户{}扫码验证过后登录获取到的ip地址{}", loginUser.getUser().getUserId(), ipAddr);
|
|
|
+ List<String> ipList = new ArrayList<>();
|
|
|
+ if (StringUtils.isNotEmpty(loginIp)) {
|
|
|
+ String[] ips = loginIp.split(",");
|
|
|
+ for (String ip : ips) {
|
|
|
+ ip = ip.trim();
|
|
|
+ if (!ip.isEmpty()) {
|
|
|
+ ipList.add(ip);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ipList.add(ipAddr);
|
|
|
+ List<String> distinctList = ipList.stream()
|
|
|
+ .map(String::trim) // 再次确保去掉空格
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ companyUser.setLoginIp(String.join(",", distinctList));
|
|
|
+ companyUser.setLoginDate(new Date());
|
|
|
+ companyUserService.updateCompanyUser(companyUser);
|
|
|
+ return tokenService.createToken(loginUser);
|
|
|
+ }else if (StringUtils.isNotEmpty(status)&&status.startsWith("error:")) {
|
|
|
+ // 把错误返回给前端
|
|
|
+ throw new ServiceException(status);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 微信扫码回调
|
|
|
+ */
|
|
|
+ public void handleCallback(String code, String ticket) {
|
|
|
+ String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId
|
|
|
+ + "&secret=" + secret
|
|
|
+ + "&code=" + code
|
|
|
+ + "&grant_type=authorization_code";
|
|
|
+
|
|
|
+ JSONObject json = JSON.parseObject(HttpUtil.get(url));
|
|
|
+ String unionid = json.getString("unionid");
|
|
|
+ if (unionid == null) throw new ServiceException("微信授权失败");
|
|
|
+
|
|
|
+ String username = redisCache.getCacheObject("login:ticket:" + ticket);
|
|
|
+ if (username == null) throw new ServiceException("ticket无效或过期");
|
|
|
+ CompanyUser companyUser = companyUserService.selectUserByUserName(username);
|
|
|
+ if (companyUser == null) throw new ServiceException("用户不存在");
|
|
|
+ if (companyUser.getUnionId() == null || companyUser.getUnionId().isEmpty()) {
|
|
|
+ // 如果用户没有绑定 unionid,则绑定当前扫码用户的 unionid
|
|
|
+ companyUser.setUnionId(unionid);
|
|
|
+ companyUserService.updateCompanyUser(companyUser);
|
|
|
+ } else if (!companyUser.getUnionId().equals(unionid)) {
|
|
|
+ // 如果用户已绑定 unionid,但与扫码用户不一致,则拒绝登录
|
|
|
+ redisCache.setCacheObject("wechat:scan:" + ticket, "error:账号与绑定用户不匹配", 30, TimeUnit.SECONDS);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ redisCache.setCacheObject("wechat:scan:" + ticket, "ok", 30, TimeUnit.SECONDS);
|
|
|
+ }
|
|
|
+
|
|
|
}
|