springboot,dubbo,nacos,mysql,elasticsearch(商品需放在里面),spark,redis,rocketmq,sharding-jdbc等。
内部模块对外提供接口是buyer-api,调用内部的dubbo
service服务(注意api是可调用多个service服务)。
注册中心用的nacos,注册内容为ip:port/接口名称/方法名称。
前端跨域地址可写在properties中,使用注解
@Value("${buyer.url}")获取
private String buyerUrl;
只有在这个配置下的才会被扫描到(需加dubbo的服务注解@DubboService),并提供相应服务通常写在service模块中。
在bin目录下cmd中输入:单机启动startup.cmd -m standalone
地址为:http://localhost:8848/nacos/index.html#/login
@Configurationpublic class WebMVCConfig implements WebMvcConfigurer {
@Value("${buyer.url}")
private String buyerUrl;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins(buyerUrl);
}
}
需实现WebMvcConfigurer接口,写上允许跨域的url请求地址。
1.服务名 要唯一,如果服务名一样 会组成集群
2.组名是 服务在不在一个分组内
3.如果不在 不能进行互通
分组名的的名字在application中编写为dubbo.registry.group=dubbo_buyer_service所设置。
服务名由@DubboService所备注的接口名字提供。
额外
spring.application.name=mall-service-provider
服务启动名称如有一样多的服务 那么nacos的实例数就会增加
也就是说服务消费方调用的时候,nacos可以根据负载均衡策略 来进行提供服务提供者的地址
系统的承载能力就强
为Swagger增强版本,配置如下
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {
@Bean(value = "defaultApi2")
public Docket defaultApi2() {
Docket docket=new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("码神商城API列表")
.description("码神商城 rest API列表")
.termsOfServiceUrl("https://mall.mszlu.com/")
.contact(new Contact("码神之路","","[email protected]"))
.version("1.0")
.build())
//分组名称
.groupName("3.X版本")
.select()
//这里指定Controller扫描包路径 .apis(RequestHandlerSelectors.basePackage("com.mszlu.shop.buyer.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
@ControllerAdvice
@Slf4j
public class BusinessExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result doException(Exception e){
e.printStackTrace();
log.error("出异常了:{}",e.getMessage());
return Result.fail();
}
}
@ControllerAdvice注解 ,增强Controller。
可以实现三个方面的功能:
全局异常处理
全局数据绑定
全局数据预处理
SpringMVC 提供的功能,可在Spring Boot 中可以直接使用。
一.
二.
这种分类数据一般都是固定的,一般不用每次都请求后端。
第一次请求后,就把子节点的数据加在父节点里面list并获取所有的树形父子结构数据并保存到浏览器中。
opsForZSet().reverseRangeWithScores(HOT_WORDS_REDIS_KEY, 0, 2);
逆序获取数据,相当于先拿热度(分数)最高的数据放在前面,并控制获取元素数量大小。
使用mybatisplus的分页插件,以page类型返回数据,Result<Page<ArticleVO>>
实战也没做过,得new一个page对象泛型为实体类,在设置当前页和页面大小
public Page(long current, long size) {
this(current, size, 0L);
}
private static final long serialVersionUID = 8545996863226528798L;
protected List<T> records;
protected long total;
protected long size;
protected long current;
protected List<OrderItem> orders;
protected boolean optimizeCountSql;
protected boolean searchCount;
protected String countId;
protected Long maxLimit;
并将所取的实体类数据存在records中。
代码思路,前端传来一个uuid(来证明未登录用户的一个唯一标识的sessionId),并再传一个正登录标识之类的。
数据库存两种资源,一个是RESOURCE原图资源,另一个SLIDER滑块的原图资源。
随机操作两种资源的其中一个,使用工具类SliderImageUtil.pictureTemplatesCut(放入其中一个SLIDER的url,再放入RESOURCE的url),两种url都可访问。
切割好的对象数据如下图
滑块base64和原图背景的模块base64等等,什么是base64?
图片的 base64 编码就是可以将一副图片数据编码成一串字符串,使用该字符串代替图像地址。
优点:减少请求数量(减轻服务器压力)
缺点:图片体积会更大(文件请求速度更慢)
所以一般8-12kb以下的图片适合用base64。
eg:生成的两个base64可用转码工具转成原图片。
业务逻辑实现继续,再存入uuid为键,再将切割出来的数据x坐标(抠图坐标)放入值中,形成键值对。
一个用户对应一个x坐标,并存过期时间。
再将切割好的图片的x坐标设置为0,再放回给前端,让滑动的图片一开始就最左边开始滑动。
SliderImageCut sliderImageCut = SliderImageUtil.pictureTemplatesCut(
getInputStream(sliderUrl), getInputStream(resourceUrl));
//如果不设置为0 滑块直接就匹配
//生成验证参数 120可以验证 无需手动清除,120秒有效时间自动清除
redisTemplate.opsForValue().set(verificationRedisKey(uuid, verificationEnums), String.valueOf(sliderImageCut.getRandomX()), 120, TimeUnit.SECONDS);
sliderImageCut.setRandomX(0);
代码思路传三个参数,登录标识,该登录的uuid和滑动的坐标,去redis寻找该登录用户的uuid所对应的x坐标值,
和传来的滑动的坐标x作比较。
//允许有误差
boolean result = Math.abs(Integer.parseInt(x) - xPos) < 3;
//滑块验证成功之后 随后的一段时间 如果有操作,认为是验证通过的 不需要重复进行滑块验证 redisTemplate.opsForValue().set("VERIFICATION_IMAGE_RESULT_"+verificationEnums.name()+uuid, "true", 120,TimeUnit.SECONDS);
使用了token令牌的登录认证方式。
单点登录一般使用jwt技术实现,jwt是一个加密技术,用于生成token。
在进行登录认证的时候,buyer-api使用security进行认证,调用sso服务,进行登录认证。
JWT一般由三部分组成,分别是头信息、有效载荷、签名。
AuthUser authUser = new AuthUser(member.getUsername(),String.valueOf(member.getId()),member.getNickName(), UserEnums.MEMBER);
String accessToken = TokenUtils.createToken(member.getUsername(), authUser, 7 * 24 * 60L);
jwt有好多种加密算法,一般默认为hash256
项目中用了Base64.decodeBase64。
该电商项目中使用的工具类,放了四种
//jwt私有声明
//JWT的主体
//失效时间 当前时间+过期分钟
//签名算法和密钥
在这一步设置token的过期时间是为了后面解析的时候,如果过期直接就拒绝,不让它在访问Redis耗费时间。
过滤器与拦截器的区别:拦截器是AOP思想的具体应用。
过滤器:
servlet规范中的一部分,任何java web工程都可以使用。
在url-pattern中配置了/*之后,可以对所有要访问的资源进行拦截。
拦截器:
拦截器是SpringMVC框架自己的,只要使用了SpringMVC框架的工程
才能使用,拦截器只会拦截访问的控制器方法,如果访问的是jsp/html/css/image/js是不会进行拦截的。
最常用的是登录拦截、或是权限校验、或是防重复提交
想要自定义拦截器,就必须实现HandlerInterceptor接口。
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
项目中的token加密那部分,使用的是对称加密,加密和解密为同一个。
并对公钥与私钥的概念做一个笔记。
1.密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥。
2.密钥分为两种:对称密钥与非对称密钥。
3.对称密钥加密,又称私钥加密或会话密钥加密算法,即信息的发送方和接收方用同一个密钥去加密和解密数据。它的最大优势是加/解密速度快,适合于对大数据量进行加密,但密钥管理困难。
4.非对称密钥加密系统,又称公钥密钥加密。它需要使用不同的密钥来分别完成加密和解密操作,一个公开发布,即公开密钥,另一个由用户自己秘密保存,即私用密钥。
5.对于普通的对称密码学,加密运算与解密运算使用同样的密钥。通常,使用的对称加密算法比较简便高效,密钥简短,破译极其困难,由于系统的保密性主要取决于密钥的安全性,所以,在公开的计算机网络上安全地传送和保管密钥是一个严峻的问题。
项目的Security安全认证貌似都是以继承WebSecurityConfigurerAdapter适配器为基础, 在配置的时候,需要我们自己写个配置类去继承他,然后编写自己所特殊需要的配置
比如无需进行登录就可以访问的连接,和允许跨域等。
并添加我们需要的业务,JWT认证过滤器。
至此在前端发起登录时(需要过滤操作的)我们重写BasicAuthenticationFilter结果过滤器FilterInternal方法,使用(HttpServletRequest)request.getHeader方法获取请求头token(JWT),如果存在的话并对该token解析,利用的同一个密钥看是否"正常",并将解析出来的json转换成用户对象,并用这个传来的token往redis查一遍里面有没有这个token(是上一步返回后并保存到redis里)防止伪造。如果有则认定该token具有权限,并进行业务操作将用户对象返回,存到当前SecurityContextHolder.getContext().setAuthentication(authentication)中。
该步骤是为了后面业务层获取当前用户。
Spring Security使用一个Authentication对象来描述当前用户的相关信息。SecurityContextHolder中持有的是当前用户的SecurityContext,而SecurityContext持有的是代表当前用户相关信息的Authentication的引用。这个Authentication对象不需要我们自己去创建,在与系统交互的过程中,Spring Security会自动为我们创建相应的Authentication对象,然后赋值给当前的SecurityContext。
后续需在业务实现层使用AuthUser currentUser = UserContext.getCurrentUser()(封装了SecurityContextHolder.getContext().getAuthentication())来获取用户的一些字段值等,去数据库配对,返回用户具体信息给前端。
1、检查参数是否合法
2、根据用户名或者密码去user准备去表中查询(参考了博客项目)
这里两种方式:
传来的明文密码md5下并加盐(默认该用户注册的时候就这样做的,并已经存到数据库里了),加完后带用户名和密码去数据库查如果没查到就是账户or密码输入可能有误。
另一种用的security的提供的bcrypt的加密方式
//底层会随机的给密码加盐
BCryptPasswordEncoder encoder =new BCryptPasswordEncoder();
String encode = new BCryptPasswordEncoder().encode("123456");
boolean matches = new BCryptPasswordEncoder().matches("123456", encode);
System.out.println(matches);
true
先用户名去查数据库,查到该用户的数据中的密码(相当于encode),
然后用security的提供的技术BCryptPasswordEncoder().matches方法和传来的明文123456作比较它两是否正确。
3、如果不存在,登录失败
4、如果存在,使用jwt生成token返回给前端
5、token放入redis当中,redis token:user信息 设置过期时间 * (登录认证的时候,先认证token字符串是否合法,去redis认证是否存在)
退出登录的话就是把token传来,删除在redis里面的token。
SPU : 标准化产品单元。 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
SKU : 库存量单位。 是库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU是物理上不可分割的最小存货单元。在服装、鞋类商品中使用最多最普遍。
简单来说苹果6是一个spu,苹果6加上要选的颜色和内存大小就是一个具体的sku。
目前电商项目中添加商品的接口采用pc端方式,没有登录是无法添加购物车的。与霍哥沟通是目前市场上趋势pc端将不再未登录的情况下添加购物车,而app端也就是手机端可以未登录就添加购物车,app端可以用手机的mac地址,序列号,手机型号等生成唯一用户Id。