1.商品上架
1.商品Mapping
创建一个product索引和指定映射关系
PUT product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catalogId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"catalogName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
2.首页
项目的页面视图部署在对应的微服务项目的静态资源中
2.2.1整合thymeleaf
添加thymeleaf的依赖
<!-- thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
将资料中的静态资源放在微服务工程的对应位置,并在配置文件中加入对应的配置
首页页面
如果首页样式乱或者没显示两种解决方案:
1.可以在product服务config包添加如下配置
package com.yxw.gulimall.product.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Simple to Introduction
* className: WebMVCConfig
*
* @author yanxw
* @version 2023/12/14 7:57 AM
*/
@Configuration
public class WebMVCConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
}
}
2.去掉index.html路径里面的static
2.2.2搭建域名访问环境
编辑操作hosts文件
192.168.0.88 gulimall.com
保存成功之后可以直接访问域名,解析到虚拟机的ip
Tips:有科学上网的测试的时候要先关闭科学上网的软件
安装了ES的访问gulimall.com:9200可以看到相应的内容
正式搭建项目的域名访问环境
1.nginx的配置文件
配置nginx
#切换到nginx挂载在本机的配置文件所在的目录
cd /mydata/nginx/conf
#切换到nginx的分配置文件下复制一份 定义gulimall的配置,主配置文件中有include /etc/nginx/conf.d/*.conf;这么一段配置,主配置文件
#会包含conf.d目录下的所有配置
cd /mydata/nginx/conf/conf.d
cp default.conf gulimall.conf 复制一份
#在gulimall.conf配置文件中配置我们的配置
#配置如下配置
niginx的配置文件如下
server {
listen 80;
server_name gulimall.com;
location / {
#服务的IP 192.168.0.112
#服务的端口: 10000
proxy_pass http://192.168.0.112:10000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
重启nginx:
docker restart nginx
2.让nginx代理到网关
在nginx.conf配置文件中加上如下的配置
#gulimall是上游服务器的组名
upstream gulimall{
#网关的地址 注意这里是自己本机的ip,不是服务器ip
server 192.168.0.112:88;
}
修改gulimall.conf中的配置
server {
listen 80;
server_name gulimall.com;
location / {
proxy_pass http://gulimall;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
在网关的配置文件中加上以下的配置
- id: gulimall_host_route
uri: lb://mall-product
predicates:
- Host=**.gulimall.com
这时我们重启网关的时候发现无法访问到相应的页面 是由于nginx访问网关的时候丢了host
这时我们需要加上如下的配置
proxy_set_header Host $host;
3.性能压测
3.1性能监控
jconsole
jvisualvm
我用的是jvisualvm
3.2jvisualvm 能干什么
监控内存泄露,跟踪垃圾回收,执行时内存、cpu 分析,线程分析...
运行:正在运行的
休眠:sleep
等待:wait 驻留:线程池里面的空闲线程 监视:阻塞的线程,正在等待锁
3.3安装插件方便查看 gc
Cmd启动jvisualvm
工具->插件
如果503错误解决:
打开网址https://visualvm.github.io/pluginscenters.html cmd查看自己的jdk版本,找到对应的
复制下面查询出来的链接。并重新设置上即可
4.nginx动静分离
现在服务器nginx目录创建文件夹如下
将mall-product服务下的index文件夹上传到刚才创建的static文件夹下,同时删除mall-product服务下的index文件夹
修改nginx配置
修改nginx后要重启 docker restart nginx
5.SpringBoot整合redis
1.Redis的介绍
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
中文文档:https://www.redis.net.cn/
2.入门
2.1 引入依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
2.2 添加配置类
package com.atguigu.yygh.common.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
import java.time.Duration;
/**
* @author GongChangjiang
* @version 1.0
* @Date 2023/4/14
* @Description
*/
@Configuration
@EnableCaching
public class RedisConfig {
/**
* 自定义key规则
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 设置RedisTemplate规则
*
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 设置CacheManager缓存规则
* 缓存的时间
* 乱码的处理
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
说明:
@EnableCaching:标记注解 @EnableCaching,开启缓存,并配置Redis缓存管理器。@EnableCaching 注释触发后置处理器, 检查每一个Spring bean 的 public 方法是否存在缓存注解。如果找到这样的一个注释, 自动创建一个代理拦截方法调用和处理相应的缓存行为。
2.3 配置文件中加入redis的配置
application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
2.4 使用Spring Cache + Redis执行缓存操作
2.1.2 @Cacheable
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
2.1.2 @CachePut
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
2.1.3 @CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
3.示例
下面的第一个方法是查询数据位list集合
@Cacheable(value = “dict”,keyGenerator = “keyGenerator”) 对方法的结果进行缓存
value属性表示key的前缀
keyGenerator表示key的生成规则,生成规则在配置文件中配置,这里我们使用的是方法的全类名作为key的后缀
第二个方法是添加数据 添加数据会造成数据库中数据的变化 我们要清除缓存
@CacheEvict(value = “dict”,allEntries = true) 清空指定的缓存
value属性表示清空以dict为前缀的所有缓存
allEntries 属性表示是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
@Override
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
public List<Dict> findByParentId(Long parentId) {
LambdaQueryWrapper<Dict> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Dict::getParentId, parentId);
List<Dict> dictList = dictMapper.selectList(queryWrapper);
for (Dict dict : dictList) {
dict.setHasChildren(this.isHasChildren(dict.getId()));
}
return dictList;
}
@CacheEvict(value = "dict",allEntries = true)
public void importDictData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(dictMapper)).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
6.项目中使用Redis
6.1引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
6.2 添加配置
spring:
redis:
host: 192.168.0.88
port: 6379
6.3 测试使用
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void testRedis(){
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("hello","word_"+ UUID.randomUUID().toString());
String hello = ops.get("hello");
System.out.println(hello);
}
lettuce堆外内存溢出bug
产生原因:
1)、springboot2.0以后默认使用 lettuce作为操作redis的客户端,它使用netty进行网络通信
2)、lettuce的bug导致netty堆外内存溢出。netty如果没有指定堆外内存,默认使用Xms的值,可以使用-Dio.netty.maxDirectMemory进行设置
解决方案:由于是lettuce的bug造成,不要直接使用-Dio.netty.maxDirectMemory去调大虚拟机堆外内存,治标不治本。
1)、升级lettuce客户端
2)、切换使用jedis
修改依赖文件,使用jedis作为客户端工具
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
6.4高并发下缓存失效问题
缓存穿透
缓存雪崩
缓存击穿
7.检索服务
7.1整合页面
在nginx里面放入如下文件
7.2配置域名访问
7.3Nginx的配置
server {
listen 80;
server_name *.gulimall.com;
location / {
proxy_set_header Host $host;
proxy_pass http://gulimall;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
重启nginx docker restart nginx
配置本机的host文件
ip写对应自己的服务器ip
网关配置
7.4首页和搜索页相互跳转
修改nginx配置
8.检索DLS测试
按照条件查询
GET gulimall_product/_search
{
"query": {
"match_all": {
}
},
"aggs": {
"brand_agg": {
"terms": {
"field": "brandId",
"size": 10
},
"aggs": {
"brand_name_aggs": {
"terms": {
"field": "brandName",
"size": 10
}
}
}
},
"catalog_agg": {
"terms": {
"field": "catalogId",
"size": 10
}
}
}
}
报错不能加载brandName数据,因为这个数据不支持keyword类型,
解决办法修改映射和迁移数据:
创建索引mall_product
PUT mall_product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "keyword"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword"
},
"saleCount": {
"type": "long"
},
"hasStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catalogId": {
"type": "long"
},
"brandName": {
"type": "keyword"
},
"brandImg": {
"type": "keyword"
},
"catalogName": {
"type": "keyword"
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword"
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
数据迁移:
POST _reindex
{
"source": {
"index": "gulimall_product"
},
"dest": {
"index": "mall_product"
}
}
完整的dsl语句:
GET mall_product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"9"
]
}
},
{
"term": {
"attrs.attrId": "15"
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "15"
}
}
},
{
"terms": {
"attrs.attrValue": [
"系统",
"安卓"
]
}
}
]
}
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 1,
"highlight": {
"fields": {
"skuTitle": {}
},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
},
"aggs": {
"brand_agg": {
"terms": {
"field": "brandId",
"size": 10
},
"aggs": {
"brand_name_agg": {
"terms": {
"field": "brandName",
"size": 10
}
},
"brand_img_agg": {
"terms": {
"field": "brandImg",
"size": 10
}
}
}
},
"catalog_agg": {
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalog_name_agg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"attr_agg": {
"nested": {
"path": "attrs"
},
"aggs": {
"attr_id_agg": {
"terms": {
"field": "attrs.attrId",
"size": 10
},
"aggs": {
"attr_name_agg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
},
"attr_value_agg": {
"terms": {
"field": "attrs.attrValue",
"size": 10
}
}
}
}
}
}
}
}
9.认证服务
1.环境搭建
创建mall-auth-server服务
启动类:
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class MallAuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(MallAuthServerApplication.class, args);
}
}
application.yml
server:
port: 20000
spring:
application:
name: mall-auth-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
将注册和登录hmtl导入进来
2.修改host文件
192.168.0.88 auth.gulimall.com
在服务器创建文件夹
注册
登录
修改login页面
修改reg页面
将login.html修改index.html
重新启动服务访问:http://auth.gulimall.com/
3.登录和注册相互跳转
修改application.yml
thymeleaf:
cache: false
创建LoginController
package com.yxw.gulimall.auth.controller;
import com.alibaba.fastjson.TypeReference;
import com.yxw.common.utils.R;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* Simple to Introduction
* className: LoginController
*
* @author yanxw
* @version 2023/12/31 3:51 PM
*/
@Controller
public class LoginController {
@GetMapping(value = "/login.html")
public String login() {
return "login";
}
@GetMapping(value = "/reg.html")
public String regPage() {
return "reg";
}
}
将index.html修改login.html
修改login.html
<span><a href="http://auth.gulimall.com/reg.html">立即注册</a></span>
修改reg.html
4.验证码
创建GulimallWebConfig类
package com.yxw.gulimall.auth.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Simple to Introduction
* className: GulimallWebConfig
*
* @author yanxw
* @version 2023/12/31 5:22 PM
*/
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {
/**·
* 视图映射:发送一个请求,直接跳转到一个页面
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}
注释LoginController中那两个方法
5.开通短信服务
登录阿里云
测试代码
@Test
public void sendSms() {
String host = "https://dfsns.market.alicloudapi.com";
String path = "/data/send_sms";
String method = "POST";
String appcode = "6e568a4ef5b788899970e1028c872";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
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("content", "code:666666");
bodys.put("template_id", "CST_ptdie100"); //该模板为调试接口专用,短信下发有受限制,调试成功后请联系客服报备专属模板
bodys.put("phone_number", "13263275555");
// bodys.put("skin", "1");
// bodys.put("sign", "1");
//JDK 1.8示例代码请在这里下载: http://code.fegine.com/Tools.zip
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 或者直接下载:
* http://code.fegine.com/HttpUtils.zip
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
* 相关jar包(非pom)直接下载:
* http://code.fegine.com/aliyun-jar.zip
*/
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
//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();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。