2.2.1.RELEASE
,并引入Common服务依赖,因为不操作数据库,所以排除mybaitsplus依赖<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.1.RELEASEversion>
<relativePath/>
parent>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.RELEASEspring-cloud.version>
properties>
<dependency>
<groupId>com.achang.achangmallgroupId>
<artifactId>achangmall-commonartifactId>
<version>0.0.1-SNAPSHOTversion>
<exclusions>
<exclusion>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
exclusion>
exclusions>
dependency>
spring.application.name=achang-auth-server
spring.cloud.nacos.server-addr=127.0.0.1:8848
server.port=20000
spring.thymeleaf.cache=false
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class AchangAuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AchangAuthServerApplication.class, args);
}
}
192.168.109.101 auth.achangmall.com
修改reg.html、login.html里面的路径引用
添加网关转发配置,achangmall-gateway/src/main/resources/application.yml
- id: auth_route
uri: lb://achang-auth-server
predicates:
- Host=auth.achangmall.com
访问http://auth.achangmall.com/
,访问成功!!!
@Controller
public class LoginController {
@GetMapping("/login.html")
public String loginPage(){
return "login";
}
@GetMapping("/reg.html")
public String register(){
return "reg";
}
}
achang-auth-server/src/main/resources/templates/reg.html
<a id="sendCode">发送验证码a>
$(function (){
$("#sendCode").click(function (){
if ($(this).hasClass("disabled")){
//todo 发送手机验证码业务
}{
timeoutChangeSytle()
}
});
})
var num = 60;
function timeoutChangeSytle(){
$("#sendCode").attr("class","disabled")
if (num==0){
$("#sendCode").text("发送验证码")
num = 60;
$("#sendCode").attr("class","")
}else {
var str = num+"后再次发送"
$("#sendCode").text(str)
setTimeout("timeoutChangeSytle()",1000)
}
num--;
}
视图映射
com.achang.achangmall.auth.conf.AchangWebConfig
@Configuration
public class AchangWebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}
https://market.aliyun.com/products/57126001/cmapi00040066.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IIijPFAa-1634478751848)(C:/Users/PePe/AppData/Roaming/Typora/typora-user-images/image-20211017162832535.png)]
https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
spring:
application:
name: achangmall-third-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
alicloud:
sms:
host: https://intlsms.market.alicloudapi.com
path: /comms/sms/sendmsgall
method: POST
appcode: 你的appcode
channel: 0
templateID: '0000000'
@Component
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
public class SmsComponent {
private String host;
private String path;
private String method;
private String appcode;
private String channel;
private String templateID;
public void sendCode(String phone,String code){
Map<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "APPCODE " + appcode);
//根据API的要求,定义相对应的Content-Type
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, String> querys = new HashMap<String, String>();
Map<String, String> bodys = new HashMap<String, String>();
bodys.put("callbackUrl", "http://test.dev.esandcloud.com");
bodys.put("channel", channel);
bodys.put("mobile", "+86"+phone);
bodys.put("templateID", templateID);
bodys.put("templateParamSet", code+", 1");
try {
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Test
public void test(){
smsComponent.sendCode("13567790741","6379");
}
com.achang.achangmall.controller.SmsSendController
@RestController
@RequestMapping(value = "/sms")
public class SmsSendController {
@Resource
private SmsComponent smsComponent;
/**
* 提供给别的服务进行调用
* @param phone
* @param code
* @return
*/
@GetMapping(value = "/sendCode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {
//发送验证码
smsComponent.sendCode(phone,code);
return R.ok();
}
}
@FeignClient("achangmall-third-service")
public interface ThirdPartFeignService {
@GetMapping(value = "/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);
}
<input class="phone" id="phoneNum" maxlength="20" type="text" placeholder="建议使用常用手机">
$(function (){
$("#sendCode").click(function (){
if ($(this).hasClass("disabled")){
}{
var phone = $("#phoneNum").val();
$.get("/sms/sendCode?phone="+phone);
timeoutChangeSytle()
}
});
})
achang-auth-server/pom.xml
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
spring.redis.port=6379
spring.redis.host=192.168.109.101
@Controller
public class LoginController {
@Autowired
private ThirdPartFeignService thirdPartFeignService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/sms/sendCode")
@ResponseBody
public R sendCode(@RequestParam("phone")String phone){
//防止接口幂等性操作
String phoneRedisStr = stringRedisTemplate.opsForValue().get("sms:code:"+phone);
if (!StringUtils.isEmpty(phoneRedisStr)){
//活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码
long currentTime = Long.parseLong(phoneRedisStr.split("_")[1]);
if (System.currentTimeMillis() - currentTime < 60000) {
//60s内不能再发
return R.error("发送频率过多,请稍后重试");
}
}
String code = UUID.randomUUID().toString().substring(0, 5);
String redisStorage = code + "_" + System.currentTimeMillis();
//存入redis,防止同一个手机号在60秒内再次发送验证码
stringRedisTemplate.opsForValue().set("sms:code:"+ phone,
redisStorage, 10, TimeUnit.MINUTES);
thirdPartFeignService.sendCode(phone,code);
return R.ok();
}
}
通过注解可以给前端传递过来的值进行校验,例如:
@Data
public class UserRegisterVo {
@NotEmpty(message = "用户名不能为空")
@Length(min = 6, max = 19, 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;
}
但是这个注解必须配合 @Valid
使用,完成对参数的校验:
/**
*
* TODO: 重定向携带数据:利用session原理,将数据放在session中。
* TODO:只要跳转到下一个页面取出这个数据以后,session里面的数据就会删掉
* TODO:分布下session问题
* RedirectAttributes:重定向也可以保留数据,不会丢失
* 用户注册
* @return
*/
@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.achangmall.com/reg.html";
}
//1、效验验证码
String code = vos.getCode();
//获取存入Redis里的验证码
String redisCode = stringRedisTemplate.opsForValue().get("sms:code:" + vos.getPhone());
if (!StringUtils.isEmpty(redisCode)) {
//截取字符串
if (code.equals(redisCode.split("_")[0])) {
//删除验证码;令牌机制
stringRedisTemplate.delete("sms:code:"+vos.getPhone());
//验证码通过,真正注册,调用远程服务进行注册
R register = memberFeignService.register(vos);
if (register.getCode() == 0) {
//成功
return "redirect:http://auth.achangmall.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.achangmall.com/reg.html";
}
} else {
//效验出错回到注册页面
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.achangmall.com/reg.html";
}
} else {
//效验出错回到注册页面
Map<String, String> errors = new HashMap<>();
errors.put("code","验证码错误");
attributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.achangmall.com/reg.html";
}
}
@FeignClient("achangmall-member")
public interface MemberFeignService {
@PostMapping(value = "/member/member/register")
R register(@RequestBody UserRegisterVo vo);
}
@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);
}
public void checkPhoneUnique(String phone) throws Exception {
Integer phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (phoneCount > 0) {
throw new Exception();
}
}
public void checkUserNameUnique(String userName) throws Exception {
Integer usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
if (usernameCount > 0) {
throw new Exception();
}
}
<select id="getDefaultLevel" resultType="com.achang.achangmall.member.entity.MemberLevelEntity">
SELECT * FROM ums_member_level WHERE default_status = 1
select>
Message Digest algorithm 5,信息摘要算法
压缩性:任意长度的数据,算出的 MD5 值长度都是固定的;
容易计算:从原数据计算出 MD5 值很容易;
抗修改性:对原数据进行任何改动,哪怕只修改 1 个字节,所得到的 MD5 值都有很大区别;
强抗碰撞:想找到两个不同的数据,使它们具有相同的 MD5 值是非常困难的;
不可逆
@Test
void contextLoads() {
String s = DigestUtils.md5Hex("123456");
System.out.println(s);
// 盐值加密
System.out.println(Md5Crypt.md5Crypt("123456".getBytes()));
System.out.println(Md5Crypt.md5Crypt("123456".getBytes(), "$1$qqqqqqqq"));
// Spring 盐值加密
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//$2a$10$GT0TjB5YK5Vx77Y.2N7hkuYZtYAjZjMlE6NWGE2Aar/7pk/Rmhf8S
//$2a$10$cR3lis5HQQsQSSh8/c3L3ujIILXkVYmlw28vLA39xz4mHDN/NBVUi
String encode = bCryptPasswordEncoder.encode("123456");
boolean matches = bCryptPasswordEncoder.matches("123456", "$2a$10$GT0TjB5YK5Vx77Y.2N7hkuYZtYAjZjMlE6NWGE2Aar/7pk/Rmhf8S");
System.out.println(encode + "==>" + matches);
}
@Data
public class UserLoginVo {
private String loginacct;
private String password;
}
@ToString
@Data
public class MemberResponseVo implements Serializable {
private static final long serialVersionUID = 5573669251256409786L;
private Long id;
/**
* 会员等级id
*/
private Long levelId;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 昵称
*/
private String nickname;
/**
* 手机号码
*/
private String mobile;
/**
* 邮箱
*/
private String email;
/**
* 头像
*/
private String header;
/**
* 性别
*/
private Integer gender;
/**
* 生日
*/
private Date birth;
/**
* 所在城市
*/
private String city;
/**
* 职业
*/
private String job;
/**
* 个性签名
*/
private String sign;
/**
* 用户来源
*/
private Integer sourceType;
/**
* 积分
*/
private Integer integration;
/**
* 成长值
*/
private Integer growth;
/**
* 启用状态
*/
private Integer status;
/**
* 注册时间
*/
private Date createTime;
/**
* 社交登录UID
*/
private String socialUid;
/**
* 社交登录TOKEN
*/
private String accessToken;
/**
* 社交登录过期时间
*/
private long expiresIn;
}
@GetMapping(value = "/login.html")
public String loginPage(HttpSession session) {
//从session先取出来用户的信息,判断用户是否已经登录过了
Object attribute = session.getAttribute("loginUser");
//如果用户没登录那就跳转到登录页面
if (attribute == null) {
return "login";
} else {
return "redirect:http://achangmall.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("loginUser",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.achangmall.com/login.html";
}
}
@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();
}
}
@PostMapping(value = "/member/member/login")
R login(@RequestBody UserLoginVo vo);
@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;
}