环境搭建
mvn依赖在文章最下面
application.properties
spring.application.name=gulimall-auth-server
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
server.port=20000
spring.thymeleaf.cache=false
spring.redis.host=192.168.56.10
spring.redis.port=6379
启动类
package com.atlinxi.gulimall.authserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallAuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallAuthServerApplication.class, args);
}
}
hosts和nginx
192.168.56.10 gulimall.com
192.168.56.10 search.gulimall.com
192.168.56.10 item.gulimall.com
192.168.56.10 auth.gulimall.com
nginx下的html文件夹创建login和reg两个文件夹,放登录和注册的静态资源。
验证码
config
package com.atlinxi.gulimall.authserver.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 发送一个请求直接跳转到一个页面,我们又不想controller中写这些空方法
* SpringMvc viewcontroller:将请求和页面映射过来
* @return
*/
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
/**
* 直接跳转(渲染一个页面),无需写自定义的controller
*
*
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
/**
* * @GetMapping("/login.html")
* * public String loginPage(){
* *
* * return "login";
* * }
*/
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}
html
<a id="sendCode">发送验证码</a>
<input class="phone" maxlength="20" type="text" placeholder="建议使用常用手机">
function timeOutChangeStyle() {
var str = '10s后再次发送验证码';
var num = 60
countDown = setInterval(function () {
$("#sendCode").attr("class","disabled");
num--;
if (num==0){
$("#sendCode").text('发送验证码');
clearInterval(countDown);
$("#sendCode").attr("class","");
}else {
$("#sendCode").text(num + 's后再次发送')
}
},1000);
}
$("#sendCode").click(function () {
// 2. 倒计时
if (!$(this).hasClass("disabled")){
phoneNum = $(".phone").val()
// 1. 给指定手机号发送验证码
// / 代表当前项目路径
$.get({
url: "/sms/sendCode",
data:{
"phone": phoneNum},
dataType: "json",
success:function(data){
console.log("-------------------")
console.log(data)
if (data.code != 0){
alert(data.msg);
}
}
})
timeOutChangeStyle();
}
});
阿里云发送短信 - 这段代码是第三方服务的
该接口不直接提供页面调用,而是提供各微服务调用。
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-third-party</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-third-party</name>
<description>第三方服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 这个版本和springcloudalibaba的版本不统一,不知道以后会不会出什么问题-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
spring元数据处理器,加了就有提示了,不加也没任何问题
optional 依赖不会被传递
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.23</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.2.8</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-console</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-util</artifactId>
<version>0.2.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea</artifactId>
<version>1.1.14</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<!-- 之前用的一直是这个版本,springcloudoss依赖就是无法导入,换成下面那个就好了-->
<version>2021.1</version>
<!-- 这是老师用的版本,导入服务就启动不了 了-->
<!-- <version>2.1.0.RELEASE</version>-->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件
server:
port: 30000
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
alicloud:
access-key: 阿里云找自己的
secret-key: 阿里云找自己的
oss:
endpoint: oss-cn-beijing.aliyuncs.com
bucket: gulimall-linxi
application:
name: gulimall-third-party
sms:
endPoint: dysmsapi.aliyuncs.com
signName: 阿里云找自己的
templateCode: 阿里云找自己的
accessKeyId: 阿里云找自己的
accessKeySecret: 阿里云找自己的
component
package com.atlinxi.gulimall.thirdparty.component;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.dysmsapi20170525.models.SendSmsResponseBody;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import lombok.Data;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 下面的方式调用的话,不安全,我感觉阿里官方的意思是
* 需要我们加一个自己的凭证(token),而且需要我们自己维护
* 不知道老师会不会做这一步,会做的话再说吧,
*
*/
@Data
@Component
@ConfigurationProperties(prefix = "sms")
public class SmsComponent implements InitializingBean {
private static String endpoint;
private String endPoint;
private String signName;
private String templateCode;
private String accessKeyId;
private String accessKeySecret;
public static Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
Config config = new Config()
// 必填,您的 AccessKey ID
.setAccessKeyId(accessKeyId)
// 必填,您的 AccessKey Secret
.setAccessKeySecret(accessKeySecret);
// 访问的域名,下面的也不清楚是不是固定的,
// 反正换成hangzhou这个是报 InvalidVersion
// config.endpoint = "ecs-cn-hangzhou.aliyuncs.com";
// config.endpoint = "dysmsapi.aliyuncs.com";
config.endpoint = endpoint;
return new Client(config);
}
public void sendSmsCode(String phone,String code) throws Exception {
// 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html
Client client = SmsComponent.createClient(accessKeyId, accessKeySecret);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers(phone)
.setSignName(signName)
.setTemplateCode(templateCode)
.setTemplateParam("{\"code\":" + code + "}");
RuntimeOptions runtimeOptions = new RuntimeOptions();
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtimeOptions);
SendSmsResponseBody respBody = sendSmsResponse.getBody();
String respMessage = respBody.getMessage();
String respCode = respBody.getCode();
String respBizId = respBody.getBizId();
String respRequestId = respBody.getRequestId();
System.out.println(respMessage + "=" + respCode + "=" + respBizId + "=" + respRequestId);
// com.aliyun.teaconsole.Client.log(Common.toJSONString(sendSmsResponse));
// try {
// RuntimeOptions runtimeOptions = new RuntimeOptions();
// // 复制代码运行请自行打印 API 的返回值
// SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtimeOptions);
//
// System.out.println(sendSmsResponse);
//
// } catch (TeaException error) {
// // 如有需要,请打印 error
// Common.assertAsString(error.message);
// } catch (Exception _error) {
// TeaException error = new TeaException(_error.getMessage(), _error);
// // 如有需要,请打印 error
// Common.assertAsString(error.message);
// }
}
/**
*
* @ConfigurationProperties 不能为static成员变量注入配置文件中的值
*
* InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性初始化后的处理方法,
* 它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
SmsComponent.endpoint = this.endPoint;
}
}
controller
package com.atlinxi.gulimall.thirdparty.controller;
import com.atlinxi.common.utils.R;
import com.atlinxi.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) throws Exception {
smsComponent.sendSmsCode(phone,code);
return R.ok();
}
}
认证服务
controller
package com.atlinxi.gulimall.authserver.controller;
import com.alibaba.fastjson.TypeReference;
import com.atlinxi.common.constant.AuthServerConstant;
import com.atlinxi.common.exception.BizCodeEnume;
import com.atlinxi.common.utils.R;
import com.atlinxi.gulimall.authserver.feign.MemberFeignService;
import com.atlinxi.gulimall.authserver.feign.ThirdPartFeignService;
import com.atlinxi.gulimall.authserver.vo.UserRegistVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Controller
public class LoginController {
@Autowired
ThirdPartFeignService thirdPartFeignService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private MemberFeignService memberFeignService;
@ResponseBody
@GetMapping("/sms/sendCode")
public R sendCode(@RequestParam("phone") String phone) throws Exception {
// todo 1. 接口防刷(浏览器f12可以看到后端请求地址)
String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
if (!StringUtils.isEmpty(redisCode)) {
long l = Long.parseLong(redisCode.split("_")[1]);
if (System.currentTimeMillis() - l < 60 * 1000) {
// 60s内不能再发
return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(), BizCodeEnume.SMS_CODE_EXCEPTION.getMessage());
}
}
String code = UUID.randomUUID().toString().substring(0, 5) + "_" + System.currentTimeMillis();
// 2. 验证码的再次校验,存到redis,因为不是永久的
stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone, code, 10, TimeUnit.MINUTES);
thirdPartFeignService.sendCode(phone, code.split("_")[0]);
return R.ok();
}
/**
* 这里跳转是reg.html点击跳转的,不是浏览器直接访问的,所以需要重定向
* <p>
* BindingResult 校验的结果
*
* @param vo
* @param result
* @return
*/
@PostMapping("/regist")
public String regist(@Valid UserRegistVo vo, BindingResult result, /**Model model*/RedirectAttributes redirectAttributes) {
// 校验出错,转发到注册页
if (result.hasErrors()) {
// result.getFieldErrors().stream().map(fieldError -> {
// String field = fieldError.getField();
// String defaultMessage = fieldError.getDefaultMessage();
// errors.put(field,defaultMessage);
Map<String, String> errors = new HashMap<>();
try {
errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
}catch (IllegalStateException e){
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
String field = fieldError.getField();
String defaultMessage = fieldError.getDefaultMessage();
errors.put(field,defaultMessage);
}
}
// 例如 code 验证码必须提交
// model.addAttribute("errors", errors);
// addFlashAttribute 代表里面的属性只需要取一次
redirectAttributes.addFlashAttribute("errors",errors);
// Request method 'POST' not supported
// 用户注册 -> /regist(post) -> 数据校验发现有错误
// -> 转发 /reg.html(GulimallWebConfig路径映射默认都是GET方式访问的)
// 转发就是将原请求原封不动转给下一个人,然而下一个人要求的请求方式是GET方式,就不支持POST了
// reg 是拼接 templates .html
// forward和 redirect是视图解析器不拼接,通过GulimallWebConfig映射找到页面
// return "forward:/reg.html";
// return "reg"; 是一个转发的方式,浏览器的地址不会改变,再刷新一次,表单又会被提交一次
// 所以我们选择重定向的方式
// 请求转发时,model是在请求域中的,我们可以获取到
// 重定向获取不到,使用RedirectAttributes,作用就是重定向时携带参数
// RedirectAttributes 利用session原理,将数据放在session中,
// 只要跳到下一个页面取出这个数据以后,session里面的数据就会删掉
// 例如错误信息跳转过去后会显示,但是再刷新一次浏览器就不会显示了
// todo 分布式下的session问题
// 这个和上面那些注释没关系,不要误会
return "redirect:http://auth.gulimall.com/reg.html";
}
// 真正注册,调用远程服务进行注册
// 1. 校验验证码
String code = vo.getCode();
String phone = vo.getPhone();
String s = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
if (!StringUtils.isEmpty(s)){
if (code.equals(s.split("_")[0])){
// 验证码通过
// 删除验证码
stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
// 真正注册。远程调用会员服务进行注册
R r = memberFeignService.regist(vo);
// code为0是成功
if (r.getCode() == 0){
// 成功
return "redirect:http://auth.gulimall.com/login.html";
}else {
Map<String,String> errors = new HashMap<>();
errors.put("msg",r.getData(new TypeReference<String>(){
}));
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}else {
Map<String,String> errors = new HashMap<>();
errors.put("code","验证码错误");
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}else {
Map<String,String> errors = new HashMap<>();
errors.put("code","验证码错误");
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
// 注册成功返回到首页
// return "redirect:http://auth.gulimall.com/login.html";
// 因为我们在GulimallWebConfig映射了登录地址
// 所以可以这么写,/ 代表项目路径
// 但是这么写的话,它就是一个不知道什么ip+请求路径了,不是我们的域名
// return "redirect:/login.html";
}
}
feign
package com.atlinxi.gulimall.authserver.feign;
import com.atlinxi.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("gulimall-third-party")
public interface ThirdPartFeignService {
@GetMapping("/sms/sendcode")
public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) throws Exception;
}
vo
package com.atlinxi.gulimall.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;
// 以1开始,第二位3-9,剩下的9位0-9
@NotEmpty(message = "手机号必须提交")
@Pattern(regexp = "^[1][3-9][0-9]{9}$",message = "手机号格式不正确")
private String phone;
@NotEmpty(message = "验证码必须提交")
private String code;
}
common服务
package com.atlinxi.common.exception;
/* * 错误码和错误信息定义类 *
* 1. 错误码定义规则为 5 为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知 异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式 *
*
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 002: 短信验证码频率太高
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* 15:用户(会员)
*
*
**/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败"),
SMS_CODE_EXCEPTION(10002,"短信验证码获取频率太高,稍后再试"),
PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
USER_EXIST_EXCEPTION(15001,"用户已存在"),
PHONE_EXIST_EXCEPTION(15002,"手机号已存在"),
LOGINACCT_PASSWORD_INVALID_EXCEPTION(15003,"账号或密码错误");
private int code;
private String message;
BizCodeEnume(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
package com.atlinxi.common.constant;
public class AuthServerConstant {
public static final String SMS_CODE_CACHE_PREFIX = "sms:code:";
}
异常机制
之前service在完成业务的处理后,总是会返回正确或者错误的信息给到controller,
用户注册,我们需要校验用户名或者手机号是否唯一,只要有一个不唯一,则不允许注册。
在这里我们使用异常机制来处理这个问题,一旦校验失败则直接向controller抛出异常。
自定义异常
package com.atlinxi.gulimall.member.exception;
public class PhoneExistException extends RuntimeException {
public PhoneExistException() {
super("手机号存在");
}
}
package com.atlinxi.gulimall.member.exception;
public class UsernameExistException extends RuntimeException {
public UsernameExistException() {
super("用户名存在");
}
}
service
package com.atlinxi.gulimall.member.service.impl;
import com.atlinxi.gulimall.member.dao.MemberLevelDao;
import com.atlinxi.gulimall.member.entity.MemberLevelEntity;
import com.atlinxi.gulimall.member.exception.PhoneExistException;
import com.atlinxi.gulimall.member.exception.UsernameExistException;
import com.atlinxi.gulimall.member.vo.MemberRegistVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atlinxi.common.utils.PageUtils;
import com.atlinxi.common.utils.Query;
import com.atlinxi.gulimall.member.dao.MemberDao;
import com.atlinxi.gulimall.member.entity.MemberEntity;
import com.atlinxi.gulimall.member.service.MemberService;
@Override
public void regist(MemberRegistVo memberRegistVo) {
MemberEntity entity = new MemberEntity();
// 检查用户名或手机号是否唯一。为了让controller能感知异常,异常机制。
checkPhoneUnique(memberRegistVo.getPhone());
checkUsernameUnique(memberRegistVo.getUserName());
}
// 接口也同样需要声明异常
@Override
public void checkPhoneUnique(String phone) throws PhoneExistException{
Integer mobile = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
if (mobile>0){
throw new PhoneExistException();
}
}
@Override
public void checkUsernameUnique(String username) throws UsernameExistException {
Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", username));
if (count>0){
throw new UsernameExistException();
}
}
controller
@PostMapping("/regist")
public R regist(@RequestBody MemberRegistVo memberRegistVo){
try {
memberService.regist(memberRegistVo);
} catch (PhoneExistException e) {
return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnume.PHONE_EXIST_EXCEPTION.getMessage());
}catch(UsernameExistException e){
return R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(), BizCodeEnume.USER_EXIST_EXCEPTION.getMessage());
}
return R.ok();
}
common枚举
package com.atlinxi.common.exception;
/* * 错误码和错误信息定义类 *
* 1. 错误码定义规则为 5 为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知 异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式 *
*
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 002: 短信验证码频率太高
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
* 15:用户(会员)
*
*
**/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败"),
SMS_CODE_EXCEPTION(10002,"短信验证码获取频率太高,稍后再试"),
PRODUCT_UP_EXCEPTION(11000,"商品上架异常"),
USER_EXIST_EXCEPTION(15001,"用户已存在"),
PHONE_EXIST_EXCEPTION(15002,"手机号已存在");
private int code;
private String message;
BizCodeEnume(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
密码加密
如果非法人员通过爆破我们的数据库,得到了我们的账号密码,所以肯定不能存明文
密文分两种,可逆和不可逆
- 可逆:通过密文可以推算出它原来的明文,假设我知道它的加密算法
- 不可逆:我即使知道它的算法和密文,也推断不出明文
密码存为不可逆的才合理。
MD5盐值加密
严格意义上讲,MD5不算一个加密算法,是一个信息摘要算法
例如有一大段文本,MD5可以根据文本特征值得出一个固定长度的MD5值,而且这个文本长度里面只要有一个字节发生了变化,MD5就会发生变化,所以它只是一个信息摘要算法。
- MD5(原始数据一样,算出的MD5值一定一样)
- Message Digest algorithm 5,信息摘要算法
- 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
- 容易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
- 不可逆
- Message Digest algorithm 5,信息摘要算法
- 加盐:
- 通过生成随机数与MD5生成字符串进行组合
- 数据库同时存储MD5值与salt值。验证正确性时使用salt进行MD5即可
MD5为什么不可逆?
因为MD5是一个消息摘要,它会损失部分原数据,所以我们不能通过打散的原数据计算得到的MD5值推断出原数据。
为什么不能直接使用MD5来加密?
基于抗修改性
得出一样的原始数据加密的MD5值一定一样
,就可以做一张彩虹表
(把加密好的MD5值和原始数据做映射),进行暴力破解,为了防止暴力破解,不能直接使用MD5来对密码进行加密。
网上有很多MD5解密的网站,但是只能解密一些简单的,例如123456
,稍微复杂一些就不行了,例如asdsdgfdgdf
,所以MD5解密是伪概念
。
如果非抬杠,就要把世界上所有的数据都用MD5加密一遍存在数据库里用于解密,百度说的理论时间是几万年。。。。。。
MD5的应用场景
百度网盘的秒传功能,上传的时候先计算文件的MD5值,在整个百度网盘的数据库里进行匹配,如果有相同的MD5值,就可以直接拿过来用了。
简单测试
package com.atlinxi.gulimall.member;
import org.apache.commons.codec.digest.Md5Crypt;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//@SpringBootTest
class GulimallMemberApplicationTests {
@Test
void contextLoads() {
// MD5加密
// e10adc3949ba59abbe56e057f20f883e
String s = DigestUtils.md5Hex("123456");
System.out.println(s);
// MD5 盐值加密
// $1$12345678$a4ge4d5iJ5vwvbFS88TEN0
// 如果不给盐的话,md5Crypt会自动生成
// $1$ + 8位字符,固定写法
String s2 = Md5Crypt.md5Crypt("123456".getBytes(),"$1$12345678");
System.out.println(s2);
// 我们使用spring提供的密码加密器,来对密码进行加密和验证
// 对盐值的操作进行了封装,无需我们在数据库专门定义一个盐值字段
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 两次加密的格式都不一样
// $2a$10$15iQb8noKWeEEBld0M6J5O7Ovjr.IsIGrVKm4ucZLGEPW5/E15Pky
// $2a$10$l/GX4cIoDVl9S2SC5z.YmuvaBfbRBtLgNiRysh.7Zkz.J36LxlGPu
String encode = bCryptPasswordEncoder.encode("123456");
// true
boolean matches = bCryptPasswordEncoder
.matches("123456",
"$2a$10$15iQb8noKWeEEBld0M6J5O7Ovjr.IsIGrVKm4ucZLGEPW5/E15Pky");
// true
boolean matches2 = bCryptPasswordEncoder
.matches("123456",
"$2a$10$l/GX4cIoDVl9S2SC5z.YmuvaBfbRBtLgNiRysh.7Zkz.J36LxlGPu");
System.out.println(encode);
System.out.println(matches);
System.out.println(matches2);
}
}
普通登录
普通登录是相较于OAuth2.0和单点登录来说的
vo
package com.atlinxi.gulimall.authserver.vo;
import lombok.Data;
@Data
public class UserLoginVo {
private String loginacct;
private String password;
}
package com.atlinxi.gulimall.member.vo;
import lombok.Data;
@Data
public class MemberLoginVo {
private String loginacct;
private String password;
}
controller
package com.atlinxi.gulimall.authserver.controller;
import com.alibaba.fastjson.TypeReference;
import com.atlinxi.common.constant.AuthServerConstant;
import com.atlinxi.common.exception.BizCodeEnume;
import com.atlinxi.common.utils.R;
import com.atlinxi.gulimall.authserver.feign.MemberFeignService;
import com.atlinxi.gulimall.authserver.feign.ThirdPartFeignService;
import com.atlinxi.gulimall.authserver.vo.UserLoginVo;
import com.atlinxi.gulimall.authserver.vo.UserRegistVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Controller
public class LoginController {
@PostMapping("/login")
public String login(UserLoginVo vo,RedirectAttributes redirectAttributes){
R login = memberFeignService.login(vo);
if (login.getCode()==0){
// 成功
return "redirect:http://gulimall.com";
}else {
Map<String,String> errors = new HashMap<>();
errors.put("msg",login.getData("msg",new TypeReference<String>(){
}));
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/login.html";
}
}
}
package com.atlinxi.gulimall.member.controller;
import java.util.Arrays;
import java.util.Map;
import com.atlinxi.common.exception.BizCodeEnume;
import com.atlinxi.gulimall.member.exception.PhoneExistException;
import com.atlinxi.gulimall.member.exception.UsernameExistException;
import com.atlinxi.gulimall.member.feign.CouponFeignService;
import com.atlinxi.gulimall.member.vo.MemberLoginVo;
import com.atlinxi.gulimall.member.vo.MemberRegistVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import com.atlinxi.gulimall.member.entity.MemberEntity;
import com.atlinxi.gulimall.member.service.MemberService;
import com.atlinxi.common.utils.PageUtils;
import com.atlinxi.common.utils.R;
/**
* 会员
*
* @author linxi
* @email [email protected]
* @date 2022-10-13 17:20:57
*/
@RefreshScope
@RestController
@RequestMapping("member/member")
public class MemberController {
@PostMapping("/login")
public R login(@RequestBody MemberLoginVo vo){
MemberEntity memberEntity = memberService.login(vo);
if (memberEntity!=null){
return R.ok();
}else {
return R.error(BizCodeEnume.LOGINACCT_PASSWORD_INVALID_EXCEPTION.getCode(),
BizCodeEnume.LOGINACCT_PASSWORD_INVALID_EXCEPTION.getMessage());
}
}
service
package com.atlinxi.gulimall.member.service.impl;
import com.atlinxi.gulimall.member.dao.MemberLevelDao;
import com.atlinxi.gulimall.member.entity.MemberLevelEntity;
import com.atlinxi.gulimall.member.exception.PhoneExistException;
import com.atlinxi.gulimall.member.exception.UsernameExistException;
import com.atlinxi.gulimall.member.vo.MemberLoginVo;
import com.atlinxi.gulimall.member.vo.MemberRegistVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Map;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atlinxi.common.utils.PageUtils;
import com.atlinxi.common.utils.Query;
import com.atlinxi.gulimall.member.dao.MemberDao;
import com.atlinxi.gulimall.member.entity.MemberEntity;
import com.atlinxi.gulimall.member.service.MemberService;
@Service("memberService")
public class MemberServiceImpl extends ServiceImpl<MemberDao, MemberEntity> implements MemberService {
@Override
public MemberEntity login(MemberLoginVo vo) {
String loginacct = vo.getLoginacct();
String password = vo.getPassword();
MemberEntity entity = baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("username", loginacct)
.or().eq("mobile", loginacct));
if (entity==null){
// 登录失败
return null;
}else {
String passwordDb = entity.getPassword();
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
boolean matches = passwordEncoder.matches(password, passwordDb);
if (matches){
return entity;
}else {
return null;
}
}
}
html
<form action="/login" method="post">
<div style="color: red" th:text="${errors!=null && (#maps.containsKey(errors,'msg'))?errors.msg:''}"></div>
<ul>
<li class="top_1">
<img src="/static/login/JD_img/user_03.png" class="err_img1" />
<input type="text" name="loginacct" placeholder=" 邮箱/用户名/已验证手机" class="user" />
</li>
<li>
<img src="/static/login/JD_img/user_06.png" class="err_img2" />
<input type="password" name="password" placeholder=" 密码" class="password" />
</li>
<li class="bri">
<a href="/static/login/index.html">忘记密码</a>
</li>
<li class="ent"><button type="submit" class="btn2">登 录</a></button></li>
</ul>
</form>
mvn依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-third-party</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-third-party</name>
<description>第三方服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 这个版本和springcloudalibaba的版本不统一,不知道以后会不会出什么问题-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
spring元数据处理器,加了就有提示了,不加也没任何问题
optional 依赖不会被传递
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.23</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.2.8</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-console</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-util</artifactId>
<version>0.2.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea</artifactId>
<version>1.1.14</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<!-- 之前用的一直是这个版本,springcloudoss依赖就是无法导入,换成下面那个就好了-->
<version>2021.1</version>
<!-- 这是老师用的版本,导入服务就启动不了 了-->
<!-- <version>2.1.0.RELEASE</version>-->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-member</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-member</name>
<description>谷粒商城-会员服务</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 由于SpringCloud Feign高版本不使用Ribbon而是使用spring-cloud-loadbalancer,
所以需要引用spring-cloud-loadbalancer或者降版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-auth-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall-auth-server</name>
<description>认证中心(社交登录、OAuth2.0、单点登录)</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.4</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 由于SpringCloud Feign高版本不使用Ribbon而是使用spring-cloud-loadbalancer,
所以需要引用spring-cloud-loadbalancer或者降版本-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.atlinxi.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
怡婷很悲愤,她知道 的比世界上任何一个小孩都来得多,但是她永远不能得知一个自知貌美的女子走在路上低眉敛首的心情。
房思琪的初恋乐园
林奕含