环境工具

系统archlinux

手柄 北通360, usb

安卓手机 小米11

游戏 流行群侠传

步骤

手机要打开开发者调试, 并通过usb连接测试

不属于此次重点, 可自行搜索, 通过命令行adb
devices测试输出,说明连接成功

安装scrcpy

此工具用来在电脑端远程操作手机, 并且该软件开源, 而且使用还算流畅

1
sudo pacman -S scrcpy

在手机电脑正常连接的情况下, 直接启动改程序即可

测试键盘控制

通过键盘测试游戏中一些主要操作的对应按键, 后续跟手柄进行绑定

键盘

  1. 前后左右(wsad)

  2. 轻击(h)

  3. 重击(j)

  4. 技能1(u)

  5. 技能2/3(i)

    随从技能也会一起被放出去,真刺激

  6. 闪避(k)

  7. 怒气(p)

  8. 换武器(l)

编写手柄键盘按键映射程序

这里要注意, 按键使用的是keyboard组件, 测试使用pyautogui会出现连点的情况

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
"""
通过python + adb + scrcpy的方式,实现手柄玩安卓手机游戏

# Released by rdb under the Unlicense (unlicense.org)
# Based on information from:
# https://www.kernel.org/doc/Documentation/input/joystick-api.txt

# 参考
https://blog.csdn.net/Enderman_xiaohei/article/details/88050036?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-9-88050036-blog-109139735.pc_relevant_3mothn_strategy_recovery&spm=1001.2101.3001.4242.6&utm_relevant_index=12

https://blog.csdn.net/dhjabc_1/article/details/117444998

使用:
linux下需要使用管理员身份运行(触发按键需要), 否则运行报错

sudo python xx.py

"""
import pygame
# 键盘
import keyboard
import pyautogui
import time

# 鼠标, 没啥用了
# from pymouse import PyMouseMeta
# m = PyMouseMeta()


class JoyToKey:
"""
将手柄映射到键盘输入上
"""

def exec(self, joystick, event):
# 可能的joystick行为: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN JOYBUTTONUP JOYHATMOTION
# if event.type == pygame.JOYBUTTONUP:
# print("Joystick button released.")
# ********************键盘按键********************
if event.type == pygame.KEYDOWN:
# print(pygame.key.get_pressed())
pass
# event.key 表示键盘按键的值,比如k 的值是107, 回车键的值是13,等
# self.toggle_show_fps(event.key)
elif event.type == pygame.KEYUP:
# print(pygame.key.get_pressed())
pass

# ********************手柄操作********************
elif event.type == pygame.JOYBUTTONDOWN:
# 检测到手柄上的键按下
# print("Joystick button pressed.")
if joystick.get_button(7) == 1:
# 手柄start键 --> 键盘中的回车键。
keyboard.press('enter')
if joystick.get_button(0) == 1:
# 手柄A键 --> 键盘的k键,也就是对应跳(闪)的功能。
keyboard.press('k')
if joystick.get_button(2) == 1:
# 手柄X键 --> 键盘u
keyboard.press('u')
if joystick.get_button(1) == 1:
# 手柄B键 --> 键盘i
keyboard.press('i')
if joystick.get_button(3) == 1:
# 手柄Y键 --> 键盘p
keyboard.press('p')
if joystick.get_button(4) == 1:
keyboard.press('h')
if joystick.get_button(5) == 1:
# 手柄又键 --> 键盘l(换武器)
# keyboard.press('l')
keyboard.press('j')
elif event.type == pygame.JOYBUTTONUP:
# print("Joystick button release.")
if joystick.get_button(7) == 0:
# 手柄start键 --> 键盘中的回车键。
keyboard.release('enter')
if joystick.get_button(0) == 0:
# 手柄A键 --> 键盘的k键,也就是对应跳(闪)的功能。
keyboard.release('k')
if joystick.get_button(2) == 0:
# 手柄X键 --> 键盘u
keyboard.release('u')
if joystick.get_button(1) == 0:
# 手柄B键 --> 键盘i
keyboard.release('i')
if joystick.get_button(3) == 0:
# 手柄Y键 --> 键盘p
keyboard.release('p')
if joystick.get_button(4) == 0:
keyboard.release('h')
if joystick.get_button(5) == 0:
# 手柄又键 --> 键盘l(换武器)
# keyboard.release('l')
# 攻击键容易连点
keyboard.release('j')

# 攻击键
elif event.type == pygame.JOYAXISMOTION:
# print("Joystick axis pressed.")
if joystick.get_axis(2) > 0:
# 手柄左x --> 键盘h
# 注意: 这里需要让手柄, pygame来控制按压和释放, 如果用pyautogui, keydown每次会触发很多按压,(连点)
# 还是用这个保险
keyboard.press('h')
if joystick.get_axis(5) > 0:
# 手柄右x --> 键盘j
keyboard.press('j')
if round(joystick.get_axis(1)) < 0:
# 前
keyboard.press('w')
if round(joystick.get_axis(1)) > 0:
# 后
keyboard.press('s')
if round(joystick.get_axis(0)) < 0:
# 左
keyboard.press('a')
if round(joystick.get_axis(0)) > 0:
# 右
keyboard.press('d')

# 释放
# print("Joystick axis released.")
# print("s", joystick.get_axis(1))
if joystick.get_axis(2) < 0:
# 手柄左x --> 键盘h
keyboard.release('h')
if joystick.get_axis(5) < 0:
# 手柄右x --> 键盘j
keyboard.release('j')
if round(joystick.get_axis(0)) == 0:
# 释放左右
keyboard.release('a')
keyboard.release('d')
if round(joystick.get_axis(1)) == 0:
# 释放前后
keyboard.release('w')
keyboard.release('s')

# 摇杆转向
# 当前鼠标光标位置, 固定位置, 不单独获取了
# x, y = pygame.mouse.get_pos()
x, y = 482, 305
pyautogui.moveTo(x, y)
# 1. 设置当前鼠标光标位置, 放中间或偏右
# 2. 根据摇杆变化, 利用pyautogui进行拖拽
# 3, 4为遥感的横/纵向变化
if joystick.get_axis(3) != 0 or joystick.get_axis(4) != 0:
# 将当前光标位置的东西向下移动100个像素点,在拖动的过程中按住鼠标左键。
# >> > pyautogui.drag(100, 0, button='left')
# 一样的问题, 连点
# pyautogui.drag(int(round(joystick.get_axis(4))), int(round(joystick.get_axis(3))), button='left')
print(y+int(round(joystick.get_axis(4))), x+int(round(joystick.get_axis(3))))

# 使用pymouse实现上述payautogui的拖拽
# x, y = pygame.mouse.get_pos()
# m.press(x, y)
# x1 = x + joystick.get_axis(3)
# y1 = y + joystick.get_axis(4)
# m.move(x1, y1)
# m.release(x1, y1)


if __name__ == '__main__':
pygame.init()

# 初始化joystick
pygame.joystick.init()

# 得到joystick的数量
joystick_count = pygame.joystick.get_count()
print("Number of joysticks: {}".format(joystick_count))

# 之考虑一个手柄
joystick = pygame.joystick.Joystick(0)
# 按键映射对象
joyToKey = JoyToKey()

# -------- 程序主循环 -----------
# 保持循环直到用户点击关闭按钮
done = False
while not done:
# 事件处理的步骤
# 手柄事件触发
for event in pygame.event.get():
# 如果用户触发了关闭事件
if event.type == pygame.QUIT:
# 设置我们做了这件事的标志,所以我们就可以退出循环了
done = True
else:
joyToKey.exec(joystick, event)

# 关闭窗口并退出.
pygame.quit()

程序运行

程序使用的是keyboard组件, 在linux下运行时需要sudo

1
sudo python 北通360-流行群侠传手游.py

此时在scrcpy界面, 通过手柄即可控制手机进行游戏操作, 并且流畅性也还可以

说明

只是为了提高下游戏的趣味性, 其它操作可自行发掘

摇杆目前无法适应

摇杆转向跟鼠标适配未实现, 事件刷新太快, 暂时未想到好方法进行转换

需求规定

为了减少注释和swagger注解的重复定义, 通过规范注释,
让swagger可以通过javadoc来产生

替换@Api、@ApiOperation、@ApiModel、@ApiModelProperties等注解

只是对swagger的扩展,如果有swagger注解,以注解为准

运行环境

springboot2.1.7

jdk1.8

设计思想

系统构思

  1. 编译完成的class里没有注释的,所以注释信息只有在编译代码时存储起来
  2. swagger本身是通过注解实现接口定义描述等加载的,现将代码注释生成json格式,
    利用swagger扩展在启动项目时通过json进行加载到swagger中
  3. 需要配合自定义的javadoc-json-maven-plugin先将注释生成json文件

关键技术与算法

生成javadoc.json文件

com.example.CommentToJsonMain(已做成maven插件, 这里原始文件可做测试)

插件: https://github.com/zhaozhiwei1992/javadoc-json-maven-plugin

swagger扩展代码

com.example.SpringbootSwaggerJavadocApplication启动即可生效

类定义: com.example.plugin.CommentApiBuilder

方法定义: com.example.plugin.CommentOperationBuilder

类代码注释规范

1
2
3
4
5
6
7
8
9
/**
* @Title: PersonController
* @Package: com/example/springbootcache/controller/PersonController.java
* @Description: 用户信息接口
* @author: zhaozhiwei
* @date: 2022/10/25 下午8:23
* @version: V1.0
*/

方法代码注释规范

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @date: 2022/10/25-上午10:19
* @author: zhaozhiwei
* @method: findByID
* @param id : 唯一id
* @return: com.lx.demo.springbootcache.domain.Person
* @Description: 根据id获取用户信息
* 获取十次, 只有第一次是读库,后续都是取缓存
* 直接删掉redis缓存里的内容,仍然可以获取数据,并且走缓存,此时获取的是服务缓存ehcache中的信息
* seq 10 |xargs -i curl -XGET 'http://localhost:8080/persons/2'
*/

基本处理流程

系统流程图

file
1
2
3
4
5
6
7
8
9
start
:引入javadoc-json插件;
:使用插件:generate goals生成json版注释;
:正常启动web服务;
:访问swagger-ui.html;
note right
此时即可看到没有注解的方法也可以显示方法描述信息
end note
end

代码

https://github.com/zhaozhiwei1992/demo/tree/master/springboot/springboot-swagger-javadoc

参考

swagger扩展

https://github.com/hadix-lin/springfox-plus

https://blog.csdn.net/ydonghao2/article/details/109593416

https://blog.csdn.net/baiihcy/article/details/53861267

https://blog.csdn.net/qq_17623363/article/details/109259315

目的

将java的代码注释转换为json格式,并写入文件

本文介绍了完整的开发流程及如何使用

运行环境

jdk1.8

maven3.x

设计思想

系统构思

  1. 编译完成的class里没有注释的,所以注释信息只有在编译代码时存储起来
  2. 将能够生成javadoc.json的代码做成maven插件

关键技术与算法

需要实现Doclet

必须引入下述jar包, 来导入com.sun.javadoc.Doclet

1
2
3
4
5
6
7
8
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>/home/zhaozhiwei/applications/jdk1.8.0_181/lib/tools.jar</systemPath>
</dependency>

代码路径

https:github.com:zhaozhiwei1992/javadoc-json-maven-plugin

入口: com.example.Javadoc2JsonMojo#execute

生成javadoc.json文件

com.example.CommentToJson(做成maven插件使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"com.example.web.rest.PersonResource.findByID(java.lang.Long)#date": "2022/10/25-上午10:19",
"com.example.web.rest.PersonResource#Package": "com/example/springbootcache/controller/PersonController.java",
"com.example.web.rest.PersonResource.findByID(java.lang.Long)#Description": "根据id获取用户信息",
"com.example.web.rest.PersonResource.save(Person)#method": "save",
"com.example.web.rest.PersonResource#author": "zhaozhiwei",
"com.example.web.rest.PersonResource.deleteByID(java.lang.Long)#Description": "根据id删除person信息",
"com.example.web.rest.PersonResource#Title": "PersonController",
"com.example.web.rest.PersonResource.update(Person)#date": "2022/10/25-上午10:21",
"com.example.web.rest.PersonResource.save(Person)#Description": "保存方法",
"com.example.web.rest.PersonResource#Description": "用户信息接口",
"com.example.web.rest.PersonResource#date": "2022/10/25 下午8:23",
"com.example.web.rest.PersonResource.update(Person)#Description": "修改内容看是否会调整缓存",
... 省略了一大堆
}

类代码注释规范

1
2
3
4
5
6
7
8
9
/**
* @Title: PersonController
* @Package: com/example/springbootcache/controller/PersonController.java
* @Description: 用户信息接口
* @author: zhaozhiwei
* @date: 2022/10/25 下午8:23
* @version: V1.0
*/

方法代码注释规范

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @date: 2022/10/25-上午10:19
* @author: zhaozhiwei
* @method: findByID
* @param id : 唯一id
* @return: com.lx.demo.springbootcache.domain.Person
* @Description: 根据id获取用户信息
* 获取十次, 只有第一次是读库,后续都是取缓存
* 直接删掉redis缓存里的内容,仍然可以获取数据,并且走缓存,此时获取的是服务缓存ehcache中的信息
* seq 10 |xargs -i curl -XGET 'http://localhost:8080/persons/2'
*/

参数定义

1
2
3
4
5
6
7
@parameter额外属性:

@parameter alias="<aliasName>":为mojo参数使用别名

@parameter expression="${aSystemProperty}":使用系统属性表达式对mojo参数进行赋值

@parameter defaultValue="aValue/${anExpression}":提供一个默认值

开发流程

项目构建

1
mvn archetype:generate -DgroupId=com.example -DartifactId=javadoc-json-maven-plugin -DarchetypeArtifactId=maven-archetype-mojo -DinteractiveMode=false

使用插件

Install

在javadoc-json-maven-plugin项目中执行maven的install命令,
将插件安装到本地仓库

引入自定义插件

在其它项目引入插件测试

展开插件可以看到Mojo列表,由于项目里只有一个Mojo,且Mojo上用注释@goal指定了名称为generate,所以这里只能看到一个javadoc-json:generate

1
2
3
4
5
6
7
8
9
10
            <plugin>
<groupId>com.example</groupId>
<artifactId>javadoc-json-maven-plugin</artifactId>
<version>2.0-SNAPSHOT</version>
<configuration>
<!-- 指定java注释扫描目录-->
<basePackage>com/example/web/rest</basePackage>
</configuration>
</plugin>

执行插件

双击javadoc-json:generate就会执行这个插件。

执行完毕后,可以看到项目根目录的target文件下多了个javadoc.json文件。

命令行执行插件(可选)

命令格式为mvn groupId:artifactId:version:goal,执行如下命令:

mvn com.example:javadoc-json-maven-plugin:1.0.0-SNAPSHOT:generate

由于我们的插件命名符合规范,所以上面的命令可以简写为:

mvn javadoc-json:generate

尚未解决的问题

javadoc插件去掉异常提示

参考

maven插件开发

https://www.jianshu.com/p/9cfe599b3c5e

参数定义

https://blog.csdn.net/z69183787/article/details/52984622

目的

通过一台linux机器操作android手机做一些常用的操作

复杂的操作都是由简单操作开始的, 可以自行发掘

环境

1
2
3
4
5
6
7
笔记本: thinkpad t480

操作系统: archlinux

adb版本: 31.0.3p2-android-tools

手机: 小米手机 muui13.09

操作步骤

笔记本安装adb环境

1
sudo pacman -S android-tools

测试

1
2
3
4
5
adb --version

Android Debug Bridge version 1.0.41
Version 31.0.3p2-android-tools
Installed as /usr/bin/adb

手机打开usb调试开关

设置—-全部参数—–狂戳miui版本,这样就打开开发者模式了。

设置—–更多设置——开发者选项—开启开发者选项。

需要开启的按钮有:USB调试、USB安装、USB调试(安全设置)、关闭启动MIUI优化。

连接手机

通过usb连接手机后,
需要在小米手机上面的USB的用途上面选择传输文件或者传输照片(除了仅充电就行~)

测试连接: 查看连接设备 adb devices, 正常输出说明连接成功,
usb连通后也可以用wifi连接 :D

使用命令操作手机拍照

启动相机

1
adb shell am start -a android.media.action.STILL_IMAGE_CAMERA

camera键 拍照

1
adb shell input keyevent 27 

back键 暂退相机

1
adb shell input keyevent 4

这里只是个简单操作, 并且相机的启动入口基本是固定的,
如果是其它的app需要先获取到应用的入口, 可以自行搜索或联系我

对于按键操作, 最好是网上搜索一份按键编码对应表

概述

多数据源单服务写入, 分布式事务实现

使用随机数控制产生异常

注: 网上很多都是只有多数据源配置,实际不能控制事务统一回滚,
单服务场景下如果多个数据源只有一个写,剩下都是读, 则不需要分布式事务

为减少篇幅,详细代码在代码仓库,可自行参考

版本

springboot 2.1.7.RELEASE

配置引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

增加配置类

AtomikosJtaPlatform

JPAAtomikosTransactionConfig Atomikos事务配置类

PrimaryConfig 主数据源配置

SecondaryConfig 其它数据源配置

数据对象实现

User

增加仓储实现

需要分别实现primary和secondary, 从而支持多个数据源访问

PrimaryUserRepository

SecondaryUserRepository

实现服务类

PrimaryUserService

SecondaryUserService

ExampleService

负责统一调用 PrimaryUserService 和 SecondaryUserService,
模拟一个服务同时访问多个服务类(每个服务类引用了不同数据源)

如果其中某个服务抛出异常,则整个事务回滚, @Transactional(rollbackOn =
Exception.class) 是必须的

测试

com.example.springbootatomikos.services.UserServiceTest#testSave

代码

github地址

gitee地址

目标

通过指纹识别解锁slim-lock

环境

操作系统archlinux

桌面管理器slim

锁屏slimlock

硬件 Thinkpad T480

注: 指纹设备id, 06cb:009a

安装

测试设备 lsusb

1
2
3
4
5
6
7
8
9
10
11
12
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 0bda:0316 Realtek Semiconductor Corp. Card Reader
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub

# 这玩意儿应该就是指纹设备了
Bus 001 Device 005: ID 06cb:009a Synaptics, Inc. Metallica MIS Touch Fingerprint Reader

Bus 001 Device 004: ID 04f2:b604 Chicony Electronics Co., Ltd Integrated Camera (1280x720@30)
Bus 001 Device 003: ID 8087:0a2b Intel Corp. Bluetooth wireless interface
Bus 001 Device 002: ID 3044:50e0 MIIIW MW Keyboard Air Mini
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

安装libfprint-vfs009x-git (测试不通过)

yaourt -S libfprint-vfs009x-git

python-vlidity(推荐)

https://github.com/uunicorn/python-validity

安装: yaourt -S python-validity

录入指纹及解锁

fprintd-enroll

按照下述同时,录入指纹,直到completed

1
2
3
4
5
6
7
8
9
10
11
Using device /net/reactivated/Fprint/Device/1
Enrolling right-index-finger finger.
Enroll result: enroll-stage-passed
Enroll result: enroll-stage-passed
Enroll result: enroll-stage-passed
Enroll result: enroll-stage-passed
Enroll result: enroll-stage-passed
Enroll result: enroll-stage-passed
Enroll result: enroll-stage-passed
Enroll result: enroll-stage-passed
Enroll result: enroll-completed

校验指纹是否正常fprintd-verify

1
2
3
4
Using device /net/reactivated/Fprint/Device/1
Listing enrolled fingers:
- #0: WINBIO_ANSI_381_POS_RH_INDEX_FINGER
Verify result: verify-match (done)

查看当前用户指纹 fprintd-list 用户名

1
2
3
4
5
found 1 devices
Device at /net/reactivated/Fprint/Device/1
Using device /net/reactivated/Fprint/Device/1
Fingerprints for user zhaozhiwei on DBus driver (press):
- #0: WINBIO_ANSI_381_POS_RH_INDEX_FINGER

ThinkPadT480 slimlock指纹解锁

vim /etc/pam.d/system-local-login

将pam_fprintd.so添加到auth部分的顶部

auth sufficient pam_fprintd.so

重新启动计算机,会提示扫描手指以解锁登录(指纹灯亮)

slimlock认证后直接回车,不需要输入密码

问题列表

Impossible to enroll: GDBus.Error:net.reactivated.Fprint.Error.NoSuchDevice: No devices available

运行fprintd-enroll报错

https://wiki.archlinux.org/title/Lenovo_ThinkPad_T470#Fingerprint_reader

Install fprintd and libfprint-vfs009x-git AUR, then reboot. You can now
enroll your fingers with fprintd-enroll.

github下载很慢

将repository导入到gitee中,并且注意该项目有子模块,
所有需要手动增加.gitmodules

https://gitee.com/zhaozhiwei_1992/libfprint.git

https://gitee.com/zhaozhiwei_1992/libfprint-tod-vfs0090.git

libfprint/meson.build:1:0: ERROR: Unknown options: "x11-examples"

The x11-examples option seems to have been removed. It looks like it's
been replaced by "gtk-examples". I edited that in PKGBUILD and it
built successfully.

https://aur.archlinux.org/packages/libfprint-vfs009x-git

修改build文件, x11-example改成gtk-example

libfprint-vfs009x-git和fprintd >=1.92+版本冲突

安装低版本 sudo pacman -U
https://archive.archlinux.org/packages/f/fprintd/fprintd-1.90.1-1-x86_64.pkg.tar.zst

list_devices failed: No devices available

thinkpad t480 指纹id为 06cb:009a, 上述一些工具不支持,
直接使用python-validity即可

环境

archlinux

桌面管理器i3wm

登录管理器 slim

python 3.10.4

dlib

1
pip install --user -i https://pypi.tuna.tsinghua.edu.cn/simple dlib

face_recognition 依赖dlib

1
pip install --user -i https://pypi.tuna.tsinghua.edu.cn/simple face_recognition

opency 读取摄像头

1
pip install --user -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python

笔记本内置摄像头

流程图

代码

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""
人脸识别认证

使用摄像头检验当前用户是否匹配
"""
# -*- coding: utf-8 -*-
import cv2
import os
import face_recognition
import pyautogui
import time


class AuthByFace:

def __init__(self, clicked, clocked):
self.clicked = clicked
self.clocked = clocked
# 设置图像保存目录
self.SUCCESS_DIR = os.environ['HOME'] + "/Pictures/authFaces/success/"

# 认证通过返回true
def isAuthSuccess(self):

# 获取摄像头中人脸
cameraCapture = cv2.VideoCapture(0)
result, image = cameraCapture.read()
if result:
# cameraImageRgb = image[:, :, ::-1]
# 摄像头中的人脸位置, 可能出现多张脸
cameraImageFaceLocations = face_recognition.face_locations(image)
# 摄像头中人脸进行编码
cameraImageEncodings = face_recognition.face_encodings(image, cameraImageFaceLocations)[0]

# 使用仓库中文件 测试匹配
# unknownImage = face_recognition.load_image_file(self.SUCCESS_DIR + "image_1.jpg")
# cameraImageEncodings = face_recognition.face_encodings(unknownImage)[0]

personNames = []
knownImageEncodings = []
# 跟所有保存的人脸比对
files = os.listdir(self.SUCCESS_DIR)
for file in files:
if file.endswith("jpg") or file.endswith("png"):
name, _ = os.path.split(file)
personNames.append(name)
knowImagePath = self.SUCCESS_DIR + file
knownImage = face_recognition.load_image_file(knowImagePath)
# 将仓库中照片全部编码, 后续比对需要
knownImageEncodings.append(face_recognition.face_encodings(knownImage)[0])

# 遍历locations,face_encodings,识别图片中的人脸
matchs = face_recognition.compare_faces(knownImageEncodings, cameraImageEncodings)
print("hhaha", matchs)
for index, match in enumerate(matchs):
if match:
# 有一个匹配就返回
print("图像: ", personNames[index], "匹配成功")
return True

# 打开摄像头, 监听鼠标事件
def onMouse(self, event, x, y, flags, param):
# 点击左键, 重置click状态
if event == cv2.EVENT_LBUTTONUP:
self.clicked = True

def savePicture(self):
# 打开摄像头
# VideoCapture()中参数是0,表示打开笔记本的内置摄像头,
# 参数是视频文件路径则打开视频
cameraCapture = cv2.VideoCapture(0)
if not cameraCapture.isOpened():
print("摄像头未打开~~")
exit()
cameraCapture.set(3, 100)
# 帧宽度和帧高度都设置为100像素
cameraCapture.set(4, 100)
cv2.namedWindow('MyWindow')
cv2.setMouseCallback('MyWindow', self.onMouse)
# 点击左键保存
print('showing camera feed. Click window or press and key to stop.')
result, image = cameraCapture.read()
# True/False 是否读取到照片
print(result)

# 点击鼠标左键后, 保存图片, 关闭窗口
while result and cv2.waitKey(1) == -1 and not self.clicked:
cv2.imshow('MyWindow', cv2.flip(image, 0))
result, image = cameraCapture.read()
name = self.SUCCESS_DIR + 'image_0.jpg'
cv2.imwrite(name, image)
cv2.destroyWindow('MyWindow')
cameraCapture.release()


if __name__ == '__main__':
authByFace = AuthByFace(False, False)

# 仓库没有照片则先录入照片
files = os.listdir(authByFace.SUCCESS_DIR)
if len(files) < 1:
authByFace.savePicture()
else:
# 锁定状态才会走这个认证逻辑
# 每1分钟运行一次认证
while True:
time.sleep(10)
try:
isSuccess = authByFace.isAuthSuccess()
if authByFace.clocked and isSuccess:
# 认证通过则解开锁屏
print("认证通过")
# 输入锁屏密码
pyautogui.typewrite("1")
pyautogui.press("enter")
pyautogui.press("Esc")
authByFace.clocked = False
elif not isSuccess:
# 如果认证不通过, 调用系统快捷键, 锁定屏幕
pyautogui.hotkey('win', 'c')
pyautogui.press(['l'], interval=0.1)
# 对象设置为锁定状态
authByFace.clocked = True
except Exception as exc:
print(type(exc))
if not authByFace.clocked:
pyautogui.hotkey('win', 'c')
pyautogui.press(['l'], interval=0.1)
# 对象设置为锁定状态
authByFace.clocked = True

开启启动

方式1 systemd (未通过)

经过测试,
无法将该程序放入到systemd单元,跟cv2等包有关系,引入就启动失败

方式2

直接加入到i3wm的开机启动即可,加载该文件时图形界面已经加载,正合适

1
exec --no-startup-id /usr/bin/python ~/workspace/python/demo/AuthByFace.py > /tmp/authByFace.log 2>&1

参考

https://www.cnblogs.com/sclu/p/12626553.html

问题列表

解决python安装opencv速度慢

使用清华镜像(速度快)

pip install –user -i https://pypi.tuna.tsinghua.edu.cn/simple
opencv-python

cpu占用飙升

现象

系统访问很慢

排查

top 获取进程pid

top -Hp pid, 查看线程情况,获取线程id

print "%x" "线程id" #输出16进制线程id

jstack 进程pid > /tmp/jstack.txt

在jstack.txt中搜索上述16进制线程id, 紧跟着就是问题原因

jmap

查看最耗费cpu的线程堆栈信息

cat stack |grep -i 34670 -C10 –color

备注

并发场景下使用currenthashmap替代hashmap, 避免死循环

普通屏幕

连根线就行, 跳过

如何将手机/平板/其它电脑作为扩展屏幕

一些可行的方案

如果只是为了演示屏幕,方案很多, 使用各种远程工具啥的都可以如anydesk, 向日葵等.

如果是扩展, 那必须要是要支持部分内容到扩展屏幕

可以配置活动窗口如deskreen

采用vnc的工具如 VirtScreen

前提

基于Intel集显中的Virtualheads功能, 目前笔记本也是集显,没试其他方案

这里也是采用vnc技术来搞, 如果是用现成的推荐virtscreen

扩展屏在

环境

archlinux(主) + i3wm桌面管理器

android(扩展) 小米平板

xrandr设置屏幕扩展

将笔记本屏幕扩展到android平板上, 并且采用分屏方式

查看现有配置

~: xrandr

1
2
3
4
5
6
7
8
9
10
Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767
eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 310mm x 170mm
1920x1080 60.02*+ 59.93
1680x1050 59.88
1400x1050 59.98
1600x900 60.00 59.95 59.82
DP1 disconnected (normal left inverted right x axis y axis)
DP2 disconnected (normal left inverted right x axis y axis)
HDMI1 disconnected (normal left inverted right x axis y axis)
HDMI2 disconnected (normal left inverted right x axis y axis)

该命令显示了电脑当前的显示状态,Screen 0
是当前正在显示的屏幕的分辨率参数,eDP1是笔记本内置显示屏当前的分辨率参数,下面的一堆数字是该显示屏所支持的分辨率及刷新率,最底下的三行分别是
HDMI 接口输出及虚拟输出,如果没有连接则会显示disconnect

增加虚拟屏幕

  1. 使用cvt命令获取所需分辨率的相应配置信息,
    如我想让扩展屏幕分辨率为1920x1080

    ~: cvt 1920 1080

    1
    2
    # 1920x1080 59.96 Hz (CVT 2.07M9) hsync: 67.16 kHz; pclk: 173.00 MHz
    Modeline "1920x1080_60.00" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync

    其中第二行 Modeline 后面的内容 是接下来需要的

  2. 产生新的分辨率模式 xrandr –newmode

    ~: xrandr –newmode
    上述Modelline后边部分,或者直接使用下面命令一步到位

    ~: xrandr –newmode `cvt 1920 1080|tail -n1 |sed 's/Modeline
    //' |sed 's/"//g'`

    此时通过查看xrandr, 多出了Virtual1部分,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767
    eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 310mm x 170mm
    1920x1080 60.02*+ 59.93
    1680x1050 59.88
    1400x1050 59.98
    DP1 disconnected (normal left inverted right x axis y axis)
    DP2 disconnected (normal left inverted right x axis y axis)
    HDMI1 disconnected (normal left inverted right x axis y axis)
    HDMI2 disconnected (normal left inverted right x axis y axis)
    VIRTUAL1 disconnected (normal left inverted right x axis y axis)
    1920x1080_60.00 (0x20a) 173.000MHz -HSync +VSync
    h: width 1920 start 2048 end 2248 total 2576 skew 0 clock 67.16KHz
    v: height 1080 start 1083 end 1088 total 1120 clock 59.96Hz
  3. 启用新的显示器

    ~: xrandr –addmode VIRTUAL1 "1920x1080_60.00"
    注意VIRTUAL1变成了connected

    1
    2
    VIRTUAL1 connected (normal left inverted right x axis y axis)
    1920x1080_60.00 59.96

    xrandr –output VIRTUAL1 –right-of eDP1 –auto eDP1为主屏幕,
    扩展屏幕在右边,所以使用right-of,
    该命令最好执行两次,否则可能不生效

  4. 关闭扩展屏设置

    ~: xrandr –output VIRTUAL1 –off

X11vnc

启动x11vnc server服务并设置扩展屏

x11vnc -rfbport 5900 -clip 1920x1080+1920+0 -wait 1 –defer 1 -nowf -sb
0

x11vnc -rfbport 5900 -clip xinerama1 -wait 1 –defer 1 -nowf -sb 0

1
2
3
4
5
6
-rfbport:指定了连接所用的端口,默认为5900也可以自行设置。
-clip:该命令设置x11vnc映射屏幕的范围,可以使用-clip WxH+X+Y来手动设置映射范围,也可以使用-clip xinerama0来自动映射显示器,其中xinerama0为屏幕设置中从左往右数第一个显示器,以此类推xinerama1为第二个,如果你的主屏幕在左边虚拟屏幕在右边,那如果设置为xinerama0就会将主屏幕的显示内容复制到虚拟屏幕上,也就是两个屏幕的内容一样了,这时就应该设置为xinerama1。
-wait:是两次屏幕抓取的时间间隔,默认为 20 ms 效果比较卡顿,设置为 1 的话会尽可能降低抓取时间减少延迟。
-defer:向vnc客户端发送更新的间隔,默认也为 20 ms,设置为 1 可以尽可能提高发送频率(但对网速的要求就高了)。
-nowf:在移动窗口到虚拟屏幕时显示内容,否则会显示为一个框。
-sb:设置休眠时间,默认为 60s 也就是如果屏幕 60s 内没有活动的话就停止数据传送,这样会导致有时鼠标从主屏移动到虚拟屏幕时会卡顿,设置为 0 后就不会休眠了

客户端连接工具

安卓下可以使用bvnc

默认配置地址如192.168.1.xx:5900即可

adb工具(usb连接才需要)

打开手机的开发者模式, 并且选择usb调试

电脑端安装adb工具

使用adb reverse tcp:5900 tcp:5900 创建代理

扩展端配置host地址为127.0.0.1:5900即可

参考

https://blog.csdn.net/u010750137/article/details/104277527

测试

将活动程序移动到扩展屏

Win+Shift+Right

光标定位扩展屏

Win+Right 注意观察鼠标光标

问题处理

安装virtscreen以来python-quamash提示编译不通过(弃用)

python版本过高,考虑降低版本

找不到xrandr: cannot find mode "1920x1080_60.00"

执行该命令时提示上述错误xrandr –delmode VIRTUAL1 "1920x1080_60.00"

1
2
3
4
VIRTUAL1 disconnected (normal left inverted right x axis y axis)
1920x1080_60.00 (0x20a) 173.000MHz -HSync +VSync
h: width 1920 start 2048 end 2248 total 2576 skew 0 clock 67.16KHz
v: height 1080 start 1083 end 1088 total 1120 clock 59.96Hz

注意这里的mode不能有双引号, 必须是1920x1080_60.00
而不是"1920x1080_60.00"

bvnc客户端连接失败

确认服务端已经启动,客户端连接失败, 一般为防火墙导致

关闭防火墙systemctl stop iptables.service

增加防火墙规则(推荐)

扩展没效果?

该命令最好执行两次,否则可能不生效

xrandr –output VIRTUAL1 –right-of eDP1 –auto

环境

polardb版本

1
2
3
4
5
6
7
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>polardb-jdbc18</artifactId>
<version>1.0.0</version>
<scope>system</scope>
<systemPath>${lib.dir}/polardb-jdbc18.jar</systemPath>
</dependency>

mybatis版本

直接使用springboot引入

1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>

问题描述

1
2
3
4
5
6
7
javax.servlet.ServletException: org.springframework.dao.DataIntegrityViolationException: 
### The error may exist in com/xx/templatedef/dao/oracle/XXTemplateDef_SqlMap.xml
### The error may involve com.xx.templatedef.dao.XXTemplateDefDao.selectTemplateDefByCategoryAndState-Inline
### The error occurred while setting parameters
### SQL: select * from act_xx_tem_def where category=? and deploy_state=1
### Cause: com.aliyun.polardb.util.PSQLException: 不良的类型值 long :
com.aliyun.polardb.util.PSQLException: canot convert the column of type BYTEA to requested type long;

原因

mybatis查询act_tem_def表涉及到blob字段(content_bytes),
默认的BlobTypeHandler的getBlob方法不适用, 需要重写handler

解决方案

  1. 在业务下重写BlobTypeHandler, 获取getBinaryStream然后转换byte数组
  2. mapper.xml -> resultmap中, 加入下述转换
1
2
<result column="CONTENT_BYTES" property="contentBytes" jdbcType="BLOB"
typeHandler="com.example.springbootpolardb.handler.PolarDbBlobTypeHandler"/>

转换逻辑

1
2
3
4
5
6
7
8
9
public static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
}
return output.toByteArray();
}

代码

0%