mybatis-day
1.今日内容
1)批量操作和动态SQL
foreach if where set sql
2)映射器(resultMap)
3)单向多对一的映射器
4)单向一对多的映射器
5)懒加载和二级缓存(一级缓存默认就是开启的)
6)SSM集成
2.批量操作和动态SQL
2.1.回顾一下mybatis入门案例
1)创建项目+导包
2)domain
3)核心配置文件
4)映射文件
5)单元测试
2.2.批量删除
<!--
从Java端传入一个List集合,集合内放任意多个Long类型的主键id值
List<Long>
foreach标签表示循环
collection 表示循环的集合名称
item 循环项 每次循环到的数据都赋值给这个变量
open 表示循环开始前以什么开头
close 表示循环结束后以什么结尾
separator 表示分隔符,每个item之间以什么隔开,如果是最后一个数据就不加分隔符了
index 表示每次循环的数据在集合中的索引,并且将这个索引赋值这个变量
假设传入的数据为:[17,18,19]
DELETE FROM employee WHERE id in (17,18,19)
-->
<delete id="deleteMore" parameterType="list">
DELETE FROM employee WHERE id in
<foreach collection="list" item="empid" open="(" close=")" separator=",">
#{empid}
</foreach>
</delete>
2.3.批量新增
<!--
外部传入List<Employee>集合,在这里的SQL语句的VALUES后面的每一对小括号就应该循环
要注意:open和close是循环开始前和结束后添加(),而这里是每次循环的内容都需要(),所以我们只能直接写死在foreach标签内部
-->
<insert id="insertMore" parameterType="list">
INSERT INTO employee(name,sex,age,email,department_id) VALUES
<foreach collection="list" separator="," item="emp">
(#{emp.name},#{emp.sex},#{emp.age},#{emp.email},#{emp.department_id})
</foreach>
</insert>
2.4.高级查询
where和if标签
<!--
EmployeeQuery是模仿进销存系统中的BaseQuery和EmployeeQuery,并且还要将query包设置到自定义别名
where 标签的作用是:自动帮我们添加where关键字,并且自动将where后面的第一个and去掉
if 标签就是一个单分支语句 test内部要写一个判断条件(一般都是判断参数值非空或大于零),多个条件之间要使用and或者or英文单词来连接
标签内部的大于和小于符号都必须写成HTML中的特殊字符
> >
< <
-->
<select id="selectByQuery" parameterType="EmployeeQuery" resultType="Employee">
SELECT * FROM employee
<where>
<if test="name!=null and name!=''">
AND name LIKE CONCAT('%',#{name},'%')
</if>
<if test="age1!=null and age1>0">
AND age >= #{age1}
</if>
<if test="age2!=null and age2>0">
AND age <= #{age2}
</if>
</where>
</select>
2.5.SQL语句片段
<!-- SQL语句片段,在其他SQL标签内使用include标签将其引入 -->
<sql id="columnNamesWithId">
id,name,sex,age,email,department_id
</sql>
<sql id="columnNamesWithOutId">
name,sex,age,email,department_id
</sql>
<!-- include标签表示引用SQL片段,refid填sql片段的id --> 《if》标签会自动去掉第一个AND
<select id="selectByQuery" parameterType="EmployeeQuery" resultType="Employee">
SELECT <include refid="columnNamesWithId"/> FROM employee
<where>
<if test="name!=null and name!=''">
AND name LIKE CONCAT('%',#{name},'%')
</if>
<if test="age1!=null and age1>0">
AND age >= #{age1}
</if>
<if test="age2!=null and age2>0">
AND age <= #{age2}
</if>
</where>
</select>
2.6.数据丢失问题
在项目二中修改数据的时候也有数据丢失问题,当时我们采用的是:修改数据之前先去查询数据出来再进行修改!
除了这个做法之外还可以利用mybatis的动态SQL来解决:
<!--
set标签的作用:自动添加一个set关键字,并且自动将最后一个逗号去掉
-->
<update id="update" parameterType="Employee">
UPDATE employee
<set>
<if test="name!=null and name!=''">
name=#{name},
</if>
<if test="sex != null">
sex=#{sex},
</if>
<if test="age != null">
age=#{age},
</if>
<if test="email!=null and email!=''">
email=#{email},
</if>
<if test="department_id != null and department_id > 0">
department_id=#{department_id},
</if>
</set>
WHERE id=#{id}
</update>
2.7 limit 分页问题
解决返回total为0问题:添加分页插件才会生效
旧版
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
mapper接口层分页:适用于 需要自己在xml里面写复杂sql或者连表查询的时候
只需要把接口上第一个参数改成Page,返回改成IPage
IPage<ShopQueryDTO> findAll(Page<ShopQueryDTO> page,@Param("doublelist")Map<String, Double> doubles,@Param("query")ShopInfoQuery shopInfoQuery);
Page<ShopQueryDTO> page = new Page<>(0,5);
IPage<ShopQueryDTO> allbak = shopInfoMapper.findAll(page, doubles, shopInfoQuery);
原理:就是在xml sql语句最后拼接上limit 切记不能再sql语句后面加;
service层分页:适用于简单的单表查询或者使用mubaties封装的方法查询
Page<ShopQueryDTO> page = new Page<>(0,5);
shopTypeMapper.selectPage()
shopTypeMapper.selectMapsPage()
3.映射器
3.1.概念
映射器:就是将查询结果集与实体类进行映射的一个中间件。
何时使用?
当查询结果集的列名称与实体类中的字段名称不一致的时候就需要使用映射器。
3.2.声明映射器
<!--
声明了一个映射器:内部只需要写结果集列名称与实体类字段名称不一致的那些,其他一致的就不用写出来
-->
<resultMap id="EmployeeMap" type="Employee">
<id property="id" column="id" />
<result property="name" column="empName" />
<result property="sex" column="gender" />
<result property="age" column="age" />
<result property="email" column="email" />
<result property="department_id" column="department_id" />
</resultMap>
<select id="selectByQuery" parameterType="EmployeeQuery" resultMap="EmployeeMap">
SELECT <include refid="columnNamesWithId"/> FROM employee
<where>
<if test="name!=null and name!=''">
AND name LIKE CONCAT('%',#{name},'%')
</if>
<if test="age1!=null and age1>0">
AND age >= #{age1}
</if>
<if test="age2!=null and age2>0">
AND age <= #{age2}
</if>
</where>
</select>
注意:
-
只有select标签才能写resultMap和resultType属性
-
resultMap和resultType属性都表示处理查询结果,只能二选一
-
parameterType和parameterMap属性也是只能二选一,但是parameterMap属性一般很少用
-
insert、delete、update、select标签都可以写parameterType和parameterMap属性
-
- 一般只用parameterType属性
- parameterType属性可以写XxxQuery、long、int、string、map
-
声明映射器是,内部只需要写结果集列名称与实体类字段名称不一致的那些,其他一致的就不用写出来
3.3.关联映射【多对一】
3.3.1.为何需要?
假如我们在查询员工数据的时候,需要将它所属的部门对象也查询出来,这个在JPA中可以使用@ManyToOne注解+懒加载来实现。
Mybatis中没有这个注解,但是却有懒加载。
如何实现呢?
就需要使用到mybatis的关联映射了!
关联映射分为两种:
- 嵌套结果:饥饿加载、即时加载(连表查询)
- 嵌套查询:懒加载、延迟加载
3.3.2.嵌套结果
准备工作:
1)创建一张部门表 (id,name)
2)准备一个Department实体类
3)员工类写一个成员变量关联Department实体类对象
<!--
声明了一个映射器
association 表示关联对象
必须写property属性,填外部resultMap标签的type属性指定的实体类中的关联另一个实体类的成员变量名称
还要指定一下javaType,填这个关联实体类的类型
在association标签内部还要单独将部门对象的所有列都写出来,主键使用id标签,非主键使用result标签
注意:
1)association标签内部的标签默认从查询结果集从左到右查找匹配名称的列名称,如果SQL语句中没有列别名,这里就会取到员工id和name
2)association标签写了之后,其他字段即使列名称与实体类字段名称一致了,也会获取不到数据,必须一个一个全部写出来
-->
<resultMap id="EmployeeMap" type="Employee">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="age" property="age"/>
<result column="email" property="email"/>
<result column="department_id" property="department_id"/>
<association property="department" javaType="Department">
<id column="did" property="id"/>
<result column="dname" property="name"/>
</association>
</resultMap>
<!--
直接使用一个连表查询,将员工数据和部门数据都查询出来
注意:部门的列名称要使用别名,因为这些列名称与员工的列名称重复了
-->
<select id="selectByQuery" resultMap="EmployeeMap">
SELECT e.*,d.id did,d.name dname FROM employee e,department d WHERE e.department_id = d.id
</select>
3.3.3.嵌套查询
<!--
声明了一个映射器
select="selectDepartmentById" 填另一个查询标签的id
column="department_id" 将当前结果集中的department_id列的值传入过去作为参数
将本次查询员工的每一行数据中的department_id列的值传到id为selectDepartmentById的查询语句中作为参数去查询部门,
然后将查询到的部门对象设置到当前员工对象的department属性上。
-->
<resultMap id="EmployeeMap" type="Employee">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="age" property="age"/>
<result column="email" property="email"/>
<result column="department_id" property="department_id"/>
<association property="department" select="selectDepartmentById" column="department_id"></association>
</resultMap>
<select id="selectDepartmentById" parameterType="long" resultType="Department">
SELECT * FROM department WHERE id=#{did}
</select>
<!--
嵌套查询:
先查询员工,当你需要员工所属的部门对象的属性值的时候,再去查询部门(懒加载)
-->
<select id="selectByQuery" resultMap="EmployeeMap">
SELECT * FROM employee
</select>
3.4.关联映射【一对多】
3.4.1.嵌套结果
<!--
一对多:嵌套结果
使用连表查询,一次性查询出来
collection表示集合,从结果集中提取部分列的数据来封装成一个Employee对象,然后放入到employees集合中
property="employees" 填的是Department类中的List集合类型的字段名称
javaType="Employee" 填的是Department类中的List集合的泛型类型
-->
<resultMap id="DepartmentMap" type="Department">
<id column="did" property="id"/>
<result column="dname" property="name"/>
<collection property="employees" javaType="Employee">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="sex" property="sex"/>
<result column="age" property="age"/>
<result column="email" property="email"/>
<result column="department_id" property="department_id"/>
</collection>
</resultMap>
<select id="selectDepartments" resultMap="DepartmentMap">
SELECT d.id did,d.name dname, e.* FROM department d,employee e WHERE d.id = e.department_id
</select>
3.4.2.嵌套查询
<!--
一对多:嵌套查询
使用连表查询,一次性查询出来
collection表示集合,从结果集中提取部分列的数据来封装成一个Employee对象,然后放入到employees集合中
property="employees" 填的是Department类中的List集合类型的字段名称
javaType="Employee" 填的是Department类中的List集合的泛型类型
column="id" 将查询到的部门的id列的值传到另一个查询语句中作为参数
select="selectEmployeesByDepartmentId" 指定另一个查询语句的id
小结
嵌套结果:一次性查询所需要的数据,一般采用连表查询,只访问数据库一次。
嵌套查询:执行多次单表查询,会多次访问数据库,访问数据库本身比较消耗内存空间。
-->
<resultMap id="DepartmentMap" type="Department">
<id column="id" property="id"/>
<result column="name" property="name"/>
<collection property="employees" column="id" select="selectEmployeesByDepartmentId"></collection>
</resultMap>
<select id="selectEmployeesByDepartmentId" parameterType="long" resultType="Employee">
SELECT * FROM employee WHERE department_id = #{did}
</select>
<select id="selectDepartments" resultMap="DepartmentMap">
SELECT * FROM department
</select>
3.5、自我总结
ps:多对一关系也可以表示为一对多,使用一对多关系sql语句都应该以 一方表为准,谁数据多联接谁
如不分页使用嵌套结果
如果使用分页用嵌套查询
4.懒加载【了解】
首先要在核心配置文件中配置:
<!-- 懒加载的配置:JavaWeb项目懒加载有点儿多余, -->
<settings>
<!-- 全局启用或禁用懒加载,默认是false,表示禁用懒加载,关联对象都会即时加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!--
全局启用或禁用懒加载策略
true 表示调用对象的任何属性的get方法都会立即加载关联对象
false 表示只有调用了关联对象的get方法时才会去加载关联对象
-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
然后在嵌套查询的association或者collection标签上添加fetchType属性:
- lazy:懒加载
- eager:即时加载
但是只有Mybatis3.5.5以后的版本才支持这个属性;
而且经过测试:
懒加载:查询到10条员工数据,会去查询10次部门,即使部门有重复的数据也要去查询;
即时加载:查询到10条员工数据,会去查询3次部门,相同的部门数据就不再去查询了(有缓存);
结论:
在Mybatis中不要去使用懒加载,没什么意义,反而还降低了效率!!!
5.缓存【了解】
5.1.一级缓存
一级缓存是默认开启的,保存在SqlSession对象身上,以键值对方式保存!
一级缓存命中条件:同一个SqlSessionFactory,同一个SqlSession,同一个OID
与JPA中完全一致!!
/**
* 测试一级缓存
* @throws Exception
*/
@Test
public void testSelectById() throws Exception{
SqlSession sqlSession = MybatisUtils.INSTANCE.getSqlSession();
Employee employee01 = sqlSession.selectOne("cn.itsource.domain.Employee.selectById", 13L);
System.out.println(employee01);
System.out.println("==========================================================");
Employee employee02 = sqlSession.selectOne("cn.itsource.domain.Employee.selectById", 13L);
System.out.println(employee02);
sqlSession.close();
}
5.2.二级缓存
二级缓存默认是关闭,保存在SqlSessionFactory对象身上,以键值对方式保存
二级缓存命中条件:同一个SqlSessionFactory,不同的SqlSession,同一个OID
首先在映射文件中添加设置:
<!-- 开启二级缓存 -->
<cache/>
/**
* 测试二级缓存
* @throws Exception
*/
@Test
public void testSelectById2() throws Exception{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession01 = sqlSessionFactory.openSession();
Employee employee01 = sqlSession01.selectOne("cn.itsource.domain.Employee.selectById", 13L);
System.out.println(employee01);
sqlSession01.close();
System.out.println("==========================================================");
SqlSession sqlSession02 = sqlSessionFactory.openSession();
Employee employee02 = sqlSession02.selectOne("cn.itsource.domain.Employee.selectById", 13L);
System.out.println(employee02);
sqlSession02.close();
}
6.Dao层【映射接口】
package cn.itsource.dao;
import cn.itsource.domain.Employee;
import cn.itsource.query.EmployeeQuery;
import java.util.List;
public interface EmployeeMapper {
void save(Employee employee);
void update(Employee employee);
void delete(Long id);
Employee findOne(Long id);
List<Employee> findAll(EmployeeQuery employeeQuery);
/**
* 分页查询:查询当前页的数据集合
* @param employeeQuery
* @return
*/
List<Employee> findByPage(EmployeeQuery employeeQuery);
/**
* 分页查询:查询总行数
* @param employeeQuery
* @return
*/
Long findRowCount(EmployeeQuery employeeQuery);
}
package cn.itsource.dao;
import cn.itsource.domain.Employee;
import cn.itsource.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
//这里是单元测试类,今后这里的代码要放在Service层实现类中
public class IEmployeeDaoTest {
private SqlSession sqlSession = MybatisUtils.INSTANCE.getSqlSession();
//重要:接口不需要实现类
private EmployeeMapper employeeDao = sqlSession.getMapper(EmployeeMapper.class);
@Test
public void save() {
}
@Test
public void update() {
}
@Test
public void delete() {
}
@Test
public void findOne() {
System.out.println(employeeDao); //org.apache.ibatis.binding.MapperProxy@3c0ecd4b
System.out.println(employeeDao.getClass()); //com.sun.proxy.$Proxy4
/**
* 很显然:mybatis底层使用了动态代理技术,帮我们生成了一个实现类!!!
* $Proxy4继承了MapperProxy类,并且实现我们自己写的IEmployeeDao接口
* 这个结构与SpringDataJPA那个是一样的!!!
*/
Employee employee = employeeDao.findOne(13L);
System.out.println(employee);
}
@Test
public void findAll() {
}
@Test
public void findByPage() {
}
@Test
public void findRowCount() {
}
}
这个做法Dao层只写接口,不写实现类了,由Mybatis底层使用动态代理技术帮我们生成了一个实现类!
这个实现类继承了MapperProxy类,并且实现了我们自己写的接口!
这个做法必须遵守以下规范:
- 接口的名称一般命名为 XxxMapper 前面的Xxx就是你的实体类名称
- 映射文件叫XxxMapper.xml,而且建议与接口同包
- 映射文件中的namespace必须写接口的完全限定名
- 映射文件中的sql标签的id必须与接口中的抽象方法名称一致,而且参数类型和返回类型也要匹配
7.SSM集成
7.1.集成配置
重复性的配置直接复制粘贴完成!!
只有集成Mybatis的FactoryBean配置与SSJ集成时不一样,其他配置几乎一致!!
<!--
集成Mybatis:FactoryBean方式配置
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 依赖注入一个数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 指定mapper映射文件的路径 -->
<property name="mapperLocations" value="classpath:cn/itsource/dao/*Mapper.xml"/>
<!-- 定义别名 -->
<property name="typeAliasesPackage" value="cn.itsource.domain,cn.itsource.query"/>
<!-- 可以使用mybatis核心配置文件来代替上面两个标签的作用:将导入多个mapper映射文件和定义别名都写在核心配置文件内
<property name="configLocation" value="classpath:mybatis-config.xml"/>
-->
</bean>
<!-- 开启事务管理的注解驱动 支持@Transactional注解 -->
<tx:annotation-driven transaction-manager="txManager" />
<!-- 事务管理的bean -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
这个配置中的mapperLocations和typeAliasesPackage还可以直接使用mybatis核心配置文件来代替(将导入多个mapper映射文件和定义别名都写在核心配置文件内)
7.2.集成SpringMVC
service层
controller层
jsp
js
修改了映射文件
8.动态SQL
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.itsource.mapper.EmployeeMapper">
<!--添加一个员工-->
<insert id="save">
INSERT INTO employee(<include refid="columnNamesWithOutId" />) VALUES
(#{username},#{password},#{email},#{headImage},#{age})
</insert>
<!--列表,修改一个员工-->
<update id="update" parameterType="Employee">
UPDATE employee
<set>
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="password != null and password!=''">
password=#{password},
</if>
<if test="age!= null and age>0">
age=#{age},
</if>
<if test="email!=null and email!=''">
email=#{email},
</if>
<if test="headImage != null and headImage!=''">
headImage=#{headImage},
</if>
</set>
WHERE id=#{id}
</update>
<!--删除一个员工-->
<delete id="delete">
DELETE FROM employee WHERE id = #{id}
</delete>
<!--通过id查询一个员工-->
<select id="findOne" parameterType="long" resultType="Employee">
SELECT <include refid="columnNamesWithId"/> FROM employee WHERE id=#{id}
</select>
<!--查询表里所有员工记录,可支持多条件EmployeeQuery-->
<!--返回一个list集合,只填泛型-->
<select id="findAll" parameterType="EmployeeQuery" resultType="Employee">
SELECT <include refid="columnNamesWithId"/> FROM employee <include refid="whereCaules"/>
</select>
<!--页面列表的展示,多条件+分页+排序-->
<!--多表展示时,在这进行连表查询,修改返回类型Map,被给表取别名-->
<select id="findByPage" parameterType="EmployeeQuery" resultType="Employee">
SELECT <include refid="columnNamesWithId"/> FROM employee <include refid="whereCaules"/>
ORDER BY id DESC
LIMIT #{startIndex},#{pageSize} /*拿get方法的值*/
</select>
<!--根据条件查询表里的总记录数(用于findByPage方法service层调用)-->
<select id="findRowCount" parameterType="EmployeeQuery" resultType="long">
SELECT COUNT(*) FROM employee
<include refid="whereCaules"/>
</select>
<!--带id的查询返回的视图(用于查询返回的结果集)-->
<sql id="columnNamesWithId">
id,
username,
password,
email,
headImage,
age
</sql>
<!--不带id,用于添加数据时 被赋值的字段-->
<!--注意与拿参数顺序一一对应-->
<sql id="columnNamesWithOutId">
username,
password,
email,
headImage,
age
</sql>
<!--查询语句所需的条件(所需的条件都一样可以抽取)-->
<sql id="whereCaules">
<where>
<if test="username!=null and username!=''">
AND name LIKE CONCAT('%',#{username},'%')/*concat吧里面的字符拼接在一起*/
</if>
<if test="age1!=null and age1>0">
AND age >= #{age1}
</if>
<if test="age2!=null and age2>0">
AND age <= #{age2}
</if>
</where>
</sql>
<!--取集合参数-->
<select id="followList" resultMap="BaseResultMap">
SELECT s.*,a.name as activity_name , a.uuid as activity_uuid,a.ervery_sum,a.reduce_num
FROM t_shop_info as s
LEFT JOIN t_activity as a
ON s.uuid=a.shop_uuid
WHERE s.status=1
<if test="shopUuids != null and shopUuids.size != 0">
<!-- shopUuids 绑定集合的名字-->
<foreach collection="shopUuids" item="uuid" open=" and s.uuid in (" close=")" separator=",">
#{uuid}
</foreach>
</if>
</select>
</mapper>
9.笔记
1.注解 @jsonFomat和@DateTimeFormat的使用
**jackson包中:**import com.fasterxml.jackson.annotation.JsonFormat;
@DatetimeFormat是将String转换成Date,一般前台给后台传值时用
import org.springframework.format.annotation.DateTimeFormat;
/**
* 前台传后台时, 字符串自动封装成日期
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date birth;
@JsonFormat(pattern=”yyyy-MM-dd”) 将Date转换成String 一般后台传值给前台时
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 后台返给前台时, 日期自动格式化
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8") //Json才需要时区
private Date birth;
注意:
1、@JsonFormat不仅可以完成后台到前台参数传递的类型转换,还可以实现前台到后台类型转换。
2、当content-type为application/json时,优先使用@JsonForma t的pattern进行类型转换。而不能使用 @DateTimeFormat进行类型转换。
3、@JsonFormat注解的作用就是完成json字符串到java对象的转换工作,与参数传递的方向无关。
**fastjson 包中:**import com.fasterxml.jackson.annotation.JsonFormat;
@JSONField(format="yyyy-MM-dd HH:mm:ss")
2.redisTemplate的使用
redisTemplate.opsForValue(); //操作字符串
redisTemplate.opsForHash(); //操作hash
redisTemplate.opsForList(); //操作list
redisTemplate.opsForSet(); //操作set
redisTemplate.opsForZSet(); //操作有序set
StringRedisTemplate常用操作
stringRedisTemplate.opsForValue().set("test", "100",60*10,TimeUnit.SECONDS);//向redis里存入数据和设置缓存时间
stringRedisTemplate.boundValueOps("test").increment(-1);//val做-1操作
stringRedisTemplate.opsForValue().get("test")//根据key获取缓存中的val
stringRedisTemplate.boundValueOps("test").increment(1);//val +1
stringRedisTemplate.getExpire("test")//根据key获取过期时间
stringRedisTemplate.getExpire("test",TimeUnit.SECONDS)//根据key获取过期时间并换算成指定单位
stringRedisTemplate.delete("test");//根据key删除缓存
stringRedisTemplate.hasKey("546545");//检查key是否存在,返回boolean值
stringRedisTemplate.opsForSet().add("red_123", "1","2","3");//向指定key中存放set集合
stringRedisTemplate.expire("red_123",1000 , TimeUnit.MILLISECONDS);//设置过期时间
stringRedisTemplate.opsForSet().isMember("red_123", "1")//根据key查看集合中是否存在指定数据
stringRedisTemplate.opsForSet().members("red_123");//根据key获取set集合
3、如何正确的删除集合中的元素
- 普通的for循环遍历并删除
public void forRemove(List<T> list, T obj){
for(int i = 0;i < list.size(); i++){
if (obj == list.get(i))
{
list.remove(obj);
}
}
}
main方法中:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("2");
list.add("3");
re.forRemove(list,"2");//调方法
System.out.println(list.toString());
程序输出:[1,2,3]->并不是想要的结果,2没有删掉
2.forEach循环删除
3.Iterator 迭代器删除(推荐)
综上,对于集合的删除操作,特别是List,需要使用迭代器来操作。
4、时间localDateTime的使用
//自定义时间-年月日 时分秒
LocalDateTime time = LocalDateTime.of(2020, 10, 10, 10, 30, 55);
//自定义时间-年月日
LocalDate time1 = LocalDate.of(2010, 23, 12);
//自定义时间-时分秒
LocalTime time2 = LocalTime.of(12, 20, 56);
//获取秒数时间戳
Long second = time.toEpochSecond(ZoneOffset.of("+8"));
//获取毫秒时间戳
Long milliSecond = time.toInstant(ZoneOffset.of("+8")).toEpochMilli();
//格式化时间
time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
5、拉姆达表达式–快速增删改一个集合中的元素
List<People> collect = list.stream().filter(x -> { //x的类型就是集合的泛型
return x.getSex() == false; //filte需要一个过滤条件,一般返回ture 和 false
}).map(x -> {// map映射 更新集合中数据
x.setId(10);
return x;
}).collect(Collectors.toList());
6、注意:
1、Feign调用获取对象一定要值,不然回报json转换异常,Feign集成了json自动转换
7、ES学习
## _index索引库,相当于数据库;_type相当于表,_id表示该文档在index中的存储位置(区别于数据库id)
## {index}/{type}/{id} { json数据 }
GET _search
{
"query": {
"match_all": {}
}
}
##新增
PUT liin/employee2/1
{
"id":1,
"name":"李白",
"age":20
}
##随机新增
POST liin/employee
{
"id":1,
"name":"李白",
"age":20
}
##通过id查询(这个id是document的id)
GET liin/employee/1
##根据_id修改
POST liin/employee/AXgBW42kTxM9hFSxnHEA/_update
{
"doc": {
"name":"李白2",
"age":25
}
}
##根据_id删除
DELETE liin/employee/1
## 查询所有
GET liin/employee/_search
## 分页查询所有 类似limit(0,5)
GET liin/employee/_search?from=0&size=2
GET liin/employee/_search?from=1&size=2
##DSL查询与过滤
#模糊匹配
GET liin/employee/_search
{
"query": {
"match": {
"name": "李白"
}
}
}
#多条件 查询
#match:模糊匹配like
#range:范围
#term:精确条件
#terms:精确多条件
GET liin/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "李白"
}
},
{
"range": {
"age": {
"gte": 20,
"lte": 25
}
}
},
{
"term": {
"age":25
}
},
{
"terms": {
"age": [
20,
25
]
}
}
]
}
},
"from": 0,
"size": 3,
"_source": ["age","name"],
"sort": [
{
"age": "desc"
}
]
}
#DSL单条件过滤
GET liin/_search
{
"query": {
"bool": {
"filter": {
"term": {
"age": "20"
}
}
}
}
}
#2)DSL多条件过滤
GET liin/_search
{
"query": {
"bool": {
"filter": [
{
"term":{
"name":"李"
}
},
{
"range":{
"age":{
"gte":20,
"lte":25
}
}
}
]
}
}
}
#标准查询
GET liin/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "李白"
}
},
{
"term": {
"age": 25
}
}
]
}
}
}
#一个字段
GET liin/_search
{
"query": {
"match": {
"name": "3"
}
}
}
#多个字段(中文乱码)
GET liin/_search
{
"query": {
"multi_match": {
"query": "3",
"fields": ["name","age"]
}
}
}
8、Minio文件服务的搭建和使用
windows搭建:https://blog.csdn.net/kylinregister/article/details/88910350
找到服务器所在目录启动命令: .\minio.exe server D:\html\minio
liux系统搭建:http://dl.minio.org.cn/server/minio/release/linux-amd64/mini
9、MQ消息队列
一、搭建RabbitMQ
1)、下载erlang
erlang安装完成需要配置erlang环境变量: ERLANG_HOME=D:\Program Files\erl9.3 在path中添 加%ERLANG_HOME%\bin; (cmd输入erl -version查看环境是否配置成功)
2)、安装RabbitMQ
3)、启动RabbitMQ service:
1从开始菜单启动RabbitMQ 完成在开始菜单找到RabbitMQ的菜
2如果没有开始菜单则进入安装目录下sbin目录手动启动:
rabbitmq-service.bat install 必须先安装服务
rabbitmq-service.bat stop 停止服务
rabbitmq-service.bat start 启动服务
3、电脑服务器列表启动
4) 安装管理插件 安装rabbitMQ的管理插件,方便在浏览器端管理RabbitMQ
管理员身份运行 sbin目录: rabbitmq-plugins.bat enable rabbitmq_management
5)重新启动
6)启动成功 登录RabbitMQ:进入浏览器,输入:http://localhost:15672
初始账号和密码:guest/guest
注意:
1、安装erlang和rabbitMQ以管理员身份运行。
2、当卸载重新安装时会出现RabbitMQ服务注册失败,此时需要进入注册表清理erlang 搜索RabbitMQ、ErlSrv,将对应的项全部删除。
二、java操作RabbitMQ
# rabbitmq配置
rabbitmq:
host: 192.168.12.14 #服务器ip
port: 5672 #服务器端口
username: mall
password: mall
publisher-confirms: true # 开启Rabbitmq发送消息确认机制,发送消息到队列并触发回调方法
publisher-returns: true
virtualHost: /
listener:
simple:
concurrency: 10 #消费者数量
max-concurrency: 10 #最大消费者数量
prefetch: 1 #限流(消费者每次从队列获取的消息数量)
auto-startup: true #启动时自动启动容器
acknowledge-mode: manual #开启ACK手动确认模式
retry:
enabled: true
<!--rabbitMq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
三、配置交换机和队列
1、基本模式:一个交换机,一个队列,一个消费者监听
2、工作模式:一个交换机,一个队列,两个消费者监听同一个队列
3、订阅模式:
解读:
1、一个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
交换机一方面:接收生产者发送的消息。另一方面:知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
Exchange类型有以下几种:
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合指定routing key 的队列
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
(1)订阅模型-Fanout
Fanout,也称为广播。在广播模式下,消息发送流程是这样的:
1) 可以有多个消费者
2) 每个消费者有自己的queue(队列)
3) 每个队列都要绑定到Exchange(交换机)
4) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
5) 交换机把消息发送给绑定过的所有队列
6) 队列的消费者都能拿到消息。实现一条消息被多个消费者消费
(2)订阅模型-Direct:
在Direct模型下,队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key),消息的发送方在向Exchange发送消息时,也必须指定消息的routing key。
1、一个交换机,一个队列,匹配多个路由
//绑定 将队列和交换机绑定, 并设置用于匹配键(路由):TestDirectRouting
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueueTag()).to(TestDirectExchange()).with("error");
}
@Bean
Binding bindingDirect1() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("info");
}
@Bean
Binding bindingDirect2() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("error");
}
@Bean
Binding bindingDirect3() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("warning");
}
2、一个交换机,不同队列,匹配不同路由
//绑定 将队列和交换机绑定, 并设置用于匹配键(路由):TestDirectRouting
@Bean
Binding bindingDirect() {
return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
}
@Bean
Binding bindingDirect2() {
return BindingBuilder.bind(TestDirectQueue2()).to(TestDirectExchange()).with("TestDirectRouting2");
}
(3)订阅模型-Topic
Topic 类型的 Exchange 与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
通配符规则:#:匹配一个或多个词 , *:匹配不多不少恰好 1 个词
10、阿里云短信接口(免审免签)
1、购买服务
https://market.aliyun.com/products/57126001/cmapi028903.html?spm=5176.2020520132.101.8.467a7218IGnbTT#sku=yuncode2290300001
2、需要的工具类
package com.jsy.basic.util.utils;
import com.alibaba.fastjson.JSON;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.util.Map;
/**
* @Description: Apache HTTP工具类封装
* @Author: chq459799974
* @Date: 2020/12/11
**/
public class MyHttpUtils {
private static final Logger logger = LoggerFactory.getLogger(MyHttpUtils.class);
public static final int ANALYZE_TYPE_STR = 1;//返回结果解析类型-String
public static final int ANALYZE_TYPE_BYTE = 2;//返回结果解析类型-byte
//本地连通性测试
public static void main(String[] args) throws Exception {
System.out.println(ping(""));
}
public static boolean ping(String ipAddress) throws Exception {
int timeOut = 3000 ; //超时应该在3钞以上
boolean status = InetAddress.getByName(ipAddress).isReachable(timeOut); // 当返回值是true时,说明host是可用的,false则不可。
return status;
}
//默认Header
public static void setDefaultHeader(HttpRequest httpRequest){
httpRequest.setHeader("Content-Type", "application/json");
}
//设置Header
public static void setHeader(HttpRequest httpRequest,Map<String,String> headers) {
// httpRequest.setHeader("Content-Type", "application/json;charset=utf-8");
// httpRequest.setHeader("Accept", "application/json");
//设置params
if(headers != null){
for(Map.Entry<String,String> entry : headers.entrySet()){
httpRequest.setHeader(entry.getKey(),entry.getValue());
}
}
}
//Http请求配置
public static void setRequestConfig(HttpRequestBase httpRequest){
httpRequest.setConfig(getDefaultRequestConfig());
}
public static void setRequestConfig(HttpRequestBase httpRequest,RequestConfig requestConfig){
httpRequest.setConfig(requestConfig);
}
// 默认配置 (2000 1000 3000)
private static RequestConfig getDefaultRequestConfig(){
return RequestConfig.custom().setConnectTimeout(10000)
.setConnectionRequestTimeout(10000)
.setSocketTimeout(10000).build();
}
//构建URL
private static URIBuilder buildURL(String url){
URIBuilder builder = null;
try {
builder = new URIBuilder(url);
} catch (URISyntaxException e) {
e.printStackTrace();
}
return builder;
}
//构建HttpGet
public static HttpGet httpGetWithoutParams(String url){
return httpGet(url,null);
}
public static HttpGet httpGet(String url, Map<String,String> paramsMap){
URIBuilder uriBuilder = buildURL(url);
//设置params
if(paramsMap != null){
for(Map.Entry<String,String> entry : paramsMap.entrySet()){
uriBuilder.setParameter(entry.getKey(),entry.getValue());
}
}
HttpGet httpGet = null;
try {
httpGet = new HttpGet(uriBuilder.build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
return httpGet;
}
//构建HttpPost
public static HttpPost httpPostWithoutParams(String url, Map<String,Object> bodyMap){
return httpPost(url,null,bodyMap);
}
public static HttpPost httpPostWithoutBody(String url, Map<String,String> paramsMap){
return httpPost(url,paramsMap,null);
}
public static HttpPost httpPost(String url, Map<String,String> paramsMap, Map<String,Object> bodyMap){
URIBuilder uriBuilder = buildURL(url);
//设置params
if(paramsMap != null){
for(Map.Entry<String,String> entry : paramsMap.entrySet()){
uriBuilder.setParameter(entry.getKey(),entry.getValue());
}
}
HttpPost httpPost = null;
try {
httpPost = new HttpPost(uriBuilder.build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
if(bodyMap != null){
String body = JSON.toJSONString(bodyMap);
httpPost.setEntity(new StringEntity(body, "utf-8"));
}
return httpPost;
}
//构建HttpPut
public static HttpPut httpPutWithoutParams(String url, Map<String,Object> bodyMap){
return httpPut(url,null,bodyMap);
}
public static HttpPut httpPutWithoutBody(String url, Map<String,String> paramsMap){
return httpPut(url,paramsMap,null);
}
public static HttpPut httpPut(String url, Map<String,String> paramsMap, Map<String,Object> bodyMap){
URIBuilder uriBuilder = buildURL(url);
//设置params
if(paramsMap != null){
for(Map.Entry<String,String> entry : paramsMap.entrySet()){
uriBuilder.setParameter(entry.getKey(),entry.getValue());
}
}
HttpPut httpPut = null;
try {
httpPut = new HttpPut(uriBuilder.build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
if(bodyMap != null){
String body = JSON.toJSONString(bodyMap);
httpPut.setEntity(new StringEntity(body, "utf-8"));
}
return httpPut;
}
//获取连接
public static CloseableHttpClient getConn() {
return HttpClientBuilder.create().build();
}
//执行请求,返回结果
public static Object exec(HttpRequestBase httpRequestBase, int analyzeType) {
HttpResponse response = null;
try {
response = getConn().execute(httpRequestBase);
} catch (Exception e) {
e.printStackTrace();
logger.error("http执行出错", e.getMessage());
httpRequestBase.abort();
return null;
}
int statusCode = response.getStatusLine().getStatusCode();
String httpResult = "";
byte[] data = null;
if (HttpStatus.SC_OK == statusCode) {// 如果响应码是 200
try {
if(ANALYZE_TYPE_STR == analyzeType){ //解析str
httpResult = EntityUtils.toString(response.getEntity());
logger.info("http正常返回response:" + response.toString());
logger.info("http正常返回result:" + httpResult);
return httpResult;
}else if(ANALYZE_TYPE_BYTE == analyzeType){ //解析byte
data = EntityUtils.toByteArray(response.getEntity());
logger.info("http正常返回response:" + response.toString());
logger.info("http正常返回byte,数据大小:" + data.length);
return data;
}
} catch (Exception e) {
logger.error("http返回结果解析出错", e.getMessage());
}
} else {
logger.error("非正常返回结果:" + response.toString());
logger.error("http错误,返回状态码:" + statusCode);
httpRequestBase.abort();
return null;
}
return null;
}
}
3、发送短信
@Override
public boolean sendSmsPassword(String phoneNumber) {
if (Objects.isNull(phoneNumber)){
throw new JSYException(-1,"请先填写手机号");
}
String regex = "^((13[0-9])|(14[5-9])|(15([0-3]|[5-9]))|(16[6-7])|(17[1-8])|(18[0-9])|(19[1|3])|(19[5|6])|(19[8|9]))\\d{8}$";
if(!phoneNumber.matches(regex)){
throw new JSYException(-1,"请填写正确的手机号码!");
}
//接口的访问地址:写死
String url = "http://smsbanling.market.alicloudapi.com/smsapis";
//自己的appcode:阿里云市场-->已购买服务查看
String appCode = "abfc59f0cdbc4c038a2e804f9e9e37de";
Map<String, String> headers = new HashMap<>(1);
headers.put("Authorization", "APPCODE " + appCode);
//验证码生成
String code = String.valueOf((int)((Math.random()*9+1)*Math.pow(10,5)));
//模板
String template=",验证码5分钟内有效。工作人员不会向您索要,请勿向任何人泄露,以免造成账户或资金损失。";
Map<String, String> queryParam = new HashMap<>(2);
queryParam.put("mobile",phoneNumber);//手机号
queryParam.put("msg",code+template);//内容
queryParam.put("sign","e店保");//头标
//存redis 手机号为key 验证码为value
redisStateCache.cache(group+phoneNumber,code,5*60);//5分钟过期
//发送短信
HttpGet httpGet = MyHttpUtils.httpGet(url,queryParam);
MyHttpUtils.setHeader(httpGet,headers);
String result = (String) MyHttpUtils.exec(httpGet,1);//返回是否发送成功
//验证结果
if ( Objects.isNull(result) ){
return false;
}
Integer resultCode = JSON.parseObject(result).getInteger("result");
if( resultCode != 0 ){
return false;
}
return true;
}
4、工具类用到的依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.3</version>
</dependency>
5、通过官方模板方式发送
//TODO 后期重载几个方法 templateName不用传
public static Map<String,String> sendSmsCode(String phonenumber,String templateName) {
//TODO templateName模板名待申请
//TODO signName签名待申请(如有需要)
String regionId = "";
String accessKeyId = "";
String secret = "";
String signName = "";
DefaultProfile profile = DefaultProfile.getProfile(regionId,accessKeyId,secret);
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain("dysmsapi.aliyuncs.com");
request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
request.putQueryParameter("RegionId", regionId);
request.putQueryParameter("PhoneNumbers", phonenumber);
request.putQueryParameter("SignName", signName);
request.putQueryParameter("TemplateCode", templateName);
String randomCode = MyMathUtils.randomCode(4);
request.putQueryParameter("TemplateParam", "{\"code\":".concat(randomCode).concat("}"));
Map<String,String> resMap = null;
try {
CommonResponse response = client.getCommonResponse(request);
if(response != null && response.getData() != null){
JSONObject parseObject = JSONObject.parseObject(response.getData());
if("OK".equals(parseObject.getString("Message")) && "OK".equals(parseObject.getString("Code"))){
resMap = new HashMap<>();
resMap.put(phonenumber, randomCode);
}
}
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return resMap;
}
11、拉姆达Lambda表达式
ps:凡是能用Employee::getAge(通过类引用get方法) 都能写成 x->x.getAge() 通过对象获取
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
12、stream流式编程常规操作
1、filter()过滤->返回booler ture 留下的 false 过滤掉的
List<Order> orders = list.stream().filter(x -> {
if ("0".equals(x.getActivityUuid())){
return false;
}
if (!ids.contains(x.getActivityUuid())){
return false;
}
return true;
}).collect(Collectors.toList());
2、map()映射->会返回对象本身
List<Activity> collect = activities.stream().filter(x -> {
if (x.getCreatTime().toEpochSecond(ZoneOffset.of("+8")) == max.get()) {
return true;
}
return false;
}).map(x -> {
if (localTime >= x.getBeginTime().toEpochSecond(ZoneOffset.of("+8"))
&& localTime <= x.getEndTime().toEpochSecond(ZoneOffset.of("+8"))
&& x.getDeleted() != 0) {
x.setState(1);//进行中
}
if (x.getDeleted() == 0) {
x.setState(2);//已撤销
}
if (localTime > x.getEndTime().toEpochSecond(ZoneOffset.of("+8"))) {
x.setState(3);//已过期
}
if (localTime<x.getBeginTime().toEpochSecond(ZoneOffset.of("+8"))){
x.setState(4);//未开始
}
return x;
}).collect(Collectors.toList());
3、collect(收集)->聚合统计操作
//对集合中某个元素进行统计
IntSummaryStatistics collect = list.stream().collect(Collectors.summarizingInt(Employee::getAge));
System.out.println(collect.getMax());//最大
System.out.println(collect.getMin());//最小
System.out.println(collect.getCount());//个数
System.out.println(collect.getSum());//总和
System.out.println(collect.getAverage());//平均
//对集合中元素分组->指定一个属性
Map<Integer, List<Employee>> collect1 = list.stream().collect(Collectors.groupingBy(x -> x.getAge()));
4、sorted()排序
//这种写法不能根据这一元素倒排
list.stream().sorted(Comparator.comparing(x->x.getTime().toEpochSecond(ZoneOffset.of("+8"))));
//这种写法和上面一样
list.stream().sorted(Comparator.comparing(x -> {
return x.getTime().toEpochSecond(ZoneOffset.of("+8"));
}));
//这种可以倒排但属性不能格式化 比较死板
list.stream().sorted(Comparator.comparing(Employee::getAge).reversed());
5、特殊操作:map+max组合
//遍历所有元素,获取元素中某个属性最大的值 map+max
Optional<Long> max = list.stream().map(x -> x.getTime().toEpochSecond(ZoneOffset.of("+8"))).max((x, y) -> x.compareTo(y));
Optional<Long> max1 = list.stream().map(x -> x.getTime().toEpochSecond(ZoneOffset.of("+8"))).max(Long::compareTo);
Optional<Long> max2 = list.stream().map(x -> x.getTime().toEpochSecond(ZoneOffset.of("+8"))).max(Comparator.comparingLong(x->x));
if (max.isPresent()){//判断值是否存在
System.out.println(max.get());//如果max为 null 可能会报空指针
}else {
System.out.println("未找到");
//遍历所有元素,获取元素中某个属性最小的值 map+min
Optional<Long> min = list.stream().map(x -> x.getTime().toEpochSecond(ZoneOffset.of("+8"))).min((x, y) -> x.compareTo(y));
if (max.isPresent()){//判断值是否存在
System.out.println(min.get());//如果max为 null 可能会报空指针
}else {
System.out.println("未找到");
//遍历所有元素,获取元素中某个属性最大的那一条数据
Optional<Employee> max = list.stream().max(Comparator.comparing(x->x.getTime().toEpochSecond(ZoneOffset.of("+8"))));
// BigDecimal求和
BigDecimal reduce = roleEdit.stream().map(Entity::getNum).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
// 基本数据类型求和
sumGoods = carts.stream().mapToInt(Cart::getNum).sum();
13、Linux部署项目
1、ider打jar包
2、上传jar包到Linux文件夹中
3、启动jar包
java -jar o2o-eureka-service-1.0-SNAPSHOT.jar 本次运行,退出窗口就挂(显示启动内容)
nohup java -jar o2o-eureka-service-1.0-SNAPSHOT.jar >/mnt/logs/eureka.log 2>&1 & 后台永久运行jar,指定日志位置(是不显示启动内容的)
nohup java -jar o2o-eurnohup java -jar o2o-eureka-service-1.0-SNAPSHOT.jar >/null 2>&1 & 后台永久运行jar,不指定日志位置,可以项目配置(是不显示启动内容的)
ps aux|grep o2o-eureka-service-1.0-SNAPSHOT.jar 查看jar进程:有2条表示运行成功,第一条是该jar进程,第二为本次查看命令进程
kill -9 2976 杀死进程(关闭jar)
window 端口被占用
netstat -aon|findstr "8080"// 查看
taskkill /pid 15820 -t -f //关闭
14、关于ZUUL网关拦截
package com.jsy.basic.gen.filter;
import com.alibaba.fastjson.JSONObject;
import com.jsy.basic.util.utils.JwtUtils;
import com.jsy.basic.util.vo.CommonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
@Component
public class AuthFilter extends ZuulFilter{
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 1;
}
@Autowired
private StringRedisTemplate stringRedisTemplate;
/*让网关是否生效 true 生效 false不生效 为放行路径就不生效*/
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String url = request.getServletPath();
return ExcludeAuth.INSTANCE.shouldFilter(url);
}
/**
* 拦截请求,查询是否带了token
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
String jwtBearerToken = ctx.getRequest().getHeader("token");
String token = null;
if(jwtBearerToken!=null){
token = jwtBearerToken;
}
if (jwtBearerToken !=null && jwtBearerToken.startsWith("Bearer ")){
token = jwtBearerToken.substring(7);
}
if (Objects.nonNull(token)&& StringUtils.isNotEmpty(stringRedisTemplate.opsForValue().get("Login:" + token))) {//可能在redis
ctx.setSendZuulResponse(true);
ctx.set("isAuthSuccess", true);
}else if (Objects.nonNull(token) && JwtUtils.checkToken(token)){//可能在jwt
ctx.setSendZuulResponse(true);
ctx.set("isAuthSuccess", true);
} else {
ctx.setSendZuulResponse(false);
ctx.set("isAuthSuccess", false);
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
ctx.getResponse().setContentType("application/json;charset=UTF-8");
ctx.setResponseBody(JSONObject.toJSONString(CommonResult.error(-2,"未经授权的调用")));//对象转成JSON返回
}
return null;
}
}
package com.jsy.basic.gen.filter;
import java.util.ArrayList;
import java.util.List;
/**
* 不需要做token认证的URl
*/
public enum ExcludeAuth {
INSTANCE;
private List<String> execludeUrls = new ArrayList<>();
ExcludeAuth(){
/*execludeUrls.add("/services/user/user/token");//用户登陆*/
}
public boolean shouldFilter(String url) {
if(url.matches(".*swagger.*")){
return false;
}
if(url.matches(".*api-docs")){
return false;
}
if(url.matches(".*/pub/.*")){
return false;
}
if(url.matches(".*/order/.*")){
return true;
}
if(execludeUrls.contains(url)) {
return false;
}
return true;
}
}
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>o2o-support-parent</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>o2o-zuul-gateway</artifactId>
<dependencies>
<!--zuul的场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--eureka客户端的场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--网关配置swagger-->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>o2o-basic-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- springboot main方法入口 -->
<mainClass>com.jsy.basic.gen.ZuulGatewayApplication</mainClass>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
15、微信登录、微信支付下单
项目源码:https://gitee.com/li-jin-119/xdvideo.git
1、微信开放平台介绍(申请里面的网站应用需要企业资料)
网站:https://open.weixin.qq.com/
2、什么是appid、appsecret、授权码code
appid和appsecret是 资源所有者向申请人分配的一个id和秘钥
code是授权凭证,A->B 发起授权,想获取授权用户信息,那a必须携带授权码,才可以向B获取授权信息
(你要从我这里拿东西出去,就必须带身份证)
--------------------------------微信登录功能-------------------------------------------
3、微信登录功能官方文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN
4、唤起官方二位码微信登录链接(我们需要拼装url)-》请求CODE
模板:private final static String OPEN_QRCODE_URL= "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect";
(%S) 表示占位符:用string.format()方法拼装,返回给前端
5、用户扫描二维码,确认登录后,微信回调我们接口,并传给我们一个CODE参数(开发者授权回调域名+接口路径)
ps:需要内网穿透
6、code换取微信用户的access_token+openId
请求接口:https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE",
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
7、access_token+openId换取用户基本信息
请求接口 https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN
{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
--------------------------------微信支付功能-------------------------------------------
8、扫码支付文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=2_2
9、需要用到的名称理解:
appid:公众号唯一标识
appsecret:公众号的秘钥
mch_id:商户号,申请微信支付的时候分配的
key:支付交易过程生成签名的秘钥,设置路径
微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置
10、和微信支付交互方式
1、post方式提交
2、xml格式的协议
3、签名算法MD5
4、交互业务规则 先判断协议字段返回,再判断业务返回,最后判断交易状态
5、接口交易价格单位为 分
6、交易类型:JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
11、安全规范:
签名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
校验工具:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1
12、微信支付交互时序流程图
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
13、微信支付下单API开发
1、需要签名sign,生成签名
//生成签名
SortedMap<String,String> params = new TreeMap<>();
params.put("appid",weChatConfig.getAppId());
params.put("mch_id", weChatConfig.getMchId());
params.put("nonce_str",CommonUtils.generateUUID());
params.put("body",videoOrder.getVideoTitle());
params.put("out_trade_no",videoOrder.getOutTradeNo());
params.put("total_fee",videoOrder.getTotalFee().toString());
params.put("spbill_create_ip",videoOrder.getIp());
params.put("notify_url",weChatConfig.getPayCallbackUrl());
params.put("trade_type","NATIVE");
//sign签名 params+key
String sign = WXPayUtil.createSign(params, weChatConfig.getKey());
params.put("sign",sign);
//map参数转xml
String payXml = WXPayUtil.mapToXml(params);
//生成签名后,通过工具去校验
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1
2、调用官方下单接口
https://api.xdclass.net/pay/unifiedorder
//统一下单
String orderStr = HttpUtils.doPost(WeChatConfig.getUnifiedOrderUrl(),payXml,4000);
//微信统一下单响应
<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx5beac15ca207c40c]]></appid>
<mch_id><![CDATA[1503809911]]></mch_id>
<nonce_str><![CDATA[Go5gDC2CYL5HvizG]]></nonce_str>
<sign><![CDATA[BC62592B9A94F5C914FAAD93ADE7662B]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx262207318328044f75c9ebec2216783076]]></prepay_id>
<trade_type><![CDATA[NATIVE]]></trade_type>
<code_url><![CDATA[weixin://wxpay/bizpayurl?pr=hFq9fX6]]></code_url>
</xml>
//xml结果转map
Map<String, String> unifiedOrderMap = WXPayUtil.xmlToMap(orderStr);
//返回code_url
if(unifiedOrderMap != null) {
return unifiedOrderMap.get("code_url");
}
//使用谷歌二维码工具根据code_url生成扫一扫支付二维码
Map<EncodeHintType,Object> hints = new HashMap<>();
hints.put(EncodeHintType.ERROR_CORRECTION,ErrorCorrectionLevel.L);
hints.put(EncodeHintType.CHARACTER_SET,"UTF-8");
BitMatrix bitMatrix = new MultiFormatWriter().encode(codeUrl,BarcodeFormat.QR_CODE,400,400,hints);
OutputStream out = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix,"png",out);
//访问测试地址:localhost:8081/api/v1/order/add?video_id=2 得到支付二维码
14、用户扫描二维码,并支付之后,微信会以post方式回调我们的接口
#微信支付回调地址 域名+接口路径 (需要内网穿透)
wxpay.callback=http://arli123.natapp1.cc/api/v1/wechat/order/callback
public void orderCallback(HttpServletRequest request,HttpServletResponse response) throws Exception { //以流的方式传进来,读取回调输入流
InputStream inputStream = request.getInputStream();
//BufferedReader是包装设计模式,性能更高
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8"));
//把流进行拼接,就是一个xml
StringBuffer sb = new StringBuffer();
String line;
while ((line = in.readLine()) != null){
sb.append(line);
}
in.close();
inputStream.close();
}
//xml字符串转map
Map<String,String> callbackMap = WXPayUtil.xmlToMap(sb.toString());
//map转有序map
SortedMap<String,String> sortedMap = WXPayUtil.getSortedMap(callbackMap);
//判断签名是否正确
if(WXPayUtil.isCorrectSign(sortedMap,weChatConfig.getKey())){
if("SUCCESS".equals(sortedMap.get("result_code"))){
String outTradeNo = sortedMap.get("out_trade_no");
VideoOrder dbVideoOrder = videoOrderService.findByOutTradeNo(outTradeNo);
if(dbVideoOrder != null && dbVideoOrder.getState()==0){ //判断逻辑看业务场景
VideoOrder videoOrder = new VideoOrder();
videoOrder.setOpenid(sortedMap.get("openid"));
videoOrder.setOutTradeNo(outTradeNo);
videoOrder.setNotifyTime(new Date());
videoOrder.setState(1);
int rows = videoOrderService.updateVideoOderByOutTradeNo(videoOrder);
if(rows == 1){ //------response通知微信订单处理成功
response.setContentType("text/xml");
response.getWriter().println("success");
return;
}
}
}
}
//都处理失败-----response通知微信订单处理失败
response.setContentType("text/xml");
response.getWriter().println("fail");
}
#=================================微信相关====================================
#微信开放平台
#登录配置
wxopen.appid=wx025575eac69a2d5b
wxopen.appsecret=deeae310a387fa9d3e8f3830ce64caac
#重定向url ps:http://16webtest.ngrok.xiaomiqiu.cn 是开发者用户独有的授权回调域名 ;/api/v1/wechat/user/callback 会自动找到我们服务器(但是存在内网穿透),微信平台回调到这个接口
#微信登录回调地址 开发者授权回调域名+接口路径 (需要内网穿透)
wxopen.redirect_url=http://16webtest.ngrok.xiaomiqiu.cn/api/v1/wechat/user/callback
#公众号
wxpay.appid=wxk26lid61mte1z5a7
wxpay.appsecret=i031l305dk558l6757ufy9hb649f1760
#微信商户 平台
#支付配置
wxpay.mer_id=0582244336
wxpay.key=XL45y60225PVh1ZaQg9m7LFH0Ygq02vr
#微信支付回调地址 域名+接口路径 (需要内网穿透)
wxpay.callback=http://arli123.natapp1.cc/api/v1/wechat/order/callback
package net.xdclass.xdvideo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* 微信配置类
*/
@Configuration
@PropertySource(value="classpath:application.properties")
public class WeChatConfig {
/**
* 公众号appid
*/
@Value("${wxpay.appid}")
private String appId;
/**
* 公众号秘钥
*/
@Value("${wxpay.appsecret}")
private String appsecret;
/**
* 开放平台appid
*/
@Value("${wxopen.appid}")
private String openAppid;
/**
* 开放平台appsecret
*/
@Value("${wxopen.appsecret}")
private String openAppsecret;
/**
* 开放平台回调url
*/
@Value("${wxopen.redirect_url}")
private String openRedirectUrl;
/**
* 微信开放平台二维码连接
*/
private final static String OPEN_QRCODE_URL= "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect";
/**
* 开放平台获取access_token地址
*/
private final static String OPEN_ACCESS_TOKEN_URL="https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
/**
* 获取用户信息
*/
private final static String OPEN_USER_INFO_URL ="https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
/**
* 商户号id
*/
@Value("${wxpay.mer_id}")
private String mchId;
/**
* 支付key
*/
@Value("${wxpay.key}")
private String key;
/**
* 微信支付回调url
*/
@Value("${wxpay.callback}")
private String payCallbackUrl;
/**
* 统一下单url 接口地址
*
* 测试地址: https://api.xdclass.net/pay/unifiedorder
* 微信官方URL地址1:https://api.mch.weixin.qq.com/pay/unifiedorder
* 微信官方URL地址2:https://api2.mch.weixin.qq.com/pay/unifiedorder(备用域名)见跨城冗灾方案
*/
private static final String UNIFIED_ORDER_URL = "https://api.xdclass.net/pay/unifiedorder";
public static String getUnifiedOrderUrl() {
return UNIFIED_ORDER_URL;
}
public String getMchId() {
return mchId;
}
public void setMchId(String mchId) {
this.mchId = mchId;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getPayCallbackUrl() {
return payCallbackUrl;
}
public void setPayCallbackUrl(String payCallbackUrl) {
this.payCallbackUrl = payCallbackUrl;
}
public static String getOpenUserInfoUrl() {
return OPEN_USER_INFO_URL;
}
public static String getOpenAccessTokenUrl() {
return OPEN_ACCESS_TOKEN_URL;
}
public static String getOpenQrcodeUrl() {
return OPEN_QRCODE_URL;
}
public String getOpenAppid() {
return openAppid;
}
public void setOpenAppid(String openAppid) {
this.openAppid = openAppid;
}
public String getOpenAppsecret() {
return openAppsecret;
}
public void setOpenAppsecret(String openAppsecret) {
this.openAppsecret = openAppsecret;
}
public String getOpenRedirectUrl() {
return openRedirectUrl;
}
public void setOpenRedirectUrl(String openRedirectUrl) {
this.openRedirectUrl = openRedirectUrl;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getAppsecret() {
return appsecret;
}
public void setAppsecret(String appsecret) {
this.appsecret = appsecret;
}
}
16、redis实现秒杀业务
11
ps:
1、FeignClient调用controller:使用@RequestParam注解必须加上参数名字
@GetMapping("/newUser/pub/isNewUser")
IsNewUserDTO isNewUser(@RequestParam("shopUuid") String shopUuid, @RequestParam("userUuid") String userUuid);
2、使用@JsonFormat实体类总是序列化失败问题
@Bean
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
ps:返回对象包含嵌套类,都需要加上jsonFormat注解 并且格式对应
3、关于列表的接口
CRUD接口中,只要存在一个查询接口,就只需要传入查询出来对象,传入对象的id;所以可以根据id修改数据,也不需要对更新接口(添加、删除、修改)做token验证了
4、关于sql分组知识 https://www.cnblogs.com/friday69/p/9389720.html
ps:mybaties的分页方法 sqlHaving 参数 就是需要显示的sql语句字段,params就是按那个字段分组
5、关于设置ider仓库地址
6、ider启动报错 启动名太长 或者 JRE版本不兼容 需要打开启动类EditConfig 修改启动类的配置为Jar方式启动
再查看JRE栏的默认配置是哪个JDK版本
7、雪花算法
package com.jsy.community.utils;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author chq459799974
* @description 雪花算法生成ID
* @since 2020-12-09 17:22
**/
public class SnowFlake {
/**
* 起始的时间戳
*/
private final static long START_STMP = 1607504720678L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATACENTER_BIT = 5;//数据中心占用的位数
/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private static long datacenterId = 1; //数据中心
private static long machineId = 1; //机器标识
private static long sequence = 0L; //序列号
private static long lastStmp = -1L;//上一次时间戳
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
SnowFlake.datacenterId = datacenterId;
SnowFlake.machineId = machineId;
}
/**
* 产生下一个ID
*
* @return
*/
public static synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("系统时间出错,无法生成ID");
}
if (currStmp == lastStmp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastStmp = currStmp;
return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
| datacenterId << DATACENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
private static long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private static long getNewstmp() {
return System.currentTimeMillis();
}
private static final ThreadPoolExecutor threadpool = new ThreadPoolExecutor(50, 500, 5, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100000));
private static void main(String[] args) throws InterruptedException {
// SnowFlake snowFlake = new SnowFlake(1, 1);
// System.out.println(snowFlake.nextId());
// System.out.println(snowFlake.lastStmp);
// System.out.println(snowFlake.nextId());
// System.out.println(snowFlake.lastStmp);
for (int i=0;i<3;i++){
threadpool.submit(new Runnable() {
@Override
public void run() {
System.out.println(SnowFlake.nextId());
System.out.println(SnowFlake.lastStmp);
}
});
}
// threadpool.submit(new Runnable() {
// @Override
// public void run() {
// System.out.println(SnowFlake.nextId());
// System.out.println(SnowFlake.lastStmp);
// }
// });
// System.out.println(SnowFlake.nextId());
// System.out.println(SnowFlake.lastStmp);
// System.out.println(SnowFlake.nextId());
// System.out.println(SnowFlake.lastStmp);
// for (int i = 0; i < (1 << 12); i++) {
// System.out.println(snowFlake.nextId());
// }
}
}
8.关于BigDecimal加减乘除计算 https://www.jianshu.com/p/683b2406342f
加法:add()函数
减法:subtract()函数
乘法:multiply()函数
除法:divide()函数
绝对值:abs()函数
注意: //尽量用字符串的形式初始化BigDecimal
BigDecimal stringFir = new BigDecimal("0.005");
BigDecimal stringSec = new BigDecimal("1000000");
BigDecimal stringThi = new BigDecimal("-1000000");