目录
- 一、环境搭建
- 二、登录实现
- 三、注册功能
一、环境搭建
A、新建 gulimall-auth-server 项目
B、添加依赖模板
C、创建完项目后,我们需要降一下版本,方便处理
D、导入 common 工具类,同时排除 mp 的依赖
E、application.properties 配置服务的注册与发现
F、主启动类上加上注解开启服务注册与发现及远程调用功能
G、导入前端模板
H、配置域名映射
I、虚拟机中上传静态资源,完成动静分离
H、网关服务中配置路由转发
二、登录实现
在 gulimall-auth-controller 中的 LoginController 中实现对登录请求处理的接口
@Autowired
private MemberFeignService memberFeignService;
@GetMapping(value = "/login.html")
public String loginPage(HttpSession session) {
// 从 session 先取出来用户信息, 判断用户是否已经登录过了
Object attribute = session.getAttribute(AuthServerConstant.LOGIN_USER);
// 如果用户没登陆那就跳转到登录页面
if (attribute == null) {
return "login";
} else {
return "redirect:http://gulimall.com";
}
}
@PostMapping(value = "/login")
public String login(UserLoginVo vo, RedirectAttributes attributes, HttpSession session) {
// 远程登录
R login = memberFeignService.login(vo);
if (login.getCode() == 0) {
MemberResponseVo data = login.getData("data", new TypeReference<MemberResponseVo>(){});
session.setAttribute(AuthServerConstant.LOGIN_USER,data);
return "redirect:http://gulimall.com";
} else {
Map<String,String> errors = new HashMap<>();
errors.put("msg",login.getData("msg",new TypeReference<String>(){}));
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}
这里是远程调用 member 服务下的接口。。。
对应的 MemberController 中:
@PostMapping(value = "/login")
public R login(@RequestBody MemberUserLoginVo vo) {
MemberEntity memberEntity = memberService.login(vo);
if (memberEntity != null) {
return R.ok().setData(memberEntity);
} else {
return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());
}
}
MemberServiceImpl 中 login 方法的实现:
@Override
public MemberEntity login(MemberUserLoginVo vo) {
String loginacct = vo.getLoginacct();
String password = vo.getPassword();
//1、去数据库查询 SELECT * FROM ums_member WHERE username = ? OR mobile = ?
MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>()
.eq("username", loginacct).or().eq("mobile", loginacct));
if (memberEntity == null) {
//登录失败
return null;
} else {
//获取到数据库里的password
String password1 = memberEntity.getPassword();
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//进行密码匹配
boolean matches = passwordEncoder.matches(password, password1);
if (matches) {
//登录成功
return memberEntity;
}
}
return null;
}
密码匹配是 MD5 算法自带的匹配方法。。。,比对成功完成账号密码的登录功能。。
三、注册功能
注册功能中一个重要功能的实现是获取短信验证码。。
首先我们先在前端实现验证码倒计时的功能:
$(function () {
$("#sendCode").click(function () {
//2、倒计时
if ($(this).hasClass("disabled")) {
//正在倒计时。
} else {
//1、给指定手机号发送验证码
$.get("/sms/sendcode?phone=" + $("#phoneNum").val(), function (data) {
if (data.code != 0) {
alert(data.msg);
}
});
timeoutChangeStyle();
}
});
})
var num = 60;
function timeoutChangeStyle() {
$("#sendCode").attr("class", "disabled");
if (num == 0) {
$("#sendCode").text("发送验证码");
num = 60;
$("#sendCode").attr("class", "");
} else {
var str = num + "s 后再次发送";
$("#sendCode").text(str);
setTimeout("timeoutChangeStyle()", 1000);
}
num--;
}
</script>
gulimall-third-party 第三方服务模块整合短信验证码功能:
package com.fancy.gulimall.thirdparty.controller;
import com.fancy.common.utils.R;
import com.fancy.gulimall.thirdparty.component.SmsComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/sms")
public class SmsSendController {
@Autowired
SmsComponent smsComponent;
/**
* 提供给别的服务进行调用
* @param phone
* @param code
* @return
*/
@GetMapping("/sendcode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code){
smsComponent.sendSmsCode(phone,code);
return R.ok();
}
}
sendSmsCode 方法实现:这里主要是配置阿里云密钥、短信签名、手机号等
package com.fancy.gulimall.thirdparty.component;
import com.fancy.gulimall.thirdparty.utils.HttpUtils;
import lombok.Data;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
@Component
public class SmsComponent {
private String host;
private String path;
private String skin;
private String sign;
private String appcode;
public void sendSmsCode(String phone,String code){
String method = "GET";
String appcode = "93b7e19861a24c519a7548b17dc16d75";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
querys.put("code", code);
querys.put("phone", phone);
querys.put("skin", skin);
querys.put("sign", sign);
//JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.zip
try {
HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);
//System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。
//状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误
//获取response的body
System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
在 gulimall-auth-server 模块下实现第三方服务的短信服务的远程调用。。。
实现短信发送验证功能:这里将短信验证码存入 Redis 中,并设置过期时间为 3 分钟,实现三分钟内验证码有效的功能,同时实现接口防刷的功能。
@ResponseBody
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) {
//1、接口防刷
String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
if (!StringUtils.isEmpty(redisCode)) {
//活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码
long currentTime = Long.parseLong(redisCode.split("_")[1]);
if (System.currentTimeMillis() - currentTime < 60000) {
//60s内不能再发
return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMessage());
}
}
//2、验证码的再次效验 redis.存key-phone,value-code
int code = (int) ((Math.random() * 9 + 1) * 100000);
String codeNum = String.valueOf(code);
String redisStorage = codeNum + "_" + System.currentTimeMillis();
//存入redis,防止同一个手机号在60秒内再次发送验证码
stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,
redisStorage,10, TimeUnit.MINUTES);
thirdPartFeignService.sendCode(phone, codeNum);
return R.ok();
}
注册接口的实现:从Redis 中根据手机号取出验证码进行验证,进行注册是调用 远程方法
@PostMapping(value = "/register")
public String register(@Valid UserRegisterVo vos, BindingResult result,
RedirectAttributes attributes) {
//如果有错误回到注册页面
if (result.hasErrors()) {
Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
attributes.addFlashAttribute("errors",errors);
//效验出错回到注册页面
return "redirect:http://auth.gulimall.com/reg.html";
}
//1、效验验证码
String code = vos.getCode();
//获取存入Redis里的验证码
String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());
if (!StringUtils.isEmpty(redisCode)) {
//截取字符串
if (code.equals(redisCode.split("_")[0])) {
//删除验证码;令牌机制
stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX+vos.getPhone());
//验证码通过,真正注册,调用远程服务进行注册
R register = memberFeignService.register(vos);
if (register.getCode() == 0) {
//成功
return "redirect:http://auth.gulimall.com/login.html";
} else {
//失败
Map<String, String> errors = new HashMap<>();
errors.put("msg", register.getData("msg",new TypeReference<String>(){}));
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
//效验出错回到注册页面
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
} else {
//效验出错回到注册页面
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}
远程方法 register
@PostMapping(value = "/register")
public R register(@RequestBody MemberUserRegisterVo vo) {
try {
memberService.register(vo);
} catch (PhoneException e) {
return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage());
} catch (UsernameException e) {
return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage());
}
return R.ok();
}
memberService.register 方法实现:
@Override
public void register(MemberUserRegisterVo vo) {
MemberEntity memberEntity = new MemberEntity();
//设置默认等级
MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
memberEntity.setLevelId(levelEntity.getId());
//设置其它的默认信息
//检查用户名和手机号是否唯一。感知异常,异常机制
checkPhoneUnique(vo.getPhone());
checkUserNameUnique(vo.getUserName());
memberEntity.setNickname(vo.getUserName());
memberEntity.setUsername(vo.getUserName());
//密码进行MD5加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encode = bCryptPasswordEncoder.encode(vo.getPassword());
memberEntity.setPassword(encode);
memberEntity.setMobile(vo.getPhone());
memberEntity.setGender(0);
memberEntity.setCreateTime(new Date());
//保存数据
this.baseMapper.insert(memberEntity);
}
@Override
public void checkPhoneUnique(String phone) throws PhoneException {
Integer phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (phoneCount > 0) {
throw new PhoneException();
}
}
@Override
public void checkUserNameUnique(String userName) throws UsernameException {
Integer usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
if (usernameCount > 0) {
throw new UsernameException();
}
}
注意:这里的加密方式 是 MD5 + salt 的盐值加密,更难以被破解。。。
这里如果用户名或手机号有重复,会抛出相应的异常,这里是用到了异常感知的处理方式。。。