0%

Linux 工作流总结

​ 由于已经工作一年了,由于进行Android系统的客制化任务,其编译环境需要再Ubuntu环境下进行,故期间不停的在使用指令操作,渐渐的对Linux工作流有了新的认识与看法.回想起第一次秋招面试时有位面试官问我linux指令相关问题,:如果我要看log最后面几行我该怎么看.当时由于实战经验不足,又没有对该方面的知识死记硬背过,最后拧拧巴巴的最后不知道说了一通什么狗屎.

标准输入流与标准输出流

​ 要是在大二期间我看到这几个字,我一定是一脸问号,回想以前学Java时也遇到过这几个关键字,为什么称之为关键字,因为当时并没有理解什么时输出流与输入流,更不明白何为标准,学完Java后有了新的认识,一种抽象概念,讲的是将数据或者其他东西输入到系统(内存)中,这一过程抽象为输入流,反之从内存输出到其他设备如磁盘\显示器等则为输出流.在这一过程中我以为这只是Java这一编程语言的抽象概念而已.直到我开始阅读Linux的书籍<,发现有> 2> 这样的标准输出流存在,我开始怀疑我以前的体系(何来体系?)于是我开始接触Linux系统编程,通过系统API去感受所谓的输入流与输出流(虽然感觉并没有什么卵用)

于是没有人教我便自己领悟了怎么去保存diff文件,看书很神奇的一点在于它并未教你如何处理该情况,它只是告诉了你所谓的原理,你就能在工程中实战出来.由于在工作中经常要处理代码冲突的情况,并且是在Shell框中,一切都不是那么的自动化,所有的流程需要手动操作,所以难免的觉得麻烦,最开始由于不熟悉git操作,我采取的操作是直接将修改回退,并拉取到最新.此时如果修改不保存的话需要重新修改,故我才去的方式是将diff保存起来,方式也特别简单,由于执行 git diff时,会将diff信息输出到屏幕,也就是默认输出到屏幕.而最简单的方式便是将diff信息输出到文件即可,故 git diff > patch.diff .这样信息就不会输出到屏幕,而是被重定向输出到了patch.diff文件中.当将代码拉新后 git apply patch.diff 即可将刚保存的修改应用至代码中,简单粗暴.由于标准输出被重定向到了文件中故屏幕不再显示,此时也无法观察到diff信息,甚至不知道是否diff为空,如果不检查一下diff文件的话,将来apply时可能是竹篮打水一场空,白忙活一场,故可以使用升级方案: git diff | tee patch.diff,该方法的好处在于,不仅可以将diff保存到文件还让它在显示器上输出信息. 利用管道符号| 将输出重定向到 tee工具 ,该工具的作用在于不仅可以将标准输出流显示在屏幕还可以将其保存为文件.

​ 过滤信息,当我想要在一堆文件中过滤到想要的文件名时, 可以使用 tree -fi| grep xxx, 当然还可以用find(不熟悉)去找.

环境变量

​ 以前在学校学编程语言的时候,经常要做的第一件事情就是下载安装配置好该语言的环境变量,以前只知道根据教程一步一步走,并不知道为什么要这么操作,或者并不知道这样做会有什么用.

以Java为例,当我们开始学习该语言时,最最基本的我们需要去编译该语言,需要运行程序,所以我们需要JDK支撑,也就是说jdk里面的程序可以帮我们完成编译与运行操作,具体来说就是javac与java程序,配置环境变量的目的就是为了让计算机能够找到这两个程序.让我们在任意路径下都能够直接使用该程序.就是这么简单,那为什么配置好环境变量后就可以随处运行了呢? 原因也很简单,因为配置环境变量的位置会在第一次进入会话时执行一遍,既然执行过配置的环境变量,那变量自然也被赋予上了初始值.当我们使用bash去执行程序时,如果当前目录不存在该程序bash会帮我们去环境变量里配置好的路径里去寻找是否有我们需要的程序.这样自然就可以找到并执行了,环境变量分为系统变量与用户变量,三言两语难以讲明白,在加载时优先系统变量后加载用户变量,故用户变量配置的信息可以覆盖系统变量,优先级更高,在bash寻找程序时时先寻找用户变量配置,找到了便不会再去系统变量中寻找,故又是用户变量优先级更高.

​ 环境变量配置在用户配置文件或者系统配置文件,在每一次会话开始时被执行,这样的操作在后续是无感的,用起来非常方便,于是通过这个方法还可以做一些其他事情比如:别名 Linux命令行操作,有时候需要执行步骤复杂,对于的指令也非常复杂,于是我们可以将一些复杂的指令起一个别名 例如 alias ll =’ls -laF’, 起完别名后我们即可使用 ll 带她后面的操作,可是,alias的生效时间仅仅只有在当前会话中,如果退出登录后再登陆就再也没有用了,再想使用又要重新设置,于是为了解决这一办法,可以将该别名的设置写入用户配置文件中,这样每次退出会话时该别名虽然失效了,但进来时又会被重新设定,这样的无感知设置对于用户来说不就是永久生效吗? 别名是个好工具,在做调试Android部分源码时候由于代码比较多,嵌套文件夹很深,用cd一个个去进入很麻烦感觉像是在windows去点击寻找文件夹,设置了别名后像是在桌面加了一个快捷方式,直接点击即可进入.

Linux与C

​ c语言编译与java编译不一样的点在于,c编译[包括编译链接等一系列操作]最终生成就是二进制可执行文件,可以直接在系统上运行的,不需要其他环境,不像Java依赖于Java虚拟机才可运行.另外,在linux上编写程序遇到不知道使用的方法,可以通过man直接查阅使用方法.可惜我英文水平优先,阅读起来有点吃力.

编写脚本

​ 在开发过程中会遇到大量的重复性工作,比如拉取代码时有时候由于代码钟存在未commit内容问题,有时由于网络波动导致代码拉取失败,前者可以通过进入到对应仓库进行checkout 再手动拉取,后者可以通过不断重试解决,而这些操作要么繁琐,要么需要不断监督,很浪费个人精力,于是可以通过编写脚本优化工作流程,shell的编写方式简单、直接调用c语言库就可以解决大部分问题。

构建你自己的 GPT4 聊天窗口

已经存在chatGPT,Gemini等等很好的AI聊天对话框、为啥还需要自己构建一个对话框 ?

  • 1、无法访问他们服务本身
  • 2、无法访问更高级的模型 : GPT4turbo

如果你刚好被以上问题困扰、那你很适合阅读该博文

准备条件

  • openAI 账号
  • 域名(可选: 反向代理方案)
  • github 账号
  • 信用卡(虚拟)

首先解释一下: 若需要使用 GPT4 服务 本身并不是免费的、所以需要花钱,需要用信用卡支付,如果你只是用GPT3.5的话、更没有必要构建自己的聊天窗口了,直接用官方的即可,花的钱也足够让你买机场好几个月

快速开始

1、进入openAI官网、 获取token(凭证) 保存下来,想要调用三方服务、少不了付费

2、购买域名、 利用域名做反向代理是能够在国内访问openai 服务的关键、如果不想每次访问前都要多一步操作、请不要省这点钱 便宜的域名 大概不到3$ (21RMB)/1 year,但不要买国内的,国内的使用起来会非常的麻烦(备案),我购买的是NameSilo上面的

3、将域名挂在到cloudflare上,操作比较简单、大致意思是将域名填到cloudflare上、然后将原本的购买服务商那里的删除 。。。

4、github fork 项目、 该项目源码已经公开了、咱们要做的就是按照要求、一键部署

  • 1、进入源码界面
  • 2、在README中直接点击Deploy
  • 3、利用Vercel一键部署

他会让你先登录、用github关联、会让你fork该项目、然后会让你写三个参数:

OPENAI_API_KEY : 填入你openAI 的token即可

CODE : 设置访问你自己网站的访问码/密码

GOOLE_API_KEY : Google API token (可选)

接下来等待即可、傻瓜式部署就完成了、会生成一个随机命名的域名、就可以直接访问了

前面我们说到、自己购买的域名是可以自由访问的关键、因为给的域名随时有可能被墙的风险、所以自己设置一个域名是很有必要的、其次自己的域名可读性也更强,以后只需要记住域名就好了。

5、配置域名、与反向代理page

在cloudflare中新建一个page

…此处省略(不想写了)

lambda表达式

对于面向对象对象语言中 lambda表达式其实是不重要的

如果你有了解过JavaScript、你一定对函数式编程比较熟悉(Js太恶心啦!!! 用的时候看不了规则根本不知道怎么写)

多说无益、先看效果:首先我们先假设有这么一个场景要实现一个数组排序 给定一个数组、将数组按指定要求(正序/倒序)排序

Java中我们可以直接使用Arrays.sort()调用JDK中 的方法进行排序(但该方法只提供了正序排序的实现)

1
2
3
4
5
6
7
8
9
public class Sort {

public static void main(String[] args) {
int[] nums = new int[]{1,3,5,25,7,23,7,34,8,12,5,3,71,236,12};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums));
// outputL: [1, 3, 3, 5, 5, 7, 7, 8, 12, 12, 23, 25, 34, 71, 236]
}
}

当然sort()方法也可以实现倒序排序、只不过有点不一样

1
2
3
4
5
6
7
8
9
10
public class Sort {


public static void main(String[] args) {
Integer[] nums = new Integer[]{1,3,5,25,7,23,7,34,8,12,5,3,71,236,12}; // 是包装类 Integer 不是int
Arrays.sort(nums, Collections.reverseOrder());
System.out.println(Arrays.toString(nums));
// output: [236, 71, 34, 25, 23, 12, 12, 8, 7, 7, 5, 5, 3, 3, 1]
}
}

ok , 通过这两个简单方法就可以实现我们的功能、但这并不是本问介绍的重点、重点是我们要通过这个案例来学习了解到lambda表达式、并且间接的感受Java函数式编程思想

接下来我们就来实现一个简易版本的Arrays.sort(T[]nums,Comparable)
首先确定想要的效果、T[]nums 确定一个任何对象类型的数组、传入一个比较器接口类、该接口仅仅做一件事情就是帮我比较两个数大小,叫做比较器,当我们的排序数据换成其他对象时候、原始的+-已经不能满足我们的需求了、比如比较String 大小 在String中存在CompareTo方法比较两个字符串大小,因此为满足其他复杂数据类型也能够排序、引入比较器来自定义比较两个数据大小尤为重要
先定义比较器、为了不予Jdk本身接口(Comparator)冲突我取名叫ShenComparator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FunctionalInterface // 做个约束 改接口只能定义一个属于自己的抽象方法
public interface ShenComparator<T> {

/**
* 比较两个数的大小
* @param o1 第一个数
* @param o2 第二个数
* @return > 0 ? 前面的数字大 : 后面的数字大
* NOTICE:: 这里不仅仅表示值 比如integer double 等数值 、
* 可以表示一切对象按照指定规则比较大小
*
*/

int compare(T o1,T o2);

// 该方法属于Object(出现在这里属于多余)
boolean equals(Object obj);

}

这里的比较器唯一的规则就是 比较大小结果是大于0还是小于0,举个例子 假设我们需要比较两个Integer类型数字、可以直接将前面数字减去后面的数字相减

1
2
3
4
5
6
// 实现类
// 如果 o1 > o2 结果自然大于零, o1 < o2 结果自然小于零, 如果将结果反序则得到的排序结果也是相反逻辑
int compare(Integer o1,Integer o2) {
return o1 - o2;
// return o2 - o1;
}

ok、有了比较器也还先别着急、 先用最简易版本的数字排序来熟悉一下排序的过程、这里为了简单我直接用快速排序算法来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

public class Utils {

/**
* 排序
* @param nums 待排序的数组
* 而是一个具体的排序规则 ::
* 字符串有字符串的规则
* 数字类型有数字类型比较规则
*/
public static void sort(int[] nums) {
quickSort(nums,0,nums.length - 1);
}


private static void quickSort(int[] nums,int left,int right) {
if (left >= right)
return;
int pivot = partition(nums, left, right);
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}

private static void swap(int[] nums,int x, int y) {
int mid = nums[x];
nums[x] = nums[y];
nums[y] = mid;
}

/* 哨兵划分 */
private static int partition(int[] nums, int left, int right) {
// 以 nums[left] 作为基准数
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--;
while (i < j && nums[i] <= nums[left])
i++;
swap(nums, i, j);
}
swap(nums, i, left);
return i;
}
}

现在已经实现了基本的排序功能、但是只针对于int[]数组、而且不支持随意切换正序还是倒序,现在测试一下

1
2
3
4
5
6
7
8
9
public class Main {

private static final int[] nums = {43,1,56,7,23,12,46,23,8,9,4,23,56,30,23,5};
public static void main(String[] args) {
Utils.sort(nums);
System.out.println(Arrays.toString(nums));
// output: [1, 4, 5, 7, 8, 9, 12, 23, 23, 23, 23, 30, 43, 46, 56, 56]
}
}

可见基本上排序功能是已经实现了、但是还缺少本文想要描述的重点、假设现在不是一堆数字要排序、而是一堆’学生’对象想通过成绩、年龄或通过名称排序,那该怎么办,接下来就需要引入我们自定义的比较器接口 以及给我们的int[] 都替换成 T[] 来实现, 另外我们算法中对排序数比较大小的-+号都可以用comparator.compare()方法来代替

加上比较器和泛型后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Utils {

/**
* 排序
* @param nums 待排序的数组
* @param comparator 排序规则比较器 传入不是具体的某一个数据
* 而是一个具体的排序规则 ::
* 字符串有字符串的规则
* 数字类型有数字类型比较规则
*/
public static <T> void sort(T[] nums, ShenComparator<T> comparator) {
quickSort(nums,0,nums.length - 1,comparator);
}


private static <T> void quickSort(T[] nums,int left,int right,ShenComparator<T> comparator) {
if (left >= right)
return;
int pivot = partition(nums, left, right,comparator);
quickSort(nums, left, pivot - 1,comparator);
quickSort(nums, pivot + 1, right,comparator);
}

private static <T> void swap(T[] nums,int x, int y) {
T mid = nums[x];
nums[x] = nums[y];
nums[y] = mid;
}

/* 哨兵划分 */
private static <T> int partition(T[] nums, int left, int right,ShenComparator<T> comparator) {
// 以 nums[left] 作为基准数
int i = left, j = right;
while (i < j) {
// 替换 nums[j] >= nums[left]
while (i < j && comparator.compare(nums[j],nums[left]) >= 0)
j--;
// 替换nums[i] <= nums[left]
while (i < j && comparator.compare(nums[i],nums[left]) <= 0)
i++;
swap(nums, i, j);
}
swap(nums, i, left);
return i;
}
}

直接测试!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {


public static void main(String[] args) {

Student[] students = new Student[]{
new Student(18,"lip",99F),
new Student(19,"dabbe",45.5F),
new Student(20,"miky",50.5F)};

Utils.sort(students, new ShenComparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按照成绩从大到小排序
return (int)(o2.getScore() - o1.getScore());
}
});
System.out.println(Arrays.toString(students));
//output:[Student(age=18, name=lip, score=99.0), Student(age=20, name=miky, score=50.5), Student(age=19, name=dabbe, score=45.5)]
}
}

ok 这里可以看到、我们调用Utils.sort方法时new 了一个匿名类部类 并重写里面的 compare方法来实现传递比较器,这就是自定义排序规则的根本原因。

但是到现在我们还根本没有跟lambda表达式沾边、是的、java8 以后匿名类部类可以改写简化成lambda表达式、但是有前提:

  • 该接口中(ShenComparator)只能有一个抽象方法
  • 也许还有。。。不记得了

用lambda表达式改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {


public static void main(String[] args) {

Student[] students = new Student[]{
new Student(18,"lip",99F),
new Student(19,"dabbe",45.5F),
new Student(20,"miky",50.5F)};

Utils.sort(students, (o1, o2) -> (int)(o2.getScore() - o1.getScore())); // 只需要一行代码及可完成
System.out.println(Arrays.toString(students));
}
}
1
Utils.sort(students,(a,b) -> b.getName().compareTo(a.getName())); // 根据名字倒序排序
1
Utils.sort(students,(a,b) -> a.getAge() - b.getAge()); // 根据年龄正序排序

备注

为了更容易理解、贴出类项目结构(额、好像没必要,算了贴都贴了反正就两个重要类ShenComparator 与 Utils)

建议查看java函数式编程的源码内容本文只是仿照他写的一个简易实现

我是怎么使用Lambda表达式的?

首先Lambda表达式主要用于集合的操作中

其中java Stream流配合Lambda表达式对集合操作就非常便捷

比如:假设有一个包含员工信息的列表,我们希望按照部门对员工进行分组,并计算每个部门的平均工资。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {


@Data // 为了节省空间就不写setter getter构造方法了 直接用lombok 自动生成了
@AllArgsConstructor
static class Employee {
private String name;
private String department;
private double salary;
}

public static void main(String[] args) {

List<Employee> list = Arrays.asList(new Employee("Alice", "HR", 50000),
new Employee("Bob", "Engineering", 60000),
new Employee("Charlie", "HR", 55000),
new Employee("David", "Engineering", 70000),
new Employee("Eva", "HR", 48000),
new Employee("Frank", "Sales", 75000));


Map<String, Double> averageSalaryByDepartment = list.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, // 按部门分组
Collectors.averagingDouble(Employee::getSalary))); // 求平均值

averageSalaryByDepartment.forEach((department, salaryAVG) -> System.out.println("Department: " + department + ", Average Salary: " + salaryAVG));
//output:Department: Engineering, Average Salary: 65000.0
//Department: Sales, Average Salary: 75000.0
//Department: HR, Average Salary: 51000.0
}

}

另外使用lambda表达式除了要会查文档外更建议用Idea查看具体需要类型,如 我这里使用filter方法实现过滤功能、但看不明白需要传递参数是什么、可以通过idea查看、需要一个Predicate (断言)接口 、里面一个test方法 返回值为boolean类型 需要一个参数、知道这些信息就可以很轻松写出lambda表达式,不需要new 个匿名类部类再改写。

代理模式

What is that

代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制原始对象的访问。代理对象充当一个中介,通过在客户端和目标对象之间建立通信桥梁,帮助管理对目标对象的访问。代理类在接收到客户端的请求时,可以在执行具体操作之前或之后,对原始对象进行额外的处理,和如果我需要执行某段程序前想要输出日志,我不需要每次都将日志输出代码都写入代码块里,造成代码冗余。

How to use it

1
2
3
4
5
6
7
8
9
10
11
package shen.dao;

import shen.entity.User;

public interface IUserDao {
/*
* 查找单个用户
* */

User findUserById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package shen.dao.impl;

import shen.dao.IUserDao;
import shen.entity.User;

public class UserDaoImpl implements IUserDao {

@Override
public User findUserById(Integer id) {

User user = new User(0,"you catch me","Hi");
return user;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package shen.entity;

public class User {
private Integer id;
private String username;
private String password;

public User() {}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package shen.handler;
import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.entity.User;

public class ProxyHandler implements IUserDao {


public void before() {
System.out.println("turn on transaction");
}
@Override
public User findUserById(Integer id) {
this.before();
UserDaoImpl userDao = new UserDaoImpl();
User user = userDao.findUserById(id);
System.out.println(user);
this.after();
return user;
}
public void after() {
System.out.println("turn off transaction");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package shen.test;

import shen.entity.User;
import shen.handler.ProxyHandler;

public class test {
public static void main(String[] args) {
IUserDao proxyHandler = new ProxyHandler();
proxyHandler.findUserById(0);
}
}
```output
turn on transaction
User{id=0, username='You catch me', password='Hi'}
turn off transaction

通过这样静态代理实现接口的方式用proxyHandler实现获取User的同时还完成了after(),与before()两个方法

Why

代理模式存在的原因是为了达到一些额外的目的,例如:提供一些额外的安全措施、控制对资源的访问、实现懒加载等。通过代理模式,我们可以灵活地管理和控制对目标对象的访问。

JDK动态代理

What is that

如果每个类都自己实现编写静态代理,不仅麻烦而且会导致程序非常复杂,这时可以用到JDK的动态代理API,JDK动态代理是一种特殊类型的代理模式,它允许在运行时动态生成代理类和代理对象。JDK动态代理需要使用Java的反射机制,利用代理类和InvocationHandler接口来实现代理功能。

How to use it

在以上代码基础上对handler 包和test包进行更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package shen.handler;

import shen.dao.IUserDao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DaoTransactionHandler implements InvocationHandler {
// if other class want use proxy you can choose Object class
private Object obj;

public DaoTransactionHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
// if you want to enhance just only one method
if ("findUserById".equals(method.getName())) {
this.before();
res = method.invoke(obj,args);
this.after();
}else {
res = method.invoke(args);
}
return res;
}

private void before() {
System.out.println("turn on enhance transaction");
}
private void after(){
System.out.println("turn off enhance transaction");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package shen.test;

import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.handler.DaoTransactionHandler;

import java.lang.reflect.Proxy;

public class test {
public static void main(String[] args) {
// target Class
UserDaoImpl userDao = new UserDaoImpl();
// enhance Class
// invocation class
DaoTransactionHandler daoTransactionHandler = new DaoTransactionHandler(userDao);
// act proxy object
IUserDao userDaoProxy =(IUserDao) Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(), daoTransactionHandler);
userDaoProxy.findUserById(0);

}
}
```output
turn on enhance transaction
User{id=0, username='you catch me', password='Hi'}
turn off enhance transaction

Why

JDK动态代理的好处在于,它不需要事先编写代理类的源代码,而是在运行时根据需要动态生成代理类和代理对象。这样可以大大简化开发过程,特别是当需要为多个类生成代理时,能够提高代码的灵活性和可维护性。

JDK动态代理的原理是利用了Java的反射机制,通过在运行时创建一个新的类,实现目标类所实现的接口,并在方法调用时使用InvocationHandler的实现类来处理额外逻辑。当代理对象的方法被调用时,JDK动态代理会通过反射将调用转发给InvocationHandler实例中的invoke()方法,从而实现代理功能。

可以集合JDK自动生成的$Proxy的源码分析:

修改test中代码可以生成$Proxy文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package shen.test;

import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.handler.DaoTransactionHandler;
import sun.misc.ProxyGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;

public class test {
public static void main(String[] args) {
// target Class
UserDaoImpl userDao = new UserDaoImpl();
// enhance Class
// invocation class
DaoTransactionHandler daoTransactionHandler = new DaoTransactionHandler(userDao);
// act proxy object
IUserDao userDaoProxy =(IUserDao) Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(), daoTransactionHandler);
userDaoProxy.findUserById(0);
// path such as "C:\\IdeaProject\\proxyDemo\\src"
saveProxyClass("__your project path __\\src");

}
//writing $proxy to contain in localhost
private static void saveProxyClass(String path) {
byte[] $Proxy1s = ProxyGenerator.generateProxyClass("$Proxy1",
UserDaoImpl.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(new File(path + "$Proxy1.class"));
out.write($Proxy1s);
}catch (Exception e) {
e.printStackTrace();
}finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}

}
}
}
}

分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public final class $Proxy1 extends Proxy implements IUserDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy1(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final User findUserById(Integer var1) throws {
try {
return (User)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("shen.dao.IUserDao").getMethod("findUserById", Class.forName("java.lang.Integer"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

JDK动态代理的原理是利用了Java的反射机制,通过在运行时创建一个新的类,实现目标类所实现的接口,并在方法调用时使用InvocationHandler的实现类来处理额外逻辑。当代理对象的方法被调用时,JDK动态代理会通过反射将调用转发给InvocationHandler实例中的invoke()方法,从而实现代理功能。

uml图

以上就是我的理解!

香橙派打造软路由

硬件介绍

Orange Pi R1 Plus LTS(软路由)此产品定位即为了打造路由器

官方参数如下:

官方自带有openwrt、ubuntu、debian固件等,but相信大家伙眼界没有受限于此

博主也建议选择社区版本openwrt固件这样优点很明显,能够拥有更多你想要的功能,废话不多说

固件引用

官方固件

推荐固件(如果你与博主用的硬件一样请选择最后一个镜像下载)

安装固件

需要条件:

1、microSD卡(读卡器)

2、烧录工具

3、固件镜像文件

操作步骤:

1、下载镜像文件、烧录工具

2、插入读卡器(microSD卡)

3、烧录系统(烧录好后千万不要格式化)

4、插上Pi 直接可以启动

启动路由器

操作步骤

1、连接电源,插上SD卡,连接lan网口至PC

2、PC检查网络连接找到IPv4 DNS服务器ip地址

3、浏览器访问该网址:默认为192.168.1.1

出现网站信息即成功!!!

4、登录openwrt、账号:root、原版没有密码、lean大佬版本密码:password

祝你好运!!!

附录

编译固件

想要自己diy插件功能等,可以自行编译固件,有详细教程,不需要编程知识,要求具备一定互联网搜索解决问题能力。

源码及编译教程地址

鸣谢

感谢固件提供地址:

1
https://openwrt.mpdn.fun:8443/?dir=lede/rockchip

感谢openwrt可编译源码

1
https://github.com/coolsnowwolf/lede

模拟电商日志mapRuce计算

准备环境

  • ubuntu22.04虚拟机/物理机
  • jdk安装以及环境配置
  • maven安装以及环境配置
  • hadoop安装以及环境配置

日志信息模拟生成

利用javaIO模拟生成100w条购买记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
&20320230401170400&Larry&虚拟产品&68.0&3.0&204.0&北京
&66420230401170400&吴九&电脑&56.0&13.0&728.0&北京
&90620230401170400&郑十&牙刷&32.0&9.0&288.0&北京
&60020230401170400&郑十&毛巾&78.0&1.0&78.0&长沙
&23020230401170400&吴九&虚拟产品&61.0&17.0&1037.0&长沙
&70520230401170400&张三&鞋子&70.0&15.0&1050.0&上海
&44620230401170400&王五&牙刷&11.0&7.0&77.0&上海
&18320230401170400&赵六&篮球&48.0&3.0&144.0&上海
&61620230401170400&周八&牙刷&30.0&19.0&570.0&上海
......
&80320230401170400&李四&水杯&19.0&9.0&171.0&北京
&48620230401170400&周八&牙刷&97.0&9.0&873.0&张家界
&43120230401170400&张三&篮球&36.0&6.0&216.0&佛山
&93720230401170400&李四&毛巾&56.0&19.0&1064.0&广州

构建maven项目

项目结构:将hadoop/etc/hadoop目录下的core-site.xml,hdfs-site.xml,log4g.properties文件分别复制到resources目录下

修改pom.xml 配置文件(博主用的Hadoop2.10.1,jdk1.8 maven3.9)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>mapReduceDemo</artifactId>
<version>1.0-SNAPSHOT</version>

<repositories>
<repository>
<id>apache</id>
<url>http://maven.apache.org</url>
</repository>
</repositories>


<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>

</project>

计算每个城市对应的购买总金额,accountByCity.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
//import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;
import java.util.StringTokenizer;

/**
* <城市,小计金额>
*
* <城市,{小计金额,小计金额...}>
*
* <城市,总计金额>
*/
public class accountByCity {
public static class myMapper extends Mapper<Object,Text, Text, LongWritable> {
private final static LongWritable account = new LongWritable();
private Text city = new Text();
private long startTime;
@Override
protected void setup(Context context)throws IOException,InterruptedException{
super.setup(context);
startTime = System.currentTimeMillis();

}
@Override
public void map(Object key,Text value, Context context)
throws IOException,InterruptedException{
// 此处的value是文档中的第一行文本数据
String msg = value.toString();
String[] list = msg.split(";&";);
city.set(list[7]);
double myDouble = Double.parseDouble(list[6]);
long myLong = (long)myDouble;
account.set(myLong);
context.write(city,account);
}

protected void cleanup(Context context) throws IOException,InterruptedException {
super.cleanup(context);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println(";MapperTime : ";+ duration+ ";ms";);//获取map计算总时间
}


}

public static class myReduce extends Reducer<Text,LongWritable,Text,LongWritable> {
private LongWritable result = new LongWritable();
private long startTime;
@Override
protected void setup(Context context) throws IOException,InterruptedException{
super.setup(context);
startTime = System.currentTimeMillis();
}
// 从这可以看出reduce处理的输入数据是<key,value-list>类型的键值对
public void reduce(Text key, Iterable<LongWritable> values, Context context)
throws IOException,InterruptedException{
int sum = 0;
// reduce 函数就是对列表value中的数值进行相加
for(LongWritable val : values) {
sum += val.get();
}
result.set(sum);
// 将结果写入context
context.write(key,result);
}
@Override
protected void cleanup(Context context) throws IOException,InterruptedException{
super.cleanup(context);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println(";ReducerTime :";+duration+"; ms";);//获取reduce计算总时间
}
}
/**
* 1、accountByCity的main函数
* 2、main函数主要创建一个job对象,然后对accountBycity任务所需要的map函数,reduce函数,输入文件路径,输出文件路径的信息
* 进行配置
*/
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
Job job = Job.getInstance(conf,";;accountByCity";);//获取一个任务实例
job.setJarByClass(accountByCity.class);//设置工作类
job.setMapperClass(myMapper.class);//设置Mapper类
job.setReducerClass(myReduce.class);//设置Reduce类
job.setOutputKeyClass(Text.class);//设置输出键值对中的key的类型
job.setOutputValueClass(LongWritable.class);//设置输出键值对中value的类型
FileInputFormat.addInputPath(job,new Path(args[0]));//设置输入文件的路径
FileOutputFormat.setOutputPath(job,new Path(args[1]));
FileSystem fs = FileSystem.get(conf);//获取HDFS文件系统
fs.delete(new Path(args[1]),true);//删除输出路径下可能已经存在的文件

运行项目

首先启动Hadoop服务

start-all.sh

上传日志文件到Hadoop的hdfs文件关系系统

关于Hadoop shell指令上传日志文件

假设这是我的日志文件路径/hadoop/test

hadoop fs -mkdir /data创建一个data目录

hadoop fs -mkdir /output创建一个output目录

hadoop fs -put /hadoop/test /data上传文件到data目录下

idea构建项目

执行项目

—success

map 100% reduce 100% 说明运行成功

1
2
3
4
5
6
7
8
......
23በ቞ 22:57:00 INFO mapred.LocalJobRunner: Finishing task: attempt_local524911839_0001_r_000000_0
23በ቞ 22:57:00 INFO mapred.LocalJobRunner: reduce task executor complete.
23በ቞ 22:57:01 INFO mapreduce.Job: map 100% reduce 100%
23በ቞ 22:57:01 INFO mapreduce.Job: Job job_local524911839_0001 completed successfully
23በ቞ 22:57:01 INFO mapreduce.Job: Counters: 35
......

查看生成数据

音频转文字

书接上回,AI图片生成,问答生成,接下来博主继续介绍 openAI的音频转换API,实现音频提取并翻译(->English)文字

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主依旧是润色官网代码实现功能:

    1、提取文字(不翻译)
1
2
3
4
5
6
7
8
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai

openai.api_key = "your API_Key"
audio_file= open("./demo2.mp3", "rb") #路径修改为你的音频文件路径
# 音频转化为文字
response = openai.Audio.transcribe("whisper-1", audio_file)
print(response['text'])
    2、提取文字并翻译为英文
1
2
3
4
5
6
7
8
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai

openai.api_key = "your API_Key"
audio_file= open("./demo2.mp3", "rb") #路径修改为你的音频文件路径
# 音频转化为文字并翻译为英文
response = openai.Audio.translate("whisper-1",audio_file)
print(response['text'])

3、运行截图

4、总结

相比于图形生成其效果还可以,也许适用于做短视频的字幕生成。

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,每分钟的音频计费$0.006,但每个账户都有$18的额度

调用openAI-Image models的API

调用openAI-Image models的API

博主这里承接上文调用chatGPT的API实现AI对话,接着继续用API实现AI画图

依旧用Python代码实现

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)、pip install requests
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主这里从官网直接复制代码,并添加了存储本地功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import openai
import os
import requests
def image(prompt):
openai.api_key = '你自己的API_Key'
response = openai.Image.create(
prompt=prompt,
n=1,
size="1024x1024"
)
image_url = response['data'][0]['url']
# 存储图片
fileDown(image_url)


def fileDown(url):
if not os.path.exists('./image'):
os.mkdir('./image')
headers={
"User-Agent":"Mozilla/5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 88.0.4324.150 Safari / 537.36"
}
res = requests.get(url=url,headers=headers).content
src_path = './image/' +url.split('/')[-1]+ '.jpg'
with open(src_path,'wb') as fp:
fp.write(res)

if __name__ == "__main__":
prompt = input('请输入描述信息:')
print('请稍后...')
image(prompt=prompt)
print('完成,请移步当前目录下image文件...')

3、运行结果截图

效果如大家所见,就是难以形容,大家可以自己尝试一下!!!

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,每张1024*1024的图片花费 $0.020,但每个账户都有$18的额度

调用chatGPT的API(GPT-3.5-turbo)

博主在这里用python编写代码的方式实现调用GPT-3.5-turbo的API实现终端访问chatGPT

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主这里从官网直接复制并添加润色了以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai
openai.api_key = '你的API'
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
# {"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Who are you?"},#这里content表示输入问题
# {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
# {"role": "user", "content": "Where was it played?"}
]
)
print(response['choices'][0]['message']['content'])

3、结果显示

打印类似如下内容说明成功!

1
As an AI language model developed by OpenAI, I am not a person or a human being. I am a computer program designed to understand and generate natural language responses to interact with humans.

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,但每个账户都有US$ 18的额度

参考视频—> TechDIYLife

服务器申请SSL证书

什么是SSL证书?

chatGPT给出这样的答案:

对于普通人理解SSL证书可以这样:很简单,如果你的服务器拥有SSL证书,这样你部署的网站可以通过https(更安全)访问,否则只能通过http访问

事实上我们选择申请SSL证书目的是需要搭建VPS,搭建自己的面板时候需要SSL证书,那这里呢 博主就介绍一种免费申请SSL证书的方式:acme.sh脚本搭建

什么是acme.sh?

chatGPT给出这样的答案:

实际操作

基本环境:

    1、Linux操作系统(ubuntu/debain,centOS)
    2、域名

实践:

    1、域名DNS记录/绑定指定vps(云服务器)ip

我这里使用cloudFlare绑定指定ip,(如果有需要的话)可以选择多绑定几个

值得注意的是:域名(domain)由两部分组成,顶级域名和二级域名

例如 www. example.com,这里example. com是顶级域名,也就是你自己所购买的域名,www是二级域名,再比如我的demo . example . com,我这里的名称即是demo即是二级域名,下面要填写域名操作要把一级域名和二级域名全部加上(demo . example . com)

    2、Linux环境更新
1
2
3
apt update -y          #Debian/Ubuntu 命令
apt install -y curl #Debian/Ubuntu 命令
apt install -y socat #Debian/Ubuntu 命令
1
2
3
yum update -y          #CentOS 命令
yum install -y curl #CentOS 命令
yum install -y socat #CentOS 命令
    2、安装 Acme 脚本
1
curl https://get.acme.sh | sh
    3、注册Acme
1
~/.acme.sh/acme.sh --register-account -m xxxx@xxxx.com #后面是你的个人邮箱
    4、更换服务器
1
~/.acme.sh/acme.sh --issue -d demo.example.com --dns dns_cf --server letsencrypt
    5、申请SSL
1
~/.acme.sh/acme.sh --issue -d demo.example.com --standalone -k ec-256

出现以下信息则说明已经申请成功!

前两行分别是证书公钥和密钥地址,要用到SSL证书直接复制其地址即可

最后祝你好运!!!

参考资料

波仔分享

一瓶奶油