分布式笔记
这是个人分布式项目的笔记,有点乱
1.准备工作
1.1Pom文件
父:
<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>
<groupId>com.jt</groupId>
<artifactId>jt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<!--指定插件版本 -->
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!--跳过测试类打包 -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<!--在webjar包的内部关联整合了所有的springMVC的jar包信息. 所以只需要引入一个jar包,则可以关联整合所有的有关mvc的依赖包信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加属性注入依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--支持热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--引入插件lombok 自动的set/get/构造方法插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springBoot数据库连接 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--spring整合mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<!--最新版本包 -->
<version>3.3.2</version>
</dependency>
<!--springBoot整合JSP添加依赖 -->
<!--servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!--jstl依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!--使jsp页面生效 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- 引入aop支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--spring整合redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<!--添加httpClient jar包 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!--引入dubbo配置 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<!--添加Quartz的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>
<modules>
<module>jt-common</module>
<module>jt-manager</module>
<module>jt-web</module>
<module>jt-sso</module>
<module>jt-cart</module>
<module>jt-order</module>
</modules>
</project>
manager ,子pom
<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>com.jt</groupId>
<artifactId>jt</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>jt-manager</artifactId>
<packaging>war</packaging>
<dependencies>
<!--2.添加依赖 -->
<dependency>
<groupId>com.jt</groupId>
<artifactId>jt-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!--3.添加插件 -->
<!--负责项目打包 更新 maven操作相关的配置 必须添加 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 application.yml
server:
port: 8091
servlet:
context-path: /
spring:
datasource:
#引入druid数据源
#type: com.alibaba.druid.pool.DruidDataSource
#driver-class-name: com.mysql.jdbc.Driver
# url: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
url: jdbc:mysql://192.168.140.200:8066/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
username: root
password: root
# password: 123
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
#mybatis-plush配置
mybatis-plus:
type-aliases-package: com.jt.pojo
mapper-locations: classpath:/mybatis/mappers/*.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.jt.mapper: debug
dubbo:
scan:
basePackages: com.jt
application:
name: provider-item
registry:
address: zookeeper://192.168.140.200:2181?backup=192.168.140.200:2182,192.168.140.200:2183
protocol:
name: dubbo
port: 20881 #每个服务都应该有自己独立的端口
2. 涉及知识点集合
2.1@Value方式赋值
适用于单个属性赋值,在application.yml文件赋值
jdbc:
username: root
password: root
在实现类中引入
//@Value作用: 从spring容器中找到具体的key,为属性赋值.
@Value("${jdbc.username}") //spel表达式 spring提供
private String username; //定义数据库用户名
@Value("${jdbc.password}")
private String password; //定义数据库密码
2.2 批量赋值
多个属性赋值可用以下方式,注意前缀
@ConfigurationProperties(prefix = "jdbc") //定义属性的前缀
@Data
public class JDBCController2 {
//批量为属性赋值时,要求配置文件的属性与类中的属性名称必须一致. 自动的赋值.
private String username; //定义数据库用户名
private String password; //定义数据库密码
//为属性赋值时,一定会调用对象的set方法.
@RequestMapping("/getMsgPrefix")
public String getMsgValue() {
return username+"|"+password;
}
}
2.3 @PropertySource,额外配置文件赋值
@RestController //保证返回的数据转化为JSON
//properties与spring容器建立关系,指定pro文件之后,进行加载. 默认的加载策略,采用ISO-8859-1编码
//如果其中包含中文,则应该采用utf-8格式编码.
@PropertySource(value = "classpath:/properties/jdbc.properties",encoding = "UTF-8")
public class JDBCController {
//@Value作用: 从spring容器中找到具体的key,为属性赋值.
@Value("${jdbc2.username}") //spel表达式 spring提供
private String username; //定义数据库用户名
@Value("${jdbc2.password}")
private String password; //定义数据库密码
@RequestMapping("/getMsg")
public String getMsg() {
this.username = "root";
this.password = "root";
return username+"|"+password;
}
@RequestMapping("/getMsgValue")
public String getMsgValue() {
return username+"|"+password;
}
2.4 多环境配置文件
这篇文章已实验,不再多描述
https://blog.csdn.net/Coder_Qiang/article/details/83306780
2.5 Mybatis-plus API说明
2.5.1POM
<!--spring整合mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
2.5.2 application.yml
#切换为Mybatisplus的配置
mybatis-plus:
# 定义别名包
type-aliases-package: com.jt.demo.pojo
# 批量导入mapper映射文件
mapper-locations: classpath:/mybatis/mappers/*.xml
#开启驼峰映射
configuration:
map-underscore-to-camel-case: true
2.5.3 POJO
@JsonIgnoreProperties(ignoreUnknown=true) //表示JSON转化时忽略未知属性
@TableName("tb_item")
@Data
@Accessors(chain=true)
public class Item extends BasePojo{
@TableId(type=IdType.AUTO)
private Long id; //商品id
private String title; //商品标题
private String sellPoint; //商品卖点信息
private Long price; //商品价格 Long > dubbo
private Integer num; //商品数量
private String barcode; //条形码
private String image; //商品图片信息 1.jpg,2.jpg,3.jpg
private Long cid; //表示商品的分类id
private Integer status; //1正常,2下架
//为了满足页面调用需求,添加get方法
public String[] getImages(){
return image.split(",");
}
}
2.5.4 Mapper
public interface ItemMapper extends BaseMapper<Item>{
@Select("select * from tb_item order by updated desc limit #{start},#{rows}")
List<Item> selectItemByPage(int start,int rows);
}
2.5.5 分页插件
@MapperScan(basePackages = "com.ma.mapper") //设置mapper接口的扫描包
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
sqlExplainInterceptor.setSqlParserList(sqlParserList);
return sqlExplainInterceptor;
}
/**
* SQL执行效率插件
*/
@Bean
// 设置 dev test 环境开启,保证效率
// @Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
// ms 设置 SQL 执行的最大时间,如果超过了就不执行,单位为ms
performanceInterceptor.setMaxTime(100);
// 开启格式化支持
performanceInterceptor.setFormat(false);
return performanceInterceptor;
}
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
2.5.6常用方法
查询所有数据
userMapper.selectList(null);
根据Id查询
User user=userMapper.selectById(id);
根据Id批量查询
userMapper.selectBatchIds(Arrays.asList(2L, 3L, 4L, 100L));
查询单条
QueryWrapper<User> wrapper = new QueryWrapper<>();
//查询条件
wrapper.eq("password", "123456");
// 查询的数据超过一条时,会抛出异常
User user = userMapper.selectOne(wrapper);
构建条件构造器查询
User user = new User();
user.setName("特朗普");
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
List<User> userList = userMapper.selectList(queryWrapper);
分页查询
//import com.baomidou.mybatisplus.core.metadata.IPage;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
Page<User> page = new Page<>(3, 1); //查询第一页,查询1条数据
QueryWrapper<User> wrapper = new QueryWrapper<>();
//设置查询条件
wrapper.like("email", "itcast");
IPage<User> iPage = this.userMapper.selectPage(page, wrapper);
System.out.println("数据总条数: " + iPage.getTotal());
System.out.println("数据总页数: " + iPage.getPages());
System.out.println("当前页数: " + iPage.getCurrent());
List<User> records = iPage.getRecords();
条件查询
allEq
Map<String,Object> params = new HashMap<>();
params.put("name", "李四");
params.put("age", "20");
params.put("password", null);
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,user_name,name,age,email AS mail FROM tb_user WHERE password IS NULL AND name = ? AND age = ?
// wrapper.allEq(params);
//SELECT id,user_name,name,age,email AS mail FROM tb_user WHERE name = ? AND age = ?
// wrapper.allEq(params, false);
//SELECT id,user_name,name,age,email AS mail FROM tb_user WHERE age = ?
// wrapper.allEq((k, v) -> (k.equals("age") || k.equals("id")) , params);
//SELECT id,user_name,name,age,email AS mail FROM tb_user WHERE name = ? AND age = ?
wrapper.allEq((k, v) -> (k.equals("age") || k.equals("id") || k.equals("name")) , params);
List<User> users = this.userMapper.selectList(wrapper);
eq查询
//eq 等于 =
// ne 不等于 <>
// gt 大于 >
// ge 大于等于 >=
// lt 小于 <
// le 小于等于 <=
// between BETWEEN 值1 AND 值2
// notBetween NOT BETWEEN 值1 AND 值2
// in 字段 IN (value.get(0), value.get(1), ...)
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,user_name,password,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?)
wrapper.eq("password", "123456")
.ge("age", 20)
.in("name", "李四", "王五", "赵六");
List<User> users = this.userMapper.selectList(wrapper);
三种模糊查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
// SELECT id,user_name,name,age,email AS mail FROM tb_user WHERE name LIKE ?
// 参数:%五(String)
wrapper.likeLeft("name", "五");
List<User> users = this.userMapper.selectList(wrapper);
查询倒叙
QueryWrapper<User> wrapper = new QueryWrapper<>();
//按照年龄倒序排序
// SELECT id,user_name,name,age,email AS mail FROM tb_user ORDER BY age DESC
wrapper.orderByDesc("age");
List<User> users = this.userMapper.selectList(wrapper);
or查询
wrapper.eq("name", "王五").or().eq("age", 21);
指定查询字段
wrapper.eq("name", "王五")
.or()
.eq("age", 21)
.select("id","name","age"); //指定查询的字段
查询条数
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 20); // 条件:年龄大于20岁的用户
// 根据条件查询数据条数
Integer count = this.userMapper.selectCount(wrapper);
插入
User user = new User();
user.setName("特朗普");
user.setAge(60);
user.setSex("男");
int result = userMapper.insert(user); //返回的result是受影响的行数,并不是自增后的id
boolean insertSelective(T entity); //选择性插入,null字段不插入
boolean insertBatch(List<T> entityList); //批量插入
修改
//根据id更新,更新不为null的字段
userMapper.updateById(user);
根据条件更新
User user = new User();
user.setAge(20); //更新的字段
user.setPassword("8888888");
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_name", "zhangsan"); //匹配user_name = zhangsan 的用户数据
//根据条件做更新
int result = this.userMapper.update(user, wrapper);
UpdateWrapper更新
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.set("age", 21).set("password", "999999") //更新的字段
.eq("user_name", "zhangsan"); //更新的条件
//根据条件做更新
int result = this.userMapper.update(null, wrapper);
删除
// 根据id删除数据
int result = this.userMapper.deleteById(9L);
// 根据map删除数据,多条件之间是and关系
Map<String,Object> map = new HashMap<>();
map.put("user_name", "zhangsan");
map.put("password", "999999");
int result = this.userMapper.deleteByMap(map);
// 根据包装条件做删除
User user = new User();
user.setPassword("123456");
user.setUserName("caocao");
QueryWrapper<User> wrapper = new QueryWrapper<>(user);
int result = this.userMapper.delete(wrapper);
// 根据id批量删除数据
int result = this.userMapper.deleteBatchIds(Arrays.asList(10L, 11L));
2.5.7 修改POJO后的特殊方法
POJO
@Data
@NoArgsConstructor
@AllArgsConstructor
//@TableName("tb_user")
public class User extends Model<User> {
//@TableId(type=IdType.AUTO)
private Long id;
private String userName;
@TableField(select=false,fill = FieldFill.INSERT)
private String password;
private String name;
private Integer age;
@TableField(value = "email")//数据库中为email
private String mail;
@TableField(exist = false)
private String address;//数据库中不存在
@Version
private Integer version;
@TableLogic//逻辑删除,1删除了,2未删除
private Integer deleted;
//枚举
private SexEnum sex;
}
特殊方法:
添加拦截器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入数据时填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
// 先获取到password的值,再进行判断,如果为空,就进行填充,如果不为空,就不做处理
Object password = getFieldValByName("password", metaObject);
if(null == password){
setFieldValByName("password", "888888", metaObject);
}
}
/**
* 更新数据时填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
}
}
自动注入密码88888
User user = new User();
user.setAge(20);
user.setMail("test@itcast.cn");
user.setName("曹操");
user.setUserName("caocao");
// user.setPassword("123456");
int result = userMapper.insert(user); //返回的result是受影响的行数,并不是自增后的id
System.out.println("result = " + result);
System.out.println(user.getId()); //自增后的id会回填到对象中
逻辑删除:
userMapper.deleteById(2L);
枚举:
User user = new User();
user.setName("貂蝉");
user.setUserName("diaochan");
user.setAge(20);
user.setMail("diaochan@itast.cn");
user.setVersion(1);
user.setSex(SexEnum.WOMAN);
int result = this.userMapper.insert(user);
2.6 restFul说明
可以用来动态的接收url中的参数.之后完成业务调用,可以通过不同的请求类型来标识不同的业务需求.
用restFul风格,可以简化用户url的写法.
type=“GET” 查询操作 * type=“POST” 新增操作 * type=“PUT” 更新操作 * type="DELETE"删除操作
2.7 文件上传
2.7.1 简单模式
@RestController
public class FileController {
/**
* url地址: http://localhost:8091/file
* 请求参数: fileImage
* 返回值: 文件上传成功
*
* 类型:MultipartFile
* 实现步骤:
* 1.接收资源文件
* 2.准备文件上传目录
* 3.准备文件上传的全路径 目录/文件名称
*/
@RequestMapping("/file")
public String file(MultipartFile fileImage){
//2.文件文件上传的目录
String fileDirPath = "D:/JT-SOFT/images";
File dirFile = new File(fileDirPath);
//判断文件目录是否存在
if(!dirFile.exists()){
//如果文化间目录没有,则应该新建目录
dirFile.mkdirs(); //创建多级目录
}
//3.准备文件上传的全路径. 路径+文件名称
String fileName = fileImage.getOriginalFilename(); //文件名称.后缀 123.jgp
File realFile = new File(fileDirPath+"/"+fileName);
//将字节信息输出到文件中.
try {
fileImage.transferTo(realFile); //实现文件上传
return "文件上传成功!!!";
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败!!!";
}
}
}
2.7.2 以日期为目录上传文件,完整流程
POJO对象
@Data
@Accessors(chain = true) //链式加载
@NoArgsConstructor
@AllArgsConstructor
public class ImageVO implements Serializable {
private Integer error; //确认是否有错误 0正常 1错误
private String url; //图片访问的虚拟地址.
private Integer width; //宽度
private Integer height; //高度
public static ImageVO fail(){
return new ImageVO(1, null, null, null);
}
public static ImageVO success(String url){
return new ImageVO(0, url, null, null);
}
public static ImageVO success(String url,Integer width,Integer height){
return new ImageVO(0, url, width, height);
}
}
properties配置文件
#image.localDir=D:/JT-SOFT/images
image.localDir=/usr/local/src/images
image.imageTypes=.jpg,.png,.git,.jpeg
image.imageUrl=http://image.jt.com
工具类
@Component
@PropertySource("classpath:/properties/image.properties")
public class ImageTypeUtil {
@Value("${image.imageTypes}")
private String imageTypes;
private Set<String> typeSet=new HashSet<>();
// PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。
// 支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用 PostConstruct 注释的方法也必须被调用。只有一个方法可以用此注释进行注释。
// PreDestroy 用与在依赖注入完成之前的方法前面执行,
@PostConstruct
public void init() {
String[] typeArray=imageTypes.split(",");
for(String type : typeArray) {
typeSet.add(type);
}
}
public Set<String> getImageType() {
return typeSet;
}
// 第一种方案
// private static Set<String> typeSet=new HashSet<>();
//
// static {
// typeSet.add(".jpg");
// typeSet.add(".png");
// typeSet.add(".gif");
// typeSet.add(".bmp");
// typeSet.add(".jpeg");
// }
//
// public static Set getImageType() {
// return typeSet;
// }
}
controller
/**
* 实现图片上传操作.
* url地址:http://localhost:8091/pic/upload?dir=image
* 参数信息: uploadFile
* 返回值: ImageVO对象
*/
@RequestMapping("/pic/upload")
public ImageVO upload(MultipartFile uploadFile){
return fileService.upload(uploadFile);
}
service实现类
@Service
@PropertySource(value = "classpath:/properties/image.properties")
public class FileServiceImpl implements FileService{
@Value("${image.localDir}")
//"D:/JT-SOFT/images"
private String localDir;
@Value("${image.imageUrl}")
private String imageUrl;
@Autowired
private ImageTypeUtil imageTypeUtil;
@Override
public ImageVO upload(MultipartFile uploadFile) {
//初始化图片类型集合
// List<String> typeList=new ArrayList<>();
//静态代码读取
// Set<String> typeSet = ImageTypeUtil.getImageType();
Set<String> typeSet = imageTypeUtil.getImageType();
//获取类型
String fileName=uploadFile.getOriginalFilename();
//转小写
fileName=fileName.toLowerCase();
// String fileType=StringUtils.getFilenameExtension(fileName);
int index=fileName.lastIndexOf(".");
//包含.
String fileType=fileName.substring(index);
if(!typeSet.contains(fileType)) {
return ImageVO.fail();
}
//准备文件上传的目录
String dateDir=new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());
String dirPath= localDir+dateDir;
File dirFile = new File(dirPath);
if(!dirFile.exists()) {
dirFile.mkdirs();
}
//重新制定文件名
String uuid= UUID.randomUUID().toString();
String realFileName= uuid+fileType;
//上传
File imageFile = new File(dirPath+realFileName);
System.out.println(imageFile.getAbsolutePath());
try {
uploadFile.transferTo(imageFile);
String url=imageUrl+dateDir+realFileName;
return ImageVO.success(url);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
return ImageVO.fail();
}
}
}
2.8 jackson工具类
public class ObjectMapperUtil {
//定义一个常量对象
private static final ObjectMapper MAPPER = new ObjectMapper();
//1.将对象转化为json串
public static String toJSON(Object target) {
if(target ==null) {
throw new NullPointerException("数据不能为空");
}
String json = null;
try {
json = MAPPER.writeValueAsString(target);
} catch (JsonProcessingException e) {
e.printStackTrace();
//将检查异常,转化为运行时异常!!!!
throw new RuntimeException();
}
return json;
}
//2.将json串按照指定的类型转化为对象
//实现:传递什么类型,就返回什么对象
// <T> 定义泛型
public static <T> T toObj(String json,Class<T> target) {
if(StringUtils.isEmpty(json)|| target == null) {
throw new NullPointerException("数据不能为空 ");
}
T t = null;
try {
t = MAPPER.readValue(json, target);
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new RuntimeException();
}
return t;
}
}
2.9 redis缓存
配置类省略
自定义缓存注解:
@Target(ElementType.METHOD)//方法上
@Retention(RetentionPolicy.RUNTIME)//运行时有效
public @interface CacheFind {
//key前缀
public String key();
//保存时间,单位秒
public int seconds() default 0;
}
切面类:
@Component
@Aspect
public class CacheAOP {
// jt-manager/src/main/java/com/jt/service/ItemCatServiceImpl.java
// @Pointcut("bean(itemCatServiceImpl)")
// @Pointcut("within(com.jt.service..*)")
// 返回值类型 包名.类名(参数)
// @Pointcut("execution(* com.jt.service..*.*(..))")
// @Pointcut("@annotation(com.jt.anno.CacheFind)")
// public void pointCut() {
//
// }
@Autowired(required = false)
// private Jedis jedis;
// private ShardedJedis jedis;
private JedisCluster jedis;
//正式业务
// @Around("@annotation(com.jt.anno.CacheFind)")
@SuppressWarnings("unchecked")
@Around("@annotation(cacheFind)")
public Object around(ProceedingJoinPoint pj,CacheFind cacheFind) {
String key=cacheFind.key();
//拼接redis的key
String parentId = pj.getArgs()[0].toString();
key+="::"+parentId;
System.out.println(key);
Object result=null;
if(jedis.exists(key)) {
//获取方法返回类
MethodSignature signature = (MethodSignature) pj.getSignature();
result=ObjectMapperUtil.toObj(jedis.get(key), signature.getReturnType());
}else {
try {
result=pj.proceed();
//缓存
String json=ObjectMapperUtil.toJSON(result);
int seconds=cacheFind.seconds();
if(seconds >0) {
jedis.setex(key, seconds, json);
}else {
jedis.set(key, json);
}
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException();
}
}
return result;
}
}
2.10 redis常见问题
2.10.1 缓存穿透
特点: 用户高并发环境下,访问数据库中根本不存在的数据.
影响:由于用户高并发访问,则数据库可能存在宕机的风险.
方案: 1. API网关限流 2 . 限定网关访问次数 3 . 布隆过滤器 4 . 查询为空也缓存,过期时间设置短
2.10.2 缓存击穿
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
方案 : 1. 设置永不过期 2 . redis本身是集群,尽量保证集群数量足够
2.10.3 缓存雪崩
缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
方案 : 设置随机时间,多级缓存
2.11 持久化
2.11.1 RDB模式
1.RDB模式采用定期持久化的方式. 风险:可能丢失数据.
2.RDB模式记录的是当前Redis的内存记录快照. 只记录当前状态. 持久化效率最高的
3.RDB模式是默认的持久化方式.
2.11.2 AOF模式
1.AOF模式默认是关闭状态 如果需要则手动开启.
2.AOF能够记录程序的执行过程 可以实现数据的实时持久化. AOF文件占用的空间较大.回复数据的速度较慢.
3.AOF模式开启之后.RDB模式将不生效.
2.12 内存策略
2.12.1 LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
维度: 自上一次使用的时间T
最为理想的内存置换算法.
2.12.2 LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
least frequently used (LFU) page-replacement algorithm
即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
维度: 引用次数
2.12.3 随机算法
2.13 内存优化策略
1.volatile-lru 在设定了超时时间的数据, 采用lru算法进行删除.
2.allkeys-lru 所有数据采用lru算法
3.volatile-lfu 在设定了超时时间的数据, 采用LFU算法进行删除.
4.allkeys-lfu 所有数据采用LFU算法
5.volatile-random 设定超时时间数据采用随机算法
6.allkeys-random 所有数据采用随机算法
7.volatile-ttl 设定了超时时间的数据 根据ttl规则删除. 将剩余时间少的提前删除
8.noeviction 内存满了 不做任何操作.报错返回.
2.14 全局cros
@Configuration
public class CorsConfig implements WebMvcConfigurer{
@Override
public void addCorsMappings(CorsRegistry registry) {
// TODO Auto-generated method stub
//Origins 源 http: // www.aa.com
//methods 请求类型 get put delete post
//允许携带cookie和session
// .maxAge 允许跨域的持续时间
registry.addMapping("/**")
// .allowedOrigins("http://www.aa.com")
.allowedOrigins("*")
.allowedMethods("GET","POST","DELETE","HEAD","PUT")
.allowCredentials(true);
WebMvcConfigurer.super.addCorsMappings(registry);
}
}
2.15 dubbo
2.15.1 pom
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
2.15.2 序列化对象
@Data
@Accessors(chain=true)
@TableName //如果对象名称与表名一致,则可以省略不写.
public class User implements Serializable{
//dubbo协议中传输的对象必须序列化
private static final long serialVersionUID = 1L;
@TableId(type=IdType.AUTO)
private Integer id;
private String name;
private Integer age;
private String sex;
}
2.15.3 第三方接口
public interface UserService {
//查询全部的用户信息
List<User> findAll();
//新增用户入库操作.
@Transactional
void saveUser(User user);
}
2.15.4 实现类
//import com.alibaba.dubbo.config.annotation.Service;
@Service(timeout=3000) //3秒超时 使用dubbo的注解
//@org.springframework.stereotype.Service//将对象交给spring容器管理
public class UserServiceImpl implements UserService { //实现公共的第三方
@Autowired
private UserMapper userMapper;
/*
userService接口只是定义规范.不负责具体的实现.将来由服务的提供者实现该功能.
* */
@Override
public List<User> findAll() {
return userMapper.selectList(null);
}
@Override
public void saveUser(User user) {
userMapper.insert(user);
}
}
2.15.5 application.yml
#关于Dubbo配置
dubbo:
scan:
basePackages: com.xx #指定dubbo的包路径 扫描dubbo的注解
application: #应用名称
name: provider-user #相同的接口 服务名称必定一致. 不同的接口服务名称一定不一致.
registry: #配置注册中心
address: zookeeper://192.168.126.129:2181?backup=192.168.126.129:2182,192.168.126.129:2183
protocol: #指定协议
name: dubbo #使用dubbo协议(tcp-ip) web-controller直接调用sso-Service
port: 20880 #每一个服务都有自己特定的端口 不能重复.
2.16 cookie共享
/**
* 实现用户退出操作
* url:http://www.jt.com/user/logout.html
* 返回值: 重定向到系统首页.
* 目的: 删除redis. 删除Cookie
* 前提: 需要获取cookie的key和value
*/
@RequestMapping("/logout")
public String logout(HttpServletRequest request,HttpServletResponse response){
String jtTicket = null;
//1.如何获取cookie中的数据?
Cookie[] cookies = request.getCookies();
//2.校验Cookie数据是否为null
if(cookies !=null && cookies.length>0){
for(Cookie cookie : cookies){
if(TICKET.equalsIgnoreCase(cookie.getName())){
jtTicket = cookie.getValue();
//业务需要提前删除Cookie
cookie.setMaxAge(0);
cookie.setPath("/");
cookie.setDomain("jt.com");
response.addCookie(cookie);
break;
}
}
}
//2.校验数据是否有效
if(!StringUtils.isEmpty(jtTicket)){
//如果数据不为null,则开始执行退出操作.
jedisCluster.del(jtTicket); //根据key,删除Redis中的记录
//删除cookie.
}
return "redirect:/";
}
2.17 Threadlocal
package com.jt.util;
import com.jt.pojo.User;
public class UserThreadLocal {
//1.定义本地线程变量!!!!!
private static ThreadLocal<User> threadLocal = new ThreadLocal<>();
//ThreadLocal<Map>
//2.定义数据新增的方法
public static void set(User user){
threadLocal.set(user);
}
//3.获取数据
public static User get(){
return threadLocal.get();
}
//4.移除方法 使用threadLocal时切记将数据移除.否则极端条件下,容易产出内存泄露的问题
public static void remove(){
threadLocal.remove();
}
//实现数据的移除
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}
cookie.setDomain("jt.com");
response.addCookie(cookie);
break;
}
}
}
//2.校验数据是否有效
if(!StringUtils.isEmpty(jtTicket)){
//如果数据不为null,则开始执行退出操作.
jedisCluster.del(jtTicket); //根据key,删除Redis中的记录
//删除cookie.
}
return "redirect:/";
}
### 2.17 Threadlocal
package com.jt.util;
import com.jt.pojo.User;
public class UserThreadLocal {
//1.定义本地线程变量!!!!!
private static ThreadLocal<User> threadLocal = new ThreadLocal<>();
//ThreadLocal<Map>
//2.定义数据新增的方法
public static void set(User user){
threadLocal.set(user);
}
//3.获取数据
public static User get(){
return threadLocal.get();
}
//4.移除方法 使用threadLocal时切记将数据移除.否则极端条件下,容易产出内存泄露的问题
public static void remove(){
threadLocal.remove();
}
//实现数据的移除
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}
}