“::” 和 “->” 都是Java 8中引入的Lambda表达式的一部分,用于简化代码和增强语言的函数式编程能力。
“::” 符号通常称为“方法引用”,用于引用已有的方法或构造函数,并将其作为Lambda表达式的参数。具体来说,方法引用可以将方法名和参数列表与类或对象的名称分开,以简化代码和提高可读性。方法引用的形式有以下几种:
(x) -> ClassName.staticMethodName(x)
可以使用静态方法引用的方式进行简化:
ClassName::staticMethodName
(x) -> instanceName.methodName(x)
可以使用实例方法引用的方式进行简化:
instanceName::methodName
() -> new ClassName()
可以使用构造函数引用的方式进行简化:
ClassName::new
(x, y) -> x + y
其中,参数列表指定Lambda表达式的参数,箭头符号 “->” 分隔参数列表和表达式,表达式则指定Lambda表达式要执行的操作。
(x, y) -> {
int sum = x + y;
System.out.println(sum);
}
可以理解为:
- 参数列表为 (x, y)
- 箭头符号 “->” 分隔参数列表和表达式
- 表达式为 { int sum = x + y; System.out.println(sum); }
需要注意的是,Lambda表达式和方法引用的使用都需要在Java 8及以上版本中才能实现。同时,还需要根据具体情况进行Lambda表达式的编写和调用,以确保代码的正确性和可读性。
foreach 和 map 都是 Java 8 中的流操作,它们都可以帮助我们处理数据,但是它们有着明显的不同。
foreach 方法是一种遍历操作,它可以对流中的每一个元素执行某个操作,但是不会返回任何结果,它只是简单地对数据进行处理。
例如,我们有一个列表,列表中的每一个元素代表一个字符串,我们希望将列表中的所有字符串打印出来。我们可以使用 foreach 方法实现这个需求:
mathematicaCopy codeList list = Arrays.asList("apple", "banana", "cherry");
list.stream().forEach(System.out::println);
map 方法是一种映射操作,它可以对流中的每一个元素执行某个操作,并将操作的结果作为新的流的元素。
例如,我们有一个列表,列表中的每一个元素代表一个整数,我们希望将列表中的每一个整数乘以 2,并得到一个新的列表。我们可以使用 map 方法实现这个需求:
mathematicaCopy codeList list = Arrays.asList(1, 2, 3, 4, 5);
List result = list.stream().map(x -> x * 2).collect(Collectors.toList());
因此,foreach 和 map 都是 Java 8 中的流操作,它们都可以帮助我们处理数据,但是它们的目的和实现方式不同,需要根据具体的需求
map 方法与遍历的不同在于,它不仅仅遍历了流中的元素,还对每一个元素执行了一个操作,并将操作的结果作为新的流的元素。
因此,map 方法可以说是遍历,但是它不仅仅是遍历,它还是一种高效、易于使用、易于并行计算的数据处理方式。
总的来说,map 方法代表着一种将流中的元素进行处理,并将处理结果作为新的流的元素的操作。
般来说,map 方法不能代替 foreach 方法,因为它们的目的和实现方式不同。
foreach 方法是一种遍历操作,它可以对流中的每一个元素执行某个操作,但是不会返回任何结果,它只是简单地对数据进行处理。
而 map 方法是一种映射操作,它可以对流中的每一个元素执行某个操作,并将操作的结果作为新的流的元素。
因此,如果你需要遍历流中的元素,执行某个操作,但是不需要返回任何结果,那么应该使用 foreach 方法。如果你需要对流中的元素进行处理,并得到一个新的流,那么应该使用 map 方法。
BigDecimal 类型是一种高精度的数字类型,可以处理任意精度的十进制数,并提供了许多算术运算方法,例如加、减、乘、除、求余等等。与其他数字类型不同,BigDecimal 类型能够处理小数点后的任意位数,从而避免了浮点数计算时可能出现的舍入误差和精度丢失问题。
在处理金额时,由于金额通常需要精确到小数点后两位,因此使用 BigDecimal 类型可以保证计算结果的精度和正确性。同时,需要注意在创建 BigDecimal 对象时,应该使用字符串类型的构造方法,避免使用 double 或 float 类型转换而导致精度丢失。
List<PaymentInfoVO> list = ...; // 假设这是一个 PaymentInfoVO 类型的列表
BigDecimal totalAmount = list.stream()
.map(paymentInfoVO -> new BigDecimal(paymentInfoVO.getAmount()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println(totalAmount); // 输出 paymentInfoVO.getAmount() 的总和
在这个代码段中,我们使用 .map()
方法将每个 paymentInfoVO.getAmount()
的值转换为一个 BigDecimal 类型的对象,然后使用 .reduce()
方法计算它们的总和。其中,.map(paymentInfoVO -> new BigDecimal(paymentInfoVO.getAmount()))
表示将每个 paymentInfoVO.getAmount()
的值转换为一个 BigDecimal 类型的对象,.reduce(BigDecimal.ZERO, BigDecimal::add)
表示将所有 BigDecimal 类型的对象相加得到它们的总和,并使用 BigDecimal 类型来确保计算精度和正确性。最后,我们将结果保存在 totalAmount
变量中,并输出它的值。
总之,在处理金额计算时,使用 BigDecimal 类型可以确保计算结果的精度和正确性。如果要在 Stream 流中计算金额的总和,可以使用 .map()
方法将每个金额值转换为 BigDecimal 类型的对象,然后使用 .reduce()
方法计算它们的总和,并使用 BigDecimal 类型来确保计算精度和正确性。
BigDecimal.ZERO
是什么BigDecimal.ZERO 是 BigDecimal 类的一个静态常量,表示一个值为零的 BigDecimal 对象。
在使用 BigDecimal 类进行数值计算时,有时需要初始化一个初始值为零的 BigDecimal 对象,这时可以使用 BigDecimal.ZERO 来创建这个对象。例如:
BigDecimal number = BigDecimal.ZERO;
在这个例子中,我们使用 BigDecimal.ZERO 创建了一个值为零的 BigDecimal 对象,并将其赋值给 number
变量。由于 BigDecimal 类是不可变的,因此在对它进行运算或修改时,会返回一个新的 BigDecimal 对象,不会修改原始的 BigDecimal 对象。
因此,使用 BigDecimal.ZERO 可以方便地创建一个初始值为零的 BigDecimal 对象,并在 BigDecimal 类的计算中使用它。同时,由于 BigDecimal 类是高精度的十进制数类型,因此在处理数值计算时应该使用 BigDecimal 类型,并避免使用浮点数类型来确保计算结果的精度和正确性。
BigDecimal.ONE
在使用 BigDecimal 类进行数值计算时,有时需要初始化一个值为 1 的 BigDecimal 对象,这时可以使用 .ONE
来创建这个对象。例如:
BigDecimal number = BigDecimal.ONE;
在 Java 中,可以使用 BigDecimal 类型的 multiply() 方法来进行乘法运算。该方法接受一个 BigDecimal 类型的参数,表示要乘以的值,并返回一个新的 BigDecimal 对象,表示乘法运算的结果。
以下是一个示例代码,演示如何使用 BigDecimal 类型进行乘法运算:
BigDecimal num1 = new BigDecimal("12.34"); // 创建一个 BigDecimal 对象,表示第一个数值
BigDecimal num2 = new BigDecimal("5.67"); // 创建一个 BigDecimal 对象,表示第二个数值
BigDecimal result = num1.multiply(num2); // 使用 multiply() 方法进行乘法运算
System.out.println(result); // 输出乘法运算的结果
要进行加法和减法运算,可以使用BigDecimal类提供的add()和subtract()方法。
例如,如果要将availableAmount和settlementAmount相加,可以使用以下代码:
BigDecimal newAvailableAmount = availableAmount.add(settlementAmount);
如果要将willpayAmount和settlementAmount相减,可以使用以下代码:
BigDecimal newWillpayAmount = willpayAmount.subtract(settlementAmount);
在使用BigDecimal进行加减法运算时,需要注意以下几点:
compareTo()
方法是Java中Comparable
接口的一个方法,用于比较两个对象的大小。在BigDecimal
类中,这个方法用于比较两个BigDecimal
对象的数值大小。
compareTo()
方法的返回值如下:
以下是一个使用compareTo()
方法的简单示例:
import java.math.BigDecimal;
public class BigDecimalCompareExample {
public static void main(String[] args) {
BigDecimal number1 = new BigDecimal("10.5");
BigDecimal number2 = new BigDecimal("20.5");
int comparisonResult = number1.compareTo(number2);
if (comparisonResult < 0) {
System.out.println("number1小于number2");
} else if (comparisonResult == 0) {
System.out.println("number1等于number2");
} else {
System.out.println("number1大于number2");
}
}
}
在这个示例中,我们创建了两个BigDecimal
对象number1
和number2
,然后使用compareTo()
方法对它们进行比较。根据返回值,我们可以确定它们之间的大小关系。
在 MyBatis-Plus 中,save
和 insert
方法都可以用来向数据库中插入一条新记录,但它们在使用上有以下几个区别:
save
方法的参数类型是实体对象(Entity),而 insert
方法的参数类型是 Wrapper 对象或者实体对象(Entity)。save
方法的返回值是一个 boolean 类型,表示插入操作是否成功,而 insert
方法的返回值是受影响的行数。save
方法会根据实体对象的主键属性是否有值来判断是插入还是更新操作,如果主键属性有值,就执行更新操作,否则执行插入操作;而 insert
方法只执行插入操作,不进行主键冲突检测。因此,如果希望在插入新记录的同时进行主键冲突检测,可以使用 save
方法;如果只是简单地插入新记录,可以使用 insert
方法。
insert,add和save都是数据库操作中常用的用于插入数据的方法,不同的是在不同的数据库操作框架或者实现中可能有些许差异。
在一些ORM框架中,如Hibernate和MyBatis,它们的含义和用法如下:
需要注意的是,具体的操作方式和用法可能会因为具体的ORM框架或者实现而有所差异,因此在使用这些方法时,应该根据具体的情况进行使用和选择。
对于insert、add和save这三个方法,哪个更好需要根据具体的使用情况和需求进行选择。
一般来说,如果只是简单的插入一条新的记录,那么使用insert或add都可以,它们的实现方式和性能差异不大。但如果需要同时插入多条记录,或者需要支持一些高级的插入操作,那么就需要根据具体情况选择更为适合的方法。
在使用ORM框架时,一般建议使用ORM框架提供的方法进行操作,以便能够充分利用框架的特性和优化。但在某些特殊情况下,也可以考虑使用原生SQL语句进行操作,以获得更好的性能和控制力。
综上所述,需要根据具体的使用情况和需求进行选择,选择更为适合的方法。
除了insert、add和save方法,还有其他一些更为适合的方法可以用于向数据库中插入数据,如:
需要根据具体的使用情况和需求选择更为适合的方法,以获得更好的性能和控制力。
.eq
方法是用于创建一个相等条件的查询,例如:
new LambdaQueryWrapper<User>().eq(User::getName, "John");
上面的代码表示在User
表中查询所有名字等于"John"的记录。
.in
方法则用于创建一个包含条件的查询,例如:
new LambdaQueryWrapper<User>().in(User::getId, Arrays.asList(1, 2, 3));
上面的代码表示在User
表中查询所有id
为1、2或3的记录。
因此,.eq
用于创建单个条件,而.in
用于创建多个条件。
如果你想通过姓名和身份证获取返回值,但是姓名和身份证不在一张表怎么办,你可能需要使用LambdaQueryWrapper的join方法来连接两张表,然后使用eq方法来指定查询条件。例如:
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.join(User::getId, Staff::getUserId)// 连接User表和Staff表
.eq(User::getName, “张三”) // 查询姓名为张三
.eq(Staff::getIdCard, “123456789”); // 查询身份证为123456789
List list = userService.list(wrapper); // 获取返回值
.join方法是LambdaQueryWrapper中用于实现多表联查的方法,它可以指定要连接的表和连接条件,例如:
- wrapper.join(User::getDeptId, Dept::getId)表示连接User表和Dept表,条件是User.dept_id = Dept.id。
- wrapper.leftJoin(User::getDeptId, Dept::getId)表示左连接User表和Dept表,条件是User.dept_id = Dept.id。
- wrapper.rightJoin(User::getDeptId, Dept::getId)表示右连接User表和Dept表,条件是User.dept_id = Dept.id。
使用.join方法时,需要注意以下几点:
- .join方法只能用在自定义的mapper接口中,不能用在mybatis-plus提供的通用mapper接口中。
- .join方法需要配合.select方法来指定查询的字段,否则会报错。
- .join方法不支持嵌套查询或子查询,如果需要复杂的多表联查,请使用xml文件或注解方式。
qwPayOrder
.ne( "rec_state" , 200 ) // 排除 rec_state 为 200 的 订单 这边加入了对时间的限制
.in("state", 60, 100) // 筛选 60 状态 和 100 状态 的 订单
.apply( "YEAR(update_time) = {0} AND MONTH(update_time) = {1}", year, month )
.select(
"IFNULL(SUM(CASE WHEN state = 100 THEN total_amount ELSE 0 END) + SUM(CASE WHEN state = 60 THEN total_amount - settled_amount ELSE 0 END), 0) as totalAmount",
"IFNULL(SUM(CASE WHEN state = 100 THEN total_service_amount ELSE 0 END) + SUM(CASE WHEN state = 60 THEN total_service_amount - settled_service_amount ELSE 0 END), 0) as totalServiceAmount",
"company_id"
)
.groupBy("company_id");
在这段代码中,apply
方法的作用是将传入的字符串参数应用到查询条件中。这里传入了一个字符串格式化表达式,其中 {0}
和 {1}
分别表示 year
和 month
参数,用来限定 update_time
的年份和月份。在实际查询时,year
和 month
会替换 {0}
和 {1}
,从而构成查询条件。例如,如果 year
为 2023,month
为 4,则查询条件为 YEAR(update_time) = 2023 AND MONTH(update_time) = 4
。这样就实现了对 update_time
时间的限制。
数据库索引是一种数据结构,用于快速查找数据库表中的数据。它是数据库管理系统(DBMS)用来优化查询性能的重要工具之一。通过在数据库表的一个或多个列上创建索引,可以显著提高查询效率,特别是在数据量较大时。索引可以是唯一的,也可以不是唯一的,取决于在创建索引时是否强制唯一性。在查询时,DBMS使用索引来定位需要的数据行,而不是扫描整个表,从而提高了查询效率。然而,索引也会占用额外的存储空间,并增加数据修改的成本,因此需要权衡索引的使用。
这是三个数据库表的索引信息:
company_id
ASC NORMAL BTREE:表示在该表中建立了一个名为 “employer_id” 的索引,该索引按照 “company_id” 字段的升序进行排序,索引类型是 BTREE。(BTREE 是一种数据库索引类型,它是一种平衡树结构的实现,用于加快数据库表中数据的查询速度。索引是一种数据结构,能够在进行查询时快速地定位到满足特定条件的数据。BTREE 索引使用一种基于二分查找的算法,将数据按照一定的规则保存在其中,并使用平衡树的思想减少搜索树的深度,快速找到需要的数据。这种索引类型通常用于数据量较大的表中,因为它可以显著地提高查询效率,并且对于定期更新的表也具有较高的效率。)bank_main_account_id
ASC NORMAL BTREE:表示在该表中建立了一个名为 “bank_main_account_id” 的索引,该索引按照 “bank_main_account_id” 字段的升序进行排序,索引类型是 BTREE。public_id
ASC NORMAL BTREE:表示在该表中建立了一个名为 “public_id” 的索引,该索引按照 “public_id” 字段的升序进行排序,索引类型是 BTREE。索引是数据库中用于快速查找和排序数据的一种数据结构。它可以提高数据检索的速度和效率,尤其是当数据量很大时。
ACS是索引的一种类型,表示使用了“自适应哈希索引”(Adaptive Hash Indexing)的优化技术。在数据库中,ACS索引类型通常用于高并发的查询操作,能够显著提高查询效率。
在MySQL中,索引默认是升序的,所以这里的ASC可以省略不写。如果要定义为降序,则需要写DESC。
这是一个数据库外键约束,约束名为
bank_main_account_ibfk_1
,表示在bank_main_account
表中有一个外键park_id
,它引用了park
表的主键id
。约束条件为RESTRICT
,表示当park
表中被引用的主键记录被删除或更新时,会阻止在bank_main_account
表中的外键记录被删除或更新。具体来说,当试图删除或更新park
表中的一个主键记录时,如果在bank_main_account
表中存在引用该主键记录的外键记录,则该操作将被拒绝并抛出一个异常。
注意
:当一个有 @Transactional
注解的 public 方法调用一个 private 方法时,private 方法会继承 public 方法的事务,它们属于同一个事务。也就是说,private 方法不会单独开启新的事务,但它会成为调用它的 public 方法事务的一部分。
在Spring框架中,事务指的是一组数据库操作,这些操作要么全部执行成功,要么全部执行失败,确保了数据库操作的一致性和完整性。
具体来说,事务通常涉及到数据库的增、删、改等操作,这些操作需要在同一个事务中进行,以保证数据的一致性。如果一个事务中的任何一次数据库操作失败,整个事务都会被回滚,所有已经执行的操作都会被撤销,数据库恢复到事务执行前的状态。
因此,在需要对数据库进行多个操作的情况下,使用事务可以确保操作的原子性,以避免数据不一致的问题。而Spring框架提供的事务管理功能可以简化事务的处理,降低了代码的复杂度,提高了开发效率。
在数据库中,通常有四种类型的索引,包括唯一索引、主键索引、Normal索引和全文索引。这些索引的作用如下:
综上所述,不同类型的索引都有其独特的作用和用途,您可以根据具体的数据需求选择适当的索引类型来提高数据库的性能和查询效率。
多表联查确实可能对性能产生影响,特别是当表中数据量很大时。这里有一些方法可以帮助避免或优化多表联查的性能问题:
选择性地查询所需列:尽量只查询所需的列,而不是使用 SELECT * 从每个表中获取所有列。
使用索引:确保在查询中涉及的所有表上都创建了合适的索引。这可以帮助数据库快速查找需要的数据,从而提高查询性能。
使用 INNER JOIN 代替 OUTER JOIN:在可能的情况下,尽量使用 INNER JOIN 代替 OUTER JOIN。INNER JOIN 通常比 OUTER JOIN 更快,因为它只返回匹配的行。
减少表的连接数:在可能的情况下,尝试减少查询中涉及的表的数量。例如,如果可以通过子查询或临时表的方式减少表的连接数,这可能有助于提高查询性能。
优化 WHERE 子句:对 WHERE 子句进行优化,例如使用 AND 而不是 OR,避免使用 NOT IN,使用 EXISTS 或 NOT EXISTS 代替。
优化查询顺序:尝试调整查询中的表连接顺序,以便先过滤掉更多的无关数据。这样可以减少后续连接操作的计算量。
分页查询:如果查询结果集很大,可以考虑使用 LIMIT 和 OFFSET 子句进行分页查询,每次只返回一部分数据。这样可以减轻数据库负担并提高用户体验。
使用数据库优化器的建议:大多数数据库系统都有查询优化器,可以分析查询并提供优化建议。根据优化器的建议调整查询,以提高查询性能。
将计算移到应用程序中:在某些情况下,可以考虑将一部分计算从数据库查询中移出,将其移到应用程序代码中。这可以减轻数据库的负担,但可能会增加应用程序的复杂性。
物化视图:对于经常需要执行的复杂查询,可以考虑使用物化视图。物化视图是预先计算好的查询结果集,可以大大提高查询性能。但请注意,物化视图需要定期更新以保持数据的实时性。
请注意,优化查询性能是一个复杂的过程,可能需要根据具体的查询和数据库系统进行调整。在进行优化时,请务必确保查询的正确性和数据的一致性。
在Java中,如果需要将JSON字符串转换成Java对象,通常可以使用ObjectMapper类来实现。ObjectMapper类是Jackson库中的一个核心类,可以将JSON字符串转换成Java对象,并将Java对象转换成JSON字符串。
在转换JSON字符串时,通常需要使用Java的泛型来指定转换后的数据类型。但是,如果只是简单地使用泛型,可能会出现类型擦除的问题,导致转换失败。为了避免这种问题,可以使用TypeReference类来获取map中的数据类型,确保转换的正确性。
TypeReference类是Jackson库中的一个工具类,可以用来获取泛型类型的具体类型。例如,在将JSON字符串转换成Map
ObjectMapper mapper = new ObjectMapper();
String jsonStr = "{\"key\":\"value\"}";
Map<String, Object> map = mapper.readValue(jsonStr, new TypeReference<Map<String, Object>>() {});
在这个示例中,使用TypeReference类来获取Map
在上面的示例中,也使用了TypeReference类来获取map中的数据类型,具体代码如下:
Map<String, Object> map = mapper.readValue(requestAckData, new TypeReference<Map<String, Object>>() {});
在将JSON字符串转换成Map
因此,TypeReference类可以用来获取泛型类型的具体类型,确保转换的正确性,避免类型擦除的问题。
<profiles>
<profile>
<!-- 个人profile的id -->
<id>myprofile</id>
<!-- 个人profile的配置 -->
<properties>
<my.property>myvalue</my.property>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.example</groupId>
<artifactId>my-plugin</artifactId>
<version>1.0</version>
<!-- 指定个人profile的id -->
<profiles>
<profile>myprofile</profile>
</profiles>
</plugin>
</plugins>
</build>
mvn clean install -Pmyprofile
//默认头信息,创建自定义的httpclient对象
List<Header> defaultHeaders=new ArrayList<Header>();
defaultHeaders.add(new BasicHeader("Accept","text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8"));
defaultHeaders.add(new BasicHeader("Accept-Language","zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"));
defaultHeaders.add(new BasicHeader("Connection","close"));
defaultHeaders.add(new BasicHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0"));
defaultHeaders.add(new BasicHeader("Authorization", ""));
这段代码定义了HTTP请求的默认头部信息,具体含义如下:
- “Accept”: 表示客户端能够接收的响应类型,优先级从高到低依次是"test/html"、“application/xhtml+xml”、“application/xml”、“application/json”,最后是"/"。
- “Accept-Language”: 表示客户端能够接受的语言类型,优先级从高到低依次是"zh-CN"、“zh”、“zh-TW”、“zh-HK”、“en-US”、“en”。
- “Connection”: 表示请求完成后是否立即断开连接,这里设为"close"表示立即断开连接。
- “User-Agent”: 表示客户端的身份信息,这里设为"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:59.0) Gecko/20100101 Firefox/59.0",表示使用的是Firefox 59.0浏览器在Windows 10系统上。
在Java中,字符串是不可变的,也就是说,一旦字符串被创建,就不能再修改它。因此,如果需要对一个字符串进行拼接,通常会使用字符串拼接符“+”,将多个字符串拼接在一起。
但是,在循环中多次使用字符串拼接符“+”会导致性能问题,因为每次使用“+”时,都会创建一个新的字符串对象。如果需要对大量字符串进行拼接,会导致大量的对象创建和垃圾回收,从而影响程序的性能。
为了解决这个问题,可以使用Java中的StringBuilder或StringBuffer类。这两个类都是可变的字符串类,可以在不创建新对象的情况下对字符串进行修改和拼接。在循环中使用StringBuilder或StringBuffer类的append()方法可以显著提高程序的性能,因为它们只会创建一个StringBuilder或StringBuffer对象,并在每次迭代中修改该对象。
因此,使用StringBuilder或StringBuffer类的append()方法代替“+”符号可以提高程序的性能,特别是在需要对大量字符串进行拼接时。
代发摘要是指在支付代发业务中,支付机构向代发企业发放的一份代表该企业本次代发工资的汇总信息,也称为“工资代发凭证”或“代发工资明细单”。
代发摘要通常包括以下信息:
代发摘要是代发企业和员工获取代发工资信息的重要渠道,同时也是代发支付机构进行账务核对的重要依据。代发企业和员工可通过代发摘要了解自己的工资发放情况,同时也可以及时发现和纠正发放错误,确保代发工资的准确性和及时性。
以上并发问题都可能导致程序出错、运行缓慢或无法完成任务,因此在编写多线程程序时需要特别注意并发问题的处理。常见的处理方法包括使用锁机制、同步机制、线程池、消息队列等。
线程池是一种创建和管理线程的机制,可以有效地控制并发线程的数量。使用线程池可以避免过多的线程创建导致的性能问题,同时可以降低资源消耗。
例如,你可以使用Java标准库中的ExecutorService
和Executors
类来创建线程池:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
int poolSize = 10;
ExecutorService executorService = Executors.newFixedThreadPool(poolSize);
for (int i = 0; i < 100; i++) {
executorService.execute(new Task());
}
executorService.shutdown();
}
static class Task implements Runnable {
@Override
public void run() {
// 调用第三方接口
}
}
}
为了避免线程间的竞争,可以在调用第三方接口的关键部分添加同步代码块或同步方法。这可以确保每次只有一个线程能够访问共享资源。
同步代码块示例:
public class SynchronizedExample {
private final Object lock = new Object();
public void callApi() {
synchronized (lock) {
// 调用第三方接口
}
}
}
同步方法示例:
javaCopy codepublic class SynchronizedMethodExample {
public synchronized void callApi() {
// 调用第三方接口
}
}
信号量是一种同步工具,可以限制同时访问共享资源的线程数量。在Java中,可以使用java.util.concurrent.Semaphore
类实现信号量。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore semaphore = new Semaphore(10);
public void callApi() {
try {
semaphore.acquire();
// 调用第三方接口
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
以上方法都可以在一定程度上解决并发问题。你可以根据你的具体需求和场景选择合适的方法。同时,在使用第三方接口时,需要遵循接口提供商的限制和规范,确保在规定的频率范围内调用接口。
package com.crestv.lgpt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor initExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(80);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(80);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("ThreadPoolTaskExecutor- ");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
这个配置中的参数含义如下:
其中,corePoolSize就是线程池中的核心线程数,这里设置为80。当线程池中的任务数不超过80时,线程池中始终有80个线程在等待执行任务,即核心线程数不会变化。
npm install -g @vue/cli
vue create my-project
cd my-project
替换 “my-project” 为你的项目名称。
创建好的项目如下:
.vue
文件中,例如:IDCardUpload.vue
。将此文件放入项目的 src/components
文件夹中。npm install axios
src/main.js
文件中,引入 axios 并将其添加到 Vue 原型上:import axios from 'axios';
import Vue from 'vue';
Vue.prototype.$axios = axios;
// 其他代码保持不变
src/App.vue
文件中,引入并使用刚刚创建的 IDCardUpload
组件:<template>
<div id="app">
<IDCardUpload />
div>
template>
<script>
import IDCardUpload from './components/IDCardUpload.vue';
export default {
components: {
IDCardUpload,
},
};
script>
npm run serve
现在你可以在浏览器中打开 http://localhost:8080
,看到你的 Vue.js 项目正在运行,同时包含了刚刚创建的 IDCardUpload
组件。在这个组件中,你可以选择一个身份证图片并点击 “提交” 按钮上传至后端。
注意:请根据你的实际需求替换代码中的后端接口地址。
:这个模块定义了 Vue 组件的 HTML 结构。它包含了组件的布局、样式和数据绑定等内容。在
中,你可以使用 Vue 指令、过滤器等功能。
:这个模块包含了 Vue 组件的 JavaScript 代码。在这里,你可以定义组件的数据、方法、计算属性、生命周期钩子等。它是 Vue 组件的逻辑部分。
:这个模块包含了 Vue 组件的 CSS 样式。你可以使用普通 CSS,也可以使用预处理器(如 SCSS、LESS 等)。如果你想让样式仅作用于当前组件,可以在
标签中添加 scoped
属性。main.js
:这是 Vue.js 项目的入口文件。在这个文件中,通常会导入 Vue.js 框架、根组件、路由器(如果使用了 vue-router)、Vuex(如果使用了状态管理库)等,并创建一个 Vue 实例来挂载到 HTML 页面中。router.js
或 router/index.js
:如果你的项目使用了 vue-router,那么这个模块将负责定义和配置路由规则。在这里,你可以设置各个页面组件的路径、导航守卫等。store.js
或 store/index.js
:如果你的项目使用了 Vuex 进行状态管理,那么这个模块将负责定义全局状态、mutations、actions 和 getters。你可以在这里编写处理全局状态的逻辑。components
文件夹:这个文件夹通常包含了项目中的所有 Vue 组件。组件可以是页面级别的,也可以是更小的可复用组件。通常我们会按照组件的功能和层次将它们组织在不同的子文件夹中。在这段代码中,是一个自定义Vue组件的标签,表示使用了名为“up-load-id-card”的Vue组件。尽管在这个标签中没有具体的代码,但它仍然实现了组件的注册和使用。
具体实现方面,该组件的具体实现在其他文件中,例如UploadIdCard.vue文件中。在该文件中,部分定义了组件的模板,
通过在父组件中使用标签,实现了将该组件渲染到父组件的模板中,并使得该组件的JavaScript代码被执行。这样,该组件就实现了上传身份证图片的功能,即选择图片、提交图片和将图片上传到指定的URL。
在上面的 vue.config.js
配置文件中,我们添加了一个 devServer
配置,用于配置开发服务器。主要修改的部分是添加了一个代理(proxy),这个代理会拦截以 ‘/api’ 开头的请求,并将其转发到指定的后端服务器。
关于各个配置项的解释如下:
/api
: 此处定义的是需要代理的请求路径,以 /api
开头的请求都会被代理。target
: 代理的目标服务器地址,即你的后端服务器地址。在本例中,目标服务器地址是 http://localhost:8088
。changeOrigin
: 是否更改请求源。设置为 true
时,请求头中的 Host
会被设置为 target
的值。这样做可以避免跨域问题。pathRewrite
: 路径重写规则。在本例中,我们将 ‘^/api’ 替换为空字符串。这意味着,当你访问 http://localhost:8080/api/upload
时,实际上请求的是 http://localhost:8088/upload
。这样,我们可以在不修改后端接口的情况下,通过前端配置实现跨域请求。相关代码:
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
List<String> orginList = Arrays.asList(StringUtils.split(origin, ",").clone());
config.setAllowedOriginPatterns(orginList);
config.setAllowedMethods(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setMaxAge(Duration.ofHours(1));
source.registerCorsConfiguration("/**",config);
return source;
}
这个方法创建了一个 CorsConfigurationSource
Bean,用于定义 CORS 配置。配置允许所有的请求方法(GET、POST、PUT 等)和请求头,并允许接收带有凭证(cookies 等)的请求。origin
属性从配置文件中获取,表示允许的跨域请求来源。
data()
:一个函数,返回组件的初始数据。这里定义了一个变量 idCardImage
,初始值为 null
,用于存储用户选择的图片。methods
:一个对象,包含组件的方法。这里有两个方法:onFileChange
和 submit
。
onFileChange(event)
:当用户选择图片时触发的事件处理函数。它获取选中的文件,然后使用 FileReader
对象读取文件内容。当读取操作完成时,reader.onload
事件处理函数被调用,将读取到的图片数据(Base64 编码)赋值给 idCardImage
。submit()
:提交按钮的点击事件处理函数。首先检查 idCardImage
是否有值,如果没有值则弹出提示让用户先选择图片。然后使用 Vue 的 $axios
方法发送 POST 请求,将图片数据(this.idCardImage
)作为请求体发送到服务器(http://localhost:8088/api/upload
)。请求成功后,控制台输出响应数据,如果出现错误,则在控制台输出错误信息。private final LoadingCache<String, RateLimiter> requestCaches = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<String, RateLimiter>() {
@Override
public RateLimiter load(String appId) {
return rateLimiterRegistry.rateLimiter(appId);
}
});
这段代码实现了一个LoadingCache缓存,用于存储每个appId对应的RateLimiter实例。在请求到来时,可以通过LoadingCache快速获取到对应的RateLimiter实例,从而进行限流操作。
具体来说,该缓存使用了Google Guava的CacheBuilder实现,设置了缓存的最大容量为1000,缓存的有效期为1分钟。在缓存中不存在对应的RateLimiter实例时,会使用CacheLoader来加载这个实例,具体的实现是使用rateLimiterRegistry从已经预定义好的RateLimiter配置中获取一个特定的RateLimiter实例,并将其缓存起来。
这个LoadingCache的作用是提高应用程序的性能和响应速度,同时降低限流操作的复杂度和时间成本。通过缓存RateLimiter实例,可以减少重复的限流器创建和销毁操作,提高限流器的复用性和效率。同时,该缓存还可以根据设定的有效期和缓存容量,自动清除已经过期或不再需要的缓存,避免资源浪费和内存泄露的问题。
//appid限流
return getRateLimiter(appId).executeSupplier(() -> {
try {
return joinPoint.proceed();
} catch (Throwable e) {
log.error(e.getMessage());
}
return null;
});
这段代码是一个限流器的执行方法,它使用了RateLimiter实例来控制请求速率。getRateLimiter(appId)会从LoadingCache中获取appId对应的RateLimiter实例,如果实例不存在则会新建一个。executeSupplier方法会使用获取到的RateLimiter实例来执行传入的Supplier接口,其中Supplier接口的具体实现是通过lambda表达式传入的。
在这个Supplier接口中,首先调用joinPoint.proceed()方法来执行被注解@RateLimit修饰的方法,即切入点方法。如果执行过程中发生异常,则会捕获并使用log.error输出异常信息。最终返回值为null。
executeSupplier方法会根据RateLimiter的限流规则来决定是否执行Supplier接口中的代码,如果限流器未达到限制,则会执行Supplier接口中的代码,并返回执行结果;否则将抛出异常并拒绝执行Supplier接口中的代码。如果在执行Supplier接口的过程中出现异常,限流器也会拒绝执行并抛出异常。
这段代码的作用是在限制请求速率的同时,也确保了请求的正常执行。当请求速率超过限制时,它会拒绝执行,并防止系统过载,从而保证了系统的稳定性和可靠性。
每个数据类型都有对应的操作命令,如String类型有get、set、incr等命令,List类型有lpush、rpush、lrange等命令,Set类型有sadd、srem、smembers等命令,Hash类型有hget、hset、hmget等命令,Sorted Set类型有zadd、zrange、zscore等命令。
新建一个注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
}
切入点:
@Pointcut("@annotation(com.crestv.lgpt.config.annotation.RateLimit)")
public void pointcut() {
}
@Pointcut
是一个AOP注解,用于定义切入点,可以理解为一个方法签名,被该注解标注的方法可以在其他方法中作为一个切入点引用。
在上述代码中,@Pointcut("@annotation(com.crestv.lgpt.config.annotation.RateLimit)")
定义了一个切入点,表示所有被@RateLimit
注解标注的方法都是切入点。当程序运行到切入点时,切面会被激活,并执行其中定义的逻辑。在本例中,切面会对传入的参数进行处理,然后进行限流处理。
使用@Pointcut
定义切入点的好处在于,它可以让多个切面共用同一个切入点,从而避免代码重复。另外,使用注解来定义切入点也可以使代码更加简洁易读,增加代码的可维护性。
最后在方法上添加:
@Around("pointcut()")
@Around
是一个AOP注解,用于定义环绕通知。它可以用来修饰一个方法,使该方法成为一个环绕通知。
除了 @Around
之外,AOP还有其他几种通知类型,包括:
@Before
:前置通知,该注解修饰的方法会在目标方法执行之前被执行。@AfterReturning
:后置通知,该注解修饰的方法会在目标方法返回之后被执行。@AfterThrowing
:异常通知,该注解修饰的方法会在目标方法抛出异常之后被执行。@After
:最终通知,该注解修饰的方法会在目标方法执行结束之后被执行,无论是否抛出异常都会执行。这几种通知类型的作用和执行顺序如下:
Spring 4
或 Spring Boot 1.x
正常执行顺序为:
(1)@Around
(环绕通知)
(2)@Before
(前置通知)
(3)执行方法逻辑
(4)@Around
(环绕通知)
(5)@After
(后置通知)
(6)@AfterReturning
(返回后通知)
异常执行顺序为:
(1)@Around
(环绕通知)
(2)@Before
(前置通知)
(3)执行方法逻辑
(4)@After
(后置通知)
(5)@AfterThrowing
(方法异常通知)
(6)抛出异常
Spring 5
或 Spring Boot 2.x
正常执行顺序为:
(1)@Around
(环绕通知)
(2)@Before
(前置通知)
(3)执行方法逻辑
(4)@AfterReturning
(返回后通知)
(5)@After
(后置通知)
(6)@Around
(环绕通知)
异常执行顺序为:
(1)@Around
(环绕通知)
(2)@Before
(前置通知)
(3)执行方法逻辑
(4)@AfterThrowing
(方法异常通知)
(5)@After
(后置通知)
(6)抛出异常
Spring 4
或 Spring Boot 1.x
:环绕通知执行完了后,然后再执行后置通知,最后执行的是返回后通知或者方法异常通知。
Spring 5
或 Spring Boot 2.x
:环绕通知就真的如其名一样环绕着所有通知,并且最后执行的变成了后置通知了,返回后通知或者方法异常通知在后置通知之前执行了。
package com.crestv.lgpt.config.annotation;
import com.alibaba.fastjson.JSONObject;
import com.crestv.lgpt.dto.Result;
import com.crestv.lgpt.utils.HttpUtil;
import com.crestv.lgpt.config.RedisKeyUtil;
import com.crestv.lgpt.utils.RedisUtil;
import com.crestv.lgpt.utils.digest.AesSignUtil;
import com.crestv.lgpt.utils.digest.SignatureUtils;
import com.crestv.lgpt.vo.BaseOutVo;
import com.crestv.lgpt.vo.outer.OuterRequestVo;
import com.crestv.lgpt.vo.outer.ReqDataVo;
import com.crestv.lgpt.vo.outer.OuterDataVo;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
public class RateLimitAspect {
@Resource
private RateLimiterRegistry rateLimiterRegistry;
@Resource
private RedisUtil redisUtil;
@Resource
private HttpUtil httpUtil;
// 验签方法还需修改 todo 想法,不需要用户再传签名密钥和加密密钥,只要正常传数据即可,进行验签即可判断签名密钥是否正确,加密密钥同理
private final LoadingCache<String, RateLimiter> requestCaches = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(new CacheLoader<String, RateLimiter>() {
@Override
public RateLimiter load(String appId) {
return rateLimiterRegistry.rateLimiter(appId);
}
});
@Pointcut("@annotation(com.crestv.lgpt.config.annotation.RateLimit)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Exception {
Optional<Object> optional = Arrays.stream(joinPoint.getArgs())
.filter(arg -> arg instanceof OuterRequestVo)
.findFirst();
OuterRequestVo vo = optional.isPresent() ? (OuterRequestVo) optional.get() : null;
//获取客户端所有信息
HttpServletRequest request = httpUtil.getRequest();
//获取客户端ip地址
String ipAddr = httpUtil.getRequestIP(request);
String appId = vo.getAppId();
//appid限流
return getRateLimiter(appId).executeSupplier(() -> {
try {
return joinPoint.proceed();
} catch (Throwable e) {
log.error(e.getMessage());
}
return null;
});
}
private RateLimiter getRateLimiter(String appId) {
if (null == requestCaches.getIfPresent(appId)) {
requestCaches.put(appId, rateLimiterRegistry.rateLimiter(appId));
}
RateLimiter rateLimiter = requestCaches.getIfPresent(appId);
return null != rateLimiter ? rateLimiter : rateLimiterRegistry.rateLimiter(appId);
}
}
配置为:
# 每个周期限制访问量
ratelimit.limitForPeriod=5
# 刷新周期 秒
ratelimit.limitRefreshPeriod=1
# 线程等待时间 秒
ratelimit.timeoutDuration=1
这段代码是一个基于AOP的限流切面,用于对带有@RateLimit注解的方法进行限流。执行流程如下:
package com.crestv.lgpt.config.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AppId {
}
package com.crestv.lgpt.config.annotation;
import com.crestv.lgpt.vo.outer.OuterRequestVo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Aspect
@Component
public class AppIdAspect {
@Resource
private RateLimitAspect rateLimitAspect;
/**
* 获取appid切点
*/
@Pointcut("@annotation(com.crestv.lgpt.config.annotation.AppId) && args(outerRequestVo,..)")
public void getAppIdAround(OuterRequestVo outerRequestVo) { }
/**
* 获取appid环绕通知
*/
@Around(value = "getAppIdAround(outerRequestVo)", argNames = "joinPoint,outerRequestVo")
public Object around(ProceedingJoinPoint joinPoint, OuterRequestVo outerRequestVo) throws Throwable {
// 获取appid
String appId = outerRequestVo.getAppId();
// 将获取到的appid存入ThreadLocal中,方便后续使用
AppIdHolder.setAppId(appId);
try {
// 执行限流切面的环绕通知
return rateLimitAspect.around(joinPoint);
} finally {
// 执行完后清空ThreadLocal
AppIdHolder.clearAppId();
}
}
}
package com.crestv.lgpt.config.annotation;
public class AppIdHolder {
private static final ThreadLocal<String> APP_ID_THREAD_LOCAL = new ThreadLocal<>();
public static void setAppId(String appId) {
APP_ID_THREAD_LOCAL.set(appId);
}
public static String getAppId() {
return APP_ID_THREAD_LOCAL.get();
}
public static void clearAppId() {
APP_ID_THREAD_LOCAL.remove();
}
}
package com.crestv.lgpt.config.annotation;
import com.crestv.lgpt.vo.outer.OuterRequestVo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Aspect
@Component
public class AppIdAspect {
@Resource
private RateLimitAspect rateLimitAspect;
/**
* 获取appid切点
*/
@Pointcut("@annotation(com.crestv.lgpt.config.annotation.AppId) && args(outerRequestVo,..)")
public void getAppIdAround(OuterRequestVo outerRequestVo) { }
/**
* 获取appid环绕通知
*/
@Around(value = "getAppIdAround(outerRequestVo)", argNames = "joinPoint,outerRequestVo")
public Object around(ProceedingJoinPoint joinPoint, OuterRequestVo outerRequestVo) throws Throwable {
// 获取appid
String appId = outerRequestVo.getAppId();
// 将获取到的appid存入ThreadLocal中,方便后续使用
AppIdHolder.setAppId(appId);
try {
// 执行限流切面的环绕通知
return rateLimitAspect.around(joinPoint);
} finally {
// 执行完后清空ThreadLocal
AppIdHolder.clearAppId();
}
}
}
① 非对称加密算法又称现代加密算法
。
② 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解
。
③ 与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)
和私有密(privatekey)
④ 公开密钥和私有密钥是一对
⑤ 如果用公开密钥
对数据进行加密
,只有用对应的私有密钥
才能解密
。
⑥ 如果用私有密钥
对数据进行加密
,只有用对应的公开密钥
才能解密
。
⑦ 因为加密和解密使用的是两个不同
的密钥,所以这种算法叫作非对称加密算法
。
列:我使用公钥加密,把信发给你,你使用私钥解密,我自己没有私钥也不能进行解密,就很安全
用于生成公钥和私钥对,密码使用getInstance工厂方法(返回给定类的实例的静态方法)构造
给定的算法有:
非对称加密: 在非对称加密中,加密和解密使用的是一对密钥,分别是公钥和私钥。公钥可以公开分享,而私钥需要保密。在这种情况下,服务端和客户端都有各自的一对密钥。
以客户端与服务端通信为例:
当客户端向服务端发送信息时,客户端使用服务端的公钥(S_pub)进行加密,服务端收到加密信息后,使用自己的私钥(S_pri)进行解密。
当服务端向客户端发送信息时,服务端使用客户端的公钥(C_pub)进行加密,客户端收到加密信息后,使用自己的私钥(C_pri)进行解密。
总结:在非对称加密通信中,服务端持有自己的私钥和客户端的公钥,客户端持有自己的私钥和服务端的公钥。这样可以确保通信的安全性。
所有人都用我发给外面的公钥把数据加密发送给我,然后我用自己的私钥进行解密,之后我在用客户端给我的公钥把数据进行加密,之后发给客户端,客户端用自己的数据进行解密,所以服务端可以有很多其他客户端的公钥但是只有一把自己的私钥,一个客户端只会有我服务端给他的公钥和她自己的私钥对吗
//指定为RSA算法
private static final String ALGORITHM = "RSA";
/**
* 生成密钥对方法
* @return
* @throws BaseException
*/
public static RsaKeyPair rsaKeyPair() throws BaseException {
try {
//通过keyPairGenerator生成公钥和私钥,返回一个包含RSA密钥对的实体类RsaKeyPair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
//初始化 RSA 密钥对的长度为 1024 位
keyPairGenerator.initialize(1024);
//生成一个密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//获取公钥和私钥
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
return RsaKeyPair.builder()
.publicKey(publicKey)
.rsaBase64PublicKey(Base64.getEncoder().encodeToString(publicKey.getEncoded()))
.privateKey(privateKey)
.rsaBase64PrivateKey(Base64.getEncoder().encodeToString(privateKey.getEncoded()))
.build();
} catch (Exception e) {
throw new BaseException("1111", e.getMessage());
}
}
//这段代码返回了一个包含 RSA 密钥对的实体类 RsaKeyPair。该实体类中包含了公钥和私钥两个成员变量,同时也将这两个成员变量分别以 Base64 编码的形式存储在 rsaBase64PublicKey 和 rsaBase64PrivateKey 中,方便传输和存储。可以使用 Lombok 中的 @Builder 注解进行简洁的构造器生成。
总结一下,服务端会有很多其他客户端的公钥和一把自己的私钥;每个客户端会有服务端的公钥和自己的私钥。通过这种方式,可以确保信息在传输过程中的安全性。
RSA数字签名通常分为两个步骤:签名和验证。签名是使用私钥对数据进行加密的过程,验证是使用公钥对签名进行解密和比对的过程。
具体步骤如下:
为什么要使用私钥进行签名?这是因为私钥是唯一的,具有独特性和安全性,只有持有私钥的人才能对数据进行签名操作,从而确保签名的真实性和完整性。同时,私钥通常被保存在安全的地方,不会被泄露或被篡改,因此可以确保签名的安全性和可靠性。另外,使用私钥进行签名操作还可以避免中间人攻击等安全威胁,提高数据传输的安全性和可靠性。
数据加密通常使用对称加密算法,是因为对称加密算法具有以下优点:
总之,对称加密算法具有速度快、安全性高、适用范围广、实现简单等优点,因此被广泛应用于数据传输的加密和认证过程中。当然,在进行密钥交换和认证等操作时,需要使用非对称加密算法来确保密钥的安全性和可靠性。
签名使用非对称加密是因为非对称加密算法具有数字签名的功能,可以保证签名的可信性和不可伪造性。具体来说,使用私钥对消息进行签名时,可以保证只有私钥持有者才能对消息进行签名,因此签名的可信度很高。而在验证签名时,使用公钥对签名进行验证,可以保证签名的不可伪造性,即只有私钥持有者才能对消息进行签名,从而保证了数据的完整性和真实性。
此外,使用非对称加密算法进行签名还具有以下优点:
总之,使用非对称加密算法进行签名可以保证签名的可信度和不可伪造性,具有安全性高、不需要传输密钥、可扩展性强等优点,因此被广泛应用于数据传输的认证和授权过程中。
消息摘要(Message Digest)是指将任意长度的消息(Message)作为输入,经过不可逆的哈希算法(Hash Function)处理,生成固定长度的哈希值(Hash Value)的过程。消息摘要通常也称为哈希摘要(Hash Digest)或摘要值(Digest Value)。
消息摘要的主要作用是保证消息的完整性和真实性。通过对消息进行哈希运算,可以生成一个唯一的摘要值,该摘要值具有如下特点:
消息摘要广泛应用于数字签名、消息认证码、密码学等领域。它可以用于验证数据的完整性和真实性,检测数据篡改和传输错误,防止数据被篡改或伪造,从而确保数据的安全性和可靠性。
要更改Docker中MySQL的配置文件,可以按照以下步骤操作:
docker exec -it <容器ID> bash
其中,<容器ID>
是MySQL容器的ID
/etc/mysql
目录下。使用以下命令进入该目录:cd /etc/mysql
my.cnf
文件,可以使用vim等编辑器进行编辑。例如,使用以下命令编辑my.cnf
文件:vim my.cnf
service mysql restart
如果出现权限问题,可以使用以下命令以root用户身份执行:
sudo service mysql restart
在重启服务之前,可以使用以下命令查看MySQL的运行状态:
service mysql status
如果MySQL服务已经运行,可以使用以下命令停止服务:
service mysql stop
完成上述步骤后,MySQL的配置文件就被修改成功了。注意,这种修改方式只对当前运行的MySQL容器有效,如果删除容器后再创建一个新的MySQL容器,新容器的配置文件仍然是默认的。如果需要永久修改MySQL配置文件,可以将修改后的文件打包成新的Docker镜像,然后使用新镜像创建MySQL容器。
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
<version>1.5.21version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-modelsartifactId>
<version>1.5.21version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
List<PaymentInfoDto> payList = paymentOrderDto.getPayList().stream()
.map(paymentInfoDto -> {
PaymentInfoDto newPaymentInfoDto = new PaymentInfoDto();
newPaymentInfoDto.setAccount(paymentInfoDto.getAccount());
newPaymentInfoDto.setPayee(paymentInfoDto.getPayee());
newPaymentInfoDto.setIdCard(paymentInfoDto.getIdCard());
newPaymentInfoDto.setPhone(paymentInfoDto.getPhone());
newPaymentInfoDto.setAmount(paymentInfoDto.getAmount() * 2);
return newPaymentInfoDto;
})
.collect(Collectors.toList());
使用 map()
方法将对象转换为新的对象,是为了创建一个新的、独立的对象,以便在修改其属性值时不会影响原始对象。在上述代码中,使用 map()
方法将原始 PaymentInfoDto
列表中的每个对象都映射为一个新的 PaymentInfoDto
对象,并使用新的 amount
属性值来更新每个对象。
这种方法通常用于创建一个新的列表,该列表包含原始列表中的对象,但在新列表中具有不同的属性值或状态。这样做的好处是,我们可以在不影响原始列表的情况下对新列表进行更改,从而避免对原始列表造成潜在的问题。
在上述代码中,我们使用 map()
方法将 PaymentInfoDto
对象映射为新的 PaymentInfoDto
对象,因为我们希望在创建一个新的支付列表时避免对原始支付列表造成影响。这种方式使得代码更加清晰、可读性更好,并且更容易维护和测试。
Java中的Stream是一种用于操作集合(包括数组、集合、Map等)的便捷工具。它提供了一种声明式的、函数式的、可并行的操作集合的方式,使代码更加简洁、易读、易维护。下面是一些常见的Stream用法示例:
phpCopy codeList list = Arrays.asList(1, 2, 3, 4, 5);
Stream stream = list.stream();
phpCopy codeList list = Arrays.asList(1, 2, 3, 4, 5);
Stream stream = list.stream();
Stream filteredStream = stream.filter(n -> n % 2 == 0);
rustCopy codeList list = Arrays.asList("apple", "banana", "orange");
Stream stream = list.stream();
Stream mappedStream = stream.map(s -> s.toUpperCase());
phpCopy codeList list = Arrays.asList(5, 3, 1, 4, 2);
Stream stream = list.stream();
Stream sortedStream = stream.sorted();
sqlCopy codeList list = Arrays.asList(1, 2, 3, 4, 5);
Stream stream = list.stream();
int sum = stream.reduce(0, Integer::sum);
以上仅是Stream的一些常用方法,Stream还提供了很多其他的操作,比如distinct、flatMap、peek等。需要根据具体的场景选择不同的方法来操作Stream。
Java中的Stream流可以用来对集合(如List、Set等)和数组进行操作。它提供了一种非常便捷的方式来处理集合中的元素,可以进行筛选、映射、排序、统计等操作。下面是一些Stream流的使用示例:
scssCopy codeList numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
List evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
这段代码将集合中的偶数筛选出来,并将它们放入一个新的集合中。
rustCopy codeList words = Arrays.asList("hello", "world");
List wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
这段代码将集合中的字符串映射成它们的长度,并将它们放入一个新的集合中。
scssCopy codeList numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3);
List sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
这段代码将集合中的数字排序,并将它们放入一个新的集合中。
scssCopy codeList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
long count = numbers.stream()
.count();
int max = numbers.stream()
.mapToInt(Integer::intValue)
.max()
.orElseThrow(NoSuchElementException::new);
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
double average = numbers.stream()
.mapToInt(Integer::intValue)
.average()
.orElseThrow(NoSuchElementException::new);
这段代码对集合中的元素进行统计,包括元素个数、最大值、总和和平均值。
以上只是一些常用的Stream流的使用示例,实际上Stream流可以进行的操作非常丰富。在使用Stream流时,需要注意它是一种惰性求值的方式,也就是说,在对集合进行操作时,它并不会立即执行,而是等到真正需要结果的时候才会开始执行。因此,Stream流可以帮助我们避免创建不必要的中间集合,提高程序的性能。
peek
和 forEach
都是 Java 8 引入的 Stream API 中的方法。它们之间的主要区别在于它们在 Stream 管道中的角色。以下是关于它们的详细解释:
peek
:peek
是一个中间操作,它允许在不终止流的情况下执行某些操作。peek
通常用于调试目的,因为它允许您在流的每个元素上执行某些操作(例如打印元素),同时保持流的原始状态。需要注意的是,由于 peek
是中间操作,除非有一个终止操作触发流的执行,否则它不会执行任何操作。例子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubledNumbers = numbers.stream()
.peek(n -> System.out.println("Original: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("Doubled: " + n))
.collect(Collectors.toList());
在上述示例中,peek
被用来在两个操作之间打印原始数字和加倍后的数字。
forEach
:forEach
是一个终止操作,它用于对流中的每个元素执行某个操作,并终止流。它通常用于执行诸如打印、修改列表元素或将元素添加到新集合等操作。因为它是终止操作,它确保流的执行。例子:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
在上述示例中,forEach
用于打印过滤后的偶数。
总结:
peek
是一个中间操作,它不会触发流的执行。它主要用于调试目的,因为它允许您在保持流状态的同时对每个元素执行操作。forEach
是一个终止操作,用于对流中的每个元素执行操作并终止流。它用于执行诸如打印、修改元素或将元素添加到新集合等操作。当您需要对流中的每个元素执行操作并确保流执行时,请使用 forEach
。如果您只是想在流的中间阶段查看元素或执行一些调试操作,请使用 peek
。
线程问题和并发问题是计算机科学中两个密切相关的概念,它们涉及到多个任务如何在计算机系统中同时运行和相互协作。
线程问题:线程是操作系统调度的基本单位,它是程序中一个独立执行的路径。多线程就是一个程序中有多个线程在同时运行。线程问题通常涉及线程之间的同步、通信和资源共享。当线程之间出现争用条件、死锁或者数据不一致等问题时,这些都属于线程问题。为了解决线程问题,程序员需要使用互斥量、信号量、条件变量等同步机制来确保线程之间的正确执行顺序和数据的完整性。
并发问题:并发是指多个任务在同一时间段内交替执行,但不一定是同时执行。并发问题是指在并发环境下,多个任务之间可能存在的相互影响、资源竞争或者执行顺序不确定性等问题。为了解决并发问题,程序员需要设计并发控制策略,如锁、原子操作、事务等,以确保多个任务的正确执行。
总之,线程问题主要关注多线程环境下线程之间的同步和通信,而并发问题关注多任务同时执行过程中的资源竞争和执行顺序。两者都是为了解决多任务环境下程序的正确性和性能问题。
线程问题和并发问题是多线程编程中常见的两类问题。它们有一定的关联性,但侧重点不同。
线程问题主要是指与单个线程相关的问题,包括:
并发问题主要是指在并发执行的过程中出现的问题,包括:
对于线程问题:
对于并发问题:
需要注意的是,虽然锁可以解决很多线程问题和并发问题,但使用锁也会带来一定的性能开销。因此,在实际应用中需要权衡锁的使用,尽量减小锁粒度,并尽可能使用非阻塞性的锁(如自旋锁、读写锁等)以提高性能。同时,可以考虑使用其他同步机制(如原子操作、信号量等)来解决特定的问题。
Redis分布式锁是一种基于Redis的实现方式,用于保证在分布式系统中的多个节点之间实现资源的同步访问。其主要目的是解决在分布式环境下,多个节点对共享资源的竞争访问问题。
原理:Redis分布式锁的实现原理是使用Redis的原子性操作SET
命令,为一个指定的key设置一个唯一的值,并设置一个过期时间。当多个节点尝试获取锁时,只有一个节点能成功设置这个key,从而获得锁。其他节点在尝试获取锁时,发现这个key已经存在,只能等待锁释放。
以下是Redis分布式锁的主要步骤:
SET
命令尝试为一个指定的key设置一个唯一值,并设置一个过期时间。设置成功说明获取锁成功,设置失败说明锁已被其他节点获取。public boolean tryLock(String key, long timeout, TimeUnit unit) {
String value = UUID.randomUUID().toString();
return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
}
public void unlock(String key) {
String value = redisTemplate.opsForValue().get(key);
if (value != null && value.equals(myValue)) {
redisTemplate.delete(key);
}
}
需要注意的是,为了避免死锁的发生,需要在设置锁时设定一个合理的过期时间,以便在发生异常或者节点宕机时,锁能自动释放。另外,Redis分布式锁在使用过程中可能会遇到一些问题,如锁续命问题、锁误删问题等,可能需要使用RedLock算法或者其他分布式锁组件(如Zookeeper、Etcd等)来解决这些问题
分布式锁和其他锁(如Java中的synchronized关键字、ReentrantLock等)主要区别在于它们适用的场景和目的。
总结:分布式锁和其他锁主要的区别在于它们适用的场景。分布式锁适用于分布式系统中,确保多个节点对共享资源的互斥访问;而其他锁适用于单个JVM进程内的多线程环境,确保线程间的同步和线程安全。
在Windows系统中使用ab工具进行并发测试,可以通过以下步骤完成:
ab -V
如果安装成功,会输出ab工具的版本信息。
ab -n 1000 -c 100 http://localhost:8080/myurl
其中,-n参数表示请求数量,-c参数表示并发请求数量,http://localhost:8080/myurl为需要测试的URL。
注意事项:
在Windows系统中使用ab工具进行并发测试时,可能会遇到一些问题,例如无法创建大量的并发连接。这时可以在命令行中添加“-k”参数,表示使用HTTP keep-alive连接。例如:
ab -n 1000 -c 100 -k http://localhost:8080/myurl
此外,还可以通过增加系统资源、优化代码逻辑等方式提升系统性能,以应对高并发场景的测试。
ab工具的Connection Times (ms)是针对HTTP请求的连接时间、处理时间和等待时间的统计结果,具体含义如下:
例如,上述结果中的Processing块表示,针对测试中的每个请求,从建立TCP连接到收到服务器响应的时间最小值为307ms,平均值为2076ms,中位数为2057ms,最大值为3104ms。
通过分析Connection Times的统计结果,可以帮助开发
在使用ab工具进行并发测试时,可以使用以下命令选项来查看发送了多少请求、响应状态码等信息:
通过上述命令选项,可以查看ab工具发送了多少请求、接收了多少响应、响应状态码、请求头信息、Cookie信息等详细信息。如果请求被拦截,可以在输出信息中查看响应状态码和详细的错误信息。
queryWrapper是MyBatis-Plus提供的一个查询构造器,用于构建查询条件。它包含了多个方法,每个方法都可以设置不同的查询条件,具体的各类参数及作用如下:
以上是queryWrapper中常用的方法及作用,根据具体的查询需求可以选择合适的方法进行使用。需要注意的是,queryWrapper中的大部分方法都是可链式调用的,例如eq()和like()方法可以直接在and()和or()方法之后进行调用,以构建复杂的查询条件。
Java实现异步通常有以下几种方式:
需要注意的是,异步操作需要注意线程安全性和资源管理等问题,否则可能会出现竞态条件、内存泄漏等问题。因此,在实现异步操作时需要仔细考虑设计和实现方案。
两者的区别在于,VO是用new关键字创建的由GC回收的对象,而PO是向数据库中添加新数据时创建,删除数据库中数据时削除的对象。此外,VO的属性是根据当前业务的不同而不同的,而PO的属性是跟数据库表的字段一一对应的。
DTO(数据传输对象)主要用于远程调用等需要大量传输对象的地方。比如我们一张表有100个字段,那么对应的PO就有100个属性。但是我们界面上只要显示10个字段,客户端用Web service来获取数据,没有必要把整个PO对象传递到客户端,这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO。
在Java开发中,通常会使用dto、entity和vo三个对象来传输数据。其中,dto用于接收参数,entity用于保存数据,vo用于返回整理过的数据。
以上这些对象命名方式在Java开发中非常常见,也是Java语言开发中的重要概念之一。每个对象都有其特定的作用和使用场景,开发者在实际开发过程中需要根据具体的业务需求来选择适合的对象命名方式。
VO(值对象)通常用于业务层之间的数据传递,可以看成是业务对象。它存活的目的就是为数据提供一个生存的地方。VO的作用是封装业务逻辑后返回给用户查看或处理。
PO(持久对象)是与数据库中的表相映射的Java对象,代表物理数据的对象表示。它是有状态的,每个属性代表其当前的状态。使用它,可以使程序与物理数据解耦,并且可以简化对象数据与物理数据之间的转换。PO的作用是作为持久化的数据模型,将数据库的表映射为对象。
TO(数据传输对象)主要用于远程调用等需要大量传输对象的地方,用于不同层之间的数据传输的对象。
BO(业务对象)是封装业务逻辑的Java对象,通过调用DAO方法,结合PO和VO进行业务操作。BO的作用是封装业务逻辑,对外提供业务处理的能力。
DAO(数据访问对象)是用于访问数据库的对象,通常和PO结合使用。DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作。夹在业务逻辑与数据库资源中间。配合VO,提供数据库的CRUD操作。
POJO(简单无规则Java对象)是一个符合Java Bean规范的纯Java对象,没有增加别的属性和方法。它在一些ORM(Object-Relational Mapping)工具中,能够做到维护数据库表记录的Persistent Object完全是一个符合Java Bean规范的纯Java对象。
QO(查询对象)是用于封装查询条件的对象,它与DAO结合使用,可以方便地进行数据库查询操作。
Sonatype 地址: https://issues.sonatype.org/secure/Dashboard.jspa
用户名和密码会在后面发布jar包到中央仓库上用的到
示例:
用于认证这个仓库的所属权,创建后回复他:Already created
如果在之前填错了gorup id,管理员会提示
注意,他要求创建的仓库需要是公开的
以上全部完成后会收到
收到此消息表示创建成功
之后进行下一步
链接:https://www.gpg4win.org/download.html
进链接下载软件会弹出赞助界面,选择0
命令:
上传到服务器
gpg --keyserver hkp://keyserver.ubuntu.com:11371 --send-keys 自己的密钥
检查是否上传成功
gpg --keyserver hkp://keyserver.ubuntu.com:11371 --recv-keys 自己的密钥
<properties>
<java.version>1.8java.version>
<projectUrl>https://gitee.com/spongebobpineapple-house/testmaven.gitprojectUrl>
<serverId>ossrhserverId>
properties>
<developers>
<developer>
<name>whitename>
<email>[email protected]email>
<url>${projectUrl}url>
developer>
developers>
<url>${projectUrl}url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txturl>
<distribution>repo,manualdistribution>
license>
licenses>
<scm>
<connection>${projectUrl}connection>
<developerConnection>${projectUrl}developerConnection>
<url>${projectUrl}url>
scm>
<distributionManagement>
<snapshotRepository>
<id>${serverId}id>
<name>OSS Snapshots Repositoryname>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/url>
snapshotRepository>
<repository>
<id>${serverId}id>
<name>OSS Staging Repositoryname>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/url>
repository>
distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-source-pluginartifactId>
<version>2.2.1version>
<executions>
<execution>
<id>attach-sourcesid>
<goals>
<goal>jar-no-forkgoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-javadoc-pluginartifactId>
<version>2.9.1version>
<configuration>
<additionalparam>-Xdoclint:noneadditionalparam>
<aggregate>trueaggregate>
<charset>UTF-8charset>
<encoding>UTF-8encoding>
<docencoding>UTF-8docencoding>
configuration>
<executions>
<execution>
<id>attach-javadocsid>
<goals>
<goal>jargoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-gpg-pluginartifactId>
<version>1.5version>
<executions>
<execution>
<id>sign-artifactsid>
<phase>verifyphase>
<goals>
<goal>signgoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.sonatype.pluginsgroupId>
<artifactId>nexus-staging-maven-pluginartifactId>
<version>1.6.7version>
<extensions>trueextensions>
<configuration>
<serverId>${serverId}serverId>
<nexusUrl>https://s01.oss.sonatype.org/nexusUrl>
<autoReleaseAfterClose>trueautoReleaseAfterClose>
configuration>
plugin>
plugins>
build>
<servers>
<server>
<id>ossrhid>
<username>SpongeBobusername>
<password>(你自己的)password>
server>
servers>
<profile>
<id>ossrhid>
<activation>
<activeByDefault>trueactiveByDefault>
activation>
<properties>
<gpg.executable>gpggpg.executable>
<gpg.passphrase>(你自己的)gpg.passphrase>
properties>
profile>
部署的时候报错" Failed to execute goal org.apache.maven.plugins:maven-gpg-plugin:1.5:sign (sign-artifacts) on proje"
在pom中添加如下配置
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-gpg-pluginartifactId>
<version>1.6version>
<configuration>
<skip>trueskip>
configuration>
plugin>
https://s01.oss.sonatype.org/#welcome
账号密码和第一次注册的sonatype一样
部署成功后登录,搜索仓库
直接在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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>io.gitee.stayrationalgroupId>
<artifactId>sdkartifactId>
<version>1.0version>
<packaging>jarpackaging>
<name>crestv-sdk::凯盈资讯sdkname>
<url>https://gitee.com/stayrational/sdk.giturl>
<description>凯盈资讯sdkdescription>
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>8source>
<target>8target>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-source-pluginartifactId>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-toolchains-pluginartifactId>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-gpg-pluginartifactId>
plugin>
plugins>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-assembly-pluginartifactId>
<version>2.2-beta-5version>
<configuration>
<tarLongFileMode>gnutarLongFileMode>
configuration>
<executions>
<execution>
<id>packageid>
<phase>packagephase>
<goals>
<goal>singlegoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-source-pluginartifactId>
<version>2.1version>
<configuration>
<attach>trueattach>
configuration>
<executions>
<execution>
<phase>compilephase>
<goals>
<goal>jargoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-gpg-pluginartifactId>
<version>1.6version>
<executions>
<execution>
<id>sign-artifactsid>
<phase>verifyphase>
<goals>
<goal>signgoal>
goals>
execution>
executions>
plugin>
plugins>
pluginManagement>
build>
项目的协议<-->
<licenses>
<license>
<name>The Apache Software License, Version 2.0name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txturl>
<distribution>repo,manualdistribution>
license>
licenses>
<scm>
<url>https://gitee.com/stayrational/sdk.giturl>
<connection>https://gitee.com/stayrational/sdk.gitconnection>
<developerConnection>https://gitee.com/stayrational/sdk.gitdeveloperConnection>
scm>
<developers>
<developer>
<name>gpname>
<email>[email protected]email>
<url>https://github.com/calvados1url>
developer>
developers>
<profiles>
<profile>
<id>ossrhid>
<activation>
<activeByDefault>trueactiveByDefault>
activation>
<distributionManagement>
<repository>
<id>ossrhid>
<name>OSS Staging Repositoryname>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/url>
repository>
<snapshotRepository>
<id>ossrhid>
<name>OSS Snapshots Repositoryname>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/url>
snapshotRepository>
distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-javadoc-pluginartifactId>
<version>2.10.4version>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>jargoal>
goals>
execution>
executions>
plugin>
plugins>
build>
profile>
profiles>
project>
<update id="updateBatchBalance">
update bank_sub_account
<trim prefix="set" suffixOverrides=",">
<trim prefix="total_amount = case" suffix="end">
<foreach collection="coll" item="bankSubAccount" index="index">
WHEN bank_main_account_id = #{bankSubAccount.bankMainAccountId} and account = #{bankSubAccount.account}
then #{bankSubAccount.totalAmount}
</foreach>
</trim>
</trim>
<foreach collection="coll" item="bankSubAccount" index="index" open="where bank_main_account_id in (" separator="," close=")">
#{bankSubAccount.bankMainAccountId}
</foreach>
<foreach collection="coll" item="bankSubAccount" index="index" open="and account in (" separator="," close=")">
#{bankSubAccount.account}
</foreach>
</update>
转换后:
UPDATE bank_sub_account
SET total_amount = CASE
WHEN bank_main_account_id = ? AND account = ? THEN ?
WHEN bank_main_account_id = ? AND account = ? THEN ?
...
END
WHERE bank_main_account_id IN (?, ?, ...)
AND account IN (?, ?, ...)
在实际执行此SQL语句时,需要将问号(?)占位符替换为实际值。此外,
标签用于循环遍历集合中的每个元素,并将其添加到生成的SQL语句中。因此,在实际生成的SQL语句中,WHERE
子句和AND
子句将包含所有bank_main_account_id
和account
的值,用逗号分隔。
实际执行时的SQL语句可能类似于以下格式:
UPDATE bank_sub_account
SET total_amount = CASE
WHEN bank_main_account_id = 1 AND account = 'A' THEN 1000
WHEN bank_main_account_id = 2 AND account = 'B' THEN 2000
...
END
WHERE bank_main_account_id IN (1, 2, 3, ...)
AND account IN ('A', 'B', 'C', ...)
请注意,这里的数值和字符仅作为示例,实际执行时需要根据实际情况进行替换。
因为使用了suffixOverrides=","所以不需要逗号分隔
这段代码是Mybatis中的一个update语句,具体的作用如下:
<update id="updateAvailableAndWillpayAmountBatch">
UPDATE bank_sub_account bsa
INNER JOIN
(
SELECT SUM(total_amount) settled_amount
FROM pay_order
<foreach collection="coll" item="bankSubAccount" index="index" open="WHERE bank_main_account_id in (" separator="," close=")">
#{bankSubAccount.bankMainAccountId}
</foreach>
<foreach collection="coll" item="bankSubAccount" index="index" open="and company_id in (" separator="," close=")">
#{bankSubAccount.companyId}
</foreach>
<foreach collection="coll1" item="state" index="index" open="and state in (" separator="," close=")">
#{state}
</foreach>
GROUP BY bank_main_account_id, company_id
) po
ON bsa.bank_main_account_id = bank_main_account_id
<trim prefix="set" suffixOverrides=",">
<trim prefix="willpay_amount = case" suffix="end,">
<foreach collection="coll" item="bankSubAccount" index="index">
<include refid="when"/> then IFNULL(po.settled_amount, 0)
</foreach>
</trim>
<trim prefix="available_amount = case" suffix="end">
<foreach collection="coll" item="bankSubAccount" index="index">
<include refid="when"/> then bsa.total_amount - IFNULL(po.settled_amount, 0)
</foreach>
</trim>
</trim>
<foreach collection="coll" item="bankSubAccount" index="index" open="WHERE bsa.bank_main_account_id in (" separator="," close=")">
#{bankSubAccount.bankMainAccountId}
</foreach>
<foreach collection="coll" item="bankSubAccount" index="index" open="and bsa.account in (" separator="," close=")">
#{bankSubAccount.account}
</foreach>
</update>
<sql id="when">
when bsa.bank_main_account_id = #{bankSubAccount.bankMainAccountId} and bsa.account = #{bankSubAccount.account}
</sql>
转换为sql后如下:
UPDATE bank_sub_account bsa
INNER JOIN
(
SELECT SUM(total_amount) settled_amount
FROM pay_order
WHERE bank_main_account_id in (bank_main_account_id1, bank_main_account_id2, ...)
AND company_id in (company_id1, company_id2, ...)
AND state in (state1, state2, ...)
GROUP BY bank_main_account_id, company_id
) po
ON bsa.bank_main_account_id = po.bank_main_account_id
SET
willpay_amount = CASE
WHEN <condition> THEN IFNULL(po.settled_amount, 0)
-- Add more WHEN clauses if needed
END,
available_amount = CASE
WHEN <condition> THEN bsa.total_amount - IFNULL(po.settled_amount, 0)
-- Add more WHEN clauses if needed
END
WHERE bsa.bank_main_account_id in (bank_main_account_id1, bank_main_account_id2, ...)
AND bsa.account in (account1, account2, ...);
标签:用于循环遍历集合,根据集合中的元素生成SQL语句的一部分。它有以下属性:
collection
:需要遍历的集合变量名。item
:用于表示集合中每个元素的变量名。index
:用于表示集合中元素的索引。open
:循环生成的SQL片段的开始部分。separator
:在生成的SQL片段中,各部分之间的分隔符。close
:循环生成的SQL片段的结束部分。#{...}
:用于表示参数占位符。在SQL语句中,这些占位符会被实际传入的参数值替换。例如:#{bankSubAccount.bankMainAccountId}
表示从bankSubAccount
对象中获取bankMainAccountId
属性的值。在这个XML中,
标签主要用于根据传入的集合生成WHERE
子句中的IN
条件。例如,如果传入一个包含多个bank_main_account_id
的集合,
标签会生成类似于WHERE bank_main_account_id IN (id1, id2, id3)
的SQL片段。
<update id="updateAvailableAndWillpayAmountBatch">
UPDATE bank_sub_account bsa
INNER JOIN
(
SELECT SUM(total_amount) settled_amount
FROM pay_order
<foreach collection="coll" item="bankSubAccount" index="index" open="WHERE bank_main_account_id in (" separator="," close=")">
#{bankSubAccount.bankMainAccountId}
</foreach>
<foreach collection="coll" item="bankSubAccount" index="index" open="and company_id in (" separator="," close=")">
#{bankSubAccount.companyId}
</foreach>
<!--不查询状态码为200被删除的-->
AND rec_state = 100
<foreach collection="coll1" item="state" index="index" open="and state in (" separator="," close=")">
#{state}
</foreach>
GROUP BY bank_main_account_id, company_id
) po
ON bsa.bank_main_account_id = bank_main_account_id
<trim prefix="set" suffixOverrides=",">
<trim prefix="willpay_amount = case" suffix="end,">
<foreach collection="coll" item="bankSubAccount" index="index">
<include refid="when"/> then IFNULL(po.settled_amount, 0)
</foreach>
</trim>
<trim prefix="available_amount = case" suffix="end">
<foreach collection="coll" item="bankSubAccount" index="index">
<include refid="when"/>
<choose>
<when test="bankSubAccount.companyId == null">
then bsa.total_amount
</when>
<otherwise>
then bsa.total_amount - IFNULL(po.settled_amount, 0)
</otherwise>
</choose>
</foreach>
</trim>
</trim>
<foreach collection="coll" item="bankSubAccount" index="index" open="WHERE bsa.bank_main_account_id in (" separator="," close=")">
#{bankSubAccount.bankMainAccountId}
</foreach>
<foreach collection="coll" item="bankSubAccount" index="index" open="and bsa.account in (" separator="," close=")">
#{bankSubAccount.account}
</foreach>
</update>
在这个修改中,我们的目标是在计算可用余额 available_amount
时,对于默认子账号(满足条件 companyId
为 null
),让总金额 total_amount
等于可用余额。对于非默认子账号,我们沿用原来的计算方法(bsa.total_amount - IFNULL(po.settled_amount, 0)
)。
为了实现这个目标,我们使用了
标签。
标签在 MyBatis 中类似于 SQL 中的 CASE
语句,用于根据不同的条件执行不同的操作。在
标签内,我们可以使用
标签来定义满足某个条件时要执行的操作,以及使用
标签来定义在没有满足任何
标签中定义的条件时要执行的操作。
在本例中,我们的
标签包含了一个
标签和一个
标签。
标签的条件是 bankSubAccount.companyId == null
,表示当 companyId
为 null
时(即默认子账号),我们执行 then bsa.total_amount
操作,即将总金额 total_amount
直接赋值给可用余额 available_amount
。
标签用于处理不满足
标签条件的情况,即非默认子账号。在这种情况下,我们沿用原来的计算方法:then bsa.total_amount - IFNULL(po.settled_amount, 0)
。
总之,我们通过添加一个
标签,根据不同的条件(默认子账号或非默认子账号),执行不同的计算方法来更新可用余额 available_amount
。这样,对于默认子账号,总金额 total_amount
会等于可用余额 available_amount
。
GROUP BY
子句在SQL查询中用于对查询结果进行分组。它通常与聚合函数(如COUNT()
、SUM()
、AVG()
、MIN()
和MAX()
)一起使用,以对每个分组应用聚合操作。
GROUP BY
子句按一个或多个列对查询结果进行分组。分组操作将具有相同列值的记录归为一组,并对每个组应用聚合函数。最终结果只显示每个组的一个记录,而不是所有符合条件的记录。
例如,假设您有一个销售表,包含以下字段:product_id
(产品ID)、sales_date
(销售日期)和quantity
(销售数量)。如果您想查看每种产品的总销售数量,可以使用以下查询:
sqlCopy codeSELECT product_id, SUM(quantity) as total_quantity
FROM sales
GROUP BY product_id;
在这个查询中,GROUP BY
子句按product_id
对查询结果进行分组,然后使用SUM()
函数计算每个分组的quantity
总和。这样,您可以得到每种产品的总销售数量。
ON
:ON
子句在SQL查询中主要用于指定连接两个表时的条件,它主要在JOIN
操作(如INNER JOIN
、LEFT JOIN
、RIGHT JOIN
等)中使用。例如:SELECT a.id, a.name, b.amount
FROM table_a a
INNER JOIN table_b b ON a.id = b.a_id;
在这个查询中,ON
子句指定了table_a
和table_b
连接的条件,即a.id
等于b.a_id
。
WHERE
:WHERE
子句用于筛选查询结果中符合特定条件的记录。它可以与多个逻辑运算符(如AND
、OR
、NOT
等)结合使用,以满足复杂的筛选需求。例如:SELECT id, name, age
FROM users
WHERE age > 18 AND gender = 'male';
在这个查询中,WHERE
子句筛选出年龄大于18且性别为男性的记录。
THEN
:THEN
子句主要用在CASE
语句中,用于指定当满足某个条件时应执行的操作。CASE
语句允许根据一个或多个条件执行不同的操作。例如:SELECT id, name, age,
CASE
WHEN age < 18 THEN 'minor'
WHEN age >= 18 AND age <= 60 THEN 'adult'
ELSE 'senior'
END as age_group
FROM users;
在这个查询中,THEN
子句用于指定当满足不同年龄条件时,将记录分为minor
(未成年)、adult
(成年)和senior
(老年)三个年龄段。
WHEN
关键字用于在CASE
语句中定义条件。它可以与THEN
一起使用,用于指定当满足某个条件时应返回的结果。例如,在上面的CASE
语句示例中,WHEN
用于定义条件age < 18
和age >= 18 AND age <= 60
。总之,在SQL查询中,ON
子句用于指定连接条件,WHERE
子句用于筛选符合条件的记录,而THEN
子句用于指定满足CASE
语句中某个条件时的操作。
MySQL是一个开源的关系型数据库管理系统,使用结构化查询语言(SQL)进行数据操作。以下是MySQL中一些常用关键字的简要介绍:
这些关键字可以组合使用,以执行各种复杂的数据操作。在编写SQL查询时,请注意正确使用关键字并遵循语法规则,以确保查询可以正常执行。
MyBatis 中的一些常用标签包括
和
,它们在动态 SQL 中非常有用。下面我们来详细介绍一下这两个标签。
标签:
标签用于在生成的 SQL 语句中修剪字符。它允许你在前面、后面或覆盖特定字符,如逗号、空格等。它有以下属性:
prefix
:给生成的 SQL 语句添加前缀。suffix
:给生成的 SQL 语句添加后缀。prefixOverrides
:用于覆盖在 SQL 语句开头匹配的字符串。suffixOverrides
:用于覆盖在 SQL 语句结尾匹配的字符串。例如:
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name=#{name},if>
<if test="age != null">age=#{age},if>
trim>
在这个例子中,
标签用于添加前缀 “SET”,并在生成的 SQL 语句末尾覆盖逗号。这在动态更新语句中非常有用,可以确保 SQL 语句具有正确的格式。
标签:
标签是
标签的一个特例,它用于动态生成 SQL 更新语句中的 SET 子句。它自动为生成的 SQL 语句添加 “SET” 前缀,并在末尾覆盖逗号。它没有特定的属性,只需在标签内部包含用于生成 SET 子句的条件即可。
例如:
<set>
<if test="name != null">name=#{name},if>
<if test="age != null">age=#{age},if>
set>
在这个例子中,
标签用于动态生成 SET 子句。它会自动添加 “SET” 前缀,并在生成的 SQL 语句末尾覆盖逗号。这对于动态更新语句非常有用。
总之,
和
标签在 MyBatis 中非常有用,它们可以帮助你生成更灵活、格式正确的 SQL 语句。
标签可以更通用地修剪字符,而
标签专门用于生成 SET 子句。
除了
和
标签之外,MyBatis 还提供了许多其他有用的标签,以帮助你创建动态 SQL。下面是一些常用的标签:
标签:
标签允许你根据某个条件包含或排除 SQL 片段。例如:
<if test="name != null">
AND name=#{name}
if>
在这个例子中,如果参数 name
不为空,则会将 “AND name=#{name}” 添加到 SQL 语句中。
、
和
标签:这些标签允许你在多个条件中选择一个 SQL 片段。
标签内部包含一个或多个
标签,以及一个可选的
标签。当某个
标签的条件为真时,它的内容将被添加到 SQL 语句中;如果没有
标签的条件为真,将使用
标签的内容。
<choose>
<when test="name != null">
AND name=#{name}
when>
<when test="age != null">
AND age=#{age}
when>
<otherwise>
AND id=#{id}
otherwise>
choose>
在这个例子中,如果参数 name
不为空,则将 “AND name=#{name}” 添加到 SQL 语句中;如果参数 age
不为空,则将 “AND age=#{age}” 添加到 SQL 语句中;如果两者都为空,则将 “AND id=#{id}” 添加到 SQL 语句中。
标签:
标签用于迭代集合,为集合中的每个元素生成 SQL 片段。它的属性包括:
collection
:要迭代的集合。item
:集合中每个元素的变量名。index
:集合中每个元素的索引名(可选)。open
:生成的 SQL 片段的开始部分。separator
:生成的 SQL 片段的分隔符。close
:生成的 SQL 片段的结束部分。<foreach collection="names" item="name" index="index" open="(" separator="," close=")">
#{name}
foreach>
在这个例子中,
标签用于迭代名为 names
的集合,并在生成的 SQL 语句中使用逗号分隔每个元素。
public static final String REGULAR_NO_SPACE_OR_SPECIAL_CHARS = "^[^\\s~!@#$%^&*()_+`\\-=\\[\\]\\\\{}|;':\",./<>?]*$";
这个正则表达式可以匹配除了空格和特殊字符以外的任何字符,而 ^
和 $
保证了输入数据必须完全符合这个匹配规则,否则校验不通过。
如果你需要修改这个正则表达式以匹配其他特殊字符,只需要在方括号 []
中添加需要匹配的特殊字符即可。例如,如果你想允许输入数据中出现小数点和下划线,可以修改正则表达式为:
public static final String REGULAR_NO_SPACE_OR_SPECIAL_CHARS = "^[^\\s~!@#$%^&*()_+`\\-=\\[\\]\\\\{}|;':\",./<>?._]*$";
public static final String REGULAR_NO_SPACE_OR_SPECIAL_CHARS = "^[a-zA-Z0-9]*$";
这个正则表达式将会匹配仅包含字母(大写或小写)和数字的字符串。所有的空格和特殊字符都将被排除。
//手机号正则校验
public static final String REGULAR_PHONE_NUMBER = "^1(3|4|5|6|7|8|9)\\d{9}$";
//合同时间正则校验
public static final String REGULAR_CONTRACT_TIME = "^\\d{4}-\\d{2}-\\d{2}$";
//状态类型
public static final String REGULAR_STATUS_TYPE = "[1,2]";
//省份中号码正则校验
public static final String REGULAR_ID_NUMBER = "^(\\d{18}|\\d{15}|\\d{17}X)$";
//银行卡号正则校验
public static final String REGULAR_BANK_CARD_NO = "^([1-9]{1})(\\d{15}|\\d{16}|\\d{18})$";
//交易类型正则校验
public static final String REGULAR_TRANSACTION_TYPE = "[-1,10,20]";
//交易状态正则校验
public static final String REGULAR_TRANSACTION_STATUS = "[-1,0,50,100]";
//校验空格和特殊字符,如果存在则不通过校验
public static final String REGULAR_NO_SPACE_OR_SPECIAL_CHARS = "^[^\\s~!@#$%^&*()_+`\\-=\\[\\]\\\\{}|;':\",./<>?]*$";
RabbitMQ中的相关概念:
package com.crestv.lgpt.config.rabbitmq;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description:
* @Author guop
* @Date 2023/5/20 16:31
*/
@Configuration
public class RabbitMQTest {
@Bean("testExchange")
public Exchange exchange() {
return ExchangeBuilder.topicExchange("test_exchange").build();
}
@Bean("testQueue")
public Queue queue() {
return QueueBuilder.durable("test_mq").build();
}
@Bean
public Binding binding(@Qualifier("testExchange")Exchange exchange,@Qualifier("testQueue") Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with("test.#").noargs();
}
}
7.2消息可靠性投递
package com.crestv.lgpt.config.rabbitmq;
import com.crestv.lgpt.utils.ExtLogger;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
/**
* 自定义消息发送确认的回调
* 实现接口:implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback
* ConfirmCallback:只确认消息是否正确到达交换机中,不管是否到达交换机,该回调都会执行;(不安全),被淘汰了,
* 因为消息只要不到达队列都算丢失
* returnedMessage:如果消息从交换机未正确到达队列中将会执行,正确到达则不执行;把消息记录到日志,至少没丢
* 还可以记录到数据库或者Redis
*/
@Component
public class CustomConfirmAndReturnCallback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {
private static final ExtLogger LOGGER = ExtLogger.getLogger(CustomConfirmAndReturnCallback.class);
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnsCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack) {
LOGGER.message("{} 到达队列成功!", correlationData.getId());
} else {
LOGGER.message("{} 到达队列失败! 原因: {}", correlationData.getId(), cause);
}
}
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
LOGGER.message("消息: {}, 错误码: {}, 错误原因: {}, 交换机: {}, 路由: {}",
new String(returnedMessage.getMessage().getBody(), StandardCharsets.UTF_8),
returnedMessage.getReplyCode(),
returnedMessage.getReplyText(),
returnedMessage.getExchange(),
returnedMessage.getRoutingKey());
}
}
org.springframework.boot
spring-boot-starter-parent
2.4.0
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-validation
org.projectlombok
lombok
1.18.10
com.alibaba
fastjson
1.2.76
org.bouncycastle
bcprov-jdk16
1.46
commons-beanutils
commons-beanutils
1.9.4
com.fasterxml.jackson.core
jackson-databind
org.apache.httpcomponents
httpclient
4.5.13
org.apache.commons
commons-compress
1.20
org.apache.httpcomponents
httpmime
4.5.13
junit
junit
org.apache.commons
commons-lang3
3.12.0
io.swagger
swagger-annotations
1.5.21
io.swagger
swagger-models
1.5.21
io.springfox
springfox-swagger2
2.9.2
io.swagger
swagger-annotations
io.swagger
swagger-models
io.springfox
springfox-swagger-ui
2.9.2
com.github.xiaoymin
knife4j-spring-boot-starter
2.0.9
io.github.resilience4j
resilience4j-ratelimiter
1.7.1
com.qcloud
cos_api
5.6.97
#腾讯云 cos
tencent.cos.SecretId=自己的
tencent.cos.SecretKey=自己的
tencent.cos.region=ap-guangzhou
tencent.cos.bucketName=crest-test-1305644510
# 端口
server.port=6066
#修改文件大小限制
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=1000MB
package com.test.costest.controller;
import com.test.costest.service.FileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
@Api(tags = "腾讯云demo")
@RestController
@RequestMapping("/front")
public class FrontFileController {
@Resource
private FileService fileService;
@PostMapping(value="/fillLoad")
@ApiOperation(value = "上传文件")
public void addUser(@RequestPart("file") MultipartFile file) {
fileService.upload(file);
}
@GetMapping(value = "/downFile")
@ApiOperation(value = "下载文件")
public void down(String key,String localFolderPath) {
fileService.download(key, localFolderPath);
}
}
package com.test.costest.service;
import org.springframework.web.multipart.MultipartFile;
public interface FileService {
/**
* cos上传对象
* @param file
*/
void upload(MultipartFile file);
/**
* 下载对象
* @param key
* @param localFolderPath
*/
void download(String key,String localFolderPath);
}
package com.test.costest.service.impl;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.qcloud.cos.transfer.Download;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import com.qcloud.cos.transfer.Upload;
import com.test.costest.service.FileService;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Description: 腾讯云demo
* @Author guop
* @Date 2023/4/21 8:34
*/
@Service
public class FileServiceImpl implements FileService {
@Value("${tencent.cos.SecretId}")
private String secretId;
@Value("${tencent.cos.SecretKey}")
private String secretKey;
@Value("${tencent.cos.region}")
private String region;
@Value("${tencent.cos.bucketName}")
private String bucketName;
/**
* cos上传对象
* @param file
*/
@Override
public void upload(MultipartFile file) {
// 使用高级接口必须先保证本进程存在一个 TransferManager 实例,如果没有则创建
// 详细代码参见本页:高级接口 -> 创建 TransferManager
TransferManager transferManager = createTransferManager();
String folder = new DateTime().toString("yyyy/MM/dd/");
//文件名:uuid.扩展名
String fileName = UUID.randomUUID().toString();
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String key = folder + fileName + fileExtension;
// 对象键(Key)是对象在存储桶中的唯一标识。
File tempFile = null;
try {
tempFile = File.createTempFile("temp", null);
file.transferTo(tempFile);
tempFile.deleteOnExit();
} catch (IOException e) {
e.printStackTrace();
}
// 创建 PutObjectRequest 对象
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, tempFile);
// 设置存储类型(如有需要,不需要请忽略此行代码), 默认是标准(Standard), 低频(standard_ia)
// 更多存储类型请参见 https://cloud.tencent.com/document/product/436/33417
putObjectRequest.setStorageClass(StorageClass.Standard_IA);
try {
// 高级接口会返回一个异步结果Upload
// 可同步地调用 waitForUploadResult 方法等待上传完成,成功返回 UploadResult, 失败抛出异常
Upload upload = transferManager.upload(putObjectRequest);
UploadResult uploadResult = upload.waitForUploadResult();
} catch (CosServiceException e) {
e.printStackTrace();
} catch (CosClientException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 确定本进程不再使用 transferManager 实例之后,关闭之
// 详细代码参见本页:高级接口 -> 关闭 TransferManager
shutdownTransferManager(transferManager);
}
/**
* 下载对象
*
* @param key
* @param localFolderPath
*/
@Override
public void download(String key, String localFolderPath) {
// 使用高级接口必须先保证本进程存在一个 TransferManager 实例,如果没有则创建
// 详细代码参见本页:高级接口 -> 创建 TransferManager
TransferManager transferManager = createTransferManager();
// 对象键(Key)是对象在存储桶中的唯一标识。详情请参见 [对象键](https://cloud.tencent.com/document/product/436/13324)
// 提取文件名
String fileName = key.substring(key.lastIndexOf("/") + 1);
// 构建本地文件路径
String localFilePath = localFolderPath + File.separator + fileName;
File downloadFile = new File(localFilePath);
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
try {
// 返回一个异步结果 Download, 可同步的调用 waitForCompletion 等待下载结束, 成功返回 void, 失败抛出异常
Download download = transferManager.download(getObjectRequest, downloadFile);
download.waitForCompletion();
} catch (CosServiceException e) {
e.printStackTrace();
} catch (CosClientException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 确定本进程不再使用 transferManager 实例之后,关闭之
// 详细代码参见本页:高级接口 -> 关闭 TransferManager
shutdownTransferManager(transferManager);
}
void shutdownTransferManager(TransferManager transferManager) {
// 指定参数为 true, 则同时会关闭 transferManager 内部的 COSClient 实例。
// 指定参数为 false, 则不会关闭 transferManager 内部的 COSClient 实例。
transferManager.shutdownNow(true);
}
// 创建 TransferManager 实例,这个实例用来后续调用高级接口
TransferManager createTransferManager() {
// 创建一个 COSClient 实例,这是访问 COS 服务的基础实例。
// 详细代码参见本页: 简单操作 -> 创建 COSClient
COSClient cosClient = createCOSClient();
// 自定义线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM,同地域上传 COS)的情况下,设置成16或32即可,可较充分的利用网络资源
// 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。
ExecutorService threadPool = Executors.newFixedThreadPool(32);
// 传入一个 threadpool, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。
TransferManager transferManager = new TransferManager(cosClient, threadPool);
// 设置高级接口的配置项
// 分块上传阈值和分块大小分别为 5MB 和 1MB
TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration();
transferManagerConfiguration.setMultipartUploadThreshold(5 * 1024 * 1024);
transferManagerConfiguration.setMinimumUploadPartSize(1 * 1024 * 1024);
transferManager.setConfiguration(transferManagerConfiguration);
return transferManager;
}
// 创建 COSClient 实例,这个实例用来后续调用请求
COSClient createCOSClient() {
// 设置用户身份信息。
// SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理
COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
// ClientConfig 中包含了后续请求 COS 的客户端设置:
ClientConfig clientConfig = new ClientConfig();
// 设置 bucket 的地域
// COS_REGION 请参见 https://cloud.tencent.com/document/product/436/6224
clientConfig.setRegion(new Region(region));
// 设置请求协议, http 或者 https
// 5.6.53 及更低的版本,建议设置使用 https 协议
// 5.6.54 及更高版本,默认使用了 https
clientConfig.setHttpProtocol(HttpProtocol.https);
// 以下的设置,是可选的:
// 设置 socket 读取超时,默认 30s
clientConfig.setSocketTimeout(30 * 1000);
// 设置建立连接超时,默认 30s
clientConfig.setConnectionTimeout(30 * 1000);
// 如果需要的话,设置 http 代理,ip 以及 port
// clientConfig.setHttpProxyIp("httpProxyIp");
// clientConfig.setHttpProxyPort(80);
// 生成 cos 客户端。
return new COSClient(cred, clientConfig);
}
}
package com.test.costest.dto;
import lombok.Data;
@Data
public class ResponseDto<T> {
/**
* 状态码
*/
private Integer code;
/**
* 状态信息
*/
private String message;
/**
* 数据
*/
private T data;
public ResponseDto(Integer code, String message, T data) {
this.code = code;
this.data = data;
this.message = message;
}
public ResponseDto(T data) {
this.code = 10000;
this.data = data;
this.message = "成功并返回数据";
}
}
package com.test.costest.config;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.region.Region;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class ConstantProperties {
@Value("${tencent.cos.SecretId}")
private String secretId;
@Value("${tencent.cos.SecretKey}")
private String secretKey;
@Value("${tencent.cos.region}")
private String region;
@Value("${tencent.cos.bucketName}")
private String bucketName;
// @Value("${tencent.cos.url}")
// private String path;
@Bean
public COSClient cosClient(){
// 1 初始化用户身份信息(secretId, secretKey)。
COSCredentials cred = new BasicCOSCredentials(this.secretId, this.secretKey);
// 2 设置 bucket 的区域, COS 地域的简称请参照
Region region = new Region(this.region);
ClientConfig clientConfig = new ClientConfig(region);
// 3 生成 cos 客户端。
COSClient cosClient = new COSClient(cred, clientConfig);
return cosClient;
}
}
package com.test.costest.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableKnife4j
@EnableSwagger2
@ConditionalOnExpression
public class Knife4jConfig {
@Bean(value = "createRestApi")
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.test.costest.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("lgpt项目对接文档").version("1.0").build();
}
}
package com.test.costest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
public class CosProjectApplication {
public static void main(String[] args) {
SpringApplication.run(CosProjectApplication.class, args);
log.info("http://localhost:6066/doc.html");
}
}
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
SET N := N-1;
RETURN (
# Write your MySQL query statement below.
SELECT
salary
FROM
employee
GROUP BY
salary
ORDER BY
salary DESC
LIMIT N, 1
);
END
这是一个 MySQL 存储过程,它的目的是返回员工工资表中第 N 高的薪资数额。下面是对存储过程中各个部分的解释:
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
:这一行代码定义了一个名为 getNthHighestSalary
的函数,该函数需要一个名为 N 的整数参数,并且将返回一个整数。BEGIN
:这标志着存储过程代码的开始。set n:=n-1;
:这一行将传递给函数的参数 N
减去 1 并赋值给变量 n
。这是因为在 SQL 中,limit
从 0 开始计数,所以我们需要将 N
减 1 才能获得正确的结果。RETURN
:这个关键字表明函数要返回一个值。select salary from employee group by salary order by salary desc limit n,1
:这是实际执行查询的代码。它从 employee
表中选择 salary
列,然后按照 salary
列进行分组,并按照 salary
列的降序对组进行排序。最后,它使用 limit
子句返回第 n
行,而 1
表示只返回一行。END
:这表示存储过程代码的结束。综上所述,该函数返回第 N 高的薪资数额,它使用了 SQL 中的分组和排序功能以及 limit
子句来实现。
SELECT Score,
dense_rank() over(order by Score desc) as 'Rank'
FROM Scores
这段 SQL 查询语句使用了窗口函数 dense_rank()
来为 Scores
表中的每个成绩计算排名,并将结果集中每个成绩的排名作为一个新的列名 Rank
返回。下面对这段 SQL 查询语句进行逐一解释:
SELECT Score,
dense_rank() over(order by Score desc) as 'Rank'
FROM Scores
SELECT Score
:选取 Scores
表中的 Score
列,这是查询的输出列之一。dense_rank() over(order by Score desc)
:使用窗口函数 dense_rank()
计算每个成绩在所有成绩中的排名。在 over()
子句中,order by Score desc
指定按照 Score
列的降序排列来计算排名。因此,排名最高的成绩的排名为 1,排名第二的成绩的排名为 2,以此类推。as 'Rank'
:将计算出的排名结果命名为一个新的列名 Rank
。FROM Scores
:指定查询的数据源是 Scores
表。综合来看,这段 SQL 查询语句将返回一个结果集,其中包含了 Scores
表中每个成绩的值和其在所有成绩中的排名。例如,如果 Scores
表中有以下成绩数据:
Score
-----
80
90
75
85
那么,这段 SQL 查询语句将返回以下结果:
Score | Rank
------+------
90 | 1
85 | 2
80 | 3
75 | 4
其中,每个成绩的排名根据其在所有成绩中的位置进行计算,且按照成绩的降序排列来排序。因此,90 分是所有成绩中排名第一,85 分是排名第二,80 分是排名第三,75 分是排名第四。
dense_rank()
是 MySQL 中的一种窗口函数,它用于计算在一个有序集合中的某个元素的密集排名。与 rank()
和 row_number()
函数不同,dense_rank()
在处理重复值时,会跳过重复值并根据下一个可用的排名计算密集排名。
具体来说,dense_rank()
函数在对一个数据集中的某一列进行排序后,为每一行计算一个排名值,排名值与该行在排序后的数据集中的位置相关。如果有多个行具有相同的排序键(排序列的值),则它们的密集排名将相同,并且下一个可用的排名将是它们的下一个非重复值的密集排名。
以下是使用 dense_rank()
函数计算密集排名的示例:
假设有一个包含以下数据的表:
Score
-----
80
90
75
85
80
要计算 Score 列中每个值的密集排名,可以使用以下 SQL 语句:
SELECT Score, dense_rank() over(order by Score desc) as 'Rank' FROM Scores;
执行这个查询语句后,将得到如下结果:
Score | Rank
------+------
90 | 1
85 | 2
80 | 3
80 | 3
75 | 4
在这个例子中,dense_rank()
函数按 Score 列的值进行降序排序,并为每个值计算一个密集排名。在数据集中,有两个值为 80,它们的密集排名相同,都是第 3 名。第 4 名是值为 75 的行,因为前面有三个不同的值(90、85 和 80),它们的密集排名分别是第 1、第 2 和第 3 名。
在 MySQL 中,有三种常用的窗口函数:RANK()
、DENSE_RANK()
和 ROW_NUMBER()
。它们都可以用于计算排名和行号,但计算方式略有不同。
RANK()
:计算排名,排名相同的数据具有相同的排名,并跳过下一个排名。例如,如果有两个数据的排名是第 1 名,则下一个排名应该是第 3 名。因此,RANK()
函数会跳过第 2 名。例如,如果有以下成绩数据:Score
-----
80
90
75
85
80
那么,RANK()
函数将返回以下排名结果:
Score | Rank
------+-----
90 | 1
85 | 2
80 | 3
80 | 3
75 | 5
DENSE_RANK()
:计算密集排名,与 RANK()
函数类似,但是对于排名相同的数据,密集排名不会跳过下一个排名。例如,如果有两个数据的排名是第 1 名,则下一个密集排名仍然是第 2 名。因此,DENSE_RANK()
函数不会跳过任何排名。例如,如果有以下成绩数据:Score
-----
80
90
75
85
80
那么,DENSE_RANK()
函数将返回以下密集排名结果:
Score | Rank
------+-----
90 | 1
85 | 2
80 | 3
80 | 3
75 | 4
ROW_NUMBER()
:计算行号,为每行分配一个唯一的行号。行号不考虑任何排名或排序规则,只与结果集中的行顺序相关。例如,如果有以下成绩数据:Score
-----
80
90
75
85
那么,ROW_NUMBER()
函数将返回以下行号结果:
Score | RowNumber
------+----------
80 | 1
90 | 2
75 | 3
85 | 4
需要注意的是,这些函数都是 MySQL 中的窗口函数,它们只能在 OVER()
子句中使用,以指定窗口的范围和排序规则。
泛型类和泛型方法是 Java 语言提供的一种类型安全机制,可以让程序员在编写代码时更加灵活地处理不同类型的数据。
声明一个泛型类需要在类名后加上尖括号,括号内是一个或多个用逗号分隔的类型参数。例如,下面是一个用于表示一对值的泛型类 Pair:
public class Pair<T, U> {
private T first;
private U second;
public Pair(T first, U second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public U getSecond() {
return second;
}
}
在上面的例子中,T 和 U 是类型参数,表示 Pair 类可以存储两个不同类型的对象。当创建 Pair 类的对象时,需要指定类型参数的具体类型。例如:
Pair<String, Integer> pair = new Pair<>("hello", 123);
String first = pair.getFirst();
int second = pair.getSecond();
在上面的代码中,我们创建了一个 Pair 对象,指定了 T 类型为 String,U 类型为 Integer。可以通过 getFirst 和 getSecond 方法获取存储在 Pair 对象中的值。
声明一个泛型方法需要在方法名前加上尖括号,括号内是一个或多个用逗号分隔的类型参数。例如,下面是一个用于打印数组元素的泛型方法 printArray:
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
在上面的例子中,T 是类型参数,表示 printArray 方法可以接受任意类型的数组。可以调用该方法并传入一个具体类型的数组,例如:
String[] strings = {"hello", "world"};
Integer[] integers = {1, 2, 3};
printArray(strings); // 输出 "hello" 和 "world"
printArray(integers); // 输出 1、2 和 3
在上面的代码中,我们调用了 printArray 方法并分别传入了一个 String 数组和一个 Integer 数组。由于 printArray 是一个泛型方法,它可以接受任意类型的数组作为参数。
在泛型类或泛型方法中,类型参数 T 可以出现在多个不同的位置,具体代表的含义如下:
需要注意的是,在一个泛型类中,类型参数可以被多次使用,并且可以用不同的字母来表示不同的类型参数。例如,下面是一个泛型类 Triple,它用于表示三个值:
public class Triple<T, U, V> {
private T first;
private U second;
private V third;
public Triple(T first, U second, V third) {
this.first = first;
this.second = second;
this.third = third;
}
public T getFirst() {
return first;
}
public U getSecond() {
return second;
}
public V getThird() {
return third;
}
}
在上面的例子中,我们定义了三个类型参数 T、U 和 V,用于表示 Triple 类可以存储三个不同类型的值。在 Triple 类的构造方法和成员方法中,我们使用了类型参数 T、U 和 V 来指定参数类型或返回值类型。
可以通过下面的代码来创建 Triple 类的对象:
Triple<String, Integer, Boolean> triple = new Triple<>("hello", 123, true);
String first = triple.getFirst();
int second = triple.getSecond();
boolean third = triple.getThird();
在上面的代码中,我们创建了一个 Triple 对象,指定了 T 类型为 String,U 类型为 Integer,V 类型为 Boolean。可以通过 getFirst、getSecond 和 getThird 方法获取存储在 Triple 对象中的值。
总的来说,泛型类和泛型方法可以让我们在编写代码时更加灵活地处理不同类型的数据,从而提高代码的可复用性和可读性。通过使用类型参数,我们可以将代码从特定的数据类型中解耦出来,使代码更加通用和易于维护。
泛型方法是 Java 语言中的一种特殊方法,它可以接受任意类型的参数,并且可以在方法内部使用泛型类型。相比于普通方法,泛型方法具有以下优点:
提高代码复用性:泛型方法可以接受任意类型的参数,因此可以被多个不同类型的对象调用,从而提高代码的复用性。
增加代码灵活性:泛型方法可以在方法内部使用泛型类型,因此可以处理不同类型的数据,从而增加代码的灵活性。
增加代码安全性:泛型方法可以在编译时检查类型安全,防止因为类型转换错误导致的运行时异常。
在方法前声明泛型类型参数的语法如下:
public <T> void methodName(T param) {
// 方法体
}
在上面的代码中, 表示声明一个类型参数 T,它可以在方法内部使用。可以在方法参数列表中使用泛型类型参数 T,如上面的例子中的 param 参数。在方法体内部,可以使用 T 类型参数来声明变量、调用方法等。
当调用泛型方法时,编译器会根据传入的参数类型推断出泛型类型参数 T 的具体类型。例如,下面是一个使用泛型方法的例子:
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
Integer[] integers = {1, 2, 3};
String[] strings = {"hello", "world"};
printArray(integers);
printArray(strings);
}
在上面的例子中,我们声明了一个泛型方法 printArray,它可以接受任意类型的数组作为参数,并且可以打印数组中的每一个元素。在 main 方法中,我们分别传入了一个 Integer 数组和一个 String 数组,printArray 方法会根据传入的参数类型自动推断出泛型类型参数 T 的具体类型,从而可以正确地打印数组中的元素。
需要注意的是,泛型方法只对当前方法有效,并不会对整个类产生影响。因此,如果要在类中多个方法中使用泛型类型参数,需要在每个方法前都声明一次。
泛型和 Object 是 Java 语言中两种不同的类型处理机制,它们有以下区别:
类型安全性不同:泛型提供了编译时的类型检查机制,可以在编译期间检测类型错误,从而提高了类型安全性。而 Object 类型在编译期间不会检查类型,需要在运行时进行类型转换,容易导致 ClassCastException 异常。
可读性和可维护性不同:使用泛型可以提高代码的可读性和可维护性,因为它能够使代码更加清晰明了。相反,使用 Object 类型需要手动进行类型转换,增加了代码的复杂性,降低了代码的可读性和可维护性。
代码重用性不同:泛型可以让代码更加通用,从而提高代码的重用性。因为泛型可以在不同的类型之间进行转换,使得同一个方法可以处理不同类型的数据。而使用 Object 类型,则需要针对不同类型写不同的方法,代码重用性较低。
性能方面不同:在性能方面,使用 Object 类型进行类型转换会降低程序的性能,因为需要在运行时进行类型检查和转换。而泛型在编译时已经确定了类型,因此可以避免这种性能损失。
综上所述,泛型和 Object 在类型安全性、可读性和可维护性、代码重用性以及性能方面有不同的表现。在编写代码时,应根据实际情况选择使用泛型还是 Object。如果需要在编译时检查类型安全性,并且希望代码更加通用和易于维护,应使用泛型;如果对类型安全性要求不高,或者需要处理多个不相关的类型,可以使用 Object 类型。
要将 OuterRequestVo
对象转换为 JSON 格式的字符串,可以使用 JSON 库来实现。常用的 JSON 库有 Google 的 Gson、阿里巴巴的 Fastjson 等。
以 Gson 为例,可以使用以下代码将 OuterRequestVo
对象转换为 JSON 格式的字符串:
Gson gson = new Gson();
String json = gson.toJson(outerRequestVo);
在上面的代码中,我们先创建了一个 Gson 对象,然后使用 toJson 方法将 outerRequestVo
对象转换为 JSON 格式的字符串。转换后的字符串可以通过 json
变量获取到。
如果使用 Fastjson,则可以使用以下代码将 OuterRequestVo
对象转换为 JSON 格式的字符串:
String json = JSON.toJSONString(outerRequestVo);
在上面的代码中,我们使用 JSON 工具类的 toJSONString 方法将 outerRequestVo
对象转换为 JSON 格式的字符串。转换后的字符串可以直接通过 json
变量获取到。
需要注意的是,转换为 JSON 格式的字符串需要保证 OuterRequestVo
对象中的属性都是可序列化的,否则可能会出现异常。通常情况下,只要 OuterRequestVo
中的属性都是基本数据类型、字符串、集合或其他可序列化的类型,就可以顺利进行转换。
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它以简洁、易读的方式表示结构化数据。JSON 是一种文本格式,常用于数据的序列化和传输。
String:在编程中,String 是一种数据类型,表示一串字符。在 JSON 中,数据通常以字符串形式表示。
JSONString:JSONString 是 Java 中的一个接口,它定义了将对象转换为 JSON 格式的方法。实现该接口的类可以自定义对象转换为 JSON 字符串的逻辑。
JsonObject:JsonObject 是 JSON 中的一个概念,表示一个 JSON 对象,它由一组键值对组成,键是字符串,值可以是 JSON 的各种类型(字符串、数字、布尔值、数组、嵌套的 JSON 对象等)。
在 Java 中,可以使用不同的库进行 JSON 的解析和生成。其中,fastjson 是一种常用的 JSON 处理库,它提供了丰富的 API 来处理 JSON 数据。下面是使用 fastjson 进行相互转换的示例:
JSON 字符串与 Java 对象之间的转换:
String jsonString = "{\"name\":\"John\",\"age\":30}";
JSONObject jsonObject = JSON.parseObject(jsonString);
Person person = new Person("John", 30);
String jsonString = JSON.toJSONString(person);
JSON 字符串与 JsonObject 之间的转换:
String jsonString = "{\"name\":\"John\",\"age\":30}";
JSONObject jsonObject = JSON.parseObject(jsonString);
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "John");
jsonObject.put("age", 30);
String jsonString = jsonObject.toJSONString();
Java 对象与 JsonObject 之间的转换:
Person person = new Person("John", 30);
JSONObject jsonObject = (JSONObject) JSON.toJSON(person);
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "John");
jsonObject.put("age", 30);
Person person = JSON.toJavaObject(jsonObject, Person.class);
通过上述示例,你可以看到 fastjson 提供了 parseObject()
方法用于将 JSON 字符串解析为 JsonObject 或 Java 对象,toJSONString()
方法用于将 JsonObject 或 Java 对象转换为 JSON 字符串。同时,你还可以通过 toJSON()
方法将 Java 对象转换为 JsonObject,或者使用 toJavaObject()
方法将 JsonObject 转换为 Java 对象。
需要注意的是,以上示例中的 Person
类是一个自定义的类,你可以根据实际需要定义自己的 Java 类来映射 JSON 数据。
JSONObject
和 JSONString
是 JSON 数据处理中的两个不同的概念和类:
JSONObject
:JSONObject
是一种数据结构或数据类型,在不同的编程语言中具有不同的实现。在 Java 中,JSONObject
是 fastjson 库提供的一个类,用于表示 JSON 对象。它由一组键值对组成,键是字符串,值可以是各种 JSON 数据类型(字符串、数字、布尔值、数组、嵌套的 JSON 对象等)。JSONObject
类提供了方法来访问和操作 JSON 对象的键值对。
JSONString
:JSONString
是一个接口,用于表示可以被转换为 JSON 字符串的对象。在 Java 中,fastjson 库为 JSONString
提供了默认的实现。当一个类实现了 JSONString
接口,并重写了 toJSONString()
方法时,fastjson 在将该类转换为 JSON 字符串时会使用该方法的逻辑,而不是默认的对象转换规则。通过实现 JSONString
接口,你可以对对象转换为 JSON 字符串的行为进行自定义。
区别:
JSONObject
是表示 JSON 对象的数据结构或类,用于在代码中操作和访问 JSON 数据的键值对。JSONString
是一个接口,用于定义对象如何转换为 JSON 字符串的规则。它允许你自定义对象转换为 JSON 字符串的逻辑,而不依赖于默认的转换规则。在 fastjson 中,JSONObject
类用于表示和操作 JSON 对象,而 JSONString
接口可以让你自定义对象如何转换为 JSON 字符串。
单点登录(Single Sign-On,简称 SSO)是一种身份验证机制,允许用户使用一组凭据(如用户名和密码)登录多个与之关联的系统或服务。通过这种方式,用户无需为每个系统或服务单独登录,从而提高了用户体验和操作便利性。在用户登录某个系统或服务后,其他关联的系统将自动识别该用户,无需再次验证身份。
单点登录的使用场景包括:
实现单点登录的方法有多种,如 OAuth、OpenID Connect、SAML(安全断言标记语言)等。这些方法都有各自的优缺点,具体选择需要根据实际场景和需求进行评估。
在 Java 中实现单点登录(Single Sign-On, SSO)和单会话限制(Single Session Restriction)的方法有很多。这里我将简要介绍两种常见的方法。
使用 Spring Security 可以轻松实现单点登录。常见的方法是使用 OAuth2 和 OpenID Connect。以下是实现 SSO 的简要步骤:
要在 Spring Security 中实现单会话限制,可以使用其内置的会话管理功能。以下是实现单会话限制的简要步骤:
在 Spring Security 配置类中,启用会话管理:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1)
.expiredUrl("/session-expired");
}
}
在这个示例中,我们设置了 maximumSessions(1)
,这意味着每个用户只允许有一个活跃会话。如果用户尝试在另一个设备或浏览器上登录,之前的会话将失效,并将用户重定向到 /session-expired
页面。
根据需要,创建一个 /session-expired
页面,用于告知用户他们的会话已过期。
这些方法可以帮助您在 Java 应用程序中实现单点登录和单会话限制。当然,实际的实现可能需要更多详细的配置和调整,以满足特定的需求和场景。
flyway:
enabled: true
locations: classpath:db/migration/mysql
# 当迁移时发现目标schema非空,而且带有没有元数据的表时,是否自动执行基准迁移
baseline-on-migrate: true
# 是否允许无序的迁移 开发环境最好开启, 生产环境关闭
out-of-order: true
# 关闭占位符替换,因为插入的sql里边可能包含${}关键字
placeholder-replacement: false
这是一个Spring Boot应用中的Flyway配置。以下是每个属性的含义:
flyway.enabled: true
:这个属性表示启用Flyway。如果设置为false
,Flyway将被禁用,不会执行任何数据库迁移。
flyway.locations: classpath:db/migration/mysql
:这个属性指定了存放SQL迁移脚本的位置。在这个例子中,脚本位于src/main/resources/db/migration/mysql
目录下。
flyway.baseline-on-migrate: true
:当你的数据库不是空的,且还没有Flyway的元数据表时,这个属性决定了是否在首次迁移时创建一个基线。也就是说,Flyway会假设现有的数据库所有的表都已经在基线版本中,然后只执行基线版本之后的迁移。
flyway.out-of-order: true
:这个属性决定了当发现迁移脚本的版本号比已经迁移的版本号小的时候,是否应该运行这个迁移脚本。在开发环境中,开发人员可能会并行开发多个特性,这时可能需要这个选项。在生产环境中,通常应该保持版本号的严格顺序,所以这个选项通常应该关闭。
flyway.placeholder-replacement: false
:这个属性用于开启或关闭占位符替换。占位符是SQL脚本中的特殊字符串,比如${tableName}
,Flyway会在运行SQL脚本之前替换这些占位符。在这个例子中,由于SQL脚本中可能包含${}
,但并不希望它们被替换,所以关闭了占位符替换。
Flyway是一个开源的数据库版本控制和迁移工具。它可以帮助你管理和控制数据库的版本,包括表结构和数据的变化。Flyway使用一系列的SQL脚本(也可以是Java代码)来进行数据库的迁移,每个脚本对应数据库的一个版本。
简单来说,你提供的Flyway配置做的事情如下:
启用Flyway:Flyway会在你的应用启动时运行。
指定SQL脚本的位置:Flyway会在classpath:db/migration/mysql
这个路径下查找SQL脚本。
在数据库不是空的情况下执行基线迁移:如果你的数据库已经有一些表,但还没有使用Flyway管理,Flyway会把当前的数据库状态作为初始版本,然后只执行新的迁移。
允许无序的迁移:如果你有新的SQL脚本,即使它的版本号比已经执行的版本号小,Flyway也会执行它。
关闭占位符替换:在你的SQL脚本中,Flyway不会尝试替换${}
这样的占位符。
总的来说,Flyway会在你的应用启动时,按照SQL脚本的版本顺序(可以不严格按照顺序)执行这些脚本,以此来升级(或降级)你的数据库。这对于在多个环境(如开发、测试、生产环境)保持数据库的一致性,以及追踪数据库的变化非常有用。
@Configuration是一个注解,表示这个类是一个Java配置类,用于定义Bean的配置信息。在Spring中,Bean是一个由Spring容器实例化、管理和组装的对象,可以是任何的Java对象。
@Configuration通常与@Bean一起使用,@Bean用于定义一个Bean,其返回值就是Bean的实例。在@Configuration类中,我们可以定义多个@Bean方法,用于实例化不同的Bean,并配置它们之间的依赖关系。当Spring容器初始化时,会自动调用这些@Bean方法来创建Bean实例,然后将它们装配到应用程序中。
@Configuration也可以使用@ComponentScan注解来扫描指定的包,自动装配标有@Component等注解的Bean。此外,@Import注解可以用来导入其他配置类,从而将它们的Bean定义合并到当前配置类中。
定时任务是指按照一定的时间间隔或特定的时间点自动执行的任务。在Java中,我们可以使用Spring框架提供的@Scheduled
注解来实现定时任务。该注解可以标注在方法上,并且可以设置定时任务执行的时间间隔、执行时间点等属性。
下面是一个简单的定时任务的示例:
@Component
public class MyTask {
@Scheduled(fixedRate = 5000) //每隔5秒执行一次
public void task() {
//定时任务的具体逻辑
System.out.println("定时任务执行了");
}
}
在上述示例中,我们创建了一个名为MyTask
的定时任务类,并标注了@Scheduled
注解。该注解中的fixedRate
属性表示每隔5秒执行一次该方法。
除了fixedRate
属性外,@Scheduled
注解还支持其他属性,如cron
、fixedDelay
等,可以根据具体需求进行设置。
需要注意的是,我们需要在Spring的配置类中添加@EnableScheduling
注解,以开启定时任务的支持。例如:
@Configuration
@EnableScheduling
public class AppConfig {
//其他配置
}
@RequestParam和@RequestBody是Spring MVC框架中常用的两种参数传递方式:
@RequestParam用于获取请求参数,一般用于接收GET请求中的参数或者POST请求中的简单参数,比如字符串或数字等。在方法中使用@RequestParam注解,指定参数名和是否必填,如:
@GetMapping("/user")
public User getUser(@RequestParam("id") Long id, @RequestParam(value = "name", required = false) String name) {
// ...
}
@RequestBody用于获取请求体中的数据,一般用于接收POST请求中的复杂参数,比如JSON格式的数据。在方法中使用@RequestBody注解,将请求体中的数据绑定到指定的Java对象上,如:
@PostMapping("/user")
public User createUser(@RequestBody User user) {
// ...
}
需要注意的是,@RequestBody只能用于POST请求,且请求体中的数据需要是可读取的字符流,因此需要设置Content-Type为application/json等格式。
加上@RequestBody后请求类型变成"body",也是是接收的数据是个对象,如果不加注解那么请求类型就会变成query,拼接在请求地址上,这样就会变的不安全,所以post方法都加上@requestbody注解,并且params的值只能是一个字符串,不能传递对象类型的参数。在HTTP协议中,GET请求是通过URL传递参数的,而POST请求是通过请求体(RequestBody)传递参数的。因此,当使用@RequestBody注解时,Spring会将请求体中的数据解析为参数。而不使用@RequestBody注解时,Spring会将参数解析为URL中的查询参数(query parameter)。
@RequestParam 是 SpringMVC 提供的一种用于获取请求参数的注解。它可以用来获取 GET 和 POST 请求中的参数,它主要用于获取简单类型的参数,比如 String、int 等。
而对于文件上传的情况,我们一般使用 MultipartFile 来接收文件。但是,在 SpringMVC 中,如果我们使用 @RequestParam 注解来接收参数,如果参数的类型是 MultipartFile,它会自动将上传的文件转化为 MultipartFile 对象。
这是因为,SpringMVC 内部会使用 CommonsMultipartResolver 来处理文件上传,而 CommonsMultipartResolver 可以将 multipart/form-data 类型的请求中的参数封装成 MultipartFile 类型的对象,供开发者使用。因此,即使使用 @RequestParam 注解来接收参数,也可以获取到上传的文件。
HTML、CSS、JavaScript 和 Vue 之间的关系是相辅相成的。它们在构建一个 Web 项目时各司其职,共同完成用户界面和交互功能。下面详细解释它们之间的关系:
、
等)组成的,用于定义页面的各个部分,如标题、段落、列表等。
- CSS(Cascading Style Sheets,层叠样式表):CSS 用于控制 HTML 元素的样式,包括颜色、字体、布局等。通过 CSS,我们可以为 HTML 页面添加各种视觉效果和布局样式,使页面更具吸引力。
- JavaScript(简称 JS):JavaScript 是一种脚本语言,用于为网页添加交互功能。与 HTML 和 CSS 不同,JavaScript 可以直接操作 DOM(文档对象模型),响应用户操作,实现动态效果,以及与后端服务器进行通信。
- Vue.js:Vue.js 是一个用于构建用户界面的 JavaScript 框架。它通过将 HTML、CSS 和 JavaScript 结合起来,提供了一个简洁、可扩展的方法来构建 Web 应用程序。Vue.js 采用组件化的开发方式,将 UI 拆分成多个独立的、可复用的组件,提高了开发效率和代码可维护性。
综上所述,HTML、CSS、JavaScript 和 Vue 之间的关系如下:
- HTML 提供了网页的基本结构和内容。
- CSS 控制了网页的样式和布局。
- JavaScript 提供了网页的交互功能。
- Vue.js 将 HTML、CSS 和 JavaScript 结合起来,提供了一种组件化的开发方式,简化了 Web 应用程序的开发和维护。
在使用 Vue.js 开发项目时,你依然需要使用 HTML、CSS 和 JavaScript 这三个基本技术。Vue.js 只是为你提供了一个更加高效、组织化的方式来组合和管理它们。
6.@Builder.Default
@Builder.Default
是Lombok库中的注解,可以在使用 @Builder
注解时提供默认值。使用该注解可以方便地为一个或多个属性指定默认值,如果在调用 build()
方法时没有为这些属性提供值,则会使用默认值。
@Builder
注解可以让我们编写更简洁的代码,可以使用链式调用的方式设置属性值,并且可以在不使用构造函数的情况下创建对象。 @Builder.Default
则是 @Builder
注解的一个补充,用于设置默认属性值。
例如,以下代码使用了 @Builder
和 @Builder.Default
注解:
@Builder
public class Person {
private String name;
private int age;
@Builder.Default
private String gender = "unknown";
}
这表示 gender
属性的默认值为 "unknown"
,如果在使用 Person.builder()
创建对象时没有设置 gender
属性的值,则使用该默认值。
7.IDEA配置类的注释模板
- File–>settings–>Editor–>File and Code Templates–>Files
8. @Getter @Setter @NoArgsConstructor @AllArgsConstructor @SuperBuilder
这是五个常用的Java注解。
- @Getter和@Setter注解用于自动生成JavaBean的getter和setter方法;
- @NoArgsConstructor和@AllArgsConstructor注解分别用于生成无参构造函数和全参构造函数;
- @SuperBuilder注解是Lombok库中的一个注解,它可以自动生成一个建造者模式的构造器,使得代码更加简洁易读。
在Java中,通常使用构造函数来初始化类的实例。但是,当有很多属性需要初始化时,构造函数就会变得很长并且难以管理。此时,可以使用建造者模式来创建对象,使代码更加简洁易读。
使用建造者模式需要定义一个内部静态类来作为建造者。这个建造者类包含与目标类相同的属性,并提供一个方法链来设置属性的值。最后,该建造者类提供一个 build()
方法来创建目标类的实例。
使用Lombok库中的 @SuperBuilder
注解可以自动生成这个建造者模式的代码。这个注解会自动添加一个带有所有属性的参数的构造函数、一个无参数构造函数、一个名为 builder()
的静态方法,以及一个名为 build()
的实例方法。
使用 @SuperBuilder
注解可以让代码更加简洁易读,而且省去了手动编写建造者模式的代码的时间和精力。
9.lambda中的@build注解
使用 Lombok 中的 @Builder 注解可以生成一个简洁易用的构造器,无需手动编写繁琐的构造器代码。@Builder 注解会自动生成一个静态内部类 Builder,该 Builder 中包含了所有字段的 set 方法,以及一个 build 方法。在使用时,只需在类上加上 @Builder 注解,并在需要创建对象时使用 Builder 进行链式调用即可。
例如,对于以下类:
public class User {
private String name;
private int age;
private String address;
public User(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
// 省略 getter 和 setter 方法
}
使用 @Builder 注解后的代码如下:
import lombok.Builder;
@Builder
public class User {
private String name;
private int age;
private String address;
}
使用时,可以通过链式调用 Builder 中的 set 方法来设置对象的属性,并最终调用 build 方法创建对象,如下所示:
User user = User.builder()
.name("Tom")
.age(18)
.address("China")
.build();
10.MapStruct
如果MapStruct中的映射没映射成功,build一下就可以了
11.@EqualsAndHashCode(callSuper = true)
@EqualsAndHashCode(callSuper = true) 表示在生成 equals 和 hashCode 方法时,同时调用父类的 equals 和 hashCode 方法。
在 Java 中,equals 和 hashCode 是用于比较对象相等性的方法。当一个类需要进行对象相等性比较时,通常需要重写这两个方法。在重写时,需要注意保留父类的实现,否则可能会导致不正确的结果。使用 @EqualsAndHashCode(callSuper = true) 可以确保在生成这两个方法时,同时调用父类的方法,从而保证正确性。
12.@requestBody的作用
如果传输的是单层json对象,我们后台可以直接用 @RequestParam接收
如果传输的是多层嵌套json对象,这个时候会就会出现数据丢失问题,所以我们使用@requestBody
13.实现序列化作用
当我们需要在 Java 应用程序之间或将 Java 对象存储到文件或数据库等持久化媒介中时,我们需要将 Java 对象序列化成字节流,然后再反序列化回 Java 对象。Java 序列化机制提供了将 Java 对象序列化成字节流的功能,同时也提供了将字节流反序列化回 Java 对象的功能。
当一个 Java 类实现了 Serializable 接口时,表示该类的对象可以被序列化。序列化后,Java 对象就可以被以二进制的形式保存到文件或数据库中,或者通过网络传输到远程节点。在反序列化时,Java 对象会从字节流中被还原出来。
当一个类实现 Serializable 接口后,需要指定一个 serialVersionUID。这个 serialVersionUID 是用来确定类的版本的。在反序列化时,如果序列化的字节流中的 serialVersionUID 与反序列化时类中的 serialVersionUID 不匹配,就会抛出 InvalidClassException 异常,这是因为序列化的字节流可能是从不同版本的类中序列化出来的。
因此,实现序列化并生成 serialVersionUID,可以确保在序列化和反序列化时类的版本一致,从而避免版本不兼容的问题。虽然不实现序列化也可以进行数据传输,但实现序列化可以提高数据传输的效率和可靠性,同时也方便将对象保存到文件或数据库中进行持久化存储。
13.1如果实现快捷键生成序列化UUID
如图搜索:serialVersionUID
14.IDEA快捷键
- CTRL + G 输入行号,快速切换到指定行
- CTRL + SHIFT + 数字键 :标记一行,之后可以通过CTRL + 数字快速跳转过去
15.使用单键代替分布式锁
16.枚举转JSON
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(OuterRequestCode.SUCCESS);
System.out.println(json); // {"code":"0","message":"success"}
17.bit
在32位虚拟机中,1字宽等于4字节,即32bit
18. trim
“.trim” 是一个字符串方法,用于去除字符串开头和结尾的空白字符(包括空格、制表符、换行符等)。具体来说,它会返回一个新的字符串,其中不包含开头和结尾的空白字符。
在代码中,通常会使用 “.trim” 方法来对用户输入的字符串进行预处理,去除可能存在的空白字符,以免对后续的数据处理和查询造成影响。例如,在查询条件中使用 “like” 条件进行模糊查询时,如果不去除字符串中的空白字符,可能会出现查询不到想要的结果的情况。
使用 “.trim” 方法的语法很简单,只需要在字符串后面加上 “.trim()” 即可。例如,“abc “.trim()” 的返回结果是 “abc”。需要注意的是,”.trim" 方法不会改变原始字符串,而是返回一个新的字符串。
19.获取当前月的起始和结束日期
// 获取当月的起始日期和结束日期
LocalDate currentDate = LocalDate.now();
LocalDate startDate = currentDate.withDayOfMonth(1);
LocalDate endDate = currentDate.plusMonths(1).withDayOfMonth(1).minusDays(1);
20.concat方法
concat() 方法是一种用于将两个或多个字符串连接起来创建一个新字符串的 JavaScript 字符串方法。它可以用于字符串、数组和类数组对象。
语法:
str.concat(string2, string3, ..., stringX)
其中,str 是必需的,而 string2 到 stringX 则是可选的。这些参数是要连接在一起的字符串,可以是常量字符串、变量、字符串表达式等。
示例:
var str1 = "Hello";
var str2 = "world!";
var str3 = " How are you?";
var res = str1.concat(" ", str2, str3);
console.log(res); // 输出 "Hello world! How are you?"
注意,如果你只是想将两个字符串连接起来,可以使用加号运算符 “+”,它也可以将两个或多个字符串连接起来,如下所示:
var str1 = "Hello";
var str2 = "world!";
var res = str1 + " " + str2;
console.log(res); // 输出 "Hello world!"
但如果你需要连接多个字符串,推荐使用 concat() 方法,因为它比使用加号运算符更高效。
21.参数校验注解
@Size(max = 20, message = "园区publicId限制")
package com.crestv.openapitest.exception;
import com.crestv.openapitest.vo.Result;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
/**
* @author guop
* @ClassName: GlobalExceptionHandler
* @Description: 全局异常处理器
* @date 2023/4/19
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 参数校验异常异常处理
@ExceptionHandler(value = BindException.class)
@ResponseBody
public Result validExceptionHander(BindException e) {
List<FieldError> fieldErrors = e.getFieldErrors();
StringBuffer errMessage = new StringBuffer();
fieldErrors.forEach(err -> {
errMessage.append(err.getDefaultMessage());
errMessage.append("! ");
});
return Result.failure(errMessage.toString());
}
}
-
首先,我们需要安装 Node.js 和 npm(Node 包管理器)。可以访问 Node.js 官网(https://nodejs.org/)下载并安装最新版本。
-
使用 npm 全局安装 Vue CLI:
npm install -g @vue/cli
-
创建一个 Vue 项目:
vue create my-project
其中,my-project
是你的项目名称,可以根据实际情况更改。
-
切换到项目目录:
cd my-project
-
编译项目:
npm run build
这将生成一个 dist
文件夹,其中包含编译后的文件。
-
将 dist
文件夹上传到你的服务器。你可以使用 FTP 工具,如 FileZilla,或者使用 SSH 进行上传。
-
安装并配置 Nginx。在你的服务器上安装 Nginx,然后编辑 Nginx 的配置文件,通常位于 /etc/nginx/conf.d/
或 /etc/nginx/sites-available/
目录下。创建一个新的配置文件,如 my-project.conf
,并添加以下内容:
server {
listen 80;
server_name example.com; # 将此处更改为你的域名
location / {
root /path/to/your/dist; # 将此处更改为你的 dist 文件夹路径
try_files $uri $uri/ /index.html;
}
}
保存文件后,创建一个符号链接到 sites-enabled
目录(如果需要):
sudo ln -s /etc/nginx/sites-available/my-project.conf /etc/nginx/sites-enabled/
-
重启 Nginx 服务:
sudo service nginx restart
22.Collections.singletonList作用
Collections.singletonList() 方法用于创建一个只包含一个元素的不可变列表。该方法接受一个参数,即要包含在列表中的元素,然后返回一个不可变列表。
由于返回的列表是不可变的,因此不能添加、删除或修改它的元素。这使得它非常适合用作只有一个元素的参数传递,尤其是在需要传递一个集合时。
23.Comparator.comparingInt方法有什么用
Comparator.comparingInt
方法是Java中的一个工具方法,用于根据提供的函数生成一个整数类型的比较器。它是java.util.Comparator
接口的一个静态方法。
这个方法的主要作用是根据提供的函数生成一个新的比较器,用于比较两个对象的整数值。这个函数通常是一个lambda表达式或方法引用,它接受一个对象作为输入参数,返回一个整数值。生成的比较器可以用于对整数值进行升序排序。
在我们的示例中,我们使用Comparator.comparingInt()
方法根据AmountAndServiceADto
对象的periodicTime
属性(即月份)生成一个比较器:
yearCollect.sort(Comparator.comparingInt(e -> Integer.parseInt(e.getPeriodicTime())));
这里,我们传入一个lambda表达式e -> Integer.parseInt(e.getPeriodicTime())
作为提供的函数。这个函数将AmountAndServiceADto
对象作为输入,使用Integer.parseInt()
方法将periodicTime
属性(月份的字符串表示)转换为整数,用于排序。
最后,将生成的比较器传递给sort()
方法,对yearCollect
列表进行升序排序。这样我们就可以确保返回的月份顺序从1到12。
24.空结果集和 null
空结果集和 null
是不同的概念:
- 空结果集(Empty result set):当你执行一个 SQL 查询,如果没有符合条件的记录,查询结果将返回一个空结果集。空结果集表示查询没有找到任何匹配的行,但查询本身是有效的。换句话说,空结果集是一个没有数据的表,但它仍然具有列名和列结构。
null
:在数据库中,null
是一个特殊的值,表示缺失或未知的数据。当某个字段没有值时,它的内容将被设置为 null
。null
不等于空字符串或零,它表示数据根本不存在。在查询结果中,null
会以特定的方式对待,例如在聚合函数和连接操作中。
当你执行一个查询并得到一个空结果集时,它表示查询已经执行,但没有找到符合条件的数据。而当查询结果中的某个字段值为 null
时,表示该字段的数据不存在或未知。
回到你的问题,当使用 LIMIT 1 OFFSET 1
查询第二高的薪水时,如果只有一个薪水值,查询会返回空结果集,而不是 null
。这是因为查询确实执行了,但没有找到符合条件的第二高的薪水。为了在这种情况下返回 null
,你需要使用子查询的方法,如我之前所示。
25.sql查询
你提供的查询语句本身是正确的,可以用来获得第二高的薪水。以下是你的查询:
SELECT DISTINCT
Salary AS SecondHighestSalary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1;
这个查询将会选取所有不同的薪水(使用 DISTINCT
),然后按降序排列。接着,使用 LIMIT 1 OFFSET 1
选择第二行,也就是第二高的薪水。
然而,如果表中只有一个薪水值时,这个查询将返回空结果集,而不是返回 null
。为了确保在这种情况下返回 null
,可以使用子查询,如我之前给出的示例:
SELECT
(SELECT DISTINCT salary
FROM Employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1) AS SecondHighestSalary;
使用子查询,当表中只有一个薪水值时,子查询将返回空结果集,而外部查询将返回 null
。
1.为什么使用子查询会返回null
使用子查询时,查询会尝试查找第二高的薪水。当子查询找不到符合条件的记录时,它会返回空结果集。然后,子查询的结果将被外部查询所引用。在这种情况下,如果子查询返回空结果集,外部查询将无法找到对应的值,因此将返回 null
。
这是因为在子查询的外部,我们使用 SELECT
语句查询一个可能不存在的值(即,第二高的薪水)。当子查询找不到第二高的薪水时,外部查询将返回 null
,表示没有找到这个值。
下面是一个查询示例,用于找到第二高的薪水,如果不存在第二高的薪水,则返回 null
:
SELECT
(SELECT DISTINCT
Salary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1) AS SecondHighestSalary;
在这个查询中,子查询首先尝试找到第二高的薪水。如果找到了,子查询会返回这个值,否则返回空结果集。外部查询会将子查询的结果作为 SecondHighestSalary
列的值。如果子查询返回空结果集,外部查询将返回 null
作为 SecondHighestSalary
的值。
26.object,JSONobject,string之间的关系
在编程和数据处理中,对象(object)、JSON 对象(JSON object)和字符串(string)之间有着密切的关系。我们可以这样理解它们之间的关系:
-
对象(Object):在许多编程语言(如 JavaScript、Python 等)中,对象是一种复合数据类型,通常用于存储和操作一组有序的键值对。对象的键通常是字符串,值可以是各种不同的数据类型,如字符串、数字、布尔值、数组、函数或其他对象。对象是编程中常用的一种数据结构。
-
JSON 对象(JSON object):JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它易于阅读和编写,同时也易于机器解析和生成。JSON 对象是一种特殊的对象,它的格式类似于 JavaScript 对象,但只能包含字符串、数字、布尔值、数组和其他 JSON 对象。JSON 对象通常用于在不同系统和应用程序之间传输和存储数据。
-
字符串(String):字符串是一种基本的数据类型,用于表示一系列字符。在许多编程语言中,字符串可以用单引号或双引号括起来。字符串可以包含文本、数字、标点符号等各种字符。
对象、JSON 对象和字符串之间的关系在于,它们在不同层面上处理和表示数据。对象通常用于编程中的数据结构和操作,JSON 对象用于数据交换和存储,而字符串是一种基本的数据类型,可以用于表示对象的键或值。
在实际应用中,你可能会遇到需要将对象转换为 JSON 对象或字符串的情况,以便于数据传输或存储。例如,在 JavaScript 中,你可以使用 JSON.stringify()
方法将一个对象转换为 JSON 字符串,或使用 JSON.parse()
方法将一个 JSON 字符串转换为一个 JavaScript 对象。类似地,在 Python 中,你可以使用 json.dumps()
和 json.loads()
函数实现这些转换。
27…map(taskIdStr -> taskIdStr.replaceAll(“[\[\]]”, “”).split(“,”))
当然,我很高兴为您解释这段代码。这行代码的主要目的是将 taskId
字符串(如 "[28,29]"
)转换为一个不包含方括号的字符串数组(如 {"28", "29"}
)。
让我们逐步分析这段代码:
-
.map(taskIdStr -> ...)
: 这是一个 map
操作,它对流中的每个元素应用一个函数。在这个例子中,每个元素都是一个 taskId
字符串(如 "[28,29]"
),我们将对其进行处理。
-
taskIdStr.replaceAll("[\\[\\]]", "")
: 这个方法调用将删除 taskId
字符串中的方括号。"[\\[\\]]"
是一个正则表达式,它匹配左方括号 [
和右方括号 ]
。我们使用两个反斜杠 \\
对方括号进行转义,因为它们在正则表达式中有特殊含义。replaceAll
方法将匹配到的所有字符替换为空字符串 ""
,因此方括号会被删除。例如,对于输入字符串 "[28,29]"
,该方法调用将返回 "28,29"
。
-
.split(",")
: 这个方法调用会根据逗号对字符串进行分割。在我们的示例中,"28,29"
将被分割为一个字符串数组,包含两个元素:"28"
和 "29"
。
所以,.map(taskIdStr -> taskIdStr.replaceAll("[\\[\\]]", "").split(","))
这段代码的作用是将一个类似于 "[28,29]"
的 taskId
字符串转换为一个不包含方括号的字符串数组,如 {"28", "29"}
。后续的代码将处理这个字符串数组,将其转换为整数并收集到一个列表中。
28.cron表达式
1.写一个cron,凌晨十二点运行
要创建一个 cron 表达式,在凌晨 12 点(午夜)运行,您可以使用以下表达式:
0 0 0 * * ?
这个 cron 表达式的含义如下:
- 第一个字段(秒):0 - 在第 0 秒触发任务。
- 第二个字段(分):0 - 在第 0 分钟触发任务。
- 第三个字段(时):0 - 在第 0 小时(即午夜)触发任务。
- 第四个字段(日):* - 每天都触发任务。
- 第五个字段(月):* - 每个月都触发任务。
- 第六个字段(星期):? - 不关心星期几,因为已经指定了每天都触发任务。
这个 cron 表达式表示每天凌晨 12 点(午夜)触发任务。
lable/my-project.conf /etc/nginx/sites-enabled/
8. 重启 Nginx 服务:
```bash
sudo service nginx restart
22.Collections.singletonList作用
Collections.singletonList() 方法用于创建一个只包含一个元素的不可变列表。该方法接受一个参数,即要包含在列表中的元素,然后返回一个不可变列表。
由于返回的列表是不可变的,因此不能添加、删除或修改它的元素。这使得它非常适合用作只有一个元素的参数传递,尤其是在需要传递一个集合时。
23.Comparator.comparingInt方法有什么用
Comparator.comparingInt
方法是Java中的一个工具方法,用于根据提供的函数生成一个整数类型的比较器。它是java.util.Comparator
接口的一个静态方法。
这个方法的主要作用是根据提供的函数生成一个新的比较器,用于比较两个对象的整数值。这个函数通常是一个lambda表达式或方法引用,它接受一个对象作为输入参数,返回一个整数值。生成的比较器可以用于对整数值进行升序排序。
在我们的示例中,我们使用Comparator.comparingInt()
方法根据AmountAndServiceADto
对象的periodicTime
属性(即月份)生成一个比较器:
yearCollect.sort(Comparator.comparingInt(e -> Integer.parseInt(e.getPeriodicTime())));
这里,我们传入一个lambda表达式e -> Integer.parseInt(e.getPeriodicTime())
作为提供的函数。这个函数将AmountAndServiceADto
对象作为输入,使用Integer.parseInt()
方法将periodicTime
属性(月份的字符串表示)转换为整数,用于排序。
最后,将生成的比较器传递给sort()
方法,对yearCollect
列表进行升序排序。这样我们就可以确保返回的月份顺序从1到12。
24.空结果集和 null
空结果集和 null
是不同的概念:
- 空结果集(Empty result set):当你执行一个 SQL 查询,如果没有符合条件的记录,查询结果将返回一个空结果集。空结果集表示查询没有找到任何匹配的行,但查询本身是有效的。换句话说,空结果集是一个没有数据的表,但它仍然具有列名和列结构。
null
:在数据库中,null
是一个特殊的值,表示缺失或未知的数据。当某个字段没有值时,它的内容将被设置为 null
。null
不等于空字符串或零,它表示数据根本不存在。在查询结果中,null
会以特定的方式对待,例如在聚合函数和连接操作中。
当你执行一个查询并得到一个空结果集时,它表示查询已经执行,但没有找到符合条件的数据。而当查询结果中的某个字段值为 null
时,表示该字段的数据不存在或未知。
回到你的问题,当使用 LIMIT 1 OFFSET 1
查询第二高的薪水时,如果只有一个薪水值,查询会返回空结果集,而不是 null
。这是因为查询确实执行了,但没有找到符合条件的第二高的薪水。为了在这种情况下返回 null
,你需要使用子查询的方法,如我之前所示。
25.sql查询
你提供的查询语句本身是正确的,可以用来获得第二高的薪水。以下是你的查询:
SELECT DISTINCT
Salary AS SecondHighestSalary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1;
这个查询将会选取所有不同的薪水(使用 DISTINCT
),然后按降序排列。接着,使用 LIMIT 1 OFFSET 1
选择第二行,也就是第二高的薪水。
然而,如果表中只有一个薪水值时,这个查询将返回空结果集,而不是返回 null
。为了确保在这种情况下返回 null
,可以使用子查询,如我之前给出的示例:
SELECT
(SELECT DISTINCT salary
FROM Employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1) AS SecondHighestSalary;
使用子查询,当表中只有一个薪水值时,子查询将返回空结果集,而外部查询将返回 null
。
1.为什么使用子查询会返回null
使用子查询时,查询会尝试查找第二高的薪水。当子查询找不到符合条件的记录时,它会返回空结果集。然后,子查询的结果将被外部查询所引用。在这种情况下,如果子查询返回空结果集,外部查询将无法找到对应的值,因此将返回 null
。
这是因为在子查询的外部,我们使用 SELECT
语句查询一个可能不存在的值(即,第二高的薪水)。当子查询找不到第二高的薪水时,外部查询将返回 null
,表示没有找到这个值。
下面是一个查询示例,用于找到第二高的薪水,如果不存在第二高的薪水,则返回 null
:
SELECT
(SELECT DISTINCT
Salary
FROM
Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1) AS SecondHighestSalary;
在这个查询中,子查询首先尝试找到第二高的薪水。如果找到了,子查询会返回这个值,否则返回空结果集。外部查询会将子查询的结果作为 SecondHighestSalary
列的值。如果子查询返回空结果集,外部查询将返回 null
作为 SecondHighestSalary
的值。
26.object,JSONobject,string之间的关系
在编程和数据处理中,对象(object)、JSON 对象(JSON object)和字符串(string)之间有着密切的关系。我们可以这样理解它们之间的关系:
-
对象(Object):在许多编程语言(如 JavaScript、Python 等)中,对象是一种复合数据类型,通常用于存储和操作一组有序的键值对。对象的键通常是字符串,值可以是各种不同的数据类型,如字符串、数字、布尔值、数组、函数或其他对象。对象是编程中常用的一种数据结构。
-
JSON 对象(JSON object):JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它易于阅读和编写,同时也易于机器解析和生成。JSON 对象是一种特殊的对象,它的格式类似于 JavaScript 对象,但只能包含字符串、数字、布尔值、数组和其他 JSON 对象。JSON 对象通常用于在不同系统和应用程序之间传输和存储数据。
-
字符串(String):字符串是一种基本的数据类型,用于表示一系列字符。在许多编程语言中,字符串可以用单引号或双引号括起来。字符串可以包含文本、数字、标点符号等各种字符。
对象、JSON 对象和字符串之间的关系在于,它们在不同层面上处理和表示数据。对象通常用于编程中的数据结构和操作,JSON 对象用于数据交换和存储,而字符串是一种基本的数据类型,可以用于表示对象的键或值。
在实际应用中,你可能会遇到需要将对象转换为 JSON 对象或字符串的情况,以便于数据传输或存储。例如,在 JavaScript 中,你可以使用 JSON.stringify()
方法将一个对象转换为 JSON 字符串,或使用 JSON.parse()
方法将一个 JSON 字符串转换为一个 JavaScript 对象。类似地,在 Python 中,你可以使用 json.dumps()
和 json.loads()
函数实现这些转换。
27…map(taskIdStr -> taskIdStr.replaceAll(“[\[\]]”, “”).split(“,”))
当然,我很高兴为您解释这段代码。这行代码的主要目的是将 taskId
字符串(如 "[28,29]"
)转换为一个不包含方括号的字符串数组(如 {"28", "29"}
)。
让我们逐步分析这段代码:
-
.map(taskIdStr -> ...)
: 这是一个 map
操作,它对流中的每个元素应用一个函数。在这个例子中,每个元素都是一个 taskId
字符串(如 "[28,29]"
),我们将对其进行处理。
-
taskIdStr.replaceAll("[\\[\\]]", "")
: 这个方法调用将删除 taskId
字符串中的方括号。"[\\[\\]]"
是一个正则表达式,它匹配左方括号 [
和右方括号 ]
。我们使用两个反斜杠 \\
对方括号进行转义,因为它们在正则表达式中有特殊含义。replaceAll
方法将匹配到的所有字符替换为空字符串 ""
,因此方括号会被删除。例如,对于输入字符串 "[28,29]"
,该方法调用将返回 "28,29"
。
-
.split(",")
: 这个方法调用会根据逗号对字符串进行分割。在我们的示例中,"28,29"
将被分割为一个字符串数组,包含两个元素:"28"
和 "29"
。
所以,.map(taskIdStr -> taskIdStr.replaceAll("[\\[\\]]", "").split(","))
这段代码的作用是将一个类似于 "[28,29]"
的 taskId
字符串转换为一个不包含方括号的字符串数组,如 {"28", "29"}
。后续的代码将处理这个字符串数组,将其转换为整数并收集到一个列表中。
28.cron表达式
1.写一个cron,凌晨十二点运行
要创建一个 cron 表达式,在凌晨 12 点(午夜)运行,您可以使用以下表达式:
0 0 0 * * ?
这个 cron 表达式的含义如下:
- 第一个字段(秒):0 - 在第 0 秒触发任务。
- 第二个字段(分):0 - 在第 0 分钟触发任务。
- 第三个字段(时):0 - 在第 0 小时(即午夜)触发任务。
- 第四个字段(日):* - 每天都触发任务。
- 第五个字段(月):* - 每个月都触发任务。
- 第六个字段(星期):? - 不关心星期几,因为已经指定了每天都触发任务。
这个 cron 表达式表示每天凌晨 12 点(午夜)触发任务。