目录
一、搭建环境
1.1、创建-微服务
1.2、配置文件
1.3、网关配置:
二、前端验证码倒计时
学习产出:
学习目标:
学习内容:
学习时间:
学习产出:
学习目标:
学习内容:
学习时间:
学习产出:
学习目标:
学习内容:
学习时间:
学习产出:
一、搭建环境
1.1、创建-微服务
com.firefly.fireflymall
firefly-common
0.0.1-SNAPSHOT
com.baomidou
mybatis-plus-boot-starter
com.firefly.fireflymall firefly-common0.0.1-SNAPSHOT com.baomidou mybatis-plus-boot-starter
提示:排除mybatis的依赖
1.2、配置文件
创建authserver命名空间
nacos.yml
spring:
cloud:
nacos:
discovery:
server-addr: 124.223.14.248:8848
application:
name: firefly-authserver
server:
port: 16000
thymeleaf.yml
spring:
thymeleaf:
cache: false
suffix: .html
prefix: classpath:/templates/
logging.yml
# 日志级别
logging:
level:
com.firefly.fireflymall: debug
spring:
jackson:
date-format: yyyy-MM-dd
time-zone: GMT+8
thread.properties
firefly.thread.core.size= 20 firefly.thread.max.size= 200 firefly.thread.keep.alive.time= 10
redis.yml
Spring:
redis:
host: 124.223.14.248
port: 6379
SpringCache.properties
#指定缓存类型为redis spring.cache.type=redis # 指定redis中的过期时间为1h spring.cache.redis.time-to-live=1000000
配置域名映射:IP地址 auth.fire.flymall.com
1.3、网关配置:
- id: firefly_authserver_route
uri: lb://firefly-authserver
predicates:
- Host=auth.fire.flymall.com
提示:测试访问登录和注册页面
@Controller
@RequestMapping
public class LoginController {
@GetMapping("/login.html")
public String login() {
return "login";
}
@GetMapping("/reg.html")
public String reg() {
return "reg";
}
}
二、前端验证码倒计时 2.1、添加发送验证码按钮
定义id 使用 Jquery 触发点击事件
发送验证码
创建一个方法
$(function () {
$("#sendCode").click(function () {
//判断是否有该样式
if ($(this).hasClass("disabled")) {
// 正在倒计时
} else {
// 发送验证码
$.get("/sms/sendcode?phone=" + $("#phoneNum").val(), function (data) {
if (data.code != 0) {
alert(data.msg)
}
})
timeoutChangeStyle();
}
})
})
// 60秒
var num = 60;
function timeoutChangeStyle() {
// 先添加样式,防止重复点击
$("#sendCode").attr("class", "disabled")
// 到达0秒后 重置时间,去除样式
if (num == 0) {
$("#sendCode").text("发送验证码")
num = 60;
// 时间到达后清除样式
$("#sendCode").attr("class", "");
} else {
var str = num + "s 后再次发送"
$("#sendCode").text(str);
setTimeout("timeoutChangeStyle()", 1000);
}
num--;
}
2.2、映射请求页面
package com.firefly.common.config;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}
2.3、整合短信验证码
com.alibaba
fastjson
1.2.76
com.aliyun
aliyun-java-sdk-core
3.2.3
com.aliyun
aliyun-java-sdk-dysmsapi
1.0.0
SmsUtils
package com.firefly.fireflymall.fireflymallthirdparty.utils.sms;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import org.springframework.stereotype.Component;
@Component
public class SmsUtils {
public static final String VALIDATE_CODE = "SMS_******";//发送短信验证码
public void sendShortMessage(String templateCode, String phoneNumbers, String param) throws ClientException {
// 设置超时时间-可自行调整
System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
System.setProperty("sun.net.client.defaultReadTimeout", "10000");
// 初始化ascClient需要的几个参数
final String product = "Dysmsapi";// 短信API产品名称(短信产品名固定,无需修改)
final String domain = "dysmsapi.aliyuncs.com";// 短信API产品域名(接口地址固定,无需修改)
// 替换成你的AK
final String accessKeyId = "LTAIY8Mawj3VMseR";// 你的accessKeyId,参考本文档步骤2
final String accessKeySecret = "2eHxOORZf4YzpQebROfOoXBFLHJCRY";// 你的accessKeySecret,参考本文档步骤2
// 初始化ascClient,暂时不支持多region(请勿修改)
IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
IAcsClient acsClient = new DefaultAcsClient(profile);
// 组装请求对象
SendSmsRequest request = new SendSmsRequest();
// 使用post提交
request.setMethod(MethodType.POST);
// 必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
request.setPhoneNumbers(phoneNumbers);
// 必填:短信签名-可在短信控制台中找到
request.setSignName("项目名称");
// 必填:短信模板-可在短信控制台中找到
request.setTemplateCode(templateCode);
// 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
// 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含rn的情况在JSON中需要表示成\r\n,否则会导致JSON在服务端解析失败
request.setTemplateParam("{"code":"" + param + ""}");
// 可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段)
// request.setSmsUpExtendCode("90997");
// 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
// request.setOutId("yourOutId");
// 请求失败这里会抛ClientException异常
SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
// 请求成功
System.out.println("请求成功");
}
}
//测试-截取手机号后四位
public String sendCode(String phoneNumbers, String start, String end) {
int startInt = Integer.parseInt(start);
int endInt = Integer.parseInt(end);
String substring = phoneNumbers.substring(startInt, endInt);
return substring;
}
}
SmsController
package com.firefly.fireflymall.fireflymallthirdparty.controller;
import com.firefly.common.utils.R;
import com.firefly.fireflymall.fireflymallthirdparty.utils.sms.SmsUtils;
import org.springframework.stereotype.Controller;
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.ResponseBody;
import static com.firefly.common.exception.BizCodeEnum.SEND_CODE_FAIL;
import static com.firefly.common.exception.BizCodeEnum.SEND_CODE_SUCCESS;
@Controller
@RequestMapping("/sms")
public class SmsController {
@ResponseBody
@GetMapping("/sendcode")
public R sendCode(@RequestParam("phone") String phoneNumber,
@RequestParam("code") String code) {
try {
SmsUtils.sendCode(phoneNumber, code);
return R.ok(SEND_CODE_SUCCESS.getCode(), SEND_CODE_SUCCESS.getMsg());
} catch (Exception e) {
e.printStackTrace();
return R.error(SEND_CODE_FAIL.getCode(), SEND_CODE_FAIL.getMsg());
}
}
}
远程调用短信接口:SmsFeignService
package com.firefly.fireflymall.authserver.feign;
import com.firefly.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("firefly-third-party")
public interface SmsFeignService {
@GetMapping("/sms/sendcode")
public R sendCode(@RequestParam("phone") String phoneNumber,
@RequestParam("code") String code);
}
2.4、验证码防刷校验
用户要是一直提交验证码
前台:限制一分钟后提交后台:存入redis 如果有就返回
package com.firefly.fireflymall.authserver.controller;
import com.firefly.common.utils.R;
import com.firefly.fireflymall.authserver.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import static com.firefly.common.exception.BizCodeEnum.SMS_CODE_EXCEPTION;
@Slf4j
@Controller
public class LoginController {
@Autowired
private SmsService smsService;
@GetMapping("/sendcode")
public R sendCode(@RequestParam("phone") String phoneNumber) {
try {
smsService.sendCode(phoneNumber);
return R.ok(SEND_CODE_SUCCESS.getCode(), SEND_CODE_SUCCESS.getMsg());
} catch (RRException e) {
return R.error(e.getCode(), e.getMsg());
}
}
}
SmsServiceImpl
package com.firefly.fireflymall.authserver.service.impl;
import com.firefly.common.exception.RRException;
import com.firefly.common.utils.RandomUtil;
import com.firefly.fireflymall.authserver.feign.SmsFeignService;
import com.firefly.fireflymall.authserver.service.SmsService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
import static com.firefly.common.constant.AuthServerConstant.SMS_CODE_REDIS_PREFIX;
import static com.firefly.common.exception.BizCodeEnum.SMS_CODE_EXCEPTION;
@Service("SmsService")
public class SmsServiceImpl implements SmsService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private SmsFeignService smsFeignService;
@Override
public void sendCode(String phoneNumber) {
if (StringUtils.isNotEmpty(phoneNumber)) {
//缓存中的key
String redisKey = SMS_CODE_REDIS_PREFIX + phoneNumber;
String redisCode = redisTemplate.opsForValue().get(redisKey);
if (!StringUtils.isEmpty(redisCode)) {
long time = Long.parseLong(redisCode.split("_")[1]);
//防止多次刷新
if (System.currentTimeMillis() - time < 600000) {
throw new RRException(SMS_CODE_EXCEPTION.getMsg(),SMS_CODE_EXCEPTION.getCode());
}
}else {
String code = RandomUtil.getFourBitRandom().toUpperCase();
// 拼接验证码
String substring = code + "_" + System.currentTimeMillis();
//redis缓存验证码 防止同一个phone在60秒内发出多次验证吗
redisTemplate.opsForValue().set(redisKey, substring, 10, TimeUnit.MINUTES);
// 调用第三方服务发送验证码
R r = smsFeignService.sendCode(phoneNumber, code);
if (r.getCode() != SEND_CODE_SUCCESS.getCode()) {
log.error(SEND_CODE_FAIL.getMsg());
}
}
}
}
}
2.5、会员注册 2.5.1、编写 vo 接收页面提交
使用到了 JSR303校验
package com.firefly.fireflymall.authserver.vo;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@Data
public class UserRegistVo {
@NotEmpty(message = "用户名必须提交")
@Length(min = 6,max = 18,message = "用户名必须是6-18位字符")
private String userName;
@NotEmpty(message = "密码必须填写")
@Length(min = 6,max = 18,message = "密码必须是6-18位字符")
private String password;
@NotEmpty(message = "手机号码必须提交")
@Pattern(regexp = "^[1]([3-9])[0-9]{9}$",message = "手机格式不正确")
private String phone;
@NotEmpty(message = "验证码必须填写")
private String code;
}
重定向携带数据,利用session原理,将数据放在session中 只要跳转到下一个页面取出这个数据,session中的数据就会删除掉。
TODO:分布式下 session 的问题
RedirectAttributes redirectAttributes 重定向携带数据 redirectAttributes.addFlashAttribute("errors", errors) 只能获取一次。
前端页面获取数据:
重定向地址默认是跳转到本项目的地址,如果跳转到其他服务,则需要写全路径:
return "redirect:http://auth.fire.flymall.com/reg.html";2.5.2、会员注册接口
LoginController
@PostMapping("/register")
public String Register(@Valid UserRegisterVo userRegisterVo, BindingResult result, RedirectAttributes redirectAttributes) {
String url = registService.register(userRegisterVo, result, redirectAttributes);
return url;
}
RegisterServiceImpl
@Override
public String register(UserRegisterVo userRegisterVo, BindingResult result, RedirectAttributes redirectAttributes) {
// 校验是否通过
if (result.hasErrors()) {
// 拿到错误信息转换成Map
Map errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
//用一次的属性
redirectAttributes.addFlashAttribute("errors", errors);
// 校验出错,转发到注册页
return "redirect:http://auth.fire.flymall.com/reg.html";
}
// 将传递过来的验证码 与 存redis中的验证码进行比较
String code = userRegisterVo.getCode();
String s = redisTemplate.opsForValue().get(SMS_CODE_REDIS_PREFIX + userRegisterVo.getPhone());
//判断缓存中是否有手机号对应的验证码
if (!StringUtils.isEmpty(s)) {
// 判断验证码和redis中的一致
if (code.equals(s.split("_")[0])) {
// 删除验证码:令牌机制
redisTemplate.delete(SMS_CODE_REDIS_PREFIX + userRegisterVo.getPhone());
// 调用远程服务,真正注册
R r = memberFeignService.register(userRegisterVo);
if (r.getCode() == MEMBER_REGISTER_SUCCESS.getCode()) {
// 远程调用注册服务成功
return "redirect:http://auth.fire.flymall.com/login.html";
} else {
Map errors = new HashMap<>();
errors.put("msg", r.getData(new TypeReference() {
}));
redirectAttributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.fire.flymall.com/reg.html";
}
} else {
Map errors = new HashMap<>();
errors.put("code", "验证码错误");
redirectAttributes.addFlashAttribute("code", "验证码错误");
// 校验出错,转发到注册页
return "redirect:http://auth.fire.flymall.com/reg.html";
}
} else {
Map errors = new HashMap<>();
errors.put("code", "验证码错误");
redirectAttributes.addFlashAttribute("code", "验证码错误");
// 校验出错,转发到注册页
return "redirect:http://auth.fire.flymall.com/reg.html";
}
}
前端获取数据方式
真正完成会员注册远程接口:MemberFeignService
package com.firefly.fireflymall.authserver.feign;
import com.firefly.common.utils.R;
import com.firefly.fireflymall.authserver.vo.MemberRegisterVo;
import com.firefly.fireflymall.authserver.vo.UserRegisterVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("firefly-member")
public interface MemberFeignService {
@PostMapping("/register")
public R register(@RequestBody UserRegisterVo userRegisterVo);
}
真正的完成会员注册接口
注册信息: MemberRegister
package com.firefly.fireflymall.member.vo;
import lombok.Data;
@Data
public class MemberRegister {
private String userName;
private String password;
private String phone;
}
MemberController
微服务之间传递对象都是http+json形式
@PostMapping("/register")
public R register(@RequestBody MemberRegisterVo registerVo) {
try {
memberService.register(registerVo);
return R.ok(MEMBER_REGISTER_SUCCESS.getCode(), MEMBER_REGISTER_SUCCESS.getMsg());
} catch (RRException e) {
return R.error(e.getCode(), e.getMsg());
}
}
@Override
public void register(MemberRegisterVo registerVo) {
MemberEntity memberEntity = new MemberEntity();
// 设置默认等级
MemberLevelEntity memberLevelEntity = memberLevelDao.getDefaultLevel();
memberEntity.setLevelId(memberLevelEntity.getId());
// 检查手机号和用户名是否唯一
this.checkPhoneUnique(registerVo.getPhone());
this.checkUserNameUnique(registerVo.getUserName());
memberEntity.setMobile(registerVo.getPhone());
memberEntity.setUsername(registerVo.getUserName());
baseMapper.insert(memberEntity);
}
@Override
//检查用户名是否唯一
public void checkUserNameUnique(String username) {
MemberDao memberDao = this.baseMapper;
Integer count = memberDao.selectCount(new QueryWrapper().eq("username", username));
if (count > 0) {
throw new RRException(BizCodeEnum.USERNAME_IS_EXIST.getMsg());
}
}
@Override
//检查手机号是否唯一
public void checkPhoneUnique(String phone) {
MemberDao memberDao = this.baseMapper;
Integer mobile = memberDao.selectCount(new QueryWrapper().eq("mobile", phone));
if (mobile > 0) {
throw new RRException(BizCodeEnum.PHONE_IS_EXIST.getMsg());
}
}
MD5加密
//设置密码并进行MD5加密 BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encode = passwordEncoder.encode(registVo.getPassword()); entity.setPassword(encode);
学习目标:
使用到了 JSR303校验
学习与思考:密码是直接存入数据库吗?
这样子会导致数据的不安全引出了使用 MD5进行加密,但是MD5加密后,别人任然可以暴力破解可以使用加盐的方式,将密码加密后,得到一串随机字符,随机字符和密码和进行验证相同结果返回true否则false
学习时间:
提示:这里可以添加计划学习的时间
例如:
1、 周一至周五晚上 7 点—晚上9点
2、 周六上午 9 点-上午 11 点
3、 周日下午 3 点-下午 6 点
学习产出:
提示:这里统计学习计划的总量
例如:
1、 技术笔记 2 遍
2、CSDN 技术博客 3 篇
3、 学习的 vlog 视频 1 个
提示:这里可以添加学习目标
例如:一周掌握 Java 入门知识
学习内容:
提示:这里可以添加要学的内容
例如:
1、 搭建 Java 开发环境
2、 掌握 Java 基本语法
3、 掌握条件语句
4、 掌握循环语句
学习时间:
提示:这里可以添加计划学习的时间
例如:
1、 周一至周五晚上 7 点—晚上9点
2、 周六上午 9 点-上午 11 点
3、 周日下午 3 点-下午 6 点
学习产出:
提示:这里统计学习计划的总量
例如:
1、 技术笔记 2 遍
2、CSDN 技术博客 3 篇
3、 学习的 vlog 视频 1 个
提示:这里可以添加学习目标
例如:一周掌握 Java 入门知识
学习内容:
提示:这里可以添加要学的内容
例如:
1、 搭建 Java 开发环境
2、 掌握 Java 基本语法
3、 掌握条件语句
4、 掌握循环语句
学习时间:
提示:这里可以添加计划学习的时间
例如:
1、 周一至周五晚上 7 点—晚上9点
2、 周六上午 9 点-上午 11 点
3、 周日下午 3 点-下午 6 点
学习产出:
提示:这里统计学习计划的总量
例如:
1、 技术笔记 2 遍
2、CSDN 技术博客 3 篇
3、 学习的 vlog 视频 1 个



