package com.project.framework.web.service; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.aliyuncs.CommonRequest; import com.aliyuncs.CommonResponse; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.project.common.constant.AliyunSmsConstants; import com.project.common.constant.CacheConstants; import com.project.common.constant.Constants; import com.project.common.core.domain.entity.SysUser; import com.project.common.core.domain.model.LoginUser; import com.project.common.core.redis.RedisCache; import com.project.common.exception.ServiceException; import com.project.common.exception.base.BaseException; import com.project.common.exception.user.CaptchaException; import com.project.common.exception.user.CaptchaExpireException; import com.project.common.exception.user.UserPasswordNotMatchException; import com.project.common.utils.DateUtils; import com.project.common.utils.MessageUtils; import com.project.common.utils.ServletUtils; import com.project.common.utils.StringUtils; import com.project.common.utils.ip.IpUtils; import com.project.framework.manager.AsyncManager; import com.project.framework.manager.factory.AsyncFactory; import com.project.framework.security.context.AuthenticationContextHolder; import com.project.system.domain.bo.editBo.UserPhoneLoginBo; import com.project.system.mapper.SysUserMapper; import com.project.system.service.ISysConfigService; import com.project.system.service.ISysUserService; import com.project.system.sms.YPSmsApi; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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.stereotype.Component; import java.util.Random; import java.util.concurrent.TimeUnit; /** * 登录校验方法 * * @author project */ @Component @Slf4j @RequiredArgsConstructor public class SysLoginService { private final TokenService tokenService; private final AuthenticationManager authenticationManager; private final RedisCache redisCache; private final ISysUserService userService; private final SysUserMapper userMapper; private final ISysConfigService configService; private final YPSmsApi smsApi; /** * 登录验证 * * @param username 用户名 * @param password 密码 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public String login(String username, String password, String code, String uuid) { boolean captchaEnabled = configService.selectCaptchaEnabled(); // 验证码开关 if (captchaEnabled) { validateCaptcha(username, code, uuid); } // 用户验证 Authentication authentication; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId()); // 生成token return tokenService.createToken(loginUser); } /** * 新注册获取验证码 * @param phone 手机 * @return 验证码 */ public Boolean getVerifyCodeNew(String phone) { // 生成4位随机数 String code = ""; Random ran = new Random(); int randomNum = ran.nextInt(10000); code = String.format("%04d", randomNum); log.info("手机号:"+phone+"->验证码:"+code); boolean send = sendYp(phone, code); if (send){ redisCache.setCacheObject(getCacheKey(phone), code, Constants.PHONE_EXPIRATION, TimeUnit.MINUTES); return true; } redisCache.setCacheObject(getCacheKey(phone), code, Constants.PHONE_EXPIRATION, TimeUnit.MINUTES); return false; } /** * 获取验证码 * @param phone 手机号 * @return 验证码 */ public Boolean getVerifyCode(String phone) { SysUser user = userMapper.selectOne(new LambdaQueryWrapper().eq(SysUser::getPhonenumber,phone)); if (user==null){ throw new BaseException("您手机号尚未注册!"); } return getVerifyCodeNew(phone); } /** * 云片验证码 * @param phone 手机 * @param code 验证码 * @return 结果 */ private boolean sendYp(String phone, String code) { String result = smsApi.sendSms(phone, StringUtils.format(YPSmsApi.CODE_TMP, code, Constants.PHONE_EXPIRATION)); if (result.contains("\"code\":0,\"msg\":\"OK\"")){ log.info("发送成功 ->验证码:"+code); return true; } return false; } /** * 阿里验证码 * @param phone 手机 * @param code 验证码 * @return 结果 */ private boolean sendAl(String phone, String code ) { DefaultProfile profile = DefaultProfile.getProfile("cn-beijing", AliyunSmsConstants.SMS_APPID, AliyunSmsConstants.SMS_SECRET); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setMethod(MethodType.POST); request.setDomain("dysmsapi.aliyuncs.com"); request.setVersion("2017-05-25"); request.setAction("SendSms"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "盛商珠宝"); request.putQueryParameter("TemplateCode", "SMS_460945884"); request.putQueryParameter("TemplateParam", "{code:" + code + "}"); try { CommonResponse response = client.getCommonResponse(request); JSONObject jsonObject = JSON.parseObject(response.getData()); if ("OK".equals(jsonObject.get("Code"))) { log.info("发送成功 ->验证码:"+code); return true; } } catch (ClientException e) { e.printStackTrace(); } return false; } /** * 验证码登录 * @param bo 参数 * @return 结果 */ public String phoneLogin(UserPhoneLoginBo bo) { String phone = bo.getPhone(); // Boolean verified = verifyPhone(phone, bo.getCode()); // if (!verified){ // throw new BaseException("手机号验证码校验失败!"); // } SysUser user = null; if ("01".equals(bo.getUserType())){ user = userMapper.selectOne(new LambdaQueryWrapper() .eq(SysUser::getPhonenumber,bo.getPhone()) .and(wrapper->wrapper.eq(SysUser::getUserType,"00").or().eq(SysUser::getUserType, "01"))); } else { user = userMapper.selectOne(new LambdaQueryWrapper() .eq(SysUser::getPhonenumber,bo.getPhone()) .eq(SysUser::getUserType,bo.getUserType()) ); } if (user==null){ throw new BaseException("您手机号尚未注册或您选择登录类型有误!"); } if (!"0".equals(user.getStatus())){ throw new BaseException("您账号已停用或待审批,请联系营商办管理人员!"); } return this.login(user.getUserName(), user.getRecommendUser(), null, null); } /** * 校验验证码 * * @param username 用户名 * @param code 验证码 * @param uuid 唯一标识 */ public void validateCaptcha(String username, String code, String uuid) { String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); String captcha = redisCache.getCacheObject(verifyKey); redisCache.deleteObject(verifyKey); if (captcha == null) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new CaptchaException(); } } /** * 记录登录信息 * * @param userId 用户ID */ public void recordLoginInfo(Long userId) { SysUser sysUser = new SysUser(); sysUser.setUserId(userId); sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); sysUser.setLoginDate(DateUtils.getNowDate()); userService.updateUserProfile(sysUser); } /** * 获取验证码前缀 * @param phone 手机号 * @return 验证码 */ private String getCacheKey(String phone) { return CacheConstants.PHONE_CODE_KEY + phone; } /** * 校验验证码 * @param phone 手机号 * @param code 验证码 * @return 校验结果 */ public Boolean verifyPhone(String phone, String code) { String key = getCacheKey(phone); if (!redisCache.hasKey(key)) { return false; } String redisCode = redisCache.getCacheObject(key); boolean verify = redisCode.equals(code); if (verify){ redisCache.deleteObject(getCacheKey(phone)); } return verify; } }